@yansigit/opencodex 2.35.2-dev.20260829.15 → 2.36.1-dev.20260829.19

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.
@@ -17,6 +17,8 @@ import {
17
17
  armClaudeCodeBaseline,
18
18
  loadConfig,
19
19
  saveConfig,
20
+ saveConfigPreservingClaudeCode,
21
+ mutatePersistedConfig,
20
22
  getConfigDir,
21
23
  websocketsEnabled,
22
24
  } from "../config";
@@ -531,6 +533,11 @@ export function warnAgentTaskRecoveryStartup(config: {
531
533
  }
532
534
 
533
535
  export function startServer(port?: number, deps: StartServerDeps = {}): Server<WsData> {
536
+ const managementApi: ManagementApiDeps = {
537
+ saveConfigPreservingClaudeCode,
538
+ mutatePersistedConfig,
539
+ ...deps.managementApi,
540
+ };
534
541
  const localAttestationSecret = deps.localAttestationSecret ?? createLocalAttestationSecret();
535
542
  const config = runModelRenameStartupMigration(runAlibabaRegionStartupMigration(runOpenAiTierStartupMigration(loadConfig())));
536
543
  warnAgentTaskRecoveryStartup(config);
@@ -1044,7 +1051,7 @@ export function startServer(port?: number, deps: StartServerDeps = {}): Server<W
1044
1051
  method: "POST",
1045
1052
  headers: { Host: req.headers.get("Host") ?? "127.0.0.1" },
1046
1053
  });
1047
- const probeResponse = await handleManagementAPI(probeRequest, new URL(probeRequest.url), config, deps.managementApi);
1054
+ const probeResponse = await handleManagementAPI(probeRequest, new URL(probeRequest.url), config, managementApi);
1048
1055
  const probe = await probeResponse?.json().catch(() => null) as { ok?: boolean; error?: string } | null;
1049
1056
  if (!probe?.ok) return jsonResponse({ ok: false, error: probe?.error ?? "AI Studio connection probe failed" }, 502, req, policy);
1050
1057
  return jsonResponse({ ok: true, sessionPath: login.sessionPath }, 200, req, policy);
@@ -1122,7 +1129,7 @@ export function startServer(port?: number, deps: StartServerDeps = {}): Server<W
1122
1129
  // gate used. Consent-bearing routes need this: request headers are forgeable
1123
1130
  // by anything holding the admin token, the credential is not.
1124
1131
  const principal = managementPrincipal(req, managementAuth, config, localManagementAuth) ?? undefined;
1125
- const mgmtResponse = await handleManagementAPI(req, url, config, deps.managementApi, principal);
1132
+ const mgmtResponse = await handleManagementAPI(req, url, config, managementApi, principal);
1126
1133
  if (mgmtResponse) return withManagementCors(mgmtResponse, req, config);
1127
1134
  return withManagementCors(formatErrorResponse(404, "not_found", `Unknown endpoint: ${req.method} ${url.pathname}`), req, config);
1128
1135
  }
@@ -10,10 +10,8 @@ import {
10
10
  isValidProviderName,
11
11
  loadConfig,
12
12
  multiAgentGuidanceEnabled,
13
- mutatePersistedConfig,
14
13
  providerBaseUrlConfigError,
15
14
  providerHeadersConfigError,
16
- saveConfigPreservingClaudeCode,
17
15
  subagentDefaultSyncEffective,
18
16
  } from "../../config";
19
17
  import {
@@ -91,9 +89,29 @@ let grokApplyTestHooks: { now?: () => number; run?: () => Promise<unknown> } | n
91
89
 
92
90
  type V2NativeParentOverrideInput = { enabled: boolean; model: string | null };
93
91
  type AgentTaskRecoveryInput = { enabled: boolean; model: string | null };
92
+ const V2_CONFIG_KEYS = ["multiAgentMode", "keepNativeChatGptOnV1", "v2NativeParentOverride", "v2RoutedDelegationBridge", "agentTaskRecovery"] as const;
93
+ type V2ConfigKey = typeof V2_CONFIG_KEYS[number];
94
+ type V2ConfigSnapshot = Pick<OcxConfig, V2ConfigKey>;
94
95
 
95
- function persistV2RoutedDelegationBridge(config: OcxConfig, enabled: boolean): { ok: true } | { ok: false; reason: string } {
96
- const outcome = mutatePersistedConfig(persisted => {
96
+ function v2ConfigSnapshot(config: OcxConfig): V2ConfigSnapshot {
97
+ return Object.fromEntries(V2_CONFIG_KEYS.map(key => [key, structuredClone(config[key])])) as V2ConfigSnapshot;
98
+ }
99
+
100
+ function setV2ConfigField(config: OcxConfig, key: V2ConfigKey, value: OcxConfig[V2ConfigKey]): void {
101
+ if (value === undefined) delete (config as unknown as Record<string, unknown>)[key];
102
+ else (config as unknown as Record<string, unknown>)[key] = structuredClone(value);
103
+ }
104
+
105
+ function sameV2ConfigField(left: unknown, right: unknown): boolean {
106
+ return JSON.stringify(left) === JSON.stringify(right);
107
+ }
108
+
109
+ function persistV2RoutedDelegationBridge(
110
+ deps: ManagementApiDeps,
111
+ config: OcxConfig,
112
+ enabled: boolean,
113
+ ): { ok: true } | { ok: false; reason: string } {
114
+ const outcome = mutateManagementConfig(deps, persisted => {
97
115
  const changed = persisted.v2RoutedDelegationBridge !== enabled;
98
116
  if (changed) persisted.v2RoutedDelegationBridge = enabled;
99
117
  return { changed, value: true };
@@ -114,10 +132,11 @@ function agentTaskRecoveryDto(
114
132
  }
115
133
 
116
134
  function persistAgentTaskRecovery(
135
+ deps: ManagementApiDeps,
117
136
  config: OcxConfig,
118
137
  next: AgentTaskRecoveryInput,
119
138
  ): { ok: true } | { ok: false; reason: string } {
120
- const outcome = mutatePersistedConfig(persisted => {
139
+ const outcome = mutateManagementConfig(deps, persisted => {
121
140
  const nextPersisted = {
122
141
  enabled: next.enabled,
123
142
  ...(next.model === null ? {} : { model: next.model }),
@@ -163,10 +182,11 @@ function v2NativeParentOverrideTargetIsNoncanonical(config: OcxConfig, model: st
163
182
  }
164
183
 
165
184
  function persistV2NativeParentOverride(
185
+ deps: ManagementApiDeps,
166
186
  config: OcxConfig,
167
187
  next: V2NativeParentOverrideInput,
168
188
  ): { ok: true } | { ok: false; reason: string } {
169
- const outcome = mutatePersistedConfig(persisted => {
189
+ const outcome = mutateManagementConfig(deps, persisted => {
170
190
  const nextPersisted = {
171
191
  enabled: next.enabled,
172
192
  ...(next.model === null ? {} : { model: next.model }),
@@ -215,10 +235,11 @@ function mirrorDesiredEnabledOntoSnapshot(config: OcxConfig, client: "claude-des
215
235
  * unrelated key another writer just committed.
216
236
  */
217
237
  function persistDesktopProfileField(
238
+ deps: ManagementApiDeps,
218
239
  config: OcxConfig,
219
240
  desktopProfile: NonNullable<OcxConfig["claudeCode"]>["desktopProfile"],
220
241
  ): { ok: true } | { ok: false; reason: "missing" | "invalid" | "conflict" } {
221
- const outcome = mutatePersistedConfig(persisted => {
242
+ const outcome = mutateManagementConfig(deps, persisted => {
222
243
  persisted.claudeCode = { ...(persisted.claudeCode ?? {}), desktopProfile };
223
244
  return { changed: true, value: true };
224
245
  });
@@ -281,7 +302,7 @@ export function setGrokApplyFlightTestHooks(
281
302
  grokApplyFlight = null;
282
303
  grokApplyHighWaterBytes = 0;
283
304
  }
284
- import type { ManagementContext } from "./context";
305
+ import { ManagementPersistenceError, MissingManagementPersistenceError, mutateManagementConfig, saveManagementConfig, type ManagementApiDeps, type ManagementContext } from "./context";
285
306
 
286
307
  export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise<Response | null> {
287
308
  const { req, url, config, deps, convergeCodexCatalog, syncClaudeAgentDefsBestEffort } = ctx;
@@ -321,7 +342,7 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
321
342
  );
322
343
  if (result.written && result.fingerprint) {
323
344
  current.claudeCode = { ...current.claudeCode, desktopProfile: { ...current.claudeCode.desktopProfile, appliedFingerprint: result.fingerprint, appliedAt: new Date().toISOString() } };
324
- saveConfigPreservingClaudeCode(current);
345
+ saveManagementConfig(deps, current);
325
346
  }
326
347
  } catch { /* best-effort */ }
327
348
  }
@@ -512,19 +533,19 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
512
533
  }
513
534
  if (wantsV2RoutedDelegationBridge && !wantsFlag && !wantsThreads && !wantsMode && !wantsKeepNative
514
535
  && !wantsAgentsEnabled && !wantsMaxDepth && !wantsSubagentInstructions && !wantsModeHintText && !wantsV2NativeParentOverride && !wantsAgentTaskRecovery) {
515
- const persisted = persistV2RoutedDelegationBridge(config, body.v2RoutedDelegationBridge as boolean);
536
+ const persisted = persistV2RoutedDelegationBridge(deps, config, body.v2RoutedDelegationBridge as boolean);
516
537
  if (!persisted.ok) return jsonResponse({ error: `persisting v2RoutedDelegationBridge failed: ${persisted.reason}` }, 502);
517
538
  return jsonResponse({ ok: true, v2RoutedDelegationBridge: config.v2RoutedDelegationBridge === true });
518
539
  }
519
540
  if (agentTaskRecovery && !wantsFlag && !wantsThreads && !wantsMode && !wantsKeepNative
520
541
  && !wantsAgentsEnabled && !wantsMaxDepth && !wantsSubagentInstructions && !wantsModeHintText && !wantsV2NativeParentOverride) {
521
- const persisted = persistAgentTaskRecovery(config, agentTaskRecovery);
542
+ const persisted = persistAgentTaskRecovery(deps, config, agentTaskRecovery);
522
543
  if (!persisted.ok) return jsonResponse({ error: `persisting agentTaskRecovery failed: ${persisted.reason}` }, 502);
523
544
  return jsonResponse({ ok: true, agentTaskRecovery: agentTaskRecoveryDto(config) });
524
545
  }
525
546
  if (v2NativeParentOverride && !wantsFlag && !wantsThreads && !wantsMode && !wantsKeepNative
526
547
  && !wantsAgentsEnabled && !wantsMaxDepth && !wantsSubagentInstructions && !wantsModeHintText) {
527
- const persisted = persistV2NativeParentOverride(config, v2NativeParentOverride);
548
+ const persisted = persistV2NativeParentOverride(deps, config, v2NativeParentOverride);
528
549
  if (!persisted.ok) return jsonResponse({ error: `persisting v2NativeParentOverride failed: ${persisted.reason}` }, 502);
529
550
  return jsonResponse({ ok: true, v2NativeParentOverride: v2NativeParentOverrideDto(config, readMultiAgentV2Enabled()) });
530
551
  }
@@ -549,6 +570,87 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
549
570
  error: "body.enabled=true conflicts with keepNativeChatGptOnV1: Codex's global multi_agent_v2 override outranks catalog pins",
550
571
  }, 400);
551
572
  }
573
+ let rollbackV2Config: (() => string | null) | undefined;
574
+ if (wantsMode || wantsKeepNative || v2NativeParentOverride || wantsV2RoutedDelegationBridge || agentTaskRecovery) {
575
+ const requestedKeys = V2_CONFIG_KEYS.filter(key => (
576
+ (key === "multiAgentMode" && wantsMode)
577
+ || (key === "keepNativeChatGptOnV1" && wantsKeepNative)
578
+ || (key === "v2NativeParentOverride" && v2NativeParentOverride !== undefined)
579
+ || (key === "v2RoutedDelegationBridge" && wantsV2RoutedDelegationBridge)
580
+ || (key === "agentTaskRecovery" && agentTaskRecovery !== undefined)
581
+ ));
582
+ let before!: V2ConfigSnapshot;
583
+ let committed!: OcxConfig;
584
+ const persisted = mutateManagementConfig(deps, disk => {
585
+ before = v2ConfigSnapshot(disk);
586
+ if (wantsMode) {
587
+ if (mode === "default") deleteConfigTopLevelKey(disk, "multiAgentMode");
588
+ else disk.multiAgentMode = mode;
589
+ }
590
+ if (wantsKeepNative) {
591
+ if (body.keepNativeChatGptOnV1 === true) disk.keepNativeChatGptOnV1 = true;
592
+ else deleteConfigTopLevelKey(disk, "keepNativeChatGptOnV1");
593
+ }
594
+ if (v2NativeParentOverride) {
595
+ disk.v2NativeParentOverride = {
596
+ enabled: v2NativeParentOverride.enabled,
597
+ ...(v2NativeParentOverride.model === null ? {} : { model: v2NativeParentOverride.model }),
598
+ };
599
+ }
600
+ if (wantsV2RoutedDelegationBridge) disk.v2RoutedDelegationBridge = body.v2RoutedDelegationBridge as boolean;
601
+ if (agentTaskRecovery) {
602
+ disk.agentTaskRecovery = {
603
+ enabled: agentTaskRecovery.enabled,
604
+ ...(agentTaskRecovery.model === null ? {} : { model: agentTaskRecovery.model }),
605
+ };
606
+ }
607
+ committed = structuredClone(disk);
608
+ return { changed: true, value: true };
609
+ });
610
+ if (persisted.status === "unavailable") {
611
+ return jsonResponse({ error: `persisting V2 settings failed: ${persisted.reason}` }, 502);
612
+ }
613
+ if (wantsMode) {
614
+ if (committed.multiAgentMode === undefined) deleteConfigTopLevelKey(config, "multiAgentMode");
615
+ else config.multiAgentMode = committed.multiAgentMode;
616
+ }
617
+ if (wantsKeepNative) {
618
+ if (committed.keepNativeChatGptOnV1 === undefined) deleteConfigTopLevelKey(config, "keepNativeChatGptOnV1");
619
+ else config.keepNativeChatGptOnV1 = committed.keepNativeChatGptOnV1;
620
+ }
621
+ if (v2NativeParentOverride) config.v2NativeParentOverride = committed.v2NativeParentOverride;
622
+ if (wantsV2RoutedDelegationBridge) config.v2RoutedDelegationBridge = committed.v2RoutedDelegationBridge;
623
+ if (agentTaskRecovery) config.agentTaskRecovery = committed.agentTaskRecovery;
624
+ const committedSnapshot = v2ConfigSnapshot(committed);
625
+ rollbackV2Config = () => {
626
+ let finalSnapshot!: V2ConfigSnapshot;
627
+ try {
628
+ const rollback = mutateManagementConfig(deps, disk => {
629
+ let changed = false;
630
+ for (const key of requestedKeys) {
631
+ if (!sameV2ConfigField(disk[key], committedSnapshot[key])) continue;
632
+ setV2ConfigField(disk, key, before[key]);
633
+ changed = true;
634
+ }
635
+ finalSnapshot = v2ConfigSnapshot(disk);
636
+ return { changed, value: true };
637
+ });
638
+ if (rollback.status === "unavailable") return rollback.reason;
639
+ } catch (error) {
640
+ return error instanceof Error ? error.message : String(error);
641
+ }
642
+ for (const key of requestedKeys) setV2ConfigField(config, key, finalSnapshot[key]);
643
+ return null;
644
+ };
645
+ }
646
+ const rollbackDiagnostic = (message: string): string => {
647
+ const failure = rollbackV2Config?.();
648
+ return failure ? `${message}; config rollback failed: ${failure}` : message;
649
+ };
650
+ const externalChanged: string[] = [];
651
+ const scalarFailureDiagnostic = (message: string): string => externalChanged.length > 0
652
+ ? `${message}; config retained because earlier external side effects were applied: ${externalChanged.join(", ")}`
653
+ : rollbackDiagnostic(message);
552
654
  const requestedFlag = wantsFlag
553
655
  ? body.enabled as boolean
554
656
  : modeFlag ?? (wantsKeepNative && hybridPinActive ? false : undefined);
@@ -562,19 +664,15 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
562
664
  const result = transitionMultiAgentV2(targetFlag, toggle, {
563
665
  ...(wantsThreads ? { threadLimit: body.maxConcurrentThreadsPerSession as number } : {}),
564
666
  });
565
- if (!result.ok) return jsonResponse({ error: `multi_agent_v2 transition failed: ${result.error}` }, 502);
667
+ if (!result.ok) return jsonResponse({ error: rollbackDiagnostic(`multi_agent_v2 transition failed: ${result.error}`) }, 502);
668
+ if (result.changed) externalChanged.push("multi_agent_v2");
566
669
  if (result.changed && result.threadLimit !== null) warnings.push(`Thread limit ${result.threadLimit} preserved for ${targetFlag ? "v2" : "v1"}.`);
567
670
  }
568
671
  if (wantsMode) {
569
- if (mode === "default") deleteConfigTopLevelKey(config, "multiAgentMode");
570
- else config.multiAgentMode = mode;
571
- saveConfigPreservingClaudeCode(config);
572
672
  warnings.push(`Multi-agent mode set to '${mode}'. Applies to new sessions.`);
573
673
  }
574
674
  if (wantsKeepNative) {
575
- if (body.keepNativeChatGptOnV1 === true) config.keepNativeChatGptOnV1 = true;
576
- else deleteConfigTopLevelKey(config, "keepNativeChatGptOnV1");
577
- saveConfigPreservingClaudeCode(config);
675
+ const effectiveMode = mode ?? config.multiAgentMode ?? "default";
578
676
  warnings.push(body.keepNativeChatGptOnV1 === true
579
677
  ? (effectiveMode === "v2"
580
678
  ? "ChatGPT-native models stay on v1 while other models use v2. Applies to new sessions."
@@ -589,21 +687,22 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
589
687
  // asserts this route file contains no direct write primitive, and matches on the
590
688
  // symbol name even inside a comment.
591
689
  const scalarWrites: Array<{ field: string; run: () => { ok: true; changed: boolean } | { ok: false; error: string } }> = [];
592
- if (wantsAgentsEnabled) scalarWrites.push({ field: "agentsEnabled", run: () => setAgentsEnabled(body.agentsEnabled as boolean | null) });
593
- if (wantsMaxDepth) scalarWrites.push({ field: "agentsMaxDepth", run: () => setAgentsMaxDepth(body.agentsMaxDepth as number | null) });
594
- if (wantsSubagentInstructions) scalarWrites.push({ field: "subagentDeveloperInstructions", run: () => setSubagentDeveloperInstructions(body.subagentDeveloperInstructions as string | null) });
595
- if (wantsModeHintText) scalarWrites.push({ field: "multiAgentModeHintText", run: () => setMultiAgentModeHintText(body.multiAgentModeHintText as string | null) });
690
+ if (wantsAgentsEnabled) scalarWrites.push({ field: "agentsEnabled", run: () => (deps.v2ScalarWriters?.setAgentsEnabled ?? setAgentsEnabled)(body.agentsEnabled as boolean | null) });
691
+ if (wantsMaxDepth) scalarWrites.push({ field: "agentsMaxDepth", run: () => (deps.v2ScalarWriters?.setAgentsMaxDepth ?? setAgentsMaxDepth)(body.agentsMaxDepth as number | null) });
692
+ if (wantsSubagentInstructions) scalarWrites.push({ field: "subagentDeveloperInstructions", run: () => (deps.v2ScalarWriters?.setSubagentDeveloperInstructions ?? setSubagentDeveloperInstructions)(body.subagentDeveloperInstructions as string | null) });
693
+ if (wantsModeHintText) scalarWrites.push({ field: "multiAgentModeHintText", run: () => (deps.v2ScalarWriters?.setMultiAgentModeHintText ?? setMultiAgentModeHintText)(body.multiAgentModeHintText as string | null) });
596
694
  const landed: string[] = [];
597
695
  for (const write of scalarWrites) {
598
696
  try {
599
697
  const result = write.run();
600
698
  if (!result.ok) {
601
- return jsonResponse({ error: `writing ${write.field} failed: ${result.error}${landed.length > 0 ? ` (already applied: ${landed.join(", ")})` : ""}` }, 502);
699
+ return jsonResponse({ error: scalarFailureDiagnostic(`writing ${write.field} failed: ${result.error}${landed.length > 0 ? ` (already applied: ${landed.join(", ")})` : ""}`) }, 502);
602
700
  }
603
701
  landed.push(write.field);
702
+ if (result.changed) externalChanged.push(write.field);
604
703
  } catch (err) {
605
704
  const message = err instanceof Error ? err.message : String(err);
606
- return jsonResponse({ error: `writing ${write.field} failed: ${message}${landed.length > 0 ? ` (already applied: ${landed.join(", ")})` : ""}` }, 502);
705
+ return jsonResponse({ error: scalarFailureDiagnostic(`writing ${write.field} failed: ${message}${landed.length > 0 ? ` (already applied: ${landed.join(", ")})` : ""}`) }, 502);
607
706
  }
608
707
  }
609
708
  // Derived from fresh post-write readers (readConfigText is uncached): upstream
@@ -612,18 +711,6 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
612
711
  if (getAgentsEnabled() === false && isMultiAgentV2Enabled()) {
613
712
  warnings.push("agents.enabled = false has no effect while features.multi_agent_v2 is enabled; upstream keeps V2 active.");
614
713
  }
615
- if (v2NativeParentOverride) {
616
- const persisted = persistV2NativeParentOverride(config, v2NativeParentOverride);
617
- if (!persisted.ok) return jsonResponse({ error: `persisting v2NativeParentOverride failed: ${persisted.reason}` }, 502);
618
- }
619
- if (wantsV2RoutedDelegationBridge) {
620
- const persisted = persistV2RoutedDelegationBridge(config, body.v2RoutedDelegationBridge as boolean);
621
- if (!persisted.ok) return jsonResponse({ error: `persisting v2RoutedDelegationBridge failed: ${persisted.reason}` }, 502);
622
- }
623
- if (agentTaskRecovery) {
624
- const persisted = persistAgentTaskRecovery(config, agentTaskRecovery);
625
- if (!persisted.ok) return jsonResponse({ error: `persisting agentTaskRecovery failed: ${persisted.reason}` }, 502);
626
- }
627
714
  const catalogRefresh = await convergeCodexCatalog();
628
715
  if (requestedFlag !== undefined) warnings.push("Applies to new sessions; restart the Codex app or wait out its picker cache to see the ladder change.");
629
716
  const enabled = isMultiAgentV2Enabled();
@@ -806,7 +893,7 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
806
893
  if (nextPrompt) config.injectionPrompt = nextPrompt;
807
894
  else deleteConfigTopLevelKey(config, "injectionPrompt");
808
895
 
809
- saveConfigPreservingClaudeCode(config);
896
+ saveManagementConfig(deps, config);
810
897
  return jsonResponse({
811
898
  ok: true,
812
899
  multiAgentGuidanceEnabled: multiAgentGuidanceEnabled(config),
@@ -841,7 +928,7 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
841
928
  }
842
929
  config[key] = value;
843
930
  }
844
- saveConfigPreservingClaudeCode(config);
931
+ saveManagementConfig(deps, config);
845
932
  return jsonResponse({ ok: true, effortCap: config.effortCap ?? null, subagentEffortCap: config.subagentEffortCap ?? null });
846
933
  }
847
934
 
@@ -887,8 +974,7 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
887
974
  try { body = await readManagementJsonBody(req); } catch (error) { rethrowManagementBodyTooLarge(error); return jsonResponse({ error: "invalid JSON body" }, 400); }
888
975
  const chosen = Array.isArray(body.models) ? body.models.filter((m): m is string => typeof m === "string").slice(0, 5) : [];
889
976
  config.subagentModels = chosen;
890
- const { saveConfigPreservingClaudeCode: save } = await import("../../config");
891
- save(config);
977
+ saveManagementConfig(deps, config);
892
978
  const catalogRefresh = await convergeCodexCatalog();
893
979
  await syncClaudeAgentDefsBestEffort();
894
980
  await autoApplyDesktopBestEffort();
@@ -935,8 +1021,7 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
935
1021
  }
936
1022
  const id = body.remove.trim();
937
1023
  config.subagentRoles = (config.subagentRoles ?? []).filter(role => role.id !== id);
938
- const { saveConfigPreservingClaudeCode: save } = await import("../../config");
939
- save(config);
1024
+ saveManagementConfig(deps, config);
940
1025
  const warnings = [...syncCodexAgentRoles(config).warnings];
941
1026
  const catalogRefresh = await convergeCodexCatalog();
942
1027
  await syncClaudeAgentDefsBestEffort();
@@ -967,8 +1052,7 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
967
1052
  if ("syncCodexAgentRoles" in body && typeof body.syncCodexAgentRoles === "boolean") {
968
1053
  config.syncCodexAgentRoles = body.syncCodexAgentRoles;
969
1054
  }
970
- const { saveConfigPreservingClaudeCode: save } = await import("../../config");
971
- save(config);
1055
+ saveManagementConfig(deps, config);
972
1056
  warnings.push(...syncCodexAgentRoles(config).warnings);
973
1057
  const catalogRefresh = await convergeCodexCatalog();
974
1058
  await syncClaudeAgentDefsBestEffort();
@@ -1045,7 +1129,7 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
1045
1129
  else deleteConfigTopLevelKey(config, "subagentModelFallback");
1046
1130
  if (nextPollMs !== undefined) config.subagentModelFallbackPollMs = nextPollMs;
1047
1131
  else deleteConfigTopLevelKey(config, "subagentModelFallbackPollMs");
1048
- saveConfigPreservingClaudeCode(config);
1132
+ saveManagementConfig(deps, config);
1049
1133
  return jsonResponse({
1050
1134
  ok: true,
1051
1135
  models: config.subagentModelFallback ?? [],
@@ -1087,7 +1171,7 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
1087
1171
  if (excluded.length > 2000) return jsonResponse({ error: "excluded list is too large" }, 400);
1088
1172
  if (excluded.length === 0) deleteConfigTopLevelKey(config, "grokExcludedModels");
1089
1173
  else config.grokExcludedModels = excluded;
1090
- saveConfigPreservingClaudeCode(config);
1174
+ saveManagementConfig(deps, config);
1091
1175
  return jsonResponse({ ok: true, excluded });
1092
1176
  }
1093
1177
 
@@ -1144,11 +1228,12 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
1144
1228
  }
1145
1229
  const state = await buildClaudeDesktopState(config, parsed);
1146
1230
  config.claudeCode = { ...(config.claudeCode ?? {}), desktopProfile: reconcileDesktopProfile(state.profile, state.models) };
1147
- saveConfigPreservingClaudeCode(config);
1231
+ saveManagementConfig(deps, config);
1148
1232
  const saved = await buildClaudeDesktopState(config);
1149
1233
  const runtimePort = Number(url.port) || config.port;
1150
1234
  return jsonResponse({ ok: true, ...saved, port: runtimePort });
1151
1235
  } catch (error) {
1236
+ if (error instanceof MissingManagementPersistenceError || error instanceof ManagementPersistenceError) throw error;
1152
1237
  return jsonResponse({ error: error instanceof Error ? error.message : String(error) }, 400);
1153
1238
  }
1154
1239
  }
@@ -1197,7 +1282,7 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
1197
1282
  // its stale `clientIntegrations` back over that write and turn the enable
1198
1283
  // action into an immediate self-cancelling OFF — the guard below would then
1199
1284
  // refuse the apply it was asked to perform. Persist ONLY the profile field.
1200
- const profileSaved = persistDesktopProfileField(config, state.profile);
1285
+ const profileSaved = persistDesktopProfileField(deps, config, state.profile);
1201
1286
  if (!profileSaved.ok) {
1202
1287
  return jsonResponse({
1203
1288
  error: `Claude Desktop profile could not be saved (${profileSaved.reason}); nothing was applied.`,
@@ -1240,7 +1325,7 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
1240
1325
  if (result.fingerprint) {
1241
1326
  // The Desktop write already landed, so a failed bookkeeping save is not
1242
1327
  // an apply failure: report the miss instead of claiming a clean apply.
1243
- const marked = persistDesktopProfileField(config, {
1328
+ const marked = persistDesktopProfileField(deps, config, {
1244
1329
  ...state.profile,
1245
1330
  appliedFingerprint: result.fingerprint,
1246
1331
  appliedAt: new Date().toISOString(),
@@ -1635,8 +1720,6 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
1635
1720
  else delete next.modelMap;
1636
1721
  }
1637
1722
  }
1638
- if (body.fastMode !== undefined) config.fastMode = nextFastMode;
1639
- config.claudeCode = next;
1640
1723
  // Stamp the migration sentinel on EVERY persist of this block. The migration reads
1641
1724
  // "a claudeCode block with no authMode" as a pre-upgrade subscriber and pins it to
1642
1725
  // literal subscription — correct for a config written before `auto` existed, fatal
@@ -1645,8 +1728,46 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
1645
1728
  // would be converted into a sticky manual subscription by the next startServer, and
1646
1729
  // auto would survive exactly one proxy lifetime with no way back.
1647
1730
  if (!next.authModeMigratedAt) next.authModeMigratedAt = new Date().toISOString();
1648
- const { saveConfigPreservingClaudeCode: save } = await import("../../config");
1649
- save(config);
1731
+ let committedClaude!: OcxClaudeCodeConfig;
1732
+ const persisted = mutateManagementConfig(deps, disk => {
1733
+ const latest = { ...(disk.claudeCode ?? {}) };
1734
+ for (const field of ["enabled", "authMode", "model", "smallFastModel", "modelMap", "classifierModel", "classifierFallbacks", "systemEnv", "alwaysEnableEffort", "maxContextTokens", "autoContext", "injectAgents", "autoCompactWindow", "blockedSkills", "tierModels"] as const) {
1735
+ if (!Object.hasOwn(body, field)) continue;
1736
+ if (Object.hasOwn(next, field)) latest[field] = next[field] as never;
1737
+ else delete latest[field];
1738
+ }
1739
+ for (const field of ["webSearchSidecar", "visionSidecar"] as const) {
1740
+ const section = body[field];
1741
+ if (section === undefined) continue;
1742
+ if (section === null || Object.keys(section as Record<string, unknown>).length === 0) {
1743
+ delete latest[field];
1744
+ continue;
1745
+ }
1746
+ const override = { ...latest[field] } as { backend?: string; model?: string };
1747
+ const desired = next[field] as { backend?: string; model?: string } | undefined;
1748
+ for (const key of ["backend", "model"] as const) {
1749
+ if (!Object.hasOwn(section, key)) continue;
1750
+ if (Object.hasOwn(desired ?? {}, key)) override[key] = desired![key];
1751
+ else delete override[key];
1752
+ }
1753
+ if (Object.keys(override).length > 0) latest[field] = override as never;
1754
+ else delete latest[field];
1755
+ }
1756
+ latest.authModeMigratedAt = next.authModeMigratedAt;
1757
+ disk.claudeCode = latest;
1758
+ committedClaude = structuredClone(latest);
1759
+ if (body.fastMode !== undefined) {
1760
+ if (nextFastMode === undefined) delete disk.fastMode;
1761
+ else disk.fastMode = nextFastMode;
1762
+ }
1763
+ return { changed: true, value: true };
1764
+ });
1765
+ if (persisted.status === "unavailable") return jsonResponse({ error: "management persistence unavailable" }, 500, req, config);
1766
+ config.claudeCode = committedClaude;
1767
+ if (body.fastMode !== undefined) {
1768
+ if (nextFastMode === undefined) deleteConfigTopLevelKey(config, "fastMode");
1769
+ else config.fastMode = nextFastMode;
1770
+ }
1650
1771
  const warnings: string[] = [];
1651
1772
  // authMode changes must reconcile the injected system env too: switching back to
1652
1773
  // Subscription has to remove the opencodex-owned dummy ANTHROPIC_AUTH_TOKEN
@@ -11,7 +11,6 @@ import {
11
11
  multiAgentGuidanceEnabled,
12
12
  providerBaseUrlConfigError,
13
13
  providerHeadersConfigError,
14
- saveConfigPreservingClaudeCode,
15
14
  } from "../../config";
16
15
  import {
17
16
  clearLoginState,
@@ -63,7 +62,7 @@ import { applySystemEnvToggle } from "../system-env";
63
62
 
64
63
  import { isPlainRecord, parseDebugLogQuery, tokPerSecondResult, unavailableCostReason, costResult, requestLogDto, stripRegistryOnlyStaticHeaders, fetchAllModels } from "./shared";
65
64
  import type { MetricUnavailableReason, TokPerSecondResult, CostEstimateReason, CostResult, MetricSource } from "./shared";
66
- import type { ManagementContext } from "./context";
65
+ import { saveManagementConfig, type ManagementContext } from "./context";
67
66
  import { readManagementJsonBody, rethrowManagementBodyTooLarge } from "./body";
68
67
 
69
68
 
@@ -236,7 +235,7 @@ export async function handleComboRoutes(ctx: ManagementContext): Promise<Respons
236
235
  oldDisabledSelectors.has(model) ? newDisabledModel : model
237
236
  )))];
238
237
  }
239
- saveConfigPreservingClaudeCode(config);
238
+ saveManagementConfig(deps, config);
240
239
  reconcileLiveStateStores();
241
240
  clearComboSelectionState(id);
242
241
  clearComboTargetCooldowns(id);
@@ -259,7 +258,7 @@ export async function handleComboRoutes(ctx: ManagementContext): Promise<Respons
259
258
  const { clearComboSelectionState, clearComboTargetCooldowns } = await import("../../combos");
260
259
  delete config.combos![id];
261
260
  if (Object.keys(config.combos!).length === 0) deleteConfigTopLevelKey(config, "combos");
262
- saveConfigPreservingClaudeCode(config);
261
+ saveManagementConfig(deps, config);
263
262
  reconcileLiveStateStores();
264
263
  clearComboSelectionState(id);
265
264
  clearComboTargetCooldowns(id);
@@ -11,7 +11,6 @@ import {
11
11
  multiAgentGuidanceEnabled,
12
12
  providerBaseUrlConfigError,
13
13
  providerHeadersConfigError,
14
- saveConfigPreservingClaudeCode,
15
14
  } from "../../config";
16
15
  import {
17
16
  clearLoginState,
@@ -102,7 +101,7 @@ import { displayCodexRuntimePath, effortClampAppliesToRuntime, loadLastEffortCla
102
101
 
103
102
  import { isPlainRecord, parseDebugLogQuery, tokPerSecondResult, unavailableCostReason, costResult, requestLogDto, stripRegistryOnlyStaticHeaders, fetchAllModels } from "./shared";
104
103
  import type { MetricUnavailableReason, TokPerSecondResult, CostEstimateReason, CostResult, MetricSource } from "./shared";
105
- import type { ManagementContext } from "./context";
104
+ import { mutateManagementConfig, saveManagementConfig, type ManagementContext } from "./context";
106
105
  import { readManagementJsonBody, rethrowManagementBodyTooLarge } from "./body";
107
106
 
108
107
  async function sidecarVisionResponseSettings(config: OcxConfig): Promise<{
@@ -469,7 +468,7 @@ export async function handleConfigRoutes(ctx: ManagementContext): Promise<Respon
469
468
  config.showCodexSparkQuota = body.showCodexSparkQuota;
470
469
  }
471
470
  pickerIsEnabled = codexAccountPickerEnabled(config);
472
- (deps.saveConfigPreservingClaudeCode ?? saveConfigPreservingClaudeCode)(config);
471
+ saveManagementConfig(deps, config);
473
472
  } catch (error) {
474
473
  if (previousSettings.hasCodexAutoStart) config.codexAutoStart = previousSettings.codexAutoStart;
475
474
  else deleteConfigTopLevelKey(config, "codexAutoStart");
@@ -698,6 +697,7 @@ export async function handleConfigRoutes(ctx: ManagementContext): Promise<Respon
698
697
  : normalizeVisionReasoningForModel(model, sourceReasoning);
699
698
  }
700
699
 
700
+ const nextConfig = structuredClone(config);
701
701
  if (body.webSearch) {
702
702
  const pairTouched = body.webSearch.model !== undefined || body.webSearch.backend !== undefined;
703
703
  // Validate against the backend the caller SUBMITTED, across the whole
@@ -723,7 +723,7 @@ export async function handleConfigRoutes(ctx: ManagementContext): Promise<Respon
723
723
  return jsonResponse(webSearchModelRejection("webSearch.model", effectiveBackend, effectiveModel, candidates), 400);
724
724
  }
725
725
  }
726
- const webSearchCandidate = { ...config.webSearchSidecar };
726
+ const webSearchCandidate = { ...nextConfig.webSearchSidecar };
727
727
  if (typeof body.webSearch.model === "string") {
728
728
  if (body.webSearch.model === "") delete webSearchCandidate.model;
729
729
  else webSearchCandidate.model = body.webSearch.model;
@@ -793,36 +793,63 @@ export async function handleConfigRoutes(ctx: ManagementContext): Promise<Respon
793
793
  if (body.webSearch.streamRoutedModelOutput) webSearchCandidate.streamRoutedModelOutput = true;
794
794
  else delete webSearchCandidate.streamRoutedModelOutput;
795
795
  }
796
- config.webSearchSidecar = webSearchCandidate;
796
+ nextConfig.webSearchSidecar = webSearchCandidate;
797
797
  }
798
798
  if (body.vision) {
799
- config.visionSidecar = { ...config.visionSidecar };
799
+ nextConfig.visionSidecar = { ...nextConfig.visionSidecar };
800
800
  if (typeof body.vision.model === "string") {
801
- if (body.vision.model === "") delete config.visionSidecar.model;
802
- else config.visionSidecar.model = body.vision.model;
801
+ if (body.vision.model === "") delete nextConfig.visionSidecar.model;
802
+ else nextConfig.visionSidecar.model = body.vision.model;
803
803
  }
804
- if (body.vision.backend === null) delete config.visionSidecar.backend;
804
+ if (body.vision.backend === null) delete nextConfig.visionSidecar.backend;
805
805
  else if (body.vision.backend === "openai" || body.vision.backend === "anthropic"
806
806
  || body.vision.backend === "routed") {
807
- config.visionSidecar.backend = body.vision.backend;
807
+ nextConfig.visionSidecar.backend = body.vision.backend;
808
808
  }
809
809
  if (typeof body.vision.maxDescriptionsPerTurn === "number") {
810
- config.visionSidecar.maxDescriptionsPerTurn = body.vision.maxDescriptionsPerTurn;
810
+ nextConfig.visionSidecar.maxDescriptionsPerTurn = body.vision.maxDescriptionsPerTurn;
811
811
  }
812
812
  if (typeof body.vision.enabled === "boolean") {
813
813
  // `true` is the default — drop the key so disable/re-enable does not rewrite the file.
814
- if (body.vision.enabled) delete config.visionSidecar.enabled;
815
- else config.visionSidecar.enabled = false;
814
+ if (body.vision.enabled) delete nextConfig.visionSidecar.enabled;
815
+ else nextConfig.visionSidecar.enabled = false;
816
816
  }
817
817
  if (typeof body.vision.timeoutMs === "number") {
818
- config.visionSidecar.timeoutMs = body.vision.timeoutMs;
818
+ nextConfig.visionSidecar.timeoutMs = body.vision.timeoutMs;
819
819
  }
820
820
  if (visionReasoningTouched) {
821
- if (normalizedVisionReasoning === undefined) delete config.visionSidecar.reasoning;
822
- else config.visionSidecar.reasoning = normalizedVisionReasoning;
821
+ if (normalizedVisionReasoning === undefined) delete nextConfig.visionSidecar.reasoning;
822
+ else nextConfig.visionSidecar.reasoning = normalizedVisionReasoning;
823
823
  }
824
824
  }
825
- saveConfigPreservingClaudeCode(config);
825
+ let committedWebSearch: OcxConfig["webSearchSidecar"];
826
+ let committedVision: OcxConfig["visionSidecar"];
827
+ const persisted = mutateManagementConfig(deps, disk => {
828
+ if (body.webSearch) {
829
+ const latest = { ...disk.webSearchSidecar };
830
+ for (const key of ["model", "backend", "reasoning", "streamRoutedModelOutput", "exaApiKey", "xSearch"] as const) {
831
+ if (!Object.hasOwn(body.webSearch, key)) continue;
832
+ if (Object.hasOwn(nextConfig.webSearchSidecar ?? {}, key)) latest[key] = nextConfig.webSearchSidecar![key] as never;
833
+ else delete latest[key];
834
+ }
835
+ disk.webSearchSidecar = latest;
836
+ committedWebSearch = structuredClone(latest);
837
+ }
838
+ if (body.vision) {
839
+ const latest = { ...disk.visionSidecar };
840
+ for (const key of ["model", "backend", "reasoning", "maxDescriptionsPerTurn", "enabled", "timeoutMs"] as const) {
841
+ if (!Object.hasOwn(body.vision, key) && !(key === "reasoning" && visionReasoningTouched)) continue;
842
+ if (Object.hasOwn(nextConfig.visionSidecar ?? {}, key)) latest[key] = nextConfig.visionSidecar![key] as never;
843
+ else delete latest[key];
844
+ }
845
+ disk.visionSidecar = latest;
846
+ committedVision = structuredClone(latest);
847
+ }
848
+ return { changed: true, value: true };
849
+ });
850
+ if (persisted.status === "unavailable") return jsonResponse({ error: "management persistence unavailable" }, 500, req, config);
851
+ if (body.webSearch) config.webSearchSidecar = committedWebSearch;
852
+ if (body.vision) config.visionSidecar = committedVision;
826
853
  const ws = config.webSearchSidecar ?? {};
827
854
  const vision = await sidecarVisionResponseSettings(config);
828
855
  const savedWebSearchCandidates = await webSearchCandidateRows(config);
@@ -869,7 +896,7 @@ export async function handleConfigRoutes(ctx: ManagementContext): Promise<Respon
869
896
  if (body.model === "") delete config.shadowCallIntercept.model;
870
897
  else config.shadowCallIntercept.model = body.model;
871
898
  }
872
- saveConfigPreservingClaudeCode(config);
899
+ saveManagementConfig(deps, config);
873
900
  const sci = config.shadowCallIntercept;
874
901
  return jsonResponse({
875
902
  ok: true,