@code-yeongyu/senpi 2026.8.28 → 2026.8.30

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 (272) hide show
  1. package/CHANGELOG.md +140 -0
  2. package/dist/cli/args.d.ts +2 -0
  3. package/dist/cli/args.d.ts.map +1 -1
  4. package/dist/cli/args.js +4 -0
  5. package/dist/cli/args.js.map +1 -1
  6. package/dist/core/agent-session-services.d.ts.map +1 -1
  7. package/dist/core/agent-session-services.js +2 -0
  8. package/dist/core/agent-session-services.js.map +1 -1
  9. package/dist/core/agent-session.d.ts +50 -1
  10. package/dist/core/agent-session.d.ts.map +1 -1
  11. package/dist/core/agent-session.js +313 -25
  12. package/dist/core/agent-session.js.map +1 -1
  13. package/dist/core/auth-storage.d.ts +1 -0
  14. package/dist/core/auth-storage.d.ts.map +1 -1
  15. package/dist/core/auth-storage.js +3 -0
  16. package/dist/core/auth-storage.js.map +1 -1
  17. package/dist/core/bash-executor.d.ts.map +1 -1
  18. package/dist/core/bash-executor.js +155 -37
  19. package/dist/core/bash-executor.js.map +1 -1
  20. package/dist/core/credential-accounts.d.ts.map +1 -1
  21. package/dist/core/credential-accounts.js +45 -21
  22. package/dist/core/credential-accounts.js.map +1 -1
  23. package/dist/core/credential-pool/classify.d.ts +6 -1
  24. package/dist/core/credential-pool/classify.d.ts.map +1 -1
  25. package/dist/core/credential-pool/classify.js +22 -5
  26. package/dist/core/credential-pool/classify.js.map +1 -1
  27. package/dist/core/credential-pool/env-slots.d.ts.map +1 -1
  28. package/dist/core/credential-pool/env-slots.js +2 -0
  29. package/dist/core/credential-pool/env-slots.js.map +1 -1
  30. package/dist/core/credential-pool/failover.d.ts +8 -0
  31. package/dist/core/credential-pool/failover.d.ts.map +1 -1
  32. package/dist/core/credential-pool/failover.js +24 -6
  33. package/dist/core/credential-pool/failover.js.map +1 -1
  34. package/dist/core/credential-pool/rotation-stream.d.ts +13 -2
  35. package/dist/core/credential-pool/rotation-stream.d.ts.map +1 -1
  36. package/dist/core/credential-pool/rotation-stream.js +97 -10
  37. package/dist/core/credential-pool/rotation-stream.js.map +1 -1
  38. package/dist/core/extensions/builtin/compaction/deterministic-fallback.d.ts +8 -2
  39. package/dist/core/extensions/builtin/compaction/deterministic-fallback.d.ts.map +1 -1
  40. package/dist/core/extensions/builtin/compaction/deterministic-fallback.js +160 -47
  41. package/dist/core/extensions/builtin/compaction/deterministic-fallback.js.map +1 -1
  42. package/dist/core/extensions/builtin/compaction/retained-message-safety.d.ts.map +1 -1
  43. package/dist/core/extensions/builtin/compaction/retained-message-safety.js +31 -8
  44. package/dist/core/extensions/builtin/compaction/retained-message-safety.js.map +1 -1
  45. package/dist/core/extensions/builtin/cursor-cli-oauth/stream.d.ts.map +1 -1
  46. package/dist/core/extensions/builtin/cursor-cli-oauth/stream.js +8 -28
  47. package/dist/core/extensions/builtin/cursor-cli-oauth/stream.js.map +1 -1
  48. package/dist/core/extensions/builtin/goal/index.d.ts.map +1 -1
  49. package/dist/core/extensions/builtin/goal/index.js +7 -0
  50. package/dist/core/extensions/builtin/goal/index.js.map +1 -1
  51. package/dist/core/extensions/builtin/permission-system/external-dir.d.ts.map +1 -1
  52. package/dist/core/extensions/builtin/permission-system/external-dir.js +15 -5
  53. package/dist/core/extensions/builtin/permission-system/external-dir.js.map +1 -1
  54. package/dist/core/extensions/builtin/permission-system/parsers.d.ts.map +1 -1
  55. package/dist/core/extensions/builtin/permission-system/parsers.js +16 -1
  56. package/dist/core/extensions/builtin/permission-system/parsers.js.map +1 -1
  57. package/dist/core/extensions/builtin/service-tier.d.ts.map +1 -1
  58. package/dist/core/extensions/builtin/service-tier.js +13 -0
  59. package/dist/core/extensions/builtin/service-tier.js.map +1 -1
  60. package/dist/core/extensions/builtin/terminal/extension.d.ts.map +1 -1
  61. package/dist/core/extensions/builtin/terminal/extension.js +4 -2
  62. package/dist/core/extensions/builtin/terminal/extension.js.map +1 -1
  63. package/dist/core/extensions/builtin/terminal/manager.d.ts +5 -0
  64. package/dist/core/extensions/builtin/terminal/manager.d.ts.map +1 -1
  65. package/dist/core/extensions/builtin/terminal/manager.js +31 -3
  66. package/dist/core/extensions/builtin/terminal/manager.js.map +1 -1
  67. package/dist/core/extensions/builtin/terminal/monitor-permission.d.ts +8 -0
  68. package/dist/core/extensions/builtin/terminal/monitor-permission.d.ts.map +1 -0
  69. package/dist/core/extensions/builtin/terminal/monitor-permission.js +16 -0
  70. package/dist/core/extensions/builtin/terminal/monitor-permission.js.map +1 -0
  71. package/dist/core/extensions/builtin/terminal/monitor-registry.d.ts +13 -0
  72. package/dist/core/extensions/builtin/terminal/monitor-registry.d.ts.map +1 -1
  73. package/dist/core/extensions/builtin/terminal/monitor-registry.js +403 -3
  74. package/dist/core/extensions/builtin/terminal/monitor-registry.js.map +1 -1
  75. package/dist/core/extensions/builtin/terminal/session-bundle.d.ts.map +1 -1
  76. package/dist/core/extensions/builtin/terminal/session-bundle.js +1 -0
  77. package/dist/core/extensions/builtin/terminal/session-bundle.js.map +1 -1
  78. package/dist/core/extensions/builtin/terminal/tools/kill-bash.d.ts.map +1 -1
  79. package/dist/core/extensions/builtin/terminal/tools/kill-bash.js +5 -2
  80. package/dist/core/extensions/builtin/terminal/tools/kill-bash.js.map +1 -1
  81. package/dist/core/extensions/builtin/terminal/tools/monitor.d.ts +4 -0
  82. package/dist/core/extensions/builtin/terminal/tools/monitor.d.ts.map +1 -1
  83. package/dist/core/extensions/builtin/terminal/tools/monitor.js +40 -2
  84. package/dist/core/extensions/builtin/terminal/tools/monitor.js.map +1 -1
  85. package/dist/core/model-config-schema.d.ts +4 -4
  86. package/dist/core/model-resolver.js +2 -2
  87. package/dist/core/model-resolver.js.map +1 -1
  88. package/dist/core/model-runtime.d.ts +2 -0
  89. package/dist/core/model-runtime.d.ts.map +1 -1
  90. package/dist/core/model-runtime.js +74 -37
  91. package/dist/core/model-runtime.js.map +1 -1
  92. package/dist/core/retry-fallback/chains.d.ts +23 -1
  93. package/dist/core/retry-fallback/chains.d.ts.map +1 -1
  94. package/dist/core/retry-fallback/chains.js +59 -3
  95. package/dist/core/retry-fallback/chains.js.map +1 -1
  96. package/dist/core/retry-fallback/controller.d.ts.map +1 -1
  97. package/dist/core/retry-fallback/controller.js +9 -2
  98. package/dist/core/retry-fallback/controller.js.map +1 -1
  99. package/dist/core/retry-fallback/settings.d.ts.map +1 -1
  100. package/dist/core/retry-fallback/settings.js +9 -0
  101. package/dist/core/retry-fallback/settings.js.map +1 -1
  102. package/dist/core/retry-fallback/validate.js +1 -1
  103. package/dist/core/retry-fallback/validate.js.map +1 -1
  104. package/dist/core/sdk.js +1 -1
  105. package/dist/core/sdk.js.map +1 -1
  106. package/dist/core/session-manager.d.ts +9 -0
  107. package/dist/core/session-manager.d.ts.map +1 -1
  108. package/dist/core/session-manager.js +40 -0
  109. package/dist/core/session-manager.js.map +1 -1
  110. package/dist/core/settings-manager.d.ts +7 -0
  111. package/dist/core/settings-manager.d.ts.map +1 -1
  112. package/dist/core/settings-manager.js +6 -0
  113. package/dist/core/settings-manager.js.map +1 -1
  114. package/dist/core/tools/bash.d.ts.map +1 -1
  115. package/dist/core/tools/bash.js +97 -15
  116. package/dist/core/tools/bash.js.map +1 -1
  117. package/dist/core/tools/index.d.ts +1 -0
  118. package/dist/core/tools/index.d.ts.map +1 -1
  119. package/dist/core/tools/index.js +8 -0
  120. package/dist/core/tools/index.js.map +1 -1
  121. package/dist/core/tools/output-accumulator.d.ts +1 -1
  122. package/dist/core/tools/output-accumulator.d.ts.map +1 -1
  123. package/dist/core/tools/output-accumulator.js +76 -34
  124. package/dist/core/tools/output-accumulator.js.map +1 -1
  125. package/dist/main.d.ts +7 -0
  126. package/dist/main.d.ts.map +1 -1
  127. package/dist/main.js +10 -1
  128. package/dist/main.js.map +1 -1
  129. package/dist/modes/interactive/components/favorite-models-selector.d.ts.map +1 -1
  130. package/dist/modes/interactive/components/favorite-models-selector.js +2 -2
  131. package/dist/modes/interactive/components/favorite-models-selector.js.map +1 -1
  132. package/dist/modes/interactive/components/footer.d.ts +7 -0
  133. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  134. package/dist/modes/interactive/components/footer.js +34 -8
  135. package/dist/modes/interactive/components/footer.js.map +1 -1
  136. package/dist/modes/interactive/grok/chrome.d.ts +2 -0
  137. package/dist/modes/interactive/grok/chrome.d.ts.map +1 -1
  138. package/dist/modes/interactive/grok/chrome.js.map +1 -1
  139. package/dist/modes/interactive/interactive-host-runtime.d.ts +51 -1
  140. package/dist/modes/interactive/interactive-host-runtime.d.ts.map +1 -1
  141. package/dist/modes/interactive/interactive-host-runtime.js +545 -40
  142. package/dist/modes/interactive/interactive-host-runtime.js.map +1 -1
  143. package/dist/modes/interactive/interactive-mode.d.ts +7 -0
  144. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  145. package/dist/modes/interactive/interactive-mode.js +390 -239
  146. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  147. package/dist/modes/rpc/connection-handler.d.ts +8 -1
  148. package/dist/modes/rpc/connection-handler.d.ts.map +1 -1
  149. package/dist/modes/rpc/connection-handler.js +268 -63
  150. package/dist/modes/rpc/connection-handler.js.map +1 -1
  151. package/dist/modes/rpc/host-ensure.d.ts +2 -0
  152. package/dist/modes/rpc/host-ensure.d.ts.map +1 -1
  153. package/dist/modes/rpc/host-ensure.js +15 -2
  154. package/dist/modes/rpc/host-ensure.js.map +1 -1
  155. package/dist/modes/rpc/host-lifecycle.d.ts.map +1 -1
  156. package/dist/modes/rpc/host-lifecycle.js +31 -12
  157. package/dist/modes/rpc/host-lifecycle.js.map +1 -1
  158. package/dist/modes/rpc/host-watchdog.d.ts +2 -0
  159. package/dist/modes/rpc/host-watchdog.d.ts.map +1 -1
  160. package/dist/modes/rpc/host-watchdog.js +23 -12
  161. package/dist/modes/rpc/host-watchdog.js.map +1 -1
  162. package/dist/modes/rpc/multi-session-host.js +20 -8
  163. package/dist/modes/rpc/multi-session-host.js.map +1 -1
  164. package/dist/modes/rpc/rpc-client.d.ts +91 -16
  165. package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
  166. package/dist/modes/rpc/rpc-client.js +129 -37
  167. package/dist/modes/rpc/rpc-client.js.map +1 -1
  168. package/dist/modes/rpc/rpc-input-validation.d.ts +1 -0
  169. package/dist/modes/rpc/rpc-input-validation.d.ts.map +1 -1
  170. package/dist/modes/rpc/rpc-input-validation.js +66 -0
  171. package/dist/modes/rpc/rpc-input-validation.js.map +1 -1
  172. package/dist/modes/rpc/rpc-mode.d.ts +3 -1
  173. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  174. package/dist/modes/rpc/rpc-mode.js +3 -1
  175. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  176. package/dist/modes/rpc/rpc-types.d.ts +241 -3
  177. package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  178. package/dist/modes/rpc/rpc-types.js.map +1 -1
  179. package/dist/modes/rpc/session-binding.d.ts +1 -0
  180. package/dist/modes/rpc/session-binding.d.ts.map +1 -1
  181. package/dist/modes/rpc/session-binding.js +21 -1
  182. package/dist/modes/rpc/session-binding.js.map +1 -1
  183. package/dist/modes/rpc/session-command-router.d.ts +22 -0
  184. package/dist/modes/rpc/session-command-router.d.ts.map +1 -1
  185. package/dist/modes/rpc/session-command-router.js +148 -16
  186. package/dist/modes/rpc/session-command-router.js.map +1 -1
  187. package/dist/modes/rpc/session-event-writer.d.ts +4 -0
  188. package/dist/modes/rpc/session-event-writer.d.ts.map +1 -1
  189. package/dist/modes/rpc/session-event-writer.js +58 -7
  190. package/dist/modes/rpc/session-event-writer.js.map +1 -1
  191. package/dist/modes/rpc/session-extension-ui-requests.d.ts +1 -0
  192. package/dist/modes/rpc/session-extension-ui-requests.d.ts.map +1 -1
  193. package/dist/modes/rpc/session-extension-ui-requests.js +5 -0
  194. package/dist/modes/rpc/session-extension-ui-requests.js.map +1 -1
  195. package/dist/modes/rpc/session-registry.d.ts +17 -2
  196. package/dist/modes/rpc/session-registry.d.ts.map +1 -1
  197. package/dist/modes/rpc/session-registry.js +67 -4
  198. package/dist/modes/rpc/session-registry.js.map +1 -1
  199. package/dist/modes/rpc/socket-event-fanout.d.ts +22 -0
  200. package/dist/modes/rpc/socket-event-fanout.d.ts.map +1 -0
  201. package/dist/modes/rpc/socket-event-fanout.js +84 -0
  202. package/dist/modes/rpc/socket-event-fanout.js.map +1 -0
  203. package/docs/rpc.md +40 -1
  204. package/docs/settings.md +34 -0
  205. package/docs/windows.md +2 -0
  206. package/node_modules/@code-yeongyu/senpi-codemode/CHANGELOG.md +62 -0
  207. package/node_modules/@code-yeongyu/senpi-codemode/package.json +4 -4
  208. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/worker-indirect-eval.js +139 -90
  209. package/node_modules/@code-yeongyu/senpi-codemode/src/prompt/eval-prompt.ts +4 -4
  210. package/node_modules/@earendil-works/pi-agent-core/dist/agent-loop.d.ts.map +1 -1
  211. package/node_modules/@earendil-works/pi-agent-core/dist/agent-loop.js +6 -3
  212. package/node_modules/@earendil-works/pi-agent-core/dist/agent-loop.js.map +1 -1
  213. package/node_modules/@earendil-works/pi-agent-core/dist/harness/env/nodejs.d.ts.map +1 -1
  214. package/node_modules/@earendil-works/pi-agent-core/dist/harness/env/nodejs.js +40 -20
  215. package/node_modules/@earendil-works/pi-agent-core/dist/harness/env/nodejs.js.map +1 -1
  216. package/node_modules/@earendil-works/pi-agent-core/dist/harness/types.d.ts +3 -3
  217. package/node_modules/@earendil-works/pi-agent-core/dist/harness/types.d.ts.map +1 -1
  218. package/node_modules/@earendil-works/pi-agent-core/dist/harness/types.js.map +1 -1
  219. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/shell-output.d.ts +1 -1
  220. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/shell-output.d.ts.map +1 -1
  221. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/shell-output.js +28 -9
  222. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/shell-output.js.map +1 -1
  223. package/node_modules/@earendil-works/pi-agent-core/package.json +3 -3
  224. package/node_modules/@earendil-works/pi-ai/dist/api/anthropic-messages.d.ts.map +1 -1
  225. package/node_modules/@earendil-works/pi-ai/dist/api/anthropic-messages.js +58 -31
  226. package/node_modules/@earendil-works/pi-ai/dist/api/anthropic-messages.js.map +1 -1
  227. package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent/measure.d.ts +20 -0
  228. package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent/measure.d.ts.map +1 -0
  229. package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent/measure.js +443 -0
  230. package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent/measure.js.map +1 -0
  231. package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent.d.ts +2 -0
  232. package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent.d.ts.map +1 -1
  233. package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent.js +2 -0
  234. package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent.js.map +1 -1
  235. package/node_modules/@earendil-works/pi-ai/dist/api/openai-completions.js +1 -1
  236. package/node_modules/@earendil-works/pi-ai/dist/api/openai-completions.js.map +1 -1
  237. package/node_modules/@earendil-works/pi-ai/dist/index.d.ts +1 -0
  238. package/node_modules/@earendil-works/pi-ai/dist/index.d.ts.map +1 -1
  239. package/node_modules/@earendil-works/pi-ai/dist/index.js +1 -0
  240. package/node_modules/@earendil-works/pi-ai/dist/index.js.map +1 -1
  241. package/node_modules/@earendil-works/pi-ai/dist/providers/data/.manifest.json +1 -1
  242. package/node_modules/@earendil-works/pi-ai/dist/providers/data/amazon-bedrock.json +1 -1
  243. package/node_modules/@earendil-works/pi-ai/dist/providers/data/baseten.json +1 -1
  244. package/node_modules/@earendil-works/pi-ai/dist/providers/data/cloudflare-ai-gateway.json +1 -1
  245. package/node_modules/@earendil-works/pi-ai/dist/providers/data/cloudflare-workers-ai.json +1 -1
  246. package/node_modules/@earendil-works/pi-ai/dist/providers/data/fireworks.json +1 -1
  247. package/node_modules/@earendil-works/pi-ai/dist/providers/data/huggingface.json +1 -1
  248. package/node_modules/@earendil-works/pi-ai/dist/providers/data/nvidia.json +1 -1
  249. package/node_modules/@earendil-works/pi-ai/dist/providers/data/opencode-go.json +1 -1
  250. package/node_modules/@earendil-works/pi-ai/dist/providers/data/opencode.json +1 -1
  251. package/node_modules/@earendil-works/pi-ai/dist/providers/data/openrouter.json +1 -1
  252. package/node_modules/@earendil-works/pi-ai/dist/providers/data/together.json +1 -1
  253. package/node_modules/@earendil-works/pi-ai/dist/providers/data/vercel-ai-gateway.json +1 -1
  254. package/node_modules/@earendil-works/pi-ai/dist/providers/data/zai-coding-cn.json +1 -1
  255. package/node_modules/@earendil-works/pi-ai/dist/providers/data/zai.json +1 -1
  256. package/node_modules/@earendil-works/pi-ai/package.json +2 -2
  257. package/node_modules/@earendil-works/pi-pty/package.json +1 -1
  258. package/node_modules/@earendil-works/pi-telemetry/package.json +1 -1
  259. package/node_modules/@earendil-works/pi-tui/dist/components/editor.d.ts +7 -0
  260. package/node_modules/@earendil-works/pi-tui/dist/components/editor.d.ts.map +1 -1
  261. package/node_modules/@earendil-works/pi-tui/dist/components/editor.js +5 -0
  262. package/node_modules/@earendil-works/pi-tui/dist/components/editor.js.map +1 -1
  263. package/node_modules/@earendil-works/pi-tui/dist/mux.d.ts +1 -1
  264. package/node_modules/@earendil-works/pi-tui/dist/mux.d.ts.map +1 -1
  265. package/node_modules/@earendil-works/pi-tui/dist/mux.js +2 -2
  266. package/node_modules/@earendil-works/pi-tui/dist/mux.js.map +1 -1
  267. package/node_modules/@earendil-works/pi-tui/dist/terminal.d.ts +1 -0
  268. package/node_modules/@earendil-works/pi-tui/dist/terminal.d.ts.map +1 -1
  269. package/node_modules/@earendil-works/pi-tui/dist/terminal.js +19 -0
  270. package/node_modules/@earendil-works/pi-tui/dist/terminal.js.map +1 -1
  271. package/node_modules/@earendil-works/pi-tui/package.json +1 -1
  272. package/package.json +7 -7
@@ -224,6 +224,19 @@ function isDeadTerminalError(error) {
224
224
  }
225
225
  return false;
226
226
  }
227
+ function storageWriteCrashMessage(error) {
228
+ if (!error || typeof error !== "object" || !("code" in error)) {
229
+ return undefined;
230
+ }
231
+ const code = error.code;
232
+ if (code === "EDQUOT") {
233
+ return "Disk quota exceeded (EDQUOT). Free space or quota on the filesystem, then retry.";
234
+ }
235
+ if (code === "ENOSPC") {
236
+ return "Disk full (ENOSPC). Free space on the filesystem, then retry.";
237
+ }
238
+ return undefined;
239
+ }
227
240
  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. Disable this warning in /settings.";
228
241
  function isAnthropicSubscriptionAuthKey(apiKey) {
229
242
  return typeof apiKey === "string" && apiKey.startsWith("sk-ant-oat");
@@ -569,6 +582,12 @@ export class InteractiveMode {
569
582
  // Track pending bash components (shown in pending area, moved to chat on submit)
570
583
  this.pendingBashComponents = [];
571
584
  this.compactionEscapeOverrideActive = false;
585
+ /**
586
+ * One-time notice guard for the external-owner compaction delegation episode
587
+ * (e.g. the Claude Agent SDK owning compaction). Armed while true; re-armed by
588
+ * a successful compaction, a model switch, or a session rebind.
589
+ */
590
+ this.externalOwnerCompactionNoticeShown = false;
572
591
  this.autoCompactionProgressText = "";
573
592
  this.fallbackAppliedBeforeRetryStart = false;
574
593
  this.pendingZeroDelayRetryIndicator = undefined;
@@ -616,6 +635,10 @@ export class InteractiveMode {
616
635
  await this.rebindCurrentSession({ renderBeforeBind: true });
617
636
  await this.themeController.applyFromSettings();
618
637
  });
638
+ // Host-driven extension UI only exists on the shared-host lane; the classic
639
+ // local runtime renders extension UI in-process and has no such hook.
640
+ const hostUiRuntime = this.runtimeHost;
641
+ hostUiRuntime.setHostUiHandler?.((request) => this.handleHostUiRequest(request));
619
642
  this.version = DISPLAY_VERSION;
620
643
  this.renderer = createInteractiveTui({
621
644
  tuiMode,
@@ -1969,6 +1992,9 @@ export class InteractiveMode {
1969
1992
  }
1970
1993
  async rebindCurrentSession(options = {}) {
1971
1994
  InteractiveMode.restoreCompactionEscapeOverride(this);
1995
+ // A session switch/reset ends any external-owner delegation episode.
1996
+ this.externalOwnerCompactionNoticeShown = false;
1997
+ this.footer?.setCompactionDelegated?.(false);
1972
1998
  const session = this.session;
1973
1999
  this.unsubscribe?.();
1974
2000
  this.unsubscribe = undefined;
@@ -2102,6 +2128,57 @@ export class InteractiveMode {
2102
2128
  return false;
2103
2129
  };
2104
2130
  }
2131
+ async handleHostUiRequest(request) {
2132
+ switch (request.method) {
2133
+ case "select": {
2134
+ const value = await this.showExtensionSelector(request.title ?? "", request.options ?? []);
2135
+ return value === undefined
2136
+ ? { type: "extension_ui_response", id: request.id, cancelled: true }
2137
+ : { type: "extension_ui_response", id: request.id, value };
2138
+ }
2139
+ case "confirm":
2140
+ return {
2141
+ type: "extension_ui_response",
2142
+ id: request.id,
2143
+ confirmed: await this.showExtensionConfirm(request.title ?? "", request.message ?? ""),
2144
+ };
2145
+ case "input": {
2146
+ const value = await this.showExtensionInput(request.title ?? "", request.placeholder);
2147
+ return value === undefined
2148
+ ? { type: "extension_ui_response", id: request.id, cancelled: true }
2149
+ : { type: "extension_ui_response", id: request.id, value };
2150
+ }
2151
+ case "editor": {
2152
+ const value = await this.showExtensionEditor(request.title ?? "", request.prefill);
2153
+ return value === undefined
2154
+ ? { type: "extension_ui_response", id: request.id, cancelled: true }
2155
+ : { type: "extension_ui_response", id: request.id, value };
2156
+ }
2157
+ case "notify":
2158
+ this.showExtensionNotify(request.message ?? "");
2159
+ return undefined;
2160
+ case "setStatus":
2161
+ this.setExtensionStatus(request.statusKey ?? "", request.statusText);
2162
+ return undefined;
2163
+ case "setTitle":
2164
+ this.extensionTerminalTitle = request.title ?? "";
2165
+ this.applyTerminalTitle();
2166
+ return undefined;
2167
+ case "set_editor_text":
2168
+ this.editor.setText(request.text ?? "");
2169
+ return undefined;
2170
+ case "setWidget":
2171
+ this.setExtensionWidget(request.widgetKey ?? "", request.widgetLines, {
2172
+ placement: request.widgetPlacement,
2173
+ });
2174
+ return undefined;
2175
+ case "custom_unsupported":
2176
+ this.showExtensionNotify(`${request.extensionName ?? "This extension"} requires the classic TUI; its component widget cannot be rendered in the shared host.`, "warning");
2177
+ return undefined;
2178
+ default:
2179
+ return undefined;
2180
+ }
2181
+ }
2105
2182
  /**
2106
2183
  * Set extension status text in the footer.
2107
2184
  */
@@ -2967,7 +3044,7 @@ export class InteractiveMode {
2967
3044
  // so they work correctly regardless of which editor is active
2968
3045
  this.defaultEditor.onEscape = () => {
2969
3046
  if (this.session.isStreaming || this.session.retryAttempt > 0) {
2970
- void this.abortAndFireQueuedMessages();
3047
+ void this.abortAndFireQueuedMessages().catch((error) => this.showError(error instanceof Error ? error.message : String(error)));
2971
3048
  }
2972
3049
  else if (this.session.isBashRunning) {
2973
3050
  this.session.abortBash();
@@ -2987,7 +3064,7 @@ export class InteractiveMode {
2987
3064
  this.showTreeSelector();
2988
3065
  }
2989
3066
  else {
2990
- this.showUserMessageSelector();
3067
+ void this.showUserMessageSelector().catch((error) => this.showError(error instanceof Error ? error.message : String(error)));
2991
3068
  }
2992
3069
  this.lastEscapeTime = 0;
2993
3070
  }
@@ -3001,7 +3078,9 @@ export class InteractiveMode {
3001
3078
  this.defaultEditor.onAction("app.clear", () => this.handleCtrlC());
3002
3079
  this.defaultEditor.onCtrlD = () => this.handleCtrlD();
3003
3080
  this.defaultEditor.onAction("app.suspend", () => this.handleCtrlZ());
3004
- this.defaultEditor.onAction("app.thinking.cycle", () => this.cycleThinkingLevel());
3081
+ this.defaultEditor.onAction("app.thinking.cycle", () => {
3082
+ void this.cycleThinkingLevel().catch((error) => this.showError(error instanceof Error ? error.message : String(error)));
3083
+ });
3005
3084
  this.defaultEditor.onAction("app.model.cycleForward", () => this.cycleModel("forward"));
3006
3085
  this.defaultEditor.onAction("app.model.cycleBackward", () => this.cycleModel("backward"));
3007
3086
  // Global debug handler on TUI (works regardless of focus)
@@ -3023,7 +3102,7 @@ export class InteractiveMode {
3023
3102
  this.defaultEditor.onAction("app.message.dequeue", () => this.handleDequeue());
3024
3103
  this.defaultEditor.onAction("app.session.new", () => this.handleClearCommand());
3025
3104
  this.defaultEditor.onAction("app.session.tree", () => this.showTreeSelector());
3026
- this.defaultEditor.onAction("app.session.fork", () => this.showUserMessageSelector());
3105
+ this.defaultEditor.onAction("app.session.fork", () => void this.showUserMessageSelector().catch((error) => this.showError(error instanceof Error ? error.message : String(error))));
3027
3106
  this.defaultEditor.onAction("app.session.resume", () => this.showSessionSelector());
3028
3107
  this.defaultEditor.onChange = (text) => {
3029
3108
  const wasBashMode = this.isBashMode;
@@ -3179,197 +3258,158 @@ export class InteractiveMode {
3179
3258
  }
3180
3259
  setupEditorSubmitHandler() {
3181
3260
  this.defaultEditor.onSubmit = async (text) => {
3182
- // Capture-then-clear BEFORE any branch: handleFollowUp's non-streaming
3183
- // path pre-resolves images and hands off here, but slash / extension /
3184
- // bash submissions return before the consuming branches below. Clearing
3185
- // only after use would leak a stale array into a later ordinary
3186
- // submission whose text never references it.
3187
- const preResolvedImages = this.preResolvedSubmissionImages;
3188
- this.preResolvedSubmissionImages = undefined;
3189
- this.hideShortcutOverlay();
3190
- this.lastEditorText = "";
3191
- text = text.trim();
3192
- if (!text)
3193
- return;
3194
- // Handle commands
3195
- if (text === "/settings") {
3196
- this.showSettingsSelector();
3197
- this.editor.setText("");
3198
- return;
3199
- }
3200
- if (text === "/favorite-models") {
3201
- this.editor.setText("");
3202
- await this.showFavoriteModelsSelector();
3203
- return;
3204
- }
3205
- if (text === "/scoped-models") {
3206
- this.editor.setText("");
3207
- this.showScopedModelsSelector();
3208
- return;
3209
- }
3210
- if (text === "/model" || text.startsWith("/model ")) {
3211
- const searchTerm = text.startsWith("/model ") ? text.slice(7).trim() : undefined;
3212
- this.editor.setText("");
3213
- await this.handleModelCommand(searchTerm);
3214
- return;
3215
- }
3216
- if (text === "/export" || text.startsWith("/export ")) {
3217
- await this.handleExportCommand(text);
3218
- this.editor.setText("");
3219
- return;
3220
- }
3221
- if (text === "/import" || text.startsWith("/import ")) {
3222
- await this.handleImportCommand(text);
3223
- this.editor.setText("");
3224
- return;
3225
- }
3226
- if (text === "/share") {
3227
- await this.handleShareCommand();
3228
- this.editor.setText("");
3229
- return;
3230
- }
3231
- if (text === "/copy") {
3232
- await this.handleCopyCommand();
3233
- this.editor.setText("");
3234
- return;
3235
- }
3236
- if (text === "/name" || text.startsWith("/name ")) {
3237
- this.handleNameCommand(text);
3238
- this.editor.setText("");
3239
- return;
3240
- }
3241
- if (text === "/session") {
3242
- this.handleSessionCommand();
3243
- this.editor.setText("");
3244
- return;
3245
- }
3246
- if (text === "/changelog") {
3247
- this.handleChangelogCommand();
3248
- this.editor.setText("");
3249
- return;
3250
- }
3251
- if (text === "/keybindings") {
3252
- this.editor.setText("");
3253
- await this.handleKeybindingsCommand();
3254
- return;
3255
- }
3256
- if (text === "/hotkeys") {
3257
- this.handleHotkeysCommand();
3258
- this.editor.setText("");
3259
- return;
3260
- }
3261
- if (text === "/fork") {
3262
- this.showUserMessageSelector();
3263
- this.editor.setText("");
3264
- return;
3265
- }
3266
- if (text === "/clone") {
3267
- this.editor.setText("");
3268
- await this.handleCloneCommand();
3269
- return;
3270
- }
3271
- if (text === "/tree") {
3272
- this.showTreeSelector();
3273
- this.editor.setText("");
3274
- return;
3275
- }
3276
- if (text === "/trust") {
3277
- this.showTrustSelector();
3278
- this.editor.setText("");
3279
- return;
3280
- }
3281
- if (text === "/login" || text.startsWith("/login ")) {
3282
- const providerRef = text.startsWith("/login ") ? text.slice(7).trim() : undefined;
3283
- this.editor.setText("");
3284
- await this.handleLoginCommand(providerRef);
3285
- return;
3286
- }
3287
- if (text === "/logout") {
3288
- this.showOAuthSelector("logout");
3289
- this.editor.setText("");
3290
- return;
3291
- }
3292
- if (text === "/new") {
3293
- this.editor.setText("");
3294
- await this.handleClearCommand();
3295
- return;
3296
- }
3297
- if (text === "/compact" || text.startsWith("/compact ")) {
3298
- const customInstructions = text.startsWith("/compact ") ? text.slice(9).trim() : undefined;
3299
- this.editor.setText("");
3300
- await this.handleCompactCommand(customInstructions);
3301
- return;
3302
- }
3303
- if (text === "/reload") {
3304
- this.editor.setText("");
3305
- await this.handleReloadCommand();
3306
- return;
3307
- }
3308
- if (text === "/debug") {
3309
- this.handleDebugCommand();
3310
- this.editor.setText("");
3311
- return;
3312
- }
3313
- if (text === "/arminsayshi") {
3314
- this.handleArminSaysHi();
3315
- this.editor.setText("");
3316
- return;
3317
- }
3318
- if (text === "/dementedelves") {
3319
- this.handleDementedDelves();
3320
- this.editor.setText("");
3321
- return;
3322
- }
3323
- if (text === "/resume") {
3324
- this.showSessionSelector();
3325
- this.editor.setText("");
3326
- return;
3327
- }
3328
- if (text === "/quit" || text === "/exit") {
3329
- this.editor.setText("");
3330
- await this.shutdown();
3331
- return;
3332
- }
3333
- if (this.isExtensionCommand(text)) {
3334
- this.editor.addToHistory?.(text);
3335
- this.editor.setText("");
3336
- const pendingEchoId = this.optimisticUserEchoes.begin(text);
3337
- try {
3338
- await this.session.prompt(text, this.optimisticUserEchoes.promptOptions(pendingEchoId));
3261
+ try {
3262
+ // Capture-then-clear BEFORE any branch: handleFollowUp's non-streaming
3263
+ // path pre-resolves images and hands off here, but slash / extension /
3264
+ // bash submissions return before the consuming branches below. Clearing
3265
+ // only after use would leak a stale array into a later ordinary
3266
+ // submission whose text never references it.
3267
+ const preResolvedImages = this.preResolvedSubmissionImages;
3268
+ this.preResolvedSubmissionImages = undefined;
3269
+ this.hideShortcutOverlay();
3270
+ this.lastEditorText = "";
3271
+ text = text.trim();
3272
+ if (!text)
3273
+ return;
3274
+ // Handle commands
3275
+ if (text === "/settings") {
3276
+ await this.showSettingsSelector();
3277
+ this.editor.setText("");
3278
+ return;
3339
3279
  }
3340
- catch (error) {
3341
- this.optimisticUserEchoes.reject(pendingEchoId);
3342
- throw error;
3280
+ if (text === "/favorite-models") {
3281
+ this.editor.setText("");
3282
+ await this.showFavoriteModelsSelector();
3283
+ return;
3343
3284
  }
3344
- return;
3345
- }
3346
- // Handle bash command (! for normal, !! for excluded from context)
3347
- if (text.startsWith("!")) {
3348
- const isExcluded = text.startsWith("!!");
3349
- const command = isExcluded ? text.slice(2).trim() : text.slice(1).trim();
3350
- if (command) {
3351
- if (this.session.isBashRunning) {
3352
- this.showWarning("A bash command is already running. Press Esc to cancel it first.");
3353
- this.editor.setText(text);
3354
- return;
3355
- }
3356
- this.editor.addToHistory?.(text);
3357
- await this.handleBashCommand(command, isExcluded);
3358
- this.isBashMode = false;
3359
- this.updateEditorBorderColor();
3285
+ if (text === "/scoped-models") {
3286
+ this.editor.setText("");
3287
+ this.showScopedModelsSelector();
3288
+ return;
3289
+ }
3290
+ if (text === "/model" || text.startsWith("/model ")) {
3291
+ const searchTerm = text.startsWith("/model ") ? text.slice(7).trim() : undefined;
3292
+ this.editor.setText("");
3293
+ await this.handleModelCommand(searchTerm);
3294
+ return;
3295
+ }
3296
+ if (text === "/export" || text.startsWith("/export ")) {
3297
+ await this.handleExportCommand(text);
3298
+ this.editor.setText("");
3299
+ return;
3300
+ }
3301
+ if (text === "/import" || text.startsWith("/import ")) {
3302
+ await this.handleImportCommand(text);
3303
+ this.editor.setText("");
3304
+ return;
3305
+ }
3306
+ if (text === "/share") {
3307
+ await this.handleShareCommand();
3308
+ this.editor.setText("");
3309
+ return;
3310
+ }
3311
+ if (text === "/copy") {
3312
+ await this.handleCopyCommand();
3313
+ this.editor.setText("");
3314
+ return;
3315
+ }
3316
+ if (text === "/name" || text.startsWith("/name ")) {
3317
+ await this.handleNameCommand(text);
3318
+ this.editor.setText("");
3319
+ return;
3320
+ }
3321
+ if (text === "/session") {
3322
+ await this.handleSessionCommand();
3323
+ this.editor.setText("");
3324
+ return;
3325
+ }
3326
+ if (text === "/changelog") {
3327
+ this.handleChangelogCommand();
3328
+ this.editor.setText("");
3329
+ return;
3330
+ }
3331
+ if (text === "/keybindings") {
3332
+ this.editor.setText("");
3333
+ await this.handleKeybindingsCommand();
3334
+ return;
3335
+ }
3336
+ if (text === "/hotkeys") {
3337
+ this.handleHotkeysCommand();
3338
+ this.editor.setText("");
3339
+ return;
3340
+ }
3341
+ if (text === "/fork") {
3342
+ await this.showUserMessageSelector();
3343
+ this.editor.setText("");
3344
+ return;
3345
+ }
3346
+ if (text === "/clone") {
3347
+ this.editor.setText("");
3348
+ await this.handleCloneCommand();
3349
+ return;
3350
+ }
3351
+ if (text === "/tree") {
3352
+ this.showTreeSelector();
3353
+ this.editor.setText("");
3354
+ return;
3355
+ }
3356
+ if (text === "/trust") {
3357
+ this.showTrustSelector();
3358
+ this.editor.setText("");
3359
+ return;
3360
+ }
3361
+ if (text === "/login" || text.startsWith("/login ")) {
3362
+ const providerRef = text.startsWith("/login ") ? text.slice(7).trim() : undefined;
3363
+ this.editor.setText("");
3364
+ await this.handleLoginCommand(providerRef);
3365
+ return;
3366
+ }
3367
+ if (text === "/logout") {
3368
+ this.showOAuthSelector("logout");
3369
+ this.editor.setText("");
3370
+ return;
3371
+ }
3372
+ if (text === "/new") {
3373
+ this.editor.setText("");
3374
+ await this.handleClearCommand();
3375
+ return;
3376
+ }
3377
+ if (text === "/compact" || text.startsWith("/compact ")) {
3378
+ const customInstructions = text.startsWith("/compact ") ? text.slice(9).trim() : undefined;
3379
+ this.editor.setText("");
3380
+ await this.handleCompactCommand(customInstructions);
3381
+ return;
3382
+ }
3383
+ if (text === "/reload") {
3384
+ this.editor.setText("");
3385
+ await this.handleReloadCommand();
3386
+ return;
3387
+ }
3388
+ if (text === "/debug") {
3389
+ this.handleDebugCommand();
3390
+ this.editor.setText("");
3391
+ return;
3392
+ }
3393
+ if (text === "/arminsayshi") {
3394
+ this.handleArminSaysHi();
3395
+ this.editor.setText("");
3396
+ return;
3397
+ }
3398
+ if (text === "/dementedelves") {
3399
+ this.handleDementedDelves();
3400
+ this.editor.setText("");
3401
+ return;
3402
+ }
3403
+ if (text === "/resume") {
3404
+ this.showSessionSelector();
3405
+ this.editor.setText("");
3406
+ return;
3407
+ }
3408
+ if (text === "/quit" || text === "/exit") {
3409
+ this.editor.setText("");
3410
+ await this.shutdown();
3360
3411
  return;
3361
3412
  }
3362
- }
3363
- // Queue non-command input during compaction.
3364
- // Extension commands short-circuit at the isExtensionCommand branch above and
3365
- // dispatch immediately inside AgentSession.prompt(), so the only text that
3366
- // reaches this compaction branch is non-command user input, which is queued
3367
- // for delivery after compaction settles.
3368
- //
3369
- // Note: the isExtensionCommand re-check below is already unreachable today
3370
- // (the branch above returns first) and stays harmless after the
3371
- // immediate-dispatch hoist in prompt().
3372
- if (this.session.isCompacting) {
3373
3413
  if (this.isExtensionCommand(text)) {
3374
3414
  this.editor.addToHistory?.(text);
3375
3415
  this.editor.setText("");
@@ -3381,52 +3421,96 @@ export class InteractiveMode {
3381
3421
  this.optimisticUserEchoes.reject(pendingEchoId);
3382
3422
  throw error;
3383
3423
  }
3424
+ return;
3384
3425
  }
3385
- else {
3386
- this.queueCompactionSubmission(text, "steer");
3426
+ // Handle bash command (! for normal, !! for excluded from context)
3427
+ if (text.startsWith("!")) {
3428
+ const isExcluded = text.startsWith("!!");
3429
+ const command = isExcluded ? text.slice(2).trim() : text.slice(1).trim();
3430
+ if (command) {
3431
+ if (this.session.isBashRunning) {
3432
+ this.showWarning("A bash command is already running. Press Esc to cancel it first.");
3433
+ this.editor.setText(text);
3434
+ return;
3435
+ }
3436
+ this.editor.addToHistory?.(text);
3437
+ await this.handleBashCommand(command, isExcluded);
3438
+ this.isBashMode = false;
3439
+ this.updateEditorBorderColor();
3440
+ return;
3441
+ }
3387
3442
  }
3388
- return;
3389
- }
3390
- // If streaming, use prompt() with steer behavior.
3391
- // Extension commands are dispatched immediately by AgentSession.prompt()
3392
- // (short-circuited at the isExtensionCommand branch above); the steer
3393
- // behavior here applies only to ordinary text, prompt template expansion,
3394
- // and queueing.
3395
- if (this.session.isStreaming) {
3396
- // Resolve BEFORE setText(""): the editor's prune chain fires
3397
- // onImageMarkersChanged([]) and destroys pendingImages.
3443
+ // Queue non-command input during compaction.
3444
+ // Extension commands short-circuit at the isExtensionCommand branch above and
3445
+ // dispatch immediately inside AgentSession.prompt(), so the only text that
3446
+ // reaches this compaction branch is non-command user input, which is queued
3447
+ // for delivery after compaction settles.
3448
+ //
3449
+ // Note: the isExtensionCommand re-check below is already unreachable today
3450
+ // (the branch above returns first) and stays harmless after the
3451
+ // immediate-dispatch hoist in prompt().
3452
+ if (this.session.isCompacting) {
3453
+ if (this.isExtensionCommand(text)) {
3454
+ this.editor.addToHistory?.(text);
3455
+ this.editor.setText("");
3456
+ const pendingEchoId = this.optimisticUserEchoes.begin(text);
3457
+ try {
3458
+ await this.session.prompt(text, this.optimisticUserEchoes.promptOptions(pendingEchoId));
3459
+ }
3460
+ catch (error) {
3461
+ this.optimisticUserEchoes.reject(pendingEchoId);
3462
+ throw error;
3463
+ }
3464
+ }
3465
+ else {
3466
+ this.queueCompactionSubmission(text, "steer");
3467
+ }
3468
+ return;
3469
+ }
3470
+ // If streaming, use prompt() with steer behavior.
3471
+ // Extension commands are dispatched immediately by AgentSession.prompt()
3472
+ // (short-circuited at the isExtensionCommand branch above); the steer
3473
+ // behavior here applies only to ordinary text, prompt template expansion,
3474
+ // and queueing.
3475
+ if (this.session.isStreaming) {
3476
+ // Resolve BEFORE setText(""): the editor's prune chain fires
3477
+ // onImageMarkersChanged([]) and destroys pendingImages.
3478
+ const images = preResolvedImages ?? this.takeSubmissionImages(text);
3479
+ this.editor.addToHistory?.(text);
3480
+ this.editor.setText("");
3481
+ const pendingEchoId = this.optimisticUserEchoes.begin(text);
3482
+ try {
3483
+ await this.session.prompt(text, {
3484
+ streamingBehavior: "steer",
3485
+ ...(images.length > 0 ? { images } : {}),
3486
+ ...this.optimisticUserEchoes.promptOptions(pendingEchoId),
3487
+ });
3488
+ }
3489
+ catch (error) {
3490
+ this.optimisticUserEchoes.reject(pendingEchoId);
3491
+ throw error;
3492
+ }
3493
+ this.updatePendingMessagesDisplay();
3494
+ this.ui.requestRender();
3495
+ return;
3496
+ }
3497
+ // Normal message submission
3498
+ // First, move any pending bash components to chat
3499
+ this.flushPendingBashComponents();
3398
3500
  const images = preResolvedImages ?? this.takeSubmissionImages(text);
3399
- this.editor.addToHistory?.(text);
3400
- this.editor.setText("");
3401
3501
  const pendingEchoId = this.optimisticUserEchoes.begin(text);
3402
- try {
3403
- await this.session.prompt(text, {
3404
- streamingBehavior: "steer",
3405
- ...(images.length > 0 ? { images } : {}),
3406
- ...this.optimisticUserEchoes.promptOptions(pendingEchoId),
3407
- });
3502
+ const submission = images.length > 0 ? { text, images, pendingEchoId } : { text, pendingEchoId };
3503
+ if (this.onInputCallback) {
3504
+ this.onInputCallback(submission);
3408
3505
  }
3409
- catch (error) {
3410
- this.optimisticUserEchoes.reject(pendingEchoId);
3411
- throw error;
3506
+ else {
3507
+ this.pendingUserInputs.push(submission);
3412
3508
  }
3413
- this.updatePendingMessagesDisplay();
3414
- this.ui.requestRender();
3415
- return;
3416
- }
3417
- // Normal message submission
3418
- // First, move any pending bash components to chat
3419
- this.flushPendingBashComponents();
3420
- const images = preResolvedImages ?? this.takeSubmissionImages(text);
3421
- const pendingEchoId = this.optimisticUserEchoes.begin(text);
3422
- const submission = images.length > 0 ? { text, images, pendingEchoId } : { text, pendingEchoId };
3423
- if (this.onInputCallback) {
3424
- this.onInputCallback(submission);
3509
+ this.editor.addToHistory?.(text);
3425
3510
  }
3426
- else {
3427
- this.pendingUserInputs.push(submission);
3511
+ catch (error) {
3512
+ this.showError(error instanceof Error ? error.message : String(error));
3428
3513
  }
3429
- this.editor.addToHistory?.(text);
3430
3514
  };
3431
3515
  }
3432
3516
  subscribeToAgent() {
@@ -3716,7 +3800,23 @@ export class InteractiveMode {
3716
3800
  InteractiveMode.restoreCompactionEscapeOverride(this);
3717
3801
  this.clearStatusIndicator("compaction");
3718
3802
  this.autoCompactionProgressText = "";
3719
- if (event.aborted) {
3803
+ // Checked before `aborted`: production external-owner rejections are
3804
+ // emitted via `_rejectCompaction(..., true, reason)` and carry
3805
+ // `aborted: true`, so this branch must win for auto reasons or the
3806
+ // delegation state renders as a per-turn red error.
3807
+ if (event.rejectionCause === "external-owner" && event.reason !== "manual") {
3808
+ // Auto compaction is delegated to an external owner (Claude Agent SDK).
3809
+ // This is expected state, not an error: surface it at most once per
3810
+ // delegation episode as a muted informational line and mark the footer
3811
+ // so the saturated context meter reads as "handled natively".
3812
+ if (!this.externalOwnerCompactionNoticeShown) {
3813
+ this.externalOwnerCompactionNoticeShown = true;
3814
+ this.chatContainer.addChild(new Spacer(1));
3815
+ this.chatContainer.addChild(new Text(theme.fg("muted", "The Claude Agent SDK manages and compacts this session's context natively."), 1, 0));
3816
+ }
3817
+ this.footer?.setCompactionDelegated?.(true);
3818
+ }
3819
+ else if (event.aborted) {
3720
3820
  // Prefer the extension-provided reason over the generic "cancelled"
3721
3821
  // label so per-turn-cap / circuit-breaker / provider-error cancels are
3722
3822
  // no longer indistinguishable from a user-triggered abort.
@@ -3738,15 +3838,24 @@ export class InteractiveMode {
3738
3838
  // Keep the structural fallback for focused handler consumers while using
3739
3839
  // the session-owned manager on the real TUI path.
3740
3840
  const sessionManager = this.session.sessionManager ?? this.sessionManager;
3741
- const entries = sessionManager.buildContextEntries();
3841
+ let entries = sessionManager?.buildContextEntries?.() ?? [];
3742
3842
  if (entries[0]?.type !== "compaction") {
3743
- throw new Error("Completed compaction is missing from the session context");
3843
+ try {
3844
+ sessionManager?.reloadFromDisk?.();
3845
+ entries = sessionManager?.buildContextEntries?.() ?? entries;
3846
+ }
3847
+ catch {
3848
+ // Retain existing entries on reload error
3849
+ }
3744
3850
  }
3745
3851
  this.chatContainer.clear();
3746
3852
  const summaryMessage = createCompactionSummaryMessage(sanitizeTerminalLabel(event.result.summary), event.result.tokensBefore, new Date().toISOString(), event.result.details);
3747
3853
  if (typeof this.renderSessionEntries === "function") {
3748
3854
  // The latest compaction is prepended for model context; append it below at its chronological position.
3749
- this.renderSessionEntries(entries.slice(1));
3855
+ const entriesToRender = entries[0]?.type === "compaction"
3856
+ ? entries.slice(1)
3857
+ : entries.filter((e) => e.type !== "compaction");
3858
+ this.renderSessionEntries(entriesToRender);
3750
3859
  this.addMessageToChat(summaryMessage);
3751
3860
  if (event.result.usage) {
3752
3861
  this.addCompactionCostNotice({
@@ -3763,6 +3872,9 @@ export class InteractiveMode {
3763
3872
  this.addMessageToChat(summaryMessage);
3764
3873
  }
3765
3874
  this.footer.invalidate();
3875
+ // A real compaction landed: the delegation episode (if any) is over.
3876
+ this.externalOwnerCompactionNoticeShown = false;
3877
+ this.footer?.setCompactionDelegated?.(false);
3766
3878
  }
3767
3879
  else if (event.errorMessage) {
3768
3880
  const errorMessage = sanitizeTerminalLabel(event.errorMessage);
@@ -3831,6 +3943,14 @@ export class InteractiveMode {
3831
3943
  this.ui.requestRender();
3832
3944
  break;
3833
3945
  }
3946
+ case "model_changed":
3947
+ // Shared-host/other-client model switches arrive as model_changed wire
3948
+ // events; the new model must not inherit the previous model's
3949
+ // SDK-delegation episode (post-#1188 core emits no repeat rejection to
3950
+ // self-heal a stale marker).
3951
+ this.externalOwnerCompactionNoticeShown = false;
3952
+ this.footer?.setCompactionDelegated?.(false);
3953
+ break;
3834
3954
  case "retry_fallback_applied": {
3835
3955
  if (this.pendingZeroDelayRetryIndicator) {
3836
3956
  this.pendingZeroDelayRetryIndicator.fallbackApplied = true;
@@ -3844,6 +3964,10 @@ export class InteractiveMode {
3844
3964
  why: `Retry switched models (${event.reason}); the turn continues on ${event.to}.`,
3845
3965
  });
3846
3966
  this.setExtensionStatus(FALLBACK_STATUS_KEY, `fallback: ${event.to}`);
3967
+ // Provider/model failover ends any external-owner delegation episode:
3968
+ // the fallback model does not inherit SDK-owned compaction state.
3969
+ this.externalOwnerCompactionNoticeShown = false;
3970
+ this.footer?.setCompactionDelegated?.(false);
3847
3971
  break;
3848
3972
  }
3849
3973
  case "retry_fallback_succeeded":
@@ -4383,6 +4507,11 @@ export class InteractiveMode {
4383
4507
  this.chatContainer.addChild(new Text(text, 1, 0));
4384
4508
  }
4385
4509
  renderInitialMessages() {
4510
+ // Any full transcript rerender ends the external-owner delegation episode:
4511
+ // the rendered notice is gone, so the guard must re-arm and the footer
4512
+ // marker must not persist as stale state.
4513
+ this.externalOwnerCompactionNoticeShown = false;
4514
+ this.footer?.setCompactionDelegated?.(false);
4386
4515
  const entries = this.sessionManager.buildContextEntries();
4387
4516
  this.renderSessionEntries(entries, {
4388
4517
  updateFooter: true,
@@ -4441,6 +4570,12 @@ export class InteractiveMode {
4441
4570
  };
4442
4571
  }
4443
4572
  rebuildChatFromMessages() {
4573
+ try {
4574
+ this.sessionManager?.reloadFromDisk?.();
4575
+ }
4576
+ catch {
4577
+ // Keep in-memory entries if file reload is unavailable
4578
+ }
4444
4579
  this.chatContainer.clear();
4445
4580
  this.renderSessionEntries(this.sessionManager.buildContextEntries());
4446
4581
  }
@@ -4561,6 +4696,10 @@ export class InteractiveMode {
4561
4696
  }
4562
4697
  catch { }
4563
4698
  restoreInteractiveStderr();
4699
+ const storageMessage = storageWriteCrashMessage(error);
4700
+ if (storageMessage !== undefined) {
4701
+ console.error(storageMessage);
4702
+ }
4564
4703
  console.error(`${APP_NAME} exiting due to uncaughtException:`);
4565
4704
  console.error(error);
4566
4705
  process.exit(1);
@@ -4762,6 +4901,9 @@ export class InteractiveMode {
4762
4901
  }
4763
4902
  else {
4764
4903
  this.footer.invalidate();
4904
+ // A model switch ends any external-owner delegation episode.
4905
+ this.externalOwnerCompactionNoticeShown = false;
4906
+ this.footer?.setCompactionDelegated?.(false);
4765
4907
  this.updateEditorBorderColor();
4766
4908
  const thinkingStr = result.model.reasoning && result.thinkingLevel !== "off" ? ` (thinking: ${result.thinkingLevel})` : "";
4767
4909
  const systemPromptStr = result.systemPromptChange?.systemPromptName
@@ -5526,6 +5668,9 @@ export class InteractiveMode {
5526
5668
  try {
5527
5669
  const systemPromptChange = await this.session.setModel(model);
5528
5670
  this.footer.invalidate();
5671
+ // A model switch ends any external-owner delegation episode.
5672
+ this.externalOwnerCompactionNoticeShown = false;
5673
+ this.footer?.setCompactionDelegated?.(false);
5529
5674
  this.updateEditorBorderColor();
5530
5675
  const systemPromptStr = systemPromptChange?.systemPromptName
5531
5676
  ? ` (optimized system prompt applied: ${systemPromptChange.systemPromptName})`
@@ -6563,6 +6708,12 @@ export class InteractiveMode {
6563
6708
  this.resetExtensionUI();
6564
6709
  this.hideThinkingBlock = this.settingsManager.getHideThinkingBlock();
6565
6710
  this.outputPad = this.settingsManager.getOutputPad();
6711
+ // Reload replaces the session runner: a genuine ownership boundary, so the
6712
+ // external-owner delegation episode ends here (settings-only rebuilds below
6713
+ // go through rebuildChatFromMessages and must NOT reset it — post-#1188 core
6714
+ // emits no repeat rejection event to restore cleared state).
6715
+ this.externalOwnerCompactionNoticeShown = false;
6716
+ this.footer?.setCompactionDelegated?.(false);
6566
6717
  this.rebuildChatFromMessages();
6567
6718
  time("chatRebuild", "reload");
6568
6719
  chatRestoredBeforeSessionStart = true;
@@ -6617,7 +6768,7 @@ export class InteractiveMode {
6617
6768
  const outputPath = this.getPathCommandArgument(text, "/export");
6618
6769
  try {
6619
6770
  if (outputPath?.endsWith(".jsonl")) {
6620
- const filePath = this.session.exportToJsonl(outputPath);
6771
+ const filePath = await this.session.exportToJsonl(outputPath);
6621
6772
  this.showStatus(`Session exported to: ${filePath}`);
6622
6773
  }
6623
6774
  else {
@@ -6806,7 +6957,7 @@ export class InteractiveMode {
6806
6957
  this.showError(error instanceof Error ? error.message : String(error));
6807
6958
  }
6808
6959
  }
6809
- handleNameCommand(text) {
6960
+ async handleNameCommand(text) {
6810
6961
  const name = text.replace(/^\/name\s*/, "").trim();
6811
6962
  if (!name) {
6812
6963
  const currentName = this.sessionManager.getSessionName();
@@ -6820,8 +6971,8 @@ export class InteractiveMode {
6820
6971
  this.ui.requestRender();
6821
6972
  return;
6822
6973
  }
6823
- this.session.setSessionName(name);
6824
- const sessionName = this.sessionManager.getSessionName();
6974
+ await this.session.setSessionName(name);
6975
+ const sessionName = this.session.sessionName;
6825
6976
  if (sessionName !== name) {
6826
6977
  this.showWarning(`Session name was normalized from ${JSON.stringify(name)} to ${JSON.stringify(sessionName)}`);
6827
6978
  }
@@ -6832,7 +6983,7 @@ export class InteractiveMode {
6832
6983
  async handleSessionCommand() {
6833
6984
  // Awaited at the boundary: the shared-host proxy answers this over RPC.
6834
6985
  const stats = await this.session.getSessionStats();
6835
- const sessionName = this.sessionManager.getSessionName();
6986
+ const sessionName = this.session.sessionName;
6836
6987
  const entries = this.sessionManager.getEntries();
6837
6988
  const cacheWaste = computeCacheWaste(entries, this.session.modelRuntime);
6838
6989
  // Cost/token totals per provider/model actually used (e.g. OpenRouter `auto`
@@ -7114,7 +7265,7 @@ export class InteractiveMode {
7114
7265
  }
7115
7266
  this.bashComponent.setComplete(result.exitCode, result.cancelled, result.truncated ? { truncated: true, content: result.output } : undefined, result.fullOutputPath);
7116
7267
  // Record the result in session
7117
- this.session.recordBashResult(command, result, { excludeFromContext });
7268
+ void this.session.recordBashResult(command, result, { excludeFromContext });
7118
7269
  this.bashComponent = undefined;
7119
7270
  this.ui.requestRender();
7120
7271
  return;