@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,553 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* disposition.test.mjs — the decision ladder, exercised on EVERY surface with
|
|
3
|
-
* both the positive case and the negative one.
|
|
4
|
-
*
|
|
5
|
-
* The negatives matter as much as the positives here: an agent that replies to
|
|
6
|
-
* everything is exactly as broken as one that replies to nothing.
|
|
7
|
-
*
|
|
8
|
-
* Run: node --test lib/execution/disposition.test.mjs
|
|
9
|
-
*/
|
|
10
|
-
"use strict";
|
|
11
|
-
|
|
12
|
-
import { test } from "node:test";
|
|
13
|
-
import assert from "node:assert/strict";
|
|
14
|
-
|
|
15
|
-
import { SURFACE_NAMES } from "../org/inbound/surfaces.mjs";
|
|
16
|
-
import { SURFACE_POLICY } from "./surface-policy.mjs";
|
|
17
|
-
import { decide, willAct, tierOf, DEFAULT_ACTOR_FLOOD_LIMIT } from "./disposition.mjs";
|
|
18
|
-
|
|
19
|
-
const ME = "mem_me";
|
|
20
|
-
const THEM = "mem_them";
|
|
21
|
-
const NOW = Date.parse("2026-08-11T12:00:00Z");
|
|
22
|
-
|
|
23
|
-
/** A permissive history: nothing seen, no chains, no floods, no failures. */
|
|
24
|
-
function freshHistory(over = {}) {
|
|
25
|
-
return {
|
|
26
|
-
seen: () => false,
|
|
27
|
-
chainDepth: () => 0,
|
|
28
|
-
actorActivity: () => 0,
|
|
29
|
-
failures: () => 0,
|
|
30
|
-
degraded: false,
|
|
31
|
-
degradedReason: null,
|
|
32
|
-
...over,
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/** Build a candidate for a surface. */
|
|
37
|
-
function cand(over = {}) {
|
|
38
|
-
return {
|
|
39
|
-
seq: 100,
|
|
40
|
-
family: "messaging",
|
|
41
|
-
kind: "send",
|
|
42
|
-
entityId: "msg_1",
|
|
43
|
-
actor: THEM,
|
|
44
|
-
at: "2026-08-11T11:59:00Z",
|
|
45
|
-
topic: "message",
|
|
46
|
-
ids: { channelId: "chan_1", messageId: "msg_1" },
|
|
47
|
-
directTo: [],
|
|
48
|
-
payload: {},
|
|
49
|
-
...over,
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function yes(surface, reason) { return { directed: true, surface, reason, why: reason }; }
|
|
54
|
-
function no(reason) { return { directed: false, surface: null, reason, why: reason }; }
|
|
55
|
-
|
|
56
|
-
/** decide() with sane defaults so each test states only what it is about. */
|
|
57
|
-
function run(over = {}) {
|
|
58
|
-
return decide({
|
|
59
|
-
candidate: cand(over.candidate),
|
|
60
|
-
verdict: over.verdict || yes("dm", "dm"),
|
|
61
|
-
me: ME,
|
|
62
|
-
history: over.history === null ? undefined : (over.history || freshHistory()),
|
|
63
|
-
runtime: over.runtime || {},
|
|
64
|
-
obligation: over.obligation === undefined ? { key: "react.dm" } : over.obligation,
|
|
65
|
-
need: over.need,
|
|
66
|
-
manifest: over.manifest,
|
|
67
|
-
facts: over.facts || {},
|
|
68
|
-
nowMs: NOW,
|
|
69
|
-
actorFloodLimit: over.actorFloodLimit,
|
|
70
|
-
});
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// ───────────────────────────────────────────────────────────────────────────
|
|
74
|
-
// Per-surface coverage: every surface answers, and every surface can say no.
|
|
75
|
-
// ───────────────────────────────────────────────────────────────────────────
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* The directed verdict each surface is reached by, and the candidate shape.
|
|
79
|
-
* `candidate.surfaces` mirrors what `classifyEvent` really emits (most specific
|
|
80
|
-
* first) — the undirected path reads it to decide park-vs-drop, so a test
|
|
81
|
-
* candidate without it would be testing a shape that never occurs.
|
|
82
|
-
*/
|
|
83
|
-
const CHAT_SURFACES = ["dm", "mention", "thread_reply"];
|
|
84
|
-
const SURFACE_CASES = {
|
|
85
|
-
dm: { verdict: yes("dm", "dm"), candidate: { family: "messaging", kind: "send", surfaces: CHAT_SURFACES } },
|
|
86
|
-
mention: { verdict: yes("mention", "mention"), candidate: { family: "messaging", kind: "send", surfaces: CHAT_SURFACES } },
|
|
87
|
-
thread_reply: { verdict: yes("thread_reply", "thread"), candidate: { family: "messaging", kind: "send", surfaces: CHAT_SURFACES } },
|
|
88
|
-
call: { verdict: yes("call", "participant"), candidate: { family: "calling", kind: "invite", topic: "call", surfaces: ["call"], ids: { callId: "call_1" } } },
|
|
89
|
-
task_assigned:{ verdict: yes("task_assigned", "assignee"), candidate: { family: "board", kind: "item.assigned", topic: "task", surfaces: ["task_assigned"], ids: { taskId: "t1" } } },
|
|
90
|
-
task_comment: { verdict: yes("task_comment", "assignee"), candidate: { family: "board", kind: "item.commented", topic: "task", surfaces: ["task_comment"], ids: { taskId: "t1" } } },
|
|
91
|
-
file_comment: { verdict: yes("file_comment", "channel"), candidate: { family: "file", kind: "comment", topic: "file", surfaces: ["file_comment"], ids: { fileId: "f1", channelId: "c1" } } },
|
|
92
|
-
doc_comment: { verdict: yes("doc_comment", "owner"), candidate: { family: "files", kind: "comment", topic: "file", surfaces: ["doc_comment"], ids: { fileId: "d1" } } },
|
|
93
|
-
approval: { verdict: yes("approval", "approver"), candidate: { family: "approval", kind: "requested", topic: "approval", surfaces: ["approval"], ids: { approvalId: "a1" } } },
|
|
94
|
-
decision: { verdict: yes("decision", "direct"), candidate: { family: "decision", kind: "proposed", topic: "decision", surfaces: ["decision"], ids: { decisionId: "d1" } } },
|
|
95
|
-
escalation: { verdict: yes("escalation", "waiting_on"), candidate: { family: "escalation", kind: "raised", topic: "escalation", surfaces: ["escalation"], ids: { escalationId: "e1" } } },
|
|
96
|
-
handoff: { verdict: yes("handoff", "direct"), candidate: { family: "handoff", kind: "offered", topic: "handoff", surfaces: ["handoff"], ids: { handoffId: "h1" } } },
|
|
97
|
-
email: { verdict: yes("email", "direct"), candidate: { family: "email", kind: "received", topic: "email", surfaces: ["email"], ids: { messageId: "e1" } } },
|
|
98
|
-
// JOINT 3. `attendee` is the reason `resolveCalendar` emits when the ACL'd
|
|
99
|
-
// `calendar.list` read (owner OR attendeeRows) proved the event is this
|
|
100
|
-
// seat's — the payload can only ever prove `calendar_owner`.
|
|
101
|
-
calendar: { verdict: yes("calendar", "attendee"), candidate: { family: "calendar", kind: "event.created", topic: "calendar", surfaces: ["calendar"], ids: { eventId: "ev1" } } },
|
|
102
|
-
};
|
|
103
|
-
|
|
104
|
-
test("every inbound surface has a decision case in this test file", () => {
|
|
105
|
-
assert.deepEqual(Object.keys(SURFACE_CASES).sort(), [...SURFACE_NAMES].sort());
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
test("POSITIVE: every surface, when directed, produces an ACTING decision with reasoning", () => {
|
|
109
|
-
for (const [surface, c] of Object.entries(SURFACE_CASES)) {
|
|
110
|
-
const d = run(c);
|
|
111
|
-
assert.ok(willAct(d), `${surface} → ${d.disposition} (${d.reason}) — a directed event must not be dropped`);
|
|
112
|
-
assert.ok(d.why.length >= 2, `${surface} produced no reasoning`);
|
|
113
|
-
assert.ok(Number.isInteger(d.rung), `${surface} produced no rung`);
|
|
114
|
-
assert.equal(d.surface, surface);
|
|
115
|
-
}
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
test("NEGATIVE: every surface, when NOT directed, is ignored with the verdict's reason", () => {
|
|
119
|
-
for (const [surface, c] of Object.entries(SURFACE_CASES)) {
|
|
120
|
-
const d = run({ ...c, verdict: no("not_a_member") });
|
|
121
|
-
assert.equal(d.disposition, "ignore", `${surface} replied to something that was not addressed to it`);
|
|
122
|
-
assert.equal(d.reason, "not_a_member");
|
|
123
|
-
assert.ok(d.why.length >= 1, "even an ignore explains itself");
|
|
124
|
-
// Park-vs-drop is decided over the surfaces the event COULD have been —
|
|
125
|
-
// an undirected verdict names no surface, so the candidate's list is the
|
|
126
|
-
// only honest input.
|
|
127
|
-
const expectPark = c.candidate.surfaces.some((s) => SURFACE_POLICY[s].ambientDrop === false);
|
|
128
|
-
assert.equal(d.ambient, expectPark, `${surface} park-vs-drop`);
|
|
129
|
-
}
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
test("conversational surfaces answer NOW; work surfaces queue", () => {
|
|
133
|
-
assert.equal(run(SURFACE_CASES.dm).disposition, "react_now");
|
|
134
|
-
assert.equal(run(SURFACE_CASES.mention).disposition, "react_now");
|
|
135
|
-
assert.equal(run(SURFACE_CASES.thread_reply).disposition, "react_now");
|
|
136
|
-
assert.equal(run(SURFACE_CASES.escalation).disposition, "react_now");
|
|
137
|
-
assert.equal(run(SURFACE_CASES.handoff).disposition, "react_now");
|
|
138
|
-
|
|
139
|
-
// A board comment is answerable but nobody is watching for it → next tick.
|
|
140
|
-
assert.equal(run(SURFACE_CASES.task_comment).disposition, "schedule");
|
|
141
|
-
assert.equal(run(SURFACE_CASES.task_comment).reason, "batch_surface");
|
|
142
|
-
// A task assignment is work, not conversation.
|
|
143
|
-
assert.equal(run(SURFACE_CASES.task_assigned).disposition, "schedule");
|
|
144
|
-
assert.equal(run(SURFACE_CASES.task_assigned).reason, "not_respondable");
|
|
145
|
-
// An email is answerable, but on the batch tick and behind an approval.
|
|
146
|
-
assert.equal(run(SURFACE_CASES.email).disposition, "schedule");
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
test("a call invite is scheduled, never conversationally answered — hq holds the floor", () => {
|
|
150
|
-
const d = run(SURFACE_CASES.call);
|
|
151
|
-
assert.equal(d.disposition, "schedule");
|
|
152
|
-
assert.equal(d.reason, "not_respondable");
|
|
153
|
-
assert.equal(d.action.method, null);
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
test("approvals and decisions ESCALATE — the agent never decides its own", () => {
|
|
157
|
-
for (const s of ["approval", "decision"]) {
|
|
158
|
-
const d = run(SURFACE_CASES[s]);
|
|
159
|
-
assert.equal(d.disposition, "escalate", `${s} was self-decided`);
|
|
160
|
-
assert.equal(d.reason, "cannot_self_decide");
|
|
161
|
-
assert.ok(d.why.some((w) => /never be resolved by the agent itself/.test(w)));
|
|
162
|
-
}
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
test("replying by email carries an `external` approval gate; a DM does not", () => {
|
|
166
|
-
const email = run(SURFACE_CASES.email);
|
|
167
|
-
assert.deepEqual(email.gates.approval, { required: true, classes: ["external"] });
|
|
168
|
-
const dm = run(SURFACE_CASES.dm);
|
|
169
|
-
assert.equal(dm.gates.approval, undefined);
|
|
170
|
-
});
|
|
171
|
-
|
|
172
|
-
test("a reply into a SHARED room takes the thread lease; a DM does not", () => {
|
|
173
|
-
assert.ok(run(SURFACE_CASES.mention).gates.arbitration, "two agents must not answer one @mention");
|
|
174
|
-
assert.ok(run(SURFACE_CASES.thread_reply).gates.arbitration);
|
|
175
|
-
assert.equal(run(SURFACE_CASES.dm).gates.arbitration, undefined, "nobody else is in a DM");
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
// ───────────────────────────────────────────────────────────────────────────
|
|
179
|
-
// The ignore cases — the half of this system that is normally missing.
|
|
180
|
-
// ───────────────────────────────────────────────────────────────────────────
|
|
181
|
-
|
|
182
|
-
test("IGNORE: the emergency stop beats everything", () => {
|
|
183
|
-
const d = run({ runtime: { emergencyStop: true } });
|
|
184
|
-
assert.equal(d.disposition, "ignore");
|
|
185
|
-
assert.equal(d.reason, "emergency_stop");
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
test("IGNORE: a `halt` directive stops the seat; `pause_schedules` does NOT stop answering", () => {
|
|
189
|
-
assert.equal(run({ runtime: { directive: "halt" } }).reason, "halt");
|
|
190
|
-
|
|
191
|
-
// pause_schedules: keeps answering...
|
|
192
|
-
const answering = run({ ...SURFACE_CASES.dm, runtime: { directive: "pause_schedules" } });
|
|
193
|
-
assert.equal(answering.disposition, "react_now", "a paused seat still answers");
|
|
194
|
-
// ...but stops queueing new work.
|
|
195
|
-
const queueing = run({ ...SURFACE_CASES.task_assigned, runtime: { directive: "pause_schedules" } });
|
|
196
|
-
assert.equal(queueing.disposition, "ignore");
|
|
197
|
-
assert.equal(queueing.reason, "paused");
|
|
198
|
-
});
|
|
199
|
-
|
|
200
|
-
test("IGNORE: my own echo, even if the directedness layer let it through", () => {
|
|
201
|
-
const d = run({ candidate: { actor: ME } });
|
|
202
|
-
assert.equal(d.disposition, "ignore");
|
|
203
|
-
assert.equal(d.reason, "own_echo");
|
|
204
|
-
});
|
|
205
|
-
|
|
206
|
-
test("IGNORE: a redelivery. The SSE push and the sweep both deliver the same event", () => {
|
|
207
|
-
const d = run({ history: freshHistory({ seen: () => true }) });
|
|
208
|
-
assert.equal(d.disposition, "ignore");
|
|
209
|
-
assert.equal(d.reason, "duplicate");
|
|
210
|
-
});
|
|
211
|
-
|
|
212
|
-
test("IGNORE: the ping-pong ceiling stops an agent talking to itself", () => {
|
|
213
|
-
// a space allows 2 agent turns
|
|
214
|
-
const at2 = run({ ...SURFACE_CASES.mention, history: freshHistory({ chainDepth: () => 2 }) });
|
|
215
|
-
assert.equal(at2.disposition, "ignore");
|
|
216
|
-
assert.equal(at2.reason, "reply_chain_depth");
|
|
217
|
-
assert.equal(at2.chainDepth, 2);
|
|
218
|
-
|
|
219
|
-
// one below the ceiling still answers, and says how deep it is
|
|
220
|
-
const at1 = run({ ...SURFACE_CASES.mention, history: freshHistory({ chainDepth: () => 1 }) });
|
|
221
|
-
assert.equal(at1.disposition, "react_now");
|
|
222
|
-
assert.ok(at1.why.some((w) => /1 prior agent turn/.test(w)));
|
|
223
|
-
|
|
224
|
-
// a DM tolerates a longer exchange than a public space
|
|
225
|
-
const dmAt2 = run({ ...SURFACE_CASES.dm, history: freshHistory({ chainDepth: () => 2 }) });
|
|
226
|
-
assert.equal(dmAt2.disposition, "react_now");
|
|
227
|
-
});
|
|
228
|
-
|
|
229
|
-
test("IGNORE: an undirected event on a drop-only surface is dropped, not parked", () => {
|
|
230
|
-
// A board assignment that is not mine, an approval that is not on my desk, a
|
|
231
|
-
// call in a room I am not in — none of these are worth a second pass.
|
|
232
|
-
for (const s of ["task_assigned", "approval", "call", "decision"]) {
|
|
233
|
-
const d = run({ ...SURFACE_CASES[s], verdict: no("not_a_member") });
|
|
234
|
-
assert.equal(d.ambient, false, `${s} should drop, not park`);
|
|
235
|
-
}
|
|
236
|
-
});
|
|
237
|
-
|
|
238
|
-
test("PARK: chat traffic that matched no rule is kept for the ambient sweep", () => {
|
|
239
|
-
const d = run({ ...SURFACE_CASES.mention, verdict: no("ambient_channel_traffic") });
|
|
240
|
-
assert.equal(d.disposition, "ignore");
|
|
241
|
-
assert.equal(d.ambient, true, "it could have been a mention whose hydration was incomplete");
|
|
242
|
-
assert.deepEqual(d.plausibleSurfaces, CHAT_SURFACES);
|
|
243
|
-
});
|
|
244
|
-
|
|
245
|
-
test("IGNORE: a malformed event never crashes the ladder", () => {
|
|
246
|
-
assert.equal(decide({}).reason, "not_classified");
|
|
247
|
-
assert.equal(decide({ candidate: null, verdict: null }).reason, "not_classified");
|
|
248
|
-
// A candidate with no verdict fails CLOSED on speaking, without dropping silently.
|
|
249
|
-
const noVerdict = decide({ candidate: cand(), me: ME, nowMs: NOW });
|
|
250
|
-
assert.equal(noVerdict.disposition, "ignore");
|
|
251
|
-
assert.equal(noVerdict.reason, "no_verdict");
|
|
252
|
-
});
|
|
253
|
-
|
|
254
|
-
// ───────────────────────────────────────────────────────────────────────────
|
|
255
|
-
// Downgrades — nothing is ever silently dropped for load or money.
|
|
256
|
-
// ───────────────────────────────────────────────────────────────────────────
|
|
257
|
-
|
|
258
|
-
test("DOWNGRADE not silence: a flooding actor gets batched, not muted", () => {
|
|
259
|
-
// Assert on a NON-DM surface (an @mention): a directed 1:1 DM is now EXEMPT
|
|
260
|
-
// from actor-flood (see the next test), so it can no longer stand in for the
|
|
261
|
-
// general batching behaviour.
|
|
262
|
-
const floodMention = (over) => run({
|
|
263
|
-
verdict: yes("mention", "mention"),
|
|
264
|
-
candidate: { family: "messaging", kind: "send", surfaces: ["mention"] },
|
|
265
|
-
...over,
|
|
266
|
-
});
|
|
267
|
-
const d = floodMention({ history: freshHistory({ actorActivity: () => DEFAULT_ACTOR_FLOOD_LIMIT }) });
|
|
268
|
-
assert.equal(d.disposition, "schedule");
|
|
269
|
-
assert.equal(d.reason, "actor_flood");
|
|
270
|
-
assert.equal(d.batched, true);
|
|
271
|
-
// one under the limit still answers turn-by-turn
|
|
272
|
-
const under = floodMention({ history: freshHistory({ actorActivity: () => DEFAULT_ACTOR_FLOOD_LIMIT - 1 }) });
|
|
273
|
-
assert.equal(under.disposition, "react_now");
|
|
274
|
-
});
|
|
275
|
-
|
|
276
|
-
test("a directed 1:1 DM is EXEMPT from actor-flood batching", () => {
|
|
277
|
-
// The flood counter tallies ALL of an actor's activity (their doc/board edits
|
|
278
|
-
// included), so a genuine 1:1 message must not be batched just because the same
|
|
279
|
-
// person tripped the limit elsewhere. Even far over the limit, a DM reacts now.
|
|
280
|
-
const d = run({ history: freshHistory({ actorActivity: () => DEFAULT_ACTOR_FLOOD_LIMIT * 5 }) });
|
|
281
|
-
assert.equal(d.disposition, "react_now");
|
|
282
|
-
assert.equal(d.reason, "directed_now");
|
|
283
|
-
});
|
|
284
|
-
|
|
285
|
-
test("DOWNGRADE not silence: an exhausted budget queues the reply", () => {
|
|
286
|
-
for (const runtime of [{ dailyCapExhausted: true }, { remainingCents: 0 }]) {
|
|
287
|
-
const d = run({ runtime });
|
|
288
|
-
assert.equal(d.disposition, "schedule");
|
|
289
|
-
assert.equal(d.reason, "budget_exhausted");
|
|
290
|
-
}
|
|
291
|
-
});
|
|
292
|
-
|
|
293
|
-
test("a lane-tier event outside working hours waits; a directed one does not", () => {
|
|
294
|
-
const lane = run({ verdict: yes("thread_reply", "thread"), runtime: { withinWorkingHours: false } });
|
|
295
|
-
assert.equal(lane.disposition, "schedule");
|
|
296
|
-
assert.equal(lane.reason, "outside_hours");
|
|
297
|
-
|
|
298
|
-
const direct = run({ verdict: yes("dm", "dm"), runtime: { withinWorkingHours: false } });
|
|
299
|
-
assert.equal(direct.disposition, "react_now", "someone addressed me directly — hours do not apply");
|
|
300
|
-
});
|
|
301
|
-
|
|
302
|
-
// ───────────────────────────────────────────────────────────────────────────
|
|
303
|
-
// Delegate / escalate
|
|
304
|
-
// ───────────────────────────────────────────────────────────────────────────
|
|
305
|
-
|
|
306
|
-
test("DELEGATE: a lane-tier event whose owner is someone else is routed to them", () => {
|
|
307
|
-
const d = run({ verdict: yes("thread_reply", "thread"), facts: { ownerId: THEM } });
|
|
308
|
-
assert.equal(d.disposition, "delegate");
|
|
309
|
-
assert.equal(d.delegateTo, THEM);
|
|
310
|
-
});
|
|
311
|
-
|
|
312
|
-
test("NEGATIVE: an event I own, or one aimed directly at me, is NOT delegated away", () => {
|
|
313
|
-
assert.notEqual(run({ verdict: yes("thread_reply", "thread"), facts: { ownerId: ME } }).disposition, "delegate");
|
|
314
|
-
// T0 beats ownership: if someone named me, I answer, even on someone else's item.
|
|
315
|
-
assert.notEqual(run({ verdict: yes("dm", "dm"), facts: { ownerId: THEM } }).disposition, "delegate");
|
|
316
|
-
});
|
|
317
|
-
|
|
318
|
-
test("DELEGATE: an unreachable capability goes to a peer who has it", () => {
|
|
319
|
-
const d = run({ facts: { capabilityReachable: false, capableMemberId: THEM } });
|
|
320
|
-
assert.equal(d.disposition, "delegate");
|
|
321
|
-
assert.equal(d.delegateTo, THEM);
|
|
322
|
-
assert.equal(d.drift.kind, "unreachable_capability");
|
|
323
|
-
});
|
|
324
|
-
|
|
325
|
-
test("ESCALATE: a capability wall with no peer asks the owner — it never fails silently", () => {
|
|
326
|
-
const d = run({ facts: { capabilityReachable: false } });
|
|
327
|
-
assert.equal(d.disposition, "escalate");
|
|
328
|
-
assert.equal(d.reason, "unreachable_capability");
|
|
329
|
-
assert.deepEqual(d.options, ["grant the capability", "retarget the objective", "retire the obligation"]);
|
|
330
|
-
assert.equal(d.drift.kind, "unreachable_capability");
|
|
331
|
-
});
|
|
332
|
-
|
|
333
|
-
test("ESCALATE: genuine ambiguity asks with concrete options, it does not guess", () => {
|
|
334
|
-
const d = run({ facts: { ambiguousOptions: ["ship it", "hold for legal"] } });
|
|
335
|
-
assert.equal(d.disposition, "escalate");
|
|
336
|
-
assert.equal(d.reason, "ambiguous");
|
|
337
|
-
assert.equal(d.options.length, 2);
|
|
338
|
-
// ONE option is not ambiguity — that is just the answer.
|
|
339
|
-
assert.notEqual(run({ facts: { ambiguousOptions: ["ship it"] } }).disposition, "escalate");
|
|
340
|
-
});
|
|
341
|
-
|
|
342
|
-
// ───────────────────────────────────────────────────────────────────────────
|
|
343
|
-
// Staleness ladder (§6.4)
|
|
344
|
-
// ───────────────────────────────────────────────────────────────────────────
|
|
345
|
-
|
|
346
|
-
test("staleness: <24h is business as usual", () => {
|
|
347
|
-
assert.equal(run({ runtime: { mandateStaleSeconds: 3600 } }).disposition, "react_now");
|
|
348
|
-
});
|
|
349
|
-
|
|
350
|
-
test("staleness: >72h defers anything not offline-safe, and keeps what is", () => {
|
|
351
|
-
const stale = { mandateStaleSeconds: 80 * 3600 };
|
|
352
|
-
// email is external → deferred
|
|
353
|
-
const email = run({ ...SURFACE_CASES.email, runtime: stale });
|
|
354
|
-
assert.equal(email.disposition, "schedule");
|
|
355
|
-
assert.equal(email.reason, "mandate_stale_deferred");
|
|
356
|
-
assert.equal(email.drift.kind, "mandate_stale");
|
|
357
|
-
// an internal DM is offline-safe → still answered
|
|
358
|
-
const dm = run({ ...SURFACE_CASES.dm, runtime: stale });
|
|
359
|
-
assert.equal(dm.disposition, "react_now");
|
|
360
|
-
assert.ok(dm.why.some((w) => /offline-safe/.test(w)));
|
|
361
|
-
});
|
|
362
|
-
|
|
363
|
-
test("staleness: >7d idles and reports drift once — it does not go dark silently", () => {
|
|
364
|
-
const d = run({ runtime: { mandateStaleSeconds: 8 * 86400 } });
|
|
365
|
-
assert.equal(d.disposition, "ignore");
|
|
366
|
-
assert.equal(d.reason, "mandate_stale");
|
|
367
|
-
assert.equal(d.drift.kind, "mandate_stale");
|
|
368
|
-
});
|
|
369
|
-
|
|
370
|
-
test("staleness: the RECOVERY surfaces are exempt, or a partitioned agent could never recover", () => {
|
|
371
|
-
// The deadlock this guards: §6.4 refreshes the cache "on every topic:mandate
|
|
372
|
-
// frame" AND idles the daemon past 7d. Applied uniformly, the one event that
|
|
373
|
-
// clears the staleness is the one staleness drops, and the agent never comes
|
|
374
|
-
// back without a human.
|
|
375
|
-
for (const surface of ["mandate", "alert"]) {
|
|
376
|
-
const c = { family: surface, kind: "changed", topic: surface, ids: {}, entityId: "x" };
|
|
377
|
-
for (const stale of [30 * 3600, 80 * 3600, 8 * 86400, 400 * 86400]) {
|
|
378
|
-
const d = run({
|
|
379
|
-
candidate: c,
|
|
380
|
-
verdict: yes(surface, "direct"),
|
|
381
|
-
runtime: { mandateStaleSeconds: stale },
|
|
382
|
-
obligation: null,
|
|
383
|
-
});
|
|
384
|
-
assert.notEqual(d.disposition, "ignore", `${surface} was dropped at ${stale}s stale`);
|
|
385
|
-
assert.notEqual(d.reason, "mandate_stale", `${surface} was gated at ${stale}s stale`);
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
});
|
|
389
|
-
|
|
390
|
-
test("the staleness exemption is announced, never applied quietly", () => {
|
|
391
|
-
const d = run({
|
|
392
|
-
candidate: { family: "mandate", kind: "changed", topic: "mandate", ids: {}, entityId: "x" },
|
|
393
|
-
verdict: yes("mandate", "direct"),
|
|
394
|
-
runtime: { mandateStaleSeconds: 9 * 86400 },
|
|
395
|
-
obligation: null,
|
|
396
|
-
});
|
|
397
|
-
assert.ok(
|
|
398
|
-
d.why.some((w) => /recovery surface/.test(w)),
|
|
399
|
-
"the trace must say the ladder was skipped and why",
|
|
400
|
-
);
|
|
401
|
-
});
|
|
402
|
-
|
|
403
|
-
test("the exemption does NOT leak to a surface that can speak outside the org", () => {
|
|
404
|
-
const d = run({ ...SURFACE_CASES.email, runtime: { mandateStaleSeconds: 9 * 86400 } });
|
|
405
|
-
assert.equal(d.disposition, "ignore");
|
|
406
|
-
assert.equal(d.reason, "mandate_stale");
|
|
407
|
-
});
|
|
408
|
-
|
|
409
|
-
// ───────────────────────────────────────────────────────────────────────────
|
|
410
|
-
// Plan coverage + observe-only
|
|
411
|
-
// ───────────────────────────────────────────────────────────────────────────
|
|
412
|
-
|
|
413
|
-
test("a directed event with NO matching obligation is handled once, cheaply, and flagged", () => {
|
|
414
|
-
const d = run({ obligation: null });
|
|
415
|
-
assert.ok(willAct(d));
|
|
416
|
-
assert.ok(d.rung <= 1, `unplanned work must be bounded, got rung ${d.rung}`);
|
|
417
|
-
assert.equal(d.drift.kind, "uncovered_event");
|
|
418
|
-
assert.ok(d.why.some((w) => /no obligation in the compiled plan/.test(w)));
|
|
419
|
-
});
|
|
420
|
-
|
|
421
|
-
test("a matched obligation lifts the rung cap and carries its key through", () => {
|
|
422
|
-
const d = run({
|
|
423
|
-
obligation: { key: "react.dm", objective_id: "obj_1" },
|
|
424
|
-
need: { filesystem: true },
|
|
425
|
-
});
|
|
426
|
-
assert.equal(d.obligationKey, "react.dm");
|
|
427
|
-
assert.equal(d.objectiveId, "obj_1");
|
|
428
|
-
assert.equal(d.drift, null);
|
|
429
|
-
assert.equal(d.rung, 3, "a planned obligation is not capped at rung 1");
|
|
430
|
-
});
|
|
431
|
-
|
|
432
|
-
test("observe-only keeps the DECISION and downgrades only the EFFECT", () => {
|
|
433
|
-
const d = run({ runtime: { enforcement: "observe_only" } });
|
|
434
|
-
assert.equal(d.observeOnly, true);
|
|
435
|
-
assert.equal(d.disposition, "react_now", "the decision is still recorded honestly");
|
|
436
|
-
assert.ok(d.why.some((w) => /observe_only/.test(w)));
|
|
437
|
-
});
|
|
438
|
-
|
|
439
|
-
// ───────────────────────────────────────────────────────────────────────────
|
|
440
|
-
// Degradation is loud
|
|
441
|
-
// ───────────────────────────────────────────────────────────────────────────
|
|
442
|
-
|
|
443
|
-
test("a missing journal history DISABLES the guards and says so", () => {
|
|
444
|
-
const d = decide({ candidate: cand(), verdict: yes("dm", "dm"), me: ME, nowMs: NOW, obligation: { key: "k" } });
|
|
445
|
-
assert.ok(d.degraded.includes("history_absent"));
|
|
446
|
-
assert.ok(d.why.some((w) => /guards are INACTIVE/.test(w)));
|
|
447
|
-
});
|
|
448
|
-
|
|
449
|
-
test("a degraded journal read is surfaced on the decision", () => {
|
|
450
|
-
const d = run({ history: freshHistory({ degraded: true, degradedReason: "3 unparseable lines" }) });
|
|
451
|
-
assert.ok(d.degraded.some((x) => /history_degraded/.test(x)));
|
|
452
|
-
});
|
|
453
|
-
|
|
454
|
-
test("an unknown surface degrades to the restrictive default, loudly", () => {
|
|
455
|
-
const d = run({ verdict: yes("surface_from_the_future", "direct") });
|
|
456
|
-
assert.ok(d.degraded.some((x) => /unknown_surface/.test(x)));
|
|
457
|
-
assert.notEqual(d.disposition, "react_now", "an unknown surface must not be spoken on");
|
|
458
|
-
assert.equal(d.policySurface, "default");
|
|
459
|
-
});
|
|
460
|
-
|
|
461
|
-
// ───────────────────────────────────────────────────────────────────────────
|
|
462
|
-
// Invariants
|
|
463
|
-
// ───────────────────────────────────────────────────────────────────────────
|
|
464
|
-
|
|
465
|
-
test("every decision is total: a disposition, a reason, a trace, and an id", () => {
|
|
466
|
-
const inputs = [
|
|
467
|
-
{}, { verdict: no("x") }, { runtime: { emergencyStop: true } },
|
|
468
|
-
...Object.values(SURFACE_CASES),
|
|
469
|
-
{ facts: { capabilityReachable: false } },
|
|
470
|
-
{ runtime: { mandateStaleSeconds: 9 * 86400 } },
|
|
471
|
-
];
|
|
472
|
-
for (const i of inputs) {
|
|
473
|
-
const d = run(i);
|
|
474
|
-
assert.ok(["react_now", "schedule", "delegate", "escalate", "ignore"].includes(d.disposition));
|
|
475
|
-
assert.ok(typeof d.reason === "string" && d.reason.length > 0);
|
|
476
|
-
assert.ok(Array.isArray(d.why));
|
|
477
|
-
assert.ok("key" in d && "thread" in d);
|
|
478
|
-
assert.ok(typeof d.at === "string");
|
|
479
|
-
}
|
|
480
|
-
});
|
|
481
|
-
|
|
482
|
-
test("tierOf: named-outright reasons are T0; lane reasons are T1; unknown is conservative", () => {
|
|
483
|
-
assert.equal(tierOf("dm"), "T0");
|
|
484
|
-
assert.equal(tierOf("assignee"), "T0");
|
|
485
|
-
assert.equal(tierOf("waiting_on"), "T0");
|
|
486
|
-
assert.equal(tierOf("channel"), "T1");
|
|
487
|
-
assert.equal(tierOf("thread"), "T1");
|
|
488
|
-
assert.equal(tierOf("something_new"), "T1", "an unknown reason must not be promoted to T0");
|
|
489
|
-
assert.equal(tierOf(undefined), "T1");
|
|
490
|
-
});
|
|
491
|
-
|
|
492
|
-
test("willAct is false for exactly the ignore disposition", () => {
|
|
493
|
-
assert.equal(willAct(run(SURFACE_CASES.dm)), true);
|
|
494
|
-
assert.equal(willAct(run({ runtime: { emergencyStop: true } })), false);
|
|
495
|
-
assert.equal(willAct(null), false);
|
|
496
|
-
assert.equal(willAct({ disposition: "nonsense" }), false);
|
|
497
|
-
});
|
|
498
|
-
|
|
499
|
-
test("decide is PURE: the same inputs give the same decision", () => {
|
|
500
|
-
const a = run(SURFACE_CASES.mention);
|
|
501
|
-
const b = run(SURFACE_CASES.mention);
|
|
502
|
-
assert.deepEqual(a, b);
|
|
503
|
-
});
|
|
504
|
-
|
|
505
|
-
// ───────────────────────────────────────────────────────────────────────────
|
|
506
|
-
// The escalate deadlock. Regression for a live failure, not a hypothetical.
|
|
507
|
-
// ───────────────────────────────────────────────────────────────────────────
|
|
508
|
-
|
|
509
|
-
test("ESCALATE is never blast-radius gated — gating it deadlocks the only route to a human", () => {
|
|
510
|
-
// `approval` and `decision` carry actionClasses:["irreversible"] AND
|
|
511
|
-
// selfApprove:false, so rule 6 always escalates them. Attaching the approval
|
|
512
|
-
// gate to that escalation means: to escalate an approval, first get an
|
|
513
|
-
// approval. drive.mjs step 4 fails closed, so the effect never ran and the
|
|
514
|
-
// lane produced nothing. Observed live (writes=[]).
|
|
515
|
-
for (const s of ["approval", "decision"]) {
|
|
516
|
-
const d = run(SURFACE_CASES[s]);
|
|
517
|
-
assert.equal(d.disposition, "escalate");
|
|
518
|
-
assert.equal(d.gates.approval, undefined, `${s} escalation was gated behind an approval`);
|
|
519
|
-
assert.ok(
|
|
520
|
-
d.why.some((w) => /would deadlock it/.test(w)),
|
|
521
|
-
"dropping the gate must be stated out loud, never done quietly",
|
|
522
|
-
);
|
|
523
|
-
}
|
|
524
|
-
});
|
|
525
|
-
|
|
526
|
-
test("an escalation carries >= 2 concrete options — hq refuses a question with fewer", () => {
|
|
527
|
-
// hq's escalation.ask 400s below two options ("a question with fewer is a
|
|
528
|
-
// message, not a decision"). Nothing supplies facts.ambiguousOptions for an
|
|
529
|
-
// approval, so without the surface policy's declared set the ladder decided
|
|
530
|
-
// `escalate` and the effect had nothing to ask with.
|
|
531
|
-
for (const s of ["approval", "decision"]) {
|
|
532
|
-
const d = run(SURFACE_CASES[s]);
|
|
533
|
-
assert.ok(Array.isArray(d.options), `${s} carried no options array`);
|
|
534
|
-
assert.ok(d.options.length >= 2, `${s} escalation offered ${d.options.length} option(s)`);
|
|
535
|
-
assert.equal(new Set(d.options).size, d.options.length, "duplicate labels are not a choice");
|
|
536
|
-
}
|
|
537
|
-
});
|
|
538
|
-
|
|
539
|
-
test("a caller-supplied option set beats the surface policy's default", () => {
|
|
540
|
-
const d = run({
|
|
541
|
-
...SURFACE_CASES.approval,
|
|
542
|
-
facts: { ambiguousOptions: ["ship it", "hold it", "hand it to finance"] },
|
|
543
|
-
});
|
|
544
|
-
assert.deepEqual(d.options, ["ship it", "hold it", "hand it to finance"]);
|
|
545
|
-
});
|
|
546
|
-
|
|
547
|
-
test("SCHEDULE keeps its blast-radius gate — the queue drain does not re-enter this ladder", () => {
|
|
548
|
-
// The exemption is for handovers (escalate/delegate) only. An inbound email
|
|
549
|
-
// queues, and dropping the `external` gate here would lose it for good.
|
|
550
|
-
const email = run(SURFACE_CASES.email);
|
|
551
|
-
assert.equal(email.disposition, "schedule");
|
|
552
|
-
assert.deepEqual(email.gates.approval, { required: true, classes: ["external"] });
|
|
553
|
-
});
|