@bitkyc08/opencodex 2.52.0-preview.20260911 → 2.52.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/gui/dist/assets/index-CWXut3rG.js +115 -0
- package/gui/dist/assets/index-EdoPnm9_.css +1 -0
- package/gui/dist/index.html +2 -2
- package/gui/dist/provider-icons/devin.svg +49 -0
- package/gui/dist/provider-icons/omo.svg +42 -0
- package/package.json +3 -1
- package/src/AGENTS.md +1 -1
- package/src/adapters/cline-pass-deepseek-v4-tool-replay.ts +0 -1
- package/src/adapters/command-code.ts +0 -1
- package/src/adapters/cursor/checkpoint-store.ts +37 -0
- package/src/adapters/cursor/live-transport.ts +74 -2
- package/src/adapters/cursor.ts +6 -0
- package/src/adapters/devin/cloud-direct/auth.ts +264 -0
- package/src/adapters/devin/cloud-direct/catalog.ts +306 -0
- package/src/adapters/devin/cloud-direct/chat.ts +1274 -0
- package/src/adapters/devin/cloud-direct/index.ts +65 -0
- package/src/adapters/devin/cloud-direct/metadata.ts +134 -0
- package/src/adapters/devin/cloud-direct/wire.ts +206 -0
- package/src/adapters/devin/live-models.ts +133 -0
- package/src/adapters/devin-cli/acp.ts +204 -0
- package/src/adapters/devin-cli/adapter.ts +345 -0
- package/src/adapters/devin-cli/binary.ts +69 -0
- package/src/adapters/devin-cli/models.ts +57 -0
- package/src/adapters/devin.ts +326 -0
- package/src/adapters/google.ts +1 -1
- package/src/adapters/openai-chat.ts +31 -10
- package/src/adapters/openai-responses.ts +16 -1
- package/src/adapters/registry.ts +25 -1
- package/src/bridge.ts +8 -2
- package/src/claude/inbound-cache-stabilize.ts +130 -0
- package/src/claude/inbound.ts +45 -5
- package/src/cli/account-auth.ts +60 -1
- package/src/cli/account-extended.ts +23 -30
- package/src/cli/account.ts +2 -1
- package/src/cli/capabilities.ts +50 -8
- package/src/cli/config-command.ts +2 -2
- package/src/cli/dispatch.ts +14 -2
- package/src/cli/export-command.ts +11 -25
- package/src/cli/help.ts +2 -2
- package/src/cli/opencode.ts +5 -0
- package/src/cli/registry.ts +15 -4
- package/src/clients/config-export/cline.ts +71 -0
- package/src/clients/config-export/contracts.ts +10 -1
- package/src/clients/config-export/model-metadata.ts +33 -0
- package/src/clients/config-export/zcode.ts +23 -12
- package/src/clients/config-export.ts +156 -4
- package/src/codex/account-store.ts +7 -2
- package/src/codex/auth-context.ts +1 -1
- package/src/codex/catalog/effort.ts +4 -3
- package/src/codex/catalog/metadata.ts +8 -4
- package/src/codex/catalog/native-models.ts +24 -4
- package/src/codex/catalog/parsing.ts +19 -1
- package/src/codex/catalog/provider-fetch.ts +57 -0
- package/src/codex/catalog/sync.ts +1 -1
- package/src/codex/context-compat.ts +97 -0
- package/src/codex/context-owner.ts +201 -0
- package/src/codex/history-provider.ts +161 -14
- package/src/codex/inject-coordination.ts +3 -2
- package/src/codex/inject.ts +128 -9
- package/src/codex/pool-rotation.ts +8 -292
- package/src/codex/quota.ts +41 -12
- package/src/codex/retired-model-migration.ts +41 -0
- package/src/codex/routing.ts +327 -22
- package/src/codex/warmup.ts +2 -2
- package/src/combos/failover.ts +5 -0
- package/src/combos/resolve.ts +11 -15
- package/src/config.ts +46 -14
- package/src/generated/compatibility-version.json +293 -113
- package/src/grok/grpc-web.ts +120 -0
- package/src/grok/reset-coupon-ledger.ts +139 -0
- package/src/grok/reset-coupons.ts +278 -0
- package/src/integrations/catalog-refresh.ts +1 -1
- package/src/integrations/cline-document.ts +73 -0
- package/src/integrations/cline-io.ts +149 -0
- package/src/integrations/cline-transaction.ts +42 -0
- package/src/integrations/config-io.ts +21 -2
- package/src/integrations/journal.ts +43 -1
- package/src/integrations/omp-yaml-source.ts +123 -4
- package/src/integrations/ownership-policy.ts +7 -0
- package/src/integrations/ownership.ts +36 -0
- package/src/integrations/registry.ts +27 -0
- package/src/integrations/state.ts +5 -2
- package/src/integrations/store.ts +6 -0
- package/src/integrations/writer.ts +53 -9
- package/src/lab/subject/behavior-fingerprint.ts +1 -1
- package/src/lib/abort.ts +36 -0
- package/src/lib/local-destinations.ts +1 -1
- package/src/lib/upstream-retry.ts +36 -1
- package/src/oauth/account-quota-rank.ts +11 -0
- package/src/oauth/callback-server.ts +10 -5
- package/src/oauth/devin/api-base.ts +63 -0
- package/src/oauth/devin/login.ts +1 -0
- package/src/oauth/devin/register-user.ts +186 -0
- package/src/oauth/devin/types.ts +71 -0
- package/src/oauth/devin-cli.ts +149 -0
- package/src/oauth/devin.ts +170 -0
- package/src/oauth/generic-account-failover.ts +169 -1
- package/src/oauth/index.ts +20 -1
- package/src/oauth/login-cli.ts +19 -5
- package/src/oauth/pool-kernel.ts +321 -0
- package/src/oauth/pool-settings-capability.ts +127 -9
- package/src/oauth/store.ts +7 -3
- package/src/oauth/token-guardian.ts +1 -1
- package/src/providers/codebuddy-models.ts +0 -3
- package/src/providers/command-code-efforts.ts +23 -4
- package/src/providers/default-aliases.ts +4 -0
- package/src/providers/derive.ts +8 -0
- package/src/providers/devin-cli-authmode-migration.ts +57 -0
- package/src/providers/key-failover.ts +175 -0
- package/src/providers/model-rename-startup.ts +21 -1
- package/src/providers/qoder-models.ts +0 -1
- package/src/providers/quota-key-accounts.ts +50 -0
- package/src/providers/quota-routing-cache.ts +65 -6
- package/src/providers/quota-types.ts +7 -0
- package/src/providers/quota.ts +113 -30
- package/src/providers/registry.ts +237 -77
- package/src/providers/stale-context-window-migration.ts +92 -0
- package/src/providers/zai-responses-migration.ts +45 -0
- package/src/quota/reset-observer.ts +2 -1
- package/src/quota/reset-seen-store.ts +9 -1
- package/src/remote-control/crypto.ts +442 -0
- package/src/remote-control/host.ts +175 -0
- package/src/remote-control/index.ts +100 -0
- package/src/remote-control/protocol.ts +200 -0
- package/src/remote-control/relay.ts +162 -0
- package/src/remote-control/workspace-agent-protocol.ts +246 -0
- package/src/remote-control/workspace-rpc-framing.ts +128 -0
- package/src/remote-control/workspace-tools.ts +237 -0
- package/src/remote-control/workspace-utf8.ts +24 -0
- package/src/responses/custom-tool-compat.ts +23 -0
- package/src/router.ts +6 -1
- package/src/routing/compatibility/behavior.ts +3 -0
- package/src/server/adapter-resolve.ts +6 -2
- package/src/server/auth-cors.ts +71 -6
- package/src/server/chat-completions.ts +5 -3
- package/src/server/chat-native.ts +9 -0
- package/src/server/claude-messages.ts +25 -30
- package/src/server/context-history.ts +207 -0
- package/src/server/images.ts +28 -1
- package/src/server/index.ts +42 -22
- package/src/server/live.ts +2 -1
- package/src/server/management/agent-settings-routes.ts +7 -1
- package/src/server/management/config-routes.ts +3 -3
- package/src/server/management/grok-coupon-routes.ts +287 -0
- package/src/server/management/integration-routes.ts +6 -1
- package/src/server/management/oauth-account-routes.ts +124 -7
- package/src/server/management/provider-routes.ts +77 -2
- package/src/server/management/route-registry.ts +6 -1
- package/src/server/management-api.ts +7 -0
- package/src/server/request-log.ts +4 -0
- package/src/server/responses/codex-ws-exchange.ts +90 -14
- package/src/server/responses/codex-ws-wire.ts +51 -1
- package/src/server/responses/compact.ts +8 -1
- package/src/server/responses/core.ts +188 -22
- package/src/server/responses/ws-upstream.ts +1 -1
- package/src/server/responses-undeclared-tool-guard.ts +261 -12
- package/src/server/zai-responses-startup.ts +21 -0
- package/src/types/config.ts +33 -3
- package/src/types/provider.ts +54 -4
- package/src/types/request.ts +1 -1
- package/src/types/tools.ts +36 -6
- package/src/update/job.ts +33 -11
- package/src/vision/plan.ts +40 -37
- package/src/web-search/index.ts +1 -0
- package/gui/dist/assets/index-BoBRSehJ.css +0 -1
- package/gui/dist/assets/index-Dx0xv2EA.js +0 -115
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
-
import { closeSync, existsSync, fsyncSync, lstatSync, mkdirSync, openSync, readFileSync, readSync, statSync, unlinkSync, writeSync } from "node:fs";
|
|
2
|
+
import { closeSync, constants, existsSync, fstatSync, fsyncSync, lstatSync, mkdirSync, openSync, readFileSync, readSync, statSync, unlinkSync, writeSync } from "node:fs";
|
|
3
3
|
import { dirname, join, resolve } from "node:path";
|
|
4
4
|
import { zstdDecompressSync } from "node:zlib";
|
|
5
5
|
import { Database } from "bun:sqlite";
|
|
@@ -75,14 +75,21 @@ function openStateDb(stateDbPath: string): Database {
|
|
|
75
75
|
* rollout's updated_at), and forcing it backwards could hide a real edit from list ordering.
|
|
76
76
|
*/
|
|
77
77
|
function appendRolloutLine(path: string, line: string): Buffer {
|
|
78
|
-
|
|
78
|
+
assertLegacyHistoryRecord(line);
|
|
79
|
+
// No O_CREAT: a disappeared target is not an invitation to recreate history.
|
|
80
|
+
const fd = openSync(path, constants.O_RDWR | constants.O_APPEND);
|
|
79
81
|
const buf = Buffer.from(line.endsWith("\n") ? line : `${line}\n`, "utf8");
|
|
80
82
|
try {
|
|
83
|
+
assertLegacyHistoryWritable(path, fd);
|
|
84
|
+
historyAppendHooks?.beforeWrite?.(path);
|
|
85
|
+
assertHistoryDescriptorIdentity(path, fd);
|
|
81
86
|
let offset = 0;
|
|
82
87
|
while (offset < buf.length) {
|
|
83
88
|
offset += writeSync(fd, buf, offset, buf.length - offset, null);
|
|
84
89
|
}
|
|
85
90
|
try { fsyncSync(fd); } catch { /* best-effort durability */ }
|
|
91
|
+
historyAppendHooks?.afterWrite?.(path);
|
|
92
|
+
assertHistoryDescriptorIdentity(path, fd);
|
|
86
93
|
} finally {
|
|
87
94
|
closeSync(fd);
|
|
88
95
|
}
|
|
@@ -186,21 +193,38 @@ function readFirstLineProviderValue(path: string, expectedId: string): string |
|
|
|
186
193
|
}
|
|
187
194
|
}
|
|
188
195
|
|
|
189
|
-
function patchFirstLineProviderInPlace(
|
|
190
|
-
|
|
196
|
+
function patchFirstLineProviderInPlace(
|
|
197
|
+
path: string, expectedId: string, provider: string,
|
|
198
|
+
expectedIdentity: { dev: number | bigint; ino: number | bigint },
|
|
199
|
+
): FirstLineProviderResult {
|
|
200
|
+
historyAppendHooks?.beforeFirstLineOpen?.(path);
|
|
191
201
|
const fd = openSync(path, "r+");
|
|
192
202
|
try {
|
|
203
|
+
const assertIdentity = (): void => {
|
|
204
|
+
assertHistoryDescriptorIdentity(path, fd);
|
|
205
|
+
const held = fstatSync(fd);
|
|
206
|
+
if (held.dev !== expectedIdentity.dev || held.ino !== expectedIdentity.ino) {
|
|
207
|
+
throw new CodexHistoryIntegrityError("history_rollout_identity_changed");
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
assertIdentity();
|
|
211
|
+
assertLegacyHistoryWritable(path, fd);
|
|
193
212
|
const firstLine = readFirstRolloutLine(fd);
|
|
194
213
|
if (firstLine === null) return "unsafe";
|
|
195
214
|
const plan = planFirstLineProvider(firstLine, expectedId, provider);
|
|
196
215
|
if (plan.state === "unsafe") return "unsafe";
|
|
197
216
|
if (plan.state === "current") return "current";
|
|
198
217
|
const out = Buffer.from(plan.patchedLine, "utf8");
|
|
218
|
+
historyAppendHooks?.beforeFirstLineWrite?.(path);
|
|
219
|
+
assertIdentity();
|
|
220
|
+
assertLegacyHistoryWritable(path, fd);
|
|
199
221
|
let offset = 0;
|
|
200
222
|
while (offset < out.length) {
|
|
201
223
|
offset += writeSync(fd, out, offset, out.length - offset, offset);
|
|
202
224
|
}
|
|
203
225
|
try { fsyncSync(fd); } catch { /* best-effort durability */ }
|
|
226
|
+
historyAppendHooks?.afterFirstLineWrite?.(path);
|
|
227
|
+
assertIdentity();
|
|
204
228
|
return "patched";
|
|
205
229
|
} finally {
|
|
206
230
|
closeSync(fd);
|
|
@@ -221,6 +245,109 @@ class CodexHistoryIntegrityError extends Error {
|
|
|
221
245
|
}
|
|
222
246
|
}
|
|
223
247
|
|
|
248
|
+
/** Paginated ordinals and projection offsets belong to Codex's live writer.
|
|
249
|
+
* O_APPEND does not allocate an ordinal or update that writer's in-memory cursor.
|
|
250
|
+
* Refuse before changing the DB, manifest, or first-line provider; never guess N+1.
|
|
251
|
+
*/
|
|
252
|
+
function assertLegacyHistoryRecord(line: string): void {
|
|
253
|
+
let value: unknown;
|
|
254
|
+
try { value = JSON.parse(line); } catch { throw new CodexHistoryIntegrityError("history_rollout_record_invalid"); }
|
|
255
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
256
|
+
throw new CodexHistoryIntegrityError("history_rollout_record_invalid");
|
|
257
|
+
}
|
|
258
|
+
const record = value as Record<string, unknown>;
|
|
259
|
+
const payload = record.payload;
|
|
260
|
+
if (Object.hasOwn(record, "ordinal") || (payload !== null && typeof payload === "object" && (payload as Record<string, unknown>).history_mode === "paginated")) {
|
|
261
|
+
throw new CodexHistoryIntegrityError("history_paginated_requires_native_writer");
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function assertHistoryDescriptorIdentity(path: string, fd: number): void {
|
|
266
|
+
const held = fstatSync(fd);
|
|
267
|
+
let current: ReturnType<typeof lstatSync>;
|
|
268
|
+
try { current = lstatSync(path); } catch { throw new CodexHistoryIntegrityError("history_rollout_identity_changed"); }
|
|
269
|
+
if (!current.isFile() || current.isSymbolicLink() || current.dev !== held.dev || current.ino !== held.ino) {
|
|
270
|
+
throw new CodexHistoryIntegrityError("history_rollout_identity_changed");
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
let historyAppendHooks: {
|
|
275
|
+
beforeWrite?: (path: string) => void;
|
|
276
|
+
afterWrite?: (path: string) => void;
|
|
277
|
+
beforeFirstLineOpen?: (path: string) => void;
|
|
278
|
+
beforeFirstLineWrite?: (path: string) => void;
|
|
279
|
+
afterFirstLineWrite?: (path: string) => void;
|
|
280
|
+
} | undefined;
|
|
281
|
+
export function setHistoryAppendHooksForTests(hooks: typeof historyAppendHooks): void { historyAppendHooks = hooks; }
|
|
282
|
+
|
|
283
|
+
function assertLegacyHistoryWritable(path: string, heldFd?: number): void {
|
|
284
|
+
if (!path || !existsSync(path)) return;
|
|
285
|
+
const fd = heldFd ?? openSync(path, "r");
|
|
286
|
+
try {
|
|
287
|
+
assertHistoryDescriptorIdentity(path, fd);
|
|
288
|
+
const first = readFirstRolloutLine(fd);
|
|
289
|
+
if (!first) throw new CodexHistoryIntegrityError("history_rollout_record_invalid");
|
|
290
|
+
assertLegacyHistoryRecord(first);
|
|
291
|
+
} finally {
|
|
292
|
+
if (heldFd === undefined) closeSync(fd);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/** A store with history_mode can migrate legacy rows while Codex is running.
|
|
297
|
+
* Refuse the entire external mutation, not only rows already marked paginated.
|
|
298
|
+
*/
|
|
299
|
+
function assertLegacyHistoryStore(db: Database): void {
|
|
300
|
+
const columns = db.query<{ name: string }, []>("PRAGMA table_info(threads)").all();
|
|
301
|
+
if (columns.some(column => column.name === "history_mode")) {
|
|
302
|
+
throw new CodexHistoryIntegrityError("history_paginated_requires_native_writer");
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/** Read-only preflight before the injector changes provider definitions.
|
|
307
|
+
* Native paginated history cannot participate in the legacy relabel protocol.
|
|
308
|
+
* Returning a refusal preserves the existing config as well as the rollout.
|
|
309
|
+
*/
|
|
310
|
+
export function preflightCodexHistoryInjection(
|
|
311
|
+
providerTableMode: boolean,
|
|
312
|
+
resumeHistory: boolean,
|
|
313
|
+
stateDbPath?: string,
|
|
314
|
+
): string | null {
|
|
315
|
+
let db: Database | undefined;
|
|
316
|
+
try {
|
|
317
|
+
const resolvedPath = stateDbPath ?? resolveCodexStateDbPath();
|
|
318
|
+
// A partially restored row may already be native while its manifest still
|
|
319
|
+
// owns work. Match the restore worker's target set before removing routing.
|
|
320
|
+
const restoreEntries = providerTableMode ? []
|
|
321
|
+
: Object.values(readBackup(historyBackupPathFor(resolvedPath), resolvedPath).manifest.entries);
|
|
322
|
+
if (!existsSync(resolvedPath)) {
|
|
323
|
+
return restoreEntries.length > 0 ? "history_state_database_missing" : null;
|
|
324
|
+
}
|
|
325
|
+
db = new Database(resolvedPath, { readonly: true });
|
|
326
|
+
const columns = db.query<{ name: string }, []>("PRAGMA table_info(threads)").all();
|
|
327
|
+
const paginatedColumn = columns.some(column => column.name === "history_mode");
|
|
328
|
+
if (paginatedColumn && restoreEntries.length > 0) return "history_paginated_requires_native_writer";
|
|
329
|
+
for (const entry of restoreEntries) assertLegacyHistoryWritable(entry.rolloutPath);
|
|
330
|
+
const rows = db.query<{ rollout_path: string; history_mode: string | null }, []>(`
|
|
331
|
+
SELECT rollout_path, ${paginatedColumn ? "history_mode" : "NULL AS history_mode"}
|
|
332
|
+
FROM threads
|
|
333
|
+
WHERE ${providerTableMode
|
|
334
|
+
? resumeHistory ? "model_provider IN ('openai', 'opencodex')" : "0"
|
|
335
|
+
: "model_provider = 'opencodex'"}
|
|
336
|
+
`).all();
|
|
337
|
+
for (const row of rows) {
|
|
338
|
+
if (paginatedColumn || row.history_mode === "paginated") return "history_paginated_requires_native_writer";
|
|
339
|
+
assertLegacyHistoryWritable(row.rollout_path);
|
|
340
|
+
}
|
|
341
|
+
return null;
|
|
342
|
+
} catch (error) {
|
|
343
|
+
return error instanceof CodexHistoryIntegrityError
|
|
344
|
+
? error.message
|
|
345
|
+
: "history_injection_preflight_unavailable";
|
|
346
|
+
} finally {
|
|
347
|
+
db?.close();
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
224
351
|
function integrityFailureResult(error: CodexHistoryIntegrityError): CodexHistorySyncResult {
|
|
225
352
|
return {
|
|
226
353
|
rows: error.progress.rows,
|
|
@@ -1026,6 +1153,7 @@ function updateSessionMeta(
|
|
|
1026
1153
|
} = {},
|
|
1027
1154
|
): SessionMetaUpdateResult {
|
|
1028
1155
|
if (!path || !existsSync(path)) return { changed: false, durableProvider: false };
|
|
1156
|
+
const validatedIdentity = lstatSync(path);
|
|
1029
1157
|
if (options.expectedFileIdentity !== undefined
|
|
1030
1158
|
&& historyFileIdentity(path) !== options.expectedFileIdentity) {
|
|
1031
1159
|
return { changed: false, durableProvider: false, conflict: true };
|
|
@@ -1040,6 +1168,11 @@ function updateSessionMeta(
|
|
|
1040
1168
|
if (!latest) return { changed: false, durableProvider: false };
|
|
1041
1169
|
const record = latest.record;
|
|
1042
1170
|
|
|
1171
|
+
assertLegacyHistoryWritable(path);
|
|
1172
|
+
if (Object.hasOwn(record, "ordinal") || record.payload.history_mode === "paginated") {
|
|
1173
|
+
throw new CodexHistoryIntegrityError("history_paginated_requires_native_writer");
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1043
1176
|
const latestProvider = typeof record.payload.model_provider === "string" && record.payload.model_provider
|
|
1044
1177
|
? record.payload.model_provider
|
|
1045
1178
|
: "openai";
|
|
@@ -1094,8 +1227,9 @@ function updateSessionMeta(
|
|
|
1094
1227
|
let firstLine: FirstLineProviderResult = "current";
|
|
1095
1228
|
if (patch.provider !== undefined) {
|
|
1096
1229
|
try {
|
|
1097
|
-
firstLine = patchFirstLineProviderInPlace(path, expectedId, patch.provider);
|
|
1098
|
-
} catch {
|
|
1230
|
+
firstLine = patchFirstLineProviderInPlace(path, expectedId, patch.provider, validatedIdentity);
|
|
1231
|
+
} catch (error) {
|
|
1232
|
+
if (error instanceof CodexHistoryIntegrityError) throw error;
|
|
1099
1233
|
firstLine = "unsafe";
|
|
1100
1234
|
}
|
|
1101
1235
|
}
|
|
@@ -1122,8 +1256,9 @@ function updateSessionMeta(
|
|
|
1122
1256
|
let firstLine: FirstLineProviderResult = "current";
|
|
1123
1257
|
if (patch.provider !== undefined) {
|
|
1124
1258
|
try {
|
|
1125
|
-
firstLine = patchFirstLineProviderInPlace(path, expectedId, patch.provider);
|
|
1126
|
-
} catch {
|
|
1259
|
+
firstLine = patchFirstLineProviderInPlace(path, expectedId, patch.provider, validatedIdentity);
|
|
1260
|
+
} catch (error) {
|
|
1261
|
+
if (error instanceof CodexHistoryIntegrityError) throw error;
|
|
1127
1262
|
firstLine = "unsafe";
|
|
1128
1263
|
}
|
|
1129
1264
|
if (options.requireDurableProvider && firstLine === "unsafe") {
|
|
@@ -1151,6 +1286,8 @@ function relabelAllRoutedHistoryToOpenai(db: Database): { rows: number; files: n
|
|
|
1151
1286
|
`)
|
|
1152
1287
|
.all();
|
|
1153
1288
|
|
|
1289
|
+
if (rows.length > 0) assertLegacyHistoryStore(db);
|
|
1290
|
+
for (const row of rows) assertLegacyHistoryWritable(row.rollout_path);
|
|
1154
1291
|
let files = 0;
|
|
1155
1292
|
for (const row of rows) {
|
|
1156
1293
|
try {
|
|
@@ -1158,8 +1295,8 @@ function relabelAllRoutedHistoryToOpenai(db: Database): { rows: number; files: n
|
|
|
1158
1295
|
provider: "openai",
|
|
1159
1296
|
source: row.source === "exec" ? "cli" : undefined,
|
|
1160
1297
|
}).changed) files++;
|
|
1161
|
-
} catch {
|
|
1162
|
-
|
|
1298
|
+
} catch (error) {
|
|
1299
|
+
if ((error as NodeJS.ErrnoException)?.code !== "ENOENT") throw error;
|
|
1163
1300
|
}
|
|
1164
1301
|
}
|
|
1165
1302
|
|
|
@@ -1302,6 +1439,8 @@ function syncCodexHistoryProviderUnsafe(provider: CodexHistoryProvider, stateDbP
|
|
|
1302
1439
|
`)
|
|
1303
1440
|
.all();
|
|
1304
1441
|
|
|
1442
|
+
if (openaiRows.length + execRows.length > 0) assertLegacyHistoryStore(db);
|
|
1443
|
+
for (const row of [...openaiRows, ...execRows]) assertLegacyHistoryWritable(row.rollout_path);
|
|
1305
1444
|
const manifest = readBackup(backupPath, stateDbPath).manifest;
|
|
1306
1445
|
for (const row of [...openaiRows, ...execRows]) rememberOriginal(manifest, row);
|
|
1307
1446
|
writeBackup(backupPath, manifest, stateDbPath);
|
|
@@ -1367,15 +1506,15 @@ function syncCodexHistoryProviderUnsafe(provider: CodexHistoryProvider, stateDbP
|
|
|
1367
1506
|
for (const row of openaiRows) {
|
|
1368
1507
|
try {
|
|
1369
1508
|
if (updateSessionMeta(row.rollout_path, row.id, { provider: "opencodex" }).changed) files++;
|
|
1370
|
-
} catch {
|
|
1371
|
-
|
|
1509
|
+
} catch (error) {
|
|
1510
|
+
if ((error as NodeJS.ErrnoException)?.code !== "ENOENT") throw error;
|
|
1372
1511
|
}
|
|
1373
1512
|
}
|
|
1374
1513
|
for (const row of execRows) {
|
|
1375
1514
|
try {
|
|
1376
1515
|
if (updateSessionMeta(row.rollout_path, row.id, { source: "cli" }).changed) files++;
|
|
1377
|
-
} catch {
|
|
1378
|
-
|
|
1516
|
+
} catch (error) {
|
|
1517
|
+
if ((error as NodeJS.ErrnoException)?.code !== "ENOENT") throw error;
|
|
1379
1518
|
}
|
|
1380
1519
|
}
|
|
1381
1520
|
});
|
|
@@ -1409,9 +1548,12 @@ function restoreCodexHistoryProvider(stateDbPath: string, backupPath: string): C
|
|
|
1409
1548
|
const manifest = backup.manifest;
|
|
1410
1549
|
const entries = Object.values(manifest.entries);
|
|
1411
1550
|
|
|
1551
|
+
for (const entry of entries) assertLegacyHistoryWritable(entry.rolloutPath);
|
|
1552
|
+
|
|
1412
1553
|
const db = openStateDb(stateDbPath);
|
|
1413
1554
|
try {
|
|
1414
1555
|
if (entries.length === 0) return { rows: 0, files: 0 };
|
|
1556
|
+
assertLegacyHistoryStore(db);
|
|
1415
1557
|
|
|
1416
1558
|
// Validate the whole manifest-to-database target set before touching a rollout. Only the
|
|
1417
1559
|
// OpenCodex post-image (or an already-restored target from an interrupted retry) is owned by
|
|
@@ -1547,6 +1689,7 @@ function restoreCodexHistoryProvider(stateDbPath: string, backupPath: string): C
|
|
|
1547
1689
|
|
|
1548
1690
|
export function restoreLegacyOpenaiHistory(stateDbPath = resolveCodexStateDbPath()): CodexHistorySyncResult {
|
|
1549
1691
|
if (!existsSync(stateDbPath)) return { rows: 0, files: 0 };
|
|
1692
|
+
try {
|
|
1550
1693
|
const retried = withHistoryRetryResult(() => {
|
|
1551
1694
|
const db = openStateDb(stateDbPath);
|
|
1552
1695
|
try {
|
|
@@ -1556,6 +1699,10 @@ export function restoreLegacyOpenaiHistory(stateDbPath = resolveCodexStateDbPath
|
|
|
1556
1699
|
}
|
|
1557
1700
|
});
|
|
1558
1701
|
return retried.ok ? retried.value : { rows: 0, files: 0, failed: true, failureReason: retried.reason };
|
|
1702
|
+
} catch (error) {
|
|
1703
|
+
if (error instanceof CodexHistoryIntegrityError) return integrityFailureResult(error);
|
|
1704
|
+
throw error;
|
|
1705
|
+
}
|
|
1559
1706
|
}
|
|
1560
1707
|
|
|
1561
1708
|
/**
|
|
@@ -172,8 +172,9 @@ export interface CodexPreImages {
|
|
|
172
172
|
function readOrNull(path: string): string | null {
|
|
173
173
|
try {
|
|
174
174
|
return readFileSync(path, "utf-8");
|
|
175
|
-
} catch {
|
|
176
|
-
return null;
|
|
175
|
+
} catch (error) {
|
|
176
|
+
if ((error as NodeJS.ErrnoException).code === "ENOENT") return null;
|
|
177
|
+
throw error;
|
|
177
178
|
}
|
|
178
179
|
}
|
|
179
180
|
|
package/src/codex/inject.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { contextCompatibleBaseLine } from "./context-compat";
|
|
1
2
|
import { existsSync, readFileSync, unlinkSync } from "node:fs";
|
|
2
3
|
import {
|
|
3
4
|
atomicWriteFile,
|
|
@@ -46,7 +47,7 @@ import {
|
|
|
46
47
|
} from "./journal";
|
|
47
48
|
import { withCatalogWriteSerialization } from "./catalog-write-serialization";
|
|
48
49
|
import { restoreCodexCatalogWithPermit } from "./catalog/sync";
|
|
49
|
-
import { syncCodexHistoryProvider, type CodexHistoryFailureReason } from "./history-provider";
|
|
50
|
+
import { preflightCodexHistoryInjection, syncCodexHistoryProvider, type CodexHistoryFailureReason } from "./history-provider";
|
|
50
51
|
import {
|
|
51
52
|
describeHistoryJobFailure,
|
|
52
53
|
deriveCodexHistoryOperation,
|
|
@@ -75,6 +76,7 @@ import {
|
|
|
75
76
|
parseTomlString,
|
|
76
77
|
readRootTomlString,
|
|
77
78
|
resolveCodexConfigPath,
|
|
79
|
+
resolveCodexStateDbPath,
|
|
78
80
|
tomlString,
|
|
79
81
|
} from "./paths";
|
|
80
82
|
import { resolveEffectiveProjectModelProvider } from "./project-config-warnings";
|
|
@@ -395,7 +397,7 @@ export function setRootOpenaiBaseUrl(
|
|
|
395
397
|
const lines = content.split("\n");
|
|
396
398
|
const firstTable = lines.findIndex((l) => /^\s*\[/.test(l));
|
|
397
399
|
const rootEnd = firstTable === -1 ? lines.length : firstTable;
|
|
398
|
-
const key = buildOpenaiBaseUrlLine(portOrTarget, hostname);
|
|
400
|
+
const key = contextCompatibleBaseLine(content, buildOpenaiBaseUrlLine(portOrTarget, hostname));
|
|
399
401
|
|
|
400
402
|
for (let i = 0; i < rootEnd; i++) {
|
|
401
403
|
if (!isRootOpenaiBaseUrlLine(lines[i])) continue;
|
|
@@ -430,7 +432,7 @@ function setRootOpenaiBaseUrlForTarget(
|
|
|
430
432
|
const lines = content.split("\n");
|
|
431
433
|
const firstTable = lines.findIndex((line) => /^\s*\[/.test(line));
|
|
432
434
|
const rootEnd = firstTable === -1 ? lines.length : firstTable;
|
|
433
|
-
const key = buildOpenaiBaseUrlLineForTarget(target);
|
|
435
|
+
const key = contextCompatibleBaseLine(content, buildOpenaiBaseUrlLineForTarget(target));
|
|
434
436
|
for (let index = 0; index < rootEnd; index += 1) {
|
|
435
437
|
if (!isRootOpenaiBaseUrlLine(lines[index])) continue;
|
|
436
438
|
const markerOwned = index > 0 && lines[index - 1].includes(OCX_SECTION_MARKER);
|
|
@@ -899,10 +901,41 @@ export interface CodexInjectResult {
|
|
|
899
901
|
nativeSubagentDefaultsWarning?: string;
|
|
900
902
|
}
|
|
901
903
|
|
|
904
|
+
class CodexHistoryPreflightRefusal extends Error {}
|
|
905
|
+
class CodexRestoreRefusal extends Error {
|
|
906
|
+
constructor(readonly config: CodexRestoreConfigResult) {
|
|
907
|
+
super(config.message);
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
let historyArtifactStageForTests: ((stage: string) => void) | undefined;
|
|
911
|
+
export function setHistoryArtifactStageForTests(hook: typeof historyArtifactStageForTests): void {
|
|
912
|
+
historyArtifactStageForTests = hook;
|
|
913
|
+
}
|
|
914
|
+
let beforeRestoreConfigForTests: ((kind: string) => void) | undefined;
|
|
915
|
+
export function setBeforeRestoreConfigForTests(hook: typeof beforeRestoreConfigForTests): void {
|
|
916
|
+
beforeRestoreConfigForTests = hook;
|
|
917
|
+
}
|
|
918
|
+
let beforeHistoryArtifactCommitForTests: ((kind: string) => void) | undefined;
|
|
919
|
+
export function setBeforeHistoryArtifactCommitForTests(hook: typeof beforeHistoryArtifactCommitForTests): void {
|
|
920
|
+
beforeHistoryArtifactCommitForTests = hook;
|
|
921
|
+
}
|
|
922
|
+
|
|
902
923
|
export async function injectCodexConfig(
|
|
903
924
|
port: number,
|
|
904
925
|
config?: OcxConfig,
|
|
905
926
|
options: InjectCodexOptions = {},
|
|
927
|
+
): Promise<CodexInjectResult> {
|
|
928
|
+
try { return await injectCodexConfigImpl(port, config, options); }
|
|
929
|
+
catch (error) {
|
|
930
|
+
if (error instanceof CodexHistoryPreflightRefusal) return { success: false, message: `Codex config injection refused: ${error.message}. Existing configuration and history were preserved.` };
|
|
931
|
+
throw error;
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
async function injectCodexConfigImpl(
|
|
936
|
+
port: number,
|
|
937
|
+
config?: OcxConfig,
|
|
938
|
+
options: InjectCodexOptions = {},
|
|
906
939
|
): Promise<CodexInjectResult> {
|
|
907
940
|
// Point Codex at the unauthenticated loopback listener when it is enabled (#1102).
|
|
908
941
|
//
|
|
@@ -929,6 +962,10 @@ export async function injectCodexConfig(
|
|
|
929
962
|
}
|
|
930
963
|
|
|
931
964
|
const rawContent = readFileSync(CODEX_CONFIG_PATH, "utf-8");
|
|
965
|
+
const preflightTableMode = usesProviderTable(routingTarget);
|
|
966
|
+
const compactionOnly = routingTarget.clientCompaction === true
|
|
967
|
+
&& routingTarget.desktopAuthless !== true
|
|
968
|
+
&& routingTarget.requiresAdmissionToken !== true;
|
|
932
969
|
const activeProvider = externalCodexModelProvider(rawContent);
|
|
933
970
|
if (activeProvider) {
|
|
934
971
|
// A launcher may have journaled before the provider manager took ownership. Never let shutdown
|
|
@@ -1138,6 +1175,29 @@ export async function injectCodexConfig(
|
|
|
1138
1175
|
);
|
|
1139
1176
|
content = applyEol(content, eol);
|
|
1140
1177
|
|
|
1178
|
+
// Resolve storage from the normalized candidate. Owned duplicate catalog keys
|
|
1179
|
+
// are repairable above and must not make this read-only preflight throw.
|
|
1180
|
+
const historyPreflight = (): string | null => {
|
|
1181
|
+
try {
|
|
1182
|
+
return preflightCodexHistoryInjection(
|
|
1183
|
+
preflightTableMode,
|
|
1184
|
+
config?.syncResumeHistory !== false && !compactionOnly,
|
|
1185
|
+
resolveCodexStateDbPath({ readConfig: () => content }),
|
|
1186
|
+
);
|
|
1187
|
+
} catch {
|
|
1188
|
+
return "history_injection_preflight_unavailable";
|
|
1189
|
+
}
|
|
1190
|
+
};
|
|
1191
|
+
const historyPreflightError = historyPreflight();
|
|
1192
|
+
if (historyPreflightError) {
|
|
1193
|
+
return {
|
|
1194
|
+
success: false,
|
|
1195
|
+
message: `Codex config injection refused: ${historyPreflightError}. `
|
|
1196
|
+
+ "Existing provider definitions and conversation files were preserved. "
|
|
1197
|
+
+ "Paginated history requires native-writer coordination; do not run legacy recovery or retry this transition blindly.",
|
|
1198
|
+
};
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1141
1201
|
/*
|
|
1142
1202
|
* The witness, built from the FINAL bytes. Everything it hashes is either the
|
|
1143
1203
|
* output about to be written or evidence that can be re-read under the lock;
|
|
@@ -1228,6 +1288,12 @@ export async function injectCodexConfig(
|
|
|
1228
1288
|
}
|
|
1229
1289
|
|
|
1230
1290
|
const applyNativeArtifacts = (): void => {
|
|
1291
|
+
beforeHistoryArtifactCommitForTests?.(eligibility.kind);
|
|
1292
|
+
const historyError = historyPreflight();
|
|
1293
|
+
if (historyError) throw new CodexHistoryPreflightRefusal(historyError);
|
|
1294
|
+
const preImages = captureCodexPreImages();
|
|
1295
|
+
try {
|
|
1296
|
+
historyArtifactStageForTests?.("after-preflight");
|
|
1231
1297
|
writeJournal({
|
|
1232
1298
|
currentStateIsNative: journalBaselineIsNative(),
|
|
1233
1299
|
configContent: baselineContent,
|
|
@@ -1237,6 +1303,7 @@ export async function injectCodexConfig(
|
|
|
1237
1303
|
// must not gain the new injection's hash and later overwrite preserved user edits.
|
|
1238
1304
|
if (hasUnverifiedJournalBaseline(baselineContent, readCurrentProfile())) throw new Error(unverifiedJournalMessage);
|
|
1239
1305
|
atomicWriteFile(CODEX_CONFIG_PATH, content);
|
|
1306
|
+
historyArtifactStageForTests?.("after-config");
|
|
1240
1307
|
atomicWriteFile(CODEX_PROFILE_PATH, profileContent);
|
|
1241
1308
|
markJournalInjectedState(content, profileContent, {
|
|
1242
1309
|
// A root override is ours whenever we wrote one and no user-owned value won. That is
|
|
@@ -1257,6 +1324,16 @@ export async function injectCodexConfig(
|
|
|
1257
1324
|
// already points at that path and therefore needs no textual rewrite.
|
|
1258
1325
|
injectedCatalogPath: catalogPath,
|
|
1259
1326
|
});
|
|
1327
|
+
historyArtifactStageForTests?.("after-artifacts");
|
|
1328
|
+
// Detect migration throughout the artifact transaction, not just at entry.
|
|
1329
|
+
// This is compensation, not a native-writer lock or permission to append ordinals.
|
|
1330
|
+
const finalHistoryError = historyPreflight();
|
|
1331
|
+
if (finalHistoryError) throw new CodexHistoryPreflightRefusal(finalHistoryError);
|
|
1332
|
+
} catch (error) {
|
|
1333
|
+
const compensated = restoreCodexPreImages(preImages);
|
|
1334
|
+
if (!compensated.complete) throw new CodexPartialWriteError(compensated.unrestored);
|
|
1335
|
+
throw error;
|
|
1336
|
+
}
|
|
1260
1337
|
};
|
|
1261
1338
|
|
|
1262
1339
|
/*
|
|
@@ -1638,6 +1715,8 @@ function hasOpencodexRouting(content: string): boolean {
|
|
|
1638
1715
|
export function removeCodexConfig(
|
|
1639
1716
|
options: { preserveProfile?: boolean } = {},
|
|
1640
1717
|
): { success: boolean; message: string } {
|
|
1718
|
+
const historyError = preflightCodexHistoryInjection(false, false);
|
|
1719
|
+
if (historyError) return { success: false, message: `Codex configuration preserved: ${historyError}. Native writer coordination is required.` };
|
|
1641
1720
|
if (!existsSync(CODEX_CONFIG_PATH)) {
|
|
1642
1721
|
if (!options.preserveProfile && existsSync(CODEX_PROFILE_PATH))
|
|
1643
1722
|
unlinkSync(CODEX_PROFILE_PATH);
|
|
@@ -1837,9 +1916,29 @@ export function skippedRestoreEnvelope(success: boolean, message: string): Codex
|
|
|
1837
1916
|
};
|
|
1838
1917
|
}
|
|
1839
1918
|
|
|
1919
|
+
/** Config was attempted and failed; downstream artifacts were never attempted. */
|
|
1920
|
+
function failedConfigRestoreEnvelope(config: CodexRestoreConfigResult): CodexNativeRestoreResult {
|
|
1921
|
+
const result = skippedRestoreEnvelope(false, config.message);
|
|
1922
|
+
result.artifacts.config = config;
|
|
1923
|
+
return result;
|
|
1924
|
+
}
|
|
1925
|
+
|
|
1840
1926
|
/** The config/profile half of a native restore, reported as one artifact. */
|
|
1841
|
-
function restoreCodexConfigInline(): CodexRestoreConfigResult {
|
|
1927
|
+
function restoreCodexConfigInline(kind = "sync"): CodexRestoreConfigResult {
|
|
1928
|
+
const preImages = captureCodexPreImages();
|
|
1929
|
+
const result = restoreCodexConfigInlineImpl(kind);
|
|
1930
|
+
if (result.state === "failed") {
|
|
1931
|
+
const compensated = restoreCodexPreImages(preImages);
|
|
1932
|
+
if (!compensated.complete) throw new CodexPartialWriteError(compensated.unrestored);
|
|
1933
|
+
}
|
|
1934
|
+
return result;
|
|
1935
|
+
}
|
|
1936
|
+
|
|
1937
|
+
function restoreCodexConfigInlineImpl(kind: string): CodexRestoreConfigResult {
|
|
1842
1938
|
try {
|
|
1939
|
+
beforeRestoreConfigForTests?.(kind);
|
|
1940
|
+
const historyError = preflightCodexHistoryInjection(false, false);
|
|
1941
|
+
if (historyError) return { state: "failed", changed: false, action: "failed", message: `Codex configuration and journal preserved: ${historyError}.` };
|
|
1843
1942
|
const journal = restoreJournalState();
|
|
1844
1943
|
if (journal.unverified) {
|
|
1845
1944
|
return {
|
|
@@ -1912,6 +2011,17 @@ function restoreCodexCatalogArtifact(
|
|
|
1912
2011
|
*/
|
|
1913
2012
|
export async function restoreNativeCodexAsync(
|
|
1914
2013
|
options: { revalidateDesiredState?: boolean } = {},
|
|
2014
|
+
): Promise<CodexNativeRestoreResult> {
|
|
2015
|
+
try {
|
|
2016
|
+
return await restoreNativeCodexAsyncImpl(options);
|
|
2017
|
+
} catch (error) {
|
|
2018
|
+
if (!(error instanceof CodexRestoreRefusal)) throw error;
|
|
2019
|
+
return failedConfigRestoreEnvelope(error.config);
|
|
2020
|
+
}
|
|
2021
|
+
}
|
|
2022
|
+
|
|
2023
|
+
async function restoreNativeCodexAsyncImpl(
|
|
2024
|
+
options: { revalidateDesiredState?: boolean },
|
|
1915
2025
|
): Promise<CodexNativeRestoreResult> {
|
|
1916
2026
|
const activeProvider = currentExternalCodexModelProvider();
|
|
1917
2027
|
if (activeProvider) {
|
|
@@ -1929,8 +2039,12 @@ export async function restoreNativeCodexAsync(
|
|
|
1929
2039
|
if (options.revalidateDesiredState) {
|
|
1930
2040
|
const ownership = inspectNativeCodexOwnership();
|
|
1931
2041
|
if (ownership.ownership === "foreign") return foreignOwnershipRestoreRefusal(ownership.reason);
|
|
2042
|
+
if (shouldSyncCodexOnStart(loadConfig())) return desiredEnabledRestoreSkip();
|
|
1932
2043
|
}
|
|
1933
2044
|
|
|
2045
|
+
const historyError = preflightCodexHistoryInjection(false, false);
|
|
2046
|
+
if (historyError) return skippedRestoreEnvelope(false, `Native restore refused: ${historyError}. Config, catalog, history and provenance were preserved.`);
|
|
2047
|
+
|
|
1934
2048
|
const eligibility = codexWriteCoordinationEligibility({
|
|
1935
2049
|
coordinatorPath: () =>
|
|
1936
2050
|
resolveCodexCoordinatorDatabasePath(resolveEffectiveUserIdentity(), getCodexHome()),
|
|
@@ -1980,7 +2094,9 @@ export async function restoreNativeCodexAsync(
|
|
|
1980
2094
|
const preImages = captureCodexPreImages();
|
|
1981
2095
|
let restored: CodexRestoreConfigResult;
|
|
1982
2096
|
try {
|
|
1983
|
-
restored = restoreCodexConfigInline();
|
|
2097
|
+
restored = restoreCodexConfigInline(eligibility.kind);
|
|
2098
|
+
// Throw inside N so the published remove transition rolls back too.
|
|
2099
|
+
if (restored.state === "failed") throw new CodexRestoreRefusal(restored);
|
|
1984
2100
|
} catch (error) {
|
|
1985
2101
|
const compensated = restoreCodexPreImages(preImages);
|
|
1986
2102
|
if (!compensated.complete) throw new CodexPartialWriteError(compensated.unrestored);
|
|
@@ -2021,9 +2137,10 @@ export async function restoreNativeCodexAsync(
|
|
|
2021
2137
|
if (options.revalidateDesiredState && shouldSyncCodexOnStart(loadConfig())) {
|
|
2022
2138
|
return desiredEnabledRestoreSkip();
|
|
2023
2139
|
}
|
|
2024
|
-
config = restoreCodexConfigInline();
|
|
2140
|
+
config = restoreCodexConfigInline(eligibility.kind);
|
|
2025
2141
|
}
|
|
2026
2142
|
|
|
2143
|
+
if (config.state === "failed") return failedConfigRestoreEnvelope(config);
|
|
2027
2144
|
const catalog = restoreCodexCatalogArtifact(options.revalidateDesiredState === true, journaledCatalogPath);
|
|
2028
2145
|
const outcome = await runCodexHistoryJob({
|
|
2029
2146
|
...resolveCodexHistoryJobTarget(),
|
|
@@ -2055,8 +2172,7 @@ export async function restoreNativeCodexAsync(
|
|
|
2055
2172
|
const base = catalog.removed > 0
|
|
2056
2173
|
? `${config.message} Catalog restored to ${catalog.kept} native model(s) (dropped ${catalog.removed} proxy-routed).`
|
|
2057
2174
|
: config.message;
|
|
2058
|
-
const success =
|
|
2059
|
-
&& catalog.state !== "failed"
|
|
2175
|
+
const success = catalog.state !== "failed"
|
|
2060
2176
|
&& history.state !== "failed";
|
|
2061
2177
|
return {
|
|
2062
2178
|
success,
|
|
@@ -2074,11 +2190,14 @@ export function restoreNativeCodex(options: { skipHistory?: boolean; revalidateD
|
|
|
2074
2190
|
if (options.revalidateDesiredState && shouldSyncCodexOnStart(loadConfig())) {
|
|
2075
2191
|
return desiredEnabledRestoreSkip();
|
|
2076
2192
|
}
|
|
2193
|
+
const historyError = preflightCodexHistoryInjection(false, false);
|
|
2194
|
+
if (historyError) return skippedRestoreEnvelope(false, `Native restore refused: ${historyError}. Config, catalog, history and provenance were preserved.`);
|
|
2077
2195
|
// Captured before the config half: a successful journal restore DELETES the journal, and
|
|
2078
2196
|
// restoring the config can drop `model_catalog_json`. Either one would hide the routed
|
|
2079
2197
|
// catalog we actually wrote (#1798).
|
|
2080
2198
|
const journaledCatalogPath = journaledInjectedCatalogPath();
|
|
2081
2199
|
const config = restoreCodexConfigInline();
|
|
2200
|
+
if (config.state === "failed") return failedConfigRestoreEnvelope(config);
|
|
2082
2201
|
const catalog = restoreCodexCatalogArtifact(options.revalidateDesiredState === true, journaledCatalogPath);
|
|
2083
2202
|
// Design B (loopback) steady state: threads are already tagged openai, so prove the
|
|
2084
2203
|
// no-op with a readonly probe instead of write-opening a DB the Codex app may hold
|
|
@@ -2115,7 +2234,7 @@ export function restoreNativeCodex(options: { skipHistory?: boolean; revalidateD
|
|
|
2115
2234
|
? `${config.message} Catalog restored to ${catalog.kept} native model(s) (dropped ${catalog.removed} proxy-routed).`
|
|
2116
2235
|
: config.message;
|
|
2117
2236
|
return {
|
|
2118
|
-
success:
|
|
2237
|
+
success: catalog.state !== "failed" && history.state !== "failed",
|
|
2119
2238
|
message,
|
|
2120
2239
|
artifacts: { config, catalog, history },
|
|
2121
2240
|
};
|