@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,453 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* hydrate.test.mjs — per-family body/context hydration.
|
|
3
|
-
*
|
|
4
|
-
* Two things are being asserted throughout:
|
|
5
|
-
* 1. every surface produces a body a classifier can actually act on, and
|
|
6
|
-
* 2. hydration only ever uses the agent's OWN entitled reads — the call log is
|
|
7
|
-
* checked so a hydrator cannot quietly acquire a wider aperture than the
|
|
8
|
-
* directedness probe that let the item through.
|
|
9
|
-
*
|
|
10
|
-
* Run: node --test lib/org/inbound/hydrate.test.mjs
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
"use strict";
|
|
14
|
-
|
|
15
|
-
import { test } from "node:test";
|
|
16
|
-
import assert from "node:assert/strict";
|
|
17
|
-
|
|
18
|
-
import { hydrate, clip, renderThread, describeBoardKind, describeApprovalKind } from "./hydrate.mjs";
|
|
19
|
-
import { classifyEvent } from "./directedness.mjs";
|
|
20
|
-
|
|
21
|
-
const ME = "M-me";
|
|
22
|
-
const THEM = "M-them";
|
|
23
|
-
const okFrame = (result) => ({ ok: true, result });
|
|
24
|
-
|
|
25
|
-
function fakeIo(routes = {}) {
|
|
26
|
-
const calls = [];
|
|
27
|
-
const logs = [];
|
|
28
|
-
return {
|
|
29
|
-
calls,
|
|
30
|
-
logs,
|
|
31
|
-
log: (level, message, meta) => logs.push({ level, message, meta }),
|
|
32
|
-
async call(method, params) {
|
|
33
|
-
calls.push({ method, params });
|
|
34
|
-
const h = routes[method];
|
|
35
|
-
if (!h) return { ok: false, error: { code: "NOT_FOUND", message: method } };
|
|
36
|
-
return typeof h === "function" ? await h(params) : h;
|
|
37
|
-
},
|
|
38
|
-
async read() {
|
|
39
|
-
return { ok: false, error: { code: "NOT_FOUND", message: "read" }, status: 404 };
|
|
40
|
-
},
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function facts(over = {}) {
|
|
45
|
-
return {
|
|
46
|
-
me: ME,
|
|
47
|
-
myNames: [],
|
|
48
|
-
channels: new Map(),
|
|
49
|
-
channelPages: new Map(),
|
|
50
|
-
myThreadRootIds: new Set(),
|
|
51
|
-
tasks: null,
|
|
52
|
-
decisions: null,
|
|
53
|
-
escalations: null,
|
|
54
|
-
approvals: null,
|
|
55
|
-
fileAcl: null,
|
|
56
|
-
degraded: [],
|
|
57
|
-
...over,
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function ev(over) {
|
|
62
|
-
return { seq: 7, at: "2026-08-11T00:00:00.000Z", actor: THEM, ...over };
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
// ---------------------------------------------------------------------------
|
|
66
|
-
// messaging
|
|
67
|
-
// ---------------------------------------------------------------------------
|
|
68
|
-
|
|
69
|
-
test("messaging hydrates from the ACL'd history page — no extra RPC", async () => {
|
|
70
|
-
const f = facts();
|
|
71
|
-
f.channels.set("C-pub", { id: "C-pub", kind: "PUBLIC", name: "General", slug: "general" });
|
|
72
|
-
f.channelPages.set("C-pub", {
|
|
73
|
-
byId: new Map([
|
|
74
|
-
["m1", { id: "m1", authorId: THEM, authorName: "Casey", body: "@Isla take a look", mentions: [ME] }],
|
|
75
|
-
]),
|
|
76
|
-
messages: [{ id: "m1", authorId: THEM, authorName: "Casey", body: "@Isla take a look", mentions: [ME] }],
|
|
77
|
-
});
|
|
78
|
-
const c = classifyEvent(ev({ family: "messaging", kind: "send", entity_id: "m1", payload: { actor: THEM, channelId: "C-pub", channelKind: "PUBLIC", mentionCount: 1 } }));
|
|
79
|
-
const io = fakeIo({});
|
|
80
|
-
const h = await hydrate({ candidate: c, verdict: { surface: "mention", reason: "mention" }, facts: f, io });
|
|
81
|
-
|
|
82
|
-
assert.equal(h.ok, true);
|
|
83
|
-
assert.equal(h.text, "@Isla take a look");
|
|
84
|
-
assert.deepEqual(h.from, { id: THEM, name: "Casey" });
|
|
85
|
-
assert.equal(h.channelLabel, "General");
|
|
86
|
-
assert.equal(io.calls.length, 0, "the body was already on the page");
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
test("a message that never hydrated is DROPPED, not delivered blank", async () => {
|
|
90
|
-
const c = classifyEvent(ev({ family: "messaging", kind: "send", entity_id: "m404", payload: { actor: THEM, channelId: "C-pub", channelKind: "PUBLIC" } }));
|
|
91
|
-
const h = await hydrate({ candidate: c, verdict: { surface: "mention" }, facts: facts(), io: fakeIo({}) });
|
|
92
|
-
assert.equal(h.ok, false);
|
|
93
|
-
assert.equal(h.reason, "message_not_in_page");
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
test("an empty body is dropped (a blank inbound produces a nonsense reply)", async () => {
|
|
97
|
-
const f = facts();
|
|
98
|
-
f.channelPages.set("C", { byId: new Map([["m1", { id: "m1", authorId: THEM, body: " " }]]), messages: [] });
|
|
99
|
-
const c = classifyEvent(ev({ family: "messaging", kind: "send", entity_id: "m1", payload: { actor: THEM, channelId: "C", channelKind: "DM" } }));
|
|
100
|
-
const h = await hydrate({ candidate: c, verdict: { surface: "dm" }, facts: f, io: fakeIo({}) });
|
|
101
|
-
assert.equal(h.ok, false);
|
|
102
|
-
assert.equal(h.reason, "empty_body");
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
test("a threaded message carries prior-thread context", async () => {
|
|
106
|
-
const msgs = [
|
|
107
|
-
{ id: "r1", authorId: ME, authorName: "Isla", body: "kick off", threadRootId: null },
|
|
108
|
-
{ id: "m2", authorId: THEM, authorName: "Casey", body: "one more thing", threadRootId: "r1" },
|
|
109
|
-
];
|
|
110
|
-
const f = facts();
|
|
111
|
-
f.channelPages.set("C-pub", { byId: new Map(msgs.map((m) => [m.id, m])), messages: msgs });
|
|
112
|
-
const c = classifyEvent(ev({ family: "messaging", kind: "send", entity_id: "m2", payload: { actor: THEM, channelId: "C-pub", channelKind: "PUBLIC", threaded: true } }));
|
|
113
|
-
const h = await hydrate({ candidate: c, verdict: { surface: "thread_reply", reason: "thread" }, facts: f, io: fakeIo({}) });
|
|
114
|
-
|
|
115
|
-
assert.equal(h.isReply, true);
|
|
116
|
-
assert.equal(h.threadId, "r1");
|
|
117
|
-
assert.match(h.threadContext, /Isla: kick off/);
|
|
118
|
-
assert.doesNotMatch(h.threadContext, /one more thing/, "the trigger itself is not its own context");
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
// A DM has no thread root — the CHANNEL is the conversation. Before this, the
|
|
122
|
-
// `if (root)` guard meant a top-level message carried no context at all, so the
|
|
123
|
-
// agent answered "fix this" with no idea what "this" referred to. The page is
|
|
124
|
-
// already in hand from the ACL'd `messaging.history` read, so this costs no RPC.
|
|
125
|
-
test("a top-level DM carries the preceding conversation as context", async () => {
|
|
126
|
-
// Newest-first, exactly as hq's messaging.history returns it.
|
|
127
|
-
const msgs = [
|
|
128
|
-
{ id: "m3", authorId: THEM, authorName: "Casey", body: "fix this then", createdAt: "2026-08-11T15:33:54.000Z" },
|
|
129
|
-
{ id: "m2", authorId: ME, authorName: "Alex", body: "16 min pickup on the morning batch", createdAt: "2026-08-11T15:32:00.000Z" },
|
|
130
|
-
{ id: "m1", authorId: THEM, authorName: "Casey", body: "what is the reply latency here", createdAt: "2026-08-11T15:31:00.000Z" },
|
|
131
|
-
];
|
|
132
|
-
const f = facts();
|
|
133
|
-
f.channels.set("C-dm", { id: "C-dm", kind: "DM", name: "dm-h001-a016" });
|
|
134
|
-
f.channelPages.set("C-dm", { byId: new Map(msgs.map((m) => [m.id, m])), messages: msgs });
|
|
135
|
-
const c = classifyEvent(ev({ family: "messaging", kind: "send", entity_id: "m3", payload: { actor: THEM, channelId: "C-dm", channelKind: "DM" } }));
|
|
136
|
-
const io = fakeIo({});
|
|
137
|
-
const h = await hydrate({ candidate: c, verdict: { surface: "dm", reason: "dm" }, facts: f, io });
|
|
138
|
-
|
|
139
|
-
assert.equal(h.ok, true);
|
|
140
|
-
assert.equal(io.calls.length, 0, "the page was already in hand — context must cost no extra RPC");
|
|
141
|
-
assert.ok(h.threadContext, "a DM with prior turns must carry them");
|
|
142
|
-
assert.match(h.threadContext, /Casey: what is the reply latency here/);
|
|
143
|
-
assert.match(h.threadContext, /Alex: 16 min pickup/);
|
|
144
|
-
assert.doesNotMatch(h.threadContext, /fix this then/, "the trigger itself is not its own context");
|
|
145
|
-
assert.ok(
|
|
146
|
-
h.threadContext.indexOf("what is the reply latency") < h.threadContext.indexOf("16 min pickup"),
|
|
147
|
-
"oldest first — the conversation must read forwards",
|
|
148
|
-
);
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
test("a first-ever message in a channel has no context, not an empty block", async () => {
|
|
152
|
-
const msgs = [{ id: "m1", authorId: THEM, authorName: "Casey", body: "hello", createdAt: "2026-08-11T15:31:00.000Z" }];
|
|
153
|
-
const f = facts();
|
|
154
|
-
f.channels.set("C-dm", { id: "C-dm", kind: "DM" });
|
|
155
|
-
f.channelPages.set("C-dm", { byId: new Map(msgs.map((m) => [m.id, m])), messages: msgs });
|
|
156
|
-
const c = classifyEvent(ev({ family: "messaging", kind: "send", entity_id: "m1", payload: { actor: THEM, channelId: "C-dm", channelKind: "DM" } }));
|
|
157
|
-
const h = await hydrate({ candidate: c, verdict: { surface: "dm", reason: "dm" }, facts: f, io: fakeIo({}) });
|
|
158
|
-
|
|
159
|
-
assert.equal(h.ok, true);
|
|
160
|
-
assert.equal(h.threadContext, null);
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
test("messages AFTER the trigger are never passed off as prior context", async () => {
|
|
164
|
-
const msgs = [
|
|
165
|
-
{ id: "m3", authorId: THEM, authorName: "Casey", body: "later message", createdAt: "2026-08-11T16:00:00.000Z" },
|
|
166
|
-
{ id: "m2", authorId: THEM, authorName: "Casey", body: "the trigger", createdAt: "2026-08-11T15:00:00.000Z" },
|
|
167
|
-
{ id: "m1", authorId: ME, authorName: "Alex", body: "earlier message", createdAt: "2026-08-11T14:00:00.000Z" },
|
|
168
|
-
];
|
|
169
|
-
const f = facts();
|
|
170
|
-
f.channels.set("C-dm", { id: "C-dm", kind: "DM" });
|
|
171
|
-
f.channelPages.set("C-dm", { byId: new Map(msgs.map((m) => [m.id, m])), messages: msgs });
|
|
172
|
-
const c = classifyEvent(ev({ family: "messaging", kind: "send", entity_id: "m2", payload: { actor: THEM, channelId: "C-dm", channelKind: "DM" } }));
|
|
173
|
-
const h = await hydrate({ candidate: c, verdict: { surface: "dm", reason: "dm" }, facts: f, io: fakeIo({}) });
|
|
174
|
-
|
|
175
|
-
assert.match(h.threadContext, /earlier message/);
|
|
176
|
-
assert.doesNotMatch(h.threadContext, /later message/);
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
// ---------------------------------------------------------------------------
|
|
180
|
-
// board
|
|
181
|
-
// ---------------------------------------------------------------------------
|
|
182
|
-
|
|
183
|
-
test("board hydrates the task card + its comment thread", async () => {
|
|
184
|
-
const f = facts({ tasks: new Map([["T-1", { id: "T-1", title: "Ship the thing", col: "doing", priority: 1, detail: "make it fast", channelId: "C-eng" }]]) });
|
|
185
|
-
const io = fakeIo({
|
|
186
|
-
"board.taskComments": ({ taskId }) =>
|
|
187
|
-
okFrame({
|
|
188
|
-
taskId,
|
|
189
|
-
comments: [
|
|
190
|
-
{ id: "c1", authorId: ME, authorName: "Isla", body: "on it" },
|
|
191
|
-
{ id: "c2", authorId: THEM, authorName: "Casey", body: "any update?" },
|
|
192
|
-
],
|
|
193
|
-
}),
|
|
194
|
-
});
|
|
195
|
-
const c = classifyEvent(ev({ family: "board", kind: "item.commented", entity_id: "T-1", payload: { actor: THEM, kind: "human" } }));
|
|
196
|
-
const h = await hydrate({ candidate: c, verdict: { surface: "task_comment", reason: "assignee" }, facts: f, io });
|
|
197
|
-
|
|
198
|
-
assert.equal(h.ok, true);
|
|
199
|
-
assert.match(h.text, /New comment on your board item — "Ship the thing"/);
|
|
200
|
-
assert.match(h.text, /Column: doing/);
|
|
201
|
-
assert.match(h.text, /any update\?/);
|
|
202
|
-
assert.match(h.threadContext, /Isla: on it/);
|
|
203
|
-
assert.equal(h.threadId, "T-1");
|
|
204
|
-
assert.deepEqual(io.calls.map((x) => x.method), ["board.taskComments"]);
|
|
205
|
-
});
|
|
206
|
-
|
|
207
|
-
test("a reviewer is TOLD they are the reviewer", async () => {
|
|
208
|
-
const f = facts({ tasks: new Map([["T-2", { id: "T-2", title: "Review me", col: "review" }]]) });
|
|
209
|
-
const io = fakeIo({ "board.taskComments": okFrame({ comments: [] }) });
|
|
210
|
-
const c = classifyEvent(ev({ family: "board", kind: "item.moved", entity_id: "T-2", payload: { actor: THEM, to: "review" } }));
|
|
211
|
-
const h = await hydrate({ candidate: c, verdict: { surface: "task_comment", reason: "reviewer" }, facts: f, io });
|
|
212
|
-
assert.match(h.text, /You are the REVIEWER/);
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
test("two candidates on one task share a single taskComments read", async () => {
|
|
216
|
-
let n = 0;
|
|
217
|
-
const io = fakeIo({ "board.taskComments": () => { n += 1; return okFrame({ comments: [] }); } });
|
|
218
|
-
const f = facts({ tasks: new Map([["T-3", { id: "T-3", title: "x" }]]) });
|
|
219
|
-
const cache = new Map();
|
|
220
|
-
const mk = (seq, kind) => classifyEvent(ev({ seq, family: "board", kind, entity_id: "T-3", payload: { actor: THEM } }));
|
|
221
|
-
await hydrate({ candidate: mk(1, "item.commented"), verdict: { surface: "task_comment" }, facts: f, io, cache });
|
|
222
|
-
await hydrate({ candidate: mk(2, "item.moved"), verdict: { surface: "task_comment" }, facts: f, io, cache });
|
|
223
|
-
assert.equal(n, 1);
|
|
224
|
-
});
|
|
225
|
-
|
|
226
|
-
// ---------------------------------------------------------------------------
|
|
227
|
-
// file / doc
|
|
228
|
-
// ---------------------------------------------------------------------------
|
|
229
|
-
|
|
230
|
-
test("a chat-file comment hydrates through file.listComments, scoped by channel", async () => {
|
|
231
|
-
const f = facts();
|
|
232
|
-
f.channels.set("C-team", { id: "C-team", kind: "PRIVATE", name: "Leads" });
|
|
233
|
-
const io = fakeIo({
|
|
234
|
-
"file.listComments": okFrame({ comments: [{ id: "fc1", authorId: THEM, authorName: "Casey", body: "typo on slide 3" }] }),
|
|
235
|
-
});
|
|
236
|
-
const c = classifyEvent(ev({ family: "file", kind: "file.commented", entity_id: "fc1", payload: { fileKey: "message-attachment:abc", channelId: "C-team", authorId: THEM } }));
|
|
237
|
-
const h = await hydrate({ candidate: c, verdict: { surface: "file_comment", reason: "channel" }, facts: f, io });
|
|
238
|
-
|
|
239
|
-
assert.equal(h.ok, true);
|
|
240
|
-
assert.match(h.text, /typo on slide 3/);
|
|
241
|
-
// The channel is forwarded so hq can run its own visibility gate.
|
|
242
|
-
assert.equal(io.calls[0].params.channelId, "C-team");
|
|
243
|
-
});
|
|
244
|
-
|
|
245
|
-
test("an unreadable file thread is dropped rather than delivered empty", async () => {
|
|
246
|
-
const c = classifyEvent(ev({ family: "file", kind: "file.commented", entity_id: "fc2", payload: { fileKey: "message-attachment:z", channelId: "C-team" } }));
|
|
247
|
-
const h = await hydrate({ candidate: c, verdict: { surface: "file_comment" }, facts: facts(), io: fakeIo({}) });
|
|
248
|
-
assert.equal(h.ok, false);
|
|
249
|
-
assert.equal(h.reason, "file_comment_unreadable");
|
|
250
|
-
});
|
|
251
|
-
|
|
252
|
-
test("a workspace doc comment hydrates through files.comments", async () => {
|
|
253
|
-
const f = facts({ fileAcl: new Map([["F-1", { ownerId: ME, shared: false, name: "Spec" }]]) });
|
|
254
|
-
const io = fakeIo({
|
|
255
|
-
"files.comments": okFrame({ comments: [{ id: "dc1", authorId: THEM, authorName: "Casey", body: "can we cut section 4?" }] }),
|
|
256
|
-
});
|
|
257
|
-
const c = classifyEvent(ev({ family: "files", kind: "doc.comment", entity_id: "F-1", payload: { name: "Spec", commentId: "dc1" } }));
|
|
258
|
-
const h = await hydrate({ candidate: c, verdict: { surface: "doc_comment", reason: "owner" }, facts: f, io });
|
|
259
|
-
|
|
260
|
-
assert.equal(h.ok, true);
|
|
261
|
-
assert.match(h.text, /New comment on "Spec"/);
|
|
262
|
-
assert.match(h.text, /cut section 4/);
|
|
263
|
-
});
|
|
264
|
-
|
|
265
|
-
// ---------------------------------------------------------------------------
|
|
266
|
-
// decision / escalation / approval / handoff
|
|
267
|
-
// ---------------------------------------------------------------------------
|
|
268
|
-
|
|
269
|
-
test("a decision carries status, body, rationale and the latest comment", async () => {
|
|
270
|
-
const f = facts({
|
|
271
|
-
decisions: new Map([["D-1", { id: "D-1", title: "Adopt X", status: "proposed", scope: "eng", body: "we adopt X", rationale: "cheaper" }]]),
|
|
272
|
-
});
|
|
273
|
-
const io = fakeIo({
|
|
274
|
-
"decision.listComments": okFrame({ comments: [{ id: "c1", authorId: THEM, authorName: "Casey", body: "what about Y?" }] }),
|
|
275
|
-
});
|
|
276
|
-
const c = classifyEvent(ev({ family: "decision", kind: "decision.adjustment_requested", entity_id: "D-1", payload: { commentId: "c1", authorId: THEM } }));
|
|
277
|
-
const h = await hydrate({ candidate: c, verdict: { surface: "decision", reason: "proposer" }, facts: f, io });
|
|
278
|
-
|
|
279
|
-
assert.match(h.text, /An adjustment was requested on a decision — "Adopt X"/);
|
|
280
|
-
assert.match(h.text, /Status: proposed/);
|
|
281
|
-
assert.match(h.text, /Rationale: cheaper/);
|
|
282
|
-
assert.match(h.text, /what about Y\?/);
|
|
283
|
-
assert.match(h.text, /needs a response/);
|
|
284
|
-
});
|
|
285
|
-
|
|
286
|
-
test("an escalation needs no extra read — the facts pass already had it", async () => {
|
|
287
|
-
const f = facts({ escalations: new Map([["E-1", { id: "E-1", title: "prod down", severity: "high", detail: "500s on /v1", waitingOn: ME }]]) });
|
|
288
|
-
const io = fakeIo({});
|
|
289
|
-
const c = classifyEvent(ev({ family: "escalation", kind: "escalation.raised", entity_id: "E-1", payload: { actor: THEM, severity: "high" } }));
|
|
290
|
-
const h = await hydrate({ candidate: c, verdict: { surface: "escalation", reason: "waiting_on" }, facts: f, io });
|
|
291
|
-
|
|
292
|
-
assert.match(h.text, /Escalation raised — "prod down"/);
|
|
293
|
-
assert.match(h.text, /Severity: high/);
|
|
294
|
-
assert.match(h.text, /Waiting on: M-me/);
|
|
295
|
-
assert.equal(io.calls.length, 0);
|
|
296
|
-
});
|
|
297
|
-
|
|
298
|
-
test("an approval renders the whole decision packet", async () => {
|
|
299
|
-
const f = facts({
|
|
300
|
-
approvals: new Map([["A-1", { id: "A-1", requester: THEM, approver: ME, status: "pending", actionClass: "spend.over_1k", reason: "vendor renewal", itemId: "T-9", expiresAt: "2026-08-12T00:00:00.000Z" }]]),
|
|
301
|
-
});
|
|
302
|
-
const c = classifyEvent(ev({ family: "approval", kind: "approval.requested", entity_id: "A-1", payload: { requester: THEM, actionClass: "spend.over_1k" } }));
|
|
303
|
-
const h = await hydrate({ candidate: c, verdict: { surface: "approval", reason: "approver" }, facts: f, io: fakeIo({}) });
|
|
304
|
-
|
|
305
|
-
assert.match(h.text, /An approval is waiting on you — spend\.over_1k/);
|
|
306
|
-
assert.match(h.text, /Status: pending/);
|
|
307
|
-
assert.match(h.text, /vendor renewal/);
|
|
308
|
-
assert.match(h.text, /Board item: T-9/);
|
|
309
|
-
});
|
|
310
|
-
|
|
311
|
-
test("a handoff renders intent and both ends from the payload alone", async () => {
|
|
312
|
-
const f = facts({ tasks: new Map([["T-1", { id: "T-1", title: "Migrate DB", detail: "zero downtime" }]]) });
|
|
313
|
-
const c = classifyEvent(ev({ family: "handoff", kind: "handoff.offered", entity_id: "R-1", payload: { itemId: "T-1", from: THEM, to: ME, intent: "please take this over" } }));
|
|
314
|
-
const h = await hydrate({ candidate: c, verdict: { surface: "handoff", reason: "direct" }, facts: f, io: fakeIo({}) });
|
|
315
|
-
|
|
316
|
-
assert.match(h.text, /A handoff was offered to you — "Migrate DB"/);
|
|
317
|
-
assert.match(h.text, /Intent: please take this over/);
|
|
318
|
-
assert.match(h.text, /From M-them → to M-me/);
|
|
319
|
-
});
|
|
320
|
-
|
|
321
|
-
// ---------------------------------------------------------------------------
|
|
322
|
-
// email
|
|
323
|
-
// ---------------------------------------------------------------------------
|
|
324
|
-
|
|
325
|
-
test("email hydrates through email.thread, which hq scopes to MY mailbox", async () => {
|
|
326
|
-
const io = fakeIo({
|
|
327
|
-
"email.thread": ({ threadId }) =>
|
|
328
|
-
okFrame({
|
|
329
|
-
thread: { id: threadId, subject: "Contract renewal" },
|
|
330
|
-
messages: [
|
|
331
|
-
{ id: "EM-0", subject: "Contract renewal", text: "here is the draft", from: { name: "Dana", address: "dana@acme.com" } },
|
|
332
|
-
{ id: "EM-1", subject: "Re: Contract renewal", text: "any thoughts?", from: { name: "Dana", address: "dana@acme.com" } },
|
|
333
|
-
],
|
|
334
|
-
}),
|
|
335
|
-
});
|
|
336
|
-
const c = classifyEvent(ev({ family: "email", kind: "received", actor: "system", entity_id: "EM-1", payload: { emailMessageId: "EM-1", threadId: "ET-1", mailboxId: "MB-1", memberId: ME } }));
|
|
337
|
-
const h = await hydrate({ candidate: c, verdict: { surface: "email", reason: "direct" }, facts: facts(), io });
|
|
338
|
-
|
|
339
|
-
assert.equal(h.ok, true);
|
|
340
|
-
assert.equal(h.text, "any thoughts?");
|
|
341
|
-
assert.equal(h.subjectDetail, "Re: Contract renewal");
|
|
342
|
-
assert.equal(h.from.name, "Dana");
|
|
343
|
-
assert.match(h.threadContext, /here is the draft/);
|
|
344
|
-
assert.deepEqual(io.calls.map((x) => x.method), ["email.thread"]);
|
|
345
|
-
});
|
|
346
|
-
|
|
347
|
-
test("unreadable mail is dropped (not another seat's mail leaking through)", async () => {
|
|
348
|
-
const c = classifyEvent(ev({ family: "email", kind: "received", entity_id: "EM-9", payload: { emailMessageId: "EM-9", threadId: "ET-9", memberId: ME } }));
|
|
349
|
-
const h = await hydrate({ candidate: c, verdict: { surface: "email" }, facts: facts(), io: fakeIo({}) });
|
|
350
|
-
assert.equal(h.ok, false);
|
|
351
|
-
assert.equal(h.reason, "email_unreadable");
|
|
352
|
-
});
|
|
353
|
-
|
|
354
|
-
// ---------------------------------------------------------------------------
|
|
355
|
-
// robustness
|
|
356
|
-
// ---------------------------------------------------------------------------
|
|
357
|
-
|
|
358
|
-
test("a hydrator that throws fails open, loudly, and never takes the pull down", async () => {
|
|
359
|
-
const io = fakeIo({
|
|
360
|
-
"board.taskComments": () => {
|
|
361
|
-
throw new Error("boom");
|
|
362
|
-
},
|
|
363
|
-
});
|
|
364
|
-
const c = classifyEvent(ev({ family: "board", kind: "item.commented", entity_id: "T-1", payload: { actor: THEM } }));
|
|
365
|
-
const h = await hydrate({ candidate: c, verdict: { surface: "task_comment" }, facts: facts(), io });
|
|
366
|
-
assert.equal(h.ok, false);
|
|
367
|
-
assert.equal(h.reason, "hydrate_threw");
|
|
368
|
-
assert.ok(io.logs.some((l) => l.level === "warn" && /hydrate board threw/.test(l.message)));
|
|
369
|
-
});
|
|
370
|
-
|
|
371
|
-
test("clip and renderThread are bounded", () => {
|
|
372
|
-
assert.equal(clip("abc", 10), "abc");
|
|
373
|
-
assert.ok(clip("x".repeat(100), 10).length <= 11);
|
|
374
|
-
assert.equal(renderThread([]), null);
|
|
375
|
-
assert.equal(renderThread(null), null);
|
|
376
|
-
assert.match(renderThread([{ authorName: "A", body: "hi" }]), /A: hi/);
|
|
377
|
-
});
|
|
378
|
-
|
|
379
|
-
// `messaging.history` returns DESCENDING; every comment source returns ASCENDING.
|
|
380
|
-
// A bare slice(-limit) therefore took the OLDEST rows off a message page and
|
|
381
|
-
// printed them backwards — the wrong window, in the wrong order.
|
|
382
|
-
test("renderThread reads forwards and keeps the most RECENT turns", () => {
|
|
383
|
-
const rows = [];
|
|
384
|
-
for (let i = 12; i >= 1; i -= 1) {
|
|
385
|
-
rows.push({ authorName: "A", body: `turn ${i}`, createdAt: `2026-08-11T00:${String(i).padStart(2, "0")}:00.000Z` });
|
|
386
|
-
}
|
|
387
|
-
const out = renderThread(rows); // limit 8
|
|
388
|
-
|
|
389
|
-
assert.match(out, /turn 12/, "the newest turn is the one that matters most");
|
|
390
|
-
assert.doesNotMatch(out, /turn 1\b/, "the oldest turns fall off the window, not the newest");
|
|
391
|
-
assert.ok(out.indexOf("turn 5") < out.indexOf("turn 12"), "oldest first");
|
|
392
|
-
});
|
|
393
|
-
|
|
394
|
-
test("renderThread leaves undated rows in the order they arrived", () => {
|
|
395
|
-
const out = renderThread([
|
|
396
|
-
{ authorName: "A", body: "first" },
|
|
397
|
-
{ authorName: "B", body: "second" },
|
|
398
|
-
]);
|
|
399
|
-
assert.ok(out.indexOf("first") < out.indexOf("second"));
|
|
400
|
-
});
|
|
401
|
-
|
|
402
|
-
// The `limit` window and the `max` CHARACTER budget are two different windows,
|
|
403
|
-
// and only the first was covered: twelve short turns never reach 2000 chars, so
|
|
404
|
-
// the clip never fired and the direction it clips in was never asserted. On the
|
|
405
|
-
// real DM it fired immediately — one long reply filled the budget and the four
|
|
406
|
-
// newest turns, including the question being answered, were the ones discarded.
|
|
407
|
-
test("renderThread keeps the NEWEST turns when the CHARACTER budget binds", () => {
|
|
408
|
-
const out = renderThread(
|
|
409
|
-
[
|
|
410
|
-
{ authorName: "A", body: "x".repeat(1500), createdAt: "2026-08-11T00:01:00.000Z" },
|
|
411
|
-
{ authorName: "B", body: "the thing I actually asked", createdAt: "2026-08-11T00:02:00.000Z" },
|
|
412
|
-
],
|
|
413
|
-
{ max: 300 },
|
|
414
|
-
);
|
|
415
|
-
assert.match(out, /the thing I actually asked/, "the latest turn must survive the budget");
|
|
416
|
-
});
|
|
417
|
-
|
|
418
|
-
test("renderThread does not let one huge turn starve the turns around it", () => {
|
|
419
|
-
const out = renderThread(
|
|
420
|
-
[
|
|
421
|
-
{ authorName: "A", body: "y".repeat(5000), createdAt: "2026-08-11T00:01:00.000Z" },
|
|
422
|
-
{ authorName: "B", body: "second", createdAt: "2026-08-11T00:02:00.000Z" },
|
|
423
|
-
{ authorName: "C", body: "third", createdAt: "2026-08-11T00:03:00.000Z" },
|
|
424
|
-
],
|
|
425
|
-
{ max: 2000 },
|
|
426
|
-
);
|
|
427
|
-
assert.match(out, /B: second/);
|
|
428
|
-
assert.match(out, /C: third/);
|
|
429
|
-
assert.ok(out.length <= 2100, `bounded, got ${out.length}`);
|
|
430
|
-
});
|
|
431
|
-
|
|
432
|
-
// Until hq's `authorName` denormalisation is deployed every row carries a bare
|
|
433
|
-
// cuid, and the agent must still be able to see which turns are its own.
|
|
434
|
-
test("a transcript labels my own turns even when the server sends no names", async () => {
|
|
435
|
-
const msgs = [
|
|
436
|
-
{ id: "m1", authorId: ME, body: "already answered that", createdAt: "2026-08-11T14:00:00.000Z" },
|
|
437
|
-
{ id: "m2", authorId: THEM, body: "and the other thing?", createdAt: "2026-08-11T15:00:00.000Z" },
|
|
438
|
-
];
|
|
439
|
-
const f = facts({ myNames: ["Isla"] });
|
|
440
|
-
f.channels.set("C-dm", { id: "C-dm", kind: "DM" });
|
|
441
|
-
f.channelPages.set("C-dm", { byId: new Map(msgs.map((m) => [m.id, m])), messages: msgs });
|
|
442
|
-
const c = classifyEvent(ev({ family: "messaging", kind: "send", entity_id: "m2", payload: { actor: THEM, channelId: "C-dm", channelKind: "DM" } }));
|
|
443
|
-
const h = await hydrate({ candidate: c, verdict: { surface: "dm", reason: "dm" }, facts: f, io: fakeIo({}) });
|
|
444
|
-
|
|
445
|
-
assert.match(h.threadContext, /Isla: already answered that/, "my own turn is mine, not a cuid");
|
|
446
|
-
});
|
|
447
|
-
|
|
448
|
-
test("event-kind phrasing is stable and total", () => {
|
|
449
|
-
assert.match(describeBoardKind("item.assigned"), /assigned to you/);
|
|
450
|
-
assert.match(describeBoardKind("who.knows"), /Update on your board item/);
|
|
451
|
-
assert.match(describeApprovalKind("approval.requested"), /waiting on you/);
|
|
452
|
-
assert.match(describeApprovalKind("approval.rejected"), /REJECTED/);
|
|
453
|
-
});
|