gsd-pi 2.39.0 → 2.40.0-dev.4a93031
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/dist/resource-loader.js +66 -2
- package/dist/resources/extensions/async-jobs/index.js +10 -0
- package/dist/resources/extensions/get-secrets-from-user.js +1 -1
- package/dist/resources/extensions/gsd/auto-dashboard.js +7 -0
- package/dist/resources/extensions/gsd/auto-loop.js +761 -673
- package/dist/resources/extensions/gsd/auto-post-unit.js +10 -2
- package/dist/resources/extensions/gsd/auto-prompts.js +3 -3
- package/dist/resources/extensions/gsd/auto-start.js +6 -1
- package/dist/resources/extensions/gsd/auto.js +6 -4
- package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +126 -0
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +233 -0
- package/dist/resources/extensions/gsd/bootstrap/dynamic-tools.js +59 -0
- package/dist/resources/extensions/gsd/bootstrap/register-extension.js +38 -0
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +156 -0
- package/dist/resources/extensions/gsd/bootstrap/register-shortcuts.js +46 -0
- package/dist/resources/extensions/gsd/bootstrap/system-context.js +300 -0
- package/dist/resources/extensions/gsd/bootstrap/write-gate.js +38 -0
- package/dist/resources/extensions/gsd/commands/catalog.js +278 -0
- package/dist/resources/extensions/gsd/commands/context.js +84 -0
- package/dist/resources/extensions/gsd/commands/dispatcher.js +21 -0
- package/dist/resources/extensions/gsd/commands/handlers/auto.js +72 -0
- package/dist/resources/extensions/gsd/commands/handlers/core.js +246 -0
- package/dist/resources/extensions/gsd/commands/handlers/ops.js +166 -0
- package/dist/resources/extensions/gsd/commands/handlers/parallel.js +94 -0
- package/dist/resources/extensions/gsd/commands/handlers/workflow.js +102 -0
- package/dist/resources/extensions/gsd/commands/index.js +11 -0
- package/dist/resources/extensions/gsd/commands-handlers.js +1 -1
- package/dist/resources/extensions/gsd/commands.js +8 -1190
- package/dist/resources/extensions/gsd/dashboard-overlay.js +9 -0
- package/dist/resources/extensions/gsd/doctor-proactive.js +80 -10
- package/dist/resources/extensions/gsd/doctor.js +32 -2
- package/dist/resources/extensions/gsd/export-html.js +46 -0
- package/dist/resources/extensions/gsd/files.js +1 -1
- package/dist/resources/extensions/gsd/health-widget.js +1 -1
- package/dist/resources/extensions/gsd/index.js +4 -1115
- package/dist/resources/extensions/gsd/progress-score.js +20 -1
- package/dist/resources/extensions/gsd/prompts/forensics.md +121 -46
- package/dist/resources/extensions/gsd/visualizer-data.js +26 -1
- package/dist/resources/extensions/gsd/visualizer-views.js +52 -0
- package/dist/welcome-screen.d.ts +3 -2
- package/dist/welcome-screen.js +66 -22
- package/package.json +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.d.ts +12 -0
- package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.js +107 -24
- package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/skill-tool.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/skill-tool.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/skill-tool.test.js +70 -0
- package/packages/pi-coding-agent/dist/core/skill-tool.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/skills.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/skills.js +2 -1
- package/packages/pi-coding-agent/dist/core/skills.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts +17 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +244 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/extension-ui-controller.d.ts +3 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/extension-ui-controller.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/extension-ui-controller.js +58 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/extension-ui-controller.js.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.d.ts +12 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js +54 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/model-controller.d.ts +6 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/model-controller.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/model-controller.js +63 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/model-controller.js.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts +38 -0
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.js +2 -0
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.js.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +15 -457
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/pi-coding-agent/package.json +1 -1
- package/packages/pi-coding-agent/src/core/agent-session.ts +122 -23
- package/packages/pi-coding-agent/src/core/skill-tool.test.ts +89 -0
- package/packages/pi-coding-agent/src/core/skills.ts +2 -1
- package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +302 -0
- package/packages/pi-coding-agent/src/modes/interactive/controllers/extension-ui-controller.ts +59 -0
- package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.ts +68 -0
- package/packages/pi-coding-agent/src/modes/interactive/controllers/model-controller.ts +71 -0
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode-state.ts +37 -0
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +18 -510
- package/pkg/package.json +1 -1
- package/src/resources/extensions/async-jobs/index.ts +11 -0
- package/src/resources/extensions/get-secrets-from-user.ts +1 -1
- package/src/resources/extensions/gsd/auto-dashboard.ts +10 -0
- package/src/resources/extensions/gsd/auto-loop.ts +1075 -921
- package/src/resources/extensions/gsd/auto-post-unit.ts +10 -2
- package/src/resources/extensions/gsd/auto-prompts.ts +3 -3
- package/src/resources/extensions/gsd/auto-start.ts +6 -1
- package/src/resources/extensions/gsd/auto.ts +13 -10
- package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +142 -0
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +238 -0
- package/src/resources/extensions/gsd/bootstrap/dynamic-tools.ts +90 -0
- package/src/resources/extensions/gsd/bootstrap/register-extension.ts +46 -0
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +167 -0
- package/src/resources/extensions/gsd/bootstrap/register-shortcuts.ts +55 -0
- package/src/resources/extensions/gsd/bootstrap/system-context.ts +340 -0
- package/src/resources/extensions/gsd/bootstrap/write-gate.ts +51 -0
- package/src/resources/extensions/gsd/commands/catalog.ts +301 -0
- package/src/resources/extensions/gsd/commands/context.ts +101 -0
- package/src/resources/extensions/gsd/commands/dispatcher.ts +32 -0
- package/src/resources/extensions/gsd/commands/handlers/auto.ts +74 -0
- package/src/resources/extensions/gsd/commands/handlers/core.ts +274 -0
- package/src/resources/extensions/gsd/commands/handlers/ops.ts +169 -0
- package/src/resources/extensions/gsd/commands/handlers/parallel.ts +118 -0
- package/src/resources/extensions/gsd/commands/handlers/workflow.ts +109 -0
- package/src/resources/extensions/gsd/commands/index.ts +14 -0
- package/src/resources/extensions/gsd/commands-handlers.ts +1 -1
- package/src/resources/extensions/gsd/commands.ts +10 -1329
- package/src/resources/extensions/gsd/dashboard-overlay.ts +10 -0
- package/src/resources/extensions/gsd/doctor-proactive.ts +106 -10
- package/src/resources/extensions/gsd/doctor.ts +47 -3
- package/src/resources/extensions/gsd/export-html.ts +51 -0
- package/src/resources/extensions/gsd/files.ts +1 -1
- package/src/resources/extensions/gsd/health-widget.ts +2 -1
- package/src/resources/extensions/gsd/index.ts +12 -1314
- package/src/resources/extensions/gsd/progress-score.ts +23 -0
- package/src/resources/extensions/gsd/prompts/forensics.md +121 -46
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +13 -9
- package/src/resources/extensions/gsd/tests/doctor-proactive.test.ts +3 -3
- package/src/resources/extensions/gsd/tests/provider-errors.test.ts +16 -16
- package/src/resources/extensions/gsd/tests/skill-activation.test.ts +4 -4
- package/src/resources/extensions/gsd/tests/visualizer-data.test.ts +10 -10
- package/src/resources/extensions/gsd/visualizer-data.ts +51 -1
- package/src/resources/extensions/gsd/visualizer-views.ts +58 -0
- /package/dist/resources/extensions/{env-utils.js → gsd/env-utils.js} +0 -0
- /package/src/resources/extensions/{env-utils.ts → gsd/env-utils.ts} +0 -0
|
@@ -89,6 +89,15 @@ import { TreeSelectorComponent } from "./components/tree-selector.js";
|
|
|
89
89
|
import { UserMessageComponent } from "./components/user-message.js";
|
|
90
90
|
import { UserMessageSelectorComponent } from "./components/user-message-selector.js";
|
|
91
91
|
import { type SlashCommandContext, dispatchSlashCommand, getAppKeyDisplay } from "./slash-command-handlers.js";
|
|
92
|
+
import { handleAgentEvent } from "./controllers/chat-controller.js";
|
|
93
|
+
import { createExtensionUIContext as buildExtensionUIContext } from "./controllers/extension-ui-controller.js";
|
|
94
|
+
import { setupEditorSubmitHandler as setupEditorSubmitHandlerController } from "./controllers/input-controller.js";
|
|
95
|
+
import {
|
|
96
|
+
findExactModelMatch as findExactModelMatchController,
|
|
97
|
+
getModelCandidates as getModelCandidatesController,
|
|
98
|
+
handleModelCommand as handleModelCommandController,
|
|
99
|
+
updateAvailableProviderCount as updateAvailableProviderCountController,
|
|
100
|
+
} from "./controllers/model-controller.js";
|
|
92
101
|
import {
|
|
93
102
|
getAvailableThemes,
|
|
94
103
|
getAvailableThemesWithPaths,
|
|
@@ -1175,12 +1184,10 @@ export class InteractiveMode {
|
|
|
1175
1184
|
}
|
|
1176
1185
|
|
|
1177
1186
|
/**
|
|
1178
|
-
* Get a
|
|
1187
|
+
* Get a tool definition by name (for custom rendering).
|
|
1179
1188
|
*/
|
|
1180
1189
|
private getRegisteredToolDefinition(toolName: string) {
|
|
1181
|
-
|
|
1182
|
-
const registeredTool = tools.find((t) => t.definition.name === toolName);
|
|
1183
|
-
return registeredTool?.definition;
|
|
1190
|
+
return this.session.getRenderableToolDefinition(toolName);
|
|
1184
1191
|
}
|
|
1185
1192
|
|
|
1186
1193
|
/**
|
|
@@ -1486,60 +1493,7 @@ export class InteractiveMode {
|
|
|
1486
1493
|
* Create the ExtensionUIContext for extensions.
|
|
1487
1494
|
*/
|
|
1488
1495
|
private createExtensionUIContext(): ExtensionUIContext {
|
|
1489
|
-
return
|
|
1490
|
-
select: (title, options, opts) => this.showExtensionSelector(title, options, opts),
|
|
1491
|
-
confirm: (title, message, opts) => this.showExtensionConfirm(title, message, opts),
|
|
1492
|
-
input: (title, placeholder, opts) => this.showExtensionInput(title, placeholder, opts),
|
|
1493
|
-
notify: (message, type) => this.showExtensionNotify(message, type),
|
|
1494
|
-
onTerminalInput: (handler) => this.addExtensionTerminalInputListener(handler),
|
|
1495
|
-
setStatus: (key, text) => this.setExtensionStatus(key, text),
|
|
1496
|
-
setWorkingMessage: (message) => {
|
|
1497
|
-
if (this.loadingAnimation) {
|
|
1498
|
-
if (message) {
|
|
1499
|
-
this.loadingAnimation.setMessage(message);
|
|
1500
|
-
} else {
|
|
1501
|
-
this.loadingAnimation.setMessage(
|
|
1502
|
-
`${this.defaultWorkingMessage} (${appKey(this.keybindings, "interrupt")} to interrupt)`,
|
|
1503
|
-
);
|
|
1504
|
-
}
|
|
1505
|
-
} else {
|
|
1506
|
-
// Queue message for when loadingAnimation is created (handles agent_start race)
|
|
1507
|
-
this.pendingWorkingMessage = message;
|
|
1508
|
-
}
|
|
1509
|
-
},
|
|
1510
|
-
setWidget: (key, content, options) => this.setExtensionWidget(key, content, options),
|
|
1511
|
-
setFooter: (factory) => this.setExtensionFooter(factory),
|
|
1512
|
-
setHeader: (factory) => this.setExtensionHeader(factory),
|
|
1513
|
-
setTitle: (title) => this.ui.terminal.setTitle(title),
|
|
1514
|
-
custom: (factory, options) => this.showExtensionCustom(factory, options),
|
|
1515
|
-
pasteToEditor: (text) => this.editor.handleInput(`\x1b[200~${text}\x1b[201~`),
|
|
1516
|
-
setEditorText: (text) => this.editor.setText(text),
|
|
1517
|
-
getEditorText: () => this.editor.getText(),
|
|
1518
|
-
editor: (title, prefill) => this.showExtensionEditor(title, prefill),
|
|
1519
|
-
setEditorComponent: (factory) => this.setCustomEditorComponent(factory),
|
|
1520
|
-
get theme() {
|
|
1521
|
-
return theme;
|
|
1522
|
-
},
|
|
1523
|
-
getAllThemes: () => getAvailableThemesWithPaths(),
|
|
1524
|
-
getTheme: (name) => getThemeByName(name),
|
|
1525
|
-
setTheme: (themeOrName) => {
|
|
1526
|
-
if (themeOrName instanceof Theme) {
|
|
1527
|
-
setThemeInstance(themeOrName);
|
|
1528
|
-
this.ui.requestRender();
|
|
1529
|
-
return { success: true };
|
|
1530
|
-
}
|
|
1531
|
-
const result = setTheme(themeOrName, true);
|
|
1532
|
-
if (result.success) {
|
|
1533
|
-
if (this.settingsManager.getTheme() !== themeOrName) {
|
|
1534
|
-
this.settingsManager.setTheme(themeOrName);
|
|
1535
|
-
}
|
|
1536
|
-
this.ui.requestRender();
|
|
1537
|
-
}
|
|
1538
|
-
return result;
|
|
1539
|
-
},
|
|
1540
|
-
getToolsExpanded: () => this.toolOutputExpanded,
|
|
1541
|
-
setToolsExpanded: (expanded) => this.setToolsExpanded(expanded),
|
|
1542
|
-
};
|
|
1496
|
+
return buildExtensionUIContext(this);
|
|
1543
1497
|
}
|
|
1544
1498
|
|
|
1545
1499
|
/**
|
|
@@ -2017,69 +1971,7 @@ export class InteractiveMode {
|
|
|
2017
1971
|
}
|
|
2018
1972
|
|
|
2019
1973
|
private setupEditorSubmitHandler(): void {
|
|
2020
|
-
this
|
|
2021
|
-
text = text.trim();
|
|
2022
|
-
if (!text) return;
|
|
2023
|
-
|
|
2024
|
-
// Handle slash commands
|
|
2025
|
-
if (text.startsWith("/")) {
|
|
2026
|
-
const handled = await dispatchSlashCommand(text, this.getSlashCommandContext());
|
|
2027
|
-
if (handled) {
|
|
2028
|
-
this.editor.setText("");
|
|
2029
|
-
return;
|
|
2030
|
-
}
|
|
2031
|
-
}
|
|
2032
|
-
|
|
2033
|
-
// Handle bash command (! for normal, !! for excluded from context)
|
|
2034
|
-
if (text.startsWith("!")) {
|
|
2035
|
-
const isExcluded = text.startsWith("!!");
|
|
2036
|
-
const command = isExcluded ? text.slice(2).trim() : text.slice(1).trim();
|
|
2037
|
-
if (command) {
|
|
2038
|
-
if (this.session.isBashRunning) {
|
|
2039
|
-
this.showWarning("A bash command is already running. Press Esc to cancel it first.");
|
|
2040
|
-
this.editor.setText(text);
|
|
2041
|
-
return;
|
|
2042
|
-
}
|
|
2043
|
-
this.editor.addToHistory?.(text);
|
|
2044
|
-
await this.handleBashCommand(command, isExcluded);
|
|
2045
|
-
this.isBashMode = false;
|
|
2046
|
-
this.updateEditorBorderColor();
|
|
2047
|
-
return;
|
|
2048
|
-
}
|
|
2049
|
-
}
|
|
2050
|
-
|
|
2051
|
-
// Queue input during compaction (extension commands execute immediately)
|
|
2052
|
-
if (this.session.isCompacting) {
|
|
2053
|
-
if (this.isExtensionCommand(text)) {
|
|
2054
|
-
this.editor.addToHistory?.(text);
|
|
2055
|
-
this.editor.setText("");
|
|
2056
|
-
await this.session.prompt(text);
|
|
2057
|
-
} else {
|
|
2058
|
-
this.queueCompactionMessage(text, "steer");
|
|
2059
|
-
}
|
|
2060
|
-
return;
|
|
2061
|
-
}
|
|
2062
|
-
|
|
2063
|
-
// If streaming, use prompt() with steer behavior
|
|
2064
|
-
// This handles extension commands (execute immediately), prompt template expansion, and queueing
|
|
2065
|
-
if (this.session.isStreaming) {
|
|
2066
|
-
this.editor.addToHistory?.(text);
|
|
2067
|
-
this.editor.setText("");
|
|
2068
|
-
await this.session.prompt(text, { streamingBehavior: "steer" });
|
|
2069
|
-
this.updatePendingMessagesDisplay();
|
|
2070
|
-
this.ui.requestRender();
|
|
2071
|
-
return;
|
|
2072
|
-
}
|
|
2073
|
-
|
|
2074
|
-
// Normal message submission
|
|
2075
|
-
// First, move any pending bash components to chat
|
|
2076
|
-
this.flushPendingBashComponents();
|
|
2077
|
-
|
|
2078
|
-
if (this.onInputCallback) {
|
|
2079
|
-
this.onInputCallback(text);
|
|
2080
|
-
}
|
|
2081
|
-
this.editor.addToHistory?.(text);
|
|
2082
|
-
};
|
|
1974
|
+
setupEditorSubmitHandlerController(this as any);
|
|
2083
1975
|
}
|
|
2084
1976
|
|
|
2085
1977
|
private subscribeToAgent(): void {
|
|
@@ -2089,338 +1981,7 @@ export class InteractiveMode {
|
|
|
2089
1981
|
}
|
|
2090
1982
|
|
|
2091
1983
|
private async handleEvent(event: AgentSessionEvent): Promise<void> {
|
|
2092
|
-
|
|
2093
|
-
await this.init();
|
|
2094
|
-
}
|
|
2095
|
-
|
|
2096
|
-
this.footer.invalidate();
|
|
2097
|
-
|
|
2098
|
-
switch (event.type) {
|
|
2099
|
-
case "agent_start":
|
|
2100
|
-
// Restore main escape handler if retry handler is still active
|
|
2101
|
-
// (retry success event fires later, but we need main handler now)
|
|
2102
|
-
if (this.retryEscapeHandler) {
|
|
2103
|
-
this.defaultEditor.onEscape = this.retryEscapeHandler;
|
|
2104
|
-
this.retryEscapeHandler = undefined;
|
|
2105
|
-
}
|
|
2106
|
-
if (this.retryLoader) {
|
|
2107
|
-
this.retryLoader.stop();
|
|
2108
|
-
this.retryLoader = undefined;
|
|
2109
|
-
}
|
|
2110
|
-
if (this.loadingAnimation) {
|
|
2111
|
-
this.loadingAnimation.stop();
|
|
2112
|
-
}
|
|
2113
|
-
this.statusContainer.clear();
|
|
2114
|
-
this.loadingAnimation = new Loader(
|
|
2115
|
-
this.ui,
|
|
2116
|
-
(spinner) => theme.fg("accent", spinner),
|
|
2117
|
-
(text) => theme.fg("muted", text),
|
|
2118
|
-
this.defaultWorkingMessage,
|
|
2119
|
-
);
|
|
2120
|
-
this.statusContainer.addChild(this.loadingAnimation);
|
|
2121
|
-
// Apply any pending working message queued before loader existed
|
|
2122
|
-
if (this.pendingWorkingMessage !== undefined) {
|
|
2123
|
-
if (this.pendingWorkingMessage) {
|
|
2124
|
-
this.loadingAnimation.setMessage(this.pendingWorkingMessage);
|
|
2125
|
-
}
|
|
2126
|
-
this.pendingWorkingMessage = undefined;
|
|
2127
|
-
}
|
|
2128
|
-
this.ui.requestRender();
|
|
2129
|
-
break;
|
|
2130
|
-
|
|
2131
|
-
case "message_start":
|
|
2132
|
-
if (event.message.role === "custom") {
|
|
2133
|
-
this.addMessageToChat(event.message);
|
|
2134
|
-
this.ui.requestRender();
|
|
2135
|
-
} else if (event.message.role === "user") {
|
|
2136
|
-
this.addMessageToChat(event.message);
|
|
2137
|
-
this.updatePendingMessagesDisplay();
|
|
2138
|
-
this.ui.requestRender();
|
|
2139
|
-
} else if (event.message.role === "assistant") {
|
|
2140
|
-
this.streamingComponent = new AssistantMessageComponent(
|
|
2141
|
-
undefined,
|
|
2142
|
-
this.hideThinkingBlock,
|
|
2143
|
-
this.getMarkdownThemeWithSettings(),
|
|
2144
|
-
);
|
|
2145
|
-
this.streamingMessage = event.message;
|
|
2146
|
-
this.chatContainer.addChild(this.streamingComponent);
|
|
2147
|
-
this.streamingComponent.updateContent(this.streamingMessage);
|
|
2148
|
-
this.ui.requestRender();
|
|
2149
|
-
}
|
|
2150
|
-
break;
|
|
2151
|
-
|
|
2152
|
-
case "message_update":
|
|
2153
|
-
if (this.streamingComponent && event.message.role === "assistant") {
|
|
2154
|
-
this.streamingMessage = event.message;
|
|
2155
|
-
this.streamingComponent.updateContent(this.streamingMessage);
|
|
2156
|
-
|
|
2157
|
-
for (const content of this.streamingMessage.content) {
|
|
2158
|
-
if (content.type === "toolCall") {
|
|
2159
|
-
if (!this.pendingTools.has(content.id)) {
|
|
2160
|
-
const component = new ToolExecutionComponent(
|
|
2161
|
-
content.name,
|
|
2162
|
-
content.arguments,
|
|
2163
|
-
{
|
|
2164
|
-
showImages: this.settingsManager.getShowImages(),
|
|
2165
|
-
},
|
|
2166
|
-
this.getRegisteredToolDefinition(content.name),
|
|
2167
|
-
this.ui,
|
|
2168
|
-
);
|
|
2169
|
-
component.setExpanded(this.toolOutputExpanded);
|
|
2170
|
-
this.chatContainer.addChild(component);
|
|
2171
|
-
this.pendingTools.set(content.id, component);
|
|
2172
|
-
} else {
|
|
2173
|
-
const component = this.pendingTools.get(content.id);
|
|
2174
|
-
if (component) {
|
|
2175
|
-
component.updateArgs(content.arguments);
|
|
2176
|
-
}
|
|
2177
|
-
}
|
|
2178
|
-
} else if (content.type === "serverToolUse") {
|
|
2179
|
-
// Server-side tool (e.g., native web search) — show as pending tool execution
|
|
2180
|
-
if (!this.pendingTools.has(content.id)) {
|
|
2181
|
-
const component = new ToolExecutionComponent(
|
|
2182
|
-
content.name,
|
|
2183
|
-
content.input ?? {},
|
|
2184
|
-
{
|
|
2185
|
-
showImages: this.settingsManager.getShowImages(),
|
|
2186
|
-
},
|
|
2187
|
-
undefined,
|
|
2188
|
-
this.ui,
|
|
2189
|
-
);
|
|
2190
|
-
component.setExpanded(this.toolOutputExpanded);
|
|
2191
|
-
this.chatContainer.addChild(component);
|
|
2192
|
-
this.pendingTools.set(content.id, component);
|
|
2193
|
-
}
|
|
2194
|
-
} else if (content.type === "webSearchResult") {
|
|
2195
|
-
// Server-side tool result — resolve the pending server tool execution
|
|
2196
|
-
const component = this.pendingTools.get(content.toolUseId);
|
|
2197
|
-
if (component) {
|
|
2198
|
-
const searchContent = content.content;
|
|
2199
|
-
const isError = searchContent && typeof searchContent === "object" && "type" in (searchContent as any) && (searchContent as any).type === "web_search_tool_result_error";
|
|
2200
|
-
const resultText = this.formatWebSearchResult(searchContent);
|
|
2201
|
-
component.updateResult({
|
|
2202
|
-
content: [{ type: "text", text: resultText }],
|
|
2203
|
-
isError: !!isError,
|
|
2204
|
-
});
|
|
2205
|
-
this.pendingTools.delete(content.toolUseId);
|
|
2206
|
-
}
|
|
2207
|
-
}
|
|
2208
|
-
}
|
|
2209
|
-
this.ui.requestRender();
|
|
2210
|
-
}
|
|
2211
|
-
break;
|
|
2212
|
-
|
|
2213
|
-
case "message_end":
|
|
2214
|
-
if (event.message.role === "user") break;
|
|
2215
|
-
if (this.streamingComponent && event.message.role === "assistant") {
|
|
2216
|
-
this.streamingMessage = event.message;
|
|
2217
|
-
let errorMessage: string | undefined;
|
|
2218
|
-
if (this.streamingMessage.stopReason === "aborted") {
|
|
2219
|
-
const retryAttempt = this.session.retryAttempt;
|
|
2220
|
-
errorMessage =
|
|
2221
|
-
retryAttempt > 0
|
|
2222
|
-
? `Aborted after ${retryAttempt} retry attempt${retryAttempt > 1 ? "s" : ""}`
|
|
2223
|
-
: "Operation aborted";
|
|
2224
|
-
this.streamingMessage.errorMessage = errorMessage;
|
|
2225
|
-
}
|
|
2226
|
-
this.streamingComponent.updateContent(this.streamingMessage);
|
|
2227
|
-
|
|
2228
|
-
if (this.streamingMessage.stopReason === "aborted" || this.streamingMessage.stopReason === "error") {
|
|
2229
|
-
if (!errorMessage) {
|
|
2230
|
-
errorMessage = this.streamingMessage.errorMessage || "Error";
|
|
2231
|
-
}
|
|
2232
|
-
for (const [, component] of this.pendingTools.entries()) {
|
|
2233
|
-
component.updateResult({
|
|
2234
|
-
content: [{ type: "text", text: errorMessage }],
|
|
2235
|
-
isError: true,
|
|
2236
|
-
});
|
|
2237
|
-
}
|
|
2238
|
-
this.pendingTools.clear();
|
|
2239
|
-
} else {
|
|
2240
|
-
// Args are now complete - trigger diff computation for edit tools
|
|
2241
|
-
for (const [, component] of this.pendingTools.entries()) {
|
|
2242
|
-
component.setArgsComplete();
|
|
2243
|
-
}
|
|
2244
|
-
}
|
|
2245
|
-
this.streamingComponent = undefined;
|
|
2246
|
-
this.streamingMessage = undefined;
|
|
2247
|
-
this.footer.invalidate();
|
|
2248
|
-
}
|
|
2249
|
-
this.ui.requestRender();
|
|
2250
|
-
break;
|
|
2251
|
-
|
|
2252
|
-
case "tool_execution_start": {
|
|
2253
|
-
if (!this.pendingTools.has(event.toolCallId)) {
|
|
2254
|
-
const component = new ToolExecutionComponent(
|
|
2255
|
-
event.toolName,
|
|
2256
|
-
event.args,
|
|
2257
|
-
{
|
|
2258
|
-
showImages: this.settingsManager.getShowImages(),
|
|
2259
|
-
},
|
|
2260
|
-
this.getRegisteredToolDefinition(event.toolName),
|
|
2261
|
-
this.ui,
|
|
2262
|
-
);
|
|
2263
|
-
component.setExpanded(this.toolOutputExpanded);
|
|
2264
|
-
this.chatContainer.addChild(component);
|
|
2265
|
-
this.pendingTools.set(event.toolCallId, component);
|
|
2266
|
-
this.ui.requestRender();
|
|
2267
|
-
}
|
|
2268
|
-
break;
|
|
2269
|
-
}
|
|
2270
|
-
|
|
2271
|
-
case "tool_execution_update": {
|
|
2272
|
-
const component = this.pendingTools.get(event.toolCallId);
|
|
2273
|
-
if (component) {
|
|
2274
|
-
component.updateResult({ ...event.partialResult, isError: false }, true);
|
|
2275
|
-
this.ui.requestRender();
|
|
2276
|
-
}
|
|
2277
|
-
break;
|
|
2278
|
-
}
|
|
2279
|
-
|
|
2280
|
-
case "tool_execution_end": {
|
|
2281
|
-
const component = this.pendingTools.get(event.toolCallId);
|
|
2282
|
-
if (component) {
|
|
2283
|
-
component.updateResult({ ...event.result, isError: event.isError });
|
|
2284
|
-
this.pendingTools.delete(event.toolCallId);
|
|
2285
|
-
this.ui.requestRender();
|
|
2286
|
-
}
|
|
2287
|
-
break;
|
|
2288
|
-
}
|
|
2289
|
-
|
|
2290
|
-
case "agent_end":
|
|
2291
|
-
if (this.loadingAnimation) {
|
|
2292
|
-
this.loadingAnimation.stop();
|
|
2293
|
-
this.loadingAnimation = undefined;
|
|
2294
|
-
this.statusContainer.clear();
|
|
2295
|
-
}
|
|
2296
|
-
if (this.streamingComponent) {
|
|
2297
|
-
this.chatContainer.removeChild(this.streamingComponent);
|
|
2298
|
-
this.streamingComponent = undefined;
|
|
2299
|
-
this.streamingMessage = undefined;
|
|
2300
|
-
}
|
|
2301
|
-
this.pendingTools.clear();
|
|
2302
|
-
|
|
2303
|
-
await this.checkShutdownRequested();
|
|
2304
|
-
|
|
2305
|
-
this.ui.requestRender();
|
|
2306
|
-
break;
|
|
2307
|
-
|
|
2308
|
-
case "auto_compaction_start": {
|
|
2309
|
-
// Keep editor active; submissions are queued during compaction.
|
|
2310
|
-
// Set up escape to abort auto-compaction
|
|
2311
|
-
this.autoCompactionEscapeHandler = this.defaultEditor.onEscape;
|
|
2312
|
-
this.defaultEditor.onEscape = () => {
|
|
2313
|
-
this.session.abortCompaction();
|
|
2314
|
-
};
|
|
2315
|
-
// Show compacting indicator with reason
|
|
2316
|
-
this.statusContainer.clear();
|
|
2317
|
-
const reasonText = event.reason === "overflow" ? "Context overflow detected, " : "";
|
|
2318
|
-
this.autoCompactionLoader = new Loader(
|
|
2319
|
-
this.ui,
|
|
2320
|
-
(spinner) => theme.fg("accent", spinner),
|
|
2321
|
-
(text) => theme.fg("muted", text),
|
|
2322
|
-
`${reasonText}Auto-compacting... (${appKey(this.keybindings, "interrupt")} to cancel)`,
|
|
2323
|
-
);
|
|
2324
|
-
this.statusContainer.addChild(this.autoCompactionLoader);
|
|
2325
|
-
this.ui.requestRender();
|
|
2326
|
-
break;
|
|
2327
|
-
}
|
|
2328
|
-
|
|
2329
|
-
case "auto_compaction_end": {
|
|
2330
|
-
// Restore escape handler
|
|
2331
|
-
if (this.autoCompactionEscapeHandler) {
|
|
2332
|
-
this.defaultEditor.onEscape = this.autoCompactionEscapeHandler;
|
|
2333
|
-
this.autoCompactionEscapeHandler = undefined;
|
|
2334
|
-
}
|
|
2335
|
-
// Stop loader
|
|
2336
|
-
if (this.autoCompactionLoader) {
|
|
2337
|
-
this.autoCompactionLoader.stop();
|
|
2338
|
-
this.autoCompactionLoader = undefined;
|
|
2339
|
-
this.statusContainer.clear();
|
|
2340
|
-
}
|
|
2341
|
-
// Handle result
|
|
2342
|
-
if (event.aborted) {
|
|
2343
|
-
this.showStatus("Auto-compaction cancelled");
|
|
2344
|
-
} else if (event.result) {
|
|
2345
|
-
// Rebuild chat to show compacted state
|
|
2346
|
-
this.chatContainer.clear();
|
|
2347
|
-
this.rebuildChatFromMessages();
|
|
2348
|
-
// Add compaction component at bottom so user sees it without scrolling
|
|
2349
|
-
this.addMessageToChat({
|
|
2350
|
-
role: "compactionSummary",
|
|
2351
|
-
tokensBefore: event.result.tokensBefore,
|
|
2352
|
-
summary: event.result.summary,
|
|
2353
|
-
timestamp: Date.now(),
|
|
2354
|
-
});
|
|
2355
|
-
this.footer.invalidate();
|
|
2356
|
-
} else if (event.errorMessage) {
|
|
2357
|
-
// Compaction failed (e.g., quota exceeded, API error)
|
|
2358
|
-
this.chatContainer.addChild(new Spacer(1));
|
|
2359
|
-
this.chatContainer.addChild(new Text(theme.fg("error", event.errorMessage), 1, 0));
|
|
2360
|
-
}
|
|
2361
|
-
void this.flushCompactionQueue({ willRetry: event.willRetry });
|
|
2362
|
-
this.ui.requestRender();
|
|
2363
|
-
break;
|
|
2364
|
-
}
|
|
2365
|
-
|
|
2366
|
-
case "auto_retry_start": {
|
|
2367
|
-
// Set up escape to abort retry
|
|
2368
|
-
this.retryEscapeHandler = this.defaultEditor.onEscape;
|
|
2369
|
-
this.defaultEditor.onEscape = () => {
|
|
2370
|
-
this.session.abortRetry();
|
|
2371
|
-
};
|
|
2372
|
-
// Show retry indicator
|
|
2373
|
-
this.statusContainer.clear();
|
|
2374
|
-
const delaySeconds = Math.round(event.delayMs / 1000);
|
|
2375
|
-
this.retryLoader = new Loader(
|
|
2376
|
-
this.ui,
|
|
2377
|
-
(spinner) => theme.fg("warning", spinner),
|
|
2378
|
-
(text) => theme.fg("muted", text),
|
|
2379
|
-
`Retrying (${event.attempt}/${event.maxAttempts}) in ${delaySeconds}s... (${appKey(this.keybindings, "interrupt")} to cancel)`,
|
|
2380
|
-
);
|
|
2381
|
-
this.statusContainer.addChild(this.retryLoader);
|
|
2382
|
-
this.ui.requestRender();
|
|
2383
|
-
break;
|
|
2384
|
-
}
|
|
2385
|
-
|
|
2386
|
-
case "auto_retry_end": {
|
|
2387
|
-
// Restore escape handler
|
|
2388
|
-
if (this.retryEscapeHandler) {
|
|
2389
|
-
this.defaultEditor.onEscape = this.retryEscapeHandler;
|
|
2390
|
-
this.retryEscapeHandler = undefined;
|
|
2391
|
-
}
|
|
2392
|
-
// Stop loader
|
|
2393
|
-
if (this.retryLoader) {
|
|
2394
|
-
this.retryLoader.stop();
|
|
2395
|
-
this.retryLoader = undefined;
|
|
2396
|
-
this.statusContainer.clear();
|
|
2397
|
-
}
|
|
2398
|
-
// Show error only on final failure (success shows normal response)
|
|
2399
|
-
if (!event.success) {
|
|
2400
|
-
this.showError(`Retry failed after ${event.attempt} attempts: ${event.finalError || "Unknown error"}`);
|
|
2401
|
-
}
|
|
2402
|
-
this.ui.requestRender();
|
|
2403
|
-
break;
|
|
2404
|
-
}
|
|
2405
|
-
|
|
2406
|
-
case "fallback_provider_switch": {
|
|
2407
|
-
this.showStatus(`Switched from ${event.from} → ${event.to} (${event.reason})`);
|
|
2408
|
-
this.ui.requestRender();
|
|
2409
|
-
break;
|
|
2410
|
-
}
|
|
2411
|
-
|
|
2412
|
-
case "fallback_provider_restored": {
|
|
2413
|
-
this.showStatus(`Restored to ${event.provider}`);
|
|
2414
|
-
this.ui.requestRender();
|
|
2415
|
-
break;
|
|
2416
|
-
}
|
|
2417
|
-
|
|
2418
|
-
case "fallback_chain_exhausted": {
|
|
2419
|
-
this.showError(event.reason);
|
|
2420
|
-
this.ui.requestRender();
|
|
2421
|
-
break;
|
|
2422
|
-
}
|
|
2423
|
-
}
|
|
1984
|
+
await handleAgentEvent(this as any, event);
|
|
2424
1985
|
}
|
|
2425
1986
|
|
|
2426
1987
|
/** Extract text content from a user message */
|
|
@@ -3299,73 +2860,20 @@ export class InteractiveMode {
|
|
|
3299
2860
|
}
|
|
3300
2861
|
|
|
3301
2862
|
private async handleModelCommand(searchTerm?: string): Promise<void> {
|
|
3302
|
-
|
|
3303
|
-
this.showModelSelector();
|
|
3304
|
-
return;
|
|
3305
|
-
}
|
|
3306
|
-
|
|
3307
|
-
const model = await this.findExactModelMatch(searchTerm);
|
|
3308
|
-
if (model) {
|
|
3309
|
-
try {
|
|
3310
|
-
await this.session.setModel(model);
|
|
3311
|
-
this.footer.invalidate();
|
|
3312
|
-
this.updateEditorBorderColor();
|
|
3313
|
-
this.showStatus(`Model: ${model.id}`);
|
|
3314
|
-
this.checkDaxnutsEasterEgg(model);
|
|
3315
|
-
} catch (error) {
|
|
3316
|
-
this.showError(error instanceof Error ? error.message : String(error));
|
|
3317
|
-
}
|
|
3318
|
-
return;
|
|
3319
|
-
}
|
|
3320
|
-
|
|
3321
|
-
this.showModelSelector(searchTerm);
|
|
2863
|
+
await handleModelCommandController(this, searchTerm);
|
|
3322
2864
|
}
|
|
3323
2865
|
|
|
3324
2866
|
private async findExactModelMatch(searchTerm: string): Promise<Model<any> | undefined> {
|
|
3325
|
-
|
|
3326
|
-
if (!term) return undefined;
|
|
3327
|
-
|
|
3328
|
-
let targetProvider: string | undefined;
|
|
3329
|
-
let targetModelId = "";
|
|
3330
|
-
|
|
3331
|
-
if (term.includes("/")) {
|
|
3332
|
-
const parts = term.split("/", 2);
|
|
3333
|
-
targetProvider = parts[0]?.trim().toLowerCase();
|
|
3334
|
-
targetModelId = parts[1]?.trim().toLowerCase() ?? "";
|
|
3335
|
-
} else {
|
|
3336
|
-
targetModelId = term.toLowerCase();
|
|
3337
|
-
}
|
|
3338
|
-
|
|
3339
|
-
if (!targetModelId) return undefined;
|
|
3340
|
-
|
|
3341
|
-
const models = await this.getModelCandidates();
|
|
3342
|
-
const exactMatches = models.filter((item) => {
|
|
3343
|
-
const idMatch = item.id.toLowerCase() === targetModelId;
|
|
3344
|
-
const providerMatch = !targetProvider || item.provider.toLowerCase() === targetProvider;
|
|
3345
|
-
return idMatch && providerMatch;
|
|
3346
|
-
});
|
|
3347
|
-
|
|
3348
|
-
return exactMatches.length === 1 ? exactMatches[0] : undefined;
|
|
2867
|
+
return findExactModelMatchController(this, searchTerm);
|
|
3349
2868
|
}
|
|
3350
2869
|
|
|
3351
2870
|
private async getModelCandidates(): Promise<Model<any>[]> {
|
|
3352
|
-
|
|
3353
|
-
return this.session.scopedModels.map((scoped) => scoped.model);
|
|
3354
|
-
}
|
|
3355
|
-
|
|
3356
|
-
this.session.modelRegistry.refresh();
|
|
3357
|
-
try {
|
|
3358
|
-
return await this.session.modelRegistry.getAvailable();
|
|
3359
|
-
} catch {
|
|
3360
|
-
return [];
|
|
3361
|
-
}
|
|
2871
|
+
return getModelCandidatesController(this);
|
|
3362
2872
|
}
|
|
3363
2873
|
|
|
3364
2874
|
/** Update the footer's available provider count from current model candidates */
|
|
3365
2875
|
private async updateAvailableProviderCount(): Promise<void> {
|
|
3366
|
-
|
|
3367
|
-
const uniqueProviders = new Set(models.map((m) => m.provider));
|
|
3368
|
-
this.footerDataProvider.setAvailableProviderCount(uniqueProviders.size);
|
|
2876
|
+
await updateAvailableProviderCountController(this);
|
|
3369
2877
|
}
|
|
3370
2878
|
|
|
3371
2879
|
private showModelSelector(initialSearchInput?: string): void {
|
package/pkg/package.json
CHANGED
|
@@ -78,6 +78,17 @@ export default function AsyncJobs(pi: ExtensionAPI) {
|
|
|
78
78
|
});
|
|
79
79
|
});
|
|
80
80
|
|
|
81
|
+
pi.on("session_before_switch", async () => {
|
|
82
|
+
if (manager) {
|
|
83
|
+
// Cancel all running background jobs — their results are no longer
|
|
84
|
+
// relevant to the new session and would produce wasteful follow-up
|
|
85
|
+
// notifications that trigger empty LLM turns (#1642).
|
|
86
|
+
for (const job of manager.getRunningJobs()) {
|
|
87
|
+
manager.cancel(job.id);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
|
|
81
92
|
pi.on("session_shutdown", async () => {
|
|
82
93
|
if (manager) {
|
|
83
94
|
manager.shutdown();
|
|
@@ -70,7 +70,7 @@ async function writeEnvKey(filePath: string, key: string, value: string): Promis
|
|
|
70
70
|
// Re-export from env-utils.ts so existing consumers still work.
|
|
71
71
|
// The implementation lives in env-utils.ts to avoid pulling @gsd/pi-tui
|
|
72
72
|
// into modules that only need env-checking (e.g. files.ts during reports).
|
|
73
|
-
import { checkExistingEnvKeys } from "./env-utils.js";
|
|
73
|
+
import { checkExistingEnvKeys } from "./gsd/env-utils.js";
|
|
74
74
|
export { checkExistingEnvKeys };
|
|
75
75
|
|
|
76
76
|
/**
|
|
@@ -390,6 +390,8 @@ export interface WidgetStateAccessors {
|
|
|
390
390
|
getCmdCtx(): ExtensionCommandContext | null;
|
|
391
391
|
getBasePath(): string;
|
|
392
392
|
isVerbose(): boolean;
|
|
393
|
+
/** True while newSession() is in-flight — render must not access session state. */
|
|
394
|
+
isSessionSwitching(): boolean;
|
|
393
395
|
}
|
|
394
396
|
|
|
395
397
|
export function updateProgressWidget(
|
|
@@ -460,6 +462,14 @@ export function updateProgressWidget(
|
|
|
460
462
|
render(width: number): string[] {
|
|
461
463
|
if (cachedLines && cachedWidth === width) return cachedLines;
|
|
462
464
|
|
|
465
|
+
// While newSession() is in-flight, session state is mid-mutation.
|
|
466
|
+
// Accessing cmdCtx.sessionManager or cmdCtx.getContextUsage() can
|
|
467
|
+
// block the render loop and freeze the TUI. Return the last cached
|
|
468
|
+
// frame (or an empty frame on first render) until the switch settles.
|
|
469
|
+
if (accessors.isSessionSwitching()) {
|
|
470
|
+
return cachedLines ?? [];
|
|
471
|
+
}
|
|
472
|
+
|
|
463
473
|
const ui = makeUI(theme, width);
|
|
464
474
|
const lines: string[] = [];
|
|
465
475
|
const pad = INDENT.base;
|