@kolisachint/hoocode-agent 0.5.30 → 0.5.32
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 +234 -0
- package/dist/core/agent-selection-eval.d.ts +91 -0
- package/dist/core/agent-selection-eval.d.ts.map +1 -0
- package/dist/core/agent-selection-eval.js +186 -0
- package/dist/core/agent-selection-eval.js.map +1 -0
- package/dist/core/agent-session-runtime.d.ts +28 -0
- package/dist/core/agent-session-runtime.d.ts.map +1 -1
- package/dist/core/agent-session-runtime.js +61 -2
- package/dist/core/agent-session-runtime.js.map +1 -1
- package/dist/core/builtin-skills.d.ts +61 -0
- package/dist/core/builtin-skills.d.ts.map +1 -0
- package/dist/core/builtin-skills.js +106 -0
- package/dist/core/builtin-skills.js.map +1 -0
- package/dist/core/extensions/plugins/default-marketplace/.agents-plugin/marketplace.json +5 -1
- package/dist/core/extensions/plugins/trigger-judge.d.ts +42 -0
- package/dist/core/extensions/plugins/trigger-judge.d.ts.map +1 -0
- package/dist/core/extensions/plugins/trigger-judge.js +121 -0
- package/dist/core/extensions/plugins/trigger-judge.js.map +1 -0
- package/dist/core/external-tools.d.ts +67 -0
- package/dist/core/external-tools.d.ts.map +1 -0
- package/dist/core/external-tools.js +120 -0
- package/dist/core/external-tools.js.map +1 -0
- package/dist/core/keybindings.d.ts +102 -30
- package/dist/core/keybindings.d.ts.map +1 -1
- package/dist/core/keybindings.js +123 -29
- package/dist/core/keybindings.js.map +1 -1
- package/dist/core/mode-prompts.d.ts +15 -3
- package/dist/core/mode-prompts.d.ts.map +1 -1
- package/dist/core/mode-prompts.js +17 -29
- package/dist/core/mode-prompts.js.map +1 -1
- package/dist/core/settings-defaults.d.ts +1 -1
- package/dist/core/settings-defaults.d.ts.map +1 -1
- package/dist/core/settings-defaults.js +1 -1
- package/dist/core/settings-defaults.js.map +1 -1
- package/dist/core/settings-manager.d.ts +10 -3
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +19 -8
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/settings-types.d.ts +1 -1
- package/dist/core/settings-types.d.ts.map +1 -1
- package/dist/core/settings-types.js.map +1 -1
- package/dist/core/slash-commands.d.ts.map +1 -1
- package/dist/core/slash-commands.js +5 -1
- package/dist/core/slash-commands.js.map +1 -1
- package/dist/core/tool-output-view.d.ts +33 -0
- package/dist/core/tool-output-view.d.ts.map +1 -0
- package/dist/core/tool-output-view.js +31 -0
- package/dist/core/tool-output-view.js.map +1 -0
- package/dist/core/tools/edit.d.ts.map +1 -1
- package/dist/core/tools/edit.js +4 -0
- package/dist/core/tools/edit.js.map +1 -1
- package/dist/core/tools/propose-plugin.d.ts.map +1 -1
- package/dist/core/tools/propose-plugin.js +7 -7
- package/dist/core/tools/propose-plugin.js.map +1 -1
- package/dist/core/tools/subagent.d.ts +7 -1
- package/dist/core/tools/subagent.d.ts.map +1 -1
- package/dist/core/tools/subagent.js +12 -24
- package/dist/core/tools/subagent.js.map +1 -1
- package/dist/extensions/core/modes.d.ts.map +1 -1
- package/dist/extensions/core/modes.js +21 -23
- package/dist/extensions/core/modes.js.map +1 -1
- package/dist/extensions/core/scaffold.d.ts.map +1 -1
- package/dist/extensions/core/scaffold.js +28 -24
- package/dist/extensions/core/scaffold.js.map +1 -1
- package/dist/init-templates.generated.d.ts +3 -0
- package/dist/init-templates.generated.d.ts.map +1 -1
- package/dist/init-templates.generated.js +12 -0
- package/dist/init-templates.generated.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +14 -1
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/command-executor.d.ts +20 -0
- package/dist/modes/interactive/command-executor.d.ts.map +1 -1
- package/dist/modes/interactive/command-executor.js +138 -9
- package/dist/modes/interactive/command-executor.js.map +1 -1
- package/dist/modes/interactive/components/footer.d.ts +4 -0
- package/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/dist/modes/interactive/components/footer.js +31 -2
- package/dist/modes/interactive/components/footer.js.map +1 -1
- package/dist/modes/interactive/components/scoped-models-selector.d.ts +2 -2
- package/dist/modes/interactive/components/scoped-models-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/scoped-models-selector.js +2 -2
- package/dist/modes/interactive/components/scoped-models-selector.js.map +1 -1
- package/dist/modes/interactive/components/session-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/session-selector.js +2 -2
- package/dist/modes/interactive/components/session-selector.js.map +1 -1
- package/dist/modes/interactive/components/settings-selector.d.ts +10 -2
- package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/settings-selector.js +165 -23
- package/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/dist/modes/interactive/components/tool-chain-summary.d.ts +65 -0
- package/dist/modes/interactive/components/tool-chain-summary.d.ts.map +1 -0
- package/dist/modes/interactive/components/tool-chain-summary.js +216 -0
- package/dist/modes/interactive/components/tool-chain-summary.js.map +1 -0
- package/dist/modes/interactive/components/tool-chain.d.ts +61 -0
- package/dist/modes/interactive/components/tool-chain.d.ts.map +1 -0
- package/dist/modes/interactive/components/tool-chain.js +156 -0
- package/dist/modes/interactive/components/tool-chain.js.map +1 -0
- package/dist/modes/interactive/components/tool-execution.d.ts +55 -9
- package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/tool-execution.js +173 -33
- package/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/dist/modes/interactive/components/tool-signal.d.ts +50 -0
- package/dist/modes/interactive/components/tool-signal.d.ts.map +1 -0
- package/dist/modes/interactive/components/tool-signal.js +151 -0
- package/dist/modes/interactive/components/tool-signal.js.map +1 -0
- package/dist/modes/interactive/interactive-mode.d.ts +50 -1
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +244 -29
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/resource-display.d.ts +9 -0
- package/dist/modes/interactive/resource-display.d.ts.map +1 -1
- package/dist/modes/interactive/resource-display.js +13 -0
- package/dist/modes/interactive/resource-display.js.map +1 -1
- package/dist/utils/tools-manager.d.ts +30 -0
- package/dist/utils/tools-manager.d.ts.map +1 -1
- package/dist/utils/tools-manager.js +35 -0
- package/dist/utils/tools-manager.js.map +1 -1
- package/docs/modes.md +4 -0
- package/docs/plugins.md +10 -0
- package/docs/settings.md +42 -0
- package/docs/skills.md +30 -0
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/with-deps/package.json +1 -1
- package/package.json +5 -4
- package/templates/prompts/grill-bridge.md +1 -0
- package/templates/prompts/grill-me.md +7 -0
- package/templates/prompts/grill-plan.md +9 -0
- package/templates/prompts/task-background-agents.md +2 -0
- package/templates/prompts/task-background-none.md +1 -0
- package/templates/prompts/task-main.md +19 -0
- package/templates/skills/plugin-authoring/SKILL.md +81 -0
|
@@ -18,6 +18,7 @@ import { canvasSearchRoots, discoverCanvasExtensions } from "../../core/canvas/d
|
|
|
18
18
|
import { pluginCanvasExtensions } from "../../core/canvas/plugin-canvases.js";
|
|
19
19
|
import { gateCanvasExtensions } from "../../core/canvas/trust.js";
|
|
20
20
|
import { getWorkspacePlatforms, setPlatforms } from "../../core/extensions/plugins/formats/platform-targets.js";
|
|
21
|
+
import { describeExternalTools } from "../../core/external-tools.js";
|
|
21
22
|
import { FooterDataProvider } from "../../core/footer-data-provider.js";
|
|
22
23
|
import { formatDurationSecs } from "../../core/format-duration.js";
|
|
23
24
|
import { formatTokens } from "../../core/format-tokens.js";
|
|
@@ -28,6 +29,7 @@ import { SessionManager } from "../../core/session-manager.js";
|
|
|
28
29
|
import { BUILTIN_SLASH_COMMANDS } from "../../core/slash-commands.js";
|
|
29
30
|
import { startupProgress } from "../../core/startup-progress.js";
|
|
30
31
|
import { taskStore } from "../../core/task-store.js";
|
|
32
|
+
import { cycleToolOutputView } from "../../core/tool-output-view.js";
|
|
31
33
|
import { settleDanglingMainTasks } from "../../core/tools/todo.js";
|
|
32
34
|
import { buildCompactWordmark } from "../../core/wordmark.js";
|
|
33
35
|
import { extensionForImageMimeType, readClipboardImage } from "../../utils/clipboard-image.js";
|
|
@@ -52,6 +54,7 @@ import { SessionSelectorComponent } from "./components/session-selector.js";
|
|
|
52
54
|
import { SettingsSelectorComponent } from "./components/settings-selector.js";
|
|
53
55
|
import { SkillInvocationMessageComponent } from "./components/skill-invocation-message.js";
|
|
54
56
|
import { TaskPanelComponent } from "./components/task-panel.js";
|
|
57
|
+
import { ToolChainComponent } from "./components/tool-chain.js";
|
|
55
58
|
import { ToolExecutionComponent } from "./components/tool-execution.js";
|
|
56
59
|
import { TreeSelectorComponent } from "./components/tree-selector.js";
|
|
57
60
|
import { UserMessageComponent } from "./components/user-message.js";
|
|
@@ -196,8 +199,13 @@ export class InteractiveMode {
|
|
|
196
199
|
pendingTools = new Map();
|
|
197
200
|
// Tool output expansion state
|
|
198
201
|
toolOutputExpanded = false;
|
|
199
|
-
// Persisted
|
|
200
|
-
|
|
202
|
+
// Persisted view dial (radar / glance / full). See core/tool-output-view.ts.
|
|
203
|
+
toolOutputView = "glance";
|
|
204
|
+
// The chain currently collecting tool calls, if the agent is mid-run.
|
|
205
|
+
openChain;
|
|
206
|
+
// Whether the assistant message being streamed has said anything yet. The
|
|
207
|
+
// first words it speaks close the chain the previous message's calls built.
|
|
208
|
+
sawTextInCurrentMessage = false;
|
|
201
209
|
// Thinking block visibility state
|
|
202
210
|
hideThinkingBlock = false;
|
|
203
211
|
// Skill commands: command name -> skill file path
|
|
@@ -371,6 +379,7 @@ export class InteractiveMode {
|
|
|
371
379
|
this.footerDataProvider = new FooterDataProvider(this.sessionManager.getCwd());
|
|
372
380
|
this.footer = new FooterComponent(this.session, this.footerDataProvider);
|
|
373
381
|
this.footer.setAutoCompactEnabled(this.session.autoCompactionEnabled);
|
|
382
|
+
this.footer.setToolOutputView(this.toolOutputView);
|
|
374
383
|
this.footerDataProvider.setSubagentEnabled(this.session.getActiveToolNames().includes("Task"));
|
|
375
384
|
this.chrome = new ExtensionChrome({
|
|
376
385
|
ui: this.ui,
|
|
@@ -459,8 +468,8 @@ export class InteractiveMode {
|
|
|
459
468
|
this.taskPanel.onExitFocus = () => this.teamFocus.exitFocus();
|
|
460
469
|
// Load hide thinking block setting
|
|
461
470
|
this.hideThinkingBlock = this.settingsManager.getHideThinkingBlock();
|
|
462
|
-
// Load tool-output
|
|
463
|
-
this.
|
|
471
|
+
// Load the tool-output view dial
|
|
472
|
+
this.toolOutputView = this.settingsManager.getToolOutputView();
|
|
464
473
|
// Completion chime: rings the terminal bell when a long turn finishes or the
|
|
465
474
|
// agent blocks awaiting input. Enable is read fresh so a live toggle applies.
|
|
466
475
|
this.chime = new CompletionChime({
|
|
@@ -542,6 +551,14 @@ export class InteractiveMode {
|
|
|
542
551
|
}));
|
|
543
552
|
};
|
|
544
553
|
}
|
|
554
|
+
const cdCommand = slashCommands.find((command) => command.name === "cd");
|
|
555
|
+
if (cdCommand) {
|
|
556
|
+
cdCommand.argumentHint = "<path>";
|
|
557
|
+
cdCommand.getArgumentCompletions = (prefix) => {
|
|
558
|
+
const completions = this.commandExecutor.getChangeDirectoryCompletions(prefix);
|
|
559
|
+
return completions.length > 0 ? completions : null;
|
|
560
|
+
};
|
|
561
|
+
}
|
|
545
562
|
// Convert prompt templates to SlashCommand format for autocomplete
|
|
546
563
|
const templateCommands = this.session.promptTemplates.map((cmd) => ({
|
|
547
564
|
name: cmd.name,
|
|
@@ -657,7 +674,9 @@ export class InteractiveMode {
|
|
|
657
674
|
// Add header with keybindings from config (unless silenced)
|
|
658
675
|
if (this.options.verbose || !this.settingsManager.getQuietStartup()) {
|
|
659
676
|
const termW = this.ui.terminal.columns;
|
|
660
|
-
|
|
677
|
+
// A function, not a value: the banner names the working directory, and
|
|
678
|
+
// `/cd` moves it. `refreshBuiltInHeader` re-reads this after a move.
|
|
679
|
+
const logo = () => termW >= 40
|
|
661
680
|
? buildCompactWordmark({
|
|
662
681
|
appName: APP_NAME,
|
|
663
682
|
version: this.version,
|
|
@@ -681,10 +700,16 @@ export class InteractiveMode {
|
|
|
681
700
|
hint("app.thinking.cycle", "to cycle thinking level"),
|
|
682
701
|
rawKeyHint(`${keyText("app.model.cycleForward")}/${keyText("app.model.cycleBackward")}`, "to cycle models"),
|
|
683
702
|
hint("app.model.select", "to select model"),
|
|
684
|
-
hint("app.tools.expand", "to expand
|
|
703
|
+
hint("app.tools.expand", "to expand all tool output"),
|
|
704
|
+
hint("app.tools.unfoldOne", "to open one chain or block (repeat to peel back)"),
|
|
705
|
+
hint("app.view.cycleForward", "to cycle tool output (radar/glance/full)"),
|
|
685
706
|
hint("app.thinking.toggle", "to expand thinking"),
|
|
686
707
|
hint("app.tasks.cycleView", "to cycle task panel view"),
|
|
687
708
|
...(this.teamFocus.connected ? [hint("app.team.focus", "to focus team roster")] : []),
|
|
709
|
+
hint("app.mode.cycle", "to cycle agent mode"),
|
|
710
|
+
hint("app.session.changeDirectory", "to change working directory"),
|
|
711
|
+
hint("app.settings.open", "for settings"),
|
|
712
|
+
hint("app.hotkeys.open", "for all shortcuts"),
|
|
688
713
|
hint("app.editor.external", "for external editor"),
|
|
689
714
|
rawKeyHint("/", "for commands"),
|
|
690
715
|
rawKeyHint("!", "to run bash"),
|
|
@@ -695,7 +720,7 @@ export class InteractiveMode {
|
|
|
695
720
|
rawKeyHint("drop files", "to attach"),
|
|
696
721
|
].join("\n");
|
|
697
722
|
const onboarding = theme.fg("dim", `${APP_NAME} can explain its own features and look up its docs. Ask it how to use or extend ${APP_NAME}.`);
|
|
698
|
-
this.builtInHeader = new ExpandableText(() => logo, () => `${logo}\n${expandedInstructions}\n\n${onboarding}`, this.getStartupExpansionState(), 1, 0);
|
|
723
|
+
this.builtInHeader = new ExpandableText(() => logo(), () => `${logo()}\n${expandedInstructions}\n\n${onboarding}`, this.getStartupExpansionState(), 1, 0);
|
|
699
724
|
// Setup UI layout
|
|
700
725
|
this.headerContainer.addChild(this.builtInHeader);
|
|
701
726
|
}
|
|
@@ -1001,6 +1026,11 @@ export class InteractiveMode {
|
|
|
1001
1026
|
applyRuntimeSettings() {
|
|
1002
1027
|
this.footer.setSession(this.session);
|
|
1003
1028
|
this.footer.setAutoCompactEnabled(this.session.autoCompactionEnabled);
|
|
1029
|
+
this.footer.setToolOutputView(this.toolOutputView);
|
|
1030
|
+
// The startup banner names the cwd, which `/cd` changes under it.
|
|
1031
|
+
if (this.builtInHeader instanceof ExpandableText) {
|
|
1032
|
+
this.builtInHeader.refresh();
|
|
1033
|
+
}
|
|
1004
1034
|
this.footerDataProvider.setCwd(this.sessionManager.getCwd());
|
|
1005
1035
|
this.hideThinkingBlock = this.settingsManager.getHideThinkingBlock();
|
|
1006
1036
|
this.ui.setShowHardwareCursor(this.settingsManager.getShowHardwareCursor());
|
|
@@ -1047,6 +1077,7 @@ export class InteractiveMode {
|
|
|
1047
1077
|
*/
|
|
1048
1078
|
renderCurrentSessionState() {
|
|
1049
1079
|
this.chatContainer.clear();
|
|
1080
|
+
this.openChain = undefined;
|
|
1050
1081
|
this.pendingMessagesContainer.clear();
|
|
1051
1082
|
this.messageQueue.resetCompactionQueue();
|
|
1052
1083
|
this.streamingComponent = undefined;
|
|
@@ -1063,12 +1094,7 @@ export class InteractiveMode {
|
|
|
1063
1094
|
* later full rebuild (theme toggle / reload) restores full fidelity.
|
|
1064
1095
|
*/
|
|
1065
1096
|
trimTranscriptMemory() {
|
|
1066
|
-
const freezable =
|
|
1067
|
-
for (const child of this.chatContainer.children) {
|
|
1068
|
-
if (child instanceof ToolExecutionComponent && child.isFreezable()) {
|
|
1069
|
-
freezable.push(child);
|
|
1070
|
-
}
|
|
1071
|
-
}
|
|
1097
|
+
const freezable = this.transcriptToolBlocks().filter((block) => block.isFreezable());
|
|
1072
1098
|
const excess = freezable.length - LIVE_TOOL_WINDOW;
|
|
1073
1099
|
for (let i = 0; i < excess; i++) {
|
|
1074
1100
|
freezable[i].freeze();
|
|
@@ -1443,6 +1469,10 @@ export class InteractiveMode {
|
|
|
1443
1469
|
this.ui.onDebug = () => this.commandExecutor.handleDebug();
|
|
1444
1470
|
this.defaultEditor.onAction("app.model.select", () => this.modelController.showModelSelector());
|
|
1445
1471
|
this.defaultEditor.onAction("app.tools.expand", () => this.toggleToolOutputExpansion());
|
|
1472
|
+
this.defaultEditor.onAction("app.view.cycleForward", () => this.cycleToolOutputView("forward"));
|
|
1473
|
+
this.defaultEditor.onAction("app.view.cycleBackward", () => this.cycleToolOutputView("backward"));
|
|
1474
|
+
this.defaultEditor.onAction("app.tools.unfoldOne", () => this.stepToolOutput("unfold"));
|
|
1475
|
+
this.defaultEditor.onAction("app.tools.foldOne", () => this.stepToolOutput("fold"));
|
|
1446
1476
|
this.defaultEditor.onAction("app.thinking.toggle", () => this.toggleThinkingBlockVisibility());
|
|
1447
1477
|
this.defaultEditor.onAction("app.tasks.cycleView", () => {
|
|
1448
1478
|
this.taskPanel.cycleView();
|
|
@@ -1456,6 +1486,15 @@ export class InteractiveMode {
|
|
|
1456
1486
|
this.defaultEditor.onAction("app.session.tree", () => this.showTreeSelector());
|
|
1457
1487
|
this.defaultEditor.onAction("app.session.fork", () => this.showUserMessageSelector());
|
|
1458
1488
|
this.defaultEditor.onAction("app.session.resume", () => this.showSessionSelector());
|
|
1489
|
+
this.defaultEditor.onAction("app.session.changeDirectory", () => {
|
|
1490
|
+
// Prefill rather than act: a directory change needs a target, and the
|
|
1491
|
+
// editor's own path completion is already the best way to name one.
|
|
1492
|
+
this.editor.setText("/cd ");
|
|
1493
|
+
this.ui.requestRender();
|
|
1494
|
+
});
|
|
1495
|
+
this.defaultEditor.onAction("app.settings.open", () => this.showSettingsSelector());
|
|
1496
|
+
this.defaultEditor.onAction("app.hotkeys.open", () => this.commandExecutor.handleHotkeys());
|
|
1497
|
+
this.defaultEditor.onAction("app.mode.cycle", () => void this.cycleAgentMode());
|
|
1459
1498
|
this.defaultEditor.onChange = (text) => {
|
|
1460
1499
|
const wasBashMode = this.isBashMode;
|
|
1461
1500
|
this.isBashMode = text.trimStart().startsWith("!");
|
|
@@ -1644,6 +1683,13 @@ export class InteractiveMode {
|
|
|
1644
1683
|
clearEditor();
|
|
1645
1684
|
},
|
|
1646
1685
|
},
|
|
1686
|
+
"/cd": {
|
|
1687
|
+
withArgs: true,
|
|
1688
|
+
run: async (text) => {
|
|
1689
|
+
clearEditor();
|
|
1690
|
+
await this.commandExecutor.handleChangeDirectory(text);
|
|
1691
|
+
},
|
|
1692
|
+
},
|
|
1647
1693
|
"/quit": {
|
|
1648
1694
|
run: async () => {
|
|
1649
1695
|
clearEditor();
|
|
@@ -1742,6 +1788,10 @@ export class InteractiveMode {
|
|
|
1742
1788
|
if (this.chimePendingRetry || this.session.isStreaming || this.session.isCompacting) {
|
|
1743
1789
|
return;
|
|
1744
1790
|
}
|
|
1791
|
+
// The last chain of a turn has nothing after it to close it, so it
|
|
1792
|
+
// settles here. A turn that did not end cleanly leaves its chain in the
|
|
1793
|
+
// running rendering plus a marker rather than claiming an outcome.
|
|
1794
|
+
this.closeOpenChain(this.turnStopReason === "stop" ? "done" : "interrupted");
|
|
1745
1795
|
this.showTurnCost();
|
|
1746
1796
|
this.settleDanglingPlanItems();
|
|
1747
1797
|
this.chime?.onTurnComplete({ aborted: this.turnStopReason === "aborted" });
|
|
@@ -1893,6 +1943,7 @@ export class InteractiveMode {
|
|
|
1893
1943
|
else if (event.message.role === "assistant") {
|
|
1894
1944
|
this.streamingComponent = new AssistantMessageComponent(undefined, this.hideThinkingBlock, this.getMarkdownThemeWithSettings(), this.hiddenThinkingLabel);
|
|
1895
1945
|
this.streamingMessage = event.message;
|
|
1946
|
+
this.sawTextInCurrentMessage = false;
|
|
1896
1947
|
this.chatContainer.addChild(this.streamingComponent);
|
|
1897
1948
|
this.streamingComponent.updateContent(this.streamingMessage);
|
|
1898
1949
|
this.ui.requestRender();
|
|
@@ -1902,16 +1953,25 @@ export class InteractiveMode {
|
|
|
1902
1953
|
if (this.streamingComponent && event.message.role === "assistant") {
|
|
1903
1954
|
this.streamingMessage = event.message;
|
|
1904
1955
|
this.scheduleStreamingRender();
|
|
1956
|
+
// The agent speaking ends the run its previous calls formed. Tool
|
|
1957
|
+
// calls later in this same message open a fresh chain.
|
|
1958
|
+
if (!this.sawTextInCurrentMessage) {
|
|
1959
|
+
const spoke = this.streamingMessage.content.some((content) => content.type === "text" && content.text.trim() !== "");
|
|
1960
|
+
if (spoke) {
|
|
1961
|
+
this.sawTextInCurrentMessage = true;
|
|
1962
|
+
this.closeOpenChain("done");
|
|
1963
|
+
}
|
|
1964
|
+
}
|
|
1905
1965
|
for (const content of this.streamingMessage.content) {
|
|
1906
1966
|
if (content.type === "toolCall") {
|
|
1907
1967
|
if (!this.pendingTools.has(content.id)) {
|
|
1908
1968
|
const component = new ToolExecutionComponent(content.name, content.id, content.arguments, {
|
|
1909
1969
|
showImages: this.settingsManager.getShowImages(),
|
|
1910
1970
|
imageWidthCells: this.settingsManager.getImageWidthCells(),
|
|
1911
|
-
|
|
1971
|
+
view: this.toolOutputView,
|
|
1912
1972
|
}, this.getRegisteredToolDefinition(content.name), this.ui, this.sessionManager.getCwd());
|
|
1913
1973
|
component.setExpanded(this.toolOutputExpanded);
|
|
1914
|
-
this.
|
|
1974
|
+
this.attachToolBlock(component);
|
|
1915
1975
|
this.pendingTools.set(content.id, component);
|
|
1916
1976
|
}
|
|
1917
1977
|
else {
|
|
@@ -1975,10 +2035,10 @@ export class InteractiveMode {
|
|
|
1975
2035
|
component = new ToolExecutionComponent(event.toolName, event.toolCallId, event.args, {
|
|
1976
2036
|
showImages: this.settingsManager.getShowImages(),
|
|
1977
2037
|
imageWidthCells: this.settingsManager.getImageWidthCells(),
|
|
1978
|
-
|
|
2038
|
+
view: this.toolOutputView,
|
|
1979
2039
|
}, this.getRegisteredToolDefinition(event.toolName), this.ui, this.sessionManager.getCwd());
|
|
1980
2040
|
component.setExpanded(this.toolOutputExpanded);
|
|
1981
|
-
this.
|
|
2041
|
+
this.attachToolBlock(component);
|
|
1982
2042
|
this.pendingTools.set(event.toolCallId, component);
|
|
1983
2043
|
}
|
|
1984
2044
|
component.markExecutionStarted();
|
|
@@ -2255,9 +2315,18 @@ export class InteractiveMode {
|
|
|
2255
2315
|
this.footer.invalidate();
|
|
2256
2316
|
this.updateEditorBorderColor();
|
|
2257
2317
|
}
|
|
2318
|
+
let lastAssistantStopReason;
|
|
2258
2319
|
for (const message of sessionContext.messages) {
|
|
2259
2320
|
// Assistant messages need special handling for tool calls
|
|
2260
2321
|
if (message.role === "assistant") {
|
|
2322
|
+
// Same boundary the live path uses: the agent speaking ends the run
|
|
2323
|
+
// its previous calls formed. Rebuilding history has to reproduce it,
|
|
2324
|
+
// or a resumed session would show one chain where it lived through
|
|
2325
|
+
// several.
|
|
2326
|
+
const spoke = message.content.some((content) => content.type === "text" && content.text.trim() !== "");
|
|
2327
|
+
if (spoke)
|
|
2328
|
+
this.closeOpenChain("done");
|
|
2329
|
+
lastAssistantStopReason = message.stopReason;
|
|
2261
2330
|
this.addMessageToChat(message);
|
|
2262
2331
|
// Render tool call components
|
|
2263
2332
|
for (const content of message.content) {
|
|
@@ -2265,10 +2334,10 @@ export class InteractiveMode {
|
|
|
2265
2334
|
const component = new ToolExecutionComponent(content.name, content.id, content.arguments, {
|
|
2266
2335
|
showImages: this.settingsManager.getShowImages(),
|
|
2267
2336
|
imageWidthCells: this.settingsManager.getImageWidthCells(),
|
|
2268
|
-
|
|
2337
|
+
view: this.toolOutputView,
|
|
2269
2338
|
}, this.getRegisteredToolDefinition(content.name), this.ui, this.sessionManager.getCwd());
|
|
2270
2339
|
component.setExpanded(this.toolOutputExpanded);
|
|
2271
|
-
this.
|
|
2340
|
+
this.attachToolBlock(component);
|
|
2272
2341
|
if (message.stopReason === "aborted" || message.stopReason === "error") {
|
|
2273
2342
|
let errorMessage;
|
|
2274
2343
|
if (message.stopReason === "aborted") {
|
|
@@ -2302,6 +2371,13 @@ export class InteractiveMode {
|
|
|
2302
2371
|
this.addMessageToChat(message, options);
|
|
2303
2372
|
}
|
|
2304
2373
|
}
|
|
2374
|
+
// History has no live state: a chain left open at the end of the rebuild is
|
|
2375
|
+
// finished, and how the last assistant message ended says whether it
|
|
2376
|
+
// finished cleanly. Tool calls still awaiting results are the exception —
|
|
2377
|
+
// this is a resumed run in flight, so its chain stays open for them.
|
|
2378
|
+
if (renderedPendingTools.size === 0) {
|
|
2379
|
+
this.closeOpenChain(lastAssistantStopReason === "stop" ? "done" : "interrupted");
|
|
2380
|
+
}
|
|
2305
2381
|
for (const [toolCallId, component] of renderedPendingTools) {
|
|
2306
2382
|
this.pendingTools.set(toolCallId, component);
|
|
2307
2383
|
}
|
|
@@ -2528,9 +2604,124 @@ export class InteractiveMode {
|
|
|
2528
2604
|
this.showStatus(`Thinking level: ${newLevel}`);
|
|
2529
2605
|
}
|
|
2530
2606
|
}
|
|
2607
|
+
/**
|
|
2608
|
+
* Put a tool block into the transcript, extending the open chain or starting
|
|
2609
|
+
* one. Every path that renders a tool call comes through here — live
|
|
2610
|
+
* streaming, execution start, and rebuilding history — so a chain cannot be
|
|
2611
|
+
* assembled correctly in one place and forgotten in another.
|
|
2612
|
+
*/
|
|
2613
|
+
attachToolBlock(block) {
|
|
2614
|
+
if (!this.openChain || !this.openChain.isOpen) {
|
|
2615
|
+
this.openChain = new ToolChainComponent(this.toolOutputView);
|
|
2616
|
+
this.chatContainer.addChild(this.openChain);
|
|
2617
|
+
}
|
|
2618
|
+
this.openChain.add(block);
|
|
2619
|
+
}
|
|
2620
|
+
/**
|
|
2621
|
+
* Settle the open chain.
|
|
2622
|
+
*
|
|
2623
|
+
* Called when the agent speaks (the run it was doing is over, whatever comes
|
|
2624
|
+
* next is a new one) and when the turn settles. Closing on the agent's next
|
|
2625
|
+
* words rather than only at turn end is what keeps the flip cheap: the line
|
|
2626
|
+
* is still at the bottom of the screen, so the TUI rewrites it in place
|
|
2627
|
+
* instead of taking the full-redraw path that clears terminal scrollback.
|
|
2628
|
+
*/
|
|
2629
|
+
closeOpenChain(outcome) {
|
|
2630
|
+
if (!this.openChain)
|
|
2631
|
+
return;
|
|
2632
|
+
if (this.openChain.isEmpty) {
|
|
2633
|
+
this.chatContainer.removeChild(this.openChain);
|
|
2634
|
+
}
|
|
2635
|
+
else {
|
|
2636
|
+
this.openChain.close(outcome);
|
|
2637
|
+
}
|
|
2638
|
+
this.openChain = undefined;
|
|
2639
|
+
this.ui.requestRender();
|
|
2640
|
+
}
|
|
2641
|
+
/** Every tool block in the transcript, in order, across all chains. */
|
|
2642
|
+
transcriptToolBlocks() {
|
|
2643
|
+
const blocks = [];
|
|
2644
|
+
for (const child of this.chatContainer.children) {
|
|
2645
|
+
if (child instanceof ToolChainComponent)
|
|
2646
|
+
blocks.push(...child.toolBlocks);
|
|
2647
|
+
else if (child instanceof ToolExecutionComponent)
|
|
2648
|
+
blocks.push(child);
|
|
2649
|
+
}
|
|
2650
|
+
return blocks;
|
|
2651
|
+
}
|
|
2652
|
+
/** Every chain in the transcript, in order. */
|
|
2653
|
+
transcriptChains() {
|
|
2654
|
+
return this.chatContainer.children.filter((child) => child instanceof ToolChainComponent);
|
|
2655
|
+
}
|
|
2531
2656
|
toggleToolOutputExpansion() {
|
|
2532
2657
|
this.setToolsExpanded(!this.toolOutputExpanded);
|
|
2533
2658
|
}
|
|
2659
|
+
/**
|
|
2660
|
+
* Open or close one tool block instead of all of them.
|
|
2661
|
+
*
|
|
2662
|
+
* `ctrl+o` is all-or-nothing, which left the ▸ caret on every glance and
|
|
2663
|
+
* radar row advertising something no key could do. This is the missing half:
|
|
2664
|
+
* unfold walks back from the newest block to the first one still folded,
|
|
2665
|
+
* fold walks back to the most recently opened one.
|
|
2666
|
+
*
|
|
2667
|
+
* Working from the tail is not a shortcut, it is the only thing this view can
|
|
2668
|
+
* honestly offer. The transcript is bottom-anchored and has no app-level
|
|
2669
|
+
* scrolling — anything far enough up is in the terminal's own scrollback,
|
|
2670
|
+
* where this process cannot put a cursor or scroll to a selection. So the
|
|
2671
|
+
* key peels backwards through what is actually on screen, and each block it
|
|
2672
|
+
* opens is its own marker for where you have got to.
|
|
2673
|
+
*/
|
|
2674
|
+
stepToolOutput(direction) {
|
|
2675
|
+
const wantOpen = direction === "unfold";
|
|
2676
|
+
// Radar's unit is the chain: one press turns the newest summary line back
|
|
2677
|
+
// into the calls it stands for. Its per-call bodies are a glance/full
|
|
2678
|
+
// question, so the step stops there rather than cascading.
|
|
2679
|
+
if (this.toolOutputView === "radar") {
|
|
2680
|
+
const chains = this.transcriptChains();
|
|
2681
|
+
for (let i = chains.length - 1; i >= 0; i--) {
|
|
2682
|
+
if (chains[i].isEmpty || chains[i].isOpened === wantOpen)
|
|
2683
|
+
continue;
|
|
2684
|
+
chains[i].setOpened(wantOpen);
|
|
2685
|
+
this.ui.requestRender();
|
|
2686
|
+
return;
|
|
2687
|
+
}
|
|
2688
|
+
this.showStatus(wantOpen ? "No collapsed chains below this point" : "No open chains below this point");
|
|
2689
|
+
return;
|
|
2690
|
+
}
|
|
2691
|
+
const blocks = this.transcriptToolBlocks();
|
|
2692
|
+
for (let i = blocks.length - 1; i >= 0; i--) {
|
|
2693
|
+
const block = blocks[i];
|
|
2694
|
+
if (block.isRevealed() === wantOpen)
|
|
2695
|
+
continue;
|
|
2696
|
+
block.setExpanded(wantOpen);
|
|
2697
|
+
this.ui.requestRender();
|
|
2698
|
+
return;
|
|
2699
|
+
}
|
|
2700
|
+
this.showStatus(wantOpen ? "No folded tool output below this point" : "No unfolded tool output below this point");
|
|
2701
|
+
}
|
|
2702
|
+
/**
|
|
2703
|
+
* Move the view dial one stop and report where it landed.
|
|
2704
|
+
*
|
|
2705
|
+
* The dial is the persistent decision ("how much do I ever want to see"),
|
|
2706
|
+
* which is why it saves; `app.tools.expand` stays the momentary one ("open
|
|
2707
|
+
* what is in front of me"), which is why it does not.
|
|
2708
|
+
*/
|
|
2709
|
+
cycleToolOutputView(direction) {
|
|
2710
|
+
const next = cycleToolOutputView(this.toolOutputView, direction);
|
|
2711
|
+
this.applyToolOutputView(next);
|
|
2712
|
+
}
|
|
2713
|
+
applyToolOutputView(view) {
|
|
2714
|
+
this.toolOutputView = view;
|
|
2715
|
+
this.settingsManager.setToolOutputView(view);
|
|
2716
|
+
this.footer.setToolOutputView(view);
|
|
2717
|
+
for (const child of this.chatContainer.children) {
|
|
2718
|
+
if (child instanceof ToolChainComponent)
|
|
2719
|
+
child.setView(view);
|
|
2720
|
+
else if (child instanceof ToolExecutionComponent)
|
|
2721
|
+
child.setView(view);
|
|
2722
|
+
}
|
|
2723
|
+
this.ui.requestRender();
|
|
2724
|
+
}
|
|
2534
2725
|
setToolsExpanded(expanded) {
|
|
2535
2726
|
this.toolOutputExpanded = expanded;
|
|
2536
2727
|
const activeHeader = this.chrome.customHeader ?? this.builtInHeader;
|
|
@@ -2544,6 +2735,33 @@ export class InteractiveMode {
|
|
|
2544
2735
|
}
|
|
2545
2736
|
this.ui.requestRender();
|
|
2546
2737
|
}
|
|
2738
|
+
/**
|
|
2739
|
+
* Step the agent mode one along (ask → plan → build → debug → ask).
|
|
2740
|
+
*
|
|
2741
|
+
* Mode is the product's central guardrail and sits first in the footer, yet
|
|
2742
|
+
* it was reachable only by typing `/mode <name>`. The list comes from the
|
|
2743
|
+
* mode command's own argument completions rather than a copy kept here, so
|
|
2744
|
+
* a project that adds a mode gets it in the rotation for free; if the command
|
|
2745
|
+
* is missing (a `--light` run strips the extension), the key says so instead
|
|
2746
|
+
* of guessing.
|
|
2747
|
+
*/
|
|
2748
|
+
async cycleAgentMode() {
|
|
2749
|
+
const command = this.session.extensionRunner.getCommand("mode");
|
|
2750
|
+
if (!command) {
|
|
2751
|
+
this.showWarning("Modes are not available in this session");
|
|
2752
|
+
return;
|
|
2753
|
+
}
|
|
2754
|
+
const completions = (await command.getArgumentCompletions?.("")) ?? [];
|
|
2755
|
+
const modes = completions.map((item) => item.value);
|
|
2756
|
+
if (modes.length === 0) {
|
|
2757
|
+
this.showWarning("No modes are configured");
|
|
2758
|
+
return;
|
|
2759
|
+
}
|
|
2760
|
+
const current = this.footerDataProvider.getActiveMode();
|
|
2761
|
+
const index = modes.indexOf(current);
|
|
2762
|
+
const next = modes[(index + 1) % modes.length];
|
|
2763
|
+
await this.session.prompt(`/mode ${next}`);
|
|
2764
|
+
}
|
|
2547
2765
|
toggleThinkingBlockVisibility() {
|
|
2548
2766
|
this.hideThinkingBlock = !this.hideThinkingBlock;
|
|
2549
2767
|
this.settingsManager.setHideThinkingBlock(this.hideThinkingBlock);
|
|
@@ -2742,8 +2960,12 @@ export class InteractiveMode {
|
|
|
2742
2960
|
autoCompact: this.session.autoCompactionEnabled,
|
|
2743
2961
|
tools: toolToggles,
|
|
2744
2962
|
toolGroups,
|
|
2963
|
+
// Resolved fresh on every open, never downloaded: a binary that
|
|
2964
|
+
// arrived since the last open (a startup fetch, a `brew install`)
|
|
2965
|
+
// shows as present without a restart.
|
|
2966
|
+
externalTools: describeExternalTools(),
|
|
2745
2967
|
flags,
|
|
2746
|
-
|
|
2968
|
+
toolOutputView: this.toolOutputView,
|
|
2747
2969
|
toolOutputMaxBytes: this.settingsManager.getToolOutputMaxBytes(),
|
|
2748
2970
|
toolOutputMaxLines: this.settingsManager.getToolOutputMaxLines(),
|
|
2749
2971
|
contextGc: this.settingsManager.getContextGcEnabled(),
|
|
@@ -2843,15 +3065,8 @@ export class InteractiveMode {
|
|
|
2843
3065
|
break;
|
|
2844
3066
|
}
|
|
2845
3067
|
},
|
|
2846
|
-
|
|
2847
|
-
this.
|
|
2848
|
-
this.settingsManager.setToolOutputDisplay(level);
|
|
2849
|
-
for (const child of this.chatContainer.children) {
|
|
2850
|
-
if (child instanceof ToolExecutionComponent) {
|
|
2851
|
-
child.setDisplayLevel(level);
|
|
2852
|
-
}
|
|
2853
|
-
}
|
|
2854
|
-
this.ui.requestRender();
|
|
3068
|
+
onToolOutputViewChange: (view) => {
|
|
3069
|
+
this.applyToolOutputView(view);
|
|
2855
3070
|
},
|
|
2856
3071
|
onToolOutputMaxBytesChange: (bytes) => {
|
|
2857
3072
|
this.settingsManager.setToolOutputMaxBytes(bytes);
|