@bitkyc08/opencodex 2.12.0 → 2.13.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 +1 -1
- package/gui/dist/assets/index-BHldBl6_.js +76 -0
- package/gui/dist/index.html +1 -1
- package/package.json +1 -1
- package/src/adapters/anthropic-output-schema.ts +137 -0
- package/src/adapters/anthropic.ts +15 -0
- package/src/adapters/google-antigravity-replay.ts +269 -5
- package/src/adapters/openai-chat.ts +25 -18
- package/src/adapters/openai-responses.ts +63 -17
- package/src/bridge.ts +11 -5
- package/src/claude/inbound.ts +24 -3
- package/src/cli/account-api.ts +2 -0
- package/src/cli/account-extended.ts +262 -1
- package/src/cli/account.ts +3 -1
- package/src/cli/dispatch.ts +532 -0
- package/src/cli/doctor.ts +26 -21
- package/src/cli/help.ts +9 -278
- package/src/cli/index.ts +45 -506
- package/src/cli/lab.ts +171 -65
- package/src/cli/registry.ts +384 -0
- package/src/cli/root.ts +86 -0
- package/src/cli/status.ts +9 -2
- package/src/cli/v2.ts +37 -2
- package/src/codex/account-label.ts +14 -1
- package/src/codex/account-store.ts +12 -1
- package/src/codex/admission.ts +2 -1
- package/src/codex/auth-api.ts +3 -2
- package/src/codex/catalog/metadata.ts +6 -4
- package/src/codex/catalog/parsing.ts +31 -7
- package/src/codex/catalog/provider-fetch.ts +2 -1
- package/src/codex/catalog/sync.ts +65 -11
- package/src/codex/features.ts +437 -31
- package/src/codex/history-job.ts +6 -11
- package/src/codex/history-provider.ts +10 -9
- package/src/codex/inject.ts +14 -1
- package/src/codex/native-profile-startup.ts +49 -3
- package/src/codex/native-residue.ts +14 -2
- package/src/codex/paths.ts +75 -1
- package/src/codex/reset-credit-recovery.ts +1044 -0
- package/src/codex/shim.ts +876 -16
- package/src/codex/sync.ts +27 -2
- package/src/config.ts +148 -10
- package/src/generated/compatibility-version.json +2572 -0
- package/src/images/loop.ts +1 -1
- package/src/lab/artifacts/sanitize.ts +3 -3
- package/src/lab/automation/budgets.ts +78 -0
- package/src/lab/automation/config-persistence.ts +256 -0
- package/src/lab/automation/constants.ts +39 -0
- package/src/lab/automation/cooldown.ts +103 -0
- package/src/lab/automation/dispatch.ts +211 -0
- package/src/lab/automation/index.ts +13 -0
- package/src/lab/automation/orchestrator.ts +480 -0
- package/src/lab/automation/persistence.ts +512 -0
- package/src/lab/automation/planner.ts +371 -0
- package/src/lab/automation/policy.ts +136 -0
- package/src/lab/automation/queue.ts +191 -0
- package/src/lab/automation/recovery.ts +24 -0
- package/src/lab/automation/route-context.ts +21 -0
- package/src/lab/automation/run-key.ts +44 -0
- package/src/lab/automation/runs-query.ts +34 -0
- package/src/lab/automation/types.ts +160 -0
- package/src/lab/conformance/types.ts +23 -0
- package/src/lab/events/types.ts +2 -1
- package/src/lab/events/validate.ts +2 -1
- package/src/lab/fabric/constants.ts +40 -0
- package/src/lab/fabric/executor.ts +492 -0
- package/src/lab/fabric/index.ts +80 -0
- package/src/lab/fabric/manifest.ts +222 -0
- package/src/lab/fabric/observe.ts +454 -0
- package/src/lab/fabric/patch.ts +79 -0
- package/src/lab/fabric/producer-child.ts +139 -0
- package/src/lab/fabric/producer-isolate.ts +276 -0
- package/src/lab/fabric/producer-protocol.ts +61 -0
- package/src/lab/fabric/scratch.ts +439 -0
- package/src/lab/fabric/subject.ts +106 -0
- package/src/lab/fabric/types.ts +134 -0
- package/src/lab/fabric/verifier.ts +98 -0
- package/src/lab/index.ts +2 -0
- package/src/lab/ledger/store.ts +173 -0
- package/src/lab/live/executor.ts +29 -4
- package/src/lab/live/transport.ts +3 -1
- package/src/lab/observe/from-conformance.ts +12 -57
- package/src/lab/observe/from-live.ts +9 -1
- package/src/lab/paths.ts +74 -9
- package/src/lab/projection/schema.ts +14 -1
- package/src/lab/projection/verdicts.ts +89 -53
- package/src/lab/projection/verification.ts +83 -3
- package/src/lab/query/catalog.ts +34 -5
- package/src/lab/query/freshness.ts +53 -0
- package/src/lab/query/index.ts +11 -0
- package/src/lab/query/latest-observation.ts +59 -0
- package/src/lab/query/passive-production.ts +159 -0
- package/src/lab/subject/installation-salt.ts +24 -4
- package/src/lab/subject/protocol-subject.ts +80 -0
- package/src/lab/subject/route-subject.ts +2 -1
- package/src/lib/fabric-task-execution-authority.ts +7 -0
- package/src/lib/fabric-task-host.ts +29 -0
- package/src/lib/lab-live-route-production.ts +130 -0
- package/src/lib/local-management-capability.ts +100 -0
- package/src/lib/server-resource-ownership.ts +71 -0
- package/src/lib/windows-elevation.ts +8 -1
- package/src/lib/windows-user-principal.ts +62 -2
- package/src/lib/winsw.ts +8 -2
- package/src/oauth/account-import/google-antigravity-adapter.ts +74 -0
- package/src/oauth/account-import/index.ts +15 -0
- package/src/oauth/account-import/parser.ts +83 -0
- package/src/oauth/account-import/registry.ts +18 -0
- package/src/oauth/account-import/service.ts +75 -0
- package/src/oauth/account-import/types.ts +91 -0
- package/src/oauth/anthropic.ts +12 -1
- package/src/oauth/callback-server.ts +8 -2
- package/src/oauth/chatgpt.ts +12 -1
- package/src/oauth/google-antigravity.ts +27 -0
- package/src/oauth/health.ts +12 -48
- package/src/oauth/index.ts +122 -0
- package/src/oauth/kimi.ts +16 -2
- package/src/oauth/nous.ts +749 -0
- package/src/oauth/store.ts +56 -3
- package/src/providers/codex-capacity.ts +1 -0
- package/src/providers/registry.ts +161 -16
- package/src/responses/custom-tool-compat.ts +182 -0
- package/src/responses/parser.ts +22 -0
- package/src/responses/reasoning-replay-cache.ts +168 -11
- package/src/responses/schema.ts +1 -1
- package/src/router.ts +5 -20
- package/src/routing/capability.ts +9 -3
- package/src/routing/compatibility/assemble.ts +159 -0
- package/src/routing/compatibility/behavior.ts +224 -0
- package/src/routing/compatibility/catalog.ts +99 -0
- package/src/routing/compatibility/endpoint.ts +52 -0
- package/src/routing/compatibility/policy.ts +181 -0
- package/src/routing/compatibility/reader.ts +110 -0
- package/src/routing/compatibility/subject.ts +191 -0
- package/src/routing/compatibility/types.ts +64 -0
- package/src/routing/compatibility/version.ts +104 -0
- package/src/routing/evaluator.ts +27 -32
- package/src/routing/profile.ts +118 -4
- package/src/routing/trace.ts +82 -28
- package/src/server/auth-cors.ts +7 -0
- package/src/server/background-lifecycle.ts +20 -7
- package/src/server/chat-completions.ts +2 -2
- package/src/server/claude-messages.ts +6 -3
- package/src/server/direct-local-http.ts +343 -0
- package/src/server/index.ts +80 -18
- package/src/server/lifecycle.ts +12 -4
- package/src/server/local-management-read-client.ts +90 -0
- package/src/server/management/agent-settings-routes.ts +27 -3
- package/src/server/management/lab-automation-routes.ts +206 -0
- package/src/server/management/lab-routes.ts +13 -0
- package/src/server/management/logs-usage-routes.ts +1 -0
- package/src/server/management/oauth-account-routes.ts +57 -1
- package/src/server/management/provider-routes.ts +16 -0
- package/src/server/management/routing-profile-routes.ts +12 -24
- package/src/server/management-api.ts +2 -0
- package/src/server/management-auth.ts +68 -3
- package/src/server/proxy-liveness.ts +3 -2
- package/src/server/request-decompress.ts +9 -3
- package/src/server/request-log.ts +53 -4
- package/src/server/responses/collaboration.ts +93 -17
- package/src/server/responses/compact.ts +3 -0
- package/src/server/responses/core.ts +202 -15
- package/src/server/responses/policy-fallback.ts +1 -0
- package/src/server/responses-custom-tool-repair.ts +280 -0
- package/src/server/responses-terminal-repair.ts +342 -0
- package/src/server/startup-action-control.ts +8 -1
- package/src/service.ts +278 -12
- package/src/types.ts +49 -3
- package/src/usage/expected-prices.ts +30 -0
- package/src/usage/log.ts +30 -4
- package/src/usage/summary.ts +153 -2
- package/src/web-search/loop.ts +1 -1
- package/gui/dist/assets/index-Cw1_mxO8.js +0 -70
- package/src/cli/internal-dispatch.ts +0 -20
package/src/service.ts
CHANGED
|
@@ -9,12 +9,12 @@ import { execFileSync, execSync, spawnSync } from "node:child_process";
|
|
|
9
9
|
import { findLiveProxy, proxyIdentityAt, SERVICE_STOP_LIVENESS } from "./server/proxy-liveness";
|
|
10
10
|
import { chmodSync, existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
|
|
11
11
|
import { homedir } from "node:os";
|
|
12
|
-
import { dirname, join, resolve } from "node:path";
|
|
12
|
+
import { dirname, join, resolve, win32 } from "node:path";
|
|
13
13
|
import { expandUserPath, getConfigDir, readPid, removePid, removeRuntimePort, verifyPidIdentity } from "./config";
|
|
14
14
|
import { loadConfig } from "./config";
|
|
15
15
|
import { restoreNativeCodex, restoreNativeCodexAsync } from "./codex/inject";
|
|
16
16
|
import { stripGrokConfig } from "./grok/inject";
|
|
17
|
-
import { isWslRuntime } from "./codex/home";
|
|
17
|
+
import { isWslRuntime, resolveCodexHomeDir, type CodexHomeDeps } from "./codex/home";
|
|
18
18
|
import { BUN_RUNTIME_PATH_ENV, BUN_RUNTIME_SOURCE_ENV, durableBunRuntime } from "./lib/bun-runtime";
|
|
19
19
|
import type { BunRuntimeSource } from "./lib/bun-runtime";
|
|
20
20
|
import { isProcessAlive, stopProxy } from "./lib/process-control";
|
|
@@ -29,6 +29,7 @@ import {
|
|
|
29
29
|
runWindowsElevated,
|
|
30
30
|
toWindowsSchtasksError,
|
|
31
31
|
WindowsElevationError,
|
|
32
|
+
WindowsSchtasksError,
|
|
32
33
|
type ElevatedSchedulerOutcome,
|
|
33
34
|
type ElevatedSchtasksCreateAndRunExecution,
|
|
34
35
|
type ElevatedSchtasksCreateAndRunResult,
|
|
@@ -94,9 +95,23 @@ function serviceStatePaths(): string[] {
|
|
|
94
95
|
return paths;
|
|
95
96
|
}
|
|
96
97
|
|
|
97
|
-
function currentCodexHome(): string {
|
|
98
|
-
|
|
99
|
-
|
|
98
|
+
function currentCodexHome(deps: CodexHomeDeps = {}): string {
|
|
99
|
+
// Service ownership must identify the same home as the runtime. In WSL an
|
|
100
|
+
// unset CODEX_HOME can resolve to the single Windows Desktop home rather than
|
|
101
|
+
// Linux ~/.codex; recording the fallback here creates a false foreign owner.
|
|
102
|
+
return resolveCodexHomeDir(deps);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function currentCodexSqliteHomeAbsolute(target: "native" | "windows" = "native"): string | undefined {
|
|
106
|
+
const raw = process.env.CODEX_SQLITE_HOME?.trim();
|
|
107
|
+
if (!raw) return undefined;
|
|
108
|
+
const expanded = expandUserPath(raw);
|
|
109
|
+
// Windows service artifacts can be rendered by cross-platform tests and
|
|
110
|
+
// repair tooling. Preserve an already-absolute drive/UNC path instead of
|
|
111
|
+
// anchoring it beneath the current POSIX worktree.
|
|
112
|
+
return target === "windows" && win32.isAbsolute(expanded)
|
|
113
|
+
? win32.normalize(expanded)
|
|
114
|
+
: resolve(expanded);
|
|
100
115
|
}
|
|
101
116
|
|
|
102
117
|
function currentOpenCodexHome(): string {
|
|
@@ -218,8 +233,8 @@ export function inspectServiceStateEvidence(
|
|
|
218
233
|
}
|
|
219
234
|
|
|
220
235
|
/** The homes this process is actually using, for comparison against a claim. */
|
|
221
|
-
export function currentServiceHomes(): { codexHome: string; opencodexHome: string } {
|
|
222
|
-
return { codexHome: currentCodexHome(), opencodexHome: currentOpenCodexHome() };
|
|
236
|
+
export function currentServiceHomes(deps: CodexHomeDeps = {}): { codexHome: string; opencodexHome: string } {
|
|
237
|
+
return { codexHome: currentCodexHome(deps), opencodexHome: currentOpenCodexHome() };
|
|
223
238
|
}
|
|
224
239
|
|
|
225
240
|
export function serviceHomeMatches(a: string, b: string): boolean {
|
|
@@ -283,11 +298,12 @@ export function serviceEnvironmentOwnedHere(): boolean {
|
|
|
283
298
|
export function assertServiceEnvironmentMatchesInstall(): void {
|
|
284
299
|
const state = readServiceInstallState();
|
|
285
300
|
if (!state) return;
|
|
301
|
+
const actualCodexHome = currentCodexHome();
|
|
286
302
|
const expected = normalizePathForCompare(state.codexHome);
|
|
287
|
-
const actual = normalizePathForCompare(
|
|
303
|
+
const actual = normalizePathForCompare(actualCodexHome);
|
|
288
304
|
if (expected !== actual) {
|
|
289
305
|
throw new ServiceOwnershipError(
|
|
290
|
-
`Service was installed with CODEX_HOME=${state.codexHome}, but current CODEX_HOME=${
|
|
306
|
+
`Service was installed with CODEX_HOME=${state.codexHome}, but current CODEX_HOME=${actualCodexHome}. ` +
|
|
291
307
|
"Run the service command from the same Codex home so native Codex restore updates the correct config.",
|
|
292
308
|
);
|
|
293
309
|
}
|
|
@@ -363,6 +379,7 @@ export function buildPlist(): string {
|
|
|
363
379
|
const log = logPath();
|
|
364
380
|
const path = process.env.PATH ?? "/usr/local/bin:/usr/bin:/bin";
|
|
365
381
|
const codexHome = process.env.CODEX_HOME?.trim();
|
|
382
|
+
const codexSqliteHome = currentCodexSqliteHomeAbsolute();
|
|
366
383
|
const opencodexHome = process.env.OPENCODEX_HOME?.trim();
|
|
367
384
|
const envLines = [
|
|
368
385
|
` <key>OCX_SERVICE</key><string>1</string>`,
|
|
@@ -370,6 +387,7 @@ export function buildPlist(): string {
|
|
|
370
387
|
` <key>${BUN_RUNTIME_PATH_ENV}</key><string>${plistString(bun)}</string>`,
|
|
371
388
|
` <key>PATH</key><string>${plistString(path)}</string>`,
|
|
372
389
|
codexHome ? ` <key>CODEX_HOME</key><string>${plistString(codexHome)}</string>` : null,
|
|
390
|
+
codexSqliteHome ? ` <key>CODEX_SQLITE_HOME</key><string>${plistString(codexSqliteHome)}</string>` : null,
|
|
373
391
|
opencodexHome ? ` <key>OPENCODEX_HOME</key><string>${plistString(opencodexHome)}</string>` : null,
|
|
374
392
|
].filter((line): line is string => Boolean(line)).join("\n");
|
|
375
393
|
const command = buildServiceShellCommand(bun, cli);
|
|
@@ -981,6 +999,48 @@ async function elevateSchtasks(args: string[]): Promise<void> {
|
|
|
981
999
|
}
|
|
982
1000
|
}
|
|
983
1001
|
|
|
1002
|
+
export interface WindowsSchedulerRollbackDeps {
|
|
1003
|
+
queryXml?: () => string;
|
|
1004
|
+
deleteTask?: () => Promise<void>;
|
|
1005
|
+
probe?: () => WindowsSchedulerTaskProbe;
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
export async function rollbackWindowsSchedulerTaskOwnedByAttempt(
|
|
1009
|
+
attemptNonce: string,
|
|
1010
|
+
taskName = TASK,
|
|
1011
|
+
deps: WindowsSchedulerRollbackDeps = {},
|
|
1012
|
+
): Promise<string | null> {
|
|
1013
|
+
let registeredXml = "";
|
|
1014
|
+
try {
|
|
1015
|
+
registeredXml = (deps.queryXml ?? (() => querySchtasks(["/query", "/tn", taskName, "/xml"])))();
|
|
1016
|
+
} catch (error) {
|
|
1017
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
1018
|
+
return `Task Scheduler task ${taskName} ownership could not be proven: ${detail}. Residual scheduler state: task ${taskName} presence is unknown; no rollback deletion was attempted.`;
|
|
1019
|
+
}
|
|
1020
|
+
if (!registeredXml.trim()) {
|
|
1021
|
+
return `Task Scheduler task ${taskName} ownership could not be proven because its live XML was empty. Residual scheduler state: task ${taskName} presence is unknown; no rollback deletion was attempted.`;
|
|
1022
|
+
}
|
|
1023
|
+
if (!windowsTaskRegistrationOwnedByAttempt(registeredXml, attemptNonce)) {
|
|
1024
|
+
return `Task Scheduler task ${taskName} ownership could not be proven because its attempt nonce does not match. Residual scheduler state: task ${taskName} remains registered; no rollback deletion was attempted.`;
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
try {
|
|
1028
|
+
await (deps.deleteTask ?? (() => elevateSchtasks(["/delete", "/tn", taskName, "/f"])))();
|
|
1029
|
+
} catch (error) {
|
|
1030
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
1031
|
+
return `Rollback deletion failed: ${detail}. Residual scheduler state: task ${taskName} may remain registered.`;
|
|
1032
|
+
}
|
|
1033
|
+
const probe = (deps.probe ?? (() => resolveWindowsSchedulerTaskProbe(taskName)))();
|
|
1034
|
+
if (probe.status === "absent") return null;
|
|
1035
|
+
if (probe.status === "unknown") {
|
|
1036
|
+
return `Task Scheduler task ${taskName} presence could not be verified after rollback: ${probe.detail}. Residual scheduler state: task presence is unknown.`;
|
|
1037
|
+
}
|
|
1038
|
+
return `Residual scheduler state: task ${taskName} is still present after rollback.`;
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
// Legacy dashboard finalization creates and runs in one elevated child, whose protocol
|
|
1042
|
+
// performs its own rollback before returning. This fallback remains for indeterminate
|
|
1043
|
+
// protocol outcomes that predate the staged CLI transaction.
|
|
984
1044
|
async function rollbackElevatedSchedulerTask(taskName = TASK): Promise<string | null> {
|
|
985
1045
|
try {
|
|
986
1046
|
await elevateSchtasks(["/delete", "/tn", taskName, "/f"]);
|
|
@@ -1454,6 +1514,7 @@ export function buildWindowsServiceScript(entry = cliEntry(), port = resolveServ
|
|
|
1454
1514
|
windowsBatchSet(BUN_RUNTIME_PATH_ENV, bun, "path"),
|
|
1455
1515
|
windowsBatchSet("PATH", path, "pathList"),
|
|
1456
1516
|
windowsBatchSet("CODEX_HOME", process.env.CODEX_HOME?.trim(), "path"),
|
|
1517
|
+
windowsBatchSet("CODEX_SQLITE_HOME", currentCodexSqliteHomeAbsolute("windows"), "path"),
|
|
1457
1518
|
windowsBatchSet("OPENCODEX_HOME", process.env.OPENCODEX_HOME?.trim(), "path"),
|
|
1458
1519
|
windowsBatchSet("OCX_API_TOKEN_FILE", serviceApiTokenFilePath(), "path"),
|
|
1459
1520
|
windowsBatchSet("OCX_SERVICE_LOG", serviceLogPath(), "path"),
|
|
@@ -1488,6 +1549,11 @@ export function buildWindowsSchtasksCreateArgs(script = windowsServiceScriptPath
|
|
|
1488
1549
|
return ["/create", "/tn", TASK, "/xml", xml, "/f"];
|
|
1489
1550
|
}
|
|
1490
1551
|
|
|
1552
|
+
/** Build the fixed scheduler-create command from an explicit staged XML document. */
|
|
1553
|
+
export function buildWindowsSchtasksCreateArgsForXml(xml: string): string[] {
|
|
1554
|
+
return ["/create", "/tn", TASK, "/xml", xml, "/f"];
|
|
1555
|
+
}
|
|
1556
|
+
|
|
1491
1557
|
/**
|
|
1492
1558
|
* VBS launcher that starts the batch wrapper with a hidden window (style 0).
|
|
1493
1559
|
* bWaitOnReturn=True keeps wscript.exe resident for the wrapper's lifetime so the
|
|
@@ -1508,7 +1574,17 @@ export function buildWindowsLauncherVbs(script = windowsServiceScriptPath()): st
|
|
|
1508
1574
|
return `${lines.join("\r\n")}\r\n`;
|
|
1509
1575
|
}
|
|
1510
1576
|
|
|
1511
|
-
|
|
1577
|
+
function windowsTaskDescription(attemptNonce?: string): string {
|
|
1578
|
+
return attemptNonce
|
|
1579
|
+
? `OpenCodex proxy service wrapper; install-attempt=${attemptNonce}`
|
|
1580
|
+
: "OpenCodex proxy service wrapper";
|
|
1581
|
+
}
|
|
1582
|
+
|
|
1583
|
+
export function buildWindowsTaskXml(
|
|
1584
|
+
script = windowsServiceScriptPath(),
|
|
1585
|
+
launcher = windowsLauncherVbsPath(),
|
|
1586
|
+
attemptNonce?: string,
|
|
1587
|
+
): string {
|
|
1512
1588
|
const escapedWscript = taskXmlString(windowsWscript());
|
|
1513
1589
|
// Escape the launcher path independently for the <Arguments> element; quoting it
|
|
1514
1590
|
// keeps spaces intact, and /b (batch mode) suppresses script error popups.
|
|
@@ -1516,7 +1592,7 @@ export function buildWindowsTaskXml(script = windowsServiceScriptPath(), launche
|
|
|
1516
1592
|
return `<?xml version="1.0" encoding="UTF-16"?>
|
|
1517
1593
|
<Task version="1.4" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
|
|
1518
1594
|
<RegistrationInfo>
|
|
1519
|
-
<Description
|
|
1595
|
+
<Description>${taskXmlString(windowsTaskDescription(attemptNonce))}</Description>
|
|
1520
1596
|
</RegistrationInfo>
|
|
1521
1597
|
<Triggers>
|
|
1522
1598
|
<LogonTrigger>
|
|
@@ -1636,6 +1712,21 @@ function taskXmlOptionalValueEquals(xml: string, tag: string, expected: string):
|
|
|
1636
1712
|
return value?.trim().toLowerCase() === expected.toLowerCase();
|
|
1637
1713
|
}
|
|
1638
1714
|
|
|
1715
|
+
/** True only when the exported live task carries this install attempt's nonce. */
|
|
1716
|
+
export function windowsTaskRegistrationOwnedByAttempt(xml: string, attemptNonce: string): boolean {
|
|
1717
|
+
if (!attemptNonce) return false;
|
|
1718
|
+
const scrubbed = taskXmlWithoutCommentsAndCdata(xml);
|
|
1719
|
+
if (taskXmlElementCount(scrubbed, "Data") > 0 || taskXmlHasPrefixedTag(scrubbed, "Data")) return false;
|
|
1720
|
+
if (taskXmlHasPrefixedTag(scrubbed, "RegistrationInfo")) return false;
|
|
1721
|
+
if (taskXmlElementCount(scrubbed, "RegistrationInfo") !== 1) return false;
|
|
1722
|
+
const registrationInfo = taskXmlSection(scrubbed, "RegistrationInfo");
|
|
1723
|
+
return taskXmlDecodedValueEquals(
|
|
1724
|
+
registrationInfo,
|
|
1725
|
+
"Description",
|
|
1726
|
+
windowsTaskDescription(attemptNonce),
|
|
1727
|
+
);
|
|
1728
|
+
}
|
|
1729
|
+
|
|
1639
1730
|
/** Validate the security/lifecycle-critical fields of the registered scheduler task. */
|
|
1640
1731
|
export function windowsTaskRegistrationHealthy(
|
|
1641
1732
|
xml: string,
|
|
@@ -1805,6 +1896,88 @@ function writeWindowsSchedulerAssets(): void {
|
|
|
1805
1896
|
writeServiceAssetWithRetry(windowsTaskXmlPath(), `\uFEFF${buildWindowsTaskXml(script)}`, "utf16le");
|
|
1806
1897
|
}
|
|
1807
1898
|
|
|
1899
|
+
function stageWindowsSchedulerRegistrationXml(attemptNonce: string): string {
|
|
1900
|
+
if (!existsSync(getConfigDir())) mkdirSync(getConfigDir(), { recursive: true, mode: 0o700 });
|
|
1901
|
+
const path = join(getConfigDir(), `.opencodex-service-task.${randomUUID()}.xml`);
|
|
1902
|
+
// This document points at the canonical launcher but does not publish or rewrite that
|
|
1903
|
+
// launcher. UAC can therefore be refused while the current proxy still owns its port.
|
|
1904
|
+
writeServiceAssetWithRetry(
|
|
1905
|
+
path,
|
|
1906
|
+
`\uFEFF${buildWindowsTaskXml(windowsServiceScriptPath(), windowsLauncherVbsPath(), attemptNonce)}`,
|
|
1907
|
+
"utf16le",
|
|
1908
|
+
);
|
|
1909
|
+
return path;
|
|
1910
|
+
}
|
|
1911
|
+
|
|
1912
|
+
export interface FreshWindowsSchedulerRegistrationDeps {
|
|
1913
|
+
create?: (args: string[]) => void;
|
|
1914
|
+
elevate?: (args: string[]) => Promise<void>;
|
|
1915
|
+
probe?: () => WindowsSchedulerTaskProbe;
|
|
1916
|
+
queryXml?: () => string;
|
|
1917
|
+
rollback?: () => Promise<string | null>;
|
|
1918
|
+
}
|
|
1919
|
+
|
|
1920
|
+
export async function registerFreshWindowsSchedulerTask(
|
|
1921
|
+
xmlPath: string,
|
|
1922
|
+
attemptNonce: string,
|
|
1923
|
+
deps: FreshWindowsSchedulerRegistrationDeps = {},
|
|
1924
|
+
): Promise<void> {
|
|
1925
|
+
const args = buildWindowsSchtasksCreateArgsForXml(xmlPath);
|
|
1926
|
+
try {
|
|
1927
|
+
(deps.create ?? schtasks)(args);
|
|
1928
|
+
} catch (error) {
|
|
1929
|
+
if (
|
|
1930
|
+
!(error instanceof WindowsSchtasksError)
|
|
1931
|
+
|| error.operation !== "create"
|
|
1932
|
+
|| error.reason !== "access-denied"
|
|
1933
|
+
) {
|
|
1934
|
+
throw error;
|
|
1935
|
+
}
|
|
1936
|
+
// The elevated command is still the fixed trusted schtasks executable plus the
|
|
1937
|
+
// owned create shape. It registers only; the task is not run until cleanup commits.
|
|
1938
|
+
await (deps.elevate ?? elevateSchtasks)(args);
|
|
1939
|
+
}
|
|
1940
|
+
|
|
1941
|
+
const rollbackTask = deps.rollback ?? (() => rollbackWindowsSchedulerTaskOwnedByAttempt(attemptNonce, TASK));
|
|
1942
|
+
const probe = (deps.probe ?? (() => probeWindowsSchedulerTask(TASK)))();
|
|
1943
|
+
if (probe.status === "absent") {
|
|
1944
|
+
throw new Error("Task Scheduler reported success, but the new registration is absent; no service cleanup was started.");
|
|
1945
|
+
}
|
|
1946
|
+
if (probe.status === "unknown") {
|
|
1947
|
+
const rollback = await rollbackTask();
|
|
1948
|
+
throw new Error(
|
|
1949
|
+
`Task Scheduler registration was not verifiably present after create (${probe.detail}).`
|
|
1950
|
+
+ (rollback ? ` Cleanup also failed: ${rollback}` : " The unverified registration was rolled back."),
|
|
1951
|
+
);
|
|
1952
|
+
}
|
|
1953
|
+
|
|
1954
|
+
let registeredXml = "";
|
|
1955
|
+
let queryDetail: string | null = null;
|
|
1956
|
+
try {
|
|
1957
|
+
registeredXml = (deps.queryXml ?? (() => querySchtasks(["/query", "/tn", TASK, "/xml"])))();
|
|
1958
|
+
} catch (error) {
|
|
1959
|
+
queryDetail = error instanceof Error ? error.message : String(error);
|
|
1960
|
+
}
|
|
1961
|
+
if (!registeredXml.trim()) {
|
|
1962
|
+
const rollback = await rollbackTask();
|
|
1963
|
+
throw new Error(
|
|
1964
|
+
"Task Scheduler registration was created, but its live XML could not be verified."
|
|
1965
|
+
+ (queryDetail ? ` Query failed: ${queryDetail}` : " The query returned an empty document.")
|
|
1966
|
+
+ (rollback ? ` Cleanup also failed: ${rollback}` : " The unverified registration was rolled back."),
|
|
1967
|
+
);
|
|
1968
|
+
}
|
|
1969
|
+
if (
|
|
1970
|
+
!windowsTaskRegistrationHealthy(registeredXml)
|
|
1971
|
+
|| !windowsTaskRegistrationOwnedByAttempt(registeredXml, attemptNonce)
|
|
1972
|
+
) {
|
|
1973
|
+
const rollback = await rollbackTask();
|
|
1974
|
+
throw new Error(
|
|
1975
|
+
"Task Scheduler registration was created but failed the OpenCodex action/trigger or attempt-ownership verification."
|
|
1976
|
+
+ (rollback ? ` Cleanup also failed: ${rollback}` : " The invalid registration was rolled back."),
|
|
1977
|
+
);
|
|
1978
|
+
}
|
|
1979
|
+
}
|
|
1980
|
+
|
|
1808
1981
|
function installWindows(): void {
|
|
1809
1982
|
recordOwnedConfigPath(getConfigDir(), serviceStatePath());
|
|
1810
1983
|
// Transactional backend switch: installing the scheduler backend removes a native
|
|
@@ -2046,6 +2219,7 @@ export function buildUnit(): string {
|
|
|
2046
2219
|
const log = logPath();
|
|
2047
2220
|
const path = process.env.PATH ?? "/usr/local/bin:/usr/bin:/bin";
|
|
2048
2221
|
const codexHome = systemdEnvironmentAssignment("CODEX_HOME", process.env.CODEX_HOME?.trim());
|
|
2222
|
+
const codexSqliteHome = systemdEnvironmentAssignment("CODEX_SQLITE_HOME", currentCodexSqliteHomeAbsolute());
|
|
2049
2223
|
const opencodexHome = systemdEnvironmentAssignment("OPENCODEX_HOME", process.env.OPENCODEX_HOME?.trim());
|
|
2050
2224
|
const envLines = [
|
|
2051
2225
|
systemdEnvironmentAssignment("OCX_SERVICE", "1"),
|
|
@@ -2053,6 +2227,7 @@ export function buildUnit(): string {
|
|
|
2053
2227
|
systemdEnvironmentAssignment(BUN_RUNTIME_PATH_ENV, bun),
|
|
2054
2228
|
systemdEnvironmentAssignment("PATH", path),
|
|
2055
2229
|
codexHome,
|
|
2230
|
+
codexSqliteHome,
|
|
2056
2231
|
opencodexHome,
|
|
2057
2232
|
].filter((line): line is string => Boolean(line)).join("\n");
|
|
2058
2233
|
return `[Unit]
|
|
@@ -2417,6 +2592,85 @@ export async function installServiceSafely(
|
|
|
2417
2592
|
await install();
|
|
2418
2593
|
}
|
|
2419
2594
|
|
|
2595
|
+
export interface FreshWindowsSchedulerInstallDeps {
|
|
2596
|
+
stageRegistrationXml?: (attemptNonce: string) => string;
|
|
2597
|
+
register?: (xmlPath: string, attemptNonce: string) => Promise<void>;
|
|
2598
|
+
prepare?: () => Promise<void>;
|
|
2599
|
+
publishAssets?: () => void;
|
|
2600
|
+
runTask?: () => void;
|
|
2601
|
+
writeState?: () => void;
|
|
2602
|
+
rollbackTask?: (attemptNonce: string) => Promise<string | null>;
|
|
2603
|
+
removeStagedXml?: (xmlPath: string) => void;
|
|
2604
|
+
}
|
|
2605
|
+
|
|
2606
|
+
/**
|
|
2607
|
+
* Fresh Windows scheduler install with UAC before the destructive commit.
|
|
2608
|
+
*
|
|
2609
|
+
* The registration is created but never run before `prepare`: UAC cancellation and
|
|
2610
|
+
* create failure therefore cannot stop the existing proxy or trigger its native-routing
|
|
2611
|
+
* cleanup. Rollback proves ownership from the live registration's attempt nonce before
|
|
2612
|
+
* deleting, because the fixed task name can be replaced by another process at any time.
|
|
2613
|
+
*/
|
|
2614
|
+
export async function installFreshWindowsSchedulerSafely(
|
|
2615
|
+
deps: FreshWindowsSchedulerInstallDeps = {},
|
|
2616
|
+
): Promise<void> {
|
|
2617
|
+
const stage = deps.stageRegistrationXml ?? stageWindowsSchedulerRegistrationXml;
|
|
2618
|
+
const register = deps.register ?? registerFreshWindowsSchedulerTask;
|
|
2619
|
+
const prepare = deps.prepare ?? (() => prepareServiceInstall("scheduler"));
|
|
2620
|
+
const publishAssets = deps.publishAssets ?? writeWindowsSchedulerAssets;
|
|
2621
|
+
const runTask = deps.runTask ?? startWindows;
|
|
2622
|
+
const writeState = deps.writeState ?? (() => writeServiceInstallState("scheduler"));
|
|
2623
|
+
const rollbackTask = deps.rollbackTask ?? ((attemptNonce: string) => (
|
|
2624
|
+
rollbackWindowsSchedulerTaskOwnedByAttempt(attemptNonce, TASK)
|
|
2625
|
+
));
|
|
2626
|
+
const removeStagedXml = deps.removeStagedXml ?? ((path: string) => {
|
|
2627
|
+
if (existsSync(path)) unlinkSync(path);
|
|
2628
|
+
});
|
|
2629
|
+
|
|
2630
|
+
let stagedXml: string | null = null;
|
|
2631
|
+
const attemptNonce = randomUUID();
|
|
2632
|
+
let registered = false;
|
|
2633
|
+
let started = false;
|
|
2634
|
+
try {
|
|
2635
|
+
stagedXml = stage(attemptNonce);
|
|
2636
|
+
await register(stagedXml, attemptNonce);
|
|
2637
|
+
registered = true;
|
|
2638
|
+
|
|
2639
|
+
// The destructive boundary begins only after Task Scheduler accepted the definition.
|
|
2640
|
+
await prepare();
|
|
2641
|
+
publishAssets();
|
|
2642
|
+
runTask();
|
|
2643
|
+
started = true;
|
|
2644
|
+
writeState();
|
|
2645
|
+
} catch (error) {
|
|
2646
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
2647
|
+
if (registered && !started) {
|
|
2648
|
+
const rollback = await rollbackTask(attemptNonce);
|
|
2649
|
+
throw new Error(
|
|
2650
|
+
`${detail}\n`
|
|
2651
|
+
+ (rollback
|
|
2652
|
+
? `The new Task Scheduler registration may remain: ${rollback}`
|
|
2653
|
+
: "The new Task Scheduler registration was rolled back. The previous proxy/routing state was not assumed restored."),
|
|
2654
|
+
);
|
|
2655
|
+
}
|
|
2656
|
+
if (started) {
|
|
2657
|
+
throw new Error(
|
|
2658
|
+
`${detail}\nThe scheduler task started, but install state was not published. `
|
|
2659
|
+
+ "The task was left in place; inspect `ocx service status` before retrying.",
|
|
2660
|
+
);
|
|
2661
|
+
}
|
|
2662
|
+
throw error;
|
|
2663
|
+
} finally {
|
|
2664
|
+
if (stagedXml) {
|
|
2665
|
+
try { removeStagedXml(stagedXml); } catch (error) {
|
|
2666
|
+
console.error(
|
|
2667
|
+
`⚠️ Failed to remove temporary Task Scheduler XML ${stagedXml}: ${error instanceof Error ? error.message : String(error)}`,
|
|
2668
|
+
);
|
|
2669
|
+
}
|
|
2670
|
+
}
|
|
2671
|
+
}
|
|
2672
|
+
}
|
|
2673
|
+
|
|
2420
2674
|
/**
|
|
2421
2675
|
* If a service is installed, stop it so the process manager doesn't respawn after `ocx stop`.
|
|
2422
2676
|
* Returns true if a service was found and stopped.
|
|
@@ -2792,7 +3046,19 @@ export async function serviceCommand(...args: (string | undefined)[]): Promise<v
|
|
|
2792
3046
|
// restart-loops on EADDRINUSE, and the old standalone process makes the install
|
|
2793
3047
|
// verification report a false success.
|
|
2794
3048
|
try {
|
|
2795
|
-
|
|
3049
|
+
if (process.platform === "win32" && backend === "scheduler") {
|
|
3050
|
+
const scheduler = probeWindowsSchedulerTask(TASK);
|
|
3051
|
+
if (scheduler.status === "unknown") {
|
|
3052
|
+
throw new Error(`Task Scheduler state could not be verified before install: ${scheduler.detail}`);
|
|
3053
|
+
}
|
|
3054
|
+
if (scheduler.status === "absent") {
|
|
3055
|
+
await installFreshWindowsSchedulerSafely();
|
|
3056
|
+
} else {
|
|
3057
|
+
await installServiceSafely(backend, ops.install);
|
|
3058
|
+
}
|
|
3059
|
+
} else {
|
|
3060
|
+
await installServiceSafely(backend, ops.install);
|
|
3061
|
+
}
|
|
2796
3062
|
} catch (error) {
|
|
2797
3063
|
console.error(`❌ Service install cleanup failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
2798
3064
|
process.exitCode = 1;
|
package/src/types.ts
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
import type { KiroOAuthMetadata } from "./oauth/types";
|
|
2
2
|
|
|
3
|
+
/** Exact provider/credential namespace for process-local reasoning replay. */
|
|
4
|
+
export interface OcxReasoningReplayIdentity {
|
|
5
|
+
providerName: string;
|
|
6
|
+
/** Opaque process-local digest of the exact upstream destination. */
|
|
7
|
+
providerDestinationIdentity: string;
|
|
8
|
+
adapterName: string;
|
|
9
|
+
modelId: string;
|
|
10
|
+
/** Opaque process-local credential identity; never a raw token or API key. */
|
|
11
|
+
credentialIdentity: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Stable holder shared by parsed-request copies and already-created bridges.
|
|
16
|
+
* Credential/provider rotation replaces `current` atomically without replacing
|
|
17
|
+
* the holder, so late tool-call cache writes see the active physical identity.
|
|
18
|
+
*/
|
|
19
|
+
export interface OcxReasoningReplayScopeRef {
|
|
20
|
+
readonly clientThreadId: string;
|
|
21
|
+
current?: Readonly<OcxReasoningReplayIdentity>;
|
|
22
|
+
}
|
|
23
|
+
|
|
3
24
|
export interface OcxParsedRequest {
|
|
4
25
|
modelId: string;
|
|
5
26
|
/** Client-facing model selector retained for Anthropic routes after wire-model normalization. */
|
|
@@ -13,12 +34,16 @@ export interface OcxParsedRequest {
|
|
|
13
34
|
_rawBody?: unknown;
|
|
14
35
|
/** Number of leading raw input items restored from local previous_response_id state. */
|
|
15
36
|
_replayPrefixLen?: number;
|
|
37
|
+
/** Parsed-message index before the first conversational item in a continuation's current delta. */
|
|
38
|
+
_continuationConversationMessageIndex?: number;
|
|
16
39
|
/** True when the proxy expanded a previous_response_id request into a full input replay. */
|
|
17
40
|
_previousResponseInputExpanded?: boolean;
|
|
18
41
|
/** Provider-private stable Cursor conversation id resolved from the Responses previous_response_id chain. */
|
|
19
42
|
_cursorConversationId?: string;
|
|
20
43
|
/** Stable upstream client thread identity, used only to derive provider-scoped continuation ids. */
|
|
21
44
|
_clientThreadId?: string;
|
|
45
|
+
/** Provider/account/model-bound namespace for process-local raw-reasoning replay. */
|
|
46
|
+
_reasoningReplayScope?: OcxReasoningReplayScopeRef;
|
|
22
47
|
/**
|
|
23
48
|
* Optional authenticated tenant/operator namespace for Cursor thread→conversation derivation.
|
|
24
49
|
* When absent (single-operator local proxy), derivation stays local-scoped.
|
|
@@ -995,6 +1020,19 @@ export interface OcxRoutingProfileUnknownEvidence {
|
|
|
995
1020
|
cost?: OcxRoutingUnknownEvidenceMode;
|
|
996
1021
|
}
|
|
997
1022
|
|
|
1023
|
+
export interface OcxRoutingProfileCompatibilitySuite {
|
|
1024
|
+
suiteId: string;
|
|
1025
|
+
evidenceLayer: "protocol_conformance" | "live_route_compatibility";
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
export interface OcxRoutingProfileCompatibility {
|
|
1029
|
+
requiredSuites?: OcxRoutingProfileCompatibilitySuite[];
|
|
1030
|
+
minStatus?: "PROBED" | "VERIFIED";
|
|
1031
|
+
maxEvidenceAgeMs?: number;
|
|
1032
|
+
unknownEvidence?: OcxRoutingUnknownEvidenceMode;
|
|
1033
|
+
degradedEvidence?: OcxRoutingUnknownEvidenceMode;
|
|
1034
|
+
}
|
|
1035
|
+
|
|
998
1036
|
export interface OcxRoutingProfileConfig {
|
|
999
1037
|
/**
|
|
1000
1038
|
* Explicit candidate allowlist (`provider/model` refs). No implicit
|
|
@@ -1010,6 +1048,8 @@ export interface OcxRoutingProfileConfig {
|
|
|
1010
1048
|
limits?: OcxRoutingProfileLimits;
|
|
1011
1049
|
/** How unknown evidence is handled per dimension. */
|
|
1012
1050
|
unknownEvidence?: OcxRoutingProfileUnknownEvidence;
|
|
1051
|
+
/** Optional Compatibility Lab policy (CL-06). */
|
|
1052
|
+
compatibility?: OcxRoutingProfileCompatibility;
|
|
1013
1053
|
}
|
|
1014
1054
|
|
|
1015
1055
|
/**
|
|
@@ -1210,9 +1250,9 @@ export interface OcxProviderConfig {
|
|
|
1210
1250
|
*/
|
|
1211
1251
|
statelessResponses?: boolean;
|
|
1212
1252
|
/**
|
|
1213
|
-
* Responses upstream whose parser requires
|
|
1214
|
-
*
|
|
1215
|
-
*
|
|
1253
|
+
* Responses upstream whose parser requires an unambiguous call batch and its matched
|
|
1254
|
+
* result batch to remain contiguous. Hook-injected context that splits the batch is
|
|
1255
|
+
* preserved after it, and parallel calls stay together with the reasoning turn that produced them.
|
|
1216
1256
|
*/
|
|
1217
1257
|
requiresAdjacentResponsesToolResults?: boolean;
|
|
1218
1258
|
/**
|
|
@@ -1378,6 +1418,12 @@ export interface OcxProviderConfig {
|
|
|
1378
1418
|
noTopPModels?: string[];
|
|
1379
1419
|
/** Model ids that reject caller-specified presence/frequency penalty values. */
|
|
1380
1420
|
noPenaltyModels?: string[];
|
|
1421
|
+
/**
|
|
1422
|
+
* Model ids whose Chat Completions endpoint rejects `response_format`.
|
|
1423
|
+
* Structured-output translation remains enabled by default; this is a narrow
|
|
1424
|
+
* per-model compatibility escape hatch for mixed-capability gateways.
|
|
1425
|
+
*/
|
|
1426
|
+
noStructuredOutputModels?: string[];
|
|
1381
1427
|
/**
|
|
1382
1428
|
* Allow multiple tool calls per completion. DEFAULT-ON for openai-chat providers (the
|
|
1383
1429
|
* buffered stream parser assembles interleaved/fragmented multi-call turns safely);
|
|
@@ -40,6 +40,15 @@ export interface ExpectedPriceOverlay {
|
|
|
40
40
|
|
|
41
41
|
const GEMINI_31_PRO: Cost4 = { input: 2, output: 12, cacheRead: 0.2, cacheWrite: 0 };
|
|
42
42
|
const GPT56_SOL: Cost4 = { input: 5, output: 30, cacheRead: 0.5, cacheWrite: 6.25 };
|
|
43
|
+
/**
|
|
44
|
+
* Daybreak aliases. `daybreak-*-latest` never appears in the pricing table itself — only its
|
|
45
|
+
* current snapshot does — so these tuples are the snapshot's published rates and carry
|
|
46
|
+
* `verified-derived`. That status also keeps the `estimated` marker on, which matters more
|
|
47
|
+
* here than for a normal row: OpenAI can repoint an alias at a differently-priced model.
|
|
48
|
+
* Red = gpt-5.6-cyber (12.50 / 1.25 / 15.625 / 75, cache write = 1.25x uncached input).
|
|
49
|
+
* Blue = gpt-5.6-sol. Verified 2026-08-11 against the pricing page's Cyber models table.
|
|
50
|
+
*/
|
|
51
|
+
const DAYBREAK_RED: Cost4 = { input: 12.5, output: 75, cacheRead: 1.25, cacheWrite: 15.625 };
|
|
43
52
|
const GPT56_TERRA: Cost4 = { input: 2, output: 12, cacheRead: 0.2, cacheWrite: 2.5 };
|
|
44
53
|
const GPT56_LUNA: Cost4 = { input: 0.2, output: 1.2, cacheRead: 0.02, cacheWrite: 0.25 };
|
|
45
54
|
const GEMINI_36_FLASH: Cost4 = { input: 1.5, output: 7.5, cacheRead: 0.15, cacheWrite: 0 };
|
|
@@ -103,6 +112,11 @@ export const EXPECTED_PRICE_OVERLAYS: readonly ExpectedPriceOverlay[] = [
|
|
|
103
112
|
{ provider: "openai-apikey", modelId: "gpt-5.6-sol-pro", cost4: GPT56_SOL, source: `collapsed base ID ${OPENAI_GPT56_PRICING}`, verifiedAt: "2026-08-03", status: "verified-derived" },
|
|
104
113
|
{ provider: "openai-apikey", modelId: "gpt-5.6-terra-pro", cost4: GPT56_TERRA, source: `collapsed base ID ${OPENAI_GPT56_PRICING}`, verifiedAt: "2026-08-03", status: "verified-derived" },
|
|
105
114
|
{ provider: "openai-apikey", modelId: "gpt-5.6-luna-pro", cost4: GPT56_LUNA, source: `collapsed base ID ${OPENAI_GPT56_PRICING}`, verifiedAt: "2026-08-03", status: "verified-derived" },
|
|
115
|
+
// Daybreak aliases: priced as their current snapshots (red -> gpt-5.6-cyber,
|
|
116
|
+
// blue -> gpt-5.6-sol). The alias ids carry no rows of their own upstream, hence
|
|
117
|
+
// verified-derived. Blue deliberately reuses GPT56_SOL rather than duplicating the tuple.
|
|
118
|
+
{ provider: "openai-apikey", modelId: "daybreak-red-latest", cost4: DAYBREAK_RED, source: `alias of gpt-5.6-cyber ${OPENAI_GPT56_PRICING}`, verifiedAt: "2026-08-11", status: "verified-derived" },
|
|
119
|
+
{ provider: "openai-apikey", modelId: "daybreak-blue-latest", cost4: GPT56_SOL, source: `alias of gpt-5.6-sol ${OPENAI_GPT56_PRICING}`, verifiedAt: "2026-08-11", status: "verified-derived" },
|
|
106
120
|
{ provider: "google-antigravity", modelId: "gemini-3.1-pro-low", cost4: GEMINI_31_PRO, source: `derived: gemini-3.1-pro (<=200k tier) ${GEMINI_PRICING}`, verifiedAt: "2026-07-20", status: "verified-derived" },
|
|
107
121
|
{ provider: "google-antigravity", modelId: "gemini-3.1-pro-high", cost4: GEMINI_31_PRO, source: `derived: gemini-3.1-pro (<=200k tier) ${GEMINI_PRICING}`, verifiedAt: "2026-07-20", status: "verified-derived" },
|
|
108
122
|
{ provider: "google-antigravity", modelId: "gemini-pro-agent", cost4: GEMINI_31_PRO, source: `wire id for gemini-3.1-pro high ${GEMINI_PRICING}`, verifiedAt: "2026-07-23", status: "verified-derived" },
|
|
@@ -263,6 +277,22 @@ export const CONTEXT_TIERS: readonly ContextTier[] = [
|
|
|
263
277
|
source: "https://docs.x.ai/developers/pricing",
|
|
264
278
|
verifiedAt: "2026-08-03",
|
|
265
279
|
},
|
|
280
|
+
{
|
|
281
|
+
// daybreak-blue-latest aliases gpt-5.6-sol, which publishes the full long-context row
|
|
282
|
+
// ($10 / $1 / $12.50 / $45). Scoped to openai-apikey ON PURPOSE: Daybreak is not
|
|
283
|
+
// routable on the Codex-login `openai` provider, so adding the alias to the shared
|
|
284
|
+
// OPENAI_GPT56_CONTEXT_MODELS list (expanded across both providers above) would mint a
|
|
285
|
+
// tier for a provider/model pair that cannot exist.
|
|
286
|
+
// daybreak-red-latest has NO tier row: the cyber snapshot's four long-context cells are
|
|
287
|
+
// all "-" on the pricing page (verified 2026-08-11).
|
|
288
|
+
provider: "openai-apikey",
|
|
289
|
+
modelId: "daybreak-blue-latest",
|
|
290
|
+
thresholdInputTokens: 272_000,
|
|
291
|
+
inclusive: false,
|
|
292
|
+
multiplier: OPENAI_LONG_CONTEXT,
|
|
293
|
+
source: OPENAI_PRICING_DOC,
|
|
294
|
+
verifiedAt: "2026-08-11",
|
|
295
|
+
},
|
|
266
296
|
...["minimax", "minimax-cn"].map((provider): ContextTier => ({
|
|
267
297
|
provider,
|
|
268
298
|
modelId: "MiniMax-M3",
|
package/src/usage/log.ts
CHANGED
|
@@ -5,8 +5,14 @@ import { recordOwnedConfigPath } from "../lib/config-ownership";
|
|
|
5
5
|
import { usageDisplayTotalTokens } from "./totals";
|
|
6
6
|
import type { OcxUsage } from "../types";
|
|
7
7
|
import { normalizeRouteDecisionTrace, type RouteDecisionTraceV1 } from "../routing/trace";
|
|
8
|
+
import { CODEX_ACCOUNT_LOG_LABEL_RE } from "../codex/account-label";
|
|
8
9
|
|
|
9
10
|
export type UsageStatus = "reported" | "unreported" | "unsupported" | "estimated";
|
|
11
|
+
export type CodexUsageAccountLogLabel = "main" | `p${string}`;
|
|
12
|
+
|
|
13
|
+
export function isCodexUsageAccountLogLabel(value: unknown): value is CodexUsageAccountLogLabel {
|
|
14
|
+
return value === "main" || (typeof value === "string" && CODEX_ACCOUNT_LOG_LABEL_RE.test(value));
|
|
15
|
+
}
|
|
10
16
|
|
|
11
17
|
/**
|
|
12
18
|
* Recovery kinds recorded per attempt in the usage log; the GUI renders localized labels
|
|
@@ -33,10 +39,14 @@ export interface PersistedUsageAttempt {
|
|
|
33
39
|
sendCount: number;
|
|
34
40
|
recoveryKinds: AttemptRecoveryKind[];
|
|
35
41
|
usageStatus: UsageStatus;
|
|
42
|
+
/** Stable non-PII identity for the Codex pool account that served this attempt. */
|
|
43
|
+
accountLogLabel?: CodexUsageAccountLogLabel;
|
|
36
44
|
inputTokenEstimate?: number;
|
|
37
45
|
usage?: OcxUsage;
|
|
38
46
|
totalTokens?: number;
|
|
39
47
|
errorCode?: string;
|
|
48
|
+
/** Installation-local exact Compatibility Lab route-subject digest for this attempt. */
|
|
49
|
+
labRouteSubjectId?: string;
|
|
40
50
|
/** Target-specific reasoning intent and exact adapter-normalized wire parameter. */
|
|
41
51
|
requestedEffort?: string;
|
|
42
52
|
effectiveEffort?: string;
|
|
@@ -56,6 +66,8 @@ export interface PersistedUsageEntry {
|
|
|
56
66
|
admissionKind?: "configured" | "environment" | "loopback";
|
|
57
67
|
/** The inbound wire, not the client product — see `surface`. */
|
|
58
68
|
inboundProtocol?: "responses" | "chat" | "messages";
|
|
69
|
+
/** Stable non-PII identity for Codex Pool usage; absent for Direct/non-Codex traffic. */
|
|
70
|
+
accountLogLabel?: CodexUsageAccountLogLabel;
|
|
59
71
|
/** Best-effort chat/session correlation for Logs grouping (#330). */
|
|
60
72
|
conversationId?: string;
|
|
61
73
|
resolvedModel?: string;
|
|
@@ -130,8 +142,8 @@ export function isKnownInboundProtocol(value: unknown): value is NonNullable<Per
|
|
|
130
142
|
return typeof value === "string" && KNOWN_INBOUND_PROTOCOLS.has(value as NonNullable<PersistedUsageEntry["inboundProtocol"]>);
|
|
131
143
|
}
|
|
132
144
|
|
|
133
|
-
export function usageLogPath(): string {
|
|
134
|
-
return join(getConfigDir(), "usage.jsonl");
|
|
145
|
+
export function usageLogPath(configDir?: string): string {
|
|
146
|
+
return join(configDir ?? getConfigDir(), "usage.jsonl");
|
|
135
147
|
}
|
|
136
148
|
|
|
137
149
|
export function usageTotalTokens(usage: OcxUsage | undefined): number | undefined {
|
|
@@ -195,6 +207,11 @@ const USAGE_STATUSES = new Set<UsageStatus>([
|
|
|
195
207
|
"unsupported",
|
|
196
208
|
"estimated",
|
|
197
209
|
]);
|
|
210
|
+
const LAB_ROUTE_SUBJECT_ID_RE = /^[0-9a-f]{64}$/;
|
|
211
|
+
|
|
212
|
+
export function isLabRouteSubjectId(value: unknown): value is string {
|
|
213
|
+
return typeof value === "string" && LAB_ROUTE_SUBJECT_ID_RE.test(value);
|
|
214
|
+
}
|
|
198
215
|
|
|
199
216
|
function isNonNegativeFiniteNumber(value: unknown): value is number {
|
|
200
217
|
return typeof value === "number" && Number.isFinite(value) && value >= 0;
|
|
@@ -264,6 +281,9 @@ function normalizeUsageAttempt(raw: unknown): PersistedUsageAttempt | null {
|
|
|
264
281
|
sendCount: attempt.sendCount as number,
|
|
265
282
|
recoveryKinds,
|
|
266
283
|
usageStatus: attempt.usageStatus as UsageStatus,
|
|
284
|
+
...(isCodexUsageAccountLogLabel(attempt.accountLogLabel)
|
|
285
|
+
? { accountLogLabel: attempt.accountLogLabel }
|
|
286
|
+
: {}),
|
|
267
287
|
...(isNonNegativeFiniteNumber(attempt.inputTokenEstimate)
|
|
268
288
|
? { inputTokenEstimate: attempt.inputTokenEstimate }
|
|
269
289
|
: {}),
|
|
@@ -272,6 +292,9 @@ function normalizeUsageAttempt(raw: unknown): PersistedUsageAttempt | null {
|
|
|
272
292
|
? { totalTokens: attempt.totalTokens }
|
|
273
293
|
: {}),
|
|
274
294
|
...(typeof attempt.errorCode === "string" ? { errorCode: attempt.errorCode } : {}),
|
|
295
|
+
...(isLabRouteSubjectId(attempt.labRouteSubjectId)
|
|
296
|
+
? { labRouteSubjectId: attempt.labRouteSubjectId }
|
|
297
|
+
: {}),
|
|
275
298
|
...(typeof attempt.requestedEffort === "string" && attempt.requestedEffort
|
|
276
299
|
? { requestedEffort: capMetadataString(attempt.requestedEffort) }
|
|
277
300
|
: {}),
|
|
@@ -340,6 +363,9 @@ function normalizeUsageEntry(entry: PersistedUsageEntry): PersistedUsageEntry {
|
|
|
340
363
|
: {}),
|
|
341
364
|
...(isKnownAdmissionKind(entry.admissionKind) ? { admissionKind: entry.admissionKind } : {}),
|
|
342
365
|
...(isKnownInboundProtocol(entry.inboundProtocol) ? { inboundProtocol: entry.inboundProtocol } : {}),
|
|
366
|
+
...(isCodexUsageAccountLogLabel(entry.accountLogLabel)
|
|
367
|
+
? { accountLogLabel: entry.accountLogLabel }
|
|
368
|
+
: {}),
|
|
343
369
|
...(typeof entry.conversationId === "string" && entry.conversationId.trim()
|
|
344
370
|
? { conversationId: entry.conversationId.trim().slice(0, 128) }
|
|
345
371
|
: {}),
|
|
@@ -647,9 +673,9 @@ function parseUsageLines(lines: string[]): PersistedUsageEntry[] {
|
|
|
647
673
|
* Read only the newest `limit` usage.jsonl rows without loading the whole append-only
|
|
648
674
|
* file into memory. Used by request-log hydration on `ocx start`.
|
|
649
675
|
*/
|
|
650
|
-
export function readRecentUsageEntries(limit: number): PersistedUsageEntry[] {
|
|
676
|
+
export function readRecentUsageEntries(limit: number, configDir?: string): PersistedUsageEntry[] {
|
|
651
677
|
if (!Number.isFinite(limit) || limit <= 0) return [];
|
|
652
|
-
const path = usageLogPath();
|
|
678
|
+
const path = usageLogPath(configDir);
|
|
653
679
|
if (!existsSync(path)) return [];
|
|
654
680
|
let fd: number | undefined;
|
|
655
681
|
try {
|