@cohortapp/agent-sdk 2.17.0 → 2.18.5
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/.claude/settings.json +18 -0
- package/.env.example +18 -5
- package/README.md +1 -0
- package/bin/maestro.mjs +62 -0
- package/docs/guides/billing-console-keys.md +60 -0
- package/docs/guides/front-door-session.md +54 -9
- package/docs/guides/mac-mini.md +20 -25
- package/docs/guides/setup-wizard.md +1 -1
- package/docs/runbooks/fleet-rollout.md +156 -0
- package/docs/runbooks/mac-mini-bootstrap.md +12 -14
- package/lib/action-executor.js +19 -3
- package/lib/budget-guard.mjs +279 -3
- package/lib/channels/base-adapter.mjs +3 -1
- package/lib/channels/contract.mjs +2 -1
- package/lib/channels/inbox-item.mjs +8 -0
- package/lib/claude-bin.mjs +5 -6
- package/lib/cli/doctor-checks.mjs +141 -10
- package/lib/cli/global-setup-extras.mjs +5 -1
- package/lib/cli/inbox.mjs +100 -15
- package/lib/cli/seat-auth.mjs +463 -0
- package/lib/cli/session.mjs +80 -12
- package/lib/collective/capture-slots.mjs +234 -0
- package/lib/collective/capture.mjs +8 -6
- package/lib/collective/config.mjs +2 -0
- package/lib/collective/global-config.mjs +63 -1
- package/lib/collective/loop-guard.mjs +155 -0
- package/lib/collective/presence.mjs +142 -5
- package/lib/comms/send-gate.mjs +559 -1
- package/lib/diagnostics/alerts.mjs +49 -0
- package/lib/diagnostics/cadence-output-freshness.mjs +288 -0
- package/lib/engine/agents/definitions.mjs +343 -0
- package/lib/engine/agents/persist.mjs +275 -0
- package/lib/engine/agents/runtime.mjs +748 -0
- package/lib/engine/agents/usage.mjs +95 -0
- package/lib/engine/auth-status.mjs +139 -0
- package/lib/engine/budget.mjs +194 -0
- package/lib/engine/cli.mjs +1204 -0
- package/lib/engine/commands/index.mjs +269 -0
- package/lib/engine/context/budget.mjs +219 -0
- package/lib/engine/context/cache.mjs +125 -0
- package/lib/engine/context/child-env.mjs +215 -0
- package/lib/engine/context/compaction.mjs +342 -0
- package/lib/engine/context/images.mjs +90 -0
- package/lib/engine/context/instructions.mjs +327 -0
- package/lib/engine/context/lazy-instructions.mjs +169 -0
- package/lib/engine/context/manager.mjs +182 -0
- package/lib/engine/context/real-path.mjs +91 -0
- package/lib/engine/context/secret-values.mjs +163 -0
- package/lib/engine/context/settings.mjs +274 -0
- package/lib/engine/context/stream-input.mjs +159 -0
- package/lib/engine/guard.mjs +152 -0
- package/lib/engine/hooks.mjs +713 -0
- package/lib/engine/loop.mjs +560 -0
- package/lib/engine/mcp/client.mjs +254 -0
- package/lib/engine/mcp/config.mjs +301 -0
- package/lib/engine/mcp/http.mjs +201 -0
- package/lib/engine/mcp/index.mjs +146 -0
- package/lib/engine/mcp/jsonrpc.mjs +147 -0
- package/lib/engine/mcp/naming.mjs +66 -0
- package/lib/engine/mcp/resources.mjs +89 -0
- package/lib/engine/mcp/results.mjs +133 -0
- package/lib/engine/mcp/stdio.mjs +137 -0
- package/lib/engine/mcp/supervisor.mjs +116 -0
- package/lib/engine/messages.mjs +104 -0
- package/lib/engine/output/json.mjs +164 -0
- package/lib/engine/output/stream-json.mjs +266 -0
- package/lib/engine/permissions.mjs +845 -0
- package/lib/engine/process-identity.mjs +164 -0
- package/lib/engine/process-tree.mjs +551 -0
- package/lib/engine/prompt.mjs +60 -0
- package/lib/engine/session/store.mjs +299 -0
- package/lib/engine/session-runtime/args.mjs +97 -0
- package/lib/engine/session-runtime/host.mjs +143 -0
- package/lib/engine/session-runtime/inbox.mjs +122 -0
- package/lib/engine/session-runtime/notifications.mjs +129 -0
- package/lib/engine/session-runtime/registry.mjs +328 -0
- package/lib/engine/session-runtime/runner.mjs +344 -0
- package/lib/engine/session-runtime/socket.mjs +212 -0
- package/lib/engine/session-runtime/wakeup.mjs +115 -0
- package/lib/engine/skills/index.mjs +321 -0
- package/lib/engine/tools/bash-background.mjs +533 -0
- package/lib/engine/tools/bash.mjs +216 -0
- package/lib/engine/tools/edit.mjs +97 -0
- package/lib/engine/tools/glob.mjs +81 -0
- package/lib/engine/tools/grep.mjs +224 -0
- package/lib/engine/tools/index.mjs +84 -0
- package/lib/engine/tools/list-agents.mjs +32 -0
- package/lib/engine/tools/ls.mjs +127 -0
- package/lib/engine/tools/monitor.mjs +82 -0
- package/lib/engine/tools/notebook-edit.mjs +218 -0
- package/lib/engine/tools/read.mjs +103 -0
- package/lib/engine/tools/schedule-wakeup.mjs +45 -0
- package/lib/engine/tools/schema.mjs +144 -0
- package/lib/engine/tools/send-message.mjs +77 -0
- package/lib/engine/tools/session.mjs +70 -0
- package/lib/engine/tools/todo.mjs +144 -0
- package/lib/engine/tools/toolsearch.mjs +217 -0
- package/lib/engine/tools/walk.mjs +193 -0
- package/lib/engine/tools/web-switch.mjs +31 -0
- package/lib/engine/tools/webfetch-html.mjs +387 -0
- package/lib/engine/tools/webfetch-net.mjs +340 -0
- package/lib/engine/tools/webfetch.mjs +198 -0
- package/lib/engine/tools/websearch.mjs +91 -0
- package/lib/engine/tools/workflow.mjs +95 -0
- package/lib/engine/tools/write.mjs +76 -0
- package/lib/engine/tui/line-editor.mjs +137 -0
- package/lib/engine/tui/render.mjs +86 -0
- package/lib/engine/tui/tui.mjs +274 -0
- package/lib/engine/wire/anthropic-messages.mjs +263 -0
- package/lib/engine/wire/effort.mjs +36 -0
- package/lib/engine/wire/errors.mjs +496 -0
- package/lib/engine/wire/http.mjs +441 -0
- package/lib/engine/wire/index.mjs +76 -0
- package/lib/engine/wire/openai-chat.mjs +332 -0
- package/lib/engine/wire/prompt-cache.mjs +79 -0
- package/lib/engine/wire/search.mjs +140 -0
- package/lib/engine/wire/sse.mjs +114 -0
- package/lib/engine/wire/stall.mjs +349 -0
- package/lib/engine/wire/token-provider.mjs +175 -0
- package/lib/engine/wire/usage.mjs +192 -0
- package/lib/engine/workflow/host.mjs +524 -0
- package/lib/engine/workflow/journal.mjs +188 -0
- package/lib/engine/workflow/json-schema.mjs +171 -0
- package/lib/engine/workflow/meta.mjs +329 -0
- package/lib/engine/workflow/notifications.mjs +52 -0
- package/lib/engine/workflow/runtime.mjs +447 -0
- package/lib/engine/workflow/sandbox.mjs +534 -0
- package/lib/engine/workflow/worker.mjs +141 -0
- package/lib/engine/workflow/worktree.mjs +74 -0
- package/lib/execution/disposition.mjs +1 -1
- package/lib/execution/intake.mjs +10 -0
- package/lib/execution/surface-policy.mjs +15 -0
- package/lib/learning/curator.mjs +8 -6
- package/lib/learning/reflect.mjs +8 -6
- package/lib/model-router/catalog/cohort.yaml +137 -0
- package/lib/model-router/catalog.mjs +118 -1
- package/lib/model-router/failover.mjs +67 -16
- package/lib/model-router/llm-task.mjs +39 -3
- package/lib/model-router/resolve.mjs +89 -3
- package/lib/model-router/spawn.mjs +46 -47
- package/lib/model-router/taxonomy.mjs +126 -4
- package/lib/org/cost-sync.mjs +141 -11
- package/lib/org/inbound/broadcast.mjs +289 -0
- package/lib/org/inbound/collective.mjs +375 -0
- package/lib/org/inbound/directedness.mjs +96 -8
- package/lib/org/inbound/facts.mjs +78 -2
- package/lib/org/inbound/project.mjs +22 -0
- package/lib/org/inbound/surfaces.mjs +14 -0
- package/lib/org/llm-token.mjs +879 -0
- package/lib/org/mesh.mjs +61 -0
- package/lib/org/messaging.mjs +3 -1
- package/lib/org/protocol.checksum +1 -1
- package/lib/org/protocol.mjs +15 -0
- package/lib/org/quota.mjs +520 -0
- package/lib/org/tool-surface.mjs +104 -16
- package/lib/org/ui-parity.mjs +16 -1
- package/lib/org/work-ledger.mjs +37 -6
- package/lib/rate-guard.mjs +114 -1
- package/lib/resource-governor.mjs +41 -6
- package/lib/runtime/adapter.mjs +833 -0
- package/lib/runtime/child-env.mjs +191 -0
- package/lib/runtime/legacy-shell-guard.mjs +97 -0
- package/lib/runtime/seat-engine.mjs +162 -0
- package/lib/session/ask-ledger.mjs +271 -0
- package/lib/session/current-work.mjs +676 -0
- package/lib/session/feed-core.mjs +40 -3
- package/lib/session/launch-args.mjs +56 -4
- package/lib/session/status-summary.mjs +26 -9
- package/lib/session/upgrade-notice.mjs +42 -0
- package/lib/setup/claude-probe.mjs +117 -13
- package/lib/setup/enrich.mjs +13 -10
- package/lib/setup/sections/model.mjs +39 -13
- package/lib/telemetry/collect.mjs +229 -11
- package/lib/upgrade/ignored-drift.mjs +105 -0
- package/lib/voice/post-call-brief.mjs +30 -17
- package/package.json +13 -3
- package/plugins/maestro-skills/skills/board-work.md +5 -0
- package/plugins/maestro-skills/skills/inbound-triage.md +56 -15
- package/plugins/maestro-skills/skills/main-session.md +18 -7
- package/scaffold/config/collective.yaml +7 -0
- package/scripts/ci/check-durable-write-seam.mjs +3 -1
- package/scripts/ci/check-tarball-fidelity.mjs +126 -2
- package/scripts/ci/run-tests.mjs +47 -19
- package/scripts/cohort-llm/api-key-helper.mjs +92 -0
- package/scripts/collective/hook-runner.mjs +142 -19
- package/scripts/continuous-monitor.sh +13 -0
- package/scripts/cost/track-claude-usage.mjs +15 -0
- package/scripts/daemon/agent-daemon.mjs +408 -20
- package/scripts/daemon/assurance.mjs +48 -12
- package/scripts/daemon/cadence-consumer.mjs +218 -68
- package/scripts/daemon/cadence-handlers.mjs +73 -4
- package/scripts/daemon/classifier.mjs +75 -26
- package/scripts/daemon/context-compiler.mjs +51 -37
- package/scripts/daemon/deliver.mjs +30 -1
- package/scripts/daemon/dispatcher.mjs +595 -149
- package/scripts/daemon/health.mjs +14 -1
- package/scripts/daemon/maestro-daemon.mjs +11 -0
- package/scripts/daemon/prompt-builder.mjs +24 -0
- package/scripts/daemon/responder.mjs +246 -79
- package/scripts/daemon/sdk-version.mjs +98 -16
- package/scripts/eval/probe-gateway.mjs +635 -0
- package/scripts/eval/replay/extract.mjs +270 -0
- package/scripts/eval/replay/grade.mjs +260 -0
- package/scripts/eval/replay/lib/config.mjs +50 -0
- package/scripts/eval/replay/lib/effects.mjs +65 -0
- package/scripts/eval/replay/lib/fixture.mjs +188 -0
- package/scripts/eval/replay/lib/judge.mjs +72 -0
- package/scripts/eval/replay/lib/redact.mjs +136 -0
- package/scripts/eval/replay/lib/sandbox.mjs +170 -0
- package/scripts/eval/replay/lib/schema-check.mjs +63 -0
- package/scripts/eval/replay/lib/transcript.mjs +76 -0
- package/scripts/eval/replay/mcp-replay-stub.mjs +101 -0
- package/scripts/eval/replay/report.mjs +185 -0
- package/scripts/eval/replay/run.mjs +404 -0
- package/scripts/fleet/rollout.mjs +1151 -0
- package/scripts/hooks/pre-send-audit.sh +36 -245
- package/scripts/hooks/pre-write-yaml-validate.mjs +275 -0
- package/scripts/hooks/validate-state-yaml.sh +190 -0
- package/scripts/huddle/huddle-llm.mjs +361 -0
- package/scripts/huddle/huddle-server.mjs +46 -121
- package/scripts/local-triggers/autoupdate.sh +465 -81
- package/scripts/local-triggers/run-trigger.sh +13 -0
- package/scripts/maintenance/pin-integrity.mjs +364 -0
- package/scripts/poll-slack-events.sh +41 -9
- package/scripts/poller/slack-socket-mode.mjs +28 -3
- package/scripts/session/supervisor.mjs +80 -13
- package/scripts/spawn-session.sh +13 -0
- package/bin/maestro.test.mjs +0 -1574
- package/lib/action-executor.test.mjs +0 -871
- package/lib/archetype.test.mjs +0 -132
- package/lib/assurance/plan-note.test.mjs +0 -234
- package/lib/assurance/room-budget.test.mjs +0 -486
- package/lib/assurance/tier.test.mjs +0 -174
- package/lib/autonomy.test.mjs +0 -66
- package/lib/backlog.test.mjs +0 -302
- package/lib/backup/policy.test.mjs +0 -305
- package/lib/budget-escalate.test.mjs +0 -232
- package/lib/budget-guard.envelope.test.mjs +0 -476
- package/lib/budget-guard.test.mjs +0 -427
- package/lib/cadence-bus-requeue.test.mjs +0 -83
- package/lib/cadence-bus-schedule.test.mjs +0 -194
- package/lib/cadence-bus.test.mjs +0 -720
- package/lib/cadences.test.mjs +0 -230
- package/lib/capability/inventory.test.mjs +0 -232
- package/lib/capability.test.mjs +0 -78
- package/lib/channels/base-adapter.test.mjs +0 -590
- package/lib/channels/channels.test.mjs +0 -371
- package/lib/channels/contract.test.mjs +0 -162
- package/lib/channels/inbox-item.test.mjs +0 -368
- package/lib/channels/orgmail/adapter.test.mjs +0 -448
- package/lib/channels/pairing.test.mjs +0 -270
- package/lib/channels/repeat-suppressor.test.mjs +0 -134
- package/lib/channels/slack-adapter.test.mjs +0 -212
- package/lib/channels/telegram-adapter.test.mjs +0 -306
- package/lib/channels/voice/adapter.test.mjs +0 -278
- package/lib/channels/whatsapp/adapter-baileys.test.mjs +0 -359
- package/lib/channels/whatsapp/baileys-typing.test.mjs +0 -154
- package/lib/charter.test.mjs +0 -89
- package/lib/claude-bin.test.mjs +0 -131
- package/lib/cli/board.test.mjs +0 -227
- package/lib/cli/design.test.mjs +0 -270
- package/lib/cli/doctor-checks.test.mjs +0 -336
- package/lib/cli/global-setup-extras.test.mjs +0 -462
- package/lib/cli/inbox.test.mjs +0 -230
- package/lib/cli/session-ack.test.mjs +0 -63
- package/lib/cli/session.test.mjs +0 -613
- package/lib/collective/capture.test.mjs +0 -121
- package/lib/collective/cards.test.mjs +0 -114
- package/lib/collective/config.test.mjs +0 -123
- package/lib/collective/global-config.test.mjs +0 -220
- package/lib/collective/global-skills.test.mjs +0 -126
- package/lib/collective/presence.test.mjs +0 -95
- package/lib/collective/recall.test.mjs +0 -116
- package/lib/collective/vendor-skills.test.mjs +0 -306
- package/lib/comms/send-gate.test.mjs +0 -770
- package/lib/comms.test.mjs +0 -41
- package/lib/context/budget.test.mjs +0 -252
- package/lib/context/history-scope.test.mjs +0 -79
- package/lib/cost/ledger-row.test.mjs +0 -183
- package/lib/design/design-md.test.mjs +0 -318
- package/lib/design/fixtures/DESIGN.golden.md +0 -238
- package/lib/design/fixtures/PRODUCT.golden.md +0 -67
- package/lib/design/fixtures/foundation.json +0 -133
- package/lib/design/refresh-gate.test.mjs +0 -144
- package/lib/design/write.test.mjs +0 -241
- package/lib/diagnostics/alerts.test.mjs +0 -318
- package/lib/diagnostics/backup-freshness.test.mjs +0 -185
- package/lib/diagnostics/counters.test.mjs +0 -206
- package/lib/diagnostics/events.test.mjs +0 -290
- package/lib/diagnostics/otel.test.mjs +0 -196
- package/lib/diagnostics/trace.test.mjs +0 -251
- package/lib/env-compat.test.mjs +0 -104
- package/lib/execution/disposition.test.mjs +0 -553
- package/lib/execution/drive.test.mjs +0 -270
- package/lib/execution/effects.test.mjs +0 -344
- package/lib/execution/intake.test.mjs +0 -389
- package/lib/execution/journal.test.mjs +0 -261
- package/lib/execution/match.test.mjs +0 -235
- package/lib/execution/pipeline.test.mjs +0 -392
- package/lib/execution/route.test.mjs +0 -186
- package/lib/execution/surface-policy.test.mjs +0 -162
- package/lib/fs-atomic.test.mjs +0 -72
- package/lib/fs-ownership.test.mjs +0 -158
- package/lib/goals/admission.test.mjs +0 -164
- package/lib/goals/classify.test.mjs +0 -167
- package/lib/goals/collaborate.test.mjs +0 -336
- package/lib/goals/gaps.test.mjs +0 -284
- package/lib/goals/loop.test.mjs +0 -845
- package/lib/hooks/bus.test.mjs +0 -387
- package/lib/identity/persona.test.mjs +0 -142
- package/lib/kpi-sensors.test.mjs +0 -278
- package/lib/kpi.test.mjs +0 -244
- package/lib/learning/config.test.mjs +0 -75
- package/lib/learning/counters.test.mjs +0 -69
- package/lib/learning/curator-consolidate.test.mjs +0 -238
- package/lib/learning/curator.test.mjs +0 -106
- package/lib/learning/reflect.test.mjs +0 -0
- package/lib/learning/session-index.test.mjs +0 -125
- package/lib/learning/skill-writer.test.mjs +0 -210
- package/lib/mandate/audit.test.mjs +0 -195
- package/lib/mandate/contract.test.mjs +0 -185
- package/lib/mandate/derive.test.mjs +0 -274
- package/lib/mandate/model.test.mjs +0 -164
- package/lib/mandate/refresh.test.mjs +0 -389
- package/lib/mcp/server.test.mjs +0 -426
- package/lib/model-router/auth-profiles.test.mjs +0 -580
- package/lib/model-router/catalog.test.mjs +0 -385
- package/lib/model-router/economics.test.mjs +0 -438
- package/lib/model-router/failover.test.mjs +0 -439
- package/lib/model-router/health.test.mjs +0 -338
- package/lib/model-router/integration-coverage.test.mjs +0 -831
- package/lib/model-router/integration.test.mjs +0 -564
- package/lib/model-router/ledger.test.mjs +0 -415
- package/lib/model-router/llm-task.test.mjs +0 -392
- package/lib/model-router/org-credentials.test.mjs +0 -265
- package/lib/model-router/pricing-refresh.test.mjs +0 -286
- package/lib/model-router/reconcile.test.mjs +0 -316
- package/lib/model-router/repair.test.mjs +0 -180
- package/lib/model-router/spawn.test.mjs +0 -446
- package/lib/model-router/taxonomy.test.mjs +0 -410
- package/lib/model-router.test.mjs +0 -1207
- package/lib/org/activity.test.mjs +0 -134
- package/lib/org/approvals.test.mjs +0 -216
- package/lib/org/awareness.test.mjs +0 -159
- package/lib/org/board-mine-cache.test.mjs +0 -53
- package/lib/org/board.test.mjs +0 -187
- package/lib/org/bootstrap-context.test.mjs +0 -153
- package/lib/org/client.test.mjs +0 -1206
- package/lib/org/cohort-client.test.mjs +0 -126
- package/lib/org/cost-sync.test.mjs +0 -153
- package/lib/org/doctor.test.mjs +0 -346
- package/lib/org/engagement-ledger.test.mjs +0 -112
- package/lib/org/engagement.test.mjs +0 -739
- package/lib/org/handoff.test.mjs +0 -269
- package/lib/org/inbound/directedness.test.mjs +0 -668
- package/lib/org/inbound/facts.test.mjs +0 -471
- package/lib/org/inbound/hydrate.test.mjs +0 -908
- package/lib/org/inbound/index.test.mjs +0 -429
- package/lib/org/inbound/project.test.mjs +0 -287
- package/lib/org/integration-tools.test.mjs +0 -160
- package/lib/org/keys.test.mjs +0 -92
- package/lib/org/knowledge.test.mjs +0 -326
- package/lib/org/leases.test.mjs +0 -235
- package/lib/org/mesh-directives.test.mjs +0 -110
- package/lib/org/mesh-integration.test.mjs +0 -127
- package/lib/org/mesh.test.mjs +0 -400
- package/lib/org/messaging.test.mjs +0 -471
- package/lib/org/param-contract.test.mjs +0 -477
- package/lib/org/policy.test.mjs +0 -237
- package/lib/org/protocol.checksum.test.mjs +0 -90
- package/lib/org/protocol.test.mjs +0 -323
- package/lib/org/push.test.mjs +0 -792
- package/lib/org/registry.test.mjs +0 -100
- package/lib/org/resource-tools.test.mjs +0 -361
- package/lib/org/tool-access.test.mjs +0 -144
- package/lib/org/tool-surface-integration.test.mjs +0 -120
- package/lib/org/tool-surface.test.mjs +0 -1268
- package/lib/org/typing.test.mjs +0 -291
- package/lib/org/ui-parity.test.mjs +0 -560
- package/lib/org/verify.test.mjs +0 -194
- package/lib/org/work-ledger.test.mjs +0 -273
- package/lib/plan/adoption-e2e.test.mjs +0 -366
- package/lib/plan/budget-enforcement.test.mjs +0 -400
- package/lib/plan/compile.test.mjs +0 -382
- package/lib/plan/emit.test.mjs +0 -269
- package/lib/plan/explain.test.mjs +0 -188
- package/lib/prompts/parallelism.test.mjs +0 -177
- package/lib/rag/rag.test.mjs +0 -505
- package/lib/rate-guard.test.mjs +0 -272
- package/lib/reactive-gate.test.mjs +0 -57
- package/lib/render.test.mjs +0 -68
- package/lib/resource-governor.test.mjs +0 -488
- package/lib/scheduling/dynamic-jobs.test.mjs +0 -344
- package/lib/scheduling/jitter.test.mjs +0 -140
- package/lib/secrets/broker.test.mjs +0 -280
- package/lib/secrets/providers.test.mjs +0 -274
- package/lib/security/audit-engine.test.mjs +0 -424
- package/lib/security/coerce-args.test.mjs +0 -281
- package/lib/security/dangerous-tools.test.mjs +0 -68
- package/lib/security/external-content.test.mjs +0 -84
- package/lib/security/redact.test.mjs +0 -441
- package/lib/security/secret-equal.test.mjs +0 -55
- package/lib/session/config.test.mjs +0 -92
- package/lib/session/feed-core.test.mjs +0 -198
- package/lib/session/first-run.test.mjs +0 -121
- package/lib/session/frontdoor.test.mjs +0 -205
- package/lib/session/handoffs.test.mjs +0 -183
- package/lib/session/identity.test.mjs +0 -180
- package/lib/session/inbox-claims.test.mjs +0 -286
- package/lib/session/launch-args.test.mjs +0 -157
- package/lib/session/liveness.test.mjs +0 -100
- package/lib/session/status-summary.test.mjs +0 -118
- package/lib/session-permissions.test.mjs +0 -120
- package/lib/setup/claude-probe.test.mjs +0 -187
- package/lib/setup/completeness.test.mjs +0 -110
- package/lib/setup/context-pack.test.mjs +0 -89
- package/lib/setup/enrich.test.mjs +0 -115
- package/lib/setup/enroll-from-cohort.test.mjs +0 -300
- package/lib/setup/integration.test.mjs +0 -162
- package/lib/setup/io.test.mjs +0 -77
- package/lib/setup/runner.test.mjs +0 -132
- package/lib/setup/sections/identity.test.mjs +0 -234
- package/lib/setup/sections/inventory.test.mjs +0 -198
- package/lib/setup/sections/learning.test.mjs +0 -81
- package/lib/setup/sections/mandate.test.mjs +0 -388
- package/lib/setup/sections/messaging.test.mjs +0 -127
- package/lib/setup/sections/model.test.mjs +0 -240
- package/lib/setup/sections/org.test.mjs +0 -346
- package/lib/setup/sections/orgmail.test.mjs +0 -118
- package/lib/setup/sections/recovery.test.mjs +0 -98
- package/lib/setup/sections/subagents.test.mjs +0 -429
- package/lib/setup/sections/verify.test.mjs +0 -175
- package/lib/setup/sot.test.mjs +0 -81
- package/lib/setup/state.test.mjs +0 -115
- package/lib/singleton.test.mjs +0 -151
- package/lib/subagents/cli.test.mjs +0 -389
- package/lib/subagents/client.test.mjs +0 -309
- package/lib/subagents/gap.test.mjs +0 -234
- package/lib/subagents/lock.test.mjs +0 -248
- package/lib/subagents/manifest.test.mjs +0 -175
- package/lib/subagents/refs.test.mjs +0 -204
- package/lib/subagents/resolve.test.mjs +0 -422
- package/lib/subagents/schema.test.mjs +0 -328
- package/lib/telemetry/alerts.test.mjs +0 -109
- package/lib/telemetry/collect.test.mjs +0 -1274
- package/lib/tool-definitions-integration.test.mjs +0 -83
- package/lib/tool-definitions.test.mjs +0 -437
- package/lib/upgrade/global-refresh.test.mjs +0 -65
- package/lib/upgrade/launchd-reconcile.test.mjs +0 -272
- package/lib/upgrade/post-steps.test.mjs +0 -200
- package/lib/upgrade/verify.test.mjs +0 -164
- package/lib/util/fetch-timeout.test.mjs +0 -202
- package/lib/util/reconnect.test.mjs +0 -369
- package/lib/util/unhandled.test.mjs +0 -216
- package/lib/voice/outbound.test.mjs +0 -69
- package/lib/voice/session-rotation.test.mjs +0 -114
- package/lib/voice/stt.test.mjs +0 -226
- package/lib/voice/voice.test.mjs +0 -990
- package/scripts/cadence/enqueue-cadence-tick.test.mjs +0 -187
- package/scripts/ci/check-docs-accuracy.test.mjs +0 -409
- package/scripts/ci/check-durable-write-seam.test.mjs +0 -90
- package/scripts/ci/check-no-build-artifacts.test.mjs +0 -71
- package/scripts/ci/check-no-residual-identity.test.mjs +0 -202
- package/scripts/ci/check-skill-packs.test.mjs +0 -495
- package/scripts/ci/check-subagent-frontmatter.test.mjs +0 -124
- package/scripts/ci/check.test.mjs +0 -194
- package/scripts/ci/conformance-org-api.test.mjs +0 -425
- package/scripts/cloud-relay/voice/relay-identity.test.mjs +0 -96
- package/scripts/collective/hook-runner.test.mjs +0 -173
- package/scripts/cost/fleet-digest.test.mjs +0 -207
- package/scripts/cost/track-claude-usage-pricing.test.mjs +0 -183
- package/scripts/cost/track-claude-usage.test.mjs +0 -148
- package/scripts/daemon/agent-daemon-board-mine.test.mjs +0 -96
- package/scripts/daemon/agent-daemon-design.test.mjs +0 -238
- package/scripts/daemon/agent-daemon-frontdoor.test.mjs +0 -60
- package/scripts/daemon/agent-daemon.test.mjs +0 -995
- package/scripts/daemon/assurance-e2e.test.mjs +0 -613
- package/scripts/daemon/assurance.test.mjs +0 -1791
- package/scripts/daemon/board-mirror.test.mjs +0 -165
- package/scripts/daemon/cadence-consumer-frontdoor.test.mjs +0 -393
- package/scripts/daemon/cadence-consumer-governance.test.mjs +0 -276
- package/scripts/daemon/cadence-consumer.test.mjs +0 -776
- package/scripts/daemon/cadence-handlers.test.mjs +0 -837
- package/scripts/daemon/classifier-identity.test.mjs +0 -137
- package/scripts/daemon/classifier.test.mjs +0 -266
- package/scripts/daemon/classify-kind.test.mjs +0 -40
- package/scripts/daemon/context-compiler.test.mjs +0 -406
- package/scripts/daemon/deliver.test.mjs +0 -564
- package/scripts/daemon/dispatcher-cooldown.test.mjs +0 -122
- package/scripts/daemon/dispatcher-governance.test.mjs +0 -1013
- package/scripts/daemon/dispatcher-resume.test.mjs +0 -166
- package/scripts/daemon/dispatcher-session-continuity.test.mjs +0 -365
- package/scripts/daemon/execution-ladder.test.mjs +0 -470
- package/scripts/daemon/goal-steward-cadence.test.mjs +0 -312
- package/scripts/daemon/inbox-deferral-session.test.mjs +0 -49
- package/scripts/daemon/inbox-deferral.test.mjs +0 -336
- package/scripts/daemon/inbox-wake.test.mjs +0 -199
- package/scripts/daemon/integration.test.mjs +0 -149
- package/scripts/daemon/lib/self-echo.test.mjs +0 -153
- package/scripts/daemon/lib/session-router.test.mjs +0 -554
- package/scripts/daemon/prompt-builder-preamble.test.mjs +0 -210
- package/scripts/daemon/prompt-builder.test.mjs +0 -556
- package/scripts/daemon/responder-cost.test.mjs +0 -68
- package/scripts/daemon/responder-history.test.mjs +0 -221
- package/scripts/daemon/sdk-version.test.mjs +0 -31
- package/scripts/daemon/session-lock.test.mjs +0 -252
- package/scripts/daemon/session-outcomes.test.mjs +0 -533
- package/scripts/daemon/typing-registry.test.mjs +0 -102
- package/scripts/hooks/pre-send-audit.test.mjs +0 -354
- package/scripts/huddle/huddle-prompt.test.mjs +0 -176
- package/scripts/local-triggers/autoupdate.test.mjs +0 -518
- package/scripts/local-triggers/generate-plists.test.mjs +0 -456
- package/scripts/media-generation/brand-clause.test.mjs +0 -135
- package/scripts/org/send-orgmail.first-contact.test.mjs +0 -102
- package/scripts/poller/inbox-privilege-injection.test.mjs +0 -167
- package/scripts/poller/inbox-scan-poller.test.mjs +0 -295
- package/scripts/poller/lib/cloud-relay-dedup.test.mjs +0 -133
- package/scripts/poller/slack-socket-mode.test.mjs +0 -805
- package/scripts/poller-launchd/install.test.mjs +0 -243
- package/scripts/restore-from-backup.test.mjs +0 -181
- package/scripts/session/feed.test.mjs +0 -196
- package/scripts/session/supervisor-sh.test.mjs +0 -218
- package/scripts/session/supervisor.test.mjs +0 -482
- package/scripts/setup/configure-macos.test.mjs +0 -306
- package/scripts/setup/gen-subagent-manifest.test.mjs +0 -124
- package/scripts/setup/generate-agent-package-json.test.mjs +0 -143
- package/scripts/setup/generate-capability.test.mjs +0 -134
- package/scripts/setup/init-agent.test.mjs +0 -370
- package/scripts/setup/init-skill-marketplace.test.mjs +0 -193
- package/scripts/vendor/sync-skill-packs.test.mjs +0 -103
- package/scripts/watchdog/memory-watchdog.test.mjs +0 -64
|
@@ -18,7 +18,26 @@
|
|
|
18
18
|
* House style: ESM, Node built-ins only, mirrors rate-guard's deps-injection.
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
|
-
import { DISPOSITION, asFailoverReason, cooldownMsFor } from "./taxonomy.mjs";
|
|
21
|
+
import { DISPOSITION, asFailoverReason, cooldownMsFor, MAX_SAME_RETRY_AFTER_MS } from "./taxonomy.mjs";
|
|
22
|
+
|
|
23
|
+
function defaultSleep(ms) {
|
|
24
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* The cohort refusal an attempt reported (a normalised §4.8 object, as
|
|
29
|
+
* lib/org/quota.mjs `parseRefusalBody` returns), or null.
|
|
30
|
+
* @param {object|undefined} meta
|
|
31
|
+
*/
|
|
32
|
+
function refusalOf(meta) {
|
|
33
|
+
const r = meta && meta.refusal;
|
|
34
|
+
return r && typeof r === "object" && typeof r.code === "string" && r.code.startsWith("cohort_") ? r : null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** A finite, non-negative retry-after (ms) an attempt reported, or null. */
|
|
38
|
+
function retryAfterOf(meta) {
|
|
39
|
+
return meta && Number.isFinite(meta.retryAfterMs) && meta.retryAfterMs >= 0 ? meta.retryAfterMs : null;
|
|
40
|
+
}
|
|
22
41
|
|
|
23
42
|
/**
|
|
24
43
|
* @typedef {object} Candidate
|
|
@@ -74,6 +93,15 @@ function cooldownKey(candidate, scope) {
|
|
|
74
93
|
* may advance ONCE (the "one Anthropic-only re-route
|
|
75
94
|
* permitted" rule). Default false (sticky refusal).
|
|
76
95
|
* maxAttempts → hard cap on total attempts (safety net; default 32)
|
|
96
|
+
* sleep(ms) → injected sleep for a disposition's retryBackoffMs
|
|
97
|
+
* ladder (request_timeout). Default: a real timer.
|
|
98
|
+
*
|
|
99
|
+
* A cohort refusal is handed back, never scheduled here. When the final attempt
|
|
100
|
+
* reported `meta.refusal` (budget_refusal, gateway_retry) the result carries it
|
|
101
|
+
* as `refusal`, with `retryAfterMs` when the response named one. The daemon's
|
|
102
|
+
* one scheduler for cohort holds is `rateGuard.recordCohortRefusal(refusal)`;
|
|
103
|
+
* stepChain computes no wake time for these reasons (CF-143: the earlier
|
|
104
|
+
* gateway_retry wake had no production caller — see docs/engine/README.md).
|
|
77
105
|
*
|
|
78
106
|
* @param {Candidate[]} chain
|
|
79
107
|
* @param {object} opts
|
|
@@ -83,7 +111,9 @@ function cooldownKey(candidate, scope) {
|
|
|
83
111
|
* chosen?: Candidate,
|
|
84
112
|
* reason: string|null, // final reason (null on success)
|
|
85
113
|
* tried: Array<{ref, reason, retry?:boolean, meta?:object}>,
|
|
86
|
-
* soonestCooldownExpiry: number|null, // epoch ms
|
|
114
|
+
* soonestCooldownExpiry: number|null, // epoch ms a BREAKER cooldown ends, or null
|
|
115
|
+
* refusal?: object|null, // the final attempt's cohort refusal (resetsAt, window, scope)
|
|
116
|
+
* retryAfterMs?: number|null, // the final attempt's retry-after
|
|
87
117
|
* attempts: number
|
|
88
118
|
* }>}
|
|
89
119
|
*/
|
|
@@ -94,6 +124,7 @@ export async function stepChain(chain, opts = {}) {
|
|
|
94
124
|
const onAttempt = typeof opts.onAttempt === "function" ? opts.onAttempt : null;
|
|
95
125
|
const allowContentReroute = opts.allowContentReroute === true;
|
|
96
126
|
const maxAttempts = Number.isFinite(opts.maxAttempts) ? opts.maxAttempts : 32;
|
|
127
|
+
const sleep = typeof opts.sleep === "function" ? opts.sleep : defaultSleep;
|
|
97
128
|
|
|
98
129
|
const tried = [];
|
|
99
130
|
const candidates = Array.isArray(chain) ? chain.filter(Boolean) : [];
|
|
@@ -142,14 +173,14 @@ export async function stepChain(chain, opts = {}) {
|
|
|
142
173
|
|
|
143
174
|
// ── Attempt this candidate, with same-candidate retry where allowed ──
|
|
144
175
|
let advanced = false;
|
|
145
|
-
let
|
|
176
|
+
let sameRetries = 0;
|
|
146
177
|
while (attempts < maxAttempts) {
|
|
147
178
|
attempts++;
|
|
148
179
|
let res;
|
|
149
180
|
try {
|
|
150
181
|
res = await attempt(candidate, {
|
|
151
182
|
attemptIndex: attempts - 1,
|
|
152
|
-
retryOfRef:
|
|
183
|
+
retryOfRef: sameRetries > 0 ? candidate.ref : null,
|
|
153
184
|
now: now(),
|
|
154
185
|
});
|
|
155
186
|
} catch (err) {
|
|
@@ -182,7 +213,7 @@ export async function stepChain(chain, opts = {}) {
|
|
|
182
213
|
const record = {
|
|
183
214
|
ref: candidate.ref,
|
|
184
215
|
reason,
|
|
185
|
-
retry:
|
|
216
|
+
retry: sameRetries > 0 || undefined,
|
|
186
217
|
meta: res.meta,
|
|
187
218
|
};
|
|
188
219
|
tried.push(record);
|
|
@@ -212,13 +243,25 @@ export async function stepChain(chain, opts = {}) {
|
|
|
212
243
|
}
|
|
213
244
|
|
|
214
245
|
// ── retry-same? ──────────────────────────────────────────────────
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
246
|
+
// A row with a retryBackoffMs ladder retries once per entry, sleeping
|
|
247
|
+
// first (request_timeout, CF-143); any other retrying row retries once,
|
|
248
|
+
// immediately. "cli" means the CLI already retried internally.
|
|
249
|
+
const ladder = Array.isArray(d.retryBackoffMs) ? d.retryBackoffMs : null;
|
|
250
|
+
const sameBudget = ladder ? ladder.length : d.retrySame && d.retrySame !== "cli" ? 1 : 0;
|
|
251
|
+
if (sameRetries < sameBudget) {
|
|
252
|
+
if (ladder) {
|
|
253
|
+
const ra = retryAfterOf(res.meta);
|
|
254
|
+
// A retry-after longer than the ceiling is not slept through: fail over.
|
|
255
|
+
if (ra != null && ra > MAX_SAME_RETRY_AFTER_MS) {
|
|
256
|
+
sameRetries = sameBudget;
|
|
257
|
+
} else {
|
|
258
|
+
await sleep(ra != null ? ra : ladder[sameRetries]);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
if (sameRetries < sameBudget) {
|
|
262
|
+
sameRetries++;
|
|
263
|
+
continue; // retry the SAME candidate
|
|
264
|
+
}
|
|
222
265
|
}
|
|
223
266
|
|
|
224
267
|
// ── failover-next? ───────────────────────────────────────────────
|
|
@@ -232,12 +275,15 @@ export async function stepChain(chain, opts = {}) {
|
|
|
232
275
|
break; // advance to next candidate
|
|
233
276
|
}
|
|
234
277
|
// No failover for this reason (context_overflow / payload_too_large /
|
|
235
|
-
// network / sticky content_policy
|
|
278
|
+
// network / sticky content_policy / budget_refusal / gateway_retry).
|
|
279
|
+
// Terminate the whole walk; a cohort refusal rides back to the caller.
|
|
236
280
|
return {
|
|
237
281
|
ok: false,
|
|
238
282
|
reason,
|
|
239
283
|
tried,
|
|
240
284
|
soonestCooldownExpiry, // null for network/overflow (no cooldown)
|
|
285
|
+
refusal: refusalOf(res.meta),
|
|
286
|
+
retryAfterMs: retryAfterOf(res.meta),
|
|
241
287
|
attempts,
|
|
242
288
|
chosen: undefined,
|
|
243
289
|
};
|
|
@@ -251,11 +297,14 @@ export async function stepChain(chain, opts = {}) {
|
|
|
251
297
|
}
|
|
252
298
|
|
|
253
299
|
// ── Chain exhausted ──────────────────────────────────────────────────
|
|
300
|
+
const lastMeta = tried.length ? tried[tried.length - 1].meta : undefined;
|
|
254
301
|
return {
|
|
255
302
|
ok: false,
|
|
256
303
|
reason: lastReason || "unknown",
|
|
257
304
|
tried,
|
|
258
305
|
soonestCooldownExpiry,
|
|
306
|
+
refusal: refusalOf(lastMeta),
|
|
307
|
+
retryAfterMs: retryAfterOf(lastMeta),
|
|
259
308
|
attempts,
|
|
260
309
|
chosen: undefined,
|
|
261
310
|
};
|
|
@@ -288,11 +337,13 @@ export function summarizeExhaustion(summary) {
|
|
|
288
337
|
const trail = (summary.tried || [])
|
|
289
338
|
.map((t) => `${t.ref ?? "?"}=${t.reason}`)
|
|
290
339
|
.join(" → ");
|
|
291
|
-
const
|
|
292
|
-
|
|
340
|
+
const r = summary.refusal;
|
|
341
|
+
const wake = r
|
|
342
|
+
? `cohort refusal ${r.code}${r.resetsAt ? ` (resets ${r.resetsAt})` : ""}; hold the seat via rate-guard, do not re-queue on this summary`
|
|
343
|
+
: summary.soonestCooldownExpiry == null
|
|
293
344
|
? "no cooldown (re-queue immediately / DEFER)"
|
|
294
345
|
: `wake at ${new Date(summary.soonestCooldownExpiry).toISOString()}`;
|
|
295
346
|
return `chain exhausted (final=${summary.reason}): ${trail}; ${wake}`;
|
|
296
347
|
}
|
|
297
348
|
|
|
298
|
-
export const _internals = { cooldownKey, tableCooldownUntil, noopBreaker };
|
|
349
|
+
export const _internals = { cooldownKey, tableCooldownUntil, noopBreaker, refusalOf, retryAfterOf };
|
|
@@ -36,8 +36,9 @@
|
|
|
36
36
|
import { loadCatalogCached, listModels, lookupModel } from "./catalog.mjs";
|
|
37
37
|
import { estimateCost, writeLedgerRow } from "./ledger.mjs";
|
|
38
38
|
import { keyFor, isOpen as healthIsOpen } from "./health.mjs";
|
|
39
|
-
import { classifyHttp, classifyTransportError } from "./taxonomy.mjs";
|
|
39
|
+
import { classifyHttp, classifyTransportError, DISPOSITION } from "./taxonomy.mjs";
|
|
40
40
|
import { repairToolCalls } from "./repair.mjs";
|
|
41
|
+
import { parseRefusalBody } from "../org/quota.mjs";
|
|
41
42
|
|
|
42
43
|
const GRADE_RANK = { A: 3, B: 2, C: 1 };
|
|
43
44
|
const DATA_CLASSES = new Set(["public", "internal", "sensitive"]);
|
|
@@ -389,7 +390,21 @@ export async function llmTask(prompt, opts = {}) {
|
|
|
389
390
|
if (!ok) {
|
|
390
391
|
const reason = classifyHttp(status, parsedBody ?? rawText ?? null);
|
|
391
392
|
writeRow(catalog, row, wire, {}, opts, request, reason);
|
|
392
|
-
|
|
393
|
+
// retry-after (seconds) rides along so a caller can honour it.
|
|
394
|
+
let retryAfterMs = null;
|
|
395
|
+
try {
|
|
396
|
+
const ra = res && res.headers && typeof res.headers.get === "function" ? res.headers.get("retry-after") : null;
|
|
397
|
+
const secs = ra == null || ra === "" ? NaN : Number(ra);
|
|
398
|
+
if (Number.isFinite(secs) && secs >= 0) retryAfterMs = Math.round(secs * 1000);
|
|
399
|
+
} catch { /* advisory */ }
|
|
400
|
+
// A cohort refusal object (§4.8: code, window, scope, resetsAt, requestId)
|
|
401
|
+
// is preserved verbatim, so a scheduler can hold until resetsAt
|
|
402
|
+
// (rateGuard.recordCohortRefusal takes exactly this shape).
|
|
403
|
+
const refusal = safe(() => parseRefusalBody(parsedBody ?? rawText ?? null), null);
|
|
404
|
+
const extra = { model: row.id, provider: row.provider, ref: row.ref, wire, status, retryAfterMs, refusal };
|
|
405
|
+
// A budget refusal is typed non-retryable (x-should-retry: false, CF-143).
|
|
406
|
+
if (reason === "budget_refusal") extra.retryable = false;
|
|
407
|
+
return fail(reason, extra);
|
|
393
408
|
}
|
|
394
409
|
|
|
395
410
|
const { text, usage } = parseResponse(parsedBody, wire);
|
|
@@ -453,6 +468,27 @@ export async function llmTask(prompt, opts = {}) {
|
|
|
453
468
|
return out;
|
|
454
469
|
}
|
|
455
470
|
|
|
471
|
+
/**
|
|
472
|
+
* An llmTask result as a stepChain AttemptResult: the reason drives the
|
|
473
|
+
* disposition table, and `meta` carries what the table and the caller need —
|
|
474
|
+
* the status, retry-after (a request_timeout ladder honours it) and the cohort
|
|
475
|
+
* refusal (stepChain hands it back as `outcome.refusal`). PURE.
|
|
476
|
+
*
|
|
477
|
+
* @param {object} res an llmTask result
|
|
478
|
+
* @returns {{ok:boolean, reason?:string, value?:object, meta:{status:number|null, retryAfterMs:number|null, refusal:object|null, retryable?:boolean}}}
|
|
479
|
+
*/
|
|
480
|
+
export function attemptResultOf(res) {
|
|
481
|
+
if (!res || typeof res !== "object") return { ok: false, reason: "unknown", meta: { status: null, retryAfterMs: null, refusal: null } };
|
|
482
|
+
const meta = {
|
|
483
|
+
status: Number.isFinite(res.status) ? res.status : null,
|
|
484
|
+
retryAfterMs: Number.isFinite(res.retryAfterMs) ? res.retryAfterMs : null,
|
|
485
|
+
refusal: res.refusal || null,
|
|
486
|
+
};
|
|
487
|
+
if (typeof res.retryable === "boolean") meta.retryable = res.retryable;
|
|
488
|
+
if (res.ok === true) return { ok: true, value: res, meta };
|
|
489
|
+
return { ok: false, reason: DISPOSITION[res.reason] ? res.reason : "unknown", meta };
|
|
490
|
+
}
|
|
491
|
+
|
|
456
492
|
/** Pick the cheapest anthropic direct row (kill-switch path). */
|
|
457
493
|
function pickAnthropicDirect(catalog, request, opts) {
|
|
458
494
|
const minRank = GRADE_RANK[opts.minGrade || request.minGrade || "C"] || GRADE_RANK.C;
|
|
@@ -512,4 +548,4 @@ export const _internals = {
|
|
|
512
548
|
credentialEnvFor,
|
|
513
549
|
};
|
|
514
550
|
|
|
515
|
-
export default { llmTask, pickDirectModel };
|
|
551
|
+
export default { llmTask, pickDirectModel, attemptResultOf };
|
|
@@ -34,6 +34,7 @@
|
|
|
34
34
|
*/
|
|
35
35
|
|
|
36
36
|
import { loadCatalogCached, lookupModel } from "./catalog.mjs";
|
|
37
|
+
import { hasSeatCredential, apiKeyHelperCommand } from "../org/llm-token.mjs";
|
|
37
38
|
import { estimateCost } from "./ledger.mjs";
|
|
38
39
|
import { keyFor, isOpen as breakerIsOpen } from "./health.mjs";
|
|
39
40
|
import { nextCredential as authNextCredential, available as authAvailable } from "./auth-profiles.mjs";
|
|
@@ -364,7 +365,10 @@ function gateCandidate(row, req, ctx) {
|
|
|
364
365
|
|
|
365
366
|
// 5. data_class — deny-by-default. The provider's allowed_data_classes
|
|
366
367
|
// (config backends ∩ overlay max) must include the request's data_class.
|
|
367
|
-
|
|
368
|
+
// A FIRST-PARTY provider (Cohort's own gateway) declares its allowance in
|
|
369
|
+
// the catalog; a config backends entry or overlay cap still narrows it.
|
|
370
|
+
const allowed = ctx.allowedDataClasses[row.provider] ||
|
|
371
|
+
(row.first_party === true && Array.isArray(row.data_classes) ? new Set(row.data_classes) : null);
|
|
368
372
|
if (allowed && !allowed.has(req.data_class)) {
|
|
369
373
|
return { ok: false, reason: "data_class_denied" };
|
|
370
374
|
}
|
|
@@ -381,12 +385,26 @@ function gateCandidate(row, req, ctx) {
|
|
|
381
385
|
// anthropic-cli the same way. Direct-api Anthropic and ALL third-party rows
|
|
382
386
|
// require their credential or they are structurally unreachable.
|
|
383
387
|
const sessionAnthropic = row.provider === "anthropic" && harness === "session";
|
|
384
|
-
|
|
388
|
+
// A seat-token row (cohort-llm) has no static key: it is reachable when the
|
|
389
|
+
// seat can mint a token — it holds an OrgApiKey or a live cached token.
|
|
390
|
+
if (row.auth === "seat_token") {
|
|
391
|
+
if (!(typeof ctx.seatCredential === "function" && ctx.seatCredential())) {
|
|
392
|
+
return { ok: false, reason: "missing_credential" };
|
|
393
|
+
}
|
|
394
|
+
} else if (row.auth_env && !sessionAnthropic) {
|
|
385
395
|
const cred = resolveRowCredential(row, ctx);
|
|
386
396
|
if (!cred.token) {
|
|
387
397
|
return { ok: false, reason: "missing_credential" };
|
|
388
398
|
}
|
|
389
399
|
}
|
|
400
|
+
// A third-party SESSION row with no auth_env at all used to pass this gate
|
|
401
|
+
// (the check above only runs when auth_env is set) and was then retargeted
|
|
402
|
+
// with a base URL and no token — leaving the seat's Claude subscription token
|
|
403
|
+
// as the child's only credential, sent to that backend (F1 §5.3). Such a row
|
|
404
|
+
// is unreachable: it cannot authenticate as itself.
|
|
405
|
+
if (row.auth !== "seat_token" && !row.auth_env && harness === "session" && row.provider !== "anthropic") {
|
|
406
|
+
return { ok: false, reason: "missing_credential" };
|
|
407
|
+
}
|
|
390
408
|
|
|
391
409
|
// 7. breaker — the per-backend AND per-backend:model breaker must be closed.
|
|
392
410
|
const backendKey = keyFor(row.provider);
|
|
@@ -421,6 +439,9 @@ function modelClassOf(row) {
|
|
|
421
439
|
function buildEnvForSpawn(row, harness, env, config, credCtx) {
|
|
422
440
|
if (harness !== "session") return {};
|
|
423
441
|
if (row.provider === "anthropic") return {};
|
|
442
|
+
if (row.auth === "seat_token") {
|
|
443
|
+
return cohortRetargetEnv(row, { agentRoot: (credCtx && credCtx.agentRoot) || env.AGENT_ROOT || env.AGENT_DIR });
|
|
444
|
+
}
|
|
424
445
|
|
|
425
446
|
const baseUrl = row.endpoints && row.endpoints.anthropic;
|
|
426
447
|
if (!baseUrl) return {}; // not session-capable on the anthropic wire; caller falls back
|
|
@@ -434,14 +455,23 @@ function buildEnvForSpawn(row, harness, env, config, credCtx) {
|
|
|
434
455
|
// Auth token: a pooled auth-profile set picks the next non-cooled credential
|
|
435
456
|
// (additive — a single-key setup falls through to env[auth_env] exactly as
|
|
436
457
|
// before). credCtx carries { authProfiles, authProfileDeps, now, env }.
|
|
458
|
+
// Only a NON-EMPTY token is set. An absent one leaves ANTHROPIC_AUTH_TOKEN
|
|
459
|
+
// out of the retarget set, and the runtime env builder
|
|
460
|
+
// (lib/runtime/child-env.mjs) refuses to spawn a retarget without it — so the
|
|
461
|
+
// seat's own Claude credential can never be the token that reaches this
|
|
462
|
+
// backend.
|
|
437
463
|
if (row.auth_env) {
|
|
438
464
|
const cred = credCtx ? resolveRowCredential(row, credCtx) : null;
|
|
439
465
|
if (cred && cred.pooled && cred.token) {
|
|
440
466
|
out.ANTHROPIC_AUTH_TOKEN = cred.token;
|
|
441
|
-
} else if (typeof env[row.auth_env] === "string") {
|
|
467
|
+
} else if (typeof env[row.auth_env] === "string" && env[row.auth_env].trim() !== "") {
|
|
442
468
|
out.ANTHROPIC_AUTH_TOKEN = env[row.auth_env];
|
|
443
469
|
}
|
|
444
470
|
}
|
|
471
|
+
// `strip_attribution_header: false` still shapes the DECISION record, but the
|
|
472
|
+
// runtime env builder forces CLAUDE_CODE_ATTRIBUTION_HEADER=0 on every
|
|
473
|
+
// retarget: a per-request attribution header poisons a third-party prompt
|
|
474
|
+
// cache, and nothing on this path wants that.
|
|
445
475
|
if (!config || config.strip_attribution_header !== false) {
|
|
446
476
|
out.CLAUDE_CODE_ATTRIBUTION_HEADER = "0";
|
|
447
477
|
}
|
|
@@ -451,6 +481,47 @@ function buildEnvForSpawn(row, harness, env, config, credCtx) {
|
|
|
451
481
|
return out;
|
|
452
482
|
}
|
|
453
483
|
|
|
484
|
+
/** How often Claude Code re-runs the cohort key helper (well inside a 60-min token). */
|
|
485
|
+
export const COHORT_HELPER_TTL_MS = 15 * 60 * 1000;
|
|
486
|
+
|
|
487
|
+
/**
|
|
488
|
+
* The retarget env for a seat-token (cohort-llm) session row — the internal
|
|
489
|
+
* Path B testbed, where the `claude` binary is pointed at Cohort's gateway
|
|
490
|
+
* (design C1 / §4.7):
|
|
491
|
+
* - ANTHROPIC_BASE_URL = the gateway; NO ANTHROPIC_AUTH_TOKEN. The credential
|
|
492
|
+
* is the apiKeyHelper (COHORT_LLM_API_KEY_HELPER, which the runtime adapter
|
|
493
|
+
* passes as `--settings`), because a static token outranks the helper and
|
|
494
|
+
* would expire mid-session (lib/runtime/child-env.mjs COHORT_HELPER_KEY);
|
|
495
|
+
* - every Anthropic slot alias maps to a Cohort tier through the provider's
|
|
496
|
+
* tier_map (ANTHROPIC_DEFAULT_{OPUS,SONNET,HAIKU,FABLE}_MODEL), so a
|
|
497
|
+
* `--model sonnet` or a subagent's `haiku` never names an Anthropic model at
|
|
498
|
+
* the gateway.
|
|
499
|
+
* Exported for spawnRouted's per-candidate failover env. PURE.
|
|
500
|
+
* @param {object} row a catalog row with auth: "seat_token"
|
|
501
|
+
* @param {{agentRoot?:string, nodeBin?:string}} [o]
|
|
502
|
+
*/
|
|
503
|
+
export function cohortRetargetEnv(row, o = {}) {
|
|
504
|
+
const baseUrl = row && row.endpoints && row.endpoints.anthropic;
|
|
505
|
+
if (!baseUrl) return {};
|
|
506
|
+
const tm = (row.tier_map && typeof row.tier_map === "object") ? row.tier_map : {};
|
|
507
|
+
return {
|
|
508
|
+
ANTHROPIC_BASE_URL: baseUrl,
|
|
509
|
+
ANTHROPIC_API_KEY: "",
|
|
510
|
+
ANTHROPIC_MODEL: row.id,
|
|
511
|
+
ANTHROPIC_SMALL_FAST_MODEL: tm.haiku || row.id,
|
|
512
|
+
ANTHROPIC_DEFAULT_FABLE_MODEL: tm.fable || row.id,
|
|
513
|
+
ANTHROPIC_DEFAULT_OPUS_MODEL: tm.opus || row.id,
|
|
514
|
+
ANTHROPIC_DEFAULT_SONNET_MODEL: tm.sonnet || row.id,
|
|
515
|
+
ANTHROPIC_DEFAULT_HAIKU_MODEL: tm.haiku || row.id,
|
|
516
|
+
CLAUDE_CODE_SUBAGENT_MODEL: row.id,
|
|
517
|
+
COHORT_LLM_BASE_URL: baseUrl,
|
|
518
|
+
COHORT_LLM_API_KEY_HELPER: apiKeyHelperCommand({ agentRoot: o.agentRoot, nodeBin: o.nodeBin }),
|
|
519
|
+
CLAUDE_CODE_API_KEY_HELPER_TTL_MS: String(COHORT_HELPER_TTL_MS),
|
|
520
|
+
CLAUDE_CODE_ATTRIBUTION_HEADER: "0",
|
|
521
|
+
CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS: "1",
|
|
522
|
+
};
|
|
523
|
+
}
|
|
524
|
+
|
|
454
525
|
function transportFor(row, harness) {
|
|
455
526
|
if (harness === "session") {
|
|
456
527
|
if (row.provider === "anthropic") return TRANSPORT.anthropicApi;
|
|
@@ -630,6 +701,21 @@ export function resolveChain(request = {}, opts = {}) {
|
|
|
630
701
|
authProfiles: opts.authProfiles || null,
|
|
631
702
|
authProfileDeps: opts.authProfileDeps || (agentRoot ? { agentRoot } : undefined),
|
|
632
703
|
now,
|
|
704
|
+
agentRoot: agentRoot || env.AGENT_ROOT || env.AGENT_DIR || null,
|
|
705
|
+
// Seat-token (cohort-llm) reachability, asked at most once per resolve.
|
|
706
|
+
seatCredential: (() => {
|
|
707
|
+
let memo;
|
|
708
|
+
return () => {
|
|
709
|
+
if (memo === undefined) {
|
|
710
|
+
memo = typeof opts.seatCredential === "function"
|
|
711
|
+
? !!opts.seatCredential()
|
|
712
|
+
// helperReadable: a Path B child authenticates through the
|
|
713
|
+
// apiKeyHelper, which cannot see an env-only key (it is scrubbed).
|
|
714
|
+
: hasSeatCredential({ agentRoot: agentRoot || env.AGENT_ROOT || env.AGENT_DIR, env, now, helperReadable: true });
|
|
715
|
+
}
|
|
716
|
+
return memo;
|
|
717
|
+
};
|
|
718
|
+
})(),
|
|
633
719
|
};
|
|
634
720
|
|
|
635
721
|
let chosenRow = null;
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
* @module lib/model-router/spawn
|
|
40
40
|
*/
|
|
41
41
|
|
|
42
|
-
import { resolveChain } from "./resolve.mjs";
|
|
42
|
+
import { resolveChain, cohortRetargetEnv } from "./resolve.mjs";
|
|
43
43
|
import { stepChain } from "./failover.mjs";
|
|
44
44
|
import { keyFor, isOpen as healthIsOpen, open as healthOpen, close as healthClose } from "./health.mjs";
|
|
45
45
|
import { lookupModel } from "./catalog.mjs";
|
|
@@ -47,43 +47,38 @@ import {
|
|
|
47
47
|
nextCredential as authNextCredential,
|
|
48
48
|
markCredentialCoolingDown as authMarkCooling,
|
|
49
49
|
} from "./auth-profiles.mjs";
|
|
50
|
+
import { buildRuntimeEnv, isCredentialKey, RETARGET_KEYS } from "../runtime/child-env.mjs";
|
|
50
51
|
|
|
51
52
|
// Failover reasons that indicate the CREDENTIAL itself is the problem (a 429 /
|
|
52
53
|
// auth failure on this key) — these cool THAT credential in the pool so the next
|
|
53
54
|
// attempt rotates to a sibling. A backend/model-class reason (overloaded,
|
|
54
|
-
// server_error, …) cools the breaker, not the key.
|
|
55
|
+
// server_error, …) cools the breaker, not the key. A cohort budget refusal
|
|
56
|
+
// (`budget_refusal`, CF-143) and a gateway transient (`gateway_retry`) are never
|
|
57
|
+
// here: the credential is healthy, and a sibling key of the same tenant would be
|
|
58
|
+
// refused again or would slip past an operator limit.
|
|
55
59
|
const CRED_COOL_REASONS = new Set(["rate_limit", "auth", "auth_permanent", "billing"]);
|
|
56
60
|
|
|
57
|
-
// Env keys that the session-retarget set is allowed to introduce. Everything
|
|
58
|
-
// else credential-shaped is scrubbed (§7.3) before injection.
|
|
59
|
-
const RETARGET_KEYS = new Set([
|
|
60
|
-
"ANTHROPIC_BASE_URL",
|
|
61
|
-
"ANTHROPIC_AUTH_TOKEN",
|
|
62
|
-
"ANTHROPIC_API_KEY",
|
|
63
|
-
"ANTHROPIC_MODEL",
|
|
64
|
-
"ANTHROPIC_SMALL_FAST_MODEL",
|
|
65
|
-
"CLAUDE_CODE_ATTRIBUTION_HEADER",
|
|
66
|
-
"CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS",
|
|
67
|
-
]);
|
|
68
|
-
|
|
69
61
|
// ---------------------------------------------------------------------------
|
|
70
62
|
// Child-env scrubbing (graft #3 / §7.3)
|
|
71
63
|
// ---------------------------------------------------------------------------
|
|
72
64
|
|
|
73
65
|
/**
|
|
74
|
-
* Build the child env by allowlist
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
66
|
+
* Build the child env by allowlist. A thin compatibility wrapper over the one
|
|
67
|
+
* runtime env builder, `lib/runtime/child-env.mjs#buildRuntimeEnv`, which owns
|
|
68
|
+
* the rules:
|
|
69
|
+
*
|
|
70
|
+
* - stock (envForSpawn has no ANTHROPIC_BASE_URL): every foreign
|
|
71
|
+
* credential-shaped var is scrubbed; the seat's CLAUDE_CODE_OAUTH_TOKEN is
|
|
72
|
+
* kept; ANTHROPIC_API_KEY is kept only with opts.keepAnthropicApiKey.
|
|
73
|
+
* - retarget: CLAUDE_CODE_OAUTH_TOKEN and every inherited ANTHROPIC_* var are
|
|
74
|
+
* scrubbed too, ANTHROPIC_API_KEY="" and CLAUDE_CODE_ATTRIBUTION_HEADER=0
|
|
75
|
+
* are forced, and a retarget without a third-party token is REFUSED.
|
|
79
76
|
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
* the retarget set and is injected verbatim (empty-string discipline, W:
|
|
86
|
-
* keychain fallthrough).
|
|
77
|
+
* A refusal is an expected failure, so callers that can meet one (every spawn
|
|
78
|
+
* site) call buildRuntimeEnv and branch on `ok`. This wrapper exists for the
|
|
79
|
+
* callers that hand it a decision the resolver already credential-gated; for
|
|
80
|
+
* them a refusal means the decision was built wrong, which is a defect — so it
|
|
81
|
+
* throws rather than return an env that could be spawned.
|
|
87
82
|
*
|
|
88
83
|
* @param {object} baseEnv usually process.env
|
|
89
84
|
* @param {object} envForSpawn decision.envForSpawn (the retarget pair) or {}
|
|
@@ -92,27 +87,13 @@ const RETARGET_KEYS = new Set([
|
|
|
92
87
|
* @returns {object} the child env
|
|
93
88
|
*/
|
|
94
89
|
export function buildChildEnv(baseEnv, envForSpawn = {}, opts = {}) {
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
}
|
|
101
|
-
// For a retarget (third-party session), force the empty-string ANTHROPIC_API_KEY
|
|
102
|
-
// so Claude Code does NOT fall back to keychain OAuth.
|
|
103
|
-
const retargeting = !!(envForSpawn && envForSpawn.ANTHROPIC_BASE_URL);
|
|
104
|
-
if (!retargeting && opts.keepAnthropicApiKey && typeof src.ANTHROPIC_API_KEY === "string") {
|
|
105
|
-
out.ANTHROPIC_API_KEY = src.ANTHROPIC_API_KEY;
|
|
106
|
-
}
|
|
107
|
-
// Inject only the retarget set (whitelisted keys).
|
|
108
|
-
for (const [k, v] of Object.entries(envForSpawn || {})) {
|
|
109
|
-
if (RETARGET_KEYS.has(k)) out[k] = v;
|
|
90
|
+
const r = buildRuntimeEnv({ baseEnv, retarget: envForSpawn, keepAnthropicApiKey: !!opts.keepAnthropicApiKey });
|
|
91
|
+
if (!r.ok) {
|
|
92
|
+
const err = new Error(r.error.message);
|
|
93
|
+
err.code = r.error.code;
|
|
94
|
+
throw err;
|
|
110
95
|
}
|
|
111
|
-
return
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
function isCredentialKey(k) {
|
|
115
|
-
return /(_API_KEY|_AUTH_TOKEN|_SECRET|_ACCESS_KEY|_PRIVATE_KEY)$/i.test(k) || k === "ANTHROPIC_API_KEY";
|
|
96
|
+
return r.env;
|
|
116
97
|
}
|
|
117
98
|
|
|
118
99
|
// ---------------------------------------------------------------------------
|
|
@@ -193,6 +174,9 @@ function envForCandidate(catalog, ref, harness, baseEnv, decision, authCtx) {
|
|
|
193
174
|
}
|
|
194
175
|
if (!row || harness !== "session") return isChosen ? decision.envForSpawn || {} : {};
|
|
195
176
|
if (row.provider === "anthropic") return isChosen ? decision.envForSpawn || {} : {};
|
|
177
|
+
// A seat-token (cohort-llm) candidate authenticates through the key helper,
|
|
178
|
+
// never a pooled/static token.
|
|
179
|
+
if (row.auth === "seat_token") return cohortRetargetEnv(row, { agentRoot: baseEnv && (baseEnv.AGENT_ROOT || baseEnv.AGENT_DIR) });
|
|
196
180
|
const baseUrl = row.endpoints && row.endpoints.anthropic;
|
|
197
181
|
if (!baseUrl) return isChosen ? decision.envForSpawn || {} : {};
|
|
198
182
|
const out = {
|
|
@@ -393,8 +377,20 @@ export async function spawnRouted(request = {}, opts = {}) {
|
|
|
393
377
|
// strip it from the retarget set so it can never leak into the child env.
|
|
394
378
|
const credFingerprint = retarget.__credFingerprint || null;
|
|
395
379
|
if (retarget.__credFingerprint !== undefined) delete retarget.__credFingerprint;
|
|
396
|
-
const
|
|
380
|
+
const built = buildRuntimeEnv({ baseEnv: env, retarget, keepAnthropicApiKey: !!opts.keepAnthropicApiKey });
|
|
397
381
|
lastArgs = args;
|
|
382
|
+
if (!built.ok) {
|
|
383
|
+
// Refused retarget (no third-party token, a subscription token as the
|
|
384
|
+
// token, a helper aimed at another gateway): this candidate cannot be
|
|
385
|
+
// spawned without handing it the seat's own credential. Nothing left the
|
|
386
|
+
// machine, so nothing is known about the backend: fail over as a
|
|
387
|
+
// LOCAL refusal, which opens no breaker (CF-11 — reporting it as `auth`
|
|
388
|
+
// cooled a real provider for 5 minutes on a local config gap) and cools
|
|
389
|
+
// no credential.
|
|
390
|
+
lastEnv = null;
|
|
391
|
+
return { ok: false, reason: "local_refusal", meta: { refused: built.error.code } };
|
|
392
|
+
}
|
|
393
|
+
const childEnv = built.env;
|
|
398
394
|
lastEnv = childEnv;
|
|
399
395
|
|
|
400
396
|
let res;
|
|
@@ -442,6 +438,9 @@ export async function spawnRouted(request = {}, opts = {}) {
|
|
|
442
438
|
env: lastEnv,
|
|
443
439
|
outcome,
|
|
444
440
|
tried: outcome.tried || [],
|
|
441
|
+
// A cohort refusal the walk stopped on (budget_refusal / gateway_retry),
|
|
442
|
+
// with resetsAt: hand it to rateGuard.recordCohortRefusal to hold the seat.
|
|
443
|
+
refusal: outcome.refusal || null,
|
|
445
444
|
ok: outcome.ok === true,
|
|
446
445
|
};
|
|
447
446
|
}
|