@cohortapp/agent-sdk 2.17.0 → 2.18.4
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.mjs +8 -6
- package/lib/collective/global-config.mjs +63 -1
- 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 +823 -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 +208 -9
- 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/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 +29 -2
- 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 +1094 -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 +448 -78
- 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
|
@@ -0,0 +1,823 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/runtime/adapter.mjs — the runtime adapter seam.
|
|
3
|
+
*
|
|
4
|
+
* The ONE owner of "which agent runtime, which binary, which argv, which env"
|
|
5
|
+
* for every JavaScript site that starts an agent runtime. A site describes what
|
|
6
|
+
* it wants (`lane`, `model`, `prompt`, …); `buildSpawn` answers with the exact
|
|
7
|
+
* `{bin, argv, env}` to hand to `spawn`/`execFile`/a multiplexer. Before this
|
|
8
|
+
* seam each of the 17 sites below built its own argv inline and five of them
|
|
9
|
+
* hand-built their own Claude auth env, so swapping the runtime meant editing
|
|
10
|
+
* ~20 files and re-deriving each one's credential rules (A1 §11).
|
|
11
|
+
*
|
|
12
|
+
* Engines
|
|
13
|
+
* -------
|
|
14
|
+
* claude the Claude Code CLI. Implemented; the default for every seat.
|
|
15
|
+
* cohort the Cohort Engine (lib/engine/cli.mjs `cohort run`). buildSpawn
|
|
16
|
+
* maps the lane's claude `--print` argv onto engine flags
|
|
17
|
+
* (`translateClaudeArgv`), runs it under this node, and hands it the
|
|
18
|
+
* gateway URL + a seat token with every Anthropic/Claude credential
|
|
19
|
+
* scrubbed. A flag the engine does not support yet is a returned
|
|
20
|
+
* `engine_flag_unsupported` naming the flag — never silently dropped
|
|
21
|
+
* (dropping `--allowedTools` would WIDEN a session's tools). No seat
|
|
22
|
+
* runs it by default (design §8.4: flip per seat behind parity gates).
|
|
23
|
+
* Outbound sends (CF-20): the engine screens and counts every
|
|
24
|
+
* `mcp__*__{messaging_send,…}` call with its in-process send gate,
|
|
25
|
+
* so this adapter mounts NO pre-send-audit hook for an engine spawn
|
|
26
|
+
* (it passes no hook-bearing --settings at all). The seat's global
|
|
27
|
+
* ~/.claude/settings.json still carries that hook for the claude
|
|
28
|
+
* binary; the engine's hook runner skips it for the gated tools
|
|
29
|
+
* (lib/engine/hooks.mjs `supersededBySendGate`), so a send is
|
|
30
|
+
* counted once.
|
|
31
|
+
*
|
|
32
|
+
* cohort-llm admission
|
|
33
|
+
* --------------------
|
|
34
|
+
* A session routed to cohort-llm (engine cohort, or the claude binary
|
|
35
|
+
* retargeted to the cohort provider) must not start when the seat's quota
|
|
36
|
+
* already refuses it: stock Claude Code retries a 429 up to ten times and
|
|
37
|
+
* ignores the gateway's quota headers (design C6), so the gate BEFORE the spawn
|
|
38
|
+
* is the barrier. `admitSpawn` (async) / `admitSpawnCached` (sync, from the
|
|
39
|
+
* quota cache) answer with a typed deferral the call site turns into a requeue.
|
|
40
|
+
*
|
|
41
|
+
* Lanes (the JS spawn sites, A1 §3)
|
|
42
|
+
* ---------------------------------
|
|
43
|
+
* dispatcher scripts/daemon/dispatcher.mjs spawnSession --print json, prompt argv, retargetable
|
|
44
|
+
* resume scripts/daemon/dispatcher.mjs buildResumeSpawn same shape as dispatcher
|
|
45
|
+
* cadence scripts/daemon/cadence-consumer.mjs --print json, prompt argv, retargetable
|
|
46
|
+
* responder scripts/daemon/responder.mjs runClaudeCLI --print json, prompt stdin
|
|
47
|
+
* classifier scripts/daemon/classifier.mjs runClaudeCLI --print, prompt stdin
|
|
48
|
+
* ack scripts/daemon/assurance.mjs runAckModel --print, prompt stdin
|
|
49
|
+
* lookup lib/action-executor.js executeLookup --print -p <prompt>
|
|
50
|
+
* capture lib/collective/capture.mjs defaultDistiller --print, prompt stdin
|
|
51
|
+
* reflect lib/learning/reflect.mjs defaultReviewSpawn --print, prompt stdin
|
|
52
|
+
* curator lib/learning/curator.mjs defaultConsolidateSpawn --print, prompt stdin
|
|
53
|
+
* enrich lib/setup/enrich.mjs defaultSpawn --print, prompt argv
|
|
54
|
+
* auth-status lib/setup/claude-probe.mjs probeSubscription `auth status --json`
|
|
55
|
+
* mcp-register lib/cli/global-setup-extras.mjs `mcp add-json … --scope user`
|
|
56
|
+
* main-session scripts/session/supervisor.mjs (launch-args) interactive, --name/--session-id|--resume
|
|
57
|
+
* peer lib/cli/session.mjs `session spawn` interactive, --name
|
|
58
|
+
* voice-brief lib/voice/post-call-brief.mjs briefSpawnSpec --print, prompt argv
|
|
59
|
+
* huddle scripts/huddle/huddle-llm.mjs huddleCliSpawn --print -p <prompt>
|
|
60
|
+
*
|
|
61
|
+
* Both voice lanes, and the lookup lane their tools reach, read the seat's
|
|
62
|
+
* engine per call (seat-engine resolveSeatSpawn, row 36). The huddle's direct API lane (HUDDLE_USE_API=1) is not a spawn: on
|
|
63
|
+
* a claude seat it is the Anthropic SDK, on an engine-cohort seat the engine
|
|
64
|
+
* wire to cohort-llm with the seat token (scripts/huddle/huddle-llm.mjs
|
|
65
|
+
* runHuddleApiTurn).
|
|
66
|
+
*
|
|
67
|
+
* Legacy shell lanes — NOT behind this seam
|
|
68
|
+
* -----------------------------------------
|
|
69
|
+
* Three shell scripts still run `claude` directly. They are legacy, manual or
|
|
70
|
+
* one-shot tools (no daemon lane depends on them). W4-E1 RETIRED them for
|
|
71
|
+
* engine-cohort seats: each calls lib/runtime/legacy-shell-guard.mjs first,
|
|
72
|
+
* which refuses (exit 3) when the seat's runtime.engine is cohort, and doctor
|
|
73
|
+
* warns on such seats. On a claude seat they run as before:
|
|
74
|
+
* scripts/spawn-session.sh:115-124 `claude -p "$SESSION_PROMPT" --output-format text`
|
|
75
|
+
* scripts/continuous-monitor.sh:48-57 `claude -p "<observe prompt>" --output-format text`
|
|
76
|
+
* scripts/local-triggers/run-trigger.sh:59 `"$CLAUDE_BIN" --print --dangerously-skip-permissions "$PROMPT"`
|
|
77
|
+
* See {@link SHELL_LEGACY_LANES}.
|
|
78
|
+
*
|
|
79
|
+
* Env postures
|
|
80
|
+
* ------------
|
|
81
|
+
* Every lane's env is composed here from ONE credential builder,
|
|
82
|
+
* `lib/runtime/child-env.mjs#buildRuntimeEnv`, for the retargetable lanes, and
|
|
83
|
+
* from one of a small set of named postures for the rest. The postures are the
|
|
84
|
+
* five hand-built envs the sites used to carry, byte-for-byte:
|
|
85
|
+
* stock-oauth {...env, PATH, ANTHROPIC_API_KEY:"", ANTHROPIC_AUTH_TOKEN:""}
|
|
86
|
+
* — strip stale API keys so the CLI rides the seat's
|
|
87
|
+
* subscription credential (dispatcher/resume/responder/classifier/ack).
|
|
88
|
+
* An API-key seat (no prefer flag, no OAuth token) keeps its
|
|
89
|
+
* ANTHROPIC_API_KEY: stripping it would leave no credential.
|
|
90
|
+
* cadence {...env, AGENT_ROOT, AGENT_DIR, PATH}, ANTHROPIC_API_KEY deleted
|
|
91
|
+
* when subscription auth is preferred or the key is a placeholder
|
|
92
|
+
* path {...env, PATH} (capture/reflect/curator)
|
|
93
|
+
* lookup {...env, TERM:"dumb", PATH} (lookup)
|
|
94
|
+
* huddle {...env, TERM:"dumb"} (huddle)
|
|
95
|
+
* agent-root {...env, AGENT_ROOT, AGENT_DIR} (voice-brief)
|
|
96
|
+
* inherit {...env} (enrich/auth-status/mcp-register/main-session/peer)
|
|
97
|
+
* A retarget (router envForSpawn carrying ANTHROPIC_BASE_URL) is accepted only
|
|
98
|
+
* on the three retargetable lanes and always goes through buildRuntimeEnv, which
|
|
99
|
+
* scrubs the OAuth token, forces the attribution header off and refuses a
|
|
100
|
+
* retarget with no third-party token.
|
|
101
|
+
*
|
|
102
|
+
* Results, not throws: an unavailable engine, an unsupported retarget or a
|
|
103
|
+
* refused retarget is a returned `{ok:false, error}`. An unknown lane is a
|
|
104
|
+
* programming defect and throws.
|
|
105
|
+
*
|
|
106
|
+
* @module lib/runtime/adapter
|
|
107
|
+
*/
|
|
108
|
+
|
|
109
|
+
"use strict";
|
|
110
|
+
|
|
111
|
+
import { resolveClaudeBin, daemonClaudeArgs, augmentedPath } from "../claude-bin.mjs";
|
|
112
|
+
import { join, dirname } from "node:path";
|
|
113
|
+
import { readFileSync } from "node:fs";
|
|
114
|
+
import { fileURLToPath } from "node:url";
|
|
115
|
+
|
|
116
|
+
import { buildRuntimeEnv, isRetarget, isCohortHelperRetarget, COHORT_HELPER_KEY } from "./child-env.mjs";
|
|
117
|
+
import { buildClaudeArgs } from "../session/launch-args.mjs";
|
|
118
|
+
import { cohortTierFor } from "../model-router/catalog.mjs";
|
|
119
|
+
import { DEFAULT_LLM_BASE_URL, apiKeyHelperCommand } from "../org/llm-token.mjs";
|
|
120
|
+
import { COHORT_BACKEND } from "../rate-guard.mjs";
|
|
121
|
+
|
|
122
|
+
/** Engines a seat can name in `runtime.engine`. */
|
|
123
|
+
export const ENGINES = Object.freeze(["claude", "cohort"]);
|
|
124
|
+
export const DEFAULT_ENGINE = "claude";
|
|
125
|
+
|
|
126
|
+
/** The legacy shell lanes that do not go through this seam (documented, not wired). */
|
|
127
|
+
export const SHELL_LEGACY_LANES = Object.freeze([
|
|
128
|
+
Object.freeze({ path: "scripts/spawn-session.sh", invocation: "claude -p \"$SESSION_PROMPT\" --output-format text" }),
|
|
129
|
+
Object.freeze({ path: "scripts/continuous-monitor.sh", invocation: "claude -p \"<observe prompt>\" --output-format text" }),
|
|
130
|
+
Object.freeze({ path: "scripts/local-triggers/run-trigger.sh", invocation: "\"$CLAUDE_BIN\" --print --dangerously-skip-permissions \"$PROMPT\"" }),
|
|
131
|
+
]);
|
|
132
|
+
|
|
133
|
+
/** Placeholder API keys the cadence lane has always stripped. */
|
|
134
|
+
const PLACEHOLDER_API_KEY = /^(your-api-key|placeholder|xxx+|sk-ant-xxx)/i;
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Pure: the engine a seat runs, from its agent config. `runtime.engine` must be
|
|
138
|
+
* one of {@link ENGINES}; anything else (absent, blank, unknown) is the default.
|
|
139
|
+
* @param {object} [agentConfig] config/agent.json
|
|
140
|
+
* @returns {"claude"|"cohort"}
|
|
141
|
+
*/
|
|
142
|
+
export function resolveEngine(agentConfig) {
|
|
143
|
+
const v = agentConfig && agentConfig.runtime && typeof agentConfig.runtime.engine === "string"
|
|
144
|
+
? agentConfig.runtime.engine.trim().toLowerCase()
|
|
145
|
+
: "";
|
|
146
|
+
return ENGINES.includes(v) ? v : DEFAULT_ENGINE;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* The engine the seat at `agentRoot` runs, read from its config/agent.json.
|
|
151
|
+
* The edge twin of {@link resolveEngine}: the daemon's pre-spawn gates call it
|
|
152
|
+
* so a seat on engine cohort is metered and gated even when model routing
|
|
153
|
+
* never names the cohort provider.
|
|
154
|
+
*
|
|
155
|
+
* An absent or unparseable config answers the default engine — exactly what
|
|
156
|
+
* `resolveEngine(undefined)` answers, and what every spawn on such a seat runs.
|
|
157
|
+
* @param {string} agentRoot
|
|
158
|
+
* @param {{readFile?:(p:string)=>string}} [deps]
|
|
159
|
+
* @returns {"claude"|"cohort"}
|
|
160
|
+
*/
|
|
161
|
+
export function readSeatEngine(agentRoot, deps = {}) {
|
|
162
|
+
const readFile = deps.readFile || ((p) => readFileSync(p, "utf-8"));
|
|
163
|
+
try {
|
|
164
|
+
return resolveEngine(JSON.parse(readFile(join(String(agentRoot || ""), "config", "agent.json"))));
|
|
165
|
+
} catch {
|
|
166
|
+
// No/invalid config/agent.json: the seat runs the default engine, so its
|
|
167
|
+
// gate is the default engine's gate (routing still decides cohort-ness).
|
|
168
|
+
return DEFAULT_ENGINE;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/** `--max-turns`/`--effort`/`--agents` from the router's spawn knobs. Order is fixed. */
|
|
173
|
+
function knobArgs(knobs) {
|
|
174
|
+
const out = [];
|
|
175
|
+
if (!knobs || typeof knobs !== "object") return out;
|
|
176
|
+
if (knobs.maxTurns != null && Number.isFinite(Number(knobs.maxTurns))) out.push("--max-turns", String(knobs.maxTurns));
|
|
177
|
+
if (knobs.effort) out.push("--effort", String(knobs.effort));
|
|
178
|
+
if (knobs.agentsJson) out.push("--agents", typeof knobs.agentsJson === "string" ? knobs.agentsJson : JSON.stringify(knobs.agentsJson));
|
|
179
|
+
return out;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/** The MCP flags for a lane: none, strict-only, or the daemon's per-source set. */
|
|
183
|
+
function mcpArgs(mcp, deps) {
|
|
184
|
+
if (mcp == null || mcp === false) return [];
|
|
185
|
+
if (mcp === "strict") return ["--strict-mcp-config"];
|
|
186
|
+
if (typeof mcp === "object") return deps.daemonArgs(mcp.agentRoot, mcp.source ? { source: mcp.source } : {});
|
|
187
|
+
return [];
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const arr = (v) => (Array.isArray(v) ? v : []);
|
|
191
|
+
const nonEmpty = (v) => typeof v === "string" && v.trim() !== "";
|
|
192
|
+
|
|
193
|
+
/** Is this env on Claude subscription auth (prefer flag, or an OAuth token)? */
|
|
194
|
+
export function subscriptionAuth(env) {
|
|
195
|
+
const e = env && typeof env === "object" ? env : {};
|
|
196
|
+
return /^(1|true|yes)$/i.test(String(e.MAESTRO_PREFER_SUBSCRIPTION_AUTH || "").trim()) || nonEmpty(e.CLAUDE_CODE_OAUTH_TOKEN);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Lane table. `argv(i, x)` gets the normalised input and the computed flag
|
|
201
|
+
* fragments `x = {perm, mcp, knobs}`; `stdin` says whether the prompt is
|
|
202
|
+
* written to the child's stdin instead of argv; `posture` names the env shape;
|
|
203
|
+
* `retarget` says whether a router retarget is legal on the lane.
|
|
204
|
+
*/
|
|
205
|
+
const LANES = Object.freeze({
|
|
206
|
+
dispatcher: {
|
|
207
|
+
posture: "stock-oauth", stdin: false, retarget: true,
|
|
208
|
+
argv: (i, x) => ["--print", "--output-format", "json", ...x.settings, ...x.perm, ...x.mcp, ...x.knobs, "--session-id", i.sessionId, "--model", i.model, i.prompt],
|
|
209
|
+
},
|
|
210
|
+
resume: {
|
|
211
|
+
posture: "stock-oauth", stdin: false, retarget: true,
|
|
212
|
+
argv: (i, x) => ["--print", "--output-format", "json", ...x.settings, ...x.perm, ...x.mcp, ...x.knobs, "--session-id", i.sessionId, "--model", i.model, i.prompt],
|
|
213
|
+
},
|
|
214
|
+
cadence: {
|
|
215
|
+
posture: "cadence", stdin: false, retarget: true,
|
|
216
|
+
argv: (i, x) => ["--print", "--output-format", "json", ...x.settings, ...x.perm, ...x.mcp, ...x.knobs, ...(i.model ? ["--model", String(i.model)] : []), i.prompt],
|
|
217
|
+
},
|
|
218
|
+
responder: {
|
|
219
|
+
posture: "stock-oauth", stdin: true, retarget: false,
|
|
220
|
+
argv: (i, x) => ["--print", ...x.perm, ...x.mcp, "--model", i.model, "--append-system-prompt", i.systemPrompt, "--output-format", "json", ...(i.sessionId ? ["--session-id", i.sessionId] : [])],
|
|
221
|
+
},
|
|
222
|
+
classifier: {
|
|
223
|
+
posture: "stock-oauth", stdin: true, retarget: false,
|
|
224
|
+
argv: (i, x) => ["--print", ...x.perm, ...x.mcp, "--model", i.model, "--append-system-prompt", i.systemPrompt],
|
|
225
|
+
},
|
|
226
|
+
ack: {
|
|
227
|
+
posture: "stock-oauth", stdin: true, retarget: false,
|
|
228
|
+
argv: (i, x) => ["--print", ...x.perm, ...x.mcp, "--model", i.model, "--append-system-prompt", i.systemPrompt],
|
|
229
|
+
},
|
|
230
|
+
lookup: {
|
|
231
|
+
posture: "lookup", stdin: false, retarget: false,
|
|
232
|
+
argv: (i) => ["--print", "--model", i.model, "-p", i.prompt],
|
|
233
|
+
},
|
|
234
|
+
capture: {
|
|
235
|
+
posture: "path", stdin: true, retarget: false,
|
|
236
|
+
argv: (i, x) => ["--print", "--model", i.model, ...x.mcp],
|
|
237
|
+
},
|
|
238
|
+
reflect: {
|
|
239
|
+
posture: "path", stdin: true, retarget: false,
|
|
240
|
+
argv: (i, x) => ["--print", "--model", i.model, ...x.mcp],
|
|
241
|
+
},
|
|
242
|
+
curator: {
|
|
243
|
+
posture: "path", stdin: true, retarget: false,
|
|
244
|
+
argv: (i, x) => ["--print", "--model", i.model, ...x.mcp],
|
|
245
|
+
},
|
|
246
|
+
enrich: {
|
|
247
|
+
posture: "inherit", stdin: false, retarget: false,
|
|
248
|
+
argv: (i, x) => ["--print", ...x.mcp, "--model", i.model, "--append-system-prompt", i.systemPrompt, i.prompt],
|
|
249
|
+
},
|
|
250
|
+
"auth-status": {
|
|
251
|
+
posture: "inherit", stdin: false, retarget: false,
|
|
252
|
+
argv: () => ["auth", "status", "--json"],
|
|
253
|
+
},
|
|
254
|
+
"mcp-register": {
|
|
255
|
+
posture: "inherit", stdin: false, retarget: false,
|
|
256
|
+
argv: (i) => {
|
|
257
|
+
const r = i.registration || {};
|
|
258
|
+
return ["mcp", "add-json", r.name, r.config, "--scope", r.scope || "user"];
|
|
259
|
+
},
|
|
260
|
+
},
|
|
261
|
+
"main-session": {
|
|
262
|
+
posture: "inherit", stdin: false, retarget: false,
|
|
263
|
+
// launch-args owns the main-session argv contract (and its defect checks).
|
|
264
|
+
argv: (i, x) => buildClaudeArgs({
|
|
265
|
+
first: i.first,
|
|
266
|
+
sessionId: i.sessionId,
|
|
267
|
+
mode: i.resumeMode,
|
|
268
|
+
permissionArgs: x.perm,
|
|
269
|
+
extraArgs: arr(i.extraArgs),
|
|
270
|
+
prompt: i.prompt,
|
|
271
|
+
}),
|
|
272
|
+
},
|
|
273
|
+
peer: {
|
|
274
|
+
posture: "inherit", stdin: false, retarget: false,
|
|
275
|
+
argv: (i, x) => ["--name", i.name, ...x.perm, i.prompt],
|
|
276
|
+
},
|
|
277
|
+
"voice-brief": {
|
|
278
|
+
posture: "agent-root", stdin: false, retarget: false,
|
|
279
|
+
defaultPermissions: ["--dangerously-skip-permissions"],
|
|
280
|
+
argv: (i, x) => ["--print", ...x.perm, ...x.mcp, "--model", i.model, i.prompt],
|
|
281
|
+
},
|
|
282
|
+
huddle: {
|
|
283
|
+
posture: "huddle", stdin: false, retarget: false,
|
|
284
|
+
argv: (i) => ["--print", "--model", i.model, "-p", i.prompt],
|
|
285
|
+
},
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
/** Every lane name, for tests and docs. */
|
|
289
|
+
export const LANE_NAMES = Object.freeze(Object.keys(LANES));
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Compose a lane's env. Pure over its inputs.
|
|
293
|
+
* @returns {{ok:true, env:object, retargeting:boolean}|{ok:false, error:object}}
|
|
294
|
+
*/
|
|
295
|
+
function laneEnv(spec, i, deps) {
|
|
296
|
+
const base = i.env && typeof i.env === "object" ? i.env : {};
|
|
297
|
+
const retargeting = isRetarget(i.retarget);
|
|
298
|
+
const path = () => deps.augmentedPath();
|
|
299
|
+
|
|
300
|
+
if (retargeting) {
|
|
301
|
+
// Only the retargetable lanes reach here (checked by the caller).
|
|
302
|
+
const pre = spec.posture === "cadence"
|
|
303
|
+
? { ...base, AGENT_ROOT: i.agentRoot, AGENT_DIR: i.agentRoot, PATH: path() }
|
|
304
|
+
: { ...base, PATH: path() };
|
|
305
|
+
const built = buildRuntimeEnv({ baseEnv: pre, retarget: i.retarget });
|
|
306
|
+
if (!built.ok) return built;
|
|
307
|
+
const env = spec.posture === "cadence"
|
|
308
|
+
? { ...built.env, AGENT_ROOT: i.agentRoot, AGENT_DIR: i.agentRoot, PATH: path() }
|
|
309
|
+
: { ...built.env, PATH: path() };
|
|
310
|
+
return { ok: true, env, retargeting: true };
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
switch (spec.posture) {
|
|
314
|
+
case "stock-oauth": {
|
|
315
|
+
// A non-retarget router set (an Anthropic decision) may still carry
|
|
316
|
+
// harmless keys; they ride on top exactly as the sites always merged them.
|
|
317
|
+
const overlay = i.retarget && typeof i.retarget === "object" ? i.retarget : {};
|
|
318
|
+
const env = { ...base, PATH: path(), ANTHROPIC_API_KEY: "", ANTHROPIC_AUTH_TOKEN: "", ...overlay };
|
|
319
|
+
// A seat on subscription auth (the prefer flag or an OAuth token) strips
|
|
320
|
+
// stale keys so the CLI rides the subscription — the historical posture,
|
|
321
|
+
// unchanged for every such seat. A seat with NEITHER is an API-key seat
|
|
322
|
+
// (the setup wizard's default since W1-E): blanking its key would leave
|
|
323
|
+
// the CLI with no credential at all, so the key is kept.
|
|
324
|
+
if (!subscriptionAuth(base) && nonEmpty(base.ANTHROPIC_API_KEY) && !("ANTHROPIC_API_KEY" in overlay)) {
|
|
325
|
+
env.ANTHROPIC_API_KEY = base.ANTHROPIC_API_KEY;
|
|
326
|
+
}
|
|
327
|
+
return { ok: true, retargeting: false, env };
|
|
328
|
+
}
|
|
329
|
+
case "cadence": {
|
|
330
|
+
const env = { ...base, AGENT_ROOT: i.agentRoot, AGENT_DIR: i.agentRoot, PATH: path() };
|
|
331
|
+
const preferSubscription = base.MAESTRO_PREFER_SUBSCRIPTION_AUTH === "1";
|
|
332
|
+
const apiKey = env.ANTHROPIC_API_KEY || "";
|
|
333
|
+
if (preferSubscription || !String(apiKey).trim() || PLACEHOLDER_API_KEY.test(apiKey)) delete env.ANTHROPIC_API_KEY;
|
|
334
|
+
return { ok: true, retargeting: false, env };
|
|
335
|
+
}
|
|
336
|
+
case "path":
|
|
337
|
+
return { ok: true, retargeting: false, env: { ...base, PATH: path() } };
|
|
338
|
+
case "lookup":
|
|
339
|
+
return { ok: true, retargeting: false, env: { ...base, TERM: "dumb", PATH: path() } };
|
|
340
|
+
case "huddle":
|
|
341
|
+
return { ok: true, retargeting: false, env: { ...base, TERM: "dumb" } };
|
|
342
|
+
case "agent-root":
|
|
343
|
+
return { ok: true, retargeting: false, env: { ...base, AGENT_ROOT: i.agentRoot, AGENT_DIR: i.agentRoot } };
|
|
344
|
+
case "inherit":
|
|
345
|
+
default:
|
|
346
|
+
return { ok: true, retargeting: false, env: { ...base } };
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Build one runtime spawn.
|
|
352
|
+
*
|
|
353
|
+
* @param {object} i
|
|
354
|
+
* @param {string} i.lane one of {@link LANE_NAMES} (unknown → throws: a defect)
|
|
355
|
+
* @param {"claude"|"cohort"} [i.engine="claude"]
|
|
356
|
+
* @param {string} [i.model] the --model value
|
|
357
|
+
* @param {string} [i.prompt] the prompt; argv or stdin per lane
|
|
358
|
+
* @param {string} [i.systemPrompt] --append-system-prompt (responder/classifier/ack/enrich)
|
|
359
|
+
* @param {string} [i.sessionId] --session-id (dispatcher/resume/responder/main-session)
|
|
360
|
+
* @param {null|"strict"|{source?:string, agentRoot?:string}} [i.mcp] MCP flags
|
|
361
|
+
* @param {string[]} [i.permissions] resolved permission args (lib/session-permissions.mjs)
|
|
362
|
+
* @param {object} [i.knobs] {maxTurns, effort, agentsJson} router spawn knobs
|
|
363
|
+
* @param {object} [i.env] the base env (default process.env)
|
|
364
|
+
* @param {object} [i.retarget] the router's envForSpawn (retargetable lanes only)
|
|
365
|
+
* @param {object} [i.extraEnv] plain identifiers set after every merge (never credentials)
|
|
366
|
+
* @param {string[]} [i.extraArgs] main-session only: args before the prompt
|
|
367
|
+
* @param {string} [i.agentRoot] cadence/voice-brief: AGENT_ROOT/AGENT_DIR
|
|
368
|
+
* @param {string} [i.name] peer: the --name value
|
|
369
|
+
* @param {string} [i.first] main-session: the agent's first name
|
|
370
|
+
* @param {"session-id"|"resume"} [i.resumeMode] main-session launch mode
|
|
371
|
+
* @param {{name:string, config:string, scope?:string}} [i.registration] mcp-register
|
|
372
|
+
* @param {string} [i.bin] explicit binary (a caller's injected dep); else resolved
|
|
373
|
+
* @param {number} [i.maxBudgetUsd] engine cohort run lanes only: `--max-budget-usd` (ignored for claude)
|
|
374
|
+
* @param {string} [i.cohortToken] engine cohort: the seat token the spawn starts with
|
|
375
|
+
* @param {object} [deps] {resolveBin, daemonArgs, augmentedPath} — test seams
|
|
376
|
+
* @returns {{ok:true, engine:string, lane:string, bin:string, argv:string[], env:object, stdin:string|null, retargeting:boolean} |
|
|
377
|
+
* {ok:false, error:{code:string, message:string, lane:string, engine:string}}}
|
|
378
|
+
*/
|
|
379
|
+
export function buildSpawn(i = {}, deps = {}) {
|
|
380
|
+
const spec = LANES[i.lane];
|
|
381
|
+
if (!spec) throw new TypeError(`buildSpawn: unknown lane ${JSON.stringify(i.lane)} (known: ${LANE_NAMES.join(", ")})`);
|
|
382
|
+
const engine = i.engine == null ? DEFAULT_ENGINE : i.engine;
|
|
383
|
+
const fail = (code, message) => ({ ok: false, error: { code, message, lane: i.lane, engine: String(engine) } });
|
|
384
|
+
|
|
385
|
+
if (engine !== "claude" && engine !== "cohort") {
|
|
386
|
+
return fail("unknown_engine", `unknown runtime engine ${JSON.stringify(engine)} (known: ${ENGINES.join(", ")})`);
|
|
387
|
+
}
|
|
388
|
+
if (isRetarget(i.retarget) && !spec.retarget) {
|
|
389
|
+
return fail("retarget_unsupported", `lane "${i.lane}" cannot be retargeted to another backend`);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
const d = {
|
|
393
|
+
resolveBin: deps.resolveBin || resolveClaudeBin,
|
|
394
|
+
daemonArgs: deps.daemonArgs || daemonClaudeArgs,
|
|
395
|
+
augmentedPath: deps.augmentedPath || augmentedPath,
|
|
396
|
+
};
|
|
397
|
+
const input = { ...i, env: i.env === undefined ? process.env : i.env };
|
|
398
|
+
|
|
399
|
+
if (engine === "cohort") return buildCohortEngineSpawn(spec, input, d, deps, fail);
|
|
400
|
+
|
|
401
|
+
const envResult = laneEnv(spec, input, d);
|
|
402
|
+
if (!envResult.ok) return fail(envResult.error.code, envResult.error.message);
|
|
403
|
+
|
|
404
|
+
const perm = Array.isArray(i.permissions) ? i.permissions : (spec.defaultPermissions || []);
|
|
405
|
+
// Path B: a cohort-llm retarget authenticates through the seat-token helper,
|
|
406
|
+
// which Claude Code only reads from settings (child-env COHORT_HELPER_KEY).
|
|
407
|
+
const settings = envResult.retargeting && isCohortHelperRetarget(i.retarget)
|
|
408
|
+
? ["--settings", JSON.stringify({ apiKeyHelper: i.retarget[COHORT_HELPER_KEY] })]
|
|
409
|
+
: [];
|
|
410
|
+
const x = { perm, mcp: mcpArgs(i.mcp, d), knobs: knobArgs(i.knobs), settings };
|
|
411
|
+
const argv = spec.argv(input, x);
|
|
412
|
+
|
|
413
|
+
const env = envResult.env;
|
|
414
|
+
if (i.extraEnv && typeof i.extraEnv === "object") {
|
|
415
|
+
for (const [k, v] of Object.entries(i.extraEnv)) if (v !== undefined && v !== null) env[k] = String(v);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
return {
|
|
419
|
+
ok: true,
|
|
420
|
+
engine,
|
|
421
|
+
lane: i.lane,
|
|
422
|
+
bin: i.bin || d.resolveBin(),
|
|
423
|
+
argv,
|
|
424
|
+
env,
|
|
425
|
+
stdin: spec.stdin ? (i.prompt == null ? "" : String(i.prompt)) : null,
|
|
426
|
+
retargeting: envResult.retargeting,
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// ---------------------------------------------------------------------------
|
|
431
|
+
// Engine: cohort
|
|
432
|
+
// ---------------------------------------------------------------------------
|
|
433
|
+
|
|
434
|
+
/** The engine's headless entry point. */
|
|
435
|
+
export const ENGINE_CLI_PATH = join(dirname(fileURLToPath(import.meta.url)), "..", "engine", "cli.mjs");
|
|
436
|
+
|
|
437
|
+
/**
|
|
438
|
+
* The flags `cohort run` accepts TODAY (lib/engine/cli.mjs `parseRunArgs`).
|
|
439
|
+
* adapter-cohort.test.mjs pins this list against the real parser in both
|
|
440
|
+
* directions, so when the engine grows a flag (W2-D: MCP, permissions) the
|
|
441
|
+
* test fails until the flag is listed here — and only then does the adapter
|
|
442
|
+
* pass it through.
|
|
443
|
+
*/
|
|
444
|
+
export const ENGINE_RUN_FLAGS = Object.freeze([
|
|
445
|
+
"-p", "--print", "--output-format", "--model", "--session-id", "--resume", "--max-turns", "--append-system-prompt",
|
|
446
|
+
// W2-D: MCP, permissions, settings.
|
|
447
|
+
"--mcp-config", "--strict-mcp-config", "--permission-mode", "--dangerously-skip-permissions",
|
|
448
|
+
"--allowedTools", "--disallowedTools", "--settings",
|
|
449
|
+
// W3-G (CF-45): the router knobs and the daemon's bare mode.
|
|
450
|
+
"--add-dir", "--plugin-dir", "--effort", "--agents", "--bare",
|
|
451
|
+
// W4-E1 (row 24): the per-session spend cap.
|
|
452
|
+
"--max-budget-usd",
|
|
453
|
+
]);
|
|
454
|
+
|
|
455
|
+
/**
|
|
456
|
+
* The claude `--print` subset maestro's lanes emit, and the engine flag each
|
|
457
|
+
* becomes. Anything not in this table is unsupported by construction.
|
|
458
|
+
*/
|
|
459
|
+
const CLAUDE_TO_ENGINE = Object.freeze({
|
|
460
|
+
"-p": { flag: "-p", value: false },
|
|
461
|
+
"--print": { flag: "-p", value: false },
|
|
462
|
+
"--output-format": { flag: "--output-format", value: true, values: ["json", "text"] },
|
|
463
|
+
"--model": { flag: "--model", value: true, model: true },
|
|
464
|
+
"--session-id": { flag: "--session-id", value: true },
|
|
465
|
+
"--resume": { flag: "--resume", value: true },
|
|
466
|
+
"--append-system-prompt": { flag: "--append-system-prompt", value: true },
|
|
467
|
+
"--max-turns": { flag: "--max-turns", value: true },
|
|
468
|
+
"--mcp-config": { flag: "--mcp-config", value: true },
|
|
469
|
+
"--strict-mcp-config": { flag: "--strict-mcp-config", value: false },
|
|
470
|
+
"--dangerously-skip-permissions": { flag: "--dangerously-skip-permissions", value: false },
|
|
471
|
+
"--permission-mode": { flag: "--permission-mode", value: true },
|
|
472
|
+
"--allowedTools": { flag: "--allowedTools", value: true },
|
|
473
|
+
"--disallowedTools": { flag: "--disallowedTools", value: true },
|
|
474
|
+
"--settings": { flag: "--settings", value: true },
|
|
475
|
+
"--add-dir": { flag: "--add-dir", value: true },
|
|
476
|
+
"--plugin-dir": { flag: "--plugin-dir", value: true },
|
|
477
|
+
"--effort": { flag: "--effort", value: true },
|
|
478
|
+
"--agents": { flag: "--agents", value: true },
|
|
479
|
+
"--bare": { flag: "--bare", value: false },
|
|
480
|
+
"--max-budget-usd": { flag: "--max-budget-usd", value: true },
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
/**
|
|
484
|
+
* The Anthropic-slot → Cohort-tier map the engine lane uses when the caller
|
|
485
|
+
* does not pass one. Pinned equal to lib/model-router/catalog/cohort.yaml
|
|
486
|
+
* `tier_map` by adapter-cohort.test.mjs.
|
|
487
|
+
*/
|
|
488
|
+
export const DEFAULT_COHORT_TIER_MAP = Object.freeze({ fable: "cohort-reason", opus: "cohort-agentic", sonnet: "cohort-agentic", haiku: "cohort-fast" });
|
|
489
|
+
|
|
490
|
+
/** Lanes whose shape the engine cannot serve (claude CLI subcommands). */
|
|
491
|
+
const ENGINE_UNSUPPORTED_LANES = new Set(["auth-status", "mcp-register"]);
|
|
492
|
+
|
|
493
|
+
/**
|
|
494
|
+
* W4-A1: the interactive lanes, served by `cohort session` (lib/engine/cli.mjs
|
|
495
|
+
* `session`, session-runtime/runner.mjs). Not enabled for any seat: a seat's
|
|
496
|
+
* engine stays `claude` until the §8.4 gate.
|
|
497
|
+
*/
|
|
498
|
+
export const ENGINE_INTERACTIVE_LANES = Object.freeze(["main-session", "peer"]);
|
|
499
|
+
|
|
500
|
+
/**
|
|
501
|
+
* The flags `cohort session` accepts that the interactive lanes may emit
|
|
502
|
+
* (lib/engine/session-runtime/args.mjs `SESSION_FLAGS`). adapter-session.test.mjs
|
|
503
|
+
* pins this list against the real session parser.
|
|
504
|
+
*/
|
|
505
|
+
export const ENGINE_SESSION_FLAGS = Object.freeze([
|
|
506
|
+
"--name", "--session-id", "--resume", "--model", "--max-turns", "--append-system-prompt",
|
|
507
|
+
"--mcp-config", "--strict-mcp-config", "--permission-mode", "--dangerously-skip-permissions",
|
|
508
|
+
"--allowedTools", "--disallowedTools", "--settings", "--add-dir", "--plugin-dir", "--effort", "--agents", "--bare",
|
|
509
|
+
]);
|
|
510
|
+
|
|
511
|
+
/**
|
|
512
|
+
* Map an interactive lane's claude argv (`--name …, --session-id|--resume …,
|
|
513
|
+
* permission/extra flags, prompt`) onto `cohort session` argv. The last element
|
|
514
|
+
* is always the prompt. PURE.
|
|
515
|
+
* @param {string[]} argv
|
|
516
|
+
* @param {{tierMap?:Record<string,string>, engineFlags?:readonly string[]}} [o]
|
|
517
|
+
* @returns {{ok:true, argv:string[]}|{ok:false, error:{code:string, message:string, flag?:string}}}
|
|
518
|
+
*/
|
|
519
|
+
export function translateInteractiveArgv(argv, o = {}) {
|
|
520
|
+
const engineFlags = o.engineFlags || ENGINE_SESSION_FLAGS;
|
|
521
|
+
const tierMap = o.tierMap || DEFAULT_COHORT_TIER_MAP;
|
|
522
|
+
const args = [...(Array.isArray(argv) ? argv : [])];
|
|
523
|
+
const bad = (code, message, flag) => ({ ok: false, error: { code, message, ...(flag ? { flag } : {}) } });
|
|
524
|
+
if (args.length === 0) return bad("engine_argument_unsupported", "an interactive session needs a prompt");
|
|
525
|
+
const prompt = String(args.pop());
|
|
526
|
+
const out = ["session"];
|
|
527
|
+
for (let k = 0; k < args.length; k++) {
|
|
528
|
+
const a = String(args[k]);
|
|
529
|
+
const m = a === "--name" ? { flag: "--name", value: true } : CLAUDE_TO_ENGINE[a];
|
|
530
|
+
if (!m || m.flag === "-p" || m.flag === "--output-format") {
|
|
531
|
+
if (a.startsWith("-")) return bad("engine_flag_unsupported", `cohort session does not support ${a}`, a);
|
|
532
|
+
return bad("engine_argument_unsupported", `unexpected positional argument for cohort session: ${JSON.stringify(a.slice(0, 40))}`);
|
|
533
|
+
}
|
|
534
|
+
if (!engineFlags.includes(m.flag)) return bad("engine_flag_unsupported", `cohort session does not support ${a} yet`, a);
|
|
535
|
+
if (!m.value) { out.push(m.flag); continue; }
|
|
536
|
+
let v = args[++k];
|
|
537
|
+
if (v === undefined) return bad("engine_argument_unsupported", `${a} needs a value`, a);
|
|
538
|
+
v = String(v);
|
|
539
|
+
if (m.model) {
|
|
540
|
+
const tier = cohortTierFor(v, tierMap);
|
|
541
|
+
if (!tier) return bad("engine_model_unmapped", `--model ${v} has no Cohort tier (expected cohort-* or an opus/sonnet/haiku/fable slot)`, a);
|
|
542
|
+
v = tier;
|
|
543
|
+
}
|
|
544
|
+
out.push(m.flag, v);
|
|
545
|
+
}
|
|
546
|
+
out.push("--", prompt);
|
|
547
|
+
return { ok: true, argv: out };
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
/**
|
|
551
|
+
* Map a lane's claude argv onto `cohort run` argv. PURE.
|
|
552
|
+
* @param {string[]} argv the claude argv the lane table built
|
|
553
|
+
* @param {object} o
|
|
554
|
+
* @param {string|null} [o.prompt] the prompt, when it rides argv (last element)
|
|
555
|
+
* @param {boolean} [o.stdin] the prompt rides stdin
|
|
556
|
+
* @param {Record<string,string>} [o.tierMap]
|
|
557
|
+
* @param {readonly string[]} [o.engineFlags]
|
|
558
|
+
* @returns {{ok:true, argv:string[]}|{ok:false, error:{code:string, message:string, flag?:string}}}
|
|
559
|
+
*/
|
|
560
|
+
export function translateClaudeArgv(argv, o = {}) {
|
|
561
|
+
const engineFlags = o.engineFlags || ENGINE_RUN_FLAGS;
|
|
562
|
+
const tierMap = o.tierMap || DEFAULT_COHORT_TIER_MAP;
|
|
563
|
+
const args = [...(Array.isArray(argv) ? argv : [])];
|
|
564
|
+
let promptArg = null;
|
|
565
|
+
if (!o.stdin && o.prompt != null && args.length && args[args.length - 1] === o.prompt) promptArg = args.pop();
|
|
566
|
+
const out = ["run", "-p"];
|
|
567
|
+
const bad = (code, message, flag) => ({ ok: false, error: { code, message, ...(flag ? { flag } : {}) } });
|
|
568
|
+
for (let k = 0; k < args.length; k++) {
|
|
569
|
+
const a = String(args[k]);
|
|
570
|
+
const m = CLAUDE_TO_ENGINE[a];
|
|
571
|
+
if (!m) {
|
|
572
|
+
if (a.startsWith("-")) return bad("engine_flag_unsupported", `the cohort engine does not support ${a}`, a);
|
|
573
|
+
return bad("engine_argument_unsupported", `unexpected positional argument for the cohort engine: ${JSON.stringify(a.slice(0, 40))}`);
|
|
574
|
+
}
|
|
575
|
+
if (!engineFlags.includes(m.flag)) {
|
|
576
|
+
return bad("engine_flag_unsupported", `the cohort engine does not support ${a} yet`, a);
|
|
577
|
+
}
|
|
578
|
+
if (m.flag === "-p") continue; // always emitted once, up front
|
|
579
|
+
if (!m.value) { out.push(m.flag); continue; }
|
|
580
|
+
let v = args[++k];
|
|
581
|
+
if (v === undefined) return bad("engine_argument_unsupported", `${a} needs a value`, a);
|
|
582
|
+
v = String(v);
|
|
583
|
+
if (m.values && !m.values.includes(v)) return bad("engine_flag_unsupported", `the cohort engine does not support ${a} ${v}`, a);
|
|
584
|
+
if (m.model) {
|
|
585
|
+
const tier = cohortTierFor(v, tierMap);
|
|
586
|
+
if (!tier) return bad("engine_model_unmapped", `--model ${v} has no Cohort tier (expected cohort-* or an opus/sonnet/haiku/fable slot)`, a);
|
|
587
|
+
v = tier;
|
|
588
|
+
}
|
|
589
|
+
out.push(m.flag, v);
|
|
590
|
+
}
|
|
591
|
+
if (promptArg != null) out.push("--", String(promptArg));
|
|
592
|
+
return { ok: true, argv: out };
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
/**
|
|
596
|
+
* The engine child's env: the lane's own posture with every Anthropic/Claude
|
|
597
|
+
* credential scrubbed, plus the gateway URL, the seat token minted at spawn,
|
|
598
|
+
* and — when the seat's root is known — the token helper that re-mints it
|
|
599
|
+
* (the engine re-runs it on a TTL and after a 401, so a long-lived
|
|
600
|
+
* `cohort session` never outlives its credential). PURE.
|
|
601
|
+
*/
|
|
602
|
+
export function cohortEngineEnv(laneBaseEnv, { baseUrl, token, tokenHelper = null }) {
|
|
603
|
+
const out = {};
|
|
604
|
+
for (const [k, v] of Object.entries(laneBaseEnv || {})) {
|
|
605
|
+
if (/^ANTHROPIC_/i.test(k)) continue;
|
|
606
|
+
if (/^CLAUDE_CODE_/i.test(k) && /(OAUTH|TOKEN|KEY|SECRET|HELPER)/i.test(k)) continue;
|
|
607
|
+
if (/^COHORT_LLM_(TOKEN|API_KEY_HELPER|TOKEN_HELPER)$/i.test(k)) continue;
|
|
608
|
+
out[k] = v;
|
|
609
|
+
}
|
|
610
|
+
out.COHORT_LLM_BASE_URL = baseUrl;
|
|
611
|
+
out.COHORT_LLM_TOKEN = token;
|
|
612
|
+
if (nonEmpty(tokenHelper)) out.COHORT_LLM_TOKEN_HELPER = tokenHelper;
|
|
613
|
+
return out;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
/** The seat root an engine spawn's token helper reads its key from: the lane's, its MCP seat's, or its env's. PURE. */
|
|
617
|
+
function engineSeatRoot(input, laneEnvValues, deps) {
|
|
618
|
+
const mcpRoot = input.mcp && typeof input.mcp === "object" ? input.mcp.agentRoot : null;
|
|
619
|
+
for (const v of [input.agentRoot, mcpRoot, laneEnvValues && laneEnvValues.AGENT_ROOT, input.env && input.env.AGENT_ROOT, deps.agentRoot]) {
|
|
620
|
+
if (nonEmpty(v)) return String(v);
|
|
621
|
+
}
|
|
622
|
+
return null;
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
function buildCohortEngineSpawn(spec, input, d, deps, fail) {
|
|
626
|
+
if (ENGINE_UNSUPPORTED_LANES.has(input.lane)) {
|
|
627
|
+
return fail("engine_lane_unsupported", `lane "${input.lane}" is interactive or a claude subcommand; the cohort engine runs headless lanes only`);
|
|
628
|
+
}
|
|
629
|
+
if (isRetarget(input.retarget)) {
|
|
630
|
+
return fail("retarget_unsupported", "the cohort engine talks to cohort-llm directly; a router retarget does not apply");
|
|
631
|
+
}
|
|
632
|
+
const perm = Array.isArray(input.permissions) ? input.permissions : (spec.defaultPermissions || []);
|
|
633
|
+
const claudeArgv = spec.argv(input, { perm, mcp: mcpArgs(input.mcp, d), knobs: knobArgs(input.knobs), settings: [] });
|
|
634
|
+
const tierMap = input.cohortTierMap || deps.cohortTierMap;
|
|
635
|
+
const mapped = ENGINE_INTERACTIVE_LANES.includes(input.lane)
|
|
636
|
+
? translateInteractiveArgv(claudeArgv, { tierMap })
|
|
637
|
+
: translateClaudeArgv(claudeArgv, { prompt: input.prompt, stdin: spec.stdin, tierMap });
|
|
638
|
+
if (!mapped.ok) return fail(mapped.error.code, mapped.error.message);
|
|
639
|
+
// W4-E1 (row 24): a per-session spend cap, for engine cohort run lanes only (a claude
|
|
640
|
+
// spawn ignores `maxBudgetUsd`, so a claude seat's argv is unchanged). Before the prompt's `--`.
|
|
641
|
+
if (input.maxBudgetUsd != null) {
|
|
642
|
+
if (ENGINE_INTERACTIVE_LANES.includes(input.lane)) return fail("engine_argument_unsupported", `lane "${input.lane}" is interactive; --max-budget-usd applies to cohort run lanes`);
|
|
643
|
+
const usd = Number(input.maxBudgetUsd);
|
|
644
|
+
if (!Number.isFinite(usd) || usd <= 0) return fail("engine_argument_unsupported", `maxBudgetUsd must be a positive number, got ${JSON.stringify(input.maxBudgetUsd)}`);
|
|
645
|
+
const at = mapped.argv.indexOf("--");
|
|
646
|
+
mapped.argv.splice(at === -1 ? mapped.argv.length : at, 0, "--max-budget-usd", String(usd));
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
const tokenValue = typeof input.cohortToken === "string" ? input.cohortToken
|
|
650
|
+
: (typeof deps.cohortToken === "function" ? deps.cohortToken() : null);
|
|
651
|
+
const token = tokenValue && typeof tokenValue === "object" ? tokenValue.token : tokenValue;
|
|
652
|
+
if (!nonEmpty(token)) {
|
|
653
|
+
return fail("cohort_token_unavailable", "no cohort-llm seat token for the engine — admit the spawn first (admitSpawn mints/caches one)");
|
|
654
|
+
}
|
|
655
|
+
const baseUrl = String(input.cohortBaseUrl || deps.cohortBaseUrl || (input.env && input.env.COHORT_LLM_BASE_URL) || DEFAULT_LLM_BASE_URL).replace(/\/+$/, "");
|
|
656
|
+
|
|
657
|
+
const envResult = laneEnv(spec, { ...input, retarget: {} }, d);
|
|
658
|
+
if (!envResult.ok) return fail(envResult.error.code, envResult.error.message);
|
|
659
|
+
const seatRoot = engineSeatRoot(input, envResult.env, deps);
|
|
660
|
+
const tokenHelper = seatRoot
|
|
661
|
+
? apiKeyHelperCommand({ agentRoot: seatRoot, nodeBin: deps.nodeBin || process.execPath, ...(deps.apiKeyHelperPath ? { helperPath: deps.apiKeyHelperPath } : {}) })
|
|
662
|
+
: null;
|
|
663
|
+
const env = cohortEngineEnv(envResult.env, { baseUrl, token, tokenHelper });
|
|
664
|
+
if (input.extraEnv && typeof input.extraEnv === "object") {
|
|
665
|
+
for (const [k, v] of Object.entries(input.extraEnv)) if (v !== undefined && v !== null) env[k] = String(v);
|
|
666
|
+
}
|
|
667
|
+
return {
|
|
668
|
+
ok: true,
|
|
669
|
+
engine: "cohort",
|
|
670
|
+
lane: input.lane,
|
|
671
|
+
bin: deps.nodeBin || process.execPath,
|
|
672
|
+
argv: [deps.engineCliPath || ENGINE_CLI_PATH, ...mapped.argv],
|
|
673
|
+
env,
|
|
674
|
+
stdin: spec.stdin ? (input.prompt == null ? "" : String(input.prompt)) : null,
|
|
675
|
+
retargeting: false,
|
|
676
|
+
};
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
/**
|
|
680
|
+
* Can this seat generate a reply at all — the NECESSARY presence check the
|
|
681
|
+
* daemon's ack-before-work fail-safe reads (agent-daemon claudeAvailable).
|
|
682
|
+
* A claude seat needs a safe claude binary; an engine-cohort seat never
|
|
683
|
+
* spawns claude, so it needs the engine entry point instead (conformance
|
|
684
|
+
* row 1). Neither proves the credential or the network. PURE.
|
|
685
|
+
* @param {{engine:string, claudeBinOk:()=>boolean, engineCliPresent:()=>boolean}} o
|
|
686
|
+
* @returns {boolean}
|
|
687
|
+
*/
|
|
688
|
+
export function seatGenerationAvailable({ engine, claudeBinOk, engineCliPresent }) {
|
|
689
|
+
try {
|
|
690
|
+
return engine === "cohort" ? engineCliPresent() === true : claudeBinOk() === true;
|
|
691
|
+
} catch {
|
|
692
|
+
return false;
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
// ---------------------------------------------------------------------------
|
|
697
|
+
// cohort-llm admission (the pre-spawn gate)
|
|
698
|
+
// ---------------------------------------------------------------------------
|
|
699
|
+
|
|
700
|
+
/** A credits hold re-checks after this long. */
|
|
701
|
+
export const COHORT_CREDITS_RECHECK_MS = 10 * 60 * 1000;
|
|
702
|
+
/** A refusal that waiting cannot fix (a bad key, an unpriced tier) re-checks after this long. */
|
|
703
|
+
export const COHORT_FAIL_HOLD_MS = 15 * 60 * 1000;
|
|
704
|
+
|
|
705
|
+
/**
|
|
706
|
+
* Is this spawn served by cohort-llm? engine cohort, a target routed to the
|
|
707
|
+
* cohort provider, or a retarget authenticated by the cohort key helper. PURE.
|
|
708
|
+
* @param {{engine?:string, target?:object|null}} o
|
|
709
|
+
*/
|
|
710
|
+
export function isCohortRouted({ engine, target } = {}) {
|
|
711
|
+
if (engine === "cohort") return true;
|
|
712
|
+
const t = target && typeof target === "object" ? target : null;
|
|
713
|
+
if (!t) return false;
|
|
714
|
+
if (t.backend === COHORT_BACKEND) return true;
|
|
715
|
+
return isCohortHelperRetarget(t.envForSpawn || t.retarget || null);
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
/**
|
|
719
|
+
* Turn an admission refusal into the deferral a call site acts on. PURE.
|
|
720
|
+
* `until` is always an epoch ms: the refusal's resetsAt, or a re-check time.
|
|
721
|
+
* @returns {{kind:string, until:number, code:string|null, message:string, refusal:object|null}}
|
|
722
|
+
*/
|
|
723
|
+
export function deferralFromAdmission(adm, now) {
|
|
724
|
+
const d = (adm && adm.disposition) || { kind: "fail", code: null };
|
|
725
|
+
const code = d.code || (adm && adm.error && adm.error.code) || null;
|
|
726
|
+
let until;
|
|
727
|
+
if (d.kind === "defer_until") until = d.until;
|
|
728
|
+
else if (d.kind === "retry") until = now + (Number(d.afterMs) || 0);
|
|
729
|
+
else if (d.kind === "hold_for_credits") until = now + COHORT_CREDITS_RECHECK_MS;
|
|
730
|
+
else until = now + COHORT_FAIL_HOLD_MS;
|
|
731
|
+
const what = {
|
|
732
|
+
defer_until: `cohort-llm refused (${code}); deferring until ${new Date(until).toISOString()}`,
|
|
733
|
+
hold_for_credits: "cohort-llm credits are exhausted; holding until a top-up",
|
|
734
|
+
retry: `cohort-llm is busy (${code}); retrying at ${new Date(until).toISOString()}`,
|
|
735
|
+
fail: `cohort-llm cannot admit this seat (${code})`,
|
|
736
|
+
}[d.kind] || `cohort-llm refused (${code})`;
|
|
737
|
+
return { kind: d.kind, until, code, message: what, refusal: (adm && adm.refusal) || null };
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
function holdDeferral(rateGuard, rateGuardDeps, now) {
|
|
741
|
+
if (!rateGuard || typeof rateGuard.checkRateLimit !== "function") return null;
|
|
742
|
+
try {
|
|
743
|
+
const hold = rateGuard.checkRateLimit(COHORT_BACKEND, rateGuardDeps);
|
|
744
|
+
if (hold && hold.allowed === false) {
|
|
745
|
+
return { kind: "defer_until", until: hold.retryAt, code: "cohort_hold", message: `a cohort-llm refusal holds this seat until ${new Date(hold.retryAt).toISOString()}`, refusal: null };
|
|
746
|
+
}
|
|
747
|
+
} catch { /* a hold we cannot read never blocks — the quota read still gates */ }
|
|
748
|
+
return null;
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
/**
|
|
752
|
+
* Record a refusal as a cohort hold, and when one was recorded drop the quota
|
|
753
|
+
* cache: the reading that refused must not be the one a gate trusts once the
|
|
754
|
+
* hold lifts (the cache would otherwise answer for up to its TTL).
|
|
755
|
+
*/
|
|
756
|
+
function recordRefusalHold(rateGuard, rateGuardDeps, adm, quota) {
|
|
757
|
+
if (!adm || !adm.refusal || !rateGuard || typeof rateGuard.recordCohortRefusal !== "function") return;
|
|
758
|
+
let rec = null;
|
|
759
|
+
try { rec = rateGuard.recordCohortRefusal(adm.refusal, rateGuardDeps); } catch { /* best-effort */ }
|
|
760
|
+
if (rec && rec.recorded && quota && typeof quota.invalidate === "function") {
|
|
761
|
+
try { quota.invalidate(); } catch { /* best-effort */ }
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
/**
|
|
766
|
+
* May this spawn start? Not cohort-routed → always yes. Cohort-routed → an
|
|
767
|
+
* existing cohort hold (rate-guard, backend "cohort") defers without a network
|
|
768
|
+
* call; otherwise `quota.canAdmit()` decides, and a refusal is recorded as a
|
|
769
|
+
* cohort hold so the next spawn does not ask again. Human replies are NOT
|
|
770
|
+
* exempt: a refused seat would 429/402 them just the same.
|
|
771
|
+
*
|
|
772
|
+
* @param {{engine?:string, target?:object|null}} o
|
|
773
|
+
* @param {{quota?:{canAdmit:Function}, token?:{peek:Function}, rateGuard?:object, rateGuardDeps?:object, now?:()=>number}} [deps]
|
|
774
|
+
* @returns {Promise<{ok:true, cohort:boolean, funding?:string|null, token?:string|null} |
|
|
775
|
+
* {ok:false, cohort:true, deferral:object}>}
|
|
776
|
+
*/
|
|
777
|
+
export async function admitSpawn(o = {}, deps = {}) {
|
|
778
|
+
if (!isCohortRouted(o)) return { ok: true, cohort: false };
|
|
779
|
+
const now = typeof deps.now === "function" ? deps.now() : Date.now();
|
|
780
|
+
const held = holdDeferral(deps.rateGuard, deps.rateGuardDeps, now);
|
|
781
|
+
if (held) return { ok: false, cohort: true, deferral: held };
|
|
782
|
+
if (!deps.quota || typeof deps.quota.canAdmit !== "function") {
|
|
783
|
+
return { ok: false, cohort: true, deferral: deferralFromAdmission({ disposition: { kind: "retry", afterMs: 30_000, code: "cohort_quota_unconfigured" } }, now) };
|
|
784
|
+
}
|
|
785
|
+
let adm;
|
|
786
|
+
try { adm = await deps.quota.canAdmit(); } catch (err) {
|
|
787
|
+
adm = { ok: false, error: { code: "cohort_unavailable", message: String(err && err.message || err) }, disposition: { kind: "retry", afterMs: 30_000, code: "cohort_unavailable" } };
|
|
788
|
+
}
|
|
789
|
+
if (adm && adm.ok) {
|
|
790
|
+
const t = deps.token && typeof deps.token.peek === "function" ? deps.token.peek({ allowStale: true }) : null;
|
|
791
|
+
return { ok: true, cohort: true, funding: adm.funding || null, token: (t && t.token) || null };
|
|
792
|
+
}
|
|
793
|
+
recordRefusalHold(deps.rateGuard, deps.rateGuardDeps, adm, deps.quota);
|
|
794
|
+
return { ok: false, cohort: true, deferral: deferralFromAdmission(adm, now) };
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
/**
|
|
798
|
+
* The synchronous twin of {@link admitSpawn} for call sites that must decide
|
|
799
|
+
* inside a synchronous spawn path (the dispatcher). It reads only the quota
|
|
800
|
+
* cache; with nothing cached it answers `{cold:true}` and the caller primes the
|
|
801
|
+
* cache (`quota.canAdmit()`) and retries — it never spawns on a guess.
|
|
802
|
+
*/
|
|
803
|
+
export function admitSpawnCached(o = {}, deps = {}) {
|
|
804
|
+
if (!isCohortRouted(o)) return { ok: true, cohort: false };
|
|
805
|
+
const now = typeof deps.now === "function" ? deps.now() : Date.now();
|
|
806
|
+
const held = holdDeferral(deps.rateGuard, deps.rateGuardDeps, now);
|
|
807
|
+
if (held) return { ok: false, cohort: true, deferral: held };
|
|
808
|
+
const adm = deps.quota && typeof deps.quota.canAdmitCached === "function" ? deps.quota.canAdmitCached() : { cold: true };
|
|
809
|
+
if (adm.cold) {
|
|
810
|
+
return { ok: false, cohort: true, cold: true, deferral: { kind: "retry", until: now, code: "cohort_quota_cold", message: "no fresh cohort-llm quota reading yet", refusal: null } };
|
|
811
|
+
}
|
|
812
|
+
if (adm.ok) {
|
|
813
|
+
const t = deps.token && typeof deps.token.peek === "function" ? deps.token.peek({ allowStale: true }) : null;
|
|
814
|
+
return { ok: true, cohort: true, funding: adm.funding || null, token: (t && t.token) || null };
|
|
815
|
+
}
|
|
816
|
+
recordRefusalHold(deps.rateGuard, deps.rateGuardDeps, adm, deps.quota);
|
|
817
|
+
return { ok: false, cohort: true, deferral: deferralFromAdmission(adm, now) };
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
export default {
|
|
821
|
+
buildSpawn, resolveEngine, readSeatEngine, seatGenerationAvailable, ENGINES, DEFAULT_ENGINE, LANE_NAMES, SHELL_LEGACY_LANES,
|
|
822
|
+
translateClaudeArgv, cohortEngineEnv, isCohortRouted, admitSpawn, admitSpawnCached, deferralFromAdmission,
|
|
823
|
+
};
|