lsd-pi 1.3.2 → 1.3.6

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