@code-yeongyu/senpi 2026.5.13 → 2026.5.15-2

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 (274) hide show
  1. package/CHANGELOG.md +1109 -1150
  2. package/README.md +1 -2
  3. package/dist/core/agent-session.d.ts +9 -0
  4. package/dist/core/agent-session.d.ts.map +1 -1
  5. package/dist/core/agent-session.js +121 -12
  6. package/dist/core/agent-session.js.map +1 -1
  7. package/dist/core/bash-executor.d.ts.map +1 -1
  8. package/dist/core/bash-executor.js +1 -1
  9. package/dist/core/bash-executor.js.map +1 -1
  10. package/dist/core/compaction/compaction.d.ts.map +1 -1
  11. package/dist/core/compaction/compaction.js +2 -2
  12. package/dist/core/compaction/compaction.js.map +1 -1
  13. package/dist/core/dynamic-prompt/verification.d.ts +31 -0
  14. package/dist/core/dynamic-prompt/verification.d.ts.map +1 -1
  15. package/dist/core/dynamic-prompt/verification.js +41 -0
  16. package/dist/core/dynamic-prompt/verification.js.map +1 -1
  17. package/dist/core/export-html/index.d.ts.map +1 -1
  18. package/dist/core/export-html/index.js +8 -1
  19. package/dist/core/export-html/index.js.map +1 -1
  20. package/dist/core/extensions/builtin/anthropic-web-search/index.d.ts.map +1 -1
  21. package/dist/core/extensions/builtin/anthropic-web-search/index.js +20 -0
  22. package/dist/core/extensions/builtin/anthropic-web-search/index.js.map +1 -1
  23. package/dist/core/extensions/builtin/compaction/index.d.ts.map +1 -1
  24. package/dist/core/extensions/builtin/compaction/index.js +157 -29
  25. package/dist/core/extensions/builtin/compaction/index.js.map +1 -1
  26. package/dist/core/extensions/builtin/compaction/openai-remote.d.ts +197 -0
  27. package/dist/core/extensions/builtin/compaction/openai-remote.d.ts.map +1 -0
  28. package/dist/core/extensions/builtin/compaction/openai-remote.js +690 -0
  29. package/dist/core/extensions/builtin/compaction/openai-remote.js.map +1 -0
  30. package/dist/core/extensions/builtin/compaction/prompts.d.ts +3 -3
  31. package/dist/core/extensions/builtin/compaction/prompts.d.ts.map +1 -1
  32. package/dist/core/extensions/builtin/compaction/prompts.js +0 -22
  33. package/dist/core/extensions/builtin/compaction/prompts.js.map +1 -1
  34. package/dist/core/extensions/builtin/compaction/repair-tool-pairs.d.ts +4 -0
  35. package/dist/core/extensions/builtin/compaction/repair-tool-pairs.d.ts.map +1 -0
  36. package/dist/core/extensions/builtin/compaction/repair-tool-pairs.js +48 -0
  37. package/dist/core/extensions/builtin/compaction/repair-tool-pairs.js.map +1 -0
  38. package/dist/core/extensions/builtin/compaction/speculative.d.ts +3 -1
  39. package/dist/core/extensions/builtin/compaction/speculative.d.ts.map +1 -1
  40. package/dist/core/extensions/builtin/compaction/speculative.js +82 -33
  41. package/dist/core/extensions/builtin/compaction/speculative.js.map +1 -1
  42. package/dist/core/extensions/builtin/compaction/todo-bridge.d.ts +8 -0
  43. package/dist/core/extensions/builtin/compaction/todo-bridge.d.ts.map +1 -1
  44. package/dist/core/extensions/builtin/compaction/todo-bridge.js +12 -6
  45. package/dist/core/extensions/builtin/compaction/todo-bridge.js.map +1 -1
  46. package/dist/core/extensions/builtin/index.d.ts.map +1 -1
  47. package/dist/core/extensions/builtin/index.js +0 -20
  48. package/dist/core/extensions/builtin/index.js.map +1 -1
  49. package/dist/core/extensions/builtin/openai-web-search/index.d.ts.map +1 -1
  50. package/dist/core/extensions/builtin/openai-web-search/index.js +28 -0
  51. package/dist/core/extensions/builtin/openai-web-search/index.js.map +1 -1
  52. package/dist/core/extensions/builtin/permission-system/prompt.d.ts.map +1 -1
  53. package/dist/core/extensions/builtin/permission-system/prompt.js +0 -5
  54. package/dist/core/extensions/builtin/permission-system/prompt.js.map +1 -1
  55. package/dist/core/extensions/builtin/system-messages.d.ts +7 -7
  56. package/dist/core/extensions/builtin/system-messages.d.ts.map +1 -1
  57. package/dist/core/extensions/builtin/system-messages.js +10 -10
  58. package/dist/core/extensions/builtin/system-messages.js.map +1 -1
  59. package/dist/core/extensions/builtin/todotools/continuation/prompt.d.ts +1 -1
  60. package/dist/core/extensions/builtin/todotools/continuation/prompt.d.ts.map +1 -1
  61. package/dist/core/extensions/builtin/todotools/continuation/prompt.js +1 -1
  62. package/dist/core/extensions/builtin/todotools/continuation/prompt.js.map +1 -1
  63. package/dist/core/extensions/builtin/todotools/state.d.ts +1 -1
  64. package/dist/core/extensions/builtin/todotools/state.d.ts.map +1 -1
  65. package/dist/core/extensions/builtin/todotools/state.js +2 -2
  66. package/dist/core/extensions/builtin/todotools/state.js.map +1 -1
  67. package/dist/core/extensions/builtin/todotools/system-messages.d.ts +3 -3
  68. package/dist/core/extensions/builtin/todotools/system-messages.d.ts.map +1 -1
  69. package/dist/core/extensions/builtin/todotools/system-messages.js +6 -6
  70. package/dist/core/extensions/builtin/todotools/system-messages.js.map +1 -1
  71. package/dist/core/extensions/builtin/tool-pair-guard/index.d.ts +1 -1
  72. package/dist/core/extensions/builtin/tool-pair-guard/index.d.ts.map +1 -1
  73. package/dist/core/extensions/builtin/tool-pair-guard/index.js +8 -4
  74. package/dist/core/extensions/builtin/tool-pair-guard/index.js.map +1 -1
  75. package/dist/core/extensions/builtin/tool-pair-guard/sanitize-openai-chat-completions-payload.d.ts +3 -0
  76. package/dist/core/extensions/builtin/tool-pair-guard/sanitize-openai-chat-completions-payload.d.ts.map +1 -0
  77. package/dist/core/extensions/builtin/tool-pair-guard/sanitize-openai-chat-completions-payload.js +89 -0
  78. package/dist/core/extensions/builtin/tool-pair-guard/sanitize-openai-chat-completions-payload.js.map +1 -0
  79. package/dist/core/extensions/builtin/tool-pair-guard/sanitize-openai-responses-payload.d.ts +3 -0
  80. package/dist/core/extensions/builtin/tool-pair-guard/sanitize-openai-responses-payload.d.ts.map +1 -0
  81. package/dist/core/extensions/builtin/tool-pair-guard/sanitize-openai-responses-payload.js +122 -0
  82. package/dist/core/extensions/builtin/tool-pair-guard/sanitize-openai-responses-payload.js.map +1 -0
  83. package/dist/core/extensions/loader.d.ts.map +1 -1
  84. package/dist/core/extensions/loader.js +2 -0
  85. package/dist/core/extensions/loader.js.map +1 -1
  86. package/dist/core/extensions/runner.d.ts +3 -0
  87. package/dist/core/extensions/runner.d.ts.map +1 -1
  88. package/dist/core/extensions/runner.js +18 -0
  89. package/dist/core/extensions/runner.js.map +1 -1
  90. package/dist/core/extensions/types.d.ts +22 -0
  91. package/dist/core/extensions/types.d.ts.map +1 -1
  92. package/dist/core/extensions/types.js.map +1 -1
  93. package/dist/core/messages.d.ts +3 -3
  94. package/dist/core/messages.d.ts.map +1 -1
  95. package/dist/core/messages.js +5 -10
  96. package/dist/core/messages.js.map +1 -1
  97. package/dist/core/resource-loader.d.ts.map +1 -1
  98. package/dist/core/resource-loader.js +0 -9
  99. package/dist/core/resource-loader.js.map +1 -1
  100. package/dist/core/sdk.d.ts +2 -2
  101. package/dist/core/sdk.d.ts.map +1 -1
  102. package/dist/core/sdk.js +8 -23
  103. package/dist/core/sdk.js.map +1 -1
  104. package/dist/core/session-manager.d.ts.map +1 -1
  105. package/dist/core/session-manager.js +1 -1
  106. package/dist/core/session-manager.js.map +1 -1
  107. package/dist/core/settings-manager.d.ts +0 -5
  108. package/dist/core/settings-manager.d.ts.map +1 -1
  109. package/dist/core/settings-manager.js.map +1 -1
  110. package/dist/core/thinking-levels.d.ts +6 -0
  111. package/dist/core/thinking-levels.d.ts.map +1 -0
  112. package/dist/core/thinking-levels.js +36 -0
  113. package/dist/core/thinking-levels.js.map +1 -0
  114. package/dist/core/tools/bash.d.ts.map +1 -1
  115. package/dist/core/tools/bash.js +15 -1
  116. package/dist/core/tools/bash.js.map +1 -1
  117. package/dist/core/tools/render-utils.d.ts.map +1 -1
  118. package/dist/core/tools/render-utils.js +1 -1
  119. package/dist/core/tools/render-utils.js.map +1 -1
  120. package/dist/main.d.ts.map +1 -1
  121. package/dist/main.js +3 -2
  122. package/dist/main.js.map +1 -1
  123. package/dist/modes/interactive/components/assistant-message.d.ts +0 -3
  124. package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
  125. package/dist/modes/interactive/components/assistant-message.js +3 -22
  126. package/dist/modes/interactive/components/assistant-message.js.map +1 -1
  127. package/dist/modes/interactive/components/bash-execution.d.ts.map +1 -1
  128. package/dist/modes/interactive/components/bash-execution.js +1 -1
  129. package/dist/modes/interactive/components/bash-execution.js.map +1 -1
  130. package/dist/modes/interactive/components/compaction-summary-message.d.ts.map +1 -1
  131. package/dist/modes/interactive/components/compaction-summary-message.js +20 -2
  132. package/dist/modes/interactive/components/compaction-summary-message.js.map +1 -1
  133. package/dist/modes/interactive/components/extension-selector.d.ts +2 -0
  134. package/dist/modes/interactive/components/extension-selector.d.ts.map +1 -1
  135. package/dist/modes/interactive/components/extension-selector.js +6 -1
  136. package/dist/modes/interactive/components/extension-selector.js.map +1 -1
  137. package/dist/modes/interactive/components/keybinding-hints.d.ts.map +1 -1
  138. package/dist/modes/interactive/components/keybinding-hints.js +3 -1
  139. package/dist/modes/interactive/components/keybinding-hints.js.map +1 -1
  140. package/dist/modes/interactive/interactive-mode.d.ts +23 -0
  141. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  142. package/dist/modes/interactive/interactive-mode.js +139 -54
  143. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  144. package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  145. package/dist/modes/interactive/theme/theme.js +2 -2
  146. package/dist/modes/interactive/theme/theme.js.map +1 -1
  147. package/dist/modes/print-mode.d.ts.map +1 -1
  148. package/dist/modes/print-mode.js +3 -11
  149. package/dist/modes/print-mode.js.map +1 -1
  150. package/dist/modes/provider-native-rendering.d.ts +5 -0
  151. package/dist/modes/provider-native-rendering.d.ts.map +1 -0
  152. package/dist/modes/provider-native-rendering.js +247 -0
  153. package/dist/modes/provider-native-rendering.js.map +1 -0
  154. package/dist/utils/ansi.d.ts +2 -0
  155. package/dist/utils/ansi.d.ts.map +1 -0
  156. package/dist/utils/ansi.js +52 -0
  157. package/dist/utils/ansi.js.map +1 -0
  158. package/dist/utils/html.d.ts +7 -0
  159. package/dist/utils/html.d.ts.map +1 -0
  160. package/dist/utils/html.js +40 -0
  161. package/dist/utils/html.js.map +1 -0
  162. package/dist/utils/mime.d.ts +1 -0
  163. package/dist/utils/mime.d.ts.map +1 -1
  164. package/dist/utils/mime.js +59 -16
  165. package/dist/utils/mime.js.map +1 -1
  166. package/dist/utils/syntax-highlight.d.ts +12 -0
  167. package/dist/utils/syntax-highlight.d.ts.map +1 -0
  168. package/dist/utils/syntax-highlight.js +118 -0
  169. package/dist/utils/syntax-highlight.js.map +1 -0
  170. package/dist/utils/tools-manager.d.ts.map +1 -1
  171. package/dist/utils/tools-manager.js +76 -7
  172. package/dist/utils/tools-manager.js.map +1 -1
  173. package/docs/extensions.md +0 -1
  174. package/docs/index.md +0 -1
  175. package/docs/sdk.md +25 -44
  176. package/docs/settings.md +1 -29
  177. package/docs/termux.md +2 -2
  178. package/docs/usage.md +1 -1
  179. package/examples/README.md +1 -1
  180. package/examples/extensions/README.md +0 -1
  181. package/examples/extensions/overlay-qa-tests.ts +1 -1
  182. package/examples/sdk/01-minimal.ts +14 -10
  183. package/examples/sdk/02-custom-model.ts +12 -8
  184. package/examples/sdk/03-custom-prompt.ts +24 -16
  185. package/examples/sdk/04-skills.ts +2 -2
  186. package/examples/sdk/05-tools.ts +8 -4
  187. package/examples/sdk/06-extensions.ts +11 -7
  188. package/examples/sdk/07-context-files.ts +2 -2
  189. package/examples/sdk/08-prompt-templates.ts +2 -2
  190. package/examples/sdk/09-api-keys-and-oauth.ts +8 -4
  191. package/examples/sdk/10-settings.ts +4 -4
  192. package/examples/sdk/11-sessions.ts +4 -0
  193. package/examples/sdk/12-full-control.ts +11 -7
  194. package/examples/sdk/README.md +6 -9
  195. package/package.json +7 -12
  196. package/dist/core/extensions/builtin/anthropic-code-execution/index.d.ts +0 -7
  197. package/dist/core/extensions/builtin/anthropic-code-execution/index.d.ts.map +0 -1
  198. package/dist/core/extensions/builtin/anthropic-code-execution/index.js +0 -79
  199. package/dist/core/extensions/builtin/anthropic-code-execution/index.js.map +0 -1
  200. package/dist/core/extensions/builtin/anthropic-computer-use/index.d.ts +0 -53
  201. package/dist/core/extensions/builtin/anthropic-computer-use/index.d.ts.map +0 -1
  202. package/dist/core/extensions/builtin/anthropic-computer-use/index.js +0 -676
  203. package/dist/core/extensions/builtin/anthropic-computer-use/index.js.map +0 -1
  204. package/dist/core/extensions/builtin/anthropic-text-editor/index.d.ts +0 -25
  205. package/dist/core/extensions/builtin/anthropic-text-editor/index.d.ts.map +0 -1
  206. package/dist/core/extensions/builtin/anthropic-text-editor/index.js +0 -244
  207. package/dist/core/extensions/builtin/anthropic-text-editor/index.js.map +0 -1
  208. package/dist/core/extensions/builtin/anthropic-tool-search/index.d.ts +0 -6
  209. package/dist/core/extensions/builtin/anthropic-tool-search/index.d.ts.map +0 -1
  210. package/dist/core/extensions/builtin/anthropic-tool-search/index.js +0 -112
  211. package/dist/core/extensions/builtin/anthropic-tool-search/index.js.map +0 -1
  212. package/dist/core/extensions/builtin/background-task/cancel-tool.d.ts +0 -10
  213. package/dist/core/extensions/builtin/background-task/cancel-tool.d.ts.map +0 -1
  214. package/dist/core/extensions/builtin/background-task/cancel-tool.js +0 -109
  215. package/dist/core/extensions/builtin/background-task/cancel-tool.js.map +0 -1
  216. package/dist/core/extensions/builtin/background-task/index.d.ts +0 -3
  217. package/dist/core/extensions/builtin/background-task/index.d.ts.map +0 -1
  218. package/dist/core/extensions/builtin/background-task/index.js +0 -207
  219. package/dist/core/extensions/builtin/background-task/index.js.map +0 -1
  220. package/dist/core/extensions/builtin/background-task/manager.d.ts +0 -17
  221. package/dist/core/extensions/builtin/background-task/manager.d.ts.map +0 -1
  222. package/dist/core/extensions/builtin/background-task/manager.js +0 -114
  223. package/dist/core/extensions/builtin/background-task/manager.js.map +0 -1
  224. package/dist/core/extensions/builtin/background-task/notification.d.ts +0 -22
  225. package/dist/core/extensions/builtin/background-task/notification.d.ts.map +0 -1
  226. package/dist/core/extensions/builtin/background-task/notification.js +0 -105
  227. package/dist/core/extensions/builtin/background-task/notification.js.map +0 -1
  228. package/dist/core/extensions/builtin/background-task/output-tool.d.ts +0 -11
  229. package/dist/core/extensions/builtin/background-task/output-tool.d.ts.map +0 -1
  230. package/dist/core/extensions/builtin/background-task/output-tool.js +0 -127
  231. package/dist/core/extensions/builtin/background-task/output-tool.js.map +0 -1
  232. package/dist/core/extensions/builtin/background-task/spawner.d.ts +0 -8
  233. package/dist/core/extensions/builtin/background-task/spawner.d.ts.map +0 -1
  234. package/dist/core/extensions/builtin/background-task/spawner.js +0 -207
  235. package/dist/core/extensions/builtin/background-task/spawner.js.map +0 -1
  236. package/dist/core/extensions/builtin/background-task/task-tool.d.ts +0 -20
  237. package/dist/core/extensions/builtin/background-task/task-tool.d.ts.map +0 -1
  238. package/dist/core/extensions/builtin/background-task/task-tool.js +0 -302
  239. package/dist/core/extensions/builtin/background-task/task-tool.js.map +0 -1
  240. package/dist/core/extensions/builtin/background-task/types.d.ts +0 -72
  241. package/dist/core/extensions/builtin/background-task/types.d.ts.map +0 -1
  242. package/dist/core/extensions/builtin/background-task/types.js +0 -32
  243. package/dist/core/extensions/builtin/background-task/types.js.map +0 -1
  244. package/dist/core/extensions/builtin/google-code-execution/index.d.ts +0 -7
  245. package/dist/core/extensions/builtin/google-code-execution/index.d.ts.map +0 -1
  246. package/dist/core/extensions/builtin/google-code-execution/index.js +0 -73
  247. package/dist/core/extensions/builtin/google-code-execution/index.js.map +0 -1
  248. package/dist/core/extensions/builtin/google-google-search/index.d.ts +0 -7
  249. package/dist/core/extensions/builtin/google-google-search/index.d.ts.map +0 -1
  250. package/dist/core/extensions/builtin/google-google-search/index.js +0 -83
  251. package/dist/core/extensions/builtin/google-google-search/index.js.map +0 -1
  252. package/dist/core/extensions/builtin/google-url-context/index.d.ts +0 -7
  253. package/dist/core/extensions/builtin/google-url-context/index.d.ts.map +0 -1
  254. package/dist/core/extensions/builtin/google-url-context/index.js +0 -82
  255. package/dist/core/extensions/builtin/google-url-context/index.js.map +0 -1
  256. package/dist/core/extensions/builtin/openai-api-parallel-tool-calls/index.d.ts +0 -6
  257. package/dist/core/extensions/builtin/openai-api-parallel-tool-calls/index.d.ts.map +0 -1
  258. package/dist/core/extensions/builtin/openai-api-parallel-tool-calls/index.js +0 -57
  259. package/dist/core/extensions/builtin/openai-api-parallel-tool-calls/index.js.map +0 -1
  260. package/dist/core/extensions/builtin/openai-code-interpreter/index.d.ts +0 -10
  261. package/dist/core/extensions/builtin/openai-code-interpreter/index.d.ts.map +0 -1
  262. package/dist/core/extensions/builtin/openai-code-interpreter/index.js +0 -95
  263. package/dist/core/extensions/builtin/openai-code-interpreter/index.js.map +0 -1
  264. package/docs/agents.md +0 -348
  265. package/examples/extensions/subagent/README.md +0 -172
  266. package/examples/extensions/subagent/agents/planner.md +0 -37
  267. package/examples/extensions/subagent/agents/reviewer.md +0 -35
  268. package/examples/extensions/subagent/agents/scout.md +0 -50
  269. package/examples/extensions/subagent/agents/worker.md +0 -24
  270. package/examples/extensions/subagent/agents.ts +0 -126
  271. package/examples/extensions/subagent/index.ts +0 -987
  272. package/examples/extensions/subagent/prompts/implement-and-review.md +0 -10
  273. package/examples/extensions/subagent/prompts/implement.md +0 -10
  274. package/examples/extensions/subagent/prompts/scout-and-plan.md +0 -9
@@ -9,7 +9,7 @@ import * as path from "node:path";
9
9
  import { getProviders, } from "@earendil-works/pi-ai";
10
10
  import { CombinedAutocompleteProvider, Container, fuzzyFilter, getCapabilities, hyperlink, Loader, Markdown, matchesKey, ProcessTerminal, Spacer, setKeybindings, Text, TruncatedText, TUI, visibleWidth, } from "@earendil-works/pi-tui";
11
11
  import { spawn, spawnSync } from "child_process";
12
- import { APP_NAME, APP_TITLE, getAuthPath, getDebugLogPath, getDocsPath, getShareViewerUrl, VERSION, } from "../../config.js";
12
+ import { APP_NAME, APP_TITLE, expandTildePath, getAuthPath, getDebugLogPath, getDocsPath, getShareViewerUrl, VERSION, } from "../../config.js";
13
13
  import { parseSkillBlock } from "../../core/agent-session.js";
14
14
  import { SessionImportFileNotFoundError } from "../../core/agent-session-runtime.js";
15
15
  import { FooterDataProvider } from "../../core/footer-data-provider.js";
@@ -76,6 +76,7 @@ class ExpandableText extends Text {
76
76
  }
77
77
  }
78
78
  const DEAD_TERMINAL_ERROR_CODES = new Set(["EIO", "EPIPE", "ENOTCONN"]);
79
+ const DEFAULT_WORKING_STATUS_REFRESH_INTERVAL_MS = 600;
79
80
  function isDeadTerminalError(error) {
80
81
  if (!error || typeof error !== "object" || !("code" in error)) {
81
82
  return false;
@@ -83,6 +84,23 @@ function isDeadTerminalError(error) {
83
84
  const code = error.code;
84
85
  return code !== undefined && DEAD_TERMINAL_ERROR_CODES.has(code);
85
86
  }
87
+ export function formatWorkingElapsedSeconds(elapsedSeconds) {
88
+ const totalSeconds = Math.max(0, Math.floor(elapsedSeconds));
89
+ const seconds = totalSeconds % 60;
90
+ const totalMinutes = Math.floor(totalSeconds / 60);
91
+ if (totalSeconds < 60) {
92
+ return `${totalSeconds}s`;
93
+ }
94
+ if (totalSeconds < 3600) {
95
+ return `${totalMinutes}m ${seconds.toString().padStart(2, "0")}s`;
96
+ }
97
+ const hours = Math.floor(totalMinutes / 60);
98
+ const minutes = totalMinutes % 60;
99
+ return `${hours}h ${minutes.toString().padStart(2, "0")}m ${seconds.toString().padStart(2, "0")}s`;
100
+ }
101
+ export function formatWorkingStatusMessage(message, elapsedSeconds, interruptKey) {
102
+ return `${message} (${formatWorkingElapsedSeconds(elapsedSeconds)} • ${interruptKey} to interrupt)`;
103
+ }
86
104
  const ANTHROPIC_SUBSCRIPTION_AUTH_WARNING = "Anthropic subscription auth is active. Third-party harness usage draws from extra usage and is billed per token, not your Claude plan limits. Manage extra usage at https://claude.ai/settings/usage.";
87
105
  function isAnthropicSubscriptionAuthKey(apiKey) {
88
106
  return typeof apiKey === "string" && apiKey.startsWith("sk-ant-oat");
@@ -129,7 +147,9 @@ export class InteractiveMode {
129
147
  workingMessage = undefined;
130
148
  workingVisible = true;
131
149
  workingIndicatorOptions = undefined;
132
- defaultWorkingMessage = "Working...";
150
+ workingStartedAt = undefined;
151
+ workingElapsedIntervalId = undefined;
152
+ defaultWorkingMessage = "Working";
133
153
  defaultHiddenThinkingLabel = "Thinking...";
134
154
  hiddenThinkingLabel = this.defaultHiddenThinkingLabel;
135
155
  lastSigintTime = 0;
@@ -166,6 +186,7 @@ export class InteractiveMode {
166
186
  // Auto-compaction state
167
187
  autoCompactionLoader = undefined;
168
188
  autoCompactionEscapeHandler;
189
+ autoCompactionProgressText = "";
169
190
  // Auto-retry state
170
191
  retryLoader = undefined;
171
192
  retryCountdown = undefined;
@@ -1097,11 +1118,7 @@ export class InteractiveMode {
1097
1118
  commandContextActions: {
1098
1119
  waitForIdle: () => this.session.agent.waitForIdle(),
1099
1120
  newSession: async (options) => {
1100
- if (this.loadingAnimation) {
1101
- this.loadingAnimation.stop();
1102
- this.loadingAnimation = undefined;
1103
- }
1104
- this.statusContainer.clear();
1121
+ this.stopWorkingLoader();
1105
1122
  try {
1106
1123
  const result = await this.runtimeHost.newSession(options);
1107
1124
  if (!result.cancelled) {
@@ -1285,10 +1302,43 @@ export class InteractiveMode {
1285
1302
  getWorkingLoaderMessage() {
1286
1303
  return this.workingMessage ?? this.defaultWorkingMessage;
1287
1304
  }
1305
+ getWorkingElapsedSeconds() {
1306
+ if (this.workingStartedAt === undefined) {
1307
+ return 0;
1308
+ }
1309
+ return Math.max(0, Math.floor((Date.now() - this.workingStartedAt) / 1000));
1310
+ }
1311
+ getWorkingStatusMessage() {
1312
+ return formatWorkingStatusMessage(this.getWorkingLoaderMessage(), this.getWorkingElapsedSeconds(), keyText("app.interrupt"));
1313
+ }
1314
+ refreshWorkingLoaderMessage() {
1315
+ this.loadingAnimation?.setMessage(this.getWorkingStatusMessage());
1316
+ }
1317
+ startWorkingElapsedTimer() {
1318
+ this.stopWorkingElapsedTimer();
1319
+ this.workingStartedAt = Date.now();
1320
+ this.workingElapsedIntervalId = setInterval(() => {
1321
+ this.refreshWorkingLoaderMessage();
1322
+ }, DEFAULT_WORKING_STATUS_REFRESH_INTERVAL_MS);
1323
+ }
1324
+ stopWorkingElapsedTimer() {
1325
+ if (this.workingElapsedIntervalId) {
1326
+ clearInterval(this.workingElapsedIntervalId);
1327
+ this.workingElapsedIntervalId = undefined;
1328
+ }
1329
+ }
1330
+ getWorkingIndicatorOptions() {
1331
+ return (this.workingIndicatorOptions ?? {
1332
+ frames: [theme.fg("accent", "•"), theme.fg("muted", "◦")],
1333
+ intervalMs: DEFAULT_WORKING_STATUS_REFRESH_INTERVAL_MS,
1334
+ });
1335
+ }
1288
1336
  createWorkingLoader() {
1289
- return new Loader(this.ui, (spinner) => theme.fg("accent", spinner), (text) => theme.fg("muted", text), this.getWorkingLoaderMessage(), this.workingIndicatorOptions);
1337
+ return new Loader(this.ui, (spinner) => theme.fg("accent", spinner), (text) => theme.fg("muted", text), this.getWorkingStatusMessage(), this.getWorkingIndicatorOptions());
1290
1338
  }
1291
1339
  stopWorkingLoader() {
1340
+ this.stopWorkingElapsedTimer();
1341
+ this.workingStartedAt = undefined;
1292
1342
  if (this.loadingAnimation) {
1293
1343
  this.loadingAnimation.stop();
1294
1344
  this.loadingAnimation = undefined;
@@ -1304,6 +1354,7 @@ export class InteractiveMode {
1304
1354
  }
1305
1355
  if (this.session.isStreaming && !this.loadingAnimation) {
1306
1356
  this.statusContainer.clear();
1357
+ this.startWorkingElapsedTimer();
1307
1358
  this.loadingAnimation = this.createWorkingLoader();
1308
1359
  this.statusContainer.addChild(this.loadingAnimation);
1309
1360
  }
@@ -1311,7 +1362,7 @@ export class InteractiveMode {
1311
1362
  }
1312
1363
  setWorkingIndicator(options) {
1313
1364
  this.workingIndicatorOptions = options;
1314
- this.loadingAnimation?.setIndicator(options);
1365
+ this.loadingAnimation?.setIndicator(this.getWorkingIndicatorOptions());
1315
1366
  this.ui.requestRender();
1316
1367
  }
1317
1368
  setHiddenThinkingLabel(label) {
@@ -1399,9 +1450,7 @@ export class InteractiveMode {
1399
1450
  this.workingMessage = undefined;
1400
1451
  this.workingVisible = true;
1401
1452
  this.setWorkingIndicator();
1402
- if (this.loadingAnimation) {
1403
- this.loadingAnimation.setMessage(`${this.defaultWorkingMessage} (${keyText("app.interrupt")} to interrupt)`);
1404
- }
1453
+ this.refreshWorkingLoaderMessage();
1405
1454
  this.setHiddenThinkingLabel();
1406
1455
  }
1407
1456
  // Maximum total widget lines to prevent viewport overflow
@@ -1526,9 +1575,7 @@ export class InteractiveMode {
1526
1575
  setStatus: (key, text) => this.setExtensionStatus(key, text),
1527
1576
  setWorkingMessage: (message) => {
1528
1577
  this.workingMessage = message;
1529
- if (this.loadingAnimation) {
1530
- this.loadingAnimation.setMessage(message ?? this.defaultWorkingMessage);
1531
- }
1578
+ this.refreshWorkingLoaderMessage();
1532
1579
  },
1533
1580
  setWorkingVisible: (visible) => this.setWorkingVisible(visible),
1534
1581
  setWorkingIndicator: (options) => this.setWorkingIndicator(options),
@@ -1594,7 +1641,7 @@ export class InteractiveMode {
1594
1641
  opts?.signal?.removeEventListener("abort", onAbort);
1595
1642
  this.hideExtensionSelector();
1596
1643
  resolve(undefined);
1597
- }, { tui: this.ui, timeout: opts?.timeout });
1644
+ }, { tui: this.ui, timeout: opts?.timeout, onToggleToolsExpanded: () => this.toggleToolOutputExpansion() });
1598
1645
  this.editorContainer.clear();
1599
1646
  this.editorContainer.addChild(this.extensionSelector);
1600
1647
  this.ui.setFocus(this.extensionSelector);
@@ -2149,6 +2196,7 @@ export class InteractiveMode {
2149
2196
  }
2150
2197
  this.stopWorkingLoader();
2151
2198
  if (this.workingVisible) {
2199
+ this.startWorkingElapsedTimer();
2152
2200
  this.loadingAnimation = this.createWorkingLoader();
2153
2201
  this.statusContainer.addChild(this.loadingAnimation);
2154
2202
  }
@@ -2290,11 +2338,7 @@ export class InteractiveMode {
2290
2338
  if (this.settingsManager.getShowTerminalProgress()) {
2291
2339
  this.ui.terminal.setProgress(false);
2292
2340
  }
2293
- if (this.loadingAnimation) {
2294
- this.loadingAnimation.stop();
2295
- this.loadingAnimation = undefined;
2296
- this.statusContainer.clear();
2297
- }
2341
+ this.stopWorkingLoader();
2298
2342
  if (this.streamingComponent) {
2299
2343
  this.chatContainer.removeChild(this.streamingComponent);
2300
2344
  this.streamingComponent = undefined;
@@ -2315,11 +2359,31 @@ export class InteractiveMode {
2315
2359
  };
2316
2360
  this.statusContainer.clear();
2317
2361
  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}`;
2362
+ const label = event.reason === "threshold"
2363
+ ? `Auto-compacting... ${cancelHint}`
2364
+ : event.reason === "overflow"
2365
+ ? `Context overflow detected, compacting... ${cancelHint}`
2366
+ : event.reason === "pre_prompt"
2367
+ ? `Compacting before next prompt... ${cancelHint}`
2368
+ : `Compacting context... ${cancelHint}`;
2321
2369
  this.autoCompactionLoader = new Loader(this.ui, (spinner) => theme.fg("accent", spinner), (text) => theme.fg("muted", text), label);
2322
2370
  this.statusContainer.addChild(this.autoCompactionLoader);
2371
+ this.autoCompactionProgressText = "";
2372
+ this.ui.requestRender();
2373
+ break;
2374
+ }
2375
+ case "compaction_progress": {
2376
+ if (!this.autoCompactionLoader)
2377
+ break;
2378
+ const nextText = event.text !== undefined ? event.text : `${this.autoCompactionProgressText}${event.delta ?? ""}`;
2379
+ if (!nextText)
2380
+ break;
2381
+ this.autoCompactionProgressText = nextText;
2382
+ const preview = nextText.length > 4_000 ? `...${nextText.slice(nextText.length - 4_000)}` : nextText;
2383
+ this.statusContainer.clear();
2384
+ this.statusContainer.addChild(this.autoCompactionLoader);
2385
+ this.statusContainer.addChild(new Spacer(1));
2386
+ this.statusContainer.addChild(new Text(theme.fg("muted", preview), 1, 0));
2323
2387
  this.ui.requestRender();
2324
2388
  break;
2325
2389
  }
@@ -2334,6 +2398,7 @@ export class InteractiveMode {
2334
2398
  if (this.autoCompactionLoader) {
2335
2399
  this.autoCompactionLoader.stop();
2336
2400
  this.autoCompactionLoader = undefined;
2401
+ this.autoCompactionProgressText = "";
2337
2402
  this.statusContainer.clear();
2338
2403
  }
2339
2404
  if (event.aborted) {
@@ -2347,7 +2412,7 @@ export class InteractiveMode {
2347
2412
  else if (event.result) {
2348
2413
  this.chatContainer.clear();
2349
2414
  this.rebuildChatFromMessages();
2350
- this.addMessageToChat(createCompactionSummaryMessage(event.result.summary, event.result.tokensBefore, new Date().toISOString()));
2415
+ this.addMessageToChat(createCompactionSummaryMessage(event.result.summary, event.result.tokensBefore, new Date().toISOString(), event.result.details));
2351
2416
  this.footer.invalidate();
2352
2417
  }
2353
2418
  else if (event.errorMessage) {
@@ -2653,6 +2718,38 @@ export class InteractiveMode {
2653
2718
  // extension cleanup can write restore sequences and re-trigger EIO.
2654
2719
  process.exit(129);
2655
2720
  }
2721
+ /**
2722
+ * Last-resort handler for uncaught exceptions. The TUI puts stdin into raw
2723
+ * mode and hides the cursor; without this handler, an uncaught throw from
2724
+ * anywhere (e.g. an extension's async `ChildProcess.on("exit")` callback)
2725
+ * tears down the process while leaving the terminal in raw mode with no
2726
+ * cursor, requiring `stty sane && reset` to recover.
2727
+ *
2728
+ * Unlike emergencyTerminalExit, the terminal is still alive here, so we
2729
+ * call ui.stop() to restore cooked mode, the cursor, and disable bracketed
2730
+ * paste / Kitty / modifyOtherKeys sequences.
2731
+ */
2732
+ uncaughtCrash(error) {
2733
+ if (this.isShuttingDown) {
2734
+ process.exit(1);
2735
+ }
2736
+ this.isShuttingDown = true;
2737
+ try {
2738
+ this.unregisterSignalHandlers();
2739
+ }
2740
+ catch { }
2741
+ try {
2742
+ killTrackedDetachedChildren();
2743
+ }
2744
+ catch { }
2745
+ try {
2746
+ this.ui.stop();
2747
+ }
2748
+ catch { }
2749
+ console.error("pi exiting due to uncaughtException:");
2750
+ console.error(error);
2751
+ process.exit(1);
2752
+ }
2656
2753
  /**
2657
2754
  * Check if shutdown was requested and perform shutdown if so.
2658
2755
  */
@@ -2688,6 +2785,12 @@ export class InteractiveMode {
2688
2785
  process.stderr.on("error", terminalErrorHandler);
2689
2786
  this.signalCleanupHandlers.push(() => process.stdout.off("error", terminalErrorHandler));
2690
2787
  this.signalCleanupHandlers.push(() => process.stderr.off("error", terminalErrorHandler));
2788
+ // Restore the terminal before the process dies on any uncaught throw.
2789
+ // Without this, an unhandled exception from extension code (or anywhere
2790
+ // in pi) leaves the terminal in raw mode with no cursor.
2791
+ const uncaughtExceptionHandler = (error) => this.uncaughtCrash(error);
2792
+ process.prependListener("uncaughtException", uncaughtExceptionHandler);
2793
+ this.signalCleanupHandlers.push(() => process.off("uncaughtException", uncaughtExceptionHandler));
2691
2794
  }
2692
2795
  unregisterSignalHandlers() {
2693
2796
  for (const cleanup of this.signalCleanupHandlers) {
@@ -2894,6 +2997,7 @@ export class InteractiveMode {
2894
2997
  showError(errorMessage) {
2895
2998
  this.chatContainer.addChild(new Spacer(1));
2896
2999
  this.chatContainer.addChild(new Text(theme.fg("error", `Error: ${errorMessage}`), 1, 0));
3000
+ this.chatContainer.addChild(new Spacer(1));
2897
3001
  this.ui.requestRender();
2898
3002
  }
2899
3003
  showWarning(warningMessage) {
@@ -2983,7 +3087,7 @@ export class InteractiveMode {
2983
3087
  if (allQueued.length === 0) {
2984
3088
  this.updatePendingMessagesDisplay();
2985
3089
  if (options?.abort) {
2986
- this.agent.abort();
3090
+ void this.session.abort();
2987
3091
  }
2988
3092
  return 0;
2989
3093
  }
@@ -2993,7 +3097,7 @@ export class InteractiveMode {
2993
3097
  this.editor.setText(combinedText);
2994
3098
  this.updatePendingMessagesDisplay();
2995
3099
  if (options?.abort) {
2996
- this.agent.abort();
3100
+ void this.session.abort();
2997
3101
  }
2998
3102
  return allQueued.length;
2999
3103
  }
@@ -3613,11 +3717,7 @@ export class InteractiveMode {
3613
3717
  });
3614
3718
  }
3615
3719
  async handleResumeSession(sessionPath, options) {
3616
- if (this.loadingAnimation) {
3617
- this.loadingAnimation.stop();
3618
- this.loadingAnimation = undefined;
3619
- }
3620
- this.statusContainer.clear();
3720
+ this.stopWorkingLoader();
3621
3721
  try {
3622
3722
  const result = await this.runtimeHost.switchSession(sessionPath, {
3623
3723
  withSession: options?.withSession,
@@ -4082,13 +4182,13 @@ export class InteractiveMode {
4082
4182
  if (closingQuoteIndex < 0) {
4083
4183
  return undefined;
4084
4184
  }
4085
- return argsString.slice(1, closingQuoteIndex);
4185
+ return expandTildePath(argsString.slice(1, closingQuoteIndex));
4086
4186
  }
4087
4187
  const firstWhitespaceIndex = argsString.search(/\s/);
4088
4188
  if (firstWhitespaceIndex < 0) {
4089
- return argsString;
4189
+ return expandTildePath(argsString);
4090
4190
  }
4091
- return argsString.slice(0, firstWhitespaceIndex);
4191
+ return expandTildePath(argsString.slice(0, firstWhitespaceIndex));
4092
4192
  }
4093
4193
  async handleImportCommand(text) {
4094
4194
  const inputPath = this.getPathCommandArgument(text, "/import");
@@ -4102,11 +4202,7 @@ export class InteractiveMode {
4102
4202
  return;
4103
4203
  }
4104
4204
  try {
4105
- if (this.loadingAnimation) {
4106
- this.loadingAnimation.stop();
4107
- this.loadingAnimation = undefined;
4108
- }
4109
- this.statusContainer.clear();
4205
+ this.stopWorkingLoader();
4110
4206
  const result = await this.runtimeHost.importFromJsonl(inputPath);
4111
4207
  if (result.cancelled) {
4112
4208
  this.showStatus("Import cancelled");
@@ -4430,11 +4526,7 @@ export class InteractiveMode {
4430
4526
  this.ui.requestRender();
4431
4527
  }
4432
4528
  async handleClearCommand() {
4433
- if (this.loadingAnimation) {
4434
- this.loadingAnimation.stop();
4435
- this.loadingAnimation = undefined;
4436
- }
4437
- this.statusContainer.clear();
4529
+ this.stopWorkingLoader();
4438
4530
  try {
4439
4531
  const result = await this.runtimeHost.newSession();
4440
4532
  if (result.cancelled) {
@@ -4568,11 +4660,7 @@ export class InteractiveMode {
4568
4660
  this.showWarning("Nothing to compact (no messages yet)");
4569
4661
  return;
4570
4662
  }
4571
- if (this.loadingAnimation) {
4572
- this.loadingAnimation.stop();
4573
- this.loadingAnimation = undefined;
4574
- }
4575
- this.statusContainer.clear();
4663
+ this.stopWorkingLoader();
4576
4664
  try {
4577
4665
  await this.session.compact(customInstructions);
4578
4666
  }
@@ -4585,10 +4673,7 @@ export class InteractiveMode {
4585
4673
  if (this.settingsManager.getShowTerminalProgress()) {
4586
4674
  this.ui.terminal.setProgress(false);
4587
4675
  }
4588
- if (this.loadingAnimation) {
4589
- this.loadingAnimation.stop();
4590
- this.loadingAnimation = undefined;
4591
- }
4676
+ this.stopWorkingLoader();
4592
4677
  this.clearExtensionTerminalInputListeners();
4593
4678
  this.footer.dispose();
4594
4679
  this.footerDataProvider.dispose();