lsd-pi 1.3.2 → 1.3.7

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 (205) hide show
  1. package/dist/cli.js +2 -1
  2. package/dist/lsd-settings-manager.d.ts +2 -0
  3. package/dist/lsd-settings-manager.js +5 -0
  4. package/dist/resource-loader.js +33 -3
  5. package/dist/resources/extensions/browser-tools/tools/codegen.js +5 -5
  6. package/dist/resources/extensions/browser-tools/tools/navigation.js +107 -178
  7. package/dist/resources/extensions/browser-tools/tools/network-mock.js +112 -167
  8. package/dist/resources/extensions/browser-tools/tools/pages.js +182 -234
  9. package/dist/resources/extensions/browser-tools/tools/refs.js +202 -461
  10. package/dist/resources/extensions/browser-tools/tools/session.js +176 -323
  11. package/dist/resources/extensions/browser-tools/tools/state-persistence.js +91 -154
  12. package/dist/resources/extensions/browser-tools/utils.js +1 -1
  13. package/dist/resources/extensions/cache-timer/index.js +3 -2
  14. package/dist/resources/extensions/slash-commands/extension-manifest.json +2 -2
  15. package/dist/resources/extensions/slash-commands/fast.js +73 -0
  16. package/dist/resources/extensions/slash-commands/index.js +2 -0
  17. package/dist/resources/extensions/slash-commands/plan.js +37 -12
  18. package/dist/resources/extensions/subagent/background-job-manager.js +13 -0
  19. package/dist/resources/extensions/subagent/in-process-runner.js +387 -0
  20. package/dist/resources/extensions/subagent/index.js +278 -626
  21. package/dist/resources/extensions/subagent/legacy-runner.js +503 -0
  22. package/dist/resources/extensions/voice/index.js +96 -36
  23. package/dist/resources/extensions/voice/push-to-talk.js +26 -0
  24. package/dist/welcome-screen.js +2 -2
  25. package/package.json +1 -1
  26. package/packages/pi-agent-core/dist/agent.d.ts +19 -0
  27. package/packages/pi-agent-core/dist/agent.d.ts.map +1 -1
  28. package/packages/pi-agent-core/dist/agent.js +16 -0
  29. package/packages/pi-agent-core/dist/agent.js.map +1 -1
  30. package/packages/pi-agent-core/src/agent.ts +32 -2
  31. package/packages/pi-ai/dist/providers/openai-codex-responses.d.ts +34 -1
  32. package/packages/pi-ai/dist/providers/openai-codex-responses.d.ts.map +1 -1
  33. package/packages/pi-ai/dist/providers/openai-codex-responses.js +32 -4
  34. package/packages/pi-ai/dist/providers/openai-codex-responses.js.map +1 -1
  35. package/packages/pi-ai/dist/providers/openai-codex-responses.test.js +127 -16
  36. package/packages/pi-ai/dist/providers/openai-codex-responses.test.js.map +1 -1
  37. package/packages/pi-ai/dist/providers/openai-responses.d.ts +8 -1
  38. package/packages/pi-ai/dist/providers/openai-responses.d.ts.map +1 -1
  39. package/packages/pi-ai/dist/providers/openai-responses.fast-mode.test.d.ts +2 -0
  40. package/packages/pi-ai/dist/providers/openai-responses.fast-mode.test.d.ts.map +1 -0
  41. package/packages/pi-ai/dist/providers/openai-responses.fast-mode.test.js +67 -0
  42. package/packages/pi-ai/dist/providers/openai-responses.fast-mode.test.js.map +1 -0
  43. package/packages/pi-ai/dist/providers/openai-responses.js +21 -3
  44. package/packages/pi-ai/dist/providers/openai-responses.js.map +1 -1
  45. package/packages/pi-ai/dist/providers/simple-options.d.ts.map +1 -1
  46. package/packages/pi-ai/dist/providers/simple-options.js +2 -0
  47. package/packages/pi-ai/dist/providers/simple-options.js.map +1 -1
  48. package/packages/pi-ai/dist/types.d.ts +5 -0
  49. package/packages/pi-ai/dist/types.d.ts.map +1 -1
  50. package/packages/pi-ai/dist/types.js.map +1 -1
  51. package/packages/pi-ai/src/providers/openai-codex-responses.test.ts +143 -20
  52. package/packages/pi-ai/src/providers/openai-codex-responses.ts +47 -4
  53. package/packages/pi-ai/src/providers/openai-responses.fast-mode.test.ts +73 -0
  54. package/packages/pi-ai/src/providers/openai-responses.ts +26 -3
  55. package/packages/pi-ai/src/providers/simple-options.ts +2 -0
  56. package/packages/pi-ai/src/types.ts +5 -0
  57. package/packages/pi-coding-agent/dist/core/keybindings.d.ts +1 -1
  58. package/packages/pi-coding-agent/dist/core/keybindings.d.ts.map +1 -1
  59. package/packages/pi-coding-agent/dist/core/keybindings.js +2 -0
  60. package/packages/pi-coding-agent/dist/core/keybindings.js.map +1 -1
  61. package/packages/pi-coding-agent/dist/core/sdk.d.ts.map +1 -1
  62. package/packages/pi-coding-agent/dist/core/sdk.js +4 -2
  63. package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
  64. package/packages/pi-coding-agent/dist/core/settings-manager.collapse-tool-calls.test.d.ts +2 -0
  65. package/packages/pi-coding-agent/dist/core/settings-manager.collapse-tool-calls.test.d.ts.map +1 -0
  66. package/packages/pi-coding-agent/dist/core/settings-manager.collapse-tool-calls.test.js +35 -0
  67. package/packages/pi-coding-agent/dist/core/settings-manager.collapse-tool-calls.test.js.map +1 -0
  68. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +12 -0
  69. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
  70. package/packages/pi-coding-agent/dist/core/settings-manager.fast-mode.test.d.ts +2 -0
  71. package/packages/pi-coding-agent/dist/core/settings-manager.fast-mode.test.d.ts.map +1 -0
  72. package/packages/pi-coding-agent/dist/core/settings-manager.fast-mode.test.js +35 -0
  73. package/packages/pi-coding-agent/dist/core/settings-manager.fast-mode.test.js.map +1 -0
  74. package/packages/pi-coding-agent/dist/core/settings-manager.js +24 -0
  75. package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
  76. package/packages/pi-coding-agent/dist/core/slash-commands.d.ts.map +1 -1
  77. package/packages/pi-coding-agent/dist/core/slash-commands.js +1 -0
  78. package/packages/pi-coding-agent/dist/core/slash-commands.js.map +1 -1
  79. package/packages/pi-coding-agent/dist/core/system-prompt.d.ts.map +1 -1
  80. package/packages/pi-coding-agent/dist/core/system-prompt.js +6 -1
  81. package/packages/pi-coding-agent/dist/core/system-prompt.js.map +1 -1
  82. package/packages/pi-coding-agent/dist/core/tool-priority.d.ts +4 -0
  83. package/packages/pi-coding-agent/dist/core/tool-priority.d.ts.map +1 -0
  84. package/packages/pi-coding-agent/dist/core/tool-priority.js +18 -0
  85. package/packages/pi-coding-agent/dist/core/tool-priority.js.map +1 -0
  86. package/packages/pi-coding-agent/dist/core/tool-priority.test.d.ts +2 -0
  87. package/packages/pi-coding-agent/dist/core/tool-priority.test.d.ts.map +1 -0
  88. package/packages/pi-coding-agent/dist/core/tool-priority.test.js +27 -0
  89. package/packages/pi-coding-agent/dist/core/tool-priority.test.js.map +1 -0
  90. package/packages/pi-coding-agent/dist/core/tools/edit-diff.d.ts +5 -0
  91. package/packages/pi-coding-agent/dist/core/tools/edit-diff.d.ts.map +1 -1
  92. package/packages/pi-coding-agent/dist/core/tools/edit-diff.js +21 -0
  93. package/packages/pi-coding-agent/dist/core/tools/edit-diff.js.map +1 -1
  94. package/packages/pi-coding-agent/dist/core/tools/edit-diff.test.js +16 -1
  95. package/packages/pi-coding-agent/dist/core/tools/edit-diff.test.js.map +1 -1
  96. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-summary-line.test.d.ts +2 -0
  97. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-summary-line.test.d.ts.map +1 -0
  98. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-summary-line.test.js +34 -0
  99. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-summary-line.test.js.map +1 -0
  100. package/packages/pi-coding-agent/dist/modes/interactive/components/btw-overlay.d.ts +45 -0
  101. package/packages/pi-coding-agent/dist/modes/interactive/components/btw-overlay.d.ts.map +1 -0
  102. package/packages/pi-coding-agent/dist/modes/interactive/components/btw-overlay.js +314 -0
  103. package/packages/pi-coding-agent/dist/modes/interactive/components/btw-overlay.js.map +1 -0
  104. package/packages/pi-coding-agent/dist/modes/interactive/components/btw-overlay.test.d.ts +2 -0
  105. package/packages/pi-coding-agent/dist/modes/interactive/components/btw-overlay.test.d.ts.map +1 -0
  106. package/packages/pi-coding-agent/dist/modes/interactive/components/btw-overlay.test.js +122 -0
  107. package/packages/pi-coding-agent/dist/modes/interactive/components/btw-overlay.test.js.map +1 -0
  108. package/packages/pi-coding-agent/dist/modes/interactive/components/diff.d.ts +7 -5
  109. package/packages/pi-coding-agent/dist/modes/interactive/components/diff.d.ts.map +1 -1
  110. package/packages/pi-coding-agent/dist/modes/interactive/components/diff.js +86 -28
  111. package/packages/pi-coding-agent/dist/modes/interactive/components/diff.js.map +1 -1
  112. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.d.ts +4 -0
  113. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.d.ts.map +1 -1
  114. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js +23 -10
  115. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js.map +1 -1
  116. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.d.ts +8 -0
  117. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  118. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.js +52 -6
  119. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.js.map +1 -1
  120. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts +19 -0
  121. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  122. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +127 -14
  123. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
  124. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-summary-line.d.ts +14 -0
  125. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-summary-line.d.ts.map +1 -0
  126. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-summary-line.js +93 -0
  127. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-summary-line.js.map +1 -0
  128. package/packages/pi-coding-agent/dist/modes/interactive/controllers/__tests__/chat-controller.collapsed-tool-summary.test.d.ts +2 -0
  129. package/packages/pi-coding-agent/dist/modes/interactive/controllers/__tests__/chat-controller.collapsed-tool-summary.test.d.ts.map +1 -0
  130. package/packages/pi-coding-agent/dist/modes/interactive/controllers/__tests__/chat-controller.collapsed-tool-summary.test.js +328 -0
  131. package/packages/pi-coding-agent/dist/modes/interactive/controllers/__tests__/chat-controller.collapsed-tool-summary.test.js.map +1 -0
  132. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
  133. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +123 -0
  134. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
  135. package/packages/pi-coding-agent/dist/modes/interactive/controllers/extension-ui-controller.js +1 -1
  136. package/packages/pi-coding-agent/dist/modes/interactive/controllers/extension-ui-controller.js.map +1 -1
  137. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.d.ts.map +1 -1
  138. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js +7 -0
  139. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js.map +1 -1
  140. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts +4 -0
  141. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts.map +1 -1
  142. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.js.map +1 -1
  143. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +9 -1
  144. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  145. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +103 -23
  146. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  147. package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.d.ts.map +1 -1
  148. package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.js +41 -0
  149. package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.js.map +1 -1
  150. package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.js +4 -4
  151. package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.js.map +1 -1
  152. package/packages/pi-coding-agent/package.json +1 -1
  153. package/packages/pi-coding-agent/src/core/keybindings.ts +4 -1
  154. package/packages/pi-coding-agent/src/core/sdk.ts +4 -2
  155. package/packages/pi-coding-agent/src/core/settings-manager.collapse-tool-calls.test.ts +46 -0
  156. package/packages/pi-coding-agent/src/core/settings-manager.fast-mode.test.ts +46 -0
  157. package/packages/pi-coding-agent/src/core/settings-manager.ts +36 -0
  158. package/packages/pi-coding-agent/src/core/slash-commands.ts +1 -0
  159. package/packages/pi-coding-agent/src/core/system-prompt.ts +6 -1
  160. package/packages/pi-coding-agent/src/core/tool-priority.test.ts +30 -0
  161. package/packages/pi-coding-agent/src/core/tool-priority.ts +17 -0
  162. package/packages/pi-coding-agent/src/core/tools/edit-diff.test.ts +20 -0
  163. package/packages/pi-coding-agent/src/core/tools/edit-diff.ts +26 -0
  164. package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/tool-summary-line.test.ts +41 -0
  165. package/packages/pi-coding-agent/src/modes/interactive/components/btw-overlay.test.ts +172 -0
  166. package/packages/pi-coding-agent/src/modes/interactive/components/btw-overlay.ts +402 -0
  167. package/packages/pi-coding-agent/src/modes/interactive/components/diff.ts +105 -28
  168. package/packages/pi-coding-agent/src/modes/interactive/components/footer.ts +21 -6
  169. package/packages/pi-coding-agent/src/modes/interactive/components/settings-selector.ts +63 -6
  170. package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +1262 -1138
  171. package/packages/pi-coding-agent/src/modes/interactive/components/tool-summary-line.ts +120 -0
  172. package/packages/pi-coding-agent/src/modes/interactive/controllers/__tests__/chat-controller.collapsed-tool-summary.test.ts +396 -0
  173. package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +530 -398
  174. package/packages/pi-coding-agent/src/modes/interactive/controllers/extension-ui-controller.ts +1 -1
  175. package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.ts +7 -0
  176. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode-state.ts +4 -0
  177. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +109 -23
  178. package/packages/pi-coding-agent/src/modes/interactive/slash-command-handlers.ts +60 -1
  179. package/packages/pi-coding-agent/src/modes/interactive/theme/themes.ts +4 -4
  180. package/packages/pi-tui/dist/components/editor.js +3 -3
  181. package/packages/pi-tui/dist/components/editor.js.map +1 -1
  182. package/packages/pi-tui/src/components/editor.ts +3 -3
  183. package/pkg/dist/modes/interactive/theme/themes.js +4 -4
  184. package/pkg/dist/modes/interactive/theme/themes.js.map +1 -1
  185. package/pkg/package.json +1 -1
  186. package/src/resources/extensions/browser-tools/tools/codegen.ts +5 -5
  187. package/src/resources/extensions/browser-tools/tools/navigation.ts +118 -196
  188. package/src/resources/extensions/browser-tools/tools/network-mock.ts +114 -205
  189. package/src/resources/extensions/browser-tools/tools/pages.ts +183 -237
  190. package/src/resources/extensions/browser-tools/tools/refs.ts +193 -507
  191. package/src/resources/extensions/browser-tools/tools/session.ts +182 -321
  192. package/src/resources/extensions/browser-tools/tools/state-persistence.ts +94 -172
  193. package/src/resources/extensions/browser-tools/utils.ts +1 -1
  194. package/src/resources/extensions/cache-timer/index.ts +3 -2
  195. package/src/resources/extensions/slash-commands/extension-manifest.json +2 -2
  196. package/src/resources/extensions/slash-commands/fast.ts +89 -0
  197. package/src/resources/extensions/slash-commands/index.ts +2 -0
  198. package/src/resources/extensions/slash-commands/plan.ts +42 -12
  199. package/src/resources/extensions/subagent/background-job-manager.ts +28 -0
  200. package/src/resources/extensions/subagent/in-process-runner.ts +534 -0
  201. package/src/resources/extensions/subagent/index.ts +489 -799
  202. package/src/resources/extensions/subagent/legacy-runner.ts +607 -0
  203. package/src/resources/extensions/voice/index.ts +308 -238
  204. package/src/resources/extensions/voice/push-to-talk.ts +42 -0
  205. package/src/resources/extensions/voice/tests/push-to-talk.test.ts +109 -0
@@ -1,427 +1,559 @@
1
+ import { spawn } from "node:child_process";
1
2
  import { Loader, Spacer, Text } from "@gsd/pi-tui";
2
3
 
3
4
  import type { InteractiveModeEvent, InteractiveModeStateHost } from "../interactive-mode-state.js";
4
5
  import { theme } from "../theme/theme.js";
5
6
  import { AssistantMessageComponent } from "../components/assistant-message.js";
6
7
  import { ToolExecutionComponent } from "../components/tool-execution.js";
8
+ import { ToolSummaryLine } from "../components/tool-summary-line.js";
9
+ import { shouldCollapse } from "../../../core/tool-priority.js";
7
10
  import { appKey } from "../components/keybinding-hints.js";
8
11
 
12
+ const GROUPABLE_COLLAPSED_TOOLS = new Set([
13
+ "read",
14
+ "find",
15
+ "ls",
16
+ "grep",
17
+ "lsp",
18
+
19
+ ]);
20
+
9
21
  export async function handleAgentEvent(host: InteractiveModeStateHost & {
10
- init: () => Promise<void>;
11
- getMarkdownThemeWithSettings: () => any;
12
- addMessageToChat: (message: any, options?: any) => void;
13
- formatWebSearchResult: (content: unknown) => string;
14
- getRegisteredToolDefinition: (toolName: string) => any;
15
- checkShutdownRequested: () => Promise<void>;
16
- rebuildChatFromMessages: () => void;
17
- flushCompactionQueue: (options?: { willRetry?: boolean }) => Promise<void>;
18
- showStatus: (message: string) => void;
19
- showError: (message: string) => void;
20
- updatePendingMessagesDisplay: () => void;
21
- updateTerminalTitle: () => void;
22
- updateEditorBorderColor: () => void;
23
- updateEditorExpandHint: () => void;
24
- getAgentPtyComponent: (sessionId: string) => any;
25
- ensureAgentPtyComponent: (sessionId: string, command?: string) => any;
26
- updateAgentPtyComponent: (sessionId: string, options?: { command?: string; screenText?: string; completed?: boolean; cancelled?: boolean; exitCode?: number }) => void;
27
- clearAgentPtyComponents: () => void;
28
- pendingMessagesContainer: { clear: () => void };
29
- startLoadingTips: () => void;
30
- stopLoadingTips: () => void;
22
+ init: () => Promise<void>;
23
+ getMarkdownThemeWithSettings: () => any;
24
+ addMessageToChat: (message: any, options?: any) => void;
25
+ formatWebSearchResult: (content: unknown) => string;
26
+ getRegisteredToolDefinition: (toolName: string) => any;
27
+ checkShutdownRequested: () => Promise<void>;
28
+ rebuildChatFromMessages: () => void;
29
+ flushCompactionQueue: (options?: { willRetry?: boolean }) => Promise<void>;
30
+ showStatus: (message: string) => void;
31
+ showError: (message: string) => void;
32
+ updatePendingMessagesDisplay: () => void;
33
+ updateTerminalTitle: () => void;
34
+ updateEditorBorderColor: () => void;
35
+ updateEditorExpandHint: () => void;
36
+ getAgentPtyComponent: (sessionId: string) => any;
37
+ ensureAgentPtyComponent: (sessionId: string, command?: string) => any;
38
+ updateAgentPtyComponent: (sessionId: string, options?: { command?: string; screenText?: string; completed?: boolean; cancelled?: boolean; exitCode?: number }) => void;
39
+ clearAgentPtyComponents: () => void;
40
+ pendingMessagesContainer: { clear: () => void };
41
+ startLoadingTips: () => void;
42
+ stopLoadingTips: () => void;
31
43
  }, event: InteractiveModeEvent): Promise<void> {
32
- if (!host.isInitialized) {
33
- await host.init();
34
- }
44
+ if (!host.isInitialized) {
45
+ await host.init();
46
+ }
47
+
48
+ host.footer.invalidate();
49
+
50
+ const resetCollapsedToolSummary = (): void => {
51
+ host.collapsedToolSummaryLine = undefined;
52
+ };
53
+
54
+ // Tools that always render as their own visible row, never folded into a summary line
55
+ const ALWAYS_DIRECT_TOOLS = new Set([
56
+ "bash", "bg_shell",
57
+ "web_search", "search-the-web", "google_search", "search_and_read",
58
+ "fetch_page", "resolve_library", "get_library_docs",
59
+ ]);
60
+
61
+ const shouldStartToolHidden = (toolName: string): boolean => {
62
+ const collapseToolCalls = host.settingsManager.getCollapseToolCalls?.() ?? false;
63
+ if (!collapseToolCalls || host.collapsedToolCallsExpanded) {
64
+ return false;
65
+ }
66
+ if (ALWAYS_DIRECT_TOOLS.has(toolName)) {
67
+ return false;
68
+ }
69
+ return shouldCollapse(toolName, false);
70
+ };
71
+
72
+ const hasVisibleRender = (child: { render?: (width: number) => string[] } | undefined): boolean => {
73
+ if (!child?.render) return true;
74
+ try {
75
+ return child.render(80).length > 0;
76
+ } catch {
77
+ return true;
78
+ }
79
+ };
80
+
81
+ const canGroupCollapsedTool = (toolName: string): boolean => GROUPABLE_COLLAPSED_TOOLS.has(toolName);
82
+
83
+ const findAdjacentCollapsedToolSummary = (
84
+ toolName: string,
85
+ anchor?: { render: (width: number) => string[] },
86
+ ): ToolSummaryLine | undefined => {
87
+ if (!canGroupCollapsedTool(toolName)) {
88
+ return undefined;
89
+ }
90
+ const anchorIndex = anchor ? host.chatContainer.children.indexOf(anchor) : host.chatContainer.children.length;
91
+ for (let i = anchorIndex - 1; i >= 0; i--) {
92
+ const child = host.chatContainer.children[i];
93
+ if (child instanceof ToolSummaryLine) {
94
+ return child.canGroupWith(toolName) ? child : undefined;
95
+ }
96
+ if (child instanceof ToolExecutionComponent && child.isHidden()) {
97
+ continue;
98
+ }
99
+ if (!hasVisibleRender(child)) {
100
+ continue;
101
+ }
102
+ break;
103
+ }
104
+ return undefined;
105
+ };
35
106
 
36
- host.footer.invalidate();
107
+ const appendCollapsedToolSummary = (
108
+ toolName: string,
109
+ elapsed: number,
110
+ anchor?: { render: (width: number) => string[] },
111
+ ): ToolSummaryLine => {
112
+ let summary = findAdjacentCollapsedToolSummary(toolName, anchor);
113
+ if (!summary) {
114
+ summary = new ToolSummaryLine();
115
+ summary.setHidden(host.collapsedToolCallsExpanded);
116
+ if (anchor) {
117
+ const anchorIndex = host.chatContainer.children.indexOf(anchor);
118
+ if (anchorIndex >= 0) {
119
+ host.chatContainer.children.splice(anchorIndex, 0, summary);
120
+ } else {
121
+ host.chatContainer.addChild(summary);
122
+ }
123
+ } else {
124
+ host.chatContainer.addChild(summary);
125
+ }
126
+ }
127
+ host.collapsedToolSummaryLine = summary;
128
+ summary.addTool(toolName, elapsed);
129
+ return summary;
130
+ };
37
131
 
38
- switch (event.type) {
39
- case "session_state_changed":
40
- switch (event.reason) {
41
- case "new_session":
42
- case "switch_session":
43
- case "fork":
44
- host.streamingComponent = undefined;
45
- host.streamingMessage = undefined;
46
- host.pendingTools.clear();
47
- host.clearAgentPtyComponents();
48
- host.pendingMessagesContainer.clear();
49
- host.compactionQueuedMessages = [];
50
- host.rebuildChatFromMessages();
51
- host.updatePendingMessagesDisplay();
52
- host.updateTerminalTitle();
53
- host.updateEditorBorderColor();
54
- host.ui.requestRender();
55
- return;
56
- case "set_session_name":
57
- host.updateTerminalTitle();
58
- host.ui.requestRender();
59
- return;
60
- case "set_model":
61
- case "set_thinking_level":
62
- host.updateEditorBorderColor();
63
- host.ui.requestRender();
64
- return;
65
- case "adaptive_classified": {
66
- const reasons = host.session?.lastAdaptiveDecision?.reasons ?? [];
67
- if (reasons.includes("adaptive_classifier_model_not_set")) {
68
- host.showError("adaptive classifier model not set");
69
- }
70
- host.updateEditorBorderColor();
71
- host.ui.requestRender();
72
- return;
73
- }
74
- default:
75
- host.ui.requestRender();
76
- return;
77
- }
78
- case "agent_start":
79
- if (host.retryEscapeHandler) {
80
- host.defaultEditor.onEscape = host.retryEscapeHandler;
81
- host.retryEscapeHandler = undefined;
82
- }
83
- if (host.retryLoader) {
84
- host.retryLoader.stop();
85
- host.retryLoader = undefined;
86
- }
87
- if (host.loadingAnimation) {
88
- host.loadingAnimation.stop();
89
- }
90
- host.statusContainer.clear();
91
- host.loadingAnimation = new Loader(
92
- host.ui,
93
- (spinner) => theme.fg("text", spinner),
94
- (text) => theme.fg("accent", text),
95
- host.defaultWorkingMessage,
96
- );
97
- host.loadingAnimation.setCycleMessages(host.workingMessages, 3000);
98
- host.statusContainer.addChild(host.loadingAnimation);
99
- host.startLoadingTips();
100
- // Show steer/queue + expand hint in editor bottom border while agent is running
101
- host.updateEditorExpandHint();
102
- if (host.pendingWorkingMessage !== undefined) {
103
- if (host.pendingWorkingMessage) {
104
- host.loadingAnimation.setMessage(host.pendingWorkingMessage);
105
- }
106
- host.pendingWorkingMessage = undefined;
107
- }
108
- host.ui.requestRender();
109
- break;
132
+ switch (event.type) {
133
+ case "session_state_changed":
134
+ switch (event.reason) {
135
+ case "new_session":
136
+ case "switch_session":
137
+ case "fork":
138
+ resetCollapsedToolSummary();
139
+ host.streamingComponent = undefined;
140
+ host.streamingMessage = undefined;
141
+ host.pendingTools.clear();
142
+ host.clearAgentPtyComponents();
143
+ host.pendingMessagesContainer.clear();
144
+ host.compactionQueuedMessages = [];
145
+ host.rebuildChatFromMessages();
146
+ host.updatePendingMessagesDisplay();
147
+ host.updateTerminalTitle();
148
+ host.updateEditorBorderColor();
149
+ host.ui.requestRender();
150
+ return;
151
+ case "set_session_name":
152
+ host.updateTerminalTitle();
153
+ host.ui.requestRender();
154
+ return;
155
+ case "set_model":
156
+ case "set_thinking_level":
157
+ host.updateEditorBorderColor();
158
+ host.ui.requestRender();
159
+ return;
160
+ case "adaptive_classified": {
161
+ const reasons = host.session?.lastAdaptiveDecision?.reasons ?? [];
162
+ if (reasons.includes("adaptive_classifier_model_not_set")) {
163
+ host.showError("adaptive classifier model not set");
164
+ }
165
+ host.updateEditorBorderColor();
166
+ host.ui.requestRender();
167
+ return;
168
+ }
169
+ default:
170
+ host.ui.requestRender();
171
+ return;
172
+ }
173
+ case "agent_start":
174
+ host.playNotificationSoundOnAgentEnd = false;
175
+ if (host.retryEscapeHandler) {
176
+ host.defaultEditor.onEscape = host.retryEscapeHandler;
177
+ host.retryEscapeHandler = undefined;
178
+ }
179
+ if (host.retryLoader) {
180
+ host.retryLoader.stop();
181
+ host.retryLoader = undefined;
182
+ }
183
+ if (host.loadingAnimation) {
184
+ host.loadingAnimation.stop();
185
+ }
186
+ host.statusContainer.clear();
187
+ host.loadingAnimation = new Loader(
188
+ host.ui,
189
+ (spinner) => theme.fg("text", spinner),
190
+ (text) => theme.fg("accent", text),
191
+ host.defaultWorkingMessage,
192
+ );
193
+ host.loadingAnimation.setCycleMessages(host.workingMessages, 3000);
194
+ host.statusContainer.addChild(host.loadingAnimation);
195
+ host.startLoadingTips();
196
+ // Show steer/queue + expand hint in editor bottom border while agent is running
197
+ host.updateEditorExpandHint();
198
+ if (host.pendingWorkingMessage !== undefined) {
199
+ if (host.pendingWorkingMessage) {
200
+ host.loadingAnimation.setMessage(host.pendingWorkingMessage);
201
+ }
202
+ host.pendingWorkingMessage = undefined;
203
+ }
204
+ host.ui.requestRender();
205
+ break;
110
206
 
111
- case "message_start":
112
- if (event.message.role === "custom") {
113
- host.addMessageToChat(event.message);
114
- host.ui.requestRender();
115
- } else if (event.message.role === "user") {
116
- host.addMessageToChat(event.message);
117
- host.updatePendingMessagesDisplay();
118
- host.ui.requestRender();
119
- } else if (event.message.role === "assistant") {
120
- host.streamingComponent = new AssistantMessageComponent(
121
- undefined,
122
- host.hideThinkingBlock,
123
- host.getMarkdownThemeWithSettings(),
124
- host.settingsManager.getTimestampFormat(),
125
- host.session?.thinkingLevel || "off",
126
- );
127
- host.streamingMessage = event.message;
128
- host.chatContainer.addChild(host.streamingComponent);
129
- host.streamingComponent.updateContent(host.streamingMessage);
130
- host.ui.requestRender();
131
- }
132
- break;
207
+ case "message_start":
208
+ if (event.message.role === "custom") {
209
+ host.addMessageToChat(event.message);
210
+ host.ui.requestRender();
211
+ } else if (event.message.role === "user") {
212
+ host.addMessageToChat(event.message);
213
+ host.updatePendingMessagesDisplay();
214
+ host.ui.requestRender();
215
+ } else if (event.message.role === "assistant") {
216
+ resetCollapsedToolSummary();
217
+ host.streamingComponent = new AssistantMessageComponent(
218
+ undefined,
219
+ host.hideThinkingBlock,
220
+ host.getMarkdownThemeWithSettings(),
221
+ host.settingsManager.getTimestampFormat(),
222
+ host.session?.thinkingLevel || "off",
223
+ );
224
+ host.streamingMessage = event.message;
225
+ host.chatContainer.addChild(host.streamingComponent);
226
+ host.streamingComponent.updateContent(host.streamingMessage);
227
+ host.ui.requestRender();
228
+ }
229
+ break;
133
230
 
134
- case "message_update":
135
- if (host.streamingComponent && event.message.role === "assistant") {
136
- host.streamingMessage = event.message;
137
- host.streamingComponent.updateContent(host.streamingMessage);
138
- for (const content of host.streamingMessage.content) {
139
- if (content.type === "toolCall") {
140
- if (content.name === "pty_start" || content.name === "pty_send" || content.name === "pty_read" || content.name === "pty_wait" || content.name === "pty_resize" || content.name === "pty_kill") {
141
- continue;
142
- }
143
- if (!host.pendingTools.has(content.id)) {
144
- const component = new ToolExecutionComponent(
145
- content.name,
146
- content.arguments,
147
- {
148
- showImages: host.settingsManager.getShowImages(),
149
- renderMode: host.settingsManager.getToolOutputMode(),
150
- editorScheme: host.settingsManager.getEditorScheme(),
151
- },
152
- host.getRegisteredToolDefinition(content.name),
153
- host.ui,
154
- );
155
- component.setExpanded(host.toolOutputExpanded);
156
- host.chatContainer.addChild(component);
157
- host.pendingTools.set(content.id, component);
158
- } else {
159
- host.pendingTools.get(content.id)?.updateArgs(content.arguments);
160
- }
161
- } else if (content.type === "serverToolUse") {
162
- if (!host.pendingTools.has(content.id)) {
163
- const component = new ToolExecutionComponent(
164
- content.name,
165
- content.input ?? {},
166
- {
167
- showImages: host.settingsManager.getShowImages(),
168
- renderMode: host.settingsManager.getToolOutputMode(),
169
- editorScheme: host.settingsManager.getEditorScheme(),
170
- },
171
- undefined,
172
- host.ui,
173
- );
174
- component.setExpanded(host.toolOutputExpanded);
175
- host.chatContainer.addChild(component);
176
- host.pendingTools.set(content.id, component);
177
- }
178
- } else if (content.type === "webSearchResult") {
179
- const component = host.pendingTools.get(content.toolUseId);
180
- if (component) {
181
- if (process.env.PI_OFFLINE === "1") {
182
- component.updateResult({
183
- content: [{ type: "text", text: "Web search disabled (offline mode)" }],
184
- isError: false,
185
- });
186
- } else {
187
- const searchContent = content.content;
188
- const isError = searchContent && typeof searchContent === "object" && "type" in (searchContent as any) && (searchContent as any).type === "web_search_tool_result_error";
189
- component.updateResult({
190
- content: [{ type: "text", text: host.formatWebSearchResult(searchContent) }],
191
- isError: !!isError,
192
- });
193
- }
194
- }
195
- }
196
- }
197
- host.ui.requestRender();
198
- }
199
- break;
231
+ case "message_update":
232
+ if (host.streamingComponent && event.message.role === "assistant") {
233
+ host.streamingMessage = event.message;
234
+ host.streamingComponent.updateContent(host.streamingMessage);
235
+ for (const content of host.streamingMessage.content) {
236
+ // Keep collapsed summary active across assistant text updates.
237
+ // Streamed message updates include all prior text blocks, so resetting
238
+ // here fragments one contiguous collapsed-tool group into many lines.
239
+ if (content.type === "toolCall") {
240
+ if (content.name === "pty_start" || content.name === "pty_send" || content.name === "pty_read" || content.name === "pty_wait" || content.name === "pty_resize" || content.name === "pty_kill") {
241
+ continue;
242
+ }
243
+ if (!host.pendingTools.has(content.id)) {
244
+ const component = new ToolExecutionComponent(
245
+ content.name,
246
+ content.arguments,
247
+ {
248
+ showImages: host.settingsManager.getShowImages(),
249
+ renderMode: host.settingsManager.getToolOutputMode(),
250
+ editorScheme: host.settingsManager.getEditorScheme(),
251
+ },
252
+ host.getRegisteredToolDefinition(content.name),
253
+ host.ui,
254
+ );
255
+ component.setExpanded(host.toolOutputExpanded);
256
+ component.setHidden(shouldStartToolHidden(content.name));
257
+ host.chatContainer.addChild(component);
258
+ host.pendingTools.set(content.id, component);
259
+ } else {
260
+ host.pendingTools.get(content.id)?.updateArgs(content.arguments);
261
+ }
262
+ } else if (content.type === "serverToolUse") {
263
+ if (!host.pendingTools.has(content.id)) {
264
+ const component = new ToolExecutionComponent(
265
+ content.name,
266
+ content.input ?? {},
267
+ {
268
+ showImages: host.settingsManager.getShowImages(),
269
+ renderMode: host.settingsManager.getToolOutputMode(),
270
+ editorScheme: host.settingsManager.getEditorScheme(),
271
+ },
272
+ undefined,
273
+ host.ui,
274
+ );
275
+ component.setExpanded(host.toolOutputExpanded);
276
+ component.setHidden(shouldStartToolHidden(content.name));
277
+ host.chatContainer.addChild(component);
278
+ host.pendingTools.set(content.id, component);
279
+ }
280
+ } else if (content.type === "webSearchResult") {
281
+ const component = host.pendingTools.get(content.toolUseId);
282
+ if (component) {
283
+ if (process.env.PI_OFFLINE === "1") {
284
+ component.updateResult({
285
+ content: [{ type: "text", text: "Web search disabled (offline mode)" }],
286
+ isError: false,
287
+ });
288
+ } else {
289
+ const searchContent = content.content;
290
+ const isError = searchContent && typeof searchContent === "object" && "type" in (searchContent as any) && (searchContent as any).type === "web_search_tool_result_error";
291
+ component.updateResult({
292
+ content: [{ type: "text", text: host.formatWebSearchResult(searchContent) }],
293
+ isError: !!isError,
294
+ });
295
+ }
296
+ }
297
+ }
298
+ }
299
+ host.ui.requestRender();
300
+ }
301
+ break;
200
302
 
201
- case "message_end":
202
- if (event.message.role === "user") break;
203
- if (host.streamingComponent && event.message.role === "assistant") {
204
- host.streamingMessage = event.message;
205
- let errorMessage: string | undefined;
206
- if (host.streamingMessage.stopReason === "aborted") {
207
- const retryAttempt = host.session.retryAttempt;
208
- errorMessage = retryAttempt > 0
209
- ? `Aborted after ${retryAttempt} retry attempt${retryAttempt > 1 ? "s" : ""}`
210
- : "Operation aborted";
211
- host.streamingMessage.errorMessage = errorMessage;
212
- }
213
- host.streamingComponent.updateContent(host.streamingMessage);
214
- if (host.streamingMessage.stopReason === "aborted" || host.streamingMessage.stopReason === "error") {
215
- if (!errorMessage) {
216
- errorMessage = host.streamingMessage.errorMessage || "Error";
217
- }
218
- for (const [, component] of host.pendingTools.entries()) {
219
- component.updateResult({ content: [{ type: "text", text: errorMessage }], isError: true });
220
- }
221
- host.pendingTools.clear();
222
- } else {
223
- for (const [, component] of host.pendingTools.entries()) {
224
- component.setArgsComplete();
225
- }
226
- }
227
- host.streamingComponent = undefined;
228
- host.streamingMessage = undefined;
229
- host.footer.invalidate();
230
- }
231
- host.ui.requestRender();
232
- break;
303
+ case "message_end":
304
+ if (event.message.role === "user") break;
305
+ if (host.streamingComponent && event.message.role === "assistant") {
306
+ host.streamingMessage = event.message;
307
+ let errorMessage: string | undefined;
308
+ if (host.streamingMessage.stopReason === "aborted") {
309
+ const retryAttempt = host.session.retryAttempt;
310
+ errorMessage = retryAttempt > 0
311
+ ? `Aborted after ${retryAttempt} retry attempt${retryAttempt > 1 ? "s" : ""}`
312
+ : "Operation aborted";
313
+ host.streamingMessage.errorMessage = errorMessage;
314
+ }
315
+ host.streamingComponent.updateContent(host.streamingMessage);
316
+ if (host.streamingMessage.stopReason === "aborted" || host.streamingMessage.stopReason === "error") {
317
+ host.playNotificationSoundOnAgentEnd = false;
318
+ if (!errorMessage) {
319
+ errorMessage = host.streamingMessage.errorMessage || "Error";
320
+ }
321
+ for (const [, component] of host.pendingTools.entries()) {
322
+ component.updateResult({ content: [{ type: "text", text: errorMessage }], isError: true });
323
+ }
324
+ host.pendingTools.clear();
325
+ } else {
326
+ host.playNotificationSoundOnAgentEnd = true;
327
+ for (const [, component] of host.pendingTools.entries()) {
328
+ component.setArgsComplete();
329
+ }
330
+ }
331
+ host.streamingComponent = undefined;
332
+ host.streamingMessage = undefined;
333
+ resetCollapsedToolSummary();
334
+ host.footer.invalidate();
335
+ }
336
+ host.ui.requestRender();
337
+ break;
233
338
 
234
- case "tool_execution_start":
235
- if (event.toolName === "pty_start") {
236
- return;
237
- }
238
- if (event.toolName === "pty_send" || event.toolName === "pty_read" || event.toolName === "pty_wait" || event.toolName === "pty_resize" || event.toolName === "pty_kill") {
239
- return;
240
- }
241
- if (!host.pendingTools.has(event.toolCallId)) {
242
- const component = new ToolExecutionComponent(
243
- event.toolName,
244
- event.args,
245
- {
246
- showImages: host.settingsManager.getShowImages(),
247
- renderMode: host.settingsManager.getToolOutputMode(),
248
- editorScheme: host.settingsManager.getEditorScheme(),
249
- },
250
- host.getRegisteredToolDefinition(event.toolName),
251
- host.ui,
252
- );
253
- component.setExpanded(host.toolOutputExpanded);
254
- host.chatContainer.addChild(component);
255
- host.pendingTools.set(event.toolCallId, component);
256
- host.ui.requestRender();
257
- }
258
- break;
339
+ case "tool_execution_start":
340
+ if (event.toolName === "pty_start") {
341
+ return;
342
+ }
343
+ if (event.toolName === "pty_send" || event.toolName === "pty_read" || event.toolName === "pty_wait" || event.toolName === "pty_resize" || event.toolName === "pty_kill") {
344
+ return;
345
+ }
346
+ if (!host.pendingTools.has(event.toolCallId)) {
347
+ const component = new ToolExecutionComponent(
348
+ event.toolName,
349
+ event.args,
350
+ {
351
+ showImages: host.settingsManager.getShowImages(),
352
+ renderMode: host.settingsManager.getToolOutputMode(),
353
+ editorScheme: host.settingsManager.getEditorScheme(),
354
+ },
355
+ host.getRegisteredToolDefinition(event.toolName),
356
+ host.ui,
357
+ );
358
+ component.setExpanded(host.toolOutputExpanded);
359
+ component.setHidden(shouldStartToolHidden(event.toolName));
360
+ host.chatContainer.addChild(component);
361
+ host.pendingTools.set(event.toolCallId, component);
362
+ host.ui.requestRender();
363
+ }
364
+ break;
259
365
 
260
- case "tool_execution_update": {
261
- if (event.toolName === "pty_start" || event.toolName === "pty_send" || event.toolName === "pty_read" || event.toolName === "pty_wait" || event.toolName === "pty_resize" || event.toolName === "pty_kill") {
262
- const details = event.partialResult?.details as { sessionId?: string; screenText?: string; exitCode?: number; cancelled?: boolean; completed?: boolean } | undefined;
263
- const sessionId = details?.sessionId ?? (event.args as { sessionId?: string } | undefined)?.sessionId;
264
- if (sessionId) {
265
- host.updateAgentPtyComponent(sessionId, {
266
- command: event.toolName === "pty_start" ? (event.args as { command?: string } | undefined)?.command : undefined,
267
- screenText: details?.screenText,
268
- completed: details?.completed,
269
- cancelled: details?.cancelled,
270
- exitCode: details?.exitCode,
271
- });
272
- host.ui.requestRender();
273
- }
274
- break;
275
- }
276
- const component = host.pendingTools.get(event.toolCallId);
277
- if (component) {
278
- component.updateResult({ ...event.partialResult, isError: false }, true);
279
- host.ui.requestRender();
280
- }
281
- break;
282
- }
366
+ case "tool_execution_update": {
367
+ if (event.toolName === "pty_start" || event.toolName === "pty_send" || event.toolName === "pty_read" || event.toolName === "pty_wait" || event.toolName === "pty_resize" || event.toolName === "pty_kill") {
368
+ const details = event.partialResult?.details as { sessionId?: string; screenText?: string; exitCode?: number; cancelled?: boolean; completed?: boolean } | undefined;
369
+ const sessionId = details?.sessionId ?? (event.args as { sessionId?: string } | undefined)?.sessionId;
370
+ if (sessionId) {
371
+ host.updateAgentPtyComponent(sessionId, {
372
+ command: event.toolName === "pty_start" ? (event.args as { command?: string } | undefined)?.command : undefined,
373
+ screenText: details?.screenText,
374
+ completed: details?.completed,
375
+ cancelled: details?.cancelled,
376
+ exitCode: details?.exitCode,
377
+ });
378
+ host.ui.requestRender();
379
+ }
380
+ break;
381
+ }
382
+ const component = host.pendingTools.get(event.toolCallId);
383
+ if (component) {
384
+ component.updateResult({ ...event.partialResult, isError: false }, true);
385
+ host.ui.requestRender();
386
+ }
387
+ break;
388
+ }
283
389
 
284
- case "tool_execution_end": {
285
- if (event.toolName === "pty_start" || event.toolName === "pty_send" || event.toolName === "pty_read" || event.toolName === "pty_wait" || event.toolName === "pty_resize" || event.toolName === "pty_kill") {
286
- const details = event.result?.details as { sessionId?: string; screenText?: string; exitCode?: number; cancelled?: boolean; completed?: boolean } | undefined;
287
- const sessionId = details?.sessionId;
288
- if (sessionId) {
289
- host.updateAgentPtyComponent(sessionId, {
290
- command: undefined,
291
- screenText: details?.screenText,
292
- completed: event.toolName === "pty_kill" || !!details?.completed,
293
- cancelled: details?.cancelled ?? event.toolName === "pty_kill",
294
- exitCode: details?.exitCode,
295
- });
296
- host.ui.requestRender();
297
- }
298
- break;
299
- }
300
- const component = host.pendingTools.get(event.toolCallId);
301
- if (component) {
302
- component.updateResult({ ...event.result, isError: event.isError });
303
- host.pendingTools.delete(event.toolCallId);
304
- host.ui.requestRender();
305
- }
306
- break;
307
- }
390
+ case "tool_execution_end": {
391
+ if (event.toolName === "pty_start" || event.toolName === "pty_send" || event.toolName === "pty_read" || event.toolName === "pty_wait" || event.toolName === "pty_resize" || event.toolName === "pty_kill") {
392
+ const details = event.result?.details as { sessionId?: string; screenText?: string; exitCode?: number; cancelled?: boolean; completed?: boolean } | undefined;
393
+ const sessionId = details?.sessionId;
394
+ if (sessionId) {
395
+ host.updateAgentPtyComponent(sessionId, {
396
+ command: undefined,
397
+ screenText: details?.screenText,
398
+ completed: event.toolName === "pty_kill" || !!details?.completed,
399
+ cancelled: details?.cancelled ?? event.toolName === "pty_kill",
400
+ exitCode: details?.exitCode,
401
+ });
402
+ host.ui.requestRender();
403
+ }
404
+ break;
405
+ }
406
+ const component = host.pendingTools.get(event.toolCallId);
407
+ if (component) {
408
+ component.updateResult({ ...event.result, isError: event.isError });
409
+ const collapseToolCalls = host.settingsManager.getCollapseToolCalls?.() ?? false;
410
+ if (collapseToolCalls && shouldCollapse(event.toolName, event.isError) && !ALWAYS_DIRECT_TOOLS.has(event.toolName)) {
411
+ appendCollapsedToolSummary(event.toolName, component.getElapsed(), component);
412
+ component.setHidden(!host.collapsedToolCallsExpanded);
413
+ } else {
414
+ component.setHidden(false);
415
+ resetCollapsedToolSummary();
416
+ }
417
+ host.pendingTools.delete(event.toolCallId);
418
+ host.ui.requestRender();
419
+ }
420
+ break;
421
+ }
308
422
 
309
- case "agent_end":
310
- if (host.loadingAnimation) {
311
- host.loadingAnimation.stop();
312
- host.loadingAnimation = undefined;
313
- host.statusContainer.clear();
314
- }
315
- if (host.streamingComponent) {
316
- host.chatContainer.removeChild(host.streamingComponent);
317
- host.streamingComponent = undefined;
318
- host.streamingMessage = undefined;
319
- }
320
- host.pendingTools.clear();
321
- // Update hint: show expand/collapse if tool outputs exist, else clear
322
- host.defaultEditor.bottomHint = "";
323
- host.updateEditorExpandHint();
324
- await host.checkShutdownRequested();
325
- host.ui.requestRender();
326
- break;
423
+ case "agent_end":
424
+ if (host.loadingAnimation) {
425
+ host.loadingAnimation.stop();
426
+ host.loadingAnimation = undefined;
427
+ host.statusContainer.clear();
428
+ }
429
+ if (host.streamingComponent) {
430
+ host.chatContainer.removeChild(host.streamingComponent);
431
+ host.streamingComponent = undefined;
432
+ host.streamingMessage = undefined;
433
+ }
434
+ host.pendingTools.clear();
435
+ resetCollapsedToolSummary();
436
+ // Update hint: show expand/collapse if tool outputs exist, else clear
437
+ host.defaultEditor.bottomHint = "";
438
+ host.updateEditorExpandHint();
439
+ // Play notification sound only for completed (non-aborted) assistant responses
440
+ if (host.notificationSoundEnabled && host.playNotificationSoundOnAgentEnd) {
441
+ if (process.platform === "darwin") {
442
+ try {
443
+ const child = spawn("afplay", ["/System/Library/Sounds/Glass.aiff"], {
444
+ stdio: "ignore",
445
+ detached: true,
446
+ });
447
+ child.unref();
448
+ } catch {
449
+ host.ui.terminal.write("\x07");
450
+ }
451
+ } else {
452
+ host.ui.terminal.write("\x07");
453
+ }
454
+ }
455
+ host.playNotificationSoundOnAgentEnd = false;
456
+ await host.checkShutdownRequested();
457
+ host.ui.requestRender();
458
+ break;
327
459
 
328
- case "auto_compaction_start":
329
- host.autoCompactionEscapeHandler = host.defaultEditor.onEscape;
330
- host.defaultEditor.onEscape = () => host.session.abortCompaction();
331
- host.statusContainer.clear();
332
- host.autoCompactionLoader = new Loader(
333
- host.ui,
334
- (spinner) => theme.fg("text", spinner),
335
- (text) => theme.fg("muted", text),
336
- `${event.reason === "overflow" ? "Context overflow detected, " : ""}Auto-compacting... (${appKey(host.keybindings, "interrupt")} to cancel)`,
337
- );
338
- host.statusContainer.addChild(host.autoCompactionLoader);
339
- host.ui.requestRender();
340
- break;
460
+ case "auto_compaction_start":
461
+ host.autoCompactionEscapeHandler = host.defaultEditor.onEscape;
462
+ host.defaultEditor.onEscape = () => host.session.abortCompaction();
463
+ host.statusContainer.clear();
464
+ host.autoCompactionLoader = new Loader(
465
+ host.ui,
466
+ (spinner) => theme.fg("text", spinner),
467
+ (text) => theme.fg("muted", text),
468
+ `${event.reason === "overflow" ? "Context overflow detected, " : ""}Auto-compacting... (${appKey(host.keybindings, "interrupt")} to cancel)`,
469
+ );
470
+ host.statusContainer.addChild(host.autoCompactionLoader);
471
+ host.ui.requestRender();
472
+ break;
341
473
 
342
- case "auto_compaction_end":
343
- if (host.autoCompactionEscapeHandler) {
344
- host.defaultEditor.onEscape = host.autoCompactionEscapeHandler;
345
- host.autoCompactionEscapeHandler = undefined;
346
- }
347
- if (host.autoCompactionLoader) {
348
- host.autoCompactionLoader.stop();
349
- host.autoCompactionLoader = undefined;
350
- host.statusContainer.clear();
351
- }
352
- if (event.aborted) {
353
- host.showStatus("Auto-compaction cancelled");
354
- } else if (event.result) {
355
- host.chatContainer.clear();
356
- host.rebuildChatFromMessages();
357
- host.addMessageToChat({
358
- role: "compactionSummary",
359
- tokensBefore: event.result.tokensBefore,
360
- summary: event.result.summary,
361
- timestamp: Date.now(),
362
- });
363
- host.footer.invalidate();
364
- } else if (event.errorMessage) {
365
- host.chatContainer.addChild(new Spacer(1));
366
- host.chatContainer.addChild(new Text(theme.fg("error", event.errorMessage), 1, 0));
367
- }
368
- void host.flushCompactionQueue({ willRetry: event.willRetry });
369
- host.ui.requestRender();
370
- break;
474
+ case "auto_compaction_end":
475
+ if (host.autoCompactionEscapeHandler) {
476
+ host.defaultEditor.onEscape = host.autoCompactionEscapeHandler;
477
+ host.autoCompactionEscapeHandler = undefined;
478
+ }
479
+ if (host.autoCompactionLoader) {
480
+ host.autoCompactionLoader.stop();
481
+ host.autoCompactionLoader = undefined;
482
+ host.statusContainer.clear();
483
+ }
484
+ if (event.aborted) {
485
+ host.showStatus("Auto-compaction cancelled");
486
+ } else if (event.result) {
487
+ host.chatContainer.clear();
488
+ host.rebuildChatFromMessages();
489
+ host.addMessageToChat({
490
+ role: "compactionSummary",
491
+ tokensBefore: event.result.tokensBefore,
492
+ summary: event.result.summary,
493
+ timestamp: Date.now(),
494
+ });
495
+ host.footer.invalidate();
496
+ } else if (event.errorMessage) {
497
+ host.chatContainer.addChild(new Spacer(1));
498
+ host.chatContainer.addChild(new Text(theme.fg("error", event.errorMessage), 1, 0));
499
+ }
500
+ void host.flushCompactionQueue({ willRetry: event.willRetry });
501
+ host.ui.requestRender();
502
+ break;
371
503
 
372
- case "auto_retry_start":
373
- host.chatContainer.clear();
374
- host.rebuildChatFromMessages();
375
- host.retryEscapeHandler = host.defaultEditor.onEscape;
376
- host.defaultEditor.onEscape = () => host.session.abortRetry();
377
- host.statusContainer.clear();
378
- host.retryLoader = new Loader(
379
- host.ui,
380
- (spinner) => theme.fg("text", spinner),
381
- (text) => theme.fg("muted", text),
382
- `Retrying (${event.attempt}/${event.maxAttempts}) in ${Math.round(event.delayMs / 1000)}s... (${appKey(host.keybindings, "interrupt")} to cancel)`,
383
- );
384
- host.statusContainer.addChild(host.retryLoader);
385
- host.ui.requestRender();
386
- break;
504
+ case "auto_retry_start":
505
+ host.chatContainer.clear();
506
+ host.rebuildChatFromMessages();
507
+ host.retryEscapeHandler = host.defaultEditor.onEscape;
508
+ host.defaultEditor.onEscape = () => host.session.abortRetry();
509
+ host.statusContainer.clear();
510
+ host.retryLoader = new Loader(
511
+ host.ui,
512
+ (spinner) => theme.fg("text", spinner),
513
+ (text) => theme.fg("muted", text),
514
+ `Retrying (${event.attempt}/${event.maxAttempts}) in ${Math.round(event.delayMs / 1000)}s... (${appKey(host.keybindings, "interrupt")} to cancel)`,
515
+ );
516
+ host.statusContainer.addChild(host.retryLoader);
517
+ host.ui.requestRender();
518
+ break;
387
519
 
388
- case "auto_retry_end":
389
- if (host.retryEscapeHandler) {
390
- host.defaultEditor.onEscape = host.retryEscapeHandler;
391
- host.retryEscapeHandler = undefined;
392
- }
393
- if (host.retryLoader) {
394
- host.retryLoader.stop();
395
- host.retryLoader = undefined;
396
- host.statusContainer.clear();
397
- }
398
- if (!event.success) {
399
- host.showError(`Retry failed after ${event.attempt} attempts: ${event.finalError || "Unknown error"}`);
400
- }
401
- host.ui.requestRender();
402
- break;
520
+ case "auto_retry_end":
521
+ if (host.retryEscapeHandler) {
522
+ host.defaultEditor.onEscape = host.retryEscapeHandler;
523
+ host.retryEscapeHandler = undefined;
524
+ }
525
+ if (host.retryLoader) {
526
+ host.retryLoader.stop();
527
+ host.retryLoader = undefined;
528
+ host.statusContainer.clear();
529
+ }
530
+ if (!event.success) {
531
+ host.showError(`Retry failed after ${event.attempt} attempts: ${event.finalError || "Unknown error"}`);
532
+ }
533
+ host.ui.requestRender();
534
+ break;
403
535
 
404
- case "fallback_provider_switch":
405
- host.showStatus(`Switched from ${event.from} → ${event.to} (${event.reason})`);
406
- host.ui.requestRender();
407
- break;
536
+ case "fallback_provider_switch":
537
+ host.showStatus(`Switched from ${event.from} → ${event.to} (${event.reason})`);
538
+ host.ui.requestRender();
539
+ break;
408
540
 
409
- case "fallback_provider_restored":
410
- host.showStatus(`Restored to ${event.provider}`);
411
- host.ui.requestRender();
412
- break;
541
+ case "fallback_provider_restored":
542
+ host.showStatus(`Restored to ${event.provider}`);
543
+ host.ui.requestRender();
544
+ break;
413
545
 
414
- case "fallback_chain_exhausted":
415
- host.showError(event.reason);
416
- host.ui.requestRender();
417
- break;
546
+ case "fallback_chain_exhausted":
547
+ host.showError(event.reason);
548
+ host.ui.requestRender();
549
+ break;
418
550
 
419
- case "image_overflow_recovery":
420
- host.showStatus(
421
- `Removed ${event.strippedCount} older image(s) to comply with API limits. Retrying...`,
422
- );
423
- host.ui.requestRender();
424
- break;
425
- }
551
+ case "image_overflow_recovery":
552
+ host.showStatus(
553
+ `Removed ${event.strippedCount} older image(s) to comply with API limits. Retrying...`,
554
+ );
555
+ host.ui.requestRender();
556
+ break;
557
+ }
426
558
  }
427
559