@bitkyc08/opencodex 2.67.0 → 2.68.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/bin/ocx.mjs +3 -0
- package/gui/dist/assets/App-DCBismRi.css +1 -0
- package/gui/dist/assets/App-mpsibsFj.js +51 -0
- package/gui/dist/assets/Tray-Bn8WhKBH.js +1 -0
- package/gui/dist/assets/{index-DtNmX7hW.css → index-BDUBS8PW.css} +1 -1
- package/gui/dist/assets/index-kUE8tuAK.js +86 -0
- package/gui/dist/assets/quota-summary-DL-bNuNL.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
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
import type { ProviderQuota } from "../providers/quota";
|
|
1
|
+
import type { ProviderQuota, ProviderQuotaWindow } from "../providers/quota";
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
/** Which custom windows count for a ranking; provider-wide windows always count. */
|
|
4
|
+
export type QuotaWindowFilter = (window: ProviderQuotaWindow) => boolean;
|
|
5
|
+
|
|
6
|
+
function collectResetCandidates(quota: ProviderQuota, includeWindow?: QuotaWindowFilter): number[] {
|
|
4
7
|
const candidates: number[] = [];
|
|
5
8
|
const push = (value: number | undefined): void => {
|
|
6
9
|
if (value !== undefined && Number.isFinite(value)) candidates.push(value);
|
|
@@ -10,7 +13,7 @@ function collectResetCandidates(quota: ProviderQuota): number[] {
|
|
|
10
13
|
push(quota.monthlyResetAt);
|
|
11
14
|
if (quota.customWindows) {
|
|
12
15
|
for (const w of quota.customWindows) {
|
|
13
|
-
push(w.resetAt);
|
|
16
|
+
if (!includeWindow || includeWindow(w)) push(w.resetAt);
|
|
14
17
|
}
|
|
15
18
|
}
|
|
16
19
|
return candidates;
|
|
@@ -23,9 +26,10 @@ function collectResetCandidates(quota: ProviderQuota): number[] {
|
|
|
23
26
|
export function earliestQuotaResetAt(
|
|
24
27
|
quota: ProviderQuota | null,
|
|
25
28
|
now: number,
|
|
29
|
+
includeWindow?: QuotaWindowFilter,
|
|
26
30
|
): number | null {
|
|
27
31
|
if (!quota) return null;
|
|
28
|
-
const future = collectResetCandidates(quota).filter(ts => ts > now);
|
|
32
|
+
const future = collectResetCandidates(quota, includeWindow).filter(ts => ts > now);
|
|
29
33
|
if (future.length > 0) return Math.min(...future);
|
|
30
34
|
return null;
|
|
31
35
|
}
|
|
@@ -39,8 +43,9 @@ export function earliestQuotaResetAt(
|
|
|
39
43
|
export function quotaResetRemainingMs(
|
|
40
44
|
quota: ProviderQuota | null,
|
|
41
45
|
now: number,
|
|
46
|
+
includeWindow?: QuotaWindowFilter,
|
|
42
47
|
): number {
|
|
43
|
-
const nearest = earliestQuotaResetAt(quota, now);
|
|
48
|
+
const nearest = earliestQuotaResetAt(quota, now, includeWindow);
|
|
44
49
|
if (nearest === null) return Number.POSITIVE_INFINITY;
|
|
45
50
|
return nearest - now;
|
|
46
51
|
}
|
package/src/combos/resolve.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { OcxComboTarget, OcxConfig } from "../types";
|
|
2
2
|
import { getCachedProviderRoutingQuota } from "../providers/quota-routing-cache";
|
|
3
|
-
import type { ProviderQuota } from "../providers/quota-types";
|
|
3
|
+
import type { ProviderQuota, ProviderQuotaWindow } from "../providers/quota-types";
|
|
4
4
|
import { sleepWithAbort } from "../lib/upstream-retry";
|
|
5
5
|
import {
|
|
6
6
|
coolComboTarget,
|
|
@@ -64,7 +64,7 @@ function targetProviderIsUsable(config: OcxConfig, target: OcxComboTarget, now:
|
|
|
64
64
|
if (!Object.hasOwn(config.providers, target.provider)) return false;
|
|
65
65
|
const provider = config.providers[target.provider];
|
|
66
66
|
if (!provider || provider.disabled === true) return false;
|
|
67
|
-
return !cachedProviderQuotaIsExhausted(getCachedProviderRoutingQuota(target.provider, provider, now), now);
|
|
67
|
+
return !cachedProviderQuotaIsExhausted(getCachedProviderRoutingQuota(target.provider, provider, now), now, target.model);
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
function quotaWindowExhausted(percent: number | undefined, resetAt: number | undefined, now: number): boolean {
|
|
@@ -72,15 +72,39 @@ function quotaWindowExhausted(percent: number | undefined, resetAt: number | und
|
|
|
72
72
|
return typeof resetAt !== "number" || !Number.isFinite(resetAt) || resetAt > now;
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
+
// `customWindows` is a generic carrier. Most labels are provider-wide counters ("Prepaid
|
|
76
|
+
// credits", "Free trial", "burst", "Spark") and must gate every model. Anthropic also rides it
|
|
77
|
+
// for per-model family counters, where a spent Opus week says nothing about a Sonnet request.
|
|
78
|
+
// `routing/quota.ts` already scopes those per model when it ranks accounts
|
|
79
|
+
// (`anthropicFamilyWindow`); this is the same fact applied to combo target selection.
|
|
80
|
+
//
|
|
81
|
+
// The scoping keys on `window.scope`, which the PRODUCER sets only where it proved model scope
|
|
82
|
+
// structurally, NEVER on the label text: `quota/antigravity.ts` forwards an upstream
|
|
83
|
+
// `group.displayName` unchanged, so a provider-wide group named "Opus" read as per-model would
|
|
84
|
+
// leave a spent window unenforced and send a doomed request. Every unproven direction keeps
|
|
85
|
+
// gating everything, which is the behaviour before this change: no scope on the window, no
|
|
86
|
+
// model on the request, or a family this gateway cannot match against a model id. Failing
|
|
87
|
+
// closed there costs at most a diverted target.
|
|
88
|
+
const MODEL_FAMILY_WINDOW_LABELS = new Set(["fable", "opus", "sonnet"]);
|
|
89
|
+
|
|
90
|
+
function customWindowAppliesToModel(window: ProviderQuotaWindow, model: string | undefined): boolean {
|
|
91
|
+
if (window.scope !== "model" || model === undefined) return true;
|
|
92
|
+
const family = window.label.trim().toLowerCase();
|
|
93
|
+
if (!MODEL_FAMILY_WINDOW_LABELS.has(family)) return true;
|
|
94
|
+
return model.toLowerCase().includes(family);
|
|
95
|
+
}
|
|
96
|
+
|
|
75
97
|
export function cachedProviderQuotaIsExhausted(
|
|
76
98
|
quota: ProviderQuota | null,
|
|
77
99
|
now = Date.now(),
|
|
100
|
+
model?: string,
|
|
78
101
|
): boolean {
|
|
79
102
|
if (!quota) return false;
|
|
80
103
|
if (quotaWindowExhausted(quota.fiveHourPercent, quota.fiveHourResetAt, now)) return true;
|
|
81
104
|
if (quotaWindowExhausted(quota.weeklyPercent, quota.weeklyResetAt, now)) return true;
|
|
82
105
|
if (quotaWindowExhausted(quota.monthlyPercent, quota.monthlyResetAt, now)) return true;
|
|
83
|
-
if (quota.customWindows?.some(window =>
|
|
106
|
+
if (quota.customWindows?.some(window => customWindowAppliesToModel(window, model)
|
|
107
|
+
&& quotaWindowExhausted(window.percent, window.resetAt, now))) return true;
|
|
84
108
|
if (quota.creditsUsd?.unlimited !== true
|
|
85
109
|
&& typeof quota.creditsUsd?.percent === "number"
|
|
86
110
|
&& Number.isFinite(quota.creditsUsd.percent)
|
|
@@ -120,7 +144,9 @@ export type QuotaInactiveReason = "no_credit";
|
|
|
120
144
|
*/
|
|
121
145
|
export function quotaInactiveReason(
|
|
122
146
|
config: OcxConfig,
|
|
123
|
-
targets
|
|
147
|
+
// A combo votes over its own targets, which carry a model; the single-provider case has none,
|
|
148
|
+
// and an absent model keeps gating on every window exactly as before.
|
|
149
|
+
targets: readonly { provider: string; model?: string }[],
|
|
124
150
|
now = Date.now(),
|
|
125
151
|
): QuotaInactiveReason | undefined {
|
|
126
152
|
const usable = targets.filter(target => {
|
|
@@ -132,7 +158,7 @@ export function quotaInactiveReason(
|
|
|
132
158
|
for (const target of usable) {
|
|
133
159
|
const provider = config.providers[target.provider]!;
|
|
134
160
|
const quota = getCachedProviderRoutingQuota(target.provider, provider, now);
|
|
135
|
-
if (!quota || !cachedProviderQuotaIsExhausted(quota, now)) return undefined;
|
|
161
|
+
if (!quota || !cachedProviderQuotaIsExhausted(quota, now, target.model)) return undefined;
|
|
136
162
|
}
|
|
137
163
|
return "no_credit";
|
|
138
164
|
}
|
|
@@ -185,7 +211,11 @@ function resetWindowIndex(
|
|
|
185
211
|
const target = targets[index]!;
|
|
186
212
|
if (!eligible(target)) continue;
|
|
187
213
|
const remaining = quotaResetRemainingMs(
|
|
188
|
-
getCachedProviderRoutingQuota(target.provider, config.providers[target.provider], now),
|
|
214
|
+
getCachedProviderRoutingQuota(target.provider, config.providers[target.provider], now),
|
|
215
|
+
now,
|
|
216
|
+
// Rank by the windows that gate this target: a model-scoped window of another family
|
|
217
|
+
// says nothing about when this model regains capacity.
|
|
218
|
+
window => customWindowAppliesToModel(window, target.model),
|
|
189
219
|
);
|
|
190
220
|
// Strict comparison deliberately retains configured order for ties,
|
|
191
221
|
// including the no-snapshot fallback where every value is Infinity.
|
|
@@ -2,6 +2,7 @@ import { createHash } from "node:crypto";
|
|
|
2
2
|
import { lstatSync, readFileSync } from "node:fs";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import * as z from "zod/v4";
|
|
5
|
+
import { compactionRecoveryConfigError } from "./schema/compaction-recovery";
|
|
5
6
|
import type { OcxConfig } from "../types";
|
|
6
7
|
import { configReasoningPinsConfigError } from "./provider-validation";
|
|
7
8
|
import { loopbackCompanionAllowed } from "../codex/loopback-target";
|
|
@@ -598,7 +599,7 @@ export function validateConfigCandidate(value: unknown): { ok: true; config: Ocx
|
|
|
598
599
|
if (compactionRouting !== undefined && !compactionRoutingSchema.safeParse(compactionRouting).success) {
|
|
599
600
|
return { ok: false, error: "schema_invalid: compactionRouting: requires a nonblank model, an optional valid reasoningEffort, and optional non-repeating triggers drawn from \"manual\" and \"auto\"" };
|
|
600
601
|
}
|
|
601
|
-
const boundaryError = configReasoningPinsConfigError(value)
|
|
602
|
+
const boundaryError = compactionRecoveryConfigError(value) ?? configReasoningPinsConfigError(value)
|
|
602
603
|
?? blankHostnameError(value)
|
|
603
604
|
?? claudeSubagentEffortError(value)
|
|
604
605
|
?? appOwnedMemoryBudgetError(value)
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { chmodSync, existsSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
+
import { compactionRecoveryConfigError } from "./schema/compaction-recovery";
|
|
3
4
|
import {
|
|
4
5
|
modelPinnedEffortsConfigError,
|
|
5
6
|
pinnedReasoningEffortConfigError,
|
|
@@ -118,6 +119,7 @@ export function warnDegradedCompactionRouting(rawParsed: unknown, validated: Ocx
|
|
|
118
119
|
* the ratchet only ever moves down: a per-block call there costs a line the file does not have.
|
|
119
120
|
*/
|
|
120
121
|
export function warnDegradedTopLevelOptIns(rawParsed: unknown, validated: OcxConfig): void {
|
|
122
|
+
if (compactionRecoveryConfigError(rawParsed)) console.warn("⚠️ invalid compactionRecovery disabled; the original compaction failure is preserved");
|
|
121
123
|
warnDegradedStreamMode(rawParsed, validated);
|
|
122
124
|
warnDegradedCompactionRouting(rawParsed, validated);
|
|
123
125
|
}
|
package/src/config/paths.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { chmodSync, existsSync } from "node:fs";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { join, resolve } from "node:path";
|
|
4
|
-
import { hardenSecretDirAsync, windowsSecretAclApplies } from "../lib/windows-secret-acl";
|
|
4
|
+
import { flushWindowsSecretAclReapsBeforeRemoval, hardenSecretDirAsync, windowsSecretAclApplies } from "../lib/windows-secret-acl";
|
|
5
5
|
import { assertNotRealHomeUnderTest } from "../lib/test-home-guard";
|
|
6
6
|
|
|
7
7
|
/**
|
|
@@ -63,6 +63,12 @@ export async function flushConfigDirHardening(dir: string = getConfigDir()): Pro
|
|
|
63
63
|
if (flight) await flight;
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
+
/** A settled ACL deadline is not proof its child has exited; stop must drain both before removal. */
|
|
67
|
+
export async function flushConfigDirHardeningAndReaps(dir: string): Promise<void> {
|
|
68
|
+
try { await flushConfigDirHardening(dir); }
|
|
69
|
+
finally { await flushWindowsSecretAclReapsBeforeRemoval(dir); }
|
|
70
|
+
}
|
|
71
|
+
|
|
66
72
|
/** Test-only: settle every in-flight config-directory harden regardless of directory. */
|
|
67
73
|
export async function flushConfigDirHardeningForTests(): Promise<void> {
|
|
68
74
|
await Promise.all([...configDirHardeningFlights.values()]);
|
|
@@ -36,6 +36,12 @@ export type RuntimePortState = {
|
|
|
36
36
|
hostname?: string;
|
|
37
37
|
/** Per-process proof key; protected by the config directory and never served. */
|
|
38
38
|
attestationSecret?: string;
|
|
39
|
+
/**
|
|
40
|
+
* The live proxy's port when this runtime is a sibling instance started beside it
|
|
41
|
+
* (`src/codex/sibling-start.ts`). `ocx stop` reads it to leave shared client routing alone.
|
|
42
|
+
* Absent for every other runtime, so those records keep their bytes.
|
|
43
|
+
*/
|
|
44
|
+
siblingOfPort?: number;
|
|
39
45
|
};
|
|
40
46
|
|
|
41
47
|
function isValidRuntimePortState(value: unknown): value is RuntimePortState {
|
|
@@ -43,13 +49,16 @@ function isValidRuntimePortState(value: unknown): value is RuntimePortState {
|
|
|
43
49
|
const state = value as Record<string, unknown>;
|
|
44
50
|
const hostnameOk = state.hostname === undefined || typeof state.hostname === "string";
|
|
45
51
|
const attestationOk = state.attestationSecret === undefined || isLocalAttestationSecret(state.attestationSecret);
|
|
52
|
+
const siblingOk = state.siblingOfPort === undefined
|
|
53
|
+
|| (Number.isInteger(state.siblingOfPort) && Number(state.siblingOfPort) > 0 && Number(state.siblingOfPort) <= 65535);
|
|
46
54
|
return Number.isSafeInteger(state.pid)
|
|
47
55
|
&& Number(state.pid) > 0
|
|
48
56
|
&& Number.isInteger(state.port)
|
|
49
57
|
&& Number(state.port) > 0
|
|
50
58
|
&& Number(state.port) <= 65535
|
|
51
59
|
&& hostnameOk
|
|
52
|
-
&& attestationOk
|
|
60
|
+
&& attestationOk
|
|
61
|
+
&& siblingOk;
|
|
53
62
|
}
|
|
54
63
|
|
|
55
64
|
export function writeRuntimePort(state: RuntimePortState): void {
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import * as z from "zod/v4";
|
|
2
|
+
|
|
3
|
+
/** Failure-only, opt-in recovery; it never chooses the initial compaction model. */
|
|
4
|
+
export const compactionRecoverySchema = z.object({
|
|
5
|
+
enabled: z.boolean(),
|
|
6
|
+
model: z.string().trim().min(1).max(512).regex(/^[^\s\u0000-\u0020\u007f-\u009f]+$/),
|
|
7
|
+
allowDevinInvalidArgument: z.boolean().optional(),
|
|
8
|
+
}).strict();
|
|
9
|
+
|
|
10
|
+
export function compactionRecoveryConfigError(value: unknown): string | null {
|
|
11
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
12
|
+
const recovery = (value as Record<string, unknown>).compactionRecovery;
|
|
13
|
+
return recovery === undefined || compactionRecoverySchema.safeParse(recovery).success
|
|
14
|
+
? null : "schema_invalid: compactionRecovery: requires enabled, a nonblank model, and optional boolean allowDevinInvalidArgument";
|
|
15
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as z from "zod/v4";
|
|
2
|
+
import { compactionRecoverySchema } from "./compaction-recovery";
|
|
2
3
|
import {
|
|
3
4
|
agentTaskRecoverySchema,
|
|
4
5
|
catalogAutoRefreshSchema,
|
|
@@ -156,6 +157,7 @@ export const configSchema = z.object({
|
|
|
156
157
|
providers: z.record(z.string(), providerConfigSchema),
|
|
157
158
|
modelPinnedEfforts: modelPinnedEffortsSchema.optional(),
|
|
158
159
|
compactionRouting: compactionRoutingSchema.optional().catch(undefined),
|
|
160
|
+
compactionRecovery: compactionRecoverySchema.optional().catch(undefined),
|
|
159
161
|
defaultProvider: z.string().min(1).default("openai"),
|
|
160
162
|
defaultModelAliases: z.boolean().optional(),
|
|
161
163
|
// Malformed hand edits disable this opt-in projection without rejecting providers.
|
|
@@ -97,18 +97,23 @@ const requestPacingRuleSchema = z.object({
|
|
|
97
97
|
// Keep the RPM-derived timer within the same one-hour bound as minIntervalMs.
|
|
98
98
|
requestsPerMinute: z.number().min(1 / 60).max(60_000).optional(),
|
|
99
99
|
minIntervalMs: z.number().int().min(1).max(3_600_000).optional(),
|
|
100
|
-
|
|
101
|
-
|
|
100
|
+
maxConcurrentRequests: z.number().int().min(1).optional(),
|
|
101
|
+
}).strict().refine(value => value.requestsPerMinute !== undefined
|
|
102
|
+
|| value.minIntervalMs !== undefined
|
|
103
|
+
|| value.maxConcurrentRequests !== undefined, {
|
|
104
|
+
message: "request pacing rules need requestsPerMinute, minIntervalMs, or maxConcurrentRequests",
|
|
102
105
|
});
|
|
103
106
|
|
|
104
107
|
const requestPacingSchema = z.object({
|
|
105
108
|
enabled: z.boolean(),
|
|
106
109
|
requestsPerMinute: z.number().min(1 / 60).max(60_000).optional(),
|
|
107
110
|
minIntervalMs: z.number().int().min(1).max(3_600_000).optional(),
|
|
111
|
+
maxConcurrentRequests: z.number().int().min(1).optional(),
|
|
108
112
|
models: z.record(z.string().trim().min(1), requestPacingRuleSchema).optional(),
|
|
109
113
|
}).strict().refine(value => value.enabled === false
|
|
110
114
|
|| value.requestsPerMinute !== undefined
|
|
111
115
|
|| value.minIntervalMs !== undefined
|
|
116
|
+
|| value.maxConcurrentRequests !== undefined
|
|
112
117
|
|| (value.models !== undefined && Object.keys(value.models).length > 0), {
|
|
113
118
|
message: "enabled request pacing needs a provider rule or model override",
|
|
114
119
|
});
|
|
@@ -117,7 +122,7 @@ export function requestPacingConfigError(value: unknown): string | null {
|
|
|
117
122
|
if (value === undefined) return null;
|
|
118
123
|
const parsed = requestPacingSchema.safeParse(value);
|
|
119
124
|
if (parsed.success) return null;
|
|
120
|
-
return "requestPacing must contain enabled and a valid requestsPerMinute/minIntervalMs provider rule or model overrides";
|
|
125
|
+
return "requestPacing must contain enabled and a valid requestsPerMinute/minIntervalMs/maxConcurrentRequests provider rule or model overrides";
|
|
121
126
|
}
|
|
122
127
|
|
|
123
128
|
/**
|
|
@@ -938,6 +943,7 @@ export const clientConnectionSchema = z.object({
|
|
|
938
943
|
*/
|
|
939
944
|
export const codexPoolSchema = z.object({
|
|
940
945
|
excludedPlans: z.array(z.string().trim().min(1)).optional(),
|
|
946
|
+
startIdleWindows: z.boolean().optional(),
|
|
941
947
|
}).strict();
|
|
942
948
|
|
|
943
949
|
/**
|