@code-yeongyu/senpi 2026.9.5 → 2026.9.7

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 (197) hide show
  1. package/CHANGELOG.md +67 -0
  2. package/dist/core/agent-session.d.ts +21 -2
  3. package/dist/core/agent-session.d.ts.map +1 -1
  4. package/dist/core/agent-session.js +144 -56
  5. package/dist/core/agent-session.js.map +1 -1
  6. package/dist/core/compaction/compaction.d.ts +6 -2
  7. package/dist/core/compaction/compaction.d.ts.map +1 -1
  8. package/dist/core/compaction/compaction.js +4 -0
  9. package/dist/core/compaction/compaction.js.map +1 -1
  10. package/dist/core/compaction/stuck-overflow.d.ts +11 -0
  11. package/dist/core/compaction/stuck-overflow.d.ts.map +1 -0
  12. package/dist/core/compaction/stuck-overflow.js +17 -0
  13. package/dist/core/compaction/stuck-overflow.js.map +1 -0
  14. package/dist/core/extensions/builtin/claude-sdk-oauth/prompt-bridge.d.ts.map +1 -1
  15. package/dist/core/extensions/builtin/claude-sdk-oauth/prompt-bridge.js +2 -0
  16. package/dist/core/extensions/builtin/claude-sdk-oauth/prompt-bridge.js.map +1 -1
  17. package/dist/core/extensions/builtin/claude-sdk-oauth/session-registry-pump.d.ts.map +1 -1
  18. package/dist/core/extensions/builtin/claude-sdk-oauth/session-registry-pump.js +15 -2
  19. package/dist/core/extensions/builtin/claude-sdk-oauth/session-registry-pump.js.map +1 -1
  20. package/dist/core/extensions/builtin/claude-sdk-oauth/session-turn-claim.d.ts +1 -1
  21. package/dist/core/extensions/builtin/claude-sdk-oauth/session-turn-claim.d.ts.map +1 -1
  22. package/dist/core/extensions/builtin/claude-sdk-oauth/session-turn-claim.js +2 -3
  23. package/dist/core/extensions/builtin/claude-sdk-oauth/session-turn-claim.js.map +1 -1
  24. package/dist/core/extensions/builtin/compaction/deterministic-fallback.d.ts +4 -0
  25. package/dist/core/extensions/builtin/compaction/deterministic-fallback.d.ts.map +1 -1
  26. package/dist/core/extensions/builtin/compaction/deterministic-fallback.js +16 -19
  27. package/dist/core/extensions/builtin/compaction/deterministic-fallback.js.map +1 -1
  28. package/dist/core/extensions/builtin/compaction/lane-policy.d.ts +9 -0
  29. package/dist/core/extensions/builtin/compaction/lane-policy.d.ts.map +1 -1
  30. package/dist/core/extensions/builtin/compaction/lane-policy.js +5 -0
  31. package/dist/core/extensions/builtin/compaction/lane-policy.js.map +1 -1
  32. package/dist/core/extensions/builtin/compaction/openai-remote-convert.d.ts.map +1 -1
  33. package/dist/core/extensions/builtin/compaction/openai-remote-convert.js +2 -0
  34. package/dist/core/extensions/builtin/compaction/openai-remote-convert.js.map +1 -1
  35. package/dist/core/extensions/builtin/compaction/retained-message-safety.d.ts.map +1 -1
  36. package/dist/core/extensions/builtin/compaction/retained-message-safety.js +14 -1
  37. package/dist/core/extensions/builtin/compaction/retained-message-safety.js.map +1 -1
  38. package/dist/core/extensions/builtin/goal/continuation-recovery.d.ts +1 -0
  39. package/dist/core/extensions/builtin/goal/continuation-recovery.d.ts.map +1 -1
  40. package/dist/core/extensions/builtin/goal/continuation-recovery.js +2 -0
  41. package/dist/core/extensions/builtin/goal/continuation-recovery.js.map +1 -1
  42. package/dist/core/extensions/builtin/goal/continuation.d.ts +3 -1
  43. package/dist/core/extensions/builtin/goal/continuation.d.ts.map +1 -1
  44. package/dist/core/extensions/builtin/goal/continuation.js +4 -0
  45. package/dist/core/extensions/builtin/goal/continuation.js.map +1 -1
  46. package/dist/core/extensions/builtin/goal/lifecycle-helpers.d.ts +3 -0
  47. package/dist/core/extensions/builtin/goal/lifecycle-helpers.d.ts.map +1 -1
  48. package/dist/core/extensions/builtin/goal/lifecycle-helpers.js +14 -3
  49. package/dist/core/extensions/builtin/goal/lifecycle-helpers.js.map +1 -1
  50. package/dist/core/extensions/builtin/goal/monitor-continuation.d.ts.map +1 -1
  51. package/dist/core/extensions/builtin/goal/monitor-continuation.js +2 -1
  52. package/dist/core/extensions/builtin/goal/monitor-continuation.js.map +1 -1
  53. package/dist/core/extensions/builtin/permission-system/external-dir.d.ts.map +1 -1
  54. package/dist/core/extensions/builtin/permission-system/external-dir.js +42 -13
  55. package/dist/core/extensions/builtin/permission-system/external-dir.js.map +1 -1
  56. package/dist/core/extensions/builtin/prompt-preset/gpt-6-astra.d.ts +2 -2
  57. package/dist/core/extensions/builtin/prompt-preset/gpt-6-astra.d.ts.map +1 -1
  58. package/dist/core/extensions/builtin/prompt-preset/gpt-6-astra.js +8 -2
  59. package/dist/core/extensions/builtin/prompt-preset/gpt-6-astra.js.map +1 -1
  60. package/dist/core/messages.d.ts +7 -0
  61. package/dist/core/messages.d.ts.map +1 -1
  62. package/dist/core/messages.js +2 -0
  63. package/dist/core/messages.js.map +1 -1
  64. package/dist/core/package-identity.d.ts +12 -0
  65. package/dist/core/package-identity.d.ts.map +1 -0
  66. package/dist/core/package-identity.js +57 -0
  67. package/dist/core/package-identity.js.map +1 -0
  68. package/dist/core/resource-loader.d.ts +0 -2
  69. package/dist/core/resource-loader.d.ts.map +1 -1
  70. package/dist/core/resource-loader.js +8 -60
  71. package/dist/core/resource-loader.js.map +1 -1
  72. package/dist/core/retry-fallback/controller.d.ts.map +1 -1
  73. package/dist/core/retry-fallback/controller.js +5 -4
  74. package/dist/core/retry-fallback/controller.js.map +1 -1
  75. package/dist/core/sdk.d.ts.map +1 -1
  76. package/dist/core/sdk.js +5 -0
  77. package/dist/core/sdk.js.map +1 -1
  78. package/dist/core/session-manager.d.ts +12 -1
  79. package/dist/core/session-manager.d.ts.map +1 -1
  80. package/dist/core/session-manager.js +29 -3
  81. package/dist/core/session-manager.js.map +1 -1
  82. package/dist/core/settings-manager.d.ts +6 -2
  83. package/dist/core/settings-manager.d.ts.map +1 -1
  84. package/dist/core/settings-manager.js +4 -1
  85. package/dist/core/settings-manager.js.map +1 -1
  86. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  87. package/dist/modes/interactive/interactive-mode.js +5 -1
  88. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  89. package/dist/modes/rpc/multi-session-host.d.ts +3 -7
  90. package/dist/modes/rpc/multi-session-host.d.ts.map +1 -1
  91. package/dist/modes/rpc/multi-session-host.js +3 -13
  92. package/dist/modes/rpc/multi-session-host.js.map +1 -1
  93. package/dist/modes/rpc/rpc-types.d.ts +1 -2
  94. package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  95. package/dist/modes/rpc/rpc-types.js +0 -1
  96. package/dist/modes/rpc/rpc-types.js.map +1 -1
  97. package/dist/modes/rpc/session-registry.d.ts +1 -4
  98. package/dist/modes/rpc/session-registry.d.ts.map +1 -1
  99. package/dist/modes/rpc/session-registry.js +0 -15
  100. package/dist/modes/rpc/session-registry.js.map +1 -1
  101. package/docs/compaction-guide.md +2 -2
  102. package/docs/compaction.md +2 -2
  103. package/docs/rpc.md +6 -7
  104. package/node_modules/@code-yeongyu/senpi-codemode/CHANGELOG.md +57 -0
  105. package/node_modules/@code-yeongyu/senpi-codemode/README.md +20 -4
  106. package/node_modules/@code-yeongyu/senpi-codemode/package.json +4 -4
  107. package/node_modules/@code-yeongyu/senpi-codemode/src/bridge/reserved.ts +2 -0
  108. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/jl/kernel.ts +2 -2
  109. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/context-manager.ts +73 -129
  110. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/inline-worker.ts +2 -2
  111. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/interrupt-bounds.ts +66 -0
  112. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/run-queue.ts +18 -3
  113. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/worker-core.js +30 -0
  114. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/worker-runtime.js +16 -0
  115. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/worker-shell-capture.d.ts +9 -0
  116. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/worker-shell-capture.js +32 -6
  117. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/worker-slot.ts +106 -0
  118. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/worker-startup.ts +69 -0
  119. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/py/transport.ts +2 -2
  120. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/rb/kernel.ts +2 -2
  121. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/shared/runtime-asset.ts +40 -14
  122. package/node_modules/@code-yeongyu/senpi-codemode/src/prompt/eval-prompt.ts +10 -5
  123. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/cell-execution.ts +9 -11
  124. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/detached-cell-contract.ts +45 -0
  125. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/detached-cell-manager.ts +43 -71
  126. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/detached-cell-notification.ts +4 -5
  127. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/detached-cell-snapshot.ts +2 -0
  128. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/detached-cell-status.ts +30 -0
  129. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/detached-eval-result.ts +1 -0
  130. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/detached-notification-queue.ts +2 -2
  131. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/interrupt-note.ts +29 -15
  132. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/types.ts +2 -0
  133. package/node_modules/@earendil-works/pi-agent-core/dist/agent.d.ts.map +1 -1
  134. package/node_modules/@earendil-works/pi-agent-core/dist/agent.js +3 -1
  135. package/node_modules/@earendil-works/pi-agent-core/dist/agent.js.map +1 -1
  136. package/node_modules/@earendil-works/pi-agent-core/dist/harness/agent-harness.d.ts.map +1 -1
  137. package/node_modules/@earendil-works/pi-agent-core/dist/harness/agent-harness.js +17 -0
  138. package/node_modules/@earendil-works/pi-agent-core/dist/harness/agent-harness.js.map +1 -1
  139. package/node_modules/@earendil-works/pi-agent-core/dist/harness/compaction/branch-summarization.d.ts.map +1 -1
  140. package/node_modules/@earendil-works/pi-agent-core/dist/harness/compaction/branch-summarization.js +1 -0
  141. package/node_modules/@earendil-works/pi-agent-core/dist/harness/compaction/branch-summarization.js.map +1 -1
  142. package/node_modules/@earendil-works/pi-agent-core/dist/harness/compaction/compaction.d.ts.map +1 -1
  143. package/node_modules/@earendil-works/pi-agent-core/dist/harness/compaction/compaction.js +1 -0
  144. package/node_modules/@earendil-works/pi-agent-core/dist/harness/compaction/compaction.js.map +1 -1
  145. package/node_modules/@earendil-works/pi-agent-core/dist/harness/reducer.d.ts.map +1 -1
  146. package/node_modules/@earendil-works/pi-agent-core/dist/harness/reducer.js +6 -0
  147. package/node_modules/@earendil-works/pi-agent-core/dist/harness/reducer.js.map +1 -1
  148. package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/context.d.ts +2 -1
  149. package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/context.d.ts.map +1 -1
  150. package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/context.js +5 -1
  151. package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/context.js.map +1 -1
  152. package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/jsonl/codec.d.ts.map +1 -1
  153. package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/jsonl/codec.js +1 -0
  154. package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/jsonl/codec.js.map +1 -1
  155. package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/types.d.ts +7 -1
  156. package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/types.d.ts.map +1 -1
  157. package/node_modules/@earendil-works/pi-agent-core/dist/harness/session/types.js.map +1 -1
  158. package/node_modules/@earendil-works/pi-agent-core/dist/types.d.ts +2 -0
  159. package/node_modules/@earendil-works/pi-agent-core/dist/types.d.ts.map +1 -1
  160. package/node_modules/@earendil-works/pi-agent-core/dist/types.js.map +1 -1
  161. package/node_modules/@earendil-works/pi-agent-core/package.json +3 -3
  162. package/node_modules/@earendil-works/pi-ai/dist/api/mistral-conversations.js +2 -0
  163. package/node_modules/@earendil-works/pi-ai/dist/api/mistral-conversations.js.map +1 -1
  164. package/node_modules/@earendil-works/pi-ai/dist/api/openai-responses-shared.d.ts.map +1 -1
  165. package/node_modules/@earendil-works/pi-ai/dist/api/openai-responses-shared.js +15 -0
  166. package/node_modules/@earendil-works/pi-ai/dist/api/openai-responses-shared.js.map +1 -1
  167. package/node_modules/@earendil-works/pi-ai/dist/image-models.generated.js +4 -4
  168. package/node_modules/@earendil-works/pi-ai/dist/image-models.generated.js.map +1 -1
  169. package/node_modules/@earendil-works/pi-ai/dist/providers/data/.manifest.json +1 -1
  170. package/node_modules/@earendil-works/pi-ai/dist/providers/data/amazon-bedrock.json +1 -1
  171. package/node_modules/@earendil-works/pi-ai/dist/providers/data/azure-openai-responses.json +1 -1
  172. package/node_modules/@earendil-works/pi-ai/dist/providers/data/cerebras.json +1 -1
  173. package/node_modules/@earendil-works/pi-ai/dist/providers/data/cloudflare-ai-gateway.json +1 -1
  174. package/node_modules/@earendil-works/pi-ai/dist/providers/data/github-copilot.json +1 -1
  175. package/node_modules/@earendil-works/pi-ai/dist/providers/data/moonshotai-cn.json +1 -1
  176. package/node_modules/@earendil-works/pi-ai/dist/providers/data/moonshotai.json +1 -1
  177. package/node_modules/@earendil-works/pi-ai/dist/providers/data/openai-codex.json +1 -1
  178. package/node_modules/@earendil-works/pi-ai/dist/providers/data/openai.json +1 -1
  179. package/node_modules/@earendil-works/pi-ai/dist/providers/data/opencode-go.json +1 -1
  180. package/node_modules/@earendil-works/pi-ai/dist/providers/data/opencode.json +1 -1
  181. package/node_modules/@earendil-works/pi-ai/dist/providers/data/opengateway.json +1 -1
  182. package/node_modules/@earendil-works/pi-ai/dist/providers/data/openrouter.json +1 -1
  183. package/node_modules/@earendil-works/pi-ai/dist/providers/data/vercel-ai-gateway.json +1 -1
  184. package/node_modules/@earendil-works/pi-ai/dist/providers/faux.d.ts.map +1 -1
  185. package/node_modules/@earendil-works/pi-ai/dist/providers/faux.js +2 -0
  186. package/node_modules/@earendil-works/pi-ai/dist/providers/faux.js.map +1 -1
  187. package/node_modules/@earendil-works/pi-ai/dist/types.d.ts +8 -1
  188. package/node_modules/@earendil-works/pi-ai/dist/types.d.ts.map +1 -1
  189. package/node_modules/@earendil-works/pi-ai/dist/types.js.map +1 -1
  190. package/node_modules/@earendil-works/pi-ai/dist/utils/estimate.d.ts.map +1 -1
  191. package/node_modules/@earendil-works/pi-ai/dist/utils/estimate.js +2 -0
  192. package/node_modules/@earendil-works/pi-ai/dist/utils/estimate.js.map +1 -1
  193. package/node_modules/@earendil-works/pi-ai/package.json +2 -2
  194. package/node_modules/@earendil-works/pi-pty/package.json +1 -1
  195. package/node_modules/@earendil-works/pi-telemetry/package.json +1 -1
  196. package/node_modules/@earendil-works/pi-tui/package.json +1 -1
  197. package/package.json +7 -7
@@ -35,6 +35,7 @@ import { executeBashWithOperations } from "./bash-executor.js";
35
35
  import { envValue } from "./brand.js";
36
36
  import { calculateContextTokens, collectEntriesForBranchSummary, compact, estimateContextTokens, estimateTokens, generateBranchSummary, prepareCompaction, resolveThresholdContextTokens, shouldCompact, } from "./compaction/index.js";
37
37
  import { CompactionLifecycleCoordinator } from "./compaction/lifecycle.js";
38
+ import { isTurnStuckOnContextOverflow } from "./compaction/stuck-overflow.js";
38
39
  import { isWarmSummaryAnchorValid } from "./compaction/warm-anchor.js";
39
40
  import { DEFAULT_THINKING_LEVEL } from "./defaults.js";
40
41
  import { buildDynamicSystemPrompt } from "./dynamic-prompt/index.js";
@@ -208,6 +209,8 @@ function removeSkillInvocationTokens(text, tokens) {
208
209
  let result = "";
209
210
  for (const token of tokens) {
210
211
  result += text.slice(cursor, token.start);
212
+ if (token.position === "inline")
213
+ result += `[skill: ${token.name}]`;
211
214
  cursor = token.end;
212
215
  if (token.position === "inline" &&
213
216
  (result.endsWith(" ") || result.endsWith("\t")) &&
@@ -755,6 +758,25 @@ export class AgentSession {
755
758
  return { model };
756
759
  }
757
760
  }
761
+ /**
762
+ * Resolve the model used for compaction summarization. When the user sets a
763
+ * `compaction.model` override ("provider/model"), that model is used for the
764
+ * summarization call instead of the session model — this is what lets an
765
+ * SDK-owned lane (claude-sdk-oauth) compact on a cheaper/different model.
766
+ * Any resolution failure (unset, malformed, unknown model) falls back to the
767
+ * session model so compaction never silently breaks.
768
+ */
769
+ _resolveCompactionModel(sessionModel) {
770
+ const override = this._getCompactionSettings().model;
771
+ if (!override)
772
+ return sessionModel;
773
+ const slash = override.indexOf("/");
774
+ if (slash <= 0 || slash === override.length - 1)
775
+ return sessionModel;
776
+ const provider = override.slice(0, slash);
777
+ const modelId = override.slice(slash + 1);
778
+ return this._modelRuntime.getModel(provider, modelId) ?? sessionModel;
779
+ }
758
780
  async _getCompactionRequestAuth(model) {
759
781
  const auth = await this._getSummarizationRequestAuth(model);
760
782
  return {
@@ -1163,7 +1185,7 @@ export class AgentSession {
1163
1185
  systemPrompt: this.agent.state.systemPrompt,
1164
1186
  tools: this.agent.state.tools,
1165
1187
  liveContextTokens: this._getDownswitchLiveContextTokens(model),
1166
- compaction: this.settingsManager.getCompactionSettings(),
1188
+ compaction: this._getCompactionSettings(),
1167
1189
  });
1168
1190
  return projection.usable ? undefined : projection;
1169
1191
  }
@@ -1353,9 +1375,14 @@ export class AgentSession {
1353
1375
  const messages = filterContextExcludedMessages(this.agent.state.messages);
1354
1376
  return resolveThresholdContextTokens(directContextTokens, estimateMessagesTokens(messages));
1355
1377
  }
1378
+ /**
1379
+ * `compaction.enabled=false` disables proactive (threshold) compaction only.
1380
+ * A provider-confirmed overflow is an error the session cannot progress past
1381
+ * by any other route, so overflow recovery stays armed regardless (#1422).
1382
+ */
1356
1383
  _getAutoCompactionReason(message) {
1357
- const settings = this.settingsManager.getCompactionSettings();
1358
- if (!settings.enabled || message.stopReason === "aborted") {
1384
+ const settings = this._getCompactionSettings();
1385
+ if (message.stopReason === "aborted") {
1359
1386
  return undefined;
1360
1387
  }
1361
1388
  const model = this.model;
@@ -1367,11 +1394,15 @@ export class AgentSession {
1367
1394
  const currentContextNeedsCompaction = contextUsage !== undefined &&
1368
1395
  contextUsage.tokens !== null &&
1369
1396
  shouldCompact(contextUsage.tokens, contextUsage.contextWindow, settings);
1370
- if (isContextOverflow(message, model.contextWindow) && (sameModel || currentContextNeedsCompaction)) {
1371
- return "overflow";
1397
+ const isOverflow = (isContextOverflow(message, model.contextWindow) && (sameModel || currentContextNeedsCompaction)) ||
1398
+ this._isCursorPayloadOverflow(message);
1399
+ if (isOverflow) {
1400
+ if (settings.enabled || isTurnStuckOnContextOverflow(message, model.contextWindow))
1401
+ return "overflow";
1402
+ return undefined;
1372
1403
  }
1373
- if (this._isCursorPayloadOverflow(message)) {
1374
- return "overflow";
1404
+ if (!settings.enabled) {
1405
+ return undefined;
1375
1406
  }
1376
1407
  let contextTokens;
1377
1408
  const directContextTokens = message.usage ? calculateContextTokens(message.usage) : 0;
@@ -3621,7 +3652,7 @@ export class AgentSession {
3621
3652
  systemPrompt: this.agent.state.systemPrompt,
3622
3653
  tools: this.agent.state.tools,
3623
3654
  liveContextTokens,
3624
- compaction: this.settingsManager.getCompactionSettings(),
3655
+ compaction: this._getCompactionSettings(),
3625
3656
  includeSpeculationLead: options?.includeSpeculationLead,
3626
3657
  admission: options?.admission,
3627
3658
  });
@@ -3632,7 +3663,7 @@ export class AgentSession {
3632
3663
  const currentModel = this.model;
3633
3664
  if (!currentModel)
3634
3665
  return 0;
3635
- const compaction = this.settingsManager.getCompactionSettings();
3666
+ const compaction = this._getCompactionSettings();
3636
3667
  const currentBudget = projectModelUsabilityBudget({
3637
3668
  model: currentModel,
3638
3669
  systemPrompt: this.agent.state.systemPrompt,
@@ -3711,17 +3742,18 @@ export class AgentSession {
3711
3742
  const thinking = this._getThinkingForModelSwitch(model, opts.ephemeralThinkingLevel);
3712
3743
  const liveContextTokens = this._getDownswitchLiveContextTokens(model);
3713
3744
  this.agent.state.model = model;
3714
- this.agent.abortServerSideFallback =
3715
- this.settingsManager.getAbortServerSideFallback() && this._retryFallback.hasConfiguredChain();
3716
- if (opts.appendSessionEntry) {
3717
- this.sessionManager.appendModelChange(model.provider, model.id, opts.entryReason, previousModel?.provider, previousModel?.id);
3718
- }
3719
- if (opts.persistDefault) {
3720
- this.settingsManager.setDefaultModelAndProvider(model.provider, model.id);
3745
+ if (!(model.id === "gpt-6-astra" && (model.provider === "openai" || model.provider === "openai-codex"))) {
3746
+ this.agent.state.reasoningBaseline = undefined;
3721
3747
  }
3722
3748
  const scopedMatch = this._scopedModels.find((sm) => modelsAreEqual(sm.model, model));
3723
3749
  const previousTier = this._currentServiceTier;
3724
3750
  const previousFastMode = this.isFastModeActive();
3751
+ const previousThinkingLevel = this.agent.state.thinkingLevel;
3752
+ const previousThinkingSelection = this.agent.state.thinkingSelection;
3753
+ const previousReasoningBaseline = this.agent.state.reasoningBaseline;
3754
+ const previousAbortServerSideFallback = this.agent.abortServerSideFallback;
3755
+ this.agent.abortServerSideFallback =
3756
+ this.settingsManager.getAbortServerSideFallback() && this._retryFallback.hasConfiguredChain();
3725
3757
  this._currentServiceTier = this._resolveServiceTier(model, scopedMatch?.serviceTier);
3726
3758
  if (opts.ephemeralThinkingLevel !== undefined) {
3727
3759
  this._applyEphemeralThinkingLevel(thinking.level);
@@ -3730,21 +3762,25 @@ export class AgentSession {
3730
3762
  this._setThinkingLevel(thinking.level, false, thinking.selection);
3731
3763
  }
3732
3764
  this._emitHighReasoningWarningIfNeeded();
3733
- // Post-switch: the level reported here is the one actually in force (clamped, or restored
3734
- // from this model's memory), not the level requested for the previous model.
3735
- this._emit({
3736
- type: "model_changed",
3737
- model,
3738
- thinkingLevel: this.thinkingLevel,
3739
- source: opts.modelSelectSource,
3740
- });
3741
- this._emitServiceTierChangeIfNeeded(previousTier, previousFastMode);
3742
- if (!opts.emitModelSelect)
3743
- return undefined;
3744
3765
  const previousSystemPrompt = this.agent.state.systemPrompt;
3745
3766
  try {
3746
- const systemPromptChange = await this._emitModelSelect(model, previousModel, opts.modelSelectSource);
3767
+ const systemPromptChange = opts.emitModelSelect
3768
+ ? await this._emitModelSelect(model, previousModel, opts.modelSelectSource)
3769
+ : undefined;
3747
3770
  this.assertModelUsable(model, liveContextTokens);
3771
+ if (opts.appendSessionEntry) {
3772
+ this.sessionManager.appendModelChange(model.provider, model.id, opts.entryReason, previousModel?.provider, previousModel?.id);
3773
+ }
3774
+ if (opts.persistDefault)
3775
+ this.settingsManager.setDefaultModelAndProvider(model.provider, model.id);
3776
+ // Emit only after all admission hooks have accepted the candidate.
3777
+ this._emit({
3778
+ type: "model_changed",
3779
+ model,
3780
+ thinkingLevel: this.thinkingLevel,
3781
+ source: opts.modelSelectSource,
3782
+ });
3783
+ this._emitServiceTierChangeIfNeeded(previousTier, previousFastMode);
3748
3784
  return systemPromptChange;
3749
3785
  }
3750
3786
  catch (error) {
@@ -3753,6 +3789,11 @@ export class AgentSession {
3753
3789
  else
3754
3790
  delete this.agent.state.model;
3755
3791
  this.agent.state.systemPrompt = previousSystemPrompt;
3792
+ this.agent.state.thinkingLevel = previousThinkingLevel;
3793
+ this.agent.state.thinkingSelection = previousThinkingSelection;
3794
+ this.agent.state.reasoningBaseline = previousReasoningBaseline;
3795
+ this.agent.abortServerSideFallback = previousAbortServerSideFallback;
3796
+ this._currentServiceTier = previousTier;
3756
3797
  throw error;
3757
3798
  }
3758
3799
  }
@@ -3929,6 +3970,14 @@ export class AgentSession {
3929
3970
  }
3930
3971
  if (isChanging || selectionChanged) {
3931
3972
  this.sessionManager.appendThinkingLevelChange(effectiveLevel, effectiveSelection);
3973
+ const model = this.model;
3974
+ if (isChanging &&
3975
+ model?.id === "gpt-6-astra" &&
3976
+ (model.provider === "openai" || model.provider === "openai-codex")) {
3977
+ this.agent.state.reasoningBaseline ??= previousLevel;
3978
+ this.sessionManager.appendConfigurationUpdate(effectiveLevel);
3979
+ this.agent.state.messages = this.sessionManager.buildSessionContext().messages;
3980
+ }
3932
3981
  if (updateGlobalDefault && (this.supportsThinking() || effectiveLevel !== "off")) {
3933
3982
  this.settingsManager.setDefaultThinkingLevel(effectiveLevel);
3934
3983
  }
@@ -4237,7 +4286,7 @@ export class AgentSession {
4237
4286
  if (!model)
4238
4287
  throw new Error(formatNoModelSelectedMessage());
4239
4288
  const pathEntries = this.sessionManager.getBranch();
4240
- const settings = cursorOverflowCompactionSettings(this.settingsManager.getCompactionSettings(), model.provider, "manual");
4289
+ const settings = cursorOverflowCompactionSettings(this._getCompactionSettings(), model.provider, "manual");
4241
4290
  if (!prepareCompaction(pathEntries, settings)) {
4242
4291
  const requestId = randomUUID();
4243
4292
  const lastEntry = pathEntries[pathEntries.length - 1];
@@ -4477,7 +4526,7 @@ export class AgentSession {
4477
4526
  throw new CompactionCancelledError();
4478
4527
  }
4479
4528
  const pathEntries = this.sessionManager.getBranch();
4480
- const settings = cursorOverflowCompactionSettings(this.settingsManager.getCompactionSettings(), this.model?.provider, request.reason);
4529
+ const settings = cursorOverflowCompactionSettings(this._getCompactionSettings(), this.model?.provider, request.reason);
4481
4530
  let compactionResult = request.precomputed;
4482
4531
  let fromExtension = request.precomputed !== undefined;
4483
4532
  if (!compactionResult) {
@@ -4519,7 +4568,13 @@ export class AgentSession {
4519
4568
  }
4520
4569
  }
4521
4570
  if (!compactionResult) {
4522
- const { model: requestModel, apiKey, headers, extraBody, env, } = await this._getCompactionRequestAuth(model);
4571
+ // A configured compaction.model override redirects only the
4572
+ // summarization call to that model; every other part of the session
4573
+ // (lifecycle record, preparation, branch) still tracks the session
4574
+ // model. Falls back to the session model when the override is unset
4575
+ // or cannot be resolved.
4576
+ const compactionModel = this._resolveCompactionModel(model);
4577
+ const { model: requestModel, apiKey, headers, extraBody, env, } = await this._getCompactionRequestAuth(compactionModel);
4523
4578
  compactionResult = await this._runDefaultCompaction(preparation, requestModel, apiKey, headers, extraBody, request.customInstructions, signal, env, request.reason);
4524
4579
  }
4525
4580
  }
@@ -4550,11 +4605,20 @@ export class AgentSession {
4550
4605
  if (await this._wouldCompactionOverflow(pathEntries, compactionResult, fromExtension, model)) {
4551
4606
  return await this._rejectCompaction(request, requestId, operationId, "would-overflow", false);
4552
4607
  }
4608
+ const latestConfigurationEffort = this.sessionManager
4609
+ .getBranch()
4610
+ .findLast((entry) => entry.type === "configuration_update")?.reasoning.effort;
4553
4611
  const compactionEntryId = this.sessionManager.appendCompaction(compactionResult.summary, compactionResult.firstKeptEntryId, compactionResult.tokensBefore, compactionResult.details, fromExtension, compactionResult.usage);
4554
4612
  const savedEntry = this.sessionManager.getEntry(compactionEntryId);
4555
4613
  if (savedEntry?.type !== "compaction") {
4556
4614
  throw new Error("Compaction entry was not saved");
4557
4615
  }
4616
+ const modelAfterCompaction = this.model;
4617
+ if (latestConfigurationEffort !== undefined &&
4618
+ modelAfterCompaction?.id === "gpt-6-astra" &&
4619
+ (modelAfterCompaction.provider === "openai" || modelAfterCompaction.provider === "openai-codex")) {
4620
+ this.sessionManager.appendConfigurationUpdate(latestConfigurationEffort);
4621
+ }
4558
4622
  const sessionContext = this.sessionManager.buildSessionContext();
4559
4623
  const currentAgentMessages = this.agent.state.messages;
4560
4624
  const hasUnchangedPrefix = agentMessagesAtStart.every((message, index) => currentAgentMessages[index] === message);
@@ -4665,7 +4729,7 @@ export class AgentSession {
4665
4729
  simulatedMessages = truncateToolResultBodies(simulatedMessages).messages ?? simulatedMessages;
4666
4730
  }
4667
4731
  const contextTokens = estimateMessagesTokens(filterContextExcludedMessages(simulatedMessages));
4668
- const settings = this.settingsManager.getCompactionSettings();
4732
+ const settings = this._getCompactionSettings();
4669
4733
  const reserveTokens = settings.reserveScalingEnabled === false
4670
4734
  ? settings.reserveTokens
4671
4735
  : resolveReserveTokens(model.contextWindow, settings.reserveTokens);
@@ -4784,7 +4848,7 @@ export class AgentSession {
4784
4848
  blockedAdmission.revision === this._messageRevision) {
4785
4849
  throw new RequiredCompactionError();
4786
4850
  }
4787
- const settings = this.settingsManager.getCompactionSettings();
4851
+ const settings = this._getCompactionSettings();
4788
4852
  const model = this.model;
4789
4853
  const contextTokens = estimateContextTokens(filterContextExcludedMessages(this.sessionManager.buildSessionContext().messages)).tokens;
4790
4854
  const compacted = assistantMessage
@@ -4832,7 +4896,7 @@ export class AgentSession {
4832
4896
  const model = this.model;
4833
4897
  if (!model)
4834
4898
  return;
4835
- const settings = this.settingsManager.getCompactionSettings();
4899
+ const settings = this._getCompactionSettings();
4836
4900
  const reserveTokens = settings.reserveScalingEnabled === false
4837
4901
  ? settings.reserveTokens
4838
4902
  : resolveReserveTokens(model.contextWindow, settings.reserveTokens);
@@ -4866,9 +4930,7 @@ export class AgentSession {
4866
4930
  }
4867
4931
  }
4868
4932
  async _checkCompaction(assistantMessage, skipAbortedCheck = true, inlineReason, retryAfterCompaction = false) {
4869
- const settings = this.settingsManager.getCompactionSettings();
4870
- if (!settings.enabled)
4871
- return false;
4933
+ const settings = this._getCompactionSettings();
4872
4934
  // Skip if message was aborted (user cancelled) - unless skipAbortedCheck is false
4873
4935
  if (skipAbortedCheck && assistantMessage.stopReason === "aborted")
4874
4936
  return false;
@@ -4897,6 +4959,9 @@ export class AgentSession {
4897
4959
  const isOverflow = (isContextOverflow(assistantMessage, contextWindow) && (sameModel || currentContextNeedsCompaction)) ||
4898
4960
  recoverableLength ||
4899
4961
  this._isCursorPayloadOverflow(assistantMessage);
4962
+ if (isOverflow && !settings.enabled && !isTurnStuckOnContextOverflow(assistantMessage, contextWindow)) {
4963
+ return false;
4964
+ }
4900
4965
  if (isOverflow &&
4901
4966
  assistantMessage.stopReason === "stop" &&
4902
4967
  this._consumePostCompactionUsageExemption(assistantMessage)) {
@@ -4967,6 +5032,10 @@ export class AgentSession {
4967
5032
  }
4968
5033
  return compacted;
4969
5034
  }
5035
+ // Stuck-overflow recovery above runs regardless of the flag; threshold
5036
+ // compaction below is the proactive path the user switched off (#1422).
5037
+ if (!settings.enabled)
5038
+ return false;
4970
5039
  // The first ordinary response can carry provider usage calculated before
4971
5040
  // compaction. Consume that exemption only after proving this is not an
4972
5041
  // overflow, then retain the decision for later admission re-sampling.
@@ -5099,7 +5168,7 @@ export class AgentSession {
5099
5168
  }
5100
5169
  async _revalidateScheduledContinuationAdmission() {
5101
5170
  const model = this.model;
5102
- const settings = this.settingsManager.getCompactionSettings();
5171
+ const settings = this._getCompactionSettings();
5103
5172
  if (!model || !settings.enabled)
5104
5173
  return;
5105
5174
  const canonicalMessages = filterContextExcludedMessages(this.sessionManager.buildSessionContext().messages);
@@ -5279,7 +5348,7 @@ export class AgentSession {
5279
5348
  }
5280
5349
  if (!this._ownsCompactionController(autoCompactionController, "auto"))
5281
5350
  return false;
5282
- const preparation = prepareCompaction(this.sessionManager.getBranch(), cursorOverflowCompactionSettings(this.settingsManager.getCompactionSettings(), this.model?.provider, reason), reason === "overflow");
5351
+ const preparation = prepareCompaction(this.sessionManager.getBranch(), cursorOverflowCompactionSettings(this._getCompactionSettings(), this.model?.provider, reason), reason === "overflow");
5283
5352
  if (!preparation) {
5284
5353
  return endBeforeExecution();
5285
5354
  }
@@ -5363,12 +5432,21 @@ export class AgentSession {
5363
5432
  }
5364
5433
  }
5365
5434
  /**
5366
- * Toggle auto-compaction setting.
5435
+ * Toggle auto-compaction for this session only. Persisting the flag is a
5436
+ * settings-editor concern; a session-level command (RPC `set_auto_compaction`,
5437
+ * one thread of a multi-session host) must never rewrite the global setting
5438
+ * for every other session on the machine (#1422).
5367
5439
  */
5368
5440
  setAutoCompactionEnabled(enabled) {
5369
- this.settingsManager.setCompactionEnabled(enabled);
5441
+ this._autoCompactionSessionOverride = enabled;
5370
5442
  this._emitSessionSettingsChanged();
5371
5443
  }
5444
+ _getCompactionSettings() {
5445
+ const settings = this.settingsManager.getCompactionSettings();
5446
+ if (this._autoCompactionSessionOverride === undefined)
5447
+ return settings;
5448
+ return { ...settings, enabled: this._autoCompactionSessionOverride };
5449
+ }
5372
5450
  _emitSessionSettingsChanged() {
5373
5451
  this._emit({
5374
5452
  type: "session_settings_changed",
@@ -5377,9 +5455,9 @@ export class AgentSession {
5377
5455
  autoCompactionEnabled: this.autoCompactionEnabled,
5378
5456
  });
5379
5457
  }
5380
- /** Whether auto-compaction is enabled */
5458
+ /** Whether auto-compaction is enabled for this session */
5381
5459
  get autoCompactionEnabled() {
5382
- return this.settingsManager.getCompactionEnabled();
5460
+ return this._autoCompactionSessionOverride ?? this.settingsManager.getCompactionEnabled();
5383
5461
  }
5384
5462
  async bindExtensions(bindings) {
5385
5463
  const finishBindingWork = this._sessionWorkBarrier.begin();
@@ -5616,7 +5694,7 @@ export class AgentSession {
5616
5694
  this._extensionShutdownHandler?.();
5617
5695
  },
5618
5696
  getContextUsage: () => this.getContextUsage(),
5619
- getCompactionSettings: () => this.settingsManager.getCompactionSettings(),
5697
+ getCompactionSettings: () => this._getCompactionSettings(),
5620
5698
  getPromptCacheSafeWaitSeconds: () => this.resolvePromptCacheSafeWaitSeconds(),
5621
5699
  getPromptCacheGoalBackstopMaxSeconds: () => this.settingsManager.getPromptCacheGoalBackstopMaxSeconds(),
5622
5700
  getPromptCacheKeepAliveSettings: () => this.settingsManager.getPromptCacheKeepAliveSettings(),
@@ -6214,6 +6292,18 @@ export class AgentSession {
6214
6292
  let switchedFallback = false;
6215
6293
  let is429TierRouted = false;
6216
6294
  let hintTierDelayMs;
6295
+ const tryFallback = async (reason, failure) => {
6296
+ try {
6297
+ return await this._retryFallback.tryFallback(reason, failure);
6298
+ }
6299
+ catch (error) {
6300
+ if (error instanceof ModelUsabilityBudgetError) {
6301
+ this._resolveRetry();
6302
+ return false;
6303
+ }
6304
+ throw error;
6305
+ }
6306
+ };
6217
6307
  if (sameModelRemint) {
6218
6308
  this._retryAttempt++;
6219
6309
  if (this._retryAttempt > retryProfile.turn.maxRetries) {
@@ -6236,9 +6326,7 @@ export class AgentSession {
6236
6326
  // Billing-class failures never recover on this account, so the fallback
6237
6327
  // switch pins as the session model instead of reverting after the cooldown.
6238
6328
  const reason = isBillingErrorMessage(errorMessage) ? "billing" : "hard-error";
6239
- switchedFallback = await this._retryFallback.tryFallback(reason, {
6240
- errorMessage,
6241
- });
6329
+ switchedFallback = await tryFallback(reason, { errorMessage });
6242
6330
  if (!switchedFallback) {
6243
6331
  const exhaustedChainKey = this._retryFallback.exhaustedChainKey;
6244
6332
  if (exhaustedChainKey) {
@@ -6271,7 +6359,7 @@ export class AgentSession {
6271
6359
  this._resolveRetry();
6272
6360
  return "not-handled";
6273
6361
  }
6274
- switchedFallback = await this._retryFallback.tryFallback("refusal", {});
6362
+ switchedFallback = await tryFallback("refusal", {});
6275
6363
  if (!switchedFallback) {
6276
6364
  const exhaustedChainKey = this._retryFallback.exhaustedChainKey;
6277
6365
  if (exhaustedChainKey) {
@@ -6317,7 +6405,7 @@ export class AgentSession {
6317
6405
  is429TierRouted = true;
6318
6406
  this._retryAttempt++;
6319
6407
  if (this._retryAttempt > retryProfile.turn.maxRetries) {
6320
- switchedFallback = await this._retryFallback.tryFallback("transient", {
6408
+ switchedFallback = await tryFallback("transient", {
6321
6409
  errorMessage,
6322
6410
  retryAfterMs: this._getProviderRetryDelayMs(errorMessage),
6323
6411
  });
@@ -6354,7 +6442,7 @@ export class AgentSession {
6354
6442
  if (tier === "no-hint-fast-fallback") {
6355
6443
  // Fall back immediately when a candidate exists; otherwise degrade
6356
6444
  // to same-model in-turn retries instead of failing the turn.
6357
- switchedFallback = await this._retryFallback.tryFallback("transient", { errorMessage });
6445
+ switchedFallback = await tryFallback("transient", { errorMessage });
6358
6446
  if (switchedFallback) {
6359
6447
  this._retryAttempt = 1;
6360
6448
  }
@@ -6369,7 +6457,7 @@ export class AgentSession {
6369
6457
  this._retryAttempt++;
6370
6458
  if (this._retryAttempt > retryProfile.turn.maxRetries) {
6371
6459
  // Budget exhausted within tier1; fall back.
6372
- switchedFallback = await this._retryFallback.tryFallback("transient", {
6460
+ switchedFallback = await tryFallback("transient", {
6373
6461
  errorMessage,
6374
6462
  retryAfterMs: this._getProviderRetryDelayMs(errorMessage),
6375
6463
  });
@@ -6410,7 +6498,7 @@ export class AgentSession {
6410
6498
  if (inTurnResult.demoteToProbeBack) {
6411
6499
  // Cumulative hinted wait exceeded cap; demote to tier2 fallback path.
6412
6500
  const remainingHintMs = Math.max(0, (this._hintDeadlineMs ?? Date.now()) - Date.now());
6413
- switchedFallback = await this._retryFallback.tryFallback("transient", {
6501
+ switchedFallback = await tryFallback("transient", {
6414
6502
  errorMessage,
6415
6503
  retryAfterMs: remainingHintMs,
6416
6504
  });
@@ -6446,7 +6534,7 @@ export class AgentSession {
6446
6534
  else {
6447
6535
  // tier2-fallback-probe-back or tier3-fallback-only: immediate fallback.
6448
6536
  const remainingHintMs = hintMs ?? 0;
6449
- switchedFallback = await this._retryFallback.tryFallback("transient", {
6537
+ switchedFallback = await tryFallback("transient", {
6450
6538
  errorMessage,
6451
6539
  retryAfterMs: remainingHintMs,
6452
6540
  });
@@ -6469,7 +6557,7 @@ export class AgentSession {
6469
6557
  this._retryAttempt++;
6470
6558
  }
6471
6559
  if (!is429TierRouted && this._retryAttempt > retryProfile.turn.maxRetries) {
6472
- switchedFallback = await this._retryFallback.tryFallback("transient", {
6560
+ switchedFallback = await tryFallback("transient", {
6473
6561
  errorMessage,
6474
6562
  retryAfterMs: this._getProviderRetryDelayMs(errorMessage),
6475
6563
  });
@@ -6513,7 +6601,7 @@ export class AgentSession {
6513
6601
  // branch above may have already switched on this same error, and hopping again
6514
6602
  // here would skip that candidate's own retry budget.
6515
6603
  if (!switchedFallback) {
6516
- switchedFallback = await this._retryFallback.tryFallback("transient", {
6604
+ switchedFallback = await tryFallback("transient", {
6517
6605
  errorMessage,
6518
6606
  retryAfterMs: providerDelayMs,
6519
6607
  });
@@ -6595,7 +6683,7 @@ export class AgentSession {
6595
6683
  // the prior retry checks. Revalidate canonical session context immediately
6596
6684
  // before the continuation so a rejected compaction never admits that model.
6597
6685
  const model = this.model;
6598
- const compactionSettings = this.settingsManager.getCompactionSettings();
6686
+ const compactionSettings = this._getCompactionSettings();
6599
6687
  const contextTokens = estimateContextTokens(filterContextExcludedMessages(this.sessionManager.buildSessionContext().messages)).tokens;
6600
6688
  if (compactionSettings.enabled &&
6601
6689
  model &&