@bitkyc08/opencodex 2.82.0 → 2.84.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/gui/dist/assets/App-CZtqiGcw.js +56 -0
- package/gui/dist/assets/Tray-C9TGtwad.js +1 -0
- package/gui/dist/assets/{index-BAbcj51J.css → index-BB6uK6tU.css} +1 -1
- package/gui/dist/assets/index-CTm4l-HQ.js +94 -0
- package/gui/dist/assets/{quota-summary-DTBC8n7-.js → quota-summary-BbEeRfPY.js} +1 -1
- package/gui/dist/index.html +2 -2
- package/package.json +2 -2
- package/src/adapters/coding-agent/protocol.ts +15 -8
- package/src/adapters/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 +47 -15
- package/src/adapters/openai-chat/messages.ts +8 -4
- package/src/adapters/openai-responses/empty-assistant.ts +32 -0
- package/src/adapters/openai-responses/passthrough.ts +5 -0
- 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/request-strips.ts +10 -2
- package/src/adapters/qoder/adapter.ts +16 -14
- package/src/bridge/errors.ts +26 -1
- package/src/bridge.ts +1 -1
- package/src/claude/context-windows.ts +57 -4
- package/src/claude/desktop-picker.ts +1 -1
- package/src/claude/intercept/connect-proxy.ts +10 -3
- package/src/claude/intercept/picker-ca-persistence.ts +3 -1
- package/src/claude/intercept/picker-listener.ts +49 -1
- package/src/claude/intercept/settings-no-proxy.ts +146 -0
- package/src/claude/intercept/settings.ts +101 -9
- package/src/cli/account-api.ts +65 -0
- package/src/cli/account-auth.ts +44 -12
- package/src/cli/account.ts +41 -5
- package/src/cli/capabilities-access-remote.ts +8 -0
- package/src/cli/capabilities-base.ts +13 -1
- package/src/cli/catalog-command-result.ts +2 -1
- package/src/cli/claude.ts +15 -6
- package/src/cli/config-command.ts +8 -2
- package/src/cli/debug.ts +8 -0
- package/src/cli/dispatch.ts +16 -3
- package/src/cli/export-command.ts +14 -1
- package/src/cli/hub-disable.ts +99 -0
- package/src/cli/hub.ts +4 -2
- package/src/cli/index.ts +13 -4
- package/src/cli/link-child.ts +104 -0
- package/src/cli/link-enrollment.ts +96 -0
- package/src/cli/link.ts +39 -24
- package/src/cli/observe-stream.ts +6 -1
- package/src/cli/observe.ts +10 -2
- package/src/cli/provider.ts +12 -0
- package/src/cli/registry.ts +5 -4
- package/src/cli/resolve.ts +1 -1
- package/src/cli/runtime-api.ts +27 -2
- package/src/cli/status.ts +104 -20
- package/src/client/connect.ts +39 -4
- package/src/client/hub-client.ts +14 -7
- package/src/client/hub-state.ts +25 -14
- package/src/client/link-home.ts +248 -0
- package/src/client/link-management-relay.ts +61 -0
- package/src/client/link-relay.ts +6 -1
- package/src/client/link-status.ts +14 -0
- package/src/client/machine-api.ts +14 -7
- package/src/client/machine-listener.ts +29 -4
- package/src/clients/config-export/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 +47 -17
- package/src/clients/omo-role-models.ts +6 -3
- package/src/codex/agent-role-models.ts +26 -8
- package/src/codex/auth-api/account-list.ts +17 -2
- package/src/codex/auth-api/account-selection.ts +17 -7
- package/src/codex/auth-api/pool-mode-gate.ts +4 -5
- package/src/codex/auth-api/routes.ts +5 -1
- package/src/codex/auth-context.ts +8 -3
- package/src/codex/autostart-health.ts +3 -1
- package/src/codex/catalog/bundled.ts +5 -1
- package/src/codex/catalog/effort.ts +2 -2
- package/src/codex/catalog/metadata.ts +4 -2
- package/src/codex/catalog/native-models.ts +17 -3
- package/src/codex/catalog/provider-models.ts +25 -0
- 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/main-account.ts +6 -7
- package/src/codex/native-profile-startup.ts +4 -1
- package/src/codex/prompt-journal.ts +20 -2
- package/src/codex/prompt-layers/encoding.ts +49 -0
- package/src/codex/prompt-layers/import-source.ts +30 -0
- package/src/codex/prompt-layers/toml-edit.ts +269 -37
- package/src/codex/prompt-layers/toml-read.ts +52 -4
- package/src/codex/prompt-layers.ts +383 -20
- package/src/codex/prompt-lock-claim.ts +13 -3
- package/src/codex/prompt-lock-evidence.ts +3 -3
- package/src/codex/prompt-lock-owner.ts +14 -6
- package/src/codex/prompt-text-probe.ts +34 -5
- package/src/codex/quota.ts +13 -6
- package/src/codex/role-auto-assign.ts +2 -5
- package/src/codex/routing/active-account.ts +21 -0
- package/src/codex/routing.ts +1 -1
- package/src/codex/runtime.ts +196 -8
- package/src/codex/shim-overlay.ts +8 -4
- package/src/codex/shim.ts +1 -1
- package/src/combos/failover.ts +3 -0
- package/src/config/api-key-reconcile.ts +34 -0
- package/src/config/diagnostics.ts +16 -0
- package/src/config/feature-flags.ts +5 -0
- package/src/config/live-reconcile.ts +43 -29
- package/src/config/live-topology.ts +29 -0
- package/src/config/load-degrade.ts +2 -1
- package/src/config/paths.ts +3 -0
- package/src/config/salvage.ts +3 -1
- package/src/config/schema/config-schema.ts +2 -0
- package/src/generated/compatibility-version.json +373 -217
- package/src/grok/grpc-web.ts +23 -1
- package/src/grok/inject.ts +245 -221
- package/src/grok/reset-coupon-ledger.ts +293 -57
- package/src/grok/reset-coupons.ts +15 -6
- package/src/integrations/cursor-detect.ts +1 -1
- package/src/integrations/mutation-plan.ts +13 -7
- package/src/integrations/registry.ts +45 -11
- package/src/integrations/state.ts +19 -7
- package/src/integrations/target.ts +42 -5
- package/src/integrations/writer.ts +14 -6
- package/src/lib/app-owned-memory-stores.ts +4 -0
- package/src/lib/app-owned-memory.ts +11 -0
- package/src/lib/errors.ts +25 -0
- package/src/lib/json-byte-size.ts +31 -0
- package/src/lib/optional-shutdown-hooks.ts +35 -0
- package/src/lib/outbound-credential-registry.ts +210 -0
- package/src/lib/passive-read.ts +35 -0
- package/src/lib/redact.ts +2 -2
- package/src/lib/shadow-call.ts +44 -5
- package/src/link/codex-settings.ts +155 -0
- package/src/link/management-routes.ts +89 -0
- package/src/link/remote-probe.ts +38 -0
- package/src/link/ssh-argv.ts +26 -5
- package/src/link/ssh-runner.ts +58 -27
- package/src/link/status-projection.ts +22 -4
- package/src/link/supervisor.ts +144 -49
- package/src/link/tunnel-state.ts +11 -4
- package/src/oauth/anthropic-continuity.ts +4 -3
- package/src/oauth/anthropic-identity.ts +13 -2
- package/src/oauth/anthropic-model-quota.ts +13 -1
- package/src/oauth/anthropic-routing.ts +230 -23
- package/src/oauth/anthropic.ts +18 -4
- package/src/oauth/google-antigravity.ts +24 -7
- package/src/oauth/index.ts +25 -16
- package/src/oauth/kiro-account-identity.ts +106 -0
- package/src/oauth/kiro.ts +48 -5
- package/src/oauth/meta-muse-device.ts +1 -1
- package/src/oauth/plan.ts +29 -0
- package/src/oauth/store.ts +40 -3
- package/src/oauth/types.ts +3 -1
- package/src/plugins/loader.ts +11 -0
- package/src/providers/anthropic-billing-ownership.ts +74 -0
- package/src/providers/anthropic-billing-recovery.ts +31 -0
- package/src/providers/anthropic-billing-refusal.ts +55 -0
- package/src/providers/anthropic-fast.ts +1 -1
- package/src/providers/anthropic-reset-admission.ts +62 -0
- package/src/providers/anthropic-reset-grant-ledger.ts +29 -7
- package/src/providers/api-key-resolve.ts +17 -2
- 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/key-failover.ts +52 -5
- package/src/providers/model-presets.ts +1 -1
- package/src/providers/model-rename-startup.ts +5 -3
- package/src/providers/muse-subscription-usage.ts +1 -1
- package/src/providers/quota/account-cache.ts +11 -0
- package/src/providers/quota/anthropic-account-quota.ts +4 -2
- package/src/providers/quota/report-cache.ts +14 -0
- package/src/providers/quota/vendor-probes-oauth.ts +3 -3
- package/src/providers/quota.ts +4 -1
- package/src/providers/registry/entries-core.ts +20 -20
- package/src/providers/registry/entries-extended.ts +4 -1
- package/src/providers/registry/model-seeds.ts +20 -3
- package/src/providers/request-pacing.ts +9 -0
- package/src/providers/stale-no-vision-seed-migration.ts +72 -0
- package/src/providers/xai-fast-model.ts +1 -1
- package/src/responses/compaction.ts +14 -0
- package/src/responses/parser.ts +7 -1
- package/src/server/auth-cors.ts +42 -0
- package/src/server/configured-credentials.ts +194 -0
- package/src/server/inbound-body-admission.ts +24 -7
- package/src/server/index/link-listener.ts +30 -17
- package/src/server/index/link-management.ts +58 -0
- package/src/server/index/optional-listeners.ts +5 -1
- package/src/server/index/serve-options.ts +21 -2
- package/src/server/index.ts +1 -1
- package/src/server/lifecycle.ts +33 -5
- package/src/server/management/agent-settings-routes.ts +28 -10
- package/src/server/management/anthropic-reset-grant-routes.ts +17 -1
- package/src/server/management/codex-agent-role-routes.ts +19 -6
- package/src/server/management/codex-prompt-routes.ts +136 -5
- package/src/server/management/codex-role-auto-assign.ts +1 -1
- package/src/server/management/grok-coupon-routes.ts +333 -57
- package/src/server/management/link-enrollment.ts +192 -0
- package/src/server/management/link-route-state.ts +185 -0
- package/src/server/management/link-routes.ts +92 -390
- package/src/server/management/model-rows.ts +3 -1
- package/src/server/management/provider-overwrite-carry.ts +10 -0
- package/src/server/management/provider-routes.ts +1 -0
- package/src/server/management/route-registry.ts +7 -5
- package/src/server/management/shared.ts +3 -0
- package/src/server/management-api.ts +3 -1
- package/src/server/messages-native.ts +68 -42
- package/src/server/relay-eager.ts +4 -0
- package/src/server/relay.ts +18 -7
- package/src/server/request-log-failure-attribution.ts +3 -0
- package/src/server/request-log-filter.ts +6 -1
- package/src/server/request-log-terminal-status.ts +243 -0
- package/src/server/request-log.ts +15 -118
- package/src/server/responses/adapter-continuation.ts +9 -1
- package/src/server/responses/adapter-dispatch.ts +293 -81
- package/src/server/responses/code-mode-collab-surface.ts +205 -0
- package/src/server/responses/codex-ws-exchange.ts +147 -6
- package/src/server/responses/codex-ws-wire.ts +203 -0
- package/src/server/responses/collaboration.ts +36 -7
- package/src/server/responses/compact.ts +129 -50
- package/src/server/responses/context-overflow.ts +13 -1
- package/src/server/responses/core-codex-account.ts +105 -1
- package/src/server/responses/core-combo-failure.ts +14 -0
- package/src/server/responses/core-combo.ts +7 -2
- package/src/server/responses/core-errors.ts +29 -1
- package/src/server/responses/core-opaque-recovery.ts +5 -5
- package/src/server/responses/core-options.ts +2 -0
- package/src/server/responses/core.ts +6 -6
- package/src/server/responses/deferred-collab-surface.ts +158 -0
- package/src/server/responses/deferred-send-reporter.ts +25 -0
- package/src/server/responses/empty-completion-guard.ts +18 -2
- package/src/server/responses/fetch-helpers.ts +12 -1
- package/src/server/responses/jev-model-invoke.ts +1 -1
- package/src/server/responses/passthrough-delivery.ts +4 -1
- package/src/server/responses/passthrough-dispatch.ts +130 -64
- package/src/server/responses/passthrough-execution.ts +3 -3
- package/src/server/responses/request-prepare.ts +6 -4
- package/src/server/responses/request-transport.ts +131 -13
- package/src/server/responses/run-turn-execution.ts +13 -5
- package/src/server/responses/sidecar-execution.ts +16 -2
- package/src/server/responses/spend-storage-error.ts +33 -0
- package/src/server/responses/ws-upstream.ts +9 -1
- package/src/server/startup-action-control.ts +22 -2
- package/src/server/startup-health-cache.ts +4 -0
- package/src/server/system-env-shell.ts +7 -2
- package/src/server/system-env.ts +11 -1
- package/src/service/cli.ts +5 -1
- package/src/service/desktop-startup.ts +147 -34
- package/src/service/desktop-supervision.d.mts +27 -1
- package/src/service/desktop-supervision.mjs +111 -5
- package/src/service/health-sentinel-launch.ts +176 -0
- package/src/service/health-sentinel-main.ts +447 -0
- package/src/service/health-sentinel-policy.ts +241 -0
- package/src/service/health-sentinel-probe.ts +54 -0
- package/src/service/health-sentinel-windows.ts +87 -0
- package/src/service/linux-linger.ts +20 -0
- package/src/types/config.ts +5 -0
- package/src/types/request.ts +5 -0
- package/src/usage/expected-prices.ts +5 -1
- package/src/usage/log.ts +19 -1
- package/gui/dist/assets/App-Ds_qL7o8.js +0 -54
- package/gui/dist/assets/Tray-BqHF5Trd.js +0 -1
- package/gui/dist/assets/index-D3FBCNiP.js +0 -94
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ensureManagementClaudeIntercept, interceptStartRefusal, interceptStatus } from "./claude-intercept-routes";
|
|
2
2
|
import { persistCommittedDesktopGateway } from "../../claude/desktop-gateway-state";
|
|
3
3
|
import { captureDesktopAppliedMarker, commitDesktopAppliedMarker } from "../../claude/desktop-applied-marker";
|
|
4
|
+
import { isPassiveRead } from "../../lib/passive-read";
|
|
4
5
|
import { randomUUID } from "node:crypto";
|
|
5
6
|
import { readFileSync } from "node:fs";
|
|
6
7
|
import { dirname } from "node:path";
|
|
@@ -230,6 +231,32 @@ async function injectionModelOptions(config: OcxConfig, models: readonly Catalog
|
|
|
230
231
|
return [...nativeModels, ...routedModels];
|
|
231
232
|
}
|
|
232
233
|
|
|
234
|
+
/**
|
|
235
|
+
* The GET /api/injection-model body. `routed` is the discovered routed catalog, or `null` in a
|
|
236
|
+
* passive read (a Remote Link Child's, #6959). That body lists the native
|
|
237
|
+
* options and the current pick, which is all a read-only Child shows.
|
|
238
|
+
*/
|
|
239
|
+
export async function injectionModelView(config: OcxConfig, routed: readonly CatalogModel[] | null) {
|
|
240
|
+
const { CODEX_REASONING_LEVELS } = await import("../../reasoning-effort");
|
|
241
|
+
const available = await injectionModelOptions(config, routed ?? []);
|
|
242
|
+
const picked = config.injectionModel;
|
|
243
|
+
if (routed === null && picked && !available.some(option => option.namespaced === picked)) {
|
|
244
|
+
const slash = picked.indexOf("/");
|
|
245
|
+
available.push(slash > 0
|
|
246
|
+
? { provider: picked.slice(0, slash), model: picked.slice(slash + 1), namespaced: picked }
|
|
247
|
+
: { provider: "openai", model: picked, namespaced: picked });
|
|
248
|
+
}
|
|
249
|
+
return {
|
|
250
|
+
multiAgentGuidanceEnabled: multiAgentGuidanceEnabled(config),
|
|
251
|
+
syncCodexSubagentDefaults: subagentDefaultSyncEffective(config),
|
|
252
|
+
model: config.injectionModel ?? null,
|
|
253
|
+
effort: config.injectionEffort ?? null,
|
|
254
|
+
prompt: config.injectionPrompt ?? null,
|
|
255
|
+
efforts: CODEX_REASONING_LEVELS.map(l => l.effort),
|
|
256
|
+
available,
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
|
|
233
260
|
export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise<Response | null> {
|
|
234
261
|
const { req, url, config, deps, convergeCodexCatalog, syncClaudeAgentDefsBestEffort } = ctx;
|
|
235
262
|
|
|
@@ -620,16 +647,7 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
|
|
|
620
647
|
// effort the prompt tells the agent to pass to spawn_agent. GET returns the current
|
|
621
648
|
// picks + available models/efforts; PUT sets or clears them.
|
|
622
649
|
if (url.pathname === "/api/injection-model" && req.method === "GET") {
|
|
623
|
-
|
|
624
|
-
return jsonResponse({
|
|
625
|
-
multiAgentGuidanceEnabled: multiAgentGuidanceEnabled(config),
|
|
626
|
-
syncCodexSubagentDefaults: subagentDefaultSyncEffective(config),
|
|
627
|
-
model: config.injectionModel ?? null,
|
|
628
|
-
effort: config.injectionEffort ?? null,
|
|
629
|
-
prompt: config.injectionPrompt ?? null,
|
|
630
|
-
efforts: CODEX_REASONING_LEVELS.map(l => l.effort),
|
|
631
|
-
available: await injectionModelOptions(config, await fetchAllModels(config)),
|
|
632
|
-
});
|
|
650
|
+
return jsonResponse(await injectionModelView(config, isPassiveRead() ? null : await fetchAllModels(config)));
|
|
633
651
|
}
|
|
634
652
|
// Read-only: sizes the described delegated work and proposes a model and effort from the
|
|
635
653
|
// same options GET offers. The page applies an accepted proposal through the PUT below.
|
|
@@ -19,6 +19,7 @@ import { captureAnthropicPhysicalSendOwnership, anthropicPhysicalSendOwnershipIs
|
|
|
19
19
|
import { anthropicCooldownRecoveryFor } from "../../providers/quota/anthropic-cooldown-recovery";
|
|
20
20
|
import { captureProviderAccountQuotaEpoch } from "../../providers/quota/account-cache";
|
|
21
21
|
import { captureOAuthAccountSelection, getAccountSet, getAccountCredentialWithStatus, listAccounts } from "../../oauth/store";
|
|
22
|
+
import { admitAnthropicAccountAfterReset, type AnthropicResetAdmissionEvidence } from "../../providers/anthropic-reset-admission";
|
|
22
23
|
import {
|
|
23
24
|
ANTHROPIC_RESET_GRANT_ID_RE,
|
|
24
25
|
AnthropicResetGrantError,
|
|
@@ -74,6 +75,8 @@ export interface AnthropicResetGrantRouteDeps {
|
|
|
74
75
|
listAccountIds: () => string[];
|
|
75
76
|
activeAccountId: () => string | null;
|
|
76
77
|
accessTokenFor: (accountId: string) => Promise<string>;
|
|
78
|
+
/** Lift local admission after a journaled reset, bounded by its journal evidence. Tests omit it. */
|
|
79
|
+
admitReset?: (accountId: string, evidence: AnthropicResetAdmissionEvidence) => Promise<void>;
|
|
77
80
|
}
|
|
78
81
|
|
|
79
82
|
function defaultDepsFor(ctx: ManagementContext, provider: AnthropicInstanceId): AnthropicResetGrantRouteDeps | null {
|
|
@@ -109,6 +112,7 @@ function defaultDepsFor(ctx: ManagementContext, provider: AnthropicInstanceId):
|
|
|
109
112
|
if (!targetCurrent() || !owner) throw new Error("reset owner changed");
|
|
110
113
|
return snapshot.accessToken;
|
|
111
114
|
},
|
|
115
|
+
admitReset: (accountId, evidence) => admitAnthropicAccountAfterReset(provider, accountId, evidence),
|
|
112
116
|
};
|
|
113
117
|
}
|
|
114
118
|
|
|
@@ -209,6 +213,16 @@ async function handleConsume(ctx: ManagementContext, injected?: AnthropicResetGr
|
|
|
209
213
|
}
|
|
210
214
|
if (deps.isCurrent?.() === false) return scopedFail(401, "auth_failed");
|
|
211
215
|
const ledger = { journalPath: deps.journalPath, now: deps.now?.() };
|
|
216
|
+
// Retry-after cooldowns survive a usage probe. A journaled reset, including a
|
|
217
|
+
// replay after the process died before admission, has to lift this account,
|
|
218
|
+
// but only refusals recorded before the claim was journaled and only for the
|
|
219
|
+
// windows the journal says were cleared. A failed reread must not turn a
|
|
220
|
+
// spent grant into an error.
|
|
221
|
+
const admitJournaledReset = async (code: string, evidence: AnthropicResetAdmissionEvidence): Promise<void> => {
|
|
222
|
+
if (code !== "reset" || deps.isCurrent?.() === false) return;
|
|
223
|
+
try { await deps.admitReset?.(accountId, evidence); }
|
|
224
|
+
catch { /* The grant is already spent. */ }
|
|
225
|
+
};
|
|
212
226
|
|
|
213
227
|
// A new operation must pass the spend gate on a fresh read. A same-id retry of
|
|
214
228
|
// an open record skips it: that attempt was already gated, and upstream
|
|
@@ -242,6 +256,7 @@ async function handleConsume(ctx: ManagementContext, injected?: AnthropicResetGr
|
|
|
242
256
|
}
|
|
243
257
|
switch (begin.kind) {
|
|
244
258
|
case "replay":
|
|
259
|
+
await admitJournaledReset(begin.code, { claimedAt: begin.claimedAt, cleared: begin.cleared });
|
|
245
260
|
return jsonResponse(
|
|
246
261
|
{ provider, code: begin.code, replayed: true, resetsLeft: begin.resetsLeft, accountId, grantId, operationId },
|
|
247
262
|
200, ctx.req, ctx.config,
|
|
@@ -268,7 +283,7 @@ async function handleConsume(ctx: ManagementContext, injected?: AnthropicResetGr
|
|
|
268
283
|
let settled;
|
|
269
284
|
try {
|
|
270
285
|
settled = settleAnthropicResetOperation(
|
|
271
|
-
{ operationId, code: answer.code, resetsLeft: answer.resetsLeft },
|
|
286
|
+
{ operationId, code: answer.code, resetsLeft: answer.resetsLeft, cleared: answer.cleared },
|
|
272
287
|
{ journalPath: deps.journalPath, now: deps.now?.() },
|
|
273
288
|
);
|
|
274
289
|
} catch {
|
|
@@ -278,6 +293,7 @@ async function handleConsume(ctx: ManagementContext, injected?: AnthropicResetGr
|
|
|
278
293
|
// Report what the journal holds. An overlapping same-id attempt may have
|
|
279
294
|
// settled first; its answer is the canonical one.
|
|
280
295
|
const replayed = settled.code !== answer.code || settled.resetsLeft !== answer.resetsLeft;
|
|
296
|
+
await admitJournaledReset(settled.code, { claimedAt: settled.claimedAt, cleared: settled.cleared });
|
|
281
297
|
return jsonResponse(
|
|
282
298
|
{
|
|
283
299
|
provider,
|
|
@@ -96,12 +96,18 @@ export async function handleCodexAgentRoleRoutes(ctx: ManagementContext): Promis
|
|
|
96
96
|
return jsonResponse({ error: "invalid JSON body" }, 400, req, config);
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
-
|
|
100
|
-
|
|
99
|
+
// An omitted model leaves the role's model pin (or its inheritance) alone, an omitted effort
|
|
100
|
+
// keeps the effort, and an effort of null clears it. A body with neither changes nothing, which
|
|
101
|
+
// is a client mistake rather than a save, so it is refused before any role file is read.
|
|
102
|
+
if (body?.model === undefined && body?.effort === undefined) {
|
|
103
|
+
return jsonResponse({ error: "send a model, an effort, or both", code: "empty_update" }, 400, req, config);
|
|
104
|
+
}
|
|
105
|
+
let model: string | undefined;
|
|
106
|
+
let effort: string | null | undefined;
|
|
101
107
|
let toml: { status: "written" | "unchanged" };
|
|
102
108
|
try {
|
|
103
|
-
model = roles.validateAgentRoleModel(body
|
|
104
|
-
effort = body?.effort === undefined
|
|
109
|
+
model = body?.model === undefined ? undefined : roles.validateAgentRoleModel(body.model);
|
|
110
|
+
effort = body?.effort === undefined ? undefined : body.effort === null ? null : roles.validateAgentRoleEffort(body.effort);
|
|
105
111
|
toml = roles.writeCodexAgentRoleModel(role, model, codexHome, effort);
|
|
106
112
|
} catch (error) {
|
|
107
113
|
if (error instanceof roles.AgentRoleModelError) {
|
|
@@ -115,10 +121,17 @@ export async function handleCodexAgentRoleRoutes(ctx: ManagementContext): Promis
|
|
|
115
121
|
// The role TOML is what Codex obeys, so its write stands even when the omo mirror cannot follow.
|
|
116
122
|
let omoStatus: ReturnType<typeof omo.writeOmoRoleModel> | "write_failed";
|
|
117
123
|
try {
|
|
118
|
-
const reasoning = effort === undefined ? undefined : omo.omoReasoningFor(effort);
|
|
124
|
+
const reasoning = effort === undefined ? undefined : effort === null ? null : omo.omoReasoningFor(effort);
|
|
119
125
|
omoStatus = omo.writeOmoRoleModel(role, model, omo.omoJsoncPath(), reasoning);
|
|
120
126
|
} catch {
|
|
121
127
|
omoStatus = "write_failed";
|
|
122
128
|
}
|
|
123
|
-
return jsonResponse({
|
|
129
|
+
return jsonResponse({
|
|
130
|
+
ok: true,
|
|
131
|
+
role,
|
|
132
|
+
...(model !== undefined ? { model } : {}),
|
|
133
|
+
...(effort !== undefined ? { effort } : {}),
|
|
134
|
+
toml,
|
|
135
|
+
omoJsonc: { status: omoStatus },
|
|
136
|
+
}, 200, req, config);
|
|
124
137
|
}
|
|
@@ -30,11 +30,14 @@ import {
|
|
|
30
30
|
composeProjection,
|
|
31
31
|
computePromptProbeStateFingerprint,
|
|
32
32
|
findInvalidCharacter,
|
|
33
|
+
importBaseVariant,
|
|
33
34
|
inspectOwnership,
|
|
34
35
|
normalizeBody,
|
|
35
36
|
previewAdopt,
|
|
37
|
+
previewBaseImport,
|
|
36
38
|
previewSalvage,
|
|
37
39
|
readPromptLayers,
|
|
40
|
+
recoverPromptJournal,
|
|
38
41
|
salvageProjection,
|
|
39
42
|
selectBaseVariant,
|
|
40
43
|
setToggle,
|
|
@@ -76,6 +79,7 @@ const WRITE_ERROR_STATUS: Record<WriteError, number> = {
|
|
|
76
79
|
unknown_layer: 400,
|
|
77
80
|
store_unreadable: 409,
|
|
78
81
|
invalid_characters: 400,
|
|
82
|
+
body_too_large: 400,
|
|
79
83
|
write_superseded: 409,
|
|
80
84
|
// Not the caller's fault and not a race: the filesystem refused the write and the
|
|
81
85
|
// transaction rolled itself back. 500 rather than 409 — retrying the same request
|
|
@@ -84,6 +88,8 @@ const WRITE_ERROR_STATUS: Record<WriteError, number> = {
|
|
|
84
88
|
recovery_required: 409,
|
|
85
89
|
locked: 409,
|
|
86
90
|
unsafe: 422,
|
|
91
|
+
import_body_changed: 409,
|
|
92
|
+
unsupported_form: 409,
|
|
87
93
|
};
|
|
88
94
|
|
|
89
95
|
/** Read-only view for the route test that asserts every mapping is a client error. */
|
|
@@ -335,7 +341,12 @@ export async function handleCodexPromptRoutes(ctx: ManagementContext): Promise<R
|
|
|
335
341
|
if (!body) return fail(ctx, "invalid_body", 400, "expected a JSON object");
|
|
336
342
|
const id = body.id;
|
|
337
343
|
if (typeof id !== "string") return fail(ctx, "invalid_body", 400, "id must be a string");
|
|
338
|
-
|
|
344
|
+
// `null` is the restore-the-default verb: it deletes the line instead of
|
|
345
|
+
// writing the documented default back as a literal, so a later upstream
|
|
346
|
+
// change to the default still reaches this file.
|
|
347
|
+
if (typeof body.enabled !== "boolean" && body.enabled !== null) {
|
|
348
|
+
return fail(ctx, "invalid_body", 400, "enabled must be a boolean or null");
|
|
349
|
+
}
|
|
339
350
|
const revision = revisionOf(body);
|
|
340
351
|
if (!revision) return fail(ctx, "stale_revision", 409, "revision required");
|
|
341
352
|
|
|
@@ -388,6 +399,109 @@ export async function handleCodexPromptRoutes(ctx: ManagementContext): Promise<R
|
|
|
388
399
|
return settle(ctx, selectBaseVariant(selection, revision, paths(ctx)));
|
|
389
400
|
}
|
|
390
401
|
|
|
402
|
+
if (url.pathname === "/api/codex-prompt/base/import" && req.method === "POST") {
|
|
403
|
+
// The adopt flow for `model_instructions_file`: preview first, write only on
|
|
404
|
+
// confirmation. The verb exists because /base/select refuses the `external`
|
|
405
|
+
// state by design — that refusal is about SILENT retargeting, and an
|
|
406
|
+
// explicit, previewed import is precisely the opt-in it protects.
|
|
407
|
+
//
|
|
408
|
+
// The preview carries the SERIALIZED variant file — `# {title}\n{body}` —
|
|
409
|
+
// not just the body: the heading is part of what Codex will read, so the
|
|
410
|
+
// thing confirmed must be the whole stored text, title included. Both byte
|
|
411
|
+
// counts are reported and separately named: `bodyBytes` is the budget the
|
|
412
|
+
// 64 KiB cap applies to, `serializedBytes` is the complete file.
|
|
413
|
+
const body = await readBody(ctx);
|
|
414
|
+
if (!body) return fail(ctx, "invalid_body", 400, "expected a JSON object");
|
|
415
|
+
const readState = readPromptLayers(paths(ctx));
|
|
416
|
+
if (!readState.readable) {
|
|
417
|
+
return fail(ctx, "config_unreadable", 409, "the configuration file exists but could not be read", { path: readState.configPath });
|
|
418
|
+
}
|
|
419
|
+
// The title is validated before anything is previewed or written: a caller
|
|
420
|
+
// that supplies one gets the same contract as a custom-layer title —
|
|
421
|
+
// 1-80 characters on a single line.
|
|
422
|
+
if (body.title !== undefined) {
|
|
423
|
+
const t = body.title;
|
|
424
|
+
if (typeof t !== "string" || t.trim().length === 0
|
|
425
|
+
|| t.length > MAX_TITLE_CHARS || /[\r\n]/.test(t)) {
|
|
426
|
+
return fail(ctx, "invalid_title", 400, "title must be 1-80 characters on a single line");
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
const preview = previewBaseImport(paths(ctx), typeof body.title === "string" ? body.title : undefined);
|
|
430
|
+
if (preview.reason === "nothing_to_import") {
|
|
431
|
+
return fail(ctx, "nothing_to_import", 409,
|
|
432
|
+
"model_instructions_file is absent or already points at a managed variant");
|
|
433
|
+
}
|
|
434
|
+
if (preview.reason === "unsupported_form") {
|
|
435
|
+
return fail(ctx, "import_unsupported_form", 409, preview.detail, {
|
|
436
|
+
path: preview.rawPath,
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
if (preview.reason === "file_unreadable") {
|
|
440
|
+
return fail(ctx, "import_file_unreadable", 409, preview.detail, {
|
|
441
|
+
path: preview.resolvedPath ?? preview.rawPath,
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
if (preview.reason === "invalid_characters") {
|
|
445
|
+
return fail(ctx, "invalid_characters", 400, preview.detail, { path: preview.resolvedPath });
|
|
446
|
+
}
|
|
447
|
+
// Same boundary as every other write to this key: the 64 KiB cap applies to
|
|
448
|
+
// the BODY alone — the title heading lives outside that budget, unchanged
|
|
449
|
+
// from the managed-variant contract.
|
|
450
|
+
if (preview.reason === "body_too_large" || utf8Bytes(preview.body!) > MAX_BODY_BYTES) {
|
|
451
|
+
return fail(ctx, "body_too_large", 400, `the file body exceeds ${MAX_BODY_BYTES} bytes`, {
|
|
452
|
+
path: preview.resolvedPath,
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
if (preview.reason === "slots_full") {
|
|
456
|
+
return fail(ctx, "variant_slots_full", 409,
|
|
457
|
+
`at most ${MAX_BASE_VARIANTS} base variants; delete one first`, {
|
|
458
|
+
maxBaseVariants: MAX_BASE_VARIANTS,
|
|
459
|
+
serialized: preview.serialized,
|
|
460
|
+
resolvedPath: preview.resolvedPath,
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
if (body.confirm !== true) {
|
|
464
|
+
// Preview writes nothing, by construction: previewBaseImport is a pure read.
|
|
465
|
+
return jsonResponse({
|
|
466
|
+
ok: true,
|
|
467
|
+
changed: false,
|
|
468
|
+
preview: {
|
|
469
|
+
rawPath: preview.rawPath,
|
|
470
|
+
resolvedPath: preview.resolvedPath,
|
|
471
|
+
serialized: preview.serialized,
|
|
472
|
+
serializedBytes: utf8Bytes(preview.serialized!),
|
|
473
|
+
bodyBytes: utf8Bytes(preview.body!),
|
|
474
|
+
suggestedTitle: preview.suggestedTitle,
|
|
475
|
+
effectiveTitle: preview.effectiveTitle,
|
|
476
|
+
previewSha256: preview.previewSha256,
|
|
477
|
+
},
|
|
478
|
+
}, 200, req, ctx.config);
|
|
479
|
+
}
|
|
480
|
+
const revision = revisionOf(body);
|
|
481
|
+
if (!revision) return fail(ctx, "stale_revision", 409, "revision required");
|
|
482
|
+
// The preview hash is REQUIRED — a confirm that does not name exactly what
|
|
483
|
+
// was previewed (serialized text, title included) cannot be checked against
|
|
484
|
+
// it and is refused without writing.
|
|
485
|
+
const previewSha256 = body.previewSha256;
|
|
486
|
+
if (typeof previewSha256 !== "string" || !/^[0-9a-f]{64}$/.test(previewSha256)) {
|
|
487
|
+
return fail(ctx, "import_preview_required", 400,
|
|
488
|
+
"confirm requires the previewSha256 returned by a preview of the same title");
|
|
489
|
+
}
|
|
490
|
+
if (previewSha256 !== preview.previewSha256) {
|
|
491
|
+
// The file or the selected title moved between preview and confirm. Refuse
|
|
492
|
+
// rather than import a serialization nobody previewed.
|
|
493
|
+
return fail(ctx, "import_body_changed", 409,
|
|
494
|
+
"the preview no longer matches; preview it again", {
|
|
495
|
+
resolvedPath: preview.resolvedPath,
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
return settle(ctx, importBaseVariant(
|
|
499
|
+
{ title: typeof body.title === "string" ? body.title : undefined, previewSha256 },
|
|
500
|
+
revision,
|
|
501
|
+
paths(ctx),
|
|
502
|
+
));
|
|
503
|
+
}
|
|
504
|
+
|
|
391
505
|
if (url.pathname === "/api/codex-prompt/base" && req.method === "PUT") {
|
|
392
506
|
const body = await readBody(ctx);
|
|
393
507
|
if (!body) return fail(ctx, "invalid_body", 400, "expected a JSON object");
|
|
@@ -568,11 +682,28 @@ export async function handleCodexPromptRoutes(ctx: ManagementContext): Promise<R
|
|
|
568
682
|
return settle(ctx, adoptDeveloperInstructions(revision, paths(ctx)));
|
|
569
683
|
}
|
|
570
684
|
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
685
|
+
if (drift === "journal-present") {
|
|
686
|
+
// Recovery as its own locked operation — never a disguised layers write.
|
|
687
|
+
// The GUI used to replay recovery through a byte-identical custom PUT,
|
|
688
|
+
// which projected custom=[] over a config still carrying instructions
|
|
689
|
+
// whenever the store file was missing.
|
|
690
|
+
if (!confirm) {
|
|
691
|
+
return jsonResponse({
|
|
692
|
+
ok: true,
|
|
693
|
+
changed: false,
|
|
694
|
+
preview: { drift, storePath: snapshot.storePath },
|
|
695
|
+
}, 200, req, ctx.config);
|
|
696
|
+
}
|
|
697
|
+
if (!revision) return fail(ctx, "stale_revision", 409, "revision required");
|
|
698
|
+
if (revision !== snapshot.revision) {
|
|
699
|
+
return fail(ctx, "stale_revision", 409, "the configuration moved since it was read");
|
|
700
|
+
}
|
|
701
|
+
return settle(ctx, recoverPromptJournal(paths(ctx)));
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
// A drift kind this build does not repair: name it rather than guess.
|
|
574
705
|
return fail(ctx, "repair_unsupported", 409,
|
|
575
|
-
"
|
|
706
|
+
"this drift has no repair on this route", {
|
|
576
707
|
drift, storePath: snapshot.storePath,
|
|
577
708
|
});
|
|
578
709
|
}
|
|
@@ -209,7 +209,7 @@ export async function proposeDelegationModel(options: {
|
|
|
209
209
|
role: DELEGATED_WORK_ROLE,
|
|
210
210
|
model: options.config.injectionModel ?? null,
|
|
211
211
|
effort: options.config.injectionEffort ?? null,
|
|
212
|
-
}], outcomes, classified
|
|
212
|
+
}], outcomes, classified);
|
|
213
213
|
return {
|
|
214
214
|
sizingModel,
|
|
215
215
|
sizingError,
|