@bitkyc08/opencodex 2.81.0 → 2.83.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 +166 -73
- package/gui/dist/assets/App-Ba8uG696.js +54 -0
- package/gui/dist/assets/{Tray-EwAOsP0S.js → Tray-IImbNPRy.js} +1 -1
- package/gui/dist/assets/{index-Brk6F3fn.css → index-BAbcj51J.css} +1 -1
- package/gui/dist/assets/{index-RsoaAxXY.js → index-BHRZdJuv.js} +13 -13
- package/gui/dist/assets/{quota-summary-D2lN8tDm.js → quota-summary-DiJO6sYY.js} +1 -1
- package/gui/dist/index.html +2 -2
- package/package.json +1 -1
- package/src/adapters/anthropic.ts +1 -1
- package/src/adapters/cursor/catalog.ts +2 -2
- package/src/adapters/cursor/effort-map.ts +1 -1
- package/src/adapters/devin/live-models.ts +2 -2
- package/src/adapters/google-truncation.ts +22 -0
- package/src/adapters/google.ts +7 -3
- package/src/adapters/ollama-native.ts +18 -14
- package/src/adapters/openai-responses/compaction-search-history.ts +109 -0
- package/src/adapters/openai-responses/passthrough.ts +11 -4
- package/src/adapters/openai-responses/plaintext-reasoning-notice.ts +85 -0
- package/src/adapters/openai-responses/reasoning.ts +11 -1
- package/src/adapters/openai-responses/tool-output-recovery.ts +20 -0
- package/src/bridge/errors.ts +7 -1
- package/src/claude/agents-inject.ts +14 -3
- package/src/claude/context-windows.ts +22 -20
- package/src/claude/desktop-3p.ts +88 -12
- package/src/claude/desktop-picker.ts +1 -1
- package/src/claude/desktop-profile.ts +2 -1
- package/src/claude/gateway-cache.ts +18 -1
- package/src/claude/inbound.ts +77 -3
- package/src/claude/intercept/cli-catalog.ts +3 -0
- package/src/claude/intercept/picker-ca-persistence.ts +7 -6
- package/src/claude/intercept/picker-models.ts +14 -6
- package/src/claude/intercept/settings.ts +3 -0
- package/src/claude/long-context.ts +66 -0
- package/src/claude/model-info.ts +19 -16
- package/src/claude/outbound.ts +94 -9
- package/src/claude/subagent-model.ts +40 -11
- package/src/cli/account-auth.ts +20 -12
- package/src/cli/agent.ts +9 -5
- package/src/cli/aside-profile-recovery.ts +33 -0
- package/src/cli/capabilities-base.ts +5 -5
- package/src/cli/capabilities-integrations.ts +2 -2
- package/src/cli/cli-path-diagnostics.ts +132 -0
- package/src/cli/desktop-runtime-guidance.ts +65 -0
- package/src/cli/dispatch.ts +5 -0
- package/src/cli/doctor.ts +16 -3
- package/src/cli/index.ts +14 -9
- package/src/cli/integration-aside-sync.ts +2 -1
- package/src/cli/integrations.ts +16 -3
- package/src/cli/resolve.ts +26 -3
- package/src/cli/root.ts +4 -2
- package/src/cli/status.ts +64 -12
- package/src/cli/update-restart-child.ts +4 -2
- package/src/cli/update-restart-home.ts +28 -10
- package/src/cli/update-restart-service-record.ts +116 -0
- package/src/cli/update-restart-supervision.ts +96 -0
- package/src/cli/update-restart.ts +59 -13
- package/src/cli/v2.ts +27 -20
- package/src/cli/version-skew-notice.ts +77 -0
- package/src/clients/config-export/model-metadata.ts +16 -0
- package/src/clients/config-export/zcode-store.ts +46 -11
- package/src/clients/config-export/zcode.ts +3 -5
- package/src/clients/config-export.ts +1 -1
- package/src/clients/lazycodex.ts +1 -1
- package/src/clients/omo-role-models.ts +32 -7
- package/src/codex/auth-api/account-selection.ts +107 -0
- package/src/codex/auth-api/pool-mode-gate.ts +4 -5
- package/src/codex/auth-api/routes.ts +10 -49
- package/src/codex/auth-context.ts +28 -4
- package/src/codex/autostart-health.ts +12 -3
- package/src/codex/catalog/build-entries.ts +18 -0
- package/src/codex/catalog/bundled.ts +155 -26
- package/src/codex/catalog/combo-member.ts +3 -2
- package/src/codex/catalog/effort.ts +26 -5
- package/src/codex/catalog/gather-capture.ts +5 -0
- package/src/codex/catalog/model-hints.ts +15 -8
- package/src/codex/catalog/model-visibility.ts +3 -1
- package/src/codex/catalog/native-models.ts +1 -1
- package/src/codex/catalog/operator-tiers.ts +38 -0
- package/src/codex/catalog/provider-models.ts +68 -35
- package/src/codex/catalog/retained-sync.ts +7 -5
- package/src/codex/catalog/routed-gather.ts +15 -1
- package/src/codex/catalog-auto-refresh-sources.ts +3 -4
- package/src/codex/catalog-auto-refresh.ts +19 -0
- package/src/codex/catalog.ts +1 -1
- package/src/codex/codex-home-owner.ts +3 -0
- package/src/codex/config-write-lock.ts +333 -0
- package/src/codex/convergence.ts +2 -1
- package/src/codex/desktop-app/darwin.ts +1 -1
- package/src/codex/desktop-app/handoff.ts +1 -1
- package/src/codex/desktop-app/linux.ts +1 -1
- package/src/codex/desktop-app/lock.ts +1 -1
- package/src/codex/desktop-app/types.ts +1 -1
- package/src/codex/desktop-app/windows.ts +1 -1
- package/src/codex/desktop-app-restart.ts +1 -1
- package/src/codex/features.ts +257 -200
- package/src/codex/inject/config-write-section.ts +34 -0
- package/src/codex/inject/multi-agent-v2.ts +14 -6
- package/src/codex/inject/remove.ts +36 -11
- package/src/codex/inject/restore.ts +85 -20
- package/src/codex/inject-coordination.ts +15 -6
- package/src/codex/inject.ts +62 -20
- package/src/codex/journal.ts +62 -17
- package/src/codex/main-account-cache.ts +43 -0
- package/src/codex/main-account.ts +6 -7
- package/src/codex/paths.ts +2 -0
- package/src/codex/prepared-home.ts +18 -0
- package/src/codex/prompt-journal.ts +25 -12
- package/src/codex/prompt-layers.ts +49 -26
- package/src/codex/prompt-lock-claim.ts +134 -0
- package/src/codex/prompt-lock-evidence.ts +26 -0
- package/src/codex/prompt-lock-io.ts +16 -0
- package/src/codex/prompt-lock-owner.ts +130 -0
- package/src/codex/prompt-lock.ts +84 -53
- package/src/codex/routing/failure-window.ts +146 -0
- package/src/codex/routing/selection.ts +4 -1
- package/src/codex/routing.ts +13 -17
- package/src/codex/runtime.ts +106 -18
- package/src/codex/shim.ts +45 -19
- package/src/combos/jev-service-exchange.ts +281 -0
- package/src/combos/jev.ts +34 -220
- package/src/config/atomic-write.ts +1 -1
- package/src/config/codex-ws-reuse-setting.ts +10 -0
- package/src/config/live-reconcile.ts +17 -0
- package/src/config/load-degrade.ts +8 -0
- package/src/config/rebase-provenance.ts +11 -0
- package/src/config/schema/config-schema.ts +4 -0
- package/src/generated/compatibility-version.json +375 -227
- package/src/generated/model-metadata.ts +2 -2
- package/src/grok/inject.ts +276 -206
- package/src/grok/reset-coupon-ledger.ts +223 -57
- package/src/grok/reset-coupons.ts +13 -6
- package/src/grok/sync.ts +2 -1
- package/src/images/loop.ts +29 -11
- package/src/integrations/aside-profiles.ts +10 -3
- package/src/integrations/catalog-refresh.ts +18 -2
- package/src/integrations/cursor-detect.ts +1 -1
- package/src/integrations/writer.ts +9 -3
- package/src/lib/bun-path-runtime.d.mts +32 -0
- package/src/lib/bun-path-runtime.mjs +95 -0
- package/src/lib/bun-runtime-preflight.ts +140 -0
- package/src/lib/desktop-cli-handoff.d.mts +20 -0
- package/src/lib/desktop-cli-handoff.mjs +92 -0
- package/src/lib/desktop-cli-record.d.mts +29 -0
- package/src/lib/desktop-cli-record.mjs +102 -0
- package/src/lib/errors.ts +25 -0
- package/src/lib/redact.ts +2 -2
- package/src/lib/request-execution-budget.ts +8 -0
- package/src/lib/synced-state-location.ts +46 -6
- package/src/lib/test-home-guard.ts +87 -1
- package/src/lib/translator-budget.ts +17 -4
- package/src/lib/windows-atomic-replace.ts +38 -16
- package/src/lib/windows-elevation.ts +6 -0
- package/src/lib/windows-secret-acl.ts +92 -25
- package/src/lib/winsw.ts +2 -3
- package/src/oauth/anthropic-account-refusal.ts +39 -1
- package/src/oauth/anthropic-routing.ts +1 -1
- package/src/oauth/anthropic-send-ownership.ts +3 -3
- package/src/oauth/chatgpt.ts +113 -41
- package/src/oauth/index.ts +7 -3
- package/src/oauth/meta-muse-device.ts +1 -1
- package/src/oauth/store.ts +1 -1
- package/src/oauth/types.ts +1 -1
- package/src/providers/anthropic-fast.ts +1 -1
- package/src/providers/anthropic-reset-grant-ledger.ts +1 -1
- package/src/providers/azure-model-metadata.ts +115 -0
- package/src/providers/derive.ts +21 -0
- package/src/providers/devin-cli-authmode-migration.ts +1 -1
- package/src/providers/devin-provider-merge-migration.ts +1 -1
- package/src/providers/github-copilot-auto.ts +169 -0
- package/src/providers/model-presets.ts +1 -1
- package/src/providers/muse-subscription-usage.ts +1 -1
- package/src/providers/quota/vendor-probes-key.ts +127 -17
- package/src/providers/quota.ts +1 -1
- package/src/providers/registry/entries-core.ts +6 -6
- package/src/providers/registry/entries-extended.ts +4 -0
- package/src/providers/registry/model-seeds.ts +3 -3
- package/src/providers/xai-fast-model.ts +1 -1
- package/src/responses/reasoning-envelope.ts +29 -1
- package/src/responses/reasoning-replay-cache.ts +17 -1
- package/src/responses/spill-store.ts +15 -2
- package/src/responses/state/metrics.ts +8 -0
- package/src/responses/state/spill-failure.ts +12 -0
- package/src/responses/state/spill-queue.ts +12 -5
- package/src/responses/state.ts +18 -10
- package/src/responses/task-input.ts +17 -0
- package/src/server/background-lifecycle.ts +27 -0
- package/src/server/chat-completions.ts +1 -0
- package/src/server/chat-native.ts +28 -12
- package/src/server/claude-messages.ts +43 -6
- package/src/server/inbound-body-admission.ts +25 -7
- package/src/server/index/claude-intercept-lifecycle.ts +3 -1
- package/src/server/index/link-listener.ts +30 -17
- package/src/server/index/serve-options.ts +18 -4
- package/src/server/index/spend-ledger-lifecycle.ts +2 -1
- package/src/server/index/startup-warnings.ts +19 -0
- package/src/server/index.ts +2 -1
- package/src/server/lifecycle.ts +31 -4
- package/src/server/management/agent-settings-routes.ts +93 -32
- package/src/server/management/codex-agent-role-routes.ts +4 -2
- package/src/server/management/codex-prompt-routes.ts +1 -0
- package/src/server/management/codex-role-auto-assign.ts +13 -2
- package/src/server/management/config-routes.ts +59 -8
- package/src/server/management/context.ts +2 -2
- package/src/server/management/grok-coupon-routes.ts +277 -52
- package/src/server/management/link-routes.ts +42 -20
- package/src/server/management/route-registry.ts +2 -2
- package/src/server/management/shared.ts +3 -3
- package/src/server/messages-native-oauth.ts +9 -8
- package/src/server/messages-native.ts +49 -13
- package/src/server/messages-response-headers.ts +14 -0
- package/src/server/relay-eager.ts +2 -0
- package/src/server/relay.ts +18 -7
- package/src/server/request-log-conversation.ts +48 -5
- package/src/server/request-log-failure-attribution.ts +3 -0
- package/src/server/request-log-terminal-status.ts +186 -0
- package/src/server/request-log.ts +15 -89
- package/src/server/responses/adapter-continuation.ts +11 -3
- package/src/server/responses/adapter-dispatch.ts +99 -19
- package/src/server/responses/codex-ws-exchange.ts +17 -2
- package/src/server/responses/codex-ws-pool.ts +100 -13
- package/src/server/responses/codex-ws-wire.ts +9 -0
- package/src/server/responses/combo-requested-effort.ts +3 -1
- package/src/server/responses/compact.ts +3 -2
- package/src/server/responses/core-codex-account.ts +22 -1
- package/src/server/responses/core-combo-native.ts +20 -3
- package/src/server/responses/core-combo.ts +22 -14
- package/src/server/responses/core-lifetime.ts +3 -36
- package/src/server/responses/core-opaque-recovery.ts +5 -5
- package/src/server/responses/core-options.ts +5 -0
- package/src/server/responses/core-replay.ts +73 -0
- package/src/server/responses/core.ts +9 -9
- package/src/server/responses/empty-completion-guard.ts +18 -2
- package/src/server/responses/fetch-helpers.ts +26 -3
- package/src/server/responses/jev-model-invoke.ts +1 -1
- package/src/server/responses/passthrough-delivery.ts +24 -2
- package/src/server/responses/passthrough-dispatch.ts +134 -62
- package/src/server/responses/passthrough-execution.ts +3 -3
- package/src/server/responses/request-prepare.ts +4 -0
- package/src/server/responses/request-send-budget.ts +112 -49
- package/src/server/responses/request-transport.ts +125 -5
- package/src/server/responses/response-effects.ts +1 -0
- package/src/server/responses/run-turn-execution.ts +15 -5
- package/src/server/responses/sidecar-execution.ts +30 -16
- package/src/server/responses/sidecar-send-budget.ts +58 -0
- package/src/server/responses/spend-storage-error.ts +33 -0
- package/src/server/responses/ws-upstream.ts +19 -6
- package/src/server/startup-health-cache.ts +2 -1
- package/src/server/workflow-refusal.ts +3 -0
- package/src/service/cli.ts +20 -4
- package/src/service/desktop-command-guard.ts +32 -0
- package/src/service/desktop-startup.ts +66 -40
- package/src/service/desktop-supervision.d.mts +26 -0
- package/src/service/desktop-supervision.mjs +140 -0
- package/src/service/managing-cli.ts +1 -0
- package/src/service/orchestration.ts +46 -20
- package/src/service/repair.ts +25 -9
- package/src/service/state.ts +4 -3
- package/src/service/systemd.ts +17 -8
- package/src/service/windows-ops.ts +61 -22
- package/src/storage/policy-job.ts +5 -1
- package/src/storage/policy-worker.ts +3 -1
- package/src/storage/policy.ts +9 -0
- package/src/types/config.ts +21 -0
- package/src/types/request.ts +10 -0
- package/src/types/wire.ts +1 -0
- package/src/update/index.ts +57 -9
- package/src/update/restart-ownership.ts +10 -3
- package/src/update/runtime-ownership.d.mts +6 -0
- package/src/update/runtime-ownership.mjs +23 -7
- package/src/usage/expected-prices.ts +1 -1
- package/src/web-search/loop.ts +5 -2
- package/gui/dist/assets/App-7yhoys8a.js +0 -54
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
} from "./live-sideband";
|
|
22
22
|
import {
|
|
23
23
|
withRequestLogId,
|
|
24
|
+
withMessagesRequestLogId,
|
|
24
25
|
} from "./startup-warnings";
|
|
25
26
|
|
|
26
27
|
import { remoteWorkspaceEnabled } from "../../remote-control/workspace-activation";
|
|
@@ -118,6 +119,7 @@ import { handleChatCompletions } from "../chat-completions";
|
|
|
118
119
|
import { anthropicErrorResponse } from "../../claude/outbound";
|
|
119
120
|
import {
|
|
120
121
|
buildDesktop3pRegistry,
|
|
122
|
+
desktop3pModelOptions,
|
|
121
123
|
generateDesktop3pModels,
|
|
122
124
|
} from "../../claude/desktop-3p";
|
|
123
125
|
import { buildDesktopDiscoveryInputs } from "../../claude/desktop-discovery-inputs";
|
|
@@ -1024,6 +1026,7 @@ export function createServeOptions(ctx: ServeOptionsContext) {
|
|
|
1024
1026
|
const models = config.claudeCode?.enabled === false ? [] : generateDesktop3pModels(
|
|
1025
1027
|
desktopInputs.nativeSlugs, desktopInputs.routedModels,
|
|
1026
1028
|
config.claudeCode?.desktopProfile, desktopInputs.nativeContextCap,
|
|
1029
|
+
desktop3pModelOptions(config.claudeCode),
|
|
1027
1030
|
);
|
|
1028
1031
|
const response = jsonResponse({ version: 1, models }, 200, req, policy);
|
|
1029
1032
|
response.headers.set("Cache-Control", "no-store");
|
|
@@ -1556,11 +1559,16 @@ export function createServeOptions(ctx: ServeOptionsContext) {
|
|
|
1556
1559
|
// pre-translation stream + native passthrough callbacks) — do not re-wrap the
|
|
1557
1560
|
// translated Anthropic stream here.
|
|
1558
1561
|
const sessionReq = withCallerSessionIdentity(req, admission);
|
|
1559
|
-
|
|
1562
|
+
// Entering the handler, or a logged refusal, means the final request log owns requestId.
|
|
1563
|
+
let ownsRequestLog = false;
|
|
1564
|
+
const claimRequestLog = (work: (lease: ActiveTurnLease) => Promise<Response>) =>
|
|
1565
|
+
(lease: ActiveTurnLease) => { ownsRequestLog = true; return work(lease); };
|
|
1566
|
+
const response = await runAdmittedHttpTurn(sessionReq, policy, claimRequestLog(async turnAdmissionLease => withCors(
|
|
1560
1567
|
await handleClaudeMessages(sessionReq, config, logCtx, { requestId, start, turnAdmissionLease, admission }, policy, { claudeIntercept: ingress === "claude-intercept" }),
|
|
1561
1568
|
req,
|
|
1562
1569
|
policy,
|
|
1563
|
-
), { requestId, start, logCtx });
|
|
1570
|
+
)), { requestId, start, logCtx, onLogged: () => { ownsRequestLog = true; } });
|
|
1571
|
+
return ownsRequestLog ? withMessagesRequestLogId(response, requestId) : response;
|
|
1564
1572
|
}
|
|
1565
1573
|
|
|
1566
1574
|
// OpenAI Chat Completions inbound (GitHub Copilot App / OpenAI-compatible clients).
|
|
@@ -1586,8 +1594,11 @@ export function createServeOptions(ctx: ServeOptionsContext) {
|
|
|
1586
1594
|
// `policy`, not `config`: this route is now served on the unauthenticated loopback
|
|
1587
1595
|
// listener too (#4236), and only the receiving listener's view produces CORS headers
|
|
1588
1596
|
// that match the admission decision made above.
|
|
1589
|
-
|
|
1590
|
-
|
|
1597
|
+
// Promote a caller `x-session-id` before admission, as Responses and Messages do (#6520):
|
|
1598
|
+
// the ChatGPT bridge forwards only canonical session headers, so the raw marker is lost.
|
|
1599
|
+
const sessionReq = withCallerSessionIdentity(req, admission);
|
|
1600
|
+
return runAdmittedHttpTurn(sessionReq, policy, async turnAdmissionLease => withCors(
|
|
1601
|
+
await handleChatCompletions(sessionReq, config, logCtx, { requestId, start, turnAdmissionLease, admission }),
|
|
1591
1602
|
req,
|
|
1592
1603
|
policy,
|
|
1593
1604
|
), { requestId, start, logCtx });
|
|
@@ -1708,7 +1719,9 @@ export function createServeOptions(ctx: ServeOptionsContext) {
|
|
|
1708
1719
|
if (audioController) registerTurn(audioController, turnAdmissionLease);
|
|
1709
1720
|
const acquisition = audioController
|
|
1710
1721
|
? clearableDeadline(120_000, AbortSignal.any([req.signal, audioController.signal])) : undefined;
|
|
1722
|
+
const detachClientAbort = turnAdmissionLease.bindAbortSignal(req.signal);
|
|
1711
1723
|
const releaseAcquisition = () => {
|
|
1724
|
+
detachClientAbort();
|
|
1712
1725
|
acquisition?.clear();
|
|
1713
1726
|
if (audioController) unregisterTurn(audioController);
|
|
1714
1727
|
else turnAdmissionLease.release();
|
|
@@ -1827,6 +1840,7 @@ export function createServeOptions(ctx: ServeOptionsContext) {
|
|
|
1827
1840
|
return withCors(formatErrorResponse(502, "upstream_error", "Audio WebSocket upgrade failed"), req, policy);
|
|
1828
1841
|
}
|
|
1829
1842
|
if (upgraded) {
|
|
1843
|
+
detachClientAbort();
|
|
1830
1844
|
acquisition?.clear();
|
|
1831
1845
|
finalizeLiveRequest(101);
|
|
1832
1846
|
return undefined as unknown as Response;
|
|
@@ -37,7 +37,8 @@ export interface SpendLedgerServerLifecycle {
|
|
|
37
37
|
export function acquireSpendLedgerServerLifecycle(configDir: string): SpendLedgerServerLifecycle {
|
|
38
38
|
const owner: SpendLedgerOwnerLease = acquireSpendLedgerOwner(configDir);
|
|
39
39
|
// Advisory: a synced state directory makes the journal's hard-link guard refuse intermittently
|
|
40
|
-
// (#6314)
|
|
40
|
+
// (#6314), including Google Drive syncing the native Desktop or Documents folder. Said once
|
|
41
|
+
// at startup instead of being discovered from a 502. The nlink guard itself is unchanged.
|
|
41
42
|
warnIfSyncedStateDirectory(configDir);
|
|
42
43
|
// Each entry returns whatever the listener's own stop returned. Typed as void-or-promise
|
|
43
44
|
// because the rollback below has to WAIT on it: declaring it `() => void` let the call site
|
|
@@ -100,6 +100,25 @@ import {
|
|
|
100
100
|
|
|
101
101
|
const REQUEST_LOG_ID_RESPONSE_HEADER = "x-opencodex-request-id";
|
|
102
102
|
|
|
103
|
+
/** Messages clients persist request-id; the upstream diagnostic id has a separate namespace. */
|
|
104
|
+
export function withMessagesRequestLogId(response: Response, requestId: string): Response {
|
|
105
|
+
const upstreamId = response.headers.get("request-id");
|
|
106
|
+
const correlated = withRequestLogId(response, requestId);
|
|
107
|
+
correlated.headers.delete("x-opencodex-upstream-request-id");
|
|
108
|
+
if (upstreamId && /^req_[A-Za-z0-9_-]{1,128}$/.test(upstreamId)) {
|
|
109
|
+
correlated.headers.set("x-opencodex-upstream-request-id", upstreamId);
|
|
110
|
+
}
|
|
111
|
+
correlated.headers.set("request-id", requestId);
|
|
112
|
+
const exposed = correlated.headers.get("Access-Control-Expose-Headers") ?? "";
|
|
113
|
+
const names = exposed.split(",").map(name => name.trim().toLowerCase());
|
|
114
|
+
const additions = ["request-id", ...(correlated.headers.has("x-opencodex-upstream-request-id")
|
|
115
|
+
? ["x-opencodex-upstream-request-id"] : [])].filter(name => !names.includes(name));
|
|
116
|
+
if (additions.length) {
|
|
117
|
+
correlated.headers.set("Access-Control-Expose-Headers", [exposed, ...additions].filter(Boolean).join(", "));
|
|
118
|
+
}
|
|
119
|
+
return correlated;
|
|
120
|
+
}
|
|
121
|
+
|
|
103
122
|
export function withRequestLogId(response: Response, requestId: string): Response {
|
|
104
123
|
const headers = new Headers(response.headers);
|
|
105
124
|
headers.set(REQUEST_LOG_ID_RESPONSE_HEADER, requestId);
|
package/src/server/index.ts
CHANGED
|
@@ -502,7 +502,7 @@ function startServerWithSpendLedgerOwner(port: number | undefined, deps: StartSe
|
|
|
502
502
|
work: (lease: ActiveTurnLease) => Promise<Response>,
|
|
503
503
|
refusalLog?: WorkflowRefusalLog,
|
|
504
504
|
): Promise<Response> {
|
|
505
|
-
const lease = tryAdmitTurn(sessionLaneIdFromRequest(req.headers));
|
|
505
|
+
const lease = tryAdmitTurn(sessionLaneIdFromRequest(req.headers), req.signal);
|
|
506
506
|
if (!lease) return serverBusyResponse(req, "active turns", policy);
|
|
507
507
|
// Root, lane, and the refusal that follows from them, all live in ./workflow-refusal.
|
|
508
508
|
let workflow: ReturnType<typeof admitHttpWorkflowTurn>;
|
|
@@ -750,6 +750,7 @@ function startServerWithSpendLedgerOwner(port: number | undefined, deps: StartSe
|
|
|
750
750
|
Object.defineProperty(server, "stop", {
|
|
751
751
|
configurable: true,
|
|
752
752
|
value: async (closeActiveConnections?: boolean): Promise<void> => {
|
|
753
|
+
backgroundLifecycle?.revokeClientFanout();
|
|
753
754
|
remoteWorkspaceStopping = true;
|
|
754
755
|
liveCallBindings.clear();
|
|
755
756
|
// Disarm the package-tree restart timer before teardown can schedule another restart.
|
package/src/server/lifecycle.ts
CHANGED
|
@@ -36,6 +36,7 @@ export const SESSION_LANE_ID_BYTES = 32;
|
|
|
36
36
|
const turnGate = createAdmissionGate("active_turns", MAX_ACTIVE_TURNS);
|
|
37
37
|
export interface ActiveTurnLease extends AdmissionLease {
|
|
38
38
|
attach(lease: AdmissionLease): void;
|
|
39
|
+
bindAbortSignal(signal: AbortSignal): () => void;
|
|
39
40
|
bindAbortController(ac: AbortController): void;
|
|
40
41
|
beginCodexAccountSelection(): CodexAccountSelectionAdmission;
|
|
41
42
|
isTransferred(): boolean;
|
|
@@ -169,7 +170,7 @@ export function resetLifecycleDrainStateForTests(): void {
|
|
|
169
170
|
serverStartupReleaseFlights = new WeakMap<ReturnType<typeof Bun.serve>, Promise<void>>();
|
|
170
171
|
releaseServerStartupLifecycleImpl = releaseNativeMainStartupLifecycle;
|
|
171
172
|
}
|
|
172
|
-
export function tryAdmitTurn(sessionLaneId?: string): ActiveTurnLease | null {
|
|
173
|
+
export function tryAdmitTurn(sessionLaneId?: string, clientSignal?: AbortSignal): ActiveTurnLease | null {
|
|
173
174
|
if (isDraining()) return null;
|
|
174
175
|
const opaqueSessionLaneId = sessionLaneId
|
|
175
176
|
? createHash("sha256").update(sessionLaneId).digest("hex").slice(0, SESSION_LANE_ID_BYTES)
|
|
@@ -192,6 +193,8 @@ export function tryAdmitTurn(sessionLaneId?: string): ActiveTurnLease | null {
|
|
|
192
193
|
}
|
|
193
194
|
const controllers = new Set<AbortController>();
|
|
194
195
|
const attachedLeases = new Set<AdmissionLease>();
|
|
196
|
+
const abortListeners = new Map<AbortSignal, () => void>();
|
|
197
|
+
let cancellationReason: unknown;
|
|
195
198
|
let active = true;
|
|
196
199
|
let transferred = false;
|
|
197
200
|
let nativeMainClaimed = false;
|
|
@@ -200,22 +203,40 @@ export function tryAdmitTurn(sessionLaneId?: string): ActiveTurnLease | null {
|
|
|
200
203
|
if (!active) attachedLease.release();
|
|
201
204
|
else attachedLeases.add(attachedLease);
|
|
202
205
|
},
|
|
206
|
+
bindAbortSignal(signal) {
|
|
207
|
+
if (!active || abortListeners.has(signal)) return () => {};
|
|
208
|
+
const onAbort = () => {
|
|
209
|
+
cancellationReason = signal.reason;
|
|
210
|
+
const boundControllers = [...controllers];
|
|
211
|
+
lease.release();
|
|
212
|
+
for (const controller of boundControllers) controller.abort(cancellationReason);
|
|
213
|
+
};
|
|
214
|
+
abortListeners.set(signal, onAbort);
|
|
215
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
216
|
+
if (signal.aborted) onAbort();
|
|
217
|
+
return () => {
|
|
218
|
+
if (abortListeners.get(signal) !== onAbort) return;
|
|
219
|
+
signal.removeEventListener("abort", onAbort);
|
|
220
|
+
abortListeners.delete(signal);
|
|
221
|
+
};
|
|
222
|
+
},
|
|
203
223
|
bindAbortController(ac) {
|
|
204
224
|
knownTurnControllers.add(ac);
|
|
205
225
|
if (!active) {
|
|
206
|
-
ac.abort(new Error("turn already settled"));
|
|
226
|
+
ac.abort(cancellationReason ?? new Error("turn already settled"));
|
|
207
227
|
return;
|
|
208
228
|
}
|
|
209
229
|
transferred = true;
|
|
210
230
|
controllers.add(ac);
|
|
211
231
|
activeTurns.set(ac, lease);
|
|
232
|
+
lease.bindAbortSignal(ac.signal);
|
|
212
233
|
},
|
|
213
234
|
beginCodexAccountSelection() {
|
|
214
235
|
const mainProfileDraining = nativeMainDrainOwners.size > 0;
|
|
215
|
-
let selectionActive = !mainProfileDraining;
|
|
236
|
+
let selectionActive = active && !mainProfileDraining;
|
|
216
237
|
let released = false;
|
|
217
238
|
if (selectionActive) nativeMainSelections += 1;
|
|
218
|
-
|
|
239
|
+
const selection: CodexAccountSelectionAdmission = {
|
|
219
240
|
mainProfileDraining,
|
|
220
241
|
claimMainProfile() {
|
|
221
242
|
if (released || mainProfileDraining || !active) return false;
|
|
@@ -228,18 +249,23 @@ export function tryAdmitTurn(sessionLaneId?: string): ActiveTurnLease | null {
|
|
|
228
249
|
release() {
|
|
229
250
|
if (released) return;
|
|
230
251
|
released = true;
|
|
252
|
+
attachedLeases.delete(selection);
|
|
231
253
|
if (selectionActive) {
|
|
232
254
|
selectionActive = false;
|
|
233
255
|
nativeMainSelections = Math.max(0, nativeMainSelections - 1);
|
|
234
256
|
}
|
|
235
257
|
},
|
|
236
258
|
};
|
|
259
|
+
lease.attach(selection);
|
|
260
|
+
return selection;
|
|
237
261
|
},
|
|
238
262
|
isTransferred() { return transferred; },
|
|
239
263
|
release() {
|
|
240
264
|
if (!active) return;
|
|
241
265
|
active = false;
|
|
242
266
|
admittedTurns.delete(lease);
|
|
267
|
+
for (const [signal, listener] of abortListeners) signal.removeEventListener("abort", listener);
|
|
268
|
+
abortListeners.clear();
|
|
243
269
|
for (const controller of controllers) {
|
|
244
270
|
if (activeTurns.get(controller) === lease) activeTurns.delete(controller);
|
|
245
271
|
}
|
|
@@ -258,6 +284,7 @@ export function tryAdmitTurn(sessionLaneId?: string): ActiveTurnLease | null {
|
|
|
258
284
|
},
|
|
259
285
|
};
|
|
260
286
|
admittedTurns.add(lease);
|
|
287
|
+
if (clientSignal) lease.bindAbortSignal(clientSignal);
|
|
261
288
|
return lease;
|
|
262
289
|
}
|
|
263
290
|
export function codexAccountSelectionForTurn(
|
|
@@ -3,6 +3,9 @@ import { persistCommittedDesktopGateway } from "../../claude/desktop-gateway-sta
|
|
|
3
3
|
import { captureDesktopAppliedMarker, commitDesktopAppliedMarker } from "../../claude/desktop-applied-marker";
|
|
4
4
|
import { randomUUID } from "node:crypto";
|
|
5
5
|
import { readFileSync } from "node:fs";
|
|
6
|
+
import { dirname } from "node:path";
|
|
7
|
+
import { prepareCodexHome } from "../../codex/prepared-home";
|
|
8
|
+
import { codexHomeIsAbsent } from "../../codex/codex-home-owner";
|
|
6
9
|
import type { CatalogModel } from "../../codex/catalog";
|
|
7
10
|
import { catalogModelSlug, filterCatalogVisibleModels, invalidateCodexModelsCache, nativeContextLimits, nativeModelRows, uniqueCatalogModelsForPublicList } from "../../codex/catalog";
|
|
8
11
|
import { mergeModelPinnedEfforts, modelPinnedEffortsConfigError } from "../../config/provider-validation";
|
|
@@ -400,6 +403,7 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
|
|
|
400
403
|
getAgentsEnabled, getAgentsMaxDepth, getSubagentDeveloperInstructions,
|
|
401
404
|
getMultiAgentModeHintText, probeCodexSupportsModeHint, setAgentsEnabled, setAgentsMaxDepth,
|
|
402
405
|
setSubagentDeveloperInstructions, setMultiAgentModeHintText, MODE_HINT_UNSUPPORTED_ERROR,
|
|
406
|
+
activeCodexConfigPath,
|
|
403
407
|
} = await import("../../codex/features");
|
|
404
408
|
// Probe the capability before any combined-request mutation. The scalar writer
|
|
405
409
|
// repeats this check, but doing it here prevents an earlier flag/mode/agents
|
|
@@ -419,17 +423,37 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
|
|
|
419
423
|
const requestedFlag = wantsFlag
|
|
420
424
|
? body.enabled as boolean
|
|
421
425
|
: modeFlag ?? (wantsKeepNative && hybridPinActive ? false : undefined);
|
|
426
|
+
/*
|
|
427
|
+
* One held section for every config.toml write this PUT makes — the
|
|
428
|
+
* feature transition plus each scalar edit. Individually-locked writers
|
|
429
|
+
* would let a foreign write land between them and leave a half-applied
|
|
430
|
+
* combination on disk; the lock is released before the post-write readers
|
|
431
|
+
* and catalog converge below.
|
|
432
|
+
*/
|
|
433
|
+
const needsCodexConfigWrites = requestedFlag !== undefined || wantsThreads
|
|
434
|
+
|| wantsAgentsEnabled || wantsMaxDepth || wantsSubagentInstructions || wantsModeHintText;
|
|
435
|
+
if (needsCodexConfigWrites && codexHomeIsAbsent(dirname(activeCodexConfigPath()))) {
|
|
436
|
+
return jsonResponse({ error: `config.toml not readable at ${activeCodexConfigPath()}` }, 502);
|
|
437
|
+
}
|
|
438
|
+
const { acquireConfigWriteLock, releaseConfigWriteLock, configWriteLockFailureMessage } = await import("../../codex/config-write-lock");
|
|
439
|
+
const configWriteLock = needsCodexConfigWrites ? await acquireConfigWriteLock(activeCodexConfigPath()) : null;
|
|
440
|
+
if (configWriteLock !== null && !configWriteLock.ok) {
|
|
441
|
+
return jsonResponse({ error: configWriteLockFailureMessage(configWriteLock), retryable: configWriteLock.error === "locked" }, 502);
|
|
442
|
+
}
|
|
443
|
+
const heldWriteLock = configWriteLock !== null && configWriteLock.ok ? configWriteLock.handle : undefined;
|
|
444
|
+
try {
|
|
422
445
|
if (requestedFlag !== undefined || wantsThreads) {
|
|
423
446
|
const targetFlag = requestedFlag ?? isMultiAgentV2Enabled();
|
|
424
447
|
let toggle = deps.toggleCodexMultiAgentV2;
|
|
425
448
|
if (!toggle) {
|
|
426
449
|
const { runCodexFeaturesCommand } = await import("../../cli/v2");
|
|
427
|
-
toggle = (enabled
|
|
450
|
+
toggle = (enabled, env, validate) => runCodexFeaturesCommand(enabled ? "enable" : "disable", "multi_agent_v2", env, validate);
|
|
428
451
|
}
|
|
429
452
|
const result = transitionMultiAgentV2(targetFlag, toggle, {
|
|
430
453
|
...(wantsThreads ? { threadLimit: body.maxConcurrentThreadsPerSession as number } : {}),
|
|
454
|
+
heldConfigWriteLock: heldWriteLock,
|
|
431
455
|
});
|
|
432
|
-
if (!result.ok) return jsonResponse({ error: `multi_agent_v2 transition failed: ${result.error}
|
|
456
|
+
if (!result.ok) return jsonResponse({ error: `multi_agent_v2 transition failed: ${result.error}`, ...(result.retryable === false ? { retryable: false } : {}) }, 502);
|
|
433
457
|
if (result.changed && result.threadLimit !== null) warnings.push(`Thread limit ${result.threadLimit} preserved for ${targetFlag ? "v2" : "v1"}.`);
|
|
434
458
|
}
|
|
435
459
|
if (wantsMode) {
|
|
@@ -465,10 +489,10 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
|
|
|
465
489
|
// asserts this route file contains no direct write primitive, and matches on the
|
|
466
490
|
// symbol name even inside a comment.
|
|
467
491
|
const scalarWrites: Array<{ field: string; run: () => { ok: true; changed: boolean } | { ok: false; error: string } }> = [];
|
|
468
|
-
if (wantsAgentsEnabled) scalarWrites.push({ field: "agentsEnabled", run: () => setAgentsEnabled(body.agentsEnabled as boolean | null) });
|
|
469
|
-
if (wantsMaxDepth) scalarWrites.push({ field: "agentsMaxDepth", run: () => setAgentsMaxDepth(body.agentsMaxDepth as number | null) });
|
|
470
|
-
if (wantsSubagentInstructions) scalarWrites.push({ field: "subagentDeveloperInstructions", run: () => setSubagentDeveloperInstructions(body.subagentDeveloperInstructions as string | null) });
|
|
471
|
-
if (wantsModeHintText) scalarWrites.push({ field: "multiAgentModeHintText", run: () => setMultiAgentModeHintText(body.multiAgentModeHintText as string | null) });
|
|
492
|
+
if (wantsAgentsEnabled) scalarWrites.push({ field: "agentsEnabled", run: () => setAgentsEnabled(body.agentsEnabled as boolean | null, undefined, heldWriteLock) });
|
|
493
|
+
if (wantsMaxDepth) scalarWrites.push({ field: "agentsMaxDepth", run: () => setAgentsMaxDepth(body.agentsMaxDepth as number | null, undefined, heldWriteLock) });
|
|
494
|
+
if (wantsSubagentInstructions) scalarWrites.push({ field: "subagentDeveloperInstructions", run: () => setSubagentDeveloperInstructions(body.subagentDeveloperInstructions as string | null, undefined, heldWriteLock) });
|
|
495
|
+
if (wantsModeHintText) scalarWrites.push({ field: "multiAgentModeHintText", run: () => setMultiAgentModeHintText(body.multiAgentModeHintText as string | null, undefined, heldWriteLock) });
|
|
472
496
|
const landed: string[] = [];
|
|
473
497
|
for (const write of scalarWrites) {
|
|
474
498
|
try {
|
|
@@ -482,6 +506,9 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
|
|
|
482
506
|
return jsonResponse({ error: `writing ${write.field} failed: ${message}${landed.length > 0 ? ` (already applied: ${landed.join(", ")})` : ""}` }, 502);
|
|
483
507
|
}
|
|
484
508
|
}
|
|
509
|
+
} finally {
|
|
510
|
+
if (heldWriteLock) releaseConfigWriteLock(heldWriteLock);
|
|
511
|
+
}
|
|
485
512
|
// Derived from fresh post-write readers (readConfigText is uncached): upstream
|
|
486
513
|
// lets an enabled multi_agent_v2 feature override [agents].enabled = false, so
|
|
487
514
|
// warn rather than reject — silently accepting would imply multi-agent is off.
|
|
@@ -533,35 +560,59 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
|
|
|
533
560
|
}
|
|
534
561
|
const body = parsedBody as { enabled?: unknown };
|
|
535
562
|
if (typeof body.enabled !== "boolean") return jsonResponse({ error: "body.enabled must be a boolean" }, 400);
|
|
536
|
-
const { isDefaultModeRequestUserInputEnabled, DEFAULT_MODE_REQUEST_USER_INPUT_FEATURE_KEY } = await import("../../codex/features");
|
|
563
|
+
const { isDefaultModeRequestUserInputEnabled, DEFAULT_MODE_REQUEST_USER_INPUT_FEATURE_KEY, activeCodexConfigPath } = await import("../../codex/features");
|
|
537
564
|
const before = isDefaultModeRequestUserInputEnabled();
|
|
538
565
|
let toggle = deps.toggleDefaultModeRequestUserInput;
|
|
539
566
|
if (!toggle) {
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
const raw = err.stderr;
|
|
549
|
-
const stderrText = typeof raw === "string"
|
|
550
|
-
? raw.trim()
|
|
551
|
-
: raw instanceof Uint8Array ? new TextDecoder().decode(raw).trim() : "";
|
|
552
|
-
toggleError = stderrText || (err.message ?? String(error));
|
|
553
|
-
}
|
|
554
|
-
const enabled = isDefaultModeRequestUserInputEnabled();
|
|
555
|
-
if (toggleError !== null || enabled !== body.enabled) {
|
|
556
|
-
const reason = toggleError
|
|
557
|
-
?? `postcondition failed - the installed Codex build may not know the ${DEFAULT_MODE_REQUEST_USER_INPUT_FEATURE_KEY} flag yet`;
|
|
558
|
-
return jsonResponse({ error: `default_mode_request_user_input toggle failed: ${reason}` }, 502);
|
|
567
|
+
try {
|
|
568
|
+
const { codexFeaturesInvocation, runCodexFeaturesCommand } = await import("../../cli/v2");
|
|
569
|
+
const action = body.enabled ? "enable" : "disable";
|
|
570
|
+
const invocation = codexFeaturesInvocation(action, DEFAULT_MODE_REQUEST_USER_INPUT_FEATURE_KEY, process.platform, { requireAvailable: true });
|
|
571
|
+
toggle = (_enabled, env, validate) => runCodexFeaturesCommand(action, DEFAULT_MODE_REQUEST_USER_INPUT_FEATURE_KEY, env, validate, invocation);
|
|
572
|
+
} catch (error) {
|
|
573
|
+
return jsonResponse({ error: `default_mode_request_user_input toggle failed: ${error instanceof Error ? error.message : String(error)}` }, 502);
|
|
574
|
+
}
|
|
559
575
|
}
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
576
|
+
// The `codex features` subprocess rewrites config.toml itself — run it
|
|
577
|
+
// under the shared write lock so it cannot interleave with an opencodex
|
|
578
|
+
// scalar edit or injection mid-write on either side.
|
|
579
|
+
const { acquireConfigWriteLock, releaseConfigWriteLock, configWriteLockFailureMessage, runConfigWriteChild, ConfigWriteDestinationChanged } = await import("../../codex/config-write-lock");
|
|
580
|
+
const configPath = activeCodexConfigPath();
|
|
581
|
+
// Preserve native home creation only after executable resolution succeeds.
|
|
582
|
+
prepareCodexHome(dirname(configPath));
|
|
583
|
+
{
|
|
584
|
+
const configLock = await acquireConfigWriteLock(configPath);
|
|
585
|
+
if (!configLock.ok) {
|
|
586
|
+
return jsonResponse({ error: configWriteLockFailureMessage(configLock), retryable: configLock.error === "locked" }, 502);
|
|
587
|
+
}
|
|
588
|
+
let toggleError: string | null = null;
|
|
589
|
+
let destinationChanged = false;
|
|
590
|
+
try {
|
|
591
|
+
runConfigWriteChild(configPath, configLock.handle, (env, validate) => toggle!(body.enabled as boolean, env, validate));
|
|
592
|
+
} catch (error) {
|
|
593
|
+
destinationChanged = error instanceof ConfigWriteDestinationChanged;
|
|
594
|
+
const err = error as { stderr?: unknown; message?: string };
|
|
595
|
+
const raw = err.stderr;
|
|
596
|
+
const stderrText = typeof raw === "string"
|
|
597
|
+
? raw.trim()
|
|
598
|
+
: raw instanceof Uint8Array ? new TextDecoder().decode(raw).trim() : "";
|
|
599
|
+
toggleError = stderrText || (err.message ?? String(error));
|
|
600
|
+
} finally {
|
|
601
|
+
releaseConfigWriteLock(configLock.handle);
|
|
602
|
+
}
|
|
603
|
+
if (destinationChanged) return jsonResponse({ error: toggleError, retryable: false }, 502);
|
|
604
|
+
const enabled = isDefaultModeRequestUserInputEnabled();
|
|
605
|
+
if (toggleError !== null || enabled !== body.enabled) {
|
|
606
|
+
const reason = toggleError
|
|
607
|
+
?? `postcondition failed - the installed Codex build may not know the ${DEFAULT_MODE_REQUEST_USER_INPUT_FEATURE_KEY} flag yet`;
|
|
608
|
+
return jsonResponse({ error: `default_mode_request_user_input toggle failed: ${reason}` }, 502);
|
|
609
|
+
}
|
|
610
|
+
const warnings: string[] = [];
|
|
611
|
+
if (enabled !== before) {
|
|
612
|
+
warnings.push("Applies to new sessions; restart the Codex app or wait out its picker cache to see the change.");
|
|
613
|
+
}
|
|
614
|
+
return jsonResponse({ ok: true, enabled, changed: enabled !== before, warnings });
|
|
563
615
|
}
|
|
564
|
-
return jsonResponse({ ok: true, enabled, changed: enabled !== before, warnings });
|
|
565
616
|
}
|
|
566
617
|
|
|
567
618
|
// Subagent prompt injection model: single native or routed model whose info is
|
|
@@ -1421,6 +1472,7 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
|
|
|
1421
1472
|
alwaysEnableEffort: config.claudeCode?.alwaysEnableEffort === true,
|
|
1422
1473
|
autoContext: config.claudeCode?.autoContext !== false,
|
|
1423
1474
|
autoCompactWindow: config.claudeCode?.autoCompactWindow ?? null,
|
|
1475
|
+
contextAccounting: config.claudeCode?.contextAccounting === "200k" ? "200k" : "1m",
|
|
1424
1476
|
blockedSkills: config.claudeCode?.blockedSkills ?? null,
|
|
1425
1477
|
injectAgents: config.claudeCode?.injectAgents !== false,
|
|
1426
1478
|
sidecarPools: {
|
|
@@ -1457,7 +1509,7 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
|
|
|
1457
1509
|
return prototype === Object.prototype || prototype === null;
|
|
1458
1510
|
};
|
|
1459
1511
|
if (!isPlainObject(parsedBody)) return jsonResponse({ error: "body must be an object" }, 400);
|
|
1460
|
-
const body = parsedBody as { enabled?: unknown; cliFirstParty?: unknown; authMode?: unknown; model?: unknown; smallFastModel?: unknown; modelMap?: unknown; classifierModel?: unknown; classifierFallbacks?: unknown; systemEnv?: unknown; fastMode?: unknown; maxContextTokens?: unknown; alwaysEnableEffort?: unknown; tierModels?: unknown; autoContext?: unknown; autoCompactWindow?: unknown; blockedSkills?: unknown; injectAgents?: unknown; webSearchSidecar?: unknown; visionSidecar?: unknown };
|
|
1512
|
+
const body = parsedBody as { enabled?: unknown; cliFirstParty?: unknown; authMode?: unknown; model?: unknown; smallFastModel?: unknown; modelMap?: unknown; classifierModel?: unknown; classifierFallbacks?: unknown; systemEnv?: unknown; fastMode?: unknown; maxContextTokens?: unknown; alwaysEnableEffort?: unknown; tierModels?: unknown; autoContext?: unknown; autoCompactWindow?: unknown; contextAccounting?: unknown; blockedSkills?: unknown; injectAgents?: unknown; webSearchSidecar?: unknown; visionSidecar?: unknown };
|
|
1461
1513
|
if (body.cliFirstParty !== undefined && typeof body.cliFirstParty !== "boolean")
|
|
1462
1514
|
return jsonResponse({ error: "cliFirstParty must be a boolean" }, 400);
|
|
1463
1515
|
if (body.cliFirstParty !== undefined && Object.keys(body).length !== 1)
|
|
@@ -1727,6 +1779,15 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
|
|
|
1727
1779
|
if (body.autoContext) delete next.autoContext;
|
|
1728
1780
|
else next.autoContext = false;
|
|
1729
1781
|
}
|
|
1782
|
+
if (body.contextAccounting !== undefined) {
|
|
1783
|
+
// Sparse like autoContext (devlog/_plan/261009_claude_1m_default/030): "1m" is the default
|
|
1784
|
+
// and drops the key; "200k" is the only stored value.
|
|
1785
|
+
if (body.contextAccounting !== "1m" && body.contextAccounting !== "200k") {
|
|
1786
|
+
return jsonResponse({ error: "contextAccounting must be \"1m\" or \"200k\"" }, 400);
|
|
1787
|
+
}
|
|
1788
|
+
if (body.contextAccounting === "200k") next.contextAccounting = "200k";
|
|
1789
|
+
else delete next.contextAccounting;
|
|
1790
|
+
}
|
|
1730
1791
|
if (body.injectAgents !== undefined) {
|
|
1731
1792
|
// Default-on boolean (devlog 260712 070): true = drop the key, false = store.
|
|
1732
1793
|
if (typeof body.injectAgents !== "boolean") return jsonResponse({ error: "injectAgents must be a boolean" }, 400);
|
|
@@ -1841,7 +1902,7 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
|
|
|
1841
1902
|
// levers feed the same injection, so a changed or cleared slot must not linger in
|
|
1842
1903
|
// launchd until the next restart.
|
|
1843
1904
|
const systemEnvInputs = ["systemEnv", "authMode", "model", "smallFastModel", "tierModels",
|
|
1844
|
-
"maxContextTokens", "alwaysEnableEffort", "autoContext", "autoCompactWindow"] as const;
|
|
1905
|
+
"maxContextTokens", "alwaysEnableEffort", "autoContext", "autoCompactWindow", "contextAccounting"] as const;
|
|
1845
1906
|
if (systemEnvInputs.some(field => body[field] !== undefined)) {
|
|
1846
1907
|
try {
|
|
1847
1908
|
await applySystemEnvToggle(config, config.port);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Per-role model for omo (Codex / LazyCodex): `$CODEX_HOME/agents/<role>.toml` plus LazyCodex's
|
|
3
|
-
* `codex.agents.<role
|
|
3
|
+
* `"[codex]".agents.<role>` model and reasoning mirror in omo.jsonc. Both halves exist only when LazyCodex is
|
|
4
4
|
* detected; Pi-based and OpenCode-based omo are never read here.
|
|
5
5
|
*
|
|
6
6
|
* Loaded on demand from `src/server/management-api.ts`, like the quota-reset handler, so a
|
|
@@ -40,6 +40,7 @@ export async function handleCodexAgentRoleRoutes(ctx: ManagementContext): Promis
|
|
|
40
40
|
omoJsonc: { state: omoState.state },
|
|
41
41
|
roles: roles.listCodexAgentRoleModels(codexHome).map(entry => ({
|
|
42
42
|
...entry,
|
|
43
|
+
effort: roles.readCodexAgentRoleEffort(entry.role, codexHome),
|
|
43
44
|
omoJsoncModel: omoModels[entry.role] ?? null,
|
|
44
45
|
})),
|
|
45
46
|
}, 200, req, config);
|
|
@@ -114,7 +115,8 @@ export async function handleCodexAgentRoleRoutes(ctx: ManagementContext): Promis
|
|
|
114
115
|
// The role TOML is what Codex obeys, so its write stands even when the omo mirror cannot follow.
|
|
115
116
|
let omoStatus: ReturnType<typeof omo.writeOmoRoleModel> | "write_failed";
|
|
116
117
|
try {
|
|
117
|
-
|
|
118
|
+
const reasoning = effort === undefined ? undefined : omo.omoReasoningFor(effort);
|
|
119
|
+
omoStatus = omo.writeOmoRoleModel(role, model, omo.omoJsoncPath(), reasoning);
|
|
118
120
|
} catch {
|
|
119
121
|
omoStatus = "write_failed";
|
|
120
122
|
}
|
|
@@ -58,6 +58,10 @@ async function modelCandidates(slugs: readonly string[], models: readonly Catalo
|
|
|
58
58
|
]);
|
|
59
59
|
const nativeSlugs = catalog.listCatalogNativeSlugs();
|
|
60
60
|
const routed = new Map(models.map(model => [catalog.catalogModelSlug(model), model]));
|
|
61
|
+
// A routed model that declares no levels still gets the ladder Codex shows for it, the same one
|
|
62
|
+
// the Models page lists, so its proposal carries an effort instead of none. An explicit empty
|
|
63
|
+
// ladder means the model takes no effort control, and the written catalog lists none for it.
|
|
64
|
+
const written = catalog.catalogModelLadders(slugs);
|
|
61
65
|
const unitPrice = (provider: string, modelId: string): number | null => {
|
|
62
66
|
const cost = resolveMatchedPrice(provider, modelId)?.cost4;
|
|
63
67
|
return cost ? cost.input + cost.output : null;
|
|
@@ -65,11 +69,18 @@ async function modelCandidates(slugs: readonly string[], models: readonly Catalo
|
|
|
65
69
|
return slugs.map(slug => {
|
|
66
70
|
const row = routed.get(slug);
|
|
67
71
|
if (row) {
|
|
72
|
+
const ladder = written.get(slug);
|
|
73
|
+
const efforts = Array.isArray(row.reasoningEfforts) ? row.reasoningEfforts : ladder?.efforts ?? [];
|
|
74
|
+
// A row without its own default keeps the one Codex shows, when that rung is on the ladder.
|
|
75
|
+
const shownDefault = ladder?.defaultEffort !== undefined && efforts.includes(ladder.defaultEffort)
|
|
76
|
+
? ladder.defaultEffort
|
|
77
|
+
: undefined;
|
|
78
|
+
const defaultEffort = row.defaultReasoningEffort ?? shownDefault;
|
|
68
79
|
return {
|
|
69
80
|
model: slug,
|
|
70
81
|
unitPrice: unitPrice(row.provider, row.id),
|
|
71
|
-
efforts
|
|
72
|
-
...(
|
|
82
|
+
efforts,
|
|
83
|
+
...(defaultEffort ? { defaultEffort } : {}),
|
|
73
84
|
};
|
|
74
85
|
}
|
|
75
86
|
if (nativeSlugs.includes(slug)) {
|