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
@@ -16,6 +16,7 @@ import { allTools } from "../../../core/tools/index.js";
16
16
  import { DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES, formatSize } from "../../../core/tools/truncate.js";
17
17
  import { convertToPng } from "../../../utils/image-convert.js";
18
18
  import { sanitizeBinaryOutput } from "../../../utils/shell.js";
19
+ import { renderTerminalText } from "../../../utils/terminal-serializer.js";
19
20
  import { getLanguageFromPath, highlightCode, theme } from "../theme/theme.js";
20
21
  import { type EditorScheme, editorLink } from "../utils/editor-link.js";
21
22
  import { shortenPath } from "../utils/shorten-path.js";
@@ -25,17 +26,9 @@ import { truncateToVisualLines } from "./visual-truncate.js";
25
26
 
26
27
  // Preview line limit for bash when not expanded
27
28
  const BASH_PREVIEW_LINES = 5;
28
- // Flash interval for RTK badge animation (ms)
29
- const RTK_FLASH_INTERVAL_MS = 400;
30
29
  // Flash interval for tool status spinner (ms)
31
30
  const SPINNER_INTERVAL_MS = 150;
32
31
 
33
- /** Returns true when RTK is active in this process. */
34
- function isRtkEnabled(): boolean {
35
- const v = (process.env["GSD_RTK_DISABLED"] ?? "").trim().toLowerCase();
36
- return v !== "1" && v !== "true" && v !== "yes";
37
- }
38
-
39
32
  // Spinner animation frames
40
33
  const SPINNER_FRAMES = ["◯", "◔", "◑", "◕", "●"];
41
34
  let spinnerFrame = 0;
@@ -112,9 +105,7 @@ export class ToolExecutionComponent extends Container {
112
105
  private writeHighlightCache?: WriteHighlightCache;
113
106
  // When true, this component intentionally renders no lines
114
107
  private hideComponent = false;
115
- // RTK badge flash state
116
- private rtkFlashOn = true;
117
- private rtkFlashTimer: NodeJS.Timeout | null = null;
108
+
118
109
  // Tool status spinner state
119
110
  private spinnerTimer: NodeJS.Timeout | null = null;
120
111
  private spinnerFrame = 0;
@@ -138,8 +129,8 @@ export class ToolExecutionComponent extends Container {
138
129
  this.cwd = cwd;
139
130
 
140
131
  // Always create both - contentBox for custom tools/bash, contentText for other built-ins
141
- this.contentBox = new Box(1, 1, (text: string) => theme.bg("toolPendingBg", text));
142
- this.contentText = new Text("", 1, 1, (text: string) => theme.bg("toolPendingBg", text));
132
+ this.contentBox = new Box(1, 0, (text: string) => theme.bg("toolPendingBg", text));
133
+ this.contentText = new Text("", 1, 0, (text: string) => theme.bg("toolPendingBg", text));
143
134
 
144
135
  // Use contentBox for bash (visual truncation) or custom tools with custom renderers
145
136
  // Use contentText for built-in tools (including overrides without custom renderers)
@@ -321,12 +312,7 @@ export class ToolExecutionComponent extends Container {
321
312
  ): void {
322
313
  this.result = result;
323
314
  this.isPartial = isPartial;
324
- // Stop RTK flash when result arrives — settle to dim
325
- if (!isPartial && this.rtkFlashTimer) {
326
- clearInterval(this.rtkFlashTimer);
327
- this.rtkFlashTimer = null;
328
- this.rtkFlashOn = false;
329
- }
315
+
330
316
  if (this.toolName === "write" && !isPartial) {
331
317
  const rawPath = str(this.args?.file_path ?? this.args?.path);
332
318
  const fileContent = str(this.args?.content);
@@ -393,10 +379,6 @@ export class ToolExecutionComponent extends Container {
393
379
  }
394
380
 
395
381
  dispose(): void {
396
- if (this.rtkFlashTimer) {
397
- clearInterval(this.rtkFlashTimer);
398
- this.rtkFlashTimer = null;
399
- }
400
382
  if (this.spinnerTimer) {
401
383
  clearInterval(this.spinnerTimer);
402
384
  this.spinnerTimer = null;
@@ -412,7 +394,7 @@ export class ToolExecutionComponent extends Container {
412
394
  if (this.hideComponent) {
413
395
  return [];
414
396
  }
415
- return super.render(width);
397
+ return [...super.render(width), ""];
416
398
  }
417
399
 
418
400
  private updateDisplay(): void {
@@ -471,7 +453,7 @@ export class ToolExecutionComponent extends Container {
471
453
  // Render call component
472
454
  if (this.toolDefinition.renderCall) {
473
455
  try {
474
- const callComponent = this.toolDefinition.renderCall(this.args, theme);
456
+ const callComponent = this.toolDefinition.renderCall(this.args, theme, { statusIndicator });
475
457
  if (callComponent !== undefined) {
476
458
  this.contentBox.addChild(callComponent);
477
459
  customRendererHasContent = true;
@@ -493,8 +475,9 @@ export class ToolExecutionComponent extends Container {
493
475
  if (this.shouldHideCollapsedPreview()) {
494
476
  const output = this.getTextOutput();
495
477
  const hasDetails = output.trim().length > 0 || this.imageComponents.length > 0 || this.result.details !== undefined;
496
- if (hasDetails) {
497
- this.contentBox.addChild(new Text(`\n${this.collapsedExpandHint()}`, 0, 0));
478
+ const collapsedHint = this.collapsedHintWithPrefix("\n");
479
+ if (hasDetails && collapsedHint) {
480
+ this.contentBox.addChild(new Text(collapsedHint, 0, 0));
498
481
  customRendererHasContent = true;
499
482
  }
500
483
  } else {
@@ -521,7 +504,10 @@ export class ToolExecutionComponent extends Container {
521
504
  const output = this.getTextOutput();
522
505
  if (output) {
523
506
  if (this.shouldHideCollapsedPreview()) {
524
- this.contentBox.addChild(new Text(`\n${this.collapsedExpandHint()}`, 0, 0));
507
+ const collapsedHint = this.collapsedHintWithPrefix("\n");
508
+ if (collapsedHint) {
509
+ this.contentBox.addChild(new Text(collapsedHint, 0, 0));
510
+ }
525
511
  } else {
526
512
  this.contentBox.addChild(new Text(theme.fg("toolOutput", output), 0, 0));
527
513
  }
@@ -591,65 +577,55 @@ export class ToolExecutionComponent extends Container {
591
577
  private renderBashContent(statusIndicator: string): void {
592
578
  const command = str(this.args?.command);
593
579
  const timeout = this.args?.timeout as number | undefined;
594
- const rtkActive = isRtkEnabled();
580
+ const body = new Container();
595
581
 
596
- // Start RTK flash timer on first partial render
597
- if (rtkActive && this.isPartial && !this.result && !this.rtkFlashTimer) {
598
- this.rtkFlashTimer = setInterval(() => {
599
- this.rtkFlashOn = !this.rtkFlashOn;
600
- this.updateDisplay();
601
- this.ui.requestRender();
602
- }, RTK_FLASH_INTERVAL_MS);
603
- }
582
+ this.contentBox.addChild(body);
604
583
 
605
- // Header with status indicator
606
584
  const timeoutSuffix = timeout ? theme.fg("muted", ` (timeout ${timeout}s)`) : "";
607
585
  const commandDisplay =
608
- command === null ? theme.fg("error", "[invalid arg]") : command ? command : theme.fg("toolOutput", "...");
586
+ command === null
587
+ ? theme.fg("error", "[invalid arg]")
588
+ : command
589
+ ? theme.fg("toolOutput", command)
590
+ : theme.fg("toolOutput", "...");
609
591
  const sandboxBadge = this.result?.details?.sandboxed ? ` ${theme.fg("success", "[sandboxed]")}` : "";
610
- const rtkBadge = rtkActive
611
- ? " " + (this.rtkFlashOn ? theme.fg("accent", "$ RTK") : theme.fg("dim", "$ RTK"))
612
- : "";
613
- this.contentBox.addChild(
614
- new Text(`${statusIndicator} ${theme.fg("toolTitle", theme.bold(`$ ${commandDisplay}`))}${timeoutSuffix}${sandboxBadge}${rtkBadge}`, 0, 0),
615
- );
592
+ const toolLabel = theme.fg("toolTitle", theme.bold("bash"));
593
+ const shellPrompt = theme.fg("muted", "$ ");
594
+ body.addChild(new Text(`${statusIndicator} ${toolLabel} ${shellPrompt}${commandDisplay}${timeoutSuffix}${sandboxBadge}`, 0, 0));
616
595
 
617
596
  if (this.result) {
618
597
  const output = this.getTextOutput().trim();
619
598
 
620
599
  if (output) {
621
- // Style each line for the output
622
600
  const styledOutput = output
623
601
  .split("\n")
624
602
  .map((line) => theme.fg("toolOutput", line))
625
603
  .join("\n");
626
604
 
627
605
  if (this.expanded) {
628
- // Show all lines when expanded
629
- this.contentBox.addChild(new Text(`\n${styledOutput}`, 0, 0));
606
+ body.addChild(new Text(`\n${styledOutput}`, 0, 0));
630
607
  } else if (this.renderMode === "minimal") {
631
- this.contentBox.addChild(new Text(`\n${this.collapsedExpandHint()}`, 0, 0));
608
+ const collapsedHint = this.collapsedHintWithPrefix("\n");
609
+ if (collapsedHint) {
610
+ body.addChild(new Text(collapsedHint, 1, 0));
611
+ }
632
612
  } else {
633
- // Use visual line truncation when collapsed with width-aware caching
634
613
  let cachedWidth: number | undefined;
635
614
  let cachedLines: string[] | undefined;
636
615
  let cachedSkipped: number | undefined;
637
616
 
638
- this.contentBox.addChild({
617
+ body.addChild({
639
618
  render: (width: number) => {
640
619
  if (cachedLines === undefined || cachedWidth !== width) {
641
- const result = truncateToVisualLines(styledOutput, BASH_PREVIEW_LINES, width);
642
- cachedLines = result.visualLines;
620
+ const result = truncateToVisualLines(styledOutput, BASH_PREVIEW_LINES, Math.max(1, width - 1));
621
+ cachedLines = result.visualLines.map((line) => ` ${line}`);
643
622
  cachedSkipped = result.skippedCount;
644
623
  cachedWidth = width;
645
624
  }
646
625
  if (cachedSkipped && cachedSkipped > 0) {
647
- const hint =
648
- theme.fg("muted", `... (${cachedSkipped} earlier lines,`) +
649
- ` ${keyHint("expandTools", "to expand")})`;
650
- return ["", truncateToWidth(hint, width, "..."), ...cachedLines];
626
+ const hint = theme.fg("muted", `... (${cachedSkipped} earlier lines)`);
627
+ return ["", truncateToWidth(` ${hint}`, width, "..."), ...cachedLines];
651
628
  }
652
- // Add blank line for spacing (matches expanded case)
653
629
  return ["", ...cachedLines];
654
630
  },
655
631
  invalidate: () => {
@@ -661,7 +637,6 @@ export class ToolExecutionComponent extends Container {
661
637
  }
662
638
  }
663
639
 
664
- // Truncation warnings
665
640
  const truncation = this.result.details?.truncation;
666
641
  const fullOutputPath = this.result.details?.fullOutputPath;
667
642
  if (truncation?.truncated || fullOutputPath) {
@@ -678,7 +653,7 @@ export class ToolExecutionComponent extends Container {
678
653
  );
679
654
  }
680
655
  }
681
- this.contentBox.addChild(new Text(`\n${theme.fg("warning", `[${warnings.join(". ")}]`)}`, 0, 0));
656
+ body.addChild(new Text(`\n${theme.fg("warning", `[${warnings.join(". ")}]`)}`, 0, 0));
682
657
  }
683
658
  }
684
659
  }
@@ -689,13 +664,20 @@ export class ToolExecutionComponent extends Container {
689
664
  const textBlocks = this.result.content?.filter((c: any) => c.type === "text") || [];
690
665
  const imageBlocks = this.result.content?.filter((c: any) => c.type === "image") || [];
691
666
 
692
- let output = textBlocks
667
+ let output = textBlocks
693
668
  .map((c: any) => {
694
669
  // Use sanitizeBinaryOutput to handle binary data that crashes string-width
695
- return sanitizeBinaryOutput(stripAnsi(c.text || "")).replace(/\r/g, "");
670
+ return sanitizeBinaryOutput(c.text || "");
696
671
  })
697
672
  .join("\n");
698
673
 
674
+ if (this.toolName === "bash") {
675
+ output = renderTerminalText(output);
676
+ } else {
677
+ output = output.replace(/\r/g, "");
678
+ output = stripAnsi(output);
679
+ }
680
+
699
681
  const caps = getCapabilities();
700
682
  if (imageBlocks.length > 0 && (!caps.images || !this.showImages)) {
701
683
  const imageIndicators = imageBlocks
@@ -718,8 +700,13 @@ export class ToolExecutionComponent extends Container {
718
700
  return !this.expanded && this.renderMode === "minimal" && !this.result?.isError;
719
701
  }
720
702
 
721
- private collapsedExpandHint(label = keyHint("expandTools", "to expand")): string {
722
- return theme.fg("muted", `(${label})`);
703
+ private collapsedExpandHint(_label?: string): string {
704
+ return ""; // hint is shown in editor bottom border instead
705
+ }
706
+
707
+ private collapsedHintWithPrefix(prefix = "\n\n"): string {
708
+ const hint = this.collapsedExpandHint();
709
+ return hint ? `${prefix}${hint}` : "";
723
710
  }
724
711
 
725
712
  private collapsedFirstLine(output: string): string | undefined {
@@ -763,7 +750,7 @@ export class ToolExecutionComponent extends Container {
763
750
 
764
751
  if (hideCollapsedPreview) {
765
752
  if (output.trim()) {
766
- text += `\n\n${this.collapsedExpandHint()}`;
753
+ text += this.collapsedHintWithPrefix();
767
754
  }
768
755
  } else {
769
756
  const maxLines = this.expanded ? lines.length : 10;
@@ -776,7 +763,7 @@ export class ToolExecutionComponent extends Container {
776
763
  .map((line: string) => (lang ? replaceTabs(line) : theme.fg("toolOutput", replaceTabs(line))))
777
764
  .join("\n");
778
765
  if (remaining > 0) {
779
- text += `${theme.fg("muted", `\n... (${remaining} more lines,`)} ${keyHint("expandTools", "to expand")})`;
766
+ text += theme.fg("muted", `\n... (${remaining} more lines)`);
780
767
  }
781
768
  }
782
769
 
@@ -845,7 +832,7 @@ export class ToolExecutionComponent extends Container {
845
832
  }
846
833
 
847
834
  if (hideCollapsedPreview) {
848
- text += `\n\n${this.collapsedExpandHint()}`;
835
+ text += this.collapsedHintWithPrefix();
849
836
  } else {
850
837
  const totalLines = lines.length;
851
838
  const maxLines = this.expanded ? lines.length : 10;
@@ -856,9 +843,7 @@ export class ToolExecutionComponent extends Container {
856
843
  "\n\n" +
857
844
  displayLines.map((line: string) => (lang ? line : theme.fg("toolOutput", replaceTabs(line)))).join("\n");
858
845
  if (remaining > 0) {
859
- text +=
860
- theme.fg("muted", `\n... (${remaining} more lines, ${totalLines} total,`) +
861
- ` ${keyHint("expandTools", "to expand")})`;
846
+ text += theme.fg("muted", `\n... (${remaining} more lines, ${totalLines} total)`);
862
847
  }
863
848
  }
864
849
  }
@@ -907,7 +892,7 @@ export class ToolExecutionComponent extends Container {
907
892
  // This takes priority over editDiffPreview which may have a stale error
908
893
  // due to race condition (async preview computed after file was modified)
909
894
  text += hideCollapsedPreview
910
- ? `\n\n${this.collapsedExpandHint()}`
895
+ ? this.collapsedHintWithPrefix()
911
896
  : `\n\n${renderDiff(this.result.details.diff, { filePath: rawPath ?? undefined })}`;
912
897
  } else if (this.editDiffPreview) {
913
898
  // Use cached diff preview (before tool executes)
@@ -915,7 +900,7 @@ export class ToolExecutionComponent extends Container {
915
900
  text += `\n\n${theme.fg("error", this.editDiffPreview.error)}`;
916
901
  } else if (this.editDiffPreview.diff) {
917
902
  text += hideCollapsedPreview
918
- ? `\n\n${this.collapsedExpandHint()}`
903
+ ? this.collapsedHintWithPrefix()
919
904
  : `\n\n${renderDiff(this.editDiffPreview.diff, { filePath: rawPath ?? undefined })}`;
920
905
  }
921
906
  }
@@ -937,7 +922,7 @@ export class ToolExecutionComponent extends Container {
937
922
  const output = this.getTextOutput().trim();
938
923
  if (output) {
939
924
  if (hideCollapsedPreview) {
940
- text += `\n\n${this.collapsedExpandHint()}`;
925
+ text += this.collapsedHintWithPrefix();
941
926
  } else {
942
927
  const lines = output.split("\n");
943
928
  const maxLines = this.expanded ? lines.length : 20;
@@ -946,7 +931,7 @@ export class ToolExecutionComponent extends Container {
946
931
 
947
932
  text += `\n\n${displayLines.map((line: string) => theme.fg("toolOutput", line)).join("\n")}`;
948
933
  if (remaining > 0) {
949
- text += `${theme.fg("muted", `\n... (${remaining} more lines,`)} ${keyHint("expandTools", "to expand")})`;
934
+ text += theme.fg("muted", `\n... (${remaining} more lines)`);
950
935
  }
951
936
  }
952
937
  }
@@ -990,7 +975,7 @@ export class ToolExecutionComponent extends Container {
990
975
  const output = this.getTextOutput().trim();
991
976
  if (output) {
992
977
  if (hideCollapsedPreview) {
993
- text += `\n\n${this.collapsedExpandHint()}`;
978
+ text += this.collapsedHintWithPrefix();
994
979
  } else {
995
980
  const lines = output.split("\n");
996
981
  const maxLines = this.expanded ? lines.length : 20;
@@ -999,7 +984,7 @@ export class ToolExecutionComponent extends Container {
999
984
 
1000
985
  text += `\n\n${displayLines.map((line: string) => theme.fg("toolOutput", line)).join("\n")}`;
1001
986
  if (remaining > 0) {
1002
- text += `${theme.fg("muted", `\n... (${remaining} more lines,`)} ${keyHint("expandTools", "to expand")})`;
987
+ text += theme.fg("muted", `\n... (${remaining} more lines)`);
1003
988
  }
1004
989
  }
1005
990
  }
@@ -1047,7 +1032,7 @@ export class ToolExecutionComponent extends Container {
1047
1032
  const output = this.getTextOutput().trim();
1048
1033
  if (output) {
1049
1034
  if (hideCollapsedPreview) {
1050
- text += `\n\n${this.collapsedExpandHint()}`;
1035
+ text += this.collapsedHintWithPrefix();
1051
1036
  } else {
1052
1037
  const lines = output.split("\n");
1053
1038
  const maxLines = this.expanded ? lines.length : 15;
@@ -1056,7 +1041,7 @@ export class ToolExecutionComponent extends Container {
1056
1041
 
1057
1042
  text += `\n\n${displayLines.map((line: string) => theme.fg("toolOutput", line)).join("\n")}`;
1058
1043
  if (remaining > 0) {
1059
- text += `${theme.fg("muted", `\n... (${remaining} more lines,`)} ${keyHint("expandTools", "to expand")})`;
1044
+ text += theme.fg("muted", `\n... (${remaining} more lines)`);
1060
1045
  }
1061
1046
  }
1062
1047
  }
@@ -1088,7 +1073,7 @@ export class ToolExecutionComponent extends Container {
1088
1073
  const output = this.getTextOutput().trim();
1089
1074
  if (output) {
1090
1075
  if (hideCollapsedPreview) {
1091
- text += `\n\n${this.collapsedExpandHint()}`;
1076
+ text += this.collapsedHintWithPrefix();
1092
1077
  } else {
1093
1078
  const lines = output.split("\n");
1094
1079
  const maxLines = this.expanded ? lines.length : 10;
@@ -1097,17 +1082,55 @@ export class ToolExecutionComponent extends Container {
1097
1082
 
1098
1083
  text += `\n\n${displayLines.map((line: string) => theme.fg("toolOutput", line)).join("\n")}`;
1099
1084
  if (remaining > 0) {
1100
- text += `${theme.fg("muted", `\n... (${remaining} more lines,`)} ${keyHint("expandTools", "to expand")})`;
1085
+ text += theme.fg("muted", `\n... (${remaining} more lines)`);
1101
1086
  }
1102
1087
  }
1103
1088
  }
1104
1089
  }
1090
+ } else if (this.toolName === "lsp") {
1091
+ const action = this.args?.action as string | undefined;
1092
+ const file = this.args?.file as string | undefined;
1093
+ const line = this.args?.line as number | undefined;
1094
+ const symbol = this.args?.symbol as string | undefined;
1095
+ const query = this.args?.query as string | undefined;
1096
+ const newName = this.args?.new_name as string | undefined;
1097
+
1098
+ text = `${statusIndicator} ${theme.fg("toolTitle", theme.bold("lsp"))}`;
1099
+ if (action) text += ` ${theme.fg("accent", action)}`;
1100
+ if (file) {
1101
+ const shortFile = shortenPath(file);
1102
+ let styledFile = theme.fg("muted", shortFile);
1103
+ if (file && shortFile) {
1104
+ styledFile = editorLink(file, styledFile, { cwd: this.cwd, line, scheme: this.editorScheme });
1105
+ }
1106
+ text += ` ${styledFile}`;
1107
+ if (line !== undefined) text += theme.fg("warning", `:${line}`);
1108
+ }
1109
+ if (symbol) text += ` ${theme.fg("toolOutput", symbol)}`;
1110
+ if (query) text += ` ${theme.fg("muted", `"${query}"`)}`;
1111
+ if (newName) text += ` → ${theme.fg("accent", newName)}`;
1112
+
1113
+ if (this.result) {
1114
+ const output = this.getTextOutput().trim();
1115
+ if (output) {
1116
+ if (hideCollapsedPreview) {
1117
+ text += this.collapsedHintWithPrefix();
1118
+ } else {
1119
+ const lines = output.split("\n");
1120
+ const maxLines = this.expanded ? lines.length : 10;
1121
+ const displayLines = lines.slice(0, maxLines);
1122
+ const remaining = lines.length - maxLines;
1123
+ text += `\n\n${displayLines.map((l: string) => theme.fg("toolOutput", l)).join("\n")}`;
1124
+ if (remaining > 0) text += theme.fg("muted", `\n... (${remaining} more lines)`);
1125
+ }
1126
+ }
1127
+ }
1105
1128
  } else {
1106
1129
  // Generic tool (shouldn't reach here for custom tools)
1107
1130
  text = `${statusIndicator} ${theme.fg("toolTitle", theme.bold(this.toolName))}`;
1108
1131
 
1109
1132
  const content = JSON.stringify(this.args, null, 2);
1110
- text += hideCollapsedPreview ? `\n\n${this.collapsedExpandHint()}` : `\n\n${content}`;
1133
+ text += hideCollapsedPreview ? this.collapsedHintWithPrefix() : `\n\n${content}`;
1111
1134
  const output = this.getTextOutput();
1112
1135
  if (output && !hideCollapsedPreview) {
1113
1136
  text += `\n${output}`;
@@ -1,24 +1,19 @@
1
- import { Container, Markdown, type MarkdownTheme, Spacer, Text } from "@gsd/pi-tui";
1
+ import { Container, Markdown, type MarkdownTheme, Spacer } from "@gsd/pi-tui";
2
2
  import { getMarkdownTheme, theme } from "../theme/theme.js";
3
- import { formatTimestamp, type TimestampFormat } from "./timestamp.js";
4
3
 
5
4
  const OSC133_ZONE_START = "\x1b]133;A\x07";
6
5
  const OSC133_ZONE_END = "\x1b]133;B\x07";
7
6
 
8
7
  /**
9
- * Component that renders a user message with a right-aligned timestamp.
8
+ * Component that renders a user message with a visual prompt marker.
10
9
  */
11
10
  export class UserMessageComponent extends Container {
12
- private timestamp: number | undefined;
13
- private timestampFormat: TimestampFormat;
14
-
15
- constructor(text: string, markdownTheme: MarkdownTheme = getMarkdownTheme(), timestamp?: number, timestampFormat: TimestampFormat = "date-time-iso") {
11
+ constructor(text: string, markdownTheme: MarkdownTheme = getMarkdownTheme(), _timestamp?: number, _timestampFormat: string = "date-time-iso") {
16
12
  super();
17
- this.timestamp = timestamp;
18
- this.timestampFormat = timestampFormat;
19
13
  this.addChild(new Spacer(1));
14
+ const promptMarker = `${theme.fg("accent", "▶")} `;
20
15
  this.addChild(
21
- new Markdown(text, 1, 1, markdownTheme, {
16
+ new Markdown(`${promptMarker}${text}`, 1, 1, markdownTheme, {
22
17
  bgColor: (text: string) => theme.bg("userMessageBg", text),
23
18
  color: (text: string) => theme.fg("userMessageText", text),
24
19
  }),
@@ -31,15 +26,6 @@ export class UserMessageComponent extends Container {
31
26
  return lines;
32
27
  }
33
28
 
34
- // Timestamp display removed
35
- // if (this.timestamp) {
36
- // const timeStr = formatTimestamp(this.timestamp, this.timestampFormat);
37
- // const label = theme.fg("dim", timeStr);
38
- // const padding = Math.max(0, width - timeStr.length - 1);
39
- // const timestampLine = " ".repeat(padding) + label;
40
- // lines.splice(0, 0, timestampLine);
41
- // }
42
-
43
29
  lines[0] = OSC133_ZONE_START + lines[0];
44
30
  lines[lines.length - 1] = lines[lines.length - 1] + OSC133_ZONE_END;
45
31
  return lines;
@@ -20,6 +20,11 @@ export async function handleAgentEvent(host: InteractiveModeStateHost & {
20
20
  updatePendingMessagesDisplay: () => void;
21
21
  updateTerminalTitle: () => void;
22
22
  updateEditorBorderColor: () => void;
23
+ updateEditorExpandHint: () => void;
24
+ getAgentPtyComponent: (sessionId: string) => any;
25
+ ensureAgentPtyComponent: (sessionId: string, command?: string) => any;
26
+ updateAgentPtyComponent: (sessionId: string, options?: { command?: string; screenText?: string; completed?: boolean; cancelled?: boolean; exitCode?: number }) => void;
27
+ clearAgentPtyComponents: () => void;
23
28
  pendingMessagesContainer: { clear: () => void };
24
29
  }, event: InteractiveModeEvent): Promise<void> {
25
30
  if (!host.isInitialized) {
@@ -37,6 +42,7 @@ export async function handleAgentEvent(host: InteractiveModeStateHost & {
37
42
  host.streamingComponent = undefined;
38
43
  host.streamingMessage = undefined;
39
44
  host.pendingTools.clear();
45
+ host.clearAgentPtyComponents();
40
46
  host.pendingMessagesContainer.clear();
41
47
  host.compactionQueuedMessages = [];
42
48
  host.rebuildChatFromMessages();
@@ -73,19 +79,14 @@ export async function handleAgentEvent(host: InteractiveModeStateHost & {
73
79
  host.statusContainer.clear();
74
80
  host.loadingAnimation = new Loader(
75
81
  host.ui,
76
- (spinner) => theme.fg("accent", spinner),
77
- (text) => theme.fg("muted", text),
82
+ (spinner) => theme.fg("text", spinner),
83
+ (text) => theme.fg("accent", text),
78
84
  host.defaultWorkingMessage,
79
85
  );
86
+ host.loadingAnimation.setCycleMessages(host.workingMessages, 3000);
80
87
  host.statusContainer.addChild(host.loadingAnimation);
81
- // Show steer/queue hint in editor bottom border while agent is running
82
- {
83
- const enterKey = theme.fg("dim", "↵");
84
- const followUpKey = theme.fg("dim", appKey(host.keybindings, "followUp"));
85
- const steerLabel = theme.fg("muted", " steer");
86
- const queueLabel = theme.fg("muted", " queue");
87
- host.defaultEditor.bottomHint = `${enterKey}${steerLabel} ${followUpKey}${queueLabel}`;
88
- }
88
+ // Show steer/queue + expand hint in editor bottom border while agent is running
89
+ host.updateEditorExpandHint();
89
90
  if (host.pendingWorkingMessage !== undefined) {
90
91
  if (host.pendingWorkingMessage) {
91
92
  host.loadingAnimation.setMessage(host.pendingWorkingMessage);
@@ -109,6 +110,7 @@ export async function handleAgentEvent(host: InteractiveModeStateHost & {
109
110
  host.hideThinkingBlock,
110
111
  host.getMarkdownThemeWithSettings(),
111
112
  host.settingsManager.getTimestampFormat(),
113
+ host.session?.thinkingLevel || "off",
112
114
  );
113
115
  host.streamingMessage = event.message;
114
116
  host.chatContainer.addChild(host.streamingComponent);
@@ -123,6 +125,9 @@ export async function handleAgentEvent(host: InteractiveModeStateHost & {
123
125
  host.streamingComponent.updateContent(host.streamingMessage);
124
126
  for (const content of host.streamingMessage.content) {
125
127
  if (content.type === "toolCall") {
128
+ if (content.name === "pty_start" || content.name === "pty_send" || content.name === "pty_read" || content.name === "pty_wait" || content.name === "pty_resize" || content.name === "pty_kill") {
129
+ continue;
130
+ }
126
131
  if (!host.pendingTools.has(content.id)) {
127
132
  const component = new ToolExecutionComponent(
128
133
  content.name,
@@ -215,6 +220,12 @@ export async function handleAgentEvent(host: InteractiveModeStateHost & {
215
220
  break;
216
221
 
217
222
  case "tool_execution_start":
223
+ if (event.toolName === "pty_start") {
224
+ return;
225
+ }
226
+ if (event.toolName === "pty_send" || event.toolName === "pty_read" || event.toolName === "pty_wait" || event.toolName === "pty_resize" || event.toolName === "pty_kill") {
227
+ return;
228
+ }
218
229
  if (!host.pendingTools.has(event.toolCallId)) {
219
230
  const component = new ToolExecutionComponent(
220
231
  event.toolName,
@@ -235,6 +246,21 @@ export async function handleAgentEvent(host: InteractiveModeStateHost & {
235
246
  break;
236
247
 
237
248
  case "tool_execution_update": {
249
+ if (event.toolName === "pty_start" || event.toolName === "pty_send" || event.toolName === "pty_read" || event.toolName === "pty_wait" || event.toolName === "pty_resize" || event.toolName === "pty_kill") {
250
+ const details = event.partialResult?.details as { sessionId?: string; screenText?: string; exitCode?: number; cancelled?: boolean; completed?: boolean } | undefined;
251
+ const sessionId = details?.sessionId ?? (event.args as { sessionId?: string } | undefined)?.sessionId;
252
+ if (sessionId) {
253
+ host.updateAgentPtyComponent(sessionId, {
254
+ command: event.toolName === "pty_start" ? (event.args as { command?: string } | undefined)?.command : undefined,
255
+ screenText: details?.screenText,
256
+ completed: details?.completed,
257
+ cancelled: details?.cancelled,
258
+ exitCode: details?.exitCode,
259
+ });
260
+ host.ui.requestRender();
261
+ }
262
+ break;
263
+ }
238
264
  const component = host.pendingTools.get(event.toolCallId);
239
265
  if (component) {
240
266
  component.updateResult({ ...event.partialResult, isError: false }, true);
@@ -244,6 +270,21 @@ export async function handleAgentEvent(host: InteractiveModeStateHost & {
244
270
  }
245
271
 
246
272
  case "tool_execution_end": {
273
+ if (event.toolName === "pty_start" || event.toolName === "pty_send" || event.toolName === "pty_read" || event.toolName === "pty_wait" || event.toolName === "pty_resize" || event.toolName === "pty_kill") {
274
+ const details = event.result?.details as { sessionId?: string; screenText?: string; exitCode?: number; cancelled?: boolean; completed?: boolean } | undefined;
275
+ const sessionId = details?.sessionId;
276
+ if (sessionId) {
277
+ host.updateAgentPtyComponent(sessionId, {
278
+ command: undefined,
279
+ screenText: details?.screenText,
280
+ completed: event.toolName === "pty_kill" || !!details?.completed,
281
+ cancelled: details?.cancelled ?? event.toolName === "pty_kill",
282
+ exitCode: details?.exitCode,
283
+ });
284
+ host.ui.requestRender();
285
+ }
286
+ break;
287
+ }
247
288
  const component = host.pendingTools.get(event.toolCallId);
248
289
  if (component) {
249
290
  component.updateResult({ ...event.result, isError: event.isError });
@@ -265,8 +306,9 @@ export async function handleAgentEvent(host: InteractiveModeStateHost & {
265
306
  host.streamingMessage = undefined;
266
307
  }
267
308
  host.pendingTools.clear();
268
- // Clear the steer/queue hint when agent finishes
309
+ // Update hint: show expand/collapse if tool outputs exist, else clear
269
310
  host.defaultEditor.bottomHint = "";
311
+ host.updateEditorExpandHint();
270
312
  await host.checkShutdownRequested();
271
313
  host.ui.requestRender();
272
314
  break;
@@ -277,7 +319,7 @@ export async function handleAgentEvent(host: InteractiveModeStateHost & {
277
319
  host.statusContainer.clear();
278
320
  host.autoCompactionLoader = new Loader(
279
321
  host.ui,
280
- (spinner) => theme.fg("accent", spinner),
322
+ (spinner) => theme.fg("text", spinner),
281
323
  (text) => theme.fg("muted", text),
282
324
  `${event.reason === "overflow" ? "Context overflow detected, " : ""}Auto-compacting... (${appKey(host.keybindings, "interrupt")} to cancel)`,
283
325
  );
@@ -323,7 +365,7 @@ export async function handleAgentEvent(host: InteractiveModeStateHost & {
323
365
  host.statusContainer.clear();
324
366
  host.retryLoader = new Loader(
325
367
  host.ui,
326
- (spinner) => theme.fg("warning", spinner),
368
+ (spinner) => theme.fg("text", spinner),
327
369
  (text) => theme.fg("muted", text),
328
370
  `Retrying (${event.attempt}/${event.maxAttempts}) in ${Math.round(event.delayMs / 1000)}s... (${appKey(host.keybindings, "interrupt")} to cancel)`,
329
371
  );
@@ -17,6 +17,7 @@ export function createExtensionUIContext(host: any): ExtensionUIContext {
17
17
  host.loadingAnimation.setMessage(message);
18
18
  } else {
19
19
  host.loadingAnimation.setMessage(`${host.defaultWorkingMessage} (${appKey(host.keybindings, "interrupt")} to interrupt)`);
20
+ host.loadingAnimation.resumeCycle();
20
21
  }
21
22
  } else {
22
23
  host.pendingWorkingMessage = message;
@@ -56,6 +56,7 @@ export function setupEditorSubmitHandler(host: InteractiveModeStateHost & {
56
56
  if (host.session.isStreaming) {
57
57
  host.editor.addToHistory?.(text);
58
58
  host.editor.setText("");
59
+ host.recordLastSentPrompt?.(text);
59
60
  await host.session.prompt(text, { streamingBehavior: "steer" });
60
61
  host.updatePendingMessagesDisplay();
61
62
  host.ui.requestRender();
@@ -72,6 +73,7 @@ export function setupEditorSubmitHandler(host: InteractiveModeStateHost & {
72
73
 
73
74
  if (host.options?.submitPromptsDirectly) {
74
75
  host.editor.addToHistory?.(text);
76
+ host.recordLastSentPrompt?.(text);
75
77
  try {
76
78
  await host.session.prompt(text);
77
79
  } catch (error: unknown) {
@@ -19,6 +19,7 @@ export interface InteractiveModeStateHost {
19
19
  loadingAnimation?: any;
20
20
  pendingWorkingMessage?: string;
21
21
  defaultWorkingMessage: string;
22
+ workingMessages: string[];
22
23
  streamingComponent?: any;
23
24
  streamingMessage?: any;
24
25
  retryEscapeHandler?: () => void;
@@ -31,6 +32,8 @@ export interface InteractiveModeStateHost {
31
32
  extensionEditor?: any;
32
33
  editorContainer: any;
33
34
  keybindingsManager?: any;
35
+ updateEditorExpandHint(): void;
36
+ recordLastSentPrompt?(text: string): void;
34
37
  }
35
38
 
36
39
  export type InteractiveModeEvent = AgentSessionEvent;