@iislee/opencodex 2.31.0 → 2.35.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/README.md +3 -3
- package/bin/ocx.mjs +99 -70
- package/gui/dist/assets/index-Be1sDpWw.css +1 -0
- package/gui/dist/assets/index-D4OKgUV4.js +114 -0
- package/gui/dist/index.html +2 -2
- package/package.json +3 -1
- package/src/adapters/anthropic.ts +104 -15
- package/src/adapters/base.ts +29 -1
- package/src/adapters/command-code.ts +42 -6
- package/src/adapters/cursor/call-id.ts +44 -0
- package/src/adapters/cursor/catalog.ts +541 -0
- package/src/adapters/cursor/checkpoint-store.ts +15 -10
- package/src/adapters/cursor/cursor-errors.ts +15 -0
- package/src/adapters/cursor/discovery.ts +69 -18
- package/src/adapters/cursor/effort-map.ts +79 -1
- package/src/adapters/cursor/envelope-echo.ts +290 -0
- package/src/adapters/cursor/live-models.ts +7 -2
- package/src/adapters/cursor/live-transport.ts +14 -0
- package/src/adapters/cursor/message-mapper.ts +4 -1
- package/src/adapters/cursor/native-exec-desktop.ts +23 -0
- package/src/adapters/cursor/native-exec-fs.ts +2 -2
- package/src/adapters/cursor/native-exec-network.ts +1 -1
- package/src/adapters/cursor/native-exec-shell.ts +5 -2
- package/src/adapters/cursor/native-exec.ts +20 -0
- package/src/adapters/cursor/protobuf-request.ts +68 -14
- package/src/adapters/cursor/request-builder.ts +40 -10
- package/src/adapters/cursor/tool-definitions.ts +14 -4
- package/src/adapters/cursor/tool-result-normalize.ts +28 -5
- package/src/adapters/cursor/types.ts +20 -0
- package/src/adapters/cursor.ts +209 -38
- package/src/adapters/exec-tool-result-normalize.ts +99 -0
- package/src/adapters/google-antigravity-replay.ts +173 -25
- package/src/adapters/google-antigravity-wire.ts +5 -0
- package/src/adapters/google-errors.ts +44 -12
- package/src/adapters/google.ts +136 -24
- package/src/adapters/image.ts +1 -1
- package/src/adapters/kiro-constants.ts +27 -0
- package/src/adapters/kiro-tools.ts +55 -16
- package/src/adapters/kiro.ts +182 -23
- package/src/adapters/openai-chat.ts +300 -242
- package/src/adapters/openai-responses.ts +353 -27
- package/src/adapters/run-turn-queue.ts +36 -1
- package/src/adapters/tool-catalog-nudge.ts +4 -3
- package/src/adapters/xai-tool-schema.ts +436 -0
- package/src/adapters/xai-web-search.ts +17 -16
- package/src/bridge.ts +90 -37
- package/src/chat/inbound.ts +14 -1
- package/src/chat/outbound.ts +15 -7
- package/src/claude/agents-inject.ts +8 -1
- package/src/claude/context-windows.ts +16 -9
- package/src/claude/outbound.ts +24 -11
- package/src/cli/access.ts +46 -3
- package/src/cli/account-api.ts +116 -19
- package/src/cli/account-extended.ts +262 -29
- package/src/cli/account-main.ts +12 -12
- package/src/cli/account.ts +69 -15
- package/src/cli/agent.ts +8 -1
- package/src/cli/alias.ts +66 -0
- package/src/cli/capabilities-command.ts +94 -0
- package/src/cli/capabilities.ts +496 -0
- package/src/cli/claude-desktop.ts +31 -11
- package/src/cli/claude.ts +26 -1
- package/src/cli/dispatch.ts +256 -28
- package/src/cli/doctor.ts +126 -14
- package/src/cli/help.ts +17 -6
- package/src/cli/index.ts +39 -9
- package/src/cli/init.ts +1 -0
- package/src/cli/inspect.ts +230 -0
- package/src/cli/models-runtime.ts +95 -0
- package/src/cli/models.ts +26 -10
- package/src/cli/observe.ts +31 -8
- package/src/cli/provider-runtime.ts +16 -2
- package/src/cli/provider.ts +2 -1
- package/src/cli/registry.ts +49 -10
- package/src/cli/runtime-api.ts +51 -7
- package/src/cli/status.ts +41 -1
- package/src/cli/storage.ts +234 -0
- package/src/cli/system-command.ts +16 -0
- package/src/cli/system-restart-client.ts +1 -1
- package/src/cli/usage-report.ts +184 -0
- package/src/cli/v2.ts +34 -10
- package/src/cli/version-skew.ts +46 -0
- package/src/clients/config-export.ts +85 -3
- package/src/clients/effective-status.ts +30 -2
- package/src/clients/probes/cline.ts +81 -0
- package/src/codex/account-label.ts +21 -0
- package/src/codex/account-pause.ts +2 -1
- package/src/codex/account-priority.ts +3 -2
- package/src/codex/app-server-processes.ts +83 -7
- package/src/codex/auth-api.ts +50 -10
- package/src/codex/auth-context.ts +21 -18
- package/src/codex/autostart-health.ts +16 -0
- package/src/codex/catalog/aggregation.ts +18 -0
- package/src/codex/catalog/effort.ts +18 -3
- package/src/codex/catalog/metadata.ts +27 -1
- package/src/codex/catalog/native-models.ts +5 -2
- package/src/codex/catalog/parsing.ts +72 -27
- package/src/codex/catalog/provider-fetch.ts +187 -31
- package/src/codex/catalog/sync.ts +131 -5
- package/src/codex/catalog.ts +1 -1
- package/src/codex/codex-write-lock.ts +3 -1
- package/src/codex/convergence-types.ts +1 -1
- package/src/codex/convergence.ts +27 -2
- package/src/codex/desired-state.ts +2 -2
- package/src/codex/desktop-app-restart.ts +355 -0
- package/src/codex/history-job.ts +32 -3
- package/src/codex/history-manifest.ts +112 -0
- package/src/codex/history-migration-guardian.ts +5 -5
- package/src/codex/history-provider.ts +825 -247
- package/src/codex/history-worker.ts +8 -5
- package/src/codex/inject-coordination.ts +83 -0
- package/src/codex/inject.ts +63 -22
- package/src/codex/injected-marker.ts +1 -1
- package/src/codex/internal/history-writer.ts +4 -3
- package/src/codex/log-guard/inspect.ts +22 -4
- package/src/codex/model-entitlements.ts +9 -2
- package/src/codex/native-profile-startup.ts +157 -27
- package/src/codex/native-residue.ts +26 -33
- package/src/codex/prompt-layers.ts +371 -25
- package/src/codex/prompt-text-probe.ts +238 -0
- package/src/codex/quota.ts +91 -10
- package/src/codex/routing.ts +9 -0
- package/src/codex/shim.ts +56 -3
- package/src/codex/subagent-model-fallback.ts +104 -25
- package/src/codex/transition-state.ts +116 -8
- package/src/combos/failover.ts +27 -0
- package/src/combos/types.ts +10 -0
- package/src/compatibility/index.ts +26 -0
- package/src/compatibility/manifest.ts +253 -0
- package/src/compatibility/openai-responses.ts +121 -0
- package/src/config/atomic-write.ts +219 -0
- package/src/config/paths.ts +40 -0
- package/src/config/process-state.ts +309 -0
- package/src/config/provider-validation.ts +177 -0
- package/src/config/rebase-provenance.ts +68 -0
- package/src/config.ts +206 -824
- package/src/generated/compatibility-version.json +373 -213
- package/src/images/loop.ts +5 -4
- package/src/images/plan.ts +5 -4
- package/src/integrations/ownership-policy.ts +160 -0
- package/src/integrations/ownership.ts +46 -2
- package/src/integrations/registry.ts +7 -0
- package/src/integrations/state.ts +79 -7
- package/src/integrations/writer.ts +26 -2
- package/src/lab/conformance/fixtures/protocol-v1-cases.json +1 -1
- package/src/lab/fabric/producer-child.ts +1 -1
- package/src/lib/admin-secrets.ts +24 -0
- package/src/lib/bounded-body.ts +14 -2
- package/src/lib/config-ownership.ts +20 -0
- package/src/lib/errors.ts +36 -3
- package/src/lib/package-tree-integrity.ts +101 -0
- package/src/lib/process-control.ts +2 -1
- package/src/lib/service-secrets.ts +15 -0
- package/src/lib/tool-argument-integers.ts +56 -5
- package/src/oauth/callback-server.ts +22 -2
- package/src/oauth/cline.ts +203 -0
- package/src/oauth/generic-account-failover.ts +231 -0
- package/src/oauth/health.ts +1 -1
- package/src/oauth/index.ts +65 -6
- package/src/oauth/key-providers.ts +10 -1
- package/src/oauth/kimi.ts +9 -1
- package/src/oauth/kiro.ts +45 -0
- package/src/oauth/local-token-detect.ts +2 -0
- package/src/oauth/open-browser-choice.ts +26 -0
- package/src/oauth/store.ts +14 -5
- package/src/oauth/types.ts +15 -0
- package/src/providers/auto-compact-budget.ts +65 -0
- package/src/providers/command-code-efforts.ts +38 -6
- package/src/providers/context-cap.ts +4 -3
- package/src/providers/default-aliases.ts +65 -0
- package/src/providers/derive.ts +28 -0
- package/src/providers/fastwire.ts +7 -1
- package/src/providers/label.ts +34 -1
- package/src/providers/model-presets.ts +119 -0
- package/src/providers/new-model-policy.ts +146 -0
- package/src/providers/provider-id-rewrite.ts +2 -1
- package/src/providers/quota.ts +65 -7
- package/src/providers/registry.ts +184 -69
- package/src/providers/slug-codec.ts +52 -0
- package/src/providers/xai-transport.ts +21 -0
- package/src/reasoning-effort.ts +19 -2
- package/src/responses/apply-patch-envelope.ts +63 -0
- package/src/responses/code-mode-helper-compat.ts +50 -0
- package/src/responses/custom-tool-compat.ts +157 -39
- package/src/responses/namespace-tool-compat.ts +84 -4
- package/src/responses/parser.ts +7 -2
- package/src/responses/reasoning-replay-cache.ts +81 -3
- package/src/responses/schema.ts +5 -1
- package/src/responses/state.ts +94 -4
- package/src/responses/thought-signature-replay.ts +17 -0
- package/src/responses/turn-termination.ts +107 -0
- package/src/router.ts +40 -2
- package/src/routing/analytics.ts +1 -0
- package/src/routing/quota.ts +10 -0
- package/src/server/auth-cors.ts +27 -7
- package/src/server/chat-completions.ts +24 -16
- package/src/server/chat-native-sse.ts +3 -3
- package/src/server/chat-native.ts +30 -11
- package/src/server/claude-messages.ts +1 -1
- package/src/server/index.ts +179 -32
- package/src/server/lifecycle.ts +52 -1
- package/src/server/local-management-read-client.ts +1 -1
- package/src/server/local-provider-reload-client.ts +1 -1
- package/src/server/management/agent-settings-routes.ts +45 -23
- package/src/server/management/codex-prompt-routes.ts +570 -0
- package/src/server/management/combo-routes.ts +2 -1
- package/src/server/management/config-routes.ts +59 -12
- package/src/server/management/context.ts +10 -1
- package/src/server/management/logs-usage-routes.ts +30 -24
- package/src/server/management/model-routes.ts +274 -4
- package/src/server/management/model-rows.ts +4 -0
- package/src/server/management/native-integration-routes.ts +2 -1
- package/src/server/management/oauth-account-routes.ts +10 -4
- package/src/server/management/provider-capability-config.ts +1 -1
- package/src/server/management/provider-routes.ts +81 -11
- package/src/server/management/route-registry.ts +332 -0
- package/src/server/management/routing-profile-routes.ts +2 -2
- package/src/server/management/system-restart.ts +1 -1
- package/src/server/management-api.ts +18 -16
- package/src/server/port-reclaim.ts +1 -1
- package/src/server/proxy-liveness.ts +29 -5
- package/src/server/relay-eager.ts +147 -20
- package/src/server/relay.ts +251 -19
- package/src/server/request-log-conversation.ts +51 -0
- package/src/server/request-log.ts +70 -16
- package/src/server/responses/codex-auth-error.ts +55 -0
- package/src/server/responses/collaboration.ts +37 -3
- package/src/server/responses/combo-stream-preflight.ts +178 -0
- package/src/server/responses/compact.ts +6 -21
- package/src/server/responses/core.ts +810 -208
- package/src/server/responses/empty-completion-guard.ts +63 -6
- package/src/server/responses/fetch-helpers.ts +17 -102
- package/src/server/responses/input-admission.ts +3 -1
- package/src/server/responses/passthrough-error.ts +33 -9
- package/src/server/responses/responses-field-backfill.ts +105 -13
- package/src/server/responses/ws-upstream.ts +75 -2
- package/src/server/responses-custom-tool-repair.ts +87 -6
- package/src/server/responses-terminal-repair.ts +25 -4
- package/src/server/responses-undeclared-tool-guard.ts +241 -18
- package/src/server/sse-frame-buffer.ts +31 -4
- package/src/server/ws-bridge.ts +14 -2
- package/src/service.ts +42 -4
- package/src/storage/policy-job.ts +14 -4
- package/src/storage/policy.ts +88 -23
- package/src/types/config.ts +66 -0
- package/src/types/provider.ts +69 -0
- package/src/types/request.ts +17 -4
- package/src/types/tools.ts +116 -11
- package/src/types.ts +3 -1
- package/src/update/index.ts +5 -4
- package/src/update/job.ts +3 -1
- package/src/update/transactional-install.mjs +8 -1
- package/src/usage/expected-prices.ts +43 -24
- package/src/usage/log.ts +63 -12
- package/src/usage/summary.ts +211 -8
- package/src/vision/describe.ts +18 -13
- package/src/web-search/exa-executor.ts +40 -9
- package/src/web-search/executor.ts +10 -3
- package/src/web-search/loop.ts +5 -4
- package/gui/dist/assets/index-BF38heuV.js +0 -104
- package/gui/dist/assets/index-DMiI18Kv.css +0 -1
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
* Everything mutable about history happens here, behind H: the SQLite rows, the
|
|
5
5
|
* backup manifest, and every rollout patch. Those three do not share a
|
|
6
6
|
* transaction — sync writes the manifest before its database transaction, and
|
|
7
|
-
* restore
|
|
8
|
-
*
|
|
7
|
+
* restore preflights every rollout, applies database CAS before rollout changes inside one
|
|
8
|
+
* SQLite transaction, then consumes the manifest after exact readback — so a busy timeout only
|
|
9
9
|
* ever serialized a third of a state transition. Holding H across the whole unit
|
|
10
10
|
* is what stops an opposite-direction process overtaking through the other two.
|
|
11
11
|
*
|
|
@@ -74,7 +74,8 @@ export type HistoryWorkerResult =
|
|
|
74
74
|
| { readonly type: "blocked"; readonly requestId: string; readonly jobId: string;
|
|
75
75
|
readonly reason: "busy" | "database" | "unsafe-path" | "desired_disabled" | "desired_enabled" }
|
|
76
76
|
| { readonly type: "error"; readonly requestId: string; readonly jobId: string;
|
|
77
|
-
readonly message: string; readonly reason?: CodexHistoryFailureReason
|
|
77
|
+
readonly message: string; readonly reason?: CodexHistoryFailureReason;
|
|
78
|
+
readonly rows?: number; readonly files?: number };
|
|
78
79
|
|
|
79
80
|
const OPERATIONS: ReadonlySet<string> = new Set<CodexHistoryWorkerOperation>([
|
|
80
81
|
"skip",
|
|
@@ -142,8 +143,9 @@ export function runHistoryUnitUnderLock(
|
|
|
142
143
|
const proof = snapshotCodexHistoryNoop(message.canonicalStateDbPath, message.canonicalBackupPath);
|
|
143
144
|
if (proof.kind === "verified-noop") return { verifiedNoop: proof } as const;
|
|
144
145
|
}
|
|
145
|
-
// apply-opencodex routes history to opencodex; migrate/restore
|
|
146
|
-
//
|
|
146
|
+
// apply-opencodex routes history to opencodex; migrate/restore recover only
|
|
147
|
+
// manifest-backed original metadata. The provider is derived from the operation,
|
|
148
|
+
// never from a caller; only recover-legacy-openai force-labels bare routed rows.
|
|
147
149
|
const provider = operation === "apply-opencodex" ? "opencodex" : "openai";
|
|
148
150
|
return writeHistoryProviderTransition(permit, target, provider);
|
|
149
151
|
},
|
|
@@ -179,6 +181,7 @@ export function runHistoryUnitUnderLock(
|
|
|
179
181
|
jobId,
|
|
180
182
|
message: "history_transition_failed",
|
|
181
183
|
...(result.failureReason ? { reason: result.failureReason } : {}),
|
|
184
|
+
...(result.rows > 0 || result.files > 0 ? { rows: result.rows, files: result.files } : {}),
|
|
182
185
|
};
|
|
183
186
|
}
|
|
184
187
|
return {
|
|
@@ -11,7 +11,12 @@ import { atomicWriteFile } from "../config";
|
|
|
11
11
|
import type { CodexWriteLockResult } from "./codex-write-lock";
|
|
12
12
|
import { inspectCodexCoordinatorPath } from "./coordinator-doctor";
|
|
13
13
|
import { JOURNAL_PATH } from "./journal";
|
|
14
|
+
import { updateIntegrationRecord } from "./integration-record";
|
|
14
15
|
import { CODEX_CONFIG_PATH, CODEX_PROFILE_PATH } from "./paths";
|
|
16
|
+
import type {
|
|
17
|
+
CodexArtifactId,
|
|
18
|
+
CodexProvenanceEntry,
|
|
19
|
+
} from "./convergence-types";
|
|
15
20
|
import {
|
|
16
21
|
codexWriteCoordination,
|
|
17
22
|
type CodexWriteCandidate,
|
|
@@ -41,6 +46,7 @@ export const DEFAULT_INJECT_LOCK_TIMEOUT_MS = 5_000;
|
|
|
41
46
|
*/
|
|
42
47
|
export type CodexWriteCoordinationEligibility =
|
|
43
48
|
| { kind: "coordinated" }
|
|
49
|
+
| { kind: "adopt" }
|
|
44
50
|
| { kind: "legacy-uncoordinated"; reason: string }
|
|
45
51
|
| { kind: "refused"; reason: string };
|
|
46
52
|
|
|
@@ -97,6 +103,7 @@ export function codexWriteCoordinationEligibility(deps: {
|
|
|
97
103
|
|
|
98
104
|
const residue = deps.residue();
|
|
99
105
|
if (residue.kind === "clean") return { kind: "coordinated" };
|
|
106
|
+
if (residue.kind === "residue" && !coordinatorIsStableZeroByte) return { kind: "adopt" };
|
|
100
107
|
/*
|
|
101
108
|
* Everything else keeps the path it has always had.
|
|
102
109
|
*
|
|
@@ -176,6 +183,82 @@ export function captureCodexPreImages(): CodexPreImages {
|
|
|
176
183
|
};
|
|
177
184
|
}
|
|
178
185
|
|
|
186
|
+
/**
|
|
187
|
+
* How many transactions of evidence the ledger keeps.
|
|
188
|
+
*
|
|
189
|
+
* Each transaction appends three entries, and a `present` baseline embeds the artifact's exact
|
|
190
|
+
* bytes as base64 — a 25 KB `config.toml` is ~34 KB per entry, so roughly 100 KB per transaction.
|
|
191
|
+
* A machine that syncs on every start would grow this file without limit, and it is re-read and
|
|
192
|
+
* re-serialized on every append, so the cost is quadratic rather than merely large.
|
|
193
|
+
*
|
|
194
|
+
* A ledger is evidence, not an archive. The most recent transactions are the ones anyone
|
|
195
|
+
* diagnoses against, so the window keeps those and drops the oldest.
|
|
196
|
+
*/
|
|
197
|
+
export const CODEX_PROVENANCE_MAX_TRANSACTIONS = 16;
|
|
198
|
+
|
|
199
|
+
function provenanceBaseline(bytes: string | null): CodexProvenanceEntry["baseline"] {
|
|
200
|
+
if (bytes === null) return { kind: "absent" };
|
|
201
|
+
return {
|
|
202
|
+
kind: "present",
|
|
203
|
+
sha256: createHash("sha256").update(bytes).digest("hex"),
|
|
204
|
+
bytesBase64: Buffer.from(bytes).toString("base64"),
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function provenancePostImage(path: string): string | null {
|
|
209
|
+
try {
|
|
210
|
+
return createHash("sha256").update(readFileSync(path)).digest("hex");
|
|
211
|
+
} catch {
|
|
212
|
+
return null;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Keep the newest `CODEX_PROVENANCE_MAX_TRANSACTIONS` transactions, whole.
|
|
218
|
+
*
|
|
219
|
+
* Trimming by ENTRY count would cut a transaction in half and leave evidence that says a
|
|
220
|
+
* transaction touched two artifacts when it touched three — worse than dropping it outright,
|
|
221
|
+
* because a partial record still reads as complete. Order is preserved; only whole leading
|
|
222
|
+
* transactions are removed.
|
|
223
|
+
*/
|
|
224
|
+
export function boundProvenanceEntries(
|
|
225
|
+
entries: readonly CodexProvenanceEntry[],
|
|
226
|
+
maxTransactions = CODEX_PROVENANCE_MAX_TRANSACTIONS,
|
|
227
|
+
): readonly CodexProvenanceEntry[] {
|
|
228
|
+
const order: string[] = [];
|
|
229
|
+
for (const entry of entries) if (!order.includes(entry.txId)) order.push(entry.txId);
|
|
230
|
+
if (order.length <= maxTransactions) return entries;
|
|
231
|
+
const keep = new Set(order.slice(order.length - maxTransactions));
|
|
232
|
+
return entries.filter(entry => keep.has(entry.txId));
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/** Append evidence for an already-committed native transaction, best-effort. */
|
|
236
|
+
export function recordCodexNativeTransactionProvenance(
|
|
237
|
+
preImages: CodexPreImages,
|
|
238
|
+
txId: string,
|
|
239
|
+
) {
|
|
240
|
+
const at = new Date().toISOString();
|
|
241
|
+
const surfaces: readonly [CodexArtifactId, string, string | null][] = [
|
|
242
|
+
[{ kind: "config" }, CODEX_CONFIG_PATH, preImages.config],
|
|
243
|
+
[{ kind: "generated-profile" }, CODEX_PROFILE_PATH, preImages.profile],
|
|
244
|
+
[{ kind: "injection-journal" }, JOURNAL_PATH, preImages.journal],
|
|
245
|
+
];
|
|
246
|
+
const entries: readonly CodexProvenanceEntry[] = surfaces.map(([artifact, path, baseline]) => ({
|
|
247
|
+
artifact,
|
|
248
|
+
baseline: provenanceBaseline(baseline),
|
|
249
|
+
postImage: provenancePostImage(path),
|
|
250
|
+
txId,
|
|
251
|
+
at,
|
|
252
|
+
}));
|
|
253
|
+
return updateIntegrationRecord(record => ({
|
|
254
|
+
...record,
|
|
255
|
+
provenance: {
|
|
256
|
+
...record.provenance,
|
|
257
|
+
entries: boundProvenanceEntries([...(record.provenance?.entries ?? []), ...entries]),
|
|
258
|
+
},
|
|
259
|
+
}));
|
|
260
|
+
}
|
|
261
|
+
|
|
179
262
|
/**
|
|
180
263
|
* Put back exactly what was there, and report honestly when that fails.
|
|
181
264
|
*
|
package/src/codex/inject.ts
CHANGED
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
CodexWriteConflictError,
|
|
20
20
|
DEFAULT_INJECT_LOCK_TIMEOUT_MS,
|
|
21
21
|
recomputeInjectWitness,
|
|
22
|
+
recordCodexNativeTransactionProvenance,
|
|
22
23
|
restoreCodexPreImages,
|
|
23
24
|
} from "./inject-coordination";
|
|
24
25
|
import { readIntegrationRecord } from "./integration-record";
|
|
@@ -953,6 +954,7 @@ export async function injectCodexConfig(
|
|
|
953
954
|
const coordinated = await withCodexWriteLock(
|
|
954
955
|
{
|
|
955
956
|
timeoutMs: options.lockTimeoutMs ?? DEFAULT_INJECT_LOCK_TIMEOUT_MS,
|
|
957
|
+
...(eligibility.kind === "adopt" ? { adoption: { direction: "apply" as const } } : {}),
|
|
956
958
|
admitted: { authoritySnapshotId: witness.comparisonId },
|
|
957
959
|
readAdmissionUnderLock: () => ({
|
|
958
960
|
authoritySnapshotId: recomputeInjectWitness({
|
|
@@ -1019,6 +1021,7 @@ export async function injectCodexConfig(
|
|
|
1019
1021
|
}
|
|
1020
1022
|
return {
|
|
1021
1023
|
kind: "applied" as const,
|
|
1024
|
+
preImages,
|
|
1022
1025
|
/*
|
|
1023
1026
|
* The receipt the terminal update matches on. The transition commits
|
|
1024
1027
|
* when the callback returns, so this pair is what the post-job
|
|
@@ -1036,6 +1039,10 @@ export async function injectCodexConfig(
|
|
|
1036
1039
|
if (coordinated.status !== "acquired") {
|
|
1037
1040
|
return codexInjectLockOutcome(coordinated);
|
|
1038
1041
|
}
|
|
1042
|
+
recordCodexNativeTransactionProvenance(
|
|
1043
|
+
coordinated.value.preImages,
|
|
1044
|
+
coordinated.value.receipt.currentTxId,
|
|
1045
|
+
);
|
|
1039
1046
|
transitionReceipt = coordinated.value.receipt;
|
|
1040
1047
|
}
|
|
1041
1048
|
// Legacy mode still forward-tags history so re-tagged threads stay listable. Design B needs
|
|
@@ -1092,8 +1099,8 @@ export async function injectCodexConfig(
|
|
|
1092
1099
|
: legacyMode
|
|
1093
1100
|
? ` Codex resume history: ${history.rows} thread(s) made visible for opencodex; originals backed up for restore.\n`
|
|
1094
1101
|
: migratedRows > 0
|
|
1095
|
-
? ` Codex resume history: ${migratedRows}
|
|
1096
|
-
: ` Codex resume history:
|
|
1102
|
+
? ` Codex resume history: restored original provider metadata for ${migratedRows} manifest-backed thread(s) (one-time).\n`
|
|
1103
|
+
: ` Codex resume history: no backed-up metadata pending; untracked routed history left unchanged.\n`;
|
|
1097
1104
|
// A user-owned root openai_base_url means we did NOT install routing — say so honestly
|
|
1098
1105
|
// instead of claiming the proxy route is active (catalog/fast_mode were still written).
|
|
1099
1106
|
if (keptUserBaseUrl) {
|
|
@@ -1332,18 +1339,33 @@ export interface CodexNativeRestoreResult {
|
|
|
1332
1339
|
};
|
|
1333
1340
|
}
|
|
1334
1341
|
|
|
1335
|
-
function failedHistoryRestore(
|
|
1342
|
+
function failedHistoryRestore(
|
|
1343
|
+
reason?: CodexHistoryFailureReason,
|
|
1344
|
+
detail?: string,
|
|
1345
|
+
progress: { rows?: number; files?: number } = {},
|
|
1346
|
+
): CodexRestoreHistoryResult {
|
|
1347
|
+
const rows = progress.rows ?? 0;
|
|
1348
|
+
const files = progress.files ?? 0;
|
|
1349
|
+
const changed = rows > 0 || files > 0;
|
|
1336
1350
|
return {
|
|
1337
1351
|
state: "failed",
|
|
1338
|
-
changed
|
|
1352
|
+
changed,
|
|
1339
1353
|
...(reason ? { reason } : {}),
|
|
1340
|
-
rows
|
|
1341
|
-
files
|
|
1354
|
+
rows,
|
|
1355
|
+
files,
|
|
1342
1356
|
ejectedRows: 0,
|
|
1343
1357
|
message: reason === "permission"
|
|
1344
|
-
?
|
|
1358
|
+
? changed
|
|
1359
|
+
? "Codex resume history changed but did NOT converge because permission was denied while finalizing the backup manifest; the manifest was retained for review and safe retry."
|
|
1360
|
+
: "Codex resume history could NOT be restored because permission was denied."
|
|
1345
1361
|
: reason === "busy"
|
|
1346
|
-
?
|
|
1362
|
+
? changed
|
|
1363
|
+
? "Codex resume history changed but did NOT converge because backup-manifest finalization remained busy; the manifest was retained for review and safe retry."
|
|
1364
|
+
: detail ?? "Codex resume history could NOT be restored — the Codex app appears to be holding the history database."
|
|
1365
|
+
: reason === "integrity"
|
|
1366
|
+
? changed
|
|
1367
|
+
? "Codex resume history changed but did NOT converge because the backup or target changed; the manifest was retained for review and safe retry."
|
|
1368
|
+
: "Codex resume history could NOT be restored because the backup or restore target failed integrity checks; unverified provider metadata was left unchanged."
|
|
1347
1369
|
: detail
|
|
1348
1370
|
? `Codex resume history could NOT be restored: ${detail}`
|
|
1349
1371
|
: "Codex resume history could NOT be restored; the reason was not recorded. Run 'ocx doctor'.",
|
|
@@ -1365,9 +1387,18 @@ export function failedHistoryRestoreFromOutcome(
|
|
|
1365
1387
|
outcome: Extract<CodexHistoryJobOutcome, { kind: "blocked" | "failed" }>,
|
|
1366
1388
|
): CodexRestoreHistoryResult {
|
|
1367
1389
|
if (outcome.kind === "blocked" && outcome.reason === "busy") return failedHistoryRestore("busy");
|
|
1368
|
-
if (outcome.kind === "failed" && outcome.historyFailureReason === "busy")
|
|
1390
|
+
if (outcome.kind === "failed" && outcome.historyFailureReason === "busy") {
|
|
1391
|
+
return failedHistoryRestore(
|
|
1392
|
+
"busy",
|
|
1393
|
+
describeHistoryJobFailure(outcome, "restore"),
|
|
1394
|
+
{ rows: outcome.rows, files: outcome.files },
|
|
1395
|
+
);
|
|
1396
|
+
}
|
|
1369
1397
|
if (outcome.kind === "failed" && outcome.historyFailureReason === "permission") {
|
|
1370
|
-
return failedHistoryRestore("permission");
|
|
1398
|
+
return failedHistoryRestore("permission", undefined, { rows: outcome.rows, files: outcome.files });
|
|
1399
|
+
}
|
|
1400
|
+
if (outcome.kind === "failed" && outcome.historyFailureReason === "integrity") {
|
|
1401
|
+
return failedHistoryRestore("integrity", undefined, { rows: outcome.rows, files: outcome.files });
|
|
1371
1402
|
}
|
|
1372
1403
|
return failedHistoryRestore(undefined, describeHistoryJobFailure(outcome, "restore"));
|
|
1373
1404
|
}
|
|
@@ -1523,13 +1554,14 @@ export async function restoreNativeCodexAsync(
|
|
|
1523
1554
|
let config: CodexRestoreConfigResult;
|
|
1524
1555
|
let transitionReceipt: { nativeGeneration: number; currentTxId: string } | undefined;
|
|
1525
1556
|
|
|
1526
|
-
if (eligibility.kind === "coordinated") {
|
|
1557
|
+
if (eligibility.kind === "coordinated" || eligibility.kind === "adopt") {
|
|
1527
1558
|
// The restore has no candidate bytes to witness; freshness comes from the
|
|
1528
1559
|
// filesystem reads and the desired-state re-read performed under the lock.
|
|
1529
1560
|
const witness = { authoritySnapshotId: "codex-native-restore" };
|
|
1530
1561
|
const coordinated = await withCodexWriteLock(
|
|
1531
1562
|
{
|
|
1532
1563
|
timeoutMs: DEFAULT_INJECT_LOCK_TIMEOUT_MS,
|
|
1564
|
+
...(eligibility.kind === "adopt" ? { adoption: { direction: "remove" as const } } : {}),
|
|
1533
1565
|
admitted: witness,
|
|
1534
1566
|
readAdmissionUnderLock: () => witness,
|
|
1535
1567
|
},
|
|
@@ -1565,6 +1597,7 @@ export async function restoreNativeCodexAsync(
|
|
|
1565
1597
|
}
|
|
1566
1598
|
return {
|
|
1567
1599
|
config: restored,
|
|
1600
|
+
preImages,
|
|
1568
1601
|
receipt: {
|
|
1569
1602
|
nativeGeneration: ctx.expectation.nativeAfter,
|
|
1570
1603
|
currentTxId: ctx.expectation.txId,
|
|
@@ -1583,6 +1616,10 @@ export async function restoreNativeCodexAsync(
|
|
|
1583
1616
|
: `Codex configuration was not restored: ${coordinated.message}`,
|
|
1584
1617
|
};
|
|
1585
1618
|
} else {
|
|
1619
|
+
recordCodexNativeTransactionProvenance(
|
|
1620
|
+
coordinated.value.preImages,
|
|
1621
|
+
coordinated.value.receipt.currentTxId,
|
|
1622
|
+
);
|
|
1586
1623
|
config = coordinated.value.config;
|
|
1587
1624
|
transitionReceipt = coordinated.value.receipt;
|
|
1588
1625
|
}
|
|
@@ -1607,10 +1644,10 @@ export async function restoreNativeCodexAsync(
|
|
|
1607
1644
|
}
|
|
1608
1645
|
const history: CodexRestoreHistoryResult = outcome.kind === "converged"
|
|
1609
1646
|
? {
|
|
1610
|
-
state: "ok", changed: outcome.rows > 0, rows: outcome.rows, files: outcome.files, ejectedRows: 0,
|
|
1647
|
+
state: "ok", changed: outcome.rows > 0 || outcome.files > 0, rows: outcome.rows, files: outcome.files, ejectedRows: 0,
|
|
1611
1648
|
message: outcome.rows > 0
|
|
1612
|
-
? `Resume history restored from opencodex backup (${outcome.rows} thread(s)).`
|
|
1613
|
-
: "
|
|
1649
|
+
? `Resume history metadata restored from opencodex backup (${outcome.rows} thread(s)); original providers preserved.`
|
|
1650
|
+
: "No backed-up resume-history metadata was pending; untracked routed history was left unchanged.",
|
|
1614
1651
|
}
|
|
1615
1652
|
: outcome.kind === "skipped"
|
|
1616
1653
|
? { state: "skipped", changed: false, rows: 0, files: 0, ejectedRows: 0, message: "Codex resume history was skipped." }
|
|
@@ -1672,16 +1709,16 @@ export function restoreNativeCodex(options: { skipHistory?: boolean; revalidateD
|
|
|
1672
1709
|
const history: CodexRestoreHistoryResult = options.skipHistory
|
|
1673
1710
|
? { state: "skipped", changed: false, rows: 0, files: 0, ejectedRows: 0, message: "History restoration runs asynchronously." }
|
|
1674
1711
|
: rawHistory.failed
|
|
1675
|
-
? failedHistoryRestore(rawHistory.failureReason)
|
|
1712
|
+
? failedHistoryRestore(rawHistory.failureReason, undefined, rawHistory)
|
|
1676
1713
|
: {
|
|
1677
1714
|
state: "ok",
|
|
1678
|
-
changed: rawHistory.rows > 0 || (rawHistory.ejectedRows ?? 0) > 0,
|
|
1715
|
+
changed: rawHistory.rows > 0 || rawHistory.files > 0 || (rawHistory.ejectedRows ?? 0) > 0,
|
|
1679
1716
|
rows: rawHistory.rows,
|
|
1680
1717
|
files: rawHistory.files,
|
|
1681
1718
|
ejectedRows: rawHistory.ejectedRows ?? 0,
|
|
1682
1719
|
message: rawHistory.rows > 0
|
|
1683
|
-
? `Resume history restored from opencodex backup (${rawHistory.rows} thread(s)).`
|
|
1684
|
-
: "
|
|
1720
|
+
? `Resume history metadata restored from opencodex backup (${rawHistory.rows} thread(s)); original providers preserved.`
|
|
1721
|
+
: "No backed-up resume-history metadata was pending; untracked routed history was left unchanged.",
|
|
1685
1722
|
};
|
|
1686
1723
|
const message = catalog.removed > 0
|
|
1687
1724
|
? `${config.message} Catalog restored to ${catalog.kept} native model(s) (dropped ${catalog.removed} proxy-routed).`
|
|
@@ -1711,10 +1748,14 @@ export function formatApplyHistoryFailure(outcome: CodexHistoryJobOutcome, legac
|
|
|
1711
1748
|
const busy =
|
|
1712
1749
|
(outcome.kind === "blocked" && outcome.reason === "busy") ||
|
|
1713
1750
|
(outcome.kind === "failed" && outcome.historyFailureReason === "busy");
|
|
1714
|
-
const
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1751
|
+
const partiallyChanged = outcome.kind === "failed"
|
|
1752
|
+
&& ((outcome.rows ?? 0) > 0 || (outcome.files ?? 0) > 0);
|
|
1753
|
+
const headline = partiallyChanged
|
|
1754
|
+
? "Codex resume history changed but did not converge"
|
|
1755
|
+
: legacyMode
|
|
1756
|
+
? "Codex resume history sync SKIPPED"
|
|
1757
|
+
: busy
|
|
1758
|
+
? "Codex resume history metadata restore deferred"
|
|
1718
1759
|
: "Codex resume history NOT changed";
|
|
1719
1760
|
return ` ⚠️ ${headline}: ${describeHistoryJobFailure(outcome, "apply", legacyMode)}\n`;
|
|
1720
1761
|
}
|
|
@@ -22,7 +22,7 @@ export function tomlStringPattern(key: string): RegExp {
|
|
|
22
22
|
// A basic string escapes backslashes, so a Windows path is stored doubled; reading
|
|
23
23
|
// the raw bytes back returned a path that matched nothing on disk and made the
|
|
24
24
|
// journal's recorded catalog path un-restorable (#1798).
|
|
25
|
-
return new RegExp(`^\\s*${keyToken}\\s*=\\s*("(?:\\\\.|[^"])*"|'[^']*')\\s*(?:#.*)?$`);
|
|
25
|
+
return new RegExp(`^\\s*${keyToken}\\s*=\\s*("(?:\\\\.|[^"\\\\])*"|'[^']*')\\s*(?:#.*)?$`);
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
export function rootTomlString(content: string, key: string): string | null {
|
|
@@ -5,9 +5,10 @@
|
|
|
5
5
|
* This module exists because the mutations are spread across three surfaces that
|
|
6
6
|
* do not share a transaction: SQLite rows, the backup manifest, and the rollout
|
|
7
7
|
* files. `syncCodexHistoryProvider` writes the manifest BEFORE its database
|
|
8
|
-
* transaction and patches rollouts inside it; restore
|
|
9
|
-
* database
|
|
10
|
-
* (`src/codex/history-provider.ts
|
|
8
|
+
* transaction and patches rollouts inside it; restore preflights every rollout,
|
|
9
|
+
* applies database CAS before file changes in one SQLite transaction, then consumes the
|
|
10
|
+
* manifest after exact readback (`src/codex/history-provider.ts`). A
|
|
11
|
+
* SQLite busy timeout
|
|
11
12
|
* serializes exactly one of those three, which is why an opposite-direction
|
|
12
13
|
* process could overtake through the other two.
|
|
13
14
|
*
|
|
@@ -11,6 +11,9 @@ import {
|
|
|
11
11
|
|
|
12
12
|
const IMMUTABLE_READONLY_FLAGS = constants.SQLITE_OPEN_READONLY | constants.SQLITE_OPEN_URI;
|
|
13
13
|
const KNOWN_LOG_LEVELS = new Set(["TRACE", "DEBUG", "INFO", "WARN", "ERROR"]);
|
|
14
|
+
// The issue reporter measured a ~1 GB database taking 17.3s for GROUP BY level
|
|
15
|
+
// alone; skipping all row aggregates above 64 MiB reduced /api/storage to 628ms.
|
|
16
|
+
const MAX_SYNCHRONOUS_METRICS_DATABASE_BYTES = 64 * 1024 * 1024;
|
|
14
17
|
|
|
15
18
|
interface CurrentLogColumn {
|
|
16
19
|
name: string;
|
|
@@ -115,6 +118,10 @@ export interface CodexLogGuardInspection {
|
|
|
115
118
|
reclaim: CodexLogGuardCapability;
|
|
116
119
|
};
|
|
117
120
|
metrics: CodexLogGuardMetrics | null;
|
|
121
|
+
metricsSkipped: null | {
|
|
122
|
+
reason: "database_too_large";
|
|
123
|
+
thresholdBytes: number;
|
|
124
|
+
};
|
|
118
125
|
}
|
|
119
126
|
|
|
120
127
|
interface ColumnRow {
|
|
@@ -167,9 +174,8 @@ function fileSize(path: string): number {
|
|
|
167
174
|
* repeated stalls without ever serving stale numbers: any write changes the WAL
|
|
168
175
|
* and invalidates the entry.
|
|
169
176
|
*
|
|
170
|
-
*
|
|
171
|
-
*
|
|
172
|
-
* tracked separately.
|
|
177
|
+
* Memoization bounds repeat cost. The database-size gate below separately bounds
|
|
178
|
+
* cold request-thread work by omitting these aggregates for large databases.
|
|
173
179
|
*/
|
|
174
180
|
type InspectionCacheEntry = {
|
|
175
181
|
key: string;
|
|
@@ -233,6 +239,7 @@ function unavailableInspection(): CodexLogGuardInspection {
|
|
|
233
239
|
reclaim: unavailable,
|
|
234
240
|
},
|
|
235
241
|
metrics: null,
|
|
242
|
+
metricsSkipped: null,
|
|
236
243
|
};
|
|
237
244
|
}
|
|
238
245
|
|
|
@@ -425,6 +432,7 @@ function inspectCodexLogsUncached(deps: CodexSqliteHomeDeps = {}): CodexLogGuard
|
|
|
425
432
|
...common,
|
|
426
433
|
schema,
|
|
427
434
|
metrics: null,
|
|
435
|
+
metricsSkipped: null,
|
|
428
436
|
capabilities: {
|
|
429
437
|
inspection: { state: "supported" },
|
|
430
438
|
protection: mutation,
|
|
@@ -440,6 +448,7 @@ function inspectCodexLogsUncached(deps: CodexSqliteHomeDeps = {}): CodexLogGuard
|
|
|
440
448
|
...common,
|
|
441
449
|
schema,
|
|
442
450
|
metrics: null,
|
|
451
|
+
metricsSkipped: null,
|
|
443
452
|
capabilities: {
|
|
444
453
|
inspection: { state: "supported" },
|
|
445
454
|
protection: mutation,
|
|
@@ -458,6 +467,7 @@ function inspectCodexLogsUncached(deps: CodexSqliteHomeDeps = {}): CodexLogGuard
|
|
|
458
467
|
...common,
|
|
459
468
|
schema,
|
|
460
469
|
metrics: null,
|
|
470
|
+
metricsSkipped: null,
|
|
461
471
|
capabilities: {
|
|
462
472
|
inspection: { state: "supported" },
|
|
463
473
|
protection: mutation,
|
|
@@ -476,10 +486,17 @@ function inspectCodexLogsUncached(deps: CodexSqliteHomeDeps = {}): CodexLogGuard
|
|
|
476
486
|
? { state: "compatible" }
|
|
477
487
|
: { state: "unsupported", reason: "unknown_schema" };
|
|
478
488
|
const mutation = capabilityFor(schema);
|
|
489
|
+
const metricsSkipped = files.databaseBytes > MAX_SYNCHRONOUS_METRICS_DATABASE_BYTES
|
|
490
|
+
? {
|
|
491
|
+
reason: "database_too_large" as const,
|
|
492
|
+
thresholdBytes: MAX_SYNCHRONOUS_METRICS_DATABASE_BYTES,
|
|
493
|
+
}
|
|
494
|
+
: null;
|
|
479
495
|
return {
|
|
480
496
|
...common,
|
|
481
497
|
schema,
|
|
482
|
-
metrics: readMetrics(db, columns),
|
|
498
|
+
metrics: metricsSkipped === null ? readMetrics(db, columns) : null,
|
|
499
|
+
metricsSkipped,
|
|
483
500
|
capabilities: {
|
|
484
501
|
inspection: { state: "supported" },
|
|
485
502
|
protection: mutation,
|
|
@@ -496,6 +513,7 @@ function inspectCodexLogsUncached(deps: CodexSqliteHomeDeps = {}): CodexLogGuard
|
|
|
496
513
|
...common,
|
|
497
514
|
schema,
|
|
498
515
|
metrics: null,
|
|
516
|
+
metricsSkipped: null,
|
|
499
517
|
capabilities: {
|
|
500
518
|
inspection: { state: "supported" },
|
|
501
519
|
protection: mutation,
|
|
@@ -40,6 +40,10 @@ export interface CodexModelEntitlementResolveOptions {
|
|
|
40
40
|
readonly now?: number;
|
|
41
41
|
/** Test-only credential seam; production callers enumerate local main + Pool credentials. */
|
|
42
42
|
readonly credentials?: readonly CodexModelEntitlementCredentialSnapshot[];
|
|
43
|
+
/** Test-only seam for proving lifecycle exclusions happen before credential reads. */
|
|
44
|
+
readonly credentialSnapshot?: typeof accountCredentialSnapshot;
|
|
45
|
+
/** Accounts whose credentials must not be read while another lifecycle owns them. */
|
|
46
|
+
readonly excludeAccountIds?: ReadonlySet<string>;
|
|
43
47
|
}
|
|
44
48
|
|
|
45
49
|
const accountModelsCache = new Map<string, CachedAccountModels>();
|
|
@@ -252,9 +256,12 @@ export async function resolveCodexModelEntitlements(
|
|
|
252
256
|
): Promise<CodexModelEntitlementSnapshot> {
|
|
253
257
|
const now = options.now ?? Date.now();
|
|
254
258
|
const fetcher = options.fetcher ?? fetch;
|
|
259
|
+
const allowedAccountIds = candidateAccountIds(config)
|
|
260
|
+
.filter(accountId => !options.excludeAccountIds?.has(accountId));
|
|
261
|
+
const credentialSnapshot = options.credentialSnapshot ?? accountCredentialSnapshot;
|
|
255
262
|
const credentials = options.credentials
|
|
256
|
-
? [...options.credentials]
|
|
257
|
-
: (await Promise.all(
|
|
263
|
+
? [...options.credentials].filter(credential => !options.excludeAccountIds?.has(credential.accountId))
|
|
264
|
+
: (await Promise.all(allowedAccountIds.map(credentialSnapshot)))
|
|
258
265
|
.filter((value): value is CodexModelEntitlementCredentialSnapshot => value !== null);
|
|
259
266
|
const results = await Promise.all(credentials.map(async credential => ({
|
|
260
267
|
credential,
|