@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,336 +0,0 @@
|
|
|
1
|
-
import { test } from "node:test";
|
|
2
|
-
import assert from "node:assert/strict";
|
|
3
|
-
import { mkdtempSync, mkdirSync, writeFileSync, readdirSync, rmSync } from "node:fs";
|
|
4
|
-
import { join } from "node:path";
|
|
5
|
-
import { tmpdir } from "node:os";
|
|
6
|
-
|
|
7
|
-
import { markDeferred, promoteDeferred } from "./inbox-deferral.mjs";
|
|
8
|
-
|
|
9
|
-
function makeAgentRoot() {
|
|
10
|
-
const root = mkdtempSync(join(tmpdir(), "maestro-deferral-"));
|
|
11
|
-
mkdirSync(join(root, "state", "inbox", "slack"), { recursive: true });
|
|
12
|
-
return root;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
function writeInboxItem(root, name, { channel_id, timestamp, id = "test-id", thread_id, scope_id }) {
|
|
16
|
-
const body = [
|
|
17
|
-
`id: "${id}"`,
|
|
18
|
-
`service: "slack"`,
|
|
19
|
-
`channel_id: "${channel_id}"`,
|
|
20
|
-
...(thread_id ? [`thread_id: "${thread_id}"`] : []),
|
|
21
|
-
...(scope_id ? [`scope_id: "${scope_id}"`] : []),
|
|
22
|
-
`timestamp: "${timestamp}"`,
|
|
23
|
-
`content: |`,
|
|
24
|
-
` body`,
|
|
25
|
-
"",
|
|
26
|
-
].join("\n");
|
|
27
|
-
writeFileSync(join(root, "state", "inbox", "slack", name), body);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
test("markDeferred renames live file to .deferred", () => {
|
|
31
|
-
const root = makeAgentRoot();
|
|
32
|
-
try {
|
|
33
|
-
writeInboxItem(root, "msg-A.yaml", {
|
|
34
|
-
channel_id: "D001",
|
|
35
|
-
timestamp: "2026-05-13T00:00:00Z",
|
|
36
|
-
id: "msg-A",
|
|
37
|
-
});
|
|
38
|
-
const n = markDeferred({ id: "msg-A" }, "slack", root);
|
|
39
|
-
assert.equal(n, 1);
|
|
40
|
-
const files = readdirSync(join(root, "state", "inbox", "slack"));
|
|
41
|
-
assert.deepEqual(files, ["msg-A.yaml.deferred"]);
|
|
42
|
-
} finally {
|
|
43
|
-
rmSync(root, { recursive: true, force: true });
|
|
44
|
-
}
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
test("markDeferred is idempotent — already-deferred files are skipped", () => {
|
|
48
|
-
const root = makeAgentRoot();
|
|
49
|
-
try {
|
|
50
|
-
writeInboxItem(root, "msg-B.yaml.deferred", {
|
|
51
|
-
channel_id: "D001",
|
|
52
|
-
timestamp: "2026-05-13T00:00:00Z",
|
|
53
|
-
id: "msg-B",
|
|
54
|
-
});
|
|
55
|
-
const n = markDeferred({ id: "msg-B" }, "slack", root);
|
|
56
|
-
assert.equal(n, 0);
|
|
57
|
-
} finally {
|
|
58
|
-
rmSync(root, { recursive: true, force: true });
|
|
59
|
-
}
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
test("markDeferred is no-op when item has no matching file (e.g. backlog)", () => {
|
|
63
|
-
const root = makeAgentRoot();
|
|
64
|
-
try {
|
|
65
|
-
const n = markDeferred({ id: "no-such-id" }, "slack", root);
|
|
66
|
-
assert.equal(n, 0);
|
|
67
|
-
} finally {
|
|
68
|
-
rmSync(root, { recursive: true, force: true });
|
|
69
|
-
}
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
test("promoteDeferred with single item: renames back to live", () => {
|
|
73
|
-
const root = makeAgentRoot();
|
|
74
|
-
try {
|
|
75
|
-
writeInboxItem(root, "msg-C.yaml.deferred", {
|
|
76
|
-
channel_id: "D099N1JEA10",
|
|
77
|
-
timestamp: "2026-05-13T00:00:00Z",
|
|
78
|
-
id: "msg-C",
|
|
79
|
-
});
|
|
80
|
-
const r = promoteDeferred("D099N1JEA10", root);
|
|
81
|
-
assert.equal(r.promoted, 1);
|
|
82
|
-
assert.equal(r.bundled, 0);
|
|
83
|
-
assert.equal(r.service, "slack");
|
|
84
|
-
const files = readdirSync(join(root, "state", "inbox", "slack"));
|
|
85
|
-
assert.deepEqual(files, ["msg-C.yaml"]);
|
|
86
|
-
} finally {
|
|
87
|
-
rmSync(root, { recursive: true, force: true });
|
|
88
|
-
}
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
test("promoteDeferred with multiple items: keeps latest, bundles rest", () => {
|
|
92
|
-
const root = makeAgentRoot();
|
|
93
|
-
try {
|
|
94
|
-
writeInboxItem(root, "msg-1.yaml.deferred", {
|
|
95
|
-
channel_id: "D001",
|
|
96
|
-
timestamp: "2026-05-13T00:00:00Z",
|
|
97
|
-
id: "msg-1",
|
|
98
|
-
});
|
|
99
|
-
writeInboxItem(root, "msg-2.yaml.deferred", {
|
|
100
|
-
channel_id: "D001",
|
|
101
|
-
timestamp: "2026-05-13T00:01:00Z",
|
|
102
|
-
id: "msg-2",
|
|
103
|
-
});
|
|
104
|
-
writeInboxItem(root, "msg-3.yaml.deferred", {
|
|
105
|
-
channel_id: "D001",
|
|
106
|
-
timestamp: "2026-05-13T00:02:00Z",
|
|
107
|
-
id: "msg-3",
|
|
108
|
-
});
|
|
109
|
-
const r = promoteDeferred("D001", root);
|
|
110
|
-
assert.equal(r.promoted, 1);
|
|
111
|
-
assert.equal(r.bundled, 2);
|
|
112
|
-
const files = readdirSync(join(root, "state", "inbox", "slack")).sort();
|
|
113
|
-
assert.deepEqual(files, [
|
|
114
|
-
"msg-1.yaml.processed-bundled",
|
|
115
|
-
"msg-2.yaml.processed-bundled",
|
|
116
|
-
"msg-3.yaml", // latest survives as live
|
|
117
|
-
]);
|
|
118
|
-
} finally {
|
|
119
|
-
rmSync(root, { recursive: true, force: true });
|
|
120
|
-
}
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
test("promoteDeferred bundles WITHIN a surface, not across the whole channel", () => {
|
|
124
|
-
// A directed 1:1 DM thread AND the actor's doc-comment events resolve to the
|
|
125
|
-
// same channel_id. Bundling latest-wins across the channel would drop the DM
|
|
126
|
-
// just because a newer doc event landed. Each surface must promote its own
|
|
127
|
-
// latest; nothing is bundled across surfaces.
|
|
128
|
-
const root = makeAgentRoot();
|
|
129
|
-
try {
|
|
130
|
-
// The directed DM (one message, thread T-DM).
|
|
131
|
-
writeInboxItem(root, "dm-1.yaml.deferred", {
|
|
132
|
-
channel_id: "room-1", thread_id: "T-DM", id: "dm-1",
|
|
133
|
-
timestamp: "2026-05-13T00:00:30Z",
|
|
134
|
-
});
|
|
135
|
-
// Two doc-comment events on the same room but a different surface (scope S-DOC),
|
|
136
|
-
// the later of which would have "won" under the old channel-wide bundling.
|
|
137
|
-
writeInboxItem(root, "doc-1.yaml.deferred", {
|
|
138
|
-
channel_id: "room-1", scope_id: "S-DOC", id: "doc-1",
|
|
139
|
-
timestamp: "2026-05-13T00:01:00Z",
|
|
140
|
-
});
|
|
141
|
-
writeInboxItem(root, "doc-2.yaml.deferred", {
|
|
142
|
-
channel_id: "room-1", scope_id: "S-DOC", id: "doc-2",
|
|
143
|
-
timestamp: "2026-05-13T00:02:00Z",
|
|
144
|
-
});
|
|
145
|
-
const r = promoteDeferred("room-1", root);
|
|
146
|
-
assert.equal(r.promoted, 2, "the DM and the latest doc event are BOTH promoted");
|
|
147
|
-
assert.equal(r.bundled, 1, "only the older doc event (same surface) is bundled");
|
|
148
|
-
const files = readdirSync(join(root, "state", "inbox", "slack")).sort();
|
|
149
|
-
assert.deepEqual(files, [
|
|
150
|
-
"dm-1.yaml", // the directed DM survives — NOT dropped
|
|
151
|
-
"doc-1.yaml.processed-bundled", // older doc event folded into doc-2
|
|
152
|
-
"doc-2.yaml", // latest doc event promoted
|
|
153
|
-
]);
|
|
154
|
-
} finally {
|
|
155
|
-
rmSync(root, { recursive: true, force: true });
|
|
156
|
-
}
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
test("promoteDeferred ignores items in other channels", () => {
|
|
160
|
-
const root = makeAgentRoot();
|
|
161
|
-
try {
|
|
162
|
-
writeInboxItem(root, "for-A.yaml.deferred", {
|
|
163
|
-
channel_id: "D-A",
|
|
164
|
-
timestamp: "2026-05-13T00:00:00Z",
|
|
165
|
-
id: "for-A",
|
|
166
|
-
});
|
|
167
|
-
writeInboxItem(root, "for-B.yaml.deferred", {
|
|
168
|
-
channel_id: "D-B",
|
|
169
|
-
timestamp: "2026-05-13T00:00:00Z",
|
|
170
|
-
id: "for-B",
|
|
171
|
-
});
|
|
172
|
-
const r = promoteDeferred("D-A", root);
|
|
173
|
-
assert.equal(r.promoted, 1);
|
|
174
|
-
assert.equal(r.bundled, 0);
|
|
175
|
-
const files = readdirSync(join(root, "state", "inbox", "slack")).sort();
|
|
176
|
-
assert.deepEqual(files, ["for-A.yaml", "for-B.yaml.deferred"]);
|
|
177
|
-
} finally {
|
|
178
|
-
rmSync(root, { recursive: true, force: true });
|
|
179
|
-
}
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
test("promoteDeferred no-op when nothing to promote", () => {
|
|
183
|
-
const root = makeAgentRoot();
|
|
184
|
-
try {
|
|
185
|
-
const r = promoteDeferred("D999", root);
|
|
186
|
-
assert.equal(r.promoted, 0);
|
|
187
|
-
assert.equal(r.bundled, 0);
|
|
188
|
-
assert.equal(r.service, null);
|
|
189
|
-
} finally {
|
|
190
|
-
rmSync(root, { recursive: true, force: true });
|
|
191
|
-
}
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
// ---------------------------------------------------------------------------
|
|
195
|
-
// Audit L7 — block-scalar body must not be mis-parsed for routing fields
|
|
196
|
-
// ---------------------------------------------------------------------------
|
|
197
|
-
|
|
198
|
-
/**
|
|
199
|
-
* Write an inbox item whose `content` block scalar body itself contains lines
|
|
200
|
-
* that look like top-level `channel_id:` / `timestamp:` keys. A naive
|
|
201
|
-
* `^\s*field:` regex would match the INDENTED body line and mis-route.
|
|
202
|
-
*/
|
|
203
|
-
function writeInboxItemWithTrickyBody(root, name, { channel_id, timestamp, id, bodyChannel, bodyTimestamp }) {
|
|
204
|
-
const lines = [
|
|
205
|
-
`id: "${id}"`,
|
|
206
|
-
`service: "slack"`,
|
|
207
|
-
`channel_id: "${channel_id}"`,
|
|
208
|
-
`timestamp: "${timestamp}"`,
|
|
209
|
-
`content: |`,
|
|
210
|
-
` Hi team — quoting an old config below:`,
|
|
211
|
-
` channel_id: ${bodyChannel}`,
|
|
212
|
-
` timestamp: ${bodyTimestamp}`,
|
|
213
|
-
` please review.`,
|
|
214
|
-
"",
|
|
215
|
-
];
|
|
216
|
-
writeFileSync(join(root, "state", "inbox", "slack", name), lines.join("\n"));
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
test("L7: body line `channel_id:` in a block scalar does not mis-route", () => {
|
|
220
|
-
const root = makeAgentRoot();
|
|
221
|
-
try {
|
|
222
|
-
// Real top-level channel is D-REAL. The body mentions D-EVIL on an
|
|
223
|
-
// indented `channel_id:` line — promote must target D-REAL only.
|
|
224
|
-
writeInboxItemWithTrickyBody(root, "tricky.yaml.deferred", {
|
|
225
|
-
channel_id: "D-REAL",
|
|
226
|
-
timestamp: "2026-05-13T00:00:00Z",
|
|
227
|
-
id: "tricky",
|
|
228
|
-
bodyChannel: "D-EVIL",
|
|
229
|
-
bodyTimestamp: "1999-01-01T00:00:00Z",
|
|
230
|
-
});
|
|
231
|
-
|
|
232
|
-
// Promoting the body's channel must find NOTHING (the body line is not a
|
|
233
|
-
// real top-level field).
|
|
234
|
-
const evil = promoteDeferred("D-EVIL", root);
|
|
235
|
-
assert.equal(evil.promoted, 0, "must not match the indented body channel_id");
|
|
236
|
-
assert.equal(evil.bundled, 0);
|
|
237
|
-
|
|
238
|
-
// Promoting the genuine top-level channel must succeed.
|
|
239
|
-
const real = promoteDeferred("D-REAL", root);
|
|
240
|
-
assert.equal(real.promoted, 1, "real top-level channel_id must match");
|
|
241
|
-
assert.equal(real.bundled, 0);
|
|
242
|
-
const files = readdirSync(join(root, "state", "inbox", "slack"));
|
|
243
|
-
assert.deepEqual(files, ["tricky.yaml"]);
|
|
244
|
-
} finally {
|
|
245
|
-
rmSync(root, { recursive: true, force: true });
|
|
246
|
-
}
|
|
247
|
-
});
|
|
248
|
-
|
|
249
|
-
test("L7: latest-wins uses top-level timestamp, not a body `timestamp:` line", () => {
|
|
250
|
-
const root = makeAgentRoot();
|
|
251
|
-
try {
|
|
252
|
-
// Two items in the same channel. Item B is genuinely later by its
|
|
253
|
-
// top-level timestamp, but its block-scalar body contains an EARLY
|
|
254
|
-
// `timestamp:` line. A naive parser could read the body line, pick A as
|
|
255
|
-
// "latest" and bundle B. Correct behaviour: B wins (later top-level ts).
|
|
256
|
-
writeInboxItem(root, "A.yaml.deferred", {
|
|
257
|
-
channel_id: "D-TS",
|
|
258
|
-
timestamp: "2026-05-13T00:00:00Z",
|
|
259
|
-
id: "A",
|
|
260
|
-
});
|
|
261
|
-
writeInboxItemWithTrickyBody(root, "B.yaml.deferred", {
|
|
262
|
-
channel_id: "D-TS",
|
|
263
|
-
timestamp: "2026-05-13T09:00:00Z", // genuinely later
|
|
264
|
-
id: "B",
|
|
265
|
-
bodyChannel: "D-TS",
|
|
266
|
-
bodyTimestamp: "2000-01-01T00:00:00Z", // decoy early ts in the body
|
|
267
|
-
});
|
|
268
|
-
|
|
269
|
-
const r = promoteDeferred("D-TS", root);
|
|
270
|
-
assert.equal(r.promoted, 1);
|
|
271
|
-
assert.equal(r.bundled, 1);
|
|
272
|
-
const files = readdirSync(join(root, "state", "inbox", "slack")).sort();
|
|
273
|
-
assert.deepEqual(files, [
|
|
274
|
-
"A.yaml.processed-bundled", // earlier item bundled
|
|
275
|
-
"B.yaml", // later top-level timestamp survives as live
|
|
276
|
-
]);
|
|
277
|
-
} finally {
|
|
278
|
-
rmSync(root, { recursive: true, force: true });
|
|
279
|
-
}
|
|
280
|
-
});
|
|
281
|
-
|
|
282
|
-
// --- service-dir discovery (the cohort/orgmail regression) -------------------
|
|
283
|
-
|
|
284
|
-
/** Write a deferred item into ANY service dir (creating it). */
|
|
285
|
-
function writeDeferredIn(root, service, name, { channel_id, timestamp, id }) {
|
|
286
|
-
mkdirSync(join(root, "state", "inbox", service), { recursive: true });
|
|
287
|
-
const body = [
|
|
288
|
-
`id: "${id}"`,
|
|
289
|
-
`service: "${service}"`,
|
|
290
|
-
`channel_id: "${channel_id}"`,
|
|
291
|
-
`timestamp: "${timestamp}"`,
|
|
292
|
-
`content: |`,
|
|
293
|
-
` body`,
|
|
294
|
-
"",
|
|
295
|
-
].join("\n");
|
|
296
|
-
writeFileSync(join(root, "state", "inbox", service, name), body);
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
test("promoteDeferred finds services the hardcoded list never knew about (cohort/orgmail)", () => {
|
|
300
|
-
const root = makeAgentRoot();
|
|
301
|
-
try {
|
|
302
|
-
writeDeferredIn(root, "cohort", "cohort-msg.yaml.deferred", {
|
|
303
|
-
channel_id: "ch_build",
|
|
304
|
-
timestamp: "2026-08-11T00:00:00Z",
|
|
305
|
-
id: "cohort-msg",
|
|
306
|
-
});
|
|
307
|
-
const r = promoteDeferred("ch_build", root);
|
|
308
|
-
assert.equal(r.promoted, 1, "a deferred Cohort message was previously un-promotable forever");
|
|
309
|
-
assert.equal(r.service, "cohort");
|
|
310
|
-
assert.deepEqual(readdirSync(join(root, "state", "inbox", "cohort")), ["cohort-msg.yaml"]);
|
|
311
|
-
} finally {
|
|
312
|
-
rmSync(root, { recursive: true, force: true });
|
|
313
|
-
}
|
|
314
|
-
});
|
|
315
|
-
|
|
316
|
-
test("promoteDeferred signals the inbox wake so the promoted item is not stuck behind the poll", async () => {
|
|
317
|
-
const { waitForInboxWake, resetInboxWake } = await import("./inbox-wake.mjs");
|
|
318
|
-
const root = makeAgentRoot();
|
|
319
|
-
resetInboxWake();
|
|
320
|
-
try {
|
|
321
|
-
writeDeferredIn(root, "cohort", "burst-2.yaml.deferred", {
|
|
322
|
-
channel_id: "ch_dm",
|
|
323
|
-
timestamp: "2026-08-11T00:00:01Z",
|
|
324
|
-
id: "burst-2",
|
|
325
|
-
});
|
|
326
|
-
// scanMs is disabled so ONLY the in-process signal can settle this.
|
|
327
|
-
const waiting = waitForInboxWake({ agentRoot: root, timeoutMs: 3_000, scanMs: 600_000 });
|
|
328
|
-
setTimeout(() => promoteDeferred("ch_dm", root), 20);
|
|
329
|
-
const woke = await waiting;
|
|
330
|
-
assert.equal(woke.woke, true);
|
|
331
|
-
assert.equal(woke.service, "cohort");
|
|
332
|
-
} finally {
|
|
333
|
-
resetInboxWake();
|
|
334
|
-
rmSync(root, { recursive: true, force: true });
|
|
335
|
-
}
|
|
336
|
-
});
|
|
@@ -1,199 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* inbox-wake.test.mjs — the daemon's "something landed" wake signal.
|
|
3
|
-
*
|
|
4
|
-
* Exercised against a REAL temp filesystem (fs.watch semantics are the thing
|
|
5
|
-
* under test), plus one injected-watch case that proves the scan floor carries
|
|
6
|
-
* the load when every watcher is dead — the property that keeps a missed event
|
|
7
|
-
* from turning into a silently late agent.
|
|
8
|
-
*
|
|
9
|
-
* Run: node --test scripts/daemon/inbox-wake.test.mjs
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
import { test } from "node:test";
|
|
13
|
-
import assert from "node:assert/strict";
|
|
14
|
-
import { mkdtempSync, mkdirSync, writeFileSync, renameSync, rmSync } from "node:fs";
|
|
15
|
-
import { tmpdir } from "node:os";
|
|
16
|
-
import { join } from "node:path";
|
|
17
|
-
|
|
18
|
-
import {
|
|
19
|
-
waitForInboxWake,
|
|
20
|
-
signalInbox,
|
|
21
|
-
findLiveInboxService,
|
|
22
|
-
isLiveItemName,
|
|
23
|
-
resetInboxWake,
|
|
24
|
-
DEFAULT_SERVICES,
|
|
25
|
-
} from "./inbox-wake.mjs";
|
|
26
|
-
|
|
27
|
-
function makeRoot() {
|
|
28
|
-
const root = mkdtempSync(join(tmpdir(), "inbox-wake-"));
|
|
29
|
-
mkdirSync(join(root, "state", "inbox", "cohort"), { recursive: true });
|
|
30
|
-
return root;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function writeItem(root, service, name, body = "id: x\n") {
|
|
34
|
-
writeFileSync(join(root, "state", "inbox", service, name), body);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// --- the predicate ----------------------------------------------------------
|
|
38
|
-
|
|
39
|
-
test("isLiveItemName: only un-drained, non-dot files count as work", () => {
|
|
40
|
-
assert.equal(isLiveItemName("2026-08-11-abc.yaml"), true);
|
|
41
|
-
assert.equal(isLiveItemName("2026-08-11-abc.yaml.processed"), false);
|
|
42
|
-
assert.equal(isLiveItemName("2026-08-11-abc.yaml.deferred"), false);
|
|
43
|
-
assert.equal(isLiveItemName("2026-08-11-abc.yaml.processed-bundled"), false);
|
|
44
|
-
assert.equal(isLiveItemName(".DS_Store"), false);
|
|
45
|
-
assert.equal(isLiveItemName(""), false);
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
test("findLiveInboxService: names the service holding work, else null", () => {
|
|
49
|
-
const root = makeRoot();
|
|
50
|
-
try {
|
|
51
|
-
assert.equal(findLiveInboxService({ agentRoot: root }), null);
|
|
52
|
-
writeItem(root, "cohort", "a.yaml.processed");
|
|
53
|
-
assert.equal(findLiveInboxService({ agentRoot: root }), null, "a drained item is not work");
|
|
54
|
-
writeItem(root, "cohort", "b.yaml");
|
|
55
|
-
assert.equal(findLiveInboxService({ agentRoot: root }), "cohort");
|
|
56
|
-
} finally {
|
|
57
|
-
rmSync(root, { recursive: true, force: true });
|
|
58
|
-
}
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
test("the cohort + orgmail dirs are in the default service set (the ones that were missed)", () => {
|
|
62
|
-
assert.ok(DEFAULT_SERVICES.includes("cohort"));
|
|
63
|
-
assert.ok(DEFAULT_SERVICES.includes("orgmail"));
|
|
64
|
-
assert.ok(DEFAULT_SERVICES.includes("telegram"));
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
// --- the wake ---------------------------------------------------------------
|
|
68
|
-
|
|
69
|
-
test("returns immediately when work is already waiting (never sleep on a full inbox)", async () => {
|
|
70
|
-
const root = makeRoot();
|
|
71
|
-
try {
|
|
72
|
-
writeItem(root, "cohort", "a.yaml");
|
|
73
|
-
const r = await waitForInboxWake({ agentRoot: root, timeoutMs: 5_000 });
|
|
74
|
-
assert.equal(r.woke, true);
|
|
75
|
-
assert.equal(r.reason, "pending");
|
|
76
|
-
assert.equal(r.service, "cohort");
|
|
77
|
-
assert.equal(r.waitedMs, 0);
|
|
78
|
-
} finally {
|
|
79
|
-
rmSync(root, { recursive: true, force: true });
|
|
80
|
-
}
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
test("wakes on a file written by another writer, far inside the 60s poll interval", async () => {
|
|
84
|
-
const root = makeRoot();
|
|
85
|
-
try {
|
|
86
|
-
const started = Date.now();
|
|
87
|
-
const waiting = waitForInboxWake({ agentRoot: root, timeoutMs: 8_000 });
|
|
88
|
-
setTimeout(() => writeItem(root, "cohort", "late.yaml"), 50);
|
|
89
|
-
const r = await waiting;
|
|
90
|
-
assert.equal(r.woke, true);
|
|
91
|
-
assert.ok(["watch", "scan"].includes(r.reason), `unexpected reason ${r.reason}`);
|
|
92
|
-
assert.equal(r.service, "cohort");
|
|
93
|
-
assert.ok(Date.now() - started < 3_000, "a wake must not take seconds");
|
|
94
|
-
} finally {
|
|
95
|
-
rmSync(root, { recursive: true, force: true });
|
|
96
|
-
}
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
test("does NOT wake when the only change is an item being drained", async () => {
|
|
100
|
-
const root = makeRoot();
|
|
101
|
-
try {
|
|
102
|
-
writeItem(root, "cohort", "a.yaml");
|
|
103
|
-
// Drain it, then wait: renaming to `.processed` raises watch events for BOTH
|
|
104
|
-
// names, so an event-driven wake would spin here.
|
|
105
|
-
renameSync(
|
|
106
|
-
join(root, "state", "inbox", "cohort", "a.yaml"),
|
|
107
|
-
join(root, "state", "inbox", "cohort", "a.yaml.processed")
|
|
108
|
-
);
|
|
109
|
-
const r = await waitForInboxWake({ agentRoot: root, timeoutMs: 400 });
|
|
110
|
-
assert.equal(r.woke, false);
|
|
111
|
-
assert.equal(r.reason, "timeout");
|
|
112
|
-
} finally {
|
|
113
|
-
rmSync(root, { recursive: true, force: true });
|
|
114
|
-
}
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
test("times out cleanly with an empty inbox (the loop shape is unchanged)", async () => {
|
|
118
|
-
const root = makeRoot();
|
|
119
|
-
try {
|
|
120
|
-
const r = await waitForInboxWake({ agentRoot: root, timeoutMs: 200 });
|
|
121
|
-
assert.deepEqual(
|
|
122
|
-
{ woke: r.woke, reason: r.reason, service: r.service },
|
|
123
|
-
{ woke: false, reason: "timeout", service: null }
|
|
124
|
-
);
|
|
125
|
-
assert.ok(r.waitedMs >= 150);
|
|
126
|
-
} finally {
|
|
127
|
-
rmSync(root, { recursive: true, force: true });
|
|
128
|
-
}
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
test("signalInbox wakes an in-process waiter (the dispatcher's promote path)", async () => {
|
|
132
|
-
const root = makeRoot();
|
|
133
|
-
resetInboxWake();
|
|
134
|
-
try {
|
|
135
|
-
const waiting = waitForInboxWake({ agentRoot: root, timeoutMs: 5_000, scanMs: 60_000 });
|
|
136
|
-
setTimeout(() => {
|
|
137
|
-
// Simulate promoteDeferred: the file becomes live, then we nudge.
|
|
138
|
-
writeItem(root, "cohort", "promoted.yaml");
|
|
139
|
-
signalInbox("promote");
|
|
140
|
-
}, 20);
|
|
141
|
-
const r = await waiting;
|
|
142
|
-
assert.equal(r.woke, true);
|
|
143
|
-
assert.equal(r.service, "cohort");
|
|
144
|
-
} finally {
|
|
145
|
-
resetInboxWake();
|
|
146
|
-
rmSync(root, { recursive: true, force: true });
|
|
147
|
-
}
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
test("signalInbox with no work does NOT wake (a spurious nudge is free)", async () => {
|
|
151
|
-
const root = makeRoot();
|
|
152
|
-
resetInboxWake();
|
|
153
|
-
try {
|
|
154
|
-
const waiting = waitForInboxWake({ agentRoot: root, timeoutMs: 400, scanMs: 60_000 });
|
|
155
|
-
setTimeout(() => signalInbox("noise"), 20);
|
|
156
|
-
const r = await waiting;
|
|
157
|
-
assert.equal(r.woke, false);
|
|
158
|
-
} finally {
|
|
159
|
-
resetInboxWake();
|
|
160
|
-
rmSync(root, { recursive: true, force: true });
|
|
161
|
-
}
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
test("with EVERY watcher dead the scan floor still wakes it — and says so once", async () => {
|
|
165
|
-
const root = makeRoot();
|
|
166
|
-
const logs = [];
|
|
167
|
-
try {
|
|
168
|
-
const watchImpl = () => {
|
|
169
|
-
throw new Error("EMFILE: watcher limit reached");
|
|
170
|
-
};
|
|
171
|
-
const waiting = waitForInboxWake({
|
|
172
|
-
agentRoot: root,
|
|
173
|
-
timeoutMs: 6_000,
|
|
174
|
-
scanMs: 100,
|
|
175
|
-
watchImpl,
|
|
176
|
-
log: (m) => logs.push(m),
|
|
177
|
-
});
|
|
178
|
-
setTimeout(() => writeItem(root, "cohort", "floor.yaml"), 30);
|
|
179
|
-
const r = await waiting;
|
|
180
|
-
assert.equal(r.woke, true);
|
|
181
|
-
assert.equal(r.reason, "scan", "correctness must not depend on fs.watch");
|
|
182
|
-
assert.equal(logs.length, 1, "a dead watcher is reported — fail-open, never silent");
|
|
183
|
-
assert.match(logs[0], /falling back to the 100ms rescan/);
|
|
184
|
-
} finally {
|
|
185
|
-
resetInboxWake();
|
|
186
|
-
rmSync(root, { recursive: true, force: true });
|
|
187
|
-
}
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
test("a missing service dir is not an error (an un-enrolled agent just times out)", async () => {
|
|
191
|
-
const root = mkdtempSync(join(tmpdir(), "inbox-wake-bare-"));
|
|
192
|
-
try {
|
|
193
|
-
const r = await waitForInboxWake({ agentRoot: root, timeoutMs: 200 });
|
|
194
|
-
assert.equal(r.woke, false);
|
|
195
|
-
assert.equal(r.reason, "timeout");
|
|
196
|
-
} finally {
|
|
197
|
-
rmSync(root, { recursive: true, force: true });
|
|
198
|
-
}
|
|
199
|
-
});
|
|
@@ -1,149 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* test-integration.mjs — End-to-end integration tests for session lock + context compiler pipeline
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { describe, it, beforeEach, afterEach } from "node:test";
|
|
7
|
-
import assert from "node:assert/strict";
|
|
8
|
-
import { mkdirSync, rmSync, writeFileSync, readFileSync, existsSync } from "fs";
|
|
9
|
-
import { join, dirname } from "path";
|
|
10
|
-
import { fileURLToPath } from "url";
|
|
11
|
-
|
|
12
|
-
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
13
|
-
const TEST_DIR = join(__dirname, "../../.test-integration");
|
|
14
|
-
|
|
15
|
-
// Set env BEFORE importing modules. context-compiler.mjs reads its repo root
|
|
16
|
-
// from __TEST_AGENT_DIR (the legacy __TEST_SOPHIE_DIR name was never honoured by
|
|
17
|
-
// the module, so the disclosure-boundary fixtures were written here but read
|
|
18
|
-
// from the real repo — the cause of the end-to-end failure). Set both: the
|
|
19
|
-
// session-lock module reads __TEST_SESSION_DIR; the compiler reads
|
|
20
|
-
// __TEST_AGENT_DIR + __TEST_SESSION_DIR.
|
|
21
|
-
process.env.__TEST_SESSION_DIR = join(TEST_DIR, "state", "sessions");
|
|
22
|
-
process.env.__TEST_AGENT_DIR = TEST_DIR;
|
|
23
|
-
process.env.__TEST_SOPHIE_DIR = TEST_DIR; // kept for any legacy reader; harmless
|
|
24
|
-
|
|
25
|
-
function setupTestEnv() {
|
|
26
|
-
mkdirSync(join(TEST_DIR, "state", "sessions", "locks"), { recursive: true });
|
|
27
|
-
mkdirSync(join(TEST_DIR, "memory", "profiles", "users"), { recursive: true });
|
|
28
|
-
mkdirSync(join(TEST_DIR, "memory", "interactions", "slack", "dm-test-user"), { recursive: true });
|
|
29
|
-
mkdirSync(join(TEST_DIR, "policies"), { recursive: true });
|
|
30
|
-
writeFileSync(join(TEST_DIR, "state", "sessions", "active.json"), "{}");
|
|
31
|
-
|
|
32
|
-
writeFileSync(join(TEST_DIR, "policies", "information-barriers.yaml"), `domains:
|
|
33
|
-
internal-legal:
|
|
34
|
-
description: "Northwind legal strategy, GC advice, privilege-protected"
|
|
35
|
-
default_sensitivity: high
|
|
36
|
-
strategic-initiative:
|
|
37
|
-
description: "the strategic initiative M&A targets, deal terms, pipeline"
|
|
38
|
-
default_sensitivity: critical
|
|
39
|
-
public:
|
|
40
|
-
description: "Published information, press, website"
|
|
41
|
-
default_sensitivity: none
|
|
42
|
-
`);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function cleanTestEnv() {
|
|
46
|
-
rmSync(TEST_DIR, { recursive: true, force: true });
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
const { acquireLock, releaseLock, registerSent, checkRecentlySent } = await import("./session-lock.mjs");
|
|
50
|
-
const { compileContext } = await import("./context-compiler.mjs");
|
|
51
|
-
|
|
52
|
-
// ─── Integration tests ─────────────────────────────────────────────────
|
|
53
|
-
|
|
54
|
-
describe("Integration: lock → compile → prompt pipeline", () => {
|
|
55
|
-
beforeEach(setupTestEnv);
|
|
56
|
-
afterEach(cleanTestEnv);
|
|
57
|
-
|
|
58
|
-
it("acquireLock prevents duplicate processing of same item", () => {
|
|
59
|
-
const first = acquireLock("slack-C001-12345", { sender: "Test", channel: "C001" });
|
|
60
|
-
assert.equal(first.acquired, true);
|
|
61
|
-
|
|
62
|
-
const second = acquireLock("slack-C001-12345", { sender: "Test", channel: "C001" });
|
|
63
|
-
assert.equal(second.acquired, false);
|
|
64
|
-
|
|
65
|
-
releaseLock("slack-C001-12345");
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
it("sent-registry prevents duplicate sends across quick-reply and holding paths", () => {
|
|
69
|
-
// Holding message sent
|
|
70
|
-
registerSent("C001", "12345.6789", "holding", "s-1", "Looking into this now");
|
|
71
|
-
|
|
72
|
-
// A quick reply to the SAME channel+thread is blocked by the thread-cooldown
|
|
73
|
-
// rule (Rule 2): once anything has been sent to a thread, the whole thread
|
|
74
|
-
// cools down so the quick-reply path and the full-session path can't both
|
|
75
|
-
// fire into the same thread. (The original assertion expected the different
|
|
76
|
-
// type to be allowed — that predated Rule 2 and was stale; Rule 2 is the
|
|
77
|
-
// whole point of this test, "prevents duplicate sends across paths".)
|
|
78
|
-
const quickCheck = checkRecentlySent("C001", "12345.6789", "quick_reply");
|
|
79
|
-
assert.equal(quickCheck.allowed, false);
|
|
80
|
-
assert.match(quickCheck.reason, /thread cooldown/);
|
|
81
|
-
|
|
82
|
-
// Another holding to same channel+thread is likewise blocked.
|
|
83
|
-
const holdCheck = checkRecentlySent("C001", "12345.6789", "holding");
|
|
84
|
-
assert.equal(holdCheck.allowed, false);
|
|
85
|
-
|
|
86
|
-
// But a different, untouched thread is still free to receive a quick reply —
|
|
87
|
-
// the cooldown is scoped to the thread, not the whole channel (non-DM).
|
|
88
|
-
const otherThread = checkRecentlySent("C001", "99999.0000", "quick_reply");
|
|
89
|
-
assert.equal(otherThread.allowed, true);
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
it("context compiler respects disclosure boundaries end-to-end", async () => {
|
|
93
|
-
// Create a profile with restrictions
|
|
94
|
-
writeFileSync(
|
|
95
|
-
join(TEST_DIR, "memory", "profiles", "users", "test-user.yaml"),
|
|
96
|
-
`name: Test User\ninformation_boundaries:\n excluded_domains:\n - strategic-initiative\n`
|
|
97
|
-
);
|
|
98
|
-
|
|
99
|
-
// Create history with mixed content
|
|
100
|
-
const today = new Date().toISOString().split("T")[0];
|
|
101
|
-
writeFileSync(
|
|
102
|
-
join(TEST_DIR, "memory", "interactions", "slack", "dm-test-user", `${today}.jsonl`),
|
|
103
|
-
[
|
|
104
|
-
JSON.stringify({ ts: "100", from: "test-user", content: "What is the status?", received_at: "2026-04-06T10:00:00Z" }),
|
|
105
|
-
JSON.stringify({ ts: "101", from: "robin", content: "the strategic initiative deal terms are looking good", received_at: "2026-04-06T10:01:00Z" }),
|
|
106
|
-
JSON.stringify({ ts: "102", from: "robin", content: "The public website update is live", received_at: "2026-04-06T10:02:00Z" }),
|
|
107
|
-
].join("\n") + "\n"
|
|
108
|
-
);
|
|
109
|
-
|
|
110
|
-
const { contextBlock } = await compileContext(
|
|
111
|
-
{ sender: "Test User", channel: "D001", channel_id: "D001" },
|
|
112
|
-
{ priority: "normal", model: "sonnet", action: "respond" },
|
|
113
|
-
{}
|
|
114
|
-
);
|
|
115
|
-
|
|
116
|
-
// Public content should be included
|
|
117
|
-
assert.ok(contextBlock.includes("website update is live"));
|
|
118
|
-
// Strategic-initiative content should be filtered. (The old assertion
|
|
119
|
-
// checked for "Aether deal terms" — absent from the fixture — so it was a
|
|
120
|
-
// tautology; assert against the actual excluded fixture text.)
|
|
121
|
-
assert.ok(!contextBlock.includes("strategic initiative deal terms"));
|
|
122
|
-
// Should have disclosure boundaries
|
|
123
|
-
assert.ok(contextBlock.includes("DO NOT mention"));
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
it("active sessions are visible in compiled context", async () => {
|
|
127
|
-
writeFileSync(
|
|
128
|
-
join(TEST_DIR, "state", "sessions", "active.json"),
|
|
129
|
-
JSON.stringify({
|
|
130
|
-
"s-999": {
|
|
131
|
-
sender: "Alice",
|
|
132
|
-
channel: "C002",
|
|
133
|
-
summary: "Preparing board pack",
|
|
134
|
-
model: "opus",
|
|
135
|
-
startTime: Date.now() - 120000,
|
|
136
|
-
},
|
|
137
|
-
})
|
|
138
|
-
);
|
|
139
|
-
|
|
140
|
-
const { contextBlock } = await compileContext(
|
|
141
|
-
{ sender: "Test", channel: "C001" },
|
|
142
|
-
{ priority: "normal", model: "sonnet", action: "respond" },
|
|
143
|
-
{}
|
|
144
|
-
);
|
|
145
|
-
|
|
146
|
-
assert.ok(contextBlock.includes("Preparing board pack"));
|
|
147
|
-
assert.ok(contextBlock.includes("Active Sessions"));
|
|
148
|
-
});
|
|
149
|
-
});
|