@bitkyc08/opencodex 2.83.0 → 2.84.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/App-CZtqiGcw.js +56 -0
- package/gui/dist/assets/Tray-C9TGtwad.js +1 -0
- package/gui/dist/assets/{index-BAbcj51J.css → index-BB6uK6tU.css} +1 -1
- package/gui/dist/assets/index-CTm4l-HQ.js +94 -0
- package/gui/dist/assets/{quota-summary-DiJO6sYY.js → quota-summary-BbEeRfPY.js} +1 -1
- package/gui/dist/index.html +2 -2
- package/package.json +2 -2
- package/src/adapters/coding-agent/protocol.ts +15 -8
- package/src/adapters/ollama-native.ts +29 -1
- package/src/adapters/openai-chat/messages.ts +8 -4
- package/src/adapters/openai-responses/empty-assistant.ts +32 -0
- package/src/adapters/openai-responses/passthrough.ts +3 -0
- package/src/adapters/openai-responses/request-strips.ts +10 -2
- package/src/adapters/qoder/adapter.ts +16 -14
- package/src/bridge/errors.ts +19 -0
- package/src/bridge.ts +1 -1
- package/src/claude/context-windows.ts +54 -1
- package/src/claude/intercept/connect-proxy.ts +10 -3
- package/src/claude/intercept/picker-ca-persistence.ts +3 -1
- package/src/claude/intercept/picker-listener.ts +49 -1
- package/src/claude/intercept/settings-no-proxy.ts +146 -0
- package/src/claude/intercept/settings.ts +101 -9
- package/src/cli/account-api.ts +65 -0
- package/src/cli/account-auth.ts +26 -2
- package/src/cli/account.ts +41 -5
- package/src/cli/capabilities-access-remote.ts +8 -0
- package/src/cli/capabilities-base.ts +13 -1
- package/src/cli/catalog-command-result.ts +2 -1
- package/src/cli/claude.ts +15 -6
- package/src/cli/config-command.ts +8 -2
- package/src/cli/debug.ts +8 -0
- package/src/cli/dispatch.ts +16 -3
- package/src/cli/export-command.ts +14 -1
- package/src/cli/hub-disable.ts +99 -0
- package/src/cli/hub.ts +4 -2
- package/src/cli/index.ts +13 -4
- package/src/cli/link-child.ts +104 -0
- package/src/cli/link-enrollment.ts +96 -0
- package/src/cli/link.ts +39 -24
- package/src/cli/observe-stream.ts +6 -1
- package/src/cli/observe.ts +10 -2
- package/src/cli/provider.ts +12 -0
- package/src/cli/registry.ts +5 -4
- package/src/cli/runtime-api.ts +27 -2
- package/src/cli/status.ts +104 -20
- package/src/client/connect.ts +39 -4
- package/src/client/hub-client.ts +14 -7
- package/src/client/hub-state.ts +25 -14
- package/src/client/link-home.ts +248 -0
- package/src/client/link-management-relay.ts +61 -0
- package/src/client/link-relay.ts +6 -1
- package/src/client/link-status.ts +14 -0
- package/src/client/machine-api.ts +14 -7
- package/src/client/machine-listener.ts +29 -4
- package/src/clients/config-export.ts +46 -16
- package/src/clients/omo-role-models.ts +6 -3
- package/src/codex/agent-role-models.ts +26 -8
- package/src/codex/auth-api/account-list.ts +17 -2
- package/src/codex/auth-api/account-selection.ts +17 -7
- package/src/codex/auth-api/routes.ts +5 -1
- package/src/codex/autostart-health.ts +3 -1
- package/src/codex/catalog/bundled.ts +5 -1
- package/src/codex/catalog/effort.ts +2 -2
- package/src/codex/catalog/metadata.ts +4 -2
- package/src/codex/catalog/native-models.ts +16 -2
- package/src/codex/native-profile-startup.ts +4 -1
- package/src/codex/prompt-journal.ts +20 -2
- package/src/codex/prompt-layers/encoding.ts +49 -0
- package/src/codex/prompt-layers/import-source.ts +30 -0
- package/src/codex/prompt-layers/toml-edit.ts +269 -37
- package/src/codex/prompt-layers/toml-read.ts +52 -4
- package/src/codex/prompt-layers.ts +383 -20
- package/src/codex/prompt-lock-claim.ts +13 -3
- package/src/codex/prompt-lock-evidence.ts +3 -3
- package/src/codex/prompt-lock-owner.ts +14 -6
- package/src/codex/prompt-text-probe.ts +34 -5
- package/src/codex/quota.ts +13 -6
- package/src/codex/role-auto-assign.ts +2 -5
- package/src/codex/routing/active-account.ts +21 -0
- package/src/codex/routing.ts +1 -1
- package/src/codex/runtime.ts +196 -8
- package/src/codex/shim-overlay.ts +8 -4
- package/src/codex/shim.ts +1 -1
- package/src/combos/failover.ts +3 -0
- package/src/config/api-key-reconcile.ts +34 -0
- package/src/config/diagnostics.ts +16 -0
- package/src/config/feature-flags.ts +5 -0
- package/src/config/live-reconcile.ts +43 -29
- package/src/config/live-topology.ts +29 -0
- package/src/config/load-degrade.ts +2 -1
- package/src/config/paths.ts +3 -0
- package/src/config/salvage.ts +3 -1
- package/src/config/schema/config-schema.ts +2 -0
- package/src/generated/compatibility-version.json +311 -171
- package/src/grok/grpc-web.ts +23 -1
- package/src/grok/reset-coupon-ledger.ts +75 -5
- package/src/grok/reset-coupons.ts +4 -2
- package/src/integrations/mutation-plan.ts +13 -7
- package/src/integrations/registry.ts +45 -11
- package/src/integrations/state.ts +19 -7
- package/src/integrations/target.ts +42 -5
- package/src/integrations/writer.ts +14 -6
- package/src/lib/app-owned-memory-stores.ts +4 -0
- package/src/lib/app-owned-memory.ts +11 -0
- package/src/lib/json-byte-size.ts +31 -0
- package/src/lib/optional-shutdown-hooks.ts +35 -0
- package/src/lib/outbound-credential-registry.ts +210 -0
- package/src/lib/passive-read.ts +35 -0
- package/src/lib/shadow-call.ts +44 -5
- package/src/link/codex-settings.ts +155 -0
- package/src/link/management-routes.ts +89 -0
- package/src/link/remote-probe.ts +38 -0
- package/src/link/ssh-argv.ts +26 -5
- package/src/link/ssh-runner.ts +58 -27
- package/src/link/status-projection.ts +22 -4
- package/src/link/supervisor.ts +144 -49
- package/src/link/tunnel-state.ts +11 -4
- package/src/oauth/anthropic-continuity.ts +4 -3
- package/src/oauth/anthropic-identity.ts +13 -2
- package/src/oauth/anthropic-model-quota.ts +13 -1
- package/src/oauth/anthropic-routing.ts +230 -23
- package/src/oauth/anthropic.ts +18 -4
- package/src/oauth/google-antigravity.ts +24 -7
- package/src/oauth/index.ts +24 -15
- package/src/oauth/kiro-account-identity.ts +106 -0
- package/src/oauth/kiro.ts +48 -5
- package/src/oauth/plan.ts +29 -0
- package/src/oauth/store.ts +40 -3
- package/src/oauth/types.ts +2 -0
- package/src/plugins/loader.ts +11 -0
- package/src/providers/anthropic-billing-ownership.ts +74 -0
- package/src/providers/anthropic-billing-recovery.ts +31 -0
- package/src/providers/anthropic-billing-refusal.ts +55 -0
- package/src/providers/anthropic-reset-admission.ts +62 -0
- package/src/providers/anthropic-reset-grant-ledger.ts +28 -6
- package/src/providers/api-key-resolve.ts +17 -2
- package/src/providers/key-failover.ts +52 -5
- package/src/providers/model-rename-startup.ts +5 -3
- package/src/providers/quota/account-cache.ts +11 -0
- package/src/providers/quota/anthropic-account-quota.ts +4 -2
- package/src/providers/quota/report-cache.ts +14 -0
- package/src/providers/quota/vendor-probes-oauth.ts +3 -3
- package/src/providers/quota.ts +4 -1
- package/src/providers/registry/entries-core.ts +14 -14
- package/src/providers/registry/entries-extended.ts +0 -1
- package/src/providers/registry/model-seeds.ts +17 -0
- package/src/providers/request-pacing.ts +9 -0
- package/src/providers/stale-no-vision-seed-migration.ts +72 -0
- package/src/responses/compaction.ts +14 -0
- package/src/responses/parser.ts +7 -1
- package/src/server/auth-cors.ts +42 -0
- package/src/server/configured-credentials.ts +194 -0
- package/src/server/index/link-management.ts +58 -0
- package/src/server/index/optional-listeners.ts +5 -1
- package/src/server/index/serve-options.ts +13 -0
- package/src/server/lifecycle.ts +2 -1
- package/src/server/management/agent-settings-routes.ts +28 -10
- package/src/server/management/anthropic-reset-grant-routes.ts +17 -1
- package/src/server/management/codex-agent-role-routes.ts +19 -6
- package/src/server/management/codex-prompt-routes.ts +136 -5
- package/src/server/management/codex-role-auto-assign.ts +1 -1
- package/src/server/management/grok-coupon-routes.ts +78 -27
- package/src/server/management/link-enrollment.ts +192 -0
- package/src/server/management/link-route-state.ts +185 -0
- package/src/server/management/link-routes.ts +82 -402
- package/src/server/management/model-rows.ts +3 -1
- package/src/server/management/provider-overwrite-carry.ts +10 -0
- package/src/server/management/provider-routes.ts +1 -0
- package/src/server/management/route-registry.ts +5 -3
- package/src/server/management/shared.ts +3 -0
- package/src/server/management-api.ts +3 -1
- package/src/server/messages-native.ts +68 -42
- package/src/server/relay-eager.ts +2 -0
- package/src/server/request-log-filter.ts +6 -1
- package/src/server/request-log-terminal-status.ts +63 -6
- package/src/server/responses/adapter-dispatch.ts +232 -79
- package/src/server/responses/code-mode-collab-surface.ts +205 -0
- package/src/server/responses/codex-ws-exchange.ts +147 -6
- package/src/server/responses/codex-ws-wire.ts +203 -0
- package/src/server/responses/collaboration.ts +36 -7
- package/src/server/responses/compact.ts +126 -48
- package/src/server/responses/context-overflow.ts +13 -1
- package/src/server/responses/core-codex-account.ts +105 -1
- package/src/server/responses/core-combo-failure.ts +14 -0
- package/src/server/responses/core-combo.ts +7 -2
- package/src/server/responses/core-errors.ts +29 -1
- package/src/server/responses/core-options.ts +2 -0
- package/src/server/responses/deferred-collab-surface.ts +158 -0
- package/src/server/responses/deferred-send-reporter.ts +25 -0
- package/src/server/responses/fetch-helpers.ts +12 -1
- package/src/server/responses/passthrough-delivery.ts +4 -1
- package/src/server/responses/passthrough-dispatch.ts +15 -4
- package/src/server/responses/request-prepare.ts +6 -4
- package/src/server/responses/request-transport.ts +6 -8
- package/src/server/responses/sidecar-execution.ts +11 -2
- package/src/server/responses/ws-upstream.ts +9 -1
- package/src/server/startup-action-control.ts +22 -2
- package/src/server/startup-health-cache.ts +4 -0
- package/src/server/system-env-shell.ts +7 -2
- package/src/server/system-env.ts +11 -1
- package/src/service/cli.ts +5 -1
- package/src/service/desktop-startup.ts +147 -34
- package/src/service/desktop-supervision.d.mts +27 -1
- package/src/service/desktop-supervision.mjs +111 -5
- package/src/service/health-sentinel-launch.ts +176 -0
- package/src/service/health-sentinel-main.ts +447 -0
- package/src/service/health-sentinel-policy.ts +241 -0
- package/src/service/health-sentinel-probe.ts +54 -0
- package/src/service/health-sentinel-windows.ts +87 -0
- package/src/service/linux-linger.ts +20 -0
- package/src/types/config.ts +5 -0
- package/src/usage/expected-prices.ts +4 -0
- package/src/usage/log.ts +19 -1
- package/gui/dist/assets/App-Ba8uG696.js +0 -54
- package/gui/dist/assets/Tray-IImbNPRy.js +0 -1
- package/gui/dist/assets/index-BHRZdJuv.js +0 -94
|
@@ -23,6 +23,8 @@ import { projectClientLinkChild, type ClientLinkSidecarRead } from "./link-statu
|
|
|
23
23
|
import type { ClientLinkSupervisorStatus } from "./link-tunnel";
|
|
24
24
|
import { packageVersion } from "../lib/package-version";
|
|
25
25
|
import { linkRouteAllowed } from "../link/routes";
|
|
26
|
+
import { linkHomeName, linkHomePhrase } from "./link-home";
|
|
27
|
+
import { relayLinkManagementRead } from "./link-management-relay";
|
|
26
28
|
|
|
27
29
|
const VERSION = packageVersion("0.0.0");
|
|
28
30
|
const GUI_SPA_PATHS = new Set([
|
|
@@ -100,6 +102,8 @@ export function startMachineListener(
|
|
|
100
102
|
if (linkMode && !connection.link) throw new Error("link machine listener requires link transport metadata");
|
|
101
103
|
const managementAuth = deps.managementAuthState ?? initializeManagementAuthState(config);
|
|
102
104
|
let hubReachability: HubReachability = "unknown";
|
|
105
|
+
const readSidecar = deps.readSidecar ?? (() => readClientLinkState());
|
|
106
|
+
const sidecarOrNull = () => { const read = readSidecarSafely(readSidecar); return read === "invalid" ? null : read; };
|
|
103
107
|
const machineApiDeps: MachineApiDeps = {
|
|
104
108
|
sync: deps.machineApi?.sync ?? syncConnectedClient,
|
|
105
109
|
disconnect: deps.machineApi?.disconnect ?? disconnectClient,
|
|
@@ -108,8 +112,11 @@ export function startMachineListener(
|
|
|
108
112
|
}),
|
|
109
113
|
hubReachability: deps.machineApi?.hubReachability ?? (() => hubReachability),
|
|
110
114
|
setHubReachability: deps.machineApi?.setHubReachability ?? (value => { hubReachability = value; }),
|
|
115
|
+
...(linkMode ? { linkHomeName: () => linkHomeName(connection, sidecarOrNull) } : {}),
|
|
111
116
|
};
|
|
112
|
-
|
|
117
|
+
// A link Child relays its dashboard's management reads to the Home over the link (#6957);
|
|
118
|
+
// a hub client relays over its management transport when that is `relay`.
|
|
119
|
+
const relayEnabled = linkMode || connection.managementTransport === "relay";
|
|
113
120
|
// Link mode only. Everything is resolved once here, so a relayed request reads no file and
|
|
114
121
|
// builds no policy: the key is cached, the loopback policy is fixed at bind like the listener.
|
|
115
122
|
const inboundBodyLimit = resolveInboundBodyLimitBytes(config.maxInboundBodyBytes);
|
|
@@ -123,8 +130,6 @@ export function startMachineListener(
|
|
|
123
130
|
relay: { fetchImpl: deps.fetchImpl, bodyLimitBytes: inboundBodyLimit, tunnel: deps.linkTunnel, ...deps.linkRelay },
|
|
124
131
|
}
|
|
125
132
|
: null;
|
|
126
|
-
const readSidecar = deps.readSidecar ?? (() => readClientLinkState());
|
|
127
|
-
|
|
128
133
|
return (deps.serve ?? (options => Bun.serve(options)))({
|
|
129
134
|
port: port ?? config.port ?? 10100,
|
|
130
135
|
hostname: "127.0.0.1",
|
|
@@ -147,9 +152,29 @@ export function startMachineListener(
|
|
|
147
152
|
}
|
|
148
153
|
if (url.pathname.startsWith("/api/machine/hub-relay/")) {
|
|
149
154
|
if (!relayEnabled) return json404(req);
|
|
155
|
+
const prefix = "/api/machine/hub-relay";
|
|
156
|
+
if (linkIngress) {
|
|
157
|
+
const suffix = `${url.pathname.slice(prefix.length)}${url.search}`;
|
|
158
|
+
// The dashboard's shared plane bootstraps here. On a link Child that plane is this
|
|
159
|
+
// listener, so it receives this listener's own session; no Home credential exists.
|
|
160
|
+
if (suffix === "/opencodex-session") {
|
|
161
|
+
if (req.method !== "GET") return json404(req);
|
|
162
|
+
const session = issueGuiSession(req, config, managementAuth, { trustedTailscaleIngress: false });
|
|
163
|
+
return session ? serveSessionBootstrap(session) : Response.json({ error: "session unavailable" }, { status: 503 });
|
|
164
|
+
}
|
|
165
|
+
const authError = requireManagementAuth(req, managementAuth, config);
|
|
166
|
+
if (authError) return authError;
|
|
167
|
+
if (managementPrincipal(req, managementAuth, config) !== "gui-session") {
|
|
168
|
+
return Response.json({ error: "opencodex machine GUI session required" }, { status: 401 });
|
|
169
|
+
}
|
|
170
|
+
const response = await relayLinkManagementRead(req, suffix, linkIngress, linkHomePhrase(connection, sidecarOrNull));
|
|
171
|
+
if (response.status === 401) hubReachability = "unauthorized";
|
|
172
|
+
else if (response.status >= 500) hubReachability = "offline";
|
|
173
|
+
else if (response.status !== 403) hubReachability = "online";
|
|
174
|
+
return response;
|
|
175
|
+
}
|
|
150
176
|
const authError = requireMachineAuth(req, managementAuth, config);
|
|
151
177
|
if (authError) return authError;
|
|
152
|
-
const prefix = "/api/machine/hub-relay";
|
|
153
178
|
const suffix = `${url.pathname.slice(prefix.length)}${url.search}`;
|
|
154
179
|
const response = await relayHubManagementRequest(req, suffix, {
|
|
155
180
|
managementUrl: connection.managementUrl,
|
|
@@ -470,20 +470,51 @@ export function mcodeConfigPath(env: OpencodeLaunchEnv = process.env, home: stri
|
|
|
470
470
|
return join(mcodeHomeDir(env, home), "config.yaml");
|
|
471
471
|
}
|
|
472
472
|
|
|
473
|
-
/**
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
473
|
+
/** Bootstrap settings always stay at the default home, even after relocation. */
|
|
474
|
+
export type ZcodePathIO = {
|
|
475
|
+
readText: (path: string) => { kind: "text"; text: string } | { kind: "missing" } | { kind: "failed"; code?: string };
|
|
476
|
+
};
|
|
477
|
+
const zcodePathIO: ZcodePathIO = {
|
|
478
|
+
readText: path => {
|
|
479
|
+
try { return { kind: "text", text: readFileSync(path, "utf8") }; }
|
|
480
|
+
catch (error) { return (error as NodeJS.ErrnoException).code === "ENOENT" ? { kind: "missing" } : { kind: "failed" }; }
|
|
481
|
+
},
|
|
482
|
+
};
|
|
483
|
+
export class ZcodeSettingsPathError extends ClientPathError {}
|
|
484
|
+
|
|
485
|
+
export function zcodeBootstrapSettingsPath(home: string = homedir()): string {
|
|
486
|
+
return join(home, ".zcode", "v2", "setting.json");
|
|
483
487
|
}
|
|
484
488
|
|
|
485
|
-
|
|
486
|
-
|
|
489
|
+
/** The pre-relocation path is retained solely to recognize historical ownership. */
|
|
490
|
+
export function zcodeLegacyHomeDir(env: OpencodeLaunchEnv = process.env, home: string = homedir()): string {
|
|
491
|
+
const override = env.ZCODE_DATA_DIR?.trim();
|
|
492
|
+
return override ? absoluteClientPath(override, home, "ZCODE_DATA_DIR") : join(home, ".zcode");
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
/** ZCODE_DATA_DIR names the .zcode root; dataBaseDir names its parent. */
|
|
496
|
+
export function zcodeHomeDir(env: OpencodeLaunchEnv = process.env, home: string = homedir(), io: ZcodePathIO = zcodePathIO): string {
|
|
497
|
+
if (env.ZCODE_DATA_DIR?.trim()) return zcodeLegacyHomeDir(env, home);
|
|
498
|
+
const bootstrap = zcodeBootstrapSettingsPath(home);
|
|
499
|
+
const read = io.readText(bootstrap);
|
|
500
|
+
if (read.kind === "missing") return join(home, ".zcode");
|
|
501
|
+
const unknown = () => new ZcodeSettingsPathError(
|
|
502
|
+
`ZCode's active data root is unknown: ${bootstrap} must be readable JSON with an absolute dataBaseDir (or no dataBaseDir). Repair the bootstrap setting before enabling or refreshing; no legacy provider will be applied.`,
|
|
503
|
+
);
|
|
504
|
+
if (read.kind !== "text") throw unknown();
|
|
505
|
+
let settings: unknown;
|
|
506
|
+
try { settings = JSON.parse(read.text); } catch { throw unknown(); }
|
|
507
|
+
if (settings === null || typeof settings !== "object" || Array.isArray(settings)) throw unknown();
|
|
508
|
+
const base = (settings as Record<string, unknown>).dataBaseDir;
|
|
509
|
+
if (base === undefined) return join(home, ".zcode");
|
|
510
|
+
// Match the native path's value, without cwd or tilde expansion. The native
|
|
511
|
+
// ZCODE_DATA_BASE_DIR selector is not an alias for our ZCODE_DATA_DIR.
|
|
512
|
+
if (typeof base !== "string" || !isAbsolute(base) || base.includes("\0")) throw unknown();
|
|
513
|
+
return join(base, ".zcode");
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
export function zcodeConfigPath(env: OpencodeLaunchEnv = process.env, home: string = homedir(), io?: ZcodePathIO): string {
|
|
517
|
+
return join(zcodeHomeDir(env, home, io), "v2", "config.json");
|
|
487
518
|
}
|
|
488
519
|
|
|
489
520
|
/**
|
|
@@ -532,8 +563,7 @@ export function commandCodeConfigPath(env: OpencodeLaunchEnv = process.env, home
|
|
|
532
563
|
* an install that has ever run, the import has already happened and never runs
|
|
533
564
|
* again — every later write to `v2/config.json` is read by nobody (#5348).
|
|
534
565
|
*
|
|
535
|
-
*
|
|
536
|
-
* tell whether its own write can still reach the client. The env override is
|
|
566
|
+
* The integration writes this store through its existing schema-aware writer. The env override is
|
|
537
567
|
* ZCode's own (`ZCODE_PERSONAL_PROVIDER_CONFIG_FILE`), so an operator who
|
|
538
568
|
* relocated the store is measured against the file their client actually opens
|
|
539
569
|
* rather than the default location. A relative override is refused for the same
|
|
@@ -541,10 +571,10 @@ export function commandCodeConfigPath(env: OpencodeLaunchEnv = process.env, home
|
|
|
541
571
|
* which file it names, and here that disagreement decides whether an apply is
|
|
542
572
|
* reported as effective.
|
|
543
573
|
*/
|
|
544
|
-
export function zcodeProviderStorePath(env: OpencodeLaunchEnv = process.env, home: string = homedir()): string {
|
|
574
|
+
export function zcodeProviderStorePath(env: OpencodeLaunchEnv = process.env, home: string = homedir(), io?: ZcodePathIO): string {
|
|
545
575
|
const override = env.ZCODE_PERSONAL_PROVIDER_CONFIG_FILE?.trim();
|
|
546
576
|
if (override) return absoluteClientPath(override, home, "ZCODE_PERSONAL_PROVIDER_CONFIG_FILE");
|
|
547
|
-
return join(zcodeHomeDir(env, home), "v2", "provider_config.json");
|
|
577
|
+
return join(zcodeHomeDir(env, home, io), "v2", "provider_config.json");
|
|
548
578
|
}
|
|
549
579
|
|
|
550
580
|
/**
|
|
@@ -120,10 +120,13 @@ function serialize(text: string, doc: JsonObject): string {
|
|
|
120
120
|
/**
|
|
121
121
|
* `reasoning` left undefined keeps the entry's level; null removes it, so a level LazyCodex cannot
|
|
122
122
|
* express does not leave a stale one behind for its next install to apply.
|
|
123
|
+
* `model` left undefined keeps the entry's model, so an effort-only save of a role that inherits
|
|
124
|
+
* its model never adds a pin here. LazyCodex declares both keys optional, so an entry may carry
|
|
125
|
+
* `reasoning` alone; an absent entry is created only when there is a value to put in it.
|
|
123
126
|
*/
|
|
124
127
|
export function writeOmoRoleModel(
|
|
125
128
|
role: string,
|
|
126
|
-
model: string,
|
|
129
|
+
model: string | undefined,
|
|
127
130
|
path: string = omoJsoncPath(),
|
|
128
131
|
reasoning?: string | null,
|
|
129
132
|
): OmoRoleModelWriteStatus {
|
|
@@ -137,10 +140,10 @@ export function writeOmoRoleModel(
|
|
|
137
140
|
if (!isObject(agents)) return "invalid";
|
|
138
141
|
const entry = agents[role] === undefined ? {} : agents[role];
|
|
139
142
|
if (!isObject(entry)) return "invalid";
|
|
140
|
-
const next: JsonObject = { ...entry, model };
|
|
143
|
+
const next: JsonObject = model === undefined ? { ...entry } : { ...entry, model };
|
|
141
144
|
if (reasoning === null) delete next.reasoning;
|
|
142
145
|
else if (reasoning !== undefined) next.reasoning = reasoning;
|
|
143
|
-
if (entry.model === model && entry.reasoning === next.reasoning) return "unchanged";
|
|
146
|
+
if (entry.model === next.model && entry.reasoning === next.reasoning) return "unchanged";
|
|
144
147
|
agents[role] = next;
|
|
145
148
|
codex.agents = agents;
|
|
146
149
|
doc[CODEX_SECTION] = codex;
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The dashboard's write of one Codex agent role's model,
|
|
2
|
+
* The dashboard's write of one Codex agent role's model, its reasoning effort, or both.
|
|
3
3
|
*
|
|
4
4
|
* Codex overrides the spawn-time model with the root `model` pin in
|
|
5
5
|
* `$CODEX_HOME/agents/<role>.toml`, so that pin is what decides a child's model. This module
|
|
6
|
-
* edits exactly that value, plus the root `model_reasoning_effort`
|
|
7
|
-
* nothing else: every other byte of the file, including the
|
|
8
|
-
* usually mentions `model =` in prose, is left as it was.
|
|
6
|
+
* edits exactly that value when a caller passes a model, plus the root `model_reasoning_effort`
|
|
7
|
+
* when a caller passes an effort, and nothing else: every other byte of the file, including the
|
|
8
|
+
* instructions multiline string that usually mentions `model =` in prose, is left as it was. An
|
|
9
|
+
* omitted model leaves the pin as it is, so a role that inherits its model keeps inheriting it.
|
|
9
10
|
* It runs only on an explicit user action; no sync or startup path calls it.
|
|
10
11
|
*/
|
|
11
12
|
import { lstatSync, readFileSync } from "node:fs";
|
|
@@ -97,6 +98,21 @@ export function setTomlRootReasoningEffort(content: string, effort: string): str
|
|
|
97
98
|
return setTomlRootString(content, locateTomlReasoningEffortKey(content), "model_reasoning_effort", effort);
|
|
98
99
|
}
|
|
99
100
|
|
|
101
|
+
export function removeTomlRootReasoningEffort(content: string): string {
|
|
102
|
+
const location = locateTomlReasoningEffortKey(content);
|
|
103
|
+
if (!location?.inRootTable) return content;
|
|
104
|
+
if (!location.span) {
|
|
105
|
+
throw new AgentRoleModelError("unsupported_model_value", "the existing model_reasoning_effort value is not a one-line string");
|
|
106
|
+
}
|
|
107
|
+
const lines = content.split("\n");
|
|
108
|
+
// A terminated line goes with its own newline. An unterminated last line is emptied instead,
|
|
109
|
+
// so the previous line keeps its terminator (and its \r in a CRLF file).
|
|
110
|
+
if (location.line < lines.length - 1) lines.splice(location.line, 1);
|
|
111
|
+
else lines[location.line] = "";
|
|
112
|
+
const bom = location.line === 0 && content.startsWith("\ufeff") ? "\ufeff" : "";
|
|
113
|
+
return bom + lines.join("\n");
|
|
114
|
+
}
|
|
115
|
+
|
|
100
116
|
export function readCodexAgentRoleEffort(role: string, codexHome: string): string | null {
|
|
101
117
|
try {
|
|
102
118
|
const location = locateTomlReasoningEffortKey(readFileSync(roleFile(role, codexHome), "utf8"));
|
|
@@ -148,9 +164,9 @@ function assertValidRoleToml(role: string, text: string, stage: "before" | "afte
|
|
|
148
164
|
|
|
149
165
|
export function writeCodexAgentRoleModel(
|
|
150
166
|
role: string,
|
|
151
|
-
model: string,
|
|
167
|
+
model: string | undefined,
|
|
152
168
|
codexHome: string,
|
|
153
|
-
effort?: string,
|
|
169
|
+
effort?: string | null,
|
|
154
170
|
): { status: "written" | "unchanged" } {
|
|
155
171
|
requireKnownRole(role, codexHome);
|
|
156
172
|
const path = roleFile(role, codexHome);
|
|
@@ -159,10 +175,12 @@ export function writeCodexAgentRoleModel(
|
|
|
159
175
|
}
|
|
160
176
|
const before = readFileSync(path, "utf8");
|
|
161
177
|
assertValidRoleToml(role, before, "before");
|
|
162
|
-
const withModel = setTomlRootModel(before, validateAgentRoleModel(model));
|
|
178
|
+
const withModel = model === undefined ? before : setTomlRootModel(before, validateAgentRoleModel(model));
|
|
163
179
|
const after = effort === undefined
|
|
164
180
|
? withModel
|
|
165
|
-
:
|
|
181
|
+
: effort === null
|
|
182
|
+
? removeTomlRootReasoningEffort(withModel)
|
|
183
|
+
: setTomlRootReasoningEffort(withModel, validateAgentRoleEffort(effort));
|
|
166
184
|
if (after === before) return { status: "unchanged" };
|
|
167
185
|
assertValidRoleToml(role, after, "after");
|
|
168
186
|
assertIntegrationWriteOwnership(path);
|
|
@@ -33,6 +33,7 @@ import { fetchMainAccountInfoAttempt, EMPTY_MAIN_ACCOUNT_INFO, mainResetCreditsF
|
|
|
33
33
|
import { fetchPoolAccountQuota, PoolQuotaProbeBusyError, POOL_QUOTA_REFRESH_CONCURRENCY } from "./pool-quota-probe";
|
|
34
34
|
import type { PoolQuotaResult } from "./pool-quota-probe";
|
|
35
35
|
import { getRuntimeConfig, configuredPoolAccount, mapWithConcurrency } from "./runtime-config";
|
|
36
|
+
import { isPassiveRead } from "../../lib/passive-read";
|
|
36
37
|
|
|
37
38
|
export function quotaForPlan<T extends Omit<StoredAccountQuota, "updatedAt"> | StoredAccountQuota | null>(
|
|
38
39
|
quota: T,
|
|
@@ -274,13 +275,25 @@ export async function listCodexAuthAccountsSnapshot(
|
|
|
274
275
|
pruneCodexCredits([MAIN_CODEX_ACCOUNT_ID, ...poolAccounts.map(account => account.id)]);
|
|
275
276
|
// One redaction decision for the whole snapshot, read once from the operator's config (#3859).
|
|
276
277
|
const maskEmails = emailMaskingEnabled(runtimeConfig);
|
|
277
|
-
|
|
278
|
-
|
|
278
|
+
// A passive read (a Remote Link Child's, #6959) reports what this machine already observed: no
|
|
279
|
+
// token refresh, no WHAM probe, and so none of the plan or credential writes they make.
|
|
280
|
+
const passive = isPassiveRead();
|
|
281
|
+
const mainResult: Awaited<ReturnType<typeof fetchMainAccountInfoAttempt>> = passive
|
|
282
|
+
? { info: getMainAccountInfoCache() ?? EMPTY_MAIN_ACCOUNT_INFO, credentialChecked: false, hasCredential: false,
|
|
283
|
+
identityGeneration: captureMainAccountIdentityGeneration() }
|
|
284
|
+
: await fetchMainAccountInfoAttempt(forceRefresh, 1, undefined, false, forceRefresh, false, runtimeConfig);
|
|
279
285
|
const refreshedPool = await mapWithConcurrency(poolAccounts, POOL_QUOTA_REFRESH_CONCURRENCY, async account => {
|
|
280
286
|
const cred = getCodexAccountCredential(account.id);
|
|
281
287
|
let quotaResult: PoolQuotaResult;
|
|
282
288
|
if (!cred) {
|
|
283
289
|
quotaResult = { quota: null, needsReauth: true };
|
|
290
|
+
} else if (passive) {
|
|
291
|
+
quotaResult = {
|
|
292
|
+
quota: getAccountQuota(account.id),
|
|
293
|
+
needsReauth: false,
|
|
294
|
+
credentialGeneration: readCodexAccountRecord(account.id)?.generation,
|
|
295
|
+
quotaProbeSkipped: true,
|
|
296
|
+
};
|
|
284
297
|
} else {
|
|
285
298
|
try {
|
|
286
299
|
// Credits are process-local while pool quota is hydrated from disk, so right after a
|
|
@@ -410,6 +423,8 @@ export async function listCodexAuthAccountsSnapshot(
|
|
|
410
423
|
};
|
|
411
424
|
// Project the refusal and DTO in the same owned synchronous observation. A same-account grant
|
|
412
425
|
// replacement during earlier pool probes must not inherit the previous grant's refusal.
|
|
426
|
+
// A passive read opens no claim database (its lock file is hardened on open).
|
|
427
|
+
if (passive) return projectSnapshot();
|
|
413
428
|
const projectionLease = tryAcquireNativeMainProfileClaim();
|
|
414
429
|
if (!projectionLease) return projectSnapshot();
|
|
415
430
|
try {
|
|
@@ -6,11 +6,11 @@ import { deleteConfigTopLevelKey, fileBackedConfigPath, recordFileBackedConfig }
|
|
|
6
6
|
import { MAIN_CODEX_ACCOUNT_ID, hasLegacyMainCodexPoolAccount, isSelectableCodexPoolAccount, isValidCodexAccountId } from "../account-id";
|
|
7
7
|
import { isCodexAccountPaused } from "../account-pause";
|
|
8
8
|
import { readCodexAccountRecord } from "../account-store";
|
|
9
|
-
import { codexAccountPinDrainReason, resetCodexRoutingForManualSelection } from "../routing";
|
|
9
|
+
import { codexAccountPinDrainReason, resetCodexRoutingForClearedSelection, resetCodexRoutingForManualSelection } from "../routing";
|
|
10
10
|
import { jsonResponse } from "./http";
|
|
11
11
|
|
|
12
12
|
class SelectionRejected extends Error {
|
|
13
|
-
constructor(message: string, readonly status: number) { super(message); }
|
|
13
|
+
constructor(message: string, readonly status: number, readonly code?: string) { super(message); }
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
function validateSelection(config: OcxConfig, accountId: string | null): void {
|
|
@@ -42,8 +42,14 @@ function applySelection(config: OcxConfig, accountId: string | null): boolean {
|
|
|
42
42
|
return changed;
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
+
function validateExpectedPin(config: OcxConfig, expectedPinnedAccountId: string | undefined): void {
|
|
46
|
+
if (expectedPinnedAccountId !== undefined && config.activeCodexAccountPinned !== expectedPinnedAccountId) {
|
|
47
|
+
throw new SelectionRejected("The pin changed; reload accounts before releasing it", 409, "account_pin_changed");
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
45
51
|
/** A manual selection is an explicit command, even when equal to the live merge baseline. */
|
|
46
|
-
export function persistCodexAccountSelection(config: OcxConfig, accountId: string | null): Response {
|
|
52
|
+
export function persistCodexAccountSelection(config: OcxConfig, accountId: string | null, expectedPinnedAccountId?: string): Response {
|
|
47
53
|
const unknown = () => jsonResponse({
|
|
48
54
|
error: "Account selection could not be confirmed; reload settings before retrying",
|
|
49
55
|
code: "account_selection_unavailable",
|
|
@@ -63,7 +69,9 @@ export function persistCodexAccountSelection(config: OcxConfig, accountId: strin
|
|
|
63
69
|
const normalized = validateConfigCandidate(mergeConfigDefaults(config));
|
|
64
70
|
if (!normalized.ok) return unknown();
|
|
65
71
|
const initial = normalized.config;
|
|
66
|
-
|
|
72
|
+
validateExpectedPin(initial, expectedPinnedAccountId);
|
|
73
|
+
// Conditional release must keep the baseline pin until the locked mutation checks it.
|
|
74
|
+
if (expectedPinnedAccountId === undefined) applySelection(initial, accountId);
|
|
67
75
|
initializePersistedConfigIfMissing(initial);
|
|
68
76
|
}
|
|
69
77
|
const result = mutatePersistedConfig(persisted => {
|
|
@@ -71,12 +79,13 @@ export function persistCodexAccountSelection(config: OcxConfig, accountId: strin
|
|
|
71
79
|
// A removed or paused account must not be resurrected by a stale UI snapshot.
|
|
72
80
|
validateSelection(config, accountId);
|
|
73
81
|
validateSelection(persisted, accountId);
|
|
82
|
+
validateExpectedPin(persisted, expectedPinnedAccountId);
|
|
74
83
|
return { changed: applySelection(persisted, accountId), value: persisted };
|
|
75
84
|
});
|
|
76
85
|
if (result.status === "unavailable") return unknown();
|
|
77
86
|
committed = result.value;
|
|
78
87
|
} catch (error) {
|
|
79
|
-
if (error instanceof SelectionRejected) return jsonResponse({ error: error.message }, error.status);
|
|
88
|
+
if (error instanceof SelectionRejected) return jsonResponse({ error: error.message, ...(error.code ? { code: error.code } : {}) }, error.status);
|
|
80
89
|
// Atomic publication may succeed before generation/bookkeeping fails. Confirm the exact
|
|
81
90
|
// durable selection instead of rolling live state back behind an already published write.
|
|
82
91
|
const snapshot = readConfigFileSnapshot();
|
|
@@ -95,12 +104,13 @@ export function persistCodexAccountSelection(config: OcxConfig, accountId: strin
|
|
|
95
104
|
adoptPersistedCodexAccountSelection(config, committed);
|
|
96
105
|
recordFileBackedConfig(config, configPath);
|
|
97
106
|
// No failed save may clear affinity, spend the manual preference, or move the RR cursor.
|
|
98
|
-
|
|
107
|
+
if (accountId === null) resetCodexRoutingForClearedSelection();
|
|
108
|
+
else resetCodexRoutingForManualSelection(accountId);
|
|
99
109
|
const pinDrainReason = accountId === null ? undefined : codexAccountPinDrainReason(config, accountId);
|
|
100
110
|
return jsonResponse({
|
|
101
111
|
ok: true,
|
|
102
112
|
activeCodexAccountId: config.activeCodexAccountId ?? null,
|
|
103
|
-
appliesImmediately:
|
|
113
|
+
appliesImmediately: accountId !== null,
|
|
104
114
|
...(pinDrainReason !== undefined ? { pinDrained: true, pinDrainReason } : {}),
|
|
105
115
|
...(warning ? { warning: "config_bookkeeping_failed" } : {}),
|
|
106
116
|
});
|
|
@@ -254,7 +254,11 @@ export async function handleCodexAuthAPI(
|
|
|
254
254
|
|| !("accountId" in body) || (body.accountId !== null && typeof body.accountId !== "string")) {
|
|
255
255
|
return jsonResponse({ error: "accountId must be an account id or null" }, 400);
|
|
256
256
|
}
|
|
257
|
-
|
|
257
|
+
const expectedPin = "expectedPinnedAccountId" in body ? body.expectedPinnedAccountId : undefined;
|
|
258
|
+
if (expectedPin !== undefined && (body.accountId !== null || !isCodexAccountPriorityKey(expectedPin))) {
|
|
259
|
+
return jsonResponse({ error: "expectedPinnedAccountId must be a valid account id for a release" }, 400);
|
|
260
|
+
}
|
|
261
|
+
return persistCodexAccountSelection(getRuntimeConfig(config), body.accountId, expectedPin as string | undefined);
|
|
258
262
|
}
|
|
259
263
|
|
|
260
264
|
if (url.pathname === "/api/codex-auth/active" && req.method === "GET") {
|
|
@@ -215,7 +215,9 @@ function classifyStartupHealthSummary(health: StartupHealth): string {
|
|
|
215
215
|
? "custom remote Codex routing (no local restart dependency)"
|
|
216
216
|
: "native Codex routing (no opencodex restart dependency)";
|
|
217
217
|
if (health.protection === "service") return "protected by background service";
|
|
218
|
-
if (health.protection === "desktop") return
|
|
218
|
+
if (health.protection === "desktop") return health.desktop?.loginRegistration === "registered-not-loaded"
|
|
219
|
+
? "protected by desktop app at next login and its proxy supervisor (Start at Login registered; not loaded in this session)"
|
|
220
|
+
: "protected by desktop app at login and its proxy supervisor";
|
|
219
221
|
const command = health.recommendedCommand ?? health.commands.restoreNative;
|
|
220
222
|
if (health.routingKind === "unknown") return `AT RISK after restart (Codex routing could not be verified; run '${command}')`;
|
|
221
223
|
if (health.routingKind === "custom-local") return `AT RISK after restart (custom local gateway lifecycle is not managed by opencodex; run '${command}')`;
|
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
import type { NormalizedComboConfig } from "../../combos/types";
|
|
29
29
|
import { providerDestinationResolvedError } from "../../lib/destination-policy";
|
|
30
30
|
import { redactSecretString } from "../../lib/redact";
|
|
31
|
+
import { isPassiveRead } from "../../lib/passive-read";
|
|
31
32
|
import upstreamModelsSnapshot from "../data/upstream-models.json";
|
|
32
33
|
import { decodeOverlayState } from "../shim-state-file";
|
|
33
34
|
|
|
@@ -313,7 +314,8 @@ function* bundledCatalogSteps(
|
|
|
313
314
|
const output = yield command;
|
|
314
315
|
if (!current()) return null;
|
|
315
316
|
const catalog = output === null ? null : parseCatalogJson(output);
|
|
316
|
-
if (catalog &&
|
|
317
|
+
if (catalog && catalog.models?.every(row => row !== null && typeof row === "object" && !Array.isArray(row))
|
|
318
|
+
&& findNativeTemplate(catalog)) {
|
|
317
319
|
if (cacheKey) {
|
|
318
320
|
publishBundledCatalogCache(
|
|
319
321
|
cacheKey,
|
|
@@ -377,6 +379,8 @@ export function loadBundledCodexCatalogAsync(
|
|
|
377
379
|
if (useCache && bundledCatalogFlight?.inputs === inputs && bundledCatalogFlight.epoch === epoch) {
|
|
378
380
|
return view(bundledCatalogFlight.promise);
|
|
379
381
|
}
|
|
382
|
+
// A passive read (a Remote Link Child's, #6959) runs no Codex binary and persists no runtime.
|
|
383
|
+
if (isPassiveRead()) return Promise.resolve(null);
|
|
380
384
|
const controller = new AbortController();
|
|
381
385
|
const flight = { inputs, epoch, controller, promise: Promise.resolve<ReadonlyRawCatalog | null>(null) };
|
|
382
386
|
if (useCache && !signal && isCurrent === catalogAlwaysCurrent) {
|
|
@@ -620,8 +620,8 @@ export function clampCatalogModelsToCodexSupport(models: RawEntry[], deps: Bundl
|
|
|
620
620
|
removedEfforts: [...clamp.removedEfforts],
|
|
621
621
|
affectedModels: [...clamp.affectedModels],
|
|
622
622
|
};
|
|
623
|
-
|
|
624
|
-
if (
|
|
623
|
+
const changed = deps.commandCandidates ? true : persistEffortClamp(diagnostic, { configDir: deps.configDir });
|
|
624
|
+
if (changed) for (const line of formatClampLogLines(diagnostic)) console.warn(line);
|
|
625
625
|
deps.onEffortClamp?.(diagnostic);
|
|
626
626
|
} else if (!deps.commandCandidates) {
|
|
627
627
|
persistEffortClamp(null, { configDir: deps.configDir });
|
|
@@ -42,6 +42,7 @@ import { RESERVE_METADATA_SOURCE_FIELD } from "./reserve";
|
|
|
42
42
|
import {
|
|
43
43
|
ACCOUNT_GATED_NATIVE_OPENAI_MODELS,
|
|
44
44
|
NATIVE_GPT6_CONTEXT,
|
|
45
|
+
NATIVE_GPT61_SOL_CONTEXT,
|
|
45
46
|
NATIVE_DAYBREAK_BLUE_MODEL,
|
|
46
47
|
NATIVE_GPT6_ASTRA_MINOR_MODEL,
|
|
47
48
|
NATIVE_GPT6_ASTRA_MODEL,
|
|
@@ -202,8 +203,9 @@ export const NATIVE_OPENAI_CONTEXT_OVERRIDES: Record<string, { contextWindow?: n
|
|
|
202
203
|
// the row's own ceiling rather than the GPT-5.6 family's measured 922,000.
|
|
203
204
|
[NATIVE_GPT6_SOL_MODEL]: { ...NATIVE_GPT6_CONTEXT },
|
|
204
205
|
[NATIVE_GPT6_LUNA_MODEL]: { ...NATIVE_GPT6_CONTEXT },
|
|
205
|
-
// GPT-6.1 Sol's row (openai/codex models.json after #49318) ships
|
|
206
|
-
|
|
206
|
+
// GPT-6.1 Sol's row (openai/codex models.json after #49318) ships 272,000 / 872,000, but OpenAI
|
|
207
|
+
// raised its Codex & Work default to 500,000 on 2026-10-11; the 872,000 ceiling is unchanged.
|
|
208
|
+
[NATIVE_GPT61_SOL_MODEL]: { ...NATIVE_GPT61_SOL_CONTEXT },
|
|
207
209
|
// Astra Minor borrows Astra's row, so it inherits Astra's numbers. No account we hold can reach
|
|
208
210
|
// it, so this is inheritance, not a measurement.
|
|
209
211
|
[NATIVE_GPT6_ASTRA_MINOR_MODEL]: { ...NATIVE_GPT6_CONTEXT },
|
|
@@ -44,7 +44,8 @@ export const NATIVE_GPT6_LUNA_MODEL = "gpt-6-luna";
|
|
|
44
44
|
* SELF-DESCRIBED: its row is pinned verbatim in `src/codex/data/roster-pinned-models.json` from
|
|
45
45
|
* openai/codex `codex-rs/models-manager/models.json` after #49318, which also made it the Codex
|
|
46
46
|
* catalog default (priority 1). Ladder low..ultra with default effort low, and the GPT-6
|
|
47
|
-
* 272,000 / 872,000 context pair
|
|
47
|
+
* 272,000 / 872,000 context pair in that pinned row; the effective native default is now 500,000
|
|
48
|
+
* (NATIVE_GPT61_SOL_CONTEXT). Ungated for the same owner decision as Sol and Luna.
|
|
48
49
|
*/
|
|
49
50
|
export const NATIVE_GPT61_SOL_MODEL = "gpt-6.1-sol";
|
|
50
51
|
|
|
@@ -62,11 +63,24 @@ export const NATIVE_GPT6_ASTRA_MINOR_MODEL = "gpt-6-astra-minor";
|
|
|
62
63
|
/**
|
|
63
64
|
* Context pair every GPT-6 native ships in its upstream row: a 272,000-token default window
|
|
64
65
|
* against an 872,000-token ceiling (and input limit) reached only through the long-window opt-in.
|
|
65
|
-
* The built-in GPT-6 rows and every configured native (below) inherit this one value
|
|
66
|
+
* The built-in GPT-6 rows and every configured native (below) inherit this one value, except
|
|
67
|
+
* GPT-6.1 Sol, whose default window is NATIVE_GPT61_SOL_CONTEXT.
|
|
66
68
|
*/
|
|
67
69
|
export const NATIVE_GPT6_CONTEXT: Readonly<{ contextWindow: number; maxContextWindow: number; maxInputTokens: number }> =
|
|
68
70
|
Object.freeze({ contextWindow: 272_000, maxContextWindow: 872_000, maxInputTokens: 872_000 });
|
|
69
71
|
|
|
72
|
+
/**
|
|
73
|
+
* GPT-6.1 Sol through Codex & Work: OpenAI raised the default context from 272k to 500k
|
|
74
|
+
* (Tibo Sottiaux, 2026-10-11, https://x.com/thsottiaux/status/2109430343346581527), while
|
|
75
|
+
* overrides stay allowed and the model maximum is described as 1M. The bundled codex-rs row
|
|
76
|
+
* still reads 272,000 / 872,000, and built-in natives never take roster values, so the default
|
|
77
|
+
* lives here. The 872,000 ceiling (and input limit) is kept on purpose: the announcement does
|
|
78
|
+
* not say how "1M" maps onto the native window and input fields, and raising maxInputTokens
|
|
79
|
+
* would assert an input allowance nobody has measured. Overrides therefore clamp to 872,000.
|
|
80
|
+
*/
|
|
81
|
+
export const NATIVE_GPT61_SOL_CONTEXT: Readonly<{ contextWindow: number; maxContextWindow: number; maxInputTokens: number }> =
|
|
82
|
+
Object.freeze({ ...NATIVE_GPT6_CONTEXT, contextWindow: 500_000 });
|
|
83
|
+
|
|
70
84
|
/** Pinned row a configured native borrows its capability metadata from: the current Sol. */
|
|
71
85
|
export const CONFIGURED_NATIVE_OPENAI_TEMPLATE_MODEL = NATIVE_GPT61_SOL_MODEL;
|
|
72
86
|
|
|
@@ -20,6 +20,7 @@ import { withNativeMainExclusiveClaim } from "./native-main-claim";
|
|
|
20
20
|
import { scrubNativeMainAuthTempResidues } from "./native-main-auth-temp";
|
|
21
21
|
import { NATIVE_STAGE_SWEEP_INTERVAL_MS } from "./native-profile-stage-store";
|
|
22
22
|
import type { NativeCodexOwnership } from "../integrations/native/ownership-preflight";
|
|
23
|
+
import { isPassiveRead } from "../lib/passive-read";
|
|
23
24
|
|
|
24
25
|
export type NativeMainStartupGateSnapshot =
|
|
25
26
|
| { status: "ready"; homeId: string | null }
|
|
@@ -701,7 +702,9 @@ export async function releaseNativeMainStartupLifecycle(server: object): Promise
|
|
|
701
702
|
|
|
702
703
|
export function isNativeMainTrafficBlocked(): boolean {
|
|
703
704
|
const reason = activeServiceOwnershipBlockReason();
|
|
704
|
-
|
|
705
|
+
// A passive read (a Remote Link Child's, #6959) reports the gate as it stands: re-inspecting
|
|
706
|
+
// service ownership can activate the native-main lifecycle and its recovery on this machine.
|
|
707
|
+
if (reason !== null && !isPassiveRead() && reprobeServiceOwnership(reason)) {
|
|
705
708
|
// The host became ownable after boot (#2108): the fence lifts here rather than
|
|
706
709
|
// waiting for the restart the reporter had to perform by hand.
|
|
707
710
|
return activeServiceOwnershipBlockReason() !== null || snapshot.status === "blocked";
|
|
@@ -318,7 +318,7 @@ export function recoverIfNeeded(
|
|
|
318
318
|
};
|
|
319
319
|
}
|
|
320
320
|
try {
|
|
321
|
-
publishConfigWrite(expectedTargets.configPath, held, (destination, hooks) => restore(destination, record.preConfigBytes, hooks));
|
|
321
|
+
publishConfigWrite(expectedTargets.configPath, held, (destination, hooks) => restore(destination, record.preConfigBytes, expectingImage(hooks, record.postConfig)));
|
|
322
322
|
} catch (error) {
|
|
323
323
|
return {
|
|
324
324
|
ok: false,
|
|
@@ -336,7 +336,7 @@ export function recoverIfNeeded(
|
|
|
336
336
|
};
|
|
337
337
|
}
|
|
338
338
|
try {
|
|
339
|
-
publishConfigWriteTarget(expectedTargets.configPath, held, expectedTargets.storePath, (destination, hooks) => restore(destination, record.preStoreBytes, hooks));
|
|
339
|
+
publishConfigWriteTarget(expectedTargets.configPath, held, expectedTargets.storePath, (destination, hooks) => restore(destination, record.preStoreBytes, expectingImage(hooks, record.postStore)));
|
|
340
340
|
} catch (error) {
|
|
341
341
|
return {
|
|
342
342
|
ok: false,
|
|
@@ -363,3 +363,21 @@ function restore(path: string, bytes: string | null, hooks: AtomicWriteHooks): v
|
|
|
363
363
|
if (bytes === null) durableDelete(path, hooks);
|
|
364
364
|
else durableWrite(path, bytes, hooks);
|
|
365
365
|
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Bind a write to the image the caller classified. The rename retry hooks check
|
|
369
|
+
* path and inode identity only; an in-place edit by another process during a
|
|
370
|
+
* Windows sharing-violation retry keeps the inode, so without this re-read a
|
|
371
|
+
* later attempt would replace newer bytes. Checked before every attempt.
|
|
372
|
+
*/
|
|
373
|
+
export function expectingImage(hooks: AtomicWriteHooks, expectedHash: string): AtomicWriteHooks {
|
|
374
|
+
return {
|
|
375
|
+
...hooks,
|
|
376
|
+
validateBeforeRename: (targetPath: string) => {
|
|
377
|
+
hooks.validateBeforeRename?.(targetPath);
|
|
378
|
+
if (hashBytes(readOrNull(targetPath)) !== expectedHash) {
|
|
379
|
+
throw new Error(targetPath + " changed during the write; refusing to overwrite it. Files and recovery evidence were preserved.");
|
|
380
|
+
}
|
|
381
|
+
},
|
|
382
|
+
};
|
|
383
|
+
}
|
|
@@ -119,3 +119,52 @@ export function decodeTomlBasicString(literal: string): string | null {
|
|
|
119
119
|
}
|
|
120
120
|
return out;
|
|
121
121
|
}
|
|
122
|
+
|
|
123
|
+
/** Shared scalar assignment matching; quoted and escaped keys retain their spelling. */
|
|
124
|
+
export function matchKeyHead(line: string, key: string): { prefix: string; rest: string } | null {
|
|
125
|
+
const m = /^\s*("(?:[^"\\]|\\.)*"|'[^']*'|[A-Za-z0-9_-]+)\s*=\s*/.exec(line);
|
|
126
|
+
if (!m) return null;
|
|
127
|
+
const token = m[1]!;
|
|
128
|
+
const decoded = token.startsWith('"') ? decodeTomlBasicString(token)
|
|
129
|
+
: token.startsWith("'") ? token.slice(1, -1) : token;
|
|
130
|
+
return decoded === key ? { prefix: m[0], rest: line.slice(m[0].length) } : null;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* For each line, whether it begins outside every string and composite span, or
|
|
135
|
+
* `null` when a span never closes and the scope of later lines is unknown.
|
|
136
|
+
*
|
|
137
|
+
* Line matchers for assignments and table headers must only look at lines that
|
|
138
|
+
* start at top level: `"model_instructions_file" = "x"` written inside a
|
|
139
|
+
* `"""…"""` value or a multi-line array is prose, not syntax. Readers and
|
|
140
|
+
* editors share this so a write and the snapshot read back agree.
|
|
141
|
+
*/
|
|
142
|
+
export function lexicalLineStarts(lines: readonly string[]): boolean[] | null {
|
|
143
|
+
const starts: boolean[] = [];
|
|
144
|
+
let open: string | null = null;
|
|
145
|
+
let depth = 0;
|
|
146
|
+
for (const line of lines) {
|
|
147
|
+
starts.push(open === null && depth === 0);
|
|
148
|
+
for (let i = 0; i < line.length; i += 1) {
|
|
149
|
+
const char = line[i]!;
|
|
150
|
+
if (open !== null) {
|
|
151
|
+
if (char === "\\" && (open === '"' || open === '"""')) { i += 1; continue; }
|
|
152
|
+
if (char !== open[0]) continue;
|
|
153
|
+
if (open.length === 1) { open = null; continue; }
|
|
154
|
+
let run = 0;
|
|
155
|
+
while (line[i + run] === char) run += 1;
|
|
156
|
+
// Up to two quotes may sit inside the value just before the closing three.
|
|
157
|
+
if (run >= 3) open = null;
|
|
158
|
+
i += run - 1;
|
|
159
|
+
} else if (char === "#") break;
|
|
160
|
+
else if (char === '"' || char === "'") {
|
|
161
|
+
open = line.startsWith(char.repeat(3), i) ? char.repeat(3) : char;
|
|
162
|
+
i += open.length - 1;
|
|
163
|
+
} else if (char === "[" || char === "{") depth += 1;
|
|
164
|
+
else if (char === "]" || char === "}") { depth -= 1; if (depth < 0) return null; }
|
|
165
|
+
}
|
|
166
|
+
// Single-line strings cannot cross a newline.
|
|
167
|
+
if (open !== null && open.length === 1) return null;
|
|
168
|
+
}
|
|
169
|
+
return open === null && depth === 0 ? starts : null;
|
|
170
|
+
}
|