@cohortapp/agent-sdk 2.16.0 → 2.18.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.json +18 -0
- package/.env.example +23 -7
- package/README.md +1 -0
- package/bin/maestro.mjs +62 -0
- package/docs/guides/billing-console-keys.md +60 -0
- package/docs/guides/front-door-session.md +54 -9
- package/docs/guides/mac-mini.md +20 -25
- package/docs/guides/poller-daemon-setup.md +4 -1
- package/docs/guides/setup-wizard.md +1 -1
- package/docs/runbooks/fleet-rollout.md +156 -0
- package/docs/runbooks/mac-mini-bootstrap.md +12 -14
- package/lib/action-executor.js +19 -3
- package/lib/budget-guard.mjs +279 -3
- package/lib/channels/base-adapter.mjs +3 -1
- package/lib/channels/contract.mjs +2 -1
- package/lib/channels/inbox-item.mjs +8 -0
- package/lib/claude-bin.mjs +5 -6
- package/lib/cli/doctor-checks.mjs +141 -10
- package/lib/cli/global-setup-extras.mjs +5 -1
- package/lib/cli/inbox.mjs +100 -15
- package/lib/cli/seat-auth.mjs +463 -0
- package/lib/cli/session.mjs +80 -12
- package/lib/collective/capture.mjs +8 -6
- package/lib/collective/global-config.mjs +63 -1
- package/lib/collective/presence.mjs +142 -5
- package/lib/comms/send-gate.mjs +559 -1
- package/lib/context/budget.mjs +327 -0
- package/lib/context/history-scope.mjs +138 -0
- package/lib/diagnostics/alerts.mjs +49 -0
- package/lib/diagnostics/cadence-output-freshness.mjs +288 -0
- package/lib/engine/agents/definitions.mjs +343 -0
- package/lib/engine/agents/persist.mjs +275 -0
- package/lib/engine/agents/runtime.mjs +748 -0
- package/lib/engine/agents/usage.mjs +95 -0
- package/lib/engine/auth-status.mjs +139 -0
- package/lib/engine/budget.mjs +194 -0
- package/lib/engine/cli.mjs +1204 -0
- package/lib/engine/commands/index.mjs +269 -0
- package/lib/engine/context/budget.mjs +219 -0
- package/lib/engine/context/cache.mjs +125 -0
- package/lib/engine/context/child-env.mjs +215 -0
- package/lib/engine/context/compaction.mjs +342 -0
- package/lib/engine/context/images.mjs +90 -0
- package/lib/engine/context/instructions.mjs +327 -0
- package/lib/engine/context/lazy-instructions.mjs +169 -0
- package/lib/engine/context/manager.mjs +182 -0
- package/lib/engine/context/real-path.mjs +91 -0
- package/lib/engine/context/secret-values.mjs +163 -0
- package/lib/engine/context/settings.mjs +274 -0
- package/lib/engine/context/stream-input.mjs +159 -0
- package/lib/engine/guard.mjs +152 -0
- package/lib/engine/hooks.mjs +713 -0
- package/lib/engine/loop.mjs +560 -0
- package/lib/engine/mcp/client.mjs +254 -0
- package/lib/engine/mcp/config.mjs +301 -0
- package/lib/engine/mcp/http.mjs +201 -0
- package/lib/engine/mcp/index.mjs +146 -0
- package/lib/engine/mcp/jsonrpc.mjs +147 -0
- package/lib/engine/mcp/naming.mjs +66 -0
- package/lib/engine/mcp/resources.mjs +89 -0
- package/lib/engine/mcp/results.mjs +133 -0
- package/lib/engine/mcp/stdio.mjs +137 -0
- package/lib/engine/mcp/supervisor.mjs +116 -0
- package/lib/engine/messages.mjs +104 -0
- package/lib/engine/output/json.mjs +164 -0
- package/lib/engine/output/stream-json.mjs +266 -0
- package/lib/engine/permissions.mjs +845 -0
- package/lib/engine/process-identity.mjs +164 -0
- package/lib/engine/process-tree.mjs +551 -0
- package/lib/engine/prompt.mjs +60 -0
- package/lib/engine/session/store.mjs +299 -0
- package/lib/engine/session-runtime/args.mjs +97 -0
- package/lib/engine/session-runtime/host.mjs +143 -0
- package/lib/engine/session-runtime/inbox.mjs +122 -0
- package/lib/engine/session-runtime/notifications.mjs +129 -0
- package/lib/engine/session-runtime/registry.mjs +328 -0
- package/lib/engine/session-runtime/runner.mjs +344 -0
- package/lib/engine/session-runtime/socket.mjs +212 -0
- package/lib/engine/session-runtime/wakeup.mjs +115 -0
- package/lib/engine/skills/index.mjs +321 -0
- package/lib/engine/tools/bash-background.mjs +533 -0
- package/lib/engine/tools/bash.mjs +216 -0
- package/lib/engine/tools/edit.mjs +97 -0
- package/lib/engine/tools/glob.mjs +81 -0
- package/lib/engine/tools/grep.mjs +224 -0
- package/lib/engine/tools/index.mjs +84 -0
- package/lib/engine/tools/list-agents.mjs +32 -0
- package/lib/engine/tools/ls.mjs +127 -0
- package/lib/engine/tools/monitor.mjs +82 -0
- package/lib/engine/tools/notebook-edit.mjs +218 -0
- package/lib/engine/tools/read.mjs +103 -0
- package/lib/engine/tools/schedule-wakeup.mjs +45 -0
- package/lib/engine/tools/schema.mjs +144 -0
- package/lib/engine/tools/send-message.mjs +77 -0
- package/lib/engine/tools/session.mjs +70 -0
- package/lib/engine/tools/todo.mjs +144 -0
- package/lib/engine/tools/toolsearch.mjs +217 -0
- package/lib/engine/tools/walk.mjs +193 -0
- package/lib/engine/tools/web-switch.mjs +31 -0
- package/lib/engine/tools/webfetch-html.mjs +387 -0
- package/lib/engine/tools/webfetch-net.mjs +340 -0
- package/lib/engine/tools/webfetch.mjs +198 -0
- package/lib/engine/tools/websearch.mjs +91 -0
- package/lib/engine/tools/workflow.mjs +95 -0
- package/lib/engine/tools/write.mjs +76 -0
- package/lib/engine/tui/line-editor.mjs +137 -0
- package/lib/engine/tui/render.mjs +86 -0
- package/lib/engine/tui/tui.mjs +274 -0
- package/lib/engine/wire/anthropic-messages.mjs +263 -0
- package/lib/engine/wire/effort.mjs +36 -0
- package/lib/engine/wire/errors.mjs +496 -0
- package/lib/engine/wire/http.mjs +441 -0
- package/lib/engine/wire/index.mjs +76 -0
- package/lib/engine/wire/openai-chat.mjs +332 -0
- package/lib/engine/wire/prompt-cache.mjs +79 -0
- package/lib/engine/wire/search.mjs +140 -0
- package/lib/engine/wire/sse.mjs +114 -0
- package/lib/engine/wire/stall.mjs +349 -0
- package/lib/engine/wire/token-provider.mjs +175 -0
- package/lib/engine/wire/usage.mjs +192 -0
- package/lib/engine/workflow/host.mjs +524 -0
- package/lib/engine/workflow/journal.mjs +188 -0
- package/lib/engine/workflow/json-schema.mjs +171 -0
- package/lib/engine/workflow/meta.mjs +329 -0
- package/lib/engine/workflow/notifications.mjs +52 -0
- package/lib/engine/workflow/runtime.mjs +447 -0
- package/lib/engine/workflow/sandbox.mjs +534 -0
- package/lib/engine/workflow/worker.mjs +141 -0
- package/lib/engine/workflow/worktree.mjs +74 -0
- package/lib/execution/disposition.mjs +1 -1
- package/lib/execution/intake.mjs +10 -0
- package/lib/execution/surface-policy.mjs +15 -0
- package/lib/learning/curator.mjs +8 -6
- package/lib/learning/reflect.mjs +8 -6
- package/lib/model-router/catalog/cohort.yaml +137 -0
- package/lib/model-router/catalog.mjs +118 -1
- package/lib/model-router/economics.mjs +9 -0
- package/lib/model-router/failover.mjs +67 -16
- package/lib/model-router/llm-task.mjs +39 -3
- package/lib/model-router/resolve.mjs +95 -3
- package/lib/model-router/spawn.mjs +46 -47
- package/lib/model-router/taxonomy.mjs +126 -4
- package/lib/org/cost-sync.mjs +141 -11
- package/lib/org/inbound/broadcast.mjs +289 -0
- package/lib/org/inbound/collective.mjs +375 -0
- package/lib/org/inbound/directedness.mjs +96 -8
- package/lib/org/inbound/facts.mjs +82 -4
- package/lib/org/inbound/hydrate.mjs +555 -51
- package/lib/org/inbound/project.mjs +22 -0
- package/lib/org/inbound/surfaces.mjs +14 -0
- package/lib/org/llm-token.mjs +879 -0
- package/lib/org/mesh.mjs +61 -0
- package/lib/org/messaging.mjs +3 -1
- package/lib/org/protocol.checksum +1 -1
- package/lib/org/protocol.mjs +15 -0
- package/lib/org/quota.mjs +520 -0
- package/lib/org/tool-surface.mjs +104 -16
- package/lib/org/ui-parity.mjs +16 -1
- package/lib/org/work-ledger.mjs +37 -6
- package/lib/rate-guard.mjs +114 -1
- package/lib/resource-governor.mjs +41 -6
- package/lib/runtime/adapter.mjs +823 -0
- package/lib/runtime/child-env.mjs +191 -0
- package/lib/runtime/legacy-shell-guard.mjs +97 -0
- package/lib/runtime/seat-engine.mjs +162 -0
- package/lib/session/ask-ledger.mjs +271 -0
- package/lib/session/current-work.mjs +676 -0
- package/lib/session/feed-core.mjs +40 -3
- package/lib/session/launch-args.mjs +56 -4
- package/lib/session/status-summary.mjs +26 -9
- package/lib/session/upgrade-notice.mjs +42 -0
- package/lib/setup/claude-probe.mjs +117 -13
- package/lib/setup/enrich.mjs +13 -10
- package/lib/setup/sections/model.mjs +39 -13
- package/lib/telemetry/collect.mjs +208 -9
- package/lib/upgrade/ignored-drift.mjs +105 -0
- package/lib/voice/post-call-brief.mjs +30 -17
- package/package.json +15 -3
- package/plugins/maestro-skills/skills/board-work.md +5 -0
- package/plugins/maestro-skills/skills/inbound-triage.md +56 -15
- package/plugins/maestro-skills/skills/main-session.md +18 -7
- package/scripts/ci/check-tarball-fidelity.mjs +126 -2
- package/scripts/ci/run-tests.mjs +47 -19
- package/scripts/cohort-llm/api-key-helper.mjs +92 -0
- package/scripts/collective/hook-runner.mjs +29 -2
- package/scripts/continuous-monitor.sh +13 -0
- package/scripts/cost/track-claude-usage.mjs +15 -0
- package/scripts/daemon/agent-daemon.mjs +408 -20
- package/scripts/daemon/assurance.mjs +48 -12
- package/scripts/daemon/cadence-consumer.mjs +218 -68
- package/scripts/daemon/cadence-handlers.mjs +73 -4
- package/scripts/daemon/classifier.mjs +75 -26
- package/scripts/daemon/context-compiler.mjs +104 -59
- package/scripts/daemon/deliver.mjs +30 -1
- package/scripts/daemon/dispatcher.mjs +804 -157
- package/scripts/daemon/health.mjs +14 -1
- package/scripts/daemon/lib/session-router.mjs +310 -42
- package/scripts/daemon/maestro-daemon.mjs +11 -0
- package/scripts/daemon/prompt-builder.mjs +121 -12
- package/scripts/daemon/responder.mjs +315 -146
- package/scripts/daemon/sdk-version.mjs +98 -16
- package/scripts/eval/probe-gateway.mjs +635 -0
- package/scripts/eval/replay/extract.mjs +270 -0
- package/scripts/eval/replay/grade.mjs +260 -0
- package/scripts/eval/replay/lib/config.mjs +50 -0
- package/scripts/eval/replay/lib/effects.mjs +65 -0
- package/scripts/eval/replay/lib/fixture.mjs +188 -0
- package/scripts/eval/replay/lib/judge.mjs +72 -0
- package/scripts/eval/replay/lib/redact.mjs +136 -0
- package/scripts/eval/replay/lib/sandbox.mjs +170 -0
- package/scripts/eval/replay/lib/schema-check.mjs +63 -0
- package/scripts/eval/replay/lib/transcript.mjs +76 -0
- package/scripts/eval/replay/mcp-replay-stub.mjs +101 -0
- package/scripts/eval/replay/report.mjs +185 -0
- package/scripts/eval/replay/run.mjs +404 -0
- package/scripts/fleet/rollout.mjs +1094 -0
- package/scripts/hooks/pre-send-audit.sh +36 -245
- package/scripts/hooks/pre-write-yaml-validate.mjs +275 -0
- package/scripts/hooks/validate-state-yaml.sh +190 -0
- package/scripts/huddle/huddle-llm.mjs +361 -0
- package/scripts/huddle/huddle-server.mjs +46 -121
- package/scripts/local-triggers/autoupdate.sh +448 -78
- package/scripts/local-triggers/run-trigger.sh +13 -0
- package/scripts/maintenance/pin-integrity.mjs +364 -0
- package/scripts/poll-slack-events.sh +41 -9
- package/scripts/poller/slack-socket-mode.mjs +28 -3
- package/scripts/session/supervisor.mjs +80 -13
- package/scripts/spawn-session.sh +13 -0
- package/bin/maestro.test.mjs +0 -1574
- package/lib/action-executor.test.mjs +0 -871
- package/lib/archetype.test.mjs +0 -132
- package/lib/assurance/plan-note.test.mjs +0 -234
- package/lib/assurance/room-budget.test.mjs +0 -486
- package/lib/assurance/tier.test.mjs +0 -174
- package/lib/autonomy.test.mjs +0 -66
- package/lib/backlog.test.mjs +0 -302
- package/lib/backup/policy.test.mjs +0 -305
- package/lib/budget-escalate.test.mjs +0 -232
- package/lib/budget-guard.envelope.test.mjs +0 -476
- package/lib/budget-guard.test.mjs +0 -427
- package/lib/cadence-bus-requeue.test.mjs +0 -83
- package/lib/cadence-bus-schedule.test.mjs +0 -194
- package/lib/cadence-bus.test.mjs +0 -720
- package/lib/cadences.test.mjs +0 -230
- package/lib/capability/inventory.test.mjs +0 -232
- package/lib/capability.test.mjs +0 -78
- package/lib/channels/base-adapter.test.mjs +0 -590
- package/lib/channels/channels.test.mjs +0 -371
- package/lib/channels/contract.test.mjs +0 -162
- package/lib/channels/inbox-item.test.mjs +0 -368
- package/lib/channels/orgmail/adapter.test.mjs +0 -448
- package/lib/channels/pairing.test.mjs +0 -270
- package/lib/channels/repeat-suppressor.test.mjs +0 -134
- package/lib/channels/slack-adapter.test.mjs +0 -212
- package/lib/channels/telegram-adapter.test.mjs +0 -306
- package/lib/channels/voice/adapter.test.mjs +0 -278
- package/lib/channels/whatsapp/adapter-baileys.test.mjs +0 -359
- package/lib/channels/whatsapp/baileys-typing.test.mjs +0 -154
- package/lib/charter.test.mjs +0 -89
- package/lib/claude-bin.test.mjs +0 -131
- package/lib/cli/board.test.mjs +0 -227
- package/lib/cli/design.test.mjs +0 -270
- package/lib/cli/doctor-checks.test.mjs +0 -336
- package/lib/cli/global-setup-extras.test.mjs +0 -462
- package/lib/cli/inbox.test.mjs +0 -230
- package/lib/cli/session-ack.test.mjs +0 -63
- package/lib/cli/session.test.mjs +0 -613
- package/lib/collective/capture.test.mjs +0 -121
- package/lib/collective/cards.test.mjs +0 -114
- package/lib/collective/config.test.mjs +0 -123
- package/lib/collective/global-config.test.mjs +0 -220
- package/lib/collective/global-skills.test.mjs +0 -126
- package/lib/collective/presence.test.mjs +0 -95
- package/lib/collective/recall.test.mjs +0 -116
- package/lib/collective/vendor-skills.test.mjs +0 -306
- package/lib/comms/send-gate.test.mjs +0 -770
- package/lib/comms.test.mjs +0 -41
- package/lib/cost/ledger-row.test.mjs +0 -183
- package/lib/design/design-md.test.mjs +0 -318
- package/lib/design/fixtures/DESIGN.golden.md +0 -238
- package/lib/design/fixtures/PRODUCT.golden.md +0 -67
- package/lib/design/fixtures/foundation.json +0 -133
- package/lib/design/refresh-gate.test.mjs +0 -144
- package/lib/design/write.test.mjs +0 -241
- package/lib/diagnostics/alerts.test.mjs +0 -318
- package/lib/diagnostics/backup-freshness.test.mjs +0 -185
- package/lib/diagnostics/counters.test.mjs +0 -206
- package/lib/diagnostics/events.test.mjs +0 -290
- package/lib/diagnostics/otel.test.mjs +0 -196
- package/lib/diagnostics/trace.test.mjs +0 -251
- package/lib/env-compat.test.mjs +0 -104
- package/lib/execution/disposition.test.mjs +0 -553
- package/lib/execution/drive.test.mjs +0 -270
- package/lib/execution/effects.test.mjs +0 -344
- package/lib/execution/intake.test.mjs +0 -389
- package/lib/execution/journal.test.mjs +0 -261
- package/lib/execution/match.test.mjs +0 -235
- package/lib/execution/pipeline.test.mjs +0 -392
- package/lib/execution/route.test.mjs +0 -186
- package/lib/execution/surface-policy.test.mjs +0 -162
- package/lib/fs-atomic.test.mjs +0 -72
- package/lib/fs-ownership.test.mjs +0 -158
- package/lib/goals/admission.test.mjs +0 -164
- package/lib/goals/classify.test.mjs +0 -167
- package/lib/goals/collaborate.test.mjs +0 -336
- package/lib/goals/gaps.test.mjs +0 -284
- package/lib/goals/loop.test.mjs +0 -845
- package/lib/hooks/bus.test.mjs +0 -387
- package/lib/identity/persona.test.mjs +0 -142
- package/lib/kpi-sensors.test.mjs +0 -278
- package/lib/kpi.test.mjs +0 -244
- package/lib/learning/config.test.mjs +0 -75
- package/lib/learning/counters.test.mjs +0 -69
- package/lib/learning/curator-consolidate.test.mjs +0 -238
- package/lib/learning/curator.test.mjs +0 -106
- package/lib/learning/reflect.test.mjs +0 -0
- package/lib/learning/session-index.test.mjs +0 -125
- package/lib/learning/skill-writer.test.mjs +0 -210
- package/lib/mandate/audit.test.mjs +0 -195
- package/lib/mandate/contract.test.mjs +0 -185
- package/lib/mandate/derive.test.mjs +0 -274
- package/lib/mandate/model.test.mjs +0 -164
- package/lib/mandate/refresh.test.mjs +0 -389
- package/lib/mcp/server.test.mjs +0 -426
- package/lib/model-router/auth-profiles.test.mjs +0 -580
- package/lib/model-router/catalog.test.mjs +0 -385
- package/lib/model-router/economics.test.mjs +0 -438
- package/lib/model-router/failover.test.mjs +0 -439
- package/lib/model-router/health.test.mjs +0 -338
- package/lib/model-router/integration-coverage.test.mjs +0 -831
- package/lib/model-router/integration.test.mjs +0 -564
- package/lib/model-router/ledger.test.mjs +0 -415
- package/lib/model-router/llm-task.test.mjs +0 -392
- package/lib/model-router/org-credentials.test.mjs +0 -265
- package/lib/model-router/pricing-refresh.test.mjs +0 -286
- package/lib/model-router/reconcile.test.mjs +0 -316
- package/lib/model-router/repair.test.mjs +0 -180
- package/lib/model-router/spawn.test.mjs +0 -446
- package/lib/model-router/taxonomy.test.mjs +0 -410
- package/lib/model-router.test.mjs +0 -1207
- package/lib/org/activity.test.mjs +0 -134
- package/lib/org/approvals.test.mjs +0 -216
- package/lib/org/awareness.test.mjs +0 -159
- package/lib/org/board-mine-cache.test.mjs +0 -53
- package/lib/org/board.test.mjs +0 -187
- package/lib/org/bootstrap-context.test.mjs +0 -153
- package/lib/org/client.test.mjs +0 -1206
- package/lib/org/cohort-client.test.mjs +0 -126
- package/lib/org/cost-sync.test.mjs +0 -153
- package/lib/org/doctor.test.mjs +0 -346
- package/lib/org/engagement-ledger.test.mjs +0 -112
- package/lib/org/engagement.test.mjs +0 -739
- package/lib/org/handoff.test.mjs +0 -269
- package/lib/org/inbound/directedness.test.mjs +0 -668
- package/lib/org/inbound/facts.test.mjs +0 -471
- package/lib/org/inbound/hydrate.test.mjs +0 -453
- package/lib/org/inbound/index.test.mjs +0 -429
- package/lib/org/inbound/project.test.mjs +0 -287
- package/lib/org/integration-tools.test.mjs +0 -160
- package/lib/org/keys.test.mjs +0 -92
- package/lib/org/knowledge.test.mjs +0 -326
- package/lib/org/leases.test.mjs +0 -235
- package/lib/org/mesh-directives.test.mjs +0 -110
- package/lib/org/mesh-integration.test.mjs +0 -127
- package/lib/org/mesh.test.mjs +0 -400
- package/lib/org/messaging.test.mjs +0 -471
- package/lib/org/param-contract.test.mjs +0 -477
- package/lib/org/policy.test.mjs +0 -237
- package/lib/org/protocol.checksum.test.mjs +0 -90
- package/lib/org/protocol.test.mjs +0 -323
- package/lib/org/push.test.mjs +0 -792
- package/lib/org/registry.test.mjs +0 -100
- package/lib/org/resource-tools.test.mjs +0 -361
- package/lib/org/tool-access.test.mjs +0 -144
- package/lib/org/tool-surface-integration.test.mjs +0 -120
- package/lib/org/tool-surface.test.mjs +0 -1268
- package/lib/org/typing.test.mjs +0 -291
- package/lib/org/ui-parity.test.mjs +0 -560
- package/lib/org/verify.test.mjs +0 -194
- package/lib/org/work-ledger.test.mjs +0 -273
- package/lib/plan/adoption-e2e.test.mjs +0 -366
- package/lib/plan/budget-enforcement.test.mjs +0 -400
- package/lib/plan/compile.test.mjs +0 -382
- package/lib/plan/emit.test.mjs +0 -269
- package/lib/plan/explain.test.mjs +0 -188
- package/lib/prompts/parallelism.test.mjs +0 -177
- package/lib/rag/rag.test.mjs +0 -505
- package/lib/rate-guard.test.mjs +0 -272
- package/lib/reactive-gate.test.mjs +0 -57
- package/lib/render.test.mjs +0 -68
- package/lib/resource-governor.test.mjs +0 -488
- package/lib/scheduling/dynamic-jobs.test.mjs +0 -344
- package/lib/scheduling/jitter.test.mjs +0 -140
- package/lib/secrets/broker.test.mjs +0 -280
- package/lib/secrets/providers.test.mjs +0 -274
- package/lib/security/audit-engine.test.mjs +0 -424
- package/lib/security/coerce-args.test.mjs +0 -281
- package/lib/security/dangerous-tools.test.mjs +0 -68
- package/lib/security/external-content.test.mjs +0 -84
- package/lib/security/redact.test.mjs +0 -441
- package/lib/security/secret-equal.test.mjs +0 -55
- package/lib/session/config.test.mjs +0 -92
- package/lib/session/feed-core.test.mjs +0 -198
- package/lib/session/first-run.test.mjs +0 -121
- package/lib/session/frontdoor.test.mjs +0 -205
- package/lib/session/handoffs.test.mjs +0 -183
- package/lib/session/identity.test.mjs +0 -180
- package/lib/session/inbox-claims.test.mjs +0 -286
- package/lib/session/launch-args.test.mjs +0 -157
- package/lib/session/liveness.test.mjs +0 -100
- package/lib/session/status-summary.test.mjs +0 -118
- package/lib/session-permissions.test.mjs +0 -120
- package/lib/setup/claude-probe.test.mjs +0 -187
- package/lib/setup/completeness.test.mjs +0 -110
- package/lib/setup/context-pack.test.mjs +0 -89
- package/lib/setup/enrich.test.mjs +0 -115
- package/lib/setup/enroll-from-cohort.test.mjs +0 -300
- package/lib/setup/integration.test.mjs +0 -162
- package/lib/setup/io.test.mjs +0 -77
- package/lib/setup/runner.test.mjs +0 -132
- package/lib/setup/sections/identity.test.mjs +0 -234
- package/lib/setup/sections/inventory.test.mjs +0 -198
- package/lib/setup/sections/learning.test.mjs +0 -81
- package/lib/setup/sections/mandate.test.mjs +0 -388
- package/lib/setup/sections/messaging.test.mjs +0 -127
- package/lib/setup/sections/model.test.mjs +0 -240
- package/lib/setup/sections/org.test.mjs +0 -346
- package/lib/setup/sections/orgmail.test.mjs +0 -118
- package/lib/setup/sections/recovery.test.mjs +0 -98
- package/lib/setup/sections/subagents.test.mjs +0 -429
- package/lib/setup/sections/verify.test.mjs +0 -175
- package/lib/setup/sot.test.mjs +0 -81
- package/lib/setup/state.test.mjs +0 -115
- package/lib/singleton.test.mjs +0 -151
- package/lib/subagents/cli.test.mjs +0 -389
- package/lib/subagents/client.test.mjs +0 -309
- package/lib/subagents/gap.test.mjs +0 -234
- package/lib/subagents/lock.test.mjs +0 -248
- package/lib/subagents/manifest.test.mjs +0 -175
- package/lib/subagents/refs.test.mjs +0 -204
- package/lib/subagents/resolve.test.mjs +0 -422
- package/lib/subagents/schema.test.mjs +0 -328
- package/lib/telemetry/alerts.test.mjs +0 -109
- package/lib/telemetry/collect.test.mjs +0 -1274
- package/lib/tool-definitions-integration.test.mjs +0 -83
- package/lib/tool-definitions.test.mjs +0 -437
- package/lib/upgrade/global-refresh.test.mjs +0 -65
- package/lib/upgrade/launchd-reconcile.test.mjs +0 -272
- package/lib/upgrade/post-steps.test.mjs +0 -200
- package/lib/upgrade/verify.test.mjs +0 -164
- package/lib/util/fetch-timeout.test.mjs +0 -202
- package/lib/util/reconnect.test.mjs +0 -369
- package/lib/util/unhandled.test.mjs +0 -216
- package/lib/voice/outbound.test.mjs +0 -69
- package/lib/voice/session-rotation.test.mjs +0 -114
- package/lib/voice/stt.test.mjs +0 -226
- package/lib/voice/voice.test.mjs +0 -990
- package/scripts/cadence/enqueue-cadence-tick.test.mjs +0 -187
- package/scripts/ci/check-docs-accuracy.test.mjs +0 -409
- package/scripts/ci/check-durable-write-seam.test.mjs +0 -90
- package/scripts/ci/check-no-build-artifacts.test.mjs +0 -71
- package/scripts/ci/check-no-residual-identity.test.mjs +0 -202
- package/scripts/ci/check-skill-packs.test.mjs +0 -495
- package/scripts/ci/check-subagent-frontmatter.test.mjs +0 -124
- package/scripts/ci/check.test.mjs +0 -194
- package/scripts/ci/conformance-org-api.test.mjs +0 -425
- package/scripts/cloud-relay/voice/relay-identity.test.mjs +0 -96
- package/scripts/collective/hook-runner.test.mjs +0 -173
- package/scripts/cost/fleet-digest.test.mjs +0 -207
- package/scripts/cost/track-claude-usage-pricing.test.mjs +0 -183
- package/scripts/cost/track-claude-usage.test.mjs +0 -148
- package/scripts/daemon/agent-daemon-board-mine.test.mjs +0 -96
- package/scripts/daemon/agent-daemon-design.test.mjs +0 -238
- package/scripts/daemon/agent-daemon-frontdoor.test.mjs +0 -60
- package/scripts/daemon/agent-daemon.test.mjs +0 -995
- package/scripts/daemon/assurance-e2e.test.mjs +0 -613
- package/scripts/daemon/assurance.test.mjs +0 -1791
- package/scripts/daemon/board-mirror.test.mjs +0 -165
- package/scripts/daemon/cadence-consumer-frontdoor.test.mjs +0 -393
- package/scripts/daemon/cadence-consumer-governance.test.mjs +0 -276
- package/scripts/daemon/cadence-consumer.test.mjs +0 -776
- package/scripts/daemon/cadence-handlers.test.mjs +0 -837
- package/scripts/daemon/classifier-identity.test.mjs +0 -137
- package/scripts/daemon/classifier.test.mjs +0 -266
- package/scripts/daemon/classify-kind.test.mjs +0 -40
- package/scripts/daemon/context-compiler.test.mjs +0 -300
- package/scripts/daemon/deliver.test.mjs +0 -564
- package/scripts/daemon/dispatcher-cooldown.test.mjs +0 -122
- package/scripts/daemon/dispatcher-governance.test.mjs +0 -1013
- package/scripts/daemon/dispatcher-resume.test.mjs +0 -166
- package/scripts/daemon/execution-ladder.test.mjs +0 -470
- package/scripts/daemon/goal-steward-cadence.test.mjs +0 -312
- package/scripts/daemon/inbox-deferral-session.test.mjs +0 -49
- package/scripts/daemon/inbox-deferral.test.mjs +0 -336
- package/scripts/daemon/inbox-wake.test.mjs +0 -199
- package/scripts/daemon/integration.test.mjs +0 -149
- package/scripts/daemon/lib/self-echo.test.mjs +0 -153
- package/scripts/daemon/lib/session-router.test.mjs +0 -295
- package/scripts/daemon/prompt-builder-preamble.test.mjs +0 -210
- package/scripts/daemon/prompt-builder.test.mjs +0 -344
- package/scripts/daemon/responder-cost.test.mjs +0 -68
- package/scripts/daemon/responder-history.test.mjs +0 -185
- package/scripts/daemon/sdk-version.test.mjs +0 -31
- package/scripts/daemon/session-lock.test.mjs +0 -252
- package/scripts/daemon/session-outcomes.test.mjs +0 -533
- package/scripts/daemon/typing-registry.test.mjs +0 -102
- package/scripts/hooks/pre-send-audit.test.mjs +0 -354
- package/scripts/huddle/huddle-prompt.test.mjs +0 -176
- package/scripts/local-triggers/autoupdate.test.mjs +0 -518
- package/scripts/local-triggers/generate-plists.test.mjs +0 -456
- package/scripts/media-generation/brand-clause.test.mjs +0 -135
- package/scripts/org/send-orgmail.first-contact.test.mjs +0 -102
- package/scripts/poller/inbox-privilege-injection.test.mjs +0 -167
- package/scripts/poller/inbox-scan-poller.test.mjs +0 -295
- package/scripts/poller/lib/cloud-relay-dedup.test.mjs +0 -133
- package/scripts/poller/slack-socket-mode.test.mjs +0 -805
- package/scripts/poller-launchd/install.test.mjs +0 -243
- package/scripts/restore-from-backup.test.mjs +0 -181
- package/scripts/session/feed.test.mjs +0 -196
- package/scripts/session/supervisor-sh.test.mjs +0 -218
- package/scripts/session/supervisor.test.mjs +0 -482
- package/scripts/setup/configure-macos.test.mjs +0 -306
- package/scripts/setup/gen-subagent-manifest.test.mjs +0 -124
- package/scripts/setup/generate-agent-package-json.test.mjs +0 -143
- package/scripts/setup/generate-capability.test.mjs +0 -134
- package/scripts/setup/init-agent.test.mjs +0 -370
- package/scripts/setup/init-skill-marketplace.test.mjs +0 -193
- package/scripts/vendor/sync-skill-packs.test.mjs +0 -103
- package/scripts/watchdog/memory-watchdog.test.mjs +0 -64
|
@@ -1,166 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* dispatcher-resume.test.mjs — audit F11: `defaultSpawnResume` built a BARE
|
|
3
|
-
* env (no router, no scrub) and a bare argv (no --output-format json, no
|
|
4
|
-
* knobs). A session originally routed to a third-party backend therefore
|
|
5
|
-
* resumed against api.anthropic.com with a foreign model id, and every
|
|
6
|
-
* resumed run recorded $0 because nothing parsed its usage.
|
|
7
|
-
*
|
|
8
|
-
* The resume now goes through the SAME resolveSpawnTarget / buildChildEnv
|
|
9
|
-
* path as a fresh spawnSession. The pure builder is tested directly; the
|
|
10
|
-
* real defaultSpawnResume is exercised through setSpawnForTests so the argv
|
|
11
|
-
* and env handed to `claude` are asserted byte-for-byte.
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
import { test } from "node:test";
|
|
15
|
-
import assert from "node:assert/strict";
|
|
16
|
-
import { promises as fsp } from "node:fs";
|
|
17
|
-
import { writeFileSync, mkdirSync } from "node:fs";
|
|
18
|
-
import { tmpdir } from "node:os";
|
|
19
|
-
import { join } from "node:path";
|
|
20
|
-
|
|
21
|
-
async function freshDispatcher() {
|
|
22
|
-
const dir = join(tmpdir(), `dispatcher-resume-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`);
|
|
23
|
-
mkdirSync(join(dir, "state/sessions"), { recursive: true });
|
|
24
|
-
mkdirSync(join(dir, "logs/daemon"), { recursive: true });
|
|
25
|
-
process.env.AGENT_DIR = dir;
|
|
26
|
-
const url = new URL("./dispatcher.mjs", import.meta.url);
|
|
27
|
-
const mod = await import(`${url.href}?resume=${Math.random()}`);
|
|
28
|
-
return { mod, dir };
|
|
29
|
-
}
|
|
30
|
-
async function cleanup(dir) { try { await fsp.rm(dir, { recursive: true, force: true }); } catch { /* */ } }
|
|
31
|
-
function plantMarker(dir, sessionId, marker) {
|
|
32
|
-
const d = join(dir, "state/sessions/resume-pending");
|
|
33
|
-
mkdirSync(d, { recursive: true });
|
|
34
|
-
writeFileSync(join(d, `${sessionId}.json`), JSON.stringify({
|
|
35
|
-
sessionId, claudeSessionId: `cs-${sessionId}`, source: "backlog", itemId: "BL-1", sourceFile: "q.yaml",
|
|
36
|
-
prompt: "finish the memo", modelFlag: "claude-opus-4-6", model: "opus", priority: "normal",
|
|
37
|
-
startedAt: Date.now(), recoveryAttempts: 0, ...marker,
|
|
38
|
-
}, null, 2));
|
|
39
|
-
}
|
|
40
|
-
function writeV2Config(dir) {
|
|
41
|
-
mkdirSync(join(dir, "config"), { recursive: true });
|
|
42
|
-
writeFileSync(join(dir, "config/model-routing.json"), JSON.stringify({
|
|
43
|
-
schema_version: 2,
|
|
44
|
-
aliases: { frontier: "anthropic/claude-opus-4-8", default: "anthropic/claude-sonnet-4-6", fast: "anthropic/claude-haiku-4-5" },
|
|
45
|
-
defaults: { needs_tool_use_for_sessions: true, data_class: "sensitive", cache_ttl: "1h" },
|
|
46
|
-
backends: { anthropic: { allowed_data_classes: ["public", "internal", "sensitive"] } },
|
|
47
|
-
routing_policy: [{ default: true, chain: ["default", "fast"] }],
|
|
48
|
-
fallback_to_anthropic: true,
|
|
49
|
-
}, null, 2));
|
|
50
|
-
}
|
|
51
|
-
function fakeProc() {
|
|
52
|
-
const handlers = {};
|
|
53
|
-
return { stdout: { on: () => {} }, stderr: { on: () => {} }, on: (ev, cb) => { handlers[ev] = cb; }, kill: () => {}, killed: false, _handlers: handlers };
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const ANTHROPIC_TARGET = { modelFlag: "claude-sonnet-4-6", envForSpawn: {}, decisionId: "d-1", backend: "anthropic", transport: "anthropic-cli", maxTurns: 40, effort: null, agentsJson: { "Haiku-Explore": { model: "haiku" } } };
|
|
57
|
-
const RETARGET = { modelFlag: "kimi-k2", envForSpawn: { ANTHROPIC_BASE_URL: "https://moon.example", ANTHROPIC_AUTH_TOKEN: "tok-xyz", ANTHROPIC_API_KEY: "" }, decisionId: "d-2", backend: "moonshot", transport: "anthropic-compatible", maxTurns: 40, effort: "high", agentsJson: null };
|
|
58
|
-
|
|
59
|
-
test("buildResumeSpawn (Anthropic): --print --output-format json + knobs + --session-id + marker model + prompt last; legacy key strip; session id in env", async () => {
|
|
60
|
-
const { mod, dir } = await freshDispatcher();
|
|
61
|
-
try {
|
|
62
|
-
const marker = { sessionId: "s-1", claudeSessionId: "CS-1", prompt: "finish the memo", modelFlag: "claude-opus-4-6", model: "opus" };
|
|
63
|
-
const { args, env, modelFlag } = mod.buildResumeSpawn({
|
|
64
|
-
marker, target: ANTHROPIC_TARGET,
|
|
65
|
-
baseEnv: { PATH: "/usr/bin", ANTHROPIC_API_KEY: "sk-live", ANTHROPIC_AUTH_TOKEN: "at", FOO_API_KEY: "foo", CLAUDE_CODE_OAUTH_TOKEN: "oauth-1" },
|
|
66
|
-
permissionArgs: ["--dangerously-skip-permissions"], daemonArgs: ["--strict-mcp-config"], augmentedPath: () => "/aug",
|
|
67
|
-
});
|
|
68
|
-
assert.equal(args[0], "--print");
|
|
69
|
-
assert.equal(args[args.indexOf("--output-format") + 1], "json", "usage is measurable on a resumed run");
|
|
70
|
-
assert.ok(args.includes("--dangerously-skip-permissions"));
|
|
71
|
-
assert.ok(args.includes("--strict-mcp-config"));
|
|
72
|
-
assert.equal(args[args.indexOf("--max-turns") + 1], "40", "the router's knob args ride the resume");
|
|
73
|
-
assert.ok(/Haiku-Explore/.test(args[args.indexOf("--agents") + 1]));
|
|
74
|
-
assert.equal(args[args.indexOf("--session-id") + 1], "CS-1");
|
|
75
|
-
assert.equal(args[args.indexOf("--model") + 1], "claude-opus-4-6", "an Anthropic session resumes on the exact flag it used");
|
|
76
|
-
assert.equal(modelFlag, "claude-opus-4-6");
|
|
77
|
-
assert.equal(args[args.length - 1], "finish the memo");
|
|
78
|
-
assert.ok(!args.includes("--resume"));
|
|
79
|
-
// Env: the non-retarget path keeps the historical explicit-empty-key posture (keychain/OAuth fallthrough).
|
|
80
|
-
assert.equal(env.ANTHROPIC_API_KEY, "");
|
|
81
|
-
assert.equal(env.ANTHROPIC_AUTH_TOKEN, "");
|
|
82
|
-
assert.equal(env.PATH, "/aug");
|
|
83
|
-
assert.equal(env.MAESTRO_SESSION_ID, "s-1", "delivery receipts attribute to the resumed session");
|
|
84
|
-
assert.equal(env.CLAUDE_CODE_OAUTH_TOKEN, "oauth-1", "the subscription token reaches the resumed session");
|
|
85
|
-
} finally { await cleanup(dir); }
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
test("buildResumeSpawn (retarget): env goes through buildChildEnv — base URL + token injected, foreign credentials scrubbed", async () => {
|
|
89
|
-
const { mod, dir } = await freshDispatcher();
|
|
90
|
-
try {
|
|
91
|
-
const marker = { sessionId: "s-2", claudeSessionId: "CS-2", prompt: "p", modelFlag: "kimi-k2", model: "sonnet" };
|
|
92
|
-
const { args, env } = mod.buildResumeSpawn({
|
|
93
|
-
marker, target: RETARGET,
|
|
94
|
-
baseEnv: { PATH: "/usr/bin", ANTHROPIC_API_KEY: "sk-live", FOO_API_KEY: "foo", HOME: "/h" },
|
|
95
|
-
permissionArgs: [], daemonArgs: [], augmentedPath: () => "/aug",
|
|
96
|
-
});
|
|
97
|
-
assert.equal(env.ANTHROPIC_BASE_URL, "https://moon.example", "the resumed session is retargeted like the original");
|
|
98
|
-
assert.equal(env.ANTHROPIC_AUTH_TOKEN, "tok-xyz");
|
|
99
|
-
assert.equal(env.ANTHROPIC_API_KEY, "", "empty-string discipline: no keychain fallthrough on a retarget");
|
|
100
|
-
assert.equal(env.FOO_API_KEY, undefined, "§7.3 scrub: no foreign credential leaks");
|
|
101
|
-
assert.equal(env.HOME, "/h");
|
|
102
|
-
assert.equal(env.PATH, "/aug");
|
|
103
|
-
assert.equal(env.MAESTRO_SESSION_ID, "s-2");
|
|
104
|
-
assert.equal(args[args.indexOf("--model") + 1], "kimi-k2");
|
|
105
|
-
assert.equal(args[args.indexOf("--effort") + 1], "high");
|
|
106
|
-
} finally { await cleanup(dir); }
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
test("resumeModelFlag: a foreign model id never resumes against Anthropic (F11) — the router's current flag wins", async () => {
|
|
110
|
-
const { mod, dir } = await freshDispatcher();
|
|
111
|
-
try {
|
|
112
|
-
// Original ran on a third party; the router now resolves Anthropic (provider down / config changed).
|
|
113
|
-
assert.equal(mod.resumeModelFlag({ modelFlag: "moonshot/kimi-k2" }, ANTHROPIC_TARGET), "claude-sonnet-4-6");
|
|
114
|
-
assert.equal(mod.resumeModelFlag({ modelFlag: "kimi-k2" }, ANTHROPIC_TARGET), "claude-sonnet-4-6");
|
|
115
|
-
// Original ran on Anthropic; still Anthropic → the exact flag it used (H1 pin).
|
|
116
|
-
assert.equal(mod.resumeModelFlag({ modelFlag: "claude-opus-4-6" }, ANTHROPIC_TARGET), "claude-opus-4-6");
|
|
117
|
-
assert.equal(mod.resumeModelFlag({ modelFlag: "opus" }, ANTHROPIC_TARGET), "opus");
|
|
118
|
-
// A retarget: the router's flag for that backend.
|
|
119
|
-
assert.equal(mod.resumeModelFlag({ modelFlag: "claude-opus-4-6" }, RETARGET), "kimi-k2");
|
|
120
|
-
// No router at all (target null) → marker flag, then coarse class, then sonnet.
|
|
121
|
-
assert.equal(mod.resumeModelFlag({ modelFlag: "claude-opus-4-6" }, null), "claude-opus-4-6");
|
|
122
|
-
assert.equal(mod.resumeModelFlag({ model: "opus" }, null), "opus");
|
|
123
|
-
assert.equal(mod.resumeModelFlag({}, null), "sonnet");
|
|
124
|
-
} finally { await cleanup(dir); }
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
test("defaultSpawnResume (real path, v2 config): argv carries --output-format json + router knobs; env carries the session id and the key strip", async () => {
|
|
128
|
-
const { mod, dir } = await freshDispatcher();
|
|
129
|
-
try {
|
|
130
|
-
writeV2Config(dir);
|
|
131
|
-
plantMarker(dir, "s-real", { claudeSessionId: "CS-FIXED", prompt: "finish drafting the board memo", modelFlag: "claude-opus-4-6" });
|
|
132
|
-
const calls = [];
|
|
133
|
-
const restore = mod.setSpawnForTests((bin, args, opts) => { calls.push({ bin, args, opts }); return fakeProc(); });
|
|
134
|
-
try {
|
|
135
|
-
const stats = mod.reconcileResumePending();
|
|
136
|
-
assert.equal(stats.resumed, 1);
|
|
137
|
-
} finally { restore(); }
|
|
138
|
-
assert.equal(calls.length, 1);
|
|
139
|
-
const { args, opts } = calls[0];
|
|
140
|
-
assert.equal(args[0], "--print");
|
|
141
|
-
assert.equal(args[args.indexOf("--output-format") + 1], "json");
|
|
142
|
-
assert.equal(args[args.indexOf("--max-turns") + 1], "40", "v2 knobs are no longer dropped on resume");
|
|
143
|
-
assert.ok(args.includes("--agents"));
|
|
144
|
-
assert.equal(args[args.indexOf("--session-id") + 1], "CS-FIXED");
|
|
145
|
-
assert.equal(args[args.indexOf("--model") + 1], "claude-opus-4-6");
|
|
146
|
-
assert.equal(args[args.length - 1], "finish drafting the board memo");
|
|
147
|
-
assert.ok(!args.includes("--resume"));
|
|
148
|
-
assert.equal(opts.env.MAESTRO_SESSION_ID, "s-real");
|
|
149
|
-
assert.equal(opts.env.ANTHROPIC_API_KEY, "");
|
|
150
|
-
assert.equal(opts.cwd, dir);
|
|
151
|
-
} finally { await cleanup(dir); }
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
test("defaultSpawnResume (no routing config): still a --print --output-format json spawn on the marker's flag", async () => {
|
|
155
|
-
const { mod, dir } = await freshDispatcher();
|
|
156
|
-
try {
|
|
157
|
-
plantMarker(dir, "s-plain", { claudeSessionId: "CS-P", prompt: "p", modelFlag: "claude-sonnet-4-6" });
|
|
158
|
-
const calls = [];
|
|
159
|
-
const restore = mod.setSpawnForTests((bin, args, opts) => { calls.push({ args, opts }); return fakeProc(); });
|
|
160
|
-
try { mod.reconcileResumePending(); } finally { restore(); }
|
|
161
|
-
const { args } = calls[0];
|
|
162
|
-
assert.equal(args[args.indexOf("--output-format") + 1], "json");
|
|
163
|
-
assert.equal(args[args.indexOf("--model") + 1], "claude-sonnet-4-6");
|
|
164
|
-
assert.ok(!args.includes("--max-turns"), "no router → no knobs (unchanged)");
|
|
165
|
-
} finally { await cleanup(dir); }
|
|
166
|
-
});
|
|
@@ -1,470 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* scripts/daemon/execution-ladder.test.mjs — the daemon actually ENTERS
|
|
3
|
-
* lib/execution/**.
|
|
4
|
-
*
|
|
5
|
-
* Run: node --test scripts/daemon/execution-ladder.test.mjs
|
|
6
|
-
*
|
|
7
|
-
* `lib/execution/**` had exactly one production importer (lib/goals/loop.mjs,
|
|
8
|
-
* for `routeRung`); `processItem` went from ITEM_RECEIVED straight to the Haiku
|
|
9
|
-
* classifier and never entered the ladder. These tests drive the daemon's OWN
|
|
10
|
-
* entry point — `processItem` — with the network/subprocess collaborators
|
|
11
|
-
* injected, and assert on what lands in `state/execution/journal.jsonl`,
|
|
12
|
-
* `state/queues/inbound.yaml` and the fakes. Nothing about intake, match, route,
|
|
13
|
-
* disposition, drive or journal is faked.
|
|
14
|
-
*
|
|
15
|
-
* The two regression tests at the bottom encode bugs a live run of this wiring
|
|
16
|
-
* actually produced:
|
|
17
|
-
* - with no `need` estimate every DM routed to rung 3, which vetoed the quick
|
|
18
|
-
* reply and turned a 4-second answer into a spawned Claude session;
|
|
19
|
-
* - with the rule-based directedness override applied to ALL ignore reasons,
|
|
20
|
-
* a DM could never be `duplicate` (a DM is always "directed"), so the agent
|
|
21
|
-
* answered every redelivery twice.
|
|
22
|
-
*/
|
|
23
|
-
import { test, before, beforeEach } from "node:test";
|
|
24
|
-
import assert from "node:assert/strict";
|
|
25
|
-
import { register } from "node:module";
|
|
26
|
-
import { mkdtempSync, mkdirSync, writeFileSync, readFileSync, existsSync, rmSync, readdirSync } from "node:fs";
|
|
27
|
-
import { join } from "node:path";
|
|
28
|
-
import { tmpdir } from "node:os";
|
|
29
|
-
|
|
30
|
-
// --- stub the agent-only `imapflow` dep so the daemon module can be imported ---
|
|
31
|
-
register(
|
|
32
|
-
"data:text/javascript," +
|
|
33
|
-
encodeURIComponent(`
|
|
34
|
-
export async function resolve(s, c, next) {
|
|
35
|
-
if (s === "imapflow") return { url: "imapflow-stub:main", shortCircuit: true };
|
|
36
|
-
return next(s, c);
|
|
37
|
-
}
|
|
38
|
-
export async function load(u, c, next) {
|
|
39
|
-
if (u === "imapflow-stub:main") return { format: "module", shortCircuit: true, source: "export class ImapFlow {}" };
|
|
40
|
-
return next(u, c);
|
|
41
|
-
}
|
|
42
|
-
`),
|
|
43
|
-
);
|
|
44
|
-
|
|
45
|
-
const AGENT_DIR = mkdtempSync(join(tmpdir(), "maestro-exec-ladder-"));
|
|
46
|
-
process.env.AGENT_DIR = AGENT_DIR;
|
|
47
|
-
process.env.MAESTRO_DAEMON_NO_AUTOSTART = "1";
|
|
48
|
-
process.env.COHORT_AGENT_ID = "mem_self_0001";
|
|
49
|
-
process.env.ANTHROPIC_API_KEY = "";
|
|
50
|
-
|
|
51
|
-
const PLAN = `version: 1
|
|
52
|
-
enforcement: enforce
|
|
53
|
-
obligations:
|
|
54
|
-
- key: react.message.dm
|
|
55
|
-
kind: REACT
|
|
56
|
-
status: active
|
|
57
|
-
trigger: { topic: message, kind: null, predicate: null }
|
|
58
|
-
uses: []
|
|
59
|
-
allowed_tools: []
|
|
60
|
-
action_classes: []
|
|
61
|
-
offline_safe: true
|
|
62
|
-
- key: react.task.assigned
|
|
63
|
-
kind: REACT
|
|
64
|
-
status: active
|
|
65
|
-
trigger: { topic: task, kind: assigned, predicate: null }
|
|
66
|
-
uses: []
|
|
67
|
-
allowed_tools: []
|
|
68
|
-
action_classes: []
|
|
69
|
-
offline_safe: true
|
|
70
|
-
`;
|
|
71
|
-
|
|
72
|
-
let daemon;
|
|
73
|
-
let responder;
|
|
74
|
-
|
|
75
|
-
before(async () => {
|
|
76
|
-
mkdirSync(join(AGENT_DIR, "config"), { recursive: true });
|
|
77
|
-
writeFileSync(
|
|
78
|
-
join(AGENT_DIR, "config", "agent.json"),
|
|
79
|
-
JSON.stringify({ firstName: "Nova", lastName: "Vale", slackMemberId: "U09NOVA" }),
|
|
80
|
-
);
|
|
81
|
-
writeFileSync(join(AGENT_DIR, "config", "plan.yaml"), PLAN);
|
|
82
|
-
daemon = await import("./agent-daemon.mjs");
|
|
83
|
-
responder = await import("./responder.mjs");
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
beforeEach(() => {
|
|
87
|
-
for (const rel of [["state", "execution"], ["state", "queues"], ["state", "inbox"], ["state", "locks"], ["logs", "daemon"]]) {
|
|
88
|
-
rmSync(join(AGENT_DIR, ...rel), { recursive: true, force: true });
|
|
89
|
-
}
|
|
90
|
-
if (daemon) daemon._resetLadderCaches();
|
|
91
|
-
delete process.env.DAEMON_EXECUTION_LADDER;
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
// --- helpers ---------------------------------------------------------------
|
|
95
|
-
|
|
96
|
-
function writeLive(item, service) {
|
|
97
|
-
const dir = join(AGENT_DIR, "state", "inbox", service);
|
|
98
|
-
mkdirSync(dir, { recursive: true });
|
|
99
|
-
writeFileSync(join(dir, `${item.id}.json`), JSON.stringify(item));
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
/** Fakes for exactly the network / subprocess boundary. Nothing else. */
|
|
103
|
-
function spy(classResult) {
|
|
104
|
-
const seen = { classify: 0, quick: [], dispatch: [], holding: 0 };
|
|
105
|
-
return {
|
|
106
|
-
seen,
|
|
107
|
-
deps: {
|
|
108
|
-
classify: async () => {
|
|
109
|
-
seen.classify += 1;
|
|
110
|
-
return classResult;
|
|
111
|
-
},
|
|
112
|
-
sendQuickResponse: async (item, _cr, routed) => {
|
|
113
|
-
seen.quick.push({ id: item.id, rung: routed ? routed.rung : null });
|
|
114
|
-
return { sent: true, text: "ack", via: "fake" };
|
|
115
|
-
},
|
|
116
|
-
sendHoldingMessage: async () => {
|
|
117
|
-
seen.holding += 1;
|
|
118
|
-
return { sent: false, holdingText: null };
|
|
119
|
-
},
|
|
120
|
-
dispatch: (_p, item, _cr, _s, opts) => {
|
|
121
|
-
seen.dispatch.push({ id: item.id, execution: item.execution || null });
|
|
122
|
-
opts.onClose({ ok: true, code: 0 });
|
|
123
|
-
},
|
|
124
|
-
},
|
|
125
|
-
};
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
function journal() {
|
|
129
|
-
const p = join(AGENT_DIR, "state", "execution", "journal.jsonl");
|
|
130
|
-
if (!existsSync(p)) return [];
|
|
131
|
-
return readFileSync(p, "utf-8").trim().split("\n").filter(Boolean).map((l) => JSON.parse(l));
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
const decisions = () => journal().filter((r) => r.event === "decision");
|
|
135
|
-
const outcomes = () => journal().filter((r) => r.event === "outcome");
|
|
136
|
-
|
|
137
|
-
function dm(over = {}) {
|
|
138
|
-
return {
|
|
139
|
-
id: "MSG-DM",
|
|
140
|
-
raw_ref: "slack:D77:1",
|
|
141
|
-
channel: "dm/priya",
|
|
142
|
-
channel_id: "D77",
|
|
143
|
-
sender: "Priya Raman",
|
|
144
|
-
sender_id: "U_PRIYA",
|
|
145
|
-
// A work-request ("review") so this shared fixture takes the full classify →
|
|
146
|
-
// route path — the fast-path (fastPathClassify) deliberately skips the
|
|
147
|
-
// classifier only for PLAIN short questions; that path has its own test below.
|
|
148
|
-
content: "can you review and confirm the deadline for the launch plan?",
|
|
149
|
-
timestamp: new Date().toISOString(),
|
|
150
|
-
...over,
|
|
151
|
-
};
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
const RESPOND = {
|
|
155
|
-
priority: "normal",
|
|
156
|
-
action: "respond",
|
|
157
|
-
model: "sonnet",
|
|
158
|
-
summary: "deadline question",
|
|
159
|
-
category: "action_required",
|
|
160
|
-
directed_at_agent: true,
|
|
161
|
-
};
|
|
162
|
-
|
|
163
|
-
// ---------------------------------------------------------------------------
|
|
164
|
-
// 1) A DM goes THROUGH the ladder — and is still answered exactly as before.
|
|
165
|
-
// ---------------------------------------------------------------------------
|
|
166
|
-
|
|
167
|
-
test("LADDER: a DM is journalled intake→match→route→outcome AND still quick-replies", async () => {
|
|
168
|
-
const item = dm();
|
|
169
|
-
writeLive(item, "slack");
|
|
170
|
-
const s = spy(RESPOND);
|
|
171
|
-
await daemon.processItem(item, "slack", s.deps);
|
|
172
|
-
|
|
173
|
-
const d = decisions();
|
|
174
|
-
assert.equal(d.length, 1, "one decision row");
|
|
175
|
-
assert.equal(d[0].surface, "dm");
|
|
176
|
-
assert.equal(d[0].disposition, "react_now");
|
|
177
|
-
assert.equal(d[0].reason, "directed_now");
|
|
178
|
-
assert.equal(d[0].obligationKey, "react.message.dm", "match bound the DM to the plan's REACT obligation");
|
|
179
|
-
assert.equal(d[0].rung, 0, "route chose rung 0 — one protocol method with the params in hand");
|
|
180
|
-
assert.equal(d[0].mechanism, "tool-surface");
|
|
181
|
-
assert.ok(d[0].why.some((w) => w.includes("single method messaging.send")), "the why records the rung reasoning");
|
|
182
|
-
|
|
183
|
-
const o = outcomes();
|
|
184
|
-
assert.equal(o.length, 1);
|
|
185
|
-
assert.equal(o[0].effect, "react", "the react effect ran");
|
|
186
|
-
assert.equal(o[0].ok, true);
|
|
187
|
-
assert.equal(o[0].spoke, true, "the agent spoke, so the ping-pong chain deepens");
|
|
188
|
-
assert.equal(o[0].ref.path, "quick_reply", "the effect drove the daemon's real quick-reply path");
|
|
189
|
-
|
|
190
|
-
// NO REGRESSION: the classifier ran and the reply was sent.
|
|
191
|
-
assert.equal(s.seen.classify, 1, "the Haiku classifier still ran for a directed DM");
|
|
192
|
-
assert.deepEqual(s.seen.quick, [{ id: "MSG-DM", rung: 0 }], "the quick reply was sent, carrying its rung");
|
|
193
|
-
assert.equal(s.seen.dispatch.length, 0, "no session spawned for a rung-0 reply");
|
|
194
|
-
});
|
|
195
|
-
|
|
196
|
-
test("LADDER: a plain short question DM FAST-PATHS — quick-replies WITHOUT a classifier call", async () => {
|
|
197
|
-
const item = dm({ id: "MSG-DM-FAST", raw_ref: "slack:D91:1", channel_id: "D91", content: "what's the deadline?" });
|
|
198
|
-
writeLive(item, "slack");
|
|
199
|
-
const s = spy(RESPOND);
|
|
200
|
-
await daemon.processItem(item, "slack", s.deps);
|
|
201
|
-
|
|
202
|
-
// The whole point: the ~5-8s Haiku classify call is SKIPPED for a plain question.
|
|
203
|
-
assert.equal(s.seen.classify, 0, "fast-path: a plain directed question skips the classifier");
|
|
204
|
-
// ...but the DM is still answered via the quick-reply path, still journalled.
|
|
205
|
-
assert.deepEqual(s.seen.quick, [{ id: "MSG-DM-FAST", rung: 0 }], "the quick reply still went out at rung 0");
|
|
206
|
-
assert.equal(s.seen.dispatch.length, 0, "no session spawned");
|
|
207
|
-
assert.ok(decisions().some((r) => r.surface === "dm" && r.disposition === "react_now"), "still journalled intake→route→outcome");
|
|
208
|
-
});
|
|
209
|
-
|
|
210
|
-
// ---------------------------------------------------------------------------
|
|
211
|
-
// 2) A board task is WORK: it queues, and never reaches the classifier.
|
|
212
|
-
// ---------------------------------------------------------------------------
|
|
213
|
-
|
|
214
|
-
test("LADDER: an assigned board task is queued to state/queues/inbound.yaml, not answered", async () => {
|
|
215
|
-
const task = {
|
|
216
|
-
id: "cohort-task_assigned-149",
|
|
217
|
-
raw_ref: "cohort:task_assigned:cmql3j5j301jwhhv2ipp8gmir:149",
|
|
218
|
-
service: "cohort",
|
|
219
|
-
kind: "task_assigned",
|
|
220
|
-
sender: "Priya Raman",
|
|
221
|
-
sender_id: "mem_priya_0002",
|
|
222
|
-
content: "Ship the Q3 revenue reconciliation",
|
|
223
|
-
channel_id: "board_main",
|
|
224
|
-
timestamp: new Date().toISOString(),
|
|
225
|
-
};
|
|
226
|
-
writeLive(task, "cohort");
|
|
227
|
-
const s = spy(RESPOND);
|
|
228
|
-
await daemon.processItem(task, "cohort", s.deps);
|
|
229
|
-
|
|
230
|
-
const d = decisions()[0];
|
|
231
|
-
assert.equal(d.surface, "task_assigned");
|
|
232
|
-
assert.equal(d.disposition, "schedule");
|
|
233
|
-
assert.equal(d.reason, "not_respondable", "a board assignment is work, not conversation");
|
|
234
|
-
assert.equal(d.obligationKey, "react.task.assigned");
|
|
235
|
-
assert.equal(d.rung, 3, "no lower predicate holds for open work → a bounded session");
|
|
236
|
-
|
|
237
|
-
const o = outcomes()[0];
|
|
238
|
-
assert.equal(o.effect, "schedule");
|
|
239
|
-
assert.equal(o.ok, true);
|
|
240
|
-
|
|
241
|
-
// The queue row is real, and it is the file `sweepBacklog` already reads.
|
|
242
|
-
const q = readFileSync(join(AGENT_DIR, "state", "queues", "inbound.yaml"), "utf-8");
|
|
243
|
-
assert.match(q, /id: "inb-board\.task_assigned#149"/);
|
|
244
|
-
assert.match(q, /rung: 3/);
|
|
245
|
-
assert.match(q, /obligation_key: "react\.task\.assigned"/);
|
|
246
|
-
assert.match(q, /why: ".*not a conversational surface/, "the reasoning travels with the work");
|
|
247
|
-
|
|
248
|
-
assert.equal(s.seen.classify, 0, "no Haiku call — the ladder placed it without one");
|
|
249
|
-
assert.equal(s.seen.quick.length, 0);
|
|
250
|
-
assert.equal(s.seen.dispatch.length, 0);
|
|
251
|
-
});
|
|
252
|
-
|
|
253
|
-
// ---------------------------------------------------------------------------
|
|
254
|
-
// 3) `ignore` is first-class: journalled, cheap, and it does not call Haiku.
|
|
255
|
-
// ---------------------------------------------------------------------------
|
|
256
|
-
|
|
257
|
-
test("LADDER: ambient channel chatter is IGNORED, with a journalled reason, and costs no classifier call", async () => {
|
|
258
|
-
const ambient = {
|
|
259
|
-
id: "MSG-AMBIENT",
|
|
260
|
-
raw_ref: "slack:C90:1",
|
|
261
|
-
channel: "#engineering",
|
|
262
|
-
channel_id: "C90",
|
|
263
|
-
sender: "Tomas Lind",
|
|
264
|
-
sender_id: "U_TOMAS",
|
|
265
|
-
content: "lunch anyone",
|
|
266
|
-
timestamp: new Date().toISOString(),
|
|
267
|
-
};
|
|
268
|
-
writeLive(ambient, "slack");
|
|
269
|
-
const s = spy(RESPOND);
|
|
270
|
-
await daemon.processItem(ambient, "slack", s.deps);
|
|
271
|
-
|
|
272
|
-
const d = decisions()[0];
|
|
273
|
-
assert.equal(d.disposition, "ignore");
|
|
274
|
-
assert.equal(d.reason, "ambient_channel");
|
|
275
|
-
assert.ok(d.why.some((w) => w.includes("park for the ambient sweep")), "the ignore explains itself");
|
|
276
|
-
|
|
277
|
-
const o = outcomes()[0];
|
|
278
|
-
assert.equal(o.effect, "none", "an ignore is a completed decision with no effect");
|
|
279
|
-
assert.equal(o.ok, true, "an ignore is a SUCCESS, not a failure");
|
|
280
|
-
assert.equal(o.spoke, false);
|
|
281
|
-
|
|
282
|
-
assert.equal(s.seen.classify, 0, "an ambient message no longer costs a Haiku call");
|
|
283
|
-
assert.equal(s.seen.quick.length, 0);
|
|
284
|
-
assert.equal(s.seen.dispatch.length, 0);
|
|
285
|
-
|
|
286
|
-
// It is finished with, not left to be re-polled forever.
|
|
287
|
-
const files = readdirSync(join(AGENT_DIR, "state", "inbox", "slack"));
|
|
288
|
-
assert.deepEqual(files, ["MSG-AMBIENT.json.processed"]);
|
|
289
|
-
});
|
|
290
|
-
|
|
291
|
-
// ---------------------------------------------------------------------------
|
|
292
|
-
// 4) REQUIREMENT: an event no obligation covers still reaches today's path.
|
|
293
|
-
// ---------------------------------------------------------------------------
|
|
294
|
-
|
|
295
|
-
test("LADDER: an event no obligation covers is still answered, capped at rung ≤1, and proposes the missing obligation", async () => {
|
|
296
|
-
rmSync(join(AGENT_DIR, "config", "plan.yaml"), { force: true });
|
|
297
|
-
daemon._resetLadderCaches();
|
|
298
|
-
try {
|
|
299
|
-
const item = dm({ id: "MSG-DM-UNCOVERED", raw_ref: "slack:D78:1", channel_id: "D78" });
|
|
300
|
-
writeLive(item, "slack");
|
|
301
|
-
const s = spy(RESPOND);
|
|
302
|
-
await daemon.processItem(item, "slack", s.deps);
|
|
303
|
-
|
|
304
|
-
const d = decisions()[0];
|
|
305
|
-
assert.equal(d.disposition, "react_now", "an uncovered event is still answered");
|
|
306
|
-
assert.equal(d.obligationKey, null, "…but it is recorded as uncovered");
|
|
307
|
-
assert.ok(d.rung <= 1, `uncovered work is capped at rung ≤1, got ${d.rung}`);
|
|
308
|
-
assert.ok(
|
|
309
|
-
d.why.some((w) => w.includes("no obligation in the compiled plan covers this event")),
|
|
310
|
-
"the plan gap is stated in the reasoning",
|
|
311
|
-
);
|
|
312
|
-
assert.equal(s.seen.quick.length, 1, "the sender still got an answer");
|
|
313
|
-
|
|
314
|
-
// §6.1's other half: the gap becomes a proposal, not just a complaint.
|
|
315
|
-
const proposed = join(AGENT_DIR, "state", "plan", "proposed.jsonl");
|
|
316
|
-
assert.ok(existsSync(proposed), "an obligation was proposed to cover the gap");
|
|
317
|
-
assert.match(readFileSync(proposed, "utf-8"), /react\.message\./);
|
|
318
|
-
} finally {
|
|
319
|
-
writeFileSync(join(AGENT_DIR, "config", "plan.yaml"), PLAN);
|
|
320
|
-
daemon._resetLadderCaches();
|
|
321
|
-
}
|
|
322
|
-
});
|
|
323
|
-
|
|
324
|
-
// ---------------------------------------------------------------------------
|
|
325
|
-
// 5) REGRESSION (live run): the rule override must not defeat the GUARDS.
|
|
326
|
-
// ---------------------------------------------------------------------------
|
|
327
|
-
|
|
328
|
-
test("LADDER REGRESSION: a redelivered DM is dropped as `duplicate` and is NOT answered twice", async () => {
|
|
329
|
-
const item = dm({ id: "MSG-DM-DUP", raw_ref: "slack:D79:1", channel_id: "D79" });
|
|
330
|
-
writeLive(item, "slack");
|
|
331
|
-
|
|
332
|
-
const first = spy(RESPOND);
|
|
333
|
-
await daemon.processItem(item, "slack", first.deps);
|
|
334
|
-
assert.equal(first.seen.quick.length, 1, "the first delivery is answered");
|
|
335
|
-
|
|
336
|
-
// The SSE push and the poll backstop converge — that convergence is the design.
|
|
337
|
-
const again = { ...item };
|
|
338
|
-
writeLive(again, "slack");
|
|
339
|
-
const second = spy(RESPOND);
|
|
340
|
-
await daemon.processItem(again, "slack", second.deps);
|
|
341
|
-
|
|
342
|
-
const d = decisions();
|
|
343
|
-
assert.equal(d.length, 2);
|
|
344
|
-
assert.equal(d[1].disposition, "ignore");
|
|
345
|
-
assert.equal(d[1].reason, "duplicate", "the dedupe guard fired");
|
|
346
|
-
|
|
347
|
-
// The bug this encodes: `isDirectedAtAgent` returns true for ANY DM, so
|
|
348
|
-
// consulting it on every ignore reason made `duplicate` unreachable and the
|
|
349
|
-
// agent answered both deliveries.
|
|
350
|
-
assert.equal(second.seen.quick.length, 0, "the redelivery was NOT answered");
|
|
351
|
-
assert.equal(second.seen.classify, 0, "…and did not even reach the classifier");
|
|
352
|
-
});
|
|
353
|
-
|
|
354
|
-
// ---------------------------------------------------------------------------
|
|
355
|
-
// 6) …but a directedness MISS must still fall through. Silence is the expensive
|
|
356
|
-
// failure, so the ladder never has the last word on "is this mine".
|
|
357
|
-
// ---------------------------------------------------------------------------
|
|
358
|
-
|
|
359
|
-
test("LADDER: a directedness disagreement falls through to the classifier path rather than gagging the agent", async () => {
|
|
360
|
-
// Ambient by intake's rules (no mention flag, not a DM, not a thread reply),
|
|
361
|
-
// but the agent's own name is in the text — which `isDirectedAtAgent` catches
|
|
362
|
-
// and intake does not.
|
|
363
|
-
const item = {
|
|
364
|
-
id: "MSG-NAMED",
|
|
365
|
-
raw_ref: "slack:C91:1",
|
|
366
|
-
channel: "#engineering",
|
|
367
|
-
channel_id: "C91",
|
|
368
|
-
sender: "Tomas Lind",
|
|
369
|
-
sender_id: "U_TOMAS",
|
|
370
|
-
content: "nova can you take the reconciliation?",
|
|
371
|
-
timestamp: new Date().toISOString(),
|
|
372
|
-
};
|
|
373
|
-
writeLive(item, "slack");
|
|
374
|
-
const s = spy(RESPOND);
|
|
375
|
-
const gate = await daemon.runExecutionLadder(item, "slack", item.raw_ref, "trace-x", s.deps);
|
|
376
|
-
|
|
377
|
-
assert.equal(gate.handled, false, "the ladder did not take responsibility");
|
|
378
|
-
assert.equal(gate.reason, "rule_override");
|
|
379
|
-
assert.equal(decisions()[0].disposition, "ignore", "the ignore is still journalled — the disagreement is on record");
|
|
380
|
-
});
|
|
381
|
-
|
|
382
|
-
// ---------------------------------------------------------------------------
|
|
383
|
-
// 7) The rung is a VETO on the quick path, never a promotion.
|
|
384
|
-
// ---------------------------------------------------------------------------
|
|
385
|
-
|
|
386
|
-
test("RUNG: the quick-reply responder accepts rungs 0-1 and refuses 2+", async () => {
|
|
387
|
-
assert.equal(responder.rungPermitsQuickReply(null), true, "no routing decision → today's behaviour");
|
|
388
|
-
assert.equal(responder.rungPermitsQuickReply({ rung: 0 }), true);
|
|
389
|
-
assert.equal(responder.rungPermitsQuickReply({ rung: 1 }), true);
|
|
390
|
-
assert.equal(responder.rungPermitsQuickReply({ rung: 2 }), false);
|
|
391
|
-
assert.equal(responder.rungPermitsQuickReply({ rung: 3 }), false);
|
|
392
|
-
assert.equal(responder.rungPermitsQuickReply({ rung: 5 }), false, "no subagent-fanout mechanism exists here");
|
|
393
|
-
|
|
394
|
-
// A veto, not a promotion: rung 0 does not make research quick-repliable.
|
|
395
|
-
assert.equal(responder.isQuickReply({ action: "research", priority: "normal", model: "sonnet" }, { rung: 0 }), false);
|
|
396
|
-
assert.equal(responder.isQuickReply({ action: "respond", priority: "normal", model: "sonnet" }, { rung: 0 }), true);
|
|
397
|
-
assert.equal(
|
|
398
|
-
responder.isQuickReply({ action: "respond", priority: "normal", model: "sonnet" }, { rung: 3 }),
|
|
399
|
-
false,
|
|
400
|
-
"a rung-3 answer needs a session even when the classifier called it simple",
|
|
401
|
-
);
|
|
402
|
-
});
|
|
403
|
-
|
|
404
|
-
test("ANSWERABLE: a directly-answerable question is quick-replied, not sessioned + acked", () => {
|
|
405
|
-
// "what machine are you running on?" from the CEO classifies critical/opus,
|
|
406
|
-
// which would otherwise force a session and a robotic holding ack. Flagged
|
|
407
|
-
// answerable, it is answered in the turn instead — the defect being fixed.
|
|
408
|
-
assert.equal(
|
|
409
|
-
responder.isQuickReply({ action: "respond", priority: "critical", model: "opus", answerable: true }),
|
|
410
|
-
true,
|
|
411
|
-
"an answerable question skips the session even at critical/opus",
|
|
412
|
-
);
|
|
413
|
-
// The flag only fast-paths a `respond` — a draft/research item stays on the
|
|
414
|
-
// session path even if the classifier also (contradictorily) flags it.
|
|
415
|
-
assert.equal(responder.isQuickReply({ action: "draft", priority: "high", model: "opus", answerable: true }), false);
|
|
416
|
-
assert.equal(responder.isQuickReply({ action: "research", priority: "normal", model: "sonnet", answerable: true }), false);
|
|
417
|
-
// And the rung veto still wins over answerable.
|
|
418
|
-
assert.equal(
|
|
419
|
-
responder.isQuickReply({ action: "respond", priority: "critical", model: "opus", answerable: true }, { rung: 3 }),
|
|
420
|
-
false,
|
|
421
|
-
"a rung-3 answer needs a session even when the classifier called it answerable",
|
|
422
|
-
);
|
|
423
|
-
// Without the flag, a critical/opus item still takes the session path.
|
|
424
|
-
assert.equal(responder.isQuickReply({ action: "respond", priority: "critical", model: "opus" }), false);
|
|
425
|
-
});
|
|
426
|
-
|
|
427
|
-
// ---------------------------------------------------------------------------
|
|
428
|
-
// 8) REGRESSION (live run): with no need estimate every DM routed to rung 3,
|
|
429
|
-
// which vetoed the quick reply and spawned a session for a two-line message.
|
|
430
|
-
// ---------------------------------------------------------------------------
|
|
431
|
-
|
|
432
|
-
test("LADDER REGRESSION: the need estimate marks a resolvable reply target as params-known", () => {
|
|
433
|
-
assert.equal(daemon.ladderNeed({ channel_id: "D1" }).paramsKnown, true, "a resolvable channel → rung 0 is available");
|
|
434
|
-
assert.equal(daemon.ladderNeed({ raw_ref: "cohort:dm:x:1" }).paramsKnown, true);
|
|
435
|
-
assert.equal(
|
|
436
|
-
daemon.ladderNeed({ sender: "someone" }).paramsKnown,
|
|
437
|
-
false,
|
|
438
|
-
"no resolvable target → params are NOT known and the ladder walks up to a session that can find it",
|
|
439
|
-
);
|
|
440
|
-
assert.equal(daemon.ladderNeed({ channel_id: "D1" }).steps, 1);
|
|
441
|
-
});
|
|
442
|
-
|
|
443
|
-
// ---------------------------------------------------------------------------
|
|
444
|
-
// 9) The escape hatch preserves the old behaviour exactly — and says so.
|
|
445
|
-
// ---------------------------------------------------------------------------
|
|
446
|
-
|
|
447
|
-
test("LADDER: DAEMON_EXECUTION_LADDER=0 restores the pre-ladder path and warns about what goes dark", async () => {
|
|
448
|
-
process.env.DAEMON_EXECUTION_LADDER = "0";
|
|
449
|
-
daemon._resetLadderCaches();
|
|
450
|
-
const warnings = [];
|
|
451
|
-
const realWarn = console.warn;
|
|
452
|
-
console.warn = (...a) => warnings.push(a.join(" "));
|
|
453
|
-
try {
|
|
454
|
-
const item = dm({ id: "MSG-DM-OFF", raw_ref: "slack:D80:1", channel_id: "D80" });
|
|
455
|
-
writeLive(item, "slack");
|
|
456
|
-
const s = spy(RESPOND);
|
|
457
|
-
await daemon.processItem(item, "slack", s.deps);
|
|
458
|
-
|
|
459
|
-
assert.equal(journal().length, 0, "nothing is journalled with the ladder off");
|
|
460
|
-
assert.equal(s.seen.classify, 1, "the classifier path is untouched");
|
|
461
|
-
assert.equal(s.seen.quick.length, 1, "the DM is still answered");
|
|
462
|
-
assert.ok(
|
|
463
|
-
warnings.some((w) => w.includes("DAEMON_EXECUTION_LADDER=0") && w.includes("no record of what was ignored")),
|
|
464
|
-
"turning it off is never silent",
|
|
465
|
-
);
|
|
466
|
-
} finally {
|
|
467
|
-
console.warn = realWarn;
|
|
468
|
-
delete process.env.DAEMON_EXECUTION_LADDER;
|
|
469
|
-
}
|
|
470
|
-
});
|