@gajae-code/coding-agent 0.12.21 → 0.13.0

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 (222) hide show
  1. package/CHANGELOG.md +67 -2
  2. package/dist/types/cli/args.d.ts +5 -2
  3. package/dist/types/cli/root-flags.d.ts +246 -0
  4. package/dist/types/cli.d.ts +11 -19
  5. package/dist/types/commands/daemon.d.ts +0 -30
  6. package/dist/types/commands/launch.d.ts +11 -19
  7. package/dist/types/commands/quick-lane.d.ts +32 -0
  8. package/dist/types/commands/sdk.d.ts +21 -15
  9. package/dist/types/config/file-lock-gc.d.ts +2 -0
  10. package/dist/types/config/model-equivalence.d.ts +20 -0
  11. package/dist/types/config/model-profile-activation.d.ts +62 -17
  12. package/dist/types/config/model-profiles.d.ts +10 -0
  13. package/dist/types/config/model-registry.d.ts +68 -1
  14. package/dist/types/config/model-resolver.d.ts +15 -4
  15. package/dist/types/config/models-config-schema.d.ts +7 -2
  16. package/dist/types/config/provider-selection-policy.d.ts +62 -0
  17. package/dist/types/config/settings-schema.d.ts +36 -78
  18. package/dist/types/config/settings.d.ts +2 -0
  19. package/dist/types/edit/renderer.d.ts +5 -2
  20. package/dist/types/edit/streaming.d.ts +13 -0
  21. package/dist/types/extensibility/custom-tools/types.d.ts +2 -0
  22. package/dist/types/extensibility/extensions/compact-handler.d.ts +2 -1
  23. package/dist/types/extensibility/extensions/types.d.ts +3 -0
  24. package/dist/types/gjc-runtime/gc-runtime.d.ts +19 -5
  25. package/dist/types/internal-urls/local-protocol.d.ts +1 -0
  26. package/dist/types/modes/components/model-selector.d.ts +0 -2
  27. package/dist/types/modes/components/provider-order-context.d.ts +45 -0
  28. package/dist/types/modes/components/provider-order-editor.d.ts +24 -0
  29. package/dist/types/modes/components/settings-defs.d.ts +5 -1
  30. package/dist/types/modes/components/settings-selector.d.ts +2 -0
  31. package/dist/types/quick-lane/classify.d.ts +40 -0
  32. package/dist/types/runtime/stt-settings-setup.d.ts +18 -0
  33. package/dist/types/sdk/broker/broker.d.ts +56 -2
  34. package/dist/types/sdk/broker/operation-receipt-ledger.d.ts +25 -0
  35. package/dist/types/sdk/broker/session-index.d.ts +83 -2
  36. package/dist/types/sdk/broker/state-version.d.ts +1 -1
  37. package/dist/types/sdk/bus/notification-service.d.ts +6 -0
  38. package/dist/types/sdk/bus/prompt-reconciliation.d.ts +8 -1
  39. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +4 -1
  40. package/dist/types/sdk/bus/telegram-daemon.d.ts +26 -0
  41. package/dist/types/sdk/cli/rows.d.ts +98 -0
  42. package/dist/types/sdk/cli/session-cli.d.ts +35 -3
  43. package/dist/types/sdk/elevation/allowlist.d.ts +20 -0
  44. package/dist/types/sdk/elevation/audit.d.ts +93 -0
  45. package/dist/types/sdk/elevation/capability.d.ts +3 -0
  46. package/dist/types/sdk/elevation/digest.d.ts +72 -0
  47. package/dist/types/sdk/elevation/dispatch-receipt.d.ts +85 -0
  48. package/dist/types/sdk/elevation/grant-ledger.d.ts +163 -0
  49. package/dist/types/sdk/elevation/owner.d.ts +48 -0
  50. package/dist/types/sdk/guides/cache.d.ts +98 -0
  51. package/dist/types/sdk/guides/catalog.d.ts +156 -0
  52. package/dist/types/sdk/guides/cli.d.ts +39 -0
  53. package/dist/types/sdk/guides/manifest.d.ts +66 -0
  54. package/dist/types/sdk/guides/verify.d.ts +66 -0
  55. package/dist/types/sdk/host/control/dispatch.d.ts +8 -0
  56. package/dist/types/sdk/host/control/index.d.ts +1 -1
  57. package/dist/types/sdk/host/control/operations.d.ts +3 -2
  58. package/dist/types/sdk/host/query/cursor.d.ts +10 -0
  59. package/dist/types/sdk/host/query/handlers.d.ts +21 -0
  60. package/dist/types/sdk/host/session-runtime.d.ts +9 -0
  61. package/dist/types/sdk/mcp/server.d.ts +2 -2
  62. package/dist/types/sdk/prompt-status.d.ts +11 -0
  63. package/dist/types/sdk/session.d.ts +4 -0
  64. package/dist/types/sdk/transport/index.d.ts +1 -0
  65. package/dist/types/sdk/transport/relay.d.ts +1 -0
  66. package/dist/types/sdk/transport/serve-cli.d.ts +20 -1
  67. package/dist/types/session/agent-session.d.ts +32 -18
  68. package/dist/types/session/blob-store.d.ts +13 -7
  69. package/dist/types/session/fallback-chain-controller.d.ts +15 -0
  70. package/dist/types/session/internal/managed-session-storage.d.ts +25 -1
  71. package/dist/types/session/internal/session-memory-sidecar.d.ts +5 -5
  72. package/dist/types/session/session-manager.d.ts +139 -10
  73. package/dist/types/session/session-storage.d.ts +48 -0
  74. package/dist/types/setup/provider-onboarding.d.ts +7 -1
  75. package/dist/types/slash-commands/builtin-registry.d.ts +14 -0
  76. package/dist/types/stt/setup.d.ts +2 -0
  77. package/dist/types/task/executor.d.ts +10 -2
  78. package/dist/types/tools/browser/runtime-diagnostics.d.ts +65 -0
  79. package/dist/types/tools/browser/tab-protocol.d.ts +4 -0
  80. package/dist/types/tools/browser/tab-supervisor.d.ts +2 -0
  81. package/dist/types/tools/browser/tab-worker.d.ts +2 -0
  82. package/dist/types/tools/browser.d.ts +2 -0
  83. package/dist/types/tools/image-gen.d.ts +14 -18
  84. package/dist/types/tools/index.d.ts +2 -0
  85. package/dist/types/tools/resource-gc.d.ts +2 -0
  86. package/dist/types/utils/pasted-image-path.d.ts +6 -0
  87. package/package.json +9 -9
  88. package/scripts/generate-sdk-operation-inventory.ts +8 -0
  89. package/scripts/run-test-manifest.ts +61 -21
  90. package/scripts/verify-gjc-sdk-canonicalization.ts +9 -4
  91. package/src/cli/args.ts +62 -9
  92. package/src/cli/completion-cli.ts +1 -0
  93. package/src/cli/fast-help.ts +1 -0
  94. package/src/cli/root-flags.ts +100 -0
  95. package/src/cli/setup-cli.ts +6 -2
  96. package/src/cli.ts +42 -65
  97. package/src/commands/acp.ts +1 -1
  98. package/src/commands/daemon.ts +2 -31
  99. package/src/commands/gc.ts +1 -1
  100. package/src/commands/launch.ts +8 -129
  101. package/src/commands/quick-lane.ts +51 -0
  102. package/src/commands/sdk.ts +200 -26
  103. package/src/commands/setup.ts +4 -2
  104. package/src/config/file-lock-gc.ts +4 -1
  105. package/src/config/model-equivalence.ts +76 -7
  106. package/src/config/model-profile-activation.ts +1115 -265
  107. package/src/config/model-profiles.ts +134 -2
  108. package/src/config/model-registry.ts +352 -50
  109. package/src/config/model-resolver.ts +289 -38
  110. package/src/config/models-config-schema.ts +11 -4
  111. package/src/config/provider-selection-policy.ts +146 -0
  112. package/src/config/settings-schema.ts +50 -77
  113. package/src/config/settings.ts +40 -0
  114. package/src/coordinator-mcp/server.ts +32 -2
  115. package/src/edit/renderer.ts +175 -223
  116. package/src/edit/streaming.ts +108 -1
  117. package/src/export/html/index.ts +57 -7
  118. package/src/extensibility/custom-tools/types.ts +2 -0
  119. package/src/extensibility/extensions/compact-handler.ts +3 -2
  120. package/src/extensibility/extensions/runner.ts +6 -0
  121. package/src/extensibility/extensions/types.ts +3 -0
  122. package/src/gjc-runtime/gc-runtime.ts +379 -20
  123. package/src/gjc-runtime/launch-worktree.ts +5 -1
  124. package/src/gjc-runtime/team-runtime.ts +59 -2
  125. package/src/gjc-runtime/tmux-owner-isolation.ts +19 -0
  126. package/src/gjc-runtime/ultragoal-receipt-freshness.ts +16 -1
  127. package/src/internal-urls/docs-index.generated.ts +17 -13
  128. package/src/internal-urls/local-protocol.ts +1 -0
  129. package/src/main.ts +40 -3
  130. package/src/memories/index.ts +4 -2
  131. package/src/modes/acp/acp-agent.ts +235 -91
  132. package/src/modes/components/model-selector.ts +206 -42
  133. package/src/modes/components/provider-order-context.ts +197 -0
  134. package/src/modes/components/provider-order-editor.ts +441 -0
  135. package/src/modes/components/settings-defs.ts +21 -1
  136. package/src/modes/components/settings-selector.ts +45 -2
  137. package/src/modes/components/tool-execution.ts +47 -15
  138. package/src/modes/controllers/command-controller.ts +6 -3
  139. package/src/modes/controllers/extension-ui-controller.ts +2 -0
  140. package/src/modes/controllers/input-controller.ts +73 -2
  141. package/src/modes/controllers/selector-controller.ts +260 -153
  142. package/src/modes/prompt-suggestion-controller.ts +1 -1
  143. package/src/modes/runtime-init.ts +1 -0
  144. package/src/modes/utils/ui-helpers.ts +18 -0
  145. package/src/prompts/tools/browser.md +1 -0
  146. package/src/quick-lane/classify.ts +257 -0
  147. package/src/rlm/index.ts +1 -1
  148. package/src/runtime/stt-settings-setup.ts +57 -0
  149. package/src/sdk/acp/adapter.ts +2 -1
  150. package/src/sdk/broker/broker.ts +780 -12
  151. package/src/sdk/broker/lifecycle.ts +70 -29
  152. package/src/sdk/broker/operation-receipt-ledger.ts +144 -0
  153. package/src/sdk/broker/session-index.ts +463 -48
  154. package/src/sdk/broker/state-version.ts +13 -9
  155. package/src/sdk/broker/transport.ts +14 -1
  156. package/src/sdk/bus/index.ts +34 -13
  157. package/src/sdk/bus/kind-aware-reconciliation.ts +21 -2
  158. package/src/sdk/bus/notification-service.ts +47 -1
  159. package/src/sdk/bus/prompt-reconciliation.ts +22 -3
  160. package/src/sdk/bus/telegram-daemon-contract.ts +4 -1
  161. package/src/sdk/bus/telegram-daemon.ts +54 -3
  162. package/src/sdk/cli/rows.ts +247 -0
  163. package/src/sdk/cli/session-cli.ts +930 -79
  164. package/src/sdk/elevation/allowlist.ts +54 -0
  165. package/src/sdk/elevation/audit.ts +208 -0
  166. package/src/sdk/elevation/capability.ts +41 -0
  167. package/src/sdk/elevation/digest.ts +129 -0
  168. package/src/sdk/elevation/dispatch-receipt.ts +184 -0
  169. package/src/sdk/elevation/grant-ledger.ts +1142 -0
  170. package/src/sdk/elevation/owner.ts +109 -0
  171. package/src/sdk/guides/cache.ts +363 -0
  172. package/src/sdk/guides/catalog.ts +554 -0
  173. package/src/sdk/guides/cli.ts +235 -0
  174. package/src/sdk/guides/manifest.ts +152 -0
  175. package/src/sdk/guides/verify.ts +178 -0
  176. package/src/sdk/host/control/dispatch.ts +49 -1
  177. package/src/sdk/host/control/index.ts +1 -0
  178. package/src/sdk/host/control/operations.ts +8 -1
  179. package/src/sdk/host/query/cursor.ts +47 -3
  180. package/src/sdk/host/query/handlers.ts +155 -2
  181. package/src/sdk/host/sdk-surface-parity.test.ts +1 -0
  182. package/src/sdk/host/session-runtime.test.ts +391 -1
  183. package/src/sdk/host/session-runtime.ts +308 -73
  184. package/src/sdk/host/surface-policy.ts +3 -0
  185. package/src/sdk/host/websocket-transport.ts +7 -1
  186. package/src/sdk/mcp/server.ts +88 -15
  187. package/src/sdk/prompt-status.ts +8 -0
  188. package/src/sdk/protocol/operation-inventory.generated.json +62 -0
  189. package/src/sdk/protocol/operation-registry.ts +3 -2
  190. package/src/sdk/session.ts +87 -39
  191. package/src/sdk/transport/index.ts +1 -0
  192. package/src/sdk/transport/relay.ts +3 -0
  193. package/src/sdk/transport/serve-cli.ts +111 -24
  194. package/src/secrets/obfuscator.ts +92 -12
  195. package/src/session/agent-session.ts +322 -93
  196. package/src/session/blob-store.ts +60 -8
  197. package/src/session/fallback-chain-controller.ts +35 -0
  198. package/src/session/internal/managed-session-scope.ts +25 -3
  199. package/src/session/internal/managed-session-storage.ts +574 -73
  200. package/src/session/internal/session-memory-sidecar.ts +19 -29
  201. package/src/session/session-manager.ts +2166 -743
  202. package/src/session/session-storage.ts +715 -48
  203. package/src/setup/provider-onboarding.ts +19 -3
  204. package/src/setup/provider-presets.json +3 -1
  205. package/src/slash-commands/builtin-registry.ts +85 -3
  206. package/src/stt/downloader.ts +2 -3
  207. package/src/stt/setup.ts +29 -4
  208. package/src/task/executor.ts +22 -18
  209. package/src/task/index.ts +24 -1
  210. package/src/tools/archive-reader.ts +11 -2
  211. package/src/tools/browser/runtime-diagnostics.ts +249 -0
  212. package/src/tools/browser/tab-protocol.ts +4 -0
  213. package/src/tools/browser/tab-supervisor.ts +5 -0
  214. package/src/tools/browser/tab-worker.ts +36 -1
  215. package/src/tools/browser.ts +7 -0
  216. package/src/tools/image-gen.ts +110 -166
  217. package/src/tools/index.ts +2 -0
  218. package/src/tools/read.ts +1 -1
  219. package/src/tools/resource-gc.ts +6 -1
  220. package/src/tools/tool-catalog.generated.ts +5 -1
  221. package/src/utils/pasted-image-path.ts +93 -0
  222. package/src/web/search/index.ts +3 -3
@@ -116,6 +116,7 @@ import {
116
116
  type AuthDisposition,
117
117
  beginAttempt,
118
118
  classifyFallbackTrigger,
119
+ EMPTY_RESPONSE_PROVIDER_CODE,
119
120
  type FallbackAttemptToken,
120
121
  type FallbackTriggerClass,
121
122
  STREAM_FIRST_EVENT_TIMEOUT_PROVIDER_CODE,
@@ -193,9 +194,11 @@ import type { CasReceipt } from "../config/atomic-yaml-patch";
193
194
  import { activateModelProfile, materializeActiveModelProfileAssignment } from "../config/model-profile-activation";
194
195
  import {
195
196
  ModelProfileRegistryError,
197
+ resolveModelProfileName,
196
198
  UnknownModelProfileError,
197
199
  validateModelProfileName,
198
200
  } from "../config/model-profile-contract";
201
+ import { resolveProfileBindings } from "../config/model-profiles";
199
202
  import {
200
203
  GJC_MODEL_ASSIGNMENT_TARGETS,
201
204
  isAuthenticated,
@@ -389,6 +392,7 @@ import {
389
392
  compactionRetryDelay,
390
393
  effectiveFallbackDelay,
391
394
  FallbackChainController,
395
+ type FallbackChainRuntimeState,
392
396
  } from "./fallback-chain-controller";
393
397
 
394
398
  export { DefaultModelSelectionRecoveryError } from "./default-model-selection";
@@ -716,6 +720,8 @@ export interface AgentSessionConfig {
716
720
  * so that credential sticky selection is consistent with the session's streaming calls.
717
721
  */
718
722
  providerSessionId?: string;
723
+ /** Optional auth-selection identity, distinct from logical/canonical and provider-cache identity. */
724
+ credentialSessionId?: string;
719
725
  /** Optional provider-facing cache identity, distinct from logical session identity. */
720
726
  providerCacheSessionId?: string;
721
727
  }
@@ -933,6 +939,7 @@ type RetryErrorClassification =
933
939
  | "terminal"
934
940
  | "usage_limit"
935
941
  | "first_event_timeout"
942
+ | "empty_response"
936
943
  | "transient"
937
944
  | "local_unavailable"
938
945
  | "unknown";
@@ -1785,6 +1792,12 @@ type SessionAdmissionLease = {
1785
1792
  release(): void;
1786
1793
  };
1787
1794
 
1795
+ export interface DefaultFallbackRuntimeState {
1796
+ chain: ConfiguredFallbackChain;
1797
+ controller: FallbackChainRuntimeState;
1798
+ exhaustedLastTurn: boolean;
1799
+ }
1800
+
1788
1801
  /**
1789
1802
  * Fire-and-forget continuations (auto-compaction retries, queued follow-ups) race a
1790
1803
  * still-busy agent whose current turn can legitimately hold the run for minutes.
@@ -2069,6 +2082,7 @@ export class AgentSession {
2069
2082
  #ircRosterEpoch = 0;
2070
2083
  #ircRosterClaim: IrcRosterClaim | null = null;
2071
2084
  #providerSessionId: string | undefined;
2085
+ #credentialSessionId: string | undefined;
2072
2086
  #providerCacheSessionId: string | undefined;
2073
2087
  #isDisposed = false;
2074
2088
  #disposePromise: Promise<void> | undefined;
@@ -2949,6 +2963,7 @@ export class AgentSession {
2949
2963
  });
2950
2964
  this.#agentRegistry = config.agentRegistry;
2951
2965
  this.#providerSessionId = config.providerSessionId;
2966
+ this.#credentialSessionId = config.credentialSessionId;
2952
2967
  this.#providerCacheSessionId = config.providerCacheSessionId;
2953
2968
  // Per-tool TTSR reminders are folded into the matched tool's result via this hook.
2954
2969
  this.agent.afterToolCall = ctx => this.#ttsrAfterToolCall(ctx);
@@ -4869,6 +4884,11 @@ export class AgentSession {
4869
4884
  onError?: (error: unknown) => void;
4870
4885
  resourceRunId?: string;
4871
4886
  maintenanceContinuation?: boolean;
4887
+ // Queue-consuming continuations (follow-up/steer drained after execution
4888
+ // tails settle) must run continueQueuedMessages() so they deliver the
4889
+ // queued turn without replaying the non-assistant tail. Every other
4890
+ // scheduled continuation keeps continue() semantics.
4891
+ continueQueuedOnly?: boolean;
4872
4892
  }): Promise<void> {
4873
4893
  const predecessorAgentEndHold = options?.suppressPredecessorAgentEnd
4874
4894
  ? this.#reserveDeferredAgentEndForContinuation()
@@ -4955,10 +4975,15 @@ export class AgentSession {
4955
4975
  predecessorAccepted = true;
4956
4976
  this.#releaseDeferredAgentEndLease(predecessorAgentEnd);
4957
4977
  };
4978
+ const hasQueuedMessages = this.agent.hasQueuedMessages();
4958
4979
  const startsQueuedSuccessor =
4959
- this.agent.state.messages.at(-1)?.role === "assistant" && this.agent.hasQueuedMessages();
4980
+ hasQueuedMessages &&
4981
+ (options?.continueQueuedOnly === true || this.agent.state.messages.at(-1)?.role === "assistant");
4982
+ const continueQueued = options?.continueQueuedOnly
4983
+ ? this.agent.continueQueuedMessages.bind(this.agent)
4984
+ : this.agent.continue.bind(this.agent);
4960
4985
  try {
4961
- await this.agent.continue({
4986
+ await continueQueued({
4962
4987
  ...this.#managedFallbackPromptOptions(),
4963
4988
  maintenanceContinuation: options?.maintenanceContinuation,
4964
4989
  // Reset only after continue() has claimed the queued turn. Skipped or stale
@@ -5696,6 +5721,7 @@ export class AgentSession {
5696
5721
  getManagedLegacyLocalMigrationSource: () =>
5697
5722
  prepared?.managedLegacyLocalMigrationSource ?? this.sessionManager.getManagedLegacyLocalMigrationSource(),
5698
5723
  getSessionId: () => prepared?.sessionId ?? this.sessionManager.getSessionId(),
5724
+ getCredentialSessionId: () => this.credentialSessionId,
5699
5725
  };
5700
5726
  }
5701
5727
 
@@ -6205,7 +6231,7 @@ export class AgentSession {
6205
6231
  this.agent.sessionId = sid;
6206
6232
  this.agent.providerSessionId = this.#providerCacheSessionId ?? sid;
6207
6233
  this.agent.setMetadataResolver((provider: string) =>
6208
- buildSessionMetadata(sid, provider, this.#modelRegistry.authStorage),
6234
+ buildSessionMetadata(sid, provider, this.#modelRegistry.authStorage, this.credentialSessionId),
6209
6235
  );
6210
6236
  }
6211
6237
 
@@ -7384,6 +7410,7 @@ export class AgentSession {
7384
7410
  const getCustomToolContext = (): CustomToolContext => ({
7385
7411
  sessionManager: createReadonlySessionManager(this.sessionManager),
7386
7412
  modelRegistry: this.#modelRegistry,
7413
+ credentialSessionId: this.credentialSessionId,
7387
7414
  model: this.model,
7388
7415
  isIdle: () => !this.isStreaming,
7389
7416
  hasQueuedMessages: () => this.queuedMessageCount > 0,
@@ -7427,6 +7454,7 @@ export class AgentSession {
7427
7454
  return {
7428
7455
  sessionManager: createReadonlySessionManager(this.sessionManager),
7429
7456
  modelRegistry: this.#modelRegistry,
7457
+ credentialSessionId: this.credentialSessionId,
7430
7458
  model: this.model,
7431
7459
  isIdle: () => !this.isStreaming,
7432
7460
  hasQueuedMessages: () => this.queuedMessageCount > 0,
@@ -7804,6 +7832,11 @@ export class AgentSession {
7804
7832
  return this.#providerSessionId ?? this.sessionManager.getSessionId();
7805
7833
  }
7806
7834
 
7835
+ /** Credential selection identity; defaults to the provider-facing session identity. */
7836
+ get credentialSessionId(): string {
7837
+ return this.#credentialSessionId ?? this.sessionId;
7838
+ }
7839
+
7807
7840
  /** Current session display name, if set */
7808
7841
  get sessionName(): string | undefined {
7809
7842
  return this.sessionManager.getSessionName();
@@ -8894,7 +8927,7 @@ export class AgentSession {
8894
8927
  const apiKey = await this.#awaitPromptPreflight(
8895
8928
  generation,
8896
8929
  preflightSignal,
8897
- this.#modelRegistry.getApiKey(this.model, this.sessionId, { signal: preflightSignal }),
8930
+ this.#modelRegistry.getApiKey(this.model, this.credentialSessionId, { signal: preflightSignal }),
8898
8931
  );
8899
8932
  if (!apiKey) {
8900
8933
  throw new Error(formatNoCredentialOnboardingError(this.model.provider));
@@ -9224,6 +9257,7 @@ export class AgentSession {
9224
9257
  cwd: this.sessionManager.getCwd(),
9225
9258
  sessionManager: createReadonlySessionManager(this.sessionManager),
9226
9259
  modelRegistry: this.#modelRegistry,
9260
+ credentialSessionId: this.credentialSessionId,
9227
9261
  model: this.model ?? undefined,
9228
9262
  getActivePromptHandle: () => this.activePromptHandle,
9229
9263
  isIdle: () => !this.isStreaming,
@@ -9443,6 +9477,7 @@ export class AgentSession {
9443
9477
  if (!this.#cancelAndSubmitInProgress && this.#canAutoContinueForSteer()) {
9444
9478
  this.#scheduleAgentContinue({
9445
9479
  shouldContinue: () => this.#canAutoContinueForSteer() && this.agent.hasQueuedSteering(),
9480
+ continueQueuedOnly: true,
9446
9481
  });
9447
9482
  }
9448
9483
  }
@@ -9475,11 +9510,7 @@ export class AgentSession {
9475
9510
  // agent.continue() only dequeues follow-ups from an assistant-ended state;
9476
9511
  // resuming from user/toolResult state runs an extra model call on the
9477
9512
  // stale prompt before draining the queue.
9478
- if (!this.#cancelAndSubmitInProgress && this.#canAutoContinueForFollowUp()) {
9479
- this.#scheduleAgentContinue({
9480
- shouldContinue: () => this.#canAutoContinueForFollowUp() && this.agent.hasQueuedMessages(),
9481
- });
9482
- }
9513
+ this.#scheduleQueuedFollowUpContinuation();
9483
9514
  }
9484
9515
 
9485
9516
  /**
@@ -9487,10 +9518,21 @@ export class AgentSession {
9487
9518
  */
9488
9519
  #canAutoContinueForFollowUp(): boolean {
9489
9520
  if (this.isStreaming) return false;
9521
+ if (this.isCompacting) return false;
9522
+ if (this.isBashRunning) return false;
9523
+ if (this.isEvalRunning) return false;
9490
9524
  if (this.isRetrying) return false;
9491
9525
  const messages = this.agent.state.messages;
9492
9526
  const last = messages[messages.length - 1];
9493
- return last?.role === "assistant";
9527
+ return last?.role === "assistant" || last?.role === "bashExecution" || last?.role === "pythonExecution";
9528
+ }
9529
+ #scheduleQueuedFollowUpContinuation(): void {
9530
+ if (!this.#cancelAndSubmitInProgress && this.#canAutoContinueForFollowUp() && this.agent.hasQueuedMessages()) {
9531
+ this.#scheduleAgentContinue({
9532
+ shouldContinue: () => this.#canAutoContinueForFollowUp() && this.agent.hasQueuedMessages(),
9533
+ continueQueuedOnly: true,
9534
+ });
9535
+ }
9494
9536
  }
9495
9537
 
9496
9538
  /**
@@ -10284,6 +10326,7 @@ export class AgentSession {
10284
10326
  delayMs: 1,
10285
10327
  generation: this.#promptGeneration,
10286
10328
  shouldContinue: () => this.agent.hasQueuedSteering(),
10329
+ continueQueuedOnly: true,
10287
10330
  });
10288
10331
  }
10289
10332
  return outcome;
@@ -10717,6 +10760,18 @@ export class AgentSession {
10717
10760
  }
10718
10761
  }
10719
10762
  this.#clearConstructorToolSelectionAuthority();
10763
+ const configuredDefaultProfile = this.settings.get("modelProfile.default");
10764
+ const configuredDefaultProfileIdentity = configuredDefaultProfile
10765
+ ? resolveModelProfileName(
10766
+ configuredDefaultProfile,
10767
+ this.#modelRegistry.getModelProfiles?.() ?? new Map<string, unknown>(),
10768
+ )
10769
+ : undefined;
10770
+ if (this.#activeModelProfile && this.#activeModelProfile !== configuredDefaultProfileIdentity) {
10771
+ this.settings.clearOverride("modelRoles");
10772
+ this.settings.clearOverride("task.agentModelOverrides");
10773
+ this.#activeModelProfile = undefined;
10774
+ }
10720
10775
  const inheritedThinkingLevel = resolveThinkingLevelForModel(this.model, this.#getInheritedThinkingLevel());
10721
10776
  this.#thinkingLevelMutationRevision++;
10722
10777
  this.#thinkingLevelLiveMutationRevision++;
@@ -10836,15 +10891,13 @@ export class AgentSession {
10836
10891
  await this.sessionManager.flush();
10837
10892
 
10838
10893
  const boundedColdForkEligible =
10839
- this.sessionManager.getSessionMemoryStats().coldRetirementActive &&
10840
- !this.sessionManager.isManagedDestination() &&
10841
- previousSessionFile !== undefined;
10894
+ this.sessionManager.getSessionMemoryStats().coldRetirementActive && previousSessionFile !== undefined;
10842
10895
  if (boundedColdForkEligible) {
10843
10896
  const previousManager = this.sessionManager;
10844
10897
  const forkedManager = await SessionManager.forkFrom(
10845
10898
  previousSessionFile,
10846
10899
  previousManager.getCwd(),
10847
- SessionManager.explicitDestination(previousManager.getSessionDir()),
10900
+ previousManager.getDestinationForFork(),
10848
10901
  undefined,
10849
10902
  "copy-retain",
10850
10903
  this.settings.get("sessionMemory.mode"),
@@ -10852,8 +10905,8 @@ export class AgentSession {
10852
10905
  try {
10853
10906
  await initializeLocalRoot({
10854
10907
  getArtifactsDir: () => forkedManager.getArtifactsDir(),
10855
- isManagedDestination: () => false,
10856
- getManagedLegacyLocalMigrationSource: () => null,
10908
+ isManagedDestination: () => forkedManager.isManagedDestination(),
10909
+ getManagedLegacyLocalMigrationSource: () => forkedManager.getManagedLegacyLocalMigrationSource(),
10857
10910
  getSessionId: () => forkedManager.getSessionId(),
10858
10911
  });
10859
10912
  await this.#settleOwnAsyncJobsBeforeArtifactRetirement();
@@ -10919,15 +10972,22 @@ export class AgentSession {
10919
10972
  async setModel(
10920
10973
  model: Model,
10921
10974
  role: string = "default",
10922
- options?: { selector?: string; thinkingLevel?: ThinkingLevel; cause?: ModelChangeCause },
10975
+ options?: {
10976
+ selector?: string;
10977
+ thinkingLevel?: ThinkingLevel;
10978
+ cause?: ModelChangeCause;
10979
+ onMutationStarted?: () => void;
10980
+ },
10923
10981
  ): Promise<void> {
10924
10982
  const previousEditMode = this.#resolveActiveEditMode();
10925
- const apiKey = await this.#modelRegistry.getApiKey(model, this.sessionId);
10983
+ const apiKey = await this.#modelRegistry.getApiKey(model, this.credentialSessionId);
10926
10984
  if (!apiKey) {
10927
10985
  throw new Error(`No API key for ${model.provider}/${model.id}`);
10928
10986
  }
10929
10987
 
10988
+ options?.onMutationStarted?.();
10930
10989
  this.#setModelAuthoritatively(model, options?.cause ?? "user-selection");
10990
+ this.#seedSessionCanonicalVariant(model);
10931
10991
  this.sessionManager.appendModelChange(`${model.provider}/${model.id}`, role);
10932
10992
  this.settings.setModelRole(
10933
10993
  role,
@@ -10974,6 +11034,19 @@ export class AgentSession {
10974
11034
  return this.#activeModelProfile;
10975
11035
  }
10976
11036
 
11037
+ /** Resolver intent only for assignments owned by the active profile. */
11038
+ #persistedModelProfileAliasIntent(role: string): { aliasIntent: "preset-equivalent" } | undefined {
11039
+ if (!this.#activeModelProfile) return undefined;
11040
+ const profile = this.#modelRegistry.getModelProfile?.(this.#activeModelProfile);
11041
+ if (!profile) return undefined;
11042
+ const bindings = resolveProfileBindings(profile);
11043
+ const owned =
11044
+ role === "default"
11045
+ ? bindings.defaultSelector !== undefined
11046
+ : Object.hasOwn(bindings.modelRoles, role) || Object.hasOwn(bindings.agentModelOverrides, role);
11047
+ return owned ? { aliasIntent: "preset-equivalent" } : undefined;
11048
+ }
11049
+
10977
11050
  /**
10978
11051
  * Drop the in-session profile marker and the runtime settings overrides a
10979
11052
  * session-only profile activation installed. Session transitions
@@ -11000,9 +11073,6 @@ export class AgentSession {
11000
11073
  const modelRoles = { ...this.settings.get("modelRoles") };
11001
11074
  const agentOverrides = { ...this.settings.get("task.agentModelOverrides") };
11002
11075
  for (const [role, baseline] of this.#activeProfileInstalledRoles) {
11003
- // The `default` role is rewritten durably by concrete picks and
11004
- // transitions; never shadow the newer durable value with a stale
11005
- // pre-profile override.
11006
11076
  if (role === "default" || baseline === undefined) delete modelRoles[role];
11007
11077
  else modelRoles[role] = baseline;
11008
11078
  }
@@ -11015,14 +11085,9 @@ export class AgentSession {
11015
11085
  this.#activeProfileInstalledRoles.clear();
11016
11086
  this.#activeProfileInstalledAgentOverrides.clear();
11017
11087
  }
11018
- // Configured modelBindings share these override slots with profile
11019
- // activations and must survive the drop even when the profile contributed
11020
- // no role keys; a profile-free transition leaves them untouched.
11021
11088
  if (hadInstalledKeys || this.getActiveModelProfile() !== undefined) {
11022
11089
  this.#modelRegistry.reapplyConfiguredModelBindings(this.settings);
11023
11090
  }
11024
- // A dropped profile may have installed a fallback chain; clear it so
11025
- // retry/resume does not reconstruct the stale profile chain.
11026
11091
  const defaultChain = getSessionContextForInternalRead(this.sessionManager).configuredModelChains.default;
11027
11092
  if (defaultChain && defaultChain.identity !== undefined) {
11028
11093
  this.setConfiguredModelChain("default", [], "user-selection");
@@ -11031,22 +11096,13 @@ export class AgentSession {
11031
11096
  this.#preProfileModel = undefined;
11032
11097
  }
11033
11098
 
11034
- /**
11035
- * Record which runtime override keys a profile activation installed together
11036
- * with their pre-profile effective baseline (durable/project value or a
11037
- * configured `modelBindings` value). First activation for a key wins, so a
11038
- * profile switch never captures the previous profile's roles as the
11039
- * baseline. The session-scoped reset restores exactly these keys.
11040
- */
11099
+ /** Record runtime override keys installed by a profile activation. */
11041
11100
  noteProfileInstalledOverrides(
11042
11101
  modelRoles: readonly string[],
11043
11102
  agentModelOverrides: readonly string[],
11044
11103
  preProfileModel: Model | undefined,
11045
11104
  ): void {
11046
11105
  const bindings = this.#modelRegistry.getConfiguredModelBindings?.();
11047
- // Captured by the caller before activation replaced the runtime model; reading
11048
- // `this.model` here would record the profile's own model. First activation wins,
11049
- // so a chain of session-only profiles still restores the original selection.
11050
11106
  if (this.#preProfileModel === undefined) this.#preProfileModel = preProfileModel;
11051
11107
  for (const role of modelRoles) {
11052
11108
  if (this.#activeProfileInstalledRoles.has(role)) continue;
@@ -11066,7 +11122,7 @@ export class AgentSession {
11066
11122
  }
11067
11123
  }
11068
11124
 
11069
- /** Drop the recorded profile-installed override keys (after materialization). */
11125
+ /** Drop the recorded profile-installed override keys after materialization. */
11070
11126
  clearProfileInstalledOverrides(): void {
11071
11127
  this.#activeProfileInstalledRoles.clear();
11072
11128
  this.#activeProfileInstalledAgentOverrides.clear();
@@ -11229,6 +11285,25 @@ export class AgentSession {
11229
11285
  return getSessionContextForInternalRead(this.sessionManager).configuredModelChains[role]?.entries;
11230
11286
  }
11231
11287
 
11288
+ /** Return the persisted configured chain with its durable ownership metadata. */
11289
+ getConfiguredModelChainState(role: string):
11290
+ | {
11291
+ entries: readonly string[];
11292
+ origin: string;
11293
+ identity?: string;
11294
+ explicitHead: boolean;
11295
+ }
11296
+ | undefined {
11297
+ const chain = getSessionContextForInternalRead(this.sessionManager).configuredModelChains[role];
11298
+ if (!chain) return undefined;
11299
+ return {
11300
+ entries: [...chain.entries],
11301
+ origin: chain.origin,
11302
+ identity: chain.identity,
11303
+ explicitHead: chain.explicitHead,
11304
+ };
11305
+ }
11306
+
11232
11307
  /** Persist the configured fallback selectors for a model role. */
11233
11308
  setConfiguredModelChain(
11234
11309
  role: string,
@@ -11245,6 +11320,10 @@ export class AgentSession {
11245
11320
  explicitHead,
11246
11321
  cleared: entries.length === 0,
11247
11322
  });
11323
+ if (role === "default") {
11324
+ this.#defaultFallbackController = undefined;
11325
+ this.#defaultFallbackExhaustedLastTurn = false;
11326
+ }
11248
11327
  }
11249
11328
 
11250
11329
  /**
@@ -11270,6 +11349,25 @@ export class AgentSession {
11270
11349
  this.#emitResolutionFallbackSwitch(controller);
11271
11350
  }
11272
11351
 
11352
+ getDefaultFallbackRuntimeState(): DefaultFallbackRuntimeState {
11353
+ const controller = this.#defaultFallbackChain(false);
11354
+ return {
11355
+ chain: { ...controller.chain, entries: [...controller.chain.entries] },
11356
+ controller: controller.snapshotRuntimeState(),
11357
+ exhaustedLastTurn: this.#defaultFallbackExhaustedLastTurn,
11358
+ };
11359
+ }
11360
+
11361
+ restoreDefaultFallbackRuntimeState(state: DefaultFallbackRuntimeState): void {
11362
+ const controller = new FallbackChainController(
11363
+ { ...state.chain, entries: [...state.chain.entries] },
11364
+ this.settings.get("fallback.maxAttempts"),
11365
+ );
11366
+ controller.restoreRuntimeState(state.controller);
11367
+ this.#defaultFallbackController = controller;
11368
+ this.#defaultFallbackExhaustedLastTurn = state.exhaustedLastTurn;
11369
+ }
11370
+
11273
11371
  /**
11274
11372
  * The model selector ("provider/id") that resume restores as the session
11275
11373
  * default — the latest session-log `model_change` with role="default".
@@ -11292,13 +11390,16 @@ export class AgentSession {
11292
11390
  }
11293
11391
 
11294
11392
  /**
11295
- * Re-assert the session resume default ("provider/id") in the session log
11296
- * WITHOUT touching the live runtime model. Appends a `model_change` with
11297
- * role="default"; never writes to global settings (apply-for-this-session
11298
- * semantics). Used by model-profile activation rollback to neutralize the
11299
- * profile main model the failed activation already recorded as the default.
11393
+ * Record or clear the session resume default ("provider/id") without touching
11394
+ * the live runtime model. An undefined selector appends an explicit clear
11395
+ * marker so rollback preserves the absence of a prior default during replay.
11396
+ * Never writes global settings.
11300
11397
  */
11301
- recordResumeDefaultModel(selector: string): void {
11398
+ recordResumeDefaultModel(selector: string | undefined): void {
11399
+ if (selector === undefined) {
11400
+ this.sessionManager.clearModelRole("default");
11401
+ return;
11402
+ }
11302
11403
  this.sessionManager.appendModelChange(selector, "default");
11303
11404
  }
11304
11405
 
@@ -11309,10 +11410,9 @@ export class AgentSession {
11309
11410
  * The change is recorded in the session log as `role: "temporary"` by
11310
11411
  * default, which means it is NOT restored as the session default on resume —
11311
11412
  * transient retry/fallback/context-promotion/plan switches must not clobber
11312
- * the user's explicit pick (issue #849). Model-profile activation passes
11313
- * `persistAsSessionDefault: true` so the profile's main model becomes the
11314
- * session default and survives resume, while still not being written to
11315
- * global settings (new sessions keep the global default).
11413
+ * the user's explicit pick (issue #849). Callers that intentionally own the
11414
+ * session resume default may opt into `persistAsSessionDefault: true` without
11415
+ * changing global settings.
11316
11416
  * @throws Error if no API key available for the model
11317
11417
  */
11318
11418
  async setModelTemporary(
@@ -11332,7 +11432,7 @@ export class AgentSession {
11332
11432
  if (suppliedScope && this.#temporaryProviderSessionScopes.at(-1)?.token !== suppliedScope) return;
11333
11433
  const previousEditMode = this.#resolveActiveEditMode();
11334
11434
  const expectedSessionId = this.sessionId;
11335
- const apiKey = await this.#modelRegistry.getApiKey(model, expectedSessionId);
11435
+ const apiKey = await this.#modelRegistry.getApiKey(model, this.credentialSessionId);
11336
11436
  if (options?.signal?.aborted) return;
11337
11437
  if (this.sessionId !== expectedSessionId) {
11338
11438
  throw new Error("Session changed while selecting model");
@@ -11371,6 +11471,9 @@ export class AgentSession {
11371
11471
  options?.persistAsSessionDefault ? "default" : "temporary",
11372
11472
  );
11373
11473
  this.settings.getStorage()?.recordModelUsage(`${model.provider}/${model.id}`);
11474
+ if (options?.persistAsSessionDefault) {
11475
+ this.#seedSessionCanonicalVariant(model);
11476
+ }
11374
11477
 
11375
11478
  // Apply explicit thinking level if given; otherwise prefer the model's
11376
11479
  // configured defaultLevel; otherwise re-clamp the current level.
@@ -11384,6 +11487,22 @@ export class AgentSession {
11384
11487
  return scope;
11385
11488
  }
11386
11489
 
11490
+ /** Restore the exact live-model state captured before a failed selector transaction. */
11491
+ async restoreModelSelectionForRollback(
11492
+ model: Model | undefined,
11493
+ thinkingLevel: ThinkingLevel | undefined,
11494
+ ): Promise<void> {
11495
+ if (model) {
11496
+ await this.setModelTemporary(model, thinkingLevel, { cause: "rollback", reason: "other" });
11497
+ return;
11498
+ }
11499
+ const previousEditMode = this.#resolveActiveEditMode();
11500
+ this.#clearActiveRetryFallback();
11501
+ this.#setModelWithProviderSessionReset(undefined);
11502
+ this.setThinkingLevel(thinkingLevel);
11503
+ await this.#syncEditToolModeAfterModelChange(previousEditMode);
11504
+ }
11505
+
11387
11506
  async #restoreDefaultModelSelectionCommit(
11388
11507
  commit: CasReceipt,
11389
11508
  ): Promise<{ readonly stage: DefaultModelSelectionRollbackStage; readonly message: string } | undefined> {
@@ -11446,6 +11565,7 @@ export class AgentSession {
11446
11565
  #publishDefaultModelSelection(model: Model, thinkingLevel: ThinkingLevel, systemPrompt: string[] | undefined): void {
11447
11566
  this.#clearActiveRetryFallback();
11448
11567
  this.#setModelWithProviderSessionReset(model);
11568
+ this.#seedSessionCanonicalVariant(model);
11449
11569
  const thinkingLevelChanged = this.#thinkingLevel !== thinkingLevel;
11450
11570
  this.#thinkingLevelMutationRevision++;
11451
11571
  this.#thinkingLevelLiveMutationRevision++;
@@ -11514,7 +11634,7 @@ export class AgentSession {
11514
11634
  if (thinkingLevel === ThinkingLevel.Inherit) {
11515
11635
  throw new Error("Default model selection cannot inherit a thinking level");
11516
11636
  }
11517
- const apiKey = await this.#modelRegistry.getApiKey(model, this.sessionId);
11637
+ const apiKey = await this.#modelRegistry.getApiKey(model, this.credentialSessionId);
11518
11638
  if (!apiKey) {
11519
11639
  throw new Error(`No API key for ${model.provider}/${model.id}`);
11520
11640
  }
@@ -11612,10 +11732,13 @@ export class AgentSession {
11612
11732
 
11613
11733
  /** Number of configured role-model candidates that can be cycled. */
11614
11734
  getRoleModelCycleCandidateCount(roleOrder: readonly string[] = this.settings.get("cycleOrder")): number {
11615
- return this.#getRoleModelCycleCandidates(roleOrder).length;
11735
+ return this.#getRoleModelCycleCandidates(roleOrder, undefined).length;
11616
11736
  }
11617
11737
 
11618
- #getRoleModelCycleCandidates(roleOrder: readonly string[]): RoleModelCycleCandidate[] {
11738
+ #getRoleModelCycleCandidates(
11739
+ roleOrder: readonly string[],
11740
+ canonicalSessionId: string | undefined,
11741
+ ): RoleModelCycleCandidate[] {
11619
11742
  const availableModels = this.#modelRegistry.getAvailable();
11620
11743
  const currentModel = this.model;
11621
11744
  if (availableModels.length === 0 || !currentModel) return [];
@@ -11633,7 +11756,9 @@ export class AgentSession {
11633
11756
  settings: this.settings,
11634
11757
  matchPreferences,
11635
11758
  modelRegistry: this.#modelRegistry,
11636
- sessionId: this.sessionId,
11759
+ ...(canonicalSessionId ? { sessionId: canonicalSessionId } : {}),
11760
+ ...(this.#persistedModelProfileAliasIntent(role) ?? {}),
11761
+ credentialSessionId: this.credentialSessionId,
11637
11762
  });
11638
11763
  if (!resolved.model) continue;
11639
11764
 
@@ -11658,7 +11783,7 @@ export class AgentSession {
11658
11783
  roleOrder: readonly string[],
11659
11784
  options?: { temporary?: boolean },
11660
11785
  ): Promise<RoleModelCycleResult | undefined> {
11661
- const roleModels = this.#getRoleModelCycleCandidates(roleOrder);
11786
+ const roleModels = this.#getRoleModelCycleCandidates(roleOrder, this.sessionId);
11662
11787
  if (roleModels.length <= 1) return undefined;
11663
11788
 
11664
11789
  const currentModel = this.model!;
@@ -11701,7 +11826,7 @@ export class AgentSession {
11701
11826
  if (apiKeysByProvider.has(provider)) {
11702
11827
  apiKey = apiKeysByProvider.get(provider);
11703
11828
  } else {
11704
- apiKey = await this.#modelRegistry.getApiKeyForProvider(provider, this.sessionId);
11829
+ apiKey = await this.#modelRegistry.getApiKeyForProvider(provider, this.credentialSessionId);
11705
11830
  apiKeysByProvider.set(provider, apiKey);
11706
11831
  }
11707
11832
 
@@ -11746,7 +11871,7 @@ export class AgentSession {
11746
11871
  const nextIndex = direction === "forward" ? (currentIndex + 1) % len : (currentIndex - 1 + len) % len;
11747
11872
  const nextModel = availableModels[nextIndex];
11748
11873
 
11749
- const apiKey = await this.#modelRegistry.getApiKey(nextModel, this.sessionId);
11874
+ const apiKey = await this.#modelRegistry.getApiKey(nextModel, this.credentialSessionId);
11750
11875
  if (!apiKey) {
11751
11876
  throw new Error(`No API key for ${nextModel.provider}/${nextModel.id}`);
11752
11877
  }
@@ -12936,7 +13061,7 @@ export class AgentSession {
12936
13061
  if (!model) {
12937
13062
  throw new Error("No model selected for handoff");
12938
13063
  }
12939
- const apiKey = await this.#modelRegistry.getApiKey(model, this.sessionId);
13064
+ const apiKey = await this.#modelRegistry.getApiKey(model, this.credentialSessionId);
12940
13065
  if (!apiKey) {
12941
13066
  throw new Error(`No API key for ${model.provider}`);
12942
13067
  }
@@ -12987,7 +13112,7 @@ export class AgentSession {
12987
13112
  // and the generated handoff document is preserved for copy/retry.
12988
13113
  const previousSessionFile = this.sessionFile;
12989
13114
  await this.sessionManager.flush();
12990
- const rollbackSessionState = this.sessionManager.captureState();
13115
+ const rollbackSessionState = await this.sessionManager.captureRollbackState();
12991
13116
  const rollbackAgentMessages = [...this.agent.state.messages];
12992
13117
  const rollbackSteeringMessages = [...this.#steeringMessages];
12993
13118
  const rollbackFollowUpMessages = [...this.#followUpMessages];
@@ -13099,7 +13224,7 @@ export class AgentSession {
13099
13224
  // failure is non-destructive. Predecessor gate emitter, provider
13100
13225
  // sessions, async jobs, IRC/plan bookkeeping, and injection signatures
13101
13226
  // were never mutated before commit, so they survive intact.
13102
- this.sessionManager.restoreState(rollbackSessionState);
13227
+ await this.sessionManager.restoreRollbackState(rollbackSessionState);
13103
13228
  this.#syncAgentSessionId(rollbackSessionState.sessionId);
13104
13229
  this.#rekeyHindsightMemoryForCurrentSessionId();
13105
13230
  this.agent.replaceMessages(rollbackAgentMessages, {
@@ -13531,7 +13656,10 @@ export class AgentSession {
13531
13656
  imageBytes,
13532
13657
  sessionResidentImageBytes: this.sessionManager.getResidentImageBytes(),
13533
13658
  materializedResidentBytes:
13534
- this.#streamingEditFileCache.totalBytes + sessionMemory.hotRegionBytes + sessionMemory.metaDescriptorBytes,
13659
+ this.#streamingEditFileCache.totalBytes +
13660
+ sessionMemory.allocatedCacheBytes +
13661
+ sessionMemory.hotResidentBytes +
13662
+ sessionMemory.metadataResidentBytes,
13535
13663
  tuiChatChildren: retainedMemory.tuiChatChildren ?? 0,
13536
13664
  tuiCachedRenderBytes: retainedMemory.tuiCachedRenderBytes ?? 0,
13537
13665
  };
@@ -14035,7 +14163,7 @@ export class AgentSession {
14035
14163
  if (!candidate) return undefined;
14036
14164
  if (modelsAreEqual(candidate, currentModel)) return undefined;
14037
14165
  if (candidate.contextWindow <= contextWindow) return undefined;
14038
- const apiKey = await this.#modelRegistry.getApiKey(candidate, this.sessionId);
14166
+ const apiKey = await this.#modelRegistry.getApiKey(candidate, this.credentialSessionId);
14039
14167
  if (!apiKey || signal?.aborted) return undefined;
14040
14168
  return candidate;
14041
14169
  }
@@ -14075,6 +14203,22 @@ export class AgentSession {
14075
14203
  this.#syncAppendOnlyContext(model);
14076
14204
  }
14077
14205
 
14206
+ /**
14207
+ * Seed (or clear) the session's sticky canonical variant to reflect an
14208
+ * explicit user/control model selection. When the selected concrete model has
14209
+ * a canonical identity it becomes the session's preferred variant; otherwise
14210
+ * any stale sticky variant is cleared so a previous provider selection cannot
14211
+ * silently resurrect. Never invoked from settings-edit paths — only explicit
14212
+ * model selections remap the session's canonical stickiness.
14213
+ */
14214
+ #seedSessionCanonicalVariant(model: Model): void {
14215
+ if (this.#modelRegistry.getCanonicalId?.(model)) {
14216
+ this.#modelRegistry.seedCanonicalVariant?.(this.sessionId, model);
14217
+ } else {
14218
+ this.#modelRegistry.clearCanonicalVariant?.(this.sessionId);
14219
+ }
14220
+ }
14221
+
14078
14222
  #closeCodexProviderSessionsForHistoryRewrite(): void {
14079
14223
  const currentModel = this.model;
14080
14224
  if (currentModel?.api !== "openai-codex-responses") return;
@@ -14409,7 +14553,8 @@ export class AgentSession {
14409
14553
  settings: this.settings,
14410
14554
  matchPreferences: { usageOrder: this.settings.getStorage()?.getModelUsageOrder() },
14411
14555
  modelRegistry: this.#modelRegistry,
14412
- sessionId: this.sessionId,
14556
+ credentialSessionId: this.credentialSessionId,
14557
+ ...(this.#persistedModelProfileAliasIntent(role) ?? {}),
14413
14558
  });
14414
14559
  }
14415
14560
 
@@ -14505,7 +14650,7 @@ export class AgentSession {
14505
14650
  const telemetry = resolveTelemetry(this.agent.telemetry, this.sessionId);
14506
14651
 
14507
14652
  for (const candidate of candidates) {
14508
- const apiKey = await this.#modelRegistry.getApiKey(candidate, this.sessionId);
14653
+ const apiKey = await this.#modelRegistry.getApiKey(candidate, this.credentialSessionId);
14509
14654
  if (!apiKey) continue;
14510
14655
 
14511
14656
  try {
@@ -14515,7 +14660,10 @@ export class AgentSession {
14515
14660
  metadata: this.agent.metadataForProvider(candidate.provider),
14516
14661
  convertToLlm,
14517
14662
  telemetry,
14518
- authCredentialType: this.#modelRegistry.getSessionCredentialType(candidate.provider, this.sessionId),
14663
+ authCredentialType: this.#modelRegistry.getSessionCredentialType(
14664
+ candidate.provider,
14665
+ this.credentialSessionId,
14666
+ ),
14519
14667
  });
14520
14668
  } catch (error) {
14521
14669
  if (!this.#isCompactionAuthFailure(error)) {
@@ -14785,6 +14933,7 @@ export class AgentSession {
14785
14933
  generation,
14786
14934
  shouldContinue: () => this.agent.hasQueuedMessages(),
14787
14935
  rescheduleOnBusy: true,
14936
+ continueQueuedOnly: true,
14788
14937
  onSkip: skipReason => this.#logCompactionContinuationSkipped("queued_continue", skipReason),
14789
14938
  onError: error => this.#logCompactionContinuationError("queued_continue", error),
14790
14939
  resourceRunId: options?.resourceRunId,
@@ -14806,6 +14955,7 @@ export class AgentSession {
14806
14955
 
14807
14956
  shouldContinue: () => this.agent.hasQueuedMessages(),
14808
14957
  rescheduleOnBusy: true,
14958
+ continueQueuedOnly: true,
14809
14959
  onSkip: skipReason => this.#logCompactionContinuationSkipped("queued_continue", skipReason),
14810
14960
  onError: error => this.#logCompactionContinuationError("queued_continue", error),
14811
14961
  resourceRunId: options?.resourceRunId,
@@ -14895,7 +15045,7 @@ export class AgentSession {
14895
15045
  let lastError: unknown;
14896
15046
 
14897
15047
  for (const candidate of candidates) {
14898
- const apiKey = await this.#modelRegistry.getApiKey(candidate, this.sessionId);
15048
+ const apiKey = await this.#modelRegistry.getApiKey(candidate, this.credentialSessionId);
14899
15049
  if (!apiKey) continue;
14900
15050
 
14901
15051
  let attempt = 0;
@@ -14914,7 +15064,7 @@ export class AgentSession {
14914
15064
  telemetry,
14915
15065
  authCredentialType: this.#modelRegistry.getSessionCredentialType(
14916
15066
  candidate.provider,
14917
- this.sessionId,
15067
+ this.credentialSessionId,
14918
15068
  ),
14919
15069
  });
14920
15070
  break;
@@ -15065,6 +15215,7 @@ export class AgentSession {
15065
15215
  suppressPredecessorAgentEnd: true,
15066
15216
  shouldContinue: () => this.agent.hasQueuedMessages(),
15067
15217
  rescheduleOnBusy: true,
15218
+ continueQueuedOnly: true,
15068
15219
  onSkip: reason => this.#logCompactionContinuationSkipped("queued_continue", reason),
15069
15220
  onError: error => this.#logCompactionContinuationError("queued_continue", error),
15070
15221
  resourceRunId: options?.resourceRunId,
@@ -15185,6 +15336,10 @@ export class AgentSession {
15185
15336
  return message.transportFailure?.providerCode?.toLowerCase() === STREAM_FIRST_EVENT_TIMEOUT_PROVIDER_CODE;
15186
15337
  }
15187
15338
 
15339
+ #isTypedEmptyResponse(message: AssistantMessage): boolean {
15340
+ return message.transportFailure?.providerCode?.toLowerCase() === EMPTY_RESPONSE_PROVIDER_CODE;
15341
+ }
15342
+
15188
15343
  #isTerminalProviderFirstEventTimeout(message: AssistantMessage): boolean {
15189
15344
  return this.#isKimiCodeFirstEventTimeout(message) || this.#isAlibabaTokenPlanFirstEventTimeout(message);
15190
15345
  }
@@ -15209,7 +15364,8 @@ export class AgentSession {
15209
15364
  classification === "usage_limit" ||
15210
15365
  classification === "transient" ||
15211
15366
  classification === "unknown" ||
15212
- classification === "first_event_timeout"
15367
+ classification === "first_event_timeout" ||
15368
+ classification === "empty_response"
15213
15369
  );
15214
15370
  }
15215
15371
  const trigger = classifyFallbackTrigger(transportFailure ?? { status: message.errorStatus });
@@ -15320,6 +15476,7 @@ export class AgentSession {
15320
15476
  if (message.stopReason !== "error") return "none";
15321
15477
  if (message.errorKind === "provider_safety_stop") return "terminal";
15322
15478
  if (this.#isTypedFirstEventTimeout(message)) return "first_event_timeout";
15479
+ if (this.#isTypedEmptyResponse(message)) return "empty_response";
15323
15480
  if (!message.errorMessage) return "none";
15324
15481
  const err = message.errorMessage;
15325
15482
  // Provider safety refusals (e.g. Anthropic stop_reason "refusal" /
@@ -15470,8 +15627,12 @@ export class AgentSession {
15470
15627
  controller.chain.entries.slice(resolutionStart),
15471
15628
  this.#modelRegistry,
15472
15629
  this.settings,
15473
- this.sessionId,
15474
- { managedFallback: true },
15630
+ this.credentialSessionId,
15631
+ {
15632
+ managedFallback: true,
15633
+ canonicalSessionId: this.sessionId,
15634
+ ...(this.#persistedModelProfileAliasIntent("default") ?? {}),
15635
+ },
15475
15636
  );
15476
15637
  const activeIndex = resolutionStart + resolution.activeIndex;
15477
15638
  if (activeIndex > resolutionStart) {
@@ -15489,7 +15650,7 @@ export class AgentSession {
15489
15650
  * metadata. Consumers seed only resolution state; role/origin/identity stay
15490
15651
  * intrinsic to controller construction and are never inferred at runtime.
15491
15652
  */
15492
- #defaultFallbackChain(): FallbackChainController {
15653
+ #defaultFallbackChain(materializeLegacyChain = true): FallbackChainController {
15493
15654
  const configuredChain = getSessionContextForInternalRead(this.sessionManager).configuredModelChains.default;
15494
15655
 
15495
15656
  const settingsEntries = normalizeModelSelectorValue(
@@ -15499,7 +15660,7 @@ export class AgentSession {
15499
15660
  configuredChain?.origin === "legacy_session" &&
15500
15661
  configuredChain.entries.length === 1 &&
15501
15662
  settingsEntries.length > 1;
15502
- if (materializeSettingsChain) {
15663
+ if (materializeSettingsChain && materializeLegacyChain) {
15503
15664
  this.setConfiguredModelChain("default", settingsEntries, "modelRoles");
15504
15665
  }
15505
15666
  const chain: ConfiguredFallbackChain = materializeSettingsChain
@@ -15640,12 +15801,27 @@ export class AgentSession {
15640
15801
  while (!controller.isExhausted()) {
15641
15802
  const selector = controller.currentSelector();
15642
15803
  if (!selector) return false;
15643
- const resolved = resolveModelRoleValue(selector, this.#modelRegistry.getAvailable(), {
15644
- settings: this.settings,
15645
- matchPreferences: { usageOrder: this.settings.getStorage()?.getModelUsageOrder() },
15646
- modelRegistry: this.#modelRegistry,
15647
- sessionId: this.sessionId,
15648
- });
15804
+ const profileAliasIntent = this.#persistedModelProfileAliasIntent("default");
15805
+ const resolved = profileAliasIntent
15806
+ ? await resolveModelChainWithAuth(
15807
+ [selector],
15808
+ this.#modelRegistry,
15809
+ this.settings,
15810
+ this.credentialSessionId,
15811
+ {
15812
+ managedFallback: true,
15813
+ ...profileAliasIntent,
15814
+ canonicalSessionId: this.agent.providerSessionId ?? this.sessionId,
15815
+ credentialSessionId: this.credentialSessionId,
15816
+ },
15817
+ )
15818
+ : resolveModelRoleValue(selector, this.#modelRegistry.getAvailable(), {
15819
+ settings: this.settings,
15820
+ matchPreferences: { usageOrder: this.settings.getStorage()?.getModelUsageOrder() },
15821
+ modelRegistry: this.#modelRegistry,
15822
+ sessionId: this.agent.providerSessionId ?? this.sessionId,
15823
+ credentialSessionId: this.credentialSessionId,
15824
+ });
15649
15825
  if (!resolved.model) {
15650
15826
  controller.onResolutionSkip("unknown_model");
15651
15827
  continue;
@@ -15655,7 +15831,7 @@ export class AgentSession {
15655
15831
  controller.onResolutionSkip(managedCursorUnavailable);
15656
15832
  continue;
15657
15833
  }
15658
- const key = await this.#modelRegistry.getApiKey(resolved.model, this.sessionId);
15834
+ const key = await this.#modelRegistry.getApiKey(resolved.model, this.credentialSessionId);
15659
15835
  if (!isAuthenticated(key) && key !== kNoAuth) {
15660
15836
  controller.onResolutionSkip("unauthenticated");
15661
15837
  continue;
@@ -15756,24 +15932,24 @@ export class AgentSession {
15756
15932
  // (1) Pin guard, before any mutation and for every branch.
15757
15933
  if (authStorage.hasRuntimeApiKey(provider) || authStorage.hasRuntimeCredentialSelector(provider)) return false;
15758
15934
 
15759
- const providerAffinitySessionId = this.agent.providerSessionId ?? this.agent.sessionId ?? this.sessionId;
15760
- const activeApiKey = await this.#modelRegistry.getApiKey(this.model, providerAffinitySessionId);
15935
+ const credentialSessionId = this.credentialSessionId;
15936
+ const activeApiKey = await this.#modelRegistry.getApiKey(this.model, credentialSessionId);
15761
15937
 
15762
15938
  let mutated: boolean;
15763
15939
  if (trigger.class === "auth") {
15764
15940
  if (!isAuthenticated(activeApiKey)) return false;
15765
15941
  mutated = await authStorage.invalidateCredentialMatching(provider, activeApiKey, {
15766
- sessionId: providerAffinitySessionId,
15942
+ sessionId: credentialSessionId,
15767
15943
  });
15768
15944
  } else {
15769
- mutated = await authStorage.markUsageLimitReached(provider, providerAffinitySessionId, {
15945
+ mutated = await authStorage.markUsageLimitReached(provider, credentialSessionId, {
15770
15946
  retryAfterMs: trigger.retryAfterMs,
15771
15947
  });
15772
15948
  }
15773
15949
  if (!mutated) return false;
15774
15950
 
15775
15951
  // (3) Distinct-row proof.
15776
- return (await this.#modelRegistry.getApiKey(this.model, providerAffinitySessionId)) !== activeApiKey;
15952
+ return (await this.#modelRegistry.getApiKey(this.model, credentialSessionId)) !== activeApiKey;
15777
15953
  }
15778
15954
 
15779
15955
  /** Handle retryable errors with exponential backoff. */
@@ -15797,6 +15973,7 @@ export class AgentSession {
15797
15973
  if (!managedFallback && !retrySettings.enabled) return false;
15798
15974
  const classification = this.#classifyErrorForRetry(message);
15799
15975
  const firstEventTimeout = classification === "first_event_timeout";
15976
+ const emptyResponse = classification === "empty_response";
15800
15977
  // Content-free message-only watchdog prose (wrapped canonical or bare
15801
15978
  // per-provider variants) is admitted like the typed path: it is
15802
15979
  // replay-safe, so a bare-default retry may re-issue the request even
@@ -15805,6 +15982,14 @@ export class AgentSession {
15805
15982
  if (!managedFallback && firstEventTimeout && this.#retryAttempt === 0) {
15806
15983
  this.#firstEventTimeoutRetryStartedAt = Date.now();
15807
15984
  }
15985
+ if (emptyResponse && (assistantMessageHasVisibleOrToolContent(message) || !scope || !scopeWasClean)) {
15986
+ return managedOutcome
15987
+ ? {
15988
+ type: "terminal",
15989
+ terminal: { stopReason: "error", messages: [message] },
15990
+ }
15991
+ : false;
15992
+ }
15808
15993
  const requiresScopedFirstEventTimeout = managedFallback || !legacyRetryConfigured;
15809
15994
  if (
15810
15995
  (firstEventTimeout || messageOnlyWatchdogTimeout) &&
@@ -15854,7 +16039,8 @@ export class AgentSession {
15854
16039
  // Bare defaults retain their narrow watchdog and Codex admissions. A
15855
16040
  // first-event timeout adds the typed, content-free, current-clean-scope
15856
16041
  // requirement above; other transient watchdogs preserve legacy behavior.
15857
- if (!managedFallback && !legacyRetryConfigured && !canReplayRotatedCredential) {
16042
+ const canReplayEmptyResponse = emptyResponse && (this.#retryAttempt === 0 || this.#hasCleanRetryReplaySafety);
16043
+ if (!managedFallback && !legacyRetryConfigured && !canReplayRotatedCredential && !canReplayEmptyResponse) {
15858
16044
  const bareDefaultCodexOverload = isBareDefaultCodexOverload(message);
15859
16045
  const canReplayCodexOverload = bareDefaultCodexOverload;
15860
16046
  if (
@@ -16372,6 +16558,7 @@ export class AgentSession {
16372
16558
  return result;
16373
16559
  } finally {
16374
16560
  this.#bashAbortControllers.delete(abortController);
16561
+ this.#scheduleQueuedFollowUpContinuation();
16375
16562
  }
16376
16563
  }
16377
16564
 
@@ -16514,10 +16701,12 @@ export class AgentSession {
16514
16701
  () => {
16515
16702
  this.#evalAbortControllers.delete(abortController);
16516
16703
  this.#activeEvalExecutions.delete(execution);
16704
+ this.#scheduleQueuedFollowUpContinuation();
16517
16705
  },
16518
16706
  () => {
16519
16707
  this.#evalAbortControllers.delete(abortController);
16520
16708
  this.#activeEvalExecutions.delete(execution);
16709
+ this.#scheduleQueuedFollowUpContinuation();
16521
16710
  },
16522
16711
  );
16523
16712
  return execution;
@@ -16554,6 +16743,13 @@ export class AgentSession {
16554
16743
  } else {
16555
16744
  this.agent.appendMessage(pythonMessage);
16556
16745
  this.sessionManager.appendMessage(pythonMessage);
16746
+ if (!this.#cancelAndSubmitInProgress && this.agent.hasQueuedMessages()) {
16747
+ this.#scheduleAgentContinue({
16748
+ shouldContinue: () => this.#canAutoContinueForFollowUp() && this.agent.hasQueuedMessages(),
16749
+ rescheduleOnBusy: true,
16750
+ continueQueuedOnly: true,
16751
+ });
16752
+ }
16557
16753
  options?.onPersisted?.();
16558
16754
  }
16559
16755
  }
@@ -16985,7 +17181,7 @@ export class AgentSession {
16985
17181
  thinkingLevel: this.thinkingLevel ?? ThinkingLevel.Off,
16986
17182
  hideThinkingSummary: this.agent.hideThinkingSummary ?? false,
16987
17183
  serviceTier: this.serviceTier,
16988
- credentialSessionId: providerAffinitySessionId,
17184
+ credentialSessionId: this.credentialSessionId,
16989
17185
  providerAffinitySessionId,
16990
17186
  sideSessionId: `${providerAffinitySessionId}:btw:${crypto.randomUUID()}`,
16991
17187
  };
@@ -17048,7 +17244,10 @@ export class AgentSession {
17048
17244
  try {
17049
17245
  const model = this.model;
17050
17246
  if (!model) throw new Error("No active model on session");
17051
- const apiKey = await awaitEphemeralAbort(this.#modelRegistry.getApiKey(model, this.sessionId), args.signal);
17247
+ const apiKey = await awaitEphemeralAbort(
17248
+ this.#modelRegistry.getApiKey(model, this.credentialSessionId),
17249
+ args.signal,
17250
+ );
17052
17251
  if (!apiKey) throw new Error(`No API key for ${model.provider}/${model.id}`);
17053
17252
  sideAttempt = this.agent.mintSideAttemptScope();
17054
17253
  const sideScope = sideAttempt.scope;
@@ -17079,7 +17278,7 @@ export class AgentSession {
17079
17278
  ephemeralSessionId,
17080
17279
  model.provider,
17081
17280
  this.#modelRegistry.authStorage,
17082
- this.sessionId,
17281
+ this.credentialSessionId,
17083
17282
  ),
17084
17283
  reasoning: toReasoningEffort(this.thinkingLevel),
17085
17284
  hideThinkingSummary: this.agent.hideThinkingSummary,
@@ -17405,7 +17604,7 @@ export class AgentSession {
17405
17604
  this.#disconnectFromAgent();
17406
17605
  // Flush pending writes before switching so restore snapshots reflect committed state.
17407
17606
  await this.sessionManager.flush();
17408
- const previousSessionState = this.sessionManager.captureState();
17607
+ const previousSessionState = await this.sessionManager.captureRollbackState();
17409
17608
  const previousSessionContext = this.buildDisplaySessionContext();
17410
17609
  // switchSession replaces these arrays wholesale during load/rollback, so retaining
17411
17610
  // the existing message objects is sufficient and avoids structured-clone failures for
@@ -17417,6 +17616,7 @@ export class AgentSession {
17417
17616
  const previousScheduledHiddenNextTurnGeneration = this.#scheduledHiddenNextTurnGeneration;
17418
17617
  const previousModel = this.model;
17419
17618
  const previousThinkingLevel = this.#thinkingLevel;
17619
+ const previousActiveModelProfile = this.#activeModelProfile;
17420
17620
  const previousServiceTier = this.agent.serviceTier;
17421
17621
  const previousSelectedMCPToolNames = new Set(this.#selectedMCPToolNames);
17422
17622
  const previousTools = [...this.agent.state.tools];
@@ -17479,20 +17679,48 @@ export class AgentSession {
17479
17679
  }
17480
17680
 
17481
17681
  const resumeModelBehavior = this.settings.get("session.resumeModelBehavior");
17482
- const configuredDefaultChain = sessionContext.configuredModelChains.default?.entries;
17682
+ const configuredDefaultChain = sessionContext.configuredModelChains.default;
17483
17683
  const settingsDefaultEntries = normalizeModelSelectorValue(this.settings.getModelRole("default"));
17484
17684
  const defaultEntries =
17485
17685
  resumeModelBehavior === "useCurrentDefault"
17486
17686
  ? settingsDefaultEntries
17487
- : (configuredDefaultChain ?? (sessionContext.models.default ? [sessionContext.models.default] : []));
17687
+ : (configuredDefaultChain?.entries ??
17688
+ (sessionContext.models.default ? [sessionContext.models.default] : []));
17689
+ const profileDefinitions = this.#modelRegistry.getModelProfiles?.() ?? new Map<string, unknown>();
17690
+ const configuredProfileName = this.settings.get("modelProfile.default");
17691
+ const configuredProfileIdentity = configuredProfileName
17692
+ ? resolveModelProfileName(configuredProfileName, profileDefinitions)
17693
+ : undefined;
17694
+ const persistedProfileIdentity = configuredDefaultChain?.identity
17695
+ ? resolveModelProfileName(configuredDefaultChain.identity, profileDefinitions)
17696
+ : undefined;
17697
+ const liveProfileIdentity = previousActiveModelProfile
17698
+ ? resolveModelProfileName(previousActiveModelProfile, profileDefinitions)
17699
+ : undefined;
17700
+ this.#activeModelProfile =
17701
+ resumeModelBehavior === "useCurrentDefault"
17702
+ ? liveProfileIdentity && profileDefinitions.has(liveProfileIdentity)
17703
+ ? liveProfileIdentity
17704
+ : configuredProfileIdentity && profileDefinitions.has(configuredProfileIdentity)
17705
+ ? configuredProfileIdentity
17706
+ : undefined
17707
+ : configuredDefaultChain?.origin === "profile-activation" &&
17708
+ persistedProfileIdentity &&
17709
+ profileDefinitions.has(persistedProfileIdentity)
17710
+ ? persistedProfileIdentity
17711
+ : undefined;
17488
17712
  this.#defaultFallbackController = undefined;
17489
17713
  if (defaultEntries.length > 0) {
17490
17714
  const resolution = await resolveModelChainWithAuth(
17491
17715
  defaultEntries,
17492
17716
  this.#modelRegistry,
17493
17717
  this.settings,
17494
- this.sessionId,
17495
- { managedFallback: true },
17718
+ this.credentialSessionId,
17719
+ {
17720
+ managedFallback: true,
17721
+ canonicalSessionId: this.sessionId,
17722
+ ...(this.#persistedModelProfileAliasIntent("default") ?? {}),
17723
+ },
17496
17724
  );
17497
17725
  const controller = this.#defaultFallbackChain();
17498
17726
  this.seedDefaultFallbackResolution(resolution.activeIndex, resolution.skips);
@@ -17607,9 +17835,10 @@ export class AgentSession {
17607
17835
  return true;
17608
17836
  } catch (error) {
17609
17837
  if (transitionCleanupCommitted) throw error;
17610
- this.sessionManager.restoreState(previousSessionState);
17838
+ await this.sessionManager.restoreRollbackState(previousSessionState);
17611
17839
  this.#defaultFallbackController = undefined;
17612
17840
  this.#syncAgentSessionId(previousSessionState.sessionId);
17841
+ this.#activeModelProfile = previousActiveModelProfile;
17613
17842
  this.#restoreWorkflowGateEmitter(suspendedWorkflowGateEmitter);
17614
17843
  this.#rekeyHindsightMemoryForCurrentSessionId();
17615
17844
  let restoreMcpError: unknown;
@@ -17864,7 +18093,7 @@ export class AgentSession {
17864
18093
  let summaryDetails: unknown;
17865
18094
  if (options.summarize && entriesToSummarize.length > 0 && !hookSummary) {
17866
18095
  const model = this.model!;
17867
- const apiKey = await this.#modelRegistry.getApiKey(model, this.sessionId);
18096
+ const apiKey = await this.#modelRegistry.getApiKey(model, this.credentialSessionId);
17868
18097
  if (!apiKey) {
17869
18098
  throw new Error(`No API key for ${model.provider}`);
17870
18099
  }