@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,471 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* messaging.test.mjs — the SP10 org messaging seam: send-gate enforcement,
|
|
3
|
-
* wire shape + idempotency header, afterSend hook + source="messaging"
|
|
4
|
-
* attribution, calling, and the inbound projection. Injected client/fetch +
|
|
5
|
-
* send-gate + hook bus + ledger, so no network and no disk writes leak.
|
|
6
|
-
* Run: node --test lib/org/messaging.test.mjs
|
|
7
|
-
*/
|
|
8
|
-
"use strict";
|
|
9
|
-
|
|
10
|
-
import { test } from "node:test";
|
|
11
|
-
import assert from "node:assert/strict";
|
|
12
|
-
|
|
13
|
-
import {
|
|
14
|
-
sendMessage,
|
|
15
|
-
reactMessage,
|
|
16
|
-
fetchHistory,
|
|
17
|
-
listChannels,
|
|
18
|
-
openDm,
|
|
19
|
-
openConversation,
|
|
20
|
-
createChannel,
|
|
21
|
-
addChannelMember,
|
|
22
|
-
initiateCall,
|
|
23
|
-
joinCall,
|
|
24
|
-
pullInbound,
|
|
25
|
-
isDirectedAtAgent,
|
|
26
|
-
toMessageEvent,
|
|
27
|
-
} from "./messaging.mjs";
|
|
28
|
-
import { PROTOCOL_VERSION } from "./protocol.mjs";
|
|
29
|
-
|
|
30
|
-
const BASE = "https://org.cohortapp.com";
|
|
31
|
-
const TOKEN = "tok-msg";
|
|
32
|
-
const CFG = { org: { cohort: { enabled: true, base: BASE, token: TOKEN, agentId: "agent-1" } } };
|
|
33
|
-
|
|
34
|
-
delete process.env.COHORT_API_TOKEN;
|
|
35
|
-
delete process.env.COHORT_TOKEN;
|
|
36
|
-
delete process.env.MESSAGING_INBOUND_ESCALATE;
|
|
37
|
-
|
|
38
|
-
/** A fake fetch that records calls and returns a canned res frame (mirrors client.test). */
|
|
39
|
-
function fakeFetch(responder) {
|
|
40
|
-
const calls = [];
|
|
41
|
-
const impl = async (url, init) => {
|
|
42
|
-
calls.push({ url, init, headers: (init && init.headers) || {}, body: init && init.body ? JSON.parse(init.body) : null });
|
|
43
|
-
const r = responder ? responder(url, init, calls.length - 1) : null;
|
|
44
|
-
const status = r && typeof r.status === "number" ? r.status : 200;
|
|
45
|
-
const ok = r && typeof r.ok === "boolean" ? r.ok : status >= 200 && status < 300;
|
|
46
|
-
const body = r ? r.body : { ok: true, result: null };
|
|
47
|
-
return { ok, status, headers: { get: () => undefined }, json: async () => body };
|
|
48
|
-
};
|
|
49
|
-
impl.calls = calls;
|
|
50
|
-
return impl;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/** Allow-all send-gate (the default screen would also allow benign text). */
|
|
54
|
-
const allowGate = async () => ({ allow: true, reason: "" });
|
|
55
|
-
/** A capturing ledger so we can assert source="messaging" without touching disk. */
|
|
56
|
-
function captureLedger() {
|
|
57
|
-
const rows = [];
|
|
58
|
-
const writeLedgerRow = (_catalog, entry) => { rows.push(entry); return { row: entry, written: true, path: "" }; };
|
|
59
|
-
writeLedgerRow.rows = rows;
|
|
60
|
-
return writeLedgerRow;
|
|
61
|
-
}
|
|
62
|
-
/** A hook bus stub that records afterSend emits. */
|
|
63
|
-
function captureBus() {
|
|
64
|
-
const emits = [];
|
|
65
|
-
return { emits, emit: async (ev, ctx) => { emits.push({ ev, ctx }); return { cancelled: false }; } };
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// --- send-gate enforcement (the load-bearing invariant) --------------------
|
|
69
|
-
|
|
70
|
-
test("sendMessage: a banned-phrase message is BLOCKED by the send-gate and NEVER sent", async () => {
|
|
71
|
-
const f = fakeFetch(() => ({ body: { ok: true, result: { id: "m1" } } }));
|
|
72
|
-
// Real send-gate (no gateImpl): "I'm here to help" is a banned AI-tell.
|
|
73
|
-
const frame = await sendMessage(
|
|
74
|
-
{ channel: "C-eng", body: "Hi — I'm here to help with the deploy." },
|
|
75
|
-
{ cfg: CFG, fetchImpl: f, ledgerImpl: null }
|
|
76
|
-
);
|
|
77
|
-
assert.equal(frame.ok, false, "blocked → not ok");
|
|
78
|
-
assert.equal(frame.blocked, true, "marked blocked (policy refusal, not transport)");
|
|
79
|
-
assert.match(frame.error.message, /banned/i);
|
|
80
|
-
assert.equal(f.calls.length, 0, "NO messaging.send was issued for a blocked message");
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
test("sendMessage: a clean message posts to /v1/messaging.send with Bearer + protocol + idempotency", async () => {
|
|
84
|
-
const f = fakeFetch(() => ({ body: { ok: true, result: { id: "m2", ts: "t" } } }));
|
|
85
|
-
const ledger = captureLedger();
|
|
86
|
-
const bus = captureBus();
|
|
87
|
-
const frame = await sendMessage(
|
|
88
|
-
{ channel: "C-eng", body: "Deploy is green on staging.", mentions: ["agent-2"], idempotencyId: "msg-xyz" },
|
|
89
|
-
{ cfg: CFG, fetchImpl: f, gateImpl: allowGate, hookBus: bus, ledgerImpl: ledger }
|
|
90
|
-
);
|
|
91
|
-
assert.equal(frame.ok, true);
|
|
92
|
-
assert.equal(f.calls.length, 1);
|
|
93
|
-
assert.match(f.calls[0].url, /\/v1\/messaging\.send$/);
|
|
94
|
-
assert.equal(f.calls[0].headers.authorization, `Bearer ${TOKEN}`);
|
|
95
|
-
assert.equal(f.calls[0].headers["x-org-protocol"], String(PROTOCOL_VERSION));
|
|
96
|
-
assert.equal(f.calls[0].headers["x-idempotency-key"], "msg-xyz", "idempotent on the client message id");
|
|
97
|
-
// wire shape
|
|
98
|
-
assert.equal(f.calls[0].body.channel, "C-eng");
|
|
99
|
-
assert.equal(f.calls[0].body.body, "Deploy is green on staging.");
|
|
100
|
-
// `[{memberId}]`, NOT `["agent-2"]`. hq's mentionSchema is an object schema,
|
|
101
|
-
// so the bare-string form this used to assert did not merely lose the tag — it
|
|
102
|
-
// failed zod and 400'd the entire send. The one call an agent makes to bring a
|
|
103
|
-
// colleague in was the one call guaranteed to fail.
|
|
104
|
-
assert.deepEqual(f.calls[0].body.mentions, [{ memberId: "agent-2" }]);
|
|
105
|
-
assert.equal(f.calls[0].body.id, "msg-xyz");
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
test("sendMessage: mentions accept strings or objects, de-dupe, and keep offsets", async () => {
|
|
109
|
-
const f = fakeFetch(() => ({ body: { ok: true, result: { id: "m2b" } } }));
|
|
110
|
-
await sendMessage(
|
|
111
|
-
{
|
|
112
|
-
channel: "C-eng",
|
|
113
|
-
body: "hi",
|
|
114
|
-
mentions: ["agent-2", { memberId: "agent-2" }, { id: "agent-3" }, { memberId: "agent-4", offset: 7 }, "", null],
|
|
115
|
-
idempotencyId: "msg-m",
|
|
116
|
-
},
|
|
117
|
-
{ cfg: CFG, fetchImpl: f, gateImpl: allowGate, hookBus: captureBus(), ledgerImpl: captureLedger() },
|
|
118
|
-
);
|
|
119
|
-
assert.deepEqual(f.calls[0].body.mentions, [
|
|
120
|
-
{ memberId: "agent-2" },
|
|
121
|
-
{ memberId: "agent-3" },
|
|
122
|
-
{ memberId: "agent-4", offset: 7 },
|
|
123
|
-
]);
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
test("sendMessage: no mentions means the key is absent, not an empty array", async () => {
|
|
127
|
-
const f = fakeFetch(() => ({ body: { ok: true, result: { id: "m2c" } } }));
|
|
128
|
-
await sendMessage(
|
|
129
|
-
{ channel: "C-eng", body: "hi", mentions: [] },
|
|
130
|
-
{ cfg: CFG, fetchImpl: f, gateImpl: allowGate, hookBus: captureBus(), ledgerImpl: captureLedger() },
|
|
131
|
-
);
|
|
132
|
-
assert.equal("mentions" in f.calls[0].body, false);
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
test("sendMessage: emits afterSend + attributes a source='messaging' ledger row on success", async () => {
|
|
136
|
-
const f = fakeFetch(() => ({ body: { ok: true, result: { id: "m3" } } }));
|
|
137
|
-
const ledger = captureLedger();
|
|
138
|
-
const bus = captureBus();
|
|
139
|
-
await sendMessage(
|
|
140
|
-
{ channel: "C-ops", body: "Status nominal." },
|
|
141
|
-
{ cfg: CFG, agentRoot: "/tmp/agent", fetchImpl: f, gateImpl: allowGate, hookBus: bus, ledgerImpl: ledger }
|
|
142
|
-
);
|
|
143
|
-
assert.equal(bus.emits.length, 1, "one afterSend emit");
|
|
144
|
-
assert.equal(bus.emits[0].ev, "afterSend");
|
|
145
|
-
assert.equal(bus.emits[0].ctx.source, "messaging");
|
|
146
|
-
assert.equal(ledger.rows.length, 1, "one ledger row");
|
|
147
|
-
assert.equal(ledger.rows[0].source, "messaging");
|
|
148
|
-
assert.equal(ledger.rows[0].inputTokens, 0, "zero-token attribution row (not LLM spend)");
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
test("sendMessage: redactedText from the gate is what hits the wire", async () => {
|
|
152
|
-
const f = fakeFetch(() => ({ body: { ok: true, result: { id: "m4" } } }));
|
|
153
|
-
const redactGate = async () => ({ allow: true, reason: "", redactedText: "REDACTED" });
|
|
154
|
-
await sendMessage({ channel: "C", body: "secret" }, { cfg: CFG, fetchImpl: f, gateImpl: redactGate, ledgerImpl: null });
|
|
155
|
-
assert.equal(f.calls[0].body.body, "REDACTED");
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
test("sendMessage: missing channel/body → BAD_REQUEST and never touches the gate or wire", async () => {
|
|
159
|
-
const f = fakeFetch();
|
|
160
|
-
assert.equal((await sendMessage({ body: "x" }, { cfg: CFG, fetchImpl: f })).error.code, "BAD_REQUEST");
|
|
161
|
-
assert.equal((await sendMessage({ channel: "C" }, { cfg: CFG, fetchImpl: f })).error.code, "BAD_REQUEST");
|
|
162
|
-
assert.equal(f.calls.length, 0);
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
test("sendMessage: org disabled → fail-open error frame, gate ran but no wire", async () => {
|
|
166
|
-
const f = fakeFetch();
|
|
167
|
-
const frame = await sendMessage({ channel: "C", body: "hello team" }, { cfg: { org: { cohort: { enabled: false } } }, fetchImpl: f, gateImpl: allowGate, ledgerImpl: null });
|
|
168
|
-
assert.equal(frame.ok, false);
|
|
169
|
-
assert.equal(f.calls.length, 0, "no wire when org is disabled");
|
|
170
|
-
});
|
|
171
|
-
|
|
172
|
-
test("sendMessage: a gate that throws fails CLOSED (no send)", async () => {
|
|
173
|
-
const f = fakeFetch();
|
|
174
|
-
const boom = async () => { throw new Error("gate down"); };
|
|
175
|
-
const frame = await sendMessage({ channel: "C", body: "hello" }, { cfg: CFG, fetchImpl: f, gateImpl: boom, ledgerImpl: null });
|
|
176
|
-
assert.equal(frame.ok, false);
|
|
177
|
-
assert.equal(frame.blocked, true);
|
|
178
|
-
assert.equal(f.calls.length, 0);
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
// --- react / history / channels --------------------------------------------
|
|
182
|
-
|
|
183
|
-
test("reactMessage: posts messaging.react and attributes the action", async () => {
|
|
184
|
-
const f = fakeFetch(() => ({ body: { ok: true, result: null } }));
|
|
185
|
-
const ledger = captureLedger();
|
|
186
|
-
const frame = await reactMessage({ channel: "C", messageId: "m1", emoji: "eyes" }, { cfg: CFG, fetchImpl: f, ledgerImpl: ledger });
|
|
187
|
-
assert.equal(frame.ok, true);
|
|
188
|
-
assert.match(f.calls[0].url, /messaging\.react$/);
|
|
189
|
-
assert.equal(ledger.rows[0].source, "messaging");
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
test("fetchHistory: returns the messages array (fail-open [] on error)", async () => {
|
|
193
|
-
const f = fakeFetch(() => ({ body: { ok: true, result: { messages: [{ id: "a" }, { id: "b" }] } } }));
|
|
194
|
-
const msgs = await fetchHistory({ channel: "C", limit: 10 }, { cfg: CFG, fetchImpl: f });
|
|
195
|
-
assert.equal(msgs.length, 2);
|
|
196
|
-
assert.equal(f.calls[0].body.limit, 10);
|
|
197
|
-
const bad = fakeFetch(() => ({ ok: false, status: 500, body: { ok: false, error: { code: "INTERNAL" } } }));
|
|
198
|
-
assert.deepEqual(await fetchHistory({ channel: "C" }, { cfg: CFG, fetchImpl: bad }), []);
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
test("listChannels: returns the channels array (fail-open [])", async () => {
|
|
202
|
-
const f = fakeFetch(() => ({ body: { ok: true, result: { channels: [{ id: "C1" }] } } }));
|
|
203
|
-
assert.deepEqual(await listChannels({ cfg: CFG, fetchImpl: f }), [{ id: "C1" }]);
|
|
204
|
-
assert.deepEqual(await listChannels({ cfg: { org: { cohort: { enabled: false } } } }), []);
|
|
205
|
-
});
|
|
206
|
-
|
|
207
|
-
// --- calling ----------------------------------------------------------------
|
|
208
|
-
|
|
209
|
-
test("initiateCall: posts calling.start with invitees + attributes the action", async () => {
|
|
210
|
-
const f = fakeFetch(() => ({ body: { ok: true, result: { callId: "call-1" } } }));
|
|
211
|
-
const ledger = captureLedger();
|
|
212
|
-
const frame = await initiateCall({ channel: "C", invitees: ["agent-2"], topic: "sync", idempotencyId: "c1" }, { cfg: CFG, fetchImpl: f, ledgerImpl: ledger });
|
|
213
|
-
assert.equal(frame.ok, true);
|
|
214
|
-
assert.match(f.calls[0].url, /calling\.start$/);
|
|
215
|
-
assert.equal(f.calls[0].headers["x-idempotency-key"], "c1");
|
|
216
|
-
assert.deepEqual(f.calls[0].body.invitees, ["agent-2"]);
|
|
217
|
-
assert.equal(ledger.rows[0].source, "messaging");
|
|
218
|
-
});
|
|
219
|
-
|
|
220
|
-
test("joinCall: posts calling.join (missing callId → BAD_REQUEST)", async () => {
|
|
221
|
-
const f = fakeFetch(() => ({ body: { ok: true, result: null } }));
|
|
222
|
-
assert.equal((await joinCall({}, { cfg: CFG, fetchImpl: f })).error.code, "BAD_REQUEST");
|
|
223
|
-
const frame = await joinCall({ callId: "call-1" }, { cfg: CFG, fetchImpl: f, ledgerImpl: null });
|
|
224
|
-
assert.equal(frame.ok, true);
|
|
225
|
-
assert.match(f.calls[0].url, /calling\.join$/);
|
|
226
|
-
});
|
|
227
|
-
|
|
228
|
-
// --- inbound projection -----------------------------------------------------
|
|
229
|
-
|
|
230
|
-
test("isDirectedAtAgent: @mention / DM-to / invitee = directed; own echo / ambient = not", () => {
|
|
231
|
-
assert.equal(isDirectedAtAgent({ family: "messaging", payload: { from: "x", mentions: ["me"], body: "hi" } }, "me"), true);
|
|
232
|
-
assert.equal(isDirectedAtAgent({ family: "messaging", payload: { from: "x", to: ["me"], kind: "dm", body: "hi" } }, "me"), true);
|
|
233
|
-
assert.equal(isDirectedAtAgent({ family: "calling", payload: { from: "x", invitees: ["me"], topic: "t" } }, "me"), true);
|
|
234
|
-
assert.equal(isDirectedAtAgent({ family: "messaging", payload: { from: "me", mentions: ["me"], body: "echo" } }, "me"), false, "own echo ignored");
|
|
235
|
-
assert.equal(isDirectedAtAgent({ family: "messaging", payload: { from: "x", body: "ambient channel chatter" } }, "me"), false);
|
|
236
|
-
assert.equal(isDirectedAtAgent({ family: "board", payload: {} }, "me"), false, "non-messaging family ignored");
|
|
237
|
-
});
|
|
238
|
-
|
|
239
|
-
test("toMessageEvent: projects a messaging event onto the MessageEvent contract", () => {
|
|
240
|
-
const ev = toMessageEvent({ family: "messaging", seq: 7, at: "2026-06-12T00:00:00Z", payload: { id: "m9", channel: "C-eng", from: "agent-2", fromName: "Robin", body: "ping", mentions: ["me"] } }, "me");
|
|
241
|
-
assert.equal(ev.channel, "cohort");
|
|
242
|
-
assert.equal(ev.message_id, "m9");
|
|
243
|
-
assert.equal(ev.text, "ping");
|
|
244
|
-
assert.equal(ev.channel_id, "C-eng");
|
|
245
|
-
assert.equal(ev.from.id, "agent-2");
|
|
246
|
-
assert.equal(ev.kind, "message");
|
|
247
|
-
assert.equal(ev.priority_signals.mentions_agent, true);
|
|
248
|
-
assert.equal(ev.ingest_source, "cohort");
|
|
249
|
-
});
|
|
250
|
-
|
|
251
|
-
test("toMessageEvent: projects channelKind so the daemon can tell a DM from a GROUP_DM", () => {
|
|
252
|
-
const dm = toMessageEvent({ family: "messaging", seq: 8, payload: { id: "m1", channel: "dm-x", channelKind: "DM", from: "human-1", body: "hi" } }, "me");
|
|
253
|
-
assert.equal(dm.channel_kind, "DM", "a 1:1 DM carries channel_kind DM (→ is_dm true downstream)");
|
|
254
|
-
const group = toMessageEvent({ family: "messaging", seq: 9, payload: { id: "m2", channel: "gdm-x", channelKind: "group_dm", from: "human-1", body: "hi all" } }, "me");
|
|
255
|
-
assert.equal(group.channel_kind, "GROUP_DM", "a GROUP_DM is upper-cased and NOT a 1:1 DM downstream");
|
|
256
|
-
});
|
|
257
|
-
|
|
258
|
-
test("pullInbound: filters to directed events and advances the cursor to max seq", async () => {
|
|
259
|
-
const f = fakeFetch(() => ({
|
|
260
|
-
body: {
|
|
261
|
-
events: [
|
|
262
|
-
{ family: "messaging", seq: 10, payload: { id: "a", channel: "C", from: "x", body: "ambient" } },
|
|
263
|
-
{ family: "messaging", seq: 11, payload: { id: "b", channel: "C", from: "x", mentions: ["agent-1"], body: "@me hi" } },
|
|
264
|
-
{ family: "calling", seq: 12, payload: { id: "c", callId: "call-9", from: "x", invitees: ["agent-1"], topic: "sync" } },
|
|
265
|
-
],
|
|
266
|
-
},
|
|
267
|
-
}));
|
|
268
|
-
const { events, nextCursor } = await pullInbound({ cfg: CFG, agentId: "agent-1", cursor: 9, fetchImpl: f });
|
|
269
|
-
assert.equal(events.length, 2, "only the @mention + the call-invite are directed");
|
|
270
|
-
assert.equal(nextCursor, 12, "cursor advanced to the highest seq seen (incl. the skipped ambient)");
|
|
271
|
-
// the events query carried the family filter + cursor
|
|
272
|
-
assert.match(f.calls[0].url, /events\?cursor=9/);
|
|
273
|
-
assert.match(f.calls[0].url, /family=messaging,calling/);
|
|
274
|
-
});
|
|
275
|
-
|
|
276
|
-
test("pullInbound: org disabled → empty, cursor preserved (fail-open)", async () => {
|
|
277
|
-
const { events, nextCursor } = await pullInbound({ cfg: { org: { cohort: { enabled: false } } }, cursor: 5 });
|
|
278
|
-
assert.deepEqual(events, []);
|
|
279
|
-
assert.equal(nextCursor, 5);
|
|
280
|
-
});
|
|
281
|
-
|
|
282
|
-
// ---------------------------------------------------------------------------
|
|
283
|
-
// The REAL hq event shape — regression for the silent-DM-drop
|
|
284
|
-
// ---------------------------------------------------------------------------
|
|
285
|
-
|
|
286
|
-
/** Exactly what hq appends for a human DM (redacted: no body, no recipients). */
|
|
287
|
-
function hqDmEvent(overrides = {}) {
|
|
288
|
-
return {
|
|
289
|
-
seq: 9880,
|
|
290
|
-
family: "messaging",
|
|
291
|
-
kind: "send",
|
|
292
|
-
entityId: "cmsnbhmqy004blj016kot2pwi",
|
|
293
|
-
actor: "human-1",
|
|
294
|
-
at: "2026-08-10T14:18:55.978Z",
|
|
295
|
-
payload: {
|
|
296
|
-
ts: "2026-08-10T14:18:55.978Z",
|
|
297
|
-
actor: "human-1",
|
|
298
|
-
threaded: false,
|
|
299
|
-
channelId: "dm-human-agent",
|
|
300
|
-
channelKind: "DM",
|
|
301
|
-
mentionCount: 0,
|
|
302
|
-
recipientCount: 2,
|
|
303
|
-
attachmentCount: 0,
|
|
304
|
-
...overrides,
|
|
305
|
-
},
|
|
306
|
-
};
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
test("isDirectedAtAgent: a human DM in MY room is directed (the production silent-drop)", () => {
|
|
310
|
-
// Six real DMs were dropped: the filter wanted a `to` list the redacted
|
|
311
|
-
// payload never carries, and checked `kind` for "dm" when the kind is "send"
|
|
312
|
-
// and the room type lives in `channelKind`.
|
|
313
|
-
const mine = new Set(["dm-human-agent"]);
|
|
314
|
-
assert.equal(isDirectedAtAgent(hqDmEvent(), "agent-self", { myChannelIds: mine }), true);
|
|
315
|
-
});
|
|
316
|
-
|
|
317
|
-
test("isDirectedAtAgent: a DM I am NOT in is NOT mine (org feed is org-wide)", () => {
|
|
318
|
-
// /v1/events is org-scoped, so an agent sees other people's DM events too.
|
|
319
|
-
// Reacting to those would be barging into private conversations.
|
|
320
|
-
const mine = new Set(["dm-human-agent"]);
|
|
321
|
-
const someoneElses = hqDmEvent({ channelId: "dm-human-other" });
|
|
322
|
-
assert.equal(isDirectedAtAgent(someoneElses, "agent-self", { myChannelIds: mine }), false);
|
|
323
|
-
});
|
|
324
|
-
|
|
325
|
-
test("isDirectedAtAgent: without membership context it stays CONSERVATIVE on DMs", () => {
|
|
326
|
-
// Guessing "yes" without knowing the roster is the dangerous direction.
|
|
327
|
-
assert.equal(isDirectedAtAgent(hqDmEvent(), "agent-self"), false);
|
|
328
|
-
});
|
|
329
|
-
|
|
330
|
-
test("isDirectedAtAgent: my own echo in my own DM is still ignored", () => {
|
|
331
|
-
const mine = new Set(["dm-human-agent"]);
|
|
332
|
-
const echo = hqDmEvent({ actor: "agent-self" });
|
|
333
|
-
echo.actor = "agent-self";
|
|
334
|
-
assert.equal(isDirectedAtAgent(echo, "agent-self", { myChannelIds: mine }), false);
|
|
335
|
-
});
|
|
336
|
-
|
|
337
|
-
test("isDirectedAtAgent: a PUBLIC channel message in my room is NOT directed on its own", () => {
|
|
338
|
-
// Membership in a public channel is not an invitation to answer everything;
|
|
339
|
-
// that needs an @mention (which the redacted payload cannot yet express —
|
|
340
|
-
// hq emits mentionCount, not the ids).
|
|
341
|
-
const mine = new Set(["general"]);
|
|
342
|
-
const pub = hqDmEvent({ channelId: "general", channelKind: "PUBLIC" });
|
|
343
|
-
assert.equal(isDirectedAtAgent(pub, "agent-self", { myChannelIds: mine }), false);
|
|
344
|
-
});
|
|
345
|
-
|
|
346
|
-
test("isDirectedAtAgent: an explicit mentions[] still wins anywhere", () => {
|
|
347
|
-
const mine = new Set(["general"]);
|
|
348
|
-
const pub = hqDmEvent({ channelId: "general", channelKind: "PUBLIC", mentions: ["agent-self"] });
|
|
349
|
-
assert.equal(isDirectedAtAgent(pub, "agent-self", { myChannelIds: mine }), true);
|
|
350
|
-
});
|
|
351
|
-
|
|
352
|
-
test("isDirectedAtAgent: a call event in my room is an invite", () => {
|
|
353
|
-
const mine = new Set(["room-1"]);
|
|
354
|
-
const ev = { family: "calling", kind: "start", actor: "human-1",
|
|
355
|
-
payload: { channelId: "room-1", topic: "standup" } };
|
|
356
|
-
assert.equal(isDirectedAtAgent(ev, "agent-self", { myChannelIds: mine }), true);
|
|
357
|
-
});
|
|
358
|
-
|
|
359
|
-
test("isDirectedAtAgent: the SHARED feed never names who was mentioned", () => {
|
|
360
|
-
// hq's chain payload is a REDACTED audit row read by every agent holding
|
|
361
|
-
// `org.read`, so it carries `mentionCount` and deliberately NOT the member
|
|
362
|
-
// ids — listing them would leak the association graph of private rooms. This
|
|
363
|
-
// filter therefore cannot resolve a space @mention on its own, BY DESIGN;
|
|
364
|
-
// pullInbound confirms candidates against the per-member `messaging.history`
|
|
365
|
-
// read instead. Asserted so nobody "fixes" the deafness by widening the feed.
|
|
366
|
-
const shared = {
|
|
367
|
-
family: "messaging", kind: "send", entityId: "msg_1", actor: "human-1",
|
|
368
|
-
payload: { channelId: "space-general", channelKind: "PUBLIC", mentionCount: 1 },
|
|
369
|
-
};
|
|
370
|
-
assert.equal(isDirectedAtAgent(shared, "agent-self"), false);
|
|
371
|
-
});
|
|
372
|
-
|
|
373
|
-
test("isDirectedAtAgent: an explicit mentions[] still wins when a producer supplies one", () => {
|
|
374
|
-
// hq's chain payload used to carry `mentionCount` — a NUMBER — so an agent
|
|
375
|
-
// tailing the feed could see that a mention happened but not that it was the
|
|
376
|
-
// one mentioned. Combined with hq deferring to live daemons, an @mention in a
|
|
377
|
-
// public space reached nobody: the server stayed quiet and the agent was deaf.
|
|
378
|
-
// hq now emits `mentions: [memberId…]`, which this filter already understood.
|
|
379
|
-
const ev = {
|
|
380
|
-
seq: 10500,
|
|
381
|
-
family: "messaging",
|
|
382
|
-
kind: "send",
|
|
383
|
-
entityId: "msg_1",
|
|
384
|
-
actor: "human-1",
|
|
385
|
-
payload: {
|
|
386
|
-
channelId: "space-general",
|
|
387
|
-
channelKind: "PUBLIC",
|
|
388
|
-
mentions: ["agent-self"],
|
|
389
|
-
mentionCount: 1,
|
|
390
|
-
recipientCount: 12,
|
|
391
|
-
},
|
|
392
|
-
};
|
|
393
|
-
// Membership is NOT required for a mention — being named is the invitation.
|
|
394
|
-
assert.equal(isDirectedAtAgent(ev, "agent-self"), true);
|
|
395
|
-
// …and someone else's mention in the same space is still not ours.
|
|
396
|
-
assert.equal(isDirectedAtAgent(ev, "agent-other"), false);
|
|
397
|
-
});
|
|
398
|
-
|
|
399
|
-
// ---------------------------------------------------------------------------
|
|
400
|
-
// Rooms — the four verbs the SDK had in its protocol table and could not call
|
|
401
|
-
// ---------------------------------------------------------------------------
|
|
402
|
-
|
|
403
|
-
test("openDm resolves the 1:1 channel and hands back the id the send needs", async () => {
|
|
404
|
-
const f = fakeFetch(() => ({ body: { ok: true, result: { channelId: "C-dm-1", created: true } } }));
|
|
405
|
-
const r = await openDm({ memberId: "M-2" }, { cfg: CFG, fetchImpl: f });
|
|
406
|
-
assert.equal(r.ok, true);
|
|
407
|
-
assert.equal(r.channelId, "C-dm-1");
|
|
408
|
-
assert.equal(r.created, true);
|
|
409
|
-
assert.match(f.calls[0].url, /\/v1\/channel\.resolveOrCreateDm$/);
|
|
410
|
-
assert.deepEqual(f.calls[0].body, { memberId: "M-2" });
|
|
411
|
-
});
|
|
412
|
-
|
|
413
|
-
test("openDm reads the channel id out of whichever shape the handler returns", async () => {
|
|
414
|
-
for (const result of [{ id: "C-x" }, { channelId: "C-x" }, { channel: { id: "C-x" } }]) {
|
|
415
|
-
const r = await openDm({ memberId: "M-2" }, { cfg: CFG, fetchImpl: fakeFetch(() => ({ body: { ok: true, result } })) });
|
|
416
|
-
assert.equal(r.channelId, "C-x", JSON.stringify(result));
|
|
417
|
-
}
|
|
418
|
-
});
|
|
419
|
-
|
|
420
|
-
test("openConversation de-dupes the member list and sends hq's `memberIds`", async () => {
|
|
421
|
-
const f = fakeFetch(() => ({ body: { ok: true, result: { id: "C-gdm" } } }));
|
|
422
|
-
const r = await openConversation({ memberIds: ["M-2", "M-3", "M-2", ""], name: "Vendor contract" }, { cfg: CFG, fetchImpl: f });
|
|
423
|
-
assert.equal(r.ok, true);
|
|
424
|
-
assert.equal(r.channelId, "C-gdm");
|
|
425
|
-
assert.match(f.calls[0].url, /\/v1\/channel\.createConversation$/);
|
|
426
|
-
assert.deepEqual(f.calls[0].body.memberIds, ["M-2", "M-3"]);
|
|
427
|
-
assert.equal(f.calls[0].body.name, "Vendor contract");
|
|
428
|
-
});
|
|
429
|
-
|
|
430
|
-
test("openConversation with nobody in it never reaches the wire", async () => {
|
|
431
|
-
const f = fakeFetch(() => ({ body: { ok: true, result: {} } }));
|
|
432
|
-
const r = await openConversation({ memberIds: [] }, { cfg: CFG, fetchImpl: f });
|
|
433
|
-
assert.equal(r.ok, false);
|
|
434
|
-
assert.equal(f.calls.length, 0);
|
|
435
|
-
});
|
|
436
|
-
|
|
437
|
-
test("createChannel refuses a slug hq would reject, before spending a round trip", async () => {
|
|
438
|
-
const f = fakeFetch(() => ({ body: { ok: true, result: { id: "C-1" } } }));
|
|
439
|
-
const bad = await createChannel({ slug: "Vendor Contract!", name: "Vendor" }, { cfg: CFG, fetchImpl: f });
|
|
440
|
-
assert.equal(bad.ok, false);
|
|
441
|
-
assert.match(bad.frame.error.message, /lowercase alphanumerics and hyphens/);
|
|
442
|
-
assert.equal(f.calls.length, 0);
|
|
443
|
-
|
|
444
|
-
const good = await createChannel({ slug: "vendor-contract", name: "Vendor", members: ["M-2"] }, { cfg: CFG, fetchImpl: f });
|
|
445
|
-
assert.equal(good.ok, true);
|
|
446
|
-
assert.equal(f.calls[0].body.kind, "PRIVATE", "a channel an agent makes is private by default");
|
|
447
|
-
assert.deepEqual(f.calls[0].body.members, ["M-2"]);
|
|
448
|
-
});
|
|
449
|
-
|
|
450
|
-
test("addChannelMember reports hq's idempotent re-add as added:false, not as a failure", async () => {
|
|
451
|
-
const f = fakeFetch(() => ({ body: { ok: true, result: { channelId: "C-1", memberId: "M-2", added: false } } }));
|
|
452
|
-
const r = await addChannelMember({ channelId: "C-1", memberId: "M-2" }, { cfg: CFG, fetchImpl: f });
|
|
453
|
-
assert.equal(r.ok, true);
|
|
454
|
-
assert.equal(r.added, false);
|
|
455
|
-
assert.match(f.calls[0].url, /\/v1\/channel\.addMember$/);
|
|
456
|
-
});
|
|
457
|
-
|
|
458
|
-
test("every room verb fails open to an error frame when the org is unconfigured", async () => {
|
|
459
|
-
const off = { org: { cohort: { enabled: false } } };
|
|
460
|
-
for (const [name, fn, args] of [
|
|
461
|
-
["openDm", openDm, { memberId: "M-2" }],
|
|
462
|
-
["openConversation", openConversation, { memberIds: ["M-2"] }],
|
|
463
|
-
["createChannel", createChannel, { slug: "x", name: "X" }],
|
|
464
|
-
["addChannelMember", addChannelMember, { channelId: "C", memberId: "M-2" }],
|
|
465
|
-
]) {
|
|
466
|
-
const r = await fn(args, { cfg: off });
|
|
467
|
-
assert.equal(r.ok, false, name);
|
|
468
|
-
assert.equal(r.frame.ok, false, name);
|
|
469
|
-
assert.ok(r.frame.error.message, `${name} must say why`);
|
|
470
|
-
}
|
|
471
|
-
});
|