@bitkyc08/opencodex 2.53.0-preview.20260913 → 2.54.0-preview.20260914

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 (34) hide show
  1. package/gui/dist/assets/{index-D7ynYo2K.js → index-B4VYfZcY.js} +2 -2
  2. package/gui/dist/index.html +1 -1
  3. package/package.json +1 -1
  4. package/src/adapters/devin.ts +39 -7
  5. package/src/cli/capabilities.ts +4 -2
  6. package/src/cli/catalog.ts +39 -10
  7. package/src/cli/dispatch.ts +19 -64
  8. package/src/cli/doctor.ts +1 -1
  9. package/src/cli/internal-command.ts +44 -0
  10. package/src/cli/registry.ts +9 -7
  11. package/src/cli/restart-scope.ts +184 -0
  12. package/src/codex/app-server-processes.ts +20 -5
  13. package/src/codex/app-server-restart-service.ts +29 -0
  14. package/src/codex/catalog/provider-fetch.ts +13 -5
  15. package/src/codex/catalog/sync.ts +12 -3
  16. package/src/codex/desktop-app/darwin.ts +268 -0
  17. package/src/codex/desktop-app/handoff.ts +303 -0
  18. package/src/codex/desktop-app/linux.ts +388 -0
  19. package/src/codex/desktop-app/lock.ts +226 -0
  20. package/src/codex/desktop-app/types.ts +141 -0
  21. package/src/codex/desktop-app/windows.ts +239 -0
  22. package/src/codex/desktop-app-restart.ts +264 -279
  23. package/src/codex/inject.ts +83 -21
  24. package/src/codex/sync.ts +16 -22
  25. package/src/generated/compatibility-version.json +58 -22
  26. package/src/lib/codex-restart-contract.ts +31 -0
  27. package/src/providers/registry.ts +13 -12
  28. package/src/server/responses/core.ts +62 -4
  29. package/src/server/responses/encrypted-payload.ts +161 -0
  30. package/src/server/responses.ts +1 -1
  31. package/src/types/provider.ts +6 -5
  32. package/src/web-search/index.ts +14 -67
  33. package/src/web-search/passthrough-bridge.ts +256 -32
  34. package/src/web-search/sidecar-providers.ts +76 -0
@@ -895,7 +895,13 @@ export function chooseCatalogPathForInjection(
895
895
  export interface CodexInjectResult {
896
896
  success: boolean;
897
897
  message: string;
898
- /** Structured read-only history preflight refusal; never parsed from display text. */
898
+ /**
899
+ * Structured read-only history preflight refusal; never parsed from display text.
900
+ *
901
+ * On the apply direction this reports that the conversation-history relabel unit stood
902
+ * down while the config was still written, so a caller must not read it as a failure.
903
+ * The restore and remove directions still refuse outright and say so in `message`.
904
+ */
899
905
  historyPreflightFailureReason?: string;
900
906
  status?: "skipped";
901
907
  /** `hub-gated` is the hub-role gate (#4236), distinct from the user's own OFF switch. */
@@ -903,6 +909,12 @@ export interface CodexInjectResult {
903
909
  nativeSubagentDefaultsWarning?: string;
904
910
  }
905
911
 
912
+ /**
913
+ * The one history preflight reason that is permanent rather than operational: Codex owns
914
+ * paginated rollout ordinals, so no retry makes the legacy relabel protocol available again.
915
+ */
916
+ const HISTORY_RELABEL_STANDS_DOWN = "history_paginated_requires_native_writer";
917
+
906
918
  class CodexHistoryPreflightRefusal extends Error {}
907
919
  class CodexRestoreRefusal extends Error {
908
920
  constructor(readonly config: CodexRestoreConfigResult) {
@@ -929,7 +941,7 @@ export async function injectCodexConfig(
929
941
  ): Promise<CodexInjectResult> {
930
942
  try { return await injectCodexConfigImpl(port, config, options); }
931
943
  catch (error) {
932
- if (error instanceof CodexHistoryPreflightRefusal) return { success: false, message: `Codex config injection refused: ${error.message}. Existing configuration and history were preserved.` };
944
+ if (error instanceof CodexHistoryPreflightRefusal) return { success: false, historyPreflightFailureReason: error.message, message: `Codex config injection refused: ${error.message}. Existing configuration and history were preserved.` };
933
945
  throw error;
934
946
  }
935
947
  }
@@ -1055,7 +1067,10 @@ async function injectCodexConfigImpl(
1055
1067
  journaledInjectedOpenaiBaseUrl({ readOnly: !!options.beforeClientWrite }),
1056
1068
  journaledInjectedRealtimeWsBaseUrl({ readOnly: !!options.beforeClientWrite }),
1057
1069
  );
1058
- if (hasOcxProviderTable(content)) {
1070
+ // Whether this home already published the provider id that its thread rows may reference.
1071
+ // Design B strips the table below; it may only stay stripped if those rows can be relabeled.
1072
+ const hadOcxProviderTableOnDisk = hasOcxProviderTable(content);
1073
+ if (hadOcxProviderTableOnDisk) {
1059
1074
  content = removeOcxSection(content);
1060
1075
  }
1061
1076
  content = removeProfileSection(content);
@@ -1190,16 +1205,56 @@ async function injectCodexConfigImpl(
1190
1205
  return "history_injection_preflight_unavailable";
1191
1206
  }
1192
1207
  };
1193
- const historyPreflightError = historyPreflight();
1194
- if (historyPreflightError) {
1208
+ /*
1209
+ * ONE refusal stands the relabel unit down instead of vetoing the config transition, and
1210
+ * only because it is permanent. Codex allocates paginated rollout ordinals in its own
1211
+ * writer, so `assertLegacyHistoryRecord` refuses every rollout on a current install and no
1212
+ * amount of retrying changes that. While it vetoed the write, `model_catalog_json` never
1213
+ * reached config.toml, so the app and the CLI both fell back to their built-in model list
1214
+ * while `ocx sync` still reported success.
1215
+ *
1216
+ * Every other reason — an unreadable state database, a rollout whose identity changed, a
1217
+ * preflight that could not run — describes a store that may well be relabelable on the next
1218
+ * attempt. Treating those as a stand-down would record the transition as converged and
1219
+ * suppress the relabel permanently, so they keep the hard refusal and the rollback.
1220
+ */
1221
+ /*
1222
+ * Re-observed inside the artifact transaction. A store that migrates to paginated history
1223
+ * mid-write retires the relabel unit, because the config half writes no history and rolling
1224
+ * it back is what left every paginated home with no OpenCodex models. Any other reason is
1225
+ * still treated as a failed transition so compensation can restore the pre-images.
1226
+ */
1227
+ const observeHistoryRefusalOrThrow = (known: string | null): string | null => {
1228
+ if (known) return known;
1229
+ const observed = historyPreflight();
1230
+ if (observed && observed !== HISTORY_RELABEL_STANDS_DOWN) throw new CodexHistoryPreflightRefusal(observed);
1231
+ return observed;
1232
+ };
1233
+ const observedHistoryRefusal = historyPreflight();
1234
+ if (observedHistoryRefusal && observedHistoryRefusal !== HISTORY_RELABEL_STANDS_DOWN) {
1195
1235
  return {
1196
1236
  success: false,
1197
- historyPreflightFailureReason: historyPreflightError,
1198
- message: `Codex config injection refused: ${historyPreflightError}. `
1237
+ historyPreflightFailureReason: observedHistoryRefusal,
1238
+ message: `Codex config injection refused: ${observedHistoryRefusal}. `
1199
1239
  + "Existing provider definitions and conversation files were preserved. "
1200
1240
  + "Paginated history requires native-writer coordination; do not run legacy recovery or retry this transition blindly.",
1201
1241
  };
1202
1242
  }
1243
+ let historyRelabelRefusal = observedHistoryRefusal;
1244
+
1245
+ /*
1246
+ * Rows this home may have tagged `opencodex` resolve only through a provider table. Design B
1247
+ * normally retires that table because the relabel migrates those rows back to `openai` in
1248
+ * the same pass; with the relabel stood down, stripping it anyway would leave every such
1249
+ * conversation pointing at a provider id that no longer exists. Keep what was already
1250
+ * published, and keep it BEFORE the witness so the lock admits the bytes actually written.
1251
+ */
1252
+ if (historyRelabelRefusal && hadOcxProviderTableOnDisk && !providerTableMode) {
1253
+ content = applyEol(
1254
+ content.trimEnd() + "\n" + buildProviderTableBlockForTarget(routingTarget, websocketsEnabled(config ?? {})),
1255
+ eol,
1256
+ );
1257
+ }
1203
1258
 
1204
1259
  /*
1205
1260
  * The witness, built from the FINAL bytes. Everything it hashes is either the
@@ -1286,14 +1341,14 @@ async function injectCodexConfigImpl(
1286
1341
  if (options.validateOnly) {
1287
1342
  return {
1288
1343
  success: true,
1344
+ ...(historyRelabelRefusal ? { historyPreflightFailureReason: historyRelabelRefusal } : {}),
1289
1345
  message: "Codex config injection preflight passed; no files were changed.",
1290
1346
  };
1291
1347
  }
1292
1348
 
1293
1349
  const applyNativeArtifacts = (): void => {
1294
1350
  beforeHistoryArtifactCommitForTests?.(eligibility.kind);
1295
- const historyError = historyPreflight();
1296
- if (historyError) throw new CodexHistoryPreflightRefusal(historyError);
1351
+ historyRelabelRefusal = observeHistoryRefusalOrThrow(historyRelabelRefusal);
1297
1352
  const preImages = captureCodexPreImages();
1298
1353
  try {
1299
1354
  historyArtifactStageForTests?.("after-preflight");
@@ -1329,9 +1384,7 @@ async function injectCodexConfigImpl(
1329
1384
  });
1330
1385
  historyArtifactStageForTests?.("after-artifacts");
1331
1386
  // Detect migration throughout the artifact transaction, not just at entry.
1332
- // This is compensation, not a native-writer lock or permission to append ordinals.
1333
- const finalHistoryError = historyPreflight();
1334
- if (finalHistoryError) throw new CodexHistoryPreflightRefusal(finalHistoryError);
1387
+ historyRelabelRefusal = observeHistoryRefusalOrThrow(historyRelabelRefusal);
1335
1388
  } catch (error) {
1336
1389
  const compensated = restoreCodexPreImages(preImages);
1337
1390
  if (!compensated.complete) throw new CodexPartialWriteError(compensated.unrestored);
@@ -1487,15 +1540,19 @@ async function injectCodexConfigImpl(
1487
1540
  // serialized one of them and an opposite-direction process could overtake
1488
1541
  // through the other two. The operation is derived from admitted intent here and
1489
1542
  // handed down fixed; the Worker never takes a direction from its caller.
1490
- const historyOutcome = await runCodexHistoryJob({
1491
- ...resolveCodexHistoryJobTarget(),
1492
- expectedDesiredEnabled: true,
1493
- operation: deriveCodexHistoryOperation({
1494
- direction: "apply",
1495
- resumeHistory: config?.syncResumeHistory !== false && !keepRootOverrideAlongsideTable,
1496
- legacyMode: providerTableMode,
1497
- }),
1498
- });
1543
+ // A stood-down relabel unit spawns no Worker: the preflight it would run first has
1544
+ // already refused, and the config half is committed either way.
1545
+ const historyOutcome: CodexHistoryJobOutcome = historyRelabelRefusal
1546
+ ? { kind: "skipped" }
1547
+ : await runCodexHistoryJob({
1548
+ ...resolveCodexHistoryJobTarget(),
1549
+ expectedDesiredEnabled: true,
1550
+ operation: deriveCodexHistoryOperation({
1551
+ direction: "apply",
1552
+ resumeHistory: config?.syncResumeHistory !== false && !keepRootOverrideAlongsideTable,
1553
+ legacyMode: providerTableMode,
1554
+ }),
1555
+ });
1499
1556
  // A blocked or failed unit is reported, not silently counted as zero work:
1500
1557
  // `failed` is what makes the caller's message say so.
1501
1558
  const history: { rows: number; files: number; failed?: true } =
@@ -1528,6 +1585,8 @@ async function injectCodexConfigImpl(
1528
1585
  ? (keptUserBaseUrl
1529
1586
  ? ` Codex resume history: left unchanged; threads already tagged openai follow your configured root openai_base_url.\n`
1530
1587
  : ` Codex resume history: left unchanged; existing threads keep reaching the proxy through the retained openai_base_url override.\n`)
1588
+ : historyRelabelRefusal
1589
+ ? ` ⚠️ Codex resume history: left to Codex's native writer (${historyRelabelRefusal}); existing threads keep the provider they are tagged with. Routing and the model catalog were still installed, so new threads reach the proxy.\n`
1531
1590
  : config?.syncResumeHistory === false
1532
1591
  ? ` Codex resume history: left unchanged (syncResumeHistory=false).\n`
1533
1592
  : history.failed
@@ -1548,6 +1607,7 @@ async function injectCodexConfigImpl(
1548
1607
  return {
1549
1608
  success: true,
1550
1609
  ...(nativeSubagentDefaultsWarning ? { nativeSubagentDefaultsWarning } : {}),
1610
+ ...(historyRelabelRefusal ? { historyPreflightFailureReason: historyRelabelRefusal } : {}),
1551
1611
  message:
1552
1612
  `Injected opencodex as default provider into Codex config (client-side compaction mode; ChatGPT auth remains required).\n` +
1553
1613
  ` Your root openai_base_url was left exactly as you set it, so opencodex did not add its own.\n` +
@@ -1566,6 +1626,7 @@ async function injectCodexConfigImpl(
1566
1626
  ...(nativeSubagentDefaultsWarning
1567
1627
  ? { nativeSubagentDefaultsWarning }
1568
1628
  : {}),
1629
+ ...(historyRelabelRefusal ? { historyPreflightFailureReason: historyRelabelRefusal } : {}),
1569
1630
  message:
1570
1631
  `⚠️ Codex routing NOT injected: your config already sets a root openai_base_url, and opencodex never overwrites a user-owned override.\n` +
1571
1632
  catalogMessage +
@@ -1585,6 +1646,7 @@ async function injectCodexConfigImpl(
1585
1646
  return {
1586
1647
  success: true,
1587
1648
  ...(nativeSubagentDefaultsWarning ? { nativeSubagentDefaultsWarning } : {}),
1649
+ ...(historyRelabelRefusal ? { historyPreflightFailureReason: historyRelabelRefusal } : {}),
1588
1650
  message:
1589
1651
  headline +
1590
1652
  catalogMessage +
package/src/codex/sync.ts CHANGED
@@ -19,7 +19,7 @@ export interface CodexSyncResult {
19
19
  /**
20
20
  * `skipped` is policy truth, never evidence that Codex was written.
21
21
  * `catalog-only` means an explicit sync refreshed the catalog/cache while
22
- * config/history injection was skipped (OFF, externally owned, or protected history).
22
+ * config/history injection was skipped (integration OFF, or externally owned).
23
23
  */
24
24
  status: "applied" | "skipped" | "catalog-only" | "refused";
25
25
  ok: boolean;
@@ -51,8 +51,9 @@ export interface CodexSyncOptions {
51
51
  * Explicit `ocx sync` is also the refresh path for side profiles that consume
52
52
  * the OpenCodex catalog without injection. When set, the sync still refreshes
53
53
  * the catalog and models cache even if the Codex integration toggle is OFF or
54
- * an external `model_provider` owns config.toml, or paginated history refuses
55
- * injection. Config/history injection is skipped in those cases.
54
+ * an external `model_provider` owns config.toml. Config/history injection is
55
+ * skipped in those two cases. A paginated-history refusal is NOT one of them:
56
+ * the injector writes config and stands only its relabel unit down.
56
57
  */
57
58
  catalogEvenWhenNotInjected?: boolean;
58
59
  }
@@ -209,25 +210,12 @@ export async function syncModelsToCodex(
209
210
  // working catalog/cache into the partial result of an otherwise unnecessary refresh.
210
211
  const preflight = await deps.injectCodexConfig(p, config, { validateOnly: true });
211
212
  if (!preflight.success) {
212
- // Explicit model refresh does not require legacy history relabeling. Keep the
213
- // injector's refusal intact and publish only through the existing catalog owner.
214
- // Unattended sync and other config/integrity refusals retain their hard failure.
215
- if (catalogEvenWhenNotInjected
216
- && preflight.historyPreflightFailureReason === "history_paginated_requires_native_writer") {
217
- applyProxyEnv(config);
218
- const refreshed = await refreshCatalogForSync(config, deps, undefined, log);
219
- const ok = refreshed.refreshOutcome === "committed" && refreshed.catalogExists;
220
- const message = ok
221
- ? "Model catalog synchronized; Codex config and conversation history left unchanged because paginated history requires its native writer."
222
- : "Model catalog refresh did not complete; Codex config and conversation history were left unchanged.";
223
- reportCodexHomeTarget(log, deps.collectCodexHomeDiagnostic ?? collectOrcaCodexHomeDiagnostic);
224
- return {
225
- ...refreshed,
226
- status: "catalog-only",
227
- ok,
228
- message,
229
- };
230
- }
213
+ // A paginated-history refusal used to be downgraded here to a `catalog-only` success.
214
+ // That is what made the model picker regression silent: the catalog file was rewritten
215
+ // and reported synchronized while config.toml kept no provider table and no catalog
216
+ // path, so Codex offered only its native models. The injector now writes config and
217
+ // stands the relabel unit down instead, so nothing reaches this branch for that reason
218
+ // and a surviving refusal is a real config/integrity failure again.
231
219
  log?.error(preflight.message);
232
220
  reportCodexHomeTarget(log, deps.collectCodexHomeDiagnostic ?? collectOrcaCodexHomeDiagnostic);
233
221
  return {
@@ -300,6 +288,12 @@ export async function syncModelsToCodex(
300
288
  }
301
289
  if (result.success) log?.log(result.message);
302
290
  else log?.error(result.message);
291
+ // The config was written; only the relabel unit stood down. Carry that as a warning so a
292
+ // caller reading the structured result sees it without parsing the display message.
293
+ if (result.success && result.historyPreflightFailureReason) {
294
+ const historyWarning = `Codex conversation-history relabel left to Codex's native writer: ${result.historyPreflightFailureReason}.`;
295
+ warning = warning ? `${warning} ${historyWarning}` : historyWarning;
296
+ }
303
297
  reportCodexHomeTarget(log, deps.collectCodexHomeDiagnostic ?? collectOrcaCodexHomeDiagnostic);
304
298
  const projectConfigWarnings = printProjectCodexConfigWarnings(log, { cwd: process.cwd() });
305
299
  return {
@@ -10,7 +10,7 @@
10
10
  },
11
11
  {
12
12
  "path": "package.json",
13
- "sha256": "7a7865fff1b6d029ee2b16296a4fca48963a83b7cb2257b37a1398ad35bd4ec3"
13
+ "sha256": "a640f2216bdb5ae42aee20beb16d273e59e4b359f9953f1b6185fed066db5d78"
14
14
  },
15
15
  {
16
16
  "path": "scripts/model-metadata.source.json",
@@ -266,7 +266,7 @@
266
266
  },
267
267
  {
268
268
  "path": "src/adapters/devin.ts",
269
- "sha256": "2700deb66978a5f20c553bb4d32d56d1645244862f50c6ca38fd806ccc9b244c"
269
+ "sha256": "15a82837e1d54e5de20b9ed37cb4f450534d907ecdaa08d29632787f7a38cd7c"
270
270
  },
271
271
  {
272
272
  "path": "src/adapters/devin/cloud-direct/auth.ts",
@@ -698,7 +698,7 @@
698
698
  },
699
699
  {
700
700
  "path": "src/cli/capabilities.ts",
701
- "sha256": "543e2fc9fa9406253b246eaced033d2ffa6753897d1eb8315e709018b78c5ca2"
701
+ "sha256": "eb069b79507e91300e449987d5adfae9e5637a5f70141f204e6f1e6c7ea38526"
702
702
  },
703
703
  {
704
704
  "path": "src/cli/catalog-prewarm.ts",
@@ -706,7 +706,7 @@
706
706
  },
707
707
  {
708
708
  "path": "src/cli/catalog.ts",
709
- "sha256": "05a9c86acbb1b88b08c667cc4ab56d59abb991cb7975e72d878e9ceb351b132d"
709
+ "sha256": "3ba859e525b04202b97abf62590c041984053db84510eb981a4910e7bcc3b43a"
710
710
  },
711
711
  {
712
712
  "path": "src/cli/claude-agent-startup-sync.ts",
@@ -754,11 +754,11 @@
754
754
  },
755
755
  {
756
756
  "path": "src/cli/dispatch.ts",
757
- "sha256": "1a39f4e792df8b0a626142d867e38035e82b307eea70f8bf75b905a92d334a98"
757
+ "sha256": "3c52a4d65ac960dfbe25c89fa82fc770d171cda0f53f3a3e469823c6207e4f4b"
758
758
  },
759
759
  {
760
760
  "path": "src/cli/doctor.ts",
761
- "sha256": "fa6a405bd00e4d00de038d3cffd99656978f836a6bc5135dcdeed65c7917d18a"
761
+ "sha256": "57e91bb3223ff95bde1c57b80a2674dbce8a4d403919a63a319ae4e2f96355bd"
762
762
  },
763
763
  {
764
764
  "path": "src/cli/effort.ts",
@@ -808,6 +808,10 @@
808
808
  "path": "src/cli/interactive-confirm.ts",
809
809
  "sha256": "9fdc081a5f8e7a8c74612371e044f34054151a20b9692c0fd81a2bb41e86f9e5"
810
810
  },
811
+ {
812
+ "path": "src/cli/internal-command.ts",
813
+ "sha256": "b8a181fb5276380bf322fe38d7fd0202a0e976491f3148bc357b485282132b57"
814
+ },
811
815
  {
812
816
  "path": "src/cli/lab.ts",
813
817
  "sha256": "1a9b3f10824fabd4c4bdb192a9a2dcaafd3f3aea6b840f46330e2202906aeda9"
@@ -858,12 +862,16 @@
858
862
  },
859
863
  {
860
864
  "path": "src/cli/registry.ts",
861
- "sha256": "db8c95de3798b82efa6a32e6a3a934679e782ef8b063a581bf013336262f1eaf"
865
+ "sha256": "c4c80108b8746765f4e0a1d741d4605984788161bf5562ef4459201dc137b016"
862
866
  },
863
867
  {
864
868
  "path": "src/cli/remote-workspace.ts",
865
869
  "sha256": "1f3d9a38a6c8ade4b567f6d7b010e9d6e71691fabd9f393fdedfdf6958e93339"
866
870
  },
871
+ {
872
+ "path": "src/cli/restart-scope.ts",
873
+ "sha256": "3d0dcc412b9695b4cb83d7da06dcd38cba2b8bb1ed56e6c6d8977f752341699f"
874
+ },
867
875
  {
868
876
  "path": "src/cli/root.ts",
869
877
  "sha256": "19c8d84e3d829d07a9ac8e3115e0cd1a3f3fd7d04416aa8a4754a057104b1ee2"
@@ -1074,11 +1082,11 @@
1074
1082
  },
1075
1083
  {
1076
1084
  "path": "src/codex/app-server-processes.ts",
1077
- "sha256": "23a816702a01f0a694cd1592af5bb90e600d4423285693854f8de9c4f1c41ff6"
1085
+ "sha256": "9e0d8d1ae92bbcb6cf82d16ebde3a06021e527a48100a78819a06ca0fe94478f"
1078
1086
  },
1079
1087
  {
1080
1088
  "path": "src/codex/app-server-restart-service.ts",
1081
- "sha256": "975b84bd6ce1b51e14081b44904605fec298bf386165466f8197570aa6cb2b27"
1089
+ "sha256": "6217a77e951e1155cce05f1a662bf12d7cf558677082b5ee94805907e69f6579"
1082
1090
  },
1083
1091
  {
1084
1092
  "path": "src/codex/auth-api.ts",
@@ -1150,7 +1158,7 @@
1150
1158
  },
1151
1159
  {
1152
1160
  "path": "src/codex/catalog/provider-fetch.ts",
1153
- "sha256": "8a0e187a69235a2e32e94e430c3282917fb260510cf61b1448704bb9fdc8e7e2"
1161
+ "sha256": "7e5b6be0ec07b69c74ccd4e47b9a340cf9b8503e4c84d7a73f229d2224342907"
1154
1162
  },
1155
1163
  {
1156
1164
  "path": "src/codex/catalog/remote.ts",
@@ -1162,7 +1170,7 @@
1162
1170
  },
1163
1171
  {
1164
1172
  "path": "src/codex/catalog/sync.ts",
1165
- "sha256": "99fd92002213d4203227c4e30fbc34539dbd56da2fca9e9a2246a483c1455b7d"
1173
+ "sha256": "d337c2270cd82ba94ea963127b59613a9e3086722b7625f848b5f6c5380c9007"
1166
1174
  },
1167
1175
  {
1168
1176
  "path": "src/codex/cli-install-provenance.ts",
@@ -1210,7 +1218,31 @@
1210
1218
  },
1211
1219
  {
1212
1220
  "path": "src/codex/desktop-app-restart.ts",
1213
- "sha256": "ba997ce741bae049697337ef7687f88e891a204b3bb434c43860247b26c08c7b"
1221
+ "sha256": "33fd530a45237e78f8829b49634d30b5e72e73c518a026b55d70c777f2f10ffc"
1222
+ },
1223
+ {
1224
+ "path": "src/codex/desktop-app/darwin.ts",
1225
+ "sha256": "535068797d57187eab4d3a004c13438d41fcb3300876eb7750d199d8e2e7b860"
1226
+ },
1227
+ {
1228
+ "path": "src/codex/desktop-app/handoff.ts",
1229
+ "sha256": "644c823f83223b12ba73aeb835ead89c3e8189a61f626a2db2353e87ff003748"
1230
+ },
1231
+ {
1232
+ "path": "src/codex/desktop-app/linux.ts",
1233
+ "sha256": "88038f9b068acf9a549aeebe8c590c798671a56c2d2ebbdc6fade0ba07440e5e"
1234
+ },
1235
+ {
1236
+ "path": "src/codex/desktop-app/lock.ts",
1237
+ "sha256": "c4e6fec7461b96a6397c7a06a3e8ae2741f7e9c3b13d1db36473e18b160d482d"
1238
+ },
1239
+ {
1240
+ "path": "src/codex/desktop-app/types.ts",
1241
+ "sha256": "7e42332e52ea3a3957b59df658785b37f87672f0445b03f73d9773f581dd5646"
1242
+ },
1243
+ {
1244
+ "path": "src/codex/desktop-app/windows.ts",
1245
+ "sha256": "8d5c2a333027d115456d61509e521402c178fa60b78e4a209efb0549a5d80b3a"
1214
1246
  },
1215
1247
  {
1216
1248
  "path": "src/codex/exec-invocation.ts",
@@ -1266,7 +1298,7 @@
1266
1298
  },
1267
1299
  {
1268
1300
  "path": "src/codex/inject.ts",
1269
- "sha256": "004f51a4801bcbf3960cbbae9492d5f78b2e0972d5eeb96b2880065b2cacee75"
1301
+ "sha256": "8f7807baf60620b30d87c86eb4d03bc6769376dfb147baa97e2d5a682746af14"
1270
1302
  },
1271
1303
  {
1272
1304
  "path": "src/codex/injected-marker.ts",
@@ -1574,7 +1606,7 @@
1574
1606
  },
1575
1607
  {
1576
1608
  "path": "src/codex/sync.ts",
1577
- "sha256": "4a25143724a4d5cb67f43013999193956f7bdeab86c27e418ef13b95ce53905e"
1609
+ "sha256": "4fac2a4db6165eda61d759765319329b49a2aa5a8ee14527dcf65d43c9c4fdc6"
1578
1610
  },
1579
1611
  {
1580
1612
  "path": "src/codex/transition-state.ts",
@@ -2414,7 +2446,7 @@
2414
2446
  },
2415
2447
  {
2416
2448
  "path": "src/lib/codex-restart-contract.ts",
2417
- "sha256": "25518ea4ef7f482da904bd862d165fc7cee19add6cead5ced5b1126362077a15"
2449
+ "sha256": "f2738e9d2fd79c80430aaa1e6f77161f448ccfd81ab39cbb0bc6cdd6ed7d66fd"
2418
2450
  },
2419
2451
  {
2420
2452
  "path": "src/lib/config-ownership.ts",
@@ -3078,7 +3110,7 @@
3078
3110
  },
3079
3111
  {
3080
3112
  "path": "src/providers/registry.ts",
3081
- "sha256": "c7fe45c3b2feec2c0ef22bb60d3f4298f3cc10a958e1fa24d6d39f4b3afa3309"
3113
+ "sha256": "db391a42721d43cd168dafe464bb1d3bb22cb6a935fe8679c58188a0b7fa9359"
3082
3114
  },
3083
3115
  {
3084
3116
  "path": "src/providers/request-pacing.ts",
@@ -3890,7 +3922,7 @@
3890
3922
  },
3891
3923
  {
3892
3924
  "path": "src/server/responses.ts",
3893
- "sha256": "b5a7c538e73c6c5d1245faae2a6a8968f16870796aa2c0bdbfb8e19fe2c95ed5"
3925
+ "sha256": "35b3f0b37c659a523c63738ffc0367e38fd68f5d974a402b3fd35f039b9a3e87"
3894
3926
  },
3895
3927
  {
3896
3928
  "path": "src/server/responses/agent-task-recovery-cache.ts",
@@ -3954,7 +3986,7 @@
3954
3986
  },
3955
3987
  {
3956
3988
  "path": "src/server/responses/core.ts",
3957
- "sha256": "86b3fd2c9a95a7a254d51d763d1b0a1f123494cd306aa61fcaa83f41fe42f9fe"
3989
+ "sha256": "dd6612bc2d46dbdc42f41c76bcd341216c4e66a9408fd2485cbb15631781875c"
3958
3990
  },
3959
3991
  {
3960
3992
  "path": "src/server/responses/empty-completion-guard.ts",
@@ -3962,7 +3994,7 @@
3962
3994
  },
3963
3995
  {
3964
3996
  "path": "src/server/responses/encrypted-payload.ts",
3965
- "sha256": "138eefb2fc1b63a89833251f6ab4c72b0cafc2cbbfaee53e4dd03b045c38f212"
3997
+ "sha256": "90cb251bcdd11bbe0e8b63e0d4f59608db329afd23932b301763017f427ba0a2"
3966
3998
  },
3967
3999
  {
3968
4000
  "path": "src/server/responses/fetch-helpers.ts",
@@ -4254,7 +4286,7 @@
4254
4286
  },
4255
4287
  {
4256
4288
  "path": "src/types/provider.ts",
4257
- "sha256": "f82f54c45d6642c7d1f7fbd3dca08d5b711280568df54287241f70ddc5e59a8f"
4289
+ "sha256": "be9b34e6587de2341a6a82fca69cead2017ecd3675736ebc6c2e46f70e84e221"
4258
4290
  },
4259
4291
  {
4260
4292
  "path": "src/types/request.ts",
@@ -4490,7 +4522,7 @@
4490
4522
  },
4491
4523
  {
4492
4524
  "path": "src/web-search/index.ts",
4493
- "sha256": "0140ad5bf2d78c550d782aa02e6c6f010d602ce94cd97c812a066a449e5b61a6"
4525
+ "sha256": "dcc1d3fef00b97249b3905c34a6f4f7c5319f90db0a670275e95e58c408024dd"
4494
4526
  },
4495
4527
  {
4496
4528
  "path": "src/web-search/loop.ts",
@@ -4506,12 +4538,16 @@
4506
4538
  },
4507
4539
  {
4508
4540
  "path": "src/web-search/passthrough-bridge.ts",
4509
- "sha256": "3863f9d7b942aef4cf3cee7d81377121ac00bb9f68953f64ada524f32bf1ca0d"
4541
+ "sha256": "82f2c355d6c5b8c4b557a5187aa71fc5b77396c1fe902bc50b79d62c03e9ab71"
4510
4542
  },
4511
4543
  {
4512
4544
  "path": "src/web-search/progress-stream.ts",
4513
4545
  "sha256": "0b743935e725b7bdfaf285ba973bb3fcc0faaca6225c8ba3113ce504af328126"
4514
4546
  },
4547
+ {
4548
+ "path": "src/web-search/sidecar-providers.ts",
4549
+ "sha256": "ffbc26760250ec95779d0130d18df4d0b3ce45cfbc7a1c12b7f00ba8f1be90a3"
4550
+ },
4515
4551
  {
4516
4552
  "path": "src/web-search/sources.ts",
4517
4553
  "sha256": "1b4d77d514dde268edbc3354192fe9c6edb623f977753506ef89aa77b61d6571"
@@ -32,6 +32,19 @@ export interface CodexAppServerStateResponse {
32
32
  runningCount: number;
33
33
  }
34
34
 
35
+ /**
36
+ * The desktop half of a restart. Scalar-only for the same reason as the rest of this
37
+ * contract: pid lists and a closed-vocabulary reason, never a command line or an OS
38
+ * error message, both of which routinely embed a path or a username.
39
+ */
40
+ export interface CodexDesktopRestartSummary {
41
+ attempted: boolean;
42
+ stopped: number[];
43
+ surviving: number[];
44
+ relaunch: "started" | "skipped";
45
+ reason?: string;
46
+ }
47
+
35
48
  /** POST response. All four arrays are pid lists — never command lines. */
36
49
  export interface CodexRestartResponse {
37
50
  success: boolean;
@@ -44,6 +57,12 @@ export interface CodexRestartResponse {
44
57
  surviving: number[];
45
58
  failed: number[];
46
59
  code: CodexRestartCode;
60
+ /**
61
+ * Absent on a proxy older than this change, which is why it is optional rather than
62
+ * required: this guard is a version-skew check the GUI runs, and a dashboard talking
63
+ * to an older proxy has to keep working.
64
+ */
65
+ desktopApp?: CodexDesktopRestartSummary;
47
66
  }
48
67
 
49
68
  const APP_SERVER_STATES: readonly string[] = ["fresh", "stale", "not_running", "unknown"];
@@ -103,6 +122,18 @@ export function isCodexRestartResponse(value: unknown): value is CodexRestartRes
103
122
  if ((code === "nothing_running" || code === "enumeration_unavailable") && stopped.length > 0) {
104
123
  return false;
105
124
  }
125
+ if ("desktopApp" in view && view.desktopApp !== undefined) {
126
+ const desktop = view.desktopApp;
127
+ if (typeof desktop !== "object" || desktop === null) return false;
128
+ const d = desktop as Record<string, unknown>;
129
+ if (typeof d.attempted !== "boolean") return false;
130
+ if (!isPidList(d.stopped) || !isPidList(d.surviving)) return false;
131
+ if (d.relaunch !== "started" && d.relaunch !== "skipped") return false;
132
+ if (d.reason !== undefined && typeof d.reason !== "string") return false;
133
+ // A started relaunch cannot have left anything behind: the ladder refuses to
134
+ // relaunch beside a survivor precisely so a second shell never appears.
135
+ if (d.relaunch === "started" && (d.surviving as number[]).length > 0) return false;
136
+ }
106
137
  return true;
107
138
  }
108
139
 
@@ -683,10 +683,9 @@ const DEEPSEEK_V4_LEGACY_MODELS = ["deepseek-v4-flash"];
683
683
  const DEEPSEEK_NATIVE_THINKING_MODELS = ["deepseek-flash", "deepseek-v4-flash"];
684
684
  const DEEPSEEK_GATEWAY_THINKING_MODELS = ["deepseek-v4.1-flash", "deepseek-v4-flash"];
685
685
  /*
686
- * DeepSeek's experimental vision preview (released 2026-08-21, api-docs.deepseek.com):
687
- * text+image input on the V4 Flash base. DeepSeek positions it as a preview id;
688
- * the expectation is that vision merges into `deepseek-v4-flash` proper later,
689
- * at which point this id retires the same way deepseek-chat/reasoner did.
686
+ * DeepSeek's legacy vision preview id (released 2026-08-21). First-party probes
687
+ * in #4436 resolve it to image-capable `deepseek-flash`; retain the existing
688
+ * declarations because gateway support is specific to each served identifier.
690
689
  */
691
690
  const DEEPSEEK_VISION_PREVIEW_MODEL = "deepseek-v4-flash-vision-exp";
692
691
  /**
@@ -2165,9 +2164,8 @@ export const PROVIDER_REGISTRY: readonly ProviderRegistryEntry[] = [
2165
2164
  // the list only as compatibility aliases so existing saved configs and requests
2166
2165
  // keep validating and routing (they previously mapped to v4-flash; devlog
2167
2166
  // _fin/260710_provider_hardening/002_research_cn.md). The current offerings are
2168
- // the V4 ids — defaultModel and the model-specific wiring above use them.
2169
- // deepseek-v4-flash-vision-exp: experimental vision preview (2026-08-21) —
2170
- // expected to merge into deepseek-v4-flash later; see DEEPSEEK_VISION_PREVIEW_MODEL.
2167
+ // V4.1-Flash — defaultModel and the model-specific wiring below use its live id.
2168
+ // Keep the legacy vision-preview alias; see DEEPSEEK_VISION_PREVIEW_MODEL.
2171
2169
  models: ["deepseek-chat", "deepseek-reasoner", ...DEEPSEEK_NATIVE_THINKING_MODELS, DEEPSEEK_VISION_PREVIEW_MODEL],
2172
2170
  // V4.1-Flash is the current first-party offering; `deepseek-v4-flash` now routes there
2173
2171
  // as a compatibility alias, so a new install should ask for the live id by name.
@@ -2175,7 +2173,10 @@ export const PROVIDER_REGISTRY: readonly ProviderRegistryEntry[] = [
2175
2173
  // Official DeepSeek Codex setup (codex-deepseek-setup.sh) advertises 1,048,576
2176
2174
  // for both V4 models; the older 1,000,000 figure was a rounded approximation.
2177
2175
  modelContextWindows: { "deepseek-flash": 1_048_576, "deepseek-v4-flash": 1_048_576, [DEEPSEEK_VISION_PREVIEW_MODEL]: 1_048_576 },
2178
- modelInputModalities: { [DEEPSEEK_VISION_PREVIEW_MODEL]: ["text", "image"] },
2176
+ modelInputModalities: {
2177
+ "deepseek-flash": ["text", "image"],
2178
+ [DEEPSEEK_VISION_PREVIEW_MODEL]: ["text", "image"],
2179
+ },
2179
2180
  // DeepSeek documents both V4 models as native Responses API models adapted for Codex
2180
2181
  // (model table marks Responses API ✓ for flash and pro; the /responses reference lists
2181
2182
  // both ids as accepted `model` values — verified 2026-08-13 with the V4 Pro GA,
@@ -2245,10 +2246,10 @@ export const PROVIDER_REGISTRY: readonly ProviderRegistryEntry[] = [
2245
2246
  modelReasoningEffortMap: Object.fromEntries(DEEPSEEK_NATIVE_THINKING_MODELS.map(id => [id, deepseekReasoningMapFor(id)])),
2246
2247
  modelSupportsReasoningSummaries: Object.fromEntries(DEEPSEEK_NATIVE_THINKING_MODELS.map(id => [id, true])),
2247
2248
  preserveReasoningContentModels: DEEPSEEK_NATIVE_THINKING_MODELS,
2248
- // Issue #88: every DeepSeek API model is text-only input (no image support upstream) — the
2249
- // vision sidecar describes attached images for them, and the catalog advertises image input
2250
- // on their behalf (same treatment as opencode-go's DeepSeek V4 entries above).
2251
- noVisionModels: ["deepseek-chat", "deepseek-reasoner", ...DEEPSEEK_NATIVE_THINKING_MODELS],
2249
+ // #4436: first-party deepseek-flash accepts native images on Chat and Responses.
2250
+ // Keep unprobed compatibility aliases on the #88 sidecar path. This must be fixed
2251
+ // here: router enrichment unions this list with saved config, so config cannot remove it.
2252
+ noVisionModels: ["deepseek-chat", "deepseek-reasoner", "deepseek-v4-flash"],
2252
2253
  },
2253
2254
  // llama-3.3-70b was deprecated by Cerebras on 2026-02-16. Evidence: devlog/_plan/260710_provider_hardening/003_research_aggregators.md.
2254
2255
  { id: "cerebras", label: "Cerebras", baseUrl: "https://api.cerebras.ai/v1", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://cloud.cerebras.ai/platform/apikeys", defaultModel: "gpt-oss-120b" },