@code-yeongyu/senpi 2026.8.27 → 2026.8.28-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 (181) hide show
  1. package/CHANGELOG.md +83 -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 +5 -0
  10. package/dist/core/agent-session.d.ts.map +1 -1
  11. package/dist/core/agent-session.js +1 -0
  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/credential-accounts.d.ts +31 -0
  18. package/dist/core/credential-accounts.d.ts.map +1 -0
  19. package/dist/core/credential-accounts.js +135 -0
  20. package/dist/core/credential-accounts.js.map +1 -0
  21. package/dist/core/credential-pool/classify.d.ts +6 -1
  22. package/dist/core/credential-pool/classify.d.ts.map +1 -1
  23. package/dist/core/credential-pool/classify.js +22 -5
  24. package/dist/core/credential-pool/classify.js.map +1 -1
  25. package/dist/core/credential-pool/failover.d.ts +8 -0
  26. package/dist/core/credential-pool/failover.d.ts.map +1 -1
  27. package/dist/core/credential-pool/failover.js +24 -6
  28. package/dist/core/credential-pool/failover.js.map +1 -1
  29. package/dist/core/credential-pool/rotation-stream.d.ts +13 -2
  30. package/dist/core/credential-pool/rotation-stream.d.ts.map +1 -1
  31. package/dist/core/credential-pool/rotation-stream.js +97 -10
  32. package/dist/core/credential-pool/rotation-stream.js.map +1 -1
  33. package/dist/core/extensions/builtin/account/index.d.ts +9 -0
  34. package/dist/core/extensions/builtin/account/index.d.ts.map +1 -0
  35. package/dist/core/extensions/builtin/account/index.js +69 -0
  36. package/dist/core/extensions/builtin/account/index.js.map +1 -0
  37. package/dist/core/extensions/builtin/goal/index.d.ts.map +1 -1
  38. package/dist/core/extensions/builtin/goal/index.js +7 -0
  39. package/dist/core/extensions/builtin/goal/index.js.map +1 -1
  40. package/dist/core/extensions/builtin/imagegen/index.d.ts.map +1 -1
  41. package/dist/core/extensions/builtin/imagegen/index.js +10 -3
  42. package/dist/core/extensions/builtin/imagegen/index.js.map +1 -1
  43. package/dist/core/extensions/builtin/index.d.ts.map +1 -1
  44. package/dist/core/extensions/builtin/index.js +4 -0
  45. package/dist/core/extensions/builtin/index.js.map +1 -1
  46. package/dist/core/extensions/builtin/service-tier.d.ts.map +1 -1
  47. package/dist/core/extensions/builtin/service-tier.js +13 -0
  48. package/dist/core/extensions/builtin/service-tier.js.map +1 -1
  49. package/dist/core/extensions/builtin/terminal/extension.d.ts.map +1 -1
  50. package/dist/core/extensions/builtin/terminal/extension.js +4 -2
  51. package/dist/core/extensions/builtin/terminal/extension.js.map +1 -1
  52. package/dist/core/model-config-schema.d.ts +4 -4
  53. package/dist/core/model-runtime.d.ts +2 -0
  54. package/dist/core/model-runtime.d.ts.map +1 -1
  55. package/dist/core/model-runtime.js +74 -37
  56. package/dist/core/model-runtime.js.map +1 -1
  57. package/dist/core/retry-fallback/chains.d.ts +23 -1
  58. package/dist/core/retry-fallback/chains.d.ts.map +1 -1
  59. package/dist/core/retry-fallback/chains.js +59 -3
  60. package/dist/core/retry-fallback/chains.js.map +1 -1
  61. package/dist/core/retry-fallback/controller.d.ts.map +1 -1
  62. package/dist/core/retry-fallback/controller.js +9 -2
  63. package/dist/core/retry-fallback/controller.js.map +1 -1
  64. package/dist/core/retry-fallback/settings.d.ts.map +1 -1
  65. package/dist/core/retry-fallback/settings.js +9 -0
  66. package/dist/core/retry-fallback/settings.js.map +1 -1
  67. package/dist/core/retry-fallback/validate.js +1 -1
  68. package/dist/core/retry-fallback/validate.js.map +1 -1
  69. package/dist/core/sdk.js +1 -1
  70. package/dist/core/sdk.js.map +1 -1
  71. package/dist/core/session-manager.d.ts +9 -0
  72. package/dist/core/session-manager.d.ts.map +1 -1
  73. package/dist/core/session-manager.js +31 -0
  74. package/dist/core/session-manager.js.map +1 -1
  75. package/dist/main.d.ts +7 -0
  76. package/dist/main.d.ts.map +1 -1
  77. package/dist/main.js +22 -2
  78. package/dist/main.js.map +1 -1
  79. package/dist/modes/app-server/server/account.d.ts.map +1 -1
  80. package/dist/modes/app-server/server/account.js +5 -5
  81. package/dist/modes/app-server/server/account.js.map +1 -1
  82. package/dist/modes/interactive/components/footer.d.ts +11 -3
  83. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  84. package/dist/modes/interactive/components/footer.js +31 -1
  85. package/dist/modes/interactive/components/footer.js.map +1 -1
  86. package/dist/modes/interactive/grok/chrome.d.ts +4 -4
  87. package/dist/modes/interactive/grok/chrome.d.ts.map +1 -1
  88. package/dist/modes/interactive/grok/chrome.js.map +1 -1
  89. package/dist/modes/interactive/grok/footer.d.ts +3 -3
  90. package/dist/modes/interactive/grok/footer.d.ts.map +1 -1
  91. package/dist/modes/interactive/grok/footer.js.map +1 -1
  92. package/dist/modes/interactive/interactive-host-runtime.d.ts +16 -1
  93. package/dist/modes/interactive/interactive-host-runtime.d.ts.map +1 -1
  94. package/dist/modes/interactive/interactive-host-runtime.js +321 -36
  95. package/dist/modes/interactive/interactive-host-runtime.js.map +1 -1
  96. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  97. package/dist/modes/interactive/interactive-mode.js +295 -250
  98. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  99. package/dist/modes/rpc/connection-handler.d.ts.map +1 -1
  100. package/dist/modes/rpc/connection-handler.js +144 -29
  101. package/dist/modes/rpc/connection-handler.js.map +1 -1
  102. package/dist/modes/rpc/multi-session-host.js +14 -4
  103. package/dist/modes/rpc/multi-session-host.js.map +1 -1
  104. package/dist/modes/rpc/rpc-client.d.ts +90 -8
  105. package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
  106. package/dist/modes/rpc/rpc-client.js +118 -23
  107. package/dist/modes/rpc/rpc-client.js.map +1 -1
  108. package/dist/modes/rpc/rpc-mode.d.ts +3 -1
  109. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  110. package/dist/modes/rpc/rpc-mode.js +3 -1
  111. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  112. package/dist/modes/rpc/rpc-types.d.ts +164 -2
  113. package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  114. package/dist/modes/rpc/rpc-types.js.map +1 -1
  115. package/dist/modes/rpc/session-command-router.d.ts +22 -0
  116. package/dist/modes/rpc/session-command-router.d.ts.map +1 -1
  117. package/dist/modes/rpc/session-command-router.js +153 -14
  118. package/dist/modes/rpc/session-command-router.js.map +1 -1
  119. package/dist/modes/rpc/session-event-writer.d.ts +2 -0
  120. package/dist/modes/rpc/session-event-writer.d.ts.map +1 -1
  121. package/dist/modes/rpc/session-event-writer.js +4 -0
  122. package/dist/modes/rpc/session-event-writer.js.map +1 -1
  123. package/dist/modes/rpc/session-registry.d.ts +16 -0
  124. package/dist/modes/rpc/session-registry.d.ts.map +1 -1
  125. package/dist/modes/rpc/session-registry.js +40 -3
  126. package/dist/modes/rpc/session-registry.js.map +1 -1
  127. package/docs/providers.md +48 -0
  128. package/docs/rpc.md +43 -4
  129. package/node_modules/@code-yeongyu/senpi-codemode/CHANGELOG.md +26 -0
  130. package/node_modules/@code-yeongyu/senpi-codemode/package.json +4 -4
  131. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/context-manager.ts +13 -1
  132. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/inline-worker.ts +15 -1
  133. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/py/transport.ts +14 -1
  134. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/edit.d.ts.map +1 -1
  135. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/edit.js +20 -4
  136. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/edit.js.map +1 -1
  137. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/index.d.ts +1 -1
  138. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/index.d.ts.map +1 -1
  139. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/index.js.map +1 -1
  140. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/post-mutate.d.ts +21 -0
  141. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/post-mutate.d.ts.map +1 -0
  142. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/post-mutate.js +27 -0
  143. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/post-mutate.js.map +1 -0
  144. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/tool-context.d.ts +24 -0
  145. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/tool-context.d.ts.map +1 -1
  146. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/tool-context.js.map +1 -1
  147. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/write.d.ts.map +1 -1
  148. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/write.js +7 -2
  149. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/write.js.map +1 -1
  150. package/node_modules/@earendil-works/pi-agent-core/package.json +3 -3
  151. package/node_modules/@earendil-works/pi-ai/dist/auth/credential-store.d.ts +2 -1
  152. package/node_modules/@earendil-works/pi-ai/dist/auth/credential-store.d.ts.map +1 -1
  153. package/node_modules/@earendil-works/pi-ai/dist/auth/credential-store.js +2 -1
  154. package/node_modules/@earendil-works/pi-ai/dist/auth/credential-store.js.map +1 -1
  155. package/node_modules/@earendil-works/pi-ai/dist/auth/types.d.ts +3 -2
  156. package/node_modules/@earendil-works/pi-ai/dist/auth/types.d.ts.map +1 -1
  157. package/node_modules/@earendil-works/pi-ai/dist/auth/types.js.map +1 -1
  158. package/node_modules/@earendil-works/pi-ai/dist/providers/data/.manifest.json +1 -1
  159. package/node_modules/@earendil-works/pi-ai/dist/providers/data/alibaba-token-plan.json +1 -1
  160. package/node_modules/@earendil-works/pi-ai/dist/providers/data/amazon-bedrock.json +1 -1
  161. package/node_modules/@earendil-works/pi-ai/dist/providers/data/baseten.json +1 -1
  162. package/node_modules/@earendil-works/pi-ai/dist/providers/data/cloudflare-ai-gateway.json +1 -1
  163. package/node_modules/@earendil-works/pi-ai/dist/providers/data/cloudflare-workers-ai.json +1 -1
  164. package/node_modules/@earendil-works/pi-ai/dist/providers/data/fireworks.json +1 -1
  165. package/node_modules/@earendil-works/pi-ai/dist/providers/data/github-copilot.json +1 -1
  166. package/node_modules/@earendil-works/pi-ai/dist/providers/data/huggingface.json +1 -1
  167. package/node_modules/@earendil-works/pi-ai/dist/providers/data/nvidia.json +1 -1
  168. package/node_modules/@earendil-works/pi-ai/dist/providers/data/openai-codex.json +1 -1
  169. package/node_modules/@earendil-works/pi-ai/dist/providers/data/openai.json +1 -1
  170. package/node_modules/@earendil-works/pi-ai/dist/providers/data/opencode-go.json +1 -1
  171. package/node_modules/@earendil-works/pi-ai/dist/providers/data/opencode.json +1 -1
  172. package/node_modules/@earendil-works/pi-ai/dist/providers/data/openrouter.json +1 -1
  173. package/node_modules/@earendil-works/pi-ai/dist/providers/data/qwen-token-plan-cn.json +1 -1
  174. package/node_modules/@earendil-works/pi-ai/dist/providers/data/qwen-token-plan.json +1 -1
  175. package/node_modules/@earendil-works/pi-ai/dist/providers/data/together.json +1 -1
  176. package/node_modules/@earendil-works/pi-ai/dist/providers/data/vercel-ai-gateway.json +1 -1
  177. package/node_modules/@earendil-works/pi-ai/package.json +2 -2
  178. package/node_modules/@earendil-works/pi-pty/package.json +1 -1
  179. package/node_modules/@earendil-works/pi-telemetry/package.json +1 -1
  180. package/node_modules/@earendil-works/pi-tui/package.json +1 -1
  181. 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");
@@ -490,6 +503,8 @@ export class InteractiveMode {
490
503
  this.ui.setMaxRenderFps(fps);
491
504
  }
492
505
  // Convenience accessors
506
+ // The session may be the local AgentSession or the shared-host RPC proxy; the
507
+ // four reads widened on InteractiveSession must be awaited at every call site.
493
508
  get session() {
494
509
  return this.runtimeHost.session;
495
510
  }
@@ -2985,7 +3000,7 @@ export class InteractiveMode {
2985
3000
  this.showTreeSelector();
2986
3001
  }
2987
3002
  else {
2988
- this.showUserMessageSelector();
3003
+ void this.showUserMessageSelector().catch((error) => this.showError(error instanceof Error ? error.message : String(error)));
2989
3004
  }
2990
3005
  this.lastEscapeTime = 0;
2991
3006
  }
@@ -2999,7 +3014,9 @@ export class InteractiveMode {
2999
3014
  this.defaultEditor.onAction("app.clear", () => this.handleCtrlC());
3000
3015
  this.defaultEditor.onCtrlD = () => this.handleCtrlD();
3001
3016
  this.defaultEditor.onAction("app.suspend", () => this.handleCtrlZ());
3002
- this.defaultEditor.onAction("app.thinking.cycle", () => this.cycleThinkingLevel());
3017
+ this.defaultEditor.onAction("app.thinking.cycle", () => {
3018
+ void this.cycleThinkingLevel().catch((error) => this.showError(error instanceof Error ? error.message : String(error)));
3019
+ });
3003
3020
  this.defaultEditor.onAction("app.model.cycleForward", () => this.cycleModel("forward"));
3004
3021
  this.defaultEditor.onAction("app.model.cycleBackward", () => this.cycleModel("backward"));
3005
3022
  // Global debug handler on TUI (works regardless of focus)
@@ -3021,7 +3038,7 @@ export class InteractiveMode {
3021
3038
  this.defaultEditor.onAction("app.message.dequeue", () => this.handleDequeue());
3022
3039
  this.defaultEditor.onAction("app.session.new", () => this.handleClearCommand());
3023
3040
  this.defaultEditor.onAction("app.session.tree", () => this.showTreeSelector());
3024
- this.defaultEditor.onAction("app.session.fork", () => this.showUserMessageSelector());
3041
+ this.defaultEditor.onAction("app.session.fork", () => void this.showUserMessageSelector().catch((error) => this.showError(error instanceof Error ? error.message : String(error))));
3025
3042
  this.defaultEditor.onAction("app.session.resume", () => this.showSessionSelector());
3026
3043
  this.defaultEditor.onChange = (text) => {
3027
3044
  const wasBashMode = this.isBashMode;
@@ -3177,197 +3194,158 @@ export class InteractiveMode {
3177
3194
  }
3178
3195
  setupEditorSubmitHandler() {
3179
3196
  this.defaultEditor.onSubmit = async (text) => {
3180
- // Capture-then-clear BEFORE any branch: handleFollowUp's non-streaming
3181
- // path pre-resolves images and hands off here, but slash / extension /
3182
- // bash submissions return before the consuming branches below. Clearing
3183
- // only after use would leak a stale array into a later ordinary
3184
- // submission whose text never references it.
3185
- const preResolvedImages = this.preResolvedSubmissionImages;
3186
- this.preResolvedSubmissionImages = undefined;
3187
- this.hideShortcutOverlay();
3188
- this.lastEditorText = "";
3189
- text = text.trim();
3190
- if (!text)
3191
- return;
3192
- // Handle commands
3193
- if (text === "/settings") {
3194
- this.showSettingsSelector();
3195
- this.editor.setText("");
3196
- return;
3197
- }
3198
- if (text === "/favorite-models") {
3199
- this.editor.setText("");
3200
- await this.showFavoriteModelsSelector();
3201
- return;
3202
- }
3203
- if (text === "/scoped-models") {
3204
- this.editor.setText("");
3205
- this.showScopedModelsSelector();
3206
- return;
3207
- }
3208
- if (text === "/model" || text.startsWith("/model ")) {
3209
- const searchTerm = text.startsWith("/model ") ? text.slice(7).trim() : undefined;
3210
- this.editor.setText("");
3211
- await this.handleModelCommand(searchTerm);
3212
- return;
3213
- }
3214
- if (text === "/export" || text.startsWith("/export ")) {
3215
- await this.handleExportCommand(text);
3216
- this.editor.setText("");
3217
- return;
3218
- }
3219
- if (text === "/import" || text.startsWith("/import ")) {
3220
- await this.handleImportCommand(text);
3221
- this.editor.setText("");
3222
- return;
3223
- }
3224
- if (text === "/share") {
3225
- await this.handleShareCommand();
3226
- this.editor.setText("");
3227
- return;
3228
- }
3229
- if (text === "/copy") {
3230
- await this.handleCopyCommand();
3231
- this.editor.setText("");
3232
- return;
3233
- }
3234
- if (text === "/name" || text.startsWith("/name ")) {
3235
- this.handleNameCommand(text);
3236
- this.editor.setText("");
3237
- return;
3238
- }
3239
- if (text === "/session") {
3240
- this.handleSessionCommand();
3241
- this.editor.setText("");
3242
- return;
3243
- }
3244
- if (text === "/changelog") {
3245
- this.handleChangelogCommand();
3246
- this.editor.setText("");
3247
- return;
3248
- }
3249
- if (text === "/keybindings") {
3250
- this.editor.setText("");
3251
- await this.handleKeybindingsCommand();
3252
- return;
3253
- }
3254
- if (text === "/hotkeys") {
3255
- this.handleHotkeysCommand();
3256
- this.editor.setText("");
3257
- return;
3258
- }
3259
- if (text === "/fork") {
3260
- this.showUserMessageSelector();
3261
- this.editor.setText("");
3262
- return;
3263
- }
3264
- if (text === "/clone") {
3265
- this.editor.setText("");
3266
- await this.handleCloneCommand();
3267
- return;
3268
- }
3269
- if (text === "/tree") {
3270
- this.showTreeSelector();
3271
- this.editor.setText("");
3272
- return;
3273
- }
3274
- if (text === "/trust") {
3275
- this.showTrustSelector();
3276
- this.editor.setText("");
3277
- return;
3278
- }
3279
- if (text === "/login" || text.startsWith("/login ")) {
3280
- const providerRef = text.startsWith("/login ") ? text.slice(7).trim() : undefined;
3281
- this.editor.setText("");
3282
- await this.handleLoginCommand(providerRef);
3283
- return;
3284
- }
3285
- if (text === "/logout") {
3286
- this.showOAuthSelector("logout");
3287
- this.editor.setText("");
3288
- return;
3289
- }
3290
- if (text === "/new") {
3291
- this.editor.setText("");
3292
- await this.handleClearCommand();
3293
- return;
3294
- }
3295
- if (text === "/compact" || text.startsWith("/compact ")) {
3296
- const customInstructions = text.startsWith("/compact ") ? text.slice(9).trim() : undefined;
3297
- this.editor.setText("");
3298
- await this.handleCompactCommand(customInstructions);
3299
- return;
3300
- }
3301
- if (text === "/reload") {
3302
- this.editor.setText("");
3303
- await this.handleReloadCommand();
3304
- return;
3305
- }
3306
- if (text === "/debug") {
3307
- this.handleDebugCommand();
3308
- this.editor.setText("");
3309
- return;
3310
- }
3311
- if (text === "/arminsayshi") {
3312
- this.handleArminSaysHi();
3313
- this.editor.setText("");
3314
- return;
3315
- }
3316
- if (text === "/dementedelves") {
3317
- this.handleDementedDelves();
3318
- this.editor.setText("");
3319
- return;
3320
- }
3321
- if (text === "/resume") {
3322
- this.showSessionSelector();
3323
- this.editor.setText("");
3324
- return;
3325
- }
3326
- if (text === "/quit" || text === "/exit") {
3327
- this.editor.setText("");
3328
- await this.shutdown();
3329
- return;
3330
- }
3331
- if (this.isExtensionCommand(text)) {
3332
- this.editor.addToHistory?.(text);
3333
- this.editor.setText("");
3334
- const pendingEchoId = this.optimisticUserEchoes.begin(text);
3335
- try {
3336
- await this.session.prompt(text, this.optimisticUserEchoes.promptOptions(pendingEchoId));
3197
+ try {
3198
+ // Capture-then-clear BEFORE any branch: handleFollowUp's non-streaming
3199
+ // path pre-resolves images and hands off here, but slash / extension /
3200
+ // bash submissions return before the consuming branches below. Clearing
3201
+ // only after use would leak a stale array into a later ordinary
3202
+ // submission whose text never references it.
3203
+ const preResolvedImages = this.preResolvedSubmissionImages;
3204
+ this.preResolvedSubmissionImages = undefined;
3205
+ this.hideShortcutOverlay();
3206
+ this.lastEditorText = "";
3207
+ text = text.trim();
3208
+ if (!text)
3209
+ return;
3210
+ // Handle commands
3211
+ if (text === "/settings") {
3212
+ await this.showSettingsSelector();
3213
+ this.editor.setText("");
3214
+ return;
3337
3215
  }
3338
- catch (error) {
3339
- this.optimisticUserEchoes.reject(pendingEchoId);
3340
- throw error;
3216
+ if (text === "/favorite-models") {
3217
+ this.editor.setText("");
3218
+ await this.showFavoriteModelsSelector();
3219
+ return;
3341
3220
  }
3342
- return;
3343
- }
3344
- // Handle bash command (! for normal, !! for excluded from context)
3345
- if (text.startsWith("!")) {
3346
- const isExcluded = text.startsWith("!!");
3347
- const command = isExcluded ? text.slice(2).trim() : text.slice(1).trim();
3348
- if (command) {
3349
- if (this.session.isBashRunning) {
3350
- this.showWarning("A bash command is already running. Press Esc to cancel it first.");
3351
- this.editor.setText(text);
3352
- return;
3353
- }
3354
- this.editor.addToHistory?.(text);
3355
- await this.handleBashCommand(command, isExcluded);
3356
- this.isBashMode = false;
3357
- this.updateEditorBorderColor();
3221
+ if (text === "/scoped-models") {
3222
+ this.editor.setText("");
3223
+ this.showScopedModelsSelector();
3224
+ return;
3225
+ }
3226
+ if (text === "/model" || text.startsWith("/model ")) {
3227
+ const searchTerm = text.startsWith("/model ") ? text.slice(7).trim() : undefined;
3228
+ this.editor.setText("");
3229
+ await this.handleModelCommand(searchTerm);
3230
+ return;
3231
+ }
3232
+ if (text === "/export" || text.startsWith("/export ")) {
3233
+ await this.handleExportCommand(text);
3234
+ this.editor.setText("");
3235
+ return;
3236
+ }
3237
+ if (text === "/import" || text.startsWith("/import ")) {
3238
+ await this.handleImportCommand(text);
3239
+ this.editor.setText("");
3240
+ return;
3241
+ }
3242
+ if (text === "/share") {
3243
+ await this.handleShareCommand();
3244
+ this.editor.setText("");
3245
+ return;
3246
+ }
3247
+ if (text === "/copy") {
3248
+ await this.handleCopyCommand();
3249
+ this.editor.setText("");
3250
+ return;
3251
+ }
3252
+ if (text === "/name" || text.startsWith("/name ")) {
3253
+ await this.handleNameCommand(text);
3254
+ this.editor.setText("");
3255
+ return;
3256
+ }
3257
+ if (text === "/session") {
3258
+ await this.handleSessionCommand();
3259
+ this.editor.setText("");
3260
+ return;
3261
+ }
3262
+ if (text === "/changelog") {
3263
+ this.handleChangelogCommand();
3264
+ this.editor.setText("");
3265
+ return;
3266
+ }
3267
+ if (text === "/keybindings") {
3268
+ this.editor.setText("");
3269
+ await this.handleKeybindingsCommand();
3270
+ return;
3271
+ }
3272
+ if (text === "/hotkeys") {
3273
+ this.handleHotkeysCommand();
3274
+ this.editor.setText("");
3275
+ return;
3276
+ }
3277
+ if (text === "/fork") {
3278
+ await this.showUserMessageSelector();
3279
+ this.editor.setText("");
3280
+ return;
3281
+ }
3282
+ if (text === "/clone") {
3283
+ this.editor.setText("");
3284
+ await this.handleCloneCommand();
3285
+ return;
3286
+ }
3287
+ if (text === "/tree") {
3288
+ this.showTreeSelector();
3289
+ this.editor.setText("");
3290
+ return;
3291
+ }
3292
+ if (text === "/trust") {
3293
+ this.showTrustSelector();
3294
+ this.editor.setText("");
3295
+ return;
3296
+ }
3297
+ if (text === "/login" || text.startsWith("/login ")) {
3298
+ const providerRef = text.startsWith("/login ") ? text.slice(7).trim() : undefined;
3299
+ this.editor.setText("");
3300
+ await this.handleLoginCommand(providerRef);
3301
+ return;
3302
+ }
3303
+ if (text === "/logout") {
3304
+ this.showOAuthSelector("logout");
3305
+ this.editor.setText("");
3306
+ return;
3307
+ }
3308
+ if (text === "/new") {
3309
+ this.editor.setText("");
3310
+ await this.handleClearCommand();
3311
+ return;
3312
+ }
3313
+ if (text === "/compact" || text.startsWith("/compact ")) {
3314
+ const customInstructions = text.startsWith("/compact ") ? text.slice(9).trim() : undefined;
3315
+ this.editor.setText("");
3316
+ await this.handleCompactCommand(customInstructions);
3317
+ return;
3318
+ }
3319
+ if (text === "/reload") {
3320
+ this.editor.setText("");
3321
+ await this.handleReloadCommand();
3322
+ return;
3323
+ }
3324
+ if (text === "/debug") {
3325
+ this.handleDebugCommand();
3326
+ this.editor.setText("");
3327
+ return;
3328
+ }
3329
+ if (text === "/arminsayshi") {
3330
+ this.handleArminSaysHi();
3331
+ this.editor.setText("");
3332
+ return;
3333
+ }
3334
+ if (text === "/dementedelves") {
3335
+ this.handleDementedDelves();
3336
+ this.editor.setText("");
3337
+ return;
3338
+ }
3339
+ if (text === "/resume") {
3340
+ this.showSessionSelector();
3341
+ this.editor.setText("");
3342
+ return;
3343
+ }
3344
+ if (text === "/quit" || text === "/exit") {
3345
+ this.editor.setText("");
3346
+ await this.shutdown();
3358
3347
  return;
3359
3348
  }
3360
- }
3361
- // Queue non-command input during compaction.
3362
- // Extension commands short-circuit at the isExtensionCommand branch above and
3363
- // dispatch immediately inside AgentSession.prompt(), so the only text that
3364
- // reaches this compaction branch is non-command user input, which is queued
3365
- // for delivery after compaction settles.
3366
- //
3367
- // Note: the isExtensionCommand re-check below is already unreachable today
3368
- // (the branch above returns first) and stays harmless after the
3369
- // immediate-dispatch hoist in prompt().
3370
- if (this.session.isCompacting) {
3371
3349
  if (this.isExtensionCommand(text)) {
3372
3350
  this.editor.addToHistory?.(text);
3373
3351
  this.editor.setText("");
@@ -3379,52 +3357,96 @@ export class InteractiveMode {
3379
3357
  this.optimisticUserEchoes.reject(pendingEchoId);
3380
3358
  throw error;
3381
3359
  }
3360
+ return;
3382
3361
  }
3383
- else {
3384
- this.queueCompactionSubmission(text, "steer");
3362
+ // Handle bash command (! for normal, !! for excluded from context)
3363
+ if (text.startsWith("!")) {
3364
+ const isExcluded = text.startsWith("!!");
3365
+ const command = isExcluded ? text.slice(2).trim() : text.slice(1).trim();
3366
+ if (command) {
3367
+ if (this.session.isBashRunning) {
3368
+ this.showWarning("A bash command is already running. Press Esc to cancel it first.");
3369
+ this.editor.setText(text);
3370
+ return;
3371
+ }
3372
+ this.editor.addToHistory?.(text);
3373
+ await this.handleBashCommand(command, isExcluded);
3374
+ this.isBashMode = false;
3375
+ this.updateEditorBorderColor();
3376
+ return;
3377
+ }
3385
3378
  }
3386
- return;
3387
- }
3388
- // If streaming, use prompt() with steer behavior.
3389
- // Extension commands are dispatched immediately by AgentSession.prompt()
3390
- // (short-circuited at the isExtensionCommand branch above); the steer
3391
- // behavior here applies only to ordinary text, prompt template expansion,
3392
- // and queueing.
3393
- if (this.session.isStreaming) {
3394
- // Resolve BEFORE setText(""): the editor's prune chain fires
3395
- // onImageMarkersChanged([]) and destroys pendingImages.
3379
+ // Queue non-command input during compaction.
3380
+ // Extension commands short-circuit at the isExtensionCommand branch above and
3381
+ // dispatch immediately inside AgentSession.prompt(), so the only text that
3382
+ // reaches this compaction branch is non-command user input, which is queued
3383
+ // for delivery after compaction settles.
3384
+ //
3385
+ // Note: the isExtensionCommand re-check below is already unreachable today
3386
+ // (the branch above returns first) and stays harmless after the
3387
+ // immediate-dispatch hoist in prompt().
3388
+ if (this.session.isCompacting) {
3389
+ if (this.isExtensionCommand(text)) {
3390
+ this.editor.addToHistory?.(text);
3391
+ this.editor.setText("");
3392
+ const pendingEchoId = this.optimisticUserEchoes.begin(text);
3393
+ try {
3394
+ await this.session.prompt(text, this.optimisticUserEchoes.promptOptions(pendingEchoId));
3395
+ }
3396
+ catch (error) {
3397
+ this.optimisticUserEchoes.reject(pendingEchoId);
3398
+ throw error;
3399
+ }
3400
+ }
3401
+ else {
3402
+ this.queueCompactionSubmission(text, "steer");
3403
+ }
3404
+ return;
3405
+ }
3406
+ // If streaming, use prompt() with steer behavior.
3407
+ // Extension commands are dispatched immediately by AgentSession.prompt()
3408
+ // (short-circuited at the isExtensionCommand branch above); the steer
3409
+ // behavior here applies only to ordinary text, prompt template expansion,
3410
+ // and queueing.
3411
+ if (this.session.isStreaming) {
3412
+ // Resolve BEFORE setText(""): the editor's prune chain fires
3413
+ // onImageMarkersChanged([]) and destroys pendingImages.
3414
+ const images = preResolvedImages ?? this.takeSubmissionImages(text);
3415
+ this.editor.addToHistory?.(text);
3416
+ this.editor.setText("");
3417
+ const pendingEchoId = this.optimisticUserEchoes.begin(text);
3418
+ try {
3419
+ await this.session.prompt(text, {
3420
+ streamingBehavior: "steer",
3421
+ ...(images.length > 0 ? { images } : {}),
3422
+ ...this.optimisticUserEchoes.promptOptions(pendingEchoId),
3423
+ });
3424
+ }
3425
+ catch (error) {
3426
+ this.optimisticUserEchoes.reject(pendingEchoId);
3427
+ throw error;
3428
+ }
3429
+ this.updatePendingMessagesDisplay();
3430
+ this.ui.requestRender();
3431
+ return;
3432
+ }
3433
+ // Normal message submission
3434
+ // First, move any pending bash components to chat
3435
+ this.flushPendingBashComponents();
3396
3436
  const images = preResolvedImages ?? this.takeSubmissionImages(text);
3397
- this.editor.addToHistory?.(text);
3398
- this.editor.setText("");
3399
3437
  const pendingEchoId = this.optimisticUserEchoes.begin(text);
3400
- try {
3401
- await this.session.prompt(text, {
3402
- streamingBehavior: "steer",
3403
- ...(images.length > 0 ? { images } : {}),
3404
- ...this.optimisticUserEchoes.promptOptions(pendingEchoId),
3405
- });
3438
+ const submission = images.length > 0 ? { text, images, pendingEchoId } : { text, pendingEchoId };
3439
+ if (this.onInputCallback) {
3440
+ this.onInputCallback(submission);
3406
3441
  }
3407
- catch (error) {
3408
- this.optimisticUserEchoes.reject(pendingEchoId);
3409
- throw error;
3442
+ else {
3443
+ this.pendingUserInputs.push(submission);
3410
3444
  }
3411
- this.updatePendingMessagesDisplay();
3412
- this.ui.requestRender();
3413
- return;
3414
- }
3415
- // Normal message submission
3416
- // First, move any pending bash components to chat
3417
- this.flushPendingBashComponents();
3418
- const images = preResolvedImages ?? this.takeSubmissionImages(text);
3419
- const pendingEchoId = this.optimisticUserEchoes.begin(text);
3420
- const submission = images.length > 0 ? { text, images, pendingEchoId } : { text, pendingEchoId };
3421
- if (this.onInputCallback) {
3422
- this.onInputCallback(submission);
3445
+ this.editor.addToHistory?.(text);
3423
3446
  }
3424
- else {
3425
- this.pendingUserInputs.push(submission);
3447
+ catch (error) {
3448
+ this.showError(error instanceof Error ? error.message : String(error));
3426
3449
  }
3427
- this.editor.addToHistory?.(text);
3428
3450
  };
3429
3451
  }
3430
3452
  subscribeToAgent() {
@@ -3485,6 +3507,7 @@ export class InteractiveMode {
3485
3507
  case "thinking_level_changed":
3486
3508
  this.footer.invalidate();
3487
3509
  this.updateEditorBorderColor();
3510
+ this.showStatus(`Thinking level: ${event.level}`);
3488
3511
  break;
3489
3512
  case "high_reasoning_warning":
3490
3513
  this.showHighReasoningWarning(event);
@@ -3735,15 +3758,24 @@ export class InteractiveMode {
3735
3758
  // Keep the structural fallback for focused handler consumers while using
3736
3759
  // the session-owned manager on the real TUI path.
3737
3760
  const sessionManager = this.session.sessionManager ?? this.sessionManager;
3738
- const entries = sessionManager.buildContextEntries();
3761
+ let entries = sessionManager?.buildContextEntries?.() ?? [];
3739
3762
  if (entries[0]?.type !== "compaction") {
3740
- throw new Error("Completed compaction is missing from the session context");
3763
+ try {
3764
+ sessionManager?.reloadFromDisk?.();
3765
+ entries = sessionManager?.buildContextEntries?.() ?? entries;
3766
+ }
3767
+ catch {
3768
+ // Retain existing entries on reload error
3769
+ }
3741
3770
  }
3742
3771
  this.chatContainer.clear();
3743
3772
  const summaryMessage = createCompactionSummaryMessage(sanitizeTerminalLabel(event.result.summary), event.result.tokensBefore, new Date().toISOString(), event.result.details);
3744
3773
  if (typeof this.renderSessionEntries === "function") {
3745
3774
  // The latest compaction is prepended for model context; append it below at its chronological position.
3746
- this.renderSessionEntries(entries.slice(1));
3775
+ const entriesToRender = entries[0]?.type === "compaction"
3776
+ ? entries.slice(1)
3777
+ : entries.filter((e) => e.type !== "compaction");
3778
+ this.renderSessionEntries(entriesToRender);
3747
3779
  this.addMessageToChat(summaryMessage);
3748
3780
  if (event.result.usage) {
3749
3781
  this.addCompactionCostNotice({
@@ -4438,6 +4470,12 @@ export class InteractiveMode {
4438
4470
  };
4439
4471
  }
4440
4472
  rebuildChatFromMessages() {
4473
+ try {
4474
+ this.sessionManager?.reloadFromDisk?.();
4475
+ }
4476
+ catch {
4477
+ // Keep in-memory entries if file reload is unavailable
4478
+ }
4441
4479
  this.chatContainer.clear();
4442
4480
  this.renderSessionEntries(this.sessionManager.buildContextEntries());
4443
4481
  }
@@ -4558,6 +4596,10 @@ export class InteractiveMode {
4558
4596
  }
4559
4597
  catch { }
4560
4598
  restoreInteractiveStderr();
4599
+ const storageMessage = storageWriteCrashMessage(error);
4600
+ if (storageMessage !== undefined) {
4601
+ console.error(storageMessage);
4602
+ }
4561
4603
  console.error(`${APP_NAME} exiting due to uncaughtException:`);
4562
4604
  console.error(error);
4563
4605
  process.exit(1);
@@ -4738,16 +4780,15 @@ export class InteractiveMode {
4738
4780
  }
4739
4781
  this.ui.requestRender();
4740
4782
  }
4741
- cycleThinkingLevel() {
4742
- const newLevel = this.session.cycleThinkingLevel();
4783
+ async cycleThinkingLevel() {
4784
+ // The shared-host proxy answers this over RPC. The level itself is rendered
4785
+ // from the thinking_level_changed event (single path for local and remote,
4786
+ // and for changes made by OTHER attached clients); the awaited value only
4787
+ // distinguishes "model does not support thinking".
4788
+ const newLevel = await this.session.cycleThinkingLevel();
4743
4789
  if (newLevel === undefined) {
4744
4790
  this.showStatus("Current model does not support thinking");
4745
4791
  }
4746
- else {
4747
- this.footer.invalidate();
4748
- this.updateEditorBorderColor();
4749
- this.showStatus(`Thinking level: ${newLevel}`);
4750
- }
4751
4792
  }
4752
4793
  async cycleModel(direction) {
4753
4794
  try {
@@ -5229,7 +5270,9 @@ export class InteractiveMode {
5229
5270
  this.ui.setFocus(created.focus);
5230
5271
  this.ui.requestRender();
5231
5272
  }
5232
- showSettingsSelector() {
5273
+ async showSettingsSelector() {
5274
+ // Awaited at the boundary: the shared-host proxy answers this over RPC.
5275
+ const availableThinkingLevels = await this.session.getAvailableThinkingLevels();
5233
5276
  this.showSelector((done) => {
5234
5277
  let selector;
5235
5278
  selector = new SettingsSelectorComponent({
@@ -5244,7 +5287,7 @@ export class InteractiveMode {
5244
5287
  transport: this.settingsManager.getTransport(),
5245
5288
  httpIdleTimeoutMs: this.settingsManager.getHttpIdleTimeoutMs(),
5246
5289
  thinkingLevel: this.session.thinkingLevel,
5247
- availableThinkingLevels: this.session.getAvailableThinkingLevels(),
5290
+ availableThinkingLevels,
5248
5291
  currentTheme: this.themeController.getThemeSelection() || "dark",
5249
5292
  terminalTheme: this.themeController.getTerminalTheme(),
5250
5293
  availableThemes: getAvailableThemes(),
@@ -5832,8 +5875,9 @@ export class InteractiveMode {
5832
5875
  };
5833
5876
  });
5834
5877
  }
5835
- showUserMessageSelector() {
5836
- const userMessages = this.session.getUserMessagesForForking();
5878
+ async showUserMessageSelector() {
5879
+ // Awaited at the boundary: the shared-host proxy answers this over RPC.
5880
+ const userMessages = await this.session.getUserMessagesForForking();
5837
5881
  if (userMessages.length === 0) {
5838
5882
  this.showStatus("No messages to fork from");
5839
5883
  return;
@@ -6612,7 +6656,7 @@ export class InteractiveMode {
6612
6656
  const outputPath = this.getPathCommandArgument(text, "/export");
6613
6657
  try {
6614
6658
  if (outputPath?.endsWith(".jsonl")) {
6615
- const filePath = this.session.exportToJsonl(outputPath);
6659
+ const filePath = await this.session.exportToJsonl(outputPath);
6616
6660
  this.showStatus(`Session exported to: ${filePath}`);
6617
6661
  }
6618
6662
  else {
@@ -6801,7 +6845,7 @@ export class InteractiveMode {
6801
6845
  this.showError(error instanceof Error ? error.message : String(error));
6802
6846
  }
6803
6847
  }
6804
- handleNameCommand(text) {
6848
+ async handleNameCommand(text) {
6805
6849
  const name = text.replace(/^\/name\s*/, "").trim();
6806
6850
  if (!name) {
6807
6851
  const currentName = this.sessionManager.getSessionName();
@@ -6815,8 +6859,8 @@ export class InteractiveMode {
6815
6859
  this.ui.requestRender();
6816
6860
  return;
6817
6861
  }
6818
- this.session.setSessionName(name);
6819
- const sessionName = this.sessionManager.getSessionName();
6862
+ await this.session.setSessionName(name);
6863
+ const sessionName = this.session.sessionName;
6820
6864
  if (sessionName !== name) {
6821
6865
  this.showWarning(`Session name was normalized from ${JSON.stringify(name)} to ${JSON.stringify(sessionName)}`);
6822
6866
  }
@@ -6824,9 +6868,10 @@ export class InteractiveMode {
6824
6868
  this.chatContainer.addChild(new Text(theme.fg("dim", `Session name set: ${sessionName ?? name}`), 1, 0));
6825
6869
  this.ui.requestRender();
6826
6870
  }
6827
- handleSessionCommand() {
6828
- const stats = this.session.getSessionStats();
6829
- const sessionName = this.sessionManager.getSessionName();
6871
+ async handleSessionCommand() {
6872
+ // Awaited at the boundary: the shared-host proxy answers this over RPC.
6873
+ const stats = await this.session.getSessionStats();
6874
+ const sessionName = this.session.sessionName;
6830
6875
  const entries = this.sessionManager.getEntries();
6831
6876
  const cacheWaste = computeCacheWaste(entries, this.session.modelRuntime);
6832
6877
  // Cost/token totals per provider/model actually used (e.g. OpenRouter `auto`
@@ -7108,7 +7153,7 @@ export class InteractiveMode {
7108
7153
  }
7109
7154
  this.bashComponent.setComplete(result.exitCode, result.cancelled, result.truncated ? { truncated: true, content: result.output } : undefined, result.fullOutputPath);
7110
7155
  // Record the result in session
7111
- this.session.recordBashResult(command, result, { excludeFromContext });
7156
+ void this.session.recordBashResult(command, result, { excludeFromContext });
7112
7157
  this.bashComponent = undefined;
7113
7158
  this.ui.requestRender();
7114
7159
  return;