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.
Files changed (133) hide show
  1. package/dist/resource-loader.js +66 -2
  2. package/dist/resources/extensions/async-jobs/index.js +10 -0
  3. package/dist/resources/extensions/get-secrets-from-user.js +1 -1
  4. package/dist/resources/extensions/gsd/auto-dashboard.js +7 -0
  5. package/dist/resources/extensions/gsd/auto-loop.js +761 -673
  6. package/dist/resources/extensions/gsd/auto-post-unit.js +10 -2
  7. package/dist/resources/extensions/gsd/auto-prompts.js +3 -3
  8. package/dist/resources/extensions/gsd/auto-start.js +6 -1
  9. package/dist/resources/extensions/gsd/auto.js +6 -4
  10. package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +126 -0
  11. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +233 -0
  12. package/dist/resources/extensions/gsd/bootstrap/dynamic-tools.js +59 -0
  13. package/dist/resources/extensions/gsd/bootstrap/register-extension.js +38 -0
  14. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +156 -0
  15. package/dist/resources/extensions/gsd/bootstrap/register-shortcuts.js +46 -0
  16. package/dist/resources/extensions/gsd/bootstrap/system-context.js +300 -0
  17. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +38 -0
  18. package/dist/resources/extensions/gsd/commands/catalog.js +278 -0
  19. package/dist/resources/extensions/gsd/commands/context.js +84 -0
  20. package/dist/resources/extensions/gsd/commands/dispatcher.js +21 -0
  21. package/dist/resources/extensions/gsd/commands/handlers/auto.js +72 -0
  22. package/dist/resources/extensions/gsd/commands/handlers/core.js +246 -0
  23. package/dist/resources/extensions/gsd/commands/handlers/ops.js +166 -0
  24. package/dist/resources/extensions/gsd/commands/handlers/parallel.js +94 -0
  25. package/dist/resources/extensions/gsd/commands/handlers/workflow.js +102 -0
  26. package/dist/resources/extensions/gsd/commands/index.js +11 -0
  27. package/dist/resources/extensions/gsd/commands-handlers.js +1 -1
  28. package/dist/resources/extensions/gsd/commands.js +8 -1190
  29. package/dist/resources/extensions/gsd/dashboard-overlay.js +9 -0
  30. package/dist/resources/extensions/gsd/doctor-proactive.js +80 -10
  31. package/dist/resources/extensions/gsd/doctor.js +32 -2
  32. package/dist/resources/extensions/gsd/export-html.js +46 -0
  33. package/dist/resources/extensions/gsd/files.js +1 -1
  34. package/dist/resources/extensions/gsd/health-widget.js +1 -1
  35. package/dist/resources/extensions/gsd/index.js +4 -1115
  36. package/dist/resources/extensions/gsd/progress-score.js +20 -1
  37. package/dist/resources/extensions/gsd/prompts/forensics.md +121 -46
  38. package/dist/resources/extensions/gsd/visualizer-data.js +26 -1
  39. package/dist/resources/extensions/gsd/visualizer-views.js +52 -0
  40. package/dist/welcome-screen.d.ts +3 -2
  41. package/dist/welcome-screen.js +66 -22
  42. package/package.json +1 -1
  43. package/packages/pi-coding-agent/dist/core/agent-session.d.ts +12 -0
  44. package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
  45. package/packages/pi-coding-agent/dist/core/agent-session.js +107 -24
  46. package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  47. package/packages/pi-coding-agent/dist/core/skill-tool.test.d.ts +2 -0
  48. package/packages/pi-coding-agent/dist/core/skill-tool.test.d.ts.map +1 -0
  49. package/packages/pi-coding-agent/dist/core/skill-tool.test.js +70 -0
  50. package/packages/pi-coding-agent/dist/core/skill-tool.test.js.map +1 -0
  51. package/packages/pi-coding-agent/dist/core/skills.d.ts.map +1 -1
  52. package/packages/pi-coding-agent/dist/core/skills.js +2 -1
  53. package/packages/pi-coding-agent/dist/core/skills.js.map +1 -1
  54. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts +17 -0
  55. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -0
  56. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +244 -0
  57. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -0
  58. package/packages/pi-coding-agent/dist/modes/interactive/controllers/extension-ui-controller.d.ts +3 -0
  59. package/packages/pi-coding-agent/dist/modes/interactive/controllers/extension-ui-controller.d.ts.map +1 -0
  60. package/packages/pi-coding-agent/dist/modes/interactive/controllers/extension-ui-controller.js +58 -0
  61. package/packages/pi-coding-agent/dist/modes/interactive/controllers/extension-ui-controller.js.map +1 -0
  62. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.d.ts +12 -0
  63. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.d.ts.map +1 -0
  64. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js +54 -0
  65. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js.map +1 -0
  66. package/packages/pi-coding-agent/dist/modes/interactive/controllers/model-controller.d.ts +6 -0
  67. package/packages/pi-coding-agent/dist/modes/interactive/controllers/model-controller.d.ts.map +1 -0
  68. package/packages/pi-coding-agent/dist/modes/interactive/controllers/model-controller.js +63 -0
  69. package/packages/pi-coding-agent/dist/modes/interactive/controllers/model-controller.js.map +1 -0
  70. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts +38 -0
  71. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts.map +1 -0
  72. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.js +2 -0
  73. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.js.map +1 -0
  74. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +1 -1
  75. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  76. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +15 -457
  77. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  78. package/packages/pi-coding-agent/package.json +1 -1
  79. package/packages/pi-coding-agent/src/core/agent-session.ts +122 -23
  80. package/packages/pi-coding-agent/src/core/skill-tool.test.ts +89 -0
  81. package/packages/pi-coding-agent/src/core/skills.ts +2 -1
  82. package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +302 -0
  83. package/packages/pi-coding-agent/src/modes/interactive/controllers/extension-ui-controller.ts +59 -0
  84. package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.ts +68 -0
  85. package/packages/pi-coding-agent/src/modes/interactive/controllers/model-controller.ts +71 -0
  86. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode-state.ts +37 -0
  87. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +18 -510
  88. package/pkg/package.json +1 -1
  89. package/src/resources/extensions/async-jobs/index.ts +11 -0
  90. package/src/resources/extensions/get-secrets-from-user.ts +1 -1
  91. package/src/resources/extensions/gsd/auto-dashboard.ts +10 -0
  92. package/src/resources/extensions/gsd/auto-loop.ts +1075 -921
  93. package/src/resources/extensions/gsd/auto-post-unit.ts +10 -2
  94. package/src/resources/extensions/gsd/auto-prompts.ts +3 -3
  95. package/src/resources/extensions/gsd/auto-start.ts +6 -1
  96. package/src/resources/extensions/gsd/auto.ts +13 -10
  97. package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +142 -0
  98. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +238 -0
  99. package/src/resources/extensions/gsd/bootstrap/dynamic-tools.ts +90 -0
  100. package/src/resources/extensions/gsd/bootstrap/register-extension.ts +46 -0
  101. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +167 -0
  102. package/src/resources/extensions/gsd/bootstrap/register-shortcuts.ts +55 -0
  103. package/src/resources/extensions/gsd/bootstrap/system-context.ts +340 -0
  104. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +51 -0
  105. package/src/resources/extensions/gsd/commands/catalog.ts +301 -0
  106. package/src/resources/extensions/gsd/commands/context.ts +101 -0
  107. package/src/resources/extensions/gsd/commands/dispatcher.ts +32 -0
  108. package/src/resources/extensions/gsd/commands/handlers/auto.ts +74 -0
  109. package/src/resources/extensions/gsd/commands/handlers/core.ts +274 -0
  110. package/src/resources/extensions/gsd/commands/handlers/ops.ts +169 -0
  111. package/src/resources/extensions/gsd/commands/handlers/parallel.ts +118 -0
  112. package/src/resources/extensions/gsd/commands/handlers/workflow.ts +109 -0
  113. package/src/resources/extensions/gsd/commands/index.ts +14 -0
  114. package/src/resources/extensions/gsd/commands-handlers.ts +1 -1
  115. package/src/resources/extensions/gsd/commands.ts +10 -1329
  116. package/src/resources/extensions/gsd/dashboard-overlay.ts +10 -0
  117. package/src/resources/extensions/gsd/doctor-proactive.ts +106 -10
  118. package/src/resources/extensions/gsd/doctor.ts +47 -3
  119. package/src/resources/extensions/gsd/export-html.ts +51 -0
  120. package/src/resources/extensions/gsd/files.ts +1 -1
  121. package/src/resources/extensions/gsd/health-widget.ts +2 -1
  122. package/src/resources/extensions/gsd/index.ts +12 -1314
  123. package/src/resources/extensions/gsd/progress-score.ts +23 -0
  124. package/src/resources/extensions/gsd/prompts/forensics.md +121 -46
  125. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +13 -9
  126. package/src/resources/extensions/gsd/tests/doctor-proactive.test.ts +3 -3
  127. package/src/resources/extensions/gsd/tests/provider-errors.test.ts +16 -16
  128. package/src/resources/extensions/gsd/tests/skill-activation.test.ts +4 -4
  129. package/src/resources/extensions/gsd/tests/visualizer-data.test.ts +10 -10
  130. package/src/resources/extensions/gsd/visualizer-data.ts +51 -1
  131. package/src/resources/extensions/gsd/visualizer-views.ts +58 -0
  132. /package/dist/resources/extensions/{env-utils.js → gsd/env-utils.js} +0 -0
  133. /package/src/resources/extensions/{env-utils.ts → gsd/env-utils.ts} +0 -0
@@ -45,8 +45,12 @@ import { ToolExecutionComponent } from "./components/tool-execution.js";
45
45
  import { TreeSelectorComponent } from "./components/tree-selector.js";
46
46
  import { UserMessageComponent } from "./components/user-message.js";
47
47
  import { UserMessageSelectorComponent } from "./components/user-message-selector.js";
48
- import { dispatchSlashCommand, getAppKeyDisplay } from "./slash-command-handlers.js";
49
- import { getAvailableThemes, getAvailableThemesWithPaths, getEditorTheme, getMarkdownTheme, getThemeByName, initTheme, onThemeChange, setRegisteredThemes, setTheme, setThemeInstance, Theme, theme, } from "./theme/theme.js";
48
+ import { getAppKeyDisplay } from "./slash-command-handlers.js";
49
+ import { handleAgentEvent } from "./controllers/chat-controller.js";
50
+ import { createExtensionUIContext as buildExtensionUIContext } from "./controllers/extension-ui-controller.js";
51
+ import { setupEditorSubmitHandler as setupEditorSubmitHandlerController } from "./controllers/input-controller.js";
52
+ import { findExactModelMatch as findExactModelMatchController, getModelCandidates as getModelCandidatesController, handleModelCommand as handleModelCommandController, updateAvailableProviderCount as updateAvailableProviderCountController, } from "./controllers/model-controller.js";
53
+ import { getAvailableThemes, getEditorTheme, getMarkdownTheme, initTheme, onThemeChange, setRegisteredThemes, setTheme, theme, } from "./theme/theme.js";
50
54
  function isExpandable(obj) {
51
55
  return typeof obj === "object" && obj !== null && "setExpanded" in obj && typeof obj.setExpanded === "function";
52
56
  }
@@ -896,12 +900,10 @@ export class InteractiveMode {
896
900
  this.showLoadedResources({ extensionPaths: extensionRunner.getExtensionPaths(), force: false });
897
901
  }
898
902
  /**
899
- * Get a registered tool definition by name (for custom rendering).
903
+ * Get a tool definition by name (for custom rendering).
900
904
  */
901
905
  getRegisteredToolDefinition(toolName) {
902
- const tools = this.session.extensionRunner?.getAllRegisteredTools() ?? [];
903
- const registeredTool = tools.find((t) => t.definition.name === toolName);
904
- return registeredTool?.definition;
906
+ return this.session.getRenderableToolDefinition(toolName);
905
907
  }
906
908
  /**
907
909
  * Format web search result content for display in the TUI.
@@ -1168,60 +1170,7 @@ export class InteractiveMode {
1168
1170
  * Create the ExtensionUIContext for extensions.
1169
1171
  */
1170
1172
  createExtensionUIContext() {
1171
- return {
1172
- select: (title, options, opts) => this.showExtensionSelector(title, options, opts),
1173
- confirm: (title, message, opts) => this.showExtensionConfirm(title, message, opts),
1174
- input: (title, placeholder, opts) => this.showExtensionInput(title, placeholder, opts),
1175
- notify: (message, type) => this.showExtensionNotify(message, type),
1176
- onTerminalInput: (handler) => this.addExtensionTerminalInputListener(handler),
1177
- setStatus: (key, text) => this.setExtensionStatus(key, text),
1178
- setWorkingMessage: (message) => {
1179
- if (this.loadingAnimation) {
1180
- if (message) {
1181
- this.loadingAnimation.setMessage(message);
1182
- }
1183
- else {
1184
- this.loadingAnimation.setMessage(`${this.defaultWorkingMessage} (${appKey(this.keybindings, "interrupt")} to interrupt)`);
1185
- }
1186
- }
1187
- else {
1188
- // Queue message for when loadingAnimation is created (handles agent_start race)
1189
- this.pendingWorkingMessage = message;
1190
- }
1191
- },
1192
- setWidget: (key, content, options) => this.setExtensionWidget(key, content, options),
1193
- setFooter: (factory) => this.setExtensionFooter(factory),
1194
- setHeader: (factory) => this.setExtensionHeader(factory),
1195
- setTitle: (title) => this.ui.terminal.setTitle(title),
1196
- custom: (factory, options) => this.showExtensionCustom(factory, options),
1197
- pasteToEditor: (text) => this.editor.handleInput(`\x1b[200~${text}\x1b[201~`),
1198
- setEditorText: (text) => this.editor.setText(text),
1199
- getEditorText: () => this.editor.getText(),
1200
- editor: (title, prefill) => this.showExtensionEditor(title, prefill),
1201
- setEditorComponent: (factory) => this.setCustomEditorComponent(factory),
1202
- get theme() {
1203
- return theme;
1204
- },
1205
- getAllThemes: () => getAvailableThemesWithPaths(),
1206
- getTheme: (name) => getThemeByName(name),
1207
- setTheme: (themeOrName) => {
1208
- if (themeOrName instanceof Theme) {
1209
- setThemeInstance(themeOrName);
1210
- this.ui.requestRender();
1211
- return { success: true };
1212
- }
1213
- const result = setTheme(themeOrName, true);
1214
- if (result.success) {
1215
- if (this.settingsManager.getTheme() !== themeOrName) {
1216
- this.settingsManager.setTheme(themeOrName);
1217
- }
1218
- this.ui.requestRender();
1219
- }
1220
- return result;
1221
- },
1222
- getToolsExpanded: () => this.toolOutputExpanded,
1223
- setToolsExpanded: (expanded) => this.setToolsExpanded(expanded),
1224
- };
1173
+ return buildExtensionUIContext(this);
1225
1174
  }
1226
1175
  /**
1227
1176
  * Show a selector for extensions.
@@ -1628,65 +1577,7 @@ export class InteractiveMode {
1628
1577
  };
1629
1578
  }
1630
1579
  setupEditorSubmitHandler() {
1631
- this.defaultEditor.onSubmit = async (text) => {
1632
- text = text.trim();
1633
- if (!text)
1634
- return;
1635
- // Handle slash commands
1636
- if (text.startsWith("/")) {
1637
- const handled = await dispatchSlashCommand(text, this.getSlashCommandContext());
1638
- if (handled) {
1639
- this.editor.setText("");
1640
- return;
1641
- }
1642
- }
1643
- // Handle bash command (! for normal, !! for excluded from context)
1644
- if (text.startsWith("!")) {
1645
- const isExcluded = text.startsWith("!!");
1646
- const command = isExcluded ? text.slice(2).trim() : text.slice(1).trim();
1647
- if (command) {
1648
- if (this.session.isBashRunning) {
1649
- this.showWarning("A bash command is already running. Press Esc to cancel it first.");
1650
- this.editor.setText(text);
1651
- return;
1652
- }
1653
- this.editor.addToHistory?.(text);
1654
- await this.handleBashCommand(command, isExcluded);
1655
- this.isBashMode = false;
1656
- this.updateEditorBorderColor();
1657
- return;
1658
- }
1659
- }
1660
- // Queue input during compaction (extension commands execute immediately)
1661
- if (this.session.isCompacting) {
1662
- if (this.isExtensionCommand(text)) {
1663
- this.editor.addToHistory?.(text);
1664
- this.editor.setText("");
1665
- await this.session.prompt(text);
1666
- }
1667
- else {
1668
- this.queueCompactionMessage(text, "steer");
1669
- }
1670
- return;
1671
- }
1672
- // If streaming, use prompt() with steer behavior
1673
- // This handles extension commands (execute immediately), prompt template expansion, and queueing
1674
- if (this.session.isStreaming) {
1675
- this.editor.addToHistory?.(text);
1676
- this.editor.setText("");
1677
- await this.session.prompt(text, { streamingBehavior: "steer" });
1678
- this.updatePendingMessagesDisplay();
1679
- this.ui.requestRender();
1680
- return;
1681
- }
1682
- // Normal message submission
1683
- // First, move any pending bash components to chat
1684
- this.flushPendingBashComponents();
1685
- if (this.onInputCallback) {
1686
- this.onInputCallback(text);
1687
- }
1688
- this.editor.addToHistory?.(text);
1689
- };
1580
+ setupEditorSubmitHandlerController(this);
1690
1581
  }
1691
1582
  subscribeToAgent() {
1692
1583
  this.unsubscribe = this.session.subscribe(async (event) => {
@@ -1694,290 +1585,7 @@ export class InteractiveMode {
1694
1585
  });
1695
1586
  }
1696
1587
  async handleEvent(event) {
1697
- if (!this.isInitialized) {
1698
- await this.init();
1699
- }
1700
- this.footer.invalidate();
1701
- switch (event.type) {
1702
- case "agent_start":
1703
- // Restore main escape handler if retry handler is still active
1704
- // (retry success event fires later, but we need main handler now)
1705
- if (this.retryEscapeHandler) {
1706
- this.defaultEditor.onEscape = this.retryEscapeHandler;
1707
- this.retryEscapeHandler = undefined;
1708
- }
1709
- if (this.retryLoader) {
1710
- this.retryLoader.stop();
1711
- this.retryLoader = undefined;
1712
- }
1713
- if (this.loadingAnimation) {
1714
- this.loadingAnimation.stop();
1715
- }
1716
- this.statusContainer.clear();
1717
- this.loadingAnimation = new Loader(this.ui, (spinner) => theme.fg("accent", spinner), (text) => theme.fg("muted", text), this.defaultWorkingMessage);
1718
- this.statusContainer.addChild(this.loadingAnimation);
1719
- // Apply any pending working message queued before loader existed
1720
- if (this.pendingWorkingMessage !== undefined) {
1721
- if (this.pendingWorkingMessage) {
1722
- this.loadingAnimation.setMessage(this.pendingWorkingMessage);
1723
- }
1724
- this.pendingWorkingMessage = undefined;
1725
- }
1726
- this.ui.requestRender();
1727
- break;
1728
- case "message_start":
1729
- if (event.message.role === "custom") {
1730
- this.addMessageToChat(event.message);
1731
- this.ui.requestRender();
1732
- }
1733
- else if (event.message.role === "user") {
1734
- this.addMessageToChat(event.message);
1735
- this.updatePendingMessagesDisplay();
1736
- this.ui.requestRender();
1737
- }
1738
- else if (event.message.role === "assistant") {
1739
- this.streamingComponent = new AssistantMessageComponent(undefined, this.hideThinkingBlock, this.getMarkdownThemeWithSettings());
1740
- this.streamingMessage = event.message;
1741
- this.chatContainer.addChild(this.streamingComponent);
1742
- this.streamingComponent.updateContent(this.streamingMessage);
1743
- this.ui.requestRender();
1744
- }
1745
- break;
1746
- case "message_update":
1747
- if (this.streamingComponent && event.message.role === "assistant") {
1748
- this.streamingMessage = event.message;
1749
- this.streamingComponent.updateContent(this.streamingMessage);
1750
- for (const content of this.streamingMessage.content) {
1751
- if (content.type === "toolCall") {
1752
- if (!this.pendingTools.has(content.id)) {
1753
- const component = new ToolExecutionComponent(content.name, content.arguments, {
1754
- showImages: this.settingsManager.getShowImages(),
1755
- }, this.getRegisteredToolDefinition(content.name), this.ui);
1756
- component.setExpanded(this.toolOutputExpanded);
1757
- this.chatContainer.addChild(component);
1758
- this.pendingTools.set(content.id, component);
1759
- }
1760
- else {
1761
- const component = this.pendingTools.get(content.id);
1762
- if (component) {
1763
- component.updateArgs(content.arguments);
1764
- }
1765
- }
1766
- }
1767
- else if (content.type === "serverToolUse") {
1768
- // Server-side tool (e.g., native web search) — show as pending tool execution
1769
- if (!this.pendingTools.has(content.id)) {
1770
- const component = new ToolExecutionComponent(content.name, content.input ?? {}, {
1771
- showImages: this.settingsManager.getShowImages(),
1772
- }, undefined, this.ui);
1773
- component.setExpanded(this.toolOutputExpanded);
1774
- this.chatContainer.addChild(component);
1775
- this.pendingTools.set(content.id, component);
1776
- }
1777
- }
1778
- else if (content.type === "webSearchResult") {
1779
- // Server-side tool result — resolve the pending server tool execution
1780
- const component = this.pendingTools.get(content.toolUseId);
1781
- if (component) {
1782
- const searchContent = content.content;
1783
- const isError = searchContent && typeof searchContent === "object" && "type" in searchContent && searchContent.type === "web_search_tool_result_error";
1784
- const resultText = this.formatWebSearchResult(searchContent);
1785
- component.updateResult({
1786
- content: [{ type: "text", text: resultText }],
1787
- isError: !!isError,
1788
- });
1789
- this.pendingTools.delete(content.toolUseId);
1790
- }
1791
- }
1792
- }
1793
- this.ui.requestRender();
1794
- }
1795
- break;
1796
- case "message_end":
1797
- if (event.message.role === "user")
1798
- break;
1799
- if (this.streamingComponent && event.message.role === "assistant") {
1800
- this.streamingMessage = event.message;
1801
- let errorMessage;
1802
- if (this.streamingMessage.stopReason === "aborted") {
1803
- const retryAttempt = this.session.retryAttempt;
1804
- errorMessage =
1805
- retryAttempt > 0
1806
- ? `Aborted after ${retryAttempt} retry attempt${retryAttempt > 1 ? "s" : ""}`
1807
- : "Operation aborted";
1808
- this.streamingMessage.errorMessage = errorMessage;
1809
- }
1810
- this.streamingComponent.updateContent(this.streamingMessage);
1811
- if (this.streamingMessage.stopReason === "aborted" || this.streamingMessage.stopReason === "error") {
1812
- if (!errorMessage) {
1813
- errorMessage = this.streamingMessage.errorMessage || "Error";
1814
- }
1815
- for (const [, component] of this.pendingTools.entries()) {
1816
- component.updateResult({
1817
- content: [{ type: "text", text: errorMessage }],
1818
- isError: true,
1819
- });
1820
- }
1821
- this.pendingTools.clear();
1822
- }
1823
- else {
1824
- // Args are now complete - trigger diff computation for edit tools
1825
- for (const [, component] of this.pendingTools.entries()) {
1826
- component.setArgsComplete();
1827
- }
1828
- }
1829
- this.streamingComponent = undefined;
1830
- this.streamingMessage = undefined;
1831
- this.footer.invalidate();
1832
- }
1833
- this.ui.requestRender();
1834
- break;
1835
- case "tool_execution_start": {
1836
- if (!this.pendingTools.has(event.toolCallId)) {
1837
- const component = new ToolExecutionComponent(event.toolName, event.args, {
1838
- showImages: this.settingsManager.getShowImages(),
1839
- }, this.getRegisteredToolDefinition(event.toolName), this.ui);
1840
- component.setExpanded(this.toolOutputExpanded);
1841
- this.chatContainer.addChild(component);
1842
- this.pendingTools.set(event.toolCallId, component);
1843
- this.ui.requestRender();
1844
- }
1845
- break;
1846
- }
1847
- case "tool_execution_update": {
1848
- const component = this.pendingTools.get(event.toolCallId);
1849
- if (component) {
1850
- component.updateResult({ ...event.partialResult, isError: false }, true);
1851
- this.ui.requestRender();
1852
- }
1853
- break;
1854
- }
1855
- case "tool_execution_end": {
1856
- const component = this.pendingTools.get(event.toolCallId);
1857
- if (component) {
1858
- component.updateResult({ ...event.result, isError: event.isError });
1859
- this.pendingTools.delete(event.toolCallId);
1860
- this.ui.requestRender();
1861
- }
1862
- break;
1863
- }
1864
- case "agent_end":
1865
- if (this.loadingAnimation) {
1866
- this.loadingAnimation.stop();
1867
- this.loadingAnimation = undefined;
1868
- this.statusContainer.clear();
1869
- }
1870
- if (this.streamingComponent) {
1871
- this.chatContainer.removeChild(this.streamingComponent);
1872
- this.streamingComponent = undefined;
1873
- this.streamingMessage = undefined;
1874
- }
1875
- this.pendingTools.clear();
1876
- await this.checkShutdownRequested();
1877
- this.ui.requestRender();
1878
- break;
1879
- case "auto_compaction_start": {
1880
- // Keep editor active; submissions are queued during compaction.
1881
- // Set up escape to abort auto-compaction
1882
- this.autoCompactionEscapeHandler = this.defaultEditor.onEscape;
1883
- this.defaultEditor.onEscape = () => {
1884
- this.session.abortCompaction();
1885
- };
1886
- // Show compacting indicator with reason
1887
- this.statusContainer.clear();
1888
- const reasonText = event.reason === "overflow" ? "Context overflow detected, " : "";
1889
- this.autoCompactionLoader = new Loader(this.ui, (spinner) => theme.fg("accent", spinner), (text) => theme.fg("muted", text), `${reasonText}Auto-compacting... (${appKey(this.keybindings, "interrupt")} to cancel)`);
1890
- this.statusContainer.addChild(this.autoCompactionLoader);
1891
- this.ui.requestRender();
1892
- break;
1893
- }
1894
- case "auto_compaction_end": {
1895
- // Restore escape handler
1896
- if (this.autoCompactionEscapeHandler) {
1897
- this.defaultEditor.onEscape = this.autoCompactionEscapeHandler;
1898
- this.autoCompactionEscapeHandler = undefined;
1899
- }
1900
- // Stop loader
1901
- if (this.autoCompactionLoader) {
1902
- this.autoCompactionLoader.stop();
1903
- this.autoCompactionLoader = undefined;
1904
- this.statusContainer.clear();
1905
- }
1906
- // Handle result
1907
- if (event.aborted) {
1908
- this.showStatus("Auto-compaction cancelled");
1909
- }
1910
- else if (event.result) {
1911
- // Rebuild chat to show compacted state
1912
- this.chatContainer.clear();
1913
- this.rebuildChatFromMessages();
1914
- // Add compaction component at bottom so user sees it without scrolling
1915
- this.addMessageToChat({
1916
- role: "compactionSummary",
1917
- tokensBefore: event.result.tokensBefore,
1918
- summary: event.result.summary,
1919
- timestamp: Date.now(),
1920
- });
1921
- this.footer.invalidate();
1922
- }
1923
- else if (event.errorMessage) {
1924
- // Compaction failed (e.g., quota exceeded, API error)
1925
- this.chatContainer.addChild(new Spacer(1));
1926
- this.chatContainer.addChild(new Text(theme.fg("error", event.errorMessage), 1, 0));
1927
- }
1928
- void this.flushCompactionQueue({ willRetry: event.willRetry });
1929
- this.ui.requestRender();
1930
- break;
1931
- }
1932
- case "auto_retry_start": {
1933
- // Set up escape to abort retry
1934
- this.retryEscapeHandler = this.defaultEditor.onEscape;
1935
- this.defaultEditor.onEscape = () => {
1936
- this.session.abortRetry();
1937
- };
1938
- // Show retry indicator
1939
- this.statusContainer.clear();
1940
- const delaySeconds = Math.round(event.delayMs / 1000);
1941
- this.retryLoader = new Loader(this.ui, (spinner) => theme.fg("warning", spinner), (text) => theme.fg("muted", text), `Retrying (${event.attempt}/${event.maxAttempts}) in ${delaySeconds}s... (${appKey(this.keybindings, "interrupt")} to cancel)`);
1942
- this.statusContainer.addChild(this.retryLoader);
1943
- this.ui.requestRender();
1944
- break;
1945
- }
1946
- case "auto_retry_end": {
1947
- // Restore escape handler
1948
- if (this.retryEscapeHandler) {
1949
- this.defaultEditor.onEscape = this.retryEscapeHandler;
1950
- this.retryEscapeHandler = undefined;
1951
- }
1952
- // Stop loader
1953
- if (this.retryLoader) {
1954
- this.retryLoader.stop();
1955
- this.retryLoader = undefined;
1956
- this.statusContainer.clear();
1957
- }
1958
- // Show error only on final failure (success shows normal response)
1959
- if (!event.success) {
1960
- this.showError(`Retry failed after ${event.attempt} attempts: ${event.finalError || "Unknown error"}`);
1961
- }
1962
- this.ui.requestRender();
1963
- break;
1964
- }
1965
- case "fallback_provider_switch": {
1966
- this.showStatus(`Switched from ${event.from} → ${event.to} (${event.reason})`);
1967
- this.ui.requestRender();
1968
- break;
1969
- }
1970
- case "fallback_provider_restored": {
1971
- this.showStatus(`Restored to ${event.provider}`);
1972
- this.ui.requestRender();
1973
- break;
1974
- }
1975
- case "fallback_chain_exhausted": {
1976
- this.showError(event.reason);
1977
- this.ui.requestRender();
1978
- break;
1979
- }
1980
- }
1588
+ await handleAgentEvent(this, event);
1981
1589
  }
1982
1590
  /** Extract text content from a user message */
1983
1591
  getUserMessageText(message) {
@@ -2750,67 +2358,17 @@ export class InteractiveMode {
2750
2358
  });
2751
2359
  }
2752
2360
  async handleModelCommand(searchTerm) {
2753
- if (!searchTerm) {
2754
- this.showModelSelector();
2755
- return;
2756
- }
2757
- const model = await this.findExactModelMatch(searchTerm);
2758
- if (model) {
2759
- try {
2760
- await this.session.setModel(model);
2761
- this.footer.invalidate();
2762
- this.updateEditorBorderColor();
2763
- this.showStatus(`Model: ${model.id}`);
2764
- this.checkDaxnutsEasterEgg(model);
2765
- }
2766
- catch (error) {
2767
- this.showError(error instanceof Error ? error.message : String(error));
2768
- }
2769
- return;
2770
- }
2771
- this.showModelSelector(searchTerm);
2361
+ await handleModelCommandController(this, searchTerm);
2772
2362
  }
2773
2363
  async findExactModelMatch(searchTerm) {
2774
- const term = searchTerm.trim();
2775
- if (!term)
2776
- return undefined;
2777
- let targetProvider;
2778
- let targetModelId = "";
2779
- if (term.includes("/")) {
2780
- const parts = term.split("/", 2);
2781
- targetProvider = parts[0]?.trim().toLowerCase();
2782
- targetModelId = parts[1]?.trim().toLowerCase() ?? "";
2783
- }
2784
- else {
2785
- targetModelId = term.toLowerCase();
2786
- }
2787
- if (!targetModelId)
2788
- return undefined;
2789
- const models = await this.getModelCandidates();
2790
- const exactMatches = models.filter((item) => {
2791
- const idMatch = item.id.toLowerCase() === targetModelId;
2792
- const providerMatch = !targetProvider || item.provider.toLowerCase() === targetProvider;
2793
- return idMatch && providerMatch;
2794
- });
2795
- return exactMatches.length === 1 ? exactMatches[0] : undefined;
2364
+ return findExactModelMatchController(this, searchTerm);
2796
2365
  }
2797
2366
  async getModelCandidates() {
2798
- if (this.session.scopedModels.length > 0) {
2799
- return this.session.scopedModels.map((scoped) => scoped.model);
2800
- }
2801
- this.session.modelRegistry.refresh();
2802
- try {
2803
- return await this.session.modelRegistry.getAvailable();
2804
- }
2805
- catch {
2806
- return [];
2807
- }
2367
+ return getModelCandidatesController(this);
2808
2368
  }
2809
2369
  /** Update the footer's available provider count from current model candidates */
2810
2370
  async updateAvailableProviderCount() {
2811
- const models = await this.getModelCandidates();
2812
- const uniqueProviders = new Set(models.map((m) => m.provider));
2813
- this.footerDataProvider.setAvailableProviderCount(uniqueProviders.size);
2371
+ await updateAvailableProviderCountController(this);
2814
2372
  }
2815
2373
  showModelSelector(initialSearchInput) {
2816
2374
  this.showSelector((done) => {