@bitkyc08/opencodex 2.80.0 → 2.82.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/README.md +3 -3
- package/bin/ocx.mjs +166 -73
- package/gui/dist/assets/App-Ds_qL7o8.js +54 -0
- package/gui/dist/assets/Tray-BqHF5Trd.js +1 -0
- package/gui/dist/assets/{index-Brk6F3fn.css → index-BAbcj51J.css} +1 -1
- package/gui/dist/assets/index-D3FBCNiP.js +94 -0
- package/gui/dist/assets/quota-summary-DTBC8n7-.js +1 -0
- package/gui/dist/index.html +2 -2
- package/gui/dist/provider-icons/claude-green.svg +1 -0
- package/package.json +3 -2
- package/src/adapters/anthropic-model-contract.ts +8 -2
- package/src/adapters/anthropic.ts +8 -3
- package/src/adapters/codebuddy/mcp-server.ts +2 -25
- package/src/adapters/codebuddy/tool-bridge.ts +1 -7
- package/src/adapters/coding-agent/mcp-server.ts +33 -0
- package/src/adapters/coding-agent/protocol.ts +122 -17
- package/src/adapters/coding-agent/tool-catalog.ts +75 -0
- package/src/adapters/coding-agent/turn.ts +101 -44
- package/src/adapters/cursor/catalog.ts +8 -0
- package/src/adapters/cursor/effort-map.ts +2 -0
- package/src/adapters/devin/live-models.ts +2 -0
- package/src/adapters/kiro-retry.ts +9 -4
- package/src/adapters/openai-chat/messages.ts +7 -6
- package/src/adapters/openai-responses/compaction-search-history.ts +109 -0
- package/src/adapters/openai-responses/passthrough.ts +9 -4
- package/src/adapters/openai-responses/tool-output-recovery.ts +20 -0
- package/src/adapters/physical-send.ts +8 -5
- package/src/adapters/qoder/adapter.ts +41 -6
- package/src/adapters/qoder/scaffold-guard.ts +2 -3
- package/src/adapters/zed.ts +16 -1
- package/src/claude/agents-inject.ts +14 -3
- package/src/claude/context-windows.ts +19 -17
- package/src/claude/desktop-3p.ts +88 -12
- package/src/claude/desktop-profile.ts +2 -1
- package/src/claude/gateway-cache.ts +18 -1
- package/src/claude/inbound-cache-stabilize.ts +4 -9
- package/src/claude/inbound.ts +78 -3
- package/src/claude/intercept/cli-catalog.ts +3 -0
- package/src/claude/intercept/model-bindings.ts +1 -0
- package/src/claude/intercept/picker-ca-persistence.ts +7 -6
- package/src/claude/intercept/picker-listener.ts +71 -4
- 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-anthropic-threshold.ts +14 -5
- package/src/cli/account-api.ts +3 -1
- package/src/cli/account-extended.ts +13 -9
- package/src/cli/account-policy-dto.ts +1 -1
- package/src/cli/account-policy.ts +13 -4
- package/src/cli/account.ts +3 -3
- package/src/cli/agent.ts +9 -5
- package/src/cli/aside-profile-recovery.ts +33 -0
- package/src/cli/capabilities-accounts.ts +2 -2
- package/src/cli/capabilities-agents-routing.ts +24 -0
- package/src/cli/capabilities-base.ts +5 -5
- package/src/cli/capabilities-integrations.ts +18 -2
- package/src/cli/cli-path-diagnostics.ts +132 -0
- package/src/cli/codex-shim-autorestore.ts +3 -0
- package/src/cli/desktop-runtime-guidance.ts +65 -0
- package/src/cli/dispatch.ts +15 -1
- package/src/cli/doctor.ts +16 -3
- package/src/cli/help.ts +4 -1
- package/src/cli/index.ts +25 -14
- package/src/cli/integration-aside-sync.ts +2 -1
- package/src/cli/integration-plan-dto.ts +1 -0
- package/src/cli/integration-preview.ts +8 -1
- package/src/cli/integrations.ts +48 -6
- package/src/cli/message-args.ts +34 -0
- package/src/cli/message-command.ts +43 -0
- package/src/cli/message-runtime.ts +6 -0
- package/src/cli/observe.ts +3 -1
- package/src/cli/provider.ts +29 -6
- package/src/cli/registry.ts +24 -2
- package/src/cli/resolve.ts +25 -2
- 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/commandcode.ts +131 -0
- package/src/clients/config-export/contracts.ts +12 -0
- package/src/clients/config-export.ts +64 -1
- package/src/clients/lazycodex.ts +1 -1
- package/src/clients/omo-role-models.ts +32 -7
- package/src/codex/auth-api/account-list.ts +11 -3
- package/src/codex/auth-api/account-selection.ts +107 -0
- package/src/codex/auth-api/main-account-probe.ts +25 -8
- package/src/codex/auth-api/pool-mode-gate.ts +33 -9
- package/src/codex/auth-api/pool-quota-probe.ts +15 -1
- package/src/codex/auth-api/routes.ts +11 -49
- package/src/codex/auth-context.ts +20 -1
- package/src/codex/autostart-health.ts +12 -3
- package/src/codex/catalog/auto-review.ts +13 -6
- package/src/codex/catalog/build-entries.ts +33 -7
- package/src/codex/catalog/bundled.ts +155 -26
- package/src/codex/catalog/combo-member.ts +3 -2
- package/src/codex/catalog/control-plane.ts +45 -0
- package/src/codex/catalog/effort.ts +37 -6
- package/src/codex/catalog/gather-capture.ts +10 -3
- package/src/codex/catalog/model-hints.ts +15 -8
- package/src/codex/catalog/model-visibility.ts +3 -1
- package/src/codex/catalog/operator-tiers.ts +38 -0
- package/src/codex/catalog/parsing.ts +2 -1
- package/src/codex/catalog/provider-models.ts +58 -44
- 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/control-plane-models.ts +6 -0
- package/src/codex/convergence.ts +2 -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/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-decision-contract.ts +25 -2
- package/src/combos/jev-service-exchange.ts +281 -0
- package/src/combos/jev.ts +34 -219
- package/src/combos/types.ts +1 -1
- package/src/config/atomic-write.ts +1 -1
- package/src/config/codex-ws-reuse-setting.ts +10 -0
- package/src/config/diagnostics.ts +27 -16
- package/src/config/live-reconcile.ts +17 -0
- package/src/config/load-degrade.ts +42 -0
- package/src/config/rebase-provenance.ts +11 -0
- package/src/config/schema/anthropic-account-pool.ts +143 -0
- package/src/config/schema/config-schema.ts +11 -1
- package/src/config/schema/leaf-validators.ts +4 -0
- package/src/config.ts +2 -0
- package/src/generated/compatibility-version.json +566 -298
- package/src/generated/model-metadata.ts +7 -6
- package/src/grok/inject.ts +50 -4
- package/src/grok/sync.ts +2 -1
- package/src/images/loop.ts +30 -11
- package/src/integrations/aside-profiles.ts +10 -3
- package/src/integrations/catalog-refresh.ts +19 -3
- package/src/integrations/mutation-plan.ts +7 -0
- package/src/integrations/registry.ts +8 -0
- package/src/integrations/state.ts +9 -2
- package/src/integrations/writer.ts +17 -5
- 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/lab-live-route-production.ts +9 -1
- package/src/lib/request-execution-budget.ts +244 -58
- package/src/lib/spend-pool-alias-validation.ts +79 -0
- package/src/lib/spend-pool-continuity.ts +34 -0
- package/src/lib/spend-reservation-ledger.ts +412 -50
- package/src/lib/state-store-registrations.ts +20 -3
- 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/upstream-retry.ts +17 -5
- 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/lib/workflow-budget.ts +53 -11
- package/src/messaging/budget.ts +45 -0
- package/src/messaging/discovery.ts +57 -0
- package/src/messaging/envelope.ts +49 -0
- package/src/messaging/input.ts +37 -0
- package/src/messaging/rpc.ts +196 -0
- package/src/messaging/send.ts +58 -0
- package/src/messaging/socket.ts +72 -0
- package/src/messaging/types.ts +29 -0
- package/src/oauth/anthropic-account-refusal.ts +90 -18
- package/src/oauth/anthropic-account-threshold.ts +8 -2
- package/src/oauth/anthropic-continuity.ts +15 -1
- package/src/oauth/anthropic-model-quota.ts +120 -48
- package/src/oauth/anthropic-model-routes.ts +11 -3
- package/src/oauth/anthropic-oauth-definitions.ts +98 -0
- package/src/oauth/anthropic-pool-config.ts +30 -0
- package/src/oauth/anthropic-rate-limit-policy.ts +64 -9
- package/src/oauth/anthropic-routing.ts +1202 -866
- package/src/oauth/anthropic-send-ownership.ts +41 -0
- package/src/oauth/anthropic.ts +4 -1
- package/src/oauth/chatgpt.ts +113 -41
- package/src/oauth/generic-account-failover.ts +3 -1
- package/src/oauth/health.ts +8 -6
- package/src/oauth/index.ts +96 -175
- package/src/oauth/kiro.ts +22 -9
- package/src/oauth/login-cli.ts +2 -0
- package/src/oauth/model-discovery-auth.ts +43 -0
- package/src/oauth/pool-kernel.ts +11 -5
- package/src/oauth/pool-settings-capability.ts +5 -3
- package/src/oauth/store-anthropic-instance.ts +87 -0
- package/src/oauth/store.ts +68 -10
- package/src/oauth/token-guardian.ts +2 -0
- package/src/oauth/types.ts +2 -0
- package/src/protocols/plan-snapshot.ts +5 -0
- package/src/protocols/settings.ts +20 -5
- package/src/providers/anthropic-instance-id.ts +47 -0
- package/src/providers/anthropic-instance.ts +48 -0
- package/src/providers/anthropic-reset-grant-ledger.ts +11 -1
- package/src/providers/azure-model-metadata.ts +115 -0
- package/src/providers/derive.ts +23 -0
- package/src/providers/kiro-models.ts +3 -0
- package/src/providers/label.ts +1 -1
- package/src/providers/model-rename-fields.ts +2 -0
- package/src/providers/quota/account-cache.ts +37 -11
- package/src/providers/quota/anthropic-account-quota.ts +93 -0
- package/src/providers/quota/anthropic-cooldown-recovery.ts +222 -111
- package/src/providers/quota/report-cache.ts +24 -1
- package/src/providers/quota/vendor-probes-key.ts +127 -17
- package/src/providers/quota/vendor-probes-oauth.ts +46 -14
- package/src/providers/quota.ts +23 -33
- package/src/providers/registry/entries-core.ts +35 -30
- package/src/providers/registry/model-ids.ts +1 -0
- package/src/providers/registry/model-seeds.ts +7 -4
- package/src/providers/registry/types.ts +3 -0
- package/src/providers/registry.ts +3 -3
- package/src/responses/reasoning-envelope.ts +29 -1
- package/src/responses/reasoning-replay-cache.ts +17 -1
- package/src/responses/spill-store.ts +20 -3
- package/src/responses/state/metrics.ts +95 -0
- package/src/responses/state/snapshot-codec.ts +6 -0
- package/src/responses/state/spill-failure.ts +12 -0
- package/src/responses/state/spill-queue.ts +13 -5
- package/src/responses/state/unforced-store-false.ts +55 -0
- package/src/responses/state.ts +68 -76
- package/src/responses/task-input.ts +17 -0
- package/src/router.ts +28 -4
- package/src/routing/compatibility/assemble.ts +1 -1
- package/src/routing/quota.ts +7 -3
- package/src/server/auth-cors.ts +5 -0
- package/src/server/background-lifecycle.ts +27 -0
- package/src/server/chat-completions.ts +7 -1
- package/src/server/chat-native.ts +64 -28
- package/src/server/claude-messages.ts +59 -8
- package/src/server/gui-static.ts +38 -4
- package/src/server/inbound-body-admission.ts +1 -0
- package/src/server/index/claude-intercept-lifecycle.ts +3 -1
- package/src/server/index/serve-options.ts +10 -2
- package/src/server/index/spend-ledger-lifecycle.ts +12 -5
- package/src/server/index/startup-warnings.ts +19 -0
- package/src/server/index.ts +5 -2
- package/src/server/inference/context.ts +1 -1
- package/src/server/lifecycle.ts +1 -1
- package/src/server/management/agent-settings-routes.ts +123 -38
- package/src/server/management/anthropic-account-threshold.ts +12 -9
- package/src/server/management/anthropic-pool-settings.ts +72 -0
- package/src/server/management/anthropic-reset-grant-routes.ts +98 -39
- 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 +85 -14
- package/src/server/management/context.ts +2 -2
- package/src/server/management/decision-routes.ts +2 -1
- package/src/server/management/integration-routes.ts +25 -2
- package/src/server/management/oauth-account-routes.ts +79 -98
- package/src/server/management/provider-instance-ownership.ts +44 -0
- package/src/server/management/provider-routes.ts +21 -28
- package/src/server/management/route-registry.ts +3 -1
- package/src/server/management/routing-profile-routes.ts +4 -4
- package/src/server/management/shared.ts +3 -3
- package/src/server/management/system-restart.ts +44 -14
- package/src/server/management/system-routes.ts +24 -2
- package/src/server/management/vision-sidecar-options.ts +9 -6
- package/src/server/management/web-search-sidecar-options.ts +51 -2
- package/src/server/management-auth.ts +6 -0
- package/src/server/messages-native-eligibility.ts +3 -2
- package/src/server/messages-native-oauth.ts +91 -45
- package/src/server/messages-native-selector.ts +39 -0
- package/src/server/messages-native.ts +133 -38
- package/src/server/messages-response-headers.ts +14 -0
- package/src/server/request-log-conversation.ts +48 -5
- package/src/server/request-log.ts +36 -2
- package/src/server/responses/adapter-continuation.ts +31 -19
- package/src/server/responses/adapter-dispatch.ts +75 -24
- package/src/server/responses/antigravity-validation-refusal.ts +20 -6
- 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 +25 -8
- package/src/server/responses/compaction-recovery.ts +1 -1
- package/src/server/responses/core-codex-account.ts +22 -1
- package/src/server/responses/core-combo-native.ts +24 -4
- package/src/server/responses/core-combo.ts +38 -25
- package/src/server/responses/core-lifetime.ts +3 -36
- package/src/server/responses/core-normalize.ts +1 -0
- package/src/server/responses/core-options.ts +8 -1
- package/src/server/responses/core-replay.ts +78 -0
- package/src/server/responses/core.ts +3 -3
- package/src/server/responses/fetch-helpers.ts +26 -3
- package/src/server/responses/passthrough-delivery.ts +30 -4
- package/src/server/responses/passthrough-dispatch.ts +39 -9
- package/src/server/responses/request-prepare.ts +41 -5
- package/src/server/responses/request-send-budget.ts +150 -50
- package/src/server/responses/request-spend.ts +325 -33
- package/src/server/responses/request-transport.ts +132 -52
- package/src/server/responses/response-effects.ts +1 -0
- package/src/server/responses/run-turn-execution.ts +13 -3
- package/src/server/responses/sidecar-execution.ts +113 -22
- package/src/server/responses/sidecar-send-budget.ts +58 -0
- package/src/server/responses/ws-upstream.ts +19 -6
- package/src/server/responses-undeclared-tool-guard.ts +5 -1
- package/src/server/startup-health-cache.ts +2 -1
- package/src/server/workflow-refusal.ts +33 -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/ownership-mutation-lease.d.mts +1 -1
- package/src/service/ownership-mutation-lease.mjs +5 -7
- 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/sidecar/anthropic-binding.ts +79 -0
- package/src/sidecar/auth.ts +76 -2
- 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/anthropic-account-pool.ts +33 -0
- package/src/types/config.ts +33 -28
- package/src/types/provider.ts +5 -0
- package/src/types/request.ts +5 -0
- package/src/types/tools.ts +7 -0
- package/src/types/wire.ts +1 -0
- package/src/types.ts +4 -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/cost.ts +3 -2
- package/src/usage/expected-prices.ts +66 -14
- package/src/vision/anthropic-describe.ts +16 -3
- package/src/vision/index.ts +3 -0
- package/src/vision/plan.ts +19 -6
- package/src/web-search/alpha-search.ts +7 -1
- package/src/web-search/anthropic-executor.ts +16 -4
- package/src/web-search/index.ts +7 -3
- package/src/web-search/loop.ts +32 -7
- package/src/web-search/passthrough-bridge.ts +4 -1
- package/src/web-search/sidecar-providers.ts +4 -3
- package/gui/dist/assets/App-MbDVfUeC.js +0 -54
- package/gui/dist/assets/Tray-CzkxK82A.js +0 -1
- package/gui/dist/assets/index-UJt6npXk.js +0 -94
- package/gui/dist/assets/quota-summary-DU3sD6Z-.js +0 -1
package/src/service/repair.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { getConfigDir } from "../config";
|
|
3
|
+
import { durableBunRuntime, type DurableBunRuntime } from "../lib/bun-runtime";
|
|
4
|
+
import { assertSelectedRuntimeWritable, type RuntimePreflightDeps } from "../lib/bun-runtime-preflight";
|
|
1
5
|
import { randomUUID } from "node:crypto";
|
|
2
6
|
import { defaultWinswEntry, installWinswService } from "../lib/winsw";
|
|
3
7
|
import { resolveWindowsTaskDiagnosticUserId, diagnoseService } from "./diagnostics";
|
|
@@ -14,6 +18,7 @@ import type { WindowsSchedulerTaskProbe } from "./windows-scheduler";
|
|
|
14
18
|
import { taskXmlSection, taskXmlWithoutCommentsAndCdata, taskXmlElementCount, taskXmlOptionalValueEquals, windowsTaskRegistrationOwnedByAttempt, windowsTaskHasSessionRecoveryTriggers, windowsTaskRegistrationHealthy, windowsTaskRegistrationRefreshableLegacy } from "./windows-taskxml";
|
|
15
19
|
import type { ExpectedWindowsTaskUserId } from "./windows-taskxml";
|
|
16
20
|
import { win32 } from "node:path";
|
|
21
|
+
import { assertNoDesktopSupervision, createSupervisionLatch, type SupervisionInspector, type SupervisionLatch } from "./desktop-command-guard";
|
|
17
22
|
|
|
18
23
|
/**
|
|
19
24
|
* The two CLI verbs `repairService` serves. They differ on ONE platform and ONE case: a
|
|
@@ -22,16 +27,18 @@ import { win32 } from "node:path";
|
|
|
22
27
|
*/
|
|
23
28
|
export type ServiceRepairVerb = "repair" | "restart";
|
|
24
29
|
|
|
25
|
-
export interface RepairServiceDeps {
|
|
30
|
+
export interface RepairServiceDeps extends RuntimePreflightDeps {
|
|
31
|
+
inspectSupervision?: SupervisionInspector;
|
|
32
|
+
supervisionLatch?: SupervisionLatch;
|
|
26
33
|
diagnose?: () => ServiceDiagnostic;
|
|
27
34
|
assertEnv?: () => void;
|
|
28
35
|
assertAuth?: () => void;
|
|
29
|
-
writeSchedulerAssets?: () => void;
|
|
36
|
+
writeSchedulerAssets?: (runtime: DurableBunRuntime) => void;
|
|
30
37
|
stopScheduler?: () => void;
|
|
31
38
|
startScheduler?: () => void;
|
|
32
|
-
writeSchedulerState?: () => void;
|
|
33
|
-
writeNativeState?: () => void;
|
|
34
|
-
repairNative?: () => void | Promise<void>;
|
|
39
|
+
writeSchedulerState?: (runtime: DurableBunRuntime) => void;
|
|
40
|
+
writeNativeState?: (runtime: DurableBunRuntime) => void;
|
|
41
|
+
repairNative?: (runtime: DurableBunRuntime) => void | Promise<void>;
|
|
35
42
|
repairLaunchd?: () => LaunchdInstallOutcome | void;
|
|
36
43
|
repairSystemd?: () => void;
|
|
37
44
|
/** Restarts a launchd job the install path deliberately left alone. `restart` only. */
|
|
@@ -148,6 +155,8 @@ export async function assertSchedulerRegistrationBeforeStart(
|
|
|
148
155
|
* macOS/Linux: re-run the user-level install/reload path.
|
|
149
156
|
*/
|
|
150
157
|
export async function repairService(deps: RepairServiceDeps = {}): Promise<void> {
|
|
158
|
+
const latch = deps.supervisionLatch ?? createSupervisionLatch();
|
|
159
|
+
assertNoDesktopSupervision(deps.inspectSupervision, latch);
|
|
151
160
|
const diagnose = deps.diagnose ?? diagnoseService;
|
|
152
161
|
const platform = deps.platform ?? process.platform;
|
|
153
162
|
const diag = diagnose();
|
|
@@ -176,14 +185,18 @@ export async function repairService(deps: RepairServiceDeps = {}): Promise<void>
|
|
|
176
185
|
|
|
177
186
|
(deps.assertEnv ?? assertServiceEnvironmentMatchesInstall)();
|
|
178
187
|
(deps.assertAuth ?? assertServiceAuthEnvironment)();
|
|
188
|
+
const runtime = Object.freeze({ ...(deps.selectRuntime ?? durableBunRuntime)() });
|
|
189
|
+
const configDir = (deps.configDir ?? getConfigDir)();
|
|
190
|
+
(deps.assertRuntimeWritable ?? assertSelectedRuntimeWritable)(runtime, configDir, { platform, rootWasAbsent: !existsSync(configDir) });
|
|
179
191
|
|
|
180
192
|
const temporaryLauncher = (deps.launcherPathDiagnostic ?? (() => serviceLauncherPathDiagnostic(undefined, platform, true)))();
|
|
181
193
|
if (temporaryLauncher) console.warn(`⚠️ ${temporaryLauncher}`);
|
|
182
194
|
|
|
183
195
|
if (platform === "win32") {
|
|
184
196
|
if (diag.backend === "native") {
|
|
185
|
-
await (deps.repairNative ?? (() => installWinswService(defaultWinswEntry(serviceSourceDir))))();
|
|
186
|
-
(deps.
|
|
197
|
+
await (deps.repairNative ?? ((selected: DurableBunRuntime) => installWinswService(defaultWinswEntry(serviceSourceDir, selected))))(runtime);
|
|
198
|
+
assertNoDesktopSupervision(deps.inspectSupervision, latch);
|
|
199
|
+
(deps.writeNativeState ?? ((selected: DurableBunRuntime) => writeServiceInstallState("native", undefined, {}, selected)))(runtime);
|
|
187
200
|
return;
|
|
188
201
|
}
|
|
189
202
|
const readSchedulerXml = deps.readSchedulerXml ?? statusWindowsXml;
|
|
@@ -243,7 +256,7 @@ export async function repairService(deps: RepairServiceDeps = {}): Promise<void>
|
|
|
243
256
|
);
|
|
244
257
|
}
|
|
245
258
|
try { (deps.stopScheduler ?? stopWindows)(); } catch { /* not running */ }
|
|
246
|
-
(deps.writeSchedulerAssets ?? writeWindowsSchedulerAssets)();
|
|
259
|
+
(deps.writeSchedulerAssets ?? writeWindowsSchedulerAssets)(runtime);
|
|
247
260
|
// Rewriting the on-disk assets does not touch the definition Task Scheduler holds, so a
|
|
248
261
|
// task registered by an older version keeps its old triggers forever: status reports it
|
|
249
262
|
// stale, tells the user to run repair, and repair changes nothing it complains about.
|
|
@@ -311,6 +324,7 @@ export async function repairService(deps: RepairServiceDeps = {}): Promise<void>
|
|
|
311
324
|
}
|
|
312
325
|
if (probe.status === "absent") {
|
|
313
326
|
try {
|
|
327
|
+
assertNoDesktopSupervision(deps.inspectSupervision, latch);
|
|
314
328
|
await (deps.restoreSchedulerIfAbsent ?? restoreWindowsSchedulerTaskIfAbsent)(registeredXml);
|
|
315
329
|
restartExpectedXml = registeredXml;
|
|
316
330
|
} catch (error) {
|
|
@@ -333,6 +347,7 @@ export async function repairService(deps: RepairServiceDeps = {}): Promise<void>
|
|
|
333
347
|
"The Task Scheduler registration changed again before restart; the newer definition was preserved and not started.",
|
|
334
348
|
"Task Scheduler state remained unreadable before restart; the registration was preserved and not started.",
|
|
335
349
|
);
|
|
350
|
+
assertNoDesktopSupervision(deps.inspectSupervision, latch);
|
|
336
351
|
(deps.startScheduler ?? startWindows)();
|
|
337
352
|
} catch (error) {
|
|
338
353
|
recoveryErrors.push(error);
|
|
@@ -358,8 +373,9 @@ export async function repairService(deps: RepairServiceDeps = {}): Promise<void>
|
|
|
358
373
|
"Task Scheduler registration changed before restart; the current definition was preserved and not started.",
|
|
359
374
|
"Task Scheduler registration became unreadable before restart; it was preserved and not started.",
|
|
360
375
|
);
|
|
376
|
+
assertNoDesktopSupervision(deps.inspectSupervision, latch);
|
|
361
377
|
(deps.startScheduler ?? startWindows)();
|
|
362
|
-
(deps.writeSchedulerState ?? (() => writeServiceInstallState("scheduler")))();
|
|
378
|
+
(deps.writeSchedulerState ?? ((selected: DurableBunRuntime) => writeServiceInstallState("scheduler", undefined, {}, selected)))(runtime);
|
|
363
379
|
return;
|
|
364
380
|
}
|
|
365
381
|
if (platform === "darwin") {
|
package/src/service/state.ts
CHANGED
|
@@ -360,8 +360,8 @@ export function parseServiceInstallState(value: unknown): ServiceInstallState |
|
|
|
360
360
|
* it describes the install that just ran. {@link ServiceInstallState.ownership} deliberately
|
|
361
361
|
* is not part of it.
|
|
362
362
|
*/
|
|
363
|
-
function installProvenanceRecord(backend: ServiceBackend, launcherPath
|
|
364
|
-
const { bun, cli } = cliEntry();
|
|
363
|
+
function installProvenanceRecord(backend: ServiceBackend, launcherPath: string | null | undefined, runtime: DurableBunRuntime): ServiceInstallState {
|
|
364
|
+
const { bun, cli } = cliEntry(runtime);
|
|
365
365
|
const codexHome = currentCodexHome();
|
|
366
366
|
return {
|
|
367
367
|
version: 2,
|
|
@@ -391,9 +391,10 @@ export function writeServiceInstallState(
|
|
|
391
391
|
backend: ServiceBackend = "scheduler",
|
|
392
392
|
launcherPath?: string | null,
|
|
393
393
|
deps: ServiceStateSwapDeps = {},
|
|
394
|
+
runtime: DurableBunRuntime = durableBunRuntime(),
|
|
394
395
|
): void {
|
|
395
396
|
swapServiceInstallState(current => ({
|
|
396
|
-
...installProvenanceRecord(backend, launcherPath),
|
|
397
|
+
...installProvenanceRecord(backend, launcherPath, runtime),
|
|
397
398
|
...preservedConsent(current),
|
|
398
399
|
}), deps);
|
|
399
400
|
}
|
package/src/service/systemd.ts
CHANGED
|
@@ -102,13 +102,22 @@ WantedBy=default.target
|
|
|
102
102
|
|
|
103
103
|
/** The per-user runtime dir systemd creates (holds the user-bus socket), or null. */
|
|
104
104
|
function userRuntimeDir(): string | null {
|
|
105
|
-
const
|
|
106
|
-
if (
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
105
|
+
const env = { ...process.env };
|
|
106
|
+
if (env.XDG_RUNTIME_DIR && !existsSync(env.XDG_RUNTIME_DIR)) delete env.XDG_RUNTIME_DIR;
|
|
107
|
+
return systemdUserBusEnvironment(env).XDG_RUNTIME_DIR || null;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** Return explicit SSH user-bus discovery without changing the caller's environment. */
|
|
111
|
+
export function systemdUserBusEnvironment(environment: NodeJS.ProcessEnv = process.env, deps: {
|
|
112
|
+
uid?: number; exists?: (path: string) => boolean;
|
|
113
|
+
} = {}): NodeJS.ProcessEnv {
|
|
114
|
+
const env = { ...environment };
|
|
115
|
+
if (!env.XDG_RUNTIME_DIR) {
|
|
116
|
+
const uid = deps.uid ?? process.getuid?.();
|
|
117
|
+
const candidate = uid === undefined ? null : `/run/user/${uid}`;
|
|
118
|
+
if (candidate && (deps.exists ?? existsSync)(candidate)) env.XDG_RUNTIME_DIR = candidate;
|
|
110
119
|
}
|
|
111
|
-
return
|
|
120
|
+
return env;
|
|
112
121
|
}
|
|
113
122
|
|
|
114
123
|
/**
|
|
@@ -119,8 +128,8 @@ function userRuntimeDir(): string | null {
|
|
|
119
128
|
*/
|
|
120
129
|
function ensureUserBusEnv(): void {
|
|
121
130
|
if (process.env.XDG_RUNTIME_DIR) return;
|
|
122
|
-
const
|
|
123
|
-
if (
|
|
131
|
+
const env = systemdUserBusEnvironment();
|
|
132
|
+
if (env.XDG_RUNTIME_DIR) process.env.XDG_RUNTIME_DIR = env.XDG_RUNTIME_DIR;
|
|
124
133
|
}
|
|
125
134
|
|
|
126
135
|
export function isSystemd(): boolean {
|
|
@@ -3,6 +3,8 @@ import { createHash } from "node:crypto";
|
|
|
3
3
|
import { win32 } from "node:path";
|
|
4
4
|
import { winswXmlPath } from "../lib/winsw";
|
|
5
5
|
import { hardenSecretPath } from "../lib/windows-secret-acl";
|
|
6
|
+
import { durableBunRuntime, type DurableBunRuntime } from "../lib/bun-runtime";
|
|
7
|
+
import { assertSelectedRuntimeWritable, type RuntimePreflightDeps } from "../lib/bun-runtime-preflight";
|
|
6
8
|
import { parseBakedListenPort } from "./health";
|
|
7
9
|
import { windowsServiceScriptPath } from "./state";
|
|
8
10
|
import { execFileSync } from "node:child_process";
|
|
@@ -18,7 +20,7 @@ import { killWindowsSchedulerWrappers } from "../lib/windows-service-wrappers";
|
|
|
18
20
|
import { isTestHomeGuardArmed } from "../lib/test-home-guard";
|
|
19
21
|
import { writeServiceApiTokenFile } from "./guards";
|
|
20
22
|
import type { ServiceStopOutcome } from "./orchestration";
|
|
21
|
-
import { TASK, windowsLauncherVbsPath, windowsTaskXmlPath, serviceStatePath, writeServiceInstallState, serviceSourceDir } from "./state";
|
|
23
|
+
import { TASK, windowsLauncherVbsPath, windowsTaskXmlPath, serviceStatePath, writeServiceInstallState, serviceSourceDir, cliEntry } from "./state";
|
|
22
24
|
import { decodeSchtasksOutput, querySchtasks, schtasks, type WindowsSchedulerTaskProbe, schtasksErrorDetail, probeWindowsSchedulerTask, rollbackWindowsSchedulerTaskOwnedByAttempt } from "./windows-scheduler";
|
|
23
25
|
import { buildWindowsServiceScript, buildWindowsSchtasksCreateArgs, buildWindowsSchtasksCreateArgsForXml, buildWindowsLauncherVbs, resolvedWindowsTaskSid, buildWindowsTaskXmlDocument, windowsTaskRegistrationOwnedByAttempt, windowsTaskRegistrationHealthy } from "./windows-taskxml";
|
|
24
26
|
|
|
@@ -108,11 +110,11 @@ function writeServiceAssetWithRetry(path: string, content: string, encoding: "ut
|
|
|
108
110
|
* Rewrite on-disk scheduler assets (script/VBS/XML) without itself registering the task.
|
|
109
111
|
* Fresh install creates it afterwards; repair does so only when the live definition is stale.
|
|
110
112
|
*/
|
|
111
|
-
export function writeWindowsSchedulerAssets(): void {
|
|
113
|
+
export function writeWindowsSchedulerAssets(runtime: DurableBunRuntime = durableBunRuntime()): void {
|
|
112
114
|
if (!existsSync(getConfigDir())) mkdirSync(getConfigDir(), { recursive: true });
|
|
113
115
|
writeServiceApiTokenFile();
|
|
114
116
|
const script = windowsServiceScriptPath();
|
|
115
|
-
writeServiceAssetWithRetry(script, buildWindowsServiceScript(), "utf8");
|
|
117
|
+
writeServiceAssetWithRetry(script, buildWindowsServiceScript(cliEntry(runtime)), "utf8");
|
|
116
118
|
// UTF-16LE + BOM: a BOM-less UTF-8 VBS mis-decodes non-ASCII (e.g. Korean) profile
|
|
117
119
|
// paths on some WSH/codepage combinations — same contract as the task XML below.
|
|
118
120
|
writeServiceAssetWithRetry(windowsLauncherVbsPath(), `\uFEFF${buildWindowsLauncherVbs(script)}`, "utf16le");
|
|
@@ -597,16 +599,40 @@ export function removeNativeWindowsServiceForScheduler(
|
|
|
597
599
|
}
|
|
598
600
|
}
|
|
599
601
|
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
602
|
+
/** External Windows effects shared by the gated entrypoints and admitted commit paths. */
|
|
603
|
+
export interface WindowsServiceInstallDeps extends RuntimePreflightDeps {
|
|
604
|
+
recordSchedulerOwnership?: () => boolean;
|
|
605
|
+
removeNativeService?: () => void;
|
|
606
|
+
stopScheduler?: () => void;
|
|
607
|
+
writeSchedulerAssets?: (runtime: DurableBunRuntime) => void;
|
|
608
|
+
schedulerCommand?: typeof schtasks;
|
|
609
|
+
writeState?: (backend: "scheduler" | "native", runtime: DurableBunRuntime) => void;
|
|
610
|
+
assertNativeAccount?: () => void;
|
|
611
|
+
prepareNativeConfig?: () => void;
|
|
612
|
+
uninstallScheduler?: () => void;
|
|
613
|
+
installNativeService?: typeof installWinswService;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
export function installWindows(selected?: DurableBunRuntime, deps: WindowsServiceInstallDeps = {}): void {
|
|
617
|
+
const runtime = Object.freeze({ ...(selected ?? (deps.selectRuntime ?? durableBunRuntime)()) });
|
|
618
|
+
const configDir = (deps.configDir ?? getConfigDir)();
|
|
619
|
+
(deps.assertRuntimeWritable ?? assertSelectedRuntimeWritable)(runtime, configDir, { platform: deps.platform, rootWasAbsent: !existsSync(configDir) });
|
|
620
|
+
commitWindowsInstall(runtime, deps);
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
/** Internal commit boundary: the caller has admitted this frozen runtime before cleanup. */
|
|
624
|
+
export function commitWindowsInstall(runtime: DurableBunRuntime, deps: WindowsServiceInstallDeps = {}): void {
|
|
625
|
+
(deps.recordSchedulerOwnership ?? recordWindowsSchedulerOwnership)();
|
|
626
|
+
(deps.removeNativeService ?? removeNativeWindowsServiceForScheduler)();
|
|
603
627
|
// End a running task BEFORE rewriting the assets it is executing — cmd.exe reading the
|
|
604
628
|
// script mid-rewrite runs a torn batch file, and its open handle can fail the write.
|
|
605
|
-
try { stopWindows(); } catch { /* not running */ }
|
|
606
|
-
writeWindowsSchedulerAssets();
|
|
607
|
-
schtasks
|
|
608
|
-
|
|
609
|
-
|
|
629
|
+
try { (deps.stopScheduler ?? stopWindows)(); } catch { /* not running */ }
|
|
630
|
+
(deps.writeSchedulerAssets ?? writeWindowsSchedulerAssets)(runtime);
|
|
631
|
+
const command = deps.schedulerCommand ?? schtasks;
|
|
632
|
+
command(buildWindowsSchtasksCreateArgs(windowsServiceScriptPath()));
|
|
633
|
+
command(["/run", "/tn", TASK]);
|
|
634
|
+
if (deps.writeState) deps.writeState("scheduler", runtime);
|
|
635
|
+
else writeServiceInstallState("scheduler", undefined, {}, runtime);
|
|
610
636
|
}
|
|
611
637
|
|
|
612
638
|
/**
|
|
@@ -748,26 +774,38 @@ function readWindowsPrincipalSource(): string | null {
|
|
|
748
774
|
}
|
|
749
775
|
}
|
|
750
776
|
|
|
751
|
-
export async function installWindowsNative(): Promise<void> {
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
777
|
+
export async function installWindowsNative(selected?: DurableBunRuntime, deps: WindowsServiceInstallDeps = {}): Promise<void> {
|
|
778
|
+
const runtime = Object.freeze({ ...(selected ?? (deps.selectRuntime ?? durableBunRuntime)()) });
|
|
779
|
+
const configDir = (deps.configDir ?? getConfigDir)();
|
|
780
|
+
(deps.assertRuntimeWritable ?? assertSelectedRuntimeWritable)(runtime, configDir, { platform: deps.platform, rootWasAbsent: !existsSync(configDir) });
|
|
781
|
+
await commitWindowsNativeInstall(runtime, deps);
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
/** Internal commit boundary; direct native installs go through the public admission gate. */
|
|
785
|
+
export async function commitWindowsNativeInstall(runtime: DurableBunRuntime, deps: WindowsServiceInstallDeps = {}): Promise<void> {
|
|
786
|
+
(deps.assertNativeAccount ?? assertWindowsNativeServiceAccountSupported)();
|
|
787
|
+
if (deps.prepareNativeConfig) deps.prepareNativeConfig();
|
|
788
|
+
else {
|
|
789
|
+
recordOwnedConfigPath(getConfigDir(), serviceStatePath());
|
|
790
|
+
if (!existsSync(getConfigDir())) mkdirSync(getConfigDir(), { recursive: true });
|
|
791
|
+
writeServiceApiTokenFile();
|
|
792
|
+
}
|
|
793
|
+
const command = deps.schedulerCommand ?? schtasks;
|
|
756
794
|
let hadScheduler = false;
|
|
757
795
|
try {
|
|
758
|
-
hadScheduler =
|
|
796
|
+
hadScheduler = command(["/query", "/tn", TASK]).includes(TASK);
|
|
759
797
|
} catch { /* task absent */ }
|
|
760
798
|
if (hadScheduler) {
|
|
761
799
|
console.log("🔁 Removing the Task Scheduler backend before installing the native (WinSW) service...");
|
|
762
|
-
try { stopWindows(); } catch { /* not running */ }
|
|
800
|
+
try { (deps.stopScheduler ?? stopWindows)(); } catch { /* not running */ }
|
|
763
801
|
try {
|
|
764
|
-
uninstallWindows();
|
|
802
|
+
(deps.uninstallScheduler ?? uninstallWindows)();
|
|
765
803
|
} catch (err) {
|
|
766
804
|
throw new Error(`Cannot remove the Task Scheduler backend before switching to native: ${err instanceof Error ? err.message : String(err)}`);
|
|
767
805
|
}
|
|
768
806
|
// Verify removal — schtasks /delete can silently fail if UAC or policy blocks it.
|
|
769
807
|
try {
|
|
770
|
-
if (
|
|
808
|
+
if (command(["/query", "/tn", TASK]).includes(TASK)) {
|
|
771
809
|
throw new Error("Task Scheduler backend still present after removal — aborting switch.");
|
|
772
810
|
}
|
|
773
811
|
} catch (e) {
|
|
@@ -776,12 +814,13 @@ export async function installWindowsNative(): Promise<void> {
|
|
|
776
814
|
}
|
|
777
815
|
}
|
|
778
816
|
try {
|
|
779
|
-
await installWinswService(defaultWinswEntry(serviceSourceDir));
|
|
817
|
+
await (deps.installNativeService ?? installWinswService)(defaultWinswEntry(serviceSourceDir, runtime));
|
|
780
818
|
} catch (err) {
|
|
781
819
|
if (hadScheduler) console.error("⚠️ Native install failed AFTER removing the Task Scheduler backend — no service is installed now. Run `ocx service install` to restore the scheduler backend, or retry `--native`.");
|
|
782
820
|
throw err;
|
|
783
821
|
}
|
|
784
|
-
|
|
822
|
+
if (deps.writeState) deps.writeState("native", runtime);
|
|
823
|
+
else writeServiceInstallState("native", undefined, {}, runtime);
|
|
785
824
|
}
|
|
786
825
|
|
|
787
826
|
export function startWindows(): void { schtasks(["/run", "/tn", TASK]); }
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import type { OcxConfig } from "../types";
|
|
2
|
+
import type { AnthropicInstanceId } from "../providers/anthropic-instance-id";
|
|
3
|
+
import { configuredAnthropicInstance } from "../providers/anthropic-instance";
|
|
4
|
+
import { anthropicRoutingFor } from "../oauth/anthropic-routing";
|
|
5
|
+
import { resolveAnthropicModelRouteForInstance, routeCandidates } from "../oauth/anthropic-model-routes";
|
|
6
|
+
import type { OAuthAccessSnapshot } from "../oauth";
|
|
7
|
+
import { AnthropicHelperUnavailableError } from "./auth";
|
|
8
|
+
import { captureAnthropicPhysicalSendOwnership, anthropicPhysicalSendOwnershipIsCurrent } from "../oauth/anthropic-send-ownership";
|
|
9
|
+
import { recordAnthropicAccountQuotaFromHeadersForInstance } from "../providers/quota/account-cache";
|
|
10
|
+
import { captureConfigGeneration } from "../lib/state-store-sweeper";
|
|
11
|
+
import { getAccountCredentialWithStatus } from "../oauth/store";
|
|
12
|
+
import { bindAnthropicRefusalCredentialForSend } from "../oauth/anthropic-account-refusal";
|
|
13
|
+
|
|
14
|
+
/** The one Messages URL builder shared by helper executors and the physical-send fence. */
|
|
15
|
+
export function anthropicHelperMessagesUrl(baseUrl: string): string {
|
|
16
|
+
return `${baseUrl.replace(/\/+$/, "").replace(/\/v1$/, "")}/v1/messages`;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Select the helper's account the way the legacy sidecar token path does: a read of the pool's
|
|
21
|
+
* selection for this model route, with no promotion. A helper never moves the active pointer and
|
|
22
|
+
* never consumes an operator's one-dispatch manual choice; the physical send is still fenced by
|
|
23
|
+
* the captured target/config, the account's credential generation and its send ownership.
|
|
24
|
+
*/
|
|
25
|
+
export async function resolveAnthropicHelperSnapshot(config: OcxConfig, instance: AnthropicInstanceId, model: string): Promise<OAuthAccessSnapshot> {
|
|
26
|
+
const target = config.providers[instance]?.baseUrl;
|
|
27
|
+
const assertConfigured = () => {
|
|
28
|
+
const provider = config.providers[instance];
|
|
29
|
+
if (!provider || provider.disabled || provider.authMode !== "oauth" || provider.adapter !== "anthropic"
|
|
30
|
+
|| configuredAnthropicInstance(config, instance) !== instance || provider.baseUrl !== target) {
|
|
31
|
+
throw new AnthropicHelperUnavailableError(instance);
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
assertConfigured();
|
|
35
|
+
const routing = anthropicRoutingFor(instance);
|
|
36
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
37
|
+
const route = resolveAnthropicModelRouteForInstance(instance, config, model);
|
|
38
|
+
if (route.error) throw new AnthropicHelperUnavailableError(instance);
|
|
39
|
+
const selection = routing.resolveAnthropicAccountForSession(null, config, Date.now(), route.decision, model);
|
|
40
|
+
if (!selection.accountId) throw new AnthropicHelperUnavailableError(instance);
|
|
41
|
+
const snapshot = await routing.getAnthropicPoolAccessSnapshot(selection.accountId);
|
|
42
|
+
assertConfigured();
|
|
43
|
+
const current = resolveAnthropicModelRouteForInstance(instance, config, model);
|
|
44
|
+
if (current.error || JSON.stringify(current.decision) !== JSON.stringify(route.decision)) continue;
|
|
45
|
+
if (snapshot.provider === instance && snapshot.accountId === selection.accountId) return snapshot;
|
|
46
|
+
}
|
|
47
|
+
throw new AnthropicHelperUnavailableError(instance);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** One physical helper send; reset replays call this boundary again with the same snapshot. */
|
|
51
|
+
export async function fetchAnthropicHelper(
|
|
52
|
+
config: OcxConfig, snapshot: OAuthAccessSnapshot, model: string,
|
|
53
|
+
capturedTarget: string, url: string, init: RequestInit,
|
|
54
|
+
): Promise<Response> {
|
|
55
|
+
const instance = snapshot.provider;
|
|
56
|
+
if (instance !== "anthropic" && instance !== "anthropic2") throw new Error("Invalid Anthropic helper instance");
|
|
57
|
+
const row = config.providers[instance];
|
|
58
|
+
const live = getAccountCredentialWithStatus(instance, snapshot.accountId);
|
|
59
|
+
if (!row || row.disabled || row.authMode !== "oauth" || row.adapter !== "anthropic"
|
|
60
|
+
|| configuredAnthropicInstance(config, instance) !== instance || row.baseUrl !== capturedTarget
|
|
61
|
+
|| url !== anthropicHelperMessagesUrl(capturedTarget) || !live || live.paused || live.needsReauth
|
|
62
|
+
|| new Headers(init.headers).get("authorization") !== `Bearer ${snapshot.accessToken}`) {
|
|
63
|
+
throw new AnthropicHelperUnavailableError(instance);
|
|
64
|
+
}
|
|
65
|
+
const route = resolveAnthropicModelRouteForInstance(instance, config, model);
|
|
66
|
+
const routing = anthropicRoutingFor(instance);
|
|
67
|
+
if (route.error || !routeCandidates(routing.getEligibleAnthropicAccounts(Date.now(), model), route.decision).includes(snapshot.accountId)) {
|
|
68
|
+
throw new AnthropicHelperUnavailableError(instance);
|
|
69
|
+
}
|
|
70
|
+
const owner = captureAnthropicPhysicalSendOwnership(snapshot);
|
|
71
|
+
if (!owner) throw new AnthropicHelperUnavailableError(instance);
|
|
72
|
+
const writer = captureConfigGeneration();
|
|
73
|
+
const response = await fetch(url, init);
|
|
74
|
+
if (anthropicPhysicalSendOwnershipIsCurrent(owner)) {
|
|
75
|
+
recordAnthropicAccountQuotaFromHeadersForInstance(instance, snapshot.accountId, response.headers, writer, response.status, model);
|
|
76
|
+
bindAnthropicRefusalCredentialForSend(response, owner);
|
|
77
|
+
}
|
|
78
|
+
return response;
|
|
79
|
+
}
|
package/src/sidecar/auth.ts
CHANGED
|
@@ -17,6 +17,73 @@ import { OPENAI_CODEX_PROVIDER_ID } from "../providers/openai-tiers";
|
|
|
17
17
|
import { isCodexAccountUsable } from "../codex/account-usability";
|
|
18
18
|
import { MAIN_CODEX_ACCOUNT_ID, isSelectableCodexPoolAccount } from "../codex/account-id";
|
|
19
19
|
import { getAccountSet } from "../oauth/store";
|
|
20
|
+
import { configuredAnthropicInstance, type AnthropicInstanceId } from "../providers/anthropic-instance";
|
|
21
|
+
import { isAnthropicInstanceId } from "../providers/anthropic-instance-id";
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* The parent request's pool for an inheriting helper. A missing or disabled row keeps the parent's
|
|
25
|
+
* identity, so a pool removed mid-request refuses instead of discovering another pool; only a present
|
|
26
|
+
* custom row that is not the builtin instance (an unmarked custom `anthropic2`) stops inheritance.
|
|
27
|
+
*/
|
|
28
|
+
export function inheritedAnthropicInstance(config: OcxConfig, parentProviderName: string | undefined): AnthropicInstanceId | undefined {
|
|
29
|
+
if (!isAnthropicInstanceId(parentProviderName)) return undefined;
|
|
30
|
+
const row = config.providers?.[parentProviderName];
|
|
31
|
+
if (!row || row.disabled === true) return parentProviderName;
|
|
32
|
+
return configuredAnthropicInstance(config, parentProviderName);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export class AnthropicHelperUnavailableError extends Error {
|
|
36
|
+
readonly code = "anthropic_helper_unavailable";
|
|
37
|
+
constructor(readonly instance: AnthropicInstanceId) {
|
|
38
|
+
super(`The selected ${instance} helper account pool is unavailable`);
|
|
39
|
+
this.name = "AnthropicHelperUnavailableError";
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
export type AnthropicHelperContext = {
|
|
43
|
+
backendFamily: string;
|
|
44
|
+
anthropicInstance?: AnthropicInstanceId;
|
|
45
|
+
parentProviderName?: string;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Availability of one selected pool: the configured builtin row plus at least one stored account the
|
|
50
|
+
* pool could select. Which account actually sends (model route, cooldown, manual choice) is decided
|
|
51
|
+
* by snapshot resolution at send time, exactly as the main request path does, so a paused or
|
|
52
|
+
* reauth-pending ACTIVE account does not make a pool with another usable account unavailable.
|
|
53
|
+
*/
|
|
54
|
+
export function resolveAnthropicSidecarAuth(config: OcxConfig, instance: AnthropicInstanceId):
|
|
55
|
+
{ anthropicProviderName: AnthropicInstanceId; anthropicProvider: OcxProviderConfig } | undefined {
|
|
56
|
+
const provider = config.providers[instance];
|
|
57
|
+
if (!provider || provider.disabled || provider.authMode !== "oauth" || provider.adapter !== "anthropic"
|
|
58
|
+
|| configuredAnthropicInstance(config, instance) !== instance) return undefined;
|
|
59
|
+
if (!getAccountSet(instance)?.accounts.some(row => row.paused !== true && row.needsReauth !== true)) return undefined;
|
|
60
|
+
return { anthropicProviderName: instance, anthropicProvider: provider };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Explicit target, then the parent's CONFIGURED builtin instance. The parent name alone is not
|
|
65
|
+
* enough: a custom provider that happens to be named `anthropic2` is not Pool 2 and is never inherited.
|
|
66
|
+
*/
|
|
67
|
+
export function resolveAnthropicHelperInstance(config: OcxConfig, context: AnthropicHelperContext): AnthropicInstanceId | undefined {
|
|
68
|
+
if (context.backendFamily !== "anthropic") return undefined;
|
|
69
|
+
const instance = context.anthropicInstance ?? inheritedAnthropicInstance(config, context.parentProviderName);
|
|
70
|
+
if (instance && !resolveAnthropicSidecarAuth(config, instance)) throw new AnthropicHelperUnavailableError(instance);
|
|
71
|
+
return instance;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Planner boundary for the typed refusal: an unavailable selected pool means "this helper does not
|
|
76
|
+
* run", never "fail the main request" and never "discover another pool". Other errors propagate.
|
|
77
|
+
*/
|
|
78
|
+
export function withAnthropicHelperRefusal<T>(surface: string, resolve: () => T): T | undefined {
|
|
79
|
+
try {
|
|
80
|
+
return resolve();
|
|
81
|
+
} catch (error) {
|
|
82
|
+
if (!(error instanceof AnthropicHelperUnavailableError)) throw error;
|
|
83
|
+
console.warn(`[${surface}] ${error.message}; the request continues without this helper`);
|
|
84
|
+
return undefined;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
20
87
|
|
|
21
88
|
export interface SidecarAuthState {
|
|
22
89
|
/** ChatGPT login usable: canonical forward provider AND a live stored credential. */
|
|
@@ -63,6 +130,10 @@ function findAnthropicAuthProvider(
|
|
|
63
130
|
config: OcxConfig,
|
|
64
131
|
): { providerName: string; provider: OcxProviderConfig } | undefined {
|
|
65
132
|
for (const [providerName, provider] of Object.entries(config.providers)) {
|
|
133
|
+
// Never adds Pool 2 automatically. The OAuth store key `anthropic2` belongs to the builtin pool:
|
|
134
|
+
// its token path and the token guardian refuse an unmarked row of that name, so offering such a
|
|
135
|
+
// custom row here would advertise credentials no helper executor will spend.
|
|
136
|
+
if (providerName === "anthropic2") continue;
|
|
66
137
|
if (provider.disabled === true) continue;
|
|
67
138
|
if (provider.adapter !== "anthropic" || provider.authMode !== "oauth") continue;
|
|
68
139
|
const set = getAccountSet(providerName);
|
|
@@ -72,8 +143,11 @@ function findAnthropicAuthProvider(
|
|
|
72
143
|
return undefined;
|
|
73
144
|
}
|
|
74
145
|
|
|
75
|
-
export function resolveSidecarAuth(config: OcxConfig): SidecarAuthState {
|
|
76
|
-
const
|
|
146
|
+
export function resolveSidecarAuth(config: OcxConfig, instance?: AnthropicInstanceId): SidecarAuthState {
|
|
147
|
+
const exact = instance ? resolveAnthropicSidecarAuth(config, instance) : undefined;
|
|
148
|
+
if (instance && !exact) throw new AnthropicHelperUnavailableError(instance);
|
|
149
|
+
const anthropic = exact ? { providerName: exact.anthropicProviderName, provider: exact.anthropicProvider }
|
|
150
|
+
: findAnthropicAuthProvider(config);
|
|
77
151
|
return {
|
|
78
152
|
isCodexAuth: hasUsableCodexLogin(config),
|
|
79
153
|
isAnthropicAuth: anthropic !== undefined,
|
|
@@ -65,6 +65,8 @@ export interface PolicyJobTestHooks {
|
|
|
65
65
|
* policy load, so concurrent PUTs can race completion metadata writes.
|
|
66
66
|
*/
|
|
67
67
|
blockMs?: number;
|
|
68
|
+
/** Test-only shared barrier after the Worker has loaded its initial policy. */
|
|
69
|
+
workerLoadGate?: Int32Array;
|
|
68
70
|
/**
|
|
69
71
|
* When true, run on the main thread via `queueMicrotask` + optional sleep.
|
|
70
72
|
* Used only for unit tests that cannot spawn workers; responsiveness tests
|
|
@@ -304,7 +306,7 @@ function applyMutationBusy(): void {
|
|
|
304
306
|
};
|
|
305
307
|
}
|
|
306
308
|
|
|
307
|
-
function runInWorker(opts: RequestPolicyRunOptions & { blockMs?: number }): Promise<PolicyRunResult> {
|
|
309
|
+
function runInWorker(opts: RequestPolicyRunOptions & { blockMs?: number; workerLoadGate?: Int32Array }): Promise<PolicyRunResult> {
|
|
308
310
|
const reservation = tryReserveStorageWorker();
|
|
309
311
|
if (!reservation) return Promise.reject(new StorageWorkerAdmissionBusyError());
|
|
310
312
|
return withStorageWorkerSpawnGate(() => new Promise<PolicyRunResult>((resolve, reject) => {
|
|
@@ -369,6 +371,7 @@ function runInWorker(opts: RequestPolicyRunOptions & { blockMs?: number }): Prom
|
|
|
369
371
|
...(opts.codexHome ? { codexHome: opts.codexHome } : {}),
|
|
370
372
|
...(opts.busyTimeoutMs !== undefined ? { busyTimeoutMs: opts.busyTimeoutMs } : {}),
|
|
371
373
|
...(opts.blockMs !== undefined ? { blockMs: opts.blockMs } : {}),
|
|
374
|
+
...(opts.workerLoadGate ? { workerLoadGate: opts.workerLoadGate } : {}),
|
|
372
375
|
env: {
|
|
373
376
|
...(process.env.CODEX_HOME ? { CODEX_HOME: process.env.CODEX_HOME } : {}),
|
|
374
377
|
...(process.env.OPENCODEX_HOME ? { OPENCODEX_HOME: process.env.OPENCODEX_HOME } : {}),
|
|
@@ -410,6 +413,7 @@ async function executeJob(opts: RequestPolicyRunOptions): Promise<void> {
|
|
|
410
413
|
...opts,
|
|
411
414
|
codexHome,
|
|
412
415
|
...(typeof blockMs === "number" && blockMs > 0 ? { blockMs } : {}),
|
|
416
|
+
...(testHooks?.workerLoadGate ? { workerLoadGate: testHooks.workerLoadGate } : {}),
|
|
413
417
|
});
|
|
414
418
|
}
|
|
415
419
|
|
|
@@ -13,6 +13,7 @@ interface RunMessage {
|
|
|
13
13
|
busyTimeoutMs?: number;
|
|
14
14
|
/** Test-only: block after loading the start-of-job policy (see holdAfterLoadMs). */
|
|
15
15
|
blockMs?: number;
|
|
16
|
+
workerLoadGate?: Int32Array;
|
|
16
17
|
/** Env snapshot — Workers may not see parent mutations on all platforms. */
|
|
17
18
|
env?: { CODEX_HOME?: string; OPENCODEX_HOME?: string };
|
|
18
19
|
}
|
|
@@ -29,7 +30,7 @@ declare const self: Worker;
|
|
|
29
30
|
|
|
30
31
|
self.onmessage = (event: MessageEvent<unknown>) => {
|
|
31
32
|
if (!isRunMessage(event.data)) return;
|
|
32
|
-
const { requestId, reason, force, codexHome, busyTimeoutMs, blockMs, env } = event.data;
|
|
33
|
+
const { requestId, reason, force, codexHome, busyTimeoutMs, blockMs, workerLoadGate, env } = event.data;
|
|
33
34
|
try {
|
|
34
35
|
if (env?.CODEX_HOME) process.env.CODEX_HOME = env.CODEX_HOME;
|
|
35
36
|
if (env?.OPENCODEX_HOME) process.env.OPENCODEX_HOME = env.OPENCODEX_HOME;
|
|
@@ -38,6 +39,7 @@ self.onmessage = (event: MessageEvent<unknown>) => {
|
|
|
38
39
|
force: force === true,
|
|
39
40
|
...(codexHome ? { codexHome } : {}),
|
|
40
41
|
...(busyTimeoutMs !== undefined ? { busyTimeoutMs } : {}),
|
|
42
|
+
...(workerLoadGate ? { holdAfterLoadGate: workerLoadGate } : {}),
|
|
41
43
|
...(typeof blockMs === "number" && Number.isFinite(blockMs) && blockMs > 0
|
|
42
44
|
? { holdAfterLoadMs: Math.floor(blockMs) }
|
|
43
45
|
: {}),
|
package/src/storage/policy.ts
CHANGED
|
@@ -74,6 +74,8 @@ export interface PolicyRunDeps {
|
|
|
74
74
|
* land before completion merges run metadata.
|
|
75
75
|
*/
|
|
76
76
|
holdAfterLoadMs?: number;
|
|
77
|
+
/** Test-only Worker barrier: 0=pending, 1=policy loaded, 2=released. */
|
|
78
|
+
holdAfterLoadGate?: Int32Array;
|
|
77
79
|
}
|
|
78
80
|
|
|
79
81
|
/** Canonical defaults — enabled is always false. */
|
|
@@ -500,6 +502,13 @@ export function runStorageCleanupPolicy(deps: PolicyRunDeps): PolicyRunResult {
|
|
|
500
502
|
})
|
|
501
503
|
: (patch: PolicyRunMetadataPatch) => commitPolicyRunMetadataToConfig(patch, policy);
|
|
502
504
|
|
|
505
|
+
if (deps.holdAfterLoadGate) {
|
|
506
|
+
Atomics.compareExchange(deps.holdAfterLoadGate, 0, 0, 1);
|
|
507
|
+
Atomics.notify(deps.holdAfterLoadGate, 0);
|
|
508
|
+
if (Atomics.wait(deps.holdAfterLoadGate, 0, 1, 10_000) === "timed-out") {
|
|
509
|
+
throw new Error("policy_test_load_gate_timeout");
|
|
510
|
+
}
|
|
511
|
+
}
|
|
503
512
|
if (typeof deps.holdAfterLoadMs === "number" && Number.isFinite(deps.holdAfterLoadMs) && deps.holdAfterLoadMs > 0) {
|
|
504
513
|
Bun.sleepSync(Math.floor(deps.holdAfterLoadMs));
|
|
505
514
|
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Anthropic OAuth account-pool settings. One shape, two locations:
|
|
3
|
+
* `config.anthropicAccountPool` for the `anthropic` instance and
|
|
4
|
+
* `config.providers.anthropic2.anthropicAccountPool` for `anthropic2`. Neither inherits from the other.
|
|
5
|
+
*
|
|
6
|
+
* Leaf module: `config.ts` and `provider.ts` import these types, never the reverse.
|
|
7
|
+
*/
|
|
8
|
+
export type OcxAccountPoolRotationStrategy = "quota" | "round-robin" | "fill-first";
|
|
9
|
+
|
|
10
|
+
export type OcxAccountPoolQuotaWindow = "five-hour" | "weekly" | "max-utilization";
|
|
11
|
+
|
|
12
|
+
export interface AnthropicModelRoute {
|
|
13
|
+
name: string;
|
|
14
|
+
match: string;
|
|
15
|
+
accounts: string[];
|
|
16
|
+
fallback?: boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface AnthropicAccountPoolConfig {
|
|
20
|
+
enabled?: boolean;
|
|
21
|
+
/** Preserve native Claude Messages while the pool is enabled. Default true; false selects legacy translation. */
|
|
22
|
+
nativeMessages?: boolean;
|
|
23
|
+
/** Usage % threshold for new-session auto-pick. Default 80. 0 = disabled (affinity/active only). */
|
|
24
|
+
autoSwitchThreshold?: number;
|
|
25
|
+
/** New-session rotation strategy. Default quota (today's behaviour). */
|
|
26
|
+
strategy?: OcxAccountPoolRotationStrategy;
|
|
27
|
+
/** Successful new-session binds retained on one round-robin selection. Default 1; range 1..100. */
|
|
28
|
+
stickyLimit?: number;
|
|
29
|
+
/** Usage window for quota-based scoring. Default "five-hour" (today's behaviour). */
|
|
30
|
+
quotaWindow?: OcxAccountPoolQuotaWindow;
|
|
31
|
+
/** Ordered model allowlists; inactive while the pool is disabled. Stored account IDs only. */
|
|
32
|
+
routes?: AnthropicModelRoute[];
|
|
33
|
+
}
|