@bitkyc08/opencodex 2.67.0 → 2.68.0-preview.20260927
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/bin/ocx.mjs +3 -0
- package/gui/dist/assets/App-B5-cAEd2.js +51 -0
- package/gui/dist/assets/App-DCBismRi.css +1 -0
- package/gui/dist/assets/Tray-DFnMiyD0.js +1 -0
- package/gui/dist/assets/{index-DtNmX7hW.css → index-BDUBS8PW.css} +1 -1
- package/gui/dist/assets/index-BcXblvem.js +86 -0
- package/gui/dist/assets/quota-summary-i84eOU3o.js +1 -0
- package/gui/dist/index.html +21 -2
- package/package.json +1 -1
- package/src/adapters/anthropic-image-guard.ts +13 -1
- package/src/adapters/anthropic-image-normalize.ts +3 -2
- package/src/adapters/anthropic-output-schema.ts +36 -0
- package/src/adapters/anthropic.ts +10 -2
- package/src/adapters/base.ts +7 -0
- package/src/adapters/codebuddy/live-models.ts +180 -0
- package/src/adapters/coding-agent/protocol.ts +91 -12
- package/src/adapters/coding-agent/turn.ts +32 -27
- package/src/adapters/devin/cloud-direct/index.ts +1 -0
- package/src/adapters/devin/cloud-direct/stated-reset-retry.ts +18 -4
- package/src/adapters/devin.ts +9 -6
- package/src/adapters/google-http.ts +4 -4
- package/src/adapters/google.ts +93 -3
- package/src/adapters/kiro/adapter.ts +6 -2
- package/src/adapters/kiro/stream.ts +12 -2
- package/src/adapters/kiro/usage.ts +3 -1
- package/src/adapters/kiro-errors.ts +11 -1
- package/src/adapters/kiro-events.ts +27 -1
- package/src/adapters/kiro-refusal.ts +30 -0
- package/src/adapters/kiro-retry.ts +71 -31
- package/src/adapters/openai-chat/deepseek-artifact-schema.ts +45 -0
- package/src/adapters/openai-chat/messages.ts +31 -4
- package/src/adapters/openai-chat/serialized-tool-call-content.ts +52 -7
- package/src/adapters/openai-chat/tool-call-id-remint.ts +65 -0
- package/src/adapters/openai-chat/tool-schema.ts +6 -2
- package/src/adapters/openai-chat.ts +2 -2
- package/src/adapters/openai-responses/muse-tool-choice.ts +31 -0
- package/src/adapters/openai-responses/passthrough.ts +9 -3
- package/src/adapters/opencode-go-additional-tools.ts +19 -10
- package/src/adapters/physical-send.ts +10 -3
- package/src/adapters/registry.ts +2 -1
- package/src/adapters/responses-tool-schema.ts +31 -3
- package/src/adapters/run-turn-queue.ts +63 -23
- package/src/adapters/unique-tool-call-ids.ts +63 -0
- package/src/adapters/xai-web-search.ts +32 -2
- package/src/bridge/sse.ts +14 -2
- package/src/chat/inbound.ts +20 -6
- package/src/claude/agents-inject.ts +4 -0
- package/src/claude/desktop-3p.ts +53 -13
- package/src/claude/desktop-profile.ts +41 -4
- package/src/claude/inbound-model-options.ts +14 -2
- package/src/claude/inbound.ts +1 -1
- package/src/claude/intercept/connect-proxy.ts +17 -1
- package/src/claude/intercept/local-ca.ts +7 -1
- package/src/claude/message-threads.ts +28 -0
- package/src/cli/account-api.ts +13 -0
- package/src/cli/account-auth.ts +49 -2
- package/src/cli/account-extended.ts +4 -2
- package/src/cli/account.ts +4 -2
- package/src/cli/capabilities.ts +17 -0
- package/src/cli/claude.ts +3 -1
- package/src/cli/dispatch.ts +62 -5
- package/src/cli/index.ts +84 -79
- package/src/cli/minimax.ts +4 -2
- package/src/cli/opencode.ts +6 -1
- package/src/cli/restart-handoff.ts +110 -0
- package/src/cli/status.ts +51 -0
- package/src/client/connect.ts +34 -15
- package/src/client/link-ingress.ts +102 -0
- package/src/client/link-join.ts +37 -16
- package/src/client/link-relay.ts +228 -45
- package/src/client/link-state.ts +54 -0
- package/src/client/link-status.ts +39 -0
- package/src/client/link-teardown.ts +2 -2
- package/src/client/link-tunnel.ts +615 -86
- package/src/client/machine-api.ts +10 -4
- package/src/client/machine-listener.ts +57 -10
- package/src/client/runtime.ts +210 -35
- package/src/clients/config-export/constants.ts +1 -12
- package/src/clients/config-export/contracts.ts +2 -0
- package/src/clients/config-export/model-metadata.ts +9 -3
- package/src/clients/config-export/omp.ts +1 -1
- package/src/clients/config-export/zcode-store.ts +2 -2
- package/src/clients/config-export.ts +36 -3
- package/src/codex/app-server-processes.ts +32 -0
- package/src/codex/app-server-restart-service.ts +20 -3
- package/src/codex/catalog/access-programs.ts +50 -0
- package/src/codex/catalog/build-entries.ts +13 -0
- package/src/codex/catalog/derive-entry.ts +1 -0
- package/src/codex/catalog/model-hints.ts +11 -0
- package/src/codex/catalog/parsing.ts +6 -2
- package/src/codex/catalog/provider-models.ts +81 -0
- package/src/codex/catalog/retained-sync.ts +3 -0
- package/src/codex/codex-write-lock.ts +2 -2
- package/src/codex/convergence.ts +2 -0
- package/src/codex/desired-state.ts +14 -4
- package/src/codex/home.ts +21 -3
- package/src/codex/inject/restore.ts +17 -0
- package/src/codex/inject/routing-classify.ts +3 -2
- package/src/codex/inject-coordination.ts +4 -1
- package/src/codex/inject.ts +8 -1
- package/src/codex/journal.ts +6 -2
- package/src/codex/management-convergence.ts +9 -0
- package/src/codex/model-entitlements.ts +28 -5
- package/src/codex/routing/idle-window.ts +58 -0
- package/src/codex/routing-drift.ts +134 -0
- package/src/codex/routing-healer.ts +419 -0
- package/src/codex/routing.ts +10 -0
- package/src/codex/runtime.ts +188 -57
- package/src/codex/sibling-handoff.ts +71 -0
- package/src/codex/sibling-start.ts +139 -0
- package/src/codex/sync.ts +5 -3
- package/src/combos/failover.ts +32 -5
- package/src/combos/request.ts +11 -3
- package/src/combos/reset-window.ts +10 -5
- package/src/combos/resolve.ts +36 -6
- package/src/config/diagnostics.ts +2 -1
- package/src/config/load-degrade.ts +2 -0
- package/src/config/paths.ts +7 -1
- package/src/config/process-state.ts +10 -1
- package/src/config/schema/compaction-recovery.ts +15 -0
- package/src/config/schema/config-schema.ts +2 -0
- package/src/config/schema/leaf-validators.ts +9 -3
- package/src/generated/compatibility-version.json +360 -204
- package/src/images/loop.ts +47 -32
- package/src/integrations/catalog-refresh.ts +4 -0
- package/src/integrations/omp-yaml-source.ts +1 -0
- package/src/lib/claude-request-projection.ts +101 -0
- package/src/lib/local-account-switch-capability.ts +48 -0
- package/src/lib/local-upstream.ts +146 -0
- package/src/lib/package-tree-integrity.ts +25 -3
- package/src/lib/package-tree-retarget.ts +130 -0
- package/src/lib/process-control.ts +4 -1
- package/src/lib/system-restart-contract.ts +106 -0
- package/src/lib/upstream-retry.ts +57 -1
- package/src/link/ports.ts +9 -0
- package/src/link/ssh-argv.ts +16 -0
- package/src/link/ssh-runner.ts +70 -2
- package/src/link/tunnel-state.ts +58 -9
- package/src/oauth/account-quota-rank.ts +29 -18
- package/src/oauth/generic-account-failover.ts +179 -22
- package/src/oauth/index.ts +17 -4
- package/src/oauth/kiro-account-load.ts +107 -0
- package/src/oauth/kiro-device-login.ts +312 -0
- package/src/oauth/kiro-terminal-failover.ts +28 -0
- package/src/oauth/login-flow-state.ts +7 -1
- package/src/oauth/pool-settings-capability.ts +23 -14
- package/src/oauth/store.ts +56 -3
- package/src/oauth/types.ts +4 -0
- package/src/plugins/loader.ts +350 -0
- package/src/plugins/upstream-hooks.ts +119 -0
- package/src/protocols/encoders/adapter-events.ts +9 -2
- package/src/providers/account-quota-disk.ts +42 -4
- package/src/providers/codebuddy-models.ts +6 -2
- package/src/providers/command-code-efforts.ts +33 -10
- package/src/providers/kiro-account-state-disk.ts +44 -0
- package/src/providers/kiro-model-catalog.ts +162 -0
- package/src/providers/kiro-models.ts +5 -4
- package/src/providers/kiro-quota-metrics.ts +35 -0
- package/src/providers/kiro-usage.ts +118 -14
- package/src/providers/quota/account-cache.ts +43 -7
- package/src/providers/quota/antigravity.ts +8 -3
- package/src/providers/quota/kiro-account-probe.ts +12 -0
- package/src/providers/quota/vendor-probes-key.ts +31 -26
- package/src/providers/quota/vendor-probes-oauth.ts +16 -7
- package/src/providers/quota-types.ts +14 -0
- package/src/providers/quota.ts +21 -21
- package/src/providers/registry/entries-extended.ts +5 -3
- package/src/providers/request-pacing.ts +410 -12
- package/src/remote-control/workspace-codex-runtime.ts +4 -3
- package/src/responses/citation-markers.ts +132 -65
- package/src/responses/hosted-tool-policy.ts +14 -3
- package/src/responses/parser-content.ts +3 -2
- package/src/responses/parser.ts +4 -1
- package/src/responses/schema.ts +4 -0
- package/src/responses/visualization-directives.ts +182 -0
- package/src/server/chat-native-sse.ts +24 -12
- package/src/server/chat-native.ts +137 -68
- package/src/server/claude-messages.ts +147 -12
- package/src/server/index/link-listener.ts +7 -0
- package/src/server/index/optional-listeners.ts +4 -1
- package/src/server/index/package-tree-guard.ts +35 -16
- package/src/server/index/serve-options.ts +4 -2
- package/src/server/index/startup-warnings.ts +6 -0
- package/src/server/index.ts +7 -7
- package/src/server/inference/client-encoder-delivery.ts +3 -0
- package/src/server/inference/context.ts +30 -2
- package/src/server/local-account-switch-auth.ts +79 -0
- package/src/server/management/agent-settings-routes.ts +11 -3
- package/src/server/management/config-routes.ts +28 -11
- package/src/server/management/context.ts +2 -0
- package/src/server/management/link-routes.ts +123 -39
- package/src/server/management/logs-usage-routes.ts +9 -42
- package/src/server/management/model-rows.ts +1 -0
- package/src/server/management/oauth-account-routes.ts +67 -12
- package/src/server/management/route-registry.ts +5 -5
- package/src/server/management/sibling-guard.ts +60 -0
- package/src/server/management/storage-log-guard-routes.ts +72 -6
- package/src/server/management/system-restart.ts +92 -135
- package/src/server/management/system-routes.ts +1 -1
- package/src/server/management-api.ts +31 -3
- package/src/server/management-auth.ts +3 -0
- package/src/server/port-reclaim.ts +111 -1
- package/src/server/proxy-liveness.ts +16 -0
- package/src/server/relay.ts +16 -15
- package/src/server/request-log-filter.ts +28 -27
- package/src/server/request-log.ts +7 -3
- package/src/server/request-metrics.ts +28 -0
- package/src/server/responses/adapter-continuation.ts +151 -21
- package/src/server/responses/adapter-delivery.ts +21 -7
- package/src/server/responses/adapter-dispatch.ts +239 -51
- package/src/server/responses/codex-ws-pool.ts +5 -4
- package/src/server/responses/codex-ws-request.ts +4 -1
- package/src/server/responses/compact.ts +8 -4
- package/src/server/responses/compaction-recovery-policy.ts +116 -0
- package/src/server/responses/compaction-recovery.ts +330 -0
- package/src/server/responses/core-combo-failure.ts +1 -0
- package/src/server/responses/core-combo.ts +5 -1
- package/src/server/responses/core-lifetime.ts +22 -0
- package/src/server/responses/core-options.ts +16 -1
- package/src/server/responses/core.ts +14 -4
- package/src/server/responses/empty-completion-guard.ts +2 -0
- package/src/server/responses/fetch-helpers.ts +82 -31
- package/src/server/responses/passthrough-delivery.ts +3 -2
- package/src/server/responses/passthrough-dispatch.ts +12 -2
- package/src/server/responses/passthrough-execution.ts +2 -2
- package/src/server/responses/request-send-budget.ts +21 -5
- package/src/server/responses/request-transport.ts +116 -18
- package/src/server/responses/reset-replay.ts +32 -0
- package/src/server/responses/run-turn-execution.ts +136 -19
- package/src/server/responses/sidecar-execution.ts +41 -8
- package/src/server/responses/terminal-guard.ts +2 -0
- package/src/server/responses/ws-upstream.ts +44 -4
- package/src/server/restart-replacement.ts +382 -0
- package/src/server/startup-health-cache.ts +23 -4
- package/src/server/stop-teardown.ts +35 -1
- package/src/server/system-env.ts +3 -0
- package/src/service/windows-taskxml.ts +11 -7
- package/src/service/windows-wrapper-exit.ts +8 -0
- package/src/stall-timeout.ts +35 -10
- package/src/storage/policy-job.ts +4 -0
- package/src/storage/scanner.ts +177 -29
- package/src/storage/storage-mutation-coordinator.ts +17 -0
- package/src/tray/windows-tray.ps1 +50 -23
- package/src/types/config.ts +4 -0
- package/src/types/provider.ts +5 -1
- package/src/types/request.ts +13 -2
- package/src/update/index.ts +3 -1
- package/src/update/job.ts +6 -2
- package/src/update/mise-launcher-target.ts +100 -0
- package/src/update/worker-launch.ts +46 -0
- package/src/usage/log.ts +2 -0
- package/src/web-search/loop.ts +5 -2
- package/gui/dist/assets/App-BJsT8Icc.css +0 -1
- package/gui/dist/assets/App-D3pNGiN4.js +0 -51
- package/gui/dist/assets/Tray-yb05wCjb.js +0 -1
- package/gui/dist/assets/index-CouvvtMV.js +0 -86
- package/gui/dist/assets/tray-data-CYGdjCJ7.js +0 -1
package/src/server/index.ts
CHANGED
|
@@ -23,7 +23,7 @@ import {
|
|
|
23
23
|
loopbackCompanionBindError,
|
|
24
24
|
websocketsEnabled,
|
|
25
25
|
} from "../config";
|
|
26
|
-
import {
|
|
26
|
+
import { flushConfigDirHardeningAndReaps } from "../config/paths";
|
|
27
27
|
import { migrateStartupSubagentModels } from "./subagent-models-startup";
|
|
28
28
|
import { migrateStartupXaiResponses } from "./xai-responses-startup";
|
|
29
29
|
import { migrateStartupZaiResponses } from "./zai-responses-startup";
|
|
@@ -117,7 +117,7 @@ import { setUsageLedgerRetention } from "./usage-ledger-retention";
|
|
|
117
117
|
import { admitHttpWorkflowTurn, workflowDecisionRefusalResponse, type WorkflowRefusalLog } from "./workflow-refusal";
|
|
118
118
|
export {
|
|
119
119
|
addFinalRequestLog,
|
|
120
|
-
filterRequestLogs,
|
|
120
|
+
filterRequestLogs, queryRequestLogs,
|
|
121
121
|
hydrateRequestLogsFromDisk,
|
|
122
122
|
httpStatusForTerminalStatus,
|
|
123
123
|
httpStatusFromTerminalError,
|
|
@@ -788,12 +788,12 @@ function startServerWithSpendLedgerOwner(port: number | undefined, deps: StartSe
|
|
|
788
788
|
await backgroundLifecycle.release();
|
|
789
789
|
await releaseNativeMainStartupLifecycle(server);
|
|
790
790
|
} finally {
|
|
791
|
-
// icacls.exe from hardenConfigDir() holds the config dir open
|
|
792
|
-
//
|
|
793
|
-
//
|
|
794
|
-
//
|
|
791
|
+
// icacls.exe from hardenConfigDir() holds the config dir open. The caller-facing
|
|
792
|
+
// hardening deadline can settle before its child exits, so also wait for that
|
|
793
|
+
// child's reap before stop() promises the directory is removable. The spend owner
|
|
794
|
+
// is retained when a listener stop failed because the socket may live.
|
|
795
795
|
try { if (listenersStopped) spendLedgerLifecycle.release(); }
|
|
796
|
-
finally { await
|
|
796
|
+
finally { await flushConfigDirHardeningAndReaps(startupConfigDir); }
|
|
797
797
|
}
|
|
798
798
|
},
|
|
799
799
|
);
|
|
@@ -94,6 +94,8 @@ export interface ClientEncodedDelivery {
|
|
|
94
94
|
toolSearchToolNames?: Set<string>;
|
|
95
95
|
};
|
|
96
96
|
stallTimeoutSec?: number;
|
|
97
|
+
/** Operator-trusted local upstream: an unset stall budget stays disabled there (#5876). */
|
|
98
|
+
localUpstream?: boolean;
|
|
97
99
|
turnAdmissionLease?: AdmissionLease;
|
|
98
100
|
onFirstOutput?: () => void;
|
|
99
101
|
/** The bridge's `onCancel`: stop completion notification and abort the upstream. */
|
|
@@ -212,6 +214,7 @@ export async function deliverClientEncodedResponse(input: ClientEncodedDelivery)
|
|
|
212
214
|
translatorBudget,
|
|
213
215
|
...input.fold,
|
|
214
216
|
...(input.stallTimeoutSec !== undefined ? { stallTimeoutSec: input.stallTimeoutSec } : {}),
|
|
217
|
+
...(input.localUpstream !== undefined ? { localUpstream: input.localUpstream } : {}),
|
|
215
218
|
hooks,
|
|
216
219
|
};
|
|
217
220
|
const encoded = encoder.protocol === "chat"
|
|
@@ -1,7 +1,16 @@
|
|
|
1
|
-
import { createRequestExecutionBudget, type RequestExecutionBudget } from "../../lib/request-execution-budget";
|
|
1
|
+
import { CODEX_TEXT_GUARDED_BUDGET_POLICY, createRequestExecutionBudget, type RequestExecutionBudget } from "../../lib/request-execution-budget";
|
|
2
|
+
import { TRANSIENT_RETRY_MAX_ATTEMPTS, type TransientSendBudget } from "../../lib/upstream-retry";
|
|
3
|
+
import { GENERIC_OAUTH_MAX_ACCOUNTS_PER_REQUEST } from "../../oauth/generic-account-failover";
|
|
2
4
|
import type { RequestLogContext } from "../request-log";
|
|
3
5
|
import { attachRequestSpendTracker } from "../responses/request-spend";
|
|
4
6
|
|
|
7
|
+
// Only ingress owns an expandable policy. Exact caller budgets and combo-derived scopes
|
|
8
|
+
// never enter this map, even when their policy happens to equal the default profile.
|
|
9
|
+
const ingressPolicies = new WeakMap<TransientSendBudget, {
|
|
10
|
+
maxTotalModelSends: number;
|
|
11
|
+
baseSendAllowance: number;
|
|
12
|
+
}>();
|
|
13
|
+
|
|
5
14
|
/**
|
|
6
15
|
* The one construction of an ingress-owned send budget: the default guarded policy, no logical
|
|
7
16
|
* request id, and this request's spend tracker as the send observer. Attaching the tracker
|
|
@@ -11,5 +20,24 @@ export function createInferenceSendBudget(
|
|
|
11
20
|
req: Pick<Request, "headers">,
|
|
12
21
|
logCtx: RequestLogContext,
|
|
13
22
|
): RequestExecutionBudget {
|
|
14
|
-
|
|
23
|
+
const policy = { ...CODEX_TEXT_GUARDED_BUDGET_POLICY };
|
|
24
|
+
const budget = createRequestExecutionBudget(policy, undefined, attachRequestSpendTracker(req, logCtx));
|
|
25
|
+
ingressPolicies.set(budget, policy);
|
|
26
|
+
return budget;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Fund each account's normal transient ladder once, before the first physical send. The account
|
|
31
|
+
* count is clamped here as well as at the caller, so no caller can raise the ceiling past
|
|
32
|
+
* GENERIC_OAUTH_MAX_ACCOUNTS_PER_REQUEST × TRANSIENT_RETRY_MAX_ATTEMPTS.
|
|
33
|
+
*/
|
|
34
|
+
export function expandInferenceOAuthSendBudget(budget: TransientSendBudget | undefined, accounts: number): void {
|
|
35
|
+
if (!budget) return;
|
|
36
|
+
const policy = ingressPolicies.get(budget);
|
|
37
|
+
if (!policy || budget.used !== 0) return;
|
|
38
|
+
ingressPolicies.delete(budget);
|
|
39
|
+
if (accounts < 2 || !Number.isSafeInteger(accounts)) return;
|
|
40
|
+
const sends = Math.min(accounts, GENERIC_OAUTH_MAX_ACCOUNTS_PER_REQUEST) * TRANSIENT_RETRY_MAX_ATTEMPTS;
|
|
41
|
+
policy.baseSendAllowance = Math.max(policy.baseSendAllowance, sends);
|
|
42
|
+
policy.maxTotalModelSends = Math.max(policy.maxTotalModelSends, sends);
|
|
15
43
|
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { timingSafeEqual } from "node:crypto";
|
|
2
|
+
import {
|
|
3
|
+
LOCAL_MANAGEMENT_CAPABILITY_EXPIRES_AT_HEADER,
|
|
4
|
+
LOCAL_MANAGEMENT_CAPABILITY_HEADER,
|
|
5
|
+
LOCAL_MANAGEMENT_EXPECTED_PID_HEADER,
|
|
6
|
+
LOCAL_MANAGEMENT_NONCE_HEADER,
|
|
7
|
+
parseExpectedLocalManagementPid,
|
|
8
|
+
} from "../lib/local-management-capability";
|
|
9
|
+
import {
|
|
10
|
+
accountSwitchBodyDigest, isLocalAccountSwitchPath,
|
|
11
|
+
LOCAL_ACCOUNT_SWITCH_BODY_HEADER, verifyLocalAccountSwitchCapability,
|
|
12
|
+
} from "../lib/local-account-switch-capability";
|
|
13
|
+
import type { LocalManagementAuthContext } from "./management-auth";
|
|
14
|
+
|
|
15
|
+
const REPLAY_LIMIT = 256;
|
|
16
|
+
const BODY_LIMIT = 1024;
|
|
17
|
+
const consumed = new Map<string, number>();
|
|
18
|
+
const admittedBodyDigests = new WeakMap<Request, string>();
|
|
19
|
+
|
|
20
|
+
/** Admit one exact body-bound account switch on the attested local listener. */
|
|
21
|
+
export function hasLocalAccountSwitchCapability(
|
|
22
|
+
req: Request, local: LocalManagementAuthContext | undefined, now = Date.now(),
|
|
23
|
+
): boolean {
|
|
24
|
+
if (admittedBodyDigests.has(req)) return true;
|
|
25
|
+
if (!local || req.method !== "PUT" || req.headers.has("origin")) return false;
|
|
26
|
+
let url: URL;
|
|
27
|
+
try { url = new URL(req.url); } catch { return false; }
|
|
28
|
+
if (!isLocalAccountSwitchPath(url.pathname) || url.search !== "") return false;
|
|
29
|
+
const expectedPid = parseExpectedLocalManagementPid(req.headers.get(LOCAL_MANAGEMENT_EXPECTED_PID_HEADER));
|
|
30
|
+
if (expectedPid.kind !== "present" || expectedPid.pid !== local.pid) return false;
|
|
31
|
+
const expiry = req.headers.get(LOCAL_MANAGEMENT_CAPABILITY_EXPIRES_AT_HEADER);
|
|
32
|
+
if (!expiry || !/^[1-9]\d*$/.test(expiry)) return false;
|
|
33
|
+
const expiresAt = Number(expiry);
|
|
34
|
+
const capability = req.headers.get(LOCAL_MANAGEMENT_CAPABILITY_HEADER);
|
|
35
|
+
const bodyDigest = req.headers.get(LOCAL_ACCOUNT_SWITCH_BODY_HEADER);
|
|
36
|
+
if (!verifyLocalAccountSwitchCapability(
|
|
37
|
+
local.attestationSecret, req.headers.get(LOCAL_MANAGEMENT_NONCE_HEADER),
|
|
38
|
+
req.method, url.pathname, local.pid, local.port, expiresAt, bodyDigest, capability, now,
|
|
39
|
+
)) return false;
|
|
40
|
+
for (const [proof, until] of consumed) if (until <= now) consumed.delete(proof);
|
|
41
|
+
if (!capability || !bodyDigest || consumed.has(capability) || consumed.size >= REPLAY_LIMIT) return false;
|
|
42
|
+
consumed.set(capability, expiresAt);
|
|
43
|
+
admittedBodyDigests.set(req, bodyDigest);
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Stream a bounded raw body and compare it with the authenticated, cached digest. */
|
|
48
|
+
export async function readVerifiedAccountSwitchBody(
|
|
49
|
+
req: Request,
|
|
50
|
+
): Promise<{ status: 200; body: Uint8Array } | { status: 403 | 413 }> {
|
|
51
|
+
const expected = admittedBodyDigests.get(req);
|
|
52
|
+
if (!expected) return { status: 403 };
|
|
53
|
+
const reader = req.body?.getReader();
|
|
54
|
+
const chunks: Uint8Array[] = [];
|
|
55
|
+
let size = 0;
|
|
56
|
+
if (reader) {
|
|
57
|
+
try {
|
|
58
|
+
while (true) {
|
|
59
|
+
const { done, value } = await reader.read();
|
|
60
|
+
if (done) break;
|
|
61
|
+
size += value.byteLength;
|
|
62
|
+
if (size > BODY_LIMIT) {
|
|
63
|
+
await reader.cancel();
|
|
64
|
+
return { status: 413 };
|
|
65
|
+
}
|
|
66
|
+
chunks.push(value);
|
|
67
|
+
}
|
|
68
|
+
} catch {
|
|
69
|
+
return { status: 403 };
|
|
70
|
+
} finally {
|
|
71
|
+
reader.releaseLock();
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
const body = new Uint8Array(size);
|
|
75
|
+
let offset = 0;
|
|
76
|
+
for (const chunk of chunks) { body.set(chunk, offset); offset += chunk.byteLength; }
|
|
77
|
+
if (!timingSafeEqual(Buffer.from(expected), Buffer.from(accountSwitchBodyDigest(body)))) return { status: 403 };
|
|
78
|
+
return { status: 200, body };
|
|
79
|
+
}
|
|
@@ -44,6 +44,7 @@ import { clearThreadAccountMap } from "../../codex/routing";
|
|
|
44
44
|
import { primeCodexPoolQuotas } from "../../codex/auth-api";
|
|
45
45
|
import { DEFAULT_PROVIDER_CONTEXT_CAP, globalContextCapValue, providerContextCap, providerContextCaps, setAllProviderContextCaps, setGlobalContextCapValue, setProviderContextCap } from "../../providers/context-cap";
|
|
46
46
|
import { resolveCodexHomeDir } from "../../codex/home";
|
|
47
|
+
import { siblingOfLivePort } from "../../codex/sibling-start";
|
|
47
48
|
import { MULTI_AGENT_MODE_HINT_RECOMMENDATION } from "../../codex/multi-agent-mode-policy";
|
|
48
49
|
import { readUsageEntries } from "../../usage/log";
|
|
49
50
|
import { getUsageDebugLogEntries } from "../../usage/debug";
|
|
@@ -210,6 +211,7 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
|
|
|
210
211
|
|
|
211
212
|
/** Best-effort Desktop 3P config auto-reconcile when providers change. */
|
|
212
213
|
async function autoApplyDesktopBestEffort(): Promise<void> {
|
|
214
|
+
if (siblingOfLivePort() !== null) return;
|
|
213
215
|
try {
|
|
214
216
|
const { claudeDesktopIntegrationEnabled } = await import("../../codex/desired-state");
|
|
215
217
|
const admitted = loadConfig();
|
|
@@ -226,9 +228,11 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
|
|
|
226
228
|
if (["not_installed", "no_owned_state", "foreign", "unsafe", "broken"].includes(beforeKind)) return;
|
|
227
229
|
const { filterCatalogVisibleModels, desktopVisibleNativeSlugs } = await import("../../codex/catalog");
|
|
228
230
|
const allModels = await (deps.fetchAllModels ?? fetchAllModels)(admitted);
|
|
231
|
+
if (siblingOfLivePort() !== null) return;
|
|
229
232
|
// Serialized with Desktop mode transitions: a first-party switch cannot interleave with this write.
|
|
230
233
|
const { runPickerTransition } = await import("./claude-desktop-picker-routes");
|
|
231
234
|
await runPickerTransition(admitted, async () => {
|
|
235
|
+
if (siblingOfLivePort() !== null) return undefined;
|
|
232
236
|
const current = loadConfig();
|
|
233
237
|
// This is the real guard: the catalog await admits a concurrent explicit OFF.
|
|
234
238
|
if (!claudeDesktopIntegrationEnabled(current)) return undefined;
|
|
@@ -753,9 +757,13 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
|
|
|
753
757
|
...[...new Set(chosen)].filter(model => !selectableSet.has(model)),
|
|
754
758
|
];
|
|
755
759
|
// #857: let CLI/GUI show when a running Codex app-server keeps an older
|
|
756
|
-
// in-memory catalog than the one on disk.
|
|
757
|
-
|
|
758
|
-
const
|
|
760
|
+
// in-memory catalog than the one on disk. Bounded request-path read: the synchronous
|
|
761
|
+
// collector blocked the event loop for the whole Windows CIM walk (4-7s measured).
|
|
762
|
+
const {
|
|
763
|
+
collectCodexAppServerCatalogStateWithin,
|
|
764
|
+
DASHBOARD_CATALOG_STATE_DEADLINE_MS,
|
|
765
|
+
} = await import("../../codex/app-server-processes");
|
|
766
|
+
const catalogState = await collectCodexAppServerCatalogStateWithin(DASHBOARD_CATALOG_STATE_DEADLINE_MS);
|
|
759
767
|
return jsonResponse({
|
|
760
768
|
chosen, available, catalogState,
|
|
761
769
|
pickerAvailable: [...new Set(filterCatalogVisibleModels(models, config).map(catalogModelSlug).filter(slug => slug.includes("/")))],
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { compactionRoutingSchema } from "../../config/schema/leaf-validators";
|
|
2
|
+
import { compactionRecoverySchema } from "../../config/schema/compaction-recovery";
|
|
2
3
|
import { captureConfigTopLevelRollback } from "../../config/rebase-provenance";
|
|
3
4
|
import type { IntegrationClientId } from "../../integrations/registry";
|
|
4
5
|
import { randomUUID } from "node:crypto";
|
|
@@ -69,6 +70,7 @@ import {
|
|
|
69
70
|
initializeDefaultCodexAccountNamespaces,
|
|
70
71
|
} from "../../codex/account-namespaces";
|
|
71
72
|
import { catalogRefreshIsPending } from "../../codex/catalog-refresh-status";
|
|
73
|
+
import { siblingOfLivePort } from "../../codex/sibling-start";
|
|
72
74
|
import { DEFAULT_PROVIDER_CONTEXT_CAP, globalContextCapValue, providerContextCap, providerContextCaps, setAllProviderContextCaps, setGlobalContextCapValue, setProviderContextCap } from "../../providers/context-cap";
|
|
73
75
|
import { resolveCodexHomeDir } from "../../codex/home";
|
|
74
76
|
import { readUsageEntries } from "../../usage/log";
|
|
@@ -119,10 +121,10 @@ import type { PersistedUsageAttempt } from "../../usage/log";
|
|
|
119
121
|
import { isAllowedRequestOrigin, jsonResponse, providerManagementConfigError, publicProviderBaseUrl, safeConfigDTO } from "../auth-cors";
|
|
120
122
|
import { withProviderCatalogCapabilityDTO } from "./provider-capability-config";
|
|
121
123
|
import { applySystemEnvToggle } from "../system-env";
|
|
122
|
-
import { getCachedStartupHealth, invalidateStartupHealthCache } from "../startup-health-cache";
|
|
124
|
+
import { getCachedStartupHealth, getStartupHealthSnapshot, invalidateStartupHealthCache } from "../startup-health-cache";
|
|
123
125
|
import { runWindowsTrayAction } from "../windows-tray-control";
|
|
124
126
|
import { runStartupInstallAction, type StartupInstallAction } from "../startup-action-control";
|
|
125
|
-
import { displayCodexRuntimePath, effortClampAppliesToRuntime, liveRemovedEfforts, loadLastEffortClamp
|
|
127
|
+
import { displayCodexRuntimePath, effortClampAppliesToRuntime, getCodexRuntimeSnapshot, liveRemovedEfforts, loadLastEffortClamp } from "../../codex/runtime";
|
|
126
128
|
|
|
127
129
|
import { isPlainRecord, parseDebugLogQuery, tokPerSecondResult, unavailableCostReason, costResult, requestLogDto, stripRegistryOnlyStaticHeaders, fetchAllModels } from "./shared";
|
|
128
130
|
import type { MetricUnavailableReason, TokPerSecondResult, CostEstimateReason, CostResult, MetricSource } from "./shared";
|
|
@@ -196,7 +198,9 @@ export async function syncEnabledClientIntegrations(
|
|
|
196
198
|
deps: Pick<ManagementContext["deps"],
|
|
197
199
|
"fetchAllModels" | "refreshOwnedCatalogIntegrations" | "writeDesktop3pConfig"> = {},
|
|
198
200
|
): Promise<ClientIntegrationSyncOutcome[]> {
|
|
199
|
-
|
|
201
|
+
// A sibling instance passes its OWN port here; the Grok fence and the Desktop gateway profile
|
|
202
|
+
// stay on the live owner's (`src/codex/sibling-start.ts`).
|
|
203
|
+
if (port === undefined || siblingOfLivePort() !== null) return [];
|
|
200
204
|
const { claudeDesktopIntegrationEnabled, grokIntegrationEnabled } = await import("../../codex/desired-state");
|
|
201
205
|
const out: ClientIntegrationSyncOutcome[] = [];
|
|
202
206
|
|
|
@@ -291,6 +295,10 @@ function publicVisionSidecarSettings(
|
|
|
291
295
|
export async function handleConfigRoutes(ctx: ManagementContext): Promise<Response | null> {
|
|
292
296
|
const { req, url, config, deps, convergeCodexCatalog, syncClaudeAgentDefsBestEffort } = ctx;
|
|
293
297
|
const readStartupHealth = deps.getCachedStartupHealth ?? getCachedStartupHealth;
|
|
298
|
+
// Settings only seed the dashboard chip; /api/startup-health owns the bounded fresh read.
|
|
299
|
+
// Waiting on the Windows service-manager probe here held settings reads and saves open
|
|
300
|
+
// for up to 15s, including the admin-token check. An injected reader stays authoritative.
|
|
301
|
+
const readStartupHealthSnapshot = deps.getCachedStartupHealth ?? getStartupHealthSnapshot;
|
|
294
302
|
if (url.pathname === "/api/config" && req.method === "GET") {
|
|
295
303
|
return jsonResponse(withProviderCatalogCapabilityDTO(safeConfigDTO(config), config));
|
|
296
304
|
}
|
|
@@ -300,10 +308,12 @@ export async function handleConfigRoutes(ctx: ManagementContext): Promise<Respon
|
|
|
300
308
|
}
|
|
301
309
|
|
|
302
310
|
if (url.pathname === "/api/settings" && req.method === "GET") {
|
|
303
|
-
let resolved: ReturnType<typeof
|
|
311
|
+
let resolved: ReturnType<typeof getCodexRuntimeSnapshot>;
|
|
304
312
|
try {
|
|
305
|
-
// Full alternative discovery
|
|
306
|
-
|
|
313
|
+
// Full alternative discovery so newerAvailable warnings work, served stale-while-
|
|
314
|
+
// revalidate: the sync resolver ran `codex --version` per candidate on this request and
|
|
315
|
+
// froze the whole proxy for ~0.6s every memo expiry while the dashboard polled here.
|
|
316
|
+
resolved = getCodexRuntimeSnapshot();
|
|
307
317
|
} catch {
|
|
308
318
|
resolved = {
|
|
309
319
|
runtime: { command: "codex", version: null, source: "fallback" },
|
|
@@ -363,7 +373,8 @@ export async function handleConfigRoutes(ctx: ManagementContext): Promise<Respon
|
|
|
363
373
|
codexClientCompaction: config.codexClientCompaction === true,
|
|
364
374
|
codexDesktopSwitches: describeCodexDesktopSwitches(config, await observedCodexDesktopSwitchApply()),
|
|
365
375
|
compactionRouting: config.compactionRouting ?? null,
|
|
366
|
-
|
|
376
|
+
compactionRecovery: config.compactionRecovery ?? null,
|
|
377
|
+
startupHealth: await readStartupHealthSnapshot(config),
|
|
367
378
|
codexRuntime: {
|
|
368
379
|
path: displayCodexRuntimePath(resolved.runtime.command),
|
|
369
380
|
version: resolved.runtime.version,
|
|
@@ -455,6 +466,7 @@ export async function handleConfigRoutes(ctx: ManagementContext): Promise<Respon
|
|
|
455
466
|
codexDesktopAuthless?: unknown;
|
|
456
467
|
codexClientCompaction?: unknown;
|
|
457
468
|
compactionRouting?: unknown;
|
|
469
|
+
compactionRecovery?: unknown;
|
|
458
470
|
};
|
|
459
471
|
if (body.codexAutoStart === undefined
|
|
460
472
|
&& body.streamMode === undefined
|
|
@@ -467,8 +479,8 @@ export async function handleConfigRoutes(ctx: ManagementContext): Promise<Respon
|
|
|
467
479
|
&& body.codexMainAccountHardLock === undefined
|
|
468
480
|
&& body.codexDesktopAuthless === undefined
|
|
469
481
|
&& body.codexClientCompaction === undefined
|
|
470
|
-
&& body.compactionRouting === undefined) {
|
|
471
|
-
return jsonResponse({ error: "provide codexAutoStart, streamMode, appOwnedMemoryBudgetMb, codexAccountPickerEnabled, codexQuotaAutoRefresh, oauthOpenBrowser, ultraFastTier, fastRows, codexMainAccountHardLock, codexDesktopAuthless, codexClientCompaction, or
|
|
482
|
+
&& body.compactionRouting === undefined && body.compactionRecovery === undefined) {
|
|
483
|
+
return jsonResponse({ error: "provide codexAutoStart, streamMode, appOwnedMemoryBudgetMb, codexAccountPickerEnabled, codexQuotaAutoRefresh, oauthOpenBrowser, ultraFastTier, fastRows, codexMainAccountHardLock, codexDesktopAuthless, codexClientCompaction, compactionRouting, or compactionRecovery" }, 400);
|
|
472
484
|
}
|
|
473
485
|
if (body.codexAutoStart !== undefined && typeof body.codexAutoStart !== "boolean") {
|
|
474
486
|
return jsonResponse({ error: "codexAutoStart boolean is required" }, 400);
|
|
@@ -501,6 +513,8 @@ export async function handleConfigRoutes(ctx: ManagementContext): Promise<Respon
|
|
|
501
513
|
const compactionRouting = body.compactionRouting == null
|
|
502
514
|
? body.compactionRouting
|
|
503
515
|
: compactionRoutingSchema.safeParse(body.compactionRouting);
|
|
516
|
+
const compactionRecovery = body.compactionRecovery == null ? body.compactionRecovery : compactionRecoverySchema.safeParse(body.compactionRecovery);
|
|
517
|
+
if (compactionRecovery != null && !compactionRecovery.success) return jsonResponse({ error: "compactionRecovery requires enabled, a model, and optional boolean allowDevinInvalidArgument" }, 400);
|
|
504
518
|
if (compactionRouting != null && !compactionRouting.success) {
|
|
505
519
|
return jsonResponse({ error: "compactionRouting requires a model, an optional valid reasoningEffort, and optional non-repeating triggers drawn from \"manual\" and \"auto\"" }, 400);
|
|
506
520
|
}
|
|
@@ -533,7 +547,7 @@ export async function handleConfigRoutes(ctx: ManagementContext): Promise<Respon
|
|
|
533
547
|
)) {
|
|
534
548
|
return jsonResponse({ error: `appOwnedMemoryBudgetMb must be an integer from ${MIN_APP_OWNED_MEMORY_BUDGET_MB} to ${MAX_APP_OWNED_MEMORY_BUDGET_MB}` }, 400);
|
|
535
549
|
}
|
|
536
|
-
const restoreCompactionRouting = captureConfigTopLevelRollback(config, ["compactionRouting"]);
|
|
550
|
+
const restoreCompactionRouting = captureConfigTopLevelRollback(config, ["compactionRouting", "compactionRecovery"]);
|
|
537
551
|
const previousSettings = {
|
|
538
552
|
codexAutoStart: config.codexAutoStart,
|
|
539
553
|
hasCodexAutoStart: Object.hasOwn(config, "codexAutoStart"),
|
|
@@ -604,6 +618,8 @@ export async function handleConfigRoutes(ctx: ManagementContext): Promise<Respon
|
|
|
604
618
|
else if (body.codexClientCompaction === false) deleteConfigTopLevelKey(config, "codexClientCompaction");
|
|
605
619
|
if (compactionRouting === null) deleteConfigTopLevelKey(config, "compactionRouting");
|
|
606
620
|
else if (compactionRouting?.success) config.compactionRouting = compactionRouting.data;
|
|
621
|
+
if (compactionRecovery === null) deleteConfigTopLevelKey(config, "compactionRecovery");
|
|
622
|
+
else if (compactionRecovery?.success) config.compactionRecovery = compactionRecovery.data;
|
|
607
623
|
if (quotaAutoRefreshChange) {
|
|
608
624
|
const { id, window, enabled } = quotaAutoRefreshChange;
|
|
609
625
|
const setting = { ...(config.codexQuotaAutoRefresh?.[id] ?? {}) };
|
|
@@ -705,9 +721,10 @@ export async function handleConfigRoutes(ctx: ManagementContext): Promise<Respon
|
|
|
705
721
|
codexClientCompaction: clientCompactionIsEnabled,
|
|
706
722
|
codexDesktopSwitches,
|
|
707
723
|
compactionRouting: config.compactionRouting ?? null,
|
|
724
|
+
compactionRecovery: config.compactionRecovery ?? null,
|
|
708
725
|
codexMainAccountHardLock: isMainAccountHardLockEnabled(config),
|
|
709
726
|
mainAccountHardLock: getMainAccountHardLockStatus(config),
|
|
710
|
-
startupHealth: await
|
|
727
|
+
startupHealth: await readStartupHealthSnapshot(config),
|
|
711
728
|
});
|
|
712
729
|
}
|
|
713
730
|
|
|
@@ -154,6 +154,8 @@ export interface ManagementApiDeps {
|
|
|
154
154
|
issueApiKey?: (config: OcxConfig, name: string) => IssuedApiKey;
|
|
155
155
|
revokeApiKey?: (config: OcxConfig, id: string) => boolean;
|
|
156
156
|
loadLinkCandidates?: () => Array<{ alias: string; source: "ssh_config" | "tailscale" }>;
|
|
157
|
+
/** The port this runtime listens on; a join is refused unless it is the configured port. */
|
|
158
|
+
liveListenPort?: () => number | undefined;
|
|
157
159
|
now?: () => number;
|
|
158
160
|
}
|
|
159
161
|
|