@cohortapp/agent-sdk 2.16.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 +23 -7
- 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/poller-daemon-setup.md +4 -1
- 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/context/budget.mjs +327 -0
- package/lib/context/history-scope.mjs +138 -0
- 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/economics.mjs +9 -0
- package/lib/model-router/failover.mjs +67 -16
- package/lib/model-router/llm-task.mjs +39 -3
- package/lib/model-router/resolve.mjs +95 -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 +82 -4
- package/lib/org/inbound/hydrate.mjs +555 -51
- 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 +15 -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 +104 -59
- package/scripts/daemon/deliver.mjs +30 -1
- package/scripts/daemon/dispatcher.mjs +804 -157
- package/scripts/daemon/health.mjs +14 -1
- package/scripts/daemon/lib/session-router.mjs +310 -42
- package/scripts/daemon/maestro-daemon.mjs +11 -0
- package/scripts/daemon/prompt-builder.mjs +121 -12
- package/scripts/daemon/responder.mjs +315 -146
- 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/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 -453
- 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 -300
- 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/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 -295
- package/scripts/daemon/prompt-builder-preamble.test.mjs +0 -210
- package/scripts/daemon/prompt-builder.test.mjs +0 -344
- package/scripts/daemon/responder-cost.test.mjs +0 -68
- package/scripts/daemon/responder-history.test.mjs +0 -185
- 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,165 +0,0 @@
|
|
|
1
|
-
import { test } from "node:test";
|
|
2
|
-
import assert from "node:assert/strict";
|
|
3
|
-
|
|
4
|
-
import { boardPriority, boardTitle, closeStageFor, closeBoardItem } from "./board-mirror.mjs";
|
|
5
|
-
|
|
6
|
-
// ---------------------------------------------------------------------------
|
|
7
|
-
// This file used to open AND close a board row of its own through
|
|
8
|
-
// `board.create` + `board.claim` + `board.complete`. That was a second
|
|
9
|
-
// implementation of a lifecycle hq and this repo already share exactly one of
|
|
10
|
-
// (`lib/org/work-ledger.mjs` → `board.track` → hq `server/work/ledger.ts`), and
|
|
11
|
-
// the daemon called BOTH on the same item — two cards per ask on one board, one
|
|
12
|
-
// of them carrying no requester, no comments and no provenance. Worse,
|
|
13
|
-
// `board.create` gives a channel-less row ORG-WIDE visibility and the mirror
|
|
14
|
-
// sent no channel for Slack/email/voice, so a private ask's topic was published
|
|
15
|
-
// to the whole workspace.
|
|
16
|
-
//
|
|
17
|
-
// What survives is the CLOSE hook, because `closeObligation` is the funnel every
|
|
18
|
-
// terminal path goes through and only two of them reach the daemon's onClose.
|
|
19
|
-
// These tests pin that it goes through the SHARED ledger, that it never
|
|
20
|
-
// overstates, and that it still fails open.
|
|
21
|
-
// ---------------------------------------------------------------------------
|
|
22
|
-
|
|
23
|
-
const CFG = { org: { cohort: { enabled: true, base: "https://hq.example", token: "t" } } };
|
|
24
|
-
const okCfg = { loadOrgConfig: () => CFG };
|
|
25
|
-
|
|
26
|
-
/** A recording ledger seam. Answers the way `work-ledger.mjs` does. */
|
|
27
|
-
function ledger(over = {}) {
|
|
28
|
-
const seen = { calls: [] };
|
|
29
|
-
return {
|
|
30
|
-
seen,
|
|
31
|
-
deps: {
|
|
32
|
-
...okCfg,
|
|
33
|
-
recordWorkStep: async (a) => {
|
|
34
|
-
seen.calls.push(a);
|
|
35
|
-
if (over.impl) return over.impl(a);
|
|
36
|
-
return { tracked: true, reason: "ok", taskId: "task_1", col: a.stage === "done" ? "done" : "blocked" };
|
|
37
|
-
},
|
|
38
|
-
},
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
const REC = {
|
|
43
|
-
key: "cohort:dm:C1:9",
|
|
44
|
-
state: "answered",
|
|
45
|
-
summary: "compiling the regulatory filing calendar",
|
|
46
|
-
item: {
|
|
47
|
-
id: "cohort-msg_1",
|
|
48
|
-
raw_ref: "cohort:messaging:msg_1:1",
|
|
49
|
-
service: "cohort",
|
|
50
|
-
channel_id: "chan_1",
|
|
51
|
-
content: "can you compile the regulatory filing calendar",
|
|
52
|
-
},
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
test("the title is the topic the acknowledgement already speaks aloud", () => {
|
|
56
|
-
assert.equal(
|
|
57
|
-
boardTitle({ summary: "compiling the regulatory filing calendar." }),
|
|
58
|
-
"Compiling the regulatory filing calendar"
|
|
59
|
-
);
|
|
60
|
-
// Falls back through the item, then to something honest.
|
|
61
|
-
assert.equal(boardTitle({ item: { subject: "Q3 numbers" } }), "Q3 numbers");
|
|
62
|
-
assert.equal(boardTitle({}), "Responding to an inbound request");
|
|
63
|
-
assert.ok(boardTitle({ summary: "x".repeat(400) }).length <= 120);
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
test("the classifier's priority words are mapped onto hq's P-scale, which is all it accepts", () => {
|
|
67
|
-
// hq validates priority against z.enum(["P0".."P4"]); "critical" 400s the
|
|
68
|
-
// whole call, so the obligation vocabulary has to be translated here.
|
|
69
|
-
assert.equal(boardPriority("critical"), "P0");
|
|
70
|
-
assert.equal(boardPriority("high"), "P1");
|
|
71
|
-
assert.equal(boardPriority("normal"), "P2");
|
|
72
|
-
assert.equal(boardPriority("ignore"), "P3");
|
|
73
|
-
assert.equal(boardPriority("P1"), "P1");
|
|
74
|
-
// No mapping → omit, and let hq's own default (P2) answer.
|
|
75
|
-
assert.equal(boardPriority("whatever"), undefined);
|
|
76
|
-
assert.equal(boardPriority(undefined), undefined);
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
test("only `answered` closes the row as done — every other verdict blocks it", () => {
|
|
80
|
-
assert.equal(closeStageFor("answered"), "done");
|
|
81
|
-
assert.equal(closeStageFor(undefined), "done");
|
|
82
|
-
for (const bad of ["failed", "undeliverable", "stale", "anything-else"]) {
|
|
83
|
-
assert.equal(closeStageFor(bad), "failed", `${bad} must not read as delivered`);
|
|
84
|
-
}
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
test("the close goes through the SHARED ledger, not a second board writer", async () => {
|
|
88
|
-
const { seen, deps } = ledger();
|
|
89
|
-
const r = await closeBoardItem({ rec: REC, agentRoot: "/x", deps });
|
|
90
|
-
|
|
91
|
-
assert.equal(r.mirrored, true);
|
|
92
|
-
assert.equal(r.taskId, "task_1");
|
|
93
|
-
assert.equal(seen.calls.length, 1);
|
|
94
|
-
const call = seen.calls[0];
|
|
95
|
-
// The ledger identifies the ask from the ITEM (service + channel + message),
|
|
96
|
-
// and derives the dedupe key server-side — which is what makes this land on
|
|
97
|
-
// the SAME row the daemon's `accepted`/`working` steps opened, rather than a
|
|
98
|
-
// second card beside it.
|
|
99
|
-
assert.equal(call.item, REC.item);
|
|
100
|
-
assert.equal(call.stage, "done");
|
|
101
|
-
assert.equal(call.deferred, true);
|
|
102
|
-
assert.equal(call.cfg, CFG);
|
|
103
|
-
assert.match(call.note, /Answered in the conversation/);
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
test("a failed obligation lands the row blocked, and says why", async () => {
|
|
107
|
-
const { seen, deps } = ledger();
|
|
108
|
-
await closeBoardItem({ rec: { ...REC, state: "failed", closeNote: "exit 1" }, deps });
|
|
109
|
-
assert.equal(seen.calls[0].stage, "failed");
|
|
110
|
-
assert.match(seen.calls[0].note, /exit 1/);
|
|
111
|
-
assert.match(seen.calls[0].note, /blocked/);
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
test("an undeliverable obligation says so, and still does not read as delivered", async () => {
|
|
115
|
-
const { seen, deps } = ledger();
|
|
116
|
-
await closeBoardItem({ rec: { ...REC, state: "undeliverable" }, deps });
|
|
117
|
-
assert.equal(seen.calls[0].stage, "failed");
|
|
118
|
-
assert.match(seen.calls[0].note, /no way to deliver/);
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
test("an explicit outcome overrides the record's own state", async () => {
|
|
122
|
-
const { seen, deps } = ledger();
|
|
123
|
-
await closeBoardItem({ rec: REC, outcome: "failed", deps });
|
|
124
|
-
assert.equal(seen.calls[0].stage, "failed");
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
test("a non-Cohort obligation writes NO row — a private ask is not published org-wide", async () => {
|
|
128
|
-
// THE LEAK THIS REPLACED: `board.create` with no channelId produces a row
|
|
129
|
-
// every member of the org can read, titled after the ask. The daemon polls
|
|
130
|
-
// Slack, two Gmail accounts, telegram, whatsapp and voice. The shared gate
|
|
131
|
-
// refuses all of them (`not-a-cohort-channel`) — this test pins that the
|
|
132
|
-
// refusal reaches us as a benign no-op rather than being worked around.
|
|
133
|
-
const { seen, deps } = ledger({ impl: async () => ({ tracked: false, reason: "not-a-cohort-channel" }) });
|
|
134
|
-
const r = await closeBoardItem({
|
|
135
|
-
rec: { ...REC, item: { ...REC.item, service: "slack", channel_id: "D0123" } },
|
|
136
|
-
deps,
|
|
137
|
-
});
|
|
138
|
-
assert.equal(r.mirrored, false);
|
|
139
|
-
assert.equal(r.reason, "not-a-cohort-channel");
|
|
140
|
-
assert.equal(seen.calls.length, 1, "the decision is the ledger's, not a rule re-implemented here");
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
test("an obligation with no item snapshot is a no-op, not a throw", async () => {
|
|
144
|
-
const r = await closeBoardItem({
|
|
145
|
-
rec: { key: "k1" },
|
|
146
|
-
deps: { ...okCfg, recordWorkStep: async () => { throw new Error("must not be called"); } },
|
|
147
|
-
});
|
|
148
|
-
assert.equal(r.mirrored, false);
|
|
149
|
-
assert.equal(r.reason, "no-item");
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
test("the close is fail-open: a refusal, a throw and a missing credential all resolve", async () => {
|
|
153
|
-
for (const [label, impl] of [
|
|
154
|
-
["refused", async () => ({ tracked: false, reason: "FORBIDDEN_SCOPE: no" })],
|
|
155
|
-
["threw", async () => { throw new Error("socket hang up"); }],
|
|
156
|
-
["disabled", async () => ({ tracked: false, reason: "org-disabled" })],
|
|
157
|
-
]) {
|
|
158
|
-
const r = await closeBoardItem({
|
|
159
|
-
rec: REC,
|
|
160
|
-
deps: { ...okCfg, recordWorkStep: impl },
|
|
161
|
-
});
|
|
162
|
-
assert.equal(r.mirrored, false, label);
|
|
163
|
-
assert.ok(r.reason, `${label} must report why`);
|
|
164
|
-
}
|
|
165
|
-
});
|
|
@@ -1,393 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* cadence-consumer-frontdoor.test.mjs — the consumer with a live main session
|
|
3
|
-
* (design §3.4) and the F8/F9 cadence fixes.
|
|
4
|
-
*
|
|
5
|
-
* - an escalate/guarded tick is HANDED to a live session (handoff file +
|
|
6
|
-
* rendered prompt on disk + tick processed as "handed-to-session"), never
|
|
7
|
-
* spawned; inline ticks keep running in the consumer
|
|
8
|
-
* - a session that is not live → legacy spawn, unchanged
|
|
9
|
-
* - a handoff past its deadline is re-enqueued with metadata.handoffTimedOut
|
|
10
|
-
* and that tick is NOT handed off again (legacy spawn)
|
|
11
|
-
* - per-cadence in-flight guard: a second tick for a cadence that already
|
|
12
|
-
* has an open handoff / running sub-session is deferred, not spawned twice
|
|
13
|
-
* - a deferred tick is re-keyed to the tail and tickOnce keeps scanning
|
|
14
|
-
* past the deferral instead of breaking (head-of-line block)
|
|
15
|
-
*
|
|
16
|
-
* Every test drives tickOnce() by hand (pollMs is an hour) with an injected
|
|
17
|
-
* spawner, front-door state and clock. No real process is ever launched.
|
|
18
|
-
*/
|
|
19
|
-
|
|
20
|
-
import { test } from "node:test";
|
|
21
|
-
import assert from "node:assert/strict";
|
|
22
|
-
import { promises as fsp } from "node:fs";
|
|
23
|
-
import { writeFileSync, mkdirSync, readFileSync, existsSync, readdirSync, chmodSync } from "node:fs";
|
|
24
|
-
import { tmpdir } from "node:os";
|
|
25
|
-
import { join, dirname } from "node:path";
|
|
26
|
-
|
|
27
|
-
import { enqueueTick, listInbox, busDepth, getBusPaths } from "../../lib/cadence-bus.mjs";
|
|
28
|
-
import { listHandoffs, handoffPaths, writeHandoff, ackHandoff, DEFAULT_HANDOFF_DEADLINE_MS, DEFAULT_HANDOFF_RETENTION_MS } from "../../lib/session/handoffs.mjs";
|
|
29
|
-
import { startConsumer as startConsumerRaw, renderCadencePromptBody } from "./cadence-consumer.mjs";
|
|
30
|
-
import { withParallelism, countParallelism, PARALLELISM_DIRECTIVE } from "../../lib/prompts/parallelism.mjs";
|
|
31
|
-
|
|
32
|
-
const ADMIT_ALL = { admit: () => ({ decision: "ADMIT", reason: "test", snapshot: {} }), defaultDeps: () => ({}) };
|
|
33
|
-
const RATE_OK = { checkRateLimit: () => ({ allowed: true, retryAt: null }), classifyStderr: () => false, recordRateLimit: () => {}, recordSuccess: () => {} };
|
|
34
|
-
const BUDGET_OK = { dailyStatus: () => ({ essentialOnly: false }) };
|
|
35
|
-
const LIVE = () => ({ frontDoor: "session", sessionLive: true });
|
|
36
|
-
const NOT_LIVE = () => ({ frontDoor: "session", sessionLive: false });
|
|
37
|
-
|
|
38
|
-
function startConsumer(opts = {}) {
|
|
39
|
-
return startConsumerRaw({ governor: ADMIT_ALL, rateGuard: RATE_OK, budgetGuard: BUDGET_OK, pollMs: 3_600_000, heartbeatMs: 3_600_000, recoveryMs: 3_600_000, budgetEscalateMs: 0, ...opts });
|
|
40
|
-
}
|
|
41
|
-
async function makeAgentRoot() {
|
|
42
|
-
const p = join(tmpdir(), `cadence-frontdoor-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`);
|
|
43
|
-
await fsp.mkdir(p, { recursive: true });
|
|
44
|
-
writeFileSync(join(p, "config.json"), "{}");
|
|
45
|
-
mkdirSync(join(p, "config"), { recursive: true });
|
|
46
|
-
writeFileSync(join(p, "config", "agent.json"), JSON.stringify({ firstName: "Nova", lastName: "Reyes", name: "Nova Reyes" }));
|
|
47
|
-
return p;
|
|
48
|
-
}
|
|
49
|
-
async function rmRoot(p) { try { await fsp.rm(p, { recursive: true, force: true }); } catch { /* */ } }
|
|
50
|
-
function plantPrompt(root, cadence, body = `# ${cadence} for {{agent.firstName}}`) {
|
|
51
|
-
const path = join(root, "schedules/triggers", `${cadence}.md`);
|
|
52
|
-
mkdirSync(dirname(path), { recursive: true });
|
|
53
|
-
writeFileSync(path, body);
|
|
54
|
-
return path;
|
|
55
|
-
}
|
|
56
|
-
function processedEvents(root) {
|
|
57
|
-
const dir = getBusPaths(root).processed;
|
|
58
|
-
const out = [];
|
|
59
|
-
if (!existsSync(dir)) return out;
|
|
60
|
-
for (const day of readdirSync(dir)) {
|
|
61
|
-
for (const f of readdirSync(join(dir, day))) {
|
|
62
|
-
if (f.endsWith(".json")) out.push(JSON.parse(readFileSync(join(dir, day, f), "utf-8")));
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
return out;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
test("live session: an escalate tick is handed to the session — handoff + rendered prompt on disk, no spawn", async () => {
|
|
69
|
-
const root = await makeAgentRoot();
|
|
70
|
-
plantPrompt(root, "daily-morning-brief");
|
|
71
|
-
const spawns = [];
|
|
72
|
-
const consumer = startConsumer({
|
|
73
|
-
agentRoot: root,
|
|
74
|
-
frontDoorState: LIVE,
|
|
75
|
-
spawnSession: async (o) => { spawns.push(o); return { ok: true, exit_code: 0, duration_ms: 1 }; },
|
|
76
|
-
});
|
|
77
|
-
try {
|
|
78
|
-
const t = enqueueTick({ cadence: "daily-morning-brief", source: "launchd", agentRoot: root });
|
|
79
|
-
const r = await consumer.tickOnce();
|
|
80
|
-
assert.equal(r.processed, 1);
|
|
81
|
-
assert.equal(spawns.length, 0, "must not spawn a sub-session while the session is live");
|
|
82
|
-
const open = listHandoffs(root);
|
|
83
|
-
assert.equal(open.length, 1);
|
|
84
|
-
assert.equal(open[0].tickId, t.id);
|
|
85
|
-
assert.equal(open[0].cadence, "daily-morning-brief");
|
|
86
|
-
assert.equal(open[0].mode, "escalate");
|
|
87
|
-
assert.ok(open[0].deadlineAt, "carries a deadline");
|
|
88
|
-
assert.ok(existsSync(open[0].promptPath), "the rendered prompt is on disk");
|
|
89
|
-
assert.equal(
|
|
90
|
-
readFileSync(open[0].promptPath, "utf-8"),
|
|
91
|
-
withParallelism("# daily-morning-brief for Nova"),
|
|
92
|
-
"identity tokens rendered, and the handed prompt is byte-identical to what a sub-session would have been given",
|
|
93
|
-
);
|
|
94
|
-
assert.equal(busDepth(root).inbox, 0);
|
|
95
|
-
assert.equal(busDepth(root).claimed, 0);
|
|
96
|
-
const done = processedEvents(root).find((e) => e.id === t.id);
|
|
97
|
-
assert.equal(done.result.decision, "handed-to-session");
|
|
98
|
-
assert.equal(consumer.getStats().handed_off, 1);
|
|
99
|
-
assert.equal(consumer.getStats().last_decision, "handed-to-session");
|
|
100
|
-
} finally { await consumer.stop(); await rmRoot(root); }
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
test("live session: inline ticks still run in the consumer; not-live → legacy spawn", async () => {
|
|
104
|
-
const root = await makeAgentRoot();
|
|
105
|
-
plantPrompt(root, "daily-morning-brief");
|
|
106
|
-
const spawns = [];
|
|
107
|
-
const live = { on: true };
|
|
108
|
-
const consumer = startConsumer({
|
|
109
|
-
agentRoot: root,
|
|
110
|
-
frontDoorState: () => ({ frontDoor: "session", sessionLive: live.on }),
|
|
111
|
-
spawnSession: async (o) => { spawns.push(o); return { ok: true, exit_code: 0, duration_ms: 1 }; },
|
|
112
|
-
});
|
|
113
|
-
try {
|
|
114
|
-
enqueueTick({ cadence: "cadence-bus-heartbeat", source: "manual", agentRoot: root });
|
|
115
|
-
await consumer.tickOnce();
|
|
116
|
-
assert.equal(consumer.getStats().inline, 1);
|
|
117
|
-
assert.deepEqual(listHandoffs(root), [], "inline never hands off");
|
|
118
|
-
|
|
119
|
-
live.on = false;
|
|
120
|
-
enqueueTick({ cadence: "daily-morning-brief", source: "launchd", agentRoot: root });
|
|
121
|
-
await consumer.tickOnce();
|
|
122
|
-
assert.equal(spawns.length, 1, "session not live → the legacy lane spawns");
|
|
123
|
-
assert.deepEqual(listHandoffs(root), []);
|
|
124
|
-
} finally { await consumer.stop(); await rmRoot(root); }
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
test("handoff timeout: a handoff past its deadline is re-enqueued with handoffTimedOut and then spawned, not handed off again", async () => {
|
|
128
|
-
const root = await makeAgentRoot();
|
|
129
|
-
plantPrompt(root, "daily-morning-brief");
|
|
130
|
-
const spawns = [];
|
|
131
|
-
let now = Date.parse("2026-09-08T10:00:00.000Z");
|
|
132
|
-
const consumer = startConsumer({
|
|
133
|
-
agentRoot: root,
|
|
134
|
-
now: () => now,
|
|
135
|
-
frontDoorState: LIVE,
|
|
136
|
-
spawnSession: async (o) => { spawns.push(o); return { ok: true, exit_code: 0, duration_ms: 1 }; },
|
|
137
|
-
});
|
|
138
|
-
try {
|
|
139
|
-
writeHandoff(root, { tickId: "evt-stuck", cadence: "daily-morning-brief", mode: "escalate", promptPath: "/x", metadata: { reason: "guard:escalate" } }, { now });
|
|
140
|
-
now += DEFAULT_HANDOFF_DEADLINE_MS + 1;
|
|
141
|
-
await consumer.tickOnce();
|
|
142
|
-
assert.equal(consumer.getStats().handoff_timeouts, 1);
|
|
143
|
-
assert.deepEqual(listHandoffs(root), [], "the stuck handoff is retired");
|
|
144
|
-
assert.equal(JSON.parse(readFileSync(join(handoffPaths(root).done, "evt-stuck.json"), "utf-8")).status, "expired");
|
|
145
|
-
// The re-enqueued tick ran on the legacy lane in that same tick (or the next).
|
|
146
|
-
if (spawns.length === 0) await consumer.tickOnce();
|
|
147
|
-
assert.equal(spawns.length, 1, "timed-out handoff → legacy spawn even though the session is live");
|
|
148
|
-
assert.equal(spawns[0].cadence, "daily-morning-brief");
|
|
149
|
-
const ev = processedEvents(root).find((e) => e.source === "handoff-timeout");
|
|
150
|
-
assert.equal(ev.metadata.handoffTimedOut, true);
|
|
151
|
-
assert.equal(ev.metadata.handoffTickId, "evt-stuck");
|
|
152
|
-
assert.equal(ev.result.decision, "escalated");
|
|
153
|
-
assert.deepEqual(listHandoffs(root), [], "never handed off a second time");
|
|
154
|
-
} finally { await consumer.stop(); await rmRoot(root); }
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
test("per-cadence in-flight guard: a cadence with an open handoff is deferred (re-keyed), a different cadence proceeds", async () => {
|
|
158
|
-
const root = await makeAgentRoot();
|
|
159
|
-
plantPrompt(root, "daily-morning-brief");
|
|
160
|
-
plantPrompt(root, "meeting-action-capture");
|
|
161
|
-
const consumer = startConsumer({ agentRoot: root, frontDoorState: LIVE, spawnSession: async () => ({ ok: true, exit_code: 0 }) });
|
|
162
|
-
try {
|
|
163
|
-
const a = enqueueTick({ cadence: "daily-morning-brief", source: "launchd", agentRoot: root });
|
|
164
|
-
await consumer.tickOnce();
|
|
165
|
-
assert.equal(listHandoffs(root).length, 1);
|
|
166
|
-
await new Promise((r) => setTimeout(r, 2));
|
|
167
|
-
const a2 = enqueueTick({ cadence: "daily-morning-brief", source: "launchd", agentRoot: root });
|
|
168
|
-
await new Promise((r) => setTimeout(r, 2));
|
|
169
|
-
enqueueTick({ cadence: "meeting-action-capture", source: "launchd", agentRoot: root });
|
|
170
|
-
await consumer.tickOnce();
|
|
171
|
-
const open = listHandoffs(root);
|
|
172
|
-
assert.deepEqual(open.map((h) => h.cadence).sort(), ["daily-morning-brief", "meeting-action-capture"], "second cadence handed off; first not duplicated");
|
|
173
|
-
assert.equal(open.filter((h) => h.cadence === "daily-morning-brief").length, 1, "the same cadence is never in flight twice");
|
|
174
|
-
const inbox = listInbox(root);
|
|
175
|
-
assert.equal(inbox.length, 1, "the duplicate tick waits at the tail");
|
|
176
|
-
const waiting = JSON.parse(readFileSync(join(getBusPaths(root).inbox, `${inbox[0]}.json`), "utf-8"));
|
|
177
|
-
assert.equal(waiting.cadence, "daily-morning-brief");
|
|
178
|
-
assert.equal(waiting.metadata.originalId, a2.id);
|
|
179
|
-
assert.equal(waiting.metadata.deferrals, 1);
|
|
180
|
-
assert.notEqual(inbox[0], a2.id, "re-keyed, not requeued under its own id");
|
|
181
|
-
void a;
|
|
182
|
-
} finally { await consumer.stop(); await rmRoot(root); }
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
test("per-cadence in-flight guard: a running sub-session for a cadence blocks a second spawn of the same cadence", async () => {
|
|
186
|
-
const root = await makeAgentRoot();
|
|
187
|
-
plantPrompt(root, "daily-morning-brief");
|
|
188
|
-
let release;
|
|
189
|
-
const gate = new Promise((r) => { release = r; });
|
|
190
|
-
const spawns = [];
|
|
191
|
-
const consumer = startConsumer({
|
|
192
|
-
agentRoot: root,
|
|
193
|
-
frontDoorState: NOT_LIVE,
|
|
194
|
-
spawnSession: async (o) => { spawns.push(o.cadence); await gate; return { ok: true, exit_code: 0 }; },
|
|
195
|
-
});
|
|
196
|
-
try {
|
|
197
|
-
enqueueTick({ cadence: "daily-morning-brief", source: "launchd", agentRoot: root });
|
|
198
|
-
const first = consumer.tickOnce(); // holds the spawn open
|
|
199
|
-
await new Promise((r) => setTimeout(r, 30));
|
|
200
|
-
assert.equal(spawns.length, 1);
|
|
201
|
-
await new Promise((r) => setTimeout(r, 2));
|
|
202
|
-
enqueueTick({ cadence: "daily-morning-brief", source: "launchd", agentRoot: root });
|
|
203
|
-
await consumer.tickOnce();
|
|
204
|
-
assert.equal(spawns.length, 1, "no second spawn while the first is running");
|
|
205
|
-
assert.equal(listInbox(root).length, 1, "the second tick is deferred to the tail");
|
|
206
|
-
release();
|
|
207
|
-
await first;
|
|
208
|
-
} finally { await consumer.stop(); await rmRoot(root); }
|
|
209
|
-
});
|
|
210
|
-
|
|
211
|
-
test("head-of-line: a circuit-open deferral is re-keyed to the tail and tickOnce keeps scanning to the next cadence", async () => {
|
|
212
|
-
const root = await makeAgentRoot();
|
|
213
|
-
plantPrompt(root, "weekly-strategic-memo");
|
|
214
|
-
plantPrompt(root, "daily-morning-brief");
|
|
215
|
-
const spawns = [];
|
|
216
|
-
const consumer = startConsumer({
|
|
217
|
-
agentRoot: root,
|
|
218
|
-
frontDoorState: NOT_LIVE,
|
|
219
|
-
backoffSchedule: [0, 0, 0],
|
|
220
|
-
circuitThreshold: 1,
|
|
221
|
-
circuitDurationMs: 60_000,
|
|
222
|
-
maxAttempts: 1,
|
|
223
|
-
spawnSession: async (o) => {
|
|
224
|
-
spawns.push(o.cadence);
|
|
225
|
-
if (o.cadence === "weekly-strategic-memo") return { ok: false, exit_code: 1, error: "fail", duration_ms: 1 };
|
|
226
|
-
return { ok: true, exit_code: 0, duration_ms: 1 };
|
|
227
|
-
},
|
|
228
|
-
});
|
|
229
|
-
try {
|
|
230
|
-
enqueueTick({ cadence: "weekly-strategic-memo", source: "launchd", agentRoot: root });
|
|
231
|
-
await consumer.tickOnce(); // fails once → circuit opens for weekly-strategic-memo
|
|
232
|
-
assert.equal(consumer.getStats().spawn_failures, 1);
|
|
233
|
-
|
|
234
|
-
const held = enqueueTick({ cadence: "weekly-strategic-memo", source: "launchd", agentRoot: root });
|
|
235
|
-
await new Promise((r) => setTimeout(r, 2));
|
|
236
|
-
enqueueTick({ cadence: "daily-morning-brief", source: "launchd", agentRoot: root });
|
|
237
|
-
const r = await consumer.tickOnce();
|
|
238
|
-
// Before: the circuit skip broke the loop and daily-morning-brief waited a
|
|
239
|
-
// whole poll behind a tick that could not run. Now both are visited.
|
|
240
|
-
assert.equal(r.processed, 2, `both ticks visited in one tick (stats=${JSON.stringify(consumer.getStats())})`);
|
|
241
|
-
assert.deepEqual(spawns, ["weekly-strategic-memo", "daily-morning-brief"]);
|
|
242
|
-
assert.equal(consumer.getStats().skipped_circuit_open, 1);
|
|
243
|
-
const inbox = listInbox(root);
|
|
244
|
-
assert.equal(inbox.length, 1, "the held tick is back in the inbox, once");
|
|
245
|
-
assert.notEqual(inbox[0], held.id, "under a NEW id (tail), not its own");
|
|
246
|
-
const on = JSON.parse(readFileSync(join(getBusPaths(root).inbox, `${inbox[0]}.json`), "utf-8"));
|
|
247
|
-
assert.equal(on.metadata.originalId, held.id);
|
|
248
|
-
assert.equal(on.metadata.deferrals, 1);
|
|
249
|
-
// A further tick with ONLY the held cadence queued does not spin: one visit, one re-key, then it stops.
|
|
250
|
-
const r2 = await consumer.tickOnce();
|
|
251
|
-
assert.equal(r2.processed, 1);
|
|
252
|
-
assert.equal(listInbox(root).length, 1);
|
|
253
|
-
assert.equal(JSON.parse(readFileSync(join(getBusPaths(root).inbox, `${listInbox(root)[0]}.json`), "utf-8")).metadata.deferrals, 2);
|
|
254
|
-
} finally { await consumer.stop(); await rmRoot(root); }
|
|
255
|
-
});
|
|
256
|
-
|
|
257
|
-
test("a broken front-door reader fails open to the legacy lane", async () => {
|
|
258
|
-
const root = await makeAgentRoot();
|
|
259
|
-
plantPrompt(root, "daily-morning-brief");
|
|
260
|
-
const spawns = [];
|
|
261
|
-
const consumer = startConsumer({
|
|
262
|
-
agentRoot: root,
|
|
263
|
-
frontDoorState: () => { throw new Error("boom"); },
|
|
264
|
-
spawnSession: async (o) => { spawns.push(o); return { ok: true, exit_code: 0 }; },
|
|
265
|
-
});
|
|
266
|
-
try {
|
|
267
|
-
enqueueTick({ cadence: "daily-morning-brief", source: "launchd", agentRoot: root });
|
|
268
|
-
await consumer.tickOnce();
|
|
269
|
-
assert.equal(spawns.length, 1);
|
|
270
|
-
assert.deepEqual(listHandoffs(root), []);
|
|
271
|
-
} finally { await consumer.stop(); await rmRoot(root); }
|
|
272
|
-
});
|
|
273
|
-
|
|
274
|
-
test("handoff timeout: when the re-enqueue fails the handoff stays OPEN and is retried by the next sweep — the cadence run is never lost", async () => {
|
|
275
|
-
if (process.getuid && process.getuid() === 0) return; // root ignores directory modes
|
|
276
|
-
const root = await makeAgentRoot();
|
|
277
|
-
plantPrompt(root, "daily-morning-brief");
|
|
278
|
-
let now = Date.parse("2026-09-08T10:00:00.000Z");
|
|
279
|
-
const consumer = startConsumer({ agentRoot: root, now: () => now, frontDoorState: LIVE, spawnSession: async () => ({ ok: true, exit_code: 0, duration_ms: 1 }) });
|
|
280
|
-
try {
|
|
281
|
-
writeHandoff(root, { tickId: "evt-stuck", cadence: "daily-morning-brief", mode: "escalate", promptPath: "/x" }, { now });
|
|
282
|
-
now += DEFAULT_HANDOFF_DEADLINE_MS + 1;
|
|
283
|
-
const inbox = getBusPaths(root).inbox;
|
|
284
|
-
chmodSync(inbox, 0o500); // the bus inbox is unwritable for a minute
|
|
285
|
-
try {
|
|
286
|
-
await consumer.tickOnce();
|
|
287
|
-
assert.deepEqual(listHandoffs(root).map((h) => h.tickId), ["evt-stuck"], "not retired while the tick could not be re-enqueued");
|
|
288
|
-
assert.equal(existsSync(join(handoffPaths(root).done, "evt-stuck.json")), false);
|
|
289
|
-
assert.equal(consumer.getStats().handoff_timeouts, 0);
|
|
290
|
-
} finally { chmodSync(inbox, 0o755); }
|
|
291
|
-
await consumer.tickOnce();
|
|
292
|
-
assert.deepEqual(listHandoffs(root), [], "retired once the re-enqueue succeeded");
|
|
293
|
-
assert.equal(consumer.getStats().handoff_timeouts, 1);
|
|
294
|
-
assert.equal(JSON.parse(readFileSync(join(handoffPaths(root).done, "evt-stuck.json"), "utf-8")).status, "expired");
|
|
295
|
-
} finally { await consumer.stop(); await rmRoot(root); }
|
|
296
|
-
});
|
|
297
|
-
|
|
298
|
-
test("circuit/backoff skip: re-keyed to the tail but NOT counted as a retry or a deferral in the health stats", async () => {
|
|
299
|
-
const root = await makeAgentRoot();
|
|
300
|
-
plantPrompt(root, "weekly-strategic-memo");
|
|
301
|
-
const consumer = startConsumer({
|
|
302
|
-
agentRoot: root,
|
|
303
|
-
frontDoorState: NOT_LIVE,
|
|
304
|
-
backoffSchedule: [0, 0, 0],
|
|
305
|
-
circuitThreshold: 1,
|
|
306
|
-
circuitDurationMs: 60_000,
|
|
307
|
-
maxAttempts: 1,
|
|
308
|
-
spawnSession: async () => ({ ok: false, exit_code: 1, error: "fail", duration_ms: 1 }),
|
|
309
|
-
});
|
|
310
|
-
try {
|
|
311
|
-
enqueueTick({ cadence: "weekly-strategic-memo", source: "launchd", agentRoot: root });
|
|
312
|
-
await consumer.tickOnce(); // opens the circuit
|
|
313
|
-
const before = consumer.getStats();
|
|
314
|
-
enqueueTick({ cadence: "weekly-strategic-memo", source: "launchd", agentRoot: root });
|
|
315
|
-
for (let i = 0; i < 5; i++) await consumer.tickOnce(); // five polls inside the circuit window
|
|
316
|
-
const after = consumer.getStats();
|
|
317
|
-
assert.equal(after.skipped_circuit_open, 5);
|
|
318
|
-
assert.equal(after.retries, before.retries, "a held-back cadence was never attempted — not a retry");
|
|
319
|
-
assert.equal(after.deferred, before.deferred, "nor a governance deferral");
|
|
320
|
-
assert.equal(listInbox(root).length, 1, "the held tick is still waiting, once");
|
|
321
|
-
} finally { await consumer.stop(); await rmRoot(root); }
|
|
322
|
-
});
|
|
323
|
-
|
|
324
|
-
test("handoff ledger hygiene: the sweep prunes done/ records and prompts past retention, at most once an hour", async () => {
|
|
325
|
-
const root = await makeAgentRoot();
|
|
326
|
-
let now = Date.parse("2026-09-08T10:00:00.000Z");
|
|
327
|
-
const consumer = startConsumer({ agentRoot: root, now: () => now, frontDoorState: LIVE, spawnSession: async () => ({ ok: true, exit_code: 0 }) });
|
|
328
|
-
try {
|
|
329
|
-
const paths = handoffPaths(root);
|
|
330
|
-
mkdirSync(paths.prompts, { recursive: true });
|
|
331
|
-
writeFileSync(join(paths.prompts, "evt-done.md"), "# p");
|
|
332
|
-
writeHandoff(root, { tickId: "evt-done", cadence: "a", promptPath: join(paths.prompts, "evt-done.md") }, { now });
|
|
333
|
-
ackHandoff(root, "evt-done", { now });
|
|
334
|
-
now += DEFAULT_HANDOFF_RETENTION_MS + 1000;
|
|
335
|
-
await consumer.tickOnce();
|
|
336
|
-
assert.equal(existsSync(join(paths.done, "evt-done.json")), false, "pruned by the sweep");
|
|
337
|
-
assert.equal(existsSync(join(paths.prompts, "evt-done.md")), false);
|
|
338
|
-
} finally { await consumer.stop(); await rmRoot(root); }
|
|
339
|
-
});
|
|
340
|
-
|
|
341
|
-
// ── the parallelism directive on the escalate/guarded lane (WP-M7 mechanic 5) ──
|
|
342
|
-
//
|
|
343
|
-
// The spec puts the directive on "every escalate/guarded sub-session prompt".
|
|
344
|
-
// Cadence trigger prompts never pass through `prompt-builder.mjs#buildPrompt`
|
|
345
|
-
// — they are read straight off schedules/triggers/<name>.md — so this lane has
|
|
346
|
-
// its own seam (`renderCadencePromptBody`) and needs its own pins. Without it
|
|
347
|
-
// an `escalate` tick whose trigger asks for six independent audits spawns a
|
|
348
|
-
// sub-session with no standing permission to fan out.
|
|
349
|
-
|
|
350
|
-
test("escalate: the spawned sub-session prompt leads with the parallelism directive, exactly once", async () => {
|
|
351
|
-
const root = await makeAgentRoot();
|
|
352
|
-
plantPrompt(root, "daily-morning-brief", "# daily-morning-brief for {{agent.firstName}}\n\nAudit six things.");
|
|
353
|
-
const spawns = [];
|
|
354
|
-
const consumer = startConsumer({
|
|
355
|
-
agentRoot: root,
|
|
356
|
-
frontDoorState: NOT_LIVE,
|
|
357
|
-
spawnSession: async (o) => { spawns.push(o); return { ok: true, exit_code: 0, duration_ms: 1 }; },
|
|
358
|
-
});
|
|
359
|
-
try {
|
|
360
|
-
enqueueTick({ cadence: "daily-morning-brief", source: "launchd", agentRoot: root });
|
|
361
|
-
await consumer.tickOnce();
|
|
362
|
-
assert.equal(spawns.length, 1);
|
|
363
|
-
// The injected spawner is handed the promptPath, not the body — so assert
|
|
364
|
-
// on the seam the real spawner uses to turn that path into argv.
|
|
365
|
-
const body = renderCadencePromptBody(root, readFileSync(join(root, spawns[0].promptPath), "utf-8"));
|
|
366
|
-
assert.ok(body.startsWith(PARALLELISM_DIRECTIVE), "the directive leads");
|
|
367
|
-
assert.equal(countParallelism(body), 1);
|
|
368
|
-
assert.ok(body.endsWith("# daily-morning-brief for Nova\n\nAudit six things."), "and the trigger body follows, tokens rendered");
|
|
369
|
-
} finally { await consumer.stop(); await rmRoot(root); }
|
|
370
|
-
});
|
|
371
|
-
|
|
372
|
-
test("a trigger that already carries the directive is not given a second copy", () => {
|
|
373
|
-
const body = renderCadencePromptBody("/nonexistent-agent-root", withParallelism("# already-primed"));
|
|
374
|
-
assert.equal(countParallelism(body), 1, "idempotent — a repeated directive reads as emphasis and erodes the file-scope rule");
|
|
375
|
-
assert.equal(body, withParallelism("# already-primed"));
|
|
376
|
-
});
|
|
377
|
-
|
|
378
|
-
test("the handed-off prompt and the spawned prompt are the same bytes", async () => {
|
|
379
|
-
const root = await makeAgentRoot();
|
|
380
|
-
const raw = "# weekly-strategic-memo for {{agent.firstName}}";
|
|
381
|
-
plantPrompt(root, "weekly-strategic-memo", raw);
|
|
382
|
-
const consumer = startConsumer({ agentRoot: root, frontDoorState: LIVE, spawnSession: async () => ({ ok: true, exit_code: 0 }) });
|
|
383
|
-
try {
|
|
384
|
-
enqueueTick({ cadence: "weekly-strategic-memo", source: "launchd", agentRoot: root });
|
|
385
|
-
await consumer.tickOnce();
|
|
386
|
-
const open = listHandoffs(root);
|
|
387
|
-
assert.equal(open.length, 1);
|
|
388
|
-
const handed = readFileSync(open[0].promptPath, "utf-8");
|
|
389
|
-
assert.equal(handed, renderCadencePromptBody(root, raw), "one seam, two lanes");
|
|
390
|
-
assert.equal(countParallelism(handed), 1);
|
|
391
|
-
assert.ok(handed.startsWith(PARALLELISM_DIRECTIVE));
|
|
392
|
-
} finally { await consumer.stop(); await rmRoot(root); }
|
|
393
|
-
});
|