@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
|
@@ -1,166 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* dispatcher-resume.test.mjs — audit F11: `defaultSpawnResume` built a BARE
|
|
3
|
-
* env (no router, no scrub) and a bare argv (no --output-format json, no
|
|
4
|
-
* knobs). A session originally routed to a third-party backend therefore
|
|
5
|
-
* resumed against api.anthropic.com with a foreign model id, and every
|
|
6
|
-
* resumed run recorded $0 because nothing parsed its usage.
|
|
7
|
-
*
|
|
8
|
-
* The resume now goes through the SAME resolveSpawnTarget / buildChildEnv
|
|
9
|
-
* path as a fresh spawnSession. The pure builder is tested directly; the
|
|
10
|
-
* real defaultSpawnResume is exercised through setSpawnForTests so the argv
|
|
11
|
-
* and env handed to `claude` are asserted byte-for-byte.
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
import { test } from "node:test";
|
|
15
|
-
import assert from "node:assert/strict";
|
|
16
|
-
import { promises as fsp } from "node:fs";
|
|
17
|
-
import { writeFileSync, mkdirSync } from "node:fs";
|
|
18
|
-
import { tmpdir } from "node:os";
|
|
19
|
-
import { join } from "node:path";
|
|
20
|
-
|
|
21
|
-
async function freshDispatcher() {
|
|
22
|
-
const dir = join(tmpdir(), `dispatcher-resume-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`);
|
|
23
|
-
mkdirSync(join(dir, "state/sessions"), { recursive: true });
|
|
24
|
-
mkdirSync(join(dir, "logs/daemon"), { recursive: true });
|
|
25
|
-
process.env.AGENT_DIR = dir;
|
|
26
|
-
const url = new URL("./dispatcher.mjs", import.meta.url);
|
|
27
|
-
const mod = await import(`${url.href}?resume=${Math.random()}`);
|
|
28
|
-
return { mod, dir };
|
|
29
|
-
}
|
|
30
|
-
async function cleanup(dir) { try { await fsp.rm(dir, { recursive: true, force: true }); } catch { /* */ } }
|
|
31
|
-
function plantMarker(dir, sessionId, marker) {
|
|
32
|
-
const d = join(dir, "state/sessions/resume-pending");
|
|
33
|
-
mkdirSync(d, { recursive: true });
|
|
34
|
-
writeFileSync(join(d, `${sessionId}.json`), JSON.stringify({
|
|
35
|
-
sessionId, claudeSessionId: `cs-${sessionId}`, source: "backlog", itemId: "BL-1", sourceFile: "q.yaml",
|
|
36
|
-
prompt: "finish the memo", modelFlag: "claude-opus-4-6", model: "opus", priority: "normal",
|
|
37
|
-
startedAt: Date.now(), recoveryAttempts: 0, ...marker,
|
|
38
|
-
}, null, 2));
|
|
39
|
-
}
|
|
40
|
-
function writeV2Config(dir) {
|
|
41
|
-
mkdirSync(join(dir, "config"), { recursive: true });
|
|
42
|
-
writeFileSync(join(dir, "config/model-routing.json"), JSON.stringify({
|
|
43
|
-
schema_version: 2,
|
|
44
|
-
aliases: { frontier: "anthropic/claude-opus-4-8", default: "anthropic/claude-sonnet-4-6", fast: "anthropic/claude-haiku-4-5" },
|
|
45
|
-
defaults: { needs_tool_use_for_sessions: true, data_class: "sensitive", cache_ttl: "1h" },
|
|
46
|
-
backends: { anthropic: { allowed_data_classes: ["public", "internal", "sensitive"] } },
|
|
47
|
-
routing_policy: [{ default: true, chain: ["default", "fast"] }],
|
|
48
|
-
fallback_to_anthropic: true,
|
|
49
|
-
}, null, 2));
|
|
50
|
-
}
|
|
51
|
-
function fakeProc() {
|
|
52
|
-
const handlers = {};
|
|
53
|
-
return { stdout: { on: () => {} }, stderr: { on: () => {} }, on: (ev, cb) => { handlers[ev] = cb; }, kill: () => {}, killed: false, _handlers: handlers };
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const ANTHROPIC_TARGET = { modelFlag: "claude-sonnet-4-6", envForSpawn: {}, decisionId: "d-1", backend: "anthropic", transport: "anthropic-cli", maxTurns: 40, effort: null, agentsJson: { "Haiku-Explore": { model: "haiku" } } };
|
|
57
|
-
const RETARGET = { modelFlag: "kimi-k2", envForSpawn: { ANTHROPIC_BASE_URL: "https://moon.example", ANTHROPIC_AUTH_TOKEN: "tok-xyz", ANTHROPIC_API_KEY: "" }, decisionId: "d-2", backend: "moonshot", transport: "anthropic-compatible", maxTurns: 40, effort: "high", agentsJson: null };
|
|
58
|
-
|
|
59
|
-
test("buildResumeSpawn (Anthropic): --print --output-format json + knobs + --session-id + marker model + prompt last; legacy key strip; session id in env", async () => {
|
|
60
|
-
const { mod, dir } = await freshDispatcher();
|
|
61
|
-
try {
|
|
62
|
-
const marker = { sessionId: "s-1", claudeSessionId: "CS-1", prompt: "finish the memo", modelFlag: "claude-opus-4-6", model: "opus" };
|
|
63
|
-
const { args, env, modelFlag } = mod.buildResumeSpawn({
|
|
64
|
-
marker, target: ANTHROPIC_TARGET,
|
|
65
|
-
baseEnv: { PATH: "/usr/bin", ANTHROPIC_API_KEY: "sk-live", ANTHROPIC_AUTH_TOKEN: "at", FOO_API_KEY: "foo", CLAUDE_CODE_OAUTH_TOKEN: "oauth-1" },
|
|
66
|
-
permissionArgs: ["--dangerously-skip-permissions"], daemonArgs: ["--strict-mcp-config"], augmentedPath: () => "/aug",
|
|
67
|
-
});
|
|
68
|
-
assert.equal(args[0], "--print");
|
|
69
|
-
assert.equal(args[args.indexOf("--output-format") + 1], "json", "usage is measurable on a resumed run");
|
|
70
|
-
assert.ok(args.includes("--dangerously-skip-permissions"));
|
|
71
|
-
assert.ok(args.includes("--strict-mcp-config"));
|
|
72
|
-
assert.equal(args[args.indexOf("--max-turns") + 1], "40", "the router's knob args ride the resume");
|
|
73
|
-
assert.ok(/Haiku-Explore/.test(args[args.indexOf("--agents") + 1]));
|
|
74
|
-
assert.equal(args[args.indexOf("--session-id") + 1], "CS-1");
|
|
75
|
-
assert.equal(args[args.indexOf("--model") + 1], "claude-opus-4-6", "an Anthropic session resumes on the exact flag it used");
|
|
76
|
-
assert.equal(modelFlag, "claude-opus-4-6");
|
|
77
|
-
assert.equal(args[args.length - 1], "finish the memo");
|
|
78
|
-
assert.ok(!args.includes("--resume"));
|
|
79
|
-
// Env: the non-retarget path keeps the historical explicit-empty-key posture (keychain/OAuth fallthrough).
|
|
80
|
-
assert.equal(env.ANTHROPIC_API_KEY, "");
|
|
81
|
-
assert.equal(env.ANTHROPIC_AUTH_TOKEN, "");
|
|
82
|
-
assert.equal(env.PATH, "/aug");
|
|
83
|
-
assert.equal(env.MAESTRO_SESSION_ID, "s-1", "delivery receipts attribute to the resumed session");
|
|
84
|
-
assert.equal(env.CLAUDE_CODE_OAUTH_TOKEN, "oauth-1", "the subscription token reaches the resumed session");
|
|
85
|
-
} finally { await cleanup(dir); }
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
test("buildResumeSpawn (retarget): env goes through buildChildEnv — base URL + token injected, foreign credentials scrubbed", async () => {
|
|
89
|
-
const { mod, dir } = await freshDispatcher();
|
|
90
|
-
try {
|
|
91
|
-
const marker = { sessionId: "s-2", claudeSessionId: "CS-2", prompt: "p", modelFlag: "kimi-k2", model: "sonnet" };
|
|
92
|
-
const { args, env } = mod.buildResumeSpawn({
|
|
93
|
-
marker, target: RETARGET,
|
|
94
|
-
baseEnv: { PATH: "/usr/bin", ANTHROPIC_API_KEY: "sk-live", FOO_API_KEY: "foo", HOME: "/h" },
|
|
95
|
-
permissionArgs: [], daemonArgs: [], augmentedPath: () => "/aug",
|
|
96
|
-
});
|
|
97
|
-
assert.equal(env.ANTHROPIC_BASE_URL, "https://moon.example", "the resumed session is retargeted like the original");
|
|
98
|
-
assert.equal(env.ANTHROPIC_AUTH_TOKEN, "tok-xyz");
|
|
99
|
-
assert.equal(env.ANTHROPIC_API_KEY, "", "empty-string discipline: no keychain fallthrough on a retarget");
|
|
100
|
-
assert.equal(env.FOO_API_KEY, undefined, "§7.3 scrub: no foreign credential leaks");
|
|
101
|
-
assert.equal(env.HOME, "/h");
|
|
102
|
-
assert.equal(env.PATH, "/aug");
|
|
103
|
-
assert.equal(env.MAESTRO_SESSION_ID, "s-2");
|
|
104
|
-
assert.equal(args[args.indexOf("--model") + 1], "kimi-k2");
|
|
105
|
-
assert.equal(args[args.indexOf("--effort") + 1], "high");
|
|
106
|
-
} finally { await cleanup(dir); }
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
test("resumeModelFlag: a foreign model id never resumes against Anthropic (F11) — the router's current flag wins", async () => {
|
|
110
|
-
const { mod, dir } = await freshDispatcher();
|
|
111
|
-
try {
|
|
112
|
-
// Original ran on a third party; the router now resolves Anthropic (provider down / config changed).
|
|
113
|
-
assert.equal(mod.resumeModelFlag({ modelFlag: "moonshot/kimi-k2" }, ANTHROPIC_TARGET), "claude-sonnet-4-6");
|
|
114
|
-
assert.equal(mod.resumeModelFlag({ modelFlag: "kimi-k2" }, ANTHROPIC_TARGET), "claude-sonnet-4-6");
|
|
115
|
-
// Original ran on Anthropic; still Anthropic → the exact flag it used (H1 pin).
|
|
116
|
-
assert.equal(mod.resumeModelFlag({ modelFlag: "claude-opus-4-6" }, ANTHROPIC_TARGET), "claude-opus-4-6");
|
|
117
|
-
assert.equal(mod.resumeModelFlag({ modelFlag: "opus" }, ANTHROPIC_TARGET), "opus");
|
|
118
|
-
// A retarget: the router's flag for that backend.
|
|
119
|
-
assert.equal(mod.resumeModelFlag({ modelFlag: "claude-opus-4-6" }, RETARGET), "kimi-k2");
|
|
120
|
-
// No router at all (target null) → marker flag, then coarse class, then sonnet.
|
|
121
|
-
assert.equal(mod.resumeModelFlag({ modelFlag: "claude-opus-4-6" }, null), "claude-opus-4-6");
|
|
122
|
-
assert.equal(mod.resumeModelFlag({ model: "opus" }, null), "opus");
|
|
123
|
-
assert.equal(mod.resumeModelFlag({}, null), "sonnet");
|
|
124
|
-
} finally { await cleanup(dir); }
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
test("defaultSpawnResume (real path, v2 config): argv carries --output-format json + router knobs; env carries the session id and the key strip", async () => {
|
|
128
|
-
const { mod, dir } = await freshDispatcher();
|
|
129
|
-
try {
|
|
130
|
-
writeV2Config(dir);
|
|
131
|
-
plantMarker(dir, "s-real", { claudeSessionId: "CS-FIXED", prompt: "finish drafting the board memo", modelFlag: "claude-opus-4-6" });
|
|
132
|
-
const calls = [];
|
|
133
|
-
const restore = mod.setSpawnForTests((bin, args, opts) => { calls.push({ bin, args, opts }); return fakeProc(); });
|
|
134
|
-
try {
|
|
135
|
-
const stats = mod.reconcileResumePending();
|
|
136
|
-
assert.equal(stats.resumed, 1);
|
|
137
|
-
} finally { restore(); }
|
|
138
|
-
assert.equal(calls.length, 1);
|
|
139
|
-
const { args, opts } = calls[0];
|
|
140
|
-
assert.equal(args[0], "--print");
|
|
141
|
-
assert.equal(args[args.indexOf("--output-format") + 1], "json");
|
|
142
|
-
assert.equal(args[args.indexOf("--max-turns") + 1], "40", "v2 knobs are no longer dropped on resume");
|
|
143
|
-
assert.ok(args.includes("--agents"));
|
|
144
|
-
assert.equal(args[args.indexOf("--session-id") + 1], "CS-FIXED");
|
|
145
|
-
assert.equal(args[args.indexOf("--model") + 1], "claude-opus-4-6");
|
|
146
|
-
assert.equal(args[args.length - 1], "finish drafting the board memo");
|
|
147
|
-
assert.ok(!args.includes("--resume"));
|
|
148
|
-
assert.equal(opts.env.MAESTRO_SESSION_ID, "s-real");
|
|
149
|
-
assert.equal(opts.env.ANTHROPIC_API_KEY, "");
|
|
150
|
-
assert.equal(opts.cwd, dir);
|
|
151
|
-
} finally { await cleanup(dir); }
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
test("defaultSpawnResume (no routing config): still a --print --output-format json spawn on the marker's flag", async () => {
|
|
155
|
-
const { mod, dir } = await freshDispatcher();
|
|
156
|
-
try {
|
|
157
|
-
plantMarker(dir, "s-plain", { claudeSessionId: "CS-P", prompt: "p", modelFlag: "claude-sonnet-4-6" });
|
|
158
|
-
const calls = [];
|
|
159
|
-
const restore = mod.setSpawnForTests((bin, args, opts) => { calls.push({ args, opts }); return fakeProc(); });
|
|
160
|
-
try { mod.reconcileResumePending(); } finally { restore(); }
|
|
161
|
-
const { args } = calls[0];
|
|
162
|
-
assert.equal(args[args.indexOf("--output-format") + 1], "json");
|
|
163
|
-
assert.equal(args[args.indexOf("--model") + 1], "claude-sonnet-4-6");
|
|
164
|
-
assert.ok(!args.includes("--max-turns"), "no router → no knobs (unchanged)");
|
|
165
|
-
} finally { await cleanup(dir); }
|
|
166
|
-
});
|
|
@@ -1,365 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* dispatcher-session-continuity.test.mjs — session continuity and effort
|
|
3
|
-
* routing on the dispatch path (design §5.6, R10 + R13).
|
|
4
|
-
*
|
|
5
|
-
* Two defects are pinned here:
|
|
6
|
-
*
|
|
7
|
-
* 1. `randomUUID()` per dispatch. Every full session in a live thread started
|
|
8
|
-
* COLD — the agent re-read the room and answered a follow-up as if it were
|
|
9
|
-
* an opening. The router the responder already consults now keys the
|
|
10
|
-
* conversation, and a follow-up inside the TTL continues it.
|
|
11
|
-
* 2. `taskClass = "session.responder"`, hardcoded. `effort` was therefore
|
|
12
|
-
* always null and `maxTurns` always 40, so complexity moved the model and
|
|
13
|
-
* the timeout and nothing else.
|
|
14
|
-
*
|
|
15
|
-
* The dispatcher is stateful and binds AGENT_DIR at import, so every test takes
|
|
16
|
-
* a fresh module against its own tmpdir.
|
|
17
|
-
*
|
|
18
|
-
* Run: node --test scripts/daemon/dispatcher-session-continuity.test.mjs
|
|
19
|
-
*/
|
|
20
|
-
|
|
21
|
-
import { test } from "node:test";
|
|
22
|
-
import assert from "node:assert/strict";
|
|
23
|
-
import { replyTier } from "../../lib/assurance/tier.mjs";
|
|
24
|
-
import { promises as fsp, mkdirSync, writeFileSync } from "fs";
|
|
25
|
-
import { tmpdir } from "os";
|
|
26
|
-
import { join } from "path";
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* The governor/breaker are injected ADMIT for every test here, because none of
|
|
30
|
-
* these tests is about admission — and the real governor reads live machine
|
|
31
|
-
* load, so under a full-suite run it can QUEUE a dispatch and the session id
|
|
32
|
-
* under test is never spawned at all. (It did: these passed alone and failed in
|
|
33
|
-
* `npm test`.) `dispatcher-governance.test.mjs` owns the admission decisions.
|
|
34
|
-
*/
|
|
35
|
-
const ADMIT_ALL = {
|
|
36
|
-
governor: { admit: () => ({ decision: "ADMIT", reason: "forced-ADMIT", snapshot: {} }), defaultDeps: () => ({}) },
|
|
37
|
-
rateGuard: { checkRateLimit: () => ({ allowed: true, retryAt: null }), classifyStderr: () => false, recordRateLimit: () => {}, recordSuccess: () => {} },
|
|
38
|
-
budgetGuard: { dailyStatus: () => ({ essentialOnly: false }) },
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* The in-flight lease lives in `lib/session-router.mjs` at MODULE scope, which
|
|
43
|
-
* is correct — one daemon process, one set of child processes — but it means a
|
|
44
|
-
* dispatch whose fake proc never closes leaves its key claimed for the rest of
|
|
45
|
-
* the file. Reset it per test, the way the real process would by exiting.
|
|
46
|
-
*
|
|
47
|
-
* Imported INSIDE `freshDispatcher`, after AGENT_DIR is pointed at the tmpdir:
|
|
48
|
-
* session-router pulls in `session-lock.mjs`, which binds its claim directory
|
|
49
|
-
* from AGENT_DIR at ITS first import. Importing it at module scope bound that
|
|
50
|
-
* to the real repo and left `.claim` files in the working tree.
|
|
51
|
-
*/
|
|
52
|
-
async function freshDispatcher() {
|
|
53
|
-
const dir = join(tmpdir(), `dispatcher-cont-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`);
|
|
54
|
-
await fsp.mkdir(join(dir, "state/sessions"), { recursive: true });
|
|
55
|
-
await fsp.mkdir(join(dir, "state/daemon"), { recursive: true });
|
|
56
|
-
await fsp.mkdir(join(dir, "logs/daemon"), { recursive: true });
|
|
57
|
-
process.env.AGENT_DIR = dir;
|
|
58
|
-
const { _resetInFlightForTests } = await import("./lib/session-router.mjs");
|
|
59
|
-
_resetInFlightForTests();
|
|
60
|
-
const url = new URL("./dispatcher.mjs", import.meta.url);
|
|
61
|
-
const mod = await import(`${url.href}?test=${Math.random()}`);
|
|
62
|
-
mod.setGovernanceForTests(ADMIT_ALL);
|
|
63
|
-
return { mod, dir };
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
async function cleanup(dir) {
|
|
67
|
-
try { await fsp.rm(dir, { recursive: true, force: true }); } catch { /* */ }
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
function writeV2Config(dir) {
|
|
71
|
-
mkdirSync(join(dir, "config"), { recursive: true });
|
|
72
|
-
writeFileSync(join(dir, "config/model-routing.json"), JSON.stringify({
|
|
73
|
-
schema_version: 2,
|
|
74
|
-
aliases: { frontier: "anthropic/claude-opus-4-8", default: "anthropic/claude-sonnet-4-6", fast: "anthropic/claude-haiku-4-5" },
|
|
75
|
-
defaults: { needs_tool_use_for_sessions: true, data_class: "sensitive", cache_ttl: "1h" },
|
|
76
|
-
backends: { anthropic: { allowed_data_classes: ["public", "internal", "sensitive"] } },
|
|
77
|
-
routing_policy: [{ default: true, chain: ["default", "fast"] }],
|
|
78
|
-
fallback_to_anthropic: true,
|
|
79
|
-
}, null, 2));
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
function fakeProc() {
|
|
83
|
-
const handlers = {};
|
|
84
|
-
return { stdout: { on: () => {} }, stderr: { on: () => {} }, on: (ev, cb) => { handlers[ev] = cb; }, kill: () => {}, killed: false, _handlers: handlers };
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
const COHORT_ITEM = {
|
|
88
|
-
id: "i-1",
|
|
89
|
-
service: "cohort",
|
|
90
|
-
channel: "engineering", // the display label
|
|
91
|
-
channel_id: "chan-eng", // the real room
|
|
92
|
-
sender: "Dana",
|
|
93
|
-
content: "pick up where we left off",
|
|
94
|
-
};
|
|
95
|
-
const WORK = { priority: "normal", action: "respond", model: "sonnet", summary: "follow-up in the engineering room", answerable: false };
|
|
96
|
-
|
|
97
|
-
/** Dispatch once and return the argv + the captured child process. */
|
|
98
|
-
function dispatchOnce(mod, item, classResult) {
|
|
99
|
-
const calls = [];
|
|
100
|
-
const procs = [];
|
|
101
|
-
const restore = mod.setSpawnForTests((bin, args) => { calls.push(args); const p = fakeProc(); procs.push(p); return p; });
|
|
102
|
-
try {
|
|
103
|
-
mod.dispatch("do the thing", item, classResult, "inbox");
|
|
104
|
-
} finally { restore(); }
|
|
105
|
-
assert.equal(calls.length, 1, "the dispatch did not spawn — admission, not session routing");
|
|
106
|
-
return { args: calls[0], proc: procs[0] };
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
const sessionIdOf = (args) => args[args.indexOf("--session-id") + 1];
|
|
110
|
-
|
|
111
|
-
// ---------------------------------------------------------------------------
|
|
112
|
-
// the tier → task class
|
|
113
|
-
// ---------------------------------------------------------------------------
|
|
114
|
-
|
|
115
|
-
test("replyTier: answerable alone is NOT the answer tier — the rung is required", async () => {
|
|
116
|
-
const { mod, dir } = await freshDispatcher();
|
|
117
|
-
try {
|
|
118
|
-
// The five rules of lib/assurance/tier.mjs#replyTier, in its order.
|
|
119
|
-
assert.equal(replyTier({ answerable: true, rung: 0 }), "answer");
|
|
120
|
-
assert.equal(replyTier({ answerable: true, rung: 1 }), "answer");
|
|
121
|
-
assert.equal(replyTier({ willSpawnSession: false }), "answer");
|
|
122
|
-
|
|
123
|
-
// THE BUG THIS PINS. `resolveSpawnTarget` runs only for items isQuickReply()
|
|
124
|
-
// REFUSED, and execution-ladder pins that a rung-3 answerable ask is exactly
|
|
125
|
-
// such an item. Reading `answerable` alone gave it 6 turns at low effort
|
|
126
|
-
// where the real module gives 60 at high.
|
|
127
|
-
assert.equal(replyTier({ answerable: true, rung: 3 }), "plan");
|
|
128
|
-
assert.equal(replyTier({ answerable: true, action: "respond", priority: "normal" }), "work",
|
|
129
|
-
"an unrouted answerable ask is work, never answer — unknown buys nothing");
|
|
130
|
-
|
|
131
|
-
assert.equal(replyTier({ rung: 4 }), "plan");
|
|
132
|
-
assert.equal(replyTier({ action: "research", priority: "critical" }), "plan");
|
|
133
|
-
assert.equal(replyTier({ action: "draft", priority: "high" }), "plan");
|
|
134
|
-
// …but only at the priorities the design names.
|
|
135
|
-
assert.equal(replyTier({ action: "research", priority: "normal" }), "work");
|
|
136
|
-
assert.equal(replyTier({ action: "respond", priority: "critical" }), "work");
|
|
137
|
-
// A stale or malformed rung is not a rung.
|
|
138
|
-
assert.equal(replyTier({ answerable: true, rung: "1" }), "work");
|
|
139
|
-
assert.equal(replyTier({ rung: 9 }), "work");
|
|
140
|
-
// An unrecognised classification is WORK — the historical behaviour.
|
|
141
|
-
assert.equal(replyTier({}), "work");
|
|
142
|
-
assert.equal(replyTier(), "work");
|
|
143
|
-
} finally { await cleanup(dir); }
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
/**
|
|
147
|
-
* There used to be a test here asserting that dispatcher.mjs's own copy of the
|
|
148
|
-
* tier table agreed with `lib/assurance/tier.mjs`. It earned its place: at the
|
|
149
|
-
* merge it FAILED, because the copy predated the review fix that added
|
|
150
|
-
* `willSpawnSession !== true` to rule (2) — so an item the quick path fell
|
|
151
|
-
* through on would have been tiered `answer` (total silence) while a long
|
|
152
|
-
* session ran. The copy is gone and the dispatcher imports the one definition,
|
|
153
|
-
* which makes the agreement test tautological.
|
|
154
|
-
*
|
|
155
|
-
* What it was really protecting is that the dispatcher's task-class mapping is
|
|
156
|
-
* driven by that module and degrades safely, so THAT is what is pinned now.
|
|
157
|
-
*/
|
|
158
|
-
test("a tier module that throws does not stop a dispatch — it degrades to work", async () => {
|
|
159
|
-
const { mod, dir } = await freshDispatcher();
|
|
160
|
-
try {
|
|
161
|
-
// `work` is the tier the flood was made of and the one that speaks at most
|
|
162
|
-
// once: the right thing to land on when the decision is unavailable.
|
|
163
|
-
assert.equal(mod.taskClassFor({ rung: 4 }), "session.plan");
|
|
164
|
-
assert.equal(mod.taskClassFor({ answerable: true, rung: 0 }), "session.answer");
|
|
165
|
-
assert.equal(mod.taskClassFor({}), "session.responder");
|
|
166
|
-
assert.equal(mod.taskClassFor(), "session.responder");
|
|
167
|
-
} finally { await cleanup(dir); }
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
test("rungForItem reads the ladder's decision off the item, not a field the classifier never emits", async () => {
|
|
171
|
-
const { mod, dir } = await freshDispatcher();
|
|
172
|
-
try {
|
|
173
|
-
// agent-daemon.mjs stamps `item.execution = routed`. classifier.mjs emits no
|
|
174
|
-
// `rung` at all, so reading classResult.rung finds undefined forever —
|
|
175
|
-
// which would make session.answer's knobs dead code.
|
|
176
|
-
assert.equal(mod.rungForItem({ execution: { rung: 1 } }), 1);
|
|
177
|
-
assert.equal(mod.rungForItem({ execution: { rung: 3, mechanism: "dispatcher-spawn" } }), 3);
|
|
178
|
-
assert.equal(mod.rungForItem({ rung: 0 }), 0);
|
|
179
|
-
assert.equal(mod.rungForItem({}), null);
|
|
180
|
-
assert.equal(mod.rungForItem(null), null);
|
|
181
|
-
assert.equal(mod.rungForItem({ execution: { rung: "2" } }), null);
|
|
182
|
-
} finally { await cleanup(dir); }
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
test("taskClassFor maps each tier onto the class that carries its knobs", async () => {
|
|
186
|
-
const { mod, dir } = await freshDispatcher();
|
|
187
|
-
try {
|
|
188
|
-
assert.equal(mod.taskClassFor({ answerable: true }, { execution: { rung: 0 } }), "session.answer");
|
|
189
|
-
assert.equal(mod.taskClassFor({ action: "respond", priority: "normal" }), "session.responder");
|
|
190
|
-
assert.equal(mod.taskClassFor({ action: "research", priority: "critical" }), "session.plan");
|
|
191
|
-
assert.equal(mod.taskClassFor({ answerable: true }, { execution: { rung: 3 } }), "session.plan",
|
|
192
|
-
"the ladder said this needs a session; 6 turns at low effort is the wrong answer, fast");
|
|
193
|
-
assert.equal(mod.taskClassFor(null), "session.responder", "no classification is not an excuse to throw");
|
|
194
|
-
} finally { await cleanup(dir); }
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
test("effort finally varies with the ask — a plan spawns with high effort, an answer with low", async () => {
|
|
198
|
-
const { mod, dir } = await freshDispatcher();
|
|
199
|
-
try {
|
|
200
|
-
writeV2Config(dir);
|
|
201
|
-
const plan = dispatchOnce(mod, { ...COHORT_ITEM, id: "i-plan" },
|
|
202
|
-
{ priority: "critical", action: "research", model: "opus", summary: "work out the pricing", answerable: false });
|
|
203
|
-
assert.equal(plan.args[plan.args.indexOf("--effort") + 1], "high");
|
|
204
|
-
|
|
205
|
-
// The ladder placed this at rung 0 — a single protocol call — so the answer
|
|
206
|
-
// tier is a fact about the routed work, not an opinion about the question.
|
|
207
|
-
const answer = dispatchOnce(mod,
|
|
208
|
-
{ ...COHORT_ITEM, id: "i-ans", channel_id: "chan-other", execution: { rung: 0, mechanism: "tool-surface" } },
|
|
209
|
-
{ priority: "normal", action: "respond", model: "sonnet", summary: "which model are you", answerable: true });
|
|
210
|
-
assert.equal(answer.args[answer.args.indexOf("--effort") + 1], "low");
|
|
211
|
-
assert.equal(answer.args[answer.args.indexOf("--max-turns") + 1], "6", "an answerable question does not need 40 turns");
|
|
212
|
-
} finally { await cleanup(dir); }
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
test("an answerable ask the LADDER routed to a session does not get the 6-turn knobs", async () => {
|
|
216
|
-
const { mod, dir } = await freshDispatcher();
|
|
217
|
-
try {
|
|
218
|
-
writeV2Config(dir);
|
|
219
|
-
const routed = dispatchOnce(mod,
|
|
220
|
-
{ ...COHORT_ITEM, id: "i-rung3", channel_id: "chan-rung3", execution: { rung: 3, mechanism: "dispatcher-spawn" } },
|
|
221
|
-
{ priority: "normal", action: "respond", model: "sonnet", summary: "reconcile the two models", answerable: true });
|
|
222
|
-
assert.notEqual(routed.args[routed.args.indexOf("--max-turns") + 1], "6",
|
|
223
|
-
"a session that runs out of turns without replying leaves its obligation open — which is the retry loop");
|
|
224
|
-
assert.equal(routed.args[routed.args.indexOf("--effort") + 1], "high");
|
|
225
|
-
} finally { await cleanup(dir); }
|
|
226
|
-
});
|
|
227
|
-
|
|
228
|
-
// ---------------------------------------------------------------------------
|
|
229
|
-
// session continuity
|
|
230
|
-
// ---------------------------------------------------------------------------
|
|
231
|
-
|
|
232
|
-
test("a follow-up in the same Cohort room CONTINUES the session instead of starting cold", async () => {
|
|
233
|
-
const { mod, dir } = await freshDispatcher();
|
|
234
|
-
try {
|
|
235
|
-
const first = dispatchOnce(mod, COHORT_ITEM, WORK);
|
|
236
|
-
const firstId = sessionIdOf(first.args);
|
|
237
|
-
assert.ok(firstId, "a session id is always passed");
|
|
238
|
-
|
|
239
|
-
// The session finishes cleanly — the key goes live in the registry.
|
|
240
|
-
first.proc._handlers.close(0);
|
|
241
|
-
|
|
242
|
-
const second = dispatchOnce(mod, { ...COHORT_ITEM, id: "i-2", content: "and the second half?" }, WORK);
|
|
243
|
-
assert.equal(sessionIdOf(second.args), firstId, "the follow-up must continue, not re-open");
|
|
244
|
-
} finally { await cleanup(dir); }
|
|
245
|
-
});
|
|
246
|
-
|
|
247
|
-
test("a DIFFERENT room gets a different session", async () => {
|
|
248
|
-
const { mod, dir } = await freshDispatcher();
|
|
249
|
-
try {
|
|
250
|
-
const a = dispatchOnce(mod, COHORT_ITEM, WORK);
|
|
251
|
-
a.proc._handlers.close(0);
|
|
252
|
-
const b = dispatchOnce(mod, { ...COHORT_ITEM, id: "i-3", channel_id: "chan-sales" }, WORK);
|
|
253
|
-
assert.notEqual(sessionIdOf(b.args), sessionIdOf(a.args));
|
|
254
|
-
} finally { await cleanup(dir); }
|
|
255
|
-
});
|
|
256
|
-
|
|
257
|
-
test("a session that exited NON-ZERO is never resumed into", async () => {
|
|
258
|
-
const { mod, dir } = await freshDispatcher();
|
|
259
|
-
try {
|
|
260
|
-
const first = dispatchOnce(mod, COHORT_ITEM, WORK);
|
|
261
|
-
first.proc._handlers.close(1);
|
|
262
|
-
const second = dispatchOnce(mod, { ...COHORT_ITEM, id: "i-4" }, WORK);
|
|
263
|
-
assert.notEqual(sessionIdOf(second.args), sessionIdOf(first.args));
|
|
264
|
-
} finally { await cleanup(dir); }
|
|
265
|
-
});
|
|
266
|
-
|
|
267
|
-
test("BACKLOG work is never keyed — it is not a conversation", async () => {
|
|
268
|
-
const { mod, dir } = await freshDispatcher();
|
|
269
|
-
try {
|
|
270
|
-
const calls = [];
|
|
271
|
-
const procs = [];
|
|
272
|
-
const restore = mod.setSpawnForTests((bin, args) => { calls.push(args); const p = fakeProc(); procs.push(p); return p; });
|
|
273
|
-
try {
|
|
274
|
-
const backlogItem = { id: "b-1", title: "Write the runbook", service: "cohort", channel_id: "chan-eng" };
|
|
275
|
-
mod.dispatch("p", backlogItem, { ...WORK, summary: "runbook" }, "backlog");
|
|
276
|
-
procs[0]._handlers.close(0);
|
|
277
|
-
mod.dispatch("p", { ...backlogItem, id: "b-2" }, { ...WORK, summary: "runbook again" }, "backlog");
|
|
278
|
-
} finally { restore(); }
|
|
279
|
-
assert.equal(calls.length, 2);
|
|
280
|
-
assert.notEqual(sessionIdOf(calls[1]), sessionIdOf(calls[0]), "two backlog items must not share a session");
|
|
281
|
-
} finally { await cleanup(dir); }
|
|
282
|
-
});
|
|
283
|
-
|
|
284
|
-
test("an item the router cannot key still dispatches, with a fresh id", async () => {
|
|
285
|
-
const { mod, dir } = await freshDispatcher();
|
|
286
|
-
try {
|
|
287
|
-
// A Cohort surface with only a DISPLAY label — no room id to key on.
|
|
288
|
-
const labelOnly = { id: "i-5", service: "cohort", channel: "doc/Pricing memo", sender: "Dana", content: "?" };
|
|
289
|
-
const a = dispatchOnce(mod, labelOnly, WORK);
|
|
290
|
-
a.proc._handlers.close(0);
|
|
291
|
-
const b = dispatchOnce(mod, { ...labelOnly, id: "i-6" }, WORK);
|
|
292
|
-
assert.ok(sessionIdOf(a.args));
|
|
293
|
-
assert.notEqual(sessionIdOf(b.args), sessionIdOf(a.args));
|
|
294
|
-
} finally { await cleanup(dir); }
|
|
295
|
-
});
|
|
296
|
-
|
|
297
|
-
test("crash recovery still gets a resume marker carrying the session id in play", async () => {
|
|
298
|
-
const { mod, dir } = await freshDispatcher();
|
|
299
|
-
try {
|
|
300
|
-
const first = dispatchOnce(mod, COHORT_ITEM, WORK);
|
|
301
|
-
const markers = await fsp.readdir(join(dir, "state", "sessions", "resume-pending"));
|
|
302
|
-
assert.equal(markers.length, 1, "the in-flight marker is unchanged by session routing");
|
|
303
|
-
const marker = JSON.parse(await fsp.readFile(join(dir, "state", "sessions", "resume-pending", markers[0]), "utf-8"));
|
|
304
|
-
assert.equal(marker.claudeSessionId, sessionIdOf(first.args));
|
|
305
|
-
} finally { await cleanup(dir); }
|
|
306
|
-
});
|
|
307
|
-
|
|
308
|
-
// ---------------------------------------------------------------------------
|
|
309
|
-
// ONE CLI PROCESS PER SESSION ID
|
|
310
|
-
//
|
|
311
|
-
// A registry row only goes live when a session CLOSES cleanly, so the registry
|
|
312
|
-
// alone cannot say "something is already running on this key". It did not have
|
|
313
|
-
// to before — every dispatch minted its own UUID. Now they share a key, and the
|
|
314
|
-
// room that produced 9,187 messages in 21 days is a TOP-LEVEL channel, where
|
|
315
|
-
// agent-daemon's thread lock (which needs a thread id, or a DM) does not apply.
|
|
316
|
-
// Without an in-flight lease a burst of three puts three
|
|
317
|
-
// `claude --print --session-id <same id>` processes on ONE transcript.
|
|
318
|
-
// ---------------------------------------------------------------------------
|
|
319
|
-
|
|
320
|
-
test("a burst in one room never puts two processes on ONE session id", async () => {
|
|
321
|
-
const { mod, dir } = await freshDispatcher();
|
|
322
|
-
try {
|
|
323
|
-
// Turn one completes cleanly: the key is live in the registry.
|
|
324
|
-
const first = dispatchOnce(mod, COHORT_ITEM, WORK);
|
|
325
|
-
first.proc._handlers.close(0);
|
|
326
|
-
const liveId = sessionIdOf(first.args);
|
|
327
|
-
|
|
328
|
-
// Three more land while nothing has finished.
|
|
329
|
-
const burst = [
|
|
330
|
-
dispatchOnce(mod, { ...COHORT_ITEM, id: "burst-1", content: "and the margin?" }, WORK),
|
|
331
|
-
dispatchOnce(mod, { ...COHORT_ITEM, id: "burst-2", content: "and the runway?" }, WORK),
|
|
332
|
-
dispatchOnce(mod, { ...COHORT_ITEM, id: "burst-3", content: "and the headcount?" }, WORK),
|
|
333
|
-
];
|
|
334
|
-
const ids = burst.map((b) => sessionIdOf(b.args));
|
|
335
|
-
|
|
336
|
-
assert.equal(ids[0], liveId, "the first of the burst legitimately continues the finished session");
|
|
337
|
-
assert.equal(new Set(ids).size, 3, "every concurrent turn must have its OWN session id");
|
|
338
|
-
assert.equal(ids.filter((id) => id === liveId).length, 1, "exactly one process may hold the live id");
|
|
339
|
-
|
|
340
|
-
// …and once the room goes quiet, continuity comes back.
|
|
341
|
-
burst.forEach((b) => b.proc._handlers.close(0));
|
|
342
|
-
const later = dispatchOnce(mod, { ...COHORT_ITEM, id: "burst-4", content: "thanks — one more" }, WORK);
|
|
343
|
-
assert.equal(typeof sessionIdOf(later.args), "string");
|
|
344
|
-
} finally { await cleanup(dir); }
|
|
345
|
-
});
|
|
346
|
-
|
|
347
|
-
test("a spawn that ERRORS releases the room's key rather than stranding it", async () => {
|
|
348
|
-
const { mod, dir } = await freshDispatcher();
|
|
349
|
-
try {
|
|
350
|
-
const first = dispatchOnce(mod, COHORT_ITEM, WORK);
|
|
351
|
-
first.proc._handlers.close(0);
|
|
352
|
-
const liveId = sessionIdOf(first.args);
|
|
353
|
-
|
|
354
|
-
const failed = dispatchOnce(mod, { ...COHORT_ITEM, id: "e-1" }, WORK);
|
|
355
|
-
assert.equal(sessionIdOf(failed.args), liveId);
|
|
356
|
-
failed.proc._handlers.error(Object.assign(new Error("spawn ENOENT"), { code: "ENOENT" }));
|
|
357
|
-
|
|
358
|
-
// The key is free again: the next turn is routed, not permanently cold.
|
|
359
|
-
const next = dispatchOnce(mod, { ...COHORT_ITEM, id: "e-2" }, WORK);
|
|
360
|
-
assert.equal(typeof sessionIdOf(next.args), "string");
|
|
361
|
-
next.proc._handlers.close(0);
|
|
362
|
-
const after = dispatchOnce(mod, { ...COHORT_ITEM, id: "e-3" }, WORK);
|
|
363
|
-
assert.equal(sessionIdOf(after.args), sessionIdOf(next.args), "continuity works again after the failure");
|
|
364
|
-
} finally { await cleanup(dir); }
|
|
365
|
-
});
|