lsd-pi 1.1.4 → 1.1.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 (175) hide show
  1. package/README.md +2 -1
  2. package/dist/headless-ui.js +2 -0
  3. package/dist/onboarding.js +11 -8
  4. package/dist/resources/extensions/async-jobs/async-bash-tool.js +14 -0
  5. package/dist/resources/extensions/async-jobs/await-tool.js +14 -0
  6. package/dist/resources/extensions/async-jobs/cancel-job-tool.js +7 -0
  7. package/dist/resources/extensions/cache-timer/index.js +5 -0
  8. package/dist/resources/extensions/codex-rotate/IMPLEMENTATION.md +18 -13
  9. package/dist/resources/extensions/codex-rotate/README.md +9 -3
  10. package/dist/resources/extensions/codex-rotate/commands.js +15 -8
  11. package/dist/resources/extensions/codex-rotate/index.js +17 -8
  12. package/dist/resources/extensions/memory/auto-extract.js +196 -80
  13. package/dist/resources/extensions/memory/dream.js +86 -19
  14. package/dist/resources/extensions/shared/rtk.js +89 -87
  15. package/dist/resources/extensions/subagent/index.js +33 -7
  16. package/dist/startup-model-validation.js +12 -2
  17. package/dist/update-check.js +2 -2
  18. package/dist/update-cmd.js +3 -3
  19. package/dist/welcome-screen.js +43 -14
  20. package/package.json +3 -2
  21. package/packages/pi-coding-agent/dist/core/agent-session.clear-queue.test.d.ts +2 -0
  22. package/packages/pi-coding-agent/dist/core/agent-session.clear-queue.test.d.ts.map +1 -0
  23. package/packages/pi-coding-agent/dist/core/agent-session.clear-queue.test.js +46 -0
  24. package/packages/pi-coding-agent/dist/core/agent-session.clear-queue.test.js.map +1 -0
  25. package/packages/pi-coding-agent/dist/core/agent-session.d.ts +8 -0
  26. package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
  27. package/packages/pi-coding-agent/dist/core/agent-session.js +43 -4
  28. package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  29. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts +3 -1
  30. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
  31. package/packages/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
  32. package/packages/pi-coding-agent/dist/core/keybindings.d.ts +1 -1
  33. package/packages/pi-coding-agent/dist/core/keybindings.d.ts.map +1 -1
  34. package/packages/pi-coding-agent/dist/core/keybindings.js +2 -0
  35. package/packages/pi-coding-agent/dist/core/keybindings.js.map +1 -1
  36. package/packages/pi-coding-agent/dist/core/pty-executor.d.ts +48 -0
  37. package/packages/pi-coding-agent/dist/core/pty-executor.d.ts.map +1 -0
  38. package/packages/pi-coding-agent/dist/core/pty-executor.js +173 -0
  39. package/packages/pi-coding-agent/dist/core/pty-executor.js.map +1 -0
  40. package/packages/pi-coding-agent/dist/core/sdk.d.ts.map +1 -1
  41. package/packages/pi-coding-agent/dist/core/sdk.js +16 -3
  42. package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
  43. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +9 -0
  44. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
  45. package/packages/pi-coding-agent/dist/core/settings-manager.js +18 -0
  46. package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
  47. package/packages/pi-coding-agent/dist/core/tool-approval.d.ts.map +1 -1
  48. package/packages/pi-coding-agent/dist/core/tool-approval.js +2 -2
  49. package/packages/pi-coding-agent/dist/core/tool-approval.js.map +1 -1
  50. package/packages/pi-coding-agent/dist/core/tools/index.d.ts +7 -0
  51. package/packages/pi-coding-agent/dist/core/tools/index.d.ts.map +1 -1
  52. package/packages/pi-coding-agent/dist/core/tools/index.js +23 -2
  53. package/packages/pi-coding-agent/dist/core/tools/index.js.map +1 -1
  54. package/packages/pi-coding-agent/dist/core/tools/pty.d.ts +50 -0
  55. package/packages/pi-coding-agent/dist/core/tools/pty.d.ts.map +1 -0
  56. package/packages/pi-coding-agent/dist/core/tools/pty.js +289 -0
  57. package/packages/pi-coding-agent/dist/core/tools/pty.js.map +1 -0
  58. package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.d.ts +3 -1
  59. package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
  60. package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.js +36 -22
  61. package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.js.map +1 -1
  62. package/packages/pi-coding-agent/dist/modes/interactive/components/bash-execution.d.ts +3 -5
  63. package/packages/pi-coding-agent/dist/modes/interactive/components/bash-execution.d.ts.map +1 -1
  64. package/packages/pi-coding-agent/dist/modes/interactive/components/bash-execution.js +23 -62
  65. package/packages/pi-coding-agent/dist/modes/interactive/components/bash-execution.js.map +1 -1
  66. package/packages/pi-coding-agent/dist/modes/interactive/components/branch-summary-message.d.ts.map +1 -1
  67. package/packages/pi-coding-agent/dist/modes/interactive/components/branch-summary-message.js +1 -4
  68. package/packages/pi-coding-agent/dist/modes/interactive/components/branch-summary-message.js.map +1 -1
  69. package/packages/pi-coding-agent/dist/modes/interactive/components/compaction-summary-message.d.ts.map +1 -1
  70. package/packages/pi-coding-agent/dist/modes/interactive/components/compaction-summary-message.js +1 -4
  71. package/packages/pi-coding-agent/dist/modes/interactive/components/compaction-summary-message.js.map +1 -1
  72. package/packages/pi-coding-agent/dist/modes/interactive/components/embedded-terminal.d.ts +39 -0
  73. package/packages/pi-coding-agent/dist/modes/interactive/components/embedded-terminal.d.ts.map +1 -0
  74. package/packages/pi-coding-agent/dist/modes/interactive/components/embedded-terminal.js +182 -0
  75. package/packages/pi-coding-agent/dist/modes/interactive/components/embedded-terminal.js.map +1 -0
  76. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.d.ts +6 -0
  77. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  78. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.js +36 -0
  79. package/packages/pi-coding-agent/dist/modes/interactive/components/settings-selector.js.map +1 -1
  80. package/packages/pi-coding-agent/dist/modes/interactive/components/skill-invocation-message.d.ts.map +1 -1
  81. package/packages/pi-coding-agent/dist/modes/interactive/components/skill-invocation-message.js +2 -4
  82. package/packages/pi-coding-agent/dist/modes/interactive/components/skill-invocation-message.js.map +1 -1
  83. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts +1 -2
  84. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  85. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +106 -77
  86. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
  87. package/packages/pi-coding-agent/dist/modes/interactive/components/user-message.d.ts +2 -5
  88. package/packages/pi-coding-agent/dist/modes/interactive/components/user-message.d.ts.map +1 -1
  89. package/packages/pi-coding-agent/dist/modes/interactive/components/user-message.js +4 -13
  90. package/packages/pi-coding-agent/dist/modes/interactive/components/user-message.js.map +1 -1
  91. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts +11 -0
  92. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
  93. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +49 -13
  94. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
  95. package/packages/pi-coding-agent/dist/modes/interactive/controllers/extension-ui-controller.d.ts.map +1 -1
  96. package/packages/pi-coding-agent/dist/modes/interactive/controllers/extension-ui-controller.js +1 -0
  97. package/packages/pi-coding-agent/dist/modes/interactive/controllers/extension-ui-controller.js.map +1 -1
  98. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.d.ts.map +1 -1
  99. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js +2 -0
  100. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js.map +1 -1
  101. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts +3 -0
  102. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts.map +1 -1
  103. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.js.map +1 -1
  104. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +27 -0
  105. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  106. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +251 -39
  107. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  108. package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.js +2 -2
  109. package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.js.map +1 -1
  110. package/packages/pi-coding-agent/dist/utils/terminal-screen.d.ts +10 -0
  111. package/packages/pi-coding-agent/dist/utils/terminal-screen.d.ts.map +1 -0
  112. package/packages/pi-coding-agent/dist/utils/terminal-screen.js +67 -0
  113. package/packages/pi-coding-agent/dist/utils/terminal-screen.js.map +1 -0
  114. package/packages/pi-coding-agent/dist/utils/terminal-serializer.d.ts +7 -0
  115. package/packages/pi-coding-agent/dist/utils/terminal-serializer.d.ts.map +1 -0
  116. package/packages/pi-coding-agent/dist/utils/terminal-serializer.js +67 -0
  117. package/packages/pi-coding-agent/dist/utils/terminal-serializer.js.map +1 -0
  118. package/packages/pi-coding-agent/package.json +9 -4
  119. package/packages/pi-coding-agent/src/core/agent-session.clear-queue.test.ts +50 -0
  120. package/packages/pi-coding-agent/src/core/agent-session.ts +50 -4
  121. package/packages/pi-coding-agent/src/core/extensions/types.ts +1 -1
  122. package/packages/pi-coding-agent/src/core/keybindings.ts +4 -1
  123. package/packages/pi-coding-agent/src/core/pty-executor.ts +229 -0
  124. package/packages/pi-coding-agent/src/core/sdk.ts +16 -3
  125. package/packages/pi-coding-agent/src/core/settings-manager.ts +27 -0
  126. package/packages/pi-coding-agent/src/core/tool-approval.ts +2 -2
  127. package/packages/pi-coding-agent/src/core/tools/index.ts +35 -2
  128. package/packages/pi-coding-agent/src/core/tools/pty.ts +354 -0
  129. package/packages/pi-coding-agent/src/modes/interactive/components/assistant-message.ts +37 -24
  130. package/packages/pi-coding-agent/src/modes/interactive/components/bash-execution.ts +22 -70
  131. package/packages/pi-coding-agent/src/modes/interactive/components/branch-summary-message.ts +1 -3
  132. package/packages/pi-coding-agent/src/modes/interactive/components/compaction-summary-message.ts +1 -3
  133. package/packages/pi-coding-agent/src/modes/interactive/components/embedded-terminal.ts +224 -0
  134. package/packages/pi-coding-agent/src/modes/interactive/components/settings-selector.ts +45 -0
  135. package/packages/pi-coding-agent/src/modes/interactive/components/skill-invocation-message.ts +2 -3
  136. package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +104 -81
  137. package/packages/pi-coding-agent/src/modes/interactive/components/user-message.ts +5 -19
  138. package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +55 -13
  139. package/packages/pi-coding-agent/src/modes/interactive/controllers/extension-ui-controller.ts +1 -0
  140. package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.ts +2 -0
  141. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode-state.ts +3 -0
  142. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +296 -48
  143. package/packages/pi-coding-agent/src/modes/interactive/theme/themes.ts +2 -2
  144. package/packages/pi-coding-agent/src/utils/terminal-screen.ts +77 -0
  145. package/packages/pi-coding-agent/src/utils/terminal-serializer.ts +72 -0
  146. package/packages/pi-tui/dist/components/__tests__/editor-dropped-image.test.d.ts +2 -0
  147. package/packages/pi-tui/dist/components/__tests__/editor-dropped-image.test.d.ts.map +1 -0
  148. package/packages/pi-tui/dist/components/__tests__/editor-dropped-image.test.js +105 -0
  149. package/packages/pi-tui/dist/components/__tests__/editor-dropped-image.test.js.map +1 -0
  150. package/packages/pi-tui/dist/components/editor.d.ts +4 -0
  151. package/packages/pi-tui/dist/components/editor.d.ts.map +1 -1
  152. package/packages/pi-tui/dist/components/editor.js +57 -3
  153. package/packages/pi-tui/dist/components/editor.js.map +1 -1
  154. package/packages/pi-tui/dist/components/loader.d.ts +26 -6
  155. package/packages/pi-tui/dist/components/loader.d.ts.map +1 -1
  156. package/packages/pi-tui/dist/components/loader.js +178 -18
  157. package/packages/pi-tui/dist/components/loader.js.map +1 -1
  158. package/packages/pi-tui/src/components/editor.ts +65 -3
  159. package/packages/pi-tui/src/components/loader.ts +196 -19
  160. package/pkg/dist/modes/interactive/theme/themes.js +2 -2
  161. package/pkg/dist/modes/interactive/theme/themes.js.map +1 -1
  162. package/pkg/package.json +1 -1
  163. package/src/resources/extensions/async-jobs/async-bash-tool.ts +13 -0
  164. package/src/resources/extensions/async-jobs/await-tool.ts +13 -0
  165. package/src/resources/extensions/async-jobs/cancel-job-tool.ts +8 -0
  166. package/src/resources/extensions/cache-timer/index.ts +102 -96
  167. package/src/resources/extensions/codex-rotate/IMPLEMENTATION.md +18 -13
  168. package/src/resources/extensions/codex-rotate/README.md +9 -3
  169. package/src/resources/extensions/codex-rotate/commands.ts +335 -329
  170. package/src/resources/extensions/codex-rotate/index.ts +85 -75
  171. package/src/resources/extensions/memory/auto-extract.ts +330 -204
  172. package/src/resources/extensions/memory/dream.ts +88 -21
  173. package/src/resources/extensions/memory/tests/auto-extract.test.ts +200 -144
  174. package/src/resources/extensions/shared/rtk.js +112 -0
  175. package/src/resources/extensions/subagent/index.ts +35 -6
@@ -5,23 +5,16 @@ import { allTools } from "../../../core/tools/index.js";
5
5
  import { DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES, formatSize } from "../../../core/tools/truncate.js";
6
6
  import { convertToPng } from "../../../utils/image-convert.js";
7
7
  import { sanitizeBinaryOutput } from "../../../utils/shell.js";
8
+ import { renderTerminalText } from "../../../utils/terminal-serializer.js";
8
9
  import { getLanguageFromPath, highlightCode, theme } from "../theme/theme.js";
9
10
  import { editorLink } from "../utils/editor-link.js";
10
11
  import { shortenPath } from "../utils/shorten-path.js";
11
12
  import { renderDiff } from "./diff.js";
12
- import { keyHint } from "./keybinding-hints.js";
13
13
  import { truncateToVisualLines } from "./visual-truncate.js";
14
14
  // Preview line limit for bash when not expanded
15
15
  const BASH_PREVIEW_LINES = 5;
16
- // Flash interval for RTK badge animation (ms)
17
- const RTK_FLASH_INTERVAL_MS = 400;
18
16
  // Flash interval for tool status spinner (ms)
19
17
  const SPINNER_INTERVAL_MS = 150;
20
- /** Returns true when RTK is active in this process. */
21
- function isRtkEnabled() {
22
- const v = (process.env["GSD_RTK_DISABLED"] ?? "").trim().toLowerCase();
23
- return v !== "1" && v !== "true" && v !== "yes";
24
- }
25
18
  // Spinner animation frames
26
19
  const SPINNER_FRAMES = ["◯", "◔", "◑", "◕", "●"];
27
20
  let spinnerFrame = 0;
@@ -63,9 +56,6 @@ export class ToolExecutionComponent extends Container {
63
56
  this.convertedImages = new Map();
64
57
  // When true, this component intentionally renders no lines
65
58
  this.hideComponent = false;
66
- // RTK badge flash state
67
- this.rtkFlashOn = true;
68
- this.rtkFlashTimer = null;
69
59
  // Tool status spinner state
70
60
  this.spinnerTimer = null;
71
61
  this.spinnerFrame = 0;
@@ -78,8 +68,8 @@ export class ToolExecutionComponent extends Container {
78
68
  this.ui = ui;
79
69
  this.cwd = cwd;
80
70
  // Always create both - contentBox for custom tools/bash, contentText for other built-ins
81
- this.contentBox = new Box(1, 1, (text) => theme.bg("toolPendingBg", text));
82
- this.contentText = new Text("", 1, 1, (text) => theme.bg("toolPendingBg", text));
71
+ this.contentBox = new Box(1, 0, (text) => theme.bg("toolPendingBg", text));
72
+ this.contentText = new Text("", 1, 0, (text) => theme.bg("toolPendingBg", text));
83
73
  // Use contentBox for bash (visual truncation) or custom tools with custom renderers
84
74
  // Use contentText for built-in tools (including overrides without custom renderers)
85
75
  if (toolName === "bash" || (toolDefinition && !this.shouldUseBuiltInRenderer())) {
@@ -230,12 +220,6 @@ export class ToolExecutionComponent extends Container {
230
220
  updateResult(result, isPartial = false) {
231
221
  this.result = result;
232
222
  this.isPartial = isPartial;
233
- // Stop RTK flash when result arrives — settle to dim
234
- if (!isPartial && this.rtkFlashTimer) {
235
- clearInterval(this.rtkFlashTimer);
236
- this.rtkFlashTimer = null;
237
- this.rtkFlashOn = false;
238
- }
239
223
  if (this.toolName === "write" && !isPartial) {
240
224
  const rawPath = str(this.args?.file_path ?? this.args?.path);
241
225
  const fileContent = str(this.args?.content);
@@ -298,10 +282,6 @@ export class ToolExecutionComponent extends Container {
298
282
  this.updateDisplay();
299
283
  }
300
284
  dispose() {
301
- if (this.rtkFlashTimer) {
302
- clearInterval(this.rtkFlashTimer);
303
- this.rtkFlashTimer = null;
304
- }
305
285
  if (this.spinnerTimer) {
306
286
  clearInterval(this.spinnerTimer);
307
287
  this.spinnerTimer = null;
@@ -315,7 +295,7 @@ export class ToolExecutionComponent extends Container {
315
295
  if (this.hideComponent) {
316
296
  return [];
317
297
  }
318
- return super.render(width);
298
+ return [...super.render(width), ""];
319
299
  }
320
300
  updateDisplay() {
321
301
  // Status indicator with circle
@@ -373,7 +353,7 @@ export class ToolExecutionComponent extends Container {
373
353
  // Render call component
374
354
  if (this.toolDefinition.renderCall) {
375
355
  try {
376
- const callComponent = this.toolDefinition.renderCall(this.args, theme);
356
+ const callComponent = this.toolDefinition.renderCall(this.args, theme, { statusIndicator });
377
357
  if (callComponent !== undefined) {
378
358
  this.contentBox.addChild(callComponent);
379
359
  customRendererHasContent = true;
@@ -396,8 +376,9 @@ export class ToolExecutionComponent extends Container {
396
376
  if (this.shouldHideCollapsedPreview()) {
397
377
  const output = this.getTextOutput();
398
378
  const hasDetails = output.trim().length > 0 || this.imageComponents.length > 0 || this.result.details !== undefined;
399
- if (hasDetails) {
400
- this.contentBox.addChild(new Text(`\n${this.collapsedExpandHint()}`, 0, 0));
379
+ const collapsedHint = this.collapsedHintWithPrefix("\n");
380
+ if (hasDetails && collapsedHint) {
381
+ this.contentBox.addChild(new Text(collapsedHint, 0, 0));
401
382
  customRendererHasContent = true;
402
383
  }
403
384
  }
@@ -423,7 +404,10 @@ export class ToolExecutionComponent extends Container {
423
404
  const output = this.getTextOutput();
424
405
  if (output) {
425
406
  if (this.shouldHideCollapsedPreview()) {
426
- this.contentBox.addChild(new Text(`\n${this.collapsedExpandHint()}`, 0, 0));
407
+ const collapsedHint = this.collapsedHintWithPrefix("\n");
408
+ if (collapsedHint) {
409
+ this.contentBox.addChild(new Text(collapsedHint, 0, 0));
410
+ }
427
411
  }
428
412
  else {
429
413
  this.contentBox.addChild(new Text(theme.fg("toolOutput", output), 0, 0));
@@ -483,57 +467,50 @@ export class ToolExecutionComponent extends Container {
483
467
  renderBashContent(statusIndicator) {
484
468
  const command = str(this.args?.command);
485
469
  const timeout = this.args?.timeout;
486
- const rtkActive = isRtkEnabled();
487
- // Start RTK flash timer on first partial render
488
- if (rtkActive && this.isPartial && !this.result && !this.rtkFlashTimer) {
489
- this.rtkFlashTimer = setInterval(() => {
490
- this.rtkFlashOn = !this.rtkFlashOn;
491
- this.updateDisplay();
492
- this.ui.requestRender();
493
- }, RTK_FLASH_INTERVAL_MS);
494
- }
495
- // Header with status indicator
470
+ const body = new Container();
471
+ this.contentBox.addChild(body);
496
472
  const timeoutSuffix = timeout ? theme.fg("muted", ` (timeout ${timeout}s)`) : "";
497
- const commandDisplay = command === null ? theme.fg("error", "[invalid arg]") : command ? command : theme.fg("toolOutput", "...");
473
+ const commandDisplay = command === null
474
+ ? theme.fg("error", "[invalid arg]")
475
+ : command
476
+ ? theme.fg("toolOutput", command)
477
+ : theme.fg("toolOutput", "...");
498
478
  const sandboxBadge = this.result?.details?.sandboxed ? ` ${theme.fg("success", "[sandboxed]")}` : "";
499
- const rtkBadge = rtkActive
500
- ? " " + (this.rtkFlashOn ? theme.fg("accent", "$ RTK") : theme.fg("dim", "$ RTK"))
501
- : "";
502
- this.contentBox.addChild(new Text(`${statusIndicator} ${theme.fg("toolTitle", theme.bold(`$ ${commandDisplay}`))}${timeoutSuffix}${sandboxBadge}${rtkBadge}`, 0, 0));
479
+ const toolLabel = theme.fg("toolTitle", theme.bold("bash"));
480
+ const shellPrompt = theme.fg("muted", "$ ");
481
+ body.addChild(new Text(`${statusIndicator} ${toolLabel} ${shellPrompt}${commandDisplay}${timeoutSuffix}${sandboxBadge}`, 0, 0));
503
482
  if (this.result) {
504
483
  const output = this.getTextOutput().trim();
505
484
  if (output) {
506
- // Style each line for the output
507
485
  const styledOutput = output
508
486
  .split("\n")
509
487
  .map((line) => theme.fg("toolOutput", line))
510
488
  .join("\n");
511
489
  if (this.expanded) {
512
- // Show all lines when expanded
513
- this.contentBox.addChild(new Text(`\n${styledOutput}`, 0, 0));
490
+ body.addChild(new Text(`\n${styledOutput}`, 0, 0));
514
491
  }
515
492
  else if (this.renderMode === "minimal") {
516
- this.contentBox.addChild(new Text(`\n${this.collapsedExpandHint()}`, 0, 0));
493
+ const collapsedHint = this.collapsedHintWithPrefix("\n");
494
+ if (collapsedHint) {
495
+ body.addChild(new Text(collapsedHint, 1, 0));
496
+ }
517
497
  }
518
498
  else {
519
- // Use visual line truncation when collapsed with width-aware caching
520
499
  let cachedWidth;
521
500
  let cachedLines;
522
501
  let cachedSkipped;
523
- this.contentBox.addChild({
502
+ body.addChild({
524
503
  render: (width) => {
525
504
  if (cachedLines === undefined || cachedWidth !== width) {
526
- const result = truncateToVisualLines(styledOutput, BASH_PREVIEW_LINES, width);
527
- cachedLines = result.visualLines;
505
+ const result = truncateToVisualLines(styledOutput, BASH_PREVIEW_LINES, Math.max(1, width - 1));
506
+ cachedLines = result.visualLines.map((line) => ` ${line}`);
528
507
  cachedSkipped = result.skippedCount;
529
508
  cachedWidth = width;
530
509
  }
531
510
  if (cachedSkipped && cachedSkipped > 0) {
532
- const hint = theme.fg("muted", `... (${cachedSkipped} earlier lines,`) +
533
- ` ${keyHint("expandTools", "to expand")})`;
534
- return ["", truncateToWidth(hint, width, "..."), ...cachedLines];
511
+ const hint = theme.fg("muted", `... (${cachedSkipped} earlier lines)`);
512
+ return ["", truncateToWidth(` ${hint}`, width, "..."), ...cachedLines];
535
513
  }
536
- // Add blank line for spacing (matches expanded case)
537
514
  return ["", ...cachedLines];
538
515
  },
539
516
  invalidate: () => {
@@ -544,7 +521,6 @@ export class ToolExecutionComponent extends Container {
544
521
  });
545
522
  }
546
523
  }
547
- // Truncation warnings
548
524
  const truncation = this.result.details?.truncation;
549
525
  const fullOutputPath = this.result.details?.fullOutputPath;
550
526
  if (truncation?.truncated || fullOutputPath) {
@@ -560,7 +536,7 @@ export class ToolExecutionComponent extends Container {
560
536
  warnings.push(`Truncated: ${truncation.outputLines} lines shown (${formatSize(truncation.maxBytes ?? DEFAULT_MAX_BYTES)} limit)`);
561
537
  }
562
538
  }
563
- this.contentBox.addChild(new Text(`\n${theme.fg("warning", `[${warnings.join(". ")}]`)}`, 0, 0));
539
+ body.addChild(new Text(`\n${theme.fg("warning", `[${warnings.join(". ")}]`)}`, 0, 0));
564
540
  }
565
541
  }
566
542
  }
@@ -572,9 +548,16 @@ export class ToolExecutionComponent extends Container {
572
548
  let output = textBlocks
573
549
  .map((c) => {
574
550
  // Use sanitizeBinaryOutput to handle binary data that crashes string-width
575
- return sanitizeBinaryOutput(stripAnsi(c.text || "")).replace(/\r/g, "");
551
+ return sanitizeBinaryOutput(c.text || "");
576
552
  })
577
553
  .join("\n");
554
+ if (this.toolName === "bash") {
555
+ output = renderTerminalText(output);
556
+ }
557
+ else {
558
+ output = output.replace(/\r/g, "");
559
+ output = stripAnsi(output);
560
+ }
578
561
  const caps = getCapabilities();
579
562
  if (imageBlocks.length > 0 && (!caps.images || !this.showImages)) {
580
563
  const imageIndicators = imageBlocks
@@ -594,8 +577,12 @@ export class ToolExecutionComponent extends Container {
594
577
  }
595
578
  return !this.expanded && this.renderMode === "minimal" && !this.result?.isError;
596
579
  }
597
- collapsedExpandHint(label = keyHint("expandTools", "to expand")) {
598
- return theme.fg("muted", `(${label})`);
580
+ collapsedExpandHint(_label) {
581
+ return ""; // hint is shown in editor bottom border instead
582
+ }
583
+ collapsedHintWithPrefix(prefix = "\n\n") {
584
+ const hint = this.collapsedExpandHint();
585
+ return hint ? `${prefix}${hint}` : "";
599
586
  }
600
587
  collapsedFirstLine(output) {
601
588
  const first = output.split("\n").map((line) => line.trim()).find(Boolean);
@@ -631,7 +618,7 @@ export class ToolExecutionComponent extends Container {
631
618
  const lines = lang ? highlightCode(replaceTabs(output), lang) : output.split("\n");
632
619
  if (hideCollapsedPreview) {
633
620
  if (output.trim()) {
634
- text += `\n\n${this.collapsedExpandHint()}`;
621
+ text += this.collapsedHintWithPrefix();
635
622
  }
636
623
  }
637
624
  else {
@@ -644,7 +631,7 @@ export class ToolExecutionComponent extends Container {
644
631
  .map((line) => (lang ? replaceTabs(line) : theme.fg("toolOutput", replaceTabs(line))))
645
632
  .join("\n");
646
633
  if (remaining > 0) {
647
- text += `${theme.fg("muted", `\n... (${remaining} more lines,`)} ${keyHint("expandTools", "to expand")})`;
634
+ text += theme.fg("muted", `\n... (${remaining} more lines)`);
648
635
  }
649
636
  }
650
637
  const truncation = this.result.details?.truncation;
@@ -705,7 +692,7 @@ export class ToolExecutionComponent extends Container {
705
692
  this.writeHighlightCache = undefined;
706
693
  }
707
694
  if (hideCollapsedPreview) {
708
- text += `\n\n${this.collapsedExpandHint()}`;
695
+ text += this.collapsedHintWithPrefix();
709
696
  }
710
697
  else {
711
698
  const totalLines = lines.length;
@@ -716,9 +703,7 @@ export class ToolExecutionComponent extends Container {
716
703
  "\n\n" +
717
704
  displayLines.map((line) => (lang ? line : theme.fg("toolOutput", replaceTabs(line)))).join("\n");
718
705
  if (remaining > 0) {
719
- text +=
720
- theme.fg("muted", `\n... (${remaining} more lines, ${totalLines} total,`) +
721
- ` ${keyHint("expandTools", "to expand")})`;
706
+ text += theme.fg("muted", `\n... (${remaining} more lines, ${totalLines} total)`);
722
707
  }
723
708
  }
724
709
  }
@@ -763,7 +748,7 @@ export class ToolExecutionComponent extends Container {
763
748
  // This takes priority over editDiffPreview which may have a stale error
764
749
  // due to race condition (async preview computed after file was modified)
765
750
  text += hideCollapsedPreview
766
- ? `\n\n${this.collapsedExpandHint()}`
751
+ ? this.collapsedHintWithPrefix()
767
752
  : `\n\n${renderDiff(this.result.details.diff, { filePath: rawPath ?? undefined })}`;
768
753
  }
769
754
  else if (this.editDiffPreview) {
@@ -773,7 +758,7 @@ export class ToolExecutionComponent extends Container {
773
758
  }
774
759
  else if (this.editDiffPreview.diff) {
775
760
  text += hideCollapsedPreview
776
- ? `\n\n${this.collapsedExpandHint()}`
761
+ ? this.collapsedHintWithPrefix()
777
762
  : `\n\n${renderDiff(this.editDiffPreview.diff, { filePath: rawPath ?? undefined })}`;
778
763
  }
779
764
  }
@@ -794,7 +779,7 @@ export class ToolExecutionComponent extends Container {
794
779
  const output = this.getTextOutput().trim();
795
780
  if (output) {
796
781
  if (hideCollapsedPreview) {
797
- text += `\n\n${this.collapsedExpandHint()}`;
782
+ text += this.collapsedHintWithPrefix();
798
783
  }
799
784
  else {
800
785
  const lines = output.split("\n");
@@ -803,7 +788,7 @@ export class ToolExecutionComponent extends Container {
803
788
  const remaining = lines.length - maxLines;
804
789
  text += `\n\n${displayLines.map((line) => theme.fg("toolOutput", line)).join("\n")}`;
805
790
  if (remaining > 0) {
806
- text += `${theme.fg("muted", `\n... (${remaining} more lines,`)} ${keyHint("expandTools", "to expand")})`;
791
+ text += theme.fg("muted", `\n... (${remaining} more lines)`);
807
792
  }
808
793
  }
809
794
  }
@@ -846,7 +831,7 @@ export class ToolExecutionComponent extends Container {
846
831
  const output = this.getTextOutput().trim();
847
832
  if (output) {
848
833
  if (hideCollapsedPreview) {
849
- text += `\n\n${this.collapsedExpandHint()}`;
834
+ text += this.collapsedHintWithPrefix();
850
835
  }
851
836
  else {
852
837
  const lines = output.split("\n");
@@ -855,7 +840,7 @@ export class ToolExecutionComponent extends Container {
855
840
  const remaining = lines.length - maxLines;
856
841
  text += `\n\n${displayLines.map((line) => theme.fg("toolOutput", line)).join("\n")}`;
857
842
  if (remaining > 0) {
858
- text += `${theme.fg("muted", `\n... (${remaining} more lines,`)} ${keyHint("expandTools", "to expand")})`;
843
+ text += theme.fg("muted", `\n... (${remaining} more lines)`);
859
844
  }
860
845
  }
861
846
  }
@@ -902,7 +887,7 @@ export class ToolExecutionComponent extends Container {
902
887
  const output = this.getTextOutput().trim();
903
888
  if (output) {
904
889
  if (hideCollapsedPreview) {
905
- text += `\n\n${this.collapsedExpandHint()}`;
890
+ text += this.collapsedHintWithPrefix();
906
891
  }
907
892
  else {
908
893
  const lines = output.split("\n");
@@ -911,7 +896,7 @@ export class ToolExecutionComponent extends Container {
911
896
  const remaining = lines.length - maxLines;
912
897
  text += `\n\n${displayLines.map((line) => theme.fg("toolOutput", line)).join("\n")}`;
913
898
  if (remaining > 0) {
914
- text += `${theme.fg("muted", `\n... (${remaining} more lines,`)} ${keyHint("expandTools", "to expand")})`;
899
+ text += theme.fg("muted", `\n... (${remaining} more lines)`);
915
900
  }
916
901
  }
917
902
  }
@@ -943,7 +928,7 @@ export class ToolExecutionComponent extends Container {
943
928
  const output = this.getTextOutput().trim();
944
929
  if (output) {
945
930
  if (hideCollapsedPreview) {
946
- text += `\n\n${this.collapsedExpandHint()}`;
931
+ text += this.collapsedHintWithPrefix();
947
932
  }
948
933
  else {
949
934
  const lines = output.split("\n");
@@ -952,17 +937,61 @@ export class ToolExecutionComponent extends Container {
952
937
  const remaining = lines.length - maxLines;
953
938
  text += `\n\n${displayLines.map((line) => theme.fg("toolOutput", line)).join("\n")}`;
954
939
  if (remaining > 0) {
955
- text += `${theme.fg("muted", `\n... (${remaining} more lines,`)} ${keyHint("expandTools", "to expand")})`;
940
+ text += theme.fg("muted", `\n... (${remaining} more lines)`);
956
941
  }
957
942
  }
958
943
  }
959
944
  }
960
945
  }
946
+ else if (this.toolName === "lsp") {
947
+ const action = this.args?.action;
948
+ const file = this.args?.file;
949
+ const line = this.args?.line;
950
+ const symbol = this.args?.symbol;
951
+ const query = this.args?.query;
952
+ const newName = this.args?.new_name;
953
+ text = `${statusIndicator} ${theme.fg("toolTitle", theme.bold("lsp"))}`;
954
+ if (action)
955
+ text += ` ${theme.fg("accent", action)}`;
956
+ if (file) {
957
+ const shortFile = shortenPath(file);
958
+ let styledFile = theme.fg("muted", shortFile);
959
+ if (file && shortFile) {
960
+ styledFile = editorLink(file, styledFile, { cwd: this.cwd, line, scheme: this.editorScheme });
961
+ }
962
+ text += ` ${styledFile}`;
963
+ if (line !== undefined)
964
+ text += theme.fg("warning", `:${line}`);
965
+ }
966
+ if (symbol)
967
+ text += ` ${theme.fg("toolOutput", symbol)}`;
968
+ if (query)
969
+ text += ` ${theme.fg("muted", `"${query}"`)}`;
970
+ if (newName)
971
+ text += ` → ${theme.fg("accent", newName)}`;
972
+ if (this.result) {
973
+ const output = this.getTextOutput().trim();
974
+ if (output) {
975
+ if (hideCollapsedPreview) {
976
+ text += this.collapsedHintWithPrefix();
977
+ }
978
+ else {
979
+ const lines = output.split("\n");
980
+ const maxLines = this.expanded ? lines.length : 10;
981
+ const displayLines = lines.slice(0, maxLines);
982
+ const remaining = lines.length - maxLines;
983
+ text += `\n\n${displayLines.map((l) => theme.fg("toolOutput", l)).join("\n")}`;
984
+ if (remaining > 0)
985
+ text += theme.fg("muted", `\n... (${remaining} more lines)`);
986
+ }
987
+ }
988
+ }
989
+ }
961
990
  else {
962
991
  // Generic tool (shouldn't reach here for custom tools)
963
992
  text = `${statusIndicator} ${theme.fg("toolTitle", theme.bold(this.toolName))}`;
964
993
  const content = JSON.stringify(this.args, null, 2);
965
- text += hideCollapsedPreview ? `\n\n${this.collapsedExpandHint()}` : `\n\n${content}`;
994
+ text += hideCollapsedPreview ? this.collapsedHintWithPrefix() : `\n\n${content}`;
966
995
  const output = this.getTextOutput();
967
996
  if (output && !hideCollapsedPreview) {
968
997
  text += `\n${output}`;