lsd-pi 1.3.6 → 1.3.9

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 (126) hide show
  1. package/README.md +82 -0
  2. package/dist/cli.js +2 -1
  3. package/dist/lsd-settings-manager.d.ts +2 -0
  4. package/dist/lsd-settings-manager.js +5 -0
  5. package/dist/resource-loader.js +33 -3
  6. package/dist/resources/extensions/cache-timer/index.js +3 -2
  7. package/dist/resources/extensions/mcp-client/index.js +72 -4
  8. package/dist/resources/extensions/slash-commands/plan.js +5 -5
  9. package/dist/resources/extensions/usage/index.js +34 -2
  10. package/dist/resources/extensions/voice/index.js +1 -0
  11. package/dist/resources/extensions/voice/push-to-talk.js +2 -0
  12. package/dist/welcome-screen.js +2 -2
  13. package/package.json +1 -1
  14. package/packages/pi-coding-agent/dist/core/agent-session.context-usage.test.d.ts +2 -0
  15. package/packages/pi-coding-agent/dist/core/agent-session.context-usage.test.d.ts.map +1 -0
  16. package/packages/pi-coding-agent/dist/core/agent-session.context-usage.test.js +72 -0
  17. package/packages/pi-coding-agent/dist/core/agent-session.context-usage.test.js.map +1 -0
  18. package/packages/pi-coding-agent/dist/core/agent-session.d.ts +4 -0
  19. package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
  20. package/packages/pi-coding-agent/dist/core/agent-session.js +29 -2
  21. package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  22. package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts.map +1 -1
  23. package/packages/pi-coding-agent/dist/core/extensions/runner.js +1 -0
  24. package/packages/pi-coding-agent/dist/core/extensions/runner.js.map +1 -1
  25. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts +2 -0
  26. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
  27. package/packages/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
  28. package/packages/pi-coding-agent/dist/core/settings-manager.collapse-tool-calls.test.d.ts +2 -0
  29. package/packages/pi-coding-agent/dist/core/settings-manager.collapse-tool-calls.test.d.ts.map +1 -0
  30. package/packages/pi-coding-agent/dist/core/settings-manager.collapse-tool-calls.test.js +35 -0
  31. package/packages/pi-coding-agent/dist/core/settings-manager.collapse-tool-calls.test.js.map +1 -0
  32. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +6 -0
  33. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
  34. package/packages/pi-coding-agent/dist/core/settings-manager.js +12 -0
  35. package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
  36. package/packages/pi-coding-agent/dist/core/tool-priority.js +1 -1
  37. package/packages/pi-coding-agent/dist/core/tool-priority.js.map +1 -1
  38. package/packages/pi-coding-agent/dist/core/tools/edit-diff.d.ts +5 -0
  39. package/packages/pi-coding-agent/dist/core/tools/edit-diff.d.ts.map +1 -1
  40. package/packages/pi-coding-agent/dist/core/tools/edit-diff.js +21 -0
  41. package/packages/pi-coding-agent/dist/core/tools/edit-diff.js.map +1 -1
  42. package/packages/pi-coding-agent/dist/core/tools/edit-diff.test.js +16 -1
  43. package/packages/pi-coding-agent/dist/core/tools/edit-diff.test.js.map +1 -1
  44. package/packages/pi-coding-agent/dist/main.d.ts.map +1 -1
  45. package/packages/pi-coding-agent/dist/main.js +1 -0
  46. package/packages/pi-coding-agent/dist/main.js.map +1 -1
  47. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-summary-line.test.js +12 -4
  48. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-summary-line.test.js.map +1 -1
  49. package/packages/pi-coding-agent/dist/modes/interactive/components/diff.d.ts +7 -5
  50. package/packages/pi-coding-agent/dist/modes/interactive/components/diff.d.ts.map +1 -1
  51. package/packages/pi-coding-agent/dist/modes/interactive/components/diff.js +86 -28
  52. package/packages/pi-coding-agent/dist/modes/interactive/components/diff.js.map +1 -1
  53. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.d.ts +2 -0
  54. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.d.ts.map +1 -1
  55. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js +16 -10
  56. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js.map +1 -1
  57. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.d.ts +4 -0
  58. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  59. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.js +26 -4
  60. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.js.map +1 -1
  61. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts +16 -1
  62. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  63. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +128 -13
  64. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
  65. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-summary-line.d.ts +1 -0
  66. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-summary-line.d.ts.map +1 -1
  67. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-summary-line.js +48 -4
  68. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-summary-line.js.map +1 -1
  69. package/packages/pi-coding-agent/dist/modes/interactive/controllers/__tests__/chat-controller.collapsed-tool-summary.test.js +137 -6
  70. package/packages/pi-coding-agent/dist/modes/interactive/controllers/__tests__/chat-controller.collapsed-tool-summary.test.js.map +1 -1
  71. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
  72. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +64 -15
  73. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
  74. package/packages/pi-coding-agent/dist/modes/interactive/controllers/extension-ui-controller.d.ts.map +1 -1
  75. package/packages/pi-coding-agent/dist/modes/interactive/controllers/extension-ui-controller.js +2 -1
  76. package/packages/pi-coding-agent/dist/modes/interactive/controllers/extension-ui-controller.js.map +1 -1
  77. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts +1 -0
  78. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts.map +1 -1
  79. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.js.map +1 -1
  80. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +5 -1
  81. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  82. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +73 -27
  83. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  84. package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.js +4 -4
  85. package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.js.map +1 -1
  86. package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  87. package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.js +3 -0
  88. package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.js.map +1 -1
  89. package/packages/pi-coding-agent/package.json +1 -1
  90. package/packages/pi-coding-agent/src/core/agent-session.context-usage.test.ts +87 -0
  91. package/packages/pi-coding-agent/src/core/agent-session.ts +40 -2
  92. package/packages/pi-coding-agent/src/core/extensions/runner.ts +1 -0
  93. package/packages/pi-coding-agent/src/core/extensions/types.ts +3 -0
  94. package/packages/pi-coding-agent/src/core/settings-manager.collapse-tool-calls.test.ts +46 -0
  95. package/packages/pi-coding-agent/src/core/settings-manager.ts +18 -0
  96. package/packages/pi-coding-agent/src/core/tool-priority.ts +1 -1
  97. package/packages/pi-coding-agent/src/core/tools/edit-diff.test.ts +20 -0
  98. package/packages/pi-coding-agent/src/core/tools/edit-diff.ts +26 -0
  99. package/packages/pi-coding-agent/src/main.ts +1 -0
  100. package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/tool-summary-line.test.ts +14 -4
  101. package/packages/pi-coding-agent/src/modes/interactive/components/diff.ts +105 -28
  102. package/packages/pi-coding-agent/src/modes/interactive/components/footer.ts +13 -6
  103. package/packages/pi-coding-agent/src/modes/interactive/components/settings-selector.ts +31 -4
  104. package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +137 -14
  105. package/packages/pi-coding-agent/src/modes/interactive/components/tool-summary-line.ts +60 -4
  106. package/packages/pi-coding-agent/src/modes/interactive/controllers/__tests__/chat-controller.collapsed-tool-summary.test.ts +174 -6
  107. package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +73 -15
  108. package/packages/pi-coding-agent/src/modes/interactive/controllers/extension-ui-controller.ts +2 -1
  109. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode-state.ts +1 -0
  110. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +76 -27
  111. package/packages/pi-coding-agent/src/modes/interactive/theme/themes.ts +4 -4
  112. package/packages/pi-coding-agent/src/modes/rpc/rpc-mode.ts +4 -0
  113. package/packages/pi-tui/dist/components/editor.js +3 -3
  114. package/packages/pi-tui/dist/components/editor.js.map +1 -1
  115. package/packages/pi-tui/src/components/editor.ts +3 -3
  116. package/pkg/dist/modes/interactive/theme/themes.js +4 -4
  117. package/pkg/dist/modes/interactive/theme/themes.js.map +1 -1
  118. package/pkg/package.json +1 -1
  119. package/src/resources/extensions/cache-timer/index.ts +3 -2
  120. package/src/resources/extensions/mcp-client/index.ts +83 -4
  121. package/src/resources/extensions/mcp-client/tests/server-name-spaces.test.ts +16 -0
  122. package/src/resources/extensions/slash-commands/plan.ts +6 -6
  123. package/src/resources/extensions/usage/index.ts +40 -2
  124. package/src/resources/extensions/voice/index.ts +1 -0
  125. package/src/resources/extensions/voice/push-to-talk.ts +3 -0
  126. package/src/resources/extensions/voice/tests/push-to-talk.test.ts +6 -0
@@ -9,6 +9,15 @@ import { ToolSummaryLine } from "../components/tool-summary-line.js";
9
9
  import { shouldCollapse } from "../../../core/tool-priority.js";
10
10
  import { appKey } from "../components/keybinding-hints.js";
11
11
 
12
+ const GROUPABLE_COLLAPSED_TOOLS = new Set([
13
+ "read",
14
+ "find",
15
+ "ls",
16
+ "grep",
17
+ "lsp",
18
+
19
+ ]);
20
+
12
21
  export async function handleAgentEvent(host: InteractiveModeStateHost & {
13
22
  init: () => Promise<void>;
14
23
  getMarkdownThemeWithSettings: () => any;
@@ -42,6 +51,14 @@ export async function handleAgentEvent(host: InteractiveModeStateHost & {
42
51
  host.collapsedToolSummaryLine = undefined;
43
52
  };
44
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
+ "subagent", "await_subagent",
60
+ ]);
61
+
45
62
  const hasVisibleRender = (child: { render?: (width: number) => string[] } | undefined): boolean => {
46
63
  if (!child?.render) return true;
47
64
  try {
@@ -51,14 +68,20 @@ export async function handleAgentEvent(host: InteractiveModeStateHost & {
51
68
  }
52
69
  };
53
70
 
71
+ const canGroupCollapsedTool = (toolName: string): boolean => GROUPABLE_COLLAPSED_TOOLS.has(toolName);
72
+
54
73
  const findAdjacentCollapsedToolSummary = (
74
+ toolName: string,
55
75
  anchor?: { render: (width: number) => string[] },
56
76
  ): ToolSummaryLine | undefined => {
77
+ if (!canGroupCollapsedTool(toolName)) {
78
+ return undefined;
79
+ }
57
80
  const anchorIndex = anchor ? host.chatContainer.children.indexOf(anchor) : host.chatContainer.children.length;
58
81
  for (let i = anchorIndex - 1; i >= 0; i--) {
59
82
  const child = host.chatContainer.children[i];
60
83
  if (child instanceof ToolSummaryLine) {
61
- return child;
84
+ return child.canGroupWith(toolName) ? child : undefined;
62
85
  }
63
86
  if (child instanceof ToolExecutionComponent && child.isHidden()) {
64
87
  continue;
@@ -71,17 +94,15 @@ export async function handleAgentEvent(host: InteractiveModeStateHost & {
71
94
  return undefined;
72
95
  };
73
96
 
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)) {
97
+ const appendCollapsedToolSummary = (
98
+ toolName: string,
99
+ elapsed: number,
100
+ anchor?: { render: (width: number) => string[] },
101
+ ): ToolSummaryLine => {
102
+ let summary = findAdjacentCollapsedToolSummary(toolName, anchor);
103
+ if (!summary) {
83
104
  summary = new ToolSummaryLine();
84
- summary.setHidden(host.toolOutputExpanded);
105
+ summary.setHidden(host.collapsedToolCallsExpanded);
85
106
  if (anchor) {
86
107
  const anchorIndex = host.chatContainer.children.indexOf(anchor);
87
108
  if (anchorIndex >= 0) {
@@ -92,9 +113,10 @@ export async function handleAgentEvent(host: InteractiveModeStateHost & {
92
113
  } else {
93
114
  host.chatContainer.addChild(summary);
94
115
  }
95
- host.collapsedToolSummaryLine = summary;
96
116
  }
117
+ host.collapsedToolSummaryLine = summary;
97
118
  summary.addTool(toolName, elapsed);
119
+ return summary;
98
120
  };
99
121
 
100
122
  switch (event.type) {
@@ -158,7 +180,7 @@ export async function handleAgentEvent(host: InteractiveModeStateHost & {
158
180
  (text) => theme.fg("accent", text),
159
181
  host.defaultWorkingMessage,
160
182
  );
161
- host.loadingAnimation.setCycleMessages(host.workingMessages, 3000);
183
+ host.loadingAnimation.setCycleMessages(host.workingMessages, 10_000);
162
184
  host.statusContainer.addChild(host.loadingAnimation);
163
185
  host.startLoadingTips();
164
186
  // Show steer/queue + expand hint in editor bottom border while agent is running
@@ -221,8 +243,19 @@ export async function handleAgentEvent(host: InteractiveModeStateHost & {
221
243
  host.ui,
222
244
  );
223
245
  component.setExpanded(host.toolOutputExpanded);
246
+ const collapseToolCalls = host.settingsManager.getCollapseToolCalls?.() ?? false;
247
+ const shouldHide = collapseToolCalls && !host.collapsedToolCallsExpanded && shouldCollapse(content.name, false) && !ALWAYS_DIRECT_TOOLS.has(content.name);
224
248
  host.chatContainer.addChild(component);
249
+ if (shouldHide) {
250
+ // Check for adjacent summary AFTER adding to container so indexOf works
251
+ const adjacentSummary = findAdjacentCollapsedToolSummary(content.name, component);
252
+ if (adjacentSummary) {
253
+ component.setIndented(true);
254
+ }
255
+ }
256
+ component.setHidden(shouldHide);
225
257
  host.pendingTools.set(content.id, component);
258
+ host.updateEditorExpandHint();
226
259
  } else {
227
260
  host.pendingTools.get(content.id)?.updateArgs(content.arguments);
228
261
  }
@@ -240,8 +273,19 @@ export async function handleAgentEvent(host: InteractiveModeStateHost & {
240
273
  host.ui,
241
274
  );
242
275
  component.setExpanded(host.toolOutputExpanded);
276
+ const collapseToolCalls = host.settingsManager.getCollapseToolCalls?.() ?? false;
277
+ const shouldHide = collapseToolCalls && !host.collapsedToolCallsExpanded && shouldCollapse(content.name, false) && !ALWAYS_DIRECT_TOOLS.has(content.name);
243
278
  host.chatContainer.addChild(component);
279
+ if (shouldHide) {
280
+ // Check for adjacent summary AFTER adding to container so indexOf works
281
+ const adjacentSummary = findAdjacentCollapsedToolSummary(content.name, component);
282
+ if (adjacentSummary) {
283
+ component.setIndented(true);
284
+ }
285
+ }
286
+ component.setHidden(shouldHide);
244
287
  host.pendingTools.set(content.id, component);
288
+ host.updateEditorExpandHint();
245
289
  }
246
290
  } else if (content.type === "webSearchResult") {
247
291
  const component = host.pendingTools.get(content.toolUseId);
@@ -322,8 +366,19 @@ export async function handleAgentEvent(host: InteractiveModeStateHost & {
322
366
  host.ui,
323
367
  );
324
368
  component.setExpanded(host.toolOutputExpanded);
369
+ const collapseToolCalls = host.settingsManager.getCollapseToolCalls?.() ?? false;
370
+ const shouldHide = collapseToolCalls && !host.collapsedToolCallsExpanded && shouldCollapse(event.toolName, false) && !ALWAYS_DIRECT_TOOLS.has(event.toolName);
325
371
  host.chatContainer.addChild(component);
372
+ if (shouldHide) {
373
+ // Check for adjacent summary AFTER adding to container so indexOf works
374
+ const adjacentSummary = findAdjacentCollapsedToolSummary(event.toolName, component);
375
+ if (adjacentSummary) {
376
+ component.setIndented(true);
377
+ }
378
+ }
379
+ component.setHidden(shouldHide);
326
380
  host.pendingTools.set(event.toolCallId, component);
381
+ host.updateEditorExpandHint();
327
382
  host.ui.requestRender();
328
383
  }
329
384
  break;
@@ -371,9 +426,12 @@ export async function handleAgentEvent(host: InteractiveModeStateHost & {
371
426
  const component = host.pendingTools.get(event.toolCallId);
372
427
  if (component) {
373
428
  component.updateResult({ ...event.result, isError: event.isError });
374
- if (shouldCollapse(event.toolName, event.isError)) {
429
+ const collapseToolCalls = host.settingsManager.getCollapseToolCalls?.() ?? false;
430
+ if (collapseToolCalls && shouldCollapse(event.toolName, event.isError) && !ALWAYS_DIRECT_TOOLS.has(event.toolName)) {
375
431
  appendCollapsedToolSummary(event.toolName, component.getElapsed(), component);
376
- component.setHidden(true);
432
+ component.setHidden(!host.collapsedToolCallsExpanded);
433
+ component.setIndented(false);
434
+ host.updateEditorExpandHint();
377
435
  } else {
378
436
  component.setHidden(false);
379
437
  resetCollapsedToolSummary();
@@ -53,8 +53,9 @@ export function createExtensionUIContext(host: any): ExtensionUIContext {
53
53
  }
54
54
  return result;
55
55
  },
56
- getToolsExpanded: () => host.toolOutputExpanded,
56
+ getToolsExpanded: () => host.collapsedToolCallsExpanded || host.toolOutputExpanded,
57
57
  setToolsExpanded: (expanded) => host.setToolsExpanded(expanded),
58
+ isEditorFocused: () => !!host.editor.focused,
58
59
  };
59
60
  }
60
61
 
@@ -12,6 +12,7 @@ export interface InteractiveModeStateHost {
12
12
  settingsManager: any;
13
13
  pendingTools: Map<string, any>;
14
14
  collapsedToolSummaryLine?: any;
15
+ collapsedToolCallsExpanded: boolean;
15
16
  toolOutputExpanded: boolean;
16
17
  hideThinkingBlock: boolean;
17
18
  notificationSoundEnabled: boolean;
@@ -325,6 +325,7 @@ export class InteractiveMode {
325
325
  private agentPtyComponents = new Map<string, EmbeddedTerminalComponent>();
326
326
 
327
327
  // Tool output expansion state
328
+ private collapsedToolCallsExpanded = false;
328
329
  private toolOutputExpanded = false;
329
330
 
330
331
  // Thinking block visibility state
@@ -444,6 +445,7 @@ export class InteractiveMode {
444
445
  // Load notification sound setting
445
446
  this.notificationSoundEnabled = this.settingsManager.getNotificationSound();
446
447
  this.footer.setNotificationSoundEnabled(this.notificationSoundEnabled);
448
+ this.footer.setVerboseFooterEnabled(this.settingsManager.getVerboseFooter());
447
449
 
448
450
  // Register themes from resource loader and initialize
449
451
  setRegisteredThemes(this.session.resourceLoader.getThemes().themes);
@@ -1536,7 +1538,7 @@ export class InteractiveMode {
1536
1538
  this.defaultEditor.onExtensionShortcut = undefined;
1537
1539
  this.updateTerminalTitle();
1538
1540
  if (this.loadingAnimation) {
1539
- this.loadingAnimation.setCycleMessages(this.workingMessages);
1541
+ this.loadingAnimation.setCycleMessages(this.workingMessages, 10_000);
1540
1542
  }
1541
1543
  }
1542
1544
 
@@ -1879,7 +1881,7 @@ export class InteractiveMode {
1879
1881
  /** Reset the loading animation back to cycling working messages. */
1880
1882
  private resetLoadingMessage(): void {
1881
1883
  if (this.loadingAnimation) {
1882
- this.loadingAnimation.setCycleMessages(this.workingMessages);
1884
+ this.loadingAnimation.setCycleMessages(this.workingMessages, 10_000);
1883
1885
  }
1884
1886
  }
1885
1887
 
@@ -2358,6 +2360,7 @@ export class InteractiveMode {
2358
2360
  if (wasBashMode !== this.isBashMode) {
2359
2361
  this.updateEditorBorderColor();
2360
2362
  }
2363
+ this.updateEditorExpandHint();
2361
2364
  };
2362
2365
 
2363
2366
  // Handle clipboard image paste (triggered on Ctrl+V)
@@ -3072,63 +3075,93 @@ export class InteractiveMode {
3072
3075
  }
3073
3076
  }
3074
3077
 
3078
+ private hasCollapsedToolCallSummaries(): boolean {
3079
+ return this.chatContainer.children.some((child) => child instanceof ToolSummaryLine);
3080
+ }
3081
+
3082
+ private shouldUseCollapsedToolCallIntermediateState(): boolean {
3083
+ return this.settingsManager.getCollapseToolCalls() && this.hasCollapsedToolCallSummaries();
3084
+ }
3085
+
3075
3086
  private toggleToolOutputExpansion(): void {
3076
- this.setToolsExpanded(!this.toolOutputExpanded);
3087
+ if (this.toolOutputExpanded) {
3088
+ this.setToolOutputExpansionState(false, false);
3089
+ return;
3090
+ }
3091
+ if (this.shouldUseCollapsedToolCallIntermediateState() && !this.collapsedToolCallsExpanded) {
3092
+ this.setToolOutputExpansionState(true, false);
3093
+ return;
3094
+ }
3095
+ this.setToolOutputExpansionState(true, true);
3077
3096
  }
3078
3097
 
3079
3098
  private setToolsExpanded(expanded: boolean): void {
3080
- this.toolOutputExpanded = expanded;
3099
+ this.setToolOutputExpansionState(expanded, expanded);
3100
+ }
3101
+
3102
+ private setToolOutputExpansionState(collapsedToolCallsExpanded: boolean, toolOutputExpanded: boolean): void {
3103
+ this.collapsedToolCallsExpanded = collapsedToolCallsExpanded || toolOutputExpanded;
3104
+ this.toolOutputExpanded = toolOutputExpanded;
3105
+ const collapseToolCalls = this.settingsManager.getCollapseToolCalls();
3106
+ const showCollapsedToolCalls = collapseToolCalls && this.collapsedToolCallsExpanded;
3081
3107
  for (const child of this.chatContainer.children) {
3082
3108
  if (isExpandable(child)) {
3083
- child.setExpanded(expanded);
3109
+ child.setExpanded(toolOutputExpanded);
3084
3110
  }
3085
3111
  if (child instanceof ToolExecutionComponent) {
3086
- child.setHidden(!expanded && child.shouldHideWhenCollapsed());
3112
+ child.setHidden(!showCollapsedToolCalls && child.shouldHideWhenCollapsed(collapseToolCalls));
3113
+ child.setIndented(false);
3087
3114
  }
3088
3115
  if (child instanceof ToolSummaryLine) {
3089
- child.setHidden(expanded);
3116
+ child.setHidden(!collapseToolCalls || showCollapsedToolCalls);
3090
3117
  }
3091
3118
  }
3092
3119
  if (this.bashComponent) {
3093
- this.bashComponent.setExpanded(expanded);
3120
+ this.bashComponent.setExpanded(toolOutputExpanded);
3094
3121
  }
3095
3122
  for (const component of this.pendingBashComponents) {
3096
- component.setExpanded(expanded);
3123
+ component.setExpanded(toolOutputExpanded);
3097
3124
  }
3098
3125
  this.updateEditorExpandHint();
3099
3126
  this.ui.requestRender();
3100
3127
  }
3101
3128
 
3102
- /** Append/remove the "ctrl+o to expand" hint in the editor bottom border. */
3129
+ /** Append/remove right-aligned expand and streaming hints in editor bottom border. */
3103
3130
  updateEditorExpandHint(): void {
3104
3131
  const expandKey = appKey(this.keybindings, "expandTools");
3105
3132
  const collapseHint = `${theme.fg("dim", expandKey)}${theme.fg("muted", " collapse")}`;
3106
- const expandHint = `${theme.fg("dim", expandKey)}${theme.fg("muted", " : verbose")}`;
3107
- // The base hint set during agent_start
3108
- const enterKey = theme.fg("dim", "↵");
3109
- const followUpKey = theme.fg("dim", appKey(this.keybindings, "followUp"));
3110
- const steerLabel = theme.fg("muted", " steer");
3111
- const queueLabel = theme.fg("muted", " queue");
3112
- const baseHint = `${enterKey}${steerLabel} ${followUpKey}${queueLabel}`;
3133
+ const expandToolCallsHint = `${theme.fg("dim", expandKey)}${theme.fg("muted", " : uncollapsed tools")}`;
3134
+ const expandVerboseHint = `${theme.fg("dim", expandKey)}${theme.fg("muted", " : verbose")}`;
3135
+ const editorHasText = this.defaultEditor.getText().trim().length > 0;
3136
+ const streamingHints = editorHasText
3137
+ ? `${theme.fg("dim", "↵")}${theme.fg("muted", " steer")} ${theme.fg("dim", appKey(this.keybindings, "followUp"))}${theme.fg("muted", " queue")}`
3138
+ : "";
3113
3139
 
3114
3140
  // Check if there are any expandable tool outputs in the chat
3115
3141
  const hasToolOutputs =
3116
3142
  this.chatContainer.children.some(isExpandable) ||
3117
3143
  !!this.bashComponent ||
3118
3144
  this.pendingBashComponents.length > 0;
3145
+ const canExpandCollapsedToolCalls = this.shouldUseCollapsedToolCallIntermediateState();
3119
3146
 
3120
- const activeHint = this.toolOutputExpanded ? collapseHint : expandHint;
3147
+ let activeHint = expandVerboseHint;
3148
+ if (this.toolOutputExpanded) {
3149
+ activeHint = collapseHint;
3150
+ } else if (this.collapsedToolCallsExpanded && canExpandCollapsedToolCalls) {
3151
+ activeHint = expandVerboseHint;
3152
+ } else if (canExpandCollapsedToolCalls) {
3153
+ activeHint = expandToolCallsHint;
3154
+ }
3121
3155
 
3122
- if (this.loadingAnimation) {
3123
- // Agent is running - always show expand/collapse hint when there are tool outputs
3124
- this.defaultEditor.bottomHint = hasToolOutputs
3125
- ? `${baseHint} ${activeHint}`
3126
- : baseHint;
3127
- } else if (hasToolOutputs) {
3128
- // Idle - show expand/collapse hint so user knows ctrl+o works
3129
- this.defaultEditor.bottomHint = activeHint;
3156
+ const rightHints: string[] = [];
3157
+ if (this.loadingAnimation && streamingHints) {
3158
+ rightHints.push(streamingHints);
3130
3159
  }
3131
- // If no tool outputs and idle, leave bottomHint as-is (cleared by agent_end)
3160
+ if (hasToolOutputs) {
3161
+ rightHints.push(activeHint);
3162
+ }
3163
+
3164
+ this.defaultEditor.bottomHint = rightHints.join(" ");
3132
3165
  }
3133
3166
 
3134
3167
  private toggleThinkingBlockVisibility(): void {
@@ -3481,6 +3514,7 @@ export class InteractiveMode {
3481
3514
  codexRotate: this.settingsManager.getCodexRotate(),
3482
3515
  fastMode: this.settingsManager.getFastMode(),
3483
3516
  cacheTimer: this.settingsManager.getCacheTimer(),
3517
+ verboseFooter: this.settingsManager.getVerboseFooter(),
3484
3518
  pinLastPrompt: this.settingsManager.getPinLastPrompt(),
3485
3519
  steeringMode: this.session.steeringMode,
3486
3520
  followUpMode: this.session.followUpMode,
@@ -3505,6 +3539,7 @@ export class InteractiveMode {
3505
3539
  clearOnShrink: this.settingsManager.getClearOnShrink(),
3506
3540
  timestampFormat: this.settingsManager.getTimestampFormat(),
3507
3541
  toolOutputMode: this.settingsManager.getToolOutputMode(),
3542
+ collapseToolCalls: this.settingsManager.getCollapseToolCalls(),
3508
3543
  rtk: this.settingsManager.getRtk(),
3509
3544
  editorScheme: this.settingsManager.getEditorScheme(),
3510
3545
  autoDream: this.settingsManager.getAutoDream(),
@@ -3688,6 +3723,12 @@ export class InteractiveMode {
3688
3723
  this.settingsManager.setCacheTimer(enabled);
3689
3724
  this.showStatus(`Cache timer: ${enabled ? "enabled" : "disabled"}`);
3690
3725
  },
3726
+ onVerboseFooterChange: (enabled) => {
3727
+ this.settingsManager.setVerboseFooter(enabled);
3728
+ this.footer.setVerboseFooterEnabled(enabled);
3729
+ this.ui.requestRender();
3730
+ this.showStatus(`Verbose footer: ${enabled ? "enabled" : "disabled"}`);
3731
+ },
3691
3732
  onPinLastPromptChange: (enabled) => {
3692
3733
  this.settingsManager.setPinLastPrompt(enabled);
3693
3734
  this.pinLastPromptEnabled = enabled;
@@ -3827,6 +3868,14 @@ export class InteractiveMode {
3827
3868
  this.settingsManager.setEditorScheme(scheme);
3828
3869
  this.showStatus(`Editor link scheme: ${scheme}`);
3829
3870
  },
3871
+ onCollapseToolCallsChange: (enabled) => {
3872
+ this.settingsManager.setCollapseToolCalls(enabled);
3873
+ this.setToolOutputExpansionState(enabled && (this.collapsedToolCallsExpanded || this.toolOutputExpanded), this.toolOutputExpanded);
3874
+ if (!enabled) {
3875
+ this.collapsedToolSummaryLine = undefined;
3876
+ }
3877
+ this.showStatus(`Collapse tool calls: ${enabled ? "enabled" : "disabled"}`);
3878
+ },
3830
3879
  onToolOutputModeChange: (mode) => {
3831
3880
  this.settingsManager.setToolOutputMode(mode);
3832
3881
  for (const child of this.chatContainer.children) {
@@ -78,8 +78,8 @@ const dark: ThemeJson = {
78
78
  mdHr: "gray",
79
79
  mdListBullet: "accent",
80
80
 
81
- toolDiffAdded: "green",
82
- toolDiffRemoved: "red",
81
+ toolDiffAdded: "#4ade80",
82
+ toolDiffRemoved: "#fb7185",
83
83
  toolDiffContext: "gray",
84
84
 
85
85
  syntaxComment: "#6A9955",
@@ -174,8 +174,8 @@ const light: ThemeJson = {
174
174
  mdHr: "mediumGray",
175
175
  mdListBullet: "green",
176
176
 
177
- toolDiffAdded: "green",
178
- toolDiffRemoved: "red",
177
+ toolDiffAdded: "#15803d",
178
+ toolDiffRemoved: "#b91c1c",
179
179
  toolDiffContext: "mediumGray",
180
180
 
181
181
  syntaxComment: "#008000",
@@ -422,6 +422,10 @@ export async function runRpcMode(session: AgentSession): Promise<never> {
422
422
  setToolsExpanded(_expanded: boolean) {
423
423
  // Tool expansion not supported in RPC mode - no TUI
424
424
  },
425
+
426
+ isEditorFocused() {
427
+ return false;
428
+ },
425
429
  });
426
430
 
427
431
  // Set up extensions with RPC-based UI context.
@@ -343,15 +343,15 @@ export class Editor {
343
343
  result.push(this.borderColor(indicator + "─".repeat(Math.max(0, remaining))));
344
344
  }
345
345
  else if (this.bottomHint) {
346
- // Embed hint right-aligned in the bottom border: ─────── hint ─
346
+ // Embed hint at far right in bottom border: ─────────────── hint ─
347
347
  // Apply borderColor only to the dashes so the hint's own styling is preserved.
348
348
  const hintVisible = visibleWidth(this.bottomHint);
349
349
  const minDashes = 1;
350
350
  const separatorWidth = 1; // single space on each side of hint
351
351
  const totalFixed = hintVisible + separatorWidth * 2 + minDashes * 2;
352
352
  if (width >= totalFixed) {
353
- const leftDashes = Math.floor((width - hintVisible - separatorWidth * 2) * 0.75);
354
- const rightDashes = Math.max(minDashes, width - hintVisible - separatorWidth * 2 - leftDashes);
353
+ const rightDashes = minDashes;
354
+ const leftDashes = Math.max(minDashes, width - hintVisible - separatorWidth * 2 - rightDashes);
355
355
  const line = this.borderColor("─".repeat(leftDashes)) +
356
356
  " " +
357
357
  this.bottomHint +