@bitkyc08/opencodex 2.76.0 → 2.78.0-preview.20261005
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 +11 -10
- package/gui/dist/assets/App-CppMz-6w.js +54 -0
- package/gui/dist/assets/App-lCIslZ1L.css +1 -0
- package/gui/dist/assets/{Tray-DjNwwrWS.js → Tray-CJWQqtVv.js} +1 -1
- package/gui/dist/assets/index-DQt9srNi.js +94 -0
- package/gui/dist/assets/index-DmQB8D_R.css +1 -0
- package/gui/dist/assets/quota-summary-BMsyoZH2.js +1 -0
- package/gui/dist/index.html +4 -3
- package/package.json +1 -1
- package/src/adapters/anthropic/beta-allowlist.ts +19 -2
- package/src/adapters/anthropic/client-identity.ts +13 -4
- package/src/adapters/anthropic/native-client-preamble.ts +31 -0
- package/src/adapters/anthropic/passthrough.ts +103 -25
- package/src/adapters/antigravity-refusal.ts +28 -0
- package/src/adapters/cursor/protobuf-request.ts +6 -0
- package/src/adapters/cursor/request-builder.ts +3 -0
- package/src/adapters/cursor/structured-output.ts +13 -0
- package/src/adapters/cursor/types.ts +2 -0
- package/src/adapters/devin/cloud-direct/chat.ts +7 -2
- package/src/adapters/devin/trajectory.ts +49 -0
- package/src/adapters/devin.ts +13 -0
- package/src/adapters/ollama-native.ts +62 -19
- package/src/adapters/openai-responses/passthrough.ts +133 -103
- package/src/adapters/openai-responses/request-strips.ts +10 -4
- package/src/adapters/zed.ts +26 -4
- package/src/bridge/response-json.ts +14 -14
- package/src/bridge/sse.ts +15 -5
- package/src/chatgpt/app-server-shim/bundle-trust.ts +40 -10
- package/src/chatgpt/app-server-shim/gate-rewrite.ts +3 -0
- package/src/claude/agents-inject.ts +4 -43
- package/src/claude/gateway-cache.ts +16 -13
- package/src/claude/inbound-model-options.ts +9 -2
- package/src/claude/intercept/cli-catalog.ts +27 -16
- package/src/claude/intercept/client-hello.ts +103 -0
- package/src/claude/intercept/model-bindings.ts +3 -2
- package/src/claude/intercept/picker-bootstrap.ts +37 -16
- package/src/claude/intercept/picker-budget.ts +41 -0
- package/src/claude/intercept/picker-listener.ts +279 -66
- package/src/claude/outbound.ts +2 -0
- package/src/claude/subagent-force-status.ts +53 -0
- package/src/claude/subagent-model.ts +93 -0
- package/src/cli/access-audio-input.ts +84 -0
- package/src/cli/access-audio-live.ts +99 -0
- package/src/cli/access-audio.ts +123 -0
- package/src/cli/access-data-client.ts +127 -0
- package/src/cli/access-data-plane.ts +104 -0
- package/src/cli/access-data-response.ts +76 -0
- package/src/cli/access.ts +65 -2
- package/src/cli/account-api.ts +33 -6
- package/src/cli/account-auth.ts +111 -42
- package/src/cli/account-key-quota.ts +118 -0
- package/src/cli/account-main.ts +11 -4
- package/src/cli/account-next-actions.ts +63 -0
- package/src/cli/account-policy-dto.ts +68 -0
- package/src/cli/account-policy.ts +167 -0
- package/src/cli/account-target.ts +8 -0
- package/src/cli/account.ts +37 -9
- package/src/cli/agent-runtime-settings.ts +130 -0
- package/src/cli/agent-settings.ts +101 -0
- package/src/cli/agent.ts +29 -2
- package/src/cli/alias.ts +7 -2
- package/src/cli/aside-profiles.ts +2 -2
- package/src/cli/capabilities-access-remote.ts +147 -0
- package/src/cli/capabilities-accounts.ts +195 -0
- package/src/cli/capabilities-agents-routing.ts +414 -0
- package/src/cli/capabilities-base.ts +783 -0
- package/src/cli/capabilities-command.ts +1 -0
- package/src/cli/capabilities-integrations.ts +264 -0
- package/src/cli/capabilities-lab.ts +214 -0
- package/src/cli/capabilities-observe-system.ts +662 -0
- package/src/cli/capabilities-provider-models.ts +302 -0
- package/src/cli/capabilities.ts +30 -1190
- package/src/cli/capability-types.ts +54 -0
- package/src/cli/catalog-command-result.ts +57 -0
- package/src/cli/chatgpt-command.ts +35 -9
- package/src/cli/claude-agent-startup-sync.ts +17 -0
- package/src/cli/claude-desktop-profile.ts +100 -0
- package/src/cli/claude-desktop.ts +39 -6
- package/src/cli/claude.ts +22 -6
- package/src/cli/codex-shim-autorestore.ts +2 -1
- package/src/cli/codex-shim-readiness.ts +109 -0
- package/src/cli/combo-input.ts +88 -0
- package/src/cli/combo-result.ts +52 -0
- package/src/cli/combo-stats.ts +123 -0
- package/src/cli/combo.ts +37 -14
- package/src/cli/companion-timeline.ts +142 -0
- package/src/cli/companion-usage.ts +146 -0
- package/src/cli/companion.ts +12 -3
- package/src/cli/config-command.ts +24 -8
- package/src/cli/connect.ts +19 -0
- package/src/cli/dispatch.ts +55 -82
- package/src/cli/doctor.ts +22 -4
- package/src/cli/export-command.ts +20 -9
- package/src/cli/gui.ts +8 -3
- package/src/cli/help-catalog.ts +101 -0
- package/src/cli/help-models-context.ts +17 -0
- package/src/cli/help-navigation.ts +50 -0
- package/src/cli/help-recovery.ts +66 -0
- package/src/cli/help.ts +69 -11
- package/src/cli/index.ts +38 -27
- package/src/cli/injection-follow.ts +35 -0
- package/src/cli/inspect.ts +5 -0
- package/src/cli/integration-aside-sync.ts +69 -0
- package/src/cli/integration-cursor.ts +106 -0
- package/src/cli/integration-input.ts +60 -0
- package/src/cli/integration-journal.ts +49 -0
- package/src/cli/integration-plan-dto.ts +156 -0
- package/src/cli/integration-preview.ts +215 -0
- package/src/cli/integrations.ts +24 -18
- package/src/cli/json-input.ts +134 -0
- package/src/cli/link.ts +32 -4
- package/src/cli/local-sync-result.ts +41 -0
- package/src/cli/log-follow.ts +84 -0
- package/src/cli/log-view-filter.ts +146 -0
- package/src/cli/logout-command.ts +44 -0
- package/src/cli/model-picker-ordering.ts +137 -0
- package/src/cli/models-custom-input.ts +107 -0
- package/src/cli/models-custom-runtime.ts +112 -0
- package/src/cli/models-order.ts +194 -0
- package/src/cli/models-runtime-subcommands.ts +2 -0
- package/src/cli/models-runtime.ts +9 -1
- package/src/cli/models.ts +66 -81
- package/src/cli/observe-stream.ts +142 -0
- package/src/cli/observe.ts +75 -34
- package/src/cli/opencode.ts +54 -10
- package/src/cli/provider-batch.ts +77 -0
- package/src/cli/provider-lifecycle-runtime.ts +170 -0
- package/src/cli/provider-result.ts +124 -0
- package/src/cli/provider-runtime.ts +15 -12
- package/src/cli/provider-settings.ts +143 -0
- package/src/cli/provider.ts +116 -64
- package/src/cli/registry.ts +90 -24
- package/src/cli/remote-workspace-hub.ts +83 -0
- package/src/cli/remote-workspace.ts +6 -1
- package/src/cli/restart-failure.ts +27 -0
- package/src/cli/root.ts +42 -9
- package/src/cli/route-policy-write.ts +137 -0
- package/src/cli/route-policy.ts +8 -0
- package/src/cli/runtime-api.ts +57 -80
- package/src/cli/secret-args.ts +149 -0
- package/src/cli/settings-result.ts +30 -0
- package/src/cli/storage.ts +77 -7
- package/src/cli/system-command.ts +60 -2
- package/src/cli/system-restart-client.ts +20 -3
- package/src/cli/system-settings-parity.ts +83 -0
- package/src/cli/tray-proxy.ts +3 -2
- package/src/cli/update-restart-candidate.ts +21 -0
- package/src/cli/update-restart-child.ts +81 -0
- package/src/cli/update-restart-home.ts +38 -0
- package/src/cli/update-restart-transport.ts +193 -0
- package/src/cli/update-restart.ts +260 -0
- package/src/cli/usage-model-search.ts +58 -0
- package/src/cli/usage-report.ts +13 -5
- package/src/cli/v2-input.ts +66 -0
- package/src/cli/v2-local-output.ts +37 -0
- package/src/cli/v2-runtime.ts +90 -0
- package/src/cli/v2.ts +102 -144
- package/src/clients/config-export/contracts.ts +100 -0
- package/src/clients/config-export/droid.ts +54 -2
- package/src/clients/config-export/dsh.ts +21 -0
- package/src/clients/config-export/kilo.ts +12 -2
- package/src/clients/config-export/model-metadata.ts +10 -4
- package/src/clients/config-export/reasoning-metadata.ts +47 -0
- package/src/clients/config-export.ts +53 -67
- package/src/clients/omo-role-models.ts +17 -3
- package/src/codex/account-credit-use.ts +4 -2
- package/src/codex/account-runtime-state.ts +21 -0
- package/src/codex/account-store.ts +11 -47
- package/src/codex/account-usability.ts +11 -8
- package/src/codex/auth-api/account-list.ts +73 -63
- package/src/codex/auth-api/main-account-probe.ts +25 -12
- package/src/codex/auth-api/pool-mode-gate.ts +6 -9
- package/src/codex/auth-api/pool-quota-probe.ts +2 -4
- package/src/codex/auth-api/reset-credit-service.ts +3 -2
- package/src/codex/auth-context.ts +30 -11
- package/src/codex/catalog/aggregation.ts +3 -0
- package/src/codex/catalog/build-entries.ts +1 -1
- package/src/codex/catalog/bundled.ts +9 -2
- package/src/codex/catalog/heal-observation.ts +66 -0
- package/src/codex/catalog/model-hints.ts +2 -1
- package/src/codex/catalog/model-visibility.ts +6 -3
- package/src/codex/catalog/parsing.ts +2 -0
- package/src/codex/catalog/provider-models.ts +13 -4
- package/src/codex/catalog/publication-observer.ts +19 -0
- package/src/codex/catalog/remote.ts +13 -15
- package/src/codex/catalog/restore.ts +19 -6
- package/src/codex/catalog/retained-sync.ts +60 -11
- package/src/codex/catalog/routed-removal.ts +116 -0
- package/src/codex/catalog/write-audit-contract.ts +21 -0
- package/src/codex/catalog/write-audit.ts +171 -0
- package/src/codex/catalog-self-heal.ts +284 -0
- package/src/codex/catalog-write-serialization.ts +71 -7
- package/src/codex/chatgpt-refresh-failure.ts +56 -0
- package/src/codex/codex-home-owner.ts +124 -0
- package/src/codex/convergence.ts +57 -16
- package/src/codex/features.ts +8 -1
- package/src/codex/inject/restore.ts +46 -4
- package/src/codex/inject.ts +11 -1
- package/src/codex/internal/catalog-writer.ts +120 -13
- package/src/codex/journal.ts +49 -13
- package/src/codex/main-account.ts +144 -11
- package/src/codex/management-convergence.ts +3 -0
- package/src/codex/quota-refresh-outcome.ts +17 -2
- package/src/codex/quota-types.ts +36 -1
- package/src/codex/quota.ts +67 -16
- package/src/codex/refresh.ts +20 -9
- package/src/codex/routing/cooldown-math.ts +10 -4
- package/src/codex/routing/selection.ts +11 -4
- package/src/codex/routing.ts +9 -9
- package/src/codex/runtime.ts +7 -1
- package/src/codex/shim-diagnostics.ts +80 -0
- package/src/codex/shim-fingerprint.ts +7 -2
- package/src/codex/shim-migration.ts +153 -0
- package/src/codex/shim-overlay.ts +509 -0
- package/src/codex/shim-path-resolution.ts +145 -0
- package/src/codex/shim-probe.ts +4 -3
- package/src/codex/shim-state-file.ts +93 -7
- package/src/codex/shim.ts +54 -107
- package/src/codex/subagent-model-fallback.ts +2 -1
- package/src/codex/sync.ts +24 -4
- package/src/combos/failover.ts +101 -1
- package/src/combos/index.ts +1 -0
- package/src/combos/jev.ts +13 -3
- package/src/combos/resolve.ts +12 -0
- package/src/config/atomic-write.ts +13 -8
- package/src/config/diagnostics.ts +23 -1
- package/src/config/load-degrade.ts +7 -0
- package/src/config/provider-validation.ts +3 -0
- package/src/config/schema/chatgpt-desktop.ts +22 -0
- package/src/config/schema/compaction-triggers.ts +8 -0
- package/src/config/schema/config-schema.ts +43 -14
- package/src/config/schema/leaf-validators.ts +8 -3
- package/src/config/subagent-models.ts +12 -0
- package/src/generated/compatibility-version.json +645 -237
- package/src/grok/session-identity.ts +33 -0
- package/src/integrations/catalog-refresh.ts +1 -1
- package/src/integrations/merge.ts +13 -5
- package/src/integrations/mutation-plan.ts +50 -5
- package/src/integrations/omp-yaml-source.ts +181 -1
- package/src/integrations/ownership-policy.ts +66 -0
- package/src/integrations/registry.ts +50 -1
- package/src/integrations/state.ts +106 -19
- package/src/integrations/target.ts +25 -2
- package/src/integrations/writer.ts +107 -35
- package/src/lib/gui-pair-capability.ts +17 -0
- package/src/lib/package-tree-integrity.ts +42 -1
- package/src/lib/provider-client-headers.ts +62 -0
- package/src/lib/proxy-env.ts +3 -0
- package/src/lib/test-home-guard.ts +2 -1
- package/src/oauth/anthropic-account-refusal.ts +19 -8
- package/src/oauth/anthropic-continuity.ts +65 -0
- package/src/oauth/anthropic-identity.ts +75 -0
- package/src/oauth/anthropic-routing.ts +20 -15
- package/src/oauth/anthropic.ts +34 -3
- package/src/oauth/generic-account-failover.ts +21 -0
- package/src/oauth/health.ts +32 -5
- package/src/oauth/index.ts +42 -35
- package/src/oauth/local-token-detect.ts +7 -0
- package/src/oauth/pool-settings-capability.ts +9 -2
- package/src/oauth/refresh-policy.ts +2 -0
- package/src/oauth/store.ts +55 -6
- package/src/oauth/types.ts +9 -0
- package/src/protocols/encoders/messages.ts +4 -3
- package/src/protocols/plan-snapshot.ts +1 -1
- package/src/protocols/settings.ts +28 -4
- package/src/providers/antigravity-effort-families.ts +75 -0
- package/src/providers/antigravity-models.ts +54 -16
- package/src/providers/antigravity-wire-snapshot.ts +66 -0
- package/src/providers/derive.ts +2 -0
- package/src/providers/model-rename-fields.ts +3 -0
- package/src/providers/new-model-policy.ts +48 -10
- package/src/providers/registry/entries-extended.ts +3 -0
- package/src/providers/registry/model-ids.ts +2 -0
- package/src/providers/registry/model-seeds.ts +5 -0
- package/src/providers/registry/types.ts +4 -0
- package/src/responses/compaction.ts +19 -0
- package/src/router.ts +6 -0
- package/src/server/auth-cors.ts +9 -0
- package/src/server/caller-session-identity.ts +32 -0
- package/src/server/chat-completions.ts +10 -0
- package/src/server/claude-messages.ts +29 -15
- package/src/server/droid-reasoning-default.ts +50 -0
- package/src/server/github-copilot-responses-repair.ts +3 -6
- package/src/server/grok-responses-control-frame.ts +2 -2
- package/src/server/grok-responses-snapshot-repair.ts +4 -5
- package/src/server/gui-session.ts +33 -11
- package/src/server/index/claude-intercept-lifecycle.ts +4 -0
- package/src/server/index/serve-options.ts +13 -7
- package/src/server/index.ts +1 -1
- package/src/server/management/agent-settings-routes.ts +12 -134
- package/src/server/management/config-routes.ts +1 -1
- package/src/server/management/context.ts +8 -1
- package/src/server/management/cursor-integration-routes.ts +3 -3
- package/src/server/management/integration-routes.ts +69 -11
- package/src/server/management/link-routes.ts +20 -13
- package/src/server/management/model-routes.ts +3 -3
- package/src/server/management/model-row-export-metadata.ts +150 -0
- package/src/server/management/model-rows.ts +148 -9
- package/src/server/management/native-integration-routes.ts +2 -2
- package/src/server/management/oauth-account-routes.ts +96 -30
- package/src/server/management/route-registry.ts +29 -27
- package/src/server/management/shared.ts +19 -5
- package/src/server/management/subagent-model-routes.ts +214 -0
- package/src/server/messages-native-eligibility.ts +3 -6
- package/src/server/messages-native-oauth.ts +82 -35
- package/src/server/messages-native.ts +78 -16
- package/src/server/relay.ts +14 -44
- package/src/server/request-log-generation-window.ts +38 -0
- package/src/server/request-log.ts +13 -0
- package/src/server/responses/adapter-dispatch.ts +152 -28
- package/src/server/responses/codex-auth-error.ts +10 -2
- package/src/server/responses/combo-stream-preflight.ts +29 -3
- package/src/server/responses/compaction-routing.ts +10 -1
- package/src/server/responses/core-auth.ts +67 -30
- package/src/server/responses/core-codex-account.ts +6 -3
- package/src/server/responses/core-combo-failure.ts +5 -1
- package/src/server/responses/core-combo-native.ts +7 -1
- package/src/server/responses/core-combo.ts +64 -7
- package/src/server/responses/core-normalize.ts +7 -0
- package/src/server/responses/core-options.ts +5 -0
- package/src/server/responses/fetch-helpers.ts +9 -0
- package/src/server/responses/passthrough-delivery.ts +1 -1
- package/src/server/responses/policy-fallback.ts +1 -1
- package/src/server/responses/request-prepare.ts +5 -0
- package/src/server/responses/request-spend.ts +30 -30
- package/src/server/responses-custom-tool-repair.ts +2 -2
- package/src/server/responses-hosted-image-display.ts +70 -9
- package/src/server/responses-terminal-repair.ts +15 -2
- package/src/server/sse-frame-buffer.ts +38 -36
- package/src/server/sse-payload-rewrite.ts +78 -49
- package/src/server/ws-bridge.ts +1 -11
- package/src/service/cli.ts +6 -1
- package/src/service/ownership-mutation-lease.d.mts +26 -0
- package/src/service/ownership-mutation-lease.mjs +76 -1
- package/src/service/windows-taskxml.ts +1 -2
- package/src/service-manager-probe.ts +36 -2
- package/src/types/config.ts +6 -0
- package/src/types/provider.ts +28 -10
- package/src/update/index.ts +2 -1
- package/src/update/npm-invocation.mjs +35 -3
- package/src/update/proxy-liveness-probe.mjs +38 -8
- package/src/update/update-failure-guidance.d.mts +6 -0
- package/src/update/update-failure-guidance.mjs +30 -5
- package/src/usage/expected-prices.ts +9 -0
- package/src/usage/log.ts +7 -0
- package/src/usage/summary.ts +82 -1
- package/src/web-search/loop.ts +11 -8
- package/src/web-search/run-turn-loop.ts +10 -7
- package/gui/dist/assets/App-CddorHdA.js +0 -51
- package/gui/dist/assets/App-DMudtISW.css +0 -1
- package/gui/dist/assets/index-Btze1I3T.css +0 -1
- package/gui/dist/assets/index-CvnY7E6i.js +0 -86
- package/gui/dist/assets/quota-summary-B_AWpHfP.js +0 -1
|
@@ -205,7 +205,7 @@ export function setGrokApplyFlightTestHooks(
|
|
|
205
205
|
grokApplyFlight = null;
|
|
206
206
|
grokApplyHighWaterBytes = 0;
|
|
207
207
|
}
|
|
208
|
-
import type
|
|
208
|
+
import { managementInferencePort, type ManagementContext } from "./context";
|
|
209
209
|
|
|
210
210
|
async function injectionModelOptions(config: OcxConfig, models: readonly CatalogModel[]) {
|
|
211
211
|
const disabled = new Set(config.disabledModels ?? []);
|
|
@@ -262,7 +262,8 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
|
|
|
262
262
|
const writtenProfile = current.claudeCode.desktopProfile;
|
|
263
263
|
const markerBaseline = captureDesktopAppliedMarker(writtenProfile);
|
|
264
264
|
const result = (deps.writeDesktop3pConfig ?? writeDesktop3pConfig)(
|
|
265
|
-
|
|
265
|
+
// Live bound port, not config: a CLI override or ephemeral bind must not strand Desktop (#6598).
|
|
266
|
+
managementInferencePort({ config: current, deps }) ?? 10100,
|
|
266
267
|
[...desktopVisibleNativeSlugs(current)],
|
|
267
268
|
routed,
|
|
268
269
|
current.apiKeys?.[0]?.key,
|
|
@@ -761,135 +762,9 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
|
|
|
761
762
|
});
|
|
762
763
|
}
|
|
763
764
|
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
const models = await (deps.fetchAllModels ?? fetchAllModels)(config);
|
|
768
|
-
// Native gpt (passthrough) are also valid subagent picks — they're picker-visible models in the
|
|
769
|
-
// catalog, just buried by priority. List them first so the user can feature them over routed.
|
|
770
|
-
const [{ listCatalogNativeSlugs }, { subagentSelectableModels }] = await Promise.all([
|
|
771
|
-
import("../../codex/catalog"),
|
|
772
|
-
import("../../codex/subagent-selectable-models"),
|
|
773
|
-
]);
|
|
774
|
-
const chosen = config.subagentModels ?? [];
|
|
775
|
-
// A saved roster slot must stay representable even after its model is disabled
|
|
776
|
-
// elsewhere (Models page, provider allowlist, a provider row going away). The
|
|
777
|
-
// dashboard treats `available` as the set of rows it can render, so a chosen id
|
|
778
|
-
// missing from it disappears from the roster UI and the next Save — which PUTs
|
|
779
|
-
// exactly what the UI holds — silently truncates the persisted list. Losing a
|
|
780
|
-
// deliberate 5-model roster to an unrelated visibility toggle is data loss, not a
|
|
781
|
-
// filter. Same reasoning as `fetchGrokCandidateModels`, which deliberately lists a
|
|
782
|
-
// model the user already excluded so its switch remains reachable.
|
|
783
|
-
const available = subagentSelectableModels(config, models, listCatalogNativeSlugs());
|
|
784
|
-
// #857: let CLI/GUI show when a running Codex app-server keeps an older
|
|
785
|
-
// in-memory catalog than the one on disk. Bounded request-path read: the synchronous
|
|
786
|
-
// collector blocked the event loop for the whole Windows CIM walk (4-7s measured).
|
|
787
|
-
const {
|
|
788
|
-
collectCodexAppServerCatalogStateWithin,
|
|
789
|
-
DASHBOARD_CATALOG_STATE_DEADLINE_MS,
|
|
790
|
-
} = await import("../../codex/app-server-processes");
|
|
791
|
-
const catalogState = await collectCodexAppServerCatalogStateWithin(DASHBOARD_CATALOG_STATE_DEADLINE_MS);
|
|
792
|
-
return jsonResponse({
|
|
793
|
-
chosen, available, catalogState,
|
|
794
|
-
pickerAvailable: [...new Set(filterCatalogVisibleModels(models, config).map(catalogModelSlug).filter(slug => slug.includes("/")))],
|
|
795
|
-
pickerOrder: config.modelPickerOrder ?? [],
|
|
796
|
-
pickerOrderMode: config.modelPickerOrderMode ?? null,
|
|
797
|
-
});
|
|
798
|
-
}
|
|
799
|
-
if (url.pathname === "/api/subagent-models" && req.method === "PUT") {
|
|
800
|
-
let rawBody: unknown;
|
|
801
|
-
try { rawBody = await readManagementJsonBody(req); } catch (error) { rethrowManagementBodyTooLarge(error); return jsonResponse({ error: "invalid JSON body" }, 400); }
|
|
802
|
-
if (!isPlainRecord(rawBody)) return jsonResponse({ error: "JSON body must be an object" }, 400);
|
|
803
|
-
const body = rawBody as { models?: unknown; pickerOrder?: unknown; pickerOrderMode?: unknown };
|
|
804
|
-
const updatesRoster = body.models !== undefined;
|
|
805
|
-
const updatesPicker = body.pickerOrder !== undefined;
|
|
806
|
-
if (!updatesRoster && !updatesPicker) return jsonResponse({ error: "models or pickerOrder is required" }, 400);
|
|
807
|
-
let chosen: string[] | undefined;
|
|
808
|
-
if (updatesRoster) {
|
|
809
|
-
if (!Array.isArray(body.models) || body.models.some(model => typeof model !== "string")) {
|
|
810
|
-
return jsonResponse({ error: "models must be an array of strings" }, 400);
|
|
811
|
-
}
|
|
812
|
-
// Keep the original valid roster contract: no discovery validation, trimming or deduping.
|
|
813
|
-
chosen = body.models.slice(0, 5);
|
|
814
|
-
}
|
|
815
|
-
const mode = body.pickerOrderMode;
|
|
816
|
-
if (mode !== undefined && (!updatesPicker || (mode !== null
|
|
817
|
-
&& mode !== "alphabetical" && mode !== "provider" && mode !== "most-used"))) {
|
|
818
|
-
return jsonResponse({ error: "pickerOrderMode requires pickerOrder and must be alphabetical, provider, most-used, or null" }, 400);
|
|
819
|
-
}
|
|
820
|
-
let pickerOrder: string[] | undefined;
|
|
821
|
-
if (updatesPicker) {
|
|
822
|
-
if (body.pickerOrder !== null && (!Array.isArray(body.pickerOrder)
|
|
823
|
-
|| body.pickerOrder.some(model => typeof model !== "string" || model.trim() === ""))) {
|
|
824
|
-
return jsonResponse({ error: "pickerOrder must be an array of non-empty routed model ids, or null" }, 400);
|
|
825
|
-
}
|
|
826
|
-
pickerOrder = body.pickerOrder === null ? [] : (body.pickerOrder as string[]).map(model => model.trim());
|
|
827
|
-
if (new Set(pickerOrder).size !== pickerOrder.length) {
|
|
828
|
-
return jsonResponse({ error: "pickerOrder must not contain duplicate ids" }, 400);
|
|
829
|
-
}
|
|
830
|
-
if (pickerOrder.length > 0) {
|
|
831
|
-
const models = await (deps.fetchAllModels ?? fetchAllModels)(config);
|
|
832
|
-
// Evaluate visibility AFTER discovery: a concurrent visibility write may have completed.
|
|
833
|
-
const visible = new Set(filterCatalogVisibleModels(models, config).map(catalogModelSlug).filter(slug => slug.includes("/")));
|
|
834
|
-
// A bare native id orders the complete picker (docs: guides/model-ordering.md).
|
|
835
|
-
for (const row of nativeModelRows(config)) if (!row.disabled) visible.add(row.slug);
|
|
836
|
-
if (pickerOrder.some(model => !visible.has(model))) {
|
|
837
|
-
return jsonResponse({ error: "pickerOrder must contain visible routed or native model ids, each at most once" }, 400);
|
|
838
|
-
}
|
|
839
|
-
}
|
|
840
|
-
}
|
|
841
|
-
|
|
842
|
-
// Everything above can await. From this snapshot through persistence there is no yield.
|
|
843
|
-
// Stage deletion intent before adopting the touched fields through the canonical
|
|
844
|
-
// live deletion owner. A failed save restores both fields and pending intent.
|
|
845
|
-
if (updatesPicker && config.configRebaseProvenance !== undefined
|
|
846
|
-
&& parsedConfigRebaseDeletionKeys(config) === null) {
|
|
847
|
-
// A newer provenance format must not silently discard this clear's intent on rebase.
|
|
848
|
-
return jsonResponse({ error: "unsupported config deletion provenance" }, 409);
|
|
849
|
-
}
|
|
850
|
-
const draft = { ...projectConfigRebaseProvenance(config) };
|
|
851
|
-
if (chosen !== undefined) draft.subagentModels = chosen;
|
|
852
|
-
if (pickerOrder !== undefined) {
|
|
853
|
-
if (pickerOrder.length === 0) {
|
|
854
|
-
deleteConfigTopLevelKey(draft, "modelPickerOrder");
|
|
855
|
-
deleteConfigTopLevelKey(draft, "modelPickerOrderMode");
|
|
856
|
-
} else {
|
|
857
|
-
draft.modelPickerOrder = pickerOrder;
|
|
858
|
-
if (mode === "alphabetical" || mode === "provider" || mode === "most-used") draft.modelPickerOrderMode = mode;
|
|
859
|
-
else deleteConfigTopLevelKey(draft, "modelPickerOrderMode");
|
|
860
|
-
}
|
|
861
|
-
}
|
|
862
|
-
const projected = projectConfigRebaseProvenance(draft);
|
|
863
|
-
const touched = [
|
|
864
|
-
...(updatesRoster ? ["subagentModels" as const] : []),
|
|
865
|
-
...(updatesPicker ? ["modelPickerOrder" as const, "modelPickerOrderMode" as const] : []),
|
|
866
|
-
"configRebaseProvenance" as const,
|
|
867
|
-
];
|
|
868
|
-
const rollback = captureConfigTopLevelRollback(config, touched);
|
|
869
|
-
try {
|
|
870
|
-
for (const key of touched) {
|
|
871
|
-
if (Object.hasOwn(projected, key)) Object.defineProperty(config, key, {
|
|
872
|
-
value: projected[key], writable: true, enumerable: true, configurable: true,
|
|
873
|
-
});
|
|
874
|
-
else deleteConfigTopLevelKey(config, key);
|
|
875
|
-
}
|
|
876
|
-
(deps.saveConfigPreservingClaudeCode ?? saveConfigPreservingClaudeCode)(config);
|
|
877
|
-
} catch (error) {
|
|
878
|
-
rollback();
|
|
879
|
-
throw error;
|
|
880
|
-
}
|
|
881
|
-
// Capture the result before convergence yields to another settings mutation.
|
|
882
|
-
const saved = {
|
|
883
|
-
applied: [...(config.subagentModels ?? [])],
|
|
884
|
-
pickerOrder: [...(config.modelPickerOrder ?? [])],
|
|
885
|
-
pickerOrderMode: config.modelPickerOrderMode ?? null,
|
|
886
|
-
};
|
|
887
|
-
const catalogRefresh = await convergeCodexCatalog();
|
|
888
|
-
if (updatesRoster) {
|
|
889
|
-
await syncClaudeAgentDefsBestEffort();
|
|
890
|
-
await autoApplyDesktopBestEffort();
|
|
891
|
-
}
|
|
892
|
-
return jsonResponse({ ok: true, ...saved, catalogRefresh });
|
|
765
|
+
if (url.pathname === "/api/subagent-models") {
|
|
766
|
+
const { handleSubagentModelRoutes } = await import("./subagent-model-routes");
|
|
767
|
+
return handleSubagentModelRoutes(ctx, autoApplyDesktopBestEffort);
|
|
893
768
|
}
|
|
894
769
|
|
|
895
770
|
// Priority-ordered subagent model fallback chain for quota-aware spawn routing.
|
|
@@ -1024,7 +899,7 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
|
|
|
1024
899
|
if (url.pathname === "/api/claude-desktop" && req.method === "GET") {
|
|
1025
900
|
try {
|
|
1026
901
|
const state = await buildClaudeDesktopState(config);
|
|
1027
|
-
const runtimePort =
|
|
902
|
+
const runtimePort = managementInferencePort(ctx);
|
|
1028
903
|
return jsonResponse({ ...state, port: runtimePort });
|
|
1029
904
|
} catch (error) {
|
|
1030
905
|
return jsonResponse({ error: error instanceof Error ? error.message : String(error) }, 400);
|
|
@@ -1092,7 +967,7 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
|
|
|
1092
967
|
};
|
|
1093
968
|
}
|
|
1094
969
|
const saved = await buildClaudeDesktopState(config);
|
|
1095
|
-
const runtimePort =
|
|
970
|
+
const runtimePort = managementInferencePort(ctx);
|
|
1096
971
|
return jsonResponse({ ok: true, ...saved, port: runtimePort });
|
|
1097
972
|
} catch (error) {
|
|
1098
973
|
return jsonResponse({ error: error instanceof Error ? error.message : String(error) }, 400);
|
|
@@ -1194,6 +1069,7 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
|
|
|
1194
1069
|
const picker = modeSaved.ok && pickerPreferenceOn(loadConfig())
|
|
1195
1070
|
? await ops.enableLocked({ persist: false, context: "server" })
|
|
1196
1071
|
: await pickerStatusFor(loadConfig());
|
|
1072
|
+
if (modeSaved.ok) await syncClaudeAgentDefsBestEffort();
|
|
1197
1073
|
return jsonResponse({
|
|
1198
1074
|
ok: true,
|
|
1199
1075
|
mode: "first-party",
|
|
@@ -1256,7 +1132,7 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
|
|
|
1256
1132
|
// Picker mode belongs to first-party: stop terminating claude.ai before the gateway lands.
|
|
1257
1133
|
const pickerOff = await ops.disableLocked({ persist: false });
|
|
1258
1134
|
const result = (deps.writeDesktop3pConfig ?? writeDesktop3pConfig)(
|
|
1259
|
-
|
|
1135
|
+
managementInferencePort({ config: latest, deps }),
|
|
1260
1136
|
[...desktopVisibleNativeSlugs(latest)],
|
|
1261
1137
|
routed,
|
|
1262
1138
|
latest.apiKeys?.[0]?.key,
|
|
@@ -1542,6 +1418,7 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
|
|
|
1542
1418
|
? { visionSidecar: { backend: visionOverride.backend, model: visionOverride.model } }
|
|
1543
1419
|
: {}),
|
|
1544
1420
|
fastMode: config.fastMode,
|
|
1421
|
+
forceAvailable: [...visibleNativeSlugs(config), ...filterCatalogVisibleModels(models, config).map(catalogModelSlug)],
|
|
1545
1422
|
contextWindows,
|
|
1546
1423
|
effectiveModelEnv: effectiveModelEnv(config.claudeCode, contextWindows),
|
|
1547
1424
|
available,
|
|
@@ -1678,6 +1555,7 @@ export async function handleAgentSettingsRoutes(ctx: ManagementContext): Promise
|
|
|
1678
1555
|
const finalDesired = firstPartyDesired(config, observeClaudeDesktopMode(config));
|
|
1679
1556
|
const residual = !finalDesired.desktop && !finalDesired.cli
|
|
1680
1557
|
&& readFirstPartyProxyStatus(config, bound?.proxyPort ?? null) !== "none";
|
|
1558
|
+
await syncClaudeAgentDefsBestEffort();
|
|
1681
1559
|
return jsonResponse({ ok: true, enabled: config.claudeCode?.enabled !== false,
|
|
1682
1560
|
cliFirstParty: body.cliFirstParty,
|
|
1683
1561
|
warnings: [
|
|
@@ -276,7 +276,7 @@ export async function syncEnabledClientIntegrations(
|
|
|
276
276
|
},
|
|
277
277
|
config,
|
|
278
278
|
port,
|
|
279
|
-
}, ["mcode", "pi", "aside", "raycast", "omo", "cline", "droid"]));
|
|
279
|
+
}, ["mcode", "pi", "aside", "raycast", "omo", "cline", "droid", "opencode", "kilo"]));
|
|
280
280
|
|
|
281
281
|
return out;
|
|
282
282
|
}
|
|
@@ -160,7 +160,7 @@ export interface ManagementApiDeps {
|
|
|
160
160
|
issueApiKey?: (config: OcxConfig, name: string) => IssuedApiKey;
|
|
161
161
|
revokeApiKey?: (config: OcxConfig, id: string) => boolean;
|
|
162
162
|
loadLinkCandidates?: () => Array<{ alias: string; source: "ssh_config" | "tailscale" }>;
|
|
163
|
-
/**
|
|
163
|
+
/** Bound public inference port, not the ingress receiving this management request. */
|
|
164
164
|
liveListenPort?: () => number | undefined;
|
|
165
165
|
now?: () => number;
|
|
166
166
|
}
|
|
@@ -191,3 +191,10 @@ export interface ManagementContext {
|
|
|
191
191
|
convergeCodexCatalog: () => Promise<CatalogDisposition>;
|
|
192
192
|
syncClaudeAgentDefsBestEffort: () => Promise<void>;
|
|
193
193
|
}
|
|
194
|
+
|
|
195
|
+
/** A management-only ingress cannot supply the port used by generated inference clients. */
|
|
196
|
+
export function managementInferencePort(ctx: Pick<ManagementContext, "config" | "deps">): number {
|
|
197
|
+
// Lifecycle owns the actual port (including CLI overrides/ephemeral binds); direct route
|
|
198
|
+
// fixtures fall back to config without consulting another runtime's state file.
|
|
199
|
+
return ctx.deps.liveListenPort?.() ?? ctx.config.port;
|
|
200
|
+
}
|
|
@@ -53,9 +53,9 @@ export async function buildCursorIntegrationStatus(
|
|
|
53
53
|
const privateInference = pick(installs, "private-inference");
|
|
54
54
|
const regular = pick(installs, "regular");
|
|
55
55
|
const runtime = (deps.readRuntimePort ?? readRuntimePort)(process.pid);
|
|
56
|
-
// The
|
|
57
|
-
//
|
|
58
|
-
const port =
|
|
56
|
+
// The lifecycle-bound public port wins, then the PID-matched runtime record, then config.
|
|
57
|
+
// A management-only ingress port must never become the Cursor gateway (#6598).
|
|
58
|
+
const port = deps.liveListenPort?.() ?? runtime?.port ?? config.port;
|
|
59
59
|
// Cursor runs on this machine, so the gateway URL it is told to paste is the LOCAL one: the
|
|
60
60
|
// unauthenticated loopback listener when one is enabled, and otherwise the bind address on the
|
|
61
61
|
// public port — 127.0.0.1 for a loopback or wildcard bind exactly as before, and the tailnet
|
|
@@ -24,7 +24,8 @@ import {
|
|
|
24
24
|
type IntegrationClientId,
|
|
25
25
|
} from "../../integrations/registry";
|
|
26
26
|
import { detectRaycast, type RaycastInstall } from "../../integrations/raycast-detect";
|
|
27
|
-
import { readIntegrationState } from "../../integrations/state";
|
|
27
|
+
import { readIntegrationState, readOwnedDroidReasoningDefaults } from "../../integrations/state";
|
|
28
|
+
import { droidReasoningModels, validateDroidReasoningDefaults } from "../../clients/config-export";
|
|
28
29
|
import { createIntegrationStateStore, type IntegrationStateStore } from "../../integrations/store";
|
|
29
30
|
import {
|
|
30
31
|
applyIntegrationCoordinated,
|
|
@@ -45,7 +46,7 @@ import {
|
|
|
45
46
|
} from "../../integrations/mutation-flight";
|
|
46
47
|
import { jsonResponse } from "../auth-cors";
|
|
47
48
|
import { readManagementJsonBody, rethrowManagementBodyTooLarge } from "./body";
|
|
48
|
-
import type
|
|
49
|
+
import { managementInferencePort, type ManagementContext } from "./context";
|
|
49
50
|
import { exportSnapshotIdentity, loadExportModels, previewExportSnapshot } from "./model-rows";
|
|
50
51
|
import {
|
|
51
52
|
previewIntegration,
|
|
@@ -74,6 +75,10 @@ export type IntegrationStateEnvelope = {
|
|
|
74
75
|
* the shared `IntegrationStatus`, which describes the file, not the app.
|
|
75
76
|
*/
|
|
76
77
|
raycast?: RaycastInstall;
|
|
78
|
+
droidReasoning?: {
|
|
79
|
+
models: Array<{ model: string; label: string; efforts: string[] }>;
|
|
80
|
+
defaults: Record<string, string>;
|
|
81
|
+
};
|
|
77
82
|
} & IntegrationStateRecord;
|
|
78
83
|
|
|
79
84
|
export interface IntegrationStateListEnvelope {
|
|
@@ -225,7 +230,7 @@ function asideOptions(ctx: ManagementContext): AsideProfileRouteOptions {
|
|
|
225
230
|
return {
|
|
226
231
|
input: () => input ??= {
|
|
227
232
|
config: ctx.config,
|
|
228
|
-
port:
|
|
233
|
+
port: managementInferencePort(ctx),
|
|
229
234
|
models: () => loadExportModels(ctx.config),
|
|
230
235
|
store: integrationStore(),
|
|
231
236
|
...pathOverrides(),
|
|
@@ -241,12 +246,14 @@ async function buildIntegrationWriteInput(
|
|
|
241
246
|
clientId: IntegrationClientId,
|
|
242
247
|
ctx: ManagementContext,
|
|
243
248
|
store: IntegrationStateStore,
|
|
249
|
+
droidReasoningDefaults?: Record<string, string>,
|
|
244
250
|
): Promise<IntegrationWriteInput> {
|
|
245
251
|
return {
|
|
246
252
|
clientId,
|
|
247
253
|
models: await loadExportModels(ctx.config),
|
|
248
254
|
config: ctx.config,
|
|
249
|
-
port:
|
|
255
|
+
port: managementInferencePort(ctx),
|
|
256
|
+
...(droidReasoningDefaults === undefined ? {} : { droidReasoningDefaults: { ...droidReasoningDefaults } }),
|
|
250
257
|
store,
|
|
251
258
|
io: integrationMutationTestHooks?.io,
|
|
252
259
|
...pathOverrides(),
|
|
@@ -265,6 +272,7 @@ async function buildIntegrationPreviewInput(
|
|
|
265
272
|
clientId: IntegrationClientId,
|
|
266
273
|
ctx: ManagementContext,
|
|
267
274
|
store: IntegrationStateStore,
|
|
275
|
+
droidReasoningDefaults?: Record<string, string>,
|
|
268
276
|
): Promise<{ input: IntegrationWriteInput; identity: string } | null> {
|
|
269
277
|
const snapshot = previewExportSnapshot(ctx.config);
|
|
270
278
|
// No cached roster means no honest snapshot to plan against. Gathering one here would make a
|
|
@@ -276,7 +284,8 @@ async function buildIntegrationPreviewInput(
|
|
|
276
284
|
clientId,
|
|
277
285
|
models: snapshot.models,
|
|
278
286
|
config: ctx.config,
|
|
279
|
-
port:
|
|
287
|
+
port: managementInferencePort(ctx),
|
|
288
|
+
...(droidReasoningDefaults === undefined ? {} : { droidReasoningDefaults: { ...droidReasoningDefaults } }),
|
|
280
289
|
store,
|
|
281
290
|
io: integrationMutationTestHooks?.io,
|
|
282
291
|
...pathOverrides(),
|
|
@@ -309,6 +318,31 @@ function invalidClientResponse(ctx: ManagementContext): Response {
|
|
|
309
318
|
}, 400, ctx.req, ctx.config);
|
|
310
319
|
}
|
|
311
320
|
|
|
321
|
+
function isStringRecord(value: unknown): value is Record<string, string> {
|
|
322
|
+
return value !== null && typeof value === "object" && !Array.isArray(value)
|
|
323
|
+
&& Object.values(value).every(item => typeof item === "string");
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function invalidDroidDefaultsResponse(ctx: ManagementContext, message: string): Response {
|
|
327
|
+
return jsonResponse({ error: message, code: "invalid_droid_reasoning_defaults" }, 400, ctx.req, ctx.config);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function readDroidReasoningDefaults(
|
|
331
|
+
ctx: ManagementContext,
|
|
332
|
+
body: Record<string, unknown>,
|
|
333
|
+
clientId: string,
|
|
334
|
+
operation: IntegrationPlanOperation,
|
|
335
|
+
): Record<string, string> | Response | undefined {
|
|
336
|
+
if (!Object.hasOwn(body, "droidReasoningDefaults")) return undefined;
|
|
337
|
+
if (clientId !== "droid" || (operation !== "apply" && operation !== "overwrite")) {
|
|
338
|
+
return invalidDroidDefaultsResponse(ctx, "droidReasoningDefaults is allowed only for Droid apply or overwrite");
|
|
339
|
+
}
|
|
340
|
+
const defaults = body.droidReasoningDefaults;
|
|
341
|
+
return isStringRecord(defaults)
|
|
342
|
+
? defaults
|
|
343
|
+
: invalidDroidDefaultsResponse(ctx, "droidReasoningDefaults must map model names to effort strings");
|
|
344
|
+
}
|
|
345
|
+
|
|
312
346
|
/**
|
|
313
347
|
* No cached model roster, so there is nothing honest to plan against.
|
|
314
348
|
*
|
|
@@ -364,6 +398,7 @@ function stalePlanGuard(
|
|
|
364
398
|
request: PreviewRequest,
|
|
365
399
|
fingerprint: string,
|
|
366
400
|
capturedIdentity: string | null,
|
|
401
|
+
droidReasoningDefaults?: Record<string, string>,
|
|
367
402
|
): {
|
|
368
403
|
revalidate: NonNullable<CoordinatedIntegrationOptions["revalidate"]>;
|
|
369
404
|
response: () => Response | null;
|
|
@@ -379,7 +414,7 @@ function stalePlanGuard(
|
|
|
379
414
|
* without ever swapping the roster this mutation is about to use.
|
|
380
415
|
*/
|
|
381
416
|
if (exportSnapshotIdentity(ctx.config) !== capturedIdentity) {
|
|
382
|
-
const refreshed = await buildIntegrationPreviewInput(clientId, ctx, store);
|
|
417
|
+
const refreshed = await buildIntegrationPreviewInput(clientId, ctx, store, droidReasoningDefaults);
|
|
383
418
|
stale = refreshed === null ? "unavailable" : previewIntegration(refreshed.input, request);
|
|
384
419
|
return { ok: false, reason: "conflict", state: "conflict", clientId, message: "the model roster changed while confirming" };
|
|
385
420
|
}
|
|
@@ -584,7 +619,7 @@ export async function handleIntegrationRoutes(ctx: ManagementContext): Promise<R
|
|
|
584
619
|
if (url.pathname === "/api/client-integrations" && req.method === "GET") {
|
|
585
620
|
try {
|
|
586
621
|
const models = await loadExportModels(ctx.config);
|
|
587
|
-
const port =
|
|
622
|
+
const port = managementInferencePort(ctx);
|
|
588
623
|
// One store for the whole collection read: without it this route retried
|
|
589
624
|
// maintenance and counted snapshots in the default store even when the
|
|
590
625
|
// caller had bound everything else to a temp root (A-gate round 13).
|
|
@@ -723,9 +758,17 @@ export async function handleIntegrationRoutes(ctx: ManagementContext): Promise<R
|
|
|
723
758
|
code: "invalid_preview_operation",
|
|
724
759
|
}, 400, req, ctx.config);
|
|
725
760
|
}
|
|
761
|
+
const droidDefaults = readDroidReasoningDefaults(ctx, parsed, previewClient, operation);
|
|
762
|
+
if (droidDefaults instanceof Response) return droidDefaults;
|
|
726
763
|
try {
|
|
727
|
-
const captured = await buildIntegrationPreviewInput(
|
|
764
|
+
const captured = await buildIntegrationPreviewInput(
|
|
765
|
+
previewClient as IntegrationClientId, ctx, integrationStore(), droidDefaults,
|
|
766
|
+
);
|
|
728
767
|
if (!captured) return previewUnavailableResponse(ctx);
|
|
768
|
+
if (droidDefaults !== undefined) {
|
|
769
|
+
const invalid = validateDroidReasoningDefaults(captured.input.models, droidDefaults);
|
|
770
|
+
if (invalid) return invalidDroidDefaultsResponse(ctx, invalid);
|
|
771
|
+
}
|
|
729
772
|
return jsonResponse(previewIntegration(captured.input, { operation }), 200, req, ctx.config);
|
|
730
773
|
} catch (error) {
|
|
731
774
|
return internalErrorResponse(error, ctx);
|
|
@@ -907,7 +950,15 @@ export async function handleIntegrationRoutes(ctx: ManagementContext): Promise<R
|
|
|
907
950
|
// process spawn, and no other client's read should pay for it.
|
|
908
951
|
const envelope: IntegrationStateEnvelope = requestedClient === "raycast"
|
|
909
952
|
? { ...state, raycast: (raycastDetectTestHook ?? detectRaycast)() }
|
|
910
|
-
:
|
|
953
|
+
: requestedClient === "droid"
|
|
954
|
+
? {
|
|
955
|
+
...state,
|
|
956
|
+
droidReasoning: {
|
|
957
|
+
models: droidReasoningModels(input.models),
|
|
958
|
+
defaults: readOwnedDroidReasoningDefaults(input),
|
|
959
|
+
},
|
|
960
|
+
}
|
|
961
|
+
: state;
|
|
911
962
|
return jsonResponse(envelope, 200, req, ctx.config);
|
|
912
963
|
} catch (error) {
|
|
913
964
|
return internalErrorResponse(error, ctx);
|
|
@@ -944,6 +995,8 @@ export async function handleIntegrationRoutes(ctx: ManagementContext): Promise<R
|
|
|
944
995
|
const requestedOperation: IntegrationPlanOperation = parsed.enabled
|
|
945
996
|
? (parsed.overwriteConflict === true ? "overwrite" : "apply")
|
|
946
997
|
: "disable";
|
|
998
|
+
const droidDefaults = readDroidReasoningDefaults(ctx, parsed, requestedClient, requestedOperation);
|
|
999
|
+
if (droidDefaults instanceof Response) return droidDefaults;
|
|
947
1000
|
const binding = planBindingOf(parsed);
|
|
948
1001
|
if (binding === "half") return halfBoundResponse(ctx);
|
|
949
1002
|
if (binding === "unknown-operation" || (binding !== "none" && binding.operation !== requestedOperation)) {
|
|
@@ -964,11 +1017,15 @@ export async function handleIntegrationRoutes(ctx: ManagementContext): Promise<R
|
|
|
964
1017
|
*/
|
|
965
1018
|
const boundToggle = binding === "none"
|
|
966
1019
|
? null
|
|
967
|
-
: await buildIntegrationPreviewInput(requestedClient, ctx, integrationStore());
|
|
1020
|
+
: await buildIntegrationPreviewInput(requestedClient, ctx, integrationStore(), droidDefaults);
|
|
968
1021
|
if (binding !== "none" && boundToggle === null) return previewUnavailableResponse(ctx);
|
|
969
1022
|
const input = boundToggle
|
|
970
1023
|
? boundToggle.input
|
|
971
|
-
: await buildIntegrationWriteInput(requestedClient, ctx, integrationStore());
|
|
1024
|
+
: await buildIntegrationWriteInput(requestedClient, ctx, integrationStore(), droidDefaults);
|
|
1025
|
+
if (droidDefaults !== undefined) {
|
|
1026
|
+
const invalid = validateDroidReasoningDefaults(input.models, droidDefaults);
|
|
1027
|
+
if (invalid) return invalidDroidDefaultsResponse(ctx, invalid);
|
|
1028
|
+
}
|
|
972
1029
|
const guard = binding === "none" ? null : stalePlanGuard(
|
|
973
1030
|
requestedClient,
|
|
974
1031
|
ctx,
|
|
@@ -976,6 +1033,7 @@ export async function handleIntegrationRoutes(ctx: ManagementContext): Promise<R
|
|
|
976
1033
|
{ operation: binding.operation },
|
|
977
1034
|
binding.fingerprint,
|
|
978
1035
|
boundToggle === null ? null : boundToggle.identity,
|
|
1036
|
+
droidDefaults,
|
|
979
1037
|
);
|
|
980
1038
|
const result = await runIntegrationMutationFlight(
|
|
981
1039
|
requestedClient,
|
|
@@ -86,7 +86,7 @@ function port(value: unknown): value is number {
|
|
|
86
86
|
return typeof value === "number" && Number.isInteger(value) && value >= 1 && value <= 65535;
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
-
/** A
|
|
89
|
+
/** A GUI session redeemed from an operator-created, one-use pairing grant. */
|
|
90
90
|
function pairedSession(ctx: ManagementContext): boolean {
|
|
91
91
|
return ctx.principal === "gui-session"
|
|
92
92
|
&& ctx.sessionControl?.isPaired(ctx.req, ctx.config) === true;
|
|
@@ -96,9 +96,8 @@ function pairedSession(ctx: ManagementContext): boolean {
|
|
|
96
96
|
* A paired session, or on a standalone runtime the current loopback-issued session that reached
|
|
97
97
|
* the public listener bound to a loopback hostname. The loopback bootstrap mints that session
|
|
98
98
|
* without a credential, so this is casual-path protection like POST /api/github/star, not a
|
|
99
|
-
* secret-backed boundary like the admin token. Hubs keep the paired-only rule.
|
|
100
|
-
*
|
|
101
|
-
* dashboard warns first that the restart briefly interrupts running Codex turns.
|
|
99
|
+
* secret-backed boundary like the admin token. Hubs keep the paired-only rule. Durable operations
|
|
100
|
+
* that require operator approval, such as joining this machine as a Child, use pairedSession.
|
|
102
101
|
*/
|
|
103
102
|
function dashboardSession(ctx: ManagementContext): boolean {
|
|
104
103
|
if (pairedSession(ctx)) return true;
|
|
@@ -113,9 +112,10 @@ function adminLoopback(ctx: ManagementContext): boolean {
|
|
|
113
112
|
return ctx.principal === "admin-token" && ctx.trustedLoopbackIngress;
|
|
114
113
|
}
|
|
115
114
|
|
|
116
|
-
function auth(ctx: ManagementContext, kind: "dashboard" | "admin" | "either"): Response | null {
|
|
115
|
+
function auth(ctx: ManagementContext, kind: "dashboard" | "paired" | "admin" | "either"): Response | null {
|
|
117
116
|
if (ctx.guiSessionIssuance === "tailscale-identity") return fail("tailscale_session_refused", "Tailscale identity sessions cannot use link routes.", 403);
|
|
118
117
|
const allowed = kind === "dashboard" ? dashboardSession(ctx)
|
|
118
|
+
: kind === "paired" ? pairedSession(ctx)
|
|
119
119
|
: kind === "admin" ? adminLoopback(ctx)
|
|
120
120
|
: dashboardSession(ctx) || adminLoopback(ctx);
|
|
121
121
|
return allowed ? null : fail("forbidden", "The required link authorization was not present.", 403);
|
|
@@ -131,9 +131,14 @@ function joinPortMatches(ctx: ManagementContext): boolean {
|
|
|
131
131
|
return live !== undefined && live === ctx.config.port;
|
|
132
132
|
}
|
|
133
133
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
134
|
+
type LinkJoinDenied = "pairing_required" | "standalone_required" | "join_port_mismatch";
|
|
135
|
+
|
|
136
|
+
/** Read-only explanation of the join gates, in the same order as `POST /api/link/join`. */
|
|
137
|
+
function joinDenied(ctx: ManagementContext): LinkJoinDenied | null {
|
|
138
|
+
if (!pairedSession(ctx)) return "pairing_required";
|
|
139
|
+
if ((ctx.config.runtimeRole ?? "standalone") !== "standalone") return "standalone_required";
|
|
140
|
+
if (!joinPortMatches(ctx)) return "join_port_mismatch";
|
|
141
|
+
return null;
|
|
137
142
|
}
|
|
138
143
|
|
|
139
144
|
function runnerFor(ctx: ManagementContext): SshRunner {
|
|
@@ -549,9 +554,9 @@ export async function handleLinkRoutes(ctx: ManagementContext, suppliedState?: L
|
|
|
549
554
|
if (!isLinkPath(path)) return null;
|
|
550
555
|
if (ctx.guiSessionIssuance === "tailscale-identity") return fail("tailscale_session_refused", "Tailscale identity sessions cannot use link routes.", 403);
|
|
551
556
|
if (url.pathname === "/api/link/join" && req.method === "POST") {
|
|
552
|
-
//
|
|
553
|
-
// is
|
|
554
|
-
const denied = auth(ctx, "
|
|
557
|
+
// Joining durably redirects local client traffic, so a credentiallessly bootstrapped loopback
|
|
558
|
+
// session is insufficient. Every refusal below runs before link state and before any SSH.
|
|
559
|
+
const denied = auth(ctx, "paired");
|
|
555
560
|
if (denied) return denied;
|
|
556
561
|
if ((ctx.config.runtimeRole ?? "standalone") !== "standalone") return fail("standalone_required", "Client initiated links require standalone runtime mode.", 409);
|
|
557
562
|
if (!joinPortMatches(ctx)) return fail("join_port_mismatch", "OpenCodex is not running on its configured port, so it cannot restart as a Child.", 409);
|
|
@@ -578,9 +583,11 @@ export async function handleLinkRoutes(ctx: ManagementContext, suppliedState?: L
|
|
|
578
583
|
const failure = state.compensationFailures?.get(store.links[0].id);
|
|
579
584
|
if (failure) Object.assign(dto.child, { state: "failed" as const, since: failure.since, reason: failure.reason });
|
|
580
585
|
}
|
|
581
|
-
// A dashboard session also learns
|
|
586
|
+
// A dashboard session also learns why it cannot join as a Child. The admin-token answer stays
|
|
582
587
|
// the exact K16 document that `ocx link status` validates key by key.
|
|
583
|
-
const
|
|
588
|
+
const denial = ctx.principal === "gui-session" ? joinDenied(ctx) : null;
|
|
589
|
+
const body: LinkStatusDto & { joinAvailable?: boolean; joinDenied?: LinkJoinDenied | null } = ctx.principal === "gui-session"
|
|
590
|
+
? { ...dto, joinAvailable: denial === null, joinDenied: denial } : dto;
|
|
584
591
|
return Response.json(body, { headers: { "cache-control": "no-store" } });
|
|
585
592
|
}
|
|
586
593
|
if (url.pathname === "/api/link/candidates" && req.method === "GET") {
|
|
@@ -167,7 +167,7 @@ import type {
|
|
|
167
167
|
|
|
168
168
|
import { isPlainRecord, parseDebugLogQuery, tokPerSecondResult, unavailableCostReason, costResult, requestLogDto, stripRegistryOnlyStaticHeaders, fetchAllModels } from "./shared";
|
|
169
169
|
import type { MetricUnavailableReason, TokPerSecondResult, CostEstimateReason, CostResult, MetricSource } from "./shared";
|
|
170
|
-
import type
|
|
170
|
+
import { managementInferencePort, type ManagementContext } from "./context";
|
|
171
171
|
import { effectiveModelReasoningEfforts, inheritedModelReasoningEfforts, listManagementModelRows, loadExportModels } from "./model-rows";
|
|
172
172
|
import { initialModelSelectionPending } from "../../providers/initial-model-selection";
|
|
173
173
|
import { readManagementJsonBody, rethrowManagementBodyTooLarge } from "./body";
|
|
@@ -204,7 +204,7 @@ export async function handleModelRoutes(ctx: ManagementContext): Promise<Respons
|
|
|
204
204
|
?? (await import("../../integrations/catalog-refresh")).refreshOwnedCatalogIntegrations;
|
|
205
205
|
const clientIntegrations = await refresh({
|
|
206
206
|
config,
|
|
207
|
-
port:
|
|
207
|
+
port: managementInferencePort(ctx),
|
|
208
208
|
models: () => loadExportModels(config),
|
|
209
209
|
});
|
|
210
210
|
return { catalogRefresh, clientIntegrations };
|
|
@@ -562,7 +562,7 @@ export async function handleModelRoutes(ctx: ManagementContext): Promise<Respons
|
|
|
562
562
|
reason: "non_loopback",
|
|
563
563
|
}, 400, req, config);
|
|
564
564
|
}
|
|
565
|
-
const baseUrl = opencodeProxyBaseUrl(
|
|
565
|
+
const baseUrl = opencodeProxyBaseUrl(managementInferencePort(ctx), config.hostname, config);
|
|
566
566
|
let models: ExportModel[];
|
|
567
567
|
try {
|
|
568
568
|
// The ONE loader every export surface uses. It carries the visibility
|