@code-yeongyu/senpi 2026.5.15 → 2026.5.18

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 (242) hide show
  1. package/CHANGELOG.md +1170 -1161
  2. package/README.md +1 -2
  3. package/dist/cli/config-selector.d.ts.map +1 -1
  4. package/dist/cli/config-selector.js +1 -1
  5. package/dist/cli/config-selector.js.map +1 -1
  6. package/dist/cli.d.ts.map +1 -1
  7. package/dist/cli.js +5 -1
  8. package/dist/cli.js.map +1 -1
  9. package/dist/config.d.ts.map +1 -1
  10. package/dist/config.js +12 -3
  11. package/dist/config.js.map +1 -1
  12. package/dist/core/agent-session.d.ts +11 -0
  13. package/dist/core/agent-session.d.ts.map +1 -1
  14. package/dist/core/agent-session.js +160 -13
  15. package/dist/core/agent-session.js.map +1 -1
  16. package/dist/core/compaction/compaction.d.ts +5 -3
  17. package/dist/core/compaction/compaction.d.ts.map +1 -1
  18. package/dist/core/compaction/compaction.js +22 -14
  19. package/dist/core/compaction/compaction.js.map +1 -1
  20. package/dist/core/dynamic-prompt/verification.d.ts +31 -0
  21. package/dist/core/dynamic-prompt/verification.d.ts.map +1 -1
  22. package/dist/core/dynamic-prompt/verification.js +41 -0
  23. package/dist/core/dynamic-prompt/verification.js.map +1 -1
  24. package/dist/core/extensions/builtin/compaction/context-reduction.d.ts +97 -0
  25. package/dist/core/extensions/builtin/compaction/context-reduction.d.ts.map +1 -0
  26. package/dist/core/extensions/builtin/compaction/context-reduction.js +420 -0
  27. package/dist/core/extensions/builtin/compaction/context-reduction.js.map +1 -0
  28. package/dist/core/extensions/builtin/compaction/index.d.ts.map +1 -1
  29. package/dist/core/extensions/builtin/compaction/index.js +168 -31
  30. package/dist/core/extensions/builtin/compaction/index.js.map +1 -1
  31. package/dist/core/extensions/builtin/compaction/openai-remote.d.ts +197 -0
  32. package/dist/core/extensions/builtin/compaction/openai-remote.d.ts.map +1 -0
  33. package/dist/core/extensions/builtin/compaction/openai-remote.js +690 -0
  34. package/dist/core/extensions/builtin/compaction/openai-remote.js.map +1 -0
  35. package/dist/core/extensions/builtin/compaction/prompts.d.ts +3 -3
  36. package/dist/core/extensions/builtin/compaction/prompts.d.ts.map +1 -1
  37. package/dist/core/extensions/builtin/compaction/prompts.js +0 -22
  38. package/dist/core/extensions/builtin/compaction/prompts.js.map +1 -1
  39. package/dist/core/extensions/builtin/compaction/repair-tool-pairs.d.ts +4 -0
  40. package/dist/core/extensions/builtin/compaction/repair-tool-pairs.d.ts.map +1 -0
  41. package/dist/core/extensions/builtin/compaction/repair-tool-pairs.js +48 -0
  42. package/dist/core/extensions/builtin/compaction/repair-tool-pairs.js.map +1 -0
  43. package/dist/core/extensions/builtin/compaction/speculative.d.ts +3 -1
  44. package/dist/core/extensions/builtin/compaction/speculative.d.ts.map +1 -1
  45. package/dist/core/extensions/builtin/compaction/speculative.js +80 -33
  46. package/dist/core/extensions/builtin/compaction/speculative.js.map +1 -1
  47. package/dist/core/extensions/builtin/compaction/todo-bridge.d.ts +8 -0
  48. package/dist/core/extensions/builtin/compaction/todo-bridge.d.ts.map +1 -1
  49. package/dist/core/extensions/builtin/compaction/todo-bridge.js +12 -6
  50. package/dist/core/extensions/builtin/compaction/todo-bridge.js.map +1 -1
  51. package/dist/core/extensions/builtin/diff.d.ts.map +1 -1
  52. package/dist/core/extensions/builtin/diff.js +1 -1
  53. package/dist/core/extensions/builtin/diff.js.map +1 -1
  54. package/dist/core/extensions/builtin/gpt-apply-patch/preview-format.d.ts.map +1 -1
  55. package/dist/core/extensions/builtin/gpt-apply-patch/preview-format.js +5 -128
  56. package/dist/core/extensions/builtin/gpt-apply-patch/preview-format.js.map +1 -1
  57. package/dist/core/extensions/builtin/index.d.ts.map +1 -1
  58. package/dist/core/extensions/builtin/index.js +0 -2
  59. package/dist/core/extensions/builtin/index.js.map +1 -1
  60. package/dist/core/extensions/builtin/openai-web-search/index.d.ts +6 -2
  61. package/dist/core/extensions/builtin/openai-web-search/index.d.ts.map +1 -1
  62. package/dist/core/extensions/builtin/openai-web-search/index.js +82 -10
  63. package/dist/core/extensions/builtin/openai-web-search/index.js.map +1 -1
  64. package/dist/core/extensions/builtin/permission-system/prompt.d.ts.map +1 -1
  65. package/dist/core/extensions/builtin/permission-system/prompt.js +0 -5
  66. package/dist/core/extensions/builtin/permission-system/prompt.js.map +1 -1
  67. package/dist/core/extensions/builtin/system-messages.d.ts +1 -1
  68. package/dist/core/extensions/builtin/system-messages.d.ts.map +1 -1
  69. package/dist/core/extensions/builtin/system-messages.js.map +1 -1
  70. package/dist/core/extensions/builtin/tool-pair-guard/index.d.ts +1 -1
  71. package/dist/core/extensions/builtin/tool-pair-guard/index.d.ts.map +1 -1
  72. package/dist/core/extensions/builtin/tool-pair-guard/index.js +8 -4
  73. package/dist/core/extensions/builtin/tool-pair-guard/index.js.map +1 -1
  74. package/dist/core/extensions/builtin/tool-pair-guard/sanitize-openai-chat-completions-payload.d.ts +3 -0
  75. package/dist/core/extensions/builtin/tool-pair-guard/sanitize-openai-chat-completions-payload.d.ts.map +1 -0
  76. package/dist/core/extensions/builtin/tool-pair-guard/sanitize-openai-chat-completions-payload.js +89 -0
  77. package/dist/core/extensions/builtin/tool-pair-guard/sanitize-openai-chat-completions-payload.js.map +1 -0
  78. package/dist/core/extensions/builtin/tool-pair-guard/sanitize-openai-responses-payload.d.ts +3 -0
  79. package/dist/core/extensions/builtin/tool-pair-guard/sanitize-openai-responses-payload.d.ts.map +1 -0
  80. package/dist/core/extensions/builtin/tool-pair-guard/sanitize-openai-responses-payload.js +122 -0
  81. package/dist/core/extensions/builtin/tool-pair-guard/sanitize-openai-responses-payload.js.map +1 -0
  82. package/dist/core/extensions/loader.d.ts.map +1 -1
  83. package/dist/core/extensions/loader.js +2 -0
  84. package/dist/core/extensions/loader.js.map +1 -1
  85. package/dist/core/extensions/runner.d.ts +3 -0
  86. package/dist/core/extensions/runner.d.ts.map +1 -1
  87. package/dist/core/extensions/runner.js +18 -0
  88. package/dist/core/extensions/runner.js.map +1 -1
  89. package/dist/core/extensions/types.d.ts +22 -0
  90. package/dist/core/extensions/types.d.ts.map +1 -1
  91. package/dist/core/extensions/types.js.map +1 -1
  92. package/dist/core/messages.d.ts +3 -3
  93. package/dist/core/messages.d.ts.map +1 -1
  94. package/dist/core/messages.js +5 -10
  95. package/dist/core/messages.js.map +1 -1
  96. package/dist/core/model-registry.d.ts +1 -0
  97. package/dist/core/model-registry.d.ts.map +1 -1
  98. package/dist/core/model-registry.js +66 -9
  99. package/dist/core/model-registry.js.map +1 -1
  100. package/dist/core/package-manager.d.ts +5 -0
  101. package/dist/core/package-manager.d.ts.map +1 -1
  102. package/dist/core/package-manager.js +72 -31
  103. package/dist/core/package-manager.js.map +1 -1
  104. package/dist/core/prompt-templates.d.ts.map +1 -1
  105. package/dist/core/prompt-templates.js +6 -4
  106. package/dist/core/prompt-templates.js.map +1 -1
  107. package/dist/core/sdk.d.ts +1 -1
  108. package/dist/core/sdk.d.ts.map +1 -1
  109. package/dist/core/sdk.js +7 -22
  110. package/dist/core/sdk.js.map +1 -1
  111. package/dist/core/session-manager.d.ts.map +1 -1
  112. package/dist/core/session-manager.js +39 -9
  113. package/dist/core/session-manager.js.map +1 -1
  114. package/dist/core/settings-manager.d.ts +0 -5
  115. package/dist/core/settings-manager.d.ts.map +1 -1
  116. package/dist/core/settings-manager.js.map +1 -1
  117. package/dist/core/skills.d.ts.map +1 -1
  118. package/dist/core/skills.js +2 -5
  119. package/dist/core/skills.js.map +1 -1
  120. package/dist/core/system-prompt.d.ts.map +1 -1
  121. package/dist/core/system-prompt.js +3 -2
  122. package/dist/core/system-prompt.js.map +1 -1
  123. package/dist/core/thinking-levels.d.ts +6 -0
  124. package/dist/core/thinking-levels.d.ts.map +1 -0
  125. package/dist/core/thinking-levels.js +36 -0
  126. package/dist/core/thinking-levels.js.map +1 -0
  127. package/dist/core/tools/bash.d.ts.map +1 -1
  128. package/dist/core/tools/bash.js +15 -1
  129. package/dist/core/tools/bash.js.map +1 -1
  130. package/dist/core/tools/diff-render.d.ts +13 -0
  131. package/dist/core/tools/diff-render.d.ts.map +1 -0
  132. package/dist/core/tools/diff-render.js +130 -0
  133. package/dist/core/tools/diff-render.js.map +1 -0
  134. package/dist/core/tools/edit.d.ts.map +1 -1
  135. package/dist/core/tools/edit.js +8 -3
  136. package/dist/core/tools/edit.js.map +1 -1
  137. package/dist/core/tools/write.d.ts.map +1 -1
  138. package/dist/core/tools/write.js +28 -7
  139. package/dist/core/tools/write.js.map +1 -1
  140. package/dist/modes/interactive/components/compaction-summary-message.d.ts.map +1 -1
  141. package/dist/modes/interactive/components/compaction-summary-message.js +20 -2
  142. package/dist/modes/interactive/components/compaction-summary-message.js.map +1 -1
  143. package/dist/modes/interactive/components/config-selector.d.ts +2 -2
  144. package/dist/modes/interactive/components/config-selector.d.ts.map +1 -1
  145. package/dist/modes/interactive/components/config-selector.js +7 -4
  146. package/dist/modes/interactive/components/config-selector.js.map +1 -1
  147. package/dist/modes/interactive/components/footer.d.ts +0 -1
  148. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  149. package/dist/modes/interactive/components/footer.js +42 -44
  150. package/dist/modes/interactive/components/footer.js.map +1 -1
  151. package/dist/modes/interactive/components/keybinding-hints.d.ts.map +1 -1
  152. package/dist/modes/interactive/components/keybinding-hints.js +3 -1
  153. package/dist/modes/interactive/components/keybinding-hints.js.map +1 -1
  154. package/dist/modes/interactive/interactive-mode.d.ts +9 -0
  155. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  156. package/dist/modes/interactive/interactive-mode.js +177 -82
  157. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  158. package/dist/modes/interactive/session-info-format.d.ts +3 -0
  159. package/dist/modes/interactive/session-info-format.d.ts.map +1 -0
  160. package/dist/modes/interactive/session-info-format.js +44 -0
  161. package/dist/modes/interactive/session-info-format.js.map +1 -0
  162. package/dist/modes/interactive/working-status.d.ts +21 -0
  163. package/dist/modes/interactive/working-status.d.ts.map +1 -0
  164. package/dist/modes/interactive/working-status.js +71 -0
  165. package/dist/modes/interactive/working-status.js.map +1 -0
  166. package/dist/package-manager-cli.d.ts.map +1 -1
  167. package/dist/package-manager-cli.js +3 -4
  168. package/dist/package-manager-cli.js.map +1 -1
  169. package/dist/senpi +5 -1
  170. package/dist/utils/child-process.d.ts +7 -1
  171. package/dist/utils/child-process.d.ts.map +1 -1
  172. package/dist/utils/child-process.js +60 -7
  173. package/dist/utils/child-process.js.map +1 -1
  174. package/dist/utils/clipboard-image.d.ts.map +1 -1
  175. package/dist/utils/clipboard-image.js +1 -1
  176. package/dist/utils/clipboard-image.js.map +1 -1
  177. package/dist/utils/tools-manager.d.ts.map +1 -1
  178. package/dist/utils/tools-manager.js +4 -1
  179. package/dist/utils/tools-manager.js.map +1 -1
  180. package/docs/custom-provider.md +55 -0
  181. package/docs/extensions.md +1 -2
  182. package/docs/index.md +0 -1
  183. package/docs/models.md +9 -0
  184. package/docs/sdk.md +0 -1
  185. package/docs/settings.md +2 -32
  186. package/docs/skills.md +3 -4
  187. package/docs/termux.md +2 -2
  188. package/docs/usage.md +1 -1
  189. package/examples/README.md +1 -1
  190. package/examples/extensions/README.md +0 -1
  191. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  192. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  193. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  194. package/examples/extensions/overlay-qa-tests.ts +1 -1
  195. package/examples/extensions/sandbox/package-lock.json +2 -2
  196. package/examples/extensions/sandbox/package.json +1 -1
  197. package/examples/extensions/with-deps/package-lock.json +2 -2
  198. package/examples/extensions/with-deps/package.json +1 -1
  199. package/package.json +6 -6
  200. package/dist/core/extensions/builtin/background-task/cancel-tool.d.ts +0 -10
  201. package/dist/core/extensions/builtin/background-task/cancel-tool.d.ts.map +0 -1
  202. package/dist/core/extensions/builtin/background-task/cancel-tool.js +0 -109
  203. package/dist/core/extensions/builtin/background-task/cancel-tool.js.map +0 -1
  204. package/dist/core/extensions/builtin/background-task/index.d.ts +0 -3
  205. package/dist/core/extensions/builtin/background-task/index.d.ts.map +0 -1
  206. package/dist/core/extensions/builtin/background-task/index.js +0 -207
  207. package/dist/core/extensions/builtin/background-task/index.js.map +0 -1
  208. package/dist/core/extensions/builtin/background-task/manager.d.ts +0 -17
  209. package/dist/core/extensions/builtin/background-task/manager.d.ts.map +0 -1
  210. package/dist/core/extensions/builtin/background-task/manager.js +0 -114
  211. package/dist/core/extensions/builtin/background-task/manager.js.map +0 -1
  212. package/dist/core/extensions/builtin/background-task/notification.d.ts +0 -22
  213. package/dist/core/extensions/builtin/background-task/notification.d.ts.map +0 -1
  214. package/dist/core/extensions/builtin/background-task/notification.js +0 -105
  215. package/dist/core/extensions/builtin/background-task/notification.js.map +0 -1
  216. package/dist/core/extensions/builtin/background-task/output-tool.d.ts +0 -11
  217. package/dist/core/extensions/builtin/background-task/output-tool.d.ts.map +0 -1
  218. package/dist/core/extensions/builtin/background-task/output-tool.js +0 -127
  219. package/dist/core/extensions/builtin/background-task/output-tool.js.map +0 -1
  220. package/dist/core/extensions/builtin/background-task/spawner.d.ts +0 -8
  221. package/dist/core/extensions/builtin/background-task/spawner.d.ts.map +0 -1
  222. package/dist/core/extensions/builtin/background-task/spawner.js +0 -207
  223. package/dist/core/extensions/builtin/background-task/spawner.js.map +0 -1
  224. package/dist/core/extensions/builtin/background-task/task-tool.d.ts +0 -20
  225. package/dist/core/extensions/builtin/background-task/task-tool.d.ts.map +0 -1
  226. package/dist/core/extensions/builtin/background-task/task-tool.js +0 -302
  227. package/dist/core/extensions/builtin/background-task/task-tool.js.map +0 -1
  228. package/dist/core/extensions/builtin/background-task/types.d.ts +0 -72
  229. package/dist/core/extensions/builtin/background-task/types.d.ts.map +0 -1
  230. package/dist/core/extensions/builtin/background-task/types.js +0 -32
  231. package/dist/core/extensions/builtin/background-task/types.js.map +0 -1
  232. package/docs/agents.md +0 -348
  233. package/examples/extensions/subagent/README.md +0 -172
  234. package/examples/extensions/subagent/agents/planner.md +0 -37
  235. package/examples/extensions/subagent/agents/reviewer.md +0 -35
  236. package/examples/extensions/subagent/agents/scout.md +0 -50
  237. package/examples/extensions/subagent/agents/worker.md +0 -24
  238. package/examples/extensions/subagent/agents.ts +0 -126
  239. package/examples/extensions/subagent/index.ts +0 -987
  240. package/examples/extensions/subagent/prompts/implement-and-review.md +0 -10
  241. package/examples/extensions/subagent/prompts/implement.md +0 -10
  242. package/examples/extensions/subagent/prompts/scout-and-plan.md +0 -9
@@ -58,8 +58,10 @@ import { ToolExecutionComponent } from "./components/tool-execution.js";
58
58
  import { TreeSelectorComponent } from "./components/tree-selector.js";
59
59
  import { UserMessageComponent } from "./components/user-message.js";
60
60
  import { UserMessageSelectorComponent } from "./components/user-message-selector.js";
61
+ import { formatSessionInfo } from "./session-info-format.js";
61
62
  import { resolveStartupToolPaths } from "./startup-tools.js";
62
63
  import { getAvailableThemes, getAvailableThemesWithPaths, getEditorTheme, getMarkdownTheme, getThemeByName, initTheme, onThemeChange, setRegisteredThemes, setTheme, setThemeInstance, stopThemeWatcher, Theme, theme, } from "./theme/theme.js";
64
+ import { blendWorkingStatusShimmerRgbColor, formatWorkingStatusMessageFrame, } from "./working-status.js";
63
65
  function isExpandable(obj) {
64
66
  return typeof obj === "object" && obj !== null && "setExpanded" in obj && typeof obj.setExpanded === "function";
65
67
  }
@@ -76,6 +78,48 @@ class ExpandableText extends Text {
76
78
  }
77
79
  }
78
80
  const DEAD_TERMINAL_ERROR_CODES = new Set(["EIO", "EPIPE", "ENOTCONN"]);
81
+ const DEFAULT_WORKING_STATUS_REFRESH_INTERVAL_MS = 600;
82
+ const DEFAULT_WORKING_STATUS_MESSAGE_ANIMATION_INTERVAL_MS = 32;
83
+ const RGB_FOREGROUND_PATTERN = /\x1b\[38;2;(\d+);(\d+);(\d+)m/;
84
+ const DARK_DEFAULT_WORKING_TEXT_RGB = { r: 229, g: 229, b: 231 };
85
+ const LIGHT_DEFAULT_WORKING_TEXT_RGB = { r: 17, g: 17, b: 17 };
86
+ const DARK_DEFAULT_WORKING_BASE_RGB = { r: 102, g: 102, b: 102 };
87
+ const LIGHT_DEFAULT_WORKING_BASE_RGB = { r: 118, g: 118, b: 118 };
88
+ function parseAnsiRgbForeground(ansi) {
89
+ const match = RGB_FOREGROUND_PATTERN.exec(ansi);
90
+ const red = match?.[1];
91
+ const green = match?.[2];
92
+ const blue = match?.[3];
93
+ if (red === undefined || green === undefined || blue === undefined) {
94
+ return undefined;
95
+ }
96
+ return {
97
+ r: Number.parseInt(red, 10),
98
+ g: Number.parseInt(green, 10),
99
+ b: Number.parseInt(blue, 10),
100
+ };
101
+ }
102
+ function isWorkingLightTheme() {
103
+ return theme.name?.toLowerCase().includes("light") ?? false;
104
+ }
105
+ function formatWorkingStatusShimmerText(text, intensity) {
106
+ if (theme.getColorMode() !== "truecolor") {
107
+ if (intensity < 0.2) {
108
+ return theme.fg("dim", text);
109
+ }
110
+ if (intensity < 0.6) {
111
+ return theme.fg("text", text);
112
+ }
113
+ return theme.bold(theme.fg("text", text));
114
+ }
115
+ const lightTheme = isWorkingLightTheme();
116
+ const highlight = parseAnsiRgbForeground(theme.getFgAnsi("dim")) ??
117
+ (lightTheme ? LIGHT_DEFAULT_WORKING_BASE_RGB : DARK_DEFAULT_WORKING_BASE_RGB);
118
+ const base = parseAnsiRgbForeground(theme.getFgAnsi("text")) ??
119
+ (lightTheme ? LIGHT_DEFAULT_WORKING_TEXT_RGB : DARK_DEFAULT_WORKING_TEXT_RGB);
120
+ const color = blendWorkingStatusShimmerRgbColor(highlight, base, intensity * 0.9);
121
+ return `\x1b[1m\x1b[38;2;${color.r};${color.g};${color.b}m${text}\x1b[39m\x1b[22m`;
122
+ }
79
123
  function isDeadTerminalError(error) {
80
124
  if (!error || typeof error !== "object" || !("code" in error)) {
81
125
  return false;
@@ -129,7 +173,9 @@ export class InteractiveMode {
129
173
  workingMessage = undefined;
130
174
  workingVisible = true;
131
175
  workingIndicatorOptions = undefined;
132
- defaultWorkingMessage = "Working...";
176
+ workingStartedAt = undefined;
177
+ workingElapsedIntervalId = undefined;
178
+ defaultWorkingMessage = "Working";
133
179
  defaultHiddenThinkingLabel = "Thinking...";
134
180
  hiddenThinkingLabel = this.defaultHiddenThinkingLabel;
135
181
  lastSigintTime = 0;
@@ -166,6 +212,7 @@ export class InteractiveMode {
166
212
  // Auto-compaction state
167
213
  autoCompactionLoader = undefined;
168
214
  autoCompactionEscapeHandler;
215
+ autoCompactionProgressText = "";
169
216
  // Auto-retry state
170
217
  retryLoader = undefined;
171
218
  retryCountdown = undefined;
@@ -391,6 +438,19 @@ export class InteractiveMode {
391
438
  // Startup should never wait for tool downloads. Missing tools are resolved
392
439
  // lazily by the tools that need them.
393
440
  this.fdPath = resolveStartupToolPaths().fdPath;
441
+ if (this.session.scopedModels.length > 0 && (this.options.verbose || !this.settingsManager.getQuietStartup())) {
442
+ const modelList = this.session.scopedModels
443
+ .map((sm) => {
444
+ const thinkingStr = sm.thinkingLevel ? `:${sm.thinkingLevel}` : "";
445
+ return `${sm.model.id}${thinkingStr}`;
446
+ })
447
+ .join(", ");
448
+ const cycleKeys = this.keybindings.getKeys("app.model.cycleForward");
449
+ const cycleHint = cycleKeys.length > 0
450
+ ? theme.fg("muted", ` (${formatKeyText(cycleKeys.join("/"), { capitalize: true })} to cycle)`)
451
+ : "";
452
+ console.log(theme.fg("dim", `Model scope: ${modelList}${cycleHint}`));
453
+ }
394
454
  // Add header container as first child
395
455
  this.ui.addChild(this.headerContainer);
396
456
  // Add header with keybindings from config (unless silenced)
@@ -1097,11 +1157,7 @@ export class InteractiveMode {
1097
1157
  commandContextActions: {
1098
1158
  waitForIdle: () => this.session.agent.waitForIdle(),
1099
1159
  newSession: async (options) => {
1100
- if (this.loadingAnimation) {
1101
- this.loadingAnimation.stop();
1102
- this.loadingAnimation = undefined;
1103
- }
1104
- this.statusContainer.clear();
1160
+ this.stopWorkingLoader();
1105
1161
  try {
1106
1162
  const result = await this.runtimeHost.newSession(options);
1107
1163
  if (!result.cancelled) {
@@ -1285,10 +1341,48 @@ export class InteractiveMode {
1285
1341
  getWorkingLoaderMessage() {
1286
1342
  return this.workingMessage ?? this.defaultWorkingMessage;
1287
1343
  }
1344
+ getWorkingElapsedSeconds() {
1345
+ if (this.workingStartedAt === undefined) {
1346
+ return 0;
1347
+ }
1348
+ return Math.max(0, Math.floor((Date.now() - this.workingStartedAt) / 1000));
1349
+ }
1350
+ refreshWorkingLoaderMessage() {
1351
+ this.loadingAnimation?.setMessage(this.getWorkingLoaderMessage());
1352
+ }
1353
+ startWorkingElapsedTimer() {
1354
+ this.stopWorkingElapsedTimer();
1355
+ this.workingStartedAt = Date.now();
1356
+ this.workingElapsedIntervalId = setInterval(() => {
1357
+ this.refreshWorkingLoaderMessage();
1358
+ }, DEFAULT_WORKING_STATUS_REFRESH_INTERVAL_MS);
1359
+ }
1360
+ stopWorkingElapsedTimer() {
1361
+ if (this.workingElapsedIntervalId) {
1362
+ clearInterval(this.workingElapsedIntervalId);
1363
+ this.workingElapsedIntervalId = undefined;
1364
+ }
1365
+ }
1366
+ getWorkingIndicatorOptions() {
1367
+ return (this.workingIndicatorOptions ?? {
1368
+ frames: [theme.fg("accent", "•")],
1369
+ intervalMs: DEFAULT_WORKING_STATUS_REFRESH_INTERVAL_MS,
1370
+ messageFormatter: (message, animationElapsedMs) => formatWorkingStatusMessageFrame(message, this.getWorkingElapsedSeconds(), keyText("app.interrupt"), animationElapsedMs, {
1371
+ base: (text) => theme.fg("dim", text),
1372
+ glow: (text) => theme.fg("text", text),
1373
+ highlight: (text) => theme.bold(theme.fg("text", text)),
1374
+ shimmer: formatWorkingStatusShimmerText,
1375
+ suffix: (text) => theme.fg("dim", text),
1376
+ }),
1377
+ messageIntervalMs: DEFAULT_WORKING_STATUS_MESSAGE_ANIMATION_INTERVAL_MS,
1378
+ });
1379
+ }
1288
1380
  createWorkingLoader() {
1289
- return new Loader(this.ui, (spinner) => theme.fg("accent", spinner), (text) => theme.fg("muted", text), this.getWorkingLoaderMessage(), this.workingIndicatorOptions);
1381
+ return new Loader(this.ui, (spinner) => theme.fg("accent", spinner), (text) => theme.fg("muted", text), this.getWorkingLoaderMessage(), this.getWorkingIndicatorOptions());
1290
1382
  }
1291
1383
  stopWorkingLoader() {
1384
+ this.stopWorkingElapsedTimer();
1385
+ this.workingStartedAt = undefined;
1292
1386
  if (this.loadingAnimation) {
1293
1387
  this.loadingAnimation.stop();
1294
1388
  this.loadingAnimation = undefined;
@@ -1304,6 +1398,7 @@ export class InteractiveMode {
1304
1398
  }
1305
1399
  if (this.session.isStreaming && !this.loadingAnimation) {
1306
1400
  this.statusContainer.clear();
1401
+ this.startWorkingElapsedTimer();
1307
1402
  this.loadingAnimation = this.createWorkingLoader();
1308
1403
  this.statusContainer.addChild(this.loadingAnimation);
1309
1404
  }
@@ -1311,7 +1406,7 @@ export class InteractiveMode {
1311
1406
  }
1312
1407
  setWorkingIndicator(options) {
1313
1408
  this.workingIndicatorOptions = options;
1314
- this.loadingAnimation?.setIndicator(options);
1409
+ this.loadingAnimation?.setIndicator(this.getWorkingIndicatorOptions());
1315
1410
  this.ui.requestRender();
1316
1411
  }
1317
1412
  setHiddenThinkingLabel(label) {
@@ -1399,9 +1494,7 @@ export class InteractiveMode {
1399
1494
  this.workingMessage = undefined;
1400
1495
  this.workingVisible = true;
1401
1496
  this.setWorkingIndicator();
1402
- if (this.loadingAnimation) {
1403
- this.loadingAnimation.setMessage(`${this.defaultWorkingMessage} (${keyText("app.interrupt")} to interrupt)`);
1404
- }
1497
+ this.refreshWorkingLoaderMessage();
1405
1498
  this.setHiddenThinkingLabel();
1406
1499
  }
1407
1500
  // Maximum total widget lines to prevent viewport overflow
@@ -1526,9 +1619,7 @@ export class InteractiveMode {
1526
1619
  setStatus: (key, text) => this.setExtensionStatus(key, text),
1527
1620
  setWorkingMessage: (message) => {
1528
1621
  this.workingMessage = message;
1529
- if (this.loadingAnimation) {
1530
- this.loadingAnimation.setMessage(message ?? this.defaultWorkingMessage);
1531
- }
1622
+ this.refreshWorkingLoaderMessage();
1532
1623
  },
1533
1624
  setWorkingVisible: (visible) => this.setWorkingVisible(visible),
1534
1625
  setWorkingIndicator: (options) => this.setWorkingIndicator(options),
@@ -1859,8 +1950,8 @@ export class InteractiveMode {
1859
1950
  // Set up handlers on defaultEditor - they use this.editor for text access
1860
1951
  // so they work correctly regardless of which editor is active
1861
1952
  this.defaultEditor.onEscape = () => {
1862
- if (this.session.isStreaming) {
1863
- this.restoreQueuedMessagesToEditor({ abort: true });
1953
+ if (this.session.isStreaming || this.session.retryAttempt > 0) {
1954
+ void this.abortAndFireQueuedMessages();
1864
1955
  }
1865
1956
  else if (this.session.isBashRunning) {
1866
1957
  this.session.abortBash();
@@ -2149,6 +2240,7 @@ export class InteractiveMode {
2149
2240
  }
2150
2241
  this.stopWorkingLoader();
2151
2242
  if (this.workingVisible) {
2243
+ this.startWorkingElapsedTimer();
2152
2244
  this.loadingAnimation = this.createWorkingLoader();
2153
2245
  this.statusContainer.addChild(this.loadingAnimation);
2154
2246
  }
@@ -2290,11 +2382,7 @@ export class InteractiveMode {
2290
2382
  if (this.settingsManager.getShowTerminalProgress()) {
2291
2383
  this.ui.terminal.setProgress(false);
2292
2384
  }
2293
- if (this.loadingAnimation) {
2294
- this.loadingAnimation.stop();
2295
- this.loadingAnimation = undefined;
2296
- this.statusContainer.clear();
2297
- }
2385
+ this.stopWorkingLoader();
2298
2386
  if (this.streamingComponent) {
2299
2387
  this.chatContainer.removeChild(this.streamingComponent);
2300
2388
  this.streamingComponent = undefined;
@@ -2315,11 +2403,31 @@ export class InteractiveMode {
2315
2403
  };
2316
2404
  this.statusContainer.clear();
2317
2405
  const cancelHint = `(${keyText("app.interrupt")} to cancel)`;
2318
- const label = event.reason === "manual"
2319
- ? `Compacting context... ${cancelHint}`
2320
- : `${event.reason === "overflow" ? "Context overflow detected, " : ""}Auto-compacting... ${cancelHint}`;
2406
+ const label = event.reason === "threshold"
2407
+ ? `Auto-compacting... ${cancelHint}`
2408
+ : event.reason === "overflow"
2409
+ ? `Context overflow detected, compacting... ${cancelHint}`
2410
+ : event.reason === "pre_prompt"
2411
+ ? `Compacting before next prompt... ${cancelHint}`
2412
+ : `Compacting context... ${cancelHint}`;
2321
2413
  this.autoCompactionLoader = new Loader(this.ui, (spinner) => theme.fg("accent", spinner), (text) => theme.fg("muted", text), label);
2322
2414
  this.statusContainer.addChild(this.autoCompactionLoader);
2415
+ this.autoCompactionProgressText = "";
2416
+ this.ui.requestRender();
2417
+ break;
2418
+ }
2419
+ case "compaction_progress": {
2420
+ if (!this.autoCompactionLoader)
2421
+ break;
2422
+ const nextText = event.text !== undefined ? event.text : `${this.autoCompactionProgressText}${event.delta ?? ""}`;
2423
+ if (!nextText)
2424
+ break;
2425
+ this.autoCompactionProgressText = nextText;
2426
+ const preview = nextText.length > 4_000 ? `...${nextText.slice(nextText.length - 4_000)}` : nextText;
2427
+ this.statusContainer.clear();
2428
+ this.statusContainer.addChild(this.autoCompactionLoader);
2429
+ this.statusContainer.addChild(new Spacer(1));
2430
+ this.statusContainer.addChild(new Text(theme.fg("muted", preview), 1, 0));
2323
2431
  this.ui.requestRender();
2324
2432
  break;
2325
2433
  }
@@ -2334,6 +2442,7 @@ export class InteractiveMode {
2334
2442
  if (this.autoCompactionLoader) {
2335
2443
  this.autoCompactionLoader.stop();
2336
2444
  this.autoCompactionLoader = undefined;
2445
+ this.autoCompactionProgressText = "";
2337
2446
  this.statusContainer.clear();
2338
2447
  }
2339
2448
  if (event.aborted) {
@@ -2347,7 +2456,7 @@ export class InteractiveMode {
2347
2456
  else if (event.result) {
2348
2457
  this.chatContainer.clear();
2349
2458
  this.rebuildChatFromMessages();
2350
- this.addMessageToChat(createCompactionSummaryMessage(event.result.summary, event.result.tokensBefore, new Date().toISOString()));
2459
+ this.addMessageToChat(createCompactionSummaryMessage(event.result.summary, event.result.tokensBefore, new Date().toISOString(), event.result.details));
2351
2460
  this.footer.invalidate();
2352
2461
  }
2353
2462
  else if (event.errorMessage) {
@@ -2364,11 +2473,11 @@ export class InteractiveMode {
2364
2473
  break;
2365
2474
  }
2366
2475
  case "auto_retry_start": {
2367
- // Set up escape to abort retry
2368
- this.retryEscapeHandler = this.defaultEditor.onEscape;
2369
- this.defaultEditor.onEscape = () => {
2370
- this.session.abortRetry();
2371
- };
2476
+ // During retry waits, isStreaming flips false between attempts. The main Esc handler
2477
+ // keys off both isStreaming and retryAttempt so we keep the same close-out path here;
2478
+ // no separate retry-only handler is installed (the prior one only called
2479
+ // session.abortRetry() and left queued steering messages stranded).
2480
+ this.retryEscapeHandler = undefined;
2372
2481
  // Show retry indicator
2373
2482
  this.statusContainer.clear();
2374
2483
  this.retryCountdown?.dispose();
@@ -2932,6 +3041,7 @@ export class InteractiveMode {
2932
3041
  showError(errorMessage) {
2933
3042
  this.chatContainer.addChild(new Spacer(1));
2934
3043
  this.chatContainer.addChild(new Text(theme.fg("error", `Error: ${errorMessage}`), 1, 0));
3044
+ this.chatContainer.addChild(new Spacer(1));
2935
3045
  this.ui.requestRender();
2936
3046
  }
2937
3047
  showWarning(warningMessage) {
@@ -3021,7 +3131,7 @@ export class InteractiveMode {
3021
3131
  if (allQueued.length === 0) {
3022
3132
  this.updatePendingMessagesDisplay();
3023
3133
  if (options?.abort) {
3024
- this.agent.abort();
3134
+ void this.session.abort();
3025
3135
  }
3026
3136
  return 0;
3027
3137
  }
@@ -3031,7 +3141,36 @@ export class InteractiveMode {
3031
3141
  this.editor.setText(combinedText);
3032
3142
  this.updatePendingMessagesDisplay();
3033
3143
  if (options?.abort) {
3034
- this.agent.abort();
3144
+ void this.session.abort();
3145
+ }
3146
+ return allQueued.length;
3147
+ }
3148
+ /**
3149
+ * User-abort path that drains the queue, aborts the active run (and any in-flight retry),
3150
+ * waits for settle, then re-fires queued messages as a single fresh prompt.
3151
+ *
3152
+ * Behavior contract:
3153
+ * - clearAllQueues() is called synchronously so the pending-messages display empties immediately.
3154
+ * - `await session.abort()` ensures the previous run is fully idle before the fresh prompt fires,
3155
+ * so it starts a new turn instead of being re-queued as steering behind the dying run.
3156
+ * - When no messages were queued, the helper is just a sync clear + abort (no fresh prompt).
3157
+ * - Failures from the fresh prompt are surfaced via showError but do not throw.
3158
+ */
3159
+ async abortAndFireQueuedMessages() {
3160
+ const { steering, followUp } = this.clearAllQueues();
3161
+ const allQueued = [...steering, ...followUp];
3162
+ this.updatePendingMessagesDisplay();
3163
+ await this.session.abort();
3164
+ if (allQueued.length === 0) {
3165
+ return 0;
3166
+ }
3167
+ const queuedText = allQueued.join("\n\n");
3168
+ try {
3169
+ await this.session.prompt(queuedText);
3170
+ }
3171
+ catch (error) {
3172
+ const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
3173
+ this.showError(`Failed to fire queued message: ${errorMessage}`);
3035
3174
  }
3036
3175
  return allQueued.length;
3037
3176
  }
@@ -3651,11 +3790,7 @@ export class InteractiveMode {
3651
3790
  });
3652
3791
  }
3653
3792
  async handleResumeSession(sessionPath, options) {
3654
- if (this.loadingAnimation) {
3655
- this.loadingAnimation.stop();
3656
- this.loadingAnimation = undefined;
3657
- }
3658
- this.statusContainer.clear();
3793
+ this.stopWorkingLoader();
3659
3794
  try {
3660
3795
  const result = await this.runtimeHost.switchSession(sessionPath, {
3661
3796
  withSession: options?.withSession,
@@ -4140,11 +4275,7 @@ export class InteractiveMode {
4140
4275
  return;
4141
4276
  }
4142
4277
  try {
4143
- if (this.loadingAnimation) {
4144
- this.loadingAnimation.stop();
4145
- this.loadingAnimation = undefined;
4146
- }
4147
- this.statusContainer.clear();
4278
+ this.stopWorkingLoader();
4148
4279
  const result = await this.runtimeHost.importFromJsonl(inputPath);
4149
4280
  if (result.cancelled) {
4150
4281
  this.showStatus("Import cancelled");
@@ -4299,32 +4430,7 @@ export class InteractiveMode {
4299
4430
  handleSessionCommand() {
4300
4431
  const stats = this.session.getSessionStats();
4301
4432
  const sessionName = this.sessionManager.getSessionName();
4302
- let info = `${theme.bold("Session Info")}\n\n`;
4303
- if (sessionName) {
4304
- info += `${theme.fg("dim", "Name:")} ${sessionName}\n`;
4305
- }
4306
- info += `${theme.fg("dim", "File:")} ${stats.sessionFile ?? "In-memory"}\n`;
4307
- info += `${theme.fg("dim", "ID:")} ${stats.sessionId}\n\n`;
4308
- info += `${theme.bold("Messages")}\n`;
4309
- info += `${theme.fg("dim", "User:")} ${stats.userMessages}\n`;
4310
- info += `${theme.fg("dim", "Assistant:")} ${stats.assistantMessages}\n`;
4311
- info += `${theme.fg("dim", "Tool Calls:")} ${stats.toolCalls}\n`;
4312
- info += `${theme.fg("dim", "Tool Results:")} ${stats.toolResults}\n`;
4313
- info += `${theme.fg("dim", "Total:")} ${stats.totalMessages}\n\n`;
4314
- info += `${theme.bold("Tokens")}\n`;
4315
- info += `${theme.fg("dim", "Input:")} ${stats.tokens.input.toLocaleString()}\n`;
4316
- info += `${theme.fg("dim", "Output:")} ${stats.tokens.output.toLocaleString()}\n`;
4317
- if (stats.tokens.cacheRead > 0) {
4318
- info += `${theme.fg("dim", "Cache Read:")} ${stats.tokens.cacheRead.toLocaleString()}\n`;
4319
- }
4320
- if (stats.tokens.cacheWrite > 0) {
4321
- info += `${theme.fg("dim", "Cache Write:")} ${stats.tokens.cacheWrite.toLocaleString()}\n`;
4322
- }
4323
- info += `${theme.fg("dim", "Total:")} ${stats.tokens.total.toLocaleString()}\n`;
4324
- if (stats.cost > 0) {
4325
- info += `\n${theme.bold("Cost")}\n`;
4326
- info += `${theme.fg("dim", "Total:")} ${stats.cost.toFixed(4)}`;
4327
- }
4433
+ const info = formatSessionInfo(stats, sessionName);
4328
4434
  this.chatContainer.addChild(new Spacer(1));
4329
4435
  this.chatContainer.addChild(new Text(info, 1, 0));
4330
4436
  this.ui.requestRender();
@@ -4468,11 +4574,7 @@ export class InteractiveMode {
4468
4574
  this.ui.requestRender();
4469
4575
  }
4470
4576
  async handleClearCommand() {
4471
- if (this.loadingAnimation) {
4472
- this.loadingAnimation.stop();
4473
- this.loadingAnimation = undefined;
4474
- }
4475
- this.statusContainer.clear();
4577
+ this.stopWorkingLoader();
4476
4578
  try {
4477
4579
  const result = await this.runtimeHost.newSession();
4478
4580
  if (result.cancelled) {
@@ -4606,11 +4708,7 @@ export class InteractiveMode {
4606
4708
  this.showWarning("Nothing to compact (no messages yet)");
4607
4709
  return;
4608
4710
  }
4609
- if (this.loadingAnimation) {
4610
- this.loadingAnimation.stop();
4611
- this.loadingAnimation = undefined;
4612
- }
4613
- this.statusContainer.clear();
4711
+ this.stopWorkingLoader();
4614
4712
  try {
4615
4713
  await this.session.compact(customInstructions);
4616
4714
  }
@@ -4623,10 +4721,7 @@ export class InteractiveMode {
4623
4721
  if (this.settingsManager.getShowTerminalProgress()) {
4624
4722
  this.ui.terminal.setProgress(false);
4625
4723
  }
4626
- if (this.loadingAnimation) {
4627
- this.loadingAnimation.stop();
4628
- this.loadingAnimation = undefined;
4629
- }
4724
+ this.stopWorkingLoader();
4630
4725
  this.clearExtensionTerminalInputListeners();
4631
4726
  this.footer.dispose();
4632
4727
  this.footerDataProvider.dispose();