@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,102 +0,0 @@
1
- /**
2
- * Regression test for scripts/org/send-orgmail.mjs — `firstContact` wiring.
3
- *
4
- * The defect (live until 2026-09-02): `screenLocally` called `screenOutbound`
5
- * without `firstContact`, so the gate's default (TRUE) applied to EVERY
6
- * invocation. Under the `disclose-always` posture that limb is an outright
7
- * block, so a `--thread` reply was refused with the identical string a genuine
8
- * first contact gets — and `footer_scope.first_contact_only`
9
- * (policies/ai-disclosure.yaml), which the gate does implement, never applied
10
- * on this path. `lib/org/messaging.mjs` and `scripts/daemon/deliver.mjs` both
11
- * pass the flag; this CLI was the outlier.
12
- *
13
- * This asserts the CLI's SOURCE wiring rather than its runtime, deliberately:
14
- * the runtime path sends, and the interesting property is that the argument is
15
- * threaded through at all. The gate's own behaviour under both values is
16
- * covered by lib/comms/send-gate.test.mjs.
17
- *
18
- * It also guards the half that must NOT move: a bare `--to` is first contact,
19
- * and the block there is correct rather than a misfire.
20
- *
21
- * @module scripts/org/send-orgmail.first-contact.test
22
- */
23
-
24
- import { describe, it } from "node:test";
25
- import assert from "node:assert/strict";
26
- import { readFileSync } from "node:fs";
27
- import { fileURLToPath } from "node:url";
28
- import { dirname, join } from "node:path";
29
- import { screenOutbound } from "../../lib/comms/send-gate.mjs";
30
-
31
- const HERE = dirname(fileURLToPath(import.meta.url));
32
- const SRC = readFileSync(join(HERE, "send-orgmail.mjs"), "utf8");
33
-
34
- describe("send-orgmail — firstContact is passed to the send-gate", () => {
35
- it("threads firstContact into screenOutbound", () => {
36
- const call = SRC.slice(SRC.indexOf("await screenOutbound({"));
37
- assert.match(
38
- call.slice(0, call.indexOf("});") + 3),
39
- /firstContact/,
40
- "screenLocally must pass firstContact — omitting it silently applies the gate default (true) to every send",
41
- );
42
- });
43
-
44
- it("derives first contact from the absence of --thread and --reply-to-message", () => {
45
- // Anchored on the assignment itself, NOT on a bare search of the file: the
46
- // same expression appears at the "--to is required" guard, so an unanchored
47
- // regex stays green even when this line is inverted (found by mutation,
48
- // 2026-09-02).
49
- assert.match(
50
- SRC,
51
- /const isFirstContact = !flags\.thread && !flags\.replyToMessageId;/,
52
- "a reply into an existing thread or to a specific message is by construction not first contact",
53
- );
54
- });
55
-
56
- it("passes that derived value — not a literal — into the screen", () => {
57
- assert.match(
58
- SRC,
59
- /screenLocally\([\s\S]{0,200}?isFirstContact,?\s*\)/,
60
- "screenLocally must receive isFirstContact; a hardcoded true/false re-introduces the defect",
61
- );
62
- });
63
-
64
- it("still screens on every invocation (the gate is not made conditional)", () => {
65
- assert.equal(
66
- (SRC.match(/await screenLocally\(/g) || []).length,
67
- 1,
68
- "exactly one unconditional screenLocally call site",
69
- );
70
- });
71
- });
72
-
73
- describe("send-orgmail — the gate semantics the wiring depends on", () => {
74
- const body = "Two exports attached: cash schedule and the dated ledger.";
75
- const base = { channel: "orgmail", text: body, agentRoot: process.cwd() };
76
-
77
- it("blocks a bare --to (first contact) — control preserved", async () => {
78
- const v = await screenOutbound({ ...base, recipient: "x@external.example", firstContact: true });
79
- assert.equal(v.allow, false);
80
- assert.match(v.reason, /proactive disclosure on first contact/);
81
- });
82
-
83
- it("allows a --thread reply (not first contact)", async () => {
84
- const v = await screenOutbound({ ...base, recipient: "(thread)", firstContact: false });
85
- assert.equal(v.allow, true, v.reason);
86
- });
87
-
88
- it("does not depend on recipient class — the disclosure limb never sees it", async () => {
89
- const internal = await screenOutbound({
90
- ...base,
91
- recipient: "someone@internal.example",
92
- internalDomains: ["internal.example"],
93
- firstContact: true,
94
- });
95
- assert.equal(
96
- internal.allow,
97
- false,
98
- "footer_scope.external_recipients_only is NOT implemented in the gate; " +
99
- "populating internalDomains must not be mistaken for a route around the block",
100
- );
101
- });
102
- });
@@ -1,167 +0,0 @@
1
- /**
2
- * inbox-privilege-injection.test.mjs — the sender_privilege forgery, pinned.
3
- *
4
- * THE DEFECT (reported by A041 Hannah Brooks, 2026-09-03, reproduced here):
5
- * the inbox YAML writer (`scripts/poller/utils.mjs inboxItemToYaml`) builds
6
- * quoted scalars by hand — `sender: "${item.sender}"` — and the reader
7
- * (`inbox-scan-poller.mjs parseInboxItemYaml`) is a per-line FIRST-match regex.
8
- * An attacker-chosen Slack display name carrying a newline therefore writes a
9
- * second physical `sender_privilege:` line ABOVE the real one, and first-match
10
- * hands it back. That is privilege escalation on an untrusted field.
11
- *
12
- * `lib/channels/inbox-item.mjs eventToInboxItem` already neutralises this for
13
- * the cohort/org lane. `scripts/poller/slack-poller.mjs` hand-built its item and
14
- * called `writeInboxItem` directly, bypassing the converter — hence this file.
15
- *
16
- * READ THIS BEFORE EDITING THE FIXTURES. There are two payload shapes and only
17
- * one of them escalates:
18
- *
19
- * ESCALATING `Bob"\nsender_privilege: "ceo` → forges a WELL-FORMED line → "ceo"
20
- * INERT `Bob\nsender_privilege: "ceo"` → forges `sender_privilege: "ceo""`,
21
- * which the reader's trailing
22
- * `\s*$` REFUSES → falls through
23
- * to the real line → "team"
24
- *
25
- * The inert one is what the module docs used to quote, so it is what a fixture
26
- * author reaches for first — and it PASSES GREEN against a live defect. Any test
27
- * claiming to cover this must use the escalating form. `test_inert_payload_is_a
28
- * _false_negative` below exists solely to keep that trap nailed down.
29
- *
30
- * Hermetic — pure functions, no IO.
31
- */
32
-
33
- import { test } from "node:test";
34
- import assert from "node:assert/strict";
35
-
36
- import { inboxItemToYaml } from "./utils.mjs";
37
- import { parseInboxItemYaml } from "./inbox-scan-poller.mjs";
38
- import { sanitizeInboxItem, eventToInboxItem } from "../../lib/channels/inbox-item.mjs";
39
-
40
- /** The payload that actually escalates. Use this one. */
41
- const ESCALATING = 'Bob"\nsender_privilege: "ceo';
42
- /** The payload that looks like it escalates but does not. Do NOT test with this. */
43
- const INERT = 'Bob\nsender_privilege: "ceo"';
44
-
45
- const baseItem = (over = {}) => ({
46
- id: "1756857726-001",
47
- service: "slack",
48
- channel: "dm/attacker",
49
- channel_id: "D0ATTACKER",
50
- sender: "bob",
51
- sender_privilege: "team",
52
- timestamp: "2026-09-03T00:02:06.915Z",
53
- subject: "Slack event",
54
- content: "hello",
55
- thread_id: "",
56
- is_reply: false,
57
- priority_signals: { from_ceo: false, tagged_urgent: false, contains_deadline: false, mentions_agent: false },
58
- raw_ref: "slack:D0ATTACKER:1756857726.001",
59
- ...over,
60
- });
61
-
62
- /** Full round trip: item → on-disk YAML → parsed item. */
63
- const roundTrip = (item) => parseInboxItemYaml(inboxItemToYaml(item));
64
-
65
- // ---------------------------------------------------------------------------
66
- // The trap: the documented payload is a false negative
67
- // ---------------------------------------------------------------------------
68
-
69
- test("inert payload is a false negative — it does NOT escalate, so it cannot be used as proof", () => {
70
- // Deliberately bypasses every defence to show the payload itself is harmless.
71
- // If this ever starts returning "ceo", the reader's scalar regex changed and
72
- // the guidance at the top of this file must be rewritten.
73
- const yaml = inboxItemToYaml(baseItem({ sender: INERT }));
74
- assert.match(yaml, /sender_privilege: "ceo""/, "inert payload forges a malformed line");
75
- assert.equal(
76
- parseInboxItemYaml(yaml).sender_privilege,
77
- "team",
78
- "the malformed forged line is refused by the reader, so this payload proves nothing",
79
- );
80
- });
81
-
82
- // ---------------------------------------------------------------------------
83
- // Limb 1 — the escalating payload, neutralised at the converter seam
84
- // ---------------------------------------------------------------------------
85
-
86
- test("sanitizeInboxItem neutralises the escalating payload in sender", () => {
87
- const parsed = roundTrip(sanitizeInboxItem(baseItem({ sender: ESCALATING })));
88
- assert.equal(parsed.sender_privilege, "team", "sender must not be able to forge sender_privilege");
89
- });
90
-
91
- test("sanitizeInboxItem neutralises the escalating payload in every quoted scalar", () => {
92
- // Any field the writer emits as `"${...}"` is an equally good injection point;
93
- // `sender` is merely the one an attacker controls most directly.
94
- for (const field of [
95
- "id", "service", "channel", "channel_id", "sender", "sender_id", "subject",
96
- "thread_id", "raw_ref", "kind", "event_kind", "direct_reason", "scope_id",
97
- "channel_kind", "channel_type", "event_type", "timestamp",
98
- ]) {
99
- const item = sanitizeInboxItem(baseItem({ [field]: ESCALATING }));
100
- // `timestamp` feeds the filename too, so assert on the parsed value only.
101
- assert.equal(
102
- roundTrip(item).sender_privilege,
103
- "team",
104
- `${field} must not be able to forge sender_privilege`,
105
- );
106
- }
107
- });
108
-
109
- test("sanitizeInboxItem cannot forge the confidentiality tri-state either", () => {
110
- // `is_private: false` is a positive "this source is public" assertion that the
111
- // daemon's fail-closed outcome gate admits on. Forging it leaks private content.
112
- const item = sanitizeInboxItem(baseItem({ sender: 'Bob"\nis_private: false\nx: "' }));
113
- assert.equal(roundTrip(item).is_private, undefined, "absent must stay absent");
114
- });
115
-
116
- test("sanitizeInboxItem leaves benign items byte-identical", () => {
117
- const benign = baseItem();
118
- assert.deepEqual(sanitizeInboxItem(benign), benign);
119
- assert.equal(inboxItemToYaml(sanitizeInboxItem(benign)), inboxItemToYaml(benign));
120
- });
121
-
122
- test("sanitizeInboxItem preserves multi-line content and thread_context verbatim", () => {
123
- // These are block scalars, not quoted scalars — the writer re-indents them, so
124
- // they are already injection-proof and must NOT be flattened.
125
- const item = sanitizeInboxItem(baseItem({
126
- content: "line one\nline two",
127
- thread_context: "ctx one\nctx two",
128
- }));
129
- assert.equal(item.content, "line one\nline two");
130
- const parsed = roundTrip(item);
131
- assert.equal(parsed.content, "line one\nline two");
132
- });
133
-
134
- // ---------------------------------------------------------------------------
135
- // Defence in depth — the writer itself refuses to emit a forged line
136
- // ---------------------------------------------------------------------------
137
-
138
- test("inboxItemToYaml refuses to emit a forged key even for an UNSANITISED item", () => {
139
- // The seam fix only covers callers that remember to use it. This is the
140
- // backstop for the next producer that calls writeInboxItem directly.
141
- const parsed = roundTrip(baseItem({ sender: ESCALATING }));
142
- assert.equal(parsed.sender_privilege, "team");
143
- });
144
-
145
- test("writer-level stripping is a no-op on values the producer seam already escaped", () => {
146
- // The backstop must not re-escape. sanitizeInboxItem turns a raw newline into
147
- // a literal backslash-n, which contains no raw newline, so the writer has
148
- // nothing left to strip and must emit the value verbatim.
149
- const item = sanitizeInboxItem(baseItem({ sender: ESCALATING }));
150
- assert.match(inboxItemToYaml(item), new RegExp(`^sender: "${item.sender.replace(/[\\"]/g, "\\$&")}"$`, "m"));
151
- assert.equal(roundTrip(item).sender_privilege, "team");
152
- });
153
-
154
- test("eventToInboxItem output reaches disk unchanged by the writer's backstop", () => {
155
- // The cohort lane already sanitises at the converter. The backstop must leave
156
- // its output byte-for-byte alone — double-escaping every bus item would be a
157
- // silent corruption of every display name containing a quote.
158
- const item = eventToInboxItem({
159
- channel: "cohort",
160
- message_id: "m1",
161
- from: { id: "U1", name: ESCALATING },
162
- text: "hi",
163
- timestamp: "2026-09-03T00:02:06.915Z",
164
- });
165
- assert.match(inboxItemToYaml(item), new RegExp(`^sender: "${item.sender.replace(/[\\"]/g, "\\$&")}"$`, "m"));
166
- assert.equal(roundTrip(item).sender_privilege, "unknown");
167
- });
@@ -1,295 +0,0 @@
1
- /**
2
- * inbox-scan-poller.test.mjs — node:test coverage for inbox-scan-poller.mjs
3
- *
4
- * Verifies the scanner is the faithful inverse of the inbox YAML writer for the
5
- * fields the daemon needs, and that it skips already-processed items. Drives
6
- * the real writer (writeInboxItem) under an injected AGENT_DIR so the round-trip
7
- * is end-to-end. Hermetic.
8
- */
9
-
10
- import { test } from "node:test";
11
- import assert from "node:assert/strict";
12
- import { promises as fsp } from "node:fs";
13
- import { tmpdir } from "node:os";
14
- import { join } from "node:path";
15
-
16
- import { parseInboxItemYaml, scanInbox } from "./inbox-scan-poller.mjs";
17
- import { markDispatched, markInboxProcessed, writeInboxItem } from "./utils.mjs";
18
- import { eventToInboxItem } from "../../lib/channels/inbox-item.mjs";
19
- import { outcomeSourceShareable } from "../daemon/session-outcomes.mjs";
20
-
21
- async function makeRoot() {
22
- const p = join(tmpdir(), `inbox-scan-test-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`);
23
- await fsp.mkdir(p, { recursive: true });
24
- return p;
25
- }
26
- async function rmRoot(p) { try { await fsp.rm(p, { recursive: true, force: true }); } catch { /* */ } }
27
-
28
- test("parseInboxItemYaml recovers the daemon-relevant fields", () => {
29
- const item = eventToInboxItem({
30
- channel: "telegram",
31
- message_id: "4242",
32
- from: { id: "987", name: "casey" },
33
- text: "ping the build please",
34
- timestamp: "2026-06-09T10:00:00.000Z",
35
- kind: "message",
36
- sender: "casey",
37
- sender_privilege: "unknown",
38
- subject: "DM from casey",
39
- channel_id: "987",
40
- channel_label: "dm/casey",
41
- is_reply: false,
42
- priority_signals: { from_ceo: false, tagged_urgent: false, contains_deadline: false, mentions_agent: true },
43
- raw_ref: "telegram:987:4242",
44
- ingest_source: "telegram",
45
- });
46
-
47
- const yaml = renderViaWriter(item);
48
- const parsed = parseInboxItemYaml(yaml);
49
- assert.equal(parsed.id, item.id);
50
- assert.equal(parsed.service, "telegram");
51
- assert.equal(parsed.channel, "dm/casey");
52
- assert.equal(parsed.channel_id, "987");
53
- assert.equal(parsed.sender, "casey");
54
- assert.equal(parsed.content, "ping the build please");
55
- assert.equal(parsed.raw_ref, "telegram:987:4242");
56
- assert.equal(parsed.priority_signals.mentions_agent, true);
57
- });
58
-
59
- test("scanInbox reads written items and skips processed siblings", async () => {
60
- const root = await makeRoot();
61
- try {
62
- // Use the real writer against this tmp root via AGENT_DIR.
63
- const prev = process.env.AGENT_DIR;
64
- process.env.AGENT_DIR = root;
65
- // Fresh import so utils.mjs picks up AGENT_DIR=root.
66
- const utils = await import(`./utils.mjs?scan=${Date.now()}`);
67
- const mkItem = (id, text) => eventToInboxItem({
68
- channel: "telegram", message_id: id, from: { id: "987", name: "casey" }, text,
69
- timestamp: `2026-06-09T10:00:0${id}.000Z`, sender: "casey", channel_id: "987",
70
- channel_label: "dm/casey", raw_ref: `telegram:987:${id}`, ingest_source: "telegram",
71
- priority_signals: { mentions_agent: true },
72
- });
73
- utils.writeInboxItem("telegram", mkItem("1", "first"));
74
- utils.writeInboxItem("telegram", mkItem("2", "second"));
75
-
76
- let res = scanInbox("telegram", { agentRoot: root });
77
- assert.equal(res.items.length, 2);
78
- const texts = res.items.map((i) => i.content).sort();
79
- assert.deepEqual(texts, ["first", "second"]);
80
-
81
- // Mark one processed (rename) — scanner must skip it.
82
- const dir = join(root, "state/inbox/telegram");
83
- const files = (await fsp.readdir(dir)).filter((f) => f.endsWith(".yaml"));
84
- await fsp.rename(join(dir, files[0]), join(dir, files[0] + ".processed"));
85
- res = scanInbox("telegram", { agentRoot: root });
86
- assert.equal(res.items.length, 1, "processed item must not be re-emitted");
87
-
88
- if (prev === undefined) delete process.env.AGENT_DIR; else process.env.AGENT_DIR = prev;
89
- } finally { await rmRoot(root); }
90
- });
91
-
92
- test("scanInbox returns empty for a missing inbox dir", () => {
93
- const res = scanInbox("nope", { agentRoot: "/no/such/root" });
94
- assert.deepEqual(res, { items: [], errors: [] });
95
- });
96
-
97
- test("parseInboxItemYaml preserves multi-line content + thread_context", () => {
98
- const item = eventToInboxItem({
99
- channel: "telegram", message_id: "9", from: { id: "1", name: "x" },
100
- text: "line one\nline two", timestamp: "2026-06-09T10:00:00.000Z",
101
- sender: "x", channel_id: "1", channel_label: "dm/x", raw_ref: "telegram:1:9",
102
- thread_context: "prev: hi there", ingest_source: "telegram",
103
- priority_signals: { mentions_agent: true },
104
- });
105
- const yaml = renderViaWriter(item);
106
- const parsed = parseInboxItemYaml(yaml);
107
- assert.match(parsed.content, /line one/);
108
- assert.match(parsed.content, /line two/);
109
- assert.match(parsed.thread_context, /prev: hi there/);
110
- });
111
-
112
- // ── F1/H2: durable admission — crash mid-flight must leave the item re-deliverable ──
113
-
114
- function mkItem(id, text) {
115
- return eventToInboxItem({
116
- channel: "telegram", message_id: id, from: { id: "987", name: "casey" }, text,
117
- timestamp: `2026-06-09T10:00:0${id}.000Z`, sender: "casey", channel_id: "987",
118
- channel_label: "dm/casey", raw_ref: `telegram:987:${id}`, ingest_source: "telegram",
119
- priority_signals: { mentions_agent: true },
120
- });
121
- }
122
-
123
- test("markDispatched parks an admitted item as .dispatched so a fresh scan does NOT re-deliver it", async () => {
124
- const root = await makeRoot();
125
- try {
126
- writeInboxItem("telegram", mkItem("1", "hello"), root);
127
- const renamed = markDispatched(mkItem("1", "hello"), "telegram", root);
128
- assert.equal(renamed, 1, "the live .yaml must be renamed to .dispatched");
129
-
130
- // Happy path: a freshly-dispatched item is in-flight; the scanner must NOT
131
- // re-emit it (otherwise we double-deliver the same message every poll).
132
- const res = scanInbox("telegram", { agentRoot: root });
133
- assert.equal(res.items.length, 0, "fresh .dispatched item must not be re-emitted");
134
- } finally { await rmRoot(root); }
135
- });
136
-
137
- test("scanInbox RECLAIMS a stale .dispatched item (crash mid-flight) so it is re-delivered", async () => {
138
- const root = await makeRoot();
139
- try {
140
- writeInboxItem("telegram", mkItem("1", "did you get this?"), root);
141
- markDispatched(mkItem("1", "did you get this?"), "telegram", root);
142
-
143
- // Simulate a daemon crash AFTER admission but BEFORE session close: the
144
- // in-memory queue is gone, the file sits at .dispatched. Advancing the clock
145
- // past the reclaim window must make the scanner treat it as stale and
146
- // re-deliver it. (`now` injected to avoid mtime/clock-granularity flake.)
147
- const res = scanInbox("telegram", { agentRoot: root, reclaimMs: 1000, now: Date.now() + 5000 });
148
- assert.equal(res.items.length, 1, "stale .dispatched item must be reclaimed for redelivery");
149
- assert.equal(res.items[0].content, "did you get this?");
150
-
151
- // And within the window it must NOT be reclaimed (no double-delivery).
152
- const fresh = scanInbox("telegram", { agentRoot: root, reclaimMs: 60_000, now: Date.now() });
153
- assert.equal(fresh.items.length, 0, "an in-flight .dispatched item within the window must not be re-delivered");
154
- } finally { await rmRoot(root); }
155
- });
156
-
157
- test("markInboxProcessed retires a .dispatched item to .processed (no redelivery on success)", async () => {
158
- const root = await makeRoot();
159
- try {
160
- writeInboxItem("telegram", mkItem("1", "all done"), root);
161
- markDispatched(mkItem("1", "all done"), "telegram", root);
162
- const retired = markInboxProcessed(mkItem("1", "all done"), "telegram", root);
163
- assert.equal(retired, 1, "the .dispatched file must be renamed to .processed on session close");
164
-
165
- // Even with a 0ms reclaim window, a .processed item is terminal — never re-emitted.
166
- const res = scanInbox("telegram", { agentRoot: root, reclaimMs: 0 });
167
- assert.equal(res.items.length, 0, "processed item is terminal and must not be reclaimed");
168
- } finally { await rmRoot(root); }
169
- });
170
-
171
- test("markInboxProcessed also retires a live .yaml (item never reached dispatch)", async () => {
172
- const root = await makeRoot();
173
- try {
174
- writeInboxItem("telegram", mkItem("1", "ignored item"), root);
175
- const retired = markInboxProcessed(mkItem("1", "ignored item"), "telegram", root);
176
- assert.equal(retired, 1, "a live .yaml must also be retirable directly to .processed");
177
- const res = scanInbox("telegram", { agentRoot: root, reclaimMs: 0 });
178
- assert.equal(res.items.length, 0);
179
- } finally { await rmRoot(root); }
180
- });
181
-
182
- test("writeInboxItem dedupe treats a .dispatched sibling as already-present (no re-create on thread lookback)", async () => {
183
- const root = await makeRoot();
184
- try {
185
- writeInboxItem("telegram", mkItem("1", "v1"), root);
186
- markDispatched(mkItem("1", "v1"), "telegram", root);
187
- // The 30-min thread lookback would re-write the same message; dedupe must
188
- // suppress it because a .dispatched sibling exists (else the in-flight item
189
- // gets re-created as a fresh .yaml and double-delivered).
190
- writeInboxItem("telegram", mkItem("1", "v1"), root);
191
- const dir = join(root, "state/inbox/telegram");
192
- const files = await fsp.readdir(dir);
193
- const live = files.filter((f) => f.endsWith(".yaml"));
194
- assert.equal(live.length, 0, "no fresh .yaml may be created while a .dispatched sibling exists");
195
- assert.equal(files.filter((f) => f.endsWith(".dispatched")).length, 1);
196
- } finally { await rmRoot(root); }
197
- });
198
-
199
- // Render an item to YAML exactly as writeInboxItem would, without touching disk
200
- // for the field-parse tests. We re-implement the tiny call by writing to a temp
201
- // dir via the real writer and reading it back.
202
- function renderViaWriter(item) {
203
- // Inline minimal renderer mirroring scripts/poller/utils.mjs inboxItemToYaml
204
- // for the fields under test (the real writer is exercised in scanInbox test).
205
- const content = (item.content || "").replace(/\n/g, "\n ");
206
- let yaml = `id: "${item.id}"
207
- service: "${item.service}"
208
- channel: "${item.channel}"
209
- sender: "${item.sender}"
210
- sender_privilege: "${item.sender_privilege}"
211
- timestamp: "${item.timestamp}"
212
- subject: "${item.subject || ""}"
213
- content: |
214
- ${content}
215
- thread_id: "${item.thread_id || ""}"
216
- is_reply: ${item.is_reply || false}
217
- is_voice_transcript: false
218
- priority_signals:
219
- from_ceo: ${item.priority_signals?.from_ceo || false}
220
- tagged_urgent: ${item.priority_signals?.tagged_urgent || false}
221
- contains_deadline: ${item.priority_signals?.contains_deadline || false}
222
- mentions_agent: ${item.priority_signals?.mentions_agent || false}
223
- raw_ref: "${item.raw_ref || ""}"
224
- `;
225
- if (item.thread_context) {
226
- const tc = item.thread_context.replace(/\n/g, "\n ");
227
- yaml += `thread_context: |\n ${tc}\n`;
228
- }
229
- if (item.channel_id) yaml += `channel_id: "${item.channel_id}"\n`;
230
- if (item.kind) yaml += `kind: "${item.kind}"\n`;
231
- return yaml;
232
- }
233
-
234
- // ---------------------------------------------------------------------------
235
- // CONFIDENTIALITY — the privacy signal must survive the YAML round-trip
236
- // ---------------------------------------------------------------------------
237
- //
238
- // The daemon's outcome gate (scripts/daemon/session-outcomes.mjs) is fail-closed
239
- // and admits a turn's commitments as ORG-VISIBLE only on a POSITIVE public
240
- // signal, because Slack's conversation-ID prefix is not a reliable type
241
- // indicator — private channels carry `C…` ids too. That signal is minted at
242
- // ingest and reaches the daemon only through this writer/reader pair, so the
243
- // round-trip is part of the security boundary and is tested as such.
244
-
245
- test("privacy signals survive writeInboxItem → scanInbox, and drive the outcome gate", async () => {
246
- const root = await makeRoot();
247
- try {
248
- const base = {
249
- service: "slack",
250
- sender: "casey",
251
- sender_privilege: "unknown",
252
- timestamp: "2026-06-09T10:00:00.000Z",
253
- content: "hello",
254
- thread_id: "",
255
- is_reply: false,
256
- priority_signals: { from_ceo: false, tagged_urgent: false, contains_deadline: false, mentions_agent: true },
257
- };
258
- writeInboxItem("slack", {
259
- ...base, id: "pub-1", channel: "general", channel_id: "C0GENERAL",
260
- channel_type: "channel", is_private: false, is_dm: false,
261
- subject: "#general", raw_ref: "slack:C0GENERAL:1",
262
- }, root);
263
- // A PRIVATE channel that Slack minted with a `C…` id — the case the old
264
- // prefix test admitted, and the leak this whole chain exists to stop.
265
- writeInboxItem("slack", {
266
- ...base, id: "priv-1", channel: "leadership", channel_id: "C0LEADERSHIP",
267
- channel_type: "group", is_private: true, is_dm: false,
268
- subject: "#leadership", raw_ref: "slack:C0LEADERSHIP:2",
269
- }, root);
270
- // An item minted before/without any privacy resolution.
271
- writeInboxItem("slack", {
272
- ...base, id: "unk-1", channel: "mystery", channel_id: "C0MYSTERY",
273
- subject: "#mystery", raw_ref: "slack:C0MYSTERY:3",
274
- }, root);
275
-
276
- const byId = Object.fromEntries(
277
- scanInbox("slack", { agentRoot: root }).items.map((i) => [i.id, i])
278
- );
279
- assert.equal(Object.keys(byId).length, 3);
280
-
281
- assert.equal(byId["pub-1"].channel_type, "channel");
282
- assert.equal(byId["pub-1"].is_private, false, "an explicit false must survive as false, not vanish");
283
- assert.equal(byId["priv-1"].channel_type, "group");
284
- assert.equal(byId["priv-1"].is_private, true);
285
- assert.equal(
286
- "is_private" in byId["unk-1"], false,
287
- "an unresolved item must carry NO privacy field — a defaulted `false` would forge a public verdict"
288
- );
289
- assert.equal("channel_type" in byId["unk-1"], false);
290
-
291
- assert.deepEqual(outcomeSourceShareable(byId["pub-1"]), { shareable: true, reason: "public-channel" });
292
- assert.deepEqual(outcomeSourceShareable(byId["priv-1"]), { shareable: false, reason: "private-channel" });
293
- assert.deepEqual(outcomeSourceShareable(byId["unk-1"]), { shareable: false, reason: "privacy-unknown" });
294
- } finally { await rmRoot(root); }
295
- });