mastracode 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +328 -0
- package/LICENSE.md +15 -0
- package/README.md +68 -29
- package/dist/agents/memory.d.ts.map +1 -1
- package/dist/agents/model.d.ts +17 -6
- package/dist/agents/model.d.ts.map +1 -1
- package/dist/agents/prompts/index.d.ts.map +1 -1
- package/dist/agents/prompts/tool-guidance.d.ts +2 -0
- package/dist/agents/prompts/tool-guidance.d.ts.map +1 -1
- package/dist/agents/subagents/audit-tests.d.ts +0 -7
- package/dist/agents/subagents/audit-tests.d.ts.map +1 -1
- package/dist/agents/subagents/execute.d.ts +0 -7
- package/dist/agents/subagents/execute.d.ts.map +1 -1
- package/dist/agents/subagents/explore.d.ts +0 -7
- package/dist/agents/subagents/explore.d.ts.map +1 -1
- package/dist/agents/subagents/index.d.ts.map +1 -1
- package/dist/agents/subagents/plan.d.ts +0 -7
- package/dist/agents/subagents/plan.d.ts.map +1 -1
- package/dist/agents/tools.d.ts +3 -1
- package/dist/agents/tools.d.ts.map +1 -1
- package/dist/agents/workspace.d.ts +4 -1
- package/dist/agents/workspace.d.ts.map +1 -1
- package/dist/{chunk-K4WJUBEC.cjs → chunk-AJEYT7X3.cjs} +763 -429
- package/dist/chunk-AJEYT7X3.cjs.map +1 -0
- package/dist/{chunk-U5A7TFNT.js → chunk-CC2724NI.js} +46 -10
- package/dist/chunk-CC2724NI.js.map +1 -0
- package/dist/{chunk-REVOTI2T.js → chunk-JI4M5525.js} +740 -412
- package/dist/chunk-JI4M5525.js.map +1 -0
- package/dist/{chunk-Z4QRXVST.cjs → chunk-MBPGUMYQ.cjs} +325 -251
- package/dist/chunk-MBPGUMYQ.cjs.map +1 -0
- package/dist/{chunk-MT3YCFCC.cjs → chunk-OEDRHUU5.cjs} +47 -9
- package/dist/chunk-OEDRHUU5.cjs.map +1 -0
- package/dist/{chunk-M5LKPQB4.js → chunk-WKPHD54B.js} +283 -209
- package/dist/chunk-WKPHD54B.js.map +1 -0
- package/dist/{chunk-C4X3C2DL.cjs → chunk-XVYUS2EA.cjs} +2213 -1035
- package/dist/chunk-XVYUS2EA.cjs.map +1 -0
- package/dist/{chunk-X3BGE7CL.js → chunk-YQNZ7DHQ.js} +1788 -613
- package/dist/chunk-YQNZ7DHQ.js.map +1 -0
- package/dist/cli.cjs +79 -31
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +71 -23
- package/dist/cli.js.map +1 -1
- package/dist/clipboard/index.d.ts +5 -0
- package/dist/clipboard/index.d.ts.map +1 -1
- package/dist/error-classification.d.ts +10 -0
- package/dist/error-classification.d.ts.map +1 -0
- package/dist/index.cjs +2 -2
- package/dist/index.d.ts +10 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/mcp/config.d.ts +8 -0
- package/dist/mcp/config.d.ts.map +1 -1
- package/dist/mcp/index.d.ts +1 -1
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/manager.d.ts +4 -2
- package/dist/mcp/manager.d.ts.map +1 -1
- package/dist/mcp/types.d.ts +30 -3
- package/dist/mcp/types.d.ts.map +1 -1
- package/dist/onboarding/onboarding-inline.d.ts +2 -0
- package/dist/onboarding/onboarding-inline.d.ts.map +1 -1
- package/dist/onboarding/packs.d.ts +1 -0
- package/dist/onboarding/packs.d.ts.map +1 -1
- package/dist/onboarding/settings.d.ts +37 -2
- package/dist/onboarding/settings.d.ts.map +1 -1
- package/dist/permissions-S3LGXIDB.js +3 -0
- package/dist/{permissions-CVXKYIWR.js.map → permissions-S3LGXIDB.js.map} +1 -1
- package/dist/permissions-VGABAVGD.cjs +40 -0
- package/dist/{permissions-2HIUSRQN.cjs.map → permissions-VGABAVGD.cjs.map} +1 -1
- package/dist/permissions.d.ts.map +1 -1
- package/dist/providers/claude-max.d.ts +13 -0
- package/dist/providers/claude-max.d.ts.map +1 -1
- package/dist/providers/openai-codex.d.ts +1 -0
- package/dist/providers/openai-codex.d.ts.map +1 -1
- package/dist/tool-names.d.ts +68 -0
- package/dist/tool-names.d.ts.map +1 -0
- package/dist/tools/ast-smart-edit.d.ts +77 -5
- package/dist/tools/ast-smart-edit.d.ts.map +1 -1
- package/dist/tools/index.d.ts +2 -2
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/string-replace-lsp.d.ts +15 -0
- package/dist/tools/string-replace-lsp.d.ts.map +1 -1
- package/dist/tools/subagent.d.ts.map +1 -1
- package/dist/tools/utils.d.ts +4 -2
- package/dist/tools/utils.d.ts.map +1 -1
- package/dist/tui/command-dispatch.d.ts.map +1 -1
- package/dist/tui/commands/clone.d.ts +29 -0
- package/dist/tui/commands/clone.d.ts.map +1 -0
- package/dist/tui/commands/custom-providers.d.ts +8 -0
- package/dist/tui/commands/custom-providers.d.ts.map +1 -0
- package/dist/tui/commands/index.d.ts +3 -1
- package/dist/tui/commands/index.d.ts.map +1 -1
- package/dist/tui/commands/mcp.d.ts.map +1 -1
- package/dist/tui/commands/models-pack.d.ts +4 -0
- package/dist/tui/commands/models-pack.d.ts.map +1 -1
- package/dist/tui/commands/om.d.ts.map +1 -1
- package/dist/tui/commands/report-issue.d.ts +3 -0
- package/dist/tui/commands/report-issue.d.ts.map +1 -0
- package/dist/tui/commands/resource.d.ts.map +1 -1
- package/dist/tui/commands/settings.d.ts.map +1 -1
- package/dist/tui/commands/threads.d.ts +1 -0
- package/dist/tui/commands/threads.d.ts.map +1 -1
- package/dist/tui/components/ask-question-inline.d.ts +3 -0
- package/dist/tui/components/ask-question-inline.d.ts.map +1 -1
- package/dist/tui/components/custom-editor.d.ts +1 -1
- package/dist/tui/components/custom-editor.d.ts.map +1 -1
- package/dist/tui/components/help-overlay.d.ts.map +1 -1
- package/dist/tui/components/plan-approval-inline.d.ts.map +1 -1
- package/dist/tui/components/settings.d.ts +2 -0
- package/dist/tui/components/settings.d.ts.map +1 -1
- package/dist/tui/components/subagent-execution.d.ts +6 -1
- package/dist/tui/components/subagent-execution.d.ts.map +1 -1
- package/dist/tui/components/thread-selector.d.ts +6 -0
- package/dist/tui/components/thread-selector.d.ts.map +1 -1
- package/dist/tui/components/tool-execution-enhanced.d.ts +1 -0
- package/dist/tui/components/tool-execution-enhanced.d.ts.map +1 -1
- package/dist/tui/components/tool-validation-error.d.ts.map +1 -1
- package/dist/tui/handlers/message.d.ts.map +1 -1
- package/dist/tui/handlers/prompts.d.ts +6 -0
- package/dist/tui/handlers/prompts.d.ts.map +1 -1
- package/dist/tui/handlers/subagent.d.ts.map +1 -1
- package/dist/tui/mastra-tui.d.ts +14 -5
- package/dist/tui/mastra-tui.d.ts.map +1 -1
- package/dist/tui/render-messages.d.ts.map +1 -1
- package/dist/tui/setup.d.ts.map +1 -1
- package/dist/tui/state.d.ts +4 -5
- package/dist/tui/state.d.ts.map +1 -1
- package/dist/tui.cjs +19 -19
- package/dist/tui.js +2 -2
- package/dist/utils/debug-log.d.ts +12 -0
- package/dist/utils/debug-log.d.ts.map +1 -0
- package/dist/utils/plans.d.ts +7 -0
- package/dist/utils/plans.d.ts.map +1 -0
- package/dist/utils/update-check.d.ts +40 -0
- package/dist/utils/update-check.d.ts.map +1 -0
- package/package.json +8 -8
- package/dist/chunk-C4X3C2DL.cjs.map +0 -1
- package/dist/chunk-K4WJUBEC.cjs.map +0 -1
- package/dist/chunk-M5LKPQB4.js.map +0 -1
- package/dist/chunk-MT3YCFCC.cjs.map +0 -1
- package/dist/chunk-REVOTI2T.js.map +0 -1
- package/dist/chunk-U5A7TFNT.js.map +0 -1
- package/dist/chunk-X3BGE7CL.js.map +0 -1
- package/dist/chunk-Z4QRXVST.cjs.map +0 -1
- package/dist/docs/SKILL.md +0 -30
- package/dist/docs/assets/SOURCE_MAP.json +0 -11
- package/dist/docs/references/docs-mastra-code-configuration.md +0 -299
- package/dist/docs/references/docs-mastra-code-customization.md +0 -228
- package/dist/docs/references/docs-mastra-code-modes.md +0 -104
- package/dist/docs/references/docs-mastra-code-overview.md +0 -135
- package/dist/docs/references/docs-mastra-code-tools.md +0 -229
- package/dist/docs/references/reference-mastra-code-createMastraCode.md +0 -108
- package/dist/permissions-2HIUSRQN.cjs +0 -40
- package/dist/permissions-CVXKYIWR.js +0 -3
- package/dist/tui/commands/models.d.ts +0 -3
- package/dist/tui/commands/models.d.ts.map +0 -1
|
@@ -1,22 +1,133 @@
|
|
|
1
|
-
import { theme, mastra, getMarkdownTheme, getEditorTheme, loadSettings,
|
|
2
|
-
import { getOAuthProviders, detectProject, getUserId, getCurrentGitBranch, PROVIDER_DEFAULT_MODELS } from './chunk-SM3QCOA7.js';
|
|
3
|
-
import { getToolCategory, TOOL_CATEGORIES } from './chunk-
|
|
1
|
+
import { theme, mastra, getMarkdownTheme, getEditorTheme, loadSettings, getAvailableModePacks, resolveThreadActiveModelPackId, saveSettings, getAvailableOmPacks, ONBOARDING_VERSION, THREAD_ACTIVE_MODEL_PACK_ID_KEY, ThreadLockError, parseSubagentMeta, tintHex, getSelectListTheme, getThemeMode, applyThemeMode, getCustomProviderId, getSettingsListTheme, toCustomProviderModelId } from './chunk-JI4M5525.js';
|
|
2
|
+
import { getOAuthProviders, detectProject, getUserId, getCurrentGitBranch, getAppDataDir, PROVIDER_DEFAULT_MODELS } from './chunk-SM3QCOA7.js';
|
|
3
|
+
import { MC_TOOLS, getToolCategory, TOOL_CATEGORIES } from './chunk-CC2724NI.js';
|
|
4
4
|
import { Box, Text, Spacer, Input, Container, fuzzyFilter, getEditorKeybindings, Markdown, ProcessTerminal, TUI, Editor, matchesKey, CombinedAutocompleteProvider, SelectList, visibleWidth, SettingsList, isKeyRelease } from '@mariozechner/pi-tui';
|
|
5
5
|
import chalk10 from 'chalk';
|
|
6
|
-
import { exec,
|
|
7
|
-
import fs2, { readFileSync, unlinkSync, promises } from 'fs';
|
|
8
|
-
import
|
|
9
|
-
import
|
|
6
|
+
import { exec, execFile, execSync, execFileSync } from 'child_process';
|
|
7
|
+
import fs2, { realpathSync, readFileSync, unlinkSync, promises } from 'fs';
|
|
8
|
+
import { createRequire } from 'module';
|
|
9
|
+
import * as path5 from 'path';
|
|
10
|
+
import path5__default, { join } from 'path';
|
|
10
11
|
import { defaultOMProgressState } from '@mastra/core/harness';
|
|
11
12
|
import * as os from 'os';
|
|
12
13
|
import { tmpdir } from 'os';
|
|
13
14
|
import { highlight } from 'cli-highlight';
|
|
15
|
+
import fs4 from 'fs/promises';
|
|
14
16
|
import { parse } from 'partial-json';
|
|
15
17
|
import * as yaml from 'js-yaml';
|
|
16
18
|
|
|
17
19
|
// src/auth/claude-max-warning.ts
|
|
18
20
|
var ANTHROPIC_OAUTH_PROVIDER_ID = "anthropic";
|
|
19
21
|
var CLAUDE_MAX_OAUTH_WARNING_MESSAGE = "OAuth with a Claude Max plan is a grey area. Anthropic has reportedly banned users for using Claude max credentials outside of Claude Code, and it may violate Anthropic Terms of Service. Proceed at your own risk.";
|
|
22
|
+
var AskQuestionInlineComponent = class extends Container {
|
|
23
|
+
contentBox;
|
|
24
|
+
selectList;
|
|
25
|
+
input;
|
|
26
|
+
onSubmit;
|
|
27
|
+
onCancel;
|
|
28
|
+
formatResult;
|
|
29
|
+
isNegativeAnswer;
|
|
30
|
+
allowEmptyInput = false;
|
|
31
|
+
answered = false;
|
|
32
|
+
questionText;
|
|
33
|
+
_focused = false;
|
|
34
|
+
get focused() {
|
|
35
|
+
return this._focused;
|
|
36
|
+
}
|
|
37
|
+
set focused(value) {
|
|
38
|
+
this._focused = value;
|
|
39
|
+
if (!this.answered && this.input) {
|
|
40
|
+
this.input.focused = value;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
constructor(options, _ui) {
|
|
44
|
+
super();
|
|
45
|
+
this.onSubmit = options.onSubmit;
|
|
46
|
+
this.onCancel = options.onCancel;
|
|
47
|
+
this.formatResult = options.formatResult;
|
|
48
|
+
this.isNegativeAnswer = options.isNegativeAnswer;
|
|
49
|
+
this.allowEmptyInput = Boolean(options.allowEmptyInput);
|
|
50
|
+
this.questionText = options.question;
|
|
51
|
+
this.addChild(new Spacer(1));
|
|
52
|
+
this.contentBox = new Box(1, 1, (text) => theme.bg("toolPendingBg", text));
|
|
53
|
+
this.addChild(this.contentBox);
|
|
54
|
+
this.contentBox.addChild(new Text(theme.bold(theme.fg("accent", "\u2753 Question")), 0, 0));
|
|
55
|
+
this.contentBox.addChild(new Spacer(1));
|
|
56
|
+
for (const line of options.question.split("\n")) {
|
|
57
|
+
this.contentBox.addChild(new Text(theme.fg("text", line), 0, 0));
|
|
58
|
+
}
|
|
59
|
+
this.contentBox.addChild(new Spacer(1));
|
|
60
|
+
if (options.options && options.options.length > 0) {
|
|
61
|
+
this.buildSelectMode(options.options);
|
|
62
|
+
} else {
|
|
63
|
+
this.buildInputMode();
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
buildSelectMode(opts) {
|
|
67
|
+
const items = opts.map((opt) => ({
|
|
68
|
+
value: opt.label,
|
|
69
|
+
label: opt.description ? ` ${opt.label} ${theme.fg("dim", opt.description)}` : ` ${opt.label}`
|
|
70
|
+
}));
|
|
71
|
+
this.selectList = new SelectList(items, Math.min(items.length, 8), getSelectListTheme());
|
|
72
|
+
this.selectList.onSelect = (item) => {
|
|
73
|
+
this.handleAnswer(item.value);
|
|
74
|
+
};
|
|
75
|
+
this.selectList.onCancel = () => {
|
|
76
|
+
this.handleCancel();
|
|
77
|
+
};
|
|
78
|
+
this.contentBox.addChild(this.selectList);
|
|
79
|
+
this.contentBox.addChild(new Spacer(1));
|
|
80
|
+
this.contentBox.addChild(new Text(theme.fg("dim", "\u2191\u2193 to navigate \xB7 Enter to select \xB7 Esc to skip"), 0, 0));
|
|
81
|
+
}
|
|
82
|
+
buildInputMode() {
|
|
83
|
+
this.input = new Input();
|
|
84
|
+
this.input.onSubmit = (value) => {
|
|
85
|
+
const trimmed = value.trim();
|
|
86
|
+
if (trimmed || this.allowEmptyInput) {
|
|
87
|
+
this.handleAnswer(trimmed);
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
this.contentBox.addChild(this.input);
|
|
91
|
+
this.contentBox.addChild(new Spacer(1));
|
|
92
|
+
this.contentBox.addChild(new Text(theme.fg("dim", "Enter to submit \xB7 Esc to skip"), 0, 0));
|
|
93
|
+
}
|
|
94
|
+
handleAnswer(answer) {
|
|
95
|
+
if (this.answered) return;
|
|
96
|
+
this.answered = true;
|
|
97
|
+
const isNegative = this.isNegativeAnswer?.(answer) ?? false;
|
|
98
|
+
this.contentBox.clear();
|
|
99
|
+
this.contentBox.setBgFn((text) => theme.bg(isNegative ? "toolErrorBg" : "toolSuccessBg", text));
|
|
100
|
+
const resultText = this.formatResult ? this.formatResult(answer) : `${this.questionText} \u2192 ${answer}`;
|
|
101
|
+
const icon = isNegative ? theme.fg("error", "\u2717") : theme.fg("success", "\u2713");
|
|
102
|
+
this.contentBox.addChild(new Text(theme.fg("text", `${icon} ${resultText}`), 0, 0));
|
|
103
|
+
this.onSubmit(answer);
|
|
104
|
+
}
|
|
105
|
+
handleCancel() {
|
|
106
|
+
if (this.answered) return;
|
|
107
|
+
this.answered = true;
|
|
108
|
+
this.contentBox.clear();
|
|
109
|
+
this.contentBox.setBgFn((text) => theme.bg("toolErrorBg", text));
|
|
110
|
+
this.contentBox.addChild(
|
|
111
|
+
new Text(theme.fg("dim", `${theme.fg("error", "\u2717")} ${this.questionText} (cancelled)`), 0, 0)
|
|
112
|
+
);
|
|
113
|
+
this.onCancel();
|
|
114
|
+
}
|
|
115
|
+
handleInput(data) {
|
|
116
|
+
if (this.answered) return;
|
|
117
|
+
if (this.selectList) {
|
|
118
|
+
this.selectList.handleInput(data);
|
|
119
|
+
} else if (this.input) {
|
|
120
|
+
const kb = getEditorKeybindings();
|
|
121
|
+
if (kb.matches(data, "selectCancel")) {
|
|
122
|
+
this.handleCancel();
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
this.input.handleInput(data);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
// src/onboarding/onboarding-inline.ts
|
|
20
131
|
var OnboardingInlineComponent = class extends Container {
|
|
21
132
|
tui;
|
|
22
133
|
options;
|
|
@@ -24,6 +135,7 @@ var OnboardingInlineComponent = class extends Container {
|
|
|
24
135
|
currentStep = "welcome";
|
|
25
136
|
stepBox;
|
|
26
137
|
selectList;
|
|
138
|
+
activeInlineQuestion;
|
|
27
139
|
_finished = false;
|
|
28
140
|
// Collected choices
|
|
29
141
|
loginRequested = false;
|
|
@@ -235,25 +347,22 @@ var OnboardingInlineComponent = class extends Container {
|
|
|
235
347
|
modePackDetail;
|
|
236
348
|
renderModePack() {
|
|
237
349
|
const packs = this.options.modePacks;
|
|
350
|
+
const box = this.makeBox();
|
|
238
351
|
if (!this.options.hasProviderAccess) {
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
box2.addChild(
|
|
352
|
+
box.addChild(new Text(theme.bold(theme.fg("warning", "No model providers configured")), 0, 0));
|
|
353
|
+
box.addChild(new Spacer(1));
|
|
354
|
+
box.addChild(new Text(theme.fg("text", "To use Mastra Code you need at least one API key or OAuth login"), 0, 0));
|
|
355
|
+
box.addChild(new Text(theme.fg("text", "for Anthropic, OpenAI, or another supported provider."), 0, 0));
|
|
356
|
+
box.addChild(new Spacer(1));
|
|
357
|
+
box.addChild(
|
|
246
358
|
new Text(theme.fg("dim", "See https://mastra.ai/models for supported providers and API key env vars."), 0, 0)
|
|
247
359
|
);
|
|
248
|
-
|
|
249
|
-
|
|
360
|
+
box.addChild(new Spacer(1));
|
|
361
|
+
box.addChild(
|
|
250
362
|
new Text(theme.fg("dim", "Set an API key and restart, or run /login to authenticate via OAuth."), 0, 0)
|
|
251
363
|
);
|
|
252
|
-
|
|
253
|
-
setTimeout(() => process.exit(1), 3e3);
|
|
254
|
-
return;
|
|
364
|
+
box.addChild(new Spacer(1));
|
|
255
365
|
}
|
|
256
|
-
const box = this.makeBox();
|
|
257
366
|
box.addChild(new Text(theme.bold(theme.fg("accent", "Model Packs")), 0, 0));
|
|
258
367
|
box.addChild(new Spacer(1));
|
|
259
368
|
box.addChild(new Text(theme.fg("text", "Choose default models for each mode (build / plan / fast):"), 0, 0));
|
|
@@ -310,9 +419,42 @@ var OnboardingInlineComponent = class extends Container {
|
|
|
310
419
|
// ---------------------------------------------------------------------------
|
|
311
420
|
// Custom pack flow — sequential model selection for each mode
|
|
312
421
|
// ---------------------------------------------------------------------------
|
|
422
|
+
async promptCustomPackName() {
|
|
423
|
+
return new Promise((resolve2) => {
|
|
424
|
+
const question = new AskQuestionInlineComponent(
|
|
425
|
+
{
|
|
426
|
+
question: "Name this custom pack",
|
|
427
|
+
formatResult: (answer) => `Custom pack: ${answer}`,
|
|
428
|
+
onSubmit: (answer) => {
|
|
429
|
+
this.activeInlineQuestion = void 0;
|
|
430
|
+
const trimmed = answer.trim();
|
|
431
|
+
resolve2(trimmed.length > 0 ? trimmed : null);
|
|
432
|
+
},
|
|
433
|
+
onCancel: () => {
|
|
434
|
+
this.activeInlineQuestion = void 0;
|
|
435
|
+
resolve2(null);
|
|
436
|
+
}
|
|
437
|
+
},
|
|
438
|
+
this.tui
|
|
439
|
+
);
|
|
440
|
+
this.activeInlineQuestion = question;
|
|
441
|
+
this.stepBox.addChild(new Spacer(1));
|
|
442
|
+
this.stepBox.addChild(question);
|
|
443
|
+
this.tui.requestRender();
|
|
444
|
+
});
|
|
445
|
+
}
|
|
313
446
|
async runCustomPackFlow() {
|
|
314
447
|
this.selectList = void 0;
|
|
315
|
-
this.
|
|
448
|
+
const packName = await this.promptCustomPackName();
|
|
449
|
+
if (!packName) {
|
|
450
|
+
const fallback = this.options.modePacks.find((p) => p.id !== "custom") ?? this.options.modePacks[0];
|
|
451
|
+
this.selectedModePack = fallback;
|
|
452
|
+
this.collapseStep(`Model pack \u2192 ${theme.bold(this.selectedModePack.name)} (cancelled custom)`);
|
|
453
|
+
this.renderStep("omPack");
|
|
454
|
+
this.tui.requestRender();
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
this.collapseStep(`Model pack \u2192 Custom (${packName})`);
|
|
316
458
|
const modes = [
|
|
317
459
|
{ id: "plan", label: "plan", color: mastra.blue },
|
|
318
460
|
{ id: "build", label: "build", color: mastra.purple },
|
|
@@ -333,13 +475,13 @@ var OnboardingInlineComponent = class extends Container {
|
|
|
333
475
|
models[mode.id] = modelId;
|
|
334
476
|
}
|
|
335
477
|
this.selectedModePack = {
|
|
336
|
-
id:
|
|
337
|
-
name:
|
|
338
|
-
description: "
|
|
478
|
+
id: `custom:${packName}`,
|
|
479
|
+
name: packName,
|
|
480
|
+
description: "Saved custom pack",
|
|
339
481
|
models: { build: models.build, plan: models.plan, fast: models.fast }
|
|
340
482
|
};
|
|
341
483
|
this.collapseStep(
|
|
342
|
-
`Model pack \u2192 ${theme.bold(
|
|
484
|
+
`Model pack \u2192 ${theme.bold(packName)} ${chalk10.hex(mastra.blue)("plan")} ${models.plan} ${chalk10.hex(mastra.purple)("build")} ${models.build} ${chalk10.hex(mastra.green)("fast")} ${models.fast}`
|
|
343
485
|
);
|
|
344
486
|
this.renderStep("omPack");
|
|
345
487
|
this.tui.requestRender();
|
|
@@ -481,125 +623,128 @@ var OnboardingInlineComponent = class extends Container {
|
|
|
481
623
|
this.stepBox.setBgFn((text) => theme.bg("toolSuccessBg", text));
|
|
482
624
|
this.stepBox.addChild(new Text(`${theme.fg("success", "\u2713")} ${theme.fg("text", summary)}`, 0, 0));
|
|
483
625
|
this.selectList = void 0;
|
|
626
|
+
this.activeInlineQuestion = void 0;
|
|
484
627
|
}
|
|
485
628
|
// ---------------------------------------------------------------------------
|
|
486
629
|
// Input handling
|
|
487
630
|
// ---------------------------------------------------------------------------
|
|
488
631
|
handleInput(data) {
|
|
489
632
|
if (this._finished) return;
|
|
633
|
+
if (this.activeInlineQuestion) {
|
|
634
|
+
this.activeInlineQuestion.handleInput(data);
|
|
635
|
+
return;
|
|
636
|
+
}
|
|
490
637
|
if (this.selectList) {
|
|
491
638
|
this.selectList.handleInput(data);
|
|
492
639
|
return;
|
|
493
640
|
}
|
|
494
641
|
}
|
|
495
642
|
};
|
|
496
|
-
var
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
643
|
+
var PACKAGE_NAME = "mastracode";
|
|
644
|
+
var NPM_REGISTRY_URL = `https://registry.npmjs.org/${PACKAGE_NAME}/latest`;
|
|
645
|
+
var FETCH_TIMEOUT_MS = 5e3;
|
|
646
|
+
function matchPM(str) {
|
|
647
|
+
if (/pnpm/i.test(str)) return "pnpm";
|
|
648
|
+
if (/\byarn\b/i.test(str)) return "yarn";
|
|
649
|
+
if (/\bbun\b/i.test(str)) return "bun";
|
|
650
|
+
if (/\bnpm\b/i.test(str)) return "npm";
|
|
651
|
+
return null;
|
|
652
|
+
}
|
|
653
|
+
async function detectPackageManager() {
|
|
654
|
+
const userAgent = process.env.npm_config_user_agent;
|
|
655
|
+
if (userAgent) {
|
|
656
|
+
const pm = matchPM(userAgent);
|
|
657
|
+
if (pm) return pm;
|
|
509
658
|
}
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
}
|
|
659
|
+
const execPath = process.env.npm_execpath;
|
|
660
|
+
if (execPath) {
|
|
661
|
+
const pm = matchPM(execPath);
|
|
662
|
+
if (pm) return pm;
|
|
515
663
|
}
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
this.isNegativeAnswer = options.isNegativeAnswer;
|
|
522
|
-
this.questionText = options.question;
|
|
523
|
-
this.addChild(new Spacer(1));
|
|
524
|
-
this.contentBox = new Box(1, 1, (text) => theme.bg("toolPendingBg", text));
|
|
525
|
-
this.addChild(this.contentBox);
|
|
526
|
-
this.contentBox.addChild(new Text(theme.bold(theme.fg("accent", "\u2753 Question")), 0, 0));
|
|
527
|
-
this.contentBox.addChild(new Spacer(1));
|
|
528
|
-
for (const line of options.question.split("\n")) {
|
|
529
|
-
this.contentBox.addChild(new Text(theme.fg("text", line), 0, 0));
|
|
530
|
-
}
|
|
531
|
-
this.contentBox.addChild(new Spacer(1));
|
|
532
|
-
if (options.options && options.options.length > 0) {
|
|
533
|
-
this.buildSelectMode(options.options);
|
|
534
|
-
} else {
|
|
535
|
-
this.buildInputMode();
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
|
-
buildSelectMode(opts) {
|
|
539
|
-
const items = opts.map((opt) => ({
|
|
540
|
-
value: opt.label,
|
|
541
|
-
label: opt.description ? ` ${opt.label} ${theme.fg("dim", opt.description)}` : ` ${opt.label}`
|
|
542
|
-
}));
|
|
543
|
-
this.selectList = new SelectList(items, Math.min(items.length, 8), getSelectListTheme());
|
|
544
|
-
this.selectList.onSelect = (item) => {
|
|
545
|
-
this.handleAnswer(item.value);
|
|
546
|
-
};
|
|
547
|
-
this.selectList.onCancel = () => {
|
|
548
|
-
this.handleCancel();
|
|
549
|
-
};
|
|
550
|
-
this.contentBox.addChild(this.selectList);
|
|
551
|
-
this.contentBox.addChild(new Spacer(1));
|
|
552
|
-
this.contentBox.addChild(new Text(theme.fg("dim", "\u2191\u2193 to navigate \xB7 Enter to select \xB7 Esc to skip"), 0, 0));
|
|
664
|
+
const nodePath = process.env.NODE_PATH;
|
|
665
|
+
if (nodePath) {
|
|
666
|
+
if (/[/\\]\.pnpm[/\\]/.test(nodePath) || /[/\\]pnpm[/\\]/.test(nodePath)) return "pnpm";
|
|
667
|
+
if (/[/\\]\.yarn[/\\]/.test(nodePath)) return "yarn";
|
|
668
|
+
if (/[/\\]\.bun[/\\]/.test(nodePath)) return "bun";
|
|
553
669
|
}
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
}
|
|
561
|
-
};
|
|
562
|
-
this.contentBox.addChild(this.input);
|
|
563
|
-
this.contentBox.addChild(new Spacer(1));
|
|
564
|
-
this.contentBox.addChild(new Text(theme.fg("dim", "Enter to submit \xB7 Esc to skip"), 0, 0));
|
|
670
|
+
try {
|
|
671
|
+
const scriptPath = realpathSync(process.argv[1] ?? "");
|
|
672
|
+
if (/[/\\]\.?pnpm[/\\]/.test(scriptPath)) return "pnpm";
|
|
673
|
+
if (/[/\\]\.?yarn[/\\]/.test(scriptPath)) return "yarn";
|
|
674
|
+
if (/[/\\]\.?bun[/\\]/.test(scriptPath)) return "bun";
|
|
675
|
+
} catch {
|
|
565
676
|
}
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
677
|
+
const pnpmResult = await new Promise((resolve2) => {
|
|
678
|
+
execFile("pnpm", ["list", "-g", "--depth=0", PACKAGE_NAME], { timeout: 3e3 }, (error, stdout) => {
|
|
679
|
+
resolve2(!error && stdout.includes(PACKAGE_NAME));
|
|
680
|
+
});
|
|
681
|
+
});
|
|
682
|
+
if (pnpmResult) return "pnpm";
|
|
683
|
+
return "npm";
|
|
684
|
+
}
|
|
685
|
+
function getInstallCommand(pm, version) {
|
|
686
|
+
const pkg = version ? `${PACKAGE_NAME}@${version}` : `${PACKAGE_NAME}@latest`;
|
|
687
|
+
switch (pm) {
|
|
688
|
+
case "pnpm":
|
|
689
|
+
return `pnpm add -g ${pkg}`;
|
|
690
|
+
case "yarn":
|
|
691
|
+
return `yarn global add ${pkg}`;
|
|
692
|
+
case "bun":
|
|
693
|
+
return `bun add -g ${pkg}`;
|
|
694
|
+
default:
|
|
695
|
+
return `npm install -g ${pkg}`;
|
|
576
696
|
}
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
697
|
+
}
|
|
698
|
+
function getCurrentVersion() {
|
|
699
|
+
const require2 = createRequire(import.meta.url);
|
|
700
|
+
const pkg = require2("../../package.json");
|
|
701
|
+
return pkg.version;
|
|
702
|
+
}
|
|
703
|
+
async function fetchLatestVersion() {
|
|
704
|
+
try {
|
|
705
|
+
const controller = new AbortController();
|
|
706
|
+
const timeout = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
|
|
707
|
+
const res = await fetch(NPM_REGISTRY_URL, { signal: controller.signal });
|
|
708
|
+
clearTimeout(timeout);
|
|
709
|
+
if (!res.ok) return null;
|
|
710
|
+
const data = await res.json();
|
|
711
|
+
return data.version ?? null;
|
|
712
|
+
} catch {
|
|
713
|
+
return null;
|
|
586
714
|
}
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
715
|
+
}
|
|
716
|
+
function isNewerVersion(current, latest) {
|
|
717
|
+
const parse = (v) => v.replace(/^v/, "").split("-")[0].split(".").map((s) => {
|
|
718
|
+
const n = Number(s);
|
|
719
|
+
return Number.isFinite(n) ? n : 0;
|
|
720
|
+
});
|
|
721
|
+
const [cMajor = 0, cMinor = 0, cPatch = 0] = parse(current);
|
|
722
|
+
const [lMajor = 0, lMinor = 0, lPatch = 0] = parse(latest);
|
|
723
|
+
if (lMajor !== cMajor) return lMajor > cMajor;
|
|
724
|
+
if (lMinor !== cMinor) return lMinor > cMinor;
|
|
725
|
+
return lPatch > cPatch;
|
|
726
|
+
}
|
|
727
|
+
function runUpdate(pm, targetVersion) {
|
|
728
|
+
const args = buildInstallArgs(pm, targetVersion);
|
|
729
|
+
return new Promise((resolve2) => {
|
|
730
|
+
execFile(pm, args, { timeout: 6e4 }, (error) => {
|
|
731
|
+
resolve2(!error);
|
|
732
|
+
});
|
|
733
|
+
});
|
|
734
|
+
}
|
|
735
|
+
function buildInstallArgs(pm, version) {
|
|
736
|
+
const pkg = `${PACKAGE_NAME}@${version}`;
|
|
737
|
+
switch (pm) {
|
|
738
|
+
case "pnpm":
|
|
739
|
+
return ["add", "-g", pkg];
|
|
740
|
+
case "yarn":
|
|
741
|
+
return ["global", "add", pkg];
|
|
742
|
+
case "bun":
|
|
743
|
+
return ["add", "-g", pkg];
|
|
744
|
+
default:
|
|
745
|
+
return ["install", "-g", pkg];
|
|
599
746
|
}
|
|
600
|
-
}
|
|
601
|
-
|
|
602
|
-
// src/tui/claude-max-warning.ts
|
|
747
|
+
}
|
|
603
748
|
function showClaudeMaxOAuthWarning(state, mode) {
|
|
604
749
|
const options = mode === "login" ? [
|
|
605
750
|
{ label: "Continue", description: "Proceed with Anthropic OAuth" },
|
|
@@ -695,7 +840,7 @@ async function replaceFileReferences(template, workingDir) {
|
|
|
695
840
|
for (const match of matches) {
|
|
696
841
|
const [fullMatch, filePath] = match;
|
|
697
842
|
try {
|
|
698
|
-
const fullPath =
|
|
843
|
+
const fullPath = path5.resolve(workingDir, filePath);
|
|
699
844
|
const content = await promises.readFile(fullPath, "utf-8");
|
|
700
845
|
result = result.replace(fullMatch, content);
|
|
701
846
|
} catch (error) {
|
|
@@ -715,13 +860,14 @@ function getCommands(modes) {
|
|
|
715
860
|
{ key: "/name", description: "Rename current thread" },
|
|
716
861
|
{ key: "/resource", description: "Show/switch resource ID" },
|
|
717
862
|
{ key: "/skills", description: "List available skills" },
|
|
718
|
-
{ key: "/models", description: "
|
|
719
|
-
{ key: "/
|
|
863
|
+
{ key: "/models", description: "Switch model pack" },
|
|
864
|
+
{ key: "/custom-providers", description: "Manage custom providers and models" },
|
|
720
865
|
{ key: "/subagents", description: "Configure subagent models" },
|
|
721
866
|
{ key: "/permissions", description: "Tool approval permissions" },
|
|
722
867
|
{ key: "/settings", description: "Notifications, YOLO, thinking" },
|
|
723
868
|
{ key: "/om", description: "Configure Observational Memory" },
|
|
724
869
|
{ key: "/review", description: "Review a GitHub pull request" },
|
|
870
|
+
{ key: "/report-issue", description: "Open or browse mastracode issues" },
|
|
725
871
|
{ key: "/cost", description: "Token usage and costs" },
|
|
726
872
|
{ key: "/diff", description: "Modified files or git diff" },
|
|
727
873
|
{ key: "/sandbox", description: "Manage sandbox allowed paths" },
|
|
@@ -748,7 +894,8 @@ function getShortcuts(modes) {
|
|
|
748
894
|
{ key: "Ctrl+T", description: "Toggle thinking blocks" },
|
|
749
895
|
{ key: "Ctrl+E", description: "Expand/collapse tool outputs" },
|
|
750
896
|
{ key: "Ctrl+Y", description: "Toggle YOLO mode" },
|
|
751
|
-
{ key: "Ctrl+Z", description: "
|
|
897
|
+
{ key: "Ctrl+Z", description: "Suspend process (fg to resume)" },
|
|
898
|
+
{ key: "Alt+Z", description: "Undo last clear" }
|
|
752
899
|
];
|
|
753
900
|
if (modes > 1) {
|
|
754
901
|
shortcuts.push({ key: "\u21E7+Tab", description: "Cycle agent modes" });
|
|
@@ -957,7 +1104,7 @@ async function handlePermissionsCommand(ctx, args) {
|
|
|
957
1104
|
await showPermissions(ctx);
|
|
958
1105
|
}
|
|
959
1106
|
async function showPermissions(ctx) {
|
|
960
|
-
const { TOOL_CATEGORIES: TOOL_CATEGORIES2, getToolsForCategory } = await import('./permissions-
|
|
1107
|
+
const { TOOL_CATEGORIES: TOOL_CATEGORIES2, getToolsForCategory } = await import('./permissions-S3LGXIDB.js');
|
|
961
1108
|
const rules = ctx.harness.getPermissionRules();
|
|
962
1109
|
const grants = ctx.harness.getSessionGrants();
|
|
963
1110
|
const isYolo = ctx.harness.getState().yolo === true;
|
|
@@ -1116,13 +1263,21 @@ Example mcp.json:
|
|
|
1116
1263
|
"command": "npx",
|
|
1117
1264
|
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/path"],
|
|
1118
1265
|
"env": {}
|
|
1266
|
+
},
|
|
1267
|
+
"remote-api": {
|
|
1268
|
+
"url": "https://mcp.example.com/sse",
|
|
1269
|
+
"headers": { "Authorization": "Bearer <token>" }
|
|
1119
1270
|
}
|
|
1120
1271
|
}
|
|
1121
|
-
}
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1274
|
+
Note: For dynamic auth (token refresh), use a stdio wrapper.
|
|
1275
|
+
"headers" only supports static values.`
|
|
1122
1276
|
);
|
|
1123
1277
|
return;
|
|
1124
1278
|
}
|
|
1125
1279
|
const statuses = mm.getServerStatuses();
|
|
1280
|
+
const skipped = mm.getSkippedServers();
|
|
1126
1281
|
const lines = [`MCP Servers:`];
|
|
1127
1282
|
lines.push(` Project: ${paths.project}`);
|
|
1128
1283
|
lines.push(` Global: ${paths.global}`);
|
|
@@ -1131,13 +1286,20 @@ Example mcp.json:
|
|
|
1131
1286
|
for (const status of statuses) {
|
|
1132
1287
|
const icon = status.connected ? "\u2713" : "\u2717";
|
|
1133
1288
|
const state = status.connected ? "connected" : `error: ${status.error}`;
|
|
1134
|
-
lines.push(` ${icon} ${status.name} (${state})`);
|
|
1289
|
+
lines.push(` ${icon} ${status.name} [${status.transport}] (${state})`);
|
|
1135
1290
|
if (status.toolNames.length > 0) {
|
|
1136
1291
|
for (const toolName of status.toolNames) {
|
|
1137
1292
|
lines.push(` - ${toolName}`);
|
|
1138
1293
|
}
|
|
1139
1294
|
}
|
|
1140
1295
|
}
|
|
1296
|
+
if (skipped.length > 0) {
|
|
1297
|
+
lines.push("");
|
|
1298
|
+
lines.push(" Skipped:");
|
|
1299
|
+
for (const s of skipped) {
|
|
1300
|
+
lines.push(` \u2717 ${s.name}: ${s.reason}`);
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1141
1303
|
lines.push("");
|
|
1142
1304
|
lines.push(` /mcp reload - Disconnect and reconnect all servers`);
|
|
1143
1305
|
ctx.showInfo(lines.join("\n"));
|
|
@@ -1203,11 +1365,80 @@ Skills are automatically activated by the agent when relevant.`
|
|
|
1203
1365
|
ctx.showError(`Failed to list skills: ${error instanceof Error ? error.message : String(error)}`);
|
|
1204
1366
|
}
|
|
1205
1367
|
}
|
|
1206
|
-
|
|
1207
|
-
// src/tui/commands/new.ts
|
|
1208
|
-
function handleNewCommand(ctx) {
|
|
1368
|
+
|
|
1369
|
+
// src/tui/commands/new.ts
|
|
1370
|
+
function handleNewCommand(ctx) {
|
|
1371
|
+
const { state } = ctx;
|
|
1372
|
+
state.pendingNewThread = true;
|
|
1373
|
+
state.chatContainer.clear();
|
|
1374
|
+
state.pendingTools.clear();
|
|
1375
|
+
state.allToolComponents = [];
|
|
1376
|
+
state.harness.getDisplayState().modifiedFiles.clear();
|
|
1377
|
+
if (state.taskProgress) {
|
|
1378
|
+
state.taskProgress.updateTasks([]);
|
|
1379
|
+
}
|
|
1380
|
+
state.taskWriteInsertIndex = -1;
|
|
1381
|
+
ctx.updateStatusLine();
|
|
1382
|
+
state.ui.requestRender();
|
|
1383
|
+
ctx.showInfo("Ready for new conversation");
|
|
1384
|
+
}
|
|
1385
|
+
function confirmClone(state, threadLabel) {
|
|
1386
|
+
const label = threadLabel ? `Clone thread "${threadLabel}"?` : "Clone the current thread?";
|
|
1387
|
+
return new Promise((resolve2) => {
|
|
1388
|
+
const question = new AskQuestionInlineComponent(
|
|
1389
|
+
{
|
|
1390
|
+
question: label,
|
|
1391
|
+
options: [
|
|
1392
|
+
{ label: "Yes", description: "Clone this thread" },
|
|
1393
|
+
{ label: "No", description: "Cancel" }
|
|
1394
|
+
],
|
|
1395
|
+
formatResult: (answer) => answer === "Yes" ? "Cloning thread..." : "Cancelled.",
|
|
1396
|
+
isNegativeAnswer: (answer) => answer !== "Yes",
|
|
1397
|
+
onSubmit: (answer) => {
|
|
1398
|
+
state.activeInlineQuestion = void 0;
|
|
1399
|
+
resolve2(answer === "Yes");
|
|
1400
|
+
},
|
|
1401
|
+
onCancel: () => {
|
|
1402
|
+
state.activeInlineQuestion = void 0;
|
|
1403
|
+
resolve2(false);
|
|
1404
|
+
}
|
|
1405
|
+
},
|
|
1406
|
+
state.ui
|
|
1407
|
+
);
|
|
1408
|
+
state.activeInlineQuestion = question;
|
|
1409
|
+
state.chatContainer.addChild(question);
|
|
1410
|
+
state.chatContainer.addChild(new Spacer(1));
|
|
1411
|
+
state.ui.requestRender();
|
|
1412
|
+
state.chatContainer.invalidate();
|
|
1413
|
+
});
|
|
1414
|
+
}
|
|
1415
|
+
function askCloneName(state) {
|
|
1416
|
+
return new Promise((resolve2) => {
|
|
1417
|
+
const question = new AskQuestionInlineComponent(
|
|
1418
|
+
{
|
|
1419
|
+
question: "Give the cloned thread a name? (Esc to skip)",
|
|
1420
|
+
formatResult: (answer) => `Thread name: ${answer}`,
|
|
1421
|
+
onSubmit: (answer) => {
|
|
1422
|
+
state.activeInlineQuestion = void 0;
|
|
1423
|
+
const trimmed = answer.trim();
|
|
1424
|
+
resolve2(trimmed.length > 0 ? trimmed : null);
|
|
1425
|
+
},
|
|
1426
|
+
onCancel: () => {
|
|
1427
|
+
state.activeInlineQuestion = void 0;
|
|
1428
|
+
resolve2(null);
|
|
1429
|
+
}
|
|
1430
|
+
},
|
|
1431
|
+
state.ui
|
|
1432
|
+
);
|
|
1433
|
+
state.activeInlineQuestion = question;
|
|
1434
|
+
state.chatContainer.addChild(question);
|
|
1435
|
+
state.chatContainer.addChild(new Spacer(1));
|
|
1436
|
+
state.ui.requestRender();
|
|
1437
|
+
state.chatContainer.invalidate();
|
|
1438
|
+
});
|
|
1439
|
+
}
|
|
1440
|
+
async function resetUIAfterClone(ctx, clonedTitle) {
|
|
1209
1441
|
const { state } = ctx;
|
|
1210
|
-
state.pendingNewThread = true;
|
|
1211
1442
|
state.chatContainer.clear();
|
|
1212
1443
|
state.pendingTools.clear();
|
|
1213
1444
|
state.allToolComponents = [];
|
|
@@ -1217,8 +1448,28 @@ function handleNewCommand(ctx) {
|
|
|
1217
1448
|
}
|
|
1218
1449
|
state.taskWriteInsertIndex = -1;
|
|
1219
1450
|
ctx.updateStatusLine();
|
|
1451
|
+
await ctx.renderExistingMessages();
|
|
1220
1452
|
state.ui.requestRender();
|
|
1221
|
-
ctx.showInfo(
|
|
1453
|
+
ctx.showInfo(`Cloned thread: ${clonedTitle}`);
|
|
1454
|
+
}
|
|
1455
|
+
async function handleCloneCommand(ctx) {
|
|
1456
|
+
const { state } = ctx;
|
|
1457
|
+
const currentThreadId = state.harness.getCurrentThreadId();
|
|
1458
|
+
if (!currentThreadId) {
|
|
1459
|
+
ctx.showInfo("No active thread to clone");
|
|
1460
|
+
return;
|
|
1461
|
+
}
|
|
1462
|
+
if (!await confirmClone(state)) return;
|
|
1463
|
+
const customTitle = await askCloneName(state);
|
|
1464
|
+
try {
|
|
1465
|
+
const clonedThread = await state.harness.cloneThread({
|
|
1466
|
+
sourceThreadId: currentThreadId,
|
|
1467
|
+
...customTitle ? { title: customTitle } : {}
|
|
1468
|
+
});
|
|
1469
|
+
await resetUIAfterClone(ctx, clonedThread.title || clonedThread.id);
|
|
1470
|
+
} catch (error) {
|
|
1471
|
+
ctx.showError(`Failed to clone thread: ${error instanceof Error ? error.message : String(error)}`);
|
|
1472
|
+
}
|
|
1222
1473
|
}
|
|
1223
1474
|
|
|
1224
1475
|
// src/tui/commands/resource.ts
|
|
@@ -1237,21 +1488,42 @@ async function handleResourceCommand(ctx, args) {
|
|
|
1237
1488
|
...knownIds.map((id) => ` ${id === current ? "* " : " "}${id}`),
|
|
1238
1489
|
"",
|
|
1239
1490
|
"Usage:",
|
|
1240
|
-
" /resource
|
|
1241
|
-
" /resource
|
|
1491
|
+
" /resource - Show current resource and known IDs",
|
|
1492
|
+
" /resource <id> - Switch to a resource ID (resumes latest thread)",
|
|
1493
|
+
" /resource reset - Reset to auto-detected ID"
|
|
1242
1494
|
];
|
|
1243
1495
|
ctx.showInfo(lines.join("\n"));
|
|
1244
1496
|
return;
|
|
1245
1497
|
}
|
|
1246
1498
|
const newId = sub === "reset" ? defaultId : args.join(" ").trim();
|
|
1499
|
+
if (newId === current) {
|
|
1500
|
+
ctx.showInfo(`Already on resource: ${current}`);
|
|
1501
|
+
return;
|
|
1502
|
+
}
|
|
1247
1503
|
harness.setResourceId({ resourceId: newId });
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1504
|
+
const threads = await harness.listThreads();
|
|
1505
|
+
const latest = [...threads].sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime())[0];
|
|
1506
|
+
if (latest) {
|
|
1507
|
+
await harness.switchThread({ threadId: latest.id });
|
|
1508
|
+
state.chatContainer.clear();
|
|
1509
|
+
state.pendingTools.clear();
|
|
1510
|
+
state.allToolComponents = [];
|
|
1511
|
+
state.pendingNewThread = false;
|
|
1512
|
+
await ctx.renderExistingMessages();
|
|
1513
|
+
ctx.showInfo(
|
|
1514
|
+
sub === "reset" ? `Resource ID reset to: ${defaultId} \u2014 resumed thread: ${latest.title || latest.id}` : `Switched to resource: ${newId} \u2014 resumed thread: ${latest.title || latest.id}`
|
|
1515
|
+
);
|
|
1516
|
+
} else {
|
|
1517
|
+
state.chatContainer.clear();
|
|
1518
|
+
state.pendingTools.clear();
|
|
1519
|
+
state.allToolComponents = [];
|
|
1520
|
+
state.pendingNewThread = true;
|
|
1521
|
+
ctx.showInfo(
|
|
1522
|
+
sub === "reset" ? `Resource ID reset to: ${defaultId} (no existing threads, a new one will be created)` : `Switched to resource: ${newId} (no existing threads, a new one will be created)`
|
|
1523
|
+
);
|
|
1524
|
+
}
|
|
1252
1525
|
ctx.updateStatusLine();
|
|
1253
1526
|
state.ui.requestRender();
|
|
1254
|
-
ctx.showInfo(sub === "reset" ? `Resource ID reset to: ${defaultId}` : `Switched to resource: ${newId}`);
|
|
1255
1527
|
}
|
|
1256
1528
|
function colorizeDiffLine(line) {
|
|
1257
1529
|
const t = theme.getTheme();
|
|
@@ -1382,8 +1654,10 @@ var ThreadSelectorComponent = class extends Box {
|
|
|
1382
1654
|
selectedIndex = 0;
|
|
1383
1655
|
currentThreadId;
|
|
1384
1656
|
currentResourceId;
|
|
1657
|
+
currentProjectPath;
|
|
1385
1658
|
onSelectCallback;
|
|
1386
1659
|
onCancelCallback;
|
|
1660
|
+
onCloneCallback;
|
|
1387
1661
|
tui;
|
|
1388
1662
|
getMessagePreview;
|
|
1389
1663
|
messagePreviews = /* @__PURE__ */ new Map();
|
|
@@ -1400,10 +1674,12 @@ var ThreadSelectorComponent = class extends Box {
|
|
|
1400
1674
|
super(2, 1, (text) => theme.bg("overlayBg", text));
|
|
1401
1675
|
this.tui = options.tui;
|
|
1402
1676
|
this.currentResourceId = options.currentResourceId;
|
|
1677
|
+
this.currentProjectPath = options.currentProjectPath;
|
|
1403
1678
|
this.allThreads = this.sortThreads(options.threads, options.currentThreadId);
|
|
1404
1679
|
this.currentThreadId = options.currentThreadId;
|
|
1405
1680
|
this.onSelectCallback = options.onSelect;
|
|
1406
1681
|
this.onCancelCallback = options.onCancel;
|
|
1682
|
+
this.onCloneCallback = options.onClone;
|
|
1407
1683
|
this.getMessagePreview = options.getMessagePreview;
|
|
1408
1684
|
this.filteredThreads = this.allThreads;
|
|
1409
1685
|
this.buildUI();
|
|
@@ -1426,7 +1702,10 @@ var ThreadSelectorComponent = class extends Box {
|
|
|
1426
1702
|
buildUI() {
|
|
1427
1703
|
this.addChild(new Text(theme.bold(theme.fg("accent", "Select Thread")), 0, 0));
|
|
1428
1704
|
this.addChild(new Spacer(1));
|
|
1429
|
-
this.
|
|
1705
|
+
const cloneHint = this.onCloneCallback ? " \u2022 c clone" : "";
|
|
1706
|
+
this.addChild(
|
|
1707
|
+
new Text(theme.fg("muted", `Type to search \u2022 \u2191\u2193 navigate \u2022 Enter select${cloneHint} \u2022 Esc cancel`), 0, 0)
|
|
1708
|
+
);
|
|
1430
1709
|
this.addChild(new Spacer(1));
|
|
1431
1710
|
this.searchInput = new Input();
|
|
1432
1711
|
this.searchInput.onSubmit = () => {
|
|
@@ -1444,6 +1723,7 @@ var ThreadSelectorComponent = class extends Box {
|
|
|
1444
1723
|
sortThreads(threads, currentThreadId) {
|
|
1445
1724
|
const sorted = [...threads];
|
|
1446
1725
|
const resId = this.currentResourceId;
|
|
1726
|
+
const projPath = this.currentProjectPath;
|
|
1447
1727
|
sorted.sort((a, b) => {
|
|
1448
1728
|
if (a.id === currentThreadId) return -1;
|
|
1449
1729
|
if (b.id === currentThreadId) return 1;
|
|
@@ -1453,6 +1733,12 @@ var ThreadSelectorComponent = class extends Box {
|
|
|
1453
1733
|
if (aLocal && !bLocal) return -1;
|
|
1454
1734
|
if (!aLocal && bLocal) return 1;
|
|
1455
1735
|
}
|
|
1736
|
+
if (projPath && a.resourceId === b.resourceId) {
|
|
1737
|
+
const aDir = typeof a.metadata?.projectPath === "string" && a.metadata.projectPath === projPath;
|
|
1738
|
+
const bDir = typeof b.metadata?.projectPath === "string" && b.metadata.projectPath === projPath;
|
|
1739
|
+
if (aDir && !bDir) return -1;
|
|
1740
|
+
if (!aDir && bDir) return 1;
|
|
1741
|
+
}
|
|
1456
1742
|
return b.updatedAt.getTime() - a.updatedAt.getTime();
|
|
1457
1743
|
});
|
|
1458
1744
|
return sorted;
|
|
@@ -1461,7 +1747,7 @@ var ThreadSelectorComponent = class extends Box {
|
|
|
1461
1747
|
this.filteredThreads = query ? fuzzyFilter(
|
|
1462
1748
|
this.allThreads,
|
|
1463
1749
|
query,
|
|
1464
|
-
(t) => `${t.title ?? ""} ${t.resourceId} ${t.id} ${t.metadata?.projectPath
|
|
1750
|
+
(t) => `${t.title ?? ""} ${t.resourceId} ${t.id} ${typeof t.metadata?.projectPath === "string" ? t.metadata.projectPath : ""}`
|
|
1465
1751
|
) : this.allThreads;
|
|
1466
1752
|
this.selectedIndex = Math.min(this.selectedIndex, Math.max(0, this.filteredThreads.length - 1));
|
|
1467
1753
|
this.updateList();
|
|
@@ -1537,6 +1823,11 @@ var ThreadSelectorComponent = class extends Box {
|
|
|
1537
1823
|
}
|
|
1538
1824
|
} else if (kb.matches(keyData, "selectCancel")) {
|
|
1539
1825
|
this.onCancelCallback();
|
|
1826
|
+
} else if (keyData === "c" && this.onCloneCallback && !this.searchInput.getValue()) {
|
|
1827
|
+
const selected = this.filteredThreads[this.selectedIndex];
|
|
1828
|
+
if (selected) {
|
|
1829
|
+
this.onCloneCallback(selected);
|
|
1830
|
+
}
|
|
1540
1831
|
} else {
|
|
1541
1832
|
this.searchInput.handleInput(keyData);
|
|
1542
1833
|
this.filterThreads(this.searchInput.getValue());
|
|
@@ -1553,18 +1844,39 @@ function truncatePreview(text, maxLength = 50) {
|
|
|
1553
1844
|
if (text.length <= maxLength) return text;
|
|
1554
1845
|
return text.slice(0, maxLength - 3) + "...";
|
|
1555
1846
|
}
|
|
1556
|
-
function showThreadLockPrompt(ctx, threadTitle, ownerPid) {
|
|
1847
|
+
function showThreadLockPrompt(ctx, threadTitle, ownerPid, lockedThreadId) {
|
|
1557
1848
|
const questionComponent = new AskQuestionInlineComponent(
|
|
1558
1849
|
{
|
|
1559
|
-
question: `Thread "${threadTitle}" is locked by pid ${ownerPid}.
|
|
1850
|
+
question: `Thread "${threadTitle}" is locked by pid ${ownerPid}. What would you like to do?`,
|
|
1560
1851
|
options: [
|
|
1561
|
-
{ label: "
|
|
1562
|
-
{ label: "
|
|
1852
|
+
{ label: "Switch thread", description: "Pick a different thread" },
|
|
1853
|
+
{ label: "New thread", description: "Start a fresh thread" },
|
|
1854
|
+
...lockedThreadId ? [{ label: "Clone thread", description: "Fork from this thread" }] : [],
|
|
1855
|
+
{ label: "Exit", description: "Exit" }
|
|
1563
1856
|
],
|
|
1564
|
-
formatResult: (answer) =>
|
|
1857
|
+
formatResult: (answer) => {
|
|
1858
|
+
if (answer === "Switch thread") return "Opening thread selector...";
|
|
1859
|
+
if (answer === "Clone thread") return "Cloning thread...";
|
|
1860
|
+
if (answer === "New thread") return "Starting new thread.";
|
|
1861
|
+
return "Exiting.";
|
|
1862
|
+
},
|
|
1565
1863
|
onSubmit: async (answer) => {
|
|
1566
1864
|
ctx.state.activeInlineQuestion = void 0;
|
|
1567
|
-
if (
|
|
1865
|
+
if (answer === "Switch thread") {
|
|
1866
|
+
await handleThreadsCommand(ctx);
|
|
1867
|
+
} else if (answer === "Clone thread" && lockedThreadId) {
|
|
1868
|
+
try {
|
|
1869
|
+
const customTitle = await askCloneName(ctx.state);
|
|
1870
|
+
const clonedThread = await ctx.state.harness.cloneThread({
|
|
1871
|
+
sourceThreadId: lockedThreadId,
|
|
1872
|
+
...customTitle ? { title: customTitle } : {}
|
|
1873
|
+
});
|
|
1874
|
+
ctx.state.pendingNewThread = false;
|
|
1875
|
+
await resetUIAfterClone(ctx, clonedThread.title || clonedThread.id);
|
|
1876
|
+
} catch (error) {
|
|
1877
|
+
ctx.showError(`Failed to clone thread: ${error instanceof Error ? error.message : String(error)}`);
|
|
1878
|
+
}
|
|
1879
|
+
} else if (answer === "New thread") ; else {
|
|
1568
1880
|
process.exit(0);
|
|
1569
1881
|
}
|
|
1570
1882
|
},
|
|
@@ -1596,6 +1908,7 @@ async function handleThreadsCommand(ctx) {
|
|
|
1596
1908
|
threads,
|
|
1597
1909
|
currentThreadId: currentId,
|
|
1598
1910
|
currentResourceId,
|
|
1911
|
+
currentProjectPath: state.projectInfo.rootPath,
|
|
1599
1912
|
getMessagePreview: async (threadId) => {
|
|
1600
1913
|
const firstUserMessage = await state.harness.getFirstUserMessageForThread({ threadId });
|
|
1601
1914
|
if (firstUserMessage) {
|
|
@@ -1617,7 +1930,7 @@ async function handleThreadsCommand(ctx) {
|
|
|
1617
1930
|
await state.harness.switchThread({ threadId: thread.id });
|
|
1618
1931
|
} catch (error) {
|
|
1619
1932
|
if (error instanceof ThreadLockError) {
|
|
1620
|
-
showThreadLockPrompt(ctx, thread.title || thread.id, error.ownerPid);
|
|
1933
|
+
showThreadLockPrompt(ctx, thread.title || thread.id, error.ownerPid, thread.id);
|
|
1621
1934
|
} else {
|
|
1622
1935
|
ctx.showError(`Failed to switch thread: ${error instanceof Error ? error.message : String(error)}`);
|
|
1623
1936
|
}
|
|
@@ -1632,6 +1945,25 @@ async function handleThreadsCommand(ctx) {
|
|
|
1632
1945
|
ctx.showInfo(`Switched to: ${thread.title || thread.id}`);
|
|
1633
1946
|
resolve2();
|
|
1634
1947
|
},
|
|
1948
|
+
onClone: async (thread) => {
|
|
1949
|
+
state.ui.hideOverlay();
|
|
1950
|
+
if (!await confirmClone(state, thread.title || thread.id)) {
|
|
1951
|
+
resolve2();
|
|
1952
|
+
return;
|
|
1953
|
+
}
|
|
1954
|
+
try {
|
|
1955
|
+
const customTitle = await askCloneName(state);
|
|
1956
|
+
const clonedThread = await state.harness.cloneThread({
|
|
1957
|
+
sourceThreadId: thread.id,
|
|
1958
|
+
...customTitle ? { title: customTitle } : {}
|
|
1959
|
+
});
|
|
1960
|
+
state.pendingNewThread = false;
|
|
1961
|
+
await resetUIAfterClone(ctx, clonedThread.title || clonedThread.id);
|
|
1962
|
+
} catch (error) {
|
|
1963
|
+
ctx.showError(`Failed to clone thread: ${error instanceof Error ? error.message : String(error)}`);
|
|
1964
|
+
}
|
|
1965
|
+
resolve2();
|
|
1966
|
+
},
|
|
1635
1967
|
onCancel: () => {
|
|
1636
1968
|
state.ui.hideOverlay();
|
|
1637
1969
|
resolve2();
|
|
@@ -1694,7 +2026,7 @@ async function handleThreadTagDirCommand(ctx) {
|
|
|
1694
2026
|
async function sandboxAddPath(ctx, rawPath) {
|
|
1695
2027
|
const harnessState = ctx.state.harness.getState();
|
|
1696
2028
|
const currentPaths = harnessState.sandboxAllowedPaths ?? [];
|
|
1697
|
-
const resolved =
|
|
2029
|
+
const resolved = path5__default.resolve(rawPath);
|
|
1698
2030
|
if (currentPaths.includes(resolved)) {
|
|
1699
2031
|
ctx.showInfo(`Path already allowed: ${resolved}`);
|
|
1700
2032
|
return;
|
|
@@ -1711,7 +2043,7 @@ async function sandboxAddPath(ctx, rawPath) {
|
|
|
1711
2043
|
ctx.showInfo(`Added to sandbox: ${resolved}`);
|
|
1712
2044
|
}
|
|
1713
2045
|
async function sandboxRemovePath(ctx, rawPath, currentPaths) {
|
|
1714
|
-
const resolved =
|
|
2046
|
+
const resolved = path5__default.resolve(rawPath);
|
|
1715
2047
|
const match = currentPaths.find((p) => p === resolved || p === rawPath);
|
|
1716
2048
|
if (!match) {
|
|
1717
2049
|
ctx.showError(`Path not in allowed list: ${resolved}`);
|
|
@@ -1728,7 +2060,7 @@ async function showSandboxAddPrompt(ctx) {
|
|
|
1728
2060
|
{
|
|
1729
2061
|
question: "Enter path to allow",
|
|
1730
2062
|
formatResult: (answer) => {
|
|
1731
|
-
return `Path: ${
|
|
2063
|
+
return `Path: ${path5__default.resolve(answer)}`;
|
|
1732
2064
|
},
|
|
1733
2065
|
onSubmit: async (answer) => {
|
|
1734
2066
|
ctx.state.activeInlineQuestion = void 0;
|
|
@@ -1979,149 +2311,6 @@ var ModelSelectorComponent = class extends Box {
|
|
|
1979
2311
|
return this.searchInput;
|
|
1980
2312
|
}
|
|
1981
2313
|
};
|
|
1982
|
-
|
|
1983
|
-
// src/tui/commands/models.ts
|
|
1984
|
-
async function showModelListForScope(ctx, scope, modeId, modeName) {
|
|
1985
|
-
const availableModels = await ctx.state.harness.listAvailableModels();
|
|
1986
|
-
if (availableModels.length === 0) {
|
|
1987
|
-
ctx.showInfo("No models available. Check your Mastra configuration.");
|
|
1988
|
-
return;
|
|
1989
|
-
}
|
|
1990
|
-
const currentModelId = ctx.state.harness.getCurrentModelId();
|
|
1991
|
-
const scopeLabel = scope === "global" ? `${modeName} \xB7 Global` : `${modeName} \xB7 Thread`;
|
|
1992
|
-
return new Promise((resolve2) => {
|
|
1993
|
-
const selector = new ModelSelectorComponent({
|
|
1994
|
-
tui: ctx.state.ui,
|
|
1995
|
-
models: availableModels,
|
|
1996
|
-
currentModelId,
|
|
1997
|
-
title: `Select model (${scopeLabel})`,
|
|
1998
|
-
onSelect: async (model) => {
|
|
1999
|
-
ctx.state.ui.hideOverlay();
|
|
2000
|
-
try {
|
|
2001
|
-
await ctx.state.harness.switchModel({ modelId: model.id, scope, modeId });
|
|
2002
|
-
if (scope === "global") {
|
|
2003
|
-
const settings = loadSettings();
|
|
2004
|
-
settings.models.activeModelPackId = null;
|
|
2005
|
-
settings.models.modeDefaults[modeId] = model.id;
|
|
2006
|
-
saveSettings(settings);
|
|
2007
|
-
}
|
|
2008
|
-
ctx.showInfo(`Model set for ${scopeLabel}: ${model.id}`);
|
|
2009
|
-
} catch (err) {
|
|
2010
|
-
ctx.showError(`Failed to switch model: ${err instanceof Error ? err.message : String(err)}`);
|
|
2011
|
-
}
|
|
2012
|
-
resolve2();
|
|
2013
|
-
},
|
|
2014
|
-
onCancel: () => {
|
|
2015
|
-
ctx.state.ui.hideOverlay();
|
|
2016
|
-
resolve2();
|
|
2017
|
-
}
|
|
2018
|
-
});
|
|
2019
|
-
ctx.state.ui.showOverlay(selector, {
|
|
2020
|
-
width: "80%",
|
|
2021
|
-
maxHeight: "60%",
|
|
2022
|
-
anchor: "center"
|
|
2023
|
-
});
|
|
2024
|
-
selector.focused = true;
|
|
2025
|
-
});
|
|
2026
|
-
}
|
|
2027
|
-
async function showModelScopeThenList(ctx, modeId, modeName) {
|
|
2028
|
-
const scopes = [
|
|
2029
|
-
{
|
|
2030
|
-
label: "Thread default",
|
|
2031
|
-
description: `Default for ${modeName} mode in this thread`,
|
|
2032
|
-
scope: "thread"
|
|
2033
|
-
},
|
|
2034
|
-
{
|
|
2035
|
-
label: "Global default",
|
|
2036
|
-
description: `Default for ${modeName} mode in all threads`,
|
|
2037
|
-
scope: "global"
|
|
2038
|
-
}
|
|
2039
|
-
];
|
|
2040
|
-
return new Promise((resolve2) => {
|
|
2041
|
-
const questionComponent = new AskQuestionInlineComponent(
|
|
2042
|
-
{
|
|
2043
|
-
question: `Select scope for ${modeName}`,
|
|
2044
|
-
options: scopes.map((s) => ({
|
|
2045
|
-
label: s.label,
|
|
2046
|
-
description: s.description
|
|
2047
|
-
})),
|
|
2048
|
-
formatResult: (answer) => `${modeName} \xB7 ${answer}`,
|
|
2049
|
-
onSubmit: async (answer) => {
|
|
2050
|
-
ctx.state.activeInlineQuestion = void 0;
|
|
2051
|
-
try {
|
|
2052
|
-
const selected = scopes.find((s) => s.label === answer);
|
|
2053
|
-
if (selected) {
|
|
2054
|
-
await showModelListForScope(ctx, selected.scope, modeId, modeName);
|
|
2055
|
-
}
|
|
2056
|
-
} catch (err) {
|
|
2057
|
-
ctx.showError(`Model selection failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
2058
|
-
}
|
|
2059
|
-
resolve2();
|
|
2060
|
-
},
|
|
2061
|
-
onCancel: () => {
|
|
2062
|
-
ctx.state.activeInlineQuestion = void 0;
|
|
2063
|
-
resolve2();
|
|
2064
|
-
}
|
|
2065
|
-
},
|
|
2066
|
-
ctx.state.ui
|
|
2067
|
-
);
|
|
2068
|
-
ctx.state.activeInlineQuestion = questionComponent;
|
|
2069
|
-
ctx.state.chatContainer.addChild(new Spacer(1));
|
|
2070
|
-
ctx.state.chatContainer.addChild(questionComponent);
|
|
2071
|
-
ctx.state.chatContainer.addChild(new Spacer(1));
|
|
2072
|
-
ctx.state.ui.requestRender();
|
|
2073
|
-
ctx.state.chatContainer.invalidate();
|
|
2074
|
-
});
|
|
2075
|
-
}
|
|
2076
|
-
async function handleModelsCommand(ctx) {
|
|
2077
|
-
const modes = ctx.state.harness.listModes();
|
|
2078
|
-
const currentMode = ctx.state.harness.getCurrentMode();
|
|
2079
|
-
const sortedModes = [...modes].sort((a, b) => {
|
|
2080
|
-
if (a.id === currentMode?.id) return -1;
|
|
2081
|
-
if (b.id === currentMode?.id) return 1;
|
|
2082
|
-
return 0;
|
|
2083
|
-
});
|
|
2084
|
-
const modeOptions = sortedModes.map((mode) => ({
|
|
2085
|
-
label: mode.name + (mode.id === currentMode?.id ? " (active)" : ""),
|
|
2086
|
-
modeId: mode.id,
|
|
2087
|
-
modeName: mode.name
|
|
2088
|
-
}));
|
|
2089
|
-
return new Promise((resolve2) => {
|
|
2090
|
-
const questionComponent = new AskQuestionInlineComponent(
|
|
2091
|
-
{
|
|
2092
|
-
question: "Select mode",
|
|
2093
|
-
options: modeOptions.map((m) => ({ label: m.label })),
|
|
2094
|
-
formatResult: (answer) => {
|
|
2095
|
-
const mode = modeOptions.find((m) => m.label === answer);
|
|
2096
|
-
return `Mode: ${mode?.modeName ?? answer}`;
|
|
2097
|
-
},
|
|
2098
|
-
onSubmit: async (answer) => {
|
|
2099
|
-
ctx.state.activeInlineQuestion = void 0;
|
|
2100
|
-
try {
|
|
2101
|
-
const selected = modeOptions.find((m) => m.label === answer);
|
|
2102
|
-
if (selected?.modeId && selected?.modeName) {
|
|
2103
|
-
await showModelScopeThenList(ctx, selected.modeId, selected.modeName);
|
|
2104
|
-
}
|
|
2105
|
-
} catch (err) {
|
|
2106
|
-
ctx.showError(`Model selection failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
2107
|
-
}
|
|
2108
|
-
resolve2();
|
|
2109
|
-
},
|
|
2110
|
-
onCancel: () => {
|
|
2111
|
-
ctx.state.activeInlineQuestion = void 0;
|
|
2112
|
-
resolve2();
|
|
2113
|
-
}
|
|
2114
|
-
},
|
|
2115
|
-
ctx.state.ui
|
|
2116
|
-
);
|
|
2117
|
-
ctx.state.activeInlineQuestion = questionComponent;
|
|
2118
|
-
ctx.state.chatContainer.addChild(new Spacer(1));
|
|
2119
|
-
ctx.state.chatContainer.addChild(questionComponent);
|
|
2120
|
-
ctx.state.chatContainer.addChild(new Spacer(1));
|
|
2121
|
-
ctx.state.ui.requestRender();
|
|
2122
|
-
ctx.state.chatContainer.invalidate();
|
|
2123
|
-
});
|
|
2124
|
-
}
|
|
2125
2314
|
var GRADIENT_WIDTH = 30;
|
|
2126
2315
|
var BASE_COLOR = [124, 58, 237];
|
|
2127
2316
|
function getMinBrightness() {
|
|
@@ -2617,14 +2806,14 @@ function updateStatusLine(state) {
|
|
|
2617
2806
|
}
|
|
2618
2807
|
|
|
2619
2808
|
// src/tui/commands/models-pack.ts
|
|
2620
|
-
async function selectModel(ctx, title, modeColor) {
|
|
2809
|
+
async function selectModel(ctx, title, modeColor, currentModelId) {
|
|
2621
2810
|
const availableModels = await ctx.state.harness.listAvailableModels();
|
|
2622
2811
|
if (availableModels.length === 0) return void 0;
|
|
2623
2812
|
return new Promise((resolve2) => {
|
|
2624
2813
|
const selector = new ModelSelectorComponent({
|
|
2625
2814
|
tui: ctx.state.ui,
|
|
2626
2815
|
models: availableModels,
|
|
2627
|
-
currentModelId
|
|
2816
|
+
currentModelId,
|
|
2628
2817
|
title,
|
|
2629
2818
|
titleColor: modeColor,
|
|
2630
2819
|
onSelect: (model) => {
|
|
@@ -2644,66 +2833,268 @@ async function selectModel(ctx, title, modeColor) {
|
|
|
2644
2833
|
selector.focused = true;
|
|
2645
2834
|
});
|
|
2646
2835
|
}
|
|
2647
|
-
async function
|
|
2836
|
+
async function askCustomPackName(ctx, defaultName) {
|
|
2837
|
+
return new Promise((resolve2) => {
|
|
2838
|
+
const question = new AskQuestionInlineComponent(
|
|
2839
|
+
{
|
|
2840
|
+
question: "Name this custom pack",
|
|
2841
|
+
formatResult: (answer) => `Custom pack: ${answer}`,
|
|
2842
|
+
onSubmit: (answer) => {
|
|
2843
|
+
ctx.state.activeInlineQuestion = void 0;
|
|
2844
|
+
const trimmed = answer.trim();
|
|
2845
|
+
resolve2(trimmed.length > 0 ? trimmed : null);
|
|
2846
|
+
},
|
|
2847
|
+
onCancel: () => {
|
|
2848
|
+
ctx.state.activeInlineQuestion = void 0;
|
|
2849
|
+
resolve2(null);
|
|
2850
|
+
}
|
|
2851
|
+
},
|
|
2852
|
+
ctx.state.ui
|
|
2853
|
+
);
|
|
2854
|
+
if (defaultName) {
|
|
2855
|
+
question.input?.setValue?.(defaultName);
|
|
2856
|
+
}
|
|
2857
|
+
ctx.state.activeInlineQuestion = question;
|
|
2858
|
+
ctx.state.chatContainer.addChild(new Spacer(1));
|
|
2859
|
+
ctx.state.chatContainer.addChild(question);
|
|
2860
|
+
ctx.state.chatContainer.addChild(new Spacer(1));
|
|
2861
|
+
ctx.state.ui.requestRender();
|
|
2862
|
+
ctx.state.chatContainer.invalidate();
|
|
2863
|
+
});
|
|
2864
|
+
}
|
|
2865
|
+
async function askCustomPackAction(ctx, pack) {
|
|
2866
|
+
const actions = [
|
|
2867
|
+
{ id: "activate", label: "Activate", description: "Use this pack as-is" },
|
|
2868
|
+
{ id: "edit", label: "Edit", description: "Update this pack" },
|
|
2869
|
+
{ id: "delete", label: "Delete", description: "Remove this custom pack" }
|
|
2870
|
+
];
|
|
2871
|
+
return new Promise((resolve2) => {
|
|
2872
|
+
const container = new Box(1, 1);
|
|
2873
|
+
container.addChild(new Text(theme.bold(theme.fg("accent", `Custom pack: ${pack.name}`)), 0, 0));
|
|
2874
|
+
container.addChild(new Spacer(1));
|
|
2875
|
+
const items = actions.map((action) => ({
|
|
2876
|
+
value: action.id,
|
|
2877
|
+
label: ` ${action.label} ${theme.fg("dim", action.description)}`
|
|
2878
|
+
}));
|
|
2879
|
+
const selectList = new SelectList(items, items.length, getSelectListTheme());
|
|
2880
|
+
const detailText = new Text("", 0, 0);
|
|
2881
|
+
const detailById = {
|
|
2882
|
+
activate: getPackDetail(pack),
|
|
2883
|
+
edit: theme.fg("dim", " Edit one setting at a time (Rename, plan, build, fast)."),
|
|
2884
|
+
delete: theme.fg("error", " Permanently removes this custom pack from settings.")
|
|
2885
|
+
};
|
|
2886
|
+
selectList.onSelectionChange = (item) => {
|
|
2887
|
+
detailText.setText(detailById[item.value] ?? "");
|
|
2888
|
+
ctx.state.ui.requestRender();
|
|
2889
|
+
};
|
|
2890
|
+
selectList.onSelect = (item) => {
|
|
2891
|
+
ctx.state.activeInlineQuestion = void 0;
|
|
2892
|
+
container.clear();
|
|
2893
|
+
container.addChild(
|
|
2894
|
+
new Text(theme.fg("text", `${theme.fg("success", "\u2713")} ${pack.name} \u2192 ${theme.bold(item.value)}`), 0, 0)
|
|
2895
|
+
);
|
|
2896
|
+
ctx.state.ui.requestRender();
|
|
2897
|
+
resolve2(item.value);
|
|
2898
|
+
};
|
|
2899
|
+
selectList.onCancel = () => {
|
|
2900
|
+
ctx.state.activeInlineQuestion = void 0;
|
|
2901
|
+
container.clear();
|
|
2902
|
+
container.addChild(new Text(theme.fg("dim", `${theme.fg("error", "\u2717")} ${pack.name} (cancelled)`), 0, 0));
|
|
2903
|
+
ctx.state.ui.requestRender();
|
|
2904
|
+
resolve2(null);
|
|
2905
|
+
};
|
|
2906
|
+
detailText.setText(detailById["activate"]);
|
|
2907
|
+
container.addChild(selectList);
|
|
2908
|
+
container.addChild(new Spacer(1));
|
|
2909
|
+
container.addChild(detailText);
|
|
2910
|
+
container.addChild(new Spacer(1));
|
|
2911
|
+
container.addChild(new Text(theme.fg("dim", "\u2191\u2193 navigate \xB7 Enter select \xB7 Esc cancel"), 0, 0));
|
|
2912
|
+
const inputShim = { handleInput: (data) => selectList.handleInput(data) };
|
|
2913
|
+
ctx.state.activeInlineQuestion = inputShim;
|
|
2914
|
+
ctx.state.chatContainer.addChild(container);
|
|
2915
|
+
ctx.state.ui.requestRender();
|
|
2916
|
+
ctx.state.chatContainer.invalidate();
|
|
2917
|
+
});
|
|
2918
|
+
}
|
|
2919
|
+
async function askCustomPackEditTarget(ctx, pack) {
|
|
2920
|
+
return new Promise((resolve2) => {
|
|
2921
|
+
const container = new Box(1, 1);
|
|
2922
|
+
container.addChild(new Text(theme.bold(theme.fg("accent", `Edit custom pack: ${pack.name}`)), 0, 0));
|
|
2923
|
+
container.addChild(new Spacer(1));
|
|
2924
|
+
const selectList = new SelectList(
|
|
2925
|
+
[
|
|
2926
|
+
{ value: "rename", label: ` Rename \u2192 ${theme.fg("text", pack.name)}` },
|
|
2927
|
+
{ value: "plan", label: ` ${chalk10.hex(mastra.blue)("plan")} \u2192 ${theme.fg("text", pack.models.plan)}` },
|
|
2928
|
+
{ value: "build", label: ` ${chalk10.hex(mastra.purple)("build")} \u2192 ${theme.fg("text", pack.models.build)}` },
|
|
2929
|
+
{ value: "fast", label: ` ${chalk10.hex(mastra.green)("fast")} \u2192 ${theme.fg("text", pack.models.fast)}` },
|
|
2930
|
+
{ value: "save", label: ` ${theme.fg("success", "Save")}` }
|
|
2931
|
+
],
|
|
2932
|
+
5,
|
|
2933
|
+
getSelectListTheme()
|
|
2934
|
+
);
|
|
2935
|
+
const cleanup = () => {
|
|
2936
|
+
if (ctx.state.chatContainer.children.includes(container)) {
|
|
2937
|
+
ctx.state.chatContainer.removeChild(container);
|
|
2938
|
+
}
|
|
2939
|
+
ctx.state.ui.requestRender();
|
|
2940
|
+
ctx.state.chatContainer.invalidate();
|
|
2941
|
+
};
|
|
2942
|
+
selectList.onSelect = (item) => {
|
|
2943
|
+
ctx.state.activeInlineQuestion = void 0;
|
|
2944
|
+
cleanup();
|
|
2945
|
+
resolve2(item.value);
|
|
2946
|
+
};
|
|
2947
|
+
selectList.onCancel = () => {
|
|
2948
|
+
ctx.state.activeInlineQuestion = void 0;
|
|
2949
|
+
cleanup();
|
|
2950
|
+
resolve2(null);
|
|
2951
|
+
};
|
|
2952
|
+
container.addChild(selectList);
|
|
2953
|
+
container.addChild(new Spacer(1));
|
|
2954
|
+
container.addChild(new Text(theme.fg("dim", "\u2191\u2193 navigate \xB7 Enter select \xB7 Esc cancel"), 0, 0));
|
|
2955
|
+
const inputShim = { handleInput: (data) => selectList.handleInput(data) };
|
|
2956
|
+
ctx.state.activeInlineQuestion = inputShim;
|
|
2957
|
+
ctx.state.chatContainer.addChild(container);
|
|
2958
|
+
ctx.state.ui.requestRender();
|
|
2959
|
+
ctx.state.chatContainer.invalidate();
|
|
2960
|
+
});
|
|
2961
|
+
}
|
|
2962
|
+
async function runCustomFlow(ctx, options) {
|
|
2648
2963
|
const modes = [
|
|
2649
2964
|
{ id: "plan", label: "plan", color: mastra.blue },
|
|
2650
2965
|
{ id: "build", label: "build", color: mastra.purple },
|
|
2651
2966
|
{ id: "fast", label: "fast", color: mastra.green }
|
|
2652
2967
|
];
|
|
2653
|
-
const
|
|
2968
|
+
const name = options?.skipNamePrompt ? options?.name : await askCustomPackName(ctx, void 0);
|
|
2969
|
+
if (!name) return null;
|
|
2970
|
+
const existing = options?.models ?? { build: "", plan: "", fast: "" };
|
|
2971
|
+
const models = {
|
|
2972
|
+
build: existing.build ?? "",
|
|
2973
|
+
plan: existing.plan ?? "",
|
|
2974
|
+
fast: existing.fast ?? ""
|
|
2975
|
+
};
|
|
2654
2976
|
for (const mode of modes) {
|
|
2655
|
-
const modelId = await selectModel(
|
|
2977
|
+
const modelId = await selectModel(
|
|
2978
|
+
ctx,
|
|
2979
|
+
`Select model for ${mode.label} mode`,
|
|
2980
|
+
mode.color,
|
|
2981
|
+
models[mode.id] || void 0
|
|
2982
|
+
);
|
|
2656
2983
|
if (!modelId) return null;
|
|
2657
2984
|
models[mode.id] = modelId;
|
|
2658
2985
|
}
|
|
2659
|
-
return {
|
|
2660
|
-
id:
|
|
2661
|
-
name
|
|
2662
|
-
description: "
|
|
2663
|
-
models
|
|
2664
|
-
};
|
|
2986
|
+
return {
|
|
2987
|
+
id: `custom:${name}`,
|
|
2988
|
+
name,
|
|
2989
|
+
description: "Saved custom pack",
|
|
2990
|
+
models
|
|
2991
|
+
};
|
|
2992
|
+
}
|
|
2993
|
+
async function runCustomPackEditFlow(ctx, pack) {
|
|
2994
|
+
let workingPack = { ...pack, models: { ...pack.models } };
|
|
2995
|
+
let previousPackId;
|
|
2996
|
+
while (true) {
|
|
2997
|
+
const editTarget = await askCustomPackEditTarget(ctx, workingPack);
|
|
2998
|
+
if (!editTarget) return null;
|
|
2999
|
+
if (editTarget === "save") return { pack: workingPack, previousPackId };
|
|
3000
|
+
if (editTarget === "rename") {
|
|
3001
|
+
const renamed = await askCustomPackName(ctx, workingPack.name);
|
|
3002
|
+
if (!renamed) continue;
|
|
3003
|
+
const renamedPack = {
|
|
3004
|
+
...workingPack,
|
|
3005
|
+
id: `custom:${renamed}`,
|
|
3006
|
+
name: renamed
|
|
3007
|
+
};
|
|
3008
|
+
if (renamedPack.id !== pack.id && !previousPackId) previousPackId = pack.id;
|
|
3009
|
+
workingPack = renamedPack;
|
|
3010
|
+
continue;
|
|
3011
|
+
}
|
|
3012
|
+
const modeColors = {
|
|
3013
|
+
plan: mastra.blue,
|
|
3014
|
+
build: mastra.purple,
|
|
3015
|
+
fast: mastra.green
|
|
3016
|
+
};
|
|
3017
|
+
const modelId = await selectModel(
|
|
3018
|
+
ctx,
|
|
3019
|
+
`Select model for ${editTarget} mode`,
|
|
3020
|
+
modeColors[editTarget],
|
|
3021
|
+
workingPack.models[editTarget]
|
|
3022
|
+
);
|
|
3023
|
+
if (!modelId) continue;
|
|
3024
|
+
workingPack = {
|
|
3025
|
+
...workingPack,
|
|
3026
|
+
models: {
|
|
3027
|
+
...workingPack.models,
|
|
3028
|
+
[editTarget]: modelId
|
|
3029
|
+
}
|
|
3030
|
+
};
|
|
3031
|
+
}
|
|
3032
|
+
}
|
|
3033
|
+
function upsertCustomPackInSettings(settings, pack, modeDefaults, previousPackId, setActive = true) {
|
|
3034
|
+
if (!pack.id.startsWith("custom:")) return;
|
|
3035
|
+
if (previousPackId && previousPackId.startsWith("custom:") && previousPackId !== pack.id) {
|
|
3036
|
+
removeCustomPackFromSettings(settings, previousPackId);
|
|
3037
|
+
}
|
|
3038
|
+
const customName = pack.id.slice("custom:".length);
|
|
3039
|
+
const entry = { name: customName, models: modeDefaults, createdAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
3040
|
+
const idx = settings.customModelPacks.findIndex((p) => p.name === customName);
|
|
3041
|
+
if (idx >= 0) {
|
|
3042
|
+
settings.customModelPacks[idx] = entry;
|
|
3043
|
+
} else {
|
|
3044
|
+
settings.customModelPacks.push(entry);
|
|
3045
|
+
}
|
|
3046
|
+
if (setActive) {
|
|
3047
|
+
settings.models.activeModelPackId = pack.id;
|
|
3048
|
+
settings.models.modeDefaults = modeDefaults;
|
|
3049
|
+
}
|
|
3050
|
+
}
|
|
3051
|
+
function removeCustomPackFromSettings(settings, packId) {
|
|
3052
|
+
if (!packId.startsWith("custom:")) return;
|
|
3053
|
+
const packName = packId.slice("custom:".length);
|
|
3054
|
+
const removedPack = settings.customModelPacks.find((p) => p.name === packName);
|
|
3055
|
+
settings.customModelPacks = settings.customModelPacks.filter((p) => p.name !== packName);
|
|
3056
|
+
const modeDefaultsMatchRemovedPack = !!removedPack && settings.models.modeDefaults.plan === removedPack.models.plan && settings.models.modeDefaults.build === removedPack.models.build && settings.models.modeDefaults.fast === removedPack.models.fast;
|
|
3057
|
+
if (settings.models.activeModelPackId === packId) {
|
|
3058
|
+
settings.models.activeModelPackId = null;
|
|
3059
|
+
settings.models.modeDefaults = {};
|
|
3060
|
+
} else if (modeDefaultsMatchRemovedPack) {
|
|
3061
|
+
settings.models.modeDefaults = {};
|
|
3062
|
+
}
|
|
3063
|
+
if (settings.onboarding.modePackId === packId) {
|
|
3064
|
+
settings.onboarding.modePackId = null;
|
|
3065
|
+
}
|
|
2665
3066
|
}
|
|
2666
|
-
function applyPack(ctx, pack) {
|
|
3067
|
+
async function applyPack(ctx, pack, previousPackId) {
|
|
2667
3068
|
const harness = ctx.state.harness;
|
|
2668
3069
|
const modes = harness.listModes();
|
|
2669
3070
|
for (const mode of modes) {
|
|
2670
3071
|
const modelId = pack.models[mode.id];
|
|
2671
3072
|
if (modelId) {
|
|
2672
3073
|
mode.defaultModelId = modelId;
|
|
2673
|
-
harness.setThreadSetting({ key: `modeModelId_${mode.id}`, value: modelId });
|
|
3074
|
+
await harness.setThreadSetting({ key: `modeModelId_${mode.id}`, value: modelId });
|
|
2674
3075
|
}
|
|
2675
3076
|
}
|
|
2676
3077
|
const currentModeId = harness.getCurrentModeId();
|
|
2677
3078
|
const currentModeModel = pack.models[currentModeId];
|
|
2678
3079
|
if (currentModeModel) {
|
|
2679
|
-
harness.switchModel({ modelId: currentModeModel });
|
|
3080
|
+
await harness.switchModel({ modelId: currentModeModel });
|
|
2680
3081
|
}
|
|
2681
3082
|
const subagentModeMap = { explore: "fast", plan: "plan", execute: "build" };
|
|
2682
3083
|
for (const [agentType, modeId] of Object.entries(subagentModeMap)) {
|
|
2683
3084
|
const saModelId = pack.models[modeId];
|
|
2684
3085
|
if (saModelId) {
|
|
2685
|
-
harness.setSubagentModelId({ modelId: saModelId, agentType });
|
|
3086
|
+
await harness.setSubagentModelId({ modelId: saModelId, agentType });
|
|
2686
3087
|
}
|
|
2687
3088
|
}
|
|
3089
|
+
await harness.setThreadSetting({ key: THREAD_ACTIVE_MODEL_PACK_ID_KEY, value: pack.id });
|
|
2688
3090
|
const s = loadSettings();
|
|
2689
3091
|
const modeDefaults = {};
|
|
2690
3092
|
for (const mode of modes) {
|
|
2691
3093
|
const modelId = pack.models[mode.id];
|
|
2692
3094
|
if (modelId) modeDefaults[mode.id] = modelId;
|
|
2693
3095
|
}
|
|
2694
|
-
if (pack.id
|
|
2695
|
-
|
|
2696
|
-
const entry = { name: "Setup", models: modeDefaults, createdAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
2697
|
-
if (idx >= 0) {
|
|
2698
|
-
s.customModelPacks[idx] = entry;
|
|
2699
|
-
} else {
|
|
2700
|
-
s.customModelPacks.push(entry);
|
|
2701
|
-
}
|
|
2702
|
-
s.models.activeModelPackId = "custom:Setup";
|
|
2703
|
-
s.models.modeDefaults = modeDefaults;
|
|
2704
|
-
} else if (pack.id.startsWith("custom:")) {
|
|
2705
|
-
s.models.activeModelPackId = pack.id;
|
|
2706
|
-
s.models.modeDefaults = modeDefaults;
|
|
3096
|
+
if (pack.id.startsWith("custom:")) {
|
|
3097
|
+
upsertCustomPackInSettings(s, pack, modeDefaults, previousPackId);
|
|
2707
3098
|
} else {
|
|
2708
3099
|
s.models.activeModelPackId = pack.id;
|
|
2709
3100
|
s.models.modeDefaults = {};
|
|
@@ -2719,7 +3110,7 @@ function applyPack(ctx, pack) {
|
|
|
2719
3110
|
}
|
|
2720
3111
|
function getPackDetail(pack) {
|
|
2721
3112
|
if (pack.id === "custom") {
|
|
2722
|
-
return theme.fg("dim", "
|
|
3113
|
+
return theme.fg("dim", " Create a named custom pack and pick a model for each mode.");
|
|
2723
3114
|
}
|
|
2724
3115
|
return [
|
|
2725
3116
|
` ${chalk10.hex(mastra.blue)("plan")} \u2192 ${theme.fg("text", pack.models.plan)}`,
|
|
@@ -2727,6 +3118,46 @@ function getPackDetail(pack) {
|
|
|
2727
3118
|
` ${chalk10.hex(mastra.green)("fast")} \u2192 ${theme.fg("text", pack.models.fast)}`
|
|
2728
3119
|
].join("\n");
|
|
2729
3120
|
}
|
|
3121
|
+
async function saveCustomPackEdits(ctx, pack, previousPackId) {
|
|
3122
|
+
const settings = loadSettings();
|
|
3123
|
+
const wasActive = previousPackId ? settings.models.activeModelPackId === previousPackId : settings.models.activeModelPackId === pack.id;
|
|
3124
|
+
const wasOnboarding = previousPackId ? settings.onboarding.modePackId === previousPackId : settings.onboarding.modePackId === pack.id;
|
|
3125
|
+
const modeDefaults = {
|
|
3126
|
+
plan: pack.models.plan,
|
|
3127
|
+
build: pack.models.build,
|
|
3128
|
+
fast: pack.models.fast
|
|
3129
|
+
};
|
|
3130
|
+
upsertCustomPackInSettings(settings, pack, modeDefaults, previousPackId, false);
|
|
3131
|
+
if (wasActive) {
|
|
3132
|
+
settings.models.activeModelPackId = pack.id;
|
|
3133
|
+
}
|
|
3134
|
+
if (wasOnboarding) {
|
|
3135
|
+
settings.onboarding.modePackId = pack.id;
|
|
3136
|
+
}
|
|
3137
|
+
saveSettings(settings);
|
|
3138
|
+
if (previousPackId && previousPackId !== pack.id) {
|
|
3139
|
+
const harness = ctx.state.harness;
|
|
3140
|
+
const threadId = harness.getCurrentThreadId();
|
|
3141
|
+
const thread = threadId ? (await harness.listThreads()).find((t) => t.id === threadId) : void 0;
|
|
3142
|
+
const threadPackId = thread?.metadata?.[THREAD_ACTIVE_MODEL_PACK_ID_KEY] ?? null;
|
|
3143
|
+
if (threadPackId === previousPackId) {
|
|
3144
|
+
await harness.setThreadSetting({ key: THREAD_ACTIVE_MODEL_PACK_ID_KEY, value: pack.id });
|
|
3145
|
+
}
|
|
3146
|
+
}
|
|
3147
|
+
}
|
|
3148
|
+
async function deleteCustomPack(ctx, pack) {
|
|
3149
|
+
if (!pack.id.startsWith("custom:")) return;
|
|
3150
|
+
const harness = ctx.state.harness;
|
|
3151
|
+
const threadId = harness.getCurrentThreadId();
|
|
3152
|
+
const thread = threadId ? (await harness.listThreads()).find((t) => t.id === threadId) : void 0;
|
|
3153
|
+
const threadPackId = thread?.metadata?.[THREAD_ACTIVE_MODEL_PACK_ID_KEY] ?? null;
|
|
3154
|
+
const settings = loadSettings();
|
|
3155
|
+
removeCustomPackFromSettings(settings, pack.id);
|
|
3156
|
+
saveSettings(settings);
|
|
3157
|
+
if (threadPackId === pack.id) {
|
|
3158
|
+
await harness.setThreadSetting({ key: THREAD_ACTIVE_MODEL_PACK_ID_KEY, value: null });
|
|
3159
|
+
}
|
|
3160
|
+
}
|
|
2730
3161
|
async function handleModelsPackCommand(ctx) {
|
|
2731
3162
|
const harness = ctx.state.harness;
|
|
2732
3163
|
const models = await harness.listAvailableModels();
|
|
@@ -2743,13 +3174,26 @@ async function handleModelsPackCommand(ctx) {
|
|
|
2743
3174
|
google: hasEnv("google") ? "apikey" : false,
|
|
2744
3175
|
deepseek: hasEnv("deepseek") ? "apikey" : false
|
|
2745
3176
|
};
|
|
3177
|
+
const seen = new Set(Object.keys(access));
|
|
3178
|
+
for (const m of models) {
|
|
3179
|
+
if (!seen.has(m.provider) && m.hasApiKey) {
|
|
3180
|
+
access[m.provider] = "apikey";
|
|
3181
|
+
seen.add(m.provider);
|
|
3182
|
+
}
|
|
3183
|
+
}
|
|
2746
3184
|
const settings = loadSettings();
|
|
2747
3185
|
const packs = getAvailableModePacks(access, settings.customModelPacks);
|
|
2748
3186
|
if (packs.length === 0) {
|
|
2749
|
-
ctx.showInfo("No model packs available.
|
|
3187
|
+
ctx.showInfo("No model packs available. Configure provider auth first.");
|
|
2750
3188
|
return;
|
|
2751
3189
|
}
|
|
2752
|
-
const
|
|
3190
|
+
const threadId = harness.getCurrentThreadId();
|
|
3191
|
+
const thread = threadId ? (await harness.listThreads()).find((t) => t.id === threadId) : void 0;
|
|
3192
|
+
const currentPackId = resolveThreadActiveModelPackId(
|
|
3193
|
+
settings,
|
|
3194
|
+
packs,
|
|
3195
|
+
thread?.metadata
|
|
3196
|
+
);
|
|
2753
3197
|
const items = packs.map((p) => ({
|
|
2754
3198
|
value: p.id,
|
|
2755
3199
|
label: ` ${p.name} ${theme.fg("dim", p.description)}${p.id === currentPackId ? theme.fg("dim", " (current)") : ""}`
|
|
@@ -2766,9 +3210,19 @@ async function handleModelsPackCommand(ctx) {
|
|
|
2766
3210
|
detailText.setText(getPackDetail(pack));
|
|
2767
3211
|
ctx.state.ui.requestRender();
|
|
2768
3212
|
};
|
|
3213
|
+
const collapseResult = (result) => {
|
|
3214
|
+
container.clear();
|
|
3215
|
+
if (result === "cancelled") {
|
|
3216
|
+
container.addChild(new Text(theme.fg("dim", `${theme.fg("error", "\u2717")} Model pack (cancelled)`), 0, 0));
|
|
3217
|
+
} else if (result) {
|
|
3218
|
+
container.addChild(new Text(theme.fg("text", `${theme.fg("success", "\u2713")} ${result}`), 0, 0));
|
|
3219
|
+
}
|
|
3220
|
+
ctx.state.ui.requestRender();
|
|
3221
|
+
};
|
|
2769
3222
|
selectList.onSelect = async (item) => {
|
|
2770
3223
|
ctx.state.activeInlineQuestion = void 0;
|
|
2771
|
-
|
|
3224
|
+
let pack = packs.find((p) => p.id === item.value);
|
|
3225
|
+
let previousPackId;
|
|
2772
3226
|
if (!pack) {
|
|
2773
3227
|
collapseResult("cancelled");
|
|
2774
3228
|
resolve2();
|
|
@@ -2776,19 +3230,44 @@ async function handleModelsPackCommand(ctx) {
|
|
|
2776
3230
|
}
|
|
2777
3231
|
if (pack.id === "custom") {
|
|
2778
3232
|
collapseResult(null);
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
3233
|
+
pack = await runCustomFlow(ctx);
|
|
3234
|
+
} else if (pack.id.startsWith("custom:")) {
|
|
3235
|
+
while (true) {
|
|
3236
|
+
const action = await askCustomPackAction(ctx, pack);
|
|
3237
|
+
if (action === null) {
|
|
3238
|
+
collapseResult("cancelled");
|
|
3239
|
+
resolve2();
|
|
3240
|
+
return;
|
|
3241
|
+
}
|
|
3242
|
+
if (action === "delete") {
|
|
3243
|
+
await deleteCustomPack(ctx, pack);
|
|
3244
|
+
collapseResult(`Deleted custom pack \u2192 ${theme.bold(pack.name)}`);
|
|
3245
|
+
ctx.showInfo(`Deleted custom pack: ${pack.name}`);
|
|
3246
|
+
resolve2();
|
|
3247
|
+
return;
|
|
3248
|
+
}
|
|
3249
|
+
if (action === "activate") {
|
|
3250
|
+
break;
|
|
3251
|
+
}
|
|
3252
|
+
const edited = await runCustomPackEditFlow(ctx, pack);
|
|
3253
|
+
if (!edited) {
|
|
3254
|
+
continue;
|
|
3255
|
+
}
|
|
3256
|
+
previousPackId = edited.previousPackId;
|
|
3257
|
+
pack = edited.pack;
|
|
3258
|
+
await saveCustomPackEdits(ctx, pack, previousPackId);
|
|
3259
|
+
previousPackId = void 0;
|
|
3260
|
+
ctx.showInfo(`Updated custom pack: ${pack.name}`);
|
|
2786
3261
|
}
|
|
2787
|
-
} else {
|
|
2788
|
-
applyPack(ctx, pack);
|
|
2789
|
-
collapseResult(`Model pack \u2192 ${theme.bold(pack.name)}`);
|
|
2790
|
-
ctx.showInfo(`Switched to ${pack.name} pack`);
|
|
2791
3262
|
}
|
|
3263
|
+
if (!pack) {
|
|
3264
|
+
collapseResult("cancelled");
|
|
3265
|
+
resolve2();
|
|
3266
|
+
return;
|
|
3267
|
+
}
|
|
3268
|
+
await applyPack(ctx, pack, previousPackId);
|
|
3269
|
+
collapseResult(`Model pack \u2192 ${theme.bold(pack.name)}`);
|
|
3270
|
+
ctx.showInfo(`Switched to ${pack.name} pack`);
|
|
2792
3271
|
resolve2();
|
|
2793
3272
|
};
|
|
2794
3273
|
selectList.onCancel = () => {
|
|
@@ -2810,14 +3289,6 @@ async function handleModelsPackCommand(ctx) {
|
|
|
2810
3289
|
updateDetail(packs[initialIdx].id);
|
|
2811
3290
|
const inputShim = { handleInput: (data) => selectList.handleInput(data) };
|
|
2812
3291
|
ctx.state.activeInlineQuestion = inputShim;
|
|
2813
|
-
const collapseResult = (result) => {
|
|
2814
|
-
container.clear();
|
|
2815
|
-
if (result === "cancelled") {
|
|
2816
|
-
container.addChild(new Text(theme.fg("dim", `${theme.fg("error", "\u2717")} Model pack (cancelled)`), 0, 0));
|
|
2817
|
-
} else if (result) {
|
|
2818
|
-
container.addChild(new Text(theme.fg("text", `${theme.fg("success", "\u2713")} ${result}`), 0, 0));
|
|
2819
|
-
}
|
|
2820
|
-
};
|
|
2821
3292
|
ctx.state.chatContainer.addChild(new Spacer(1));
|
|
2822
3293
|
ctx.state.chatContainer.addChild(container);
|
|
2823
3294
|
ctx.state.chatContainer.addChild(new Spacer(1));
|
|
@@ -2825,6 +3296,269 @@ async function handleModelsPackCommand(ctx) {
|
|
|
2825
3296
|
ctx.state.chatContainer.invalidate();
|
|
2826
3297
|
});
|
|
2827
3298
|
}
|
|
3299
|
+
function isValidUrl(value) {
|
|
3300
|
+
try {
|
|
3301
|
+
const parsed = new URL(value);
|
|
3302
|
+
return parsed.protocol === "http:" || parsed.protocol === "https:";
|
|
3303
|
+
} catch {
|
|
3304
|
+
return false;
|
|
3305
|
+
}
|
|
3306
|
+
}
|
|
3307
|
+
function normalizeProvider(input) {
|
|
3308
|
+
return {
|
|
3309
|
+
name: input.name.trim(),
|
|
3310
|
+
url: input.url.trim(),
|
|
3311
|
+
apiKey: input.apiKey?.trim() || void 0,
|
|
3312
|
+
models: [...new Set(input.models.map((model) => model.trim()).filter(Boolean))]
|
|
3313
|
+
};
|
|
3314
|
+
}
|
|
3315
|
+
function upsertCustomProviderInSettings(settings, provider, previousProviderId) {
|
|
3316
|
+
const next = normalizeProvider(provider);
|
|
3317
|
+
const nextProviderId = getCustomProviderId(next.name);
|
|
3318
|
+
const filteredProviders = settings.customProviders.filter((existing) => {
|
|
3319
|
+
const id = getCustomProviderId(existing.name);
|
|
3320
|
+
return id !== nextProviderId && (!previousProviderId || id !== previousProviderId);
|
|
3321
|
+
});
|
|
3322
|
+
settings.customProviders = [...filteredProviders, next];
|
|
3323
|
+
}
|
|
3324
|
+
function removeCustomProviderFromSettings(settings, providerId) {
|
|
3325
|
+
settings.customProviders = settings.customProviders.filter(
|
|
3326
|
+
(provider) => getCustomProviderId(provider.name) !== providerId
|
|
3327
|
+
);
|
|
3328
|
+
}
|
|
3329
|
+
function addModelToCustomProviderInSettings(settings, providerId, modelName) {
|
|
3330
|
+
const trimmed = modelName.trim();
|
|
3331
|
+
if (!trimmed) return false;
|
|
3332
|
+
const provider = settings.customProviders.find((entry) => getCustomProviderId(entry.name) === providerId);
|
|
3333
|
+
if (!provider) return false;
|
|
3334
|
+
provider.models = [.../* @__PURE__ */ new Set([...provider.models, trimmed])];
|
|
3335
|
+
return true;
|
|
3336
|
+
}
|
|
3337
|
+
function removeModelFromCustomProviderInSettings(settings, providerId, modelName) {
|
|
3338
|
+
const provider = settings.customProviders.find((entry) => getCustomProviderId(entry.name) === providerId);
|
|
3339
|
+
if (!provider) return false;
|
|
3340
|
+
const before = provider.models.length;
|
|
3341
|
+
provider.models = provider.models.filter((model) => model !== modelName);
|
|
3342
|
+
return provider.models.length < before;
|
|
3343
|
+
}
|
|
3344
|
+
function askText(ctx, question, defaultValue, allowEmptyInput = false) {
|
|
3345
|
+
return new Promise((resolve2) => {
|
|
3346
|
+
const component = new AskQuestionInlineComponent(
|
|
3347
|
+
{
|
|
3348
|
+
question,
|
|
3349
|
+
allowEmptyInput,
|
|
3350
|
+
onSubmit: (answer) => {
|
|
3351
|
+
ctx.state.activeInlineQuestion = void 0;
|
|
3352
|
+
const trimmed = answer.trim();
|
|
3353
|
+
resolve2(trimmed.length > 0 ? trimmed : null);
|
|
3354
|
+
},
|
|
3355
|
+
onCancel: () => {
|
|
3356
|
+
ctx.state.activeInlineQuestion = void 0;
|
|
3357
|
+
resolve2(null);
|
|
3358
|
+
}
|
|
3359
|
+
},
|
|
3360
|
+
ctx.state.ui
|
|
3361
|
+
);
|
|
3362
|
+
if (defaultValue) {
|
|
3363
|
+
component.input?.setValue?.(defaultValue);
|
|
3364
|
+
}
|
|
3365
|
+
ctx.state.activeInlineQuestion = component;
|
|
3366
|
+
ctx.state.chatContainer.addChild(new Spacer(1));
|
|
3367
|
+
ctx.state.chatContainer.addChild(component);
|
|
3368
|
+
ctx.state.chatContainer.addChild(new Spacer(1));
|
|
3369
|
+
ctx.state.ui.requestRender();
|
|
3370
|
+
ctx.state.chatContainer.invalidate();
|
|
3371
|
+
});
|
|
3372
|
+
}
|
|
3373
|
+
async function askOptionalText(ctx, question, defaultValue) {
|
|
3374
|
+
const answer = await askText(ctx, `${question} (leave blank to skip)`, defaultValue, true);
|
|
3375
|
+
return answer?.trim() || void 0;
|
|
3376
|
+
}
|
|
3377
|
+
function askSelect(ctx, question, options) {
|
|
3378
|
+
return new Promise((resolve2) => {
|
|
3379
|
+
const component = new AskQuestionInlineComponent(
|
|
3380
|
+
{
|
|
3381
|
+
question,
|
|
3382
|
+
options: options.map((option) => ({ label: option.label, description: option.description })),
|
|
3383
|
+
onSubmit: (answer) => {
|
|
3384
|
+
ctx.state.activeInlineQuestion = void 0;
|
|
3385
|
+
const selected = options.find((option) => option.label === answer);
|
|
3386
|
+
resolve2(selected?.value ?? null);
|
|
3387
|
+
},
|
|
3388
|
+
onCancel: () => {
|
|
3389
|
+
ctx.state.activeInlineQuestion = void 0;
|
|
3390
|
+
resolve2(null);
|
|
3391
|
+
}
|
|
3392
|
+
},
|
|
3393
|
+
ctx.state.ui
|
|
3394
|
+
);
|
|
3395
|
+
ctx.state.activeInlineQuestion = component;
|
|
3396
|
+
ctx.state.chatContainer.addChild(new Spacer(1));
|
|
3397
|
+
ctx.state.chatContainer.addChild(component);
|
|
3398
|
+
ctx.state.chatContainer.addChild(new Spacer(1));
|
|
3399
|
+
ctx.state.ui.requestRender();
|
|
3400
|
+
ctx.state.chatContainer.invalidate();
|
|
3401
|
+
});
|
|
3402
|
+
}
|
|
3403
|
+
async function createProviderFlow(ctx) {
|
|
3404
|
+
const settings = loadSettings();
|
|
3405
|
+
const name = await askText(ctx, "Custom provider name");
|
|
3406
|
+
if (!name) return;
|
|
3407
|
+
const providerId = getCustomProviderId(name);
|
|
3408
|
+
if (settings.customProviders.some((provider) => getCustomProviderId(provider.name) === providerId)) {
|
|
3409
|
+
ctx.showError(`Provider already exists: ${name}`);
|
|
3410
|
+
return;
|
|
3411
|
+
}
|
|
3412
|
+
const url = await askText(ctx, "Base URL (OpenAI-compatible endpoint)");
|
|
3413
|
+
if (!url) return;
|
|
3414
|
+
if (!isValidUrl(url)) {
|
|
3415
|
+
ctx.showError("Invalid URL. Use a full http(s) URL.");
|
|
3416
|
+
return;
|
|
3417
|
+
}
|
|
3418
|
+
const apiKey = await askOptionalText(ctx, "API key");
|
|
3419
|
+
upsertCustomProviderInSettings(settings, { name, url, apiKey, models: [] });
|
|
3420
|
+
saveSettings(settings);
|
|
3421
|
+
ctx.showInfo(`Added custom provider: ${name}`);
|
|
3422
|
+
await manageProviderFlow(ctx, providerId);
|
|
3423
|
+
}
|
|
3424
|
+
async function editProviderFlow(ctx, providerId) {
|
|
3425
|
+
const settings = loadSettings();
|
|
3426
|
+
const provider = settings.customProviders.find((entry) => getCustomProviderId(entry.name) === providerId);
|
|
3427
|
+
if (!provider) {
|
|
3428
|
+
ctx.showError("Provider not found.");
|
|
3429
|
+
return;
|
|
3430
|
+
}
|
|
3431
|
+
const name = await askText(ctx, "Provider name", provider.name);
|
|
3432
|
+
if (!name) return;
|
|
3433
|
+
const nextProviderId = getCustomProviderId(name);
|
|
3434
|
+
if (nextProviderId !== providerId && settings.customProviders.some((entry) => getCustomProviderId(entry.name) === nextProviderId)) {
|
|
3435
|
+
ctx.showError(`Provider already exists: ${name}`);
|
|
3436
|
+
return;
|
|
3437
|
+
}
|
|
3438
|
+
const url = await askText(ctx, "Base URL", provider.url);
|
|
3439
|
+
if (!url) return;
|
|
3440
|
+
if (!isValidUrl(url)) {
|
|
3441
|
+
ctx.showError("Invalid URL. Use a full http(s) URL.");
|
|
3442
|
+
return;
|
|
3443
|
+
}
|
|
3444
|
+
const apiKey = await askOptionalText(ctx, "API key", provider.apiKey);
|
|
3445
|
+
upsertCustomProviderInSettings(
|
|
3446
|
+
settings,
|
|
3447
|
+
{
|
|
3448
|
+
...provider,
|
|
3449
|
+
name,
|
|
3450
|
+
url,
|
|
3451
|
+
apiKey
|
|
3452
|
+
},
|
|
3453
|
+
providerId
|
|
3454
|
+
);
|
|
3455
|
+
saveSettings(settings);
|
|
3456
|
+
ctx.showInfo(`Updated custom provider: ${name}`);
|
|
3457
|
+
}
|
|
3458
|
+
async function addProviderModelFlow(ctx, providerId) {
|
|
3459
|
+
const settings = loadSettings();
|
|
3460
|
+
const provider = settings.customProviders.find((entry) => getCustomProviderId(entry.name) === providerId);
|
|
3461
|
+
if (!provider) {
|
|
3462
|
+
ctx.showError("Provider not found.");
|
|
3463
|
+
return;
|
|
3464
|
+
}
|
|
3465
|
+
const modelName = await askText(ctx, `Model ID for ${provider.name}`);
|
|
3466
|
+
if (!modelName) return;
|
|
3467
|
+
const added = addModelToCustomProviderInSettings(settings, providerId, modelName);
|
|
3468
|
+
if (!added) {
|
|
3469
|
+
ctx.showError("Unable to add model to provider.");
|
|
3470
|
+
return;
|
|
3471
|
+
}
|
|
3472
|
+
saveSettings(settings);
|
|
3473
|
+
ctx.showInfo(`Added model: ${toCustomProviderModelId(provider.name, modelName)}`);
|
|
3474
|
+
}
|
|
3475
|
+
async function removeProviderModelFlow(ctx, providerId) {
|
|
3476
|
+
const settings = loadSettings();
|
|
3477
|
+
const provider = settings.customProviders.find((entry) => getCustomProviderId(entry.name) === providerId);
|
|
3478
|
+
if (!provider) {
|
|
3479
|
+
ctx.showError("Provider not found.");
|
|
3480
|
+
return;
|
|
3481
|
+
}
|
|
3482
|
+
if (provider.models.length === 0) {
|
|
3483
|
+
ctx.showInfo(`No custom models configured for ${provider.name}.`);
|
|
3484
|
+
return;
|
|
3485
|
+
}
|
|
3486
|
+
const modelName = await askSelect(
|
|
3487
|
+
ctx,
|
|
3488
|
+
`Remove model from ${provider.name}`,
|
|
3489
|
+
provider.models.map((model) => ({
|
|
3490
|
+
label: model,
|
|
3491
|
+
value: model,
|
|
3492
|
+
description: toCustomProviderModelId(provider.name, model)
|
|
3493
|
+
}))
|
|
3494
|
+
);
|
|
3495
|
+
if (!modelName) return;
|
|
3496
|
+
const removed = removeModelFromCustomProviderInSettings(settings, providerId, modelName);
|
|
3497
|
+
if (!removed) {
|
|
3498
|
+
ctx.showError("Unable to remove model from provider.");
|
|
3499
|
+
return;
|
|
3500
|
+
}
|
|
3501
|
+
saveSettings(settings);
|
|
3502
|
+
ctx.showInfo(`Removed model: ${toCustomProviderModelId(provider.name, modelName)}`);
|
|
3503
|
+
}
|
|
3504
|
+
async function manageProviderFlow(ctx, providerId) {
|
|
3505
|
+
const settings = loadSettings();
|
|
3506
|
+
const provider = settings.customProviders.find((entry) => getCustomProviderId(entry.name) === providerId);
|
|
3507
|
+
if (!provider) {
|
|
3508
|
+
ctx.showError("Provider not found.");
|
|
3509
|
+
return;
|
|
3510
|
+
}
|
|
3511
|
+
const action = await askSelect(ctx, `Manage provider: ${provider.name}`, [
|
|
3512
|
+
{ label: "Add model", value: "add-model", description: "Attach a model ID to this provider" },
|
|
3513
|
+
{ label: "Remove model", value: "remove-model", description: "Remove a model ID from this provider" },
|
|
3514
|
+
{ label: "Edit provider", value: "edit-provider", description: "Rename, change URL, or update API key" },
|
|
3515
|
+
{ label: "Delete provider", value: "delete-provider", description: "Remove provider and all its model IDs" }
|
|
3516
|
+
]);
|
|
3517
|
+
switch (action) {
|
|
3518
|
+
case "add-model":
|
|
3519
|
+
await addProviderModelFlow(ctx, providerId);
|
|
3520
|
+
break;
|
|
3521
|
+
case "remove-model":
|
|
3522
|
+
await removeProviderModelFlow(ctx, providerId);
|
|
3523
|
+
break;
|
|
3524
|
+
case "edit-provider":
|
|
3525
|
+
await editProviderFlow(ctx, providerId);
|
|
3526
|
+
break;
|
|
3527
|
+
case "delete-provider": {
|
|
3528
|
+
const confirm = await askSelect(ctx, `Delete ${provider.name}?`, [
|
|
3529
|
+
{ label: "Delete", value: "delete", description: "This cannot be undone" }
|
|
3530
|
+
]);
|
|
3531
|
+
if (confirm !== "delete") return;
|
|
3532
|
+
const latest = loadSettings();
|
|
3533
|
+
removeCustomProviderFromSettings(latest, providerId);
|
|
3534
|
+
saveSettings(latest);
|
|
3535
|
+
ctx.showInfo(`Deleted custom provider: ${provider.name}`);
|
|
3536
|
+
break;
|
|
3537
|
+
}
|
|
3538
|
+
}
|
|
3539
|
+
}
|
|
3540
|
+
async function handleCustomProvidersCommand(ctx) {
|
|
3541
|
+
const settings = loadSettings();
|
|
3542
|
+
const providerOptions = settings.customProviders.map((provider) => {
|
|
3543
|
+
const providerId = getCustomProviderId(provider.name);
|
|
3544
|
+
const modelCount = provider.models.length;
|
|
3545
|
+
return {
|
|
3546
|
+
label: provider.name,
|
|
3547
|
+
value: providerId,
|
|
3548
|
+
description: `${provider.url} \xB7 ${modelCount} model${modelCount === 1 ? "" : "s"} \xB7 ${provider.apiKey ? "api key set" : "no api key"}`
|
|
3549
|
+
};
|
|
3550
|
+
});
|
|
3551
|
+
const action = await askSelect(ctx, "Custom providers", [
|
|
3552
|
+
{ label: "Add provider", value: "add-provider", description: "Create an OpenAI-compatible provider" },
|
|
3553
|
+
...providerOptions
|
|
3554
|
+
]);
|
|
3555
|
+
if (!action) return;
|
|
3556
|
+
if (action === "add-provider") {
|
|
3557
|
+
await createProviderFlow(ctx);
|
|
3558
|
+
return;
|
|
3559
|
+
}
|
|
3560
|
+
await manageProviderFlow(ctx, action);
|
|
3561
|
+
}
|
|
2828
3562
|
async function showSubagentModelListForScope(ctx, scope, agentType, agentTypeLabel) {
|
|
2829
3563
|
const availableModels = await ctx.state.harness.listAvailableModels();
|
|
2830
3564
|
if (availableModels.length === 0) {
|
|
@@ -3239,6 +3973,12 @@ function getShortModelName(modelId) {
|
|
|
3239
3973
|
}
|
|
3240
3974
|
|
|
3241
3975
|
// src/tui/commands/om.ts
|
|
3976
|
+
function persistOmModelOverride(modelId) {
|
|
3977
|
+
const settings = loadSettings();
|
|
3978
|
+
settings.models.activeOmPackId = "custom";
|
|
3979
|
+
settings.models.omModelOverride = modelId;
|
|
3980
|
+
saveSettings(settings);
|
|
3981
|
+
}
|
|
3242
3982
|
async function handleOMCommand(ctx) {
|
|
3243
3983
|
const availableModels = await ctx.state.harness.listAvailableModels();
|
|
3244
3984
|
const modelOptions = availableModels.map((m) => ({
|
|
@@ -3257,10 +3997,12 @@ async function handleOMCommand(ctx) {
|
|
|
3257
3997
|
{
|
|
3258
3998
|
onObserverModelChange: async (modelId) => {
|
|
3259
3999
|
await ctx.state.harness.switchObserverModel({ modelId });
|
|
4000
|
+
persistOmModelOverride(modelId);
|
|
3260
4001
|
ctx.showInfo(`Observer model \u2192 ${modelId}`);
|
|
3261
4002
|
},
|
|
3262
4003
|
onReflectorModelChange: async (modelId) => {
|
|
3263
4004
|
await ctx.state.harness.switchReflectorModel({ modelId });
|
|
4005
|
+
persistOmModelOverride(modelId);
|
|
3264
4006
|
ctx.showInfo(`Reflector model \u2192 ${modelId}`);
|
|
3265
4007
|
},
|
|
3266
4008
|
onObservationThresholdChange: (value) => {
|
|
@@ -3485,7 +4227,7 @@ var SettingsComponent = class extends Box {
|
|
|
3485
4227
|
{
|
|
3486
4228
|
id: "escapeAsCancel",
|
|
3487
4229
|
label: "Escape cancels",
|
|
3488
|
-
description: "Use Escape to cancel/clear (Ctrl+C always works).
|
|
4230
|
+
description: "Use Escape to cancel/clear (Ctrl+C always works).",
|
|
3489
4231
|
currentValue: config.escapeAsCancel ? "On" : "Off",
|
|
3490
4232
|
submenu: (_currentValue, done) => new SelectSubmenu(
|
|
3491
4233
|
[
|
|
@@ -3509,6 +4251,33 @@ var SettingsComponent = class extends Box {
|
|
|
3509
4251
|
() => done()
|
|
3510
4252
|
)
|
|
3511
4253
|
},
|
|
4254
|
+
{
|
|
4255
|
+
id: "quietMode",
|
|
4256
|
+
label: "Quiet mode",
|
|
4257
|
+
description: "Collapse subagent output to a single line after completion.",
|
|
4258
|
+
currentValue: config.quietMode ? "On" : "Off",
|
|
4259
|
+
submenu: (_currentValue, done) => new SelectSubmenu(
|
|
4260
|
+
[
|
|
4261
|
+
{
|
|
4262
|
+
value: "on",
|
|
4263
|
+
label: " On",
|
|
4264
|
+
description: "Auto-collapse subagent output when done"
|
|
4265
|
+
},
|
|
4266
|
+
{
|
|
4267
|
+
value: "off",
|
|
4268
|
+
label: " Off",
|
|
4269
|
+
description: "Keep subagent output visible when done"
|
|
4270
|
+
}
|
|
4271
|
+
],
|
|
4272
|
+
config.quietMode ? "on" : "off",
|
|
4273
|
+
(value) => {
|
|
4274
|
+
config.quietMode = value === "on";
|
|
4275
|
+
callbacks.onQuietModeChange(config.quietMode);
|
|
4276
|
+
done(config.quietMode ? "On" : "Off");
|
|
4277
|
+
},
|
|
4278
|
+
() => done()
|
|
4279
|
+
)
|
|
4280
|
+
},
|
|
3512
4281
|
{
|
|
3513
4282
|
id: "storageBackend",
|
|
3514
4283
|
label: "Storage backend",
|
|
@@ -3557,6 +4326,7 @@ async function handleSettingsCommand(ctx) {
|
|
|
3557
4326
|
thinkingLevel: state?.thinkingLevel ?? "off",
|
|
3558
4327
|
currentModelId: ctx.state.harness.getCurrentModelId() ?? "",
|
|
3559
4328
|
escapeAsCancel: ctx.state.editor.escapeEnabled,
|
|
4329
|
+
quietMode: globalSettings.preferences.quietMode,
|
|
3560
4330
|
storageBackend: globalSettings.storage.backend,
|
|
3561
4331
|
pgConnectionString: globalSettings.storage.pg?.connectionString ?? "",
|
|
3562
4332
|
libsqlUrl: globalSettings.storage.libsql?.url ?? ""
|
|
@@ -3578,6 +4348,12 @@ async function handleSettingsCommand(ctx) {
|
|
|
3578
4348
|
await ctx.state.harness.setState({ escapeAsCancel: enabled });
|
|
3579
4349
|
await ctx.state.harness.setThreadSetting({ key: "escapeAsCancel", value: enabled });
|
|
3580
4350
|
},
|
|
4351
|
+
onQuietModeChange: (enabled) => {
|
|
4352
|
+
const current = loadSettings();
|
|
4353
|
+
current.preferences.quietMode = enabled;
|
|
4354
|
+
saveSettings(current);
|
|
4355
|
+
ctx.state.quietMode = enabled;
|
|
4356
|
+
},
|
|
3581
4357
|
onStorageBackendChange: (backend, connectionUrl) => {
|
|
3582
4358
|
const current = loadSettings();
|
|
3583
4359
|
current.storage.backend = backend;
|
|
@@ -3887,6 +4663,93 @@ Pay special attention to: ${focusArea}
|
|
|
3887
4663
|
});
|
|
3888
4664
|
}
|
|
3889
4665
|
|
|
4666
|
+
// src/tui/commands/report-issue.ts
|
|
4667
|
+
var MASTRA_REPO = "mastra-ai/mastra";
|
|
4668
|
+
var MASTRA_LABEL = "mastracode";
|
|
4669
|
+
async function handleReportIssueCommand(ctx, args) {
|
|
4670
|
+
if (!ctx.state.harness.hasModelSelected()) {
|
|
4671
|
+
ctx.showInfo("No model selected. Use /models to select a model, or /login to authenticate.");
|
|
4672
|
+
return;
|
|
4673
|
+
}
|
|
4674
|
+
if (ctx.state.pendingNewThread) {
|
|
4675
|
+
await ctx.state.harness.createThread();
|
|
4676
|
+
ctx.state.pendingNewThread = false;
|
|
4677
|
+
}
|
|
4678
|
+
const extraContext = args.join(" ").trim();
|
|
4679
|
+
const prompt = `The user wants to report a GitHub issue on ${MASTRA_REPO}. Help them through this process.
|
|
4680
|
+
|
|
4681
|
+
` + (extraContext ? `The user provided this initial context: "${extraContext}"
|
|
4682
|
+
|
|
4683
|
+
` : "") + `## Step 1: Understand the problem
|
|
4684
|
+
|
|
4685
|
+
Ask the user to describe the issue in their own words. Ask follow-up questions to gather:
|
|
4686
|
+
- What happened / what's wrong
|
|
4687
|
+
- What they expected to happen
|
|
4688
|
+
- Steps to reproduce (if applicable)
|
|
4689
|
+
|
|
4690
|
+
Also gather environment info by running:
|
|
4691
|
+
\`\`\`
|
|
4692
|
+
mastracode --version 2>/dev/null || echo "unknown"
|
|
4693
|
+
node --version
|
|
4694
|
+
uname -s
|
|
4695
|
+
\`\`\`
|
|
4696
|
+
|
|
4697
|
+
Use the conversation history for additional context about what the user was working on when they hit this issue.
|
|
4698
|
+
|
|
4699
|
+
## Step 2: Check for duplicates
|
|
4700
|
+
|
|
4701
|
+
Once you understand the problem, search for similar existing issues:
|
|
4702
|
+
\`\`\`
|
|
4703
|
+
gh issue list --repo ${MASTRA_REPO} --label ${MASTRA_LABEL} --state open --limit 50 --json number,title,body
|
|
4704
|
+
\`\`\`
|
|
4705
|
+
|
|
4706
|
+
Also search more broadly:
|
|
4707
|
+
\`\`\`
|
|
4708
|
+
gh search issues --repo ${MASTRA_REPO} --state open "<relevant keywords>" --limit 20 --json number,title,body,labels
|
|
4709
|
+
\`\`\`
|
|
4710
|
+
|
|
4711
|
+
If you find similar issue(s):
|
|
4712
|
+
- Present them with their number, title, and a brief summary
|
|
4713
|
+
- Ask the user whether they'd like to add a comment on an existing issue instead of opening a new one
|
|
4714
|
+
- If they choose to comment, draft the comment, show it to the user for approval, then run:
|
|
4715
|
+
\`\`\`
|
|
4716
|
+
gh issue comment <number> --repo ${MASTRA_REPO} --body "<comment>"
|
|
4717
|
+
\`\`\`
|
|
4718
|
+
Then stop here.
|
|
4719
|
+
|
|
4720
|
+
## Step 3: Draft the issue
|
|
4721
|
+
|
|
4722
|
+
Based on what you've gathered, write a clear, well-structured issue with:
|
|
4723
|
+
- A concise, descriptive title
|
|
4724
|
+
- A body covering: description, expected behavior, steps to reproduce, and environment info
|
|
4725
|
+
|
|
4726
|
+
**Show the full title and body to the user and ask for their approval before creating it.** Let them suggest edits.
|
|
4727
|
+
|
|
4728
|
+
## Step 4: Create the issue
|
|
4729
|
+
|
|
4730
|
+
Only after the user approves, create the issue:
|
|
4731
|
+
\`\`\`
|
|
4732
|
+
gh issue create --repo ${MASTRA_REPO} --label ${MASTRA_LABEL} --title "<title>" --body "<body>"
|
|
4733
|
+
\`\`\`
|
|
4734
|
+
|
|
4735
|
+
Report the created issue URL back to the user.`;
|
|
4736
|
+
ctx.addUserMessage({
|
|
4737
|
+
id: `user-${Date.now()}`,
|
|
4738
|
+
role: "user",
|
|
4739
|
+
content: [
|
|
4740
|
+
{
|
|
4741
|
+
type: "text",
|
|
4742
|
+
text: extraContext ? `/report-issue ${extraContext}` : "/report-issue"
|
|
4743
|
+
}
|
|
4744
|
+
],
|
|
4745
|
+
createdAt: /* @__PURE__ */ new Date()
|
|
4746
|
+
});
|
|
4747
|
+
ctx.state.ui.requestRender();
|
|
4748
|
+
ctx.state.harness.sendMessage({ content: prompt }).catch((error) => {
|
|
4749
|
+
ctx.showError(error instanceof Error ? error.message : "Report issue command failed");
|
|
4750
|
+
});
|
|
4751
|
+
}
|
|
4752
|
+
|
|
3890
4753
|
// src/tui/commands/setup.ts
|
|
3891
4754
|
async function handleSetupCommand(ctx) {
|
|
3892
4755
|
await ctx.showOnboarding();
|
|
@@ -4310,6 +5173,9 @@ async function dispatchSlashCommand(input, state, buildCtx) {
|
|
|
4310
5173
|
case "new":
|
|
4311
5174
|
handleNewCommand(buildCtx());
|
|
4312
5175
|
return true;
|
|
5176
|
+
case "clone":
|
|
5177
|
+
await handleCloneCommand(buildCtx());
|
|
5178
|
+
return true;
|
|
4313
5179
|
case "threads":
|
|
4314
5180
|
await handleThreadsCommand(buildCtx());
|
|
4315
5181
|
return true;
|
|
@@ -4326,11 +5192,11 @@ async function dispatchSlashCommand(input, state, buildCtx) {
|
|
|
4326
5192
|
await handleModeCommand(buildCtx(), args);
|
|
4327
5193
|
return true;
|
|
4328
5194
|
case "models":
|
|
4329
|
-
await handleModelsCommand(buildCtx());
|
|
4330
|
-
return true;
|
|
4331
|
-
case "models:pack":
|
|
4332
5195
|
await handleModelsPackCommand(buildCtx());
|
|
4333
5196
|
return true;
|
|
5197
|
+
case "custom-providers":
|
|
5198
|
+
await handleCustomProvidersCommand(buildCtx());
|
|
5199
|
+
return true;
|
|
4334
5200
|
case "subagents":
|
|
4335
5201
|
await handleSubagentsCommand(buildCtx());
|
|
4336
5202
|
return true;
|
|
@@ -4382,6 +5248,9 @@ async function dispatchSlashCommand(input, state, buildCtx) {
|
|
|
4382
5248
|
case "review":
|
|
4383
5249
|
await handleReviewCommand(buildCtx(), args);
|
|
4384
5250
|
return true;
|
|
5251
|
+
case "report-issue":
|
|
5252
|
+
await handleReportIssueCommand(buildCtx(), args);
|
|
5253
|
+
return true;
|
|
4385
5254
|
case "setup":
|
|
4386
5255
|
await handleSetupCommand(buildCtx());
|
|
4387
5256
|
return true;
|
|
@@ -4490,13 +5359,13 @@ function handleAgentError(ctx) {
|
|
|
4490
5359
|
}
|
|
4491
5360
|
var _compId = 0;
|
|
4492
5361
|
function asmDebugLog(...args) {
|
|
4493
|
-
if (process.env.
|
|
5362
|
+
if (!["true", "1"].includes(process.env.MASTRA_TUI_DEBUG)) {
|
|
4494
5363
|
return;
|
|
4495
5364
|
}
|
|
4496
5365
|
const line = `[ASM ${(/* @__PURE__ */ new Date()).toISOString()}] ${args.map((a) => typeof a === "string" ? a : JSON.stringify(a)).join(" ")}
|
|
4497
5366
|
`;
|
|
4498
5367
|
try {
|
|
4499
|
-
fs2.appendFileSync(
|
|
5368
|
+
fs2.appendFileSync(path5__default.join(process.cwd(), "tui-debug.log"), line);
|
|
4500
5369
|
} catch {
|
|
4501
5370
|
}
|
|
4502
5371
|
}
|
|
@@ -4918,10 +5787,10 @@ var ToolValidationErrorComponent = class extends Container {
|
|
|
4918
5787
|
if (toolName === "ask_user" && errors.some((e) => e.field === "question")) {
|
|
4919
5788
|
suggestions.push('Make sure to provide a "question" parameter with your question text');
|
|
4920
5789
|
}
|
|
4921
|
-
if (toolName ===
|
|
5790
|
+
if (toolName === MC_TOOLS.EXECUTE_COMMAND && errors.some((e) => e.field === "command")) {
|
|
4922
5791
|
suggestions.push('Provide a "command" parameter with the command to execute');
|
|
4923
5792
|
}
|
|
4924
|
-
if (toolName ===
|
|
5793
|
+
if (toolName === MC_TOOLS.VIEW && errors.some((e) => e.field === "path")) {
|
|
4925
5794
|
suggestions.push('Provide a "path" parameter with the file or directory path');
|
|
4926
5795
|
}
|
|
4927
5796
|
return suggestions;
|
|
@@ -4929,12 +5798,12 @@ var ToolValidationErrorComponent = class extends Container {
|
|
|
4929
5798
|
};
|
|
4930
5799
|
|
|
4931
5800
|
// src/tui/components/tool-execution-enhanced.ts
|
|
4932
|
-
function shortenPath(
|
|
5801
|
+
function shortenPath(path6) {
|
|
4933
5802
|
const home = os.homedir();
|
|
4934
|
-
if (
|
|
4935
|
-
return `~${
|
|
5803
|
+
if (path6.startsWith(home)) {
|
|
5804
|
+
return `~${path6.slice(home.length)}`;
|
|
4936
5805
|
}
|
|
4937
|
-
return
|
|
5806
|
+
return path6;
|
|
4938
5807
|
}
|
|
4939
5808
|
function resolveAbsolutePath(filePath) {
|
|
4940
5809
|
if (filePath.startsWith("/")) return filePath;
|
|
@@ -5018,7 +5887,7 @@ var ToolExecutionComponentEnhanced = class extends Container {
|
|
|
5018
5887
|
* Only for execute_command tool - shows live output while command runs.
|
|
5019
5888
|
*/
|
|
5020
5889
|
appendStreamingOutput(output) {
|
|
5021
|
-
if (this.toolName !==
|
|
5890
|
+
if (this.toolName !== MC_TOOLS.EXECUTE_COMMAND && this.toolName !== MC_TOOLS.GET_PROCESS_OUTPUT && this.toolName !== MC_TOOLS.KILL_PROCESS) {
|
|
5022
5891
|
return;
|
|
5023
5892
|
}
|
|
5024
5893
|
this.streamingOutput += output;
|
|
@@ -5042,12 +5911,13 @@ var ToolExecutionComponentEnhanced = class extends Container {
|
|
|
5042
5911
|
this.updateBgColor();
|
|
5043
5912
|
}
|
|
5044
5913
|
updateBgColor() {
|
|
5045
|
-
const isShellCommand = this.toolName ===
|
|
5046
|
-
const isViewCommand = this.toolName ===
|
|
5047
|
-
const isEditCommand = this.toolName ===
|
|
5048
|
-
const isWriteCommand = this.toolName ===
|
|
5914
|
+
const isShellCommand = this.toolName === MC_TOOLS.EXECUTE_COMMAND;
|
|
5915
|
+
const isViewCommand = this.toolName === MC_TOOLS.VIEW;
|
|
5916
|
+
const isEditCommand = this.toolName === MC_TOOLS.STRING_REPLACE_LSP;
|
|
5917
|
+
const isWriteCommand = this.toolName === MC_TOOLS.WRITE_FILE;
|
|
5918
|
+
const isProcessCommand = this.toolName === MC_TOOLS.GET_PROCESS_OUTPUT || this.toolName === MC_TOOLS.KILL_PROCESS;
|
|
5049
5919
|
const isTaskWrite = this.toolName === "task_write";
|
|
5050
|
-
if (isShellCommand || isViewCommand || isEditCommand || isWriteCommand || isTaskWrite) {
|
|
5920
|
+
if (isShellCommand || isViewCommand || isEditCommand || isWriteCommand || isProcessCommand || isTaskWrite) {
|
|
5051
5921
|
this.contentBox.setBgFn((text) => text);
|
|
5052
5922
|
return;
|
|
5053
5923
|
}
|
|
@@ -5066,26 +5936,25 @@ var ToolExecutionComponentEnhanced = class extends Container {
|
|
|
5066
5936
|
this.contentBox.clear();
|
|
5067
5937
|
this.collapsible = void 0;
|
|
5068
5938
|
switch (this.toolName) {
|
|
5069
|
-
case
|
|
5070
|
-
case "mastra_workspace_read_file":
|
|
5939
|
+
case MC_TOOLS.VIEW:
|
|
5071
5940
|
this.renderViewToolEnhanced();
|
|
5072
5941
|
break;
|
|
5073
|
-
case
|
|
5074
|
-
case "mastra_workspace_execute_command":
|
|
5942
|
+
case MC_TOOLS.EXECUTE_COMMAND:
|
|
5075
5943
|
this.renderBashToolEnhanced();
|
|
5076
5944
|
break;
|
|
5077
|
-
case
|
|
5078
|
-
case "mastra_workspace_edit_file":
|
|
5945
|
+
case MC_TOOLS.STRING_REPLACE_LSP:
|
|
5079
5946
|
this.renderEditToolEnhanced();
|
|
5080
5947
|
break;
|
|
5081
|
-
case
|
|
5082
|
-
case "mastra_workspace_write_file":
|
|
5948
|
+
case MC_TOOLS.WRITE_FILE:
|
|
5083
5949
|
this.renderWriteToolEnhanced();
|
|
5084
5950
|
break;
|
|
5085
|
-
case
|
|
5086
|
-
case "mastra_workspace_list_files":
|
|
5951
|
+
case MC_TOOLS.FIND_FILES:
|
|
5087
5952
|
this.renderListFilesEnhanced();
|
|
5088
5953
|
break;
|
|
5954
|
+
case MC_TOOLS.GET_PROCESS_OUTPUT:
|
|
5955
|
+
case MC_TOOLS.KILL_PROCESS:
|
|
5956
|
+
this.renderProcessToolEnhanced();
|
|
5957
|
+
break;
|
|
5089
5958
|
case "task_write":
|
|
5090
5959
|
this.renderTaskWriteEnhanced();
|
|
5091
5960
|
break;
|
|
@@ -5099,10 +5968,10 @@ var ToolExecutionComponentEnhanced = class extends Container {
|
|
|
5099
5968
|
const viewRange = argsObj?.view_range;
|
|
5100
5969
|
const startLine = viewRange?.[0] ?? argsObj?.offset ?? 1;
|
|
5101
5970
|
if (!this.result || this.isPartial) {
|
|
5102
|
-
const
|
|
5971
|
+
const path7 = argsObj?.path ? shortenPath(String(argsObj.path)) : "...";
|
|
5103
5972
|
const rangeDisplay2 = viewRange ? theme.fg("muted", `:${viewRange[0]},${viewRange[1]}`) : "";
|
|
5104
5973
|
const status2 = this.getStatusIndicator();
|
|
5105
|
-
const pathDisplay2 = fullPath ? fileLink(theme.fg("accent",
|
|
5974
|
+
const pathDisplay2 = fullPath ? fileLink(theme.fg("accent", path7), fullPath, startLine) : theme.fg("accent", path7);
|
|
5106
5975
|
const headerText = `${theme.bold(theme.fg("toolTitle", "view"))} ${pathDisplay2}${rangeDisplay2}${status2}`;
|
|
5107
5976
|
this.contentBox.addChild(new Text(headerText, 0, 0));
|
|
5108
5977
|
return;
|
|
@@ -5113,11 +5982,11 @@ var ToolExecutionComponentEnhanced = class extends Container {
|
|
|
5113
5982
|
const termWidth = process.stdout.columns || 80;
|
|
5114
5983
|
const fixedParts = "\u2514\u2500\u2500 view " + (rangeDisplay ? `:XXX,XXX` : "") + " \u2713";
|
|
5115
5984
|
const availableForPath = termWidth - fixedParts.length - 6;
|
|
5116
|
-
let
|
|
5117
|
-
if (
|
|
5118
|
-
|
|
5985
|
+
let path6 = argsObj?.path ? shortenPath(String(argsObj.path)) : "...";
|
|
5986
|
+
if (path6.length > availableForPath && availableForPath > 10) {
|
|
5987
|
+
path6 = "\u2026" + path6.slice(-(availableForPath - 1));
|
|
5119
5988
|
}
|
|
5120
|
-
const pathDisplay = fullPath ? fileLink(theme.fg("accent",
|
|
5989
|
+
const pathDisplay = fullPath ? fileLink(theme.fg("accent", path6), fullPath, startLine) : theme.fg("accent", path6);
|
|
5121
5990
|
const footerText = `${theme.bold(theme.fg("toolTitle", "view"))} ${pathDisplay}${rangeDisplay}${status}`;
|
|
5122
5991
|
this.contentBox.addChild(new Text("", 0, 0));
|
|
5123
5992
|
this.contentBox.addChild(new Text(border("\u250C\u2500\u2500"), 0, 0));
|
|
@@ -5222,9 +6091,50 @@ var ToolExecutionComponentEnhanced = class extends Container {
|
|
|
5222
6091
|
renderBorderedShell(status2, prepareOutputLines(output2));
|
|
5223
6092
|
return;
|
|
5224
6093
|
}
|
|
5225
|
-
const status = theme.fg("success", " \u2713");
|
|
6094
|
+
const status = theme.fg("success", " \u2713");
|
|
6095
|
+
const output = this.streamingOutput.trim() || this.getFormattedOutput();
|
|
6096
|
+
renderBorderedShell(status, prepareOutputLines(output));
|
|
6097
|
+
}
|
|
6098
|
+
renderProcessToolEnhanced() {
|
|
6099
|
+
const argsObj = this.args;
|
|
6100
|
+
const pid = argsObj?.pid ? Number(argsObj.pid) : 0;
|
|
6101
|
+
const isKill = this.toolName === MC_TOOLS.KILL_PROCESS;
|
|
6102
|
+
const isWait = !isKill && argsObj?.wait === true;
|
|
6103
|
+
const timeSuffix = this.isPartial ? "" : this.getDurationSuffix();
|
|
6104
|
+
const label = isKill ? "kill" : isWait ? "wait" : "output";
|
|
6105
|
+
const renderBorderedProcess = (status2, outputLines) => {
|
|
6106
|
+
const border = (char) => theme.bold(theme.fg("accent", char));
|
|
6107
|
+
const footerText = `${theme.fg("toolTitle", label)} ${theme.fg("accent", `PID ${pid}`)}${timeSuffix}${status2}`;
|
|
6108
|
+
this.contentBox.addChild(new Text(border("\u250C\u2500\u2500"), 0, 0));
|
|
6109
|
+
const termWidth = process.stdout.columns || 80;
|
|
6110
|
+
const maxLineWidth = termWidth - 6;
|
|
6111
|
+
const borderedLines = outputLines.map((line) => {
|
|
6112
|
+
const truncated = truncateAnsi(line, maxLineWidth);
|
|
6113
|
+
return border("\u2502") + " " + truncated;
|
|
6114
|
+
});
|
|
6115
|
+
const displayOutput = borderedLines.join("\n");
|
|
6116
|
+
if (displayOutput.trim()) {
|
|
6117
|
+
this.contentBox.addChild(new Text(displayOutput, 0, 0));
|
|
6118
|
+
}
|
|
6119
|
+
this.contentBox.addChild(new Text(`${border("\u2514\u2500\u2500")} ${footerText}`, 0, 0));
|
|
6120
|
+
};
|
|
6121
|
+
const prepareOutputLines = (output2) => {
|
|
6122
|
+
let lines = output2.split("\n");
|
|
6123
|
+
while (lines.length > 0 && lines[0] === "") lines.shift();
|
|
6124
|
+
while (lines.length > 0 && lines[lines.length - 1] === "") lines.pop();
|
|
6125
|
+
return lines;
|
|
6126
|
+
};
|
|
6127
|
+
if (!this.result || this.isPartial) {
|
|
6128
|
+
const status2 = this.getStatusIndicator();
|
|
6129
|
+
let lines = this.streamingOutput ? this.streamingOutput.split("\n") : [];
|
|
6130
|
+
while (lines.length > 0 && lines[0] === "") lines.shift();
|
|
6131
|
+
while (lines.length > 0 && lines[lines.length - 1] === "") lines.pop();
|
|
6132
|
+
renderBorderedProcess(status2, lines);
|
|
6133
|
+
return;
|
|
6134
|
+
}
|
|
6135
|
+
const status = this.result.isError ? theme.fg("error", " \u2717") : theme.fg("success", " \u2713");
|
|
5226
6136
|
const output = this.streamingOutput.trim() || this.getFormattedOutput();
|
|
5227
|
-
|
|
6137
|
+
renderBorderedProcess(status, prepareOutputLines(output));
|
|
5228
6138
|
}
|
|
5229
6139
|
renderEditToolEnhanced() {
|
|
5230
6140
|
const argsObj = this.args;
|
|
@@ -5232,19 +6142,19 @@ var ToolExecutionComponentEnhanced = class extends Container {
|
|
|
5232
6142
|
const startLineNum = argsObj?.start_line ? Number(argsObj.start_line) : void 0;
|
|
5233
6143
|
const startLine = startLineNum ? `:${String(startLineNum)}` : "";
|
|
5234
6144
|
if (!this.result || this.isPartial) {
|
|
5235
|
-
const
|
|
6145
|
+
const path7 = argsObj?.path ? shortenPath(String(argsObj.path)) : "...";
|
|
5236
6146
|
const status2 = this.getStatusIndicator();
|
|
5237
|
-
const pathDisplay2 = fullPath ? fileLink(theme.fg("accent",
|
|
5238
|
-
|
|
6147
|
+
const pathDisplay2 = fullPath ? fileLink(theme.fg("accent", path7), fullPath, startLineNum) : theme.fg("accent", path7);
|
|
6148
|
+
const oldStr = argsObj?.old_str ?? argsObj?.old_string;
|
|
6149
|
+
const newStr = argsObj?.new_str ?? argsObj?.new_string;
|
|
6150
|
+
if (oldStr != null && newStr != null) {
|
|
5239
6151
|
const border2 = (char) => theme.bold(theme.fg("accent", char));
|
|
5240
6152
|
const termWidth2 = process.stdout.columns || 80;
|
|
5241
6153
|
const maxLineWidth = termWidth2 - 6;
|
|
5242
6154
|
const footerText2 = `${theme.bold(theme.fg("toolTitle", "edit"))} ${pathDisplay2}${theme.fg("muted", startLine)}${status2}`;
|
|
5243
6155
|
this.contentBox.addChild(new Text("", 0, 0));
|
|
5244
6156
|
this.contentBox.addChild(new Text(border2("\u250C\u2500\u2500"), 0, 0));
|
|
5245
|
-
const
|
|
5246
|
-
const newStr = String(argsObj.new_str);
|
|
5247
|
-
const { lines: diffLines } = this.generateDiffLines(oldStr, newStr);
|
|
6157
|
+
const { lines: diffLines } = this.generateDiffLines(String(oldStr), String(newStr));
|
|
5248
6158
|
const collapsedLines = 15;
|
|
5249
6159
|
const totalLines = diffLines.length;
|
|
5250
6160
|
const hasMore = !this.expanded && totalLines > collapsedLines + 1;
|
|
@@ -5276,18 +6186,18 @@ var ToolExecutionComponentEnhanced = class extends Container {
|
|
|
5276
6186
|
const termWidth = process.stdout.columns || 80;
|
|
5277
6187
|
const fixedParts = "\u2514\u2500\u2500 edit " + startLine + " \u2713";
|
|
5278
6188
|
const availableForPath = termWidth - fixedParts.length - 6;
|
|
5279
|
-
let
|
|
5280
|
-
if (
|
|
5281
|
-
|
|
6189
|
+
let path6 = argsObj?.path ? shortenPath(String(argsObj.path)) : "...";
|
|
6190
|
+
if (path6.length > availableForPath && availableForPath > 10) {
|
|
6191
|
+
path6 = "\u2026" + path6.slice(-(availableForPath - 1));
|
|
5282
6192
|
}
|
|
5283
|
-
const pathDisplay = fullPath ? fileLink(theme.fg("accent",
|
|
6193
|
+
const pathDisplay = fullPath ? fileLink(theme.fg("accent", path6), fullPath, startLineNum) : theme.fg("accent", path6);
|
|
5284
6194
|
const footerText = `${theme.bold(theme.fg("toolTitle", "edit"))} ${pathDisplay}${theme.fg("muted", startLine)}${status}`;
|
|
5285
6195
|
this.contentBox.addChild(new Text("", 0, 0));
|
|
5286
6196
|
this.contentBox.addChild(new Text(border("\u250C\u2500\u2500"), 0, 0));
|
|
5287
|
-
|
|
5288
|
-
|
|
5289
|
-
|
|
5290
|
-
const { lines: diffLines, firstChangeIndex } = this.generateDiffLines(
|
|
6197
|
+
const finalOldStr = argsObj?.old_str ?? argsObj?.old_string;
|
|
6198
|
+
const finalNewStr = argsObj?.new_str ?? argsObj?.new_string;
|
|
6199
|
+
if (finalOldStr != null && finalNewStr != null && !this.result.isError) {
|
|
6200
|
+
const { lines: diffLines, firstChangeIndex } = this.generateDiffLines(String(finalOldStr), String(finalNewStr));
|
|
5291
6201
|
const collapsedLines = 15;
|
|
5292
6202
|
const totalLines = diffLines.length;
|
|
5293
6203
|
const hasMore = !this.expanded && totalLines > collapsedLines + 1;
|
|
@@ -5331,7 +6241,9 @@ var ToolExecutionComponentEnhanced = class extends Container {
|
|
|
5331
6241
|
}
|
|
5332
6242
|
this.contentBox.addChild(new Text(`${border("\u2514\u2500\u2500")} ${footerText}`, 0, 0));
|
|
5333
6243
|
const diagnostics = this.parseLSPDiagnostics();
|
|
5334
|
-
if (diagnostics && diagnostics.hasIssues) {
|
|
6244
|
+
if (diagnostics && !diagnostics.hasIssues) {
|
|
6245
|
+
this.contentBox.addChild(new Text(theme.fg("muted", ` \u2713 No LSP issues`), 0, 0));
|
|
6246
|
+
} else if (diagnostics && diagnostics.hasIssues) {
|
|
5335
6247
|
const COLLAPSED_DIAG_LINES = 3;
|
|
5336
6248
|
const shouldCollapse = !this.expanded && diagnostics.entries.length > COLLAPSED_DIAG_LINES + 1;
|
|
5337
6249
|
const maxDiags = shouldCollapse ? COLLAPSED_DIAG_LINES : diagnostics.entries.length;
|
|
@@ -5424,9 +6336,9 @@ var ToolExecutionComponentEnhanced = class extends Container {
|
|
|
5424
6336
|
const content = argsObj?.content ? String(argsObj.content) : "";
|
|
5425
6337
|
if (!this.result || this.isPartial) {
|
|
5426
6338
|
if (!content) {
|
|
5427
|
-
const
|
|
6339
|
+
const path8 = argsObj?.path ? shortenPath(String(argsObj.path)) : "...";
|
|
5428
6340
|
const status3 = this.getStatusIndicator();
|
|
5429
|
-
const pathDisplay3 = fullPath ? fileLink(theme.fg("accent",
|
|
6341
|
+
const pathDisplay3 = fullPath ? fileLink(theme.fg("accent", path8), fullPath) : theme.fg("accent", path8);
|
|
5430
6342
|
const headerText = `${theme.bold(theme.fg("toolTitle", "write"))} ${pathDisplay3}${status3}`;
|
|
5431
6343
|
this.contentBox.addChild(new Text(headerText, 0, 0));
|
|
5432
6344
|
return;
|
|
@@ -5435,13 +6347,13 @@ var ToolExecutionComponentEnhanced = class extends Container {
|
|
|
5435
6347
|
const status2 = this.getStatusIndicator();
|
|
5436
6348
|
const termWidth2 = process.stdout.columns || 80;
|
|
5437
6349
|
const maxLineWidth2 = termWidth2 - 6;
|
|
5438
|
-
let
|
|
6350
|
+
let path7 = argsObj?.path ? shortenPath(String(argsObj.path)) : "...";
|
|
5439
6351
|
const fixedParts2 = "\u2514\u2500\u2500 write \u22EF";
|
|
5440
6352
|
const availableForPath2 = termWidth2 - fixedParts2.length - 6;
|
|
5441
|
-
if (
|
|
5442
|
-
|
|
6353
|
+
if (path7.length > availableForPath2 && availableForPath2 > 10) {
|
|
6354
|
+
path7 = "\u2026" + path7.slice(-(availableForPath2 - 1));
|
|
5443
6355
|
}
|
|
5444
|
-
const pathDisplay2 = fullPath ? fileLink(theme.fg("accent",
|
|
6356
|
+
const pathDisplay2 = fullPath ? fileLink(theme.fg("accent", path7), fullPath) : theme.fg("accent", path7);
|
|
5445
6357
|
const footerText2 = `${theme.bold(theme.fg("toolTitle", "write"))} ${pathDisplay2}${status2}`;
|
|
5446
6358
|
this.contentBox.addChild(new Text("", 0, 0));
|
|
5447
6359
|
this.contentBox.addChild(new Text(border2("\u250C\u2500\u2500"), 0, 0));
|
|
@@ -5472,13 +6384,13 @@ var ToolExecutionComponentEnhanced = class extends Container {
|
|
|
5472
6384
|
const status = this.getStatusIndicator();
|
|
5473
6385
|
const termWidth = process.stdout.columns || 80;
|
|
5474
6386
|
const maxLineWidth = termWidth - 6;
|
|
5475
|
-
let
|
|
6387
|
+
let path6 = argsObj?.path ? shortenPath(String(argsObj.path)) : "...";
|
|
5476
6388
|
const fixedParts = "\u2514\u2500\u2500 write \u2713";
|
|
5477
6389
|
const availableForPath = termWidth - fixedParts.length - 6;
|
|
5478
|
-
if (
|
|
5479
|
-
|
|
6390
|
+
if (path6.length > availableForPath && availableForPath > 10) {
|
|
6391
|
+
path6 = "\u2026" + path6.slice(-(availableForPath - 1));
|
|
5480
6392
|
}
|
|
5481
|
-
const pathDisplay = fullPath ? fileLink(theme.fg("accent",
|
|
6393
|
+
const pathDisplay = fullPath ? fileLink(theme.fg("accent", path6), fullPath) : theme.fg("accent", path6);
|
|
5482
6394
|
const footerText = `${theme.bold(theme.fg("toolTitle", "write"))} ${pathDisplay}${status}`;
|
|
5483
6395
|
this.contentBox.addChild(new Text("", 0, 0));
|
|
5484
6396
|
this.contentBox.addChild(new Text(border("\u250C\u2500\u2500"), 0, 0));
|
|
@@ -5518,25 +6430,26 @@ var ToolExecutionComponentEnhanced = class extends Container {
|
|
|
5518
6430
|
renderListFilesEnhanced() {
|
|
5519
6431
|
const argsObj = this.args;
|
|
5520
6432
|
const fullPath = argsObj?.path ? String(argsObj.path) : "";
|
|
5521
|
-
const
|
|
6433
|
+
const path6 = argsObj?.path ? shortenPath(String(argsObj.path)) : "/";
|
|
5522
6434
|
const pattern = argsObj?.pattern ? String(argsObj.pattern) : "";
|
|
5523
6435
|
const patternDisplay = pattern ? " " + theme.fg("muted", pattern) : "";
|
|
5524
6436
|
if (!this.result || this.isPartial) {
|
|
5525
6437
|
const status = this.getStatusIndicator();
|
|
5526
|
-
const pathDisplay = fullPath ? fileLink(theme.fg("accent",
|
|
6438
|
+
const pathDisplay = fullPath ? fileLink(theme.fg("accent", path6), fullPath) : theme.fg("accent", path6);
|
|
5527
6439
|
const header = `${theme.bold(theme.fg("toolTitle", "list"))} ${pathDisplay}${patternDisplay}${status}`;
|
|
5528
6440
|
this.contentBox.addChild(new Text(header, 0, 0));
|
|
5529
6441
|
return;
|
|
5530
6442
|
}
|
|
5531
6443
|
const output = this.getFormattedOutput();
|
|
5532
6444
|
if (output) {
|
|
5533
|
-
const lines = output.split("\n");
|
|
5534
|
-
const fileCount = lines.filter((l) => l.trim() && !l.includes("\u2514") && !l.includes("\u251C") && !l.includes("\u2502")).length;
|
|
5535
6445
|
const listStatus = this.getStatusIndicator();
|
|
6446
|
+
const lines = output.split("\n");
|
|
6447
|
+
const lastLine = lines[lines.length - 1]?.trim() || "";
|
|
6448
|
+
const summaryMatch = lastLine.match(/^\d+\s+directories?,\s+\d+\s+files?$/);
|
|
6449
|
+
const summaryDisplay = summaryMatch ? " " + theme.fg("muted", lastLine) : "";
|
|
5536
6450
|
this.collapsible = new CollapsibleComponent(
|
|
5537
6451
|
{
|
|
5538
|
-
header: `${theme.bold(theme.fg("toolTitle", "list"))} ${theme.fg("accent",
|
|
5539
|
-
summary: `${fileCount} items`,
|
|
6452
|
+
header: `${theme.bold(theme.fg("toolTitle", "list"))} ${theme.fg("accent", path6)}${patternDisplay}${summaryDisplay}${listStatus}`,
|
|
5540
6453
|
expanded: this.expanded,
|
|
5541
6454
|
collapsedLines: 15,
|
|
5542
6455
|
expandedLines: 100,
|
|
@@ -5715,8 +6628,8 @@ ${stackMatch.join("\n")}`;
|
|
|
5715
6628
|
this.contentBox.addChild(errorDisplay);
|
|
5716
6629
|
}
|
|
5717
6630
|
};
|
|
5718
|
-
function getLanguageFromPath(
|
|
5719
|
-
const ext =
|
|
6631
|
+
function getLanguageFromPath(path6) {
|
|
6632
|
+
const ext = path6.split(".").pop()?.toLowerCase();
|
|
5720
6633
|
const langMap = {
|
|
5721
6634
|
ts: "typescript",
|
|
5722
6635
|
tsx: "typescript",
|
|
@@ -5765,7 +6678,7 @@ function getLanguageFromPath(path5) {
|
|
|
5765
6678
|
};
|
|
5766
6679
|
return ext ? langMap[ext] : void 0;
|
|
5767
6680
|
}
|
|
5768
|
-
function highlightCode(content,
|
|
6681
|
+
function highlightCode(content, path6, startLine) {
|
|
5769
6682
|
let lines = content.split("\n").map((line) => line.trimEnd());
|
|
5770
6683
|
while (lines.length > 0 && (lines[0].includes("Here's the result of running") || lines[0].match(/^\[Truncated \d+ tokens\]$/) || lines[0].match(/^.*\(\d+ bytes\)$/) || lines[0].match(/^.*\(lines \d+-\d+ of \d+, \d+ bytes\)$/))) {
|
|
5771
6684
|
lines = lines.slice(1);
|
|
@@ -5785,7 +6698,7 @@ function highlightCode(content, path5, startLine) {
|
|
|
5785
6698
|
}
|
|
5786
6699
|
try {
|
|
5787
6700
|
return highlight(codeLines.join("\n"), {
|
|
5788
|
-
language: getLanguageFromPath(
|
|
6701
|
+
language: getLanguageFromPath(path6),
|
|
5789
6702
|
ignoreIllegals: true
|
|
5790
6703
|
});
|
|
5791
6704
|
} catch {
|
|
@@ -5921,10 +6834,12 @@ function handleMessageUpdate(ctx, message) {
|
|
|
5921
6834
|
}
|
|
5922
6835
|
}
|
|
5923
6836
|
const trailingParts = getTrailingContentParts(message);
|
|
5924
|
-
|
|
5925
|
-
|
|
5926
|
-
|
|
5927
|
-
|
|
6837
|
+
if (trailingParts.length > 0) {
|
|
6838
|
+
state.streamingComponent.updateContent({
|
|
6839
|
+
...message,
|
|
6840
|
+
content: trailingParts
|
|
6841
|
+
});
|
|
6842
|
+
}
|
|
5928
6843
|
state.ui.requestRender();
|
|
5929
6844
|
}
|
|
5930
6845
|
function handleMessageEnd(ctx, message) {
|
|
@@ -5933,10 +6848,12 @@ function handleMessageEnd(ctx, message) {
|
|
|
5933
6848
|
if (state.streamingComponent && message.role === "assistant") {
|
|
5934
6849
|
state.streamingMessage = message;
|
|
5935
6850
|
const trailingParts = getTrailingContentParts(message);
|
|
5936
|
-
|
|
5937
|
-
|
|
5938
|
-
|
|
5939
|
-
|
|
6851
|
+
if (trailingParts.length > 0 || message.stopReason === "aborted" || message.stopReason === "error") {
|
|
6852
|
+
state.streamingComponent.updateContent({
|
|
6853
|
+
...message,
|
|
6854
|
+
content: trailingParts
|
|
6855
|
+
});
|
|
6856
|
+
}
|
|
5940
6857
|
if (message.stopReason === "aborted" || message.stopReason === "error") {
|
|
5941
6858
|
const errorMessage = message.errorMessage || "Operation aborted";
|
|
5942
6859
|
for (const [, component] of state.pendingTools) {
|
|
@@ -6331,6 +7248,27 @@ function handleOMActivation(ctx, operationType, tokensActivated, observationToke
|
|
|
6331
7248
|
state.activeBufferingMarker = void 0;
|
|
6332
7249
|
state.ui.requestRender();
|
|
6333
7250
|
}
|
|
7251
|
+
function slugify(str) {
|
|
7252
|
+
const slug = str.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
7253
|
+
return slug || "untitled";
|
|
7254
|
+
}
|
|
7255
|
+
async function savePlanToDisk(opts) {
|
|
7256
|
+
const { title, plan, resourceId } = opts;
|
|
7257
|
+
const plansDir = opts.plansDir ?? process.env.MASTRA_PLANS_DIR ?? path5__default.join(getAppDataDir(), "plans");
|
|
7258
|
+
const dir = path5__default.join(plansDir, resourceId);
|
|
7259
|
+
await fs4.mkdir(dir, { recursive: true });
|
|
7260
|
+
const now = /* @__PURE__ */ new Date();
|
|
7261
|
+
const timestamp = now.toISOString().replace(/:/g, "-");
|
|
7262
|
+
const slug = slugify(title);
|
|
7263
|
+
const filename = `${timestamp}-${slug}.md`;
|
|
7264
|
+
const content = `# ${title}
|
|
7265
|
+
|
|
7266
|
+
Approved: ${now.toISOString()}
|
|
7267
|
+
|
|
7268
|
+
${plan}
|
|
7269
|
+
`;
|
|
7270
|
+
await fs4.writeFile(path5__default.join(dir, filename), content, "utf-8");
|
|
7271
|
+
}
|
|
6334
7272
|
var AskQuestionDialogComponent = class extends Box {
|
|
6335
7273
|
selectList;
|
|
6336
7274
|
input;
|
|
@@ -6488,6 +7426,11 @@ var PlanApprovalInlineComponent = class extends Container {
|
|
|
6488
7426
|
this.mode = "feedback";
|
|
6489
7427
|
this.selectList = void 0;
|
|
6490
7428
|
this.contentBox.clear();
|
|
7429
|
+
this.contentBox.addChild(new Text(theme.bold(theme.fg("accent", `Plan: ${this.planTitle}`)), 0, 0));
|
|
7430
|
+
this.contentBox.addChild(new Spacer(1));
|
|
7431
|
+
const md = new Markdown(this.planContent, 1, 0, getMarkdownTheme());
|
|
7432
|
+
this.contentBox.addChild(md);
|
|
7433
|
+
this.contentBox.addChild(new Spacer(1));
|
|
6491
7434
|
this.contentBox.addChild(new Text(theme.fg("accent", "Provide feedback for revision:"), 0, 0));
|
|
6492
7435
|
this.contentBox.addChild(new Spacer(1));
|
|
6493
7436
|
this.feedbackInput = new Input();
|
|
@@ -6558,55 +7501,71 @@ var PlanResultComponent = class extends Container {
|
|
|
6558
7501
|
};
|
|
6559
7502
|
|
|
6560
7503
|
// src/tui/handlers/prompts.ts
|
|
7504
|
+
function processNextInlineQuestion(state) {
|
|
7505
|
+
const next = state.pendingInlineQuestions.shift();
|
|
7506
|
+
if (next) {
|
|
7507
|
+
next();
|
|
7508
|
+
}
|
|
7509
|
+
}
|
|
6561
7510
|
async function handleAskQuestion(ctx, questionId, question, options) {
|
|
6562
7511
|
const { state } = ctx;
|
|
6563
7512
|
return new Promise((resolve2) => {
|
|
6564
7513
|
if (state.options.inlineQuestions) {
|
|
6565
|
-
const
|
|
6566
|
-
|
|
6567
|
-
|
|
6568
|
-
|
|
6569
|
-
|
|
6570
|
-
|
|
6571
|
-
|
|
6572
|
-
|
|
7514
|
+
const askUserComponent = state.lastAskUserComponent;
|
|
7515
|
+
const activate = () => {
|
|
7516
|
+
const questionComponent = new AskQuestionInlineComponent(
|
|
7517
|
+
{
|
|
7518
|
+
question,
|
|
7519
|
+
options,
|
|
7520
|
+
onSubmit: (answer) => {
|
|
7521
|
+
state.activeInlineQuestion = void 0;
|
|
7522
|
+
state.harness.respondToQuestion({ questionId, answer });
|
|
7523
|
+
resolve2();
|
|
7524
|
+
processNextInlineQuestion(state);
|
|
7525
|
+
},
|
|
7526
|
+
onCancel: () => {
|
|
7527
|
+
state.activeInlineQuestion = void 0;
|
|
7528
|
+
state.harness.respondToQuestion({ questionId, answer: "(skipped)" });
|
|
7529
|
+
resolve2();
|
|
7530
|
+
processNextInlineQuestion(state);
|
|
7531
|
+
}
|
|
6573
7532
|
},
|
|
6574
|
-
|
|
6575
|
-
|
|
6576
|
-
|
|
6577
|
-
|
|
6578
|
-
|
|
6579
|
-
|
|
6580
|
-
|
|
6581
|
-
|
|
6582
|
-
|
|
6583
|
-
|
|
6584
|
-
|
|
6585
|
-
|
|
6586
|
-
|
|
6587
|
-
|
|
6588
|
-
|
|
6589
|
-
|
|
6590
|
-
|
|
6591
|
-
|
|
6592
|
-
|
|
6593
|
-
|
|
6594
|
-
|
|
6595
|
-
state.chatContainer.addChild(children[i]);
|
|
7533
|
+
state.ui
|
|
7534
|
+
);
|
|
7535
|
+
state.activeInlineQuestion = questionComponent;
|
|
7536
|
+
if (askUserComponent) {
|
|
7537
|
+
const children = [...state.chatContainer.children];
|
|
7538
|
+
const askUserIndex = children.indexOf(askUserComponent);
|
|
7539
|
+
if (askUserIndex >= 0) {
|
|
7540
|
+
state.chatContainer.clear();
|
|
7541
|
+
for (let i = 0; i <= askUserIndex; i++) {
|
|
7542
|
+
state.chatContainer.addChild(children[i]);
|
|
7543
|
+
}
|
|
7544
|
+
state.chatContainer.addChild(new Spacer(1));
|
|
7545
|
+
state.chatContainer.addChild(questionComponent);
|
|
7546
|
+
state.chatContainer.addChild(new Spacer(1));
|
|
7547
|
+
for (let i = askUserIndex + 1; i < children.length; i++) {
|
|
7548
|
+
state.chatContainer.addChild(children[i]);
|
|
7549
|
+
}
|
|
7550
|
+
} else {
|
|
7551
|
+
state.chatContainer.addChild(new Spacer(1));
|
|
7552
|
+
state.chatContainer.addChild(questionComponent);
|
|
7553
|
+
state.chatContainer.addChild(new Spacer(1));
|
|
6596
7554
|
}
|
|
6597
7555
|
} else {
|
|
6598
7556
|
state.chatContainer.addChild(new Spacer(1));
|
|
6599
7557
|
state.chatContainer.addChild(questionComponent);
|
|
6600
7558
|
state.chatContainer.addChild(new Spacer(1));
|
|
6601
7559
|
}
|
|
7560
|
+
state.ui.requestRender();
|
|
7561
|
+
state.chatContainer.invalidate();
|
|
7562
|
+
questionComponent.focused = true;
|
|
7563
|
+
};
|
|
7564
|
+
if (state.activeInlineQuestion) {
|
|
7565
|
+
state.pendingInlineQuestions.push(activate);
|
|
6602
7566
|
} else {
|
|
6603
|
-
|
|
6604
|
-
state.chatContainer.addChild(questionComponent);
|
|
6605
|
-
state.chatContainer.addChild(new Spacer(1));
|
|
7567
|
+
activate();
|
|
6606
7568
|
}
|
|
6607
|
-
state.ui.requestRender();
|
|
6608
|
-
state.chatContainer.invalidate();
|
|
6609
|
-
questionComponent.focused = true;
|
|
6610
7569
|
} else {
|
|
6611
7570
|
const dialog = new AskQuestionDialogComponent({
|
|
6612
7571
|
question,
|
|
@@ -6631,39 +7590,48 @@ async function handleAskQuestion(ctx, questionId, question, options) {
|
|
|
6631
7590
|
async function handleSandboxAccessRequest(ctx, questionId, requestedPath, reason) {
|
|
6632
7591
|
const { state } = ctx;
|
|
6633
7592
|
return new Promise((resolve2) => {
|
|
6634
|
-
const
|
|
6635
|
-
|
|
6636
|
-
|
|
7593
|
+
const activate = () => {
|
|
7594
|
+
const questionComponent = new AskQuestionInlineComponent(
|
|
7595
|
+
{
|
|
7596
|
+
question: `Grant sandbox access to "${requestedPath}"?
|
|
6637
7597
|
${theme.fg("dim", `Reason: ${reason}`)}`,
|
|
6638
|
-
|
|
6639
|
-
|
|
6640
|
-
|
|
6641
|
-
|
|
6642
|
-
|
|
6643
|
-
|
|
6644
|
-
|
|
6645
|
-
|
|
6646
|
-
|
|
6647
|
-
|
|
6648
|
-
|
|
6649
|
-
|
|
6650
|
-
|
|
6651
|
-
|
|
6652
|
-
|
|
6653
|
-
|
|
6654
|
-
|
|
7598
|
+
options: [
|
|
7599
|
+
{ label: "Yes", description: "Allow access to this directory" },
|
|
7600
|
+
{ label: "No", description: "Deny access" }
|
|
7601
|
+
],
|
|
7602
|
+
onSubmit: (answer) => {
|
|
7603
|
+
state.activeInlineQuestion = void 0;
|
|
7604
|
+
state.harness.respondToQuestion({ questionId, answer });
|
|
7605
|
+
resolve2();
|
|
7606
|
+
processNextInlineQuestion(state);
|
|
7607
|
+
},
|
|
7608
|
+
onCancel: () => {
|
|
7609
|
+
state.activeInlineQuestion = void 0;
|
|
7610
|
+
state.harness.respondToQuestion({ questionId, answer: "No" });
|
|
7611
|
+
resolve2();
|
|
7612
|
+
processNextInlineQuestion(state);
|
|
7613
|
+
},
|
|
7614
|
+
formatResult: (answer) => {
|
|
7615
|
+
const approved = answer.toLowerCase().startsWith("y");
|
|
7616
|
+
return approved ? `Granted access to ${requestedPath}` : `Denied access to ${requestedPath}`;
|
|
7617
|
+
},
|
|
7618
|
+
isNegativeAnswer: (answer) => !answer.toLowerCase().startsWith("y")
|
|
6655
7619
|
},
|
|
6656
|
-
|
|
6657
|
-
|
|
6658
|
-
state.
|
|
6659
|
-
|
|
6660
|
-
|
|
6661
|
-
|
|
6662
|
-
|
|
6663
|
-
|
|
6664
|
-
|
|
6665
|
-
|
|
6666
|
-
state.
|
|
7620
|
+
state.ui
|
|
7621
|
+
);
|
|
7622
|
+
state.activeInlineQuestion = questionComponent;
|
|
7623
|
+
state.chatContainer.addChild(new Spacer(1));
|
|
7624
|
+
state.chatContainer.addChild(questionComponent);
|
|
7625
|
+
state.chatContainer.addChild(new Spacer(1));
|
|
7626
|
+
questionComponent.focused = true;
|
|
7627
|
+
state.ui.requestRender();
|
|
7628
|
+
state.chatContainer.invalidate();
|
|
7629
|
+
};
|
|
7630
|
+
if (state.activeInlineQuestion) {
|
|
7631
|
+
state.pendingInlineQuestions.push(activate);
|
|
7632
|
+
} else {
|
|
7633
|
+
activate();
|
|
7634
|
+
}
|
|
6667
7635
|
ctx.notify("sandbox_access", `Sandbox access requested: ${requestedPath}`);
|
|
6668
7636
|
});
|
|
6669
7637
|
}
|
|
@@ -6684,6 +7652,12 @@ async function handlePlanApproval(ctx, planId, title, plan) {
|
|
|
6684
7652
|
approvedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
6685
7653
|
}
|
|
6686
7654
|
});
|
|
7655
|
+
savePlanToDisk({
|
|
7656
|
+
title,
|
|
7657
|
+
plan,
|
|
7658
|
+
resourceId: state.harness.getResourceId()
|
|
7659
|
+
}).catch(() => {
|
|
7660
|
+
});
|
|
6687
7661
|
await state.harness.respondToPlanApproval({
|
|
6688
7662
|
planId,
|
|
6689
7663
|
response: { action: "approved" }
|
|
@@ -6757,12 +7731,14 @@ var SubagentExecutionComponent = class extends Container {
|
|
|
6757
7731
|
durationMs = 0;
|
|
6758
7732
|
finalResult;
|
|
6759
7733
|
expanded = false;
|
|
6760
|
-
|
|
7734
|
+
collapseOnComplete;
|
|
7735
|
+
constructor(agentType, task, ui, modelId, options) {
|
|
6761
7736
|
super();
|
|
6762
7737
|
this.agentType = agentType;
|
|
6763
7738
|
this.task = task;
|
|
6764
7739
|
this.modelId = modelId;
|
|
6765
7740
|
this.ui = ui;
|
|
7741
|
+
this.collapseOnComplete = options?.collapseOnComplete ?? false;
|
|
6766
7742
|
this.rebuild();
|
|
6767
7743
|
}
|
|
6768
7744
|
// ── Mutation API ──────────────────────────────────────────────────────
|
|
@@ -6787,6 +7763,9 @@ var SubagentExecutionComponent = class extends Container {
|
|
|
6787
7763
|
this.isError = isError;
|
|
6788
7764
|
this.durationMs = durationMs;
|
|
6789
7765
|
this.finalResult = result;
|
|
7766
|
+
if (this.collapseOnComplete) {
|
|
7767
|
+
this.expanded = false;
|
|
7768
|
+
}
|
|
6790
7769
|
this.rebuild();
|
|
6791
7770
|
}
|
|
6792
7771
|
setExpanded(expanded) {
|
|
@@ -6809,6 +7788,17 @@ var SubagentExecutionComponent = class extends Container {
|
|
|
6809
7788
|
const border = (char) => theme.bold(theme.fg("accent", char));
|
|
6810
7789
|
const termWidth = process.stdout.columns || 80;
|
|
6811
7790
|
const maxLineWidth = termWidth - 6;
|
|
7791
|
+
const typeLabel = theme.bold(theme.fg("accent", this.agentType));
|
|
7792
|
+
const modelLabel = this.modelId ? theme.fg("muted", ` ${this.modelId}`) : "";
|
|
7793
|
+
const statusIcon = this.done ? this.isError ? theme.fg("error", " \u2717") : theme.fg("success", " \u2713") : theme.fg("muted", " \u22EF");
|
|
7794
|
+
const durationStr = this.done ? theme.fg("muted", ` ${formatDuration(this.durationMs)}`) : "";
|
|
7795
|
+
const footerText = `${theme.bold(theme.fg("toolTitle", "subagent"))} ${typeLabel}${modelLabel}${durationStr}${statusIcon}`;
|
|
7796
|
+
if (this.collapseOnComplete && this.done && !this.expanded) {
|
|
7797
|
+
this.addChild(new Text(`${border("\u2514\u2500\u2500")} ${footerText}`, 0, 0));
|
|
7798
|
+
this.invalidate();
|
|
7799
|
+
this.ui.requestRender();
|
|
7800
|
+
return;
|
|
7801
|
+
}
|
|
6812
7802
|
this.addChild(new Text(border("\u250C\u2500\u2500"), 0, 0));
|
|
6813
7803
|
const taskLines = this.task.split("\n");
|
|
6814
7804
|
const wrappedTaskLines = [];
|
|
@@ -6861,18 +7851,10 @@ var SubagentExecutionComponent = class extends Container {
|
|
|
6861
7851
|
this.addChild(new Text(`${border("\u2502")} ${moreText}`, 0, 0));
|
|
6862
7852
|
}
|
|
6863
7853
|
}
|
|
6864
|
-
|
|
6865
|
-
if (showResult) {
|
|
7854
|
+
if (this.done && this.finalResult && this.expanded) {
|
|
6866
7855
|
this.addChild(new Text(`${border("\u2502")} ${theme.fg("muted", "\u2500\u2500\u2500")}`, 0, 0));
|
|
6867
7856
|
const resultLines = this.finalResult.split("\n");
|
|
6868
|
-
const
|
|
6869
|
-
const truncated = !this.expanded && resultLines.length > maxResultLines + 1;
|
|
6870
|
-
const displayLines = truncated ? resultLines.slice(-maxResultLines) : resultLines;
|
|
6871
|
-
if (truncated) {
|
|
6872
|
-
const hiddenLine = `${border("\u2502")} ${theme.fg("muted", ` ... ${resultLines.length - maxResultLines} more lines (ctrl+e to expand)`)}`;
|
|
6873
|
-
this.addChild(new Text(hiddenLine, 0, 0));
|
|
6874
|
-
}
|
|
6875
|
-
const resultContent = displayLines.map((line) => {
|
|
7857
|
+
const resultContent = resultLines.map((line) => {
|
|
6876
7858
|
const truncatedLine = line.length > maxLineWidth ? line.slice(0, maxLineWidth - 1) + "\u2026" : line;
|
|
6877
7859
|
return `${border("\u2502")} ${theme.fg("muted", truncatedLine)}`;
|
|
6878
7860
|
}).join("\n");
|
|
@@ -6880,11 +7862,6 @@ var SubagentExecutionComponent = class extends Container {
|
|
|
6880
7862
|
this.addChild(new Text(resultContent, 0, 0));
|
|
6881
7863
|
}
|
|
6882
7864
|
}
|
|
6883
|
-
const typeLabel = theme.bold(theme.fg("accent", this.agentType));
|
|
6884
|
-
const modelLabel = this.modelId ? theme.fg("muted", ` ${this.modelId}`) : "";
|
|
6885
|
-
const statusIcon = this.done ? this.isError ? theme.fg("error", " \u2717") : theme.fg("success", " \u2713") : theme.fg("muted", " \u22EF");
|
|
6886
|
-
const durationStr = this.done ? theme.fg("muted", ` ${formatDuration(this.durationMs)}`) : "";
|
|
6887
|
-
const footerText = `${theme.bold(theme.fg("toolTitle", "subagent"))} ${typeLabel}${modelLabel}${durationStr}${statusIcon}`;
|
|
6888
7865
|
this.addChild(new Text(`${border("\u2514\u2500\u2500")} ${footerText}`, 0, 0));
|
|
6889
7866
|
this.invalidate();
|
|
6890
7867
|
this.ui.requestRender();
|
|
@@ -6930,7 +7907,9 @@ function summarizeArgs(args) {
|
|
|
6930
7907
|
// src/tui/handlers/subagent.ts
|
|
6931
7908
|
function handleSubagentStart(ctx, toolCallId, agentType, task, modelId) {
|
|
6932
7909
|
const { state } = ctx;
|
|
6933
|
-
const component = new SubagentExecutionComponent(agentType, task, state.ui, modelId
|
|
7910
|
+
const component = new SubagentExecutionComponent(agentType, task, state.ui, modelId, {
|
|
7911
|
+
collapseOnComplete: state.quietMode
|
|
7912
|
+
});
|
|
6934
7913
|
state.pendingSubagents.set(toolCallId, component);
|
|
6935
7914
|
state.allToolComponents.push(component);
|
|
6936
7915
|
if (state.streamingComponent) {
|
|
@@ -7622,7 +8601,8 @@ async function renderExistingMessages(state) {
|
|
|
7622
8601
|
subArgs?.agentType ?? "unknown",
|
|
7623
8602
|
subArgs?.task ?? "",
|
|
7624
8603
|
state.ui,
|
|
7625
|
-
modelId
|
|
8604
|
+
modelId,
|
|
8605
|
+
{ collapseOnComplete: state.quietMode }
|
|
7626
8606
|
);
|
|
7627
8607
|
if (meta?.toolCalls) {
|
|
7628
8608
|
for (const tc of meta.toolCalls) {
|
|
@@ -7757,7 +8737,7 @@ async function parseCommandFile(filePath, baseDir) {
|
|
|
7757
8737
|
const content = await promises.readFile(filePath, "utf-8");
|
|
7758
8738
|
const trimmedContent = content.trim();
|
|
7759
8739
|
if (!trimmedContent.startsWith("---")) {
|
|
7760
|
-
const name2 = baseDir ? extractCommandName(filePath, baseDir) :
|
|
8740
|
+
const name2 = baseDir ? extractCommandName(filePath, baseDir) : path5.basename(filePath, ".md");
|
|
7761
8741
|
return {
|
|
7762
8742
|
name: name2,
|
|
7763
8743
|
description: "",
|
|
@@ -7778,7 +8758,7 @@ async function parseCommandFile(filePath, baseDir) {
|
|
|
7778
8758
|
} else if (baseDir) {
|
|
7779
8759
|
name = extractCommandName(filePath, baseDir);
|
|
7780
8760
|
} else {
|
|
7781
|
-
name =
|
|
8761
|
+
name = path5.basename(filePath, ".md");
|
|
7782
8762
|
}
|
|
7783
8763
|
return {
|
|
7784
8764
|
name,
|
|
@@ -7793,9 +8773,9 @@ async function parseCommandFile(filePath, baseDir) {
|
|
|
7793
8773
|
}
|
|
7794
8774
|
}
|
|
7795
8775
|
function extractCommandName(filePath, baseDir) {
|
|
7796
|
-
const relativePath =
|
|
7797
|
-
const dirName =
|
|
7798
|
-
const baseName =
|
|
8776
|
+
const relativePath = path5.relative(baseDir, filePath);
|
|
8777
|
+
const dirName = path5.dirname(relativePath);
|
|
8778
|
+
const baseName = path5.basename(relativePath, ".md");
|
|
7799
8779
|
if (dirName === "." || dirName === "") {
|
|
7800
8780
|
return baseName;
|
|
7801
8781
|
}
|
|
@@ -7807,7 +8787,7 @@ async function scanCommandDirectory(dirPath) {
|
|
|
7807
8787
|
try {
|
|
7808
8788
|
const entries = await promises.readdir(dirPath, { withFileTypes: true });
|
|
7809
8789
|
for (const entry of entries) {
|
|
7810
|
-
const fullPath =
|
|
8790
|
+
const fullPath = path5.join(dirPath, entry.name);
|
|
7811
8791
|
if (entry.isDirectory()) {
|
|
7812
8792
|
const subCommands = await scanCommandDirectory(fullPath);
|
|
7813
8793
|
commands.push(...subCommands);
|
|
@@ -7835,32 +8815,32 @@ async function loadCustomCommands(projectDir) {
|
|
|
7835
8815
|
};
|
|
7836
8816
|
const homeDir = process.env.HOME || process.env.USERPROFILE;
|
|
7837
8817
|
if (homeDir) {
|
|
7838
|
-
const opencodeUserDir =
|
|
8818
|
+
const opencodeUserDir = path5.join(homeDir, ".opencode", "command");
|
|
7839
8819
|
const opencodeUserCommands = await scanCommandDirectory(opencodeUserDir);
|
|
7840
8820
|
addCommands(opencodeUserCommands);
|
|
7841
8821
|
}
|
|
7842
8822
|
if (homeDir) {
|
|
7843
|
-
const claudeUserDir =
|
|
8823
|
+
const claudeUserDir = path5.join(homeDir, ".claude", "commands");
|
|
7844
8824
|
const claudeUserCommands = await scanCommandDirectory(claudeUserDir);
|
|
7845
8825
|
addCommands(claudeUserCommands);
|
|
7846
8826
|
}
|
|
7847
8827
|
if (homeDir) {
|
|
7848
|
-
const mastraUserDir =
|
|
8828
|
+
const mastraUserDir = path5.join(homeDir, ".mastracode", "commands");
|
|
7849
8829
|
const mastraUserCommands = await scanCommandDirectory(mastraUserDir);
|
|
7850
8830
|
addCommands(mastraUserCommands);
|
|
7851
8831
|
}
|
|
7852
8832
|
if (projectDir) {
|
|
7853
|
-
const opencodeProjectDir =
|
|
8833
|
+
const opencodeProjectDir = path5.join(projectDir, ".opencode", "command");
|
|
7854
8834
|
const opencodeProjectCommands = await scanCommandDirectory(opencodeProjectDir);
|
|
7855
8835
|
addCommands(opencodeProjectCommands);
|
|
7856
8836
|
}
|
|
7857
8837
|
if (projectDir) {
|
|
7858
|
-
const claudeProjectDir =
|
|
8838
|
+
const claudeProjectDir = path5.join(projectDir, ".claude", "commands");
|
|
7859
8839
|
const claudeProjectCommands = await scanCommandDirectory(claudeProjectDir);
|
|
7860
8840
|
addCommands(claudeProjectCommands);
|
|
7861
8841
|
}
|
|
7862
8842
|
if (projectDir) {
|
|
7863
|
-
const mastraProjectDir =
|
|
8843
|
+
const mastraProjectDir = path5.join(projectDir, ".mastracode", "commands");
|
|
7864
8844
|
const mastraProjectCommands = await scanCommandDirectory(mastraProjectDir);
|
|
7865
8845
|
addCommands(mastraProjectCommands);
|
|
7866
8846
|
}
|
|
@@ -7975,11 +8955,13 @@ function setupKeyboardShortcuts(state, callbacks) {
|
|
|
7975
8955
|
state.pendingApprovalDismiss();
|
|
7976
8956
|
state.activeInlinePlanApproval = void 0;
|
|
7977
8957
|
state.activeInlineQuestion = void 0;
|
|
8958
|
+
state.pendingInlineQuestions.length = 0;
|
|
7978
8959
|
state.userInitiatedAbort = true;
|
|
7979
8960
|
state.harness.abort();
|
|
7980
8961
|
} else if (state.harness.isRunning()) {
|
|
7981
8962
|
state.activeInlinePlanApproval = void 0;
|
|
7982
8963
|
state.activeInlineQuestion = void 0;
|
|
8964
|
+
state.pendingInlineQuestions.length = 0;
|
|
7983
8965
|
state.userInitiatedAbort = true;
|
|
7984
8966
|
state.harness.abort();
|
|
7985
8967
|
} else {
|
|
@@ -7991,6 +8973,26 @@ function setupKeyboardShortcuts(state, callbacks) {
|
|
|
7991
8973
|
state.ui.requestRender();
|
|
7992
8974
|
}
|
|
7993
8975
|
});
|
|
8976
|
+
state.editor.onAction("suspend", () => {
|
|
8977
|
+
if (process.platform === "win32") {
|
|
8978
|
+
showInfo(state, "Suspend is not supported on Windows");
|
|
8979
|
+
return;
|
|
8980
|
+
}
|
|
8981
|
+
state.ui.stop();
|
|
8982
|
+
const onContinue = () => {
|
|
8983
|
+
state.ui.start();
|
|
8984
|
+
state.ui.requestRender();
|
|
8985
|
+
};
|
|
8986
|
+
process.once("SIGCONT", onContinue);
|
|
8987
|
+
try {
|
|
8988
|
+
process.kill(process.pid, "SIGTSTP");
|
|
8989
|
+
} catch {
|
|
8990
|
+
process.off("SIGCONT", onContinue);
|
|
8991
|
+
state.ui.start();
|
|
8992
|
+
state.ui.requestRender();
|
|
8993
|
+
showError(state, "Unable to suspend in the current terminal");
|
|
8994
|
+
}
|
|
8995
|
+
});
|
|
7994
8996
|
state.editor.onAction("undo", () => {
|
|
7995
8997
|
if (state.lastClearedText && state.editor.getText().length === 0) {
|
|
7996
8998
|
state.editor.setText(state.lastClearedText);
|
|
@@ -8105,9 +9107,10 @@ function detectFdPath() {
|
|
|
8105
9107
|
function setupAutocomplete(state) {
|
|
8106
9108
|
const slashCommands = [
|
|
8107
9109
|
{ name: "new", description: "Start a new thread" },
|
|
9110
|
+
{ name: "clone", description: "Clone the current thread" },
|
|
8108
9111
|
{ name: "threads", description: "Switch between threads" },
|
|
8109
|
-
{ name: "models", description: "
|
|
8110
|
-
{ name: "
|
|
9112
|
+
{ name: "models", description: "Switch model pack" },
|
|
9113
|
+
{ name: "custom-providers", description: "Manage custom providers and models" },
|
|
8111
9114
|
{ name: "subagents", description: "Configure subagent model defaults" },
|
|
8112
9115
|
{ name: "om", description: "Configure Observational Memory models" },
|
|
8113
9116
|
{ name: "think", description: "Set thinking (off|low|medium|high|xhigh|status)" },
|
|
@@ -8144,6 +9147,7 @@ function setupAutocomplete(state) {
|
|
|
8144
9147
|
description: "Toggle YOLO mode (auto-approve all tools)"
|
|
8145
9148
|
},
|
|
8146
9149
|
{ name: "review", description: "Review a GitHub pull request" },
|
|
9150
|
+
{ name: "report-issue", description: "Open or browse mastracode issues" },
|
|
8147
9151
|
{ name: "setup", description: "Re-run the setup wizard" },
|
|
8148
9152
|
{ name: "theme", description: "Switch color theme (auto/dark/light)" },
|
|
8149
9153
|
{ name: "exit", description: "Exit the TUI" },
|
|
@@ -8190,6 +9194,9 @@ function setupKeyHandlers(state, callbacks) {
|
|
|
8190
9194
|
if (state.pendingApprovalDismiss) {
|
|
8191
9195
|
state.pendingApprovalDismiss();
|
|
8192
9196
|
}
|
|
9197
|
+
state.activeInlinePlanApproval = void 0;
|
|
9198
|
+
state.activeInlineQuestion = void 0;
|
|
9199
|
+
state.pendingInlineQuestions.length = 0;
|
|
8193
9200
|
state.userInitiatedAbort = true;
|
|
8194
9201
|
state.harness.abort();
|
|
8195
9202
|
});
|
|
@@ -8227,23 +9234,37 @@ async function promptForThreadSelection(state) {
|
|
|
8227
9234
|
return;
|
|
8228
9235
|
}
|
|
8229
9236
|
const sortedThreads = [...threads].sort((a, b) => b.updatedAt.getTime() - a.updatedAt.getTime());
|
|
8230
|
-
|
|
8231
|
-
|
|
8232
|
-
|
|
8233
|
-
|
|
8234
|
-
|
|
9237
|
+
if (sortedThreads.length === 1) {
|
|
9238
|
+
const thread = sortedThreads[0];
|
|
9239
|
+
try {
|
|
9240
|
+
await state.harness.switchThread({ threadId: thread.id });
|
|
9241
|
+
if (!thread.metadata?.projectPath) {
|
|
9242
|
+
await state.harness.setThreadSetting({ key: "projectPath", value: currentPath });
|
|
9243
|
+
}
|
|
9244
|
+
return;
|
|
9245
|
+
} catch (error) {
|
|
9246
|
+
if (error instanceof ThreadLockError) {
|
|
9247
|
+
state.pendingNewThread = true;
|
|
9248
|
+
return;
|
|
9249
|
+
}
|
|
9250
|
+
throw error;
|
|
8235
9251
|
}
|
|
8236
|
-
}
|
|
8237
|
-
|
|
8238
|
-
|
|
8239
|
-
state.
|
|
8240
|
-
|
|
8241
|
-
|
|
8242
|
-
}
|
|
9252
|
+
}
|
|
9253
|
+
for (const thread of sortedThreads) {
|
|
9254
|
+
try {
|
|
9255
|
+
await state.harness.switchThread({ threadId: thread.id });
|
|
9256
|
+
if (!thread.metadata?.projectPath) {
|
|
9257
|
+
await state.harness.setThreadSetting({ key: "projectPath", value: currentPath });
|
|
9258
|
+
}
|
|
8243
9259
|
return;
|
|
9260
|
+
} catch (error) {
|
|
9261
|
+
if (error instanceof ThreadLockError) {
|
|
9262
|
+
continue;
|
|
9263
|
+
}
|
|
9264
|
+
throw error;
|
|
8244
9265
|
}
|
|
8245
|
-
throw error;
|
|
8246
9266
|
}
|
|
9267
|
+
state.pendingNewThread = true;
|
|
8247
9268
|
}
|
|
8248
9269
|
async function renderExistingTasks(state) {
|
|
8249
9270
|
try {
|
|
@@ -8307,6 +9328,38 @@ async function handleShellPassthrough(state, command) {
|
|
|
8307
9328
|
showError(state, error instanceof Error ? error.message : "Shell command failed");
|
|
8308
9329
|
}
|
|
8309
9330
|
}
|
|
9331
|
+
function getClipboardText() {
|
|
9332
|
+
try {
|
|
9333
|
+
if (process.platform === "darwin") {
|
|
9334
|
+
const text = execSync("pbpaste", {
|
|
9335
|
+
encoding: "utf-8",
|
|
9336
|
+
timeout: 3e3,
|
|
9337
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
9338
|
+
});
|
|
9339
|
+
return text.length > 0 ? text : null;
|
|
9340
|
+
}
|
|
9341
|
+
if (process.platform === "linux") {
|
|
9342
|
+
try {
|
|
9343
|
+
const text = execSync("xclip -selection clipboard -o", {
|
|
9344
|
+
encoding: "utf-8",
|
|
9345
|
+
timeout: 3e3,
|
|
9346
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
9347
|
+
});
|
|
9348
|
+
return text.length > 0 ? text : null;
|
|
9349
|
+
} catch {
|
|
9350
|
+
const text = execSync("wl-paste", {
|
|
9351
|
+
encoding: "utf-8",
|
|
9352
|
+
timeout: 3e3,
|
|
9353
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
9354
|
+
});
|
|
9355
|
+
return text.length > 0 ? text : null;
|
|
9356
|
+
}
|
|
9357
|
+
}
|
|
9358
|
+
return null;
|
|
9359
|
+
} catch {
|
|
9360
|
+
return null;
|
|
9361
|
+
}
|
|
9362
|
+
}
|
|
8310
9363
|
function getClipboardImage() {
|
|
8311
9364
|
try {
|
|
8312
9365
|
if (process.platform === "darwin") {
|
|
@@ -8469,6 +9522,22 @@ var CustomEditor = class extends Editor {
|
|
|
8469
9522
|
return;
|
|
8470
9523
|
}
|
|
8471
9524
|
}
|
|
9525
|
+
if (matchesKey(data, "ctrl+v")) {
|
|
9526
|
+
if (this.onImagePaste) {
|
|
9527
|
+
const clipboardImage = getClipboardImage();
|
|
9528
|
+
if (clipboardImage) {
|
|
9529
|
+
this.onImagePaste(clipboardImage);
|
|
9530
|
+
return;
|
|
9531
|
+
}
|
|
9532
|
+
}
|
|
9533
|
+
const clipboardText = getClipboardText();
|
|
9534
|
+
if (clipboardText) {
|
|
9535
|
+
const syntheticPaste = `${PASTE_START}${clipboardText}${PASTE_END}`;
|
|
9536
|
+
super.handleInput(syntheticPaste);
|
|
9537
|
+
return;
|
|
9538
|
+
}
|
|
9539
|
+
return;
|
|
9540
|
+
}
|
|
8472
9541
|
if (matchesKey(data, "ctrl+c")) {
|
|
8473
9542
|
const handler = this.actionHandlers.get("clear");
|
|
8474
9543
|
if (handler) {
|
|
@@ -8491,6 +9560,13 @@ var CustomEditor = class extends Editor {
|
|
|
8491
9560
|
return;
|
|
8492
9561
|
}
|
|
8493
9562
|
if (matchesKey(data, "ctrl+z")) {
|
|
9563
|
+
const handler = this.actionHandlers.get("suspend");
|
|
9564
|
+
if (handler) {
|
|
9565
|
+
handler();
|
|
9566
|
+
return;
|
|
9567
|
+
}
|
|
9568
|
+
}
|
|
9569
|
+
if (matchesKey(data, "alt+z")) {
|
|
8494
9570
|
const handler = this.actionHandlers.get("undo");
|
|
8495
9571
|
if (handler) {
|
|
8496
9572
|
handler();
|
|
@@ -8573,11 +9649,12 @@ function createTUIState(options) {
|
|
|
8573
9649
|
pendingSubagents: /* @__PURE__ */ new Map(),
|
|
8574
9650
|
toolOutputExpanded: false,
|
|
8575
9651
|
hideThinkingBlock: true,
|
|
9652
|
+
quietMode: false,
|
|
8576
9653
|
// Thread / conversation
|
|
8577
9654
|
pendingNewThread: false,
|
|
8578
|
-
pendingLockConflict: null,
|
|
8579
9655
|
// Inline interaction
|
|
8580
9656
|
lastClearedText: "",
|
|
9657
|
+
pendingInlineQuestions: [],
|
|
8581
9658
|
followUpComponents: [],
|
|
8582
9659
|
pendingSlashCommands: [],
|
|
8583
9660
|
pendingApprovalDismiss: null,
|
|
@@ -8594,11 +9671,15 @@ function createTUIState(options) {
|
|
|
8594
9671
|
}
|
|
8595
9672
|
|
|
8596
9673
|
// src/tui/mastra-tui.ts
|
|
9674
|
+
var UPDATE_RECHECK_INTERVAL_MS = 45 * 60 * 1e3;
|
|
8597
9675
|
var MastraTUI = class _MastraTUI {
|
|
8598
9676
|
state;
|
|
9677
|
+
updateCheckTimer = null;
|
|
8599
9678
|
static DOUBLE_CTRL_C_MS = 500;
|
|
8600
9679
|
constructor(options) {
|
|
8601
9680
|
this.state = createTUIState(options);
|
|
9681
|
+
const savedSettings = loadSettings();
|
|
9682
|
+
this.state.quietMode = savedSettings.preferences.quietMode;
|
|
8602
9683
|
const originalHandleInput = this.state.editor.handleInput.bind(this.state.editor);
|
|
8603
9684
|
this.state.editor.handleInput = (data) => {
|
|
8604
9685
|
if (this.state.activeInlinePlanApproval) {
|
|
@@ -8701,7 +9782,8 @@ var MastraTUI = class _MastraTUI {
|
|
|
8701
9782
|
* Errors are handled via harness events.
|
|
8702
9783
|
*/
|
|
8703
9784
|
fireMessage(content, images) {
|
|
8704
|
-
|
|
9785
|
+
const files = images?.map((img) => ({ data: img.data, mediaType: img.mimeType }));
|
|
9786
|
+
this.state.harness.sendMessage({ content, files }).catch((error) => {
|
|
8705
9787
|
showError(this.state, error instanceof Error ? error.message : "Unknown error");
|
|
8706
9788
|
});
|
|
8707
9789
|
}
|
|
@@ -8714,6 +9796,10 @@ var MastraTUI = class _MastraTUI {
|
|
|
8714
9796
|
hookMgr.runSessionEnd().catch(() => {
|
|
8715
9797
|
});
|
|
8716
9798
|
}
|
|
9799
|
+
if (this.updateCheckTimer) {
|
|
9800
|
+
clearInterval(this.updateCheckTimer);
|
|
9801
|
+
this.updateCheckTimer = null;
|
|
9802
|
+
}
|
|
8717
9803
|
if (this.state.unsubscribe) {
|
|
8718
9804
|
this.state.unsubscribe();
|
|
8719
9805
|
}
|
|
@@ -8745,12 +9831,16 @@ var MastraTUI = class _MastraTUI {
|
|
|
8745
9831
|
await renderExistingMessages(this.state);
|
|
8746
9832
|
await renderExistingTasks(this.state);
|
|
8747
9833
|
await this.checkClaudeMaxOAuthWarning();
|
|
8748
|
-
if (this.
|
|
8749
|
-
this.showThreadLockPrompt(this.state.pendingLockConflict.threadTitle, this.state.pendingLockConflict.ownerPid);
|
|
8750
|
-
this.state.pendingLockConflict = null;
|
|
8751
|
-
} else if (this.shouldShowOnboarding()) {
|
|
9834
|
+
if (this.shouldShowOnboarding()) {
|
|
8752
9835
|
await this.showOnboarding();
|
|
8753
9836
|
}
|
|
9837
|
+
await this.checkForUpdate();
|
|
9838
|
+
this.updateCheckTimer = setInterval(() => {
|
|
9839
|
+
void this.checkForUpdate(
|
|
9840
|
+
/* passive */
|
|
9841
|
+
true
|
|
9842
|
+
);
|
|
9843
|
+
}, UPDATE_RECHECK_INTERVAL_MS);
|
|
8754
9844
|
}
|
|
8755
9845
|
async refreshModelAuthStatus() {
|
|
8756
9846
|
this.state.modelAuthStatus = await this.state.harness.getCurrentModelAuthStatus();
|
|
@@ -8769,11 +9859,60 @@ var MastraTUI = class _MastraTUI {
|
|
|
8769
9859
|
}
|
|
8770
9860
|
async handleEvent(event) {
|
|
8771
9861
|
await dispatchEvent(event, this.getEventContext(), this.state);
|
|
9862
|
+
if (event.type === "thread_created") {
|
|
9863
|
+
await this.syncThreadActivePackMetadata(event.thread);
|
|
9864
|
+
} else if (event.type === "thread_changed") {
|
|
9865
|
+
await this.syncThreadActivePackMetadata();
|
|
9866
|
+
}
|
|
8772
9867
|
if (event.type === "agent_end") {
|
|
8773
9868
|
const stopReason = event.reason === "aborted" ? "aborted" : event.reason === "error" ? "error" : "complete";
|
|
8774
9869
|
await this.runStopHook(stopReason);
|
|
8775
9870
|
}
|
|
8776
9871
|
}
|
|
9872
|
+
async buildProviderAccess() {
|
|
9873
|
+
const models = await this.state.harness.listAvailableModels();
|
|
9874
|
+
const hasEnv = (provider) => models.some((m) => m.provider === provider && m.hasApiKey);
|
|
9875
|
+
const accessLevel = (provider, oauthId) => {
|
|
9876
|
+
if (this.state.authStorage?.isLoggedIn(oauthId)) return "oauth";
|
|
9877
|
+
if (hasEnv(provider)) return "apikey";
|
|
9878
|
+
return false;
|
|
9879
|
+
};
|
|
9880
|
+
const access = {
|
|
9881
|
+
anthropic: accessLevel("anthropic", "anthropic"),
|
|
9882
|
+
openai: accessLevel("openai", "openai-codex"),
|
|
9883
|
+
cerebras: hasEnv("cerebras") ? "apikey" : false,
|
|
9884
|
+
google: hasEnv("google") ? "apikey" : false,
|
|
9885
|
+
deepseek: hasEnv("deepseek") ? "apikey" : false
|
|
9886
|
+
};
|
|
9887
|
+
const seen = new Set(Object.keys(access));
|
|
9888
|
+
for (const m of models) {
|
|
9889
|
+
if (!seen.has(m.provider) && m.hasApiKey) {
|
|
9890
|
+
access[m.provider] = "apikey";
|
|
9891
|
+
seen.add(m.provider);
|
|
9892
|
+
}
|
|
9893
|
+
}
|
|
9894
|
+
return access;
|
|
9895
|
+
}
|
|
9896
|
+
async syncThreadActivePackMetadata(thread) {
|
|
9897
|
+
const settings = loadSettings();
|
|
9898
|
+
const currentThreadId = this.state.harness.getCurrentThreadId();
|
|
9899
|
+
if (!currentThreadId) return;
|
|
9900
|
+
const resolvedThread = thread?.id === currentThreadId ? thread : (await this.state.harness.listThreads()).find((t) => t.id === currentThreadId);
|
|
9901
|
+
const access = await this.buildProviderAccess();
|
|
9902
|
+
const packs = getAvailableModePacks(access, settings.customModelPacks).filter((p) => p.id !== "custom");
|
|
9903
|
+
const resolvedPackId = resolveThreadActiveModelPackId(
|
|
9904
|
+
settings,
|
|
9905
|
+
packs,
|
|
9906
|
+
resolvedThread?.metadata
|
|
9907
|
+
);
|
|
9908
|
+
if (resolvedPackId && settings.models.activeModelPackId !== resolvedPackId) {
|
|
9909
|
+
const fresh = loadSettings();
|
|
9910
|
+
if (fresh.models.activeModelPackId !== resolvedPackId) {
|
|
9911
|
+
fresh.models.activeModelPackId = resolvedPackId;
|
|
9912
|
+
saveSettings(fresh);
|
|
9913
|
+
}
|
|
9914
|
+
}
|
|
9915
|
+
}
|
|
8777
9916
|
showHookWarnings(event, warnings) {
|
|
8778
9917
|
for (const warning of warnings) {
|
|
8779
9918
|
showInfo(this.state, `[${event}] ${warning}`);
|
|
@@ -8839,42 +9978,6 @@ var MastraTUI = class _MastraTUI {
|
|
|
8839
9978
|
};
|
|
8840
9979
|
});
|
|
8841
9980
|
}
|
|
8842
|
-
/**
|
|
8843
|
-
* Show an inline prompt when a thread is locked by another process.
|
|
8844
|
-
* User can create a new thread (y) or exit (n).
|
|
8845
|
-
*/
|
|
8846
|
-
showThreadLockPrompt(threadTitle, ownerPid) {
|
|
8847
|
-
const questionComponent = new AskQuestionInlineComponent(
|
|
8848
|
-
{
|
|
8849
|
-
question: `Thread "${threadTitle}" is locked by pid ${ownerPid}. Create a new thread?`,
|
|
8850
|
-
options: [
|
|
8851
|
-
{ label: "Yes", description: "Start a new thread" },
|
|
8852
|
-
{ label: "No", description: "Exit" }
|
|
8853
|
-
],
|
|
8854
|
-
formatResult: (answer) => answer === "Yes" ? "Thread created" : "Exiting.",
|
|
8855
|
-
onSubmit: async (answer) => {
|
|
8856
|
-
this.state.activeInlineQuestion = void 0;
|
|
8857
|
-
if (answer.toLowerCase().startsWith("y")) {
|
|
8858
|
-
if (this.shouldShowOnboarding()) {
|
|
8859
|
-
await this.showOnboarding();
|
|
8860
|
-
}
|
|
8861
|
-
} else {
|
|
8862
|
-
process.exit(0);
|
|
8863
|
-
}
|
|
8864
|
-
},
|
|
8865
|
-
onCancel: () => {
|
|
8866
|
-
this.state.activeInlineQuestion = void 0;
|
|
8867
|
-
process.exit(0);
|
|
8868
|
-
}
|
|
8869
|
-
},
|
|
8870
|
-
this.state.ui
|
|
8871
|
-
);
|
|
8872
|
-
this.state.activeInlineQuestion = questionComponent;
|
|
8873
|
-
this.state.chatContainer.addChild(questionComponent);
|
|
8874
|
-
this.state.chatContainer.addChild(new Spacer(1));
|
|
8875
|
-
this.state.ui.requestRender();
|
|
8876
|
-
this.state.chatContainer.invalidate();
|
|
8877
|
-
}
|
|
8878
9981
|
/**
|
|
8879
9982
|
* One-time startup check: if the user has Anthropic OAuth credentials and
|
|
8880
9983
|
* hasn't yet acknowledged the Claude Max ToS warning, show it now.
|
|
@@ -9016,23 +10119,7 @@ var MastraTUI = class _MastraTUI {
|
|
|
9016
10119
|
value: p.id,
|
|
9017
10120
|
loggedIn: this.state.authStorage?.isLoggedIn(p.id) ?? false
|
|
9018
10121
|
}));
|
|
9019
|
-
const
|
|
9020
|
-
const models = await this.state.harness.listAvailableModels();
|
|
9021
|
-
const hasEnv = (provider) => models.some((m) => m.provider === provider && m.hasApiKey);
|
|
9022
|
-
const accessLevel = (provider, oauthId) => {
|
|
9023
|
-
if (this.state.authStorage?.isLoggedIn(oauthId)) return "oauth";
|
|
9024
|
-
if (hasEnv(provider)) return "apikey";
|
|
9025
|
-
return false;
|
|
9026
|
-
};
|
|
9027
|
-
return {
|
|
9028
|
-
anthropic: accessLevel("anthropic", "anthropic"),
|
|
9029
|
-
openai: accessLevel("openai", "openai-codex"),
|
|
9030
|
-
cerebras: hasEnv("cerebras") ? "apikey" : false,
|
|
9031
|
-
google: hasEnv("google") ? "apikey" : false,
|
|
9032
|
-
deepseek: hasEnv("deepseek") ? "apikey" : false
|
|
9033
|
-
};
|
|
9034
|
-
};
|
|
9035
|
-
const access = await buildAccess();
|
|
10122
|
+
const access = await this.buildProviderAccess();
|
|
9036
10123
|
const hasProviderAccess = Object.values(access).some(Boolean);
|
|
9037
10124
|
const savedSettings = loadSettings();
|
|
9038
10125
|
const modePacks = getAvailableModePacks(access, savedSettings.customModelPacks);
|
|
@@ -9077,7 +10164,7 @@ var MastraTUI = class _MastraTUI {
|
|
|
9077
10164
|
}
|
|
9078
10165
|
this.performLogin(providerId).then(async () => {
|
|
9079
10166
|
try {
|
|
9080
|
-
const updatedAccess = await
|
|
10167
|
+
const updatedAccess = await this.buildProviderAccess();
|
|
9081
10168
|
const updatedHasAccess = Object.values(updatedAccess).some(Boolean);
|
|
9082
10169
|
component.updateModePacks(getAvailableModePacks(updatedAccess, savedSettings.customModelPacks));
|
|
9083
10170
|
component.updateOmPacks(getAvailableOmPacks(updatedAccess));
|
|
@@ -9158,30 +10245,32 @@ var MastraTUI = class _MastraTUI {
|
|
|
9158
10245
|
settings.onboarding.completedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
9159
10246
|
settings.onboarding.skippedAt = null;
|
|
9160
10247
|
settings.onboarding.version = ONBOARDING_VERSION;
|
|
9161
|
-
settings.onboarding.modePackId = modePack.id;
|
|
9162
10248
|
settings.onboarding.omPackId = omPack.id;
|
|
9163
10249
|
const modeDefaults = {};
|
|
9164
10250
|
for (const mode of modes) {
|
|
9165
10251
|
const modelId = modePack.models[mode.id];
|
|
9166
10252
|
if (modelId) modeDefaults[mode.id] = modelId;
|
|
9167
10253
|
}
|
|
9168
|
-
|
|
9169
|
-
|
|
9170
|
-
const
|
|
10254
|
+
let activeModePackId = modePack.id;
|
|
10255
|
+
if (modePack.id === "custom" || modePack.id.startsWith("custom:")) {
|
|
10256
|
+
const customName = modePack.id === "custom" ? modePack.name?.trim() || "Custom" : modePack.id.slice("custom:".length) || "Custom";
|
|
10257
|
+
activeModePackId = `custom:${customName}`;
|
|
10258
|
+
const entry = { name: customName, models: modeDefaults, createdAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
10259
|
+
const idx = settings.customModelPacks.findIndex((p) => p.name === customName);
|
|
9171
10260
|
if (idx >= 0) {
|
|
9172
10261
|
settings.customModelPacks[idx] = entry;
|
|
9173
10262
|
} else {
|
|
9174
10263
|
settings.customModelPacks.push(entry);
|
|
9175
10264
|
}
|
|
9176
|
-
settings.models.activeModelPackId = "custom:Setup";
|
|
9177
|
-
settings.models.modeDefaults = modeDefaults;
|
|
9178
|
-
} else if (modePack.id.startsWith("custom:")) {
|
|
9179
|
-
settings.models.activeModelPackId = modePack.id;
|
|
9180
10265
|
settings.models.modeDefaults = modeDefaults;
|
|
9181
10266
|
} else {
|
|
9182
|
-
settings.models.activeModelPackId = modePack.id;
|
|
9183
10267
|
settings.models.modeDefaults = {};
|
|
9184
10268
|
}
|
|
10269
|
+
settings.onboarding.modePackId = activeModePackId;
|
|
10270
|
+
settings.models.activeModelPackId = activeModePackId;
|
|
10271
|
+
if (harness.getCurrentThreadId()) {
|
|
10272
|
+
await harness.setThreadSetting({ key: THREAD_ACTIVE_MODEL_PACK_ID_KEY, value: activeModePackId });
|
|
10273
|
+
}
|
|
9185
10274
|
settings.models.activeOmPackId = omPack.id;
|
|
9186
10275
|
settings.models.omModelOverride = omPack.id === "custom" ? omPack.modelId : null;
|
|
9187
10276
|
settings.preferences.yolo = result.yolo;
|
|
@@ -9198,6 +10287,92 @@ var MastraTUI = class _MastraTUI {
|
|
|
9198
10287
|
}
|
|
9199
10288
|
return true;
|
|
9200
10289
|
}
|
|
10290
|
+
// ===========================================================================
|
|
10291
|
+
// Auto-Update
|
|
10292
|
+
// ===========================================================================
|
|
10293
|
+
/**
|
|
10294
|
+
* Check npm for a newer version and prompt the user to update.
|
|
10295
|
+
* - If the user previously dismissed this version, show a passive note instead.
|
|
10296
|
+
* - If the fetch fails or we're already up-to-date, silently return.
|
|
10297
|
+
* @param passive When true, only show an info message (used for periodic rechecks).
|
|
10298
|
+
*/
|
|
10299
|
+
async checkForUpdate(passive = false) {
|
|
10300
|
+
const currentVersion = this.state.options.version;
|
|
10301
|
+
if (!currentVersion) return;
|
|
10302
|
+
const latestVersion = await fetchLatestVersion();
|
|
10303
|
+
if (!latestVersion || !isNewerVersion(currentVersion, latestVersion)) return;
|
|
10304
|
+
const pm = await detectPackageManager();
|
|
10305
|
+
if (passive) {
|
|
10306
|
+
const cmd = getInstallCommand(pm);
|
|
10307
|
+
showInfo(
|
|
10308
|
+
this.state,
|
|
10309
|
+
`Update available: v${latestVersion} (current: v${currentVersion}). Run \`${cmd}\` to update.`
|
|
10310
|
+
);
|
|
10311
|
+
return;
|
|
10312
|
+
}
|
|
10313
|
+
const settings = loadSettings();
|
|
10314
|
+
if (settings.updateDismissedVersion && !isNewerVersion(settings.updateDismissedVersion, latestVersion)) {
|
|
10315
|
+
const cmd = getInstallCommand(pm);
|
|
10316
|
+
showInfo(
|
|
10317
|
+
this.state,
|
|
10318
|
+
`Update available: v${latestVersion} (current: v${currentVersion}). Run \`${cmd}\` to update.`
|
|
10319
|
+
);
|
|
10320
|
+
return;
|
|
10321
|
+
}
|
|
10322
|
+
await this.showUpdatePrompt(currentVersion, latestVersion, pm);
|
|
10323
|
+
}
|
|
10324
|
+
/**
|
|
10325
|
+
* Show an inline Y/N prompt offering to auto-update.
|
|
10326
|
+
*/
|
|
10327
|
+
showUpdatePrompt(currentVersion, latestVersion, pm) {
|
|
10328
|
+
return new Promise((resolve2) => {
|
|
10329
|
+
const questionComponent = new AskQuestionInlineComponent(
|
|
10330
|
+
{
|
|
10331
|
+
question: `A new version of Mastra Code is available: v${latestVersion} (current: v${currentVersion}). Would you like to update now?`,
|
|
10332
|
+
options: [
|
|
10333
|
+
{ label: "Yes", description: "Update and restart" },
|
|
10334
|
+
{ label: "No", description: "Skip this version" }
|
|
10335
|
+
],
|
|
10336
|
+
formatResult: (answer) => answer === "Yes" ? "Updating\u2026" : "Update skipped.",
|
|
10337
|
+
onSubmit: async (answer) => {
|
|
10338
|
+
this.state.activeInlineQuestion = void 0;
|
|
10339
|
+
if (answer === "Yes") {
|
|
10340
|
+
showInfo(this.state, `Updating to v${latestVersion}\u2026`);
|
|
10341
|
+
const ok = await runUpdate(pm, latestVersion);
|
|
10342
|
+
if (ok) {
|
|
10343
|
+
showInfo(this.state, `Updated to v${latestVersion}. Please restart Mastra Code.`);
|
|
10344
|
+
this.stop();
|
|
10345
|
+
process.exit(0);
|
|
10346
|
+
} else {
|
|
10347
|
+
const cmd = getInstallCommand(pm, latestVersion);
|
|
10348
|
+
showError(this.state, `Auto-update failed. Run \`${cmd}\` manually.`);
|
|
10349
|
+
}
|
|
10350
|
+
} else {
|
|
10351
|
+
const settings = loadSettings();
|
|
10352
|
+
settings.updateDismissedVersion = latestVersion;
|
|
10353
|
+
saveSettings(settings);
|
|
10354
|
+
const cmd = getInstallCommand(pm);
|
|
10355
|
+
showInfo(this.state, `Update skipped. Run \`${cmd}\` to update manually.`);
|
|
10356
|
+
}
|
|
10357
|
+
resolve2();
|
|
10358
|
+
},
|
|
10359
|
+
onCancel: () => {
|
|
10360
|
+
this.state.activeInlineQuestion = void 0;
|
|
10361
|
+
const settings = loadSettings();
|
|
10362
|
+
settings.updateDismissedVersion = latestVersion;
|
|
10363
|
+
saveSettings(settings);
|
|
10364
|
+
resolve2();
|
|
10365
|
+
}
|
|
10366
|
+
},
|
|
10367
|
+
this.state.ui
|
|
10368
|
+
);
|
|
10369
|
+
this.state.activeInlineQuestion = questionComponent;
|
|
10370
|
+
this.state.chatContainer.addChild(questionComponent);
|
|
10371
|
+
this.state.chatContainer.addChild(new Spacer(1));
|
|
10372
|
+
this.state.ui.requestRender();
|
|
10373
|
+
this.state.chatContainer.invalidate();
|
|
10374
|
+
});
|
|
10375
|
+
}
|
|
9201
10376
|
};
|
|
9202
10377
|
var LoginSelectorComponent = class extends Box {
|
|
9203
10378
|
listContainer;
|
|
@@ -9266,6 +10441,6 @@ var LoginSelectorComponent = class extends Box {
|
|
|
9266
10441
|
}
|
|
9267
10442
|
};
|
|
9268
10443
|
|
|
9269
|
-
export { AssistantMessageComponent, LoginDialogComponent, LoginSelectorComponent, MastraTUI, ModelSelectorComponent, OMProgressComponent, ToolExecutionComponentEnhanced, UserMessageComponent, createTUIState, detectTerminalTheme, formatOMStatus };
|
|
9270
|
-
//# sourceMappingURL=chunk-
|
|
9271
|
-
//# sourceMappingURL=chunk-
|
|
10444
|
+
export { AssistantMessageComponent, LoginDialogComponent, LoginSelectorComponent, MastraTUI, ModelSelectorComponent, OMProgressComponent, ToolExecutionComponentEnhanced, UserMessageComponent, createTUIState, detectTerminalTheme, formatOMStatus, getCurrentVersion };
|
|
10445
|
+
//# sourceMappingURL=chunk-YQNZ7DHQ.js.map
|
|
10446
|
+
//# sourceMappingURL=chunk-YQNZ7DHQ.js.map
|