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
@@ -1,23 +1,81 @@
1
1
  import type { TUI } from "../tui.js";
2
2
  import { Text } from "./text.js";
3
3
 
4
+ // ─── ANSI helpers ─────────────────────────────────────────────────────────────
5
+
6
+ const ESC = "\x1b[";
7
+ const RESET = "\x1b[0m";
8
+ const DIM = "\x1b[2m";
9
+ const BOLD = "\x1b[1m";
10
+
11
+ /** 24-bit foreground color */
12
+ function rgb(r: number, g: number, b: number, s: string): string {
13
+ return `${ESC}38;2;${r};${g};${b}m${s}${RESET}`;
14
+ }
15
+
16
+ /** Interpolate between two RGB colors at position t ∈ [0,1] */
17
+ function lerpColor(
18
+ r1: number, g1: number, b1: number,
19
+ r2: number, g2: number, b2: number,
20
+ t: number,
21
+ ): [number, number, number] {
22
+ return [
23
+ Math.round(r1 + (r2 - r1) * t),
24
+ Math.round(g1 + (g2 - g1) * t),
25
+ Math.round(b1 + (b2 - b1) * t),
26
+ ];
27
+ }
28
+
29
+ // ─── Timing constants ─────────────────────────────────────────────────────────
30
+
31
+ const TYPING_SPEED = 55; // ms per character typed
32
+ const HOLD_TIME = 5000; // ms to hold the full word
33
+ const ERASE_SPEED = 35; // ms per character erased
34
+
35
+ // ─── Phase type ───────────────────────────────────────────────────────────────
36
+
37
+ type Phase = "typing" | "hold" | "erase";
38
+
4
39
  /**
5
- * Loader component that updates every 80ms with spinning animation
40
+ * Loader component braille spinner + animated word cycling.
41
+ *
42
+ * Each word types in character by character (typewriter), holds with a moving
43
+ * blue→cyan shimmer gradient, then erases right-to-left before the next word.
6
44
  */
7
45
  export class Loader extends Text {
46
+ // ── spinner ───────────────────────────────────────────────────────────────
8
47
  private frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
9
48
  private currentFrame = 0;
10
- private intervalId: NodeJS.Timeout | null = null;
49
+ private spinnerIntervalId: NodeJS.Timeout | null = null;
11
50
  private ui: TUI | null = null;
12
51
 
52
+ // ── message state ─────────────────────────────────────────────────────────
53
+ private message: string = "";
54
+
55
+ // ── word animation ────────────────────────────────────────────────────────
56
+ private cycleMessages: string[] | null = null;
57
+ private cycleIndex = 0;
58
+ private phase: Phase = "typing";
59
+ private visibleChars = 0;
60
+ private wordAnimId: NodeJS.Timeout | null = null;
61
+
62
+ // ── shimmer gradient: theme blueHigh → blueXhigh ─────────────────────────
63
+ private readonly gradStart: [number, number, number] = [147, 197, 253]; // #93c5fd (blue-300)
64
+ private readonly gradEnd: [number, number, number] = [191, 219, 254]; // #bfdbfe (blue-200)
65
+
66
+ // Shimmer ticks independently at ~40ms for smooth gradient movement
67
+ private shimmerTick = 0;
68
+ private shimmerIntervalId: NodeJS.Timeout | null = null;
69
+
13
70
  constructor(
14
71
  ui: TUI,
15
72
  private spinnerColorFn: (str: string) => string,
16
- private messageColorFn: (str: string) => string,
17
- private message: string = "Loading...",
73
+ private _messageColorFn: (str: string) => string,
74
+ message: string = "Loading",
18
75
  ) {
19
76
  super("", 1, 0);
20
77
  this.ui = ui;
78
+ this.message = message;
21
79
  this.start();
22
80
  }
23
81
 
@@ -25,22 +83,63 @@ export class Loader extends Text {
25
83
  return ["", ...super.render(width)];
26
84
  }
27
85
 
28
- start() {
29
- if (this.intervalId) {
30
- clearInterval(this.intervalId);
86
+ // ── public API ────────────────────────────────────────────────────────────
87
+
88
+ setCycleMessages(messages: string[], _intervalMs = 3000) {
89
+ this.cycleMessages = [...messages];
90
+ this.cycleIndex = 0;
91
+ this.message = this.cycleMessages[0];
92
+ if (this.spinnerIntervalId) {
93
+ this.startWordAnimation();
31
94
  }
32
95
  this.updateDisplay();
33
- this.intervalId = setInterval(() => {
96
+ }
97
+
98
+ clearCycleMessages() {
99
+ this.stopWordAnimation();
100
+ this.cycleMessages = null;
101
+ }
102
+
103
+ setMessage(message: string) {
104
+ // When an explicit message is set externally (e.g. "Waiting for approval…"),
105
+ // pause the cycle so it doesn't immediately overwrite the override.
106
+ this.stopWordAnimation();
107
+ this.cycleMessages = null;
108
+ this.message = message;
109
+ this.updateDisplay();
110
+ }
111
+
112
+ resumeCycle() {
113
+ if (this.cycleMessages && !this.wordAnimId) {
114
+ this.startWordAnimation();
115
+ }
116
+ }
117
+
118
+ start() {
119
+ if (this.spinnerIntervalId) clearInterval(this.spinnerIntervalId);
120
+ this.spinnerIntervalId = setInterval(() => {
34
121
  this.currentFrame = (this.currentFrame + 1) % this.frames.length;
35
122
  this.updateDisplay();
36
123
  }, 80);
124
+
125
+ // Shimmer ticks independently at ~40ms for smooth gradient movement
126
+ if (this.shimmerIntervalId) clearInterval(this.shimmerIntervalId);
127
+ this.shimmerIntervalId = setInterval(() => {
128
+ this.shimmerTick++;
129
+ if (this.cycleMessages) this.updateDisplay();
130
+ }, 40);
131
+
132
+ if (this.cycleMessages) {
133
+ this.startWordAnimation();
134
+ } else {
135
+ this.updateDisplay();
136
+ }
37
137
  }
38
138
 
39
139
  stop() {
40
- if (this.intervalId) {
41
- clearInterval(this.intervalId);
42
- this.intervalId = null;
43
- }
140
+ if (this.spinnerIntervalId) { clearInterval(this.spinnerIntervalId); this.spinnerIntervalId = null; }
141
+ if (this.shimmerIntervalId) { clearInterval(this.shimmerIntervalId); this.shimmerIntervalId = null; }
142
+ this.stopWordAnimation();
44
143
  }
45
144
 
46
145
  dispose() {
@@ -48,16 +147,94 @@ export class Loader extends Text {
48
147
  this.ui = null;
49
148
  }
50
149
 
51
- setMessage(message: string) {
52
- this.message = message;
53
- this.updateDisplay();
150
+ // ── word animation internals ──────────────────────────────────────────────
151
+
152
+ private startWordAnimation() {
153
+ this.stopWordAnimation();
154
+ this.phase = "typing";
155
+ this.visibleChars = 0;
156
+ this.scheduleWordTick();
157
+ }
158
+
159
+ private stopWordAnimation() {
160
+ if (this.wordAnimId) { clearTimeout(this.wordAnimId); this.wordAnimId = null; }
161
+ }
162
+
163
+ private scheduleWordTick() {
164
+ const word = this.currentWord();
165
+
166
+ if (this.phase === "typing") {
167
+ if (this.visibleChars < word.length) {
168
+ this.wordAnimId = setTimeout(() => {
169
+ this.visibleChars++;
170
+ this.updateDisplay();
171
+ this.scheduleWordTick();
172
+ }, TYPING_SPEED);
173
+ } else {
174
+ // Full word shown → hold
175
+ this.phase = "hold";
176
+ this.wordAnimId = setTimeout(() => {
177
+ this.phase = "erase";
178
+ this.scheduleWordTick();
179
+ }, HOLD_TIME);
180
+ }
181
+ } else if (this.phase === "erase") {
182
+ if (this.visibleChars > 0) {
183
+ this.wordAnimId = setTimeout(() => {
184
+ this.visibleChars--;
185
+ this.updateDisplay();
186
+ this.scheduleWordTick();
187
+ }, ERASE_SPEED);
188
+ } else {
189
+ // Advance to next word
190
+ if (this.cycleMessages) {
191
+ this.cycleIndex = (this.cycleIndex + 1) % this.cycleMessages.length;
192
+ this.message = this.cycleMessages[this.cycleIndex];
193
+ }
194
+ this.phase = "typing";
195
+ this.scheduleWordTick();
196
+ }
197
+ }
198
+ }
199
+
200
+ private currentWord(): string {
201
+ return this.message;
202
+ }
203
+
204
+ // ── rendering ─────────────────────────────────────────────────────────────
205
+
206
+ private renderAnimatedWord(): string {
207
+ const word = this.currentWord();
208
+ const visible = word.slice(0, this.visibleChars);
209
+ if (!visible) return DIM + " " + RESET;
210
+
211
+ // Each char gets a colour position that travels through the gradient.
212
+ // The "wave" offset shifts every shimmerTick for a moving shimmer.
213
+ let out = "";
214
+ for (let i = 0; i < visible.length; i++) {
215
+ const wave = (i / Math.max(word.length - 1, 1));
216
+ // Slow rightward drift
217
+ const drift = (this.shimmerTick * 0.025) % 1;
218
+ const t = ((wave + drift) % 1);
219
+ const [r, g, b] = lerpColor(...this.gradStart, ...this.gradEnd, t);
220
+
221
+ // Last char being typed gets a bright flash
222
+ const isEdge = (this.phase === "typing" && i === visible.length - 1);
223
+ const char = isEdge
224
+ ? `${BOLD}${rgb(255, 255, 255, visible[i])}`
225
+ : rgb(r, g, b, visible[i]);
226
+ out += char;
227
+ }
228
+ return out;
54
229
  }
55
230
 
56
231
  private updateDisplay() {
57
232
  const frame = this.frames[this.currentFrame];
58
- this.setText(`${this.spinnerColorFn(frame)} ${this.messageColorFn(this.message)}`);
59
- if (this.ui) {
60
- this.ui.requestRender();
61
- }
233
+ const spinner = this.spinnerColorFn(frame);
234
+ const text = this.cycleMessages
235
+ ? this.renderAnimatedWord()
236
+ : this._messageColorFn(this.message);
237
+ this.setText(`${spinner} ${text}`);
238
+ if (this.ui) this.ui.requestRender();
62
239
  }
63
240
  }
@@ -87,7 +87,7 @@ const dark = {
87
87
  thinkingMedium: "blueHigh",
88
88
  thinkingHigh: "blueXhigh",
89
89
  thinkingXhigh: "cyan",
90
- bashMode: "green",
90
+ bashMode: "accent",
91
91
  },
92
92
  export: {
93
93
  pageBg: "#18181e",
@@ -175,7 +175,7 @@ const light = {
175
175
  thinkingMedium: "teal",
176
176
  thinkingHigh: "blueHigh",
177
177
  thinkingXhigh: "blueXhigh",
178
- bashMode: "green",
178
+ bashMode: "accent",
179
179
  },
180
180
  export: {
181
181
  pageBg: "#f8f8f8",
@@ -1 +1 @@
1
- {"version":3,"file":"themes.js","sourceRoot":"","sources":["../../../../src/modes/interactive/theme/themes.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Built-in theme definitions.\n *\n * Each theme is a self-contained record of color values. Variable references\n * (e.g. \"accent\") are resolved against the `vars` map at load time by the\n * theme engine in theme.ts.\n *\n * To add a new built-in theme, add an entry to `builtinThemes` below.\n */\n\n// Re-use the ThemeJson type from the schema defined in theme.ts.\n// We import only the type to avoid circular runtime dependencies.\nimport type { ThemeJson } from \"./theme.js\";\n\n// ---------------------------------------------------------------------------\n// Dark theme\n// ---------------------------------------------------------------------------\n\nconst dark: ThemeJson = {\n\tname: \"dark\",\n\tvars: {\n\t\tcyan: \"#4a8cf7\",\n\t\tblue: \"#4a8cf7\",\n\t\tgreen: \"#b5bd68\",\n\t\tred: \"#cc6666\",\n\t\tyellow: \"#facc15\",\n\t\tviolet: \"#a78bfa\",\n\t\tgray: \"#bec8d6\",\n\t\tdimGray: \"#8793a3\",\n\t\tdarkGray: \"#505050\",\n\t\taccent: \"#60a5fa\",\n\t\tblueMuted: \"#1e3a8a\",\n\t\tblueLow: \"#2563eb\",\n\t\tblueMedium: \"#4a8cf7\",\n\t\tblueHigh: \"#60a5fa\",\n\t\tblueXhigh: \"#93c5fd\",\n\t\tselectedBg: \"#323640\",\n\t\tuserMsgBg: \"#252930\",\n\t\ttoolPendingBg: \"#1e2230\",\n\t\ttoolSuccessBg: \"#1a2330\",\n\t\ttoolErrorBg: \"#2a1e30\",\n\t\tcustomMsgBg: \"#2d2838\",\n\t},\n\tcolors: {\n\t\taccent: \"accent\",\n\t\tborder: \"blue\",\n\t\tborderAccent: \"cyan\",\n\t\tborderMuted: \"blue\",\n\t\tsuccess: \"green\",\n\t\terror: \"red\",\n\t\twarning: \"yellow\",\n\t\tviolet: \"violet\",\n\t\tmuted: \"gray\",\n\t\tdim: \"dimGray\",\n\t\ttext: \"\",\n\t\tthinkingText: \"gray\",\n\n\t\tselectedBg: \"selectedBg\",\n\t\tuserMessageBg: \"userMsgBg\",\n\t\tuserMessageText: \"\",\n\t\tcustomMessageBg: \"customMsgBg\",\n\t\tcustomMessageText: \"\",\n\t\tcustomMessageLabel: \"#9575cd\",\n\t\ttoolPendingBg: \"toolPendingBg\",\n\t\ttoolSuccessBg: \"toolSuccessBg\",\n\t\ttoolErrorBg: \"toolErrorBg\",\n\t\ttoolTitle: \"\",\n\t\ttoolOutput: \"gray\",\n\n\t\tmdHeading: \"#f0c674\",\n\t\tmdLink: \"#5a8aaa\",\n\t\tmdLinkUrl: \"dimGray\",\n\t\tmdCode: \"accent\",\n\t\tmdCodeBlock: \"green\",\n\t\tmdCodeBlockBorder: \"gray\",\n\t\tmdQuote: \"gray\",\n\t\tmdQuoteBorder: \"gray\",\n\t\tmdHr: \"gray\",\n\t\tmdListBullet: \"accent\",\n\n\t\ttoolDiffAdded: \"green\",\n\t\ttoolDiffRemoved: \"red\",\n\t\ttoolDiffContext: \"gray\",\n\n\t\tsyntaxComment: \"#6A9955\",\n\t\tsyntaxKeyword: \"#569CD6\",\n\t\tsyntaxFunction: \"#DCDCAA\",\n\t\tsyntaxVariable: \"#9CDCFE\",\n\t\tsyntaxString: \"#CE9178\",\n\t\tsyntaxNumber: \"#B5CEA8\",\n\t\tsyntaxType: \"#4EC9B0\",\n\t\tsyntaxOperator: \"#D4D4D4\",\n\t\tsyntaxPunctuation: \"#D4D4D4\",\n\n\t\tthinkingOff: \"blueMuted\",\n\t\tthinkingMinimal: \"blueLow\",\n\t\tthinkingLow: \"blueMedium\",\n\t\tthinkingMedium: \"blueHigh\",\n\t\tthinkingHigh: \"blueXhigh\",\n\t\tthinkingXhigh: \"cyan\",\n\n\t\tbashMode: \"green\",\n\t},\n\texport: {\n\t\tpageBg: \"#18181e\",\n\t\tcardBg: \"#1e1e24\",\n\t\tinfoBg: \"#3c3728\",\n\t},\n};\n\n// ---------------------------------------------------------------------------\n// Light theme\n// ---------------------------------------------------------------------------\n\nconst light: ThemeJson = {\n\tname: \"light\",\n\tvars: {\n\t\tteal: \"#3b82f6\",\n\t\tblue: \"#547da7\",\n\t\tgreen: \"#588458\",\n\t\tred: \"#aa5555\",\n\t\tyellow: \"#eab308\",\n\t\twarning: \"#7a5a00\",\n\t\tviolet: \"#8b5cf6\",\n\t\tmediumGray: \"#6c6c6c\",\n\t\tdimGray: \"#767676\",\n\t\tlightGray: \"#b0b0b0\",\n\t\tblueMuted: \"#6b8fb8\",\n\t\tblueLow: \"#547da7\",\n\t\tblueMedium: \"#3b82f6\",\n\t\tblueHigh: \"#2563eb\",\n\t\tblueXhigh: \"#1d4ed8\",\n\t\tselectedBg: \"#d0d0e0\",\n\t\tuserMsgBg: \"#e8e8e8\",\n\t\ttoolPendingBg: \"#e8eaf0\",\n\t\ttoolSuccessBg: \"#e8f0f0\",\n\t\ttoolErrorBg: \"#f0e8ee\",\n\t\tcustomMsgBg: \"#ede7f6\",\n\t},\n\tcolors: {\n\t\taccent: \"teal\",\n\t\tborder: \"blue\",\n\t\tborderAccent: \"teal\",\n\t\tborderMuted: \"lightGray\",\n\t\tsuccess: \"green\",\n\t\terror: \"red\",\n\t\twarning: \"warning\",\n\t\tviolet: \"violet\",\n\t\tmuted: \"mediumGray\",\n\t\tdim: \"dimGray\",\n\t\ttext: \"\",\n\t\tthinkingText: \"mediumGray\",\n\n\t\tselectedBg: \"selectedBg\",\n\t\tuserMessageBg: \"userMsgBg\",\n\t\tuserMessageText: \"\",\n\t\tcustomMessageBg: \"customMsgBg\",\n\t\tcustomMessageText: \"\",\n\t\tcustomMessageLabel: \"#7e57c2\",\n\t\ttoolPendingBg: \"toolPendingBg\",\n\t\ttoolSuccessBg: \"toolSuccessBg\",\n\t\ttoolErrorBg: \"toolErrorBg\",\n\t\ttoolTitle: \"\",\n\t\ttoolOutput: \"mediumGray\",\n\n\t\tmdHeading: \"yellow\",\n\t\tmdLink: \"blue\",\n\t\tmdLinkUrl: \"dimGray\",\n\t\tmdCode: \"teal\",\n\t\tmdCodeBlock: \"green\",\n\t\tmdCodeBlockBorder: \"mediumGray\",\n\t\tmdQuote: \"mediumGray\",\n\t\tmdQuoteBorder: \"mediumGray\",\n\t\tmdHr: \"mediumGray\",\n\t\tmdListBullet: \"green\",\n\n\t\ttoolDiffAdded: \"green\",\n\t\ttoolDiffRemoved: \"red\",\n\t\ttoolDiffContext: \"mediumGray\",\n\n\t\tsyntaxComment: \"#008000\",\n\t\tsyntaxKeyword: \"#0000FF\",\n\t\tsyntaxFunction: \"#795E26\",\n\t\tsyntaxVariable: \"#001080\",\n\t\tsyntaxString: \"#A31515\",\n\t\tsyntaxNumber: \"#098658\",\n\t\tsyntaxType: \"#267F99\",\n\t\tsyntaxOperator: \"#000000\",\n\t\tsyntaxPunctuation: \"#000000\",\n\n\t\tthinkingOff: \"blueMuted\",\n\t\tthinkingMinimal: \"blueLow\",\n\t\tthinkingLow: \"blueMedium\",\n\t\tthinkingMedium: \"teal\",\n\t\tthinkingHigh: \"blueHigh\",\n\t\tthinkingXhigh: \"blueXhigh\",\n\n\t\tbashMode: \"green\",\n\t},\n\texport: {\n\t\tpageBg: \"#f8f8f8\",\n\t\tcardBg: \"#ffffff\",\n\t\tinfoBg: \"#fffae6\",\n\t},\n};\n\n// ---------------------------------------------------------------------------\n// Export\n// ---------------------------------------------------------------------------\n\nexport const builtinThemes: Record<string, ThemeJson> = { dark, light };\n"]}
1
+ {"version":3,"file":"themes.js","sourceRoot":"","sources":["../../../../src/modes/interactive/theme/themes.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Built-in theme definitions.\n *\n * Each theme is a self-contained record of color values. Variable references\n * (e.g. \"accent\") are resolved against the `vars` map at load time by the\n * theme engine in theme.ts.\n *\n * To add a new built-in theme, add an entry to `builtinThemes` below.\n */\n\n// Re-use the ThemeJson type from the schema defined in theme.ts.\n// We import only the type to avoid circular runtime dependencies.\nimport type { ThemeJson } from \"./theme.js\";\n\n// ---------------------------------------------------------------------------\n// Dark theme\n// ---------------------------------------------------------------------------\n\nconst dark: ThemeJson = {\n\tname: \"dark\",\n\tvars: {\n\t\tcyan: \"#4a8cf7\",\n\t\tblue: \"#4a8cf7\",\n\t\tgreen: \"#b5bd68\",\n\t\tred: \"#cc6666\",\n\t\tyellow: \"#facc15\",\n\t\tviolet: \"#a78bfa\",\n\t\tgray: \"#bec8d6\",\n\t\tdimGray: \"#8793a3\",\n\t\tdarkGray: \"#505050\",\n\t\taccent: \"#60a5fa\",\n\t\tblueMuted: \"#1e3a8a\",\n\t\tblueLow: \"#2563eb\",\n\t\tblueMedium: \"#4a8cf7\",\n\t\tblueHigh: \"#60a5fa\",\n\t\tblueXhigh: \"#93c5fd\",\n\t\tselectedBg: \"#323640\",\n\t\tuserMsgBg: \"#252930\",\n\t\ttoolPendingBg: \"#1e2230\",\n\t\ttoolSuccessBg: \"#1a2330\",\n\t\ttoolErrorBg: \"#2a1e30\",\n\t\tcustomMsgBg: \"#2d2838\",\n\t},\n\tcolors: {\n\t\taccent: \"accent\",\n\t\tborder: \"blue\",\n\t\tborderAccent: \"cyan\",\n\t\tborderMuted: \"blue\",\n\t\tsuccess: \"green\",\n\t\terror: \"red\",\n\t\twarning: \"yellow\",\n\t\tviolet: \"violet\",\n\t\tmuted: \"gray\",\n\t\tdim: \"dimGray\",\n\t\ttext: \"\",\n\t\tthinkingText: \"gray\",\n\n\t\tselectedBg: \"selectedBg\",\n\t\tuserMessageBg: \"userMsgBg\",\n\t\tuserMessageText: \"\",\n\t\tcustomMessageBg: \"customMsgBg\",\n\t\tcustomMessageText: \"\",\n\t\tcustomMessageLabel: \"#9575cd\",\n\t\ttoolPendingBg: \"toolPendingBg\",\n\t\ttoolSuccessBg: \"toolSuccessBg\",\n\t\ttoolErrorBg: \"toolErrorBg\",\n\t\ttoolTitle: \"\",\n\t\ttoolOutput: \"gray\",\n\n\t\tmdHeading: \"#f0c674\",\n\t\tmdLink: \"#5a8aaa\",\n\t\tmdLinkUrl: \"dimGray\",\n\t\tmdCode: \"accent\",\n\t\tmdCodeBlock: \"green\",\n\t\tmdCodeBlockBorder: \"gray\",\n\t\tmdQuote: \"gray\",\n\t\tmdQuoteBorder: \"gray\",\n\t\tmdHr: \"gray\",\n\t\tmdListBullet: \"accent\",\n\n\t\ttoolDiffAdded: \"green\",\n\t\ttoolDiffRemoved: \"red\",\n\t\ttoolDiffContext: \"gray\",\n\n\t\tsyntaxComment: \"#6A9955\",\n\t\tsyntaxKeyword: \"#569CD6\",\n\t\tsyntaxFunction: \"#DCDCAA\",\n\t\tsyntaxVariable: \"#9CDCFE\",\n\t\tsyntaxString: \"#CE9178\",\n\t\tsyntaxNumber: \"#B5CEA8\",\n\t\tsyntaxType: \"#4EC9B0\",\n\t\tsyntaxOperator: \"#D4D4D4\",\n\t\tsyntaxPunctuation: \"#D4D4D4\",\n\n\t\tthinkingOff: \"blueMuted\",\n\t\tthinkingMinimal: \"blueLow\",\n\t\tthinkingLow: \"blueMedium\",\n\t\tthinkingMedium: \"blueHigh\",\n\t\tthinkingHigh: \"blueXhigh\",\n\t\tthinkingXhigh: \"cyan\",\n\n\t\tbashMode: \"accent\",\n\t},\n\texport: {\n\t\tpageBg: \"#18181e\",\n\t\tcardBg: \"#1e1e24\",\n\t\tinfoBg: \"#3c3728\",\n\t},\n};\n\n// ---------------------------------------------------------------------------\n// Light theme\n// ---------------------------------------------------------------------------\n\nconst light: ThemeJson = {\n\tname: \"light\",\n\tvars: {\n\t\tteal: \"#3b82f6\",\n\t\tblue: \"#547da7\",\n\t\tgreen: \"#588458\",\n\t\tred: \"#aa5555\",\n\t\tyellow: \"#eab308\",\n\t\twarning: \"#7a5a00\",\n\t\tviolet: \"#8b5cf6\",\n\t\tmediumGray: \"#6c6c6c\",\n\t\tdimGray: \"#767676\",\n\t\tlightGray: \"#b0b0b0\",\n\t\tblueMuted: \"#6b8fb8\",\n\t\tblueLow: \"#547da7\",\n\t\tblueMedium: \"#3b82f6\",\n\t\tblueHigh: \"#2563eb\",\n\t\tblueXhigh: \"#1d4ed8\",\n\t\tselectedBg: \"#d0d0e0\",\n\t\tuserMsgBg: \"#e8e8e8\",\n\t\ttoolPendingBg: \"#e8eaf0\",\n\t\ttoolSuccessBg: \"#e8f0f0\",\n\t\ttoolErrorBg: \"#f0e8ee\",\n\t\tcustomMsgBg: \"#ede7f6\",\n\t},\n\tcolors: {\n\t\taccent: \"teal\",\n\t\tborder: \"blue\",\n\t\tborderAccent: \"teal\",\n\t\tborderMuted: \"lightGray\",\n\t\tsuccess: \"green\",\n\t\terror: \"red\",\n\t\twarning: \"warning\",\n\t\tviolet: \"violet\",\n\t\tmuted: \"mediumGray\",\n\t\tdim: \"dimGray\",\n\t\ttext: \"\",\n\t\tthinkingText: \"mediumGray\",\n\n\t\tselectedBg: \"selectedBg\",\n\t\tuserMessageBg: \"userMsgBg\",\n\t\tuserMessageText: \"\",\n\t\tcustomMessageBg: \"customMsgBg\",\n\t\tcustomMessageText: \"\",\n\t\tcustomMessageLabel: \"#7e57c2\",\n\t\ttoolPendingBg: \"toolPendingBg\",\n\t\ttoolSuccessBg: \"toolSuccessBg\",\n\t\ttoolErrorBg: \"toolErrorBg\",\n\t\ttoolTitle: \"\",\n\t\ttoolOutput: \"mediumGray\",\n\n\t\tmdHeading: \"yellow\",\n\t\tmdLink: \"blue\",\n\t\tmdLinkUrl: \"dimGray\",\n\t\tmdCode: \"teal\",\n\t\tmdCodeBlock: \"green\",\n\t\tmdCodeBlockBorder: \"mediumGray\",\n\t\tmdQuote: \"mediumGray\",\n\t\tmdQuoteBorder: \"mediumGray\",\n\t\tmdHr: \"mediumGray\",\n\t\tmdListBullet: \"green\",\n\n\t\ttoolDiffAdded: \"green\",\n\t\ttoolDiffRemoved: \"red\",\n\t\ttoolDiffContext: \"mediumGray\",\n\n\t\tsyntaxComment: \"#008000\",\n\t\tsyntaxKeyword: \"#0000FF\",\n\t\tsyntaxFunction: \"#795E26\",\n\t\tsyntaxVariable: \"#001080\",\n\t\tsyntaxString: \"#A31515\",\n\t\tsyntaxNumber: \"#098658\",\n\t\tsyntaxType: \"#267F99\",\n\t\tsyntaxOperator: \"#000000\",\n\t\tsyntaxPunctuation: \"#000000\",\n\n\t\tthinkingOff: \"blueMuted\",\n\t\tthinkingMinimal: \"blueLow\",\n\t\tthinkingLow: \"blueMedium\",\n\t\tthinkingMedium: \"teal\",\n\t\tthinkingHigh: \"blueHigh\",\n\t\tthinkingXhigh: \"blueXhigh\",\n\n\t\tbashMode: \"accent\",\n\t},\n\texport: {\n\t\tpageBg: \"#f8f8f8\",\n\t\tcardBg: \"#ffffff\",\n\t\tinfoBg: \"#fffae6\",\n\t},\n};\n\n// ---------------------------------------------------------------------------\n// Export\n// ---------------------------------------------------------------------------\n\nexport const builtinThemes: Record<string, ThemeJson> = { dark, light };\n"]}
package/pkg/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lsd-pi",
3
- "version": "1.1.4",
3
+ "version": "1.1.5",
4
4
  "piConfig": {
5
5
  "name": "lsd",
6
6
  "configDir": ".lsd"
@@ -13,6 +13,7 @@ import {
13
13
  DEFAULT_MAX_BYTES,
14
14
  DEFAULT_MAX_LINES,
15
15
  } from "@gsd/pi-coding-agent";
16
+ import { Text } from "@gsd/pi-tui";
16
17
  import { Type } from "@sinclair/typebox";
17
18
  import { spawn, spawnSync } from "node:child_process";
18
19
  import { createWriteStream } from "node:fs";
@@ -81,6 +82,18 @@ export function createAsyncBashTool(
81
82
  "Check /jobs to see all running and recent background jobs.",
82
83
  ],
83
84
  parameters: schema,
85
+ renderCall(args, theme, options) {
86
+ const cmd = args.command ?? "";
87
+ const display = cmd.length > 80 ? cmd.slice(0, 77) + "..." : cmd;
88
+ const indicator = options?.statusIndicator ? `${options.statusIndicator} ` : "";
89
+ let text = indicator + theme.fg("toolTitle", theme.bold("async_bash "));
90
+ text += theme.fg("bashMode", "$ ");
91
+ text += theme.fg("accent", display || theme.fg("muted", "..."));
92
+ if (args.label) text += theme.fg("muted", ` (${args.label})`);
93
+ if (args.timeout) text += theme.fg("dim", ` timeout:${args.timeout}s`);
94
+ return new Text(text, 0, 0);
95
+ },
96
+
84
97
  async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
85
98
  const manager = getManager();
86
99
  const cwd = getCwd();
@@ -6,6 +6,7 @@
6
6
  */
7
7
 
8
8
  import type { ToolDefinition } from "@gsd/pi-coding-agent";
9
+ import { Text } from "@gsd/pi-tui";
9
10
  import { Type } from "@sinclair/typebox";
10
11
  import type { AsyncJobManager, Job } from "./job-manager.js";
11
12
 
@@ -33,6 +34,18 @@ export function createAwaitTool(getManager: () => AsyncJobManager): ToolDefiniti
33
34
  description:
34
35
  "Wait for background jobs to complete. Provide specific job IDs or omit to wait for the next job that finishes. Returns results of completed jobs.",
35
36
  parameters: schema,
37
+ renderCall(args, theme, options) {
38
+ const indicator = options?.statusIndicator ? `${options.statusIndicator} ` : "";
39
+ let text = indicator + theme.fg("toolTitle", theme.bold("await_job"));
40
+ if (args.jobs && args.jobs.length > 0) {
41
+ text += " " + theme.fg("accent", args.jobs.join(", "));
42
+ } else {
43
+ text += theme.fg("muted", " (any)");
44
+ }
45
+ if (args.timeout) text += theme.fg("dim", ` timeout:${args.timeout}s`);
46
+ return new Text(text, 0, 0);
47
+ },
48
+
36
49
  async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
37
50
  const manager = getManager();
38
51
  const { jobs: jobIds, timeout } = params;
@@ -3,6 +3,7 @@
3
3
  */
4
4
 
5
5
  import type { ToolDefinition } from "@gsd/pi-coding-agent";
6
+ import { Text } from "@gsd/pi-tui";
6
7
  import { Type } from "@sinclair/typebox";
7
8
  import type { AsyncJobManager } from "./job-manager.js";
8
9
 
@@ -16,6 +17,13 @@ export function createCancelJobTool(getManager: () => AsyncJobManager): ToolDefi
16
17
  label: "Cancel Background Job",
17
18
  description: "Cancel a running background job by its ID.",
18
19
  parameters: schema,
20
+ renderCall(args, theme, options) {
21
+ const indicator = options?.statusIndicator ? `${options.statusIndicator} ` : "";
22
+ let text = indicator + theme.fg("toolTitle", theme.bold("cancel_job "));
23
+ text += theme.fg("accent", args.job_id);
24
+ return new Text(text, 0, 0);
25
+ },
26
+
19
27
  async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
20
28
  const manager = getManager();
21
29
  const result = manager.cancel(params.job_id);
@@ -17,6 +17,8 @@ import type { ExtensionAPI, ExtensionUIContext } from "@gsd/pi-coding-agent";
17
17
  import { getAgentDir } from "@gsd/pi-coding-agent";
18
18
 
19
19
  const STATUS_KEY = "cache-timer";
20
+ const IS_MEMORY_MAINTENANCE_WORKER = process.env.LSD_MEMORY_EXTRACT === "1" || process.env.LSD_MEMORY_DREAM === "1";
21
+ const IS_CACHE_TIMER_FORCED_OFF = process.env.LSD_DISABLE_CACHE_TIMER === "1" || process.env.GSD_DISABLE_CACHE_TIMER === "1";
20
22
 
21
23
  // ANSI color codes for timer display
22
24
  const ANSI_RESET = "\x1b[0m";
@@ -24,112 +26,116 @@ const ANSI_YELLOW = "\x1b[33m";
24
26
  const ANSI_RED = "\x1b[31m";
25
27
 
26
28
  function getSettingsPath(): string {
27
- return join(getAgentDir(), "settings.json");
29
+ return join(getAgentDir(), "settings.json");
28
30
  }
29
31
 
30
32
  function readEnabled(): boolean {
31
- try {
32
- const path = getSettingsPath();
33
- if (!existsSync(path)) return true;
34
- const settings = JSON.parse(readFileSync(path, "utf-8"));
35
- // SettingsManager stores `cacheTimer` as a top-level boolean (default true)
36
- return settings?.cacheTimer !== false;
37
- } catch {
38
- return true;
39
- }
33
+ try {
34
+ const path = getSettingsPath();
35
+ if (!existsSync(path)) return true;
36
+ const settings = JSON.parse(readFileSync(path, "utf-8"));
37
+ // SettingsManager stores `cacheTimer` as a top-level boolean (default true)
38
+ return settings?.cacheTimer !== false;
39
+ } catch {
40
+ return true;
41
+ }
40
42
  }
41
43
 
42
44
  function writeEnabled(enabled: boolean): void {
43
- const path = getSettingsPath();
44
- let settings: Record<string, unknown> = {};
45
- try {
46
- if (existsSync(path)) {
47
- settings = JSON.parse(readFileSync(path, "utf-8"));
48
- }
49
- } catch {
50
- // ignore parse errors, start fresh
51
- }
52
- settings.cacheTimer = enabled;
53
- writeFileSync(path, JSON.stringify(settings, null, 2) + "\n", "utf-8");
45
+ const path = getSettingsPath();
46
+ let settings: Record<string, unknown> = {};
47
+ try {
48
+ if (existsSync(path)) {
49
+ settings = JSON.parse(readFileSync(path, "utf-8"));
50
+ }
51
+ } catch {
52
+ // ignore parse errors, start fresh
53
+ }
54
+ settings.cacheTimer = enabled;
55
+ writeFileSync(path, JSON.stringify(settings, null, 2) + "\n", "utf-8");
54
56
  }
55
57
 
56
58
  function formatElapsed(ms: number): string {
57
- const totalSeconds = Math.floor(ms / 1000);
58
- const minutes = Math.floor(totalSeconds / 60);
59
- const seconds = totalSeconds % 60;
60
- const time = `${minutes}:${seconds.toString().padStart(2, "0")}`;
61
-
62
- if (ms >= 10 * 60 * 1000) {
63
- // 10+ minutes: red
64
- return `${ANSI_RED}⏱ ${time}${ANSI_RESET}`;
65
- } else if (ms >= 5 * 60 * 1000) {
66
- // 5–10 minutes: yellow
67
- return `${ANSI_YELLOW}⏱ ${time}${ANSI_RESET}`;
68
- }
69
- // Under 5 minutes: plain (inherits footer dim styling)
70
- return `⏱ ${time}`;
59
+ const totalSeconds = Math.floor(ms / 1000);
60
+ const minutes = Math.floor(totalSeconds / 60);
61
+ const seconds = totalSeconds % 60;
62
+ const time = `${minutes}:${seconds.toString().padStart(2, "0")}`;
63
+
64
+ if (ms >= 10 * 60 * 1000) {
65
+ // 10+ minutes: red
66
+ return `${ANSI_RED}⏱ ${time}${ANSI_RESET}`;
67
+ } else if (ms >= 5 * 60 * 1000) {
68
+ // 5–10 minutes: yellow
69
+ return `${ANSI_YELLOW}⏱ ${time}${ANSI_RESET}`;
70
+ }
71
+ // Under 5 minutes: plain (inherits footer dim styling)
72
+ return `⏱ ${time}`;
71
73
  }
72
74
 
73
75
  export default function cacheTimerExtension(pi: ExtensionAPI) {
74
- let timer: ReturnType<typeof setInterval> | null = null;
75
- let startTime: number | null = null;
76
- let enabled = readEnabled();
77
-
78
- // We capture the ui reference on first use — it stays valid for the session lifetime
79
- let ui: ExtensionUIContext | null = null;
80
-
81
- function startTimer(uiCtx: ExtensionUIContext): void {
82
- stopTimer();
83
- if (!enabled) return;
84
- ui = uiCtx;
85
- startTime = Date.now();
86
- uiCtx.setStatus(STATUS_KEY, formatElapsed(0));
87
- timer = setInterval(() => {
88
- if (startTime !== null && ui !== null) {
89
- ui.setStatus(STATUS_KEY, formatElapsed(Date.now() - startTime));
90
- }
91
- }, 1000);
92
- }
93
-
94
- function stopTimer(): void {
95
- if (timer !== null) {
96
- clearInterval(timer);
97
- timer = null;
98
- }
99
- startTime = null;
100
- if (ui !== null) {
101
- ui.setStatus(STATUS_KEY, undefined);
102
- }
103
- }
104
-
105
- // When the agent finishes a response, start counting up
106
- pi.on("agent_end", async (_event, ctx) => {
107
- startTimer(ctx.ui);
108
- });
109
-
110
- // When the user submits a new request, clear the timer
111
- pi.on("agent_start", async (_event, ctx) => {
112
- ui = ctx.ui;
113
- stopTimer();
114
- });
115
-
116
- // Clean up on session shutdown
117
- pi.on("session_shutdown", () => {
118
- stopTimer();
119
- });
120
-
121
- // /cache-timer toggle on/off
122
- pi.registerCommand("cache-timer", {
123
- description: "Toggle the cache elapsed-time timer in the footer",
124
- async handler(_args, ctx) {
125
- enabled = !enabled;
126
- writeEnabled(enabled);
127
- if (enabled) {
128
- ctx.ui.notify("Cache timer enabled", "info");
129
- } else {
130
- stopTimer();
131
- ctx.ui.notify("Cache timer disabled", "info");
132
- }
133
- },
134
- });
76
+ if (IS_MEMORY_MAINTENANCE_WORKER || IS_CACHE_TIMER_FORCED_OFF) {
77
+ return;
78
+ }
79
+
80
+ let timer: ReturnType<typeof setInterval> | null = null;
81
+ let startTime: number | null = null;
82
+ let enabled = readEnabled();
83
+
84
+ // We capture the ui reference on first use — it stays valid for the session lifetime
85
+ let ui: ExtensionUIContext | null = null;
86
+
87
+ function startTimer(uiCtx: ExtensionUIContext): void {
88
+ stopTimer();
89
+ if (!enabled) return;
90
+ ui = uiCtx;
91
+ startTime = Date.now();
92
+ uiCtx.setStatus(STATUS_KEY, formatElapsed(0));
93
+ timer = setInterval(() => {
94
+ if (startTime !== null && ui !== null) {
95
+ ui.setStatus(STATUS_KEY, formatElapsed(Date.now() - startTime));
96
+ }
97
+ }, 1000);
98
+ }
99
+
100
+ function stopTimer(): void {
101
+ if (timer !== null) {
102
+ clearInterval(timer);
103
+ timer = null;
104
+ }
105
+ startTime = null;
106
+ if (ui !== null) {
107
+ ui.setStatus(STATUS_KEY, undefined);
108
+ }
109
+ }
110
+
111
+ // When the agent finishes a response, start counting up
112
+ pi.on("agent_end", async (_event, ctx) => {
113
+ startTimer(ctx.ui);
114
+ });
115
+
116
+ // When the user submits a new request, clear the timer
117
+ pi.on("agent_start", async (_event, ctx) => {
118
+ ui = ctx.ui;
119
+ stopTimer();
120
+ });
121
+
122
+ // Clean up on session shutdown
123
+ pi.on("session_shutdown", () => {
124
+ stopTimer();
125
+ });
126
+
127
+ // /cache-timer — toggle on/off
128
+ pi.registerCommand("cache-timer", {
129
+ description: "Toggle the cache elapsed-time timer in the footer",
130
+ async handler(_args, ctx) {
131
+ enabled = !enabled;
132
+ writeEnabled(enabled);
133
+ if (enabled) {
134
+ ctx.ui.notify("Cache timer enabled", "info");
135
+ } else {
136
+ stopTimer();
137
+ ctx.ui.notify("Cache timer disabled", "info");
138
+ }
139
+ },
140
+ });
135
141
  }