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
@@ -0,0 +1,120 @@
1
+ import { Container, Text } from "@gsd/pi-tui";
2
+
3
+ import { theme } from "../theme/theme.js";
4
+
5
+ interface CollapsedTool {
6
+ name: string;
7
+ elapsed: number;
8
+ }
9
+
10
+ // Tools that can be mixed together in one summary line
11
+ const MIXED_GROUPABLE_TOOLS = new Set([
12
+ "read", "find", "ls", "grep", "lsp",
13
+ ]);
14
+
15
+ type SummaryDescriptor = {
16
+ action: string;
17
+ singular: string;
18
+ plural: string;
19
+ };
20
+
21
+ const TOOL_SUMMARY_DESCRIPTORS: Record<string, SummaryDescriptor> = {
22
+ read: { action: "reading", singular: "file", plural: "files" },
23
+ write: { action: "editing", singular: "file", plural: "files" },
24
+ edit: { action: "editing", singular: "file", plural: "files" },
25
+ grep: { action: "searching for", singular: "pattern", plural: "patterns" },
26
+ find: { action: "finding", singular: "path", plural: "paths" },
27
+ ls: { action: "listing", singular: "directory", plural: "directories" },
28
+ lsp: { action: "looking up", singular: "symbol", plural: "symbols" },
29
+ bash: { action: "running", singular: "command", plural: "commands" },
30
+ bg_shell: { action: "running", singular: "background command", plural: "background commands" },
31
+ fetch_page: { action: "reading", singular: "page", plural: "pages" },
32
+ resolve_library: { action: "searching for", singular: "library", plural: "libraries" },
33
+ get_library_docs: { action: "reading", singular: "doc", plural: "docs" },
34
+ web_search: { action: "searching web for", singular: "query", plural: "queries" },
35
+ "search-the-web": { action: "searching web for", singular: "query", plural: "queries" },
36
+ search_and_read: { action: "researching", singular: "topic", plural: "topics" },
37
+ google_search: { action: "searching web for", singular: "query", plural: "queries" },
38
+ };
39
+
40
+ function formatCount(count: number, singular: string, plural: string): string {
41
+ return `${count} ${count === 1 ? singular : plural}`;
42
+ }
43
+
44
+ function summarizeToolGroup(name: string, count: number): string {
45
+ if (name.startsWith("browser_")) {
46
+ return `using browser for ${formatCount(count, "step", "steps")}`;
47
+ }
48
+
49
+ const descriptor = TOOL_SUMMARY_DESCRIPTORS[name];
50
+ if (!descriptor) {
51
+ return count > 1 ? `${name} ×${count}` : name;
52
+ }
53
+
54
+ return `${descriptor.action} ${formatCount(count, descriptor.singular, descriptor.plural)}`;
55
+ }
56
+
57
+ export class ToolSummaryLine extends Container {
58
+ private tools: CollapsedTool[] = [];
59
+ private hidden = false;
60
+ private contentText: Text;
61
+
62
+ canGroupWith(toolName: string): boolean {
63
+ if (this.tools.length === 0) return true;
64
+ // Mixed-groupable tools can share a summary line regardless of order
65
+ if (MIXED_GROUPABLE_TOOLS.has(toolName) && this.tools.every((t) => MIXED_GROUPABLE_TOOLS.has(t.name))) {
66
+ return true;
67
+ }
68
+ // Otherwise only same-tool grouping
69
+ return this.tools.every((tool) => tool.name === toolName);
70
+ }
71
+
72
+ constructor() {
73
+ super();
74
+ this.contentText = new Text("", 1, 0);
75
+ this.addChild(this.contentText);
76
+ }
77
+
78
+ addTool(name: string, elapsed: number): void {
79
+ this.tools.push({ name, elapsed });
80
+ this.updateDisplay();
81
+ }
82
+
83
+ setHidden(hidden: boolean): void {
84
+ this.hidden = hidden;
85
+ }
86
+
87
+ override invalidate(): void {
88
+ super.invalidate();
89
+ this.updateDisplay();
90
+ }
91
+
92
+ override render(width: number): string[] {
93
+ if (this.hidden || this.tools.length === 0) {
94
+ return [];
95
+ }
96
+ return super.render(width);
97
+ }
98
+
99
+ private updateDisplay(): void {
100
+ if (this.tools.length === 0) {
101
+ this.contentText.setText("");
102
+ return;
103
+ }
104
+
105
+ const counts = new Map<string, number>();
106
+ let totalElapsed = 0;
107
+ for (const tool of this.tools) {
108
+ counts.set(tool.name, (counts.get(tool.name) ?? 0) + 1);
109
+ totalElapsed += tool.elapsed;
110
+ }
111
+
112
+ const groupedTools = [...counts.entries()]
113
+ .map(([name, count]) => summarizeToolGroup(name, count))
114
+ .join(" · ");
115
+ const elapsed = (totalElapsed / 1000).toFixed(1);
116
+ const indicator = theme.fg("success", "●");
117
+ const details = theme.fg("muted", `${groupedTools} · ${elapsed}s`);
118
+ this.contentText.setText(`${indicator} ${details}`);
119
+ }
120
+ }
@@ -0,0 +1,396 @@
1
+ import { describe, it } from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import stripAnsi from "strip-ansi";
4
+
5
+ import { handleAgentEvent } from "../chat-controller.js";
6
+ import { ToolSummaryLine } from "../../components/tool-summary-line.js";
7
+ import { getMarkdownTheme, initTheme } from "../../theme/theme.js";
8
+
9
+ initTheme("dark");
10
+
11
+ function assistantMessage(content: any[] = []): any {
12
+ return {
13
+ role: "assistant",
14
+ content,
15
+ timestamp: Date.now(),
16
+ };
17
+ }
18
+
19
+ function toolStartEvent(toolCallId: string, toolName: string, args: Record<string, unknown> = {}): any {
20
+ return {
21
+ type: "tool_execution_start",
22
+ toolCallId,
23
+ toolName,
24
+ args,
25
+ };
26
+ }
27
+
28
+ function toolEndEvent(toolCallId: string, toolName: string): any {
29
+ return {
30
+ type: "tool_execution_end",
31
+ toolCallId,
32
+ toolName,
33
+ isError: false,
34
+ result: {
35
+ content: [],
36
+ details: {},
37
+ },
38
+ };
39
+ }
40
+
41
+ function createChatContainer() {
42
+ return {
43
+ children: [] as any[],
44
+ addChild(child: any) {
45
+ this.children.push(child);
46
+ },
47
+ removeChild(child: any) {
48
+ this.children = this.children.filter((c) => c !== child);
49
+ },
50
+ clear() {
51
+ this.children = [];
52
+ },
53
+ };
54
+ }
55
+
56
+ function createHost(options: { collapseToolCalls?: boolean; collapsedToolCallsExpanded?: boolean; toolOutputExpanded?: boolean } = {}): any {
57
+ const chatContainer = createChatContainer();
58
+ return {
59
+ isInitialized: true,
60
+ init: async () => { },
61
+ ui: {
62
+ requestRender: () => { },
63
+ terminal: { write: () => { } },
64
+ },
65
+ footer: { invalidate: () => { } },
66
+ statusContainer: { clear: () => { }, addChild: () => { } },
67
+ chatContainer,
68
+ settingsManager: {
69
+ getTimestampFormat: () => "date-time-iso",
70
+ getShowImages: () => true,
71
+ getToolOutputMode: () => "normal",
72
+ getEditorScheme: () => "auto",
73
+ getCollapseToolCalls: () => options.collapseToolCalls ?? true,
74
+ },
75
+ pendingTools: new Map(),
76
+ collapsedToolSummaryLine: undefined,
77
+ collapsedToolCallsExpanded: options.collapsedToolCallsExpanded ?? options.toolOutputExpanded ?? false,
78
+ toolOutputExpanded: options.toolOutputExpanded ?? false,
79
+ hideThinkingBlock: false,
80
+ notificationSoundEnabled: false,
81
+ defaultEditor: { onEscape: undefined, bottomHint: "" },
82
+ session: {
83
+ thinkingLevel: "off",
84
+ retryAttempt: 0,
85
+ abortCompaction: () => { },
86
+ abortRetry: () => { },
87
+ },
88
+ keybindings: {},
89
+ pendingMessagesContainer: { clear: () => { } },
90
+ compactionQueuedMessages: [],
91
+ defaultWorkingMessage: "",
92
+ workingMessages: [],
93
+ startLoadingTips: () => { },
94
+ stopLoadingTips: () => { },
95
+ getMarkdownThemeWithSettings: () => getMarkdownTheme(),
96
+ addMessageToChat: () => { },
97
+ formatWebSearchResult: () => "",
98
+ getRegisteredToolDefinition: () => undefined,
99
+ checkShutdownRequested: async () => { },
100
+ rebuildChatFromMessages: () => { },
101
+ flushCompactionQueue: async () => { },
102
+ showStatus: () => { },
103
+ showError: () => { },
104
+ updatePendingMessagesDisplay: () => { },
105
+ updateTerminalTitle: () => { },
106
+ updateEditorBorderColor: () => { },
107
+ updateEditorExpandHint: () => { },
108
+ getAgentPtyComponent: () => undefined,
109
+ ensureAgentPtyComponent: () => undefined,
110
+ updateAgentPtyComponent: () => { },
111
+ clearAgentPtyComponents: () => { },
112
+ };
113
+ }
114
+
115
+ function addPendingTool(host: any, toolCallId: string, elapsed: number): { hidden?: boolean; component: any } {
116
+ const state: { hidden?: boolean; component: any } = { hidden: false, component: undefined };
117
+ const component = {
118
+ render: () => (state.hidden ? [] : ["tool"]),
119
+ updateResult: () => { },
120
+ setHidden: (hidden: boolean) => {
121
+ state.hidden = hidden;
122
+ },
123
+ isHidden: () => !!state.hidden,
124
+ setArgsComplete: () => { },
125
+ getElapsed: () => elapsed,
126
+ };
127
+ state.component = component;
128
+ host.chatContainer.addChild(component);
129
+ host.pendingTools.set(toolCallId, component);
130
+ return state;
131
+ }
132
+
133
+ function summaryLines(host: any): ToolSummaryLine[] {
134
+ return host.chatContainer.children.filter((child: any) => child instanceof ToolSummaryLine);
135
+ }
136
+
137
+ describe("chat-controller collapsed tool summary lifecycle", () => {
138
+ it("hides collapsible tool calls immediately to avoid blink before grouping", async () => {
139
+ const host = createHost();
140
+
141
+ await handleAgentEvent(host, {
142
+ type: "message_start",
143
+ message: assistantMessage(),
144
+ });
145
+
146
+ await handleAgentEvent(host, toolStartEvent("tool-1", "read", { path: "README.md" }));
147
+ const pending = host.pendingTools.get("tool-1");
148
+ assert.ok(pending);
149
+ assert.equal(pending.isHidden(), true);
150
+
151
+ await handleAgentEvent(host, toolEndEvent("tool-1", "read"));
152
+ assert.equal(summaryLines(host).length, 1);
153
+ });
154
+
155
+ it("hides streamed tool-call blocks immediately to avoid blink before grouping", async () => {
156
+ const host = createHost();
157
+
158
+ await handleAgentEvent(host, {
159
+ type: "message_start",
160
+ message: assistantMessage(),
161
+ });
162
+
163
+ await handleAgentEvent(host, {
164
+ type: "message_update",
165
+ message: assistantMessage([
166
+ { type: "toolCall", id: "tool-1", name: "read", arguments: { path: "README.md" } },
167
+ ]),
168
+ } as any);
169
+
170
+ const pending = host.pendingTools.get("tool-1");
171
+ assert.ok(pending);
172
+ assert.equal(pending.isHidden(), true);
173
+
174
+ await handleAgentEvent(host, toolEndEvent("tool-1", "read"));
175
+ assert.equal(summaryLines(host).length, 1);
176
+ });
177
+
178
+ it("reveals initially hidden on-error tools when they fail", async () => {
179
+ const host = createHost();
180
+
181
+ await handleAgentEvent(host, {
182
+ type: "message_start",
183
+ message: assistantMessage(),
184
+ });
185
+
186
+ await handleAgentEvent(host, toolStartEvent("tool-1", "bash", { command: "exit 1" }));
187
+ const pending = host.pendingTools.get("tool-1");
188
+ assert.ok(pending);
189
+ assert.equal(pending.isHidden(), true);
190
+
191
+ await handleAgentEvent(host, {
192
+ type: "tool_execution_end",
193
+ toolCallId: "tool-1",
194
+ toolName: "bash",
195
+ isError: true,
196
+ result: {
197
+ content: [{ type: "text", text: "failed" }],
198
+ details: {},
199
+ },
200
+ });
201
+
202
+ assert.equal(summaryLines(host).length, 0);
203
+ assert.equal(pending.isHidden(), false);
204
+ });
205
+
206
+ it("groups consecutive identical collapsed tools", async () => {
207
+ const host = createHost();
208
+
209
+ await handleAgentEvent(host, {
210
+ type: "message_start",
211
+ message: assistantMessage(),
212
+ });
213
+
214
+ const first = addPendingTool(host, "tool-1", 400);
215
+ await handleAgentEvent(host, toolEndEvent("tool-1", "read"));
216
+ assert.equal(host.chatContainer.children.indexOf(summaryLines(host)[0]), host.chatContainer.children.indexOf(first.component) - 1);
217
+
218
+ await handleAgentEvent(host, {
219
+ type: "message_update",
220
+ message: assistantMessage([{ type: "text", text: "streaming text" }]),
221
+ } as any);
222
+
223
+ addPendingTool(host, "tool-2", 600);
224
+ await handleAgentEvent(host, toolEndEvent("tool-2", "read"));
225
+
226
+ const summaries = summaryLines(host);
227
+ assert.equal(summaries.length, 1);
228
+ assert.ok(stripAnsi(summaries[0].render(160).join("\n")).includes("reading 2 files · 1.0s"));
229
+ });
230
+
231
+ it("does not group web fetches", async () => {
232
+ const host = createHost();
233
+
234
+ await handleAgentEvent(host, {
235
+ type: "message_start",
236
+ message: assistantMessage(),
237
+ });
238
+
239
+ addPendingTool(host, "tool-1", 200);
240
+ await handleAgentEvent(host, toolEndEvent("tool-1", "fetch_page"));
241
+
242
+ addPendingTool(host, "tool-2", 300);
243
+ await handleAgentEvent(host, toolEndEvent("tool-2", "fetch_page"));
244
+
245
+ const summaries = summaryLines(host);
246
+ assert.equal(summaries.length, 2);
247
+ assert.ok(stripAnsi(summaries[0].render(160).join("\n")).includes("reading 1 page · 0.2s"));
248
+ assert.ok(stripAnsi(summaries[1].render(160).join("\n")).includes("reading 1 page · 0.3s"));
249
+ });
250
+
251
+ it("keeps different collapsed tool types in separate summaries", async () => {
252
+ const host = createHost();
253
+
254
+ await handleAgentEvent(host, {
255
+ type: "message_start",
256
+ message: assistantMessage(),
257
+ });
258
+
259
+ addPendingTool(host, "tool-1", 200);
260
+ await handleAgentEvent(host, toolEndEvent("tool-1", "read"));
261
+
262
+ addPendingTool(host, "tool-2", 300);
263
+ await handleAgentEvent(host, toolEndEvent("tool-2", "find"));
264
+
265
+ const summaries = summaryLines(host);
266
+ assert.equal(summaries.length, 2);
267
+ assert.ok(stripAnsi(summaries[0].render(160).join("\n")).includes("reading 1 file · 0.2s"));
268
+ assert.ok(stripAnsi(summaries[1].render(160).join("\n")).includes("finding 1 path · 0.3s"));
269
+ });
270
+
271
+ it("resets grouping after visible tool result", async () => {
272
+ const host = createHost();
273
+
274
+ await handleAgentEvent(host, {
275
+ type: "message_start",
276
+ message: assistantMessage(),
277
+ });
278
+
279
+ addPendingTool(host, "tool-1", 200);
280
+ await handleAgentEvent(host, toolEndEvent("tool-1", "read"));
281
+
282
+ const visibleTool = addPendingTool(host, "tool-2", 300);
283
+ await handleAgentEvent(host, toolEndEvent("tool-2", "write"));
284
+ assert.equal(visibleTool.hidden, false);
285
+
286
+ addPendingTool(host, "tool-3", 400);
287
+ await handleAgentEvent(host, toolEndEvent("tool-3", "read"));
288
+
289
+ const summaries = summaryLines(host);
290
+ assert.equal(summaries.length, 2);
291
+ assert.ok(stripAnsi(summaries[0].render(160).join("\n")).includes("0.2s"));
292
+ assert.ok(stripAnsi(summaries[1].render(160).join("\n")).includes("0.4s"));
293
+ });
294
+
295
+ it("groups identical collapsed tools across empty assistant message boundaries", async () => {
296
+ const host = createHost();
297
+
298
+ await handleAgentEvent(host, {
299
+ type: "message_start",
300
+ message: assistantMessage(),
301
+ });
302
+
303
+ addPendingTool(host, "tool-1", 300);
304
+ await handleAgentEvent(host, toolEndEvent("tool-1", "read"));
305
+
306
+ await handleAgentEvent(host, {
307
+ type: "message_start",
308
+ message: assistantMessage(),
309
+ });
310
+
311
+ addPendingTool(host, "tool-2", 400);
312
+ await handleAgentEvent(host, toolEndEvent("tool-2", "read"));
313
+
314
+ const summaries = summaryLines(host);
315
+ assert.equal(summaries.length, 1);
316
+ assert.ok(stripAnsi(summaries[0].render(160).join("\n")).includes("reading 2 files · 0.7s"));
317
+ });
318
+
319
+ it("starts new collapsed group after visible assistant content", async () => {
320
+ const host = createHost();
321
+
322
+ await handleAgentEvent(host, {
323
+ type: "message_start",
324
+ message: assistantMessage(),
325
+ });
326
+
327
+ addPendingTool(host, "tool-1", 300);
328
+ await handleAgentEvent(host, toolEndEvent("tool-1", "read"));
329
+
330
+ await handleAgentEvent(host, {
331
+ type: "message_start",
332
+ message: assistantMessage(),
333
+ });
334
+ await handleAgentEvent(host, {
335
+ type: "message_update",
336
+ message: assistantMessage([{ type: "text", text: "visible reply" }]),
337
+ } as any);
338
+
339
+ addPendingTool(host, "tool-2", 400);
340
+ await handleAgentEvent(host, toolEndEvent("tool-2", "read"));
341
+
342
+ const summaries = summaryLines(host);
343
+ assert.equal(summaries.length, 2);
344
+ assert.ok(stripAnsi(summaries[0].render(160).join("\n")).includes("0.3s"));
345
+ assert.ok(stripAnsi(summaries[1].render(160).join("\n")).includes("0.4s"));
346
+ });
347
+
348
+ it("keeps collapsed tool calls visible in intermediate mode", async () => {
349
+ const host = createHost({ collapseToolCalls: true, collapsedToolCallsExpanded: true });
350
+
351
+ await handleAgentEvent(host, {
352
+ type: "message_start",
353
+ message: assistantMessage(),
354
+ });
355
+
356
+ const tool = addPendingTool(host, "tool-1", 250);
357
+ await handleAgentEvent(host, toolEndEvent("tool-1", "read"));
358
+
359
+ const summaries = summaryLines(host);
360
+ assert.equal(tool.hidden, false);
361
+ assert.equal(summaries.length, 1);
362
+ assert.deepEqual(summaries[0].render(160), []);
363
+ });
364
+
365
+ it("keeps collapsed tools visible when verbose mode is active", async () => {
366
+ const host = createHost({ collapseToolCalls: true, toolOutputExpanded: true });
367
+
368
+ await handleAgentEvent(host, {
369
+ type: "message_start",
370
+ message: assistantMessage(),
371
+ });
372
+
373
+ const tool = addPendingTool(host, "tool-1", 250);
374
+ await handleAgentEvent(host, toolEndEvent("tool-1", "read"));
375
+
376
+ const summaries = summaryLines(host);
377
+ assert.equal(tool.hidden, false);
378
+ assert.equal(summaries.length, 1);
379
+ assert.deepEqual(summaries[0].render(160), []);
380
+ });
381
+
382
+ it("does not collapse tool calls when setting is disabled", async () => {
383
+ const host = createHost({ collapseToolCalls: false });
384
+
385
+ await handleAgentEvent(host, {
386
+ type: "message_start",
387
+ message: assistantMessage(),
388
+ });
389
+
390
+ const tool = addPendingTool(host, "tool-1", 250);
391
+ await handleAgentEvent(host, toolEndEvent("tool-1", "read"));
392
+
393
+ assert.equal(tool.hidden, false);
394
+ assert.equal(summaryLines(host).length, 0);
395
+ });
396
+ });