@gajae-code/coding-agent 0.18.7 → 0.18.8

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 (279) hide show
  1. package/CHANGELOG.md +279 -1
  2. package/dist/types/async/job-manager.d.ts +1 -1
  3. package/dist/types/capability/mcp.d.ts +2 -0
  4. package/dist/types/capability/skill.d.ts +2 -0
  5. package/dist/types/capability/types.d.ts +2 -0
  6. package/dist/types/cli/public-command-errors.d.ts +3 -1
  7. package/dist/types/cli/setup-cli.d.ts +1 -1
  8. package/dist/types/commands/deep-interview.d.ts +3 -0
  9. package/dist/types/config/autorouting-tier-map.d.ts +348 -9
  10. package/dist/types/config/file-lock.d.ts +1 -0
  11. package/dist/types/config/model-bindings-applier.d.ts +2 -0
  12. package/dist/types/config/model-profile-activation.d.ts +11 -3
  13. package/dist/types/config/model-registry.d.ts +4 -9
  14. package/dist/types/config/skill-settings-defaults.d.ts +2 -0
  15. package/dist/types/cursor-delete-path.d.ts +1 -0
  16. package/dist/types/cursor-mcp-name.d.ts +8 -0
  17. package/dist/types/customization/inventory.d.ts +2 -0
  18. package/dist/types/debug/report-bundle-parent.d.ts +2 -0
  19. package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/provider/stream.d.ts +1 -0
  20. package/dist/types/defaults/gjc/extensions/grok-cli-vendor/src/provider/version-manager.d.ts +31 -0
  21. package/dist/types/defaults/gjc-skills.generated.d.ts +5 -6
  22. package/dist/types/discovery/helpers.d.ts +6 -1
  23. package/dist/types/export/html/css-color.d.ts +2 -0
  24. package/dist/types/export/html/template.generated.d.ts +1 -1
  25. package/dist/types/extensibility/extensions/types.d.ts +7 -0
  26. package/dist/types/extensibility/gjc-plugins/runtime-adapters.d.ts +14 -1
  27. package/dist/types/extensibility/plugins/marketplace/cache.d.ts +2 -0
  28. package/dist/types/extensibility/plugins/plugin-link-path.d.ts +2 -0
  29. package/dist/types/extensibility/runtime-skill-discovery.d.ts +3 -1
  30. package/dist/types/extensibility/skill-management.d.ts +3 -1
  31. package/dist/types/extensibility/skills.d.ts +2 -0
  32. package/dist/types/gjc-runtime/deep-interview-crystallize.d.ts +74 -0
  33. package/dist/types/gjc-runtime/deep-interview-runtime.d.ts +11 -0
  34. package/dist/types/gjc-runtime/gc-runtime.d.ts +5 -0
  35. package/dist/types/gjc-runtime/ralplan-runtime.d.ts +0 -5
  36. package/dist/types/gjc-runtime/state-argv.d.ts +1 -1
  37. package/dist/types/gjc-runtime/state-migrations.d.ts +8 -0
  38. package/dist/types/gjc-runtime/state-runtime.d.ts +95 -0
  39. package/dist/types/gjc-runtime/state-writer.d.ts +11 -1
  40. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +8 -0
  41. package/dist/types/internal-urls/skill-path.d.ts +1 -0
  42. package/dist/types/lsp/workspace-path.d.ts +1 -0
  43. package/dist/types/modes/acp/acp-agent.d.ts +7 -0
  44. package/dist/types/modes/components/custom-editor.d.ts +6 -0
  45. package/dist/types/modes/components/customization/customization-dashboard.d.ts +1 -0
  46. package/dist/types/modes/components/extensions/state-manager.d.ts +4 -3
  47. package/dist/types/modes/controllers/input-controller.d.ts +9 -0
  48. package/dist/types/modes/interactive-mode.d.ts +1 -0
  49. package/dist/types/modes/utils/interrupt-ownership.d.ts +3 -0
  50. package/dist/types/modes/utils/keybinding-matchers.d.ts +1 -0
  51. package/dist/types/modes/utils/macos-option-forwarding.d.ts +17 -0
  52. package/dist/types/progress/collect-project-progress.d.ts +38 -0
  53. package/dist/types/progress/progress-contract.d.ts +154 -0
  54. package/dist/types/progress/progress-snapshot.d.ts +11 -0
  55. package/dist/types/progress/project-progress.d.ts +141 -0
  56. package/dist/types/progress/render-progress.d.ts +28 -0
  57. package/dist/types/runtime-mcp/oauth-public-url.d.ts +1 -0
  58. package/dist/types/runtime-mcp/plugin-network-boundary.d.ts +1 -0
  59. package/dist/types/runtime-mcp/smithery-registry.d.ts +24 -0
  60. package/dist/types/runtime-mcp/types.d.ts +6 -0
  61. package/dist/types/sdk/acp/adapter.d.ts +1 -1
  62. package/dist/types/sdk/broker/broker-exit.d.ts +6 -2
  63. package/dist/types/sdk/broker/broker.d.ts +18 -1
  64. package/dist/types/sdk/broker/session-index.d.ts +12 -6
  65. package/dist/types/sdk/bus/index.d.ts +12 -1
  66. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +6 -1
  67. package/dist/types/sdk/client/client.d.ts +2 -0
  68. package/dist/types/sdk/host/query/handlers.d.ts +3 -0
  69. package/dist/types/sdk/index.d.ts +1 -0
  70. package/dist/types/sdk/router/session-router.d.ts +2 -0
  71. package/dist/types/sdk/session-list.d.ts +1 -1
  72. package/dist/types/sdk/session.d.ts +8 -0
  73. package/dist/types/session/agent-session.d.ts +38 -9
  74. package/dist/types/session/artifacts.d.ts +2 -2
  75. package/dist/types/session/internal/managed-gc-retirement-codec.d.ts +32 -0
  76. package/dist/types/session/internal/managed-session-scope.d.ts +79 -2
  77. package/dist/types/session/internal/managed-session-storage.d.ts +26 -1
  78. package/dist/types/session/internal/managed-task-owner-cleanup.d.ts +62 -0
  79. package/dist/types/session/internal/task-artifact-owner-access.d.ts +2 -0
  80. package/dist/types/session/internal/task-artifact-owner-transcript.d.ts +9 -1
  81. package/dist/types/session/session-manager.d.ts +31 -5
  82. package/dist/types/session/session-retirement.d.ts +31 -7
  83. package/dist/types/session/session-storage.d.ts +62 -2
  84. package/dist/types/session/task-artifact-owner-codec.d.ts +2 -0
  85. package/dist/types/setup/credential-auto-import.d.ts +1 -1
  86. package/dist/types/setup/credential-import.d.ts +6 -4
  87. package/dist/types/skill-state/active-state.d.ts +38 -0
  88. package/dist/types/slash-commands/helpers/progress-report.d.ts +8 -0
  89. package/dist/types/task/executor.d.ts +3 -0
  90. package/dist/types/tools/bash-skill-urls.d.ts +1 -1
  91. package/dist/types/tools/index.d.ts +3 -0
  92. package/dist/types/tools/python.d.ts +9 -1
  93. package/dist/types/tools/recipe/recipe-guard.d.ts +7 -0
  94. package/dist/types/tools/restricted-cwd.d.ts +1 -0
  95. package/dist/types/tools/todo-write.d.ts +6 -0
  96. package/dist/types/utils/tools-checksum.d.ts +7 -0
  97. package/package.json +7 -7
  98. package/scripts/generate-sdk-operation-inventory.ts +21 -0
  99. package/scripts/verify-managed-empty-stop.ts +28 -0
  100. package/src/async/job-manager.ts +228 -122
  101. package/src/capability/index.ts +17 -0
  102. package/src/capability/mcp.ts +2 -0
  103. package/src/capability/skill.ts +2 -0
  104. package/src/capability/types.ts +2 -0
  105. package/src/cli/auth-broker-cli.ts +1 -1
  106. package/src/cli/customize-doctor.ts +15 -2
  107. package/src/cli/public-command-errors.ts +21 -1
  108. package/src/cli/setup-cli.ts +4 -4
  109. package/src/cli/skills-cli.ts +5 -0
  110. package/src/commands/deep-interview.ts +47 -8
  111. package/src/commands/sdk.ts +28 -8
  112. package/src/config/autorouting-tier-map.ts +117 -7
  113. package/src/config/file-lock.ts +307 -68
  114. package/src/config/mcp-schema.json +4 -0
  115. package/src/config/model-bindings-applier.ts +20 -0
  116. package/src/config/model-profile-activation.ts +240 -46
  117. package/src/config/model-registry.ts +47 -153
  118. package/src/config/skill-settings-defaults.ts +7 -0
  119. package/src/coordinator-mcp/policy.ts +3 -2
  120. package/src/cursor-delete-path.ts +10 -0
  121. package/src/cursor-mcp-name.ts +15 -0
  122. package/src/cursor.ts +13 -2
  123. package/src/customization/inventory.ts +3 -0
  124. package/src/debug/report-bundle-parent.ts +22 -0
  125. package/src/debug/report-bundle.ts +3 -0
  126. package/src/debug/system-info.ts +13 -1
  127. package/src/defaults/gjc/extensions/grok-cli-vendor/src/provider/stream.ts +67 -4
  128. package/src/defaults/gjc/extensions/grok-cli-vendor/src/provider/version-manager.ts +80 -0
  129. package/src/defaults/gjc/skills/deep-interview/SKILL.md +105 -10
  130. package/src/defaults/gjc/skills/ralplan/SKILL.md +6 -4
  131. package/src/defaults/gjc-defaults.test.ts +18 -1
  132. package/src/defaults/gjc-defaults.ts +3 -0
  133. package/src/defaults/gjc-skills.generated.ts +58 -36
  134. package/src/discovery/agents.ts +6 -1
  135. package/src/discovery/builtin.ts +10 -0
  136. package/src/discovery/helpers.ts +159 -20
  137. package/src/discovery/mcp-json.ts +6 -0
  138. package/src/discovery/ssh.ts +1 -1
  139. package/src/discovery/substitute-plugin-root.ts +3 -1
  140. package/src/edit/file-read-cache.ts +1 -1
  141. package/src/eval/py/executor.ts +380 -120
  142. package/src/export/html/css-color.ts +9 -0
  143. package/src/export/html/index.ts +5 -4
  144. package/src/export/html/template.generated.ts +1 -1
  145. package/src/extensibility/extensions/runner.ts +4 -0
  146. package/src/extensibility/extensions/types.ts +7 -0
  147. package/src/extensibility/gjc-plugins/runtime-adapters.ts +73 -8
  148. package/src/extensibility/plugins/manager.ts +3 -2
  149. package/src/extensibility/plugins/marketplace/cache.ts +15 -0
  150. package/src/extensibility/plugins/marketplace/manager.ts +11 -3
  151. package/src/extensibility/plugins/plugin-link-path.ts +12 -0
  152. package/src/extensibility/runtime-skill-discovery.ts +20 -2
  153. package/src/extensibility/skill-management.ts +6 -2
  154. package/src/extensibility/skills.ts +6 -1
  155. package/src/gjc-runtime/deep-interview-crystallize.ts +1814 -0
  156. package/src/gjc-runtime/deep-interview-recorder.ts +20 -2
  157. package/src/gjc-runtime/deep-interview-runtime.ts +1415 -13
  158. package/src/gjc-runtime/deep-interview-stage.ts +98 -21
  159. package/src/gjc-runtime/gc-runtime.ts +206 -9
  160. package/src/gjc-runtime/launch-worktree.ts +14 -4
  161. package/src/gjc-runtime/ralplan-runtime.ts +447 -52
  162. package/src/gjc-runtime/state-argv.ts +2 -0
  163. package/src/gjc-runtime/state-migrations.ts +91 -29
  164. package/src/gjc-runtime/state-runtime.ts +3716 -135
  165. package/src/gjc-runtime/state-writer.ts +83 -8
  166. package/src/gjc-runtime/ultragoal-runtime.ts +16 -4
  167. package/src/gjc-runtime/workflow-manifest.generated.json +70 -42
  168. package/src/gjc-runtime/workflow-manifest.ts +54 -21
  169. package/src/harness-control-plane/operate.ts +12 -4
  170. package/src/harness-control-plane/owner.ts +5 -1
  171. package/src/hashline/parser.ts +48 -9
  172. package/src/hashline/recovery.ts +13 -4
  173. package/src/internal-urls/docs-index.generated.ts +18 -17
  174. package/src/internal-urls/skill-path.ts +19 -0
  175. package/src/internal-urls/skill-protocol.ts +2 -0
  176. package/src/lsp/edits.ts +6 -0
  177. package/src/lsp/workspace-path.ts +51 -0
  178. package/src/main.ts +195 -31
  179. package/src/modes/acp/acp-agent.ts +88 -4
  180. package/src/modes/components/custom-editor.ts +14 -0
  181. package/src/modes/components/customization/customization-dashboard.ts +12 -0
  182. package/src/modes/components/extensions/extension-dashboard.ts +2 -2
  183. package/src/modes/components/extensions/state-manager.ts +15 -5
  184. package/src/modes/components/model-selector.ts +1 -4
  185. package/src/modes/components/provider-onboarding-selector.ts +1 -1
  186. package/src/modes/components/status-line/segments.ts +4 -0
  187. package/src/modes/controllers/event-controller.ts +11 -4
  188. package/src/modes/controllers/input-controller.ts +74 -29
  189. package/src/modes/controllers/runtime-mcp-command-controller.ts +8 -3
  190. package/src/modes/controllers/selector-controller.ts +28 -15
  191. package/src/modes/interactive-mode.ts +4 -0
  192. package/src/modes/runtime-init.ts +4 -0
  193. package/src/modes/utils/abort-message.ts +3 -1
  194. package/src/modes/utils/interrupt-ownership.ts +12 -0
  195. package/src/modes/utils/keybinding-matchers.ts +4 -0
  196. package/src/modes/utils/macos-option-forwarding.ts +80 -0
  197. package/src/progress/collect-project-progress.ts +153 -0
  198. package/src/progress/progress-contract.ts +172 -0
  199. package/src/progress/progress-snapshot.ts +140 -0
  200. package/src/progress/project-progress.ts +446 -0
  201. package/src/progress/render-progress.ts +209 -0
  202. package/src/prompts/system/master-mode.md +1 -0
  203. package/src/prompts/tools/skill-discovery.md +1 -1
  204. package/src/runtime-mcp/config-writer.ts +10 -6
  205. package/src/runtime-mcp/config.ts +1 -0
  206. package/src/runtime-mcp/oauth-flow.ts +5 -0
  207. package/src/runtime-mcp/oauth-public-url.ts +8 -0
  208. package/src/runtime-mcp/plugin-network-boundary.ts +4 -0
  209. package/src/runtime-mcp/smithery-registry.ts +22 -3
  210. package/src/runtime-mcp/transports/http.ts +2 -2
  211. package/src/runtime-mcp/types.ts +6 -0
  212. package/src/sdk/acp/adapter.ts +93 -8
  213. package/src/sdk/broker/broker-exit.ts +52 -23
  214. package/src/sdk/broker/broker.ts +75 -6
  215. package/src/sdk/broker/ensure.ts +2 -0
  216. package/src/sdk/broker/lifecycle.ts +711 -66
  217. package/src/sdk/broker/session-index.ts +191 -81
  218. package/src/sdk/bus/chat-daemon-cli.ts +38 -1
  219. package/src/sdk/bus/chat-daemon-control.ts +2 -2
  220. package/src/sdk/bus/index.ts +238 -43
  221. package/src/sdk/bus/telegram-daemon-contract.ts +6 -1
  222. package/src/sdk/cli/session-cli.ts +17 -3
  223. package/src/sdk/client/client.ts +28 -8
  224. package/src/sdk/host/query/handlers.ts +9 -1
  225. package/src/sdk/host/session-runtime.test.ts +2647 -294
  226. package/src/sdk/host/session-runtime.ts +426 -109
  227. package/src/sdk/host/surface-policy.ts +1 -0
  228. package/src/sdk/index.ts +8 -0
  229. package/src/sdk/lifecycle-session.ts +3 -1
  230. package/src/sdk/prompt-deadline-manager.ts +15 -0
  231. package/src/sdk/protocol/operation-inventory.generated.json +146 -0
  232. package/src/sdk/protocol/operation-registry.ts +9 -2
  233. package/src/sdk/router/session-router.ts +75 -12
  234. package/src/sdk/session-list.ts +1 -0
  235. package/src/sdk/session.ts +67 -38
  236. package/src/session/agent-session.ts +623 -224
  237. package/src/session/artifacts.ts +174 -70
  238. package/src/session/internal/managed-gc-retirement-codec.ts +319 -0
  239. package/src/session/internal/managed-session-scope.ts +3457 -462
  240. package/src/session/internal/managed-session-storage.ts +376 -61
  241. package/src/session/internal/managed-task-owner-cleanup.ts +473 -0
  242. package/src/session/internal/task-artifact-owner-access.ts +19 -3
  243. package/src/session/internal/task-artifact-owner-transcript.ts +547 -1
  244. package/src/session/session-manager.ts +939 -133
  245. package/src/session/session-retirement.ts +573 -14
  246. package/src/session/session-storage.ts +417 -14
  247. package/src/session/task-artifact-owner-codec.ts +71 -8
  248. package/src/session/task-artifact-owner-retirement.ts +21 -19
  249. package/src/setup/credential-auto-import.ts +12 -3
  250. package/src/setup/credential-import.ts +176 -5
  251. package/src/setup/paseo/announce.ts +6 -1
  252. package/src/skill-state/active-state.ts +132 -2
  253. package/src/slash-commands/builtin-registry.ts +39 -9
  254. package/src/slash-commands/helpers/progress-report.ts +23 -0
  255. package/src/stt/setup.ts +3 -5
  256. package/src/task/commands.ts +3 -1
  257. package/src/task/executor.ts +4 -0
  258. package/src/task/index.ts +6 -0
  259. package/src/tools/ask.ts +205 -6
  260. package/src/tools/ast-edit.ts +4 -0
  261. package/src/tools/bash-allowed-prefixes.ts +15 -0
  262. package/src/tools/bash-skill-urls.ts +56 -3
  263. package/src/tools/bash.ts +48 -79
  264. package/src/tools/descriptors.ts +5 -1
  265. package/src/tools/eval.ts +56 -36
  266. package/src/tools/image-gen.ts +1 -1
  267. package/src/tools/index.ts +3 -0
  268. package/src/tools/python.ts +187 -66
  269. package/src/tools/recipe/index.ts +9 -0
  270. package/src/tools/recipe/recipe-guard.ts +19 -0
  271. package/src/tools/restricted-cwd.ts +10 -0
  272. package/src/tools/skill-discovery.ts +5 -0
  273. package/src/tools/skill.ts +28 -1
  274. package/src/tools/todo-write.ts +10 -1
  275. package/src/tools/tool-catalog.generated.ts +1 -1
  276. package/src/tools/write.ts +1 -0
  277. package/src/utils/tools-checksum.ts +34 -0
  278. package/src/utils/tools-manager.ts +24 -6
  279. package/src/web/search/providers/codex.ts +3 -2
@@ -34,6 +34,20 @@ export class ModelBindingsApplier {
34
34
  };
35
35
  }
36
36
 
37
+ /** Fork configured bindings and their applied lifecycle onto independent settings. */
38
+ snapshotForSettings(targetSettings: Settings): ModelBindingsApplier {
39
+ const snapshot = new ModelBindingsApplier();
40
+ snapshot.#targetSettings = targetSettings;
41
+ snapshot.#bindings = this.getBindings();
42
+ snapshot.#appliedRoles = new Set(this.#appliedRoles);
43
+ snapshot.#appliedAgentOverrides = new Set(this.#appliedAgentOverrides);
44
+ snapshot.#roleBaselines = this.#cloneSelectorMap(this.#roleBaselines);
45
+ snapshot.#agentBaselines = this.#cloneSelectorMap(this.#agentBaselines);
46
+ snapshot.#lastAppliedRoles = this.#cloneSelectorMap(this.#lastAppliedRoles);
47
+ snapshot.#lastAppliedAgentOverrides = this.#cloneSelectorMap(this.#lastAppliedAgentOverrides);
48
+ return snapshot;
49
+ }
50
+
37
51
  applyTo(targetSettings: Settings): void {
38
52
  if (this.#targetSettings && this.#targetSettings !== targetSettings) {
39
53
  this.#restoreTarget(this.#targetSettings);
@@ -184,6 +198,12 @@ export class ModelBindingsApplier {
184
198
  return copy;
185
199
  }
186
200
 
201
+ #cloneSelectorMap<T extends ModelSelectorValue | undefined>(source: Map<string, T>): Map<string, T> {
202
+ const copy = new Map<string, T>();
203
+ for (const [key, value] of source) copy.set(key, this.#clone(value) as T);
204
+ return copy;
205
+ }
206
+
187
207
  #clone(value: ModelSelectorValue | undefined): ModelSelectorValue | undefined {
188
208
  return Array.isArray(value) ? [...value] : value;
189
209
  }
@@ -37,6 +37,11 @@ import {
37
37
  import { type ModelSelectorValue, normalizeModelSelectorValue } from "./model-selector-value";
38
38
  import type { Settings } from "./settings";
39
39
 
40
+ type UserCanonicalVariantSelection = {
41
+ revision: number;
42
+ canonicalVariant: string | undefined;
43
+ };
44
+
40
45
  type ModelProfileActivationSession = Pick<
41
46
  AgentSession,
42
47
  "model" | "thinkingLevel" | "sessionId" | "getConfiguredModelChain" | "setConfiguredModelChain"
@@ -59,6 +64,7 @@ type ModelProfileActivationSession = Pick<
59
64
  syncEagerDelegation?: () => Promise<void>;
60
65
  getSessionDefaultModelSelector?: () => string | undefined;
61
66
  recordResumeDefaultModel?: (selector: string | undefined) => void;
67
+ getUserCanonicalVariantSelection?: () => UserCanonicalVariantSelection;
62
68
  seedDefaultFallbackResolution?: (activeIndex: number, skips: Array<{ selector: string; reason: string }>) => void;
63
69
  getDefaultFallbackRuntimeState?: () => DefaultFallbackRuntimeState;
64
70
  restoreDefaultFallbackRuntimeState?: (state: DefaultFallbackRuntimeState) => void;
@@ -73,8 +79,7 @@ type ModelProfileActivationSession = Pick<
73
79
  | "resolveModelByLookupAlias"
74
80
  | "authStorage"
75
81
  | "isCredentiallessProvider"
76
- > &
77
- Partial<Pick<ModelRegistry, "getAvailableForProfileActivation">>;
82
+ >;
78
83
  getConfiguredModelChainState?: (role: string) => ConfiguredModelChainState | undefined;
79
84
  };
80
85
 
@@ -103,7 +108,6 @@ export interface PrepareModelProfileActivationOptions {
103
108
  Pick<
104
109
  ModelRegistry,
105
110
  | "getAvailable"
106
- | "getAvailableForProfileActivation"
107
111
  | "resolveModelByLookupAlias"
108
112
  | "lookupAliasExists"
109
113
  | "clearCanonicalVariant"
@@ -123,6 +127,8 @@ export interface PrepareModelProfileActivationOptions {
123
127
  export interface ApplyModelProfileActivationOptions {
124
128
  persistDefault?: boolean;
125
129
  thinkingLevelOverride?: ThinkingLevel;
130
+ /** Cancels deferred activation if a newer user model selection exists. */
131
+ isCurrent?: () => boolean;
126
132
  }
127
133
  export interface PreparedModelProfileActivation {
128
134
  profileName: string;
@@ -176,6 +182,7 @@ export interface PreparedModelProfileActivation {
176
182
  * so a stale provider cannot silently resurrect.
177
183
  */
178
184
  previousCanonicalVariant: string | undefined;
185
+ previousUserCanonicalVariantSelection: UserCanonicalVariantSelection | undefined;
179
186
  /** Registry used to resolve and restore the session sticky canonical variant. */
180
187
  modelRegistry: PrepareModelProfileActivationOptions["modelRegistry"];
181
188
  }
@@ -258,10 +265,8 @@ function concretizeMaterializedAssignmentValues(
258
265
  if (!modelRegistry || !sessionId) return assignments;
259
266
  // Materialized assignments are persisted into `modelRoles` and
260
267
  // `task.agentModelOverrides` and later consumed by profile execution, so they
261
- // must resolve against the descriptor-backed profile-activation catalog, not
262
- // the broadened general one: otherwise a bare assignment can persist a
263
- // bundled model that fresh live profile evidence excludes.
264
- const availableModels = modelRegistry.getAvailableForProfileActivation?.() ?? modelRegistry.getAvailable();
268
+ // resolve against the same catalog profile activation uses.
269
+ const availableModels = modelRegistry.getAvailable();
265
270
  const authenticatedModels = availableModels.filter(model => {
266
271
  const isCredentiallessProvider = modelRegistry.isCredentiallessProvider?.bind(modelRegistry);
267
272
  const hasUsableAuth = modelRegistry.authStorage?.hasUsableAuth?.bind(modelRegistry.authStorage);
@@ -879,10 +884,7 @@ export async function resolveModelProfileDefaultChain(options: {
879
884
  allRoutable && proxyProvider !== undefined ? [proxyProvider] : [...group],
880
885
  );
881
886
  }
882
- const availableModels =
883
- options.modelRegistry.getAvailableForProfileActivation?.() ??
884
- options.modelRegistry.getAvailable?.() ??
885
- options.modelRegistry.getAll();
887
+ const availableModels = options.modelRegistry.getAvailable?.() ?? options.modelRegistry.getAll();
886
888
  let bindings = resolveProfileBindings(profile);
887
889
  if (alternativeGroups.length > 0)
888
890
  bindings = rewriteBindingsProviders(bindings, authenticatedProviders, alternativeGroups);
@@ -1323,6 +1325,25 @@ function restoreCanonicalVariant(
1323
1325
  modelRegistry.clearCanonicalVariant?.(sessionId);
1324
1326
  }
1325
1327
  }
1328
+
1329
+ function restoreCanonicalVariantAfterPreparation(
1330
+ modelRegistry: PrepareModelProfileActivationOptions["modelRegistry"],
1331
+ session: ModelProfileActivationSession,
1332
+ previousCanonicalVariant: string | undefined,
1333
+ previousUserSelection: UserCanonicalVariantSelection | undefined,
1334
+ ): void {
1335
+ const currentUserSelection = session.getUserCanonicalVariantSelection?.();
1336
+ const userSelectionChanged =
1337
+ previousUserSelection !== undefined &&
1338
+ currentUserSelection !== undefined &&
1339
+ currentUserSelection.revision !== previousUserSelection.revision;
1340
+ restoreCanonicalVariant(
1341
+ modelRegistry,
1342
+ session.sessionId,
1343
+ userSelectionChanged ? currentUserSelection.canonicalVariant : previousCanonicalVariant,
1344
+ );
1345
+ }
1346
+
1326
1347
  export async function prepareModelProfileActivation(
1327
1348
  options: PrepareModelProfileActivationOptions,
1328
1349
  ): Promise<PreparedModelProfileActivation> {
@@ -1337,6 +1358,7 @@ export async function prepareModelProfileActivation(
1337
1358
  // genuinely-sticky provider even when the live model is a transient switch.
1338
1359
  const credentialSessionId = options.session.credentialSessionId ?? options.session.sessionId;
1339
1360
  const previousCanonicalVariant = options.modelRegistry.getSessionCanonicalVariant?.(options.session.sessionId);
1361
+ const previousUserCanonicalVariantSelection = options.session.getUserCanonicalVariantSelection?.();
1340
1362
 
1341
1363
  // Explicit profile activation/reselection invalidates the session's sticky
1342
1364
  // canonical variant BEFORE the new profile's aliases resolve, so the old
@@ -1464,10 +1486,7 @@ export async function prepareModelProfileActivation(
1464
1486
  );
1465
1487
  }
1466
1488
 
1467
- const availableModels =
1468
- options.modelRegistry.getAvailableForProfileActivation?.() ??
1469
- options.modelRegistry.getAvailable?.() ??
1470
- options.modelRegistry.getAll();
1489
+ const availableModels = options.modelRegistry.getAvailable?.() ?? options.modelRegistry.getAll();
1471
1490
  const roleCatalogModels = options.modelRegistry.getAll();
1472
1491
  let bindings = resolveProfileBindings(profile);
1473
1492
  if (alternativeGroups.length > 0) {
@@ -1562,6 +1581,7 @@ export async function prepareModelProfileActivation(
1562
1581
  modelRegistry: options.modelRegistry,
1563
1582
  previousModel,
1564
1583
  previousCanonicalVariant,
1584
+ previousUserCanonicalVariantSelection,
1565
1585
  previousThinkingLevel: options.session.thinkingLevel,
1566
1586
  previousAgentModelOverrides: { ...options.settings.get("task.agentModelOverrides") },
1567
1587
  previousModelRoles: { ...options.settings.get("modelRoles") },
@@ -1602,7 +1622,12 @@ export async function prepareModelProfileActivation(
1602
1622
  };
1603
1623
  } catch (error) {
1604
1624
  try {
1605
- restoreCanonicalVariant(options.modelRegistry, options.session.sessionId, previousCanonicalVariant);
1625
+ restoreCanonicalVariantAfterPreparation(
1626
+ options.modelRegistry,
1627
+ options.session,
1628
+ previousCanonicalVariant,
1629
+ previousUserCanonicalVariantSelection,
1630
+ );
1606
1631
  } catch (rollbackError) {
1607
1632
  throw incompleteModelProfileRollbackError("preparation", "profile preflight", error, [
1608
1633
  { stage: "restore canonical model variant", error: rollbackError },
@@ -1648,10 +1673,45 @@ function incompleteModelProfileRollbackError(
1648
1673
  );
1649
1674
  }
1650
1675
 
1676
+ class ModelProfileActivationSupersededError extends Error {
1677
+ constructor() {
1678
+ super("Model profile activation superseded by a newer user model selection");
1679
+ this.name = "ModelProfileActivationSupersededError";
1680
+ }
1681
+ }
1682
+
1683
+ function sameSerializedValue(left: unknown, right: unknown): boolean {
1684
+ return JSON.stringify(left) === JSON.stringify(right);
1685
+ }
1686
+
1687
+ function restoreConcurrentProfileOverrides(
1688
+ previous: Readonly<Record<string, ModelSelectorValue>> | undefined,
1689
+ activated: Readonly<Record<string, ModelSelectorValue>> | undefined,
1690
+ current: Readonly<Record<string, ModelSelectorValue>> | undefined,
1691
+ ): Readonly<Record<string, ModelSelectorValue>> | undefined {
1692
+ if (sameSerializedValue(current, activated)) return previous;
1693
+ const restored = { ...(previous ?? {}) };
1694
+ const keys = new Set([
1695
+ ...Object.keys(previous ?? {}),
1696
+ ...Object.keys(activated ?? {}),
1697
+ ...Object.keys(current ?? {}),
1698
+ ]);
1699
+ for (const key of keys) {
1700
+ const wasActivated = activated !== undefined && Object.hasOwn(activated, key);
1701
+ const isCurrent = current !== undefined && Object.hasOwn(current, key);
1702
+ if (wasActivated === isCurrent && sameSerializedValue(activated?.[key], current?.[key])) continue;
1703
+ const currentValue = current?.[key];
1704
+ if (isCurrent && currentValue !== undefined) restored[key] = currentValue;
1705
+ else delete restored[key];
1706
+ }
1707
+ return Object.keys(restored).length > 0 || previous !== undefined ? restored : undefined;
1708
+ }
1709
+
1651
1710
  export async function applyPreparedModelProfileActivation(
1652
1711
  prepared: PreparedModelProfileActivation,
1653
1712
  options: ApplyModelProfileActivationOptions = {},
1654
1713
  ): Promise<void> {
1714
+ if (options.isCurrent && !options.isCurrent()) return;
1655
1715
  let activationStage = "default chain";
1656
1716
  let modelMutationStarted = false;
1657
1717
  let overridesChanged = false;
@@ -1659,8 +1719,15 @@ export async function applyPreparedModelProfileActivation(
1659
1719
  let persistentMutationStarted = false;
1660
1720
  let defaultChainChanged = false;
1661
1721
  let resumeDefaultChanged = false;
1722
+ let activatedDefaultChainState: ConfiguredModelChainState | undefined;
1723
+ let activatedFallbackRuntimeState: DefaultFallbackRuntimeState | undefined;
1724
+ let activatedModelRolesOverride: unknown;
1725
+ let activatedAgentModelOverridesOverride: unknown;
1662
1726
 
1663
1727
  try {
1728
+ // Set the fallback chain before yielding to setModelTemporary, so a user
1729
+ // selection during async mutation uses the fallback chain as-is rather than
1730
+ // having the profile's chain written over a concurrent user choice.
1664
1731
  const ownedDefaultChain =
1665
1732
  prepared.defaultChain.length > 0
1666
1733
  ? prepared.defaultChain
@@ -1681,6 +1748,8 @@ export async function applyPreparedModelProfileActivation(
1681
1748
  prepared.defaultResolutionSkips,
1682
1749
  );
1683
1750
  }
1751
+ activatedDefaultChainState = prepared.session.getConfiguredModelChainState?.("default");
1752
+ activatedFallbackRuntimeState = prepared.session.getDefaultFallbackRuntimeState?.();
1684
1753
  }
1685
1754
  if (prepared.defaultModel) {
1686
1755
  activationStage = "model selection";
@@ -1689,11 +1758,14 @@ export async function applyPreparedModelProfileActivation(
1689
1758
  options.thinkingLevelOverride ?? prepared.defaultThinkingLevel,
1690
1759
  {
1691
1760
  cause: "profile-activation",
1761
+ shouldMutate: options.isCurrent,
1692
1762
  onMutationStarted: () => {
1693
1763
  modelMutationStarted = true;
1694
1764
  },
1695
1765
  },
1696
1766
  );
1767
+ if (options.isCurrent && !modelMutationStarted) throw new ModelProfileActivationSupersededError();
1768
+ if (options.isCurrent && !options.isCurrent()) throw new ModelProfileActivationSupersededError();
1697
1769
  }
1698
1770
  // Always reinstall the model role layer from the durable base plus the
1699
1771
  // new profile's roles so omitted roles from the previous profile are dropped.
@@ -1702,6 +1774,7 @@ export async function applyPreparedModelProfileActivation(
1702
1774
  ...prepared.baseModelRoles,
1703
1775
  ...prepared.modelRoles,
1704
1776
  });
1777
+ activatedModelRolesOverride = prepared.settings.getOverride("modelRoles");
1705
1778
  modelRolesChanged = true;
1706
1779
  // Always reinstall the agent role layer from the durable base plus the
1707
1780
  // new profile's roles: a default-only or role-free successor must drop
@@ -1711,6 +1784,7 @@ export async function applyPreparedModelProfileActivation(
1711
1784
  ...prepared.baseAgentModelOverrides,
1712
1785
  ...prepared.agentModelOverrides,
1713
1786
  });
1787
+ activatedAgentModelOverridesOverride = prepared.settings.getOverride("task.agentModelOverrides");
1714
1788
  overridesChanged = true;
1715
1789
  if (options.persistDefault) {
1716
1790
  persistentMutationStarted = true;
@@ -1727,6 +1801,7 @@ export async function applyPreparedModelProfileActivation(
1727
1801
  activationStage = "forward settings flush";
1728
1802
  await prepared.settings.flushOrThrow();
1729
1803
  }
1804
+ if (options.isCurrent && !options.isCurrent()) throw new ModelProfileActivationSupersededError();
1730
1805
  activationStage = "active profile marker";
1731
1806
  prepared.session.setActiveModelProfile?.(prepared.profileName);
1732
1807
  if (prepared.defaultModel) {
@@ -1743,6 +1818,8 @@ export async function applyPreparedModelProfileActivation(
1743
1818
  prepared.previousModel,
1744
1819
  );
1745
1820
  } catch (error) {
1821
+ const activationWasCancelled = error instanceof ModelProfileActivationSupersededError;
1822
+ const selectionSuperseded = options.isCurrent?.() === false;
1746
1823
  const rollbackErrors: Array<{ stage: string; error: unknown }> = [];
1747
1824
  const restore = (stage: string, action: () => void): void => {
1748
1825
  try {
@@ -1752,42 +1829,82 @@ export async function applyPreparedModelProfileActivation(
1752
1829
  }
1753
1830
  };
1754
1831
  if (persistentMutationStarted) {
1755
- restore("restore default profile setting", () =>
1756
- prepared.previousPersistedDefaultProfile === undefined
1757
- ? prepared.settings.unset("modelProfile.default")
1758
- : prepared.settings.set("modelProfile.default", prepared.previousPersistedDefaultProfile),
1759
- );
1832
+ if (
1833
+ !selectionSuperseded ||
1834
+ sameSerializedValue(prepared.settings.getGlobal("modelProfile.default"), prepared.profileName)
1835
+ ) {
1836
+ restore("restore default profile setting", () =>
1837
+ prepared.previousPersistedDefaultProfile === undefined
1838
+ ? prepared.settings.unset("modelProfile.default")
1839
+ : prepared.settings.set("modelProfile.default", prepared.previousPersistedDefaultProfile),
1840
+ );
1841
+ }
1842
+ const currentPersistedModelRoles = prepared.settings.getGlobal("modelRoles");
1843
+ const restoredModelRoles =
1844
+ selectionSuperseded && currentPersistedModelRoles && !sameSerializedValue(currentPersistedModelRoles, {})
1845
+ ? { ...(prepared.previousPersistedModelRoles ?? {}), ...currentPersistedModelRoles }
1846
+ : prepared.previousPersistedModelRoles;
1760
1847
  restore("restore model role setting", () =>
1761
- prepared.previousPersistedModelRoles === undefined
1848
+ restoredModelRoles === undefined
1762
1849
  ? prepared.settings.unset("modelRoles")
1763
- : prepared.settings.set("modelRoles", prepared.previousPersistedModelRoles),
1850
+ : prepared.settings.set("modelRoles", restoredModelRoles),
1764
1851
  );
1852
+ const currentPersistedAgentModelOverrides = prepared.settings.getGlobal("task.agentModelOverrides");
1853
+ const restoredPersistedAgentModelOverrides =
1854
+ selectionSuperseded &&
1855
+ currentPersistedAgentModelOverrides &&
1856
+ !sameSerializedValue(currentPersistedAgentModelOverrides, {})
1857
+ ? {
1858
+ ...(prepared.previousPersistedAgentModelOverrides ?? {}),
1859
+ ...currentPersistedAgentModelOverrides,
1860
+ }
1861
+ : prepared.previousPersistedAgentModelOverrides;
1765
1862
  restore("restore agent role setting", () =>
1766
- prepared.previousPersistedAgentModelOverrides === undefined
1863
+ restoredPersistedAgentModelOverrides === undefined
1767
1864
  ? prepared.settings.unset("task.agentModelOverrides")
1768
- : prepared.settings.set("task.agentModelOverrides", prepared.previousPersistedAgentModelOverrides),
1769
- );
1770
- restore("restore thinking level setting", () =>
1771
- prepared.previousPersistedDefaultThinkingLevel === undefined
1772
- ? prepared.settings.unset("defaultThinkingLevel")
1773
- : prepared.settings.set("defaultThinkingLevel", prepared.previousPersistedDefaultThinkingLevel),
1865
+ : prepared.settings.set("task.agentModelOverrides", restoredPersistedAgentModelOverrides),
1774
1866
  );
1867
+ const activatedThinkingLevel =
1868
+ prepared.defaultThinkingLevel !== undefined && prepared.defaultThinkingLevel !== ThinkingLevel.Inherit
1869
+ ? prepared.defaultThinkingLevel
1870
+ : prepared.previousPersistedDefaultThinkingLevel;
1871
+ if (
1872
+ !selectionSuperseded ||
1873
+ sameSerializedValue(prepared.settings.getGlobal("defaultThinkingLevel"), activatedThinkingLevel)
1874
+ ) {
1875
+ restore("restore thinking level setting", () =>
1876
+ prepared.previousPersistedDefaultThinkingLevel === undefined
1877
+ ? prepared.settings.unset("defaultThinkingLevel")
1878
+ : prepared.settings.set("defaultThinkingLevel", prepared.previousPersistedDefaultThinkingLevel),
1879
+ );
1880
+ }
1775
1881
  }
1776
- if (modelRolesChanged) {
1882
+ if (
1883
+ modelRolesChanged &&
1884
+ (!selectionSuperseded ||
1885
+ sameSerializedValue(prepared.settings.getOverride("modelRoles"), activatedModelRolesOverride))
1886
+ ) {
1777
1887
  restore("restore model role overrides", () =>
1778
1888
  prepared.previousModelRolesOverride === undefined
1779
1889
  ? prepared.settings.clearOverride("modelRoles")
1780
1890
  : prepared.settings.override("modelRoles", prepared.previousModelRolesOverride),
1781
1891
  );
1782
1892
  }
1783
- if (overridesChanged) {
1893
+ if (
1894
+ overridesChanged &&
1895
+ (!selectionSuperseded ||
1896
+ sameSerializedValue(
1897
+ prepared.settings.getOverride("task.agentModelOverrides"),
1898
+ activatedAgentModelOverridesOverride,
1899
+ ))
1900
+ ) {
1784
1901
  restore("restore agent role overrides", () =>
1785
1902
  prepared.previousAgentModelOverridesOverride === undefined
1786
1903
  ? prepared.settings.clearOverride("task.agentModelOverrides")
1787
1904
  : prepared.settings.override("task.agentModelOverrides", prepared.previousAgentModelOverridesOverride),
1788
1905
  );
1789
1906
  }
1790
- if (modelMutationStarted) {
1907
+ if (modelMutationStarted && !selectionSuperseded) {
1791
1908
  try {
1792
1909
  if (prepared.session.restoreModelSelectionForRollback) {
1793
1910
  await prepared.session.restoreModelSelectionForRollback(
@@ -1810,7 +1927,11 @@ export async function applyPreparedModelProfileActivation(
1810
1927
  prepared.session.recordResumeDefaultModel?.(prepared.previousSessionDefaultModel),
1811
1928
  );
1812
1929
  }
1813
- if (defaultChainChanged) {
1930
+ if (
1931
+ defaultChainChanged &&
1932
+ (!selectionSuperseded ||
1933
+ sameSerializedValue(prepared.session.getConfiguredModelChainState?.("default"), activatedDefaultChainState))
1934
+ ) {
1814
1935
  const previousChain = prepared.previousDefaultChainState;
1815
1936
  restore("restore default chain", () =>
1816
1937
  prepared.session.setConfiguredModelChain(
@@ -1824,16 +1945,27 @@ export async function applyPreparedModelProfileActivation(
1824
1945
  ),
1825
1946
  );
1826
1947
  }
1827
- if (prepared.previousDefaultFallbackRuntimeState) {
1948
+ if (
1949
+ prepared.previousDefaultFallbackRuntimeState &&
1950
+ (!selectionSuperseded ||
1951
+ sameSerializedValue(prepared.session.getDefaultFallbackRuntimeState?.(), activatedFallbackRuntimeState))
1952
+ ) {
1828
1953
  restore("restore fallback runtime", () =>
1829
1954
  prepared.session.restoreDefaultFallbackRuntimeState?.(prepared.previousDefaultFallbackRuntimeState!),
1830
1955
  );
1831
1956
  }
1832
- restore("restore active profile", () =>
1833
- prepared.session.setActiveModelProfile?.(prepared.previousActiveModelProfile),
1834
- );
1957
+ if (!selectionSuperseded) {
1958
+ restore("restore active profile", () =>
1959
+ prepared.session.setActiveModelProfile?.(prepared.previousActiveModelProfile),
1960
+ );
1961
+ }
1835
1962
  restore("restore canonical model variant", () =>
1836
- restoreCanonicalVariant(prepared.modelRegistry, prepared.session.sessionId, prepared.previousCanonicalVariant),
1963
+ restoreCanonicalVariantAfterPreparation(
1964
+ prepared.modelRegistry,
1965
+ prepared.session,
1966
+ prepared.previousCanonicalVariant,
1967
+ prepared.previousUserCanonicalVariantSelection,
1968
+ ),
1837
1969
  );
1838
1970
  if (persistentMutationStarted) {
1839
1971
  try {
@@ -1845,6 +1977,7 @@ export async function applyPreparedModelProfileActivation(
1845
1977
  if (rollbackErrors.length > 0) {
1846
1978
  throw incompleteModelProfileRollbackError("activation", activationStage, error, rollbackErrors);
1847
1979
  }
1980
+ if (activationWasCancelled) return;
1848
1981
  throw error;
1849
1982
  }
1850
1983
  // The installed role layer decides whether this profile is vendor-separated,
@@ -2112,28 +2245,46 @@ export async function activateModelProfile(
2112
2245
  options: PrepareModelProfileActivationOptions,
2113
2246
  applyOptions: ApplyModelProfileActivationOptions = {},
2114
2247
  ): Promise<void> {
2248
+ if (applyOptions.isCurrent && !applyOptions.isCurrent()) return;
2115
2249
  const prepared = await prepareModelProfileActivation(options);
2250
+ if (applyOptions.isCurrent && !applyOptions.isCurrent()) {
2251
+ restoreCanonicalVariantAfterPreparation(
2252
+ prepared.modelRegistry,
2253
+ prepared.session,
2254
+ prepared.previousCanonicalVariant,
2255
+ prepared.previousUserCanonicalVariantSelection,
2256
+ );
2257
+ return;
2258
+ }
2116
2259
  await applyPreparedModelProfileActivation(prepared, applyOptions);
2117
2260
  }
2118
2261
 
2119
2262
  /** Install only a profile's runtime role layer, preserving session model and configured-chain intent. */
2120
- export async function applyModelProfileRuntimeBindings(options: PrepareModelProfileActivationOptions): Promise<void> {
2263
+
2264
+ export async function applyModelProfileRuntimeBindings(
2265
+ options: PrepareModelProfileActivationOptions,
2266
+ isCurrent?: () => boolean,
2267
+ ): Promise<void> {
2268
+ if (isCurrent && !isCurrent()) return;
2121
2269
  const prepared = await prepareModelProfileActivation(options);
2270
+ const previousModelRolesOverride = prepared.settings.getOverride("modelRoles");
2271
+ const previousAgentModelOverridesOverride = prepared.settings.getOverride("task.agentModelOverrides");
2272
+ const previousActiveModelProfile = prepared.session.getActiveModelProfile?.();
2273
+ let activatedModelRolesOverride: Readonly<Record<string, ModelSelectorValue>> | undefined;
2274
+ let activatedAgentModelOverridesOverride: Readonly<Record<string, ModelSelectorValue>> | undefined;
2122
2275
  try {
2276
+ if (isCurrent && !isCurrent()) return;
2123
2277
  prepared.settings.override("modelRoles", {
2124
2278
  ...prepared.baseModelRoles,
2125
2279
  ...prepared.modelRoles,
2126
2280
  });
2281
+ activatedModelRolesOverride = prepared.settings.getOverride("modelRoles");
2127
2282
  prepared.settings.override("task.agentModelOverrides", {
2128
2283
  ...prepared.baseAgentModelOverrides,
2129
2284
  ...prepared.agentModelOverrides,
2130
2285
  });
2286
+ activatedAgentModelOverridesOverride = prepared.settings.getOverride("task.agentModelOverrides");
2131
2287
  prepared.session.setActiveModelProfile?.(prepared.profileName);
2132
- prepared.session.noteProfileInstalledOverrides?.(
2133
- Object.keys(prepared.modelRoles),
2134
- Object.keys(prepared.agentModelOverrides),
2135
- prepared.previousModel,
2136
- );
2137
2288
  try {
2138
2289
  await prepared.session.syncEagerDelegation?.();
2139
2290
  } catch (error) {
@@ -2142,7 +2293,50 @@ export async function applyModelProfileRuntimeBindings(options: PrepareModelProf
2142
2293
  error: error instanceof Error ? error.message : String(error),
2143
2294
  });
2144
2295
  }
2296
+ if (isCurrent && !isCurrent()) {
2297
+ const restoredModelRolesOverride = restoreConcurrentProfileOverrides(
2298
+ previousModelRolesOverride,
2299
+ activatedModelRolesOverride,
2300
+ prepared.settings.getOverride("modelRoles"),
2301
+ );
2302
+ if (restoredModelRolesOverride === undefined) prepared.settings.clearOverride("modelRoles");
2303
+ else prepared.settings.override("modelRoles", restoredModelRolesOverride);
2304
+ const restoredAgentModelOverridesOverride = restoreConcurrentProfileOverrides(
2305
+ previousAgentModelOverridesOverride,
2306
+ activatedAgentModelOverridesOverride,
2307
+ prepared.settings.getOverride("task.agentModelOverrides"),
2308
+ );
2309
+ if (restoredAgentModelOverridesOverride === undefined)
2310
+ prepared.settings.clearOverride("task.agentModelOverrides");
2311
+ else prepared.settings.override("task.agentModelOverrides", restoredAgentModelOverridesOverride);
2312
+ if (prepared.session.getActiveModelProfile?.() === prepared.profileName) {
2313
+ prepared.session.setActiveModelProfile?.(previousActiveModelProfile);
2314
+ }
2315
+ try {
2316
+ await prepared.session.syncEagerDelegation?.();
2317
+ } catch (error) {
2318
+ logger.warn("Failed to sync eager delegation after canceled recovered profile runtime bindings", {
2319
+ profile: prepared.profileName,
2320
+ error: error instanceof Error ? error.message : String(error),
2321
+ });
2322
+ }
2323
+ return;
2324
+ }
2325
+ prepared.session.noteProfileInstalledOverrides?.(
2326
+ Object.keys(prepared.modelRoles),
2327
+ Object.keys(prepared.agentModelOverrides),
2328
+ prepared.previousModel,
2329
+ );
2145
2330
  } finally {
2146
- restoreCanonicalVariant(prepared.modelRegistry, prepared.session.sessionId, prepared.previousCanonicalVariant);
2331
+ if (isCurrent && !isCurrent()) {
2332
+ restoreCanonicalVariantAfterPreparation(
2333
+ prepared.modelRegistry,
2334
+ prepared.session,
2335
+ prepared.previousCanonicalVariant,
2336
+ prepared.previousUserCanonicalVariantSelection,
2337
+ );
2338
+ } else {
2339
+ restoreCanonicalVariant(prepared.modelRegistry, prepared.session.sessionId, prepared.previousCanonicalVariant);
2340
+ }
2147
2341
  }
2148
2342
  }