@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.
Files changed (529) hide show
  1. package/.claude/settings.json +18 -0
  2. package/.env.example +23 -7
  3. package/README.md +1 -0
  4. package/bin/maestro.mjs +62 -0
  5. package/docs/guides/billing-console-keys.md +60 -0
  6. package/docs/guides/front-door-session.md +54 -9
  7. package/docs/guides/mac-mini.md +20 -25
  8. package/docs/guides/poller-daemon-setup.md +4 -1
  9. package/docs/guides/setup-wizard.md +1 -1
  10. package/docs/runbooks/fleet-rollout.md +156 -0
  11. package/docs/runbooks/mac-mini-bootstrap.md +12 -14
  12. package/lib/action-executor.js +19 -3
  13. package/lib/budget-guard.mjs +279 -3
  14. package/lib/channels/base-adapter.mjs +3 -1
  15. package/lib/channels/contract.mjs +2 -1
  16. package/lib/channels/inbox-item.mjs +8 -0
  17. package/lib/claude-bin.mjs +5 -6
  18. package/lib/cli/doctor-checks.mjs +141 -10
  19. package/lib/cli/global-setup-extras.mjs +5 -1
  20. package/lib/cli/inbox.mjs +100 -15
  21. package/lib/cli/seat-auth.mjs +463 -0
  22. package/lib/cli/session.mjs +80 -12
  23. package/lib/collective/capture.mjs +8 -6
  24. package/lib/collective/global-config.mjs +63 -1
  25. package/lib/collective/presence.mjs +142 -5
  26. package/lib/comms/send-gate.mjs +559 -1
  27. package/lib/context/budget.mjs +327 -0
  28. package/lib/context/history-scope.mjs +138 -0
  29. package/lib/diagnostics/alerts.mjs +49 -0
  30. package/lib/diagnostics/cadence-output-freshness.mjs +288 -0
  31. package/lib/engine/agents/definitions.mjs +343 -0
  32. package/lib/engine/agents/persist.mjs +275 -0
  33. package/lib/engine/agents/runtime.mjs +748 -0
  34. package/lib/engine/agents/usage.mjs +95 -0
  35. package/lib/engine/auth-status.mjs +139 -0
  36. package/lib/engine/budget.mjs +194 -0
  37. package/lib/engine/cli.mjs +1204 -0
  38. package/lib/engine/commands/index.mjs +269 -0
  39. package/lib/engine/context/budget.mjs +219 -0
  40. package/lib/engine/context/cache.mjs +125 -0
  41. package/lib/engine/context/child-env.mjs +215 -0
  42. package/lib/engine/context/compaction.mjs +342 -0
  43. package/lib/engine/context/images.mjs +90 -0
  44. package/lib/engine/context/instructions.mjs +327 -0
  45. package/lib/engine/context/lazy-instructions.mjs +169 -0
  46. package/lib/engine/context/manager.mjs +182 -0
  47. package/lib/engine/context/real-path.mjs +91 -0
  48. package/lib/engine/context/secret-values.mjs +163 -0
  49. package/lib/engine/context/settings.mjs +274 -0
  50. package/lib/engine/context/stream-input.mjs +159 -0
  51. package/lib/engine/guard.mjs +152 -0
  52. package/lib/engine/hooks.mjs +713 -0
  53. package/lib/engine/loop.mjs +560 -0
  54. package/lib/engine/mcp/client.mjs +254 -0
  55. package/lib/engine/mcp/config.mjs +301 -0
  56. package/lib/engine/mcp/http.mjs +201 -0
  57. package/lib/engine/mcp/index.mjs +146 -0
  58. package/lib/engine/mcp/jsonrpc.mjs +147 -0
  59. package/lib/engine/mcp/naming.mjs +66 -0
  60. package/lib/engine/mcp/resources.mjs +89 -0
  61. package/lib/engine/mcp/results.mjs +133 -0
  62. package/lib/engine/mcp/stdio.mjs +137 -0
  63. package/lib/engine/mcp/supervisor.mjs +116 -0
  64. package/lib/engine/messages.mjs +104 -0
  65. package/lib/engine/output/json.mjs +164 -0
  66. package/lib/engine/output/stream-json.mjs +266 -0
  67. package/lib/engine/permissions.mjs +845 -0
  68. package/lib/engine/process-identity.mjs +164 -0
  69. package/lib/engine/process-tree.mjs +551 -0
  70. package/lib/engine/prompt.mjs +60 -0
  71. package/lib/engine/session/store.mjs +299 -0
  72. package/lib/engine/session-runtime/args.mjs +97 -0
  73. package/lib/engine/session-runtime/host.mjs +143 -0
  74. package/lib/engine/session-runtime/inbox.mjs +122 -0
  75. package/lib/engine/session-runtime/notifications.mjs +129 -0
  76. package/lib/engine/session-runtime/registry.mjs +328 -0
  77. package/lib/engine/session-runtime/runner.mjs +344 -0
  78. package/lib/engine/session-runtime/socket.mjs +212 -0
  79. package/lib/engine/session-runtime/wakeup.mjs +115 -0
  80. package/lib/engine/skills/index.mjs +321 -0
  81. package/lib/engine/tools/bash-background.mjs +533 -0
  82. package/lib/engine/tools/bash.mjs +216 -0
  83. package/lib/engine/tools/edit.mjs +97 -0
  84. package/lib/engine/tools/glob.mjs +81 -0
  85. package/lib/engine/tools/grep.mjs +224 -0
  86. package/lib/engine/tools/index.mjs +84 -0
  87. package/lib/engine/tools/list-agents.mjs +32 -0
  88. package/lib/engine/tools/ls.mjs +127 -0
  89. package/lib/engine/tools/monitor.mjs +82 -0
  90. package/lib/engine/tools/notebook-edit.mjs +218 -0
  91. package/lib/engine/tools/read.mjs +103 -0
  92. package/lib/engine/tools/schedule-wakeup.mjs +45 -0
  93. package/lib/engine/tools/schema.mjs +144 -0
  94. package/lib/engine/tools/send-message.mjs +77 -0
  95. package/lib/engine/tools/session.mjs +70 -0
  96. package/lib/engine/tools/todo.mjs +144 -0
  97. package/lib/engine/tools/toolsearch.mjs +217 -0
  98. package/lib/engine/tools/walk.mjs +193 -0
  99. package/lib/engine/tools/web-switch.mjs +31 -0
  100. package/lib/engine/tools/webfetch-html.mjs +387 -0
  101. package/lib/engine/tools/webfetch-net.mjs +340 -0
  102. package/lib/engine/tools/webfetch.mjs +198 -0
  103. package/lib/engine/tools/websearch.mjs +91 -0
  104. package/lib/engine/tools/workflow.mjs +95 -0
  105. package/lib/engine/tools/write.mjs +76 -0
  106. package/lib/engine/tui/line-editor.mjs +137 -0
  107. package/lib/engine/tui/render.mjs +86 -0
  108. package/lib/engine/tui/tui.mjs +274 -0
  109. package/lib/engine/wire/anthropic-messages.mjs +263 -0
  110. package/lib/engine/wire/effort.mjs +36 -0
  111. package/lib/engine/wire/errors.mjs +496 -0
  112. package/lib/engine/wire/http.mjs +441 -0
  113. package/lib/engine/wire/index.mjs +76 -0
  114. package/lib/engine/wire/openai-chat.mjs +332 -0
  115. package/lib/engine/wire/prompt-cache.mjs +79 -0
  116. package/lib/engine/wire/search.mjs +140 -0
  117. package/lib/engine/wire/sse.mjs +114 -0
  118. package/lib/engine/wire/stall.mjs +349 -0
  119. package/lib/engine/wire/token-provider.mjs +175 -0
  120. package/lib/engine/wire/usage.mjs +192 -0
  121. package/lib/engine/workflow/host.mjs +524 -0
  122. package/lib/engine/workflow/journal.mjs +188 -0
  123. package/lib/engine/workflow/json-schema.mjs +171 -0
  124. package/lib/engine/workflow/meta.mjs +329 -0
  125. package/lib/engine/workflow/notifications.mjs +52 -0
  126. package/lib/engine/workflow/runtime.mjs +447 -0
  127. package/lib/engine/workflow/sandbox.mjs +534 -0
  128. package/lib/engine/workflow/worker.mjs +141 -0
  129. package/lib/engine/workflow/worktree.mjs +74 -0
  130. package/lib/execution/disposition.mjs +1 -1
  131. package/lib/execution/intake.mjs +10 -0
  132. package/lib/execution/surface-policy.mjs +15 -0
  133. package/lib/learning/curator.mjs +8 -6
  134. package/lib/learning/reflect.mjs +8 -6
  135. package/lib/model-router/catalog/cohort.yaml +137 -0
  136. package/lib/model-router/catalog.mjs +118 -1
  137. package/lib/model-router/economics.mjs +9 -0
  138. package/lib/model-router/failover.mjs +67 -16
  139. package/lib/model-router/llm-task.mjs +39 -3
  140. package/lib/model-router/resolve.mjs +95 -3
  141. package/lib/model-router/spawn.mjs +46 -47
  142. package/lib/model-router/taxonomy.mjs +126 -4
  143. package/lib/org/cost-sync.mjs +141 -11
  144. package/lib/org/inbound/broadcast.mjs +289 -0
  145. package/lib/org/inbound/collective.mjs +375 -0
  146. package/lib/org/inbound/directedness.mjs +96 -8
  147. package/lib/org/inbound/facts.mjs +82 -4
  148. package/lib/org/inbound/hydrate.mjs +555 -51
  149. package/lib/org/inbound/project.mjs +22 -0
  150. package/lib/org/inbound/surfaces.mjs +14 -0
  151. package/lib/org/llm-token.mjs +879 -0
  152. package/lib/org/mesh.mjs +61 -0
  153. package/lib/org/messaging.mjs +3 -1
  154. package/lib/org/protocol.checksum +1 -1
  155. package/lib/org/protocol.mjs +15 -0
  156. package/lib/org/quota.mjs +520 -0
  157. package/lib/org/tool-surface.mjs +104 -16
  158. package/lib/org/ui-parity.mjs +16 -1
  159. package/lib/org/work-ledger.mjs +37 -6
  160. package/lib/rate-guard.mjs +114 -1
  161. package/lib/resource-governor.mjs +41 -6
  162. package/lib/runtime/adapter.mjs +823 -0
  163. package/lib/runtime/child-env.mjs +191 -0
  164. package/lib/runtime/legacy-shell-guard.mjs +97 -0
  165. package/lib/runtime/seat-engine.mjs +162 -0
  166. package/lib/session/ask-ledger.mjs +271 -0
  167. package/lib/session/current-work.mjs +676 -0
  168. package/lib/session/feed-core.mjs +40 -3
  169. package/lib/session/launch-args.mjs +56 -4
  170. package/lib/session/status-summary.mjs +26 -9
  171. package/lib/session/upgrade-notice.mjs +42 -0
  172. package/lib/setup/claude-probe.mjs +117 -13
  173. package/lib/setup/enrich.mjs +13 -10
  174. package/lib/setup/sections/model.mjs +39 -13
  175. package/lib/telemetry/collect.mjs +208 -9
  176. package/lib/upgrade/ignored-drift.mjs +105 -0
  177. package/lib/voice/post-call-brief.mjs +30 -17
  178. package/package.json +15 -3
  179. package/plugins/maestro-skills/skills/board-work.md +5 -0
  180. package/plugins/maestro-skills/skills/inbound-triage.md +56 -15
  181. package/plugins/maestro-skills/skills/main-session.md +18 -7
  182. package/scripts/ci/check-tarball-fidelity.mjs +126 -2
  183. package/scripts/ci/run-tests.mjs +47 -19
  184. package/scripts/cohort-llm/api-key-helper.mjs +92 -0
  185. package/scripts/collective/hook-runner.mjs +29 -2
  186. package/scripts/continuous-monitor.sh +13 -0
  187. package/scripts/cost/track-claude-usage.mjs +15 -0
  188. package/scripts/daemon/agent-daemon.mjs +408 -20
  189. package/scripts/daemon/assurance.mjs +48 -12
  190. package/scripts/daemon/cadence-consumer.mjs +218 -68
  191. package/scripts/daemon/cadence-handlers.mjs +73 -4
  192. package/scripts/daemon/classifier.mjs +75 -26
  193. package/scripts/daemon/context-compiler.mjs +104 -59
  194. package/scripts/daemon/deliver.mjs +30 -1
  195. package/scripts/daemon/dispatcher.mjs +804 -157
  196. package/scripts/daemon/health.mjs +14 -1
  197. package/scripts/daemon/lib/session-router.mjs +310 -42
  198. package/scripts/daemon/maestro-daemon.mjs +11 -0
  199. package/scripts/daemon/prompt-builder.mjs +121 -12
  200. package/scripts/daemon/responder.mjs +315 -146
  201. package/scripts/daemon/sdk-version.mjs +98 -16
  202. package/scripts/eval/probe-gateway.mjs +635 -0
  203. package/scripts/eval/replay/extract.mjs +270 -0
  204. package/scripts/eval/replay/grade.mjs +260 -0
  205. package/scripts/eval/replay/lib/config.mjs +50 -0
  206. package/scripts/eval/replay/lib/effects.mjs +65 -0
  207. package/scripts/eval/replay/lib/fixture.mjs +188 -0
  208. package/scripts/eval/replay/lib/judge.mjs +72 -0
  209. package/scripts/eval/replay/lib/redact.mjs +136 -0
  210. package/scripts/eval/replay/lib/sandbox.mjs +170 -0
  211. package/scripts/eval/replay/lib/schema-check.mjs +63 -0
  212. package/scripts/eval/replay/lib/transcript.mjs +76 -0
  213. package/scripts/eval/replay/mcp-replay-stub.mjs +101 -0
  214. package/scripts/eval/replay/report.mjs +185 -0
  215. package/scripts/eval/replay/run.mjs +404 -0
  216. package/scripts/fleet/rollout.mjs +1094 -0
  217. package/scripts/hooks/pre-send-audit.sh +36 -245
  218. package/scripts/hooks/pre-write-yaml-validate.mjs +275 -0
  219. package/scripts/hooks/validate-state-yaml.sh +190 -0
  220. package/scripts/huddle/huddle-llm.mjs +361 -0
  221. package/scripts/huddle/huddle-server.mjs +46 -121
  222. package/scripts/local-triggers/autoupdate.sh +448 -78
  223. package/scripts/local-triggers/run-trigger.sh +13 -0
  224. package/scripts/maintenance/pin-integrity.mjs +364 -0
  225. package/scripts/poll-slack-events.sh +41 -9
  226. package/scripts/poller/slack-socket-mode.mjs +28 -3
  227. package/scripts/session/supervisor.mjs +80 -13
  228. package/scripts/spawn-session.sh +13 -0
  229. package/bin/maestro.test.mjs +0 -1574
  230. package/lib/action-executor.test.mjs +0 -871
  231. package/lib/archetype.test.mjs +0 -132
  232. package/lib/assurance/plan-note.test.mjs +0 -234
  233. package/lib/assurance/room-budget.test.mjs +0 -486
  234. package/lib/assurance/tier.test.mjs +0 -174
  235. package/lib/autonomy.test.mjs +0 -66
  236. package/lib/backlog.test.mjs +0 -302
  237. package/lib/backup/policy.test.mjs +0 -305
  238. package/lib/budget-escalate.test.mjs +0 -232
  239. package/lib/budget-guard.envelope.test.mjs +0 -476
  240. package/lib/budget-guard.test.mjs +0 -427
  241. package/lib/cadence-bus-requeue.test.mjs +0 -83
  242. package/lib/cadence-bus-schedule.test.mjs +0 -194
  243. package/lib/cadence-bus.test.mjs +0 -720
  244. package/lib/cadences.test.mjs +0 -230
  245. package/lib/capability/inventory.test.mjs +0 -232
  246. package/lib/capability.test.mjs +0 -78
  247. package/lib/channels/base-adapter.test.mjs +0 -590
  248. package/lib/channels/channels.test.mjs +0 -371
  249. package/lib/channels/contract.test.mjs +0 -162
  250. package/lib/channels/inbox-item.test.mjs +0 -368
  251. package/lib/channels/orgmail/adapter.test.mjs +0 -448
  252. package/lib/channels/pairing.test.mjs +0 -270
  253. package/lib/channels/repeat-suppressor.test.mjs +0 -134
  254. package/lib/channels/slack-adapter.test.mjs +0 -212
  255. package/lib/channels/telegram-adapter.test.mjs +0 -306
  256. package/lib/channels/voice/adapter.test.mjs +0 -278
  257. package/lib/channels/whatsapp/adapter-baileys.test.mjs +0 -359
  258. package/lib/channels/whatsapp/baileys-typing.test.mjs +0 -154
  259. package/lib/charter.test.mjs +0 -89
  260. package/lib/claude-bin.test.mjs +0 -131
  261. package/lib/cli/board.test.mjs +0 -227
  262. package/lib/cli/design.test.mjs +0 -270
  263. package/lib/cli/doctor-checks.test.mjs +0 -336
  264. package/lib/cli/global-setup-extras.test.mjs +0 -462
  265. package/lib/cli/inbox.test.mjs +0 -230
  266. package/lib/cli/session-ack.test.mjs +0 -63
  267. package/lib/cli/session.test.mjs +0 -613
  268. package/lib/collective/capture.test.mjs +0 -121
  269. package/lib/collective/cards.test.mjs +0 -114
  270. package/lib/collective/config.test.mjs +0 -123
  271. package/lib/collective/global-config.test.mjs +0 -220
  272. package/lib/collective/global-skills.test.mjs +0 -126
  273. package/lib/collective/presence.test.mjs +0 -95
  274. package/lib/collective/recall.test.mjs +0 -116
  275. package/lib/collective/vendor-skills.test.mjs +0 -306
  276. package/lib/comms/send-gate.test.mjs +0 -770
  277. package/lib/comms.test.mjs +0 -41
  278. package/lib/cost/ledger-row.test.mjs +0 -183
  279. package/lib/design/design-md.test.mjs +0 -318
  280. package/lib/design/fixtures/DESIGN.golden.md +0 -238
  281. package/lib/design/fixtures/PRODUCT.golden.md +0 -67
  282. package/lib/design/fixtures/foundation.json +0 -133
  283. package/lib/design/refresh-gate.test.mjs +0 -144
  284. package/lib/design/write.test.mjs +0 -241
  285. package/lib/diagnostics/alerts.test.mjs +0 -318
  286. package/lib/diagnostics/backup-freshness.test.mjs +0 -185
  287. package/lib/diagnostics/counters.test.mjs +0 -206
  288. package/lib/diagnostics/events.test.mjs +0 -290
  289. package/lib/diagnostics/otel.test.mjs +0 -196
  290. package/lib/diagnostics/trace.test.mjs +0 -251
  291. package/lib/env-compat.test.mjs +0 -104
  292. package/lib/execution/disposition.test.mjs +0 -553
  293. package/lib/execution/drive.test.mjs +0 -270
  294. package/lib/execution/effects.test.mjs +0 -344
  295. package/lib/execution/intake.test.mjs +0 -389
  296. package/lib/execution/journal.test.mjs +0 -261
  297. package/lib/execution/match.test.mjs +0 -235
  298. package/lib/execution/pipeline.test.mjs +0 -392
  299. package/lib/execution/route.test.mjs +0 -186
  300. package/lib/execution/surface-policy.test.mjs +0 -162
  301. package/lib/fs-atomic.test.mjs +0 -72
  302. package/lib/fs-ownership.test.mjs +0 -158
  303. package/lib/goals/admission.test.mjs +0 -164
  304. package/lib/goals/classify.test.mjs +0 -167
  305. package/lib/goals/collaborate.test.mjs +0 -336
  306. package/lib/goals/gaps.test.mjs +0 -284
  307. package/lib/goals/loop.test.mjs +0 -845
  308. package/lib/hooks/bus.test.mjs +0 -387
  309. package/lib/identity/persona.test.mjs +0 -142
  310. package/lib/kpi-sensors.test.mjs +0 -278
  311. package/lib/kpi.test.mjs +0 -244
  312. package/lib/learning/config.test.mjs +0 -75
  313. package/lib/learning/counters.test.mjs +0 -69
  314. package/lib/learning/curator-consolidate.test.mjs +0 -238
  315. package/lib/learning/curator.test.mjs +0 -106
  316. package/lib/learning/reflect.test.mjs +0 -0
  317. package/lib/learning/session-index.test.mjs +0 -125
  318. package/lib/learning/skill-writer.test.mjs +0 -210
  319. package/lib/mandate/audit.test.mjs +0 -195
  320. package/lib/mandate/contract.test.mjs +0 -185
  321. package/lib/mandate/derive.test.mjs +0 -274
  322. package/lib/mandate/model.test.mjs +0 -164
  323. package/lib/mandate/refresh.test.mjs +0 -389
  324. package/lib/mcp/server.test.mjs +0 -426
  325. package/lib/model-router/auth-profiles.test.mjs +0 -580
  326. package/lib/model-router/catalog.test.mjs +0 -385
  327. package/lib/model-router/economics.test.mjs +0 -438
  328. package/lib/model-router/failover.test.mjs +0 -439
  329. package/lib/model-router/health.test.mjs +0 -338
  330. package/lib/model-router/integration-coverage.test.mjs +0 -831
  331. package/lib/model-router/integration.test.mjs +0 -564
  332. package/lib/model-router/ledger.test.mjs +0 -415
  333. package/lib/model-router/llm-task.test.mjs +0 -392
  334. package/lib/model-router/org-credentials.test.mjs +0 -265
  335. package/lib/model-router/pricing-refresh.test.mjs +0 -286
  336. package/lib/model-router/reconcile.test.mjs +0 -316
  337. package/lib/model-router/repair.test.mjs +0 -180
  338. package/lib/model-router/spawn.test.mjs +0 -446
  339. package/lib/model-router/taxonomy.test.mjs +0 -410
  340. package/lib/model-router.test.mjs +0 -1207
  341. package/lib/org/activity.test.mjs +0 -134
  342. package/lib/org/approvals.test.mjs +0 -216
  343. package/lib/org/awareness.test.mjs +0 -159
  344. package/lib/org/board-mine-cache.test.mjs +0 -53
  345. package/lib/org/board.test.mjs +0 -187
  346. package/lib/org/bootstrap-context.test.mjs +0 -153
  347. package/lib/org/client.test.mjs +0 -1206
  348. package/lib/org/cohort-client.test.mjs +0 -126
  349. package/lib/org/cost-sync.test.mjs +0 -153
  350. package/lib/org/doctor.test.mjs +0 -346
  351. package/lib/org/engagement-ledger.test.mjs +0 -112
  352. package/lib/org/engagement.test.mjs +0 -739
  353. package/lib/org/handoff.test.mjs +0 -269
  354. package/lib/org/inbound/directedness.test.mjs +0 -668
  355. package/lib/org/inbound/facts.test.mjs +0 -471
  356. package/lib/org/inbound/hydrate.test.mjs +0 -453
  357. package/lib/org/inbound/index.test.mjs +0 -429
  358. package/lib/org/inbound/project.test.mjs +0 -287
  359. package/lib/org/integration-tools.test.mjs +0 -160
  360. package/lib/org/keys.test.mjs +0 -92
  361. package/lib/org/knowledge.test.mjs +0 -326
  362. package/lib/org/leases.test.mjs +0 -235
  363. package/lib/org/mesh-directives.test.mjs +0 -110
  364. package/lib/org/mesh-integration.test.mjs +0 -127
  365. package/lib/org/mesh.test.mjs +0 -400
  366. package/lib/org/messaging.test.mjs +0 -471
  367. package/lib/org/param-contract.test.mjs +0 -477
  368. package/lib/org/policy.test.mjs +0 -237
  369. package/lib/org/protocol.checksum.test.mjs +0 -90
  370. package/lib/org/protocol.test.mjs +0 -323
  371. package/lib/org/push.test.mjs +0 -792
  372. package/lib/org/registry.test.mjs +0 -100
  373. package/lib/org/resource-tools.test.mjs +0 -361
  374. package/lib/org/tool-access.test.mjs +0 -144
  375. package/lib/org/tool-surface-integration.test.mjs +0 -120
  376. package/lib/org/tool-surface.test.mjs +0 -1268
  377. package/lib/org/typing.test.mjs +0 -291
  378. package/lib/org/ui-parity.test.mjs +0 -560
  379. package/lib/org/verify.test.mjs +0 -194
  380. package/lib/org/work-ledger.test.mjs +0 -273
  381. package/lib/plan/adoption-e2e.test.mjs +0 -366
  382. package/lib/plan/budget-enforcement.test.mjs +0 -400
  383. package/lib/plan/compile.test.mjs +0 -382
  384. package/lib/plan/emit.test.mjs +0 -269
  385. package/lib/plan/explain.test.mjs +0 -188
  386. package/lib/prompts/parallelism.test.mjs +0 -177
  387. package/lib/rag/rag.test.mjs +0 -505
  388. package/lib/rate-guard.test.mjs +0 -272
  389. package/lib/reactive-gate.test.mjs +0 -57
  390. package/lib/render.test.mjs +0 -68
  391. package/lib/resource-governor.test.mjs +0 -488
  392. package/lib/scheduling/dynamic-jobs.test.mjs +0 -344
  393. package/lib/scheduling/jitter.test.mjs +0 -140
  394. package/lib/secrets/broker.test.mjs +0 -280
  395. package/lib/secrets/providers.test.mjs +0 -274
  396. package/lib/security/audit-engine.test.mjs +0 -424
  397. package/lib/security/coerce-args.test.mjs +0 -281
  398. package/lib/security/dangerous-tools.test.mjs +0 -68
  399. package/lib/security/external-content.test.mjs +0 -84
  400. package/lib/security/redact.test.mjs +0 -441
  401. package/lib/security/secret-equal.test.mjs +0 -55
  402. package/lib/session/config.test.mjs +0 -92
  403. package/lib/session/feed-core.test.mjs +0 -198
  404. package/lib/session/first-run.test.mjs +0 -121
  405. package/lib/session/frontdoor.test.mjs +0 -205
  406. package/lib/session/handoffs.test.mjs +0 -183
  407. package/lib/session/identity.test.mjs +0 -180
  408. package/lib/session/inbox-claims.test.mjs +0 -286
  409. package/lib/session/launch-args.test.mjs +0 -157
  410. package/lib/session/liveness.test.mjs +0 -100
  411. package/lib/session/status-summary.test.mjs +0 -118
  412. package/lib/session-permissions.test.mjs +0 -120
  413. package/lib/setup/claude-probe.test.mjs +0 -187
  414. package/lib/setup/completeness.test.mjs +0 -110
  415. package/lib/setup/context-pack.test.mjs +0 -89
  416. package/lib/setup/enrich.test.mjs +0 -115
  417. package/lib/setup/enroll-from-cohort.test.mjs +0 -300
  418. package/lib/setup/integration.test.mjs +0 -162
  419. package/lib/setup/io.test.mjs +0 -77
  420. package/lib/setup/runner.test.mjs +0 -132
  421. package/lib/setup/sections/identity.test.mjs +0 -234
  422. package/lib/setup/sections/inventory.test.mjs +0 -198
  423. package/lib/setup/sections/learning.test.mjs +0 -81
  424. package/lib/setup/sections/mandate.test.mjs +0 -388
  425. package/lib/setup/sections/messaging.test.mjs +0 -127
  426. package/lib/setup/sections/model.test.mjs +0 -240
  427. package/lib/setup/sections/org.test.mjs +0 -346
  428. package/lib/setup/sections/orgmail.test.mjs +0 -118
  429. package/lib/setup/sections/recovery.test.mjs +0 -98
  430. package/lib/setup/sections/subagents.test.mjs +0 -429
  431. package/lib/setup/sections/verify.test.mjs +0 -175
  432. package/lib/setup/sot.test.mjs +0 -81
  433. package/lib/setup/state.test.mjs +0 -115
  434. package/lib/singleton.test.mjs +0 -151
  435. package/lib/subagents/cli.test.mjs +0 -389
  436. package/lib/subagents/client.test.mjs +0 -309
  437. package/lib/subagents/gap.test.mjs +0 -234
  438. package/lib/subagents/lock.test.mjs +0 -248
  439. package/lib/subagents/manifest.test.mjs +0 -175
  440. package/lib/subagents/refs.test.mjs +0 -204
  441. package/lib/subagents/resolve.test.mjs +0 -422
  442. package/lib/subagents/schema.test.mjs +0 -328
  443. package/lib/telemetry/alerts.test.mjs +0 -109
  444. package/lib/telemetry/collect.test.mjs +0 -1274
  445. package/lib/tool-definitions-integration.test.mjs +0 -83
  446. package/lib/tool-definitions.test.mjs +0 -437
  447. package/lib/upgrade/global-refresh.test.mjs +0 -65
  448. package/lib/upgrade/launchd-reconcile.test.mjs +0 -272
  449. package/lib/upgrade/post-steps.test.mjs +0 -200
  450. package/lib/upgrade/verify.test.mjs +0 -164
  451. package/lib/util/fetch-timeout.test.mjs +0 -202
  452. package/lib/util/reconnect.test.mjs +0 -369
  453. package/lib/util/unhandled.test.mjs +0 -216
  454. package/lib/voice/outbound.test.mjs +0 -69
  455. package/lib/voice/session-rotation.test.mjs +0 -114
  456. package/lib/voice/stt.test.mjs +0 -226
  457. package/lib/voice/voice.test.mjs +0 -990
  458. package/scripts/cadence/enqueue-cadence-tick.test.mjs +0 -187
  459. package/scripts/ci/check-docs-accuracy.test.mjs +0 -409
  460. package/scripts/ci/check-durable-write-seam.test.mjs +0 -90
  461. package/scripts/ci/check-no-build-artifacts.test.mjs +0 -71
  462. package/scripts/ci/check-no-residual-identity.test.mjs +0 -202
  463. package/scripts/ci/check-skill-packs.test.mjs +0 -495
  464. package/scripts/ci/check-subagent-frontmatter.test.mjs +0 -124
  465. package/scripts/ci/check.test.mjs +0 -194
  466. package/scripts/ci/conformance-org-api.test.mjs +0 -425
  467. package/scripts/cloud-relay/voice/relay-identity.test.mjs +0 -96
  468. package/scripts/collective/hook-runner.test.mjs +0 -173
  469. package/scripts/cost/fleet-digest.test.mjs +0 -207
  470. package/scripts/cost/track-claude-usage-pricing.test.mjs +0 -183
  471. package/scripts/cost/track-claude-usage.test.mjs +0 -148
  472. package/scripts/daemon/agent-daemon-board-mine.test.mjs +0 -96
  473. package/scripts/daemon/agent-daemon-design.test.mjs +0 -238
  474. package/scripts/daemon/agent-daemon-frontdoor.test.mjs +0 -60
  475. package/scripts/daemon/agent-daemon.test.mjs +0 -995
  476. package/scripts/daemon/assurance-e2e.test.mjs +0 -613
  477. package/scripts/daemon/assurance.test.mjs +0 -1791
  478. package/scripts/daemon/board-mirror.test.mjs +0 -165
  479. package/scripts/daemon/cadence-consumer-frontdoor.test.mjs +0 -393
  480. package/scripts/daemon/cadence-consumer-governance.test.mjs +0 -276
  481. package/scripts/daemon/cadence-consumer.test.mjs +0 -776
  482. package/scripts/daemon/cadence-handlers.test.mjs +0 -837
  483. package/scripts/daemon/classifier-identity.test.mjs +0 -137
  484. package/scripts/daemon/classifier.test.mjs +0 -266
  485. package/scripts/daemon/classify-kind.test.mjs +0 -40
  486. package/scripts/daemon/context-compiler.test.mjs +0 -300
  487. package/scripts/daemon/deliver.test.mjs +0 -564
  488. package/scripts/daemon/dispatcher-cooldown.test.mjs +0 -122
  489. package/scripts/daemon/dispatcher-governance.test.mjs +0 -1013
  490. package/scripts/daemon/dispatcher-resume.test.mjs +0 -166
  491. package/scripts/daemon/execution-ladder.test.mjs +0 -470
  492. package/scripts/daemon/goal-steward-cadence.test.mjs +0 -312
  493. package/scripts/daemon/inbox-deferral-session.test.mjs +0 -49
  494. package/scripts/daemon/inbox-deferral.test.mjs +0 -336
  495. package/scripts/daemon/inbox-wake.test.mjs +0 -199
  496. package/scripts/daemon/integration.test.mjs +0 -149
  497. package/scripts/daemon/lib/self-echo.test.mjs +0 -153
  498. package/scripts/daemon/lib/session-router.test.mjs +0 -295
  499. package/scripts/daemon/prompt-builder-preamble.test.mjs +0 -210
  500. package/scripts/daemon/prompt-builder.test.mjs +0 -344
  501. package/scripts/daemon/responder-cost.test.mjs +0 -68
  502. package/scripts/daemon/responder-history.test.mjs +0 -185
  503. package/scripts/daemon/sdk-version.test.mjs +0 -31
  504. package/scripts/daemon/session-lock.test.mjs +0 -252
  505. package/scripts/daemon/session-outcomes.test.mjs +0 -533
  506. package/scripts/daemon/typing-registry.test.mjs +0 -102
  507. package/scripts/hooks/pre-send-audit.test.mjs +0 -354
  508. package/scripts/huddle/huddle-prompt.test.mjs +0 -176
  509. package/scripts/local-triggers/autoupdate.test.mjs +0 -518
  510. package/scripts/local-triggers/generate-plists.test.mjs +0 -456
  511. package/scripts/media-generation/brand-clause.test.mjs +0 -135
  512. package/scripts/org/send-orgmail.first-contact.test.mjs +0 -102
  513. package/scripts/poller/inbox-privilege-injection.test.mjs +0 -167
  514. package/scripts/poller/inbox-scan-poller.test.mjs +0 -295
  515. package/scripts/poller/lib/cloud-relay-dedup.test.mjs +0 -133
  516. package/scripts/poller/slack-socket-mode.test.mjs +0 -805
  517. package/scripts/poller-launchd/install.test.mjs +0 -243
  518. package/scripts/restore-from-backup.test.mjs +0 -181
  519. package/scripts/session/feed.test.mjs +0 -196
  520. package/scripts/session/supervisor-sh.test.mjs +0 -218
  521. package/scripts/session/supervisor.test.mjs +0 -482
  522. package/scripts/setup/configure-macos.test.mjs +0 -306
  523. package/scripts/setup/gen-subagent-manifest.test.mjs +0 -124
  524. package/scripts/setup/generate-agent-package-json.test.mjs +0 -143
  525. package/scripts/setup/generate-capability.test.mjs +0 -134
  526. package/scripts/setup/init-agent.test.mjs +0 -370
  527. package/scripts/setup/init-skill-marketplace.test.mjs +0 -193
  528. package/scripts/vendor/sync-skill-packs.test.mjs +0 -103
  529. 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
- });