@code-yeongyu/senpi 2026.8.28 → 2026.8.29

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 (128) hide show
  1. package/CHANGELOG.md +74 -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.map +1 -1
  18. package/dist/core/credential-accounts.js +22 -12
  19. package/dist/core/credential-accounts.js.map +1 -1
  20. package/dist/core/credential-pool/classify.d.ts +6 -1
  21. package/dist/core/credential-pool/classify.d.ts.map +1 -1
  22. package/dist/core/credential-pool/classify.js +22 -5
  23. package/dist/core/credential-pool/classify.js.map +1 -1
  24. package/dist/core/credential-pool/failover.d.ts +8 -0
  25. package/dist/core/credential-pool/failover.d.ts.map +1 -1
  26. package/dist/core/credential-pool/failover.js +24 -6
  27. package/dist/core/credential-pool/failover.js.map +1 -1
  28. package/dist/core/credential-pool/rotation-stream.d.ts +13 -2
  29. package/dist/core/credential-pool/rotation-stream.d.ts.map +1 -1
  30. package/dist/core/credential-pool/rotation-stream.js +97 -10
  31. package/dist/core/credential-pool/rotation-stream.js.map +1 -1
  32. package/dist/core/extensions/builtin/goal/index.d.ts.map +1 -1
  33. package/dist/core/extensions/builtin/goal/index.js +7 -0
  34. package/dist/core/extensions/builtin/goal/index.js.map +1 -1
  35. package/dist/core/extensions/builtin/service-tier.d.ts.map +1 -1
  36. package/dist/core/extensions/builtin/service-tier.js +13 -0
  37. package/dist/core/extensions/builtin/service-tier.js.map +1 -1
  38. package/dist/core/extensions/builtin/terminal/extension.d.ts.map +1 -1
  39. package/dist/core/extensions/builtin/terminal/extension.js +4 -2
  40. package/dist/core/extensions/builtin/terminal/extension.js.map +1 -1
  41. package/dist/core/model-config-schema.d.ts +4 -4
  42. package/dist/core/model-runtime.d.ts +2 -0
  43. package/dist/core/model-runtime.d.ts.map +1 -1
  44. package/dist/core/model-runtime.js +74 -37
  45. package/dist/core/model-runtime.js.map +1 -1
  46. package/dist/core/retry-fallback/chains.d.ts +23 -1
  47. package/dist/core/retry-fallback/chains.d.ts.map +1 -1
  48. package/dist/core/retry-fallback/chains.js +59 -3
  49. package/dist/core/retry-fallback/chains.js.map +1 -1
  50. package/dist/core/retry-fallback/controller.d.ts.map +1 -1
  51. package/dist/core/retry-fallback/controller.js +9 -2
  52. package/dist/core/retry-fallback/controller.js.map +1 -1
  53. package/dist/core/retry-fallback/settings.d.ts.map +1 -1
  54. package/dist/core/retry-fallback/settings.js +9 -0
  55. package/dist/core/retry-fallback/settings.js.map +1 -1
  56. package/dist/core/retry-fallback/validate.js +1 -1
  57. package/dist/core/retry-fallback/validate.js.map +1 -1
  58. package/dist/core/sdk.js +1 -1
  59. package/dist/core/sdk.js.map +1 -1
  60. package/dist/core/session-manager.d.ts +9 -0
  61. package/dist/core/session-manager.d.ts.map +1 -1
  62. package/dist/core/session-manager.js +31 -0
  63. package/dist/core/session-manager.js.map +1 -1
  64. package/dist/main.d.ts +7 -0
  65. package/dist/main.d.ts.map +1 -1
  66. package/dist/main.js +10 -1
  67. package/dist/main.js.map +1 -1
  68. package/dist/modes/interactive/components/favorite-models-selector.d.ts.map +1 -1
  69. package/dist/modes/interactive/components/favorite-models-selector.js +2 -2
  70. package/dist/modes/interactive/components/favorite-models-selector.js.map +1 -1
  71. package/dist/modes/interactive/interactive-host-runtime.d.ts.map +1 -1
  72. package/dist/modes/interactive/interactive-host-runtime.js +323 -33
  73. package/dist/modes/interactive/interactive-host-runtime.js.map +1 -1
  74. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  75. package/dist/modes/interactive/interactive-mode.js +276 -237
  76. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  77. package/dist/modes/rpc/connection-handler.d.ts.map +1 -1
  78. package/dist/modes/rpc/connection-handler.js +133 -24
  79. package/dist/modes/rpc/connection-handler.js.map +1 -1
  80. package/dist/modes/rpc/multi-session-host.js +14 -4
  81. package/dist/modes/rpc/multi-session-host.js.map +1 -1
  82. package/dist/modes/rpc/rpc-client.d.ts +82 -13
  83. package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
  84. package/dist/modes/rpc/rpc-client.js +85 -28
  85. package/dist/modes/rpc/rpc-client.js.map +1 -1
  86. package/dist/modes/rpc/rpc-mode.d.ts +3 -1
  87. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  88. package/dist/modes/rpc/rpc-mode.js +3 -1
  89. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  90. package/dist/modes/rpc/rpc-types.d.ts +162 -2
  91. package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  92. package/dist/modes/rpc/rpc-types.js.map +1 -1
  93. package/dist/modes/rpc/session-command-router.d.ts +22 -0
  94. package/dist/modes/rpc/session-command-router.d.ts.map +1 -1
  95. package/dist/modes/rpc/session-command-router.js +147 -16
  96. package/dist/modes/rpc/session-command-router.js.map +1 -1
  97. package/dist/modes/rpc/session-event-writer.d.ts +2 -0
  98. package/dist/modes/rpc/session-event-writer.d.ts.map +1 -1
  99. package/dist/modes/rpc/session-event-writer.js +4 -0
  100. package/dist/modes/rpc/session-event-writer.js.map +1 -1
  101. package/dist/modes/rpc/session-registry.d.ts +7 -0
  102. package/dist/modes/rpc/session-registry.d.ts.map +1 -1
  103. package/dist/modes/rpc/session-registry.js +9 -0
  104. package/dist/modes/rpc/session-registry.js.map +1 -1
  105. package/docs/rpc.md +37 -0
  106. package/node_modules/@code-yeongyu/senpi-codemode/CHANGELOG.md +24 -0
  107. package/node_modules/@code-yeongyu/senpi-codemode/package.json +4 -4
  108. package/node_modules/@earendil-works/pi-agent-core/dist/agent-loop.d.ts.map +1 -1
  109. package/node_modules/@earendil-works/pi-agent-core/dist/agent-loop.js +6 -3
  110. package/node_modules/@earendil-works/pi-agent-core/dist/agent-loop.js.map +1 -1
  111. package/node_modules/@earendil-works/pi-agent-core/package.json +3 -3
  112. package/node_modules/@earendil-works/pi-ai/dist/providers/data/.manifest.json +1 -1
  113. package/node_modules/@earendil-works/pi-ai/dist/providers/data/amazon-bedrock.json +1 -1
  114. package/node_modules/@earendil-works/pi-ai/dist/providers/data/baseten.json +1 -1
  115. package/node_modules/@earendil-works/pi-ai/dist/providers/data/cloudflare-ai-gateway.json +1 -1
  116. package/node_modules/@earendil-works/pi-ai/dist/providers/data/cloudflare-workers-ai.json +1 -1
  117. package/node_modules/@earendil-works/pi-ai/dist/providers/data/fireworks.json +1 -1
  118. package/node_modules/@earendil-works/pi-ai/dist/providers/data/huggingface.json +1 -1
  119. package/node_modules/@earendil-works/pi-ai/dist/providers/data/nvidia.json +1 -1
  120. package/node_modules/@earendil-works/pi-ai/dist/providers/data/opencode-go.json +1 -1
  121. package/node_modules/@earendil-works/pi-ai/dist/providers/data/opencode.json +1 -1
  122. package/node_modules/@earendil-works/pi-ai/dist/providers/data/openrouter.json +1 -1
  123. package/node_modules/@earendil-works/pi-ai/dist/providers/data/vercel-ai-gateway.json +1 -1
  124. package/node_modules/@earendil-works/pi-ai/package.json +2 -2
  125. package/node_modules/@earendil-works/pi-pty/package.json +1 -1
  126. package/node_modules/@earendil-works/pi-telemetry/package.json +1 -1
  127. package/node_modules/@earendil-works/pi-tui/package.json +1 -1
  128. 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");
@@ -2987,7 +3000,7 @@ export class InteractiveMode {
2987
3000
  this.showTreeSelector();
2988
3001
  }
2989
3002
  else {
2990
- this.showUserMessageSelector();
3003
+ void this.showUserMessageSelector().catch((error) => this.showError(error instanceof Error ? error.message : String(error)));
2991
3004
  }
2992
3005
  this.lastEscapeTime = 0;
2993
3006
  }
@@ -3001,7 +3014,9 @@ export class InteractiveMode {
3001
3014
  this.defaultEditor.onAction("app.clear", () => this.handleCtrlC());
3002
3015
  this.defaultEditor.onCtrlD = () => this.handleCtrlD();
3003
3016
  this.defaultEditor.onAction("app.suspend", () => this.handleCtrlZ());
3004
- 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
+ });
3005
3020
  this.defaultEditor.onAction("app.model.cycleForward", () => this.cycleModel("forward"));
3006
3021
  this.defaultEditor.onAction("app.model.cycleBackward", () => this.cycleModel("backward"));
3007
3022
  // Global debug handler on TUI (works regardless of focus)
@@ -3023,7 +3038,7 @@ export class InteractiveMode {
3023
3038
  this.defaultEditor.onAction("app.message.dequeue", () => this.handleDequeue());
3024
3039
  this.defaultEditor.onAction("app.session.new", () => this.handleClearCommand());
3025
3040
  this.defaultEditor.onAction("app.session.tree", () => this.showTreeSelector());
3026
- 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))));
3027
3042
  this.defaultEditor.onAction("app.session.resume", () => this.showSessionSelector());
3028
3043
  this.defaultEditor.onChange = (text) => {
3029
3044
  const wasBashMode = this.isBashMode;
@@ -3179,197 +3194,158 @@ export class InteractiveMode {
3179
3194
  }
3180
3195
  setupEditorSubmitHandler() {
3181
3196
  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));
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;
3339
3215
  }
3340
- catch (error) {
3341
- this.optimisticUserEchoes.reject(pendingEchoId);
3342
- throw error;
3216
+ if (text === "/favorite-models") {
3217
+ this.editor.setText("");
3218
+ await this.showFavoriteModelsSelector();
3219
+ return;
3343
3220
  }
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();
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();
3360
3347
  return;
3361
3348
  }
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
3349
  if (this.isExtensionCommand(text)) {
3374
3350
  this.editor.addToHistory?.(text);
3375
3351
  this.editor.setText("");
@@ -3381,52 +3357,96 @@ export class InteractiveMode {
3381
3357
  this.optimisticUserEchoes.reject(pendingEchoId);
3382
3358
  throw error;
3383
3359
  }
3360
+ return;
3384
3361
  }
3385
- else {
3386
- 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
+ }
3387
3378
  }
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.
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();
3398
3436
  const images = preResolvedImages ?? this.takeSubmissionImages(text);
3399
- this.editor.addToHistory?.(text);
3400
- this.editor.setText("");
3401
3437
  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
- });
3438
+ const submission = images.length > 0 ? { text, images, pendingEchoId } : { text, pendingEchoId };
3439
+ if (this.onInputCallback) {
3440
+ this.onInputCallback(submission);
3408
3441
  }
3409
- catch (error) {
3410
- this.optimisticUserEchoes.reject(pendingEchoId);
3411
- throw error;
3442
+ else {
3443
+ this.pendingUserInputs.push(submission);
3412
3444
  }
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);
3445
+ this.editor.addToHistory?.(text);
3425
3446
  }
3426
- else {
3427
- this.pendingUserInputs.push(submission);
3447
+ catch (error) {
3448
+ this.showError(error instanceof Error ? error.message : String(error));
3428
3449
  }
3429
- this.editor.addToHistory?.(text);
3430
3450
  };
3431
3451
  }
3432
3452
  subscribeToAgent() {
@@ -3738,15 +3758,24 @@ export class InteractiveMode {
3738
3758
  // Keep the structural fallback for focused handler consumers while using
3739
3759
  // the session-owned manager on the real TUI path.
3740
3760
  const sessionManager = this.session.sessionManager ?? this.sessionManager;
3741
- const entries = sessionManager.buildContextEntries();
3761
+ let entries = sessionManager?.buildContextEntries?.() ?? [];
3742
3762
  if (entries[0]?.type !== "compaction") {
3743
- 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
+ }
3744
3770
  }
3745
3771
  this.chatContainer.clear();
3746
3772
  const summaryMessage = createCompactionSummaryMessage(sanitizeTerminalLabel(event.result.summary), event.result.tokensBefore, new Date().toISOString(), event.result.details);
3747
3773
  if (typeof this.renderSessionEntries === "function") {
3748
3774
  // The latest compaction is prepended for model context; append it below at its chronological position.
3749
- 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);
3750
3779
  this.addMessageToChat(summaryMessage);
3751
3780
  if (event.result.usage) {
3752
3781
  this.addCompactionCostNotice({
@@ -4441,6 +4470,12 @@ export class InteractiveMode {
4441
4470
  };
4442
4471
  }
4443
4472
  rebuildChatFromMessages() {
4473
+ try {
4474
+ this.sessionManager?.reloadFromDisk?.();
4475
+ }
4476
+ catch {
4477
+ // Keep in-memory entries if file reload is unavailable
4478
+ }
4444
4479
  this.chatContainer.clear();
4445
4480
  this.renderSessionEntries(this.sessionManager.buildContextEntries());
4446
4481
  }
@@ -4561,6 +4596,10 @@ export class InteractiveMode {
4561
4596
  }
4562
4597
  catch { }
4563
4598
  restoreInteractiveStderr();
4599
+ const storageMessage = storageWriteCrashMessage(error);
4600
+ if (storageMessage !== undefined) {
4601
+ console.error(storageMessage);
4602
+ }
4564
4603
  console.error(`${APP_NAME} exiting due to uncaughtException:`);
4565
4604
  console.error(error);
4566
4605
  process.exit(1);
@@ -6617,7 +6656,7 @@ export class InteractiveMode {
6617
6656
  const outputPath = this.getPathCommandArgument(text, "/export");
6618
6657
  try {
6619
6658
  if (outputPath?.endsWith(".jsonl")) {
6620
- const filePath = this.session.exportToJsonl(outputPath);
6659
+ const filePath = await this.session.exportToJsonl(outputPath);
6621
6660
  this.showStatus(`Session exported to: ${filePath}`);
6622
6661
  }
6623
6662
  else {
@@ -6806,7 +6845,7 @@ export class InteractiveMode {
6806
6845
  this.showError(error instanceof Error ? error.message : String(error));
6807
6846
  }
6808
6847
  }
6809
- handleNameCommand(text) {
6848
+ async handleNameCommand(text) {
6810
6849
  const name = text.replace(/^\/name\s*/, "").trim();
6811
6850
  if (!name) {
6812
6851
  const currentName = this.sessionManager.getSessionName();
@@ -6820,8 +6859,8 @@ export class InteractiveMode {
6820
6859
  this.ui.requestRender();
6821
6860
  return;
6822
6861
  }
6823
- this.session.setSessionName(name);
6824
- const sessionName = this.sessionManager.getSessionName();
6862
+ await this.session.setSessionName(name);
6863
+ const sessionName = this.session.sessionName;
6825
6864
  if (sessionName !== name) {
6826
6865
  this.showWarning(`Session name was normalized from ${JSON.stringify(name)} to ${JSON.stringify(sessionName)}`);
6827
6866
  }
@@ -6832,7 +6871,7 @@ export class InteractiveMode {
6832
6871
  async handleSessionCommand() {
6833
6872
  // Awaited at the boundary: the shared-host proxy answers this over RPC.
6834
6873
  const stats = await this.session.getSessionStats();
6835
- const sessionName = this.sessionManager.getSessionName();
6874
+ const sessionName = this.session.sessionName;
6836
6875
  const entries = this.sessionManager.getEntries();
6837
6876
  const cacheWaste = computeCacheWaste(entries, this.session.modelRuntime);
6838
6877
  // Cost/token totals per provider/model actually used (e.g. OpenRouter `auto`
@@ -7114,7 +7153,7 @@ export class InteractiveMode {
7114
7153
  }
7115
7154
  this.bashComponent.setComplete(result.exitCode, result.cancelled, result.truncated ? { truncated: true, content: result.output } : undefined, result.fullOutputPath);
7116
7155
  // Record the result in session
7117
- this.session.recordBashResult(command, result, { excludeFromContext });
7156
+ void this.session.recordBashResult(command, result, { excludeFromContext });
7118
7157
  this.bashComponent = undefined;
7119
7158
  this.ui.requestRender();
7120
7159
  return;