@cohortapp/agent-sdk 2.17.0 → 2.18.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.json +18 -0
- package/.env.example +18 -5
- package/README.md +1 -0
- package/bin/maestro.mjs +62 -0
- package/docs/guides/billing-console-keys.md +60 -0
- package/docs/guides/front-door-session.md +54 -9
- package/docs/guides/mac-mini.md +20 -25
- package/docs/guides/setup-wizard.md +1 -1
- package/docs/runbooks/fleet-rollout.md +156 -0
- package/docs/runbooks/mac-mini-bootstrap.md +12 -14
- package/lib/action-executor.js +19 -3
- package/lib/budget-guard.mjs +279 -3
- package/lib/channels/base-adapter.mjs +3 -1
- package/lib/channels/contract.mjs +2 -1
- package/lib/channels/inbox-item.mjs +8 -0
- package/lib/claude-bin.mjs +5 -6
- package/lib/cli/doctor-checks.mjs +141 -10
- package/lib/cli/global-setup-extras.mjs +5 -1
- package/lib/cli/inbox.mjs +100 -15
- package/lib/cli/seat-auth.mjs +463 -0
- package/lib/cli/session.mjs +80 -12
- package/lib/collective/capture.mjs +8 -6
- package/lib/collective/global-config.mjs +63 -1
- package/lib/collective/presence.mjs +142 -5
- package/lib/comms/send-gate.mjs +559 -1
- package/lib/diagnostics/alerts.mjs +49 -0
- package/lib/diagnostics/cadence-output-freshness.mjs +288 -0
- package/lib/engine/agents/definitions.mjs +343 -0
- package/lib/engine/agents/persist.mjs +275 -0
- package/lib/engine/agents/runtime.mjs +748 -0
- package/lib/engine/agents/usage.mjs +95 -0
- package/lib/engine/auth-status.mjs +139 -0
- package/lib/engine/budget.mjs +194 -0
- package/lib/engine/cli.mjs +1204 -0
- package/lib/engine/commands/index.mjs +269 -0
- package/lib/engine/context/budget.mjs +219 -0
- package/lib/engine/context/cache.mjs +125 -0
- package/lib/engine/context/child-env.mjs +215 -0
- package/lib/engine/context/compaction.mjs +342 -0
- package/lib/engine/context/images.mjs +90 -0
- package/lib/engine/context/instructions.mjs +327 -0
- package/lib/engine/context/lazy-instructions.mjs +169 -0
- package/lib/engine/context/manager.mjs +182 -0
- package/lib/engine/context/real-path.mjs +91 -0
- package/lib/engine/context/secret-values.mjs +163 -0
- package/lib/engine/context/settings.mjs +274 -0
- package/lib/engine/context/stream-input.mjs +159 -0
- package/lib/engine/guard.mjs +152 -0
- package/lib/engine/hooks.mjs +713 -0
- package/lib/engine/loop.mjs +560 -0
- package/lib/engine/mcp/client.mjs +254 -0
- package/lib/engine/mcp/config.mjs +301 -0
- package/lib/engine/mcp/http.mjs +201 -0
- package/lib/engine/mcp/index.mjs +146 -0
- package/lib/engine/mcp/jsonrpc.mjs +147 -0
- package/lib/engine/mcp/naming.mjs +66 -0
- package/lib/engine/mcp/resources.mjs +89 -0
- package/lib/engine/mcp/results.mjs +133 -0
- package/lib/engine/mcp/stdio.mjs +137 -0
- package/lib/engine/mcp/supervisor.mjs +116 -0
- package/lib/engine/messages.mjs +104 -0
- package/lib/engine/output/json.mjs +164 -0
- package/lib/engine/output/stream-json.mjs +266 -0
- package/lib/engine/permissions.mjs +845 -0
- package/lib/engine/process-identity.mjs +164 -0
- package/lib/engine/process-tree.mjs +551 -0
- package/lib/engine/prompt.mjs +60 -0
- package/lib/engine/session/store.mjs +299 -0
- package/lib/engine/session-runtime/args.mjs +97 -0
- package/lib/engine/session-runtime/host.mjs +143 -0
- package/lib/engine/session-runtime/inbox.mjs +122 -0
- package/lib/engine/session-runtime/notifications.mjs +129 -0
- package/lib/engine/session-runtime/registry.mjs +328 -0
- package/lib/engine/session-runtime/runner.mjs +344 -0
- package/lib/engine/session-runtime/socket.mjs +212 -0
- package/lib/engine/session-runtime/wakeup.mjs +115 -0
- package/lib/engine/skills/index.mjs +321 -0
- package/lib/engine/tools/bash-background.mjs +533 -0
- package/lib/engine/tools/bash.mjs +216 -0
- package/lib/engine/tools/edit.mjs +97 -0
- package/lib/engine/tools/glob.mjs +81 -0
- package/lib/engine/tools/grep.mjs +224 -0
- package/lib/engine/tools/index.mjs +84 -0
- package/lib/engine/tools/list-agents.mjs +32 -0
- package/lib/engine/tools/ls.mjs +127 -0
- package/lib/engine/tools/monitor.mjs +82 -0
- package/lib/engine/tools/notebook-edit.mjs +218 -0
- package/lib/engine/tools/read.mjs +103 -0
- package/lib/engine/tools/schedule-wakeup.mjs +45 -0
- package/lib/engine/tools/schema.mjs +144 -0
- package/lib/engine/tools/send-message.mjs +77 -0
- package/lib/engine/tools/session.mjs +70 -0
- package/lib/engine/tools/todo.mjs +144 -0
- package/lib/engine/tools/toolsearch.mjs +217 -0
- package/lib/engine/tools/walk.mjs +193 -0
- package/lib/engine/tools/web-switch.mjs +31 -0
- package/lib/engine/tools/webfetch-html.mjs +387 -0
- package/lib/engine/tools/webfetch-net.mjs +340 -0
- package/lib/engine/tools/webfetch.mjs +198 -0
- package/lib/engine/tools/websearch.mjs +91 -0
- package/lib/engine/tools/workflow.mjs +95 -0
- package/lib/engine/tools/write.mjs +76 -0
- package/lib/engine/tui/line-editor.mjs +137 -0
- package/lib/engine/tui/render.mjs +86 -0
- package/lib/engine/tui/tui.mjs +274 -0
- package/lib/engine/wire/anthropic-messages.mjs +263 -0
- package/lib/engine/wire/effort.mjs +36 -0
- package/lib/engine/wire/errors.mjs +496 -0
- package/lib/engine/wire/http.mjs +441 -0
- package/lib/engine/wire/index.mjs +76 -0
- package/lib/engine/wire/openai-chat.mjs +332 -0
- package/lib/engine/wire/prompt-cache.mjs +79 -0
- package/lib/engine/wire/search.mjs +140 -0
- package/lib/engine/wire/sse.mjs +114 -0
- package/lib/engine/wire/stall.mjs +349 -0
- package/lib/engine/wire/token-provider.mjs +175 -0
- package/lib/engine/wire/usage.mjs +192 -0
- package/lib/engine/workflow/host.mjs +524 -0
- package/lib/engine/workflow/journal.mjs +188 -0
- package/lib/engine/workflow/json-schema.mjs +171 -0
- package/lib/engine/workflow/meta.mjs +329 -0
- package/lib/engine/workflow/notifications.mjs +52 -0
- package/lib/engine/workflow/runtime.mjs +447 -0
- package/lib/engine/workflow/sandbox.mjs +534 -0
- package/lib/engine/workflow/worker.mjs +141 -0
- package/lib/engine/workflow/worktree.mjs +74 -0
- package/lib/execution/disposition.mjs +1 -1
- package/lib/execution/intake.mjs +10 -0
- package/lib/execution/surface-policy.mjs +15 -0
- package/lib/learning/curator.mjs +8 -6
- package/lib/learning/reflect.mjs +8 -6
- package/lib/model-router/catalog/cohort.yaml +137 -0
- package/lib/model-router/catalog.mjs +118 -1
- package/lib/model-router/failover.mjs +67 -16
- package/lib/model-router/llm-task.mjs +39 -3
- package/lib/model-router/resolve.mjs +89 -3
- package/lib/model-router/spawn.mjs +46 -47
- package/lib/model-router/taxonomy.mjs +126 -4
- package/lib/org/cost-sync.mjs +141 -11
- package/lib/org/inbound/broadcast.mjs +289 -0
- package/lib/org/inbound/collective.mjs +375 -0
- package/lib/org/inbound/directedness.mjs +96 -8
- package/lib/org/inbound/facts.mjs +78 -2
- package/lib/org/inbound/project.mjs +22 -0
- package/lib/org/inbound/surfaces.mjs +14 -0
- package/lib/org/llm-token.mjs +879 -0
- package/lib/org/mesh.mjs +61 -0
- package/lib/org/messaging.mjs +3 -1
- package/lib/org/protocol.checksum +1 -1
- package/lib/org/protocol.mjs +15 -0
- package/lib/org/quota.mjs +520 -0
- package/lib/org/tool-surface.mjs +104 -16
- package/lib/org/ui-parity.mjs +16 -1
- package/lib/org/work-ledger.mjs +37 -6
- package/lib/rate-guard.mjs +114 -1
- package/lib/resource-governor.mjs +41 -6
- package/lib/runtime/adapter.mjs +823 -0
- package/lib/runtime/child-env.mjs +191 -0
- package/lib/runtime/legacy-shell-guard.mjs +97 -0
- package/lib/runtime/seat-engine.mjs +162 -0
- package/lib/session/ask-ledger.mjs +271 -0
- package/lib/session/current-work.mjs +676 -0
- package/lib/session/feed-core.mjs +40 -3
- package/lib/session/launch-args.mjs +56 -4
- package/lib/session/status-summary.mjs +26 -9
- package/lib/session/upgrade-notice.mjs +42 -0
- package/lib/setup/claude-probe.mjs +117 -13
- package/lib/setup/enrich.mjs +13 -10
- package/lib/setup/sections/model.mjs +39 -13
- package/lib/telemetry/collect.mjs +208 -9
- package/lib/upgrade/ignored-drift.mjs +105 -0
- package/lib/voice/post-call-brief.mjs +30 -17
- package/package.json +13 -3
- package/plugins/maestro-skills/skills/board-work.md +5 -0
- package/plugins/maestro-skills/skills/inbound-triage.md +56 -15
- package/plugins/maestro-skills/skills/main-session.md +18 -7
- package/scripts/ci/check-tarball-fidelity.mjs +126 -2
- package/scripts/ci/run-tests.mjs +47 -19
- package/scripts/cohort-llm/api-key-helper.mjs +92 -0
- package/scripts/collective/hook-runner.mjs +29 -2
- package/scripts/continuous-monitor.sh +13 -0
- package/scripts/cost/track-claude-usage.mjs +15 -0
- package/scripts/daemon/agent-daemon.mjs +408 -20
- package/scripts/daemon/assurance.mjs +48 -12
- package/scripts/daemon/cadence-consumer.mjs +218 -68
- package/scripts/daemon/cadence-handlers.mjs +73 -4
- package/scripts/daemon/classifier.mjs +75 -26
- package/scripts/daemon/context-compiler.mjs +51 -37
- package/scripts/daemon/deliver.mjs +30 -1
- package/scripts/daemon/dispatcher.mjs +595 -149
- package/scripts/daemon/health.mjs +14 -1
- package/scripts/daemon/maestro-daemon.mjs +11 -0
- package/scripts/daemon/prompt-builder.mjs +24 -0
- package/scripts/daemon/responder.mjs +246 -79
- package/scripts/daemon/sdk-version.mjs +98 -16
- package/scripts/eval/probe-gateway.mjs +635 -0
- package/scripts/eval/replay/extract.mjs +270 -0
- package/scripts/eval/replay/grade.mjs +260 -0
- package/scripts/eval/replay/lib/config.mjs +50 -0
- package/scripts/eval/replay/lib/effects.mjs +65 -0
- package/scripts/eval/replay/lib/fixture.mjs +188 -0
- package/scripts/eval/replay/lib/judge.mjs +72 -0
- package/scripts/eval/replay/lib/redact.mjs +136 -0
- package/scripts/eval/replay/lib/sandbox.mjs +170 -0
- package/scripts/eval/replay/lib/schema-check.mjs +63 -0
- package/scripts/eval/replay/lib/transcript.mjs +76 -0
- package/scripts/eval/replay/mcp-replay-stub.mjs +101 -0
- package/scripts/eval/replay/report.mjs +185 -0
- package/scripts/eval/replay/run.mjs +404 -0
- package/scripts/fleet/rollout.mjs +1094 -0
- package/scripts/hooks/pre-send-audit.sh +36 -245
- package/scripts/hooks/pre-write-yaml-validate.mjs +275 -0
- package/scripts/hooks/validate-state-yaml.sh +190 -0
- package/scripts/huddle/huddle-llm.mjs +361 -0
- package/scripts/huddle/huddle-server.mjs +46 -121
- package/scripts/local-triggers/autoupdate.sh +448 -78
- package/scripts/local-triggers/run-trigger.sh +13 -0
- package/scripts/maintenance/pin-integrity.mjs +364 -0
- package/scripts/poll-slack-events.sh +41 -9
- package/scripts/poller/slack-socket-mode.mjs +28 -3
- package/scripts/session/supervisor.mjs +80 -13
- package/scripts/spawn-session.sh +13 -0
- package/bin/maestro.test.mjs +0 -1574
- package/lib/action-executor.test.mjs +0 -871
- package/lib/archetype.test.mjs +0 -132
- package/lib/assurance/plan-note.test.mjs +0 -234
- package/lib/assurance/room-budget.test.mjs +0 -486
- package/lib/assurance/tier.test.mjs +0 -174
- package/lib/autonomy.test.mjs +0 -66
- package/lib/backlog.test.mjs +0 -302
- package/lib/backup/policy.test.mjs +0 -305
- package/lib/budget-escalate.test.mjs +0 -232
- package/lib/budget-guard.envelope.test.mjs +0 -476
- package/lib/budget-guard.test.mjs +0 -427
- package/lib/cadence-bus-requeue.test.mjs +0 -83
- package/lib/cadence-bus-schedule.test.mjs +0 -194
- package/lib/cadence-bus.test.mjs +0 -720
- package/lib/cadences.test.mjs +0 -230
- package/lib/capability/inventory.test.mjs +0 -232
- package/lib/capability.test.mjs +0 -78
- package/lib/channels/base-adapter.test.mjs +0 -590
- package/lib/channels/channels.test.mjs +0 -371
- package/lib/channels/contract.test.mjs +0 -162
- package/lib/channels/inbox-item.test.mjs +0 -368
- package/lib/channels/orgmail/adapter.test.mjs +0 -448
- package/lib/channels/pairing.test.mjs +0 -270
- package/lib/channels/repeat-suppressor.test.mjs +0 -134
- package/lib/channels/slack-adapter.test.mjs +0 -212
- package/lib/channels/telegram-adapter.test.mjs +0 -306
- package/lib/channels/voice/adapter.test.mjs +0 -278
- package/lib/channels/whatsapp/adapter-baileys.test.mjs +0 -359
- package/lib/channels/whatsapp/baileys-typing.test.mjs +0 -154
- package/lib/charter.test.mjs +0 -89
- package/lib/claude-bin.test.mjs +0 -131
- package/lib/cli/board.test.mjs +0 -227
- package/lib/cli/design.test.mjs +0 -270
- package/lib/cli/doctor-checks.test.mjs +0 -336
- package/lib/cli/global-setup-extras.test.mjs +0 -462
- package/lib/cli/inbox.test.mjs +0 -230
- package/lib/cli/session-ack.test.mjs +0 -63
- package/lib/cli/session.test.mjs +0 -613
- package/lib/collective/capture.test.mjs +0 -121
- package/lib/collective/cards.test.mjs +0 -114
- package/lib/collective/config.test.mjs +0 -123
- package/lib/collective/global-config.test.mjs +0 -220
- package/lib/collective/global-skills.test.mjs +0 -126
- package/lib/collective/presence.test.mjs +0 -95
- package/lib/collective/recall.test.mjs +0 -116
- package/lib/collective/vendor-skills.test.mjs +0 -306
- package/lib/comms/send-gate.test.mjs +0 -770
- package/lib/comms.test.mjs +0 -41
- package/lib/context/budget.test.mjs +0 -252
- package/lib/context/history-scope.test.mjs +0 -79
- package/lib/cost/ledger-row.test.mjs +0 -183
- package/lib/design/design-md.test.mjs +0 -318
- package/lib/design/fixtures/DESIGN.golden.md +0 -238
- package/lib/design/fixtures/PRODUCT.golden.md +0 -67
- package/lib/design/fixtures/foundation.json +0 -133
- package/lib/design/refresh-gate.test.mjs +0 -144
- package/lib/design/write.test.mjs +0 -241
- package/lib/diagnostics/alerts.test.mjs +0 -318
- package/lib/diagnostics/backup-freshness.test.mjs +0 -185
- package/lib/diagnostics/counters.test.mjs +0 -206
- package/lib/diagnostics/events.test.mjs +0 -290
- package/lib/diagnostics/otel.test.mjs +0 -196
- package/lib/diagnostics/trace.test.mjs +0 -251
- package/lib/env-compat.test.mjs +0 -104
- package/lib/execution/disposition.test.mjs +0 -553
- package/lib/execution/drive.test.mjs +0 -270
- package/lib/execution/effects.test.mjs +0 -344
- package/lib/execution/intake.test.mjs +0 -389
- package/lib/execution/journal.test.mjs +0 -261
- package/lib/execution/match.test.mjs +0 -235
- package/lib/execution/pipeline.test.mjs +0 -392
- package/lib/execution/route.test.mjs +0 -186
- package/lib/execution/surface-policy.test.mjs +0 -162
- package/lib/fs-atomic.test.mjs +0 -72
- package/lib/fs-ownership.test.mjs +0 -158
- package/lib/goals/admission.test.mjs +0 -164
- package/lib/goals/classify.test.mjs +0 -167
- package/lib/goals/collaborate.test.mjs +0 -336
- package/lib/goals/gaps.test.mjs +0 -284
- package/lib/goals/loop.test.mjs +0 -845
- package/lib/hooks/bus.test.mjs +0 -387
- package/lib/identity/persona.test.mjs +0 -142
- package/lib/kpi-sensors.test.mjs +0 -278
- package/lib/kpi.test.mjs +0 -244
- package/lib/learning/config.test.mjs +0 -75
- package/lib/learning/counters.test.mjs +0 -69
- package/lib/learning/curator-consolidate.test.mjs +0 -238
- package/lib/learning/curator.test.mjs +0 -106
- package/lib/learning/reflect.test.mjs +0 -0
- package/lib/learning/session-index.test.mjs +0 -125
- package/lib/learning/skill-writer.test.mjs +0 -210
- package/lib/mandate/audit.test.mjs +0 -195
- package/lib/mandate/contract.test.mjs +0 -185
- package/lib/mandate/derive.test.mjs +0 -274
- package/lib/mandate/model.test.mjs +0 -164
- package/lib/mandate/refresh.test.mjs +0 -389
- package/lib/mcp/server.test.mjs +0 -426
- package/lib/model-router/auth-profiles.test.mjs +0 -580
- package/lib/model-router/catalog.test.mjs +0 -385
- package/lib/model-router/economics.test.mjs +0 -438
- package/lib/model-router/failover.test.mjs +0 -439
- package/lib/model-router/health.test.mjs +0 -338
- package/lib/model-router/integration-coverage.test.mjs +0 -831
- package/lib/model-router/integration.test.mjs +0 -564
- package/lib/model-router/ledger.test.mjs +0 -415
- package/lib/model-router/llm-task.test.mjs +0 -392
- package/lib/model-router/org-credentials.test.mjs +0 -265
- package/lib/model-router/pricing-refresh.test.mjs +0 -286
- package/lib/model-router/reconcile.test.mjs +0 -316
- package/lib/model-router/repair.test.mjs +0 -180
- package/lib/model-router/spawn.test.mjs +0 -446
- package/lib/model-router/taxonomy.test.mjs +0 -410
- package/lib/model-router.test.mjs +0 -1207
- package/lib/org/activity.test.mjs +0 -134
- package/lib/org/approvals.test.mjs +0 -216
- package/lib/org/awareness.test.mjs +0 -159
- package/lib/org/board-mine-cache.test.mjs +0 -53
- package/lib/org/board.test.mjs +0 -187
- package/lib/org/bootstrap-context.test.mjs +0 -153
- package/lib/org/client.test.mjs +0 -1206
- package/lib/org/cohort-client.test.mjs +0 -126
- package/lib/org/cost-sync.test.mjs +0 -153
- package/lib/org/doctor.test.mjs +0 -346
- package/lib/org/engagement-ledger.test.mjs +0 -112
- package/lib/org/engagement.test.mjs +0 -739
- package/lib/org/handoff.test.mjs +0 -269
- package/lib/org/inbound/directedness.test.mjs +0 -668
- package/lib/org/inbound/facts.test.mjs +0 -471
- package/lib/org/inbound/hydrate.test.mjs +0 -908
- package/lib/org/inbound/index.test.mjs +0 -429
- package/lib/org/inbound/project.test.mjs +0 -287
- package/lib/org/integration-tools.test.mjs +0 -160
- package/lib/org/keys.test.mjs +0 -92
- package/lib/org/knowledge.test.mjs +0 -326
- package/lib/org/leases.test.mjs +0 -235
- package/lib/org/mesh-directives.test.mjs +0 -110
- package/lib/org/mesh-integration.test.mjs +0 -127
- package/lib/org/mesh.test.mjs +0 -400
- package/lib/org/messaging.test.mjs +0 -471
- package/lib/org/param-contract.test.mjs +0 -477
- package/lib/org/policy.test.mjs +0 -237
- package/lib/org/protocol.checksum.test.mjs +0 -90
- package/lib/org/protocol.test.mjs +0 -323
- package/lib/org/push.test.mjs +0 -792
- package/lib/org/registry.test.mjs +0 -100
- package/lib/org/resource-tools.test.mjs +0 -361
- package/lib/org/tool-access.test.mjs +0 -144
- package/lib/org/tool-surface-integration.test.mjs +0 -120
- package/lib/org/tool-surface.test.mjs +0 -1268
- package/lib/org/typing.test.mjs +0 -291
- package/lib/org/ui-parity.test.mjs +0 -560
- package/lib/org/verify.test.mjs +0 -194
- package/lib/org/work-ledger.test.mjs +0 -273
- package/lib/plan/adoption-e2e.test.mjs +0 -366
- package/lib/plan/budget-enforcement.test.mjs +0 -400
- package/lib/plan/compile.test.mjs +0 -382
- package/lib/plan/emit.test.mjs +0 -269
- package/lib/plan/explain.test.mjs +0 -188
- package/lib/prompts/parallelism.test.mjs +0 -177
- package/lib/rag/rag.test.mjs +0 -505
- package/lib/rate-guard.test.mjs +0 -272
- package/lib/reactive-gate.test.mjs +0 -57
- package/lib/render.test.mjs +0 -68
- package/lib/resource-governor.test.mjs +0 -488
- package/lib/scheduling/dynamic-jobs.test.mjs +0 -344
- package/lib/scheduling/jitter.test.mjs +0 -140
- package/lib/secrets/broker.test.mjs +0 -280
- package/lib/secrets/providers.test.mjs +0 -274
- package/lib/security/audit-engine.test.mjs +0 -424
- package/lib/security/coerce-args.test.mjs +0 -281
- package/lib/security/dangerous-tools.test.mjs +0 -68
- package/lib/security/external-content.test.mjs +0 -84
- package/lib/security/redact.test.mjs +0 -441
- package/lib/security/secret-equal.test.mjs +0 -55
- package/lib/session/config.test.mjs +0 -92
- package/lib/session/feed-core.test.mjs +0 -198
- package/lib/session/first-run.test.mjs +0 -121
- package/lib/session/frontdoor.test.mjs +0 -205
- package/lib/session/handoffs.test.mjs +0 -183
- package/lib/session/identity.test.mjs +0 -180
- package/lib/session/inbox-claims.test.mjs +0 -286
- package/lib/session/launch-args.test.mjs +0 -157
- package/lib/session/liveness.test.mjs +0 -100
- package/lib/session/status-summary.test.mjs +0 -118
- package/lib/session-permissions.test.mjs +0 -120
- package/lib/setup/claude-probe.test.mjs +0 -187
- package/lib/setup/completeness.test.mjs +0 -110
- package/lib/setup/context-pack.test.mjs +0 -89
- package/lib/setup/enrich.test.mjs +0 -115
- package/lib/setup/enroll-from-cohort.test.mjs +0 -300
- package/lib/setup/integration.test.mjs +0 -162
- package/lib/setup/io.test.mjs +0 -77
- package/lib/setup/runner.test.mjs +0 -132
- package/lib/setup/sections/identity.test.mjs +0 -234
- package/lib/setup/sections/inventory.test.mjs +0 -198
- package/lib/setup/sections/learning.test.mjs +0 -81
- package/lib/setup/sections/mandate.test.mjs +0 -388
- package/lib/setup/sections/messaging.test.mjs +0 -127
- package/lib/setup/sections/model.test.mjs +0 -240
- package/lib/setup/sections/org.test.mjs +0 -346
- package/lib/setup/sections/orgmail.test.mjs +0 -118
- package/lib/setup/sections/recovery.test.mjs +0 -98
- package/lib/setup/sections/subagents.test.mjs +0 -429
- package/lib/setup/sections/verify.test.mjs +0 -175
- package/lib/setup/sot.test.mjs +0 -81
- package/lib/setup/state.test.mjs +0 -115
- package/lib/singleton.test.mjs +0 -151
- package/lib/subagents/cli.test.mjs +0 -389
- package/lib/subagents/client.test.mjs +0 -309
- package/lib/subagents/gap.test.mjs +0 -234
- package/lib/subagents/lock.test.mjs +0 -248
- package/lib/subagents/manifest.test.mjs +0 -175
- package/lib/subagents/refs.test.mjs +0 -204
- package/lib/subagents/resolve.test.mjs +0 -422
- package/lib/subagents/schema.test.mjs +0 -328
- package/lib/telemetry/alerts.test.mjs +0 -109
- package/lib/telemetry/collect.test.mjs +0 -1274
- package/lib/tool-definitions-integration.test.mjs +0 -83
- package/lib/tool-definitions.test.mjs +0 -437
- package/lib/upgrade/global-refresh.test.mjs +0 -65
- package/lib/upgrade/launchd-reconcile.test.mjs +0 -272
- package/lib/upgrade/post-steps.test.mjs +0 -200
- package/lib/upgrade/verify.test.mjs +0 -164
- package/lib/util/fetch-timeout.test.mjs +0 -202
- package/lib/util/reconnect.test.mjs +0 -369
- package/lib/util/unhandled.test.mjs +0 -216
- package/lib/voice/outbound.test.mjs +0 -69
- package/lib/voice/session-rotation.test.mjs +0 -114
- package/lib/voice/stt.test.mjs +0 -226
- package/lib/voice/voice.test.mjs +0 -990
- package/scripts/cadence/enqueue-cadence-tick.test.mjs +0 -187
- package/scripts/ci/check-docs-accuracy.test.mjs +0 -409
- package/scripts/ci/check-durable-write-seam.test.mjs +0 -90
- package/scripts/ci/check-no-build-artifacts.test.mjs +0 -71
- package/scripts/ci/check-no-residual-identity.test.mjs +0 -202
- package/scripts/ci/check-skill-packs.test.mjs +0 -495
- package/scripts/ci/check-subagent-frontmatter.test.mjs +0 -124
- package/scripts/ci/check.test.mjs +0 -194
- package/scripts/ci/conformance-org-api.test.mjs +0 -425
- package/scripts/cloud-relay/voice/relay-identity.test.mjs +0 -96
- package/scripts/collective/hook-runner.test.mjs +0 -173
- package/scripts/cost/fleet-digest.test.mjs +0 -207
- package/scripts/cost/track-claude-usage-pricing.test.mjs +0 -183
- package/scripts/cost/track-claude-usage.test.mjs +0 -148
- package/scripts/daemon/agent-daemon-board-mine.test.mjs +0 -96
- package/scripts/daemon/agent-daemon-design.test.mjs +0 -238
- package/scripts/daemon/agent-daemon-frontdoor.test.mjs +0 -60
- package/scripts/daemon/agent-daemon.test.mjs +0 -995
- package/scripts/daemon/assurance-e2e.test.mjs +0 -613
- package/scripts/daemon/assurance.test.mjs +0 -1791
- package/scripts/daemon/board-mirror.test.mjs +0 -165
- package/scripts/daemon/cadence-consumer-frontdoor.test.mjs +0 -393
- package/scripts/daemon/cadence-consumer-governance.test.mjs +0 -276
- package/scripts/daemon/cadence-consumer.test.mjs +0 -776
- package/scripts/daemon/cadence-handlers.test.mjs +0 -837
- package/scripts/daemon/classifier-identity.test.mjs +0 -137
- package/scripts/daemon/classifier.test.mjs +0 -266
- package/scripts/daemon/classify-kind.test.mjs +0 -40
- package/scripts/daemon/context-compiler.test.mjs +0 -406
- package/scripts/daemon/deliver.test.mjs +0 -564
- package/scripts/daemon/dispatcher-cooldown.test.mjs +0 -122
- package/scripts/daemon/dispatcher-governance.test.mjs +0 -1013
- package/scripts/daemon/dispatcher-resume.test.mjs +0 -166
- package/scripts/daemon/dispatcher-session-continuity.test.mjs +0 -365
- package/scripts/daemon/execution-ladder.test.mjs +0 -470
- package/scripts/daemon/goal-steward-cadence.test.mjs +0 -312
- package/scripts/daemon/inbox-deferral-session.test.mjs +0 -49
- package/scripts/daemon/inbox-deferral.test.mjs +0 -336
- package/scripts/daemon/inbox-wake.test.mjs +0 -199
- package/scripts/daemon/integration.test.mjs +0 -149
- package/scripts/daemon/lib/self-echo.test.mjs +0 -153
- package/scripts/daemon/lib/session-router.test.mjs +0 -554
- package/scripts/daemon/prompt-builder-preamble.test.mjs +0 -210
- package/scripts/daemon/prompt-builder.test.mjs +0 -556
- package/scripts/daemon/responder-cost.test.mjs +0 -68
- package/scripts/daemon/responder-history.test.mjs +0 -221
- package/scripts/daemon/sdk-version.test.mjs +0 -31
- package/scripts/daemon/session-lock.test.mjs +0 -252
- package/scripts/daemon/session-outcomes.test.mjs +0 -533
- package/scripts/daemon/typing-registry.test.mjs +0 -102
- package/scripts/hooks/pre-send-audit.test.mjs +0 -354
- package/scripts/huddle/huddle-prompt.test.mjs +0 -176
- package/scripts/local-triggers/autoupdate.test.mjs +0 -518
- package/scripts/local-triggers/generate-plists.test.mjs +0 -456
- package/scripts/media-generation/brand-clause.test.mjs +0 -135
- package/scripts/org/send-orgmail.first-contact.test.mjs +0 -102
- package/scripts/poller/inbox-privilege-injection.test.mjs +0 -167
- package/scripts/poller/inbox-scan-poller.test.mjs +0 -295
- package/scripts/poller/lib/cloud-relay-dedup.test.mjs +0 -133
- package/scripts/poller/slack-socket-mode.test.mjs +0 -805
- package/scripts/poller-launchd/install.test.mjs +0 -243
- package/scripts/restore-from-backup.test.mjs +0 -181
- package/scripts/session/feed.test.mjs +0 -196
- package/scripts/session/supervisor-sh.test.mjs +0 -218
- package/scripts/session/supervisor.test.mjs +0 -482
- package/scripts/setup/configure-macos.test.mjs +0 -306
- package/scripts/setup/gen-subagent-manifest.test.mjs +0 -124
- package/scripts/setup/generate-agent-package-json.test.mjs +0 -143
- package/scripts/setup/generate-capability.test.mjs +0 -134
- package/scripts/setup/init-agent.test.mjs +0 -370
- package/scripts/setup/init-skill-marketplace.test.mjs +0 -193
- package/scripts/vendor/sync-skill-packs.test.mjs +0 -103
- package/scripts/watchdog/memory-watchdog.test.mjs +0 -64
|
@@ -1,533 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* session-outcomes.test.mjs — the daemon's OUTCOME DISCIPLINE.
|
|
3
|
-
*
|
|
4
|
-
* hq guarantees a live call's action points become real board rows and sweeps the
|
|
5
|
-
* ones the chair never wrote. These tests assert the daemon's parity:
|
|
6
|
-
*
|
|
7
|
-
* CAPTURE the OUTCOMES block is read off the turn's final text, deterministically
|
|
8
|
-
* and bounded, and ordinary prose can never be mistaken for a commitment
|
|
9
|
-
* GATE a landed outcome is ORG-VISIBLE, so only an already-org-visible source
|
|
10
|
-
* may produce one — fail-closed, unlike the rest of this module
|
|
11
|
-
* LAND each commitment goes through the EXISTING org protocol (board.create +
|
|
12
|
-
* one knowledge.append episode) — no new methods
|
|
13
|
-
* SWEEP the post-session hook is DRIVEN, idempotent, fail-open, and OFF unless
|
|
14
|
-
* DAEMON_SESSION_OUTCOMES=1
|
|
15
|
-
*
|
|
16
|
-
* The org collaborators are injected, so nothing here touches a network or a DB.
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
import { test, beforeEach } from "node:test";
|
|
20
|
-
import assert from "node:assert/strict";
|
|
21
|
-
|
|
22
|
-
import * as so from "./session-outcomes.mjs";
|
|
23
|
-
|
|
24
|
-
const ENABLED_CFG = { org: { cohort: { enabled: true, base: "https://org.example" } } };
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* A public Slack channel message — the ONLY source whose outcomes may land.
|
|
28
|
-
* `channel_type: "channel"` is what establishes that; the `C…` id does NOT
|
|
29
|
-
* (Slack mints `C…` ids for private channels too).
|
|
30
|
-
*/
|
|
31
|
-
const PUBLIC_ITEM = {
|
|
32
|
-
service: "slack",
|
|
33
|
-
channel: "general",
|
|
34
|
-
channel_id: "C0GENERAL",
|
|
35
|
-
channel_type: "channel",
|
|
36
|
-
subject: "#general",
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
/** A createItem fake that records its calls and returns a success frame. */
|
|
40
|
-
function fakeCreate(sink, behaviour) {
|
|
41
|
-
return async (item, opts) => {
|
|
42
|
-
sink.push({ item, opts });
|
|
43
|
-
if (typeof behaviour === "function") return behaviour(item);
|
|
44
|
-
return { ok: true, result: { id: item.itemId } };
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function fakeRemember(sink) {
|
|
49
|
-
return async (text, opts) => {
|
|
50
|
-
sink.push({ text, opts });
|
|
51
|
-
return { ok: true, mode: "append", id: "k1" };
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
beforeEach(() => so._resetSessionOutcomes());
|
|
56
|
-
|
|
57
|
-
// ---------------------------------------------------------------------------
|
|
58
|
-
// CAPTURE
|
|
59
|
-
// ---------------------------------------------------------------------------
|
|
60
|
-
|
|
61
|
-
test("CAPTURE: reads the OUTCOMES block, with owner and timing", () => {
|
|
62
|
-
const text = [
|
|
63
|
-
"Sent Alice the revised terms and flagged the pricing gap.",
|
|
64
|
-
"",
|
|
65
|
-
"OUTCOMES:",
|
|
66
|
-
"- Send the redlined MSA to Alice — owner: Robin — due: Friday",
|
|
67
|
-
"- Book the pricing review with Finance",
|
|
68
|
-
"",
|
|
69
|
-
"Let me know if anything else comes up.",
|
|
70
|
-
].join("\n");
|
|
71
|
-
|
|
72
|
-
const out = so.parseOutcomes(text);
|
|
73
|
-
assert.equal(out.length, 2, "two commitments");
|
|
74
|
-
assert.equal(out[0].title, "Send the redlined MSA to Alice");
|
|
75
|
-
assert.equal(out[0].owner, "Robin");
|
|
76
|
-
assert.equal(out[0].due, "Friday");
|
|
77
|
-
assert.equal(out[1].title, "Book the pricing review with Finance");
|
|
78
|
-
assert.equal(out[1].owner, undefined, "no owner invented");
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
test("CAPTURE: tolerates markdown decoration around the literal heading", () => {
|
|
82
|
-
const text = [
|
|
83
|
-
"## Outcomes",
|
|
84
|
-
"",
|
|
85
|
-
"1. Draft the board update",
|
|
86
|
-
"2. [ ] Chase the vendor SOC2",
|
|
87
|
-
].join("\n");
|
|
88
|
-
const out = so.parseOutcomes(text);
|
|
89
|
-
assert.deepEqual(out.map((o) => o.title), ["Draft the board update", "Chase the vendor SOC2"]);
|
|
90
|
-
|
|
91
|
-
const bold = so.parseOutcomes(["**OUTCOMES:**", "- Ship the fix"].join("\n"));
|
|
92
|
-
assert.deepEqual(bold.map((o) => o.title), ["Ship the fix"]);
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
test("CAPTURE: the capture surface is EXACTLY the contract the prompt establishes", () => {
|
|
96
|
-
// REGRESSION. These headings used to be accepted, which made the sweep wider
|
|
97
|
-
// than the contract: a turn that merely wrote a prose "Next steps:" section —
|
|
98
|
-
// possibly from a prompt that never carried OUTCOME_DISCIPLINE at all, since
|
|
99
|
-
// the prompt injects it at build time while the sweep re-reads the flag at
|
|
100
|
-
// close time — silently minted org-visible board rows.
|
|
101
|
-
const wider = [
|
|
102
|
-
"## Next steps\n\n1. Draft the board update",
|
|
103
|
-
"Next steps:\n- Draft the board update",
|
|
104
|
-
"**Action items:**\n- Ship the fix",
|
|
105
|
-
"Action points:\n- Ship the fix",
|
|
106
|
-
"Commitments:\n- Ship the fix",
|
|
107
|
-
];
|
|
108
|
-
for (const text of wider) {
|
|
109
|
-
assert.deepEqual(
|
|
110
|
-
so.parseOutcomes(text),
|
|
111
|
-
[],
|
|
112
|
-
`only the literal OUTCOMES heading is machine-read: ${JSON.stringify(text)}`
|
|
113
|
-
);
|
|
114
|
-
}
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
test("CAPTURE: an honest empty block stays empty", () => {
|
|
118
|
-
assert.deepEqual(so.parseOutcomes("Answered inline.\n\nOUTCOMES: none"), []);
|
|
119
|
-
assert.deepEqual(so.parseOutcomes("OUTCOMES:\n- none\n"), []);
|
|
120
|
-
assert.deepEqual(so.parseOutcomes(""), []);
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
test("CAPTURE: prose is never mistaken for a commitment block", () => {
|
|
124
|
-
// No colon after the keyword ⇒ not a heading. This is the false-positive that
|
|
125
|
-
// would otherwise put junk on the org board.
|
|
126
|
-
assert.deepEqual(so.parseOutcomes("Action items are clear from the thread."), []);
|
|
127
|
-
assert.deepEqual(so.parseOutcomes("The outcomes were disappointing this quarter."), []);
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
test("CAPTURE: the block terminates at the blank line, not at the end of the reply", () => {
|
|
131
|
-
const text = ["OUTCOMES:", "- Do the thing", "", "- this is a different list entirely"].join("\n");
|
|
132
|
-
assert.deepEqual(so.parseOutcomes(text).map((o) => o.title), ["Do the thing"]);
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
test("CAPTURE: bounded and deduped — a runaway summary cannot flood the board", () => {
|
|
136
|
-
const lines = ["OUTCOMES:"];
|
|
137
|
-
for (let i = 0; i < 30; i += 1) lines.push(`- step ${i}`);
|
|
138
|
-
const out = so.parseOutcomes(lines.join("\n"));
|
|
139
|
-
assert.equal(out.length, so.MAX_OUTCOMES, `capped at ${so.MAX_OUTCOMES}`);
|
|
140
|
-
|
|
141
|
-
const dupes = so.parseOutcomes(["OUTCOMES:", "- Ship it", "- ship IT", "- Ship it"].join("\n"));
|
|
142
|
-
assert.equal(dupes.length, 1, "case-insensitively deduped");
|
|
143
|
-
|
|
144
|
-
const long = so.parseOutcomes(`OUTCOMES:\n- ${"x".repeat(500)}`);
|
|
145
|
-
assert.ok(long[0].title.length <= 200, "title bounded");
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
test("CAPTURE: recovers the final text from --output-format json stdout", () => {
|
|
149
|
-
const stdout = JSON.stringify({ type: "result", result: "OUTCOMES:\n- Email the deck", usage: {} });
|
|
150
|
-
assert.equal(so.resultTextFromStdout(stdout), "OUTCOMES:\n- Email the deck");
|
|
151
|
-
|
|
152
|
-
// A leading banner line before the JSON tail (the tolerant path).
|
|
153
|
-
assert.equal(
|
|
154
|
-
so.resultTextFromStdout(`warning: something\n${JSON.stringify({ result: "hi" })}`),
|
|
155
|
-
"hi"
|
|
156
|
-
);
|
|
157
|
-
assert.equal(so.resultTextFromStdout(""), "", "empty stdout ⇒ empty");
|
|
158
|
-
assert.equal(so.resultTextFromStdout("{"), "{", "unparseable ⇒ treated as plain text, never throws");
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
// ---------------------------------------------------------------------------
|
|
162
|
-
// GATE — a landed outcome is org-visible, so the source must already be
|
|
163
|
-
// ---------------------------------------------------------------------------
|
|
164
|
-
|
|
165
|
-
test("GATE: a public Slack channel is the only source that qualifies", () => {
|
|
166
|
-
assert.deepEqual(so.outcomeSourceShareable(PUBLIC_ITEM), {
|
|
167
|
-
shareable: true,
|
|
168
|
-
reason: "public-channel",
|
|
169
|
-
});
|
|
170
|
-
assert.equal(
|
|
171
|
-
so.outcomeSourceShareable({ ...PUBLIC_ITEM, channel_id: "C0ABCDEF" }).shareable,
|
|
172
|
-
true
|
|
173
|
-
);
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
test("GATE: a private DM's commitments NEVER become org-visible artifacts", () => {
|
|
177
|
-
// The leak this closes: board.create with no workstreamId is channel-less (hq's
|
|
178
|
-
// board query applies no channel filter to it) and knowledge.append with no
|
|
179
|
-
// group resolves to `org` — so a DM's commitment text AND its topic line would
|
|
180
|
-
// be readable by every member of the org.
|
|
181
|
-
const dms = [
|
|
182
|
-
{ service: "slack", channel: "dm/alice", channel_id: "D01ALICE", subject: "DM from Alice" },
|
|
183
|
-
{ service: "slack", channel: "general", channel_id: "C0GENERAL", is_dm: true },
|
|
184
|
-
{ service: "slack", channel: "dm/alice" },
|
|
185
|
-
{ service: "slack", channel: "general", channel_id: "D01ALICE" },
|
|
186
|
-
];
|
|
187
|
-
for (const item of dms) {
|
|
188
|
-
const gate = so.outcomeSourceShareable(item);
|
|
189
|
-
assert.equal(gate.shareable, false, `DM must not land: ${JSON.stringify(item)}`);
|
|
190
|
-
assert.equal(gate.reason, "dm");
|
|
191
|
-
}
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
test("GATE: fail-CLOSED — anything not established as public lands nothing", () => {
|
|
195
|
-
const cases = [
|
|
196
|
-
[undefined, "no-source"],
|
|
197
|
-
[null, "no-source"],
|
|
198
|
-
["not an object", "no-source"],
|
|
199
|
-
[{}, "unknown-source"],
|
|
200
|
-
[{ service: "slack" }, "unknown-channel"],
|
|
201
|
-
[{ service: "slack", channel_id: "G01GROUP" }, "private-channel"],
|
|
202
|
-
[{ service: "slack", channel_id: "C0GENERAL", is_private: true }, "private-channel"],
|
|
203
|
-
[{ service: "gmail", channel: "email/alice@example.com" }, "private-correspondence"],
|
|
204
|
-
[{ service: "gmail", subject: "Deck review" }, "non-channel-source"],
|
|
205
|
-
[{ service: "telegram", channel: "telegram" }, "non-channel-source"],
|
|
206
|
-
[{ channel: "general", channel_id: "C0GENERAL" }, "unknown-source"],
|
|
207
|
-
// A type we cannot interpret is not a type we may publish from.
|
|
208
|
-
[{ service: "slack", channel_id: "C0GENERAL", channel_type: "external_shared" }, "unknown-channel-type"],
|
|
209
|
-
];
|
|
210
|
-
for (const [item, reason] of cases) {
|
|
211
|
-
const gate = so.outcomeSourceShareable(item);
|
|
212
|
-
assert.equal(gate.shareable, false, `must not land: ${JSON.stringify(item)}`);
|
|
213
|
-
assert.equal(gate.reason, reason, `reason for ${JSON.stringify(item)}`);
|
|
214
|
-
}
|
|
215
|
-
});
|
|
216
|
-
|
|
217
|
-
test("GATE: a `C…` id is NOT a public signal — the conversation TYPE is", () => {
|
|
218
|
-
// THE DEFECT THIS CLOSES. The gate used to admit on `/^C/.test(channel_id)`,
|
|
219
|
-
// but Slack is explicit that the conversation-ID prefix is not a reliable type
|
|
220
|
-
// indicator: private channels are minted with `C…` ids too (only `D` for a DM
|
|
221
|
-
// is dependable). Every item below carries a `C…` id and must still be refused
|
|
222
|
-
// — otherwise a private channel's commitment text AND its topic line become a
|
|
223
|
-
// channel-less board row plus an `org`-group knowledge episode, readable by
|
|
224
|
-
// every member of the org.
|
|
225
|
-
const privateWithPublicLookingId = [
|
|
226
|
-
// What Socket Mode actually delivers for a private channel.
|
|
227
|
-
[{ service: "slack", channel: "leadership", channel_id: "C0PRIVATE", channel_type: "group" }, "private-channel"],
|
|
228
|
-
// A group DM, likewise not necessarily `G…`-prefixed.
|
|
229
|
-
[{ service: "slack", channel: "C0MPIM01", channel_id: "C0MPIM01", channel_type: "mpim" }, "dm"],
|
|
230
|
-
[{ service: "slack", channel: "C0IM0001", channel_id: "C0IM0001", channel_type: "im" }, "dm"],
|
|
231
|
-
// An explicit privacy verdict from conversations.list/info.
|
|
232
|
-
[{ service: "slack", channel: "leadership", channel_id: "C0PRIVATE", is_private: true }, "private-channel"],
|
|
233
|
-
// NO signal at all: ingest didn't resolve the type, so nothing is established.
|
|
234
|
-
[{ service: "slack", channel: "general", channel_id: "C0GENERAL" }, "privacy-unknown"],
|
|
235
|
-
];
|
|
236
|
-
for (const [item, reason] of privateWithPublicLookingId) {
|
|
237
|
-
const gate = so.outcomeSourceShareable(item);
|
|
238
|
-
assert.equal(gate.shareable, false, `a C-prefixed id must not admit: ${JSON.stringify(item)}`);
|
|
239
|
-
assert.equal(gate.reason, reason, `reason for ${JSON.stringify(item)}`);
|
|
240
|
-
}
|
|
241
|
-
});
|
|
242
|
-
|
|
243
|
-
test("GATE: admits ONLY on a positive public signal (channel_type or is_private:false)", () => {
|
|
244
|
-
for (const item of [
|
|
245
|
-
{ service: "slack", channel: "general", channel_id: "C0GENERAL", channel_type: "channel" },
|
|
246
|
-
{ service: "slack", channel: "general", channel_id: "C0GENERAL", is_private: false },
|
|
247
|
-
// A public channel whose id happens not to start with C is still public.
|
|
248
|
-
{ service: "slack", channel: "general", channel_id: "X0WEIRD01", channel_type: "channel" },
|
|
249
|
-
]) {
|
|
250
|
-
const gate = so.outcomeSourceShareable(item);
|
|
251
|
-
assert.equal(gate.shareable, true, `must land: ${JSON.stringify(item)}`);
|
|
252
|
-
assert.equal(gate.reason, "public-channel");
|
|
253
|
-
}
|
|
254
|
-
// …and an explicit DM/private verdict still overrides a public-looking type.
|
|
255
|
-
assert.equal(
|
|
256
|
-
so.outcomeSourceShareable({ service: "slack", channel_id: "C0GENERAL", channel_type: "channel", is_private: true }).shareable,
|
|
257
|
-
false,
|
|
258
|
-
"is_private:true wins outright"
|
|
259
|
-
);
|
|
260
|
-
assert.equal(
|
|
261
|
-
so.outcomeSourceShareable({ service: "slack", channel_id: "C0GENERAL", channel_type: "channel", is_dm: true }).shareable,
|
|
262
|
-
false,
|
|
263
|
-
"is_dm:true wins outright"
|
|
264
|
-
);
|
|
265
|
-
});
|
|
266
|
-
|
|
267
|
-
test("GATE: the SWEEP refuses a private channel that carries a `C…` id", async () => {
|
|
268
|
-
let touched = false;
|
|
269
|
-
const res = await so.sweepSessionOutcomes({
|
|
270
|
-
sessionId: "s-gate-private-c",
|
|
271
|
-
resultText: "OUTCOMES:\n- Prepare the confidential redundancy list — due: Friday",
|
|
272
|
-
enabled: true,
|
|
273
|
-
cfg: ENABLED_CFG,
|
|
274
|
-
item: {
|
|
275
|
-
service: "slack",
|
|
276
|
-
channel: "leadership",
|
|
277
|
-
channel_id: "C0LEADERSHIP",
|
|
278
|
-
channel_type: "group",
|
|
279
|
-
subject: "Headcount plan",
|
|
280
|
-
},
|
|
281
|
-
classResult: { summary: "headcount plan" },
|
|
282
|
-
createItemImpl: async () => { touched = true; return { ok: true }; },
|
|
283
|
-
rememberImpl: async () => { touched = true; return { ok: true }; },
|
|
284
|
-
});
|
|
285
|
-
assert.equal(res.created, 0);
|
|
286
|
-
assert.equal(res.captured, 0, "a private channel's commitments are never even captured");
|
|
287
|
-
assert.equal(res.reason, "source-private-channel");
|
|
288
|
-
assert.equal(touched, false, "no board row, no episode");
|
|
289
|
-
});
|
|
290
|
-
|
|
291
|
-
test("GATE: the WRITER refuses a non-public source, not just the sweep", async () => {
|
|
292
|
-
let touched = false;
|
|
293
|
-
for (const item of [undefined, { service: "slack", channel_id: "D01ALICE" }]) {
|
|
294
|
-
const res = await so.landOutcomes({
|
|
295
|
-
sessionId: "s-gate",
|
|
296
|
-
outcomes: [{ title: "Send the redlined MSA" }],
|
|
297
|
-
cfg: ENABLED_CFG,
|
|
298
|
-
item,
|
|
299
|
-
createItemImpl: async () => { touched = true; return { ok: true }; },
|
|
300
|
-
rememberImpl: async () => { touched = true; return { ok: true }; },
|
|
301
|
-
});
|
|
302
|
-
assert.equal(res.created, 0);
|
|
303
|
-
assert.equal(res.skipped, true);
|
|
304
|
-
assert.match(res.reason, /^source-/, "the refusal is reported honestly");
|
|
305
|
-
}
|
|
306
|
-
assert.equal(touched, false, "no protocol call for a private source");
|
|
307
|
-
});
|
|
308
|
-
|
|
309
|
-
test("GATE: the SWEEP refuses a DM before it even parses the text", async () => {
|
|
310
|
-
let touched = false;
|
|
311
|
-
const res = await so.sweepSessionOutcomes({
|
|
312
|
-
sessionId: "s-gate-2",
|
|
313
|
-
resultText: "OUTCOMES:\n- Send Alice the term sheet she asked for in confidence",
|
|
314
|
-
enabled: true,
|
|
315
|
-
cfg: ENABLED_CFG,
|
|
316
|
-
item: { service: "slack", channel: "dm/alice", channel_id: "D01ALICE", is_dm: true },
|
|
317
|
-
createItemImpl: async () => { touched = true; return { ok: true }; },
|
|
318
|
-
rememberImpl: async () => { touched = true; return { ok: true }; },
|
|
319
|
-
});
|
|
320
|
-
assert.equal(res.created, 0);
|
|
321
|
-
assert.equal(res.captured, 0, "a private turn's commitments are never even captured");
|
|
322
|
-
assert.equal(res.reason, "source-dm");
|
|
323
|
-
assert.equal(touched, false);
|
|
324
|
-
assert.deepEqual(so.getSessionOutcomes("s-gate-2"), [], "and nothing is retained in memory");
|
|
325
|
-
});
|
|
326
|
-
|
|
327
|
-
// ---------------------------------------------------------------------------
|
|
328
|
-
// LAND — via the existing org protocol only
|
|
329
|
-
// ---------------------------------------------------------------------------
|
|
330
|
-
|
|
331
|
-
test("LAND: writes one board.create per commitment plus ONE knowledge episode", async () => {
|
|
332
|
-
const created = [];
|
|
333
|
-
const remembered = [];
|
|
334
|
-
const res = await so.landOutcomes({
|
|
335
|
-
sessionId: "s-1",
|
|
336
|
-
outcomes: [
|
|
337
|
-
{ title: "Send the redlined MSA", owner: "Robin", due: "Friday" },
|
|
338
|
-
{ title: "Book the pricing review" },
|
|
339
|
-
],
|
|
340
|
-
cfg: ENABLED_CFG,
|
|
341
|
-
item: PUBLIC_ITEM,
|
|
342
|
-
classResult: { summary: "Acme renewal" },
|
|
343
|
-
agentName: "Robin",
|
|
344
|
-
createItemImpl: fakeCreate(created),
|
|
345
|
-
rememberImpl: fakeRemember(remembered),
|
|
346
|
-
});
|
|
347
|
-
|
|
348
|
-
assert.equal(res.created, 2, "both commitments landed");
|
|
349
|
-
assert.equal(created.length, 2);
|
|
350
|
-
assert.equal(created[0].item.title, "Send the redlined MSA");
|
|
351
|
-
assert.equal(created[0].item.priority, "P2");
|
|
352
|
-
assert.ok(created[0].item.itemId, "carries a caller-supplied id for replay safety");
|
|
353
|
-
assert.ok(created[0].item.detail.includes("Robin"), "owner recorded in the detail");
|
|
354
|
-
assert.ok(created[0].item.detail.includes("Friday"), "timing recorded in the detail");
|
|
355
|
-
assert.ok(created[0].item.detail.includes("s-1"), "provenance names the session");
|
|
356
|
-
// board.create is `.strict()` server-side — sending an unknown field would 400.
|
|
357
|
-
assert.deepEqual(
|
|
358
|
-
Object.keys(created[0].item).sort(),
|
|
359
|
-
["detail", "itemId", "priority", "title"],
|
|
360
|
-
"only board.create's own fields are sent"
|
|
361
|
-
);
|
|
362
|
-
assert.equal(created[0].opts.base, "https://org.example", "connection from the org config");
|
|
363
|
-
|
|
364
|
-
assert.equal(remembered.length, 1, "exactly one episode for the whole set");
|
|
365
|
-
assert.equal(remembered[0].opts.kind, "episode");
|
|
366
|
-
assert.equal(remembered[0].opts.provenance.sessionId, "s-1");
|
|
367
|
-
assert.ok(remembered[0].text.includes("Send the redlined MSA"));
|
|
368
|
-
assert.equal(remembered[0].opts.group, undefined, "no group hint — the server derives the ACL");
|
|
369
|
-
});
|
|
370
|
-
|
|
371
|
-
test("LAND: the board id is deterministic, so a replay cannot double a row", () => {
|
|
372
|
-
const a = so.outcomeItemId("s-1", "Send the redlined MSA");
|
|
373
|
-
const b = so.outcomeItemId("s-1", " send the REDLINED msa ");
|
|
374
|
-
const c = so.outcomeItemId("s-2", "Send the redlined MSA");
|
|
375
|
-
assert.equal(a, b, "stable under whitespace/case");
|
|
376
|
-
assert.notEqual(a, c, "scoped to the session");
|
|
377
|
-
assert.match(a, /^so-[0-9a-f]{24}$/);
|
|
378
|
-
});
|
|
379
|
-
|
|
380
|
-
test("LAND: fail-open when the org integration is disabled", async () => {
|
|
381
|
-
let touched = false;
|
|
382
|
-
const res = await so.landOutcomes({
|
|
383
|
-
sessionId: "s-1",
|
|
384
|
-
outcomes: [{ title: "x" }],
|
|
385
|
-
cfg: {},
|
|
386
|
-
item: PUBLIC_ITEM,
|
|
387
|
-
createItemImpl: async () => { touched = true; return { ok: true }; },
|
|
388
|
-
rememberImpl: async () => { touched = true; return { ok: true }; },
|
|
389
|
-
});
|
|
390
|
-
assert.equal(res.skipped, true);
|
|
391
|
-
assert.equal(res.created, 0);
|
|
392
|
-
assert.equal(touched, false, "no protocol call when disabled");
|
|
393
|
-
});
|
|
394
|
-
|
|
395
|
-
test("LAND: never throws, and one bad row does not strand the rest", async () => {
|
|
396
|
-
const created = [];
|
|
397
|
-
const res = await so.landOutcomes({
|
|
398
|
-
sessionId: "s-1",
|
|
399
|
-
outcomes: [{ title: "first" }, { title: "second" }, { title: "third" }],
|
|
400
|
-
cfg: ENABLED_CFG,
|
|
401
|
-
item: PUBLIC_ITEM,
|
|
402
|
-
createItemImpl: async (item) => {
|
|
403
|
-
created.push(item.title);
|
|
404
|
-
if (item.title === "second") throw new Error("network down");
|
|
405
|
-
if (item.title === "third") return { error: { code: "INTERNAL" } };
|
|
406
|
-
return { ok: true };
|
|
407
|
-
},
|
|
408
|
-
rememberImpl: async () => { throw new Error("also down"); },
|
|
409
|
-
});
|
|
410
|
-
assert.equal(res.created, 1, "only the row that actually landed is counted");
|
|
411
|
-
assert.deepEqual(created, ["first", "second", "third"], "every row was attempted");
|
|
412
|
-
assert.equal(res.remembered, false, "a failed episode is reported honestly");
|
|
413
|
-
});
|
|
414
|
-
|
|
415
|
-
// ---------------------------------------------------------------------------
|
|
416
|
-
// SWEEP — driven, idempotent, off by default
|
|
417
|
-
// ---------------------------------------------------------------------------
|
|
418
|
-
|
|
419
|
-
test("SWEEP: off by default — the flag must be set explicitly", async () => {
|
|
420
|
-
assert.equal(so.outcomesEnabled({}), false, "absent flag ⇒ off");
|
|
421
|
-
assert.equal(so.outcomesEnabled({ DAEMON_SESSION_OUTCOMES: "0" }), false);
|
|
422
|
-
assert.equal(so.outcomesEnabled({ DAEMON_SESSION_OUTCOMES: "true" }), false, "only '1' arms it");
|
|
423
|
-
assert.equal(so.outcomesEnabled({ DAEMON_SESSION_OUTCOMES: "1" }), true);
|
|
424
|
-
|
|
425
|
-
let touched = false;
|
|
426
|
-
const res = await so.sweepSessionOutcomes({
|
|
427
|
-
sessionId: "s-1",
|
|
428
|
-
resultText: "OUTCOMES:\n- Do the thing",
|
|
429
|
-
cfg: ENABLED_CFG,
|
|
430
|
-
item: PUBLIC_ITEM,
|
|
431
|
-
createItemImpl: async () => { touched = true; return { ok: true }; },
|
|
432
|
-
rememberImpl: async () => { touched = true; return { ok: true }; },
|
|
433
|
-
// `enabled` omitted ⇒ reads the (unset) env flag
|
|
434
|
-
});
|
|
435
|
-
assert.equal(res.skipped, true);
|
|
436
|
-
assert.equal(res.reason, "disabled");
|
|
437
|
-
assert.equal(touched, false, "nothing lands while the flag is off");
|
|
438
|
-
});
|
|
439
|
-
|
|
440
|
-
test("SWEEP: lands a turn's commitments end to end", async () => {
|
|
441
|
-
const created = [];
|
|
442
|
-
const remembered = [];
|
|
443
|
-
const stdout = JSON.stringify({
|
|
444
|
-
result: "Handled it.\n\nOUTCOMES:\n- Send the deck to Alice — due: Monday\n",
|
|
445
|
-
});
|
|
446
|
-
const res = await so.sweepSessionOutcomes({
|
|
447
|
-
sessionId: "s-9",
|
|
448
|
-
stdout,
|
|
449
|
-
enabled: true,
|
|
450
|
-
cfg: ENABLED_CFG,
|
|
451
|
-
item: { ...PUBLIC_ITEM, subject: "Deck review" },
|
|
452
|
-
agentName: "Robin",
|
|
453
|
-
createItemImpl: fakeCreate(created),
|
|
454
|
-
rememberImpl: fakeRemember(remembered),
|
|
455
|
-
});
|
|
456
|
-
assert.equal(res.captured, 1);
|
|
457
|
-
assert.equal(res.created, 1);
|
|
458
|
-
assert.equal(created[0].item.title, "Send the deck to Alice");
|
|
459
|
-
assert.equal(remembered.length, 1);
|
|
460
|
-
});
|
|
461
|
-
|
|
462
|
-
test("SWEEP: a turn that committed to nothing is a clean no-op", async () => {
|
|
463
|
-
let touched = false;
|
|
464
|
-
const res = await so.sweepSessionOutcomes({
|
|
465
|
-
sessionId: "s-10",
|
|
466
|
-
resultText: "Answered inline. OUTCOMES: none",
|
|
467
|
-
enabled: true,
|
|
468
|
-
cfg: ENABLED_CFG,
|
|
469
|
-
item: PUBLIC_ITEM,
|
|
470
|
-
createItemImpl: async () => { touched = true; return { ok: true }; },
|
|
471
|
-
rememberImpl: async () => { touched = true; return { ok: true }; },
|
|
472
|
-
});
|
|
473
|
-
assert.equal(res.captured, 0);
|
|
474
|
-
assert.equal(res.reason, "nothing-captured");
|
|
475
|
-
assert.equal(touched, false);
|
|
476
|
-
});
|
|
477
|
-
|
|
478
|
-
test("SWEEP: idempotent — a second close never re-lands", async () => {
|
|
479
|
-
const created = [];
|
|
480
|
-
const args = {
|
|
481
|
-
sessionId: "s-11",
|
|
482
|
-
resultText: "OUTCOMES:\n- Do the thing",
|
|
483
|
-
enabled: true,
|
|
484
|
-
cfg: ENABLED_CFG,
|
|
485
|
-
item: PUBLIC_ITEM,
|
|
486
|
-
createItemImpl: fakeCreate(created),
|
|
487
|
-
rememberImpl: fakeRemember([]),
|
|
488
|
-
};
|
|
489
|
-
const first = await so.sweepSessionOutcomes(args);
|
|
490
|
-
assert.equal(first.created, 1);
|
|
491
|
-
|
|
492
|
-
// Re-arm the in-process state as a double-fire would, then sweep again.
|
|
493
|
-
so.noteOutcomes("s-11", [{ title: "Do the thing" }]);
|
|
494
|
-
so.markOutcomesLanded("s-11");
|
|
495
|
-
const second = await so.sweepSessionOutcomes(args);
|
|
496
|
-
assert.equal(second.reason, "already-landed");
|
|
497
|
-
assert.equal(created.length, 1, "board.create called exactly once");
|
|
498
|
-
});
|
|
499
|
-
|
|
500
|
-
test("SWEEP: never throws, whatever the session emitted", async () => {
|
|
501
|
-
for (const stdout of [undefined, null, "", "{{{", "not json at all"]) {
|
|
502
|
-
const res = await so.sweepSessionOutcomes({
|
|
503
|
-
sessionId: "s-12",
|
|
504
|
-
stdout,
|
|
505
|
-
enabled: true,
|
|
506
|
-
cfg: ENABLED_CFG,
|
|
507
|
-
item: PUBLIC_ITEM,
|
|
508
|
-
createItemImpl: async () => { throw new Error("boom"); },
|
|
509
|
-
rememberImpl: async () => { throw new Error("boom"); },
|
|
510
|
-
});
|
|
511
|
-
assert.ok(res && typeof res.created === "number", `benign result for ${JSON.stringify(stdout)}`);
|
|
512
|
-
}
|
|
513
|
-
const noSession = await so.sweepSessionOutcomes({
|
|
514
|
-
enabled: true,
|
|
515
|
-
resultText: "OUTCOMES:\n- x",
|
|
516
|
-
item: PUBLIC_ITEM,
|
|
517
|
-
});
|
|
518
|
-
assert.equal(noSession.reason, "no-session");
|
|
519
|
-
});
|
|
520
|
-
|
|
521
|
-
test("STATE: capture is bounded and self-evicting so a long-lived daemon cannot leak", () => {
|
|
522
|
-
const many = Array.from({ length: 30 }, (_, i) => ({ title: `step ${i}` }));
|
|
523
|
-
const kept = so.noteOutcomes("s-13", many);
|
|
524
|
-
assert.equal(kept.length, so.MAX_OUTCOMES);
|
|
525
|
-
assert.equal(so.getSessionOutcomes("s-13").length, so.MAX_OUTCOMES);
|
|
526
|
-
|
|
527
|
-
so.clearSessionOutcomes("s-13");
|
|
528
|
-
assert.deepEqual(so.getSessionOutcomes("s-13"), []);
|
|
529
|
-
|
|
530
|
-
for (let i = 0; i < 400; i += 1) so.noteOutcomes(`bulk-${i}`, [{ title: "x" }]);
|
|
531
|
-
assert.deepEqual(so.getSessionOutcomes("bulk-0"), [], "oldest sessions are evicted");
|
|
532
|
-
assert.equal(so.getSessionOutcomes("bulk-399").length, 1, "newest is retained");
|
|
533
|
-
});
|
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* typing-registry.test.mjs — node:test coverage for typing-registry.mjs
|
|
3
|
-
*
|
|
4
|
-
* Verifies start/stop route to the registered adapter keyed by service, that
|
|
5
|
-
* sources are derived from items (incl. YAML-round-tripped items lacking a
|
|
6
|
-
* structured source), and that unknown/backlog channels no-op. Hermetic.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import { test } from "node:test";
|
|
10
|
-
import assert from "node:assert/strict";
|
|
11
|
-
|
|
12
|
-
import {
|
|
13
|
-
registerTypingAdapter,
|
|
14
|
-
clearTypingAdapters,
|
|
15
|
-
startTyping,
|
|
16
|
-
stopTyping,
|
|
17
|
-
sourceFromItem,
|
|
18
|
-
} from "./typing-registry.mjs";
|
|
19
|
-
import { resetCohortTypingAdapter } from "../../lib/org/typing.mjs";
|
|
20
|
-
|
|
21
|
-
function fakeAdapter() {
|
|
22
|
-
return {
|
|
23
|
-
started: [],
|
|
24
|
-
stopped: [],
|
|
25
|
-
startTypingHeartbeat(source) { this.started.push(source); return "key"; },
|
|
26
|
-
stopTypingHeartbeat(source) { this.stopped.push(source); },
|
|
27
|
-
};
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
test("startTyping/stopTyping route to the adapter for the item's service", () => {
|
|
31
|
-
clearTypingAdapters();
|
|
32
|
-
const tg = fakeAdapter();
|
|
33
|
-
registerTypingAdapter("telegram", tg);
|
|
34
|
-
const item = { service: "telegram", channel_id: "987", channel: "dm/casey", thread_id: "" };
|
|
35
|
-
startTyping(item);
|
|
36
|
-
stopTyping(item);
|
|
37
|
-
assert.equal(tg.started.length, 1);
|
|
38
|
-
assert.equal(tg.started[0].chatId, "987");
|
|
39
|
-
assert.equal(tg.started[0].userScope, "dm");
|
|
40
|
-
assert.equal(tg.stopped.length, 1);
|
|
41
|
-
clearTypingAdapters();
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
test("unknown service no-ops (no adapter registered)", () => {
|
|
45
|
-
clearTypingAdapters();
|
|
46
|
-
assert.doesNotThrow(() => startTyping({ service: "gmail", channel_id: "x" }));
|
|
47
|
-
assert.doesNotThrow(() => stopTyping({ service: "gmail", channel_id: "x" }));
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
test("item without a chatId no-ops", () => {
|
|
51
|
-
clearTypingAdapters();
|
|
52
|
-
const tg = fakeAdapter();
|
|
53
|
-
registerTypingAdapter("telegram", tg);
|
|
54
|
-
startTyping({ service: "telegram", channel: "dm/x" }); // no channel_id
|
|
55
|
-
assert.equal(tg.started.length, 0);
|
|
56
|
-
clearTypingAdapters();
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
test("sourceFromItem prefers a structured source, else reconstructs from item", () => {
|
|
60
|
-
const explicit = sourceFromItem({ service: "telegram", source: { channel: "telegram", chatId: "1", userScope: "group" } });
|
|
61
|
-
assert.equal(explicit.userScope, "group");
|
|
62
|
-
|
|
63
|
-
const dm = sourceFromItem({ service: "slack", channel: "dm/casey", channel_id: "DABC", thread_id: "" });
|
|
64
|
-
assert.equal(dm.userScope, "dm");
|
|
65
|
-
assert.equal(dm.chatId, "DABC");
|
|
66
|
-
|
|
67
|
-
const group = sourceFromItem({ service: "telegram", channel: "ops", channel_id: "-100", thread_id: "77" });
|
|
68
|
-
assert.equal(group.userScope, "group");
|
|
69
|
-
assert.equal(group.threadRef, "77");
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
test("cohort resolves the BUILT-IN adapter with nothing registered (the SDK parity gap)", () => {
|
|
73
|
-
clearTypingAdapters();
|
|
74
|
-
resetCohortTypingAdapter();
|
|
75
|
-
// No registerTypingAdapter("cohort", …) anywhere — nothing constructs a
|
|
76
|
-
// cohort channel adapter, which is why the indicator was missing entirely.
|
|
77
|
-
const item = { service: "cohort", channel_id: "ch_1", channel: "#build", thread_id: "" };
|
|
78
|
-
assert.doesNotThrow(() => startTyping(item));
|
|
79
|
-
assert.doesNotThrow(() => stopTyping(item));
|
|
80
|
-
clearTypingAdapters();
|
|
81
|
-
resetCohortTypingAdapter();
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
test("an explicitly registered cohort adapter still wins over the built-in", () => {
|
|
85
|
-
clearTypingAdapters();
|
|
86
|
-
resetCohortTypingAdapter();
|
|
87
|
-
const fake = fakeAdapter();
|
|
88
|
-
registerTypingAdapter("cohort", fake);
|
|
89
|
-
startTyping({ service: "cohort", channel_id: "ch_1", channel: "#build" });
|
|
90
|
-
assert.equal(fake.started.length, 1);
|
|
91
|
-
assert.equal(fake.started[0].chatId, "ch_1");
|
|
92
|
-
clearTypingAdapters();
|
|
93
|
-
resetCohortTypingAdapter();
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
test("registerTypingAdapter ignores adapters lacking startTypingHeartbeat", () => {
|
|
97
|
-
clearTypingAdapters();
|
|
98
|
-
registerTypingAdapter("bad", { foo: 1 });
|
|
99
|
-
// No throw, and startTyping no-ops.
|
|
100
|
-
assert.doesNotThrow(() => startTyping({ service: "bad", channel_id: "1" }));
|
|
101
|
-
clearTypingAdapters();
|
|
102
|
-
});
|