@cohortapp/agent-sdk 2.3.0

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 (731) hide show
  1. package/.claude/commands/init-agent.md +104 -0
  2. package/.claude/commands/init-maestro.md +1187 -0
  3. package/.claude/settings.json +161 -0
  4. package/.env.example +216 -0
  5. package/README.md +632 -0
  6. package/agents/browser-operator/agent.md +52 -0
  7. package/agents/calendar-ops/agent.md +50 -0
  8. package/agents/communications/agent.md +96 -0
  9. package/agents/decision-log/agent.md +65 -0
  10. package/agents/desktop-operator/agent.md +59 -0
  11. package/agents/gmail-operator/agent.md +62 -0
  12. package/agents/inbound-dispatcher/agent.md +66 -0
  13. package/agents/inbox-processor/agent.md +39 -0
  14. package/agents/pmo-execution/agent.md +60 -0
  15. package/agents/session-spawner/agent.md +64 -0
  16. package/agents/slack-operator/agent.md +60 -0
  17. package/agents/whatsapp-operator/agent.md +60 -0
  18. package/agents/workflow-automation/agent.md +61 -0
  19. package/archetypes/altitudes/c-suite.yaml +58 -0
  20. package/archetypes/altitudes/founder.yaml +68 -0
  21. package/archetypes/altitudes/senior-manager.yaml +63 -0
  22. package/archetypes/altitudes/svp.yaml +60 -0
  23. package/archetypes/altitudes/vp.yaml +50 -0
  24. package/archetypes/archetype.schema.json +77 -0
  25. package/archetypes/base.yaml +47 -0
  26. package/archetypes/capabilities/commercial-leader.yaml +159 -0
  27. package/archetypes/capabilities/compliance-officer.yaml +159 -0
  28. package/archetypes/capabilities/executive-operator.yaml +169 -0
  29. package/archetypes/capabilities/finance-leader.yaml +162 -0
  30. package/archetypes/capabilities/operations-leader.yaml +154 -0
  31. package/archetypes/capabilities/product-leader.yaml +148 -0
  32. package/archetypes/capabilities/technical-leader.yaml +146 -0
  33. package/archetypes/functions/commercial-leader.yaml +62 -0
  34. package/archetypes/functions/compliance-officer.yaml +64 -0
  35. package/archetypes/functions/executive-operator.yaml +70 -0
  36. package/archetypes/functions/finance-leader.yaml +70 -0
  37. package/archetypes/functions/operations-leader.yaml +62 -0
  38. package/archetypes/functions/product-leader.yaml +61 -0
  39. package/archetypes/functions/technical-leader.yaml +57 -0
  40. package/bin/cohort-mcp.mjs +81 -0
  41. package/bin/maestro.mjs +3516 -0
  42. package/bin/maestro.test.mjs +1015 -0
  43. package/desktop-control/README.md +56 -0
  44. package/desktop-control/app-profiles/gmail.yaml +120 -0
  45. package/desktop-control/app-profiles/slack.yaml +315 -0
  46. package/desktop-control/app-profiles/whatsapp.yaml +107 -0
  47. package/docs/architecture/agent-topology.md +2239 -0
  48. package/docs/architecture/archetype-agent-factory.md +110 -0
  49. package/docs/architecture/collective-memory-and-org-mesh.md +115 -0
  50. package/docs/architecture/continuous-monitoring.md +221 -0
  51. package/docs/architecture/mcp-capability-map.md +585 -0
  52. package/docs/architecture/system-architecture.md +1272 -0
  53. package/docs/company-context/README.md +40 -0
  54. package/docs/guides/agent-persona-setup.md +600 -0
  55. package/docs/guides/agents-observe-setup.md +64 -0
  56. package/docs/guides/billing-console-keys.md +88 -0
  57. package/docs/guides/ccxray-diagnostics.md +65 -0
  58. package/docs/guides/channel-bus.md +127 -0
  59. package/docs/guides/claude-mem-setup.md +79 -0
  60. package/docs/guides/claude-pace-setup.md +56 -0
  61. package/docs/guides/claudraband-sessions.md +98 -0
  62. package/docs/guides/clawteam-swarm.md +116 -0
  63. package/docs/guides/code-review-graph-setup.md +86 -0
  64. package/docs/guides/email-setup.md +431 -0
  65. package/docs/guides/mac-mini.md +119 -0
  66. package/docs/guides/media-generation-setup.md +349 -0
  67. package/docs/guides/model-routing.md +162 -0
  68. package/docs/guides/observability-otel.md +265 -0
  69. package/docs/guides/org-onboarding.md +132 -0
  70. package/docs/guides/outbound-governance-setup.md +437 -0
  71. package/docs/guides/pdf-generation-setup.md +315 -0
  72. package/docs/guides/poller-daemon-setup.md +563 -0
  73. package/docs/guides/rag-context-setup.md +459 -0
  74. package/docs/guides/self-optimization-pattern.md +82 -0
  75. package/docs/guides/setup-wizard.md +178 -0
  76. package/docs/guides/slack-setup.md +350 -0
  77. package/docs/guides/telegram-setup.md +227 -0
  78. package/docs/guides/twilio-subaccounts-setup.md +223 -0
  79. package/docs/guides/verification.md +128 -0
  80. package/docs/guides/voice-mode.md +188 -0
  81. package/docs/guides/voice-sms-setup.md +698 -0
  82. package/docs/guides/webhook-relay-setup.md +349 -0
  83. package/docs/guides/whatsapp-setup.md +288 -0
  84. package/docs/prompts/board-pack-cover-template.md +36 -0
  85. package/docs/prompts/decision-recommendation-template.md +88 -0
  86. package/docs/prompts/followup-message-template.md +141 -0
  87. package/docs/prompts/investor-letter-template.md +52 -0
  88. package/docs/prompts/morning-brief-template.md +82 -0
  89. package/docs/prompts/presentation-template.md +58 -0
  90. package/docs/prompts/weekly-strategic-memo-template.md +104 -0
  91. package/docs/research/hallucinated-tool-output-investigation.md +151 -0
  92. package/docs/runbooks/backup-restore.md +205 -0
  93. package/docs/runbooks/cohort-cutover.md +129 -0
  94. package/docs/runbooks/fleet-operations.md +200 -0
  95. package/docs/runbooks/incident-response.md +226 -0
  96. package/docs/runbooks/mac-mini-bootstrap.md +431 -0
  97. package/docs/runbooks/perpetual-operations.md +509 -0
  98. package/docs/runbooks/recovery-and-failover.md +260 -0
  99. package/framework-features.json +267 -0
  100. package/ingest/README.md +87 -0
  101. package/lib/action-executor.js +689 -0
  102. package/lib/action-executor.test.mjs +871 -0
  103. package/lib/agent-root.mjs +37 -0
  104. package/lib/archetype.mjs +236 -0
  105. package/lib/archetype.test.mjs +132 -0
  106. package/lib/autonomy.mjs +114 -0
  107. package/lib/autonomy.test.mjs +66 -0
  108. package/lib/backlog.mjs +358 -0
  109. package/lib/backlog.test.mjs +266 -0
  110. package/lib/budget-guard.mjs +279 -0
  111. package/lib/budget-guard.test.mjs +291 -0
  112. package/lib/cadence-bus-schedule.test.mjs +194 -0
  113. package/lib/cadence-bus.mjs +1120 -0
  114. package/lib/cadence-bus.test.mjs +720 -0
  115. package/lib/cadences.mjs +205 -0
  116. package/lib/cadences.test.mjs +125 -0
  117. package/lib/capability.mjs +154 -0
  118. package/lib/capability.test.mjs +78 -0
  119. package/lib/channels/base-adapter.mjs +719 -0
  120. package/lib/channels/base-adapter.test.mjs +590 -0
  121. package/lib/channels/channel.mjs +128 -0
  122. package/lib/channels/channels.test.mjs +371 -0
  123. package/lib/channels/contract.mjs +215 -0
  124. package/lib/channels/contract.test.mjs +137 -0
  125. package/lib/channels/conversation-resolver.mjs +95 -0
  126. package/lib/channels/gmail/adapter.mjs +87 -0
  127. package/lib/channels/inbox-item.mjs +255 -0
  128. package/lib/channels/inbox-item.test.mjs +335 -0
  129. package/lib/channels/index.mjs +94 -0
  130. package/lib/channels/orgmail/adapter.mjs +353 -0
  131. package/lib/channels/orgmail/adapter.test.mjs +311 -0
  132. package/lib/channels/pairing.mjs +363 -0
  133. package/lib/channels/pairing.test.mjs +270 -0
  134. package/lib/channels/registry.mjs +164 -0
  135. package/lib/channels/slack/adapter.mjs +317 -0
  136. package/lib/channels/slack-adapter.test.mjs +212 -0
  137. package/lib/channels/sms/adapter.mjs +43 -0
  138. package/lib/channels/telegram/adapter.mjs +432 -0
  139. package/lib/channels/telegram-adapter.test.mjs +306 -0
  140. package/lib/channels/voice/adapter.mjs +301 -0
  141. package/lib/channels/voice/adapter.test.mjs +278 -0
  142. package/lib/channels/whatsapp/adapter-baileys.mjs +587 -0
  143. package/lib/channels/whatsapp/adapter-baileys.test.mjs +359 -0
  144. package/lib/channels/whatsapp/adapter-twilio.mjs +65 -0
  145. package/lib/channels/whatsapp/baileys-typing.test.mjs +154 -0
  146. package/lib/charter.mjs +256 -0
  147. package/lib/charter.test.mjs +89 -0
  148. package/lib/claude-bin.mjs +134 -0
  149. package/lib/claude-bin.test.mjs +75 -0
  150. package/lib/collective/capture.mjs +185 -0
  151. package/lib/collective/capture.test.mjs +121 -0
  152. package/lib/collective/cards.mjs +201 -0
  153. package/lib/collective/cards.test.mjs +114 -0
  154. package/lib/collective/config.mjs +186 -0
  155. package/lib/collective/config.test.mjs +123 -0
  156. package/lib/collective/global-config.mjs +113 -0
  157. package/lib/collective/global-config.test.mjs +75 -0
  158. package/lib/collective/presence.mjs +201 -0
  159. package/lib/collective/presence.test.mjs +95 -0
  160. package/lib/collective/recall.mjs +215 -0
  161. package/lib/collective/recall.test.mjs +116 -0
  162. package/lib/comms/send-gate.mjs +554 -0
  163. package/lib/comms/send-gate.test.mjs +577 -0
  164. package/lib/comms.mjs +67 -0
  165. package/lib/comms.test.mjs +41 -0
  166. package/lib/diagnostics/alerts.mjs +424 -0
  167. package/lib/diagnostics/alerts.test.mjs +318 -0
  168. package/lib/diagnostics/backup-freshness.mjs +188 -0
  169. package/lib/diagnostics/backup-freshness.test.mjs +185 -0
  170. package/lib/diagnostics/counters.mjs +269 -0
  171. package/lib/diagnostics/counters.test.mjs +206 -0
  172. package/lib/diagnostics/events.mjs +188 -0
  173. package/lib/diagnostics/events.test.mjs +290 -0
  174. package/lib/diagnostics/otel.mjs +237 -0
  175. package/lib/diagnostics/otel.test.mjs +196 -0
  176. package/lib/diagnostics/trace.mjs +216 -0
  177. package/lib/diagnostics/trace.test.mjs +251 -0
  178. package/lib/env-compat.mjs +74 -0
  179. package/lib/env-compat.test.mjs +104 -0
  180. package/lib/feature-init.mjs +331 -0
  181. package/lib/fs-atomic.mjs +112 -0
  182. package/lib/fs-atomic.test.mjs +72 -0
  183. package/lib/fs-ownership.mjs +111 -0
  184. package/lib/fs-ownership.test.mjs +158 -0
  185. package/lib/hooks/bus.mjs +347 -0
  186. package/lib/hooks/bus.test.mjs +387 -0
  187. package/lib/index.js +16 -0
  188. package/lib/learning/config.mjs +106 -0
  189. package/lib/learning/config.test.mjs +75 -0
  190. package/lib/learning/counters.mjs +156 -0
  191. package/lib/learning/counters.test.mjs +69 -0
  192. package/lib/learning/curator-consolidate.test.mjs +238 -0
  193. package/lib/learning/curator.mjs +453 -0
  194. package/lib/learning/curator.test.mjs +106 -0
  195. package/lib/learning/log.mjs +40 -0
  196. package/lib/learning/reflect.mjs +534 -0
  197. package/lib/learning/reflect.test.mjs +0 -0
  198. package/lib/learning/session-index.mjs +352 -0
  199. package/lib/learning/session-index.test.mjs +125 -0
  200. package/lib/learning/skill-writer.mjs +474 -0
  201. package/lib/learning/skill-writer.test.mjs +210 -0
  202. package/lib/mcp/server.mjs +328 -0
  203. package/lib/mcp/server.test.mjs +400 -0
  204. package/lib/model-router/auth-profiles.mjs +758 -0
  205. package/lib/model-router/auth-profiles.test.mjs +580 -0
  206. package/lib/model-router/catalog/anthropic.yaml +153 -0
  207. package/lib/model-router/catalog/deepseek.yaml +86 -0
  208. package/lib/model-router/catalog/moonshot.yaml +81 -0
  209. package/lib/model-router/catalog/qwen.yaml +114 -0
  210. package/lib/model-router/catalog.mjs +925 -0
  211. package/lib/model-router/catalog.test.mjs +385 -0
  212. package/lib/model-router/economics.mjs +564 -0
  213. package/lib/model-router/economics.test.mjs +344 -0
  214. package/lib/model-router/failover.mjs +298 -0
  215. package/lib/model-router/failover.test.mjs +439 -0
  216. package/lib/model-router/health.mjs +453 -0
  217. package/lib/model-router/health.test.mjs +338 -0
  218. package/lib/model-router/integration-coverage.test.mjs +829 -0
  219. package/lib/model-router/integration.test.mjs +564 -0
  220. package/lib/model-router/ledger.mjs +402 -0
  221. package/lib/model-router/ledger.test.mjs +382 -0
  222. package/lib/model-router/llm-task.mjs +515 -0
  223. package/lib/model-router/llm-task.test.mjs +392 -0
  224. package/lib/model-router/org-credentials.mjs +260 -0
  225. package/lib/model-router/org-credentials.test.mjs +265 -0
  226. package/lib/model-router/pricing-refresh.mjs +463 -0
  227. package/lib/model-router/pricing-refresh.test.mjs +286 -0
  228. package/lib/model-router/reconcile.mjs +429 -0
  229. package/lib/model-router/reconcile.test.mjs +316 -0
  230. package/lib/model-router/repair.mjs +471 -0
  231. package/lib/model-router/repair.test.mjs +180 -0
  232. package/lib/model-router/resolve.mjs +1206 -0
  233. package/lib/model-router/spawn.mjs +497 -0
  234. package/lib/model-router/spawn.test.mjs +425 -0
  235. package/lib/model-router/taxonomy.mjs +893 -0
  236. package/lib/model-router/taxonomy.test.mjs +410 -0
  237. package/lib/model-router.mjs +677 -0
  238. package/lib/model-router.test.mjs +907 -0
  239. package/lib/org/activity.mjs +211 -0
  240. package/lib/org/activity.test.mjs +134 -0
  241. package/lib/org/approvals.mjs +448 -0
  242. package/lib/org/approvals.test.mjs +216 -0
  243. package/lib/org/awareness.mjs +222 -0
  244. package/lib/org/awareness.test.mjs +159 -0
  245. package/lib/org/board.mjs +229 -0
  246. package/lib/org/board.test.mjs +177 -0
  247. package/lib/org/bootstrap-context.mjs +169 -0
  248. package/lib/org/bootstrap-context.test.mjs +153 -0
  249. package/lib/org/client.mjs +1628 -0
  250. package/lib/org/client.test.mjs +1107 -0
  251. package/lib/org/cohort-client.mjs +67 -0
  252. package/lib/org/cohort-client.test.mjs +126 -0
  253. package/lib/org/cost-sync.mjs +227 -0
  254. package/lib/org/cost-sync.test.mjs +153 -0
  255. package/lib/org/doctor.mjs +212 -0
  256. package/lib/org/doctor.test.mjs +212 -0
  257. package/lib/org/handoff.mjs +293 -0
  258. package/lib/org/handoff.test.mjs +269 -0
  259. package/lib/org/integration-tools.mjs +182 -0
  260. package/lib/org/integration-tools.test.mjs +160 -0
  261. package/lib/org/keys.mjs +131 -0
  262. package/lib/org/keys.test.mjs +92 -0
  263. package/lib/org/knowledge.mjs +463 -0
  264. package/lib/org/knowledge.test.mjs +319 -0
  265. package/lib/org/leases.mjs +335 -0
  266. package/lib/org/leases.test.mjs +235 -0
  267. package/lib/org/mesh-integration.test.mjs +127 -0
  268. package/lib/org/mesh.mjs +459 -0
  269. package/lib/org/mesh.test.mjs +345 -0
  270. package/lib/org/messaging.mjs +503 -0
  271. package/lib/org/messaging.test.mjs +238 -0
  272. package/lib/org/policy.mjs +345 -0
  273. package/lib/org/policy.test.mjs +237 -0
  274. package/lib/org/protocol.checksum +1 -0
  275. package/lib/org/protocol.checksum.test.mjs +90 -0
  276. package/lib/org/protocol.mjs +967 -0
  277. package/lib/org/protocol.test.mjs +264 -0
  278. package/lib/org/registry.mjs +194 -0
  279. package/lib/org/registry.test.mjs +100 -0
  280. package/lib/org/tool-surface-integration.test.mjs +120 -0
  281. package/lib/org/tool-surface.mjs +2535 -0
  282. package/lib/org/tool-surface.test.mjs +589 -0
  283. package/lib/org/ui-parity.mjs +3236 -0
  284. package/lib/org/ui-parity.test.mjs +348 -0
  285. package/lib/org/verify.mjs +176 -0
  286. package/lib/org/verify.test.mjs +194 -0
  287. package/lib/rag/embed.mjs +188 -0
  288. package/lib/rag/indexer.mjs +425 -0
  289. package/lib/rag/rag.test.mjs +505 -0
  290. package/lib/rag/search.mjs +475 -0
  291. package/lib/rate-guard.mjs +246 -0
  292. package/lib/rate-guard.test.mjs +201 -0
  293. package/lib/render.mjs +112 -0
  294. package/lib/render.test.mjs +68 -0
  295. package/lib/resource-governor.mjs +297 -0
  296. package/lib/resource-governor.test.mjs +262 -0
  297. package/lib/scheduling/dynamic-jobs.mjs +675 -0
  298. package/lib/scheduling/dynamic-jobs.test.mjs +344 -0
  299. package/lib/scheduling/jitter.mjs +0 -0
  300. package/lib/scheduling/jitter.test.mjs +140 -0
  301. package/lib/secrets/broker.mjs +315 -0
  302. package/lib/secrets/broker.test.mjs +280 -0
  303. package/lib/secrets/providers.mjs +461 -0
  304. package/lib/secrets/providers.test.mjs +274 -0
  305. package/lib/security/audit-engine.mjs +684 -0
  306. package/lib/security/audit-engine.test.mjs +389 -0
  307. package/lib/security/coerce-args.mjs +552 -0
  308. package/lib/security/coerce-args.test.mjs +281 -0
  309. package/lib/security/dangerous-tools.mjs +97 -0
  310. package/lib/security/dangerous-tools.test.mjs +68 -0
  311. package/lib/security/external-content.mjs +145 -0
  312. package/lib/security/external-content.test.mjs +67 -0
  313. package/lib/security/redact.mjs +592 -0
  314. package/lib/security/redact.test.mjs +441 -0
  315. package/lib/security/secret-equal.mjs +73 -0
  316. package/lib/security/secret-equal.test.mjs +55 -0
  317. package/lib/session-permissions.mjs +101 -0
  318. package/lib/session-permissions.test.mjs +100 -0
  319. package/lib/setup/claude-probe.mjs +74 -0
  320. package/lib/setup/completeness.mjs +175 -0
  321. package/lib/setup/completeness.test.mjs +110 -0
  322. package/lib/setup/context-pack.mjs +173 -0
  323. package/lib/setup/context-pack.test.mjs +89 -0
  324. package/lib/setup/enrich.mjs +277 -0
  325. package/lib/setup/enrich.test.mjs +115 -0
  326. package/lib/setup/enroll-from-cohort.mjs +441 -0
  327. package/lib/setup/enroll-from-cohort.test.mjs +233 -0
  328. package/lib/setup/integration.test.mjs +162 -0
  329. package/lib/setup/io.mjs +360 -0
  330. package/lib/setup/io.test.mjs +77 -0
  331. package/lib/setup/run-generator.mjs +81 -0
  332. package/lib/setup/runner.mjs +244 -0
  333. package/lib/setup/runner.test.mjs +132 -0
  334. package/lib/setup/sections/comms.mjs +173 -0
  335. package/lib/setup/sections/company.mjs +120 -0
  336. package/lib/setup/sections/enrich.mjs +138 -0
  337. package/lib/setup/sections/identity.mjs +182 -0
  338. package/lib/setup/sections/identity.test.mjs +140 -0
  339. package/lib/setup/sections/learning.mjs +153 -0
  340. package/lib/setup/sections/learning.test.mjs +81 -0
  341. package/lib/setup/sections/messaging.mjs +219 -0
  342. package/lib/setup/sections/messaging.test.mjs +127 -0
  343. package/lib/setup/sections/model.mjs +102 -0
  344. package/lib/setup/sections/operating-model.mjs +78 -0
  345. package/lib/setup/sections/org.mjs +475 -0
  346. package/lib/setup/sections/org.test.mjs +313 -0
  347. package/lib/setup/sections/orgmail.mjs +173 -0
  348. package/lib/setup/sections/orgmail.test.mjs +118 -0
  349. package/lib/setup/sections/recovery.mjs +159 -0
  350. package/lib/setup/sections/recovery.test.mjs +98 -0
  351. package/lib/setup/sections/tools.mjs +132 -0
  352. package/lib/setup/sections/verify.mjs +97 -0
  353. package/lib/setup/sot.mjs +205 -0
  354. package/lib/setup/sot.test.mjs +81 -0
  355. package/lib/setup/state.mjs +151 -0
  356. package/lib/setup/state.test.mjs +92 -0
  357. package/lib/singleton.js +229 -0
  358. package/lib/singleton.test.mjs +135 -0
  359. package/lib/telemetry/alerts.mjs +216 -0
  360. package/lib/telemetry/alerts.test.mjs +109 -0
  361. package/lib/telemetry/collect.mjs +512 -0
  362. package/lib/telemetry/collect.test.mjs +202 -0
  363. package/lib/tool-definitions-integration.test.mjs +83 -0
  364. package/lib/tool-definitions.js +738 -0
  365. package/lib/tool-definitions.test.mjs +437 -0
  366. package/lib/util/fetch-timeout.mjs +136 -0
  367. package/lib/util/fetch-timeout.test.mjs +202 -0
  368. package/lib/util/reconnect.mjs +343 -0
  369. package/lib/util/reconnect.test.mjs +369 -0
  370. package/lib/util/unhandled.mjs +205 -0
  371. package/lib/util/unhandled.test.mjs +216 -0
  372. package/lib/voice/context-loader.mjs +466 -0
  373. package/lib/voice/index.mjs +100 -0
  374. package/lib/voice/openai-realtime.mjs +510 -0
  375. package/lib/voice/outbound.mjs +542 -0
  376. package/lib/voice/outbound.test.mjs +69 -0
  377. package/lib/voice/post-call-brief.mjs +428 -0
  378. package/lib/voice/provider.mjs +52 -0
  379. package/lib/voice/session-rotation.mjs +257 -0
  380. package/lib/voice/stt.mjs +161 -0
  381. package/lib/voice/stt.test.mjs +226 -0
  382. package/lib/voice/tool-bridge.mjs +370 -0
  383. package/lib/voice/tts.mjs +104 -0
  384. package/lib/voice/twilio-sip-bridge.mjs +288 -0
  385. package/lib/voice/voice.test.mjs +990 -0
  386. package/mcp/README.md +80 -0
  387. package/package.json +151 -0
  388. package/plugins/maestro-skills/plugin.json +139 -0
  389. package/plugins/maestro-skills/skills/agents-observe.md +110 -0
  390. package/plugins/maestro-skills/skills/board-deck.md +68 -0
  391. package/plugins/maestro-skills/skills/books-close.md +77 -0
  392. package/plugins/maestro-skills/skills/brand-steward.md +121 -0
  393. package/plugins/maestro-skills/skills/calendar-plan.md +57 -0
  394. package/plugins/maestro-skills/skills/call-working-sessions.md +124 -0
  395. package/plugins/maestro-skills/skills/ccxray-diagnostics.md +91 -0
  396. package/plugins/maestro-skills/skills/claude-pace.md +61 -0
  397. package/plugins/maestro-skills/skills/code-review-graph.md +99 -0
  398. package/plugins/maestro-skills/skills/crm-pipeline.md +65 -0
  399. package/plugins/maestro-skills/skills/decision-brief.md +89 -0
  400. package/plugins/maestro-skills/skills/directory-hygiene.md +125 -0
  401. package/plugins/maestro-skills/skills/draft-comms.md +84 -0
  402. package/plugins/maestro-skills/skills/evening-wrap.md +53 -0
  403. package/plugins/maestro-skills/skills/files-find.md +65 -0
  404. package/plugins/maestro-skills/skills/generative-ui.md +228 -0
  405. package/plugins/maestro-skills/skills/hiring-triage.md +74 -0
  406. package/plugins/maestro-skills/skills/inbox-triage.md +61 -0
  407. package/plugins/maestro-skills/skills/mail-triage.md +86 -0
  408. package/plugins/maestro-skills/skills/morning-brief.md +54 -0
  409. package/plugins/maestro-skills/skills/native-artifacts.md +157 -0
  410. package/plugins/maestro-skills/skills/org-board.md +133 -0
  411. package/plugins/maestro-skills/skills/org-credential.md +68 -0
  412. package/plugins/maestro-skills/skills/org-recall.md +81 -0
  413. package/plugins/maestro-skills/skills/pipeline-review.md +76 -0
  414. package/plugins/maestro-skills/skills/regulatory-status.md +81 -0
  415. package/plugins/maestro-skills/skills/router-why.md +78 -0
  416. package/plugins/maestro-skills/skills/schedule-meeting.md +91 -0
  417. package/plugins/maestro-skills/skills/session-search.md +71 -0
  418. package/plugins/maestro-skills/skills/set-reminder.md +93 -0
  419. package/plugins/maestro-skills/skills/slack-followup.md +64 -0
  420. package/plugins/maestro-skills/skills/team-activity.md +86 -0
  421. package/plugins/maestro-skills/skills/weekly-memo.md +70 -0
  422. package/policies/action-classification.yaml +114 -0
  423. package/policies/ai-disclosure.yaml +294 -0
  424. package/policies/communication-style.md +139 -0
  425. package/policies/information-barriers.yaml +118 -0
  426. package/policies/prompt-injection-defence.yaml +138 -0
  427. package/public/assets/icon-dark.png +0 -0
  428. package/public/assets/icon-dark.svg +9 -0
  429. package/public/assets/icon-light.svg +9 -0
  430. package/public/assets/logo-dark.svg +15 -0
  431. package/public/assets/logo-light.svg +15 -0
  432. package/scaffold/.mcp.json +7 -0
  433. package/scaffold/CLAUDE.md +368 -0
  434. package/scaffold/config/agent.json +55 -0
  435. package/scaffold/config/agent.ts +76 -0
  436. package/scaffold/config/agent.ts.example +89 -0
  437. package/scaffold/config/alerts.yaml +23 -0
  438. package/scaffold/config/allowlist.yaml.example +25 -0
  439. package/scaffold/config/caller-id-map.yaml +46 -0
  440. package/scaffold/config/collective.yaml +49 -0
  441. package/scaffold/config/company.json +20 -0
  442. package/scaffold/config/known-agents.json +6 -0
  443. package/scaffold/config/learning.yaml +55 -0
  444. package/scaffold/config/model-routing.yaml.example +104 -0
  445. package/scaffold/config/org.yaml +25 -0
  446. package/scaffold/config/orgmail.yaml.example +19 -0
  447. package/scaffold/config/recovery.yaml +72 -0
  448. package/scaffold/config/secrets.yaml +27 -0
  449. package/scaffold/config/slack.yaml.example +35 -0
  450. package/scaffold/config/telegram.yaml.example +38 -0
  451. package/scaffold/config/voice.yaml.example +89 -0
  452. package/scaffold/config/whatsapp.yaml.example +39 -0
  453. package/schedules/README.md +49 -0
  454. package/schedules/triggers/backlog-executor.md +102 -0
  455. package/schedules/triggers/brand-steward.md +72 -0
  456. package/schedules/triggers/daily-evening-wrap.md +159 -0
  457. package/schedules/triggers/daily-midday-sweep.md +58 -0
  458. package/schedules/triggers/daily-morning-brief.md +55 -0
  459. package/schedules/triggers/directory-hygiene.md +81 -0
  460. package/schedules/triggers/dynamic-jobs.md +40 -0
  461. package/schedules/triggers/inbox-processor.md +115 -0
  462. package/schedules/triggers/meeting-action-capture.md +60 -0
  463. package/schedules/triggers/meeting-prep.md +69 -0
  464. package/schedules/triggers/messaging-inbound.md +50 -0
  465. package/schedules/triggers/org-pulse.md +24 -0
  466. package/schedules/triggers/quarterly-self-assessment.md +54 -0
  467. package/schedules/triggers/weekly-engineering-health.md +37 -0
  468. package/schedules/triggers/weekly-execution.md +65 -0
  469. package/schedules/triggers/weekly-hiring.md +53 -0
  470. package/schedules/triggers/weekly-priorities.md +38 -0
  471. package/schedules/triggers/weekly-strategic-memo.md +124 -0
  472. package/scripts/archive-email.sh +55 -0
  473. package/scripts/cadence/cadence-status.mjs +36 -0
  474. package/scripts/cadence/enqueue-cadence-tick.mjs +174 -0
  475. package/scripts/cadence/enqueue-cadence-tick.test.mjs +187 -0
  476. package/scripts/cadence/launchd-cadence-wrapper.sh +85 -0
  477. package/scripts/cadence/launchd-cloud-relay-wrapper.sh +95 -0
  478. package/scripts/cadence/launchd-socket-mode-wrapper.sh +95 -0
  479. package/scripts/ci/check-docs-accuracy.mjs +493 -0
  480. package/scripts/ci/check-docs-accuracy.test.mjs +409 -0
  481. package/scripts/ci/check-exports-exist.mjs +140 -0
  482. package/scripts/ci/check-files-exist.mjs +107 -0
  483. package/scripts/ci/check-no-build-artifacts.mjs +111 -0
  484. package/scripts/ci/check-no-build-artifacts.test.mjs +71 -0
  485. package/scripts/ci/check-no-confidential.mjs +198 -0
  486. package/scripts/ci/check-no-conflict-markers.mjs +169 -0
  487. package/scripts/ci/check-no-residual-identity.mjs +163 -0
  488. package/scripts/ci/check-no-residual-identity.test.mjs +89 -0
  489. package/scripts/ci/check-tarball-fidelity.mjs +205 -0
  490. package/scripts/ci/check-unresolved-tokens.mjs +83 -0
  491. package/scripts/ci/check.mjs +109 -0
  492. package/scripts/ci/check.test.mjs +194 -0
  493. package/scripts/ci/run-coverage.mjs +82 -0
  494. package/scripts/ci/run-tests.mjs +71 -0
  495. package/scripts/cloud-relay/README.md +59 -0
  496. package/scripts/cloud-relay/index.mjs +233 -0
  497. package/scripts/cloud-relay/package.json +15 -0
  498. package/scripts/cloud-relay/railway.json +13 -0
  499. package/scripts/cloud-relay/voice/README.md +94 -0
  500. package/scripts/cloud-relay/voice/package-lock.json +39 -0
  501. package/scripts/cloud-relay/voice/package.json +16 -0
  502. package/scripts/cloud-relay/voice/railway.json +13 -0
  503. package/scripts/cloud-relay/voice/server.mjs +532 -0
  504. package/scripts/collective/hook-runner.mjs +211 -0
  505. package/scripts/collective/hook-runner.test.mjs +90 -0
  506. package/scripts/collective/org-pulse.mjs +72 -0
  507. package/scripts/collective/org-sync.mjs +61 -0
  508. package/scripts/collective/recall.mjs +45 -0
  509. package/scripts/collective/who.mjs +30 -0
  510. package/scripts/comms-monitor.sh +288 -0
  511. package/scripts/configure-whatsapp-sandbox.sh +201 -0
  512. package/scripts/continuous-monitor.sh +91 -0
  513. package/scripts/cost/fleet-digest.mjs +407 -0
  514. package/scripts/cost/fleet-digest.test.mjs +207 -0
  515. package/scripts/cost/track-claude-usage.mjs +169 -0
  516. package/scripts/daemon/agent-daemon.mjs +989 -0
  517. package/scripts/daemon/agent-daemon.test.mjs +525 -0
  518. package/scripts/daemon/cadence-consumer-governance.test.mjs +220 -0
  519. package/scripts/daemon/cadence-consumer.mjs +1080 -0
  520. package/scripts/daemon/cadence-consumer.test.mjs +770 -0
  521. package/scripts/daemon/cadence-handlers.mjs +1121 -0
  522. package/scripts/daemon/cadence-handlers.test.mjs +617 -0
  523. package/scripts/daemon/classifier.mjs +704 -0
  524. package/scripts/daemon/classifier.test.mjs +238 -0
  525. package/scripts/daemon/classify-kind.mjs +54 -0
  526. package/scripts/daemon/classify-kind.test.mjs +40 -0
  527. package/scripts/daemon/context-compiler.mjs +605 -0
  528. package/scripts/daemon/context-compiler.test.mjs +300 -0
  529. package/scripts/daemon/dispatcher-cooldown.test.mjs +122 -0
  530. package/scripts/daemon/dispatcher-governance.test.mjs +886 -0
  531. package/scripts/daemon/dispatcher.mjs +1516 -0
  532. package/scripts/daemon/health.mjs +72 -0
  533. package/scripts/daemon/inbox-deferral.mjs +210 -0
  534. package/scripts/daemon/inbox-deferral.test.mjs +242 -0
  535. package/scripts/daemon/integration.test.mjs +149 -0
  536. package/scripts/daemon/launchd-wrapper-generic.sh +96 -0
  537. package/scripts/daemon/launchd-wrapper-slack-events.sh +37 -0
  538. package/scripts/daemon/launchd-wrapper.sh +91 -0
  539. package/scripts/daemon/lib/session-router.mjs +274 -0
  540. package/scripts/daemon/lib/session-router.test.mjs +295 -0
  541. package/scripts/daemon/maestro-daemon.mjs +275 -0
  542. package/scripts/daemon/prompt-builder.mjs +685 -0
  543. package/scripts/daemon/prompt-builder.test.mjs +213 -0
  544. package/scripts/daemon/responder.mjs +854 -0
  545. package/scripts/daemon/session-lock.mjs +721 -0
  546. package/scripts/daemon/session-lock.test.mjs +252 -0
  547. package/scripts/daemon/session-outcomes.mjs +640 -0
  548. package/scripts/daemon/session-outcomes.test.mjs +533 -0
  549. package/scripts/daemon/typing-registry.mjs +90 -0
  550. package/scripts/daemon/typing-registry.test.mjs +77 -0
  551. package/scripts/daemon/voice-webhook-server.mjs +804 -0
  552. package/scripts/decisions/capture-decision.mjs +116 -0
  553. package/scripts/disclosure_assessment.py +873 -0
  554. package/scripts/disclosure_boundaries.py +562 -0
  555. package/scripts/email-signature-principal.html +52 -0
  556. package/scripts/email-signature.html +60 -0
  557. package/scripts/email_quote_thread.py +167 -0
  558. package/scripts/email_thread_dedup.py +362 -0
  559. package/scripts/emergency-stop.sh +81 -0
  560. package/scripts/healthcheck.sh +116 -0
  561. package/scripts/hooks/block-mcp-cohort-send.sh +15 -0
  562. package/scripts/hooks/block-mcp-slack-send.sh +7 -0
  563. package/scripts/hooks/post-action-log.sh +126 -0
  564. package/scripts/hooks/pre-send-audit.sh +174 -0
  565. package/scripts/hooks/pre-send-audit.test.mjs +215 -0
  566. package/scripts/hooks/session-end-log.sh +27 -0
  567. package/scripts/hooks/session-start-banner.sh +115 -0
  568. package/scripts/huddle/audio-bridge.mjs +664 -0
  569. package/scripts/huddle/boot-slack-cdp.sh +102 -0
  570. package/scripts/huddle/huddle-controller.mjs +942 -0
  571. package/scripts/huddle/huddle-server.mjs +1229 -0
  572. package/scripts/huddle/launch-slack.sh +232 -0
  573. package/scripts/huddle/openai-realtime-bridge.mjs +462 -0
  574. package/scripts/huddle/package-lock.json +62 -0
  575. package/scripts/huddle/package.json +22 -0
  576. package/scripts/huddle/setup-audio.sh +239 -0
  577. package/scripts/huddle/start-call.mjs +318 -0
  578. package/scripts/huddle/test-pipeline.mjs +263 -0
  579. package/scripts/learning/consolidate-skills.mjs +72 -0
  580. package/scripts/learning/session-search.mjs +125 -0
  581. package/scripts/llm_email_dedup.py +442 -0
  582. package/scripts/local-triggers/generate-plists.sh +432 -0
  583. package/scripts/local-triggers/generate-plists.test.mjs +413 -0
  584. package/scripts/local-triggers/install-all.sh +49 -0
  585. package/scripts/local-triggers/plists/.gitkeep +0 -0
  586. package/scripts/local-triggers/run-trigger.sh +63 -0
  587. package/scripts/local-triggers/templates/rag-reindex.plist.template +47 -0
  588. package/scripts/local-triggers/templates/voice-relay-poller.plist.template +54 -0
  589. package/scripts/local-triggers/templates/voice-tunnel.plist.template +55 -0
  590. package/scripts/local-triggers/templates/voice-webhook.plist.template +51 -0
  591. package/scripts/maintenance/backup-to-cloud.sh +124 -0
  592. package/scripts/maintenance/health-check.sh +377 -0
  593. package/scripts/media-generation/README.md +105 -0
  594. package/scripts/media-generation/gemini-image-client.mjs +173 -0
  595. package/scripts/media-generation/generate-assets.mjs +289 -0
  596. package/scripts/media-generation/veo-video-client.mjs +219 -0
  597. package/scripts/org/send-orgmail.mjs +227 -0
  598. package/scripts/outbound-dedup-cleanup.sh +43 -0
  599. package/scripts/outbound-dedup.sh +477 -0
  600. package/scripts/outbound_dedup.py +115 -0
  601. package/scripts/parse-voice-transcript.mjs +481 -0
  602. package/scripts/pdf-generation/README.md +63 -0
  603. package/scripts/pdf-generation/build-document.mjs +247 -0
  604. package/scripts/pdf-generation/templates/board-pack.latex +136 -0
  605. package/scripts/pdf-generation/templates/corporate-letter.latex +126 -0
  606. package/scripts/pdf-generation/templates/memo.latex +114 -0
  607. package/scripts/poll-slack-events.sh +35 -0
  608. package/scripts/poller/calendar-poller.mjs +12 -0
  609. package/scripts/poller/gmail-poller.mjs +192 -0
  610. package/scripts/poller/imap-client.mjs +289 -0
  611. package/scripts/poller/inbox-scan-poller.mjs +156 -0
  612. package/scripts/poller/inbox-scan-poller.test.mjs +231 -0
  613. package/scripts/poller/index.mjs +73 -0
  614. package/scripts/poller/intra-session-check.mjs +285 -0
  615. package/scripts/poller/lib/cloud-relay-dedup.mjs +88 -0
  616. package/scripts/poller/lib/cloud-relay-dedup.test.mjs +133 -0
  617. package/scripts/poller/lib/slash-command-handlers.mjs +177 -0
  618. package/scripts/poller/secondary-gmail-poller.mjs +132 -0
  619. package/scripts/poller/slack-cloud-relay-client.mjs +368 -0
  620. package/scripts/poller/slack-poller.mjs +854 -0
  621. package/scripts/poller/slack-socket-mode.mjs +917 -0
  622. package/scripts/poller/slack-socket-mode.test.mjs +753 -0
  623. package/scripts/poller/trigger.mjs +75 -0
  624. package/scripts/poller/utils.mjs +371 -0
  625. package/scripts/poller/voice-cloud-relay-client.mjs +179 -0
  626. package/scripts/poller/voice-poller.mjs +236 -0
  627. package/scripts/poller-launchd/install.sh +66 -0
  628. package/scripts/poller-launchd/poller.plist.template +40 -0
  629. package/scripts/poller-launchd/whatsapp-handler.plist.template +39 -0
  630. package/scripts/post-interaction-indexer.py +1598 -0
  631. package/scripts/pre-draft-context.py +994 -0
  632. package/scripts/pre_draft_lookup.py +258 -0
  633. package/scripts/rag/build-index.mjs +47 -0
  634. package/scripts/rag/ingest.mjs +111 -0
  635. package/scripts/rag/search.mjs +119 -0
  636. package/scripts/rag-indexer.py +629 -0
  637. package/scripts/restore-from-backup.sh +248 -0
  638. package/scripts/restore-from-backup.test.mjs +178 -0
  639. package/scripts/resume-operations.sh +80 -0
  640. package/scripts/search-secondary-inbox.py +181 -0
  641. package/scripts/secondary-inbox-poller.py +437 -0
  642. package/scripts/self-optimization/compute-metrics.py +398 -0
  643. package/scripts/send-email-as-principal.py +369 -0
  644. package/scripts/send-email-threaded.py +392 -0
  645. package/scripts/send-email-with-attachment.py +377 -0
  646. package/scripts/send-email.sh +131 -0
  647. package/scripts/send-sms.sh +175 -0
  648. package/scripts/send-whatsapp.sh +292 -0
  649. package/scripts/session-start.sh +106 -0
  650. package/scripts/setup/boot-claude-session.sh +94 -0
  651. package/scripts/setup/configure-macos.sh +674 -0
  652. package/scripts/setup/configure-twilio-sip-trunk.mjs +207 -0
  653. package/scripts/setup/configure-voice-tunnel.mjs +182 -0
  654. package/scripts/setup/generate-agent-env.mjs +92 -0
  655. package/scripts/setup/generate-agent-package-json.mjs +222 -0
  656. package/scripts/setup/generate-agent-package-json.test.mjs +143 -0
  657. package/scripts/setup/generate-autonomy.mjs +60 -0
  658. package/scripts/setup/generate-backlog.mjs +101 -0
  659. package/scripts/setup/generate-cadences.mjs +92 -0
  660. package/scripts/setup/generate-capability.mjs +162 -0
  661. package/scripts/setup/generate-charter.mjs +76 -0
  662. package/scripts/setup/generate-comms.mjs +59 -0
  663. package/scripts/setup/generate-company.mjs +92 -0
  664. package/scripts/setup/init-agent.sh +547 -0
  665. package/scripts/setup/init-agent.test.mjs +151 -0
  666. package/scripts/setup/init-archetype.mjs +74 -0
  667. package/scripts/setup/init-backup.mjs +54 -0
  668. package/scripts/setup/init-cadence-bus.mjs +60 -0
  669. package/scripts/setup/init-channel-bus.mjs +46 -0
  670. package/scripts/setup/init-cost-tracking.mjs +45 -0
  671. package/scripts/setup/init-decision-capture.mjs +66 -0
  672. package/scripts/setup/init-known-agents.mjs +57 -0
  673. package/scripts/setup/init-learning.mjs +70 -0
  674. package/scripts/setup/init-memory-executive.mjs +45 -0
  675. package/scripts/setup/init-model-router.mjs +124 -0
  676. package/scripts/setup/init-rag.mjs +174 -0
  677. package/scripts/setup/init-session-router.mjs +38 -0
  678. package/scripts/setup/init-slack-socket-mode.mjs +260 -0
  679. package/scripts/setup/init-telegram.mjs +165 -0
  680. package/scripts/setup/init-voice-realtime.mjs +204 -0
  681. package/scripts/setup/init-whatsapp-baileys.mjs +77 -0
  682. package/scripts/setup/install-dev-tools.sh +150 -0
  683. package/scripts/setup/lib/install-plist.mjs +95 -0
  684. package/scripts/setup/migrate-agent-to-sot.mjs +192 -0
  685. package/scripts/setup/render-environment-yaml.mjs +133 -0
  686. package/scripts/slack-events-ctl.sh +177 -0
  687. package/scripts/slack-events-server.mjs +1045 -0
  688. package/scripts/slack-react.mjs +89 -0
  689. package/scripts/slack-responded.sh +232 -0
  690. package/scripts/slack-send.sh +287 -0
  691. package/scripts/slack-typing.mjs +196 -0
  692. package/scripts/slack-upload-v2.py +95 -0
  693. package/scripts/sms-handler.mjs +450 -0
  694. package/scripts/spawn-session.sh +120 -0
  695. package/scripts/sync-protocol.mjs +217 -0
  696. package/scripts/system-verify.sh +184 -0
  697. package/scripts/test-email-thread-dedup.py +239 -0
  698. package/scripts/test-information-barriers.py +484 -0
  699. package/scripts/test-llm-email-dedup.py +251 -0
  700. package/scripts/test-pre-draft-integration.py +203 -0
  701. package/scripts/test-rag-phase2.sh +442 -0
  702. package/scripts/test-rag-search.sh +251 -0
  703. package/scripts/test-voice-parser.mjs +316 -0
  704. package/scripts/user-context-search.py +659 -0
  705. package/scripts/validate_outbound.py +1504 -0
  706. package/scripts/watchdog/ai.maestro.memory-watchdog.plist +41 -0
  707. package/scripts/watchdog/force-reboot.sh +157 -0
  708. package/scripts/watchdog/memory-watchdog.sh +473 -0
  709. package/scripts/whatsapp-handler.mjs +538 -0
  710. package/teams/desktop-operations.yaml +34 -0
  711. package/teams/executive-office.yaml +27 -0
  712. package/teams/legal-and-regulatory.yaml +24 -0
  713. package/teams/platform-and-engineering.yaml +23 -0
  714. package/teams/strategy-and-growth.yaml +29 -0
  715. package/workflows/continuous/backlog-executor.yaml +141 -0
  716. package/workflows/continuous/inbound-monitor.yaml +168 -0
  717. package/workflows/daily/applicant-triage.yaml +197 -0
  718. package/workflows/daily/comms-triage.yaml +80 -0
  719. package/workflows/daily/evening-wrap.yaml +105 -0
  720. package/workflows/daily/morning-brief.yaml +164 -0
  721. package/workflows/daily/slack-followup-sweep.yaml +87 -0
  722. package/workflows/event-driven/README.md +50 -0
  723. package/workflows/event-driven/agent-failure-investigation.yaml +137 -0
  724. package/workflows/event-driven/pr-review.yaml +107 -0
  725. package/workflows/monthly/board-readiness.yaml +76 -0
  726. package/workflows/quarterly/strategic-scenario-analysis.yaml +85 -0
  727. package/workflows/session-protocol.md +171 -0
  728. package/workflows/weekly/engineering-health.yaml +154 -0
  729. package/workflows/weekly/hiring-review.yaml +169 -0
  730. package/workflows/weekly/rollup-pipeline-review.yaml +76 -0
  731. package/workflows/weekly/strategic-memo.yaml +79 -0
@@ -0,0 +1,72 @@
1
+ #!/usr/bin/env node
2
+ // Health — periodic health metrics and status logging
3
+
4
+ import { writeFileSync, mkdirSync } from "fs";
5
+ import { join } from "path";
6
+ import { getStatus } from "./dispatcher.mjs";
7
+
8
+ const AGENT_REPO_DIR = process.env.AGENT_DIR || join(new URL(".", import.meta.url).pathname, "../..");
9
+
10
+ let stats = {
11
+ daemon_start: new Date().toISOString(),
12
+ polls_total: 0,
13
+ items_detected: 0,
14
+ items_classified: 0,
15
+ sessions_spawned: 0,
16
+ sessions_completed: 0,
17
+ sessions_failed: 0,
18
+ classifications_failed: 0,
19
+ last_poll: null,
20
+ last_item: null,
21
+ };
22
+
23
+ export function recordPoll(itemCount) {
24
+ stats.polls_total++;
25
+ stats.items_detected += itemCount;
26
+ stats.last_poll = new Date().toISOString();
27
+ if (itemCount > 0) stats.last_item = stats.last_poll;
28
+ }
29
+
30
+ export function recordClassification(success) {
31
+ if (success) stats.items_classified++;
32
+ else stats.classifications_failed++;
33
+ }
34
+
35
+ export function recordSession(completed, success) {
36
+ if (!completed) {
37
+ stats.sessions_spawned++;
38
+ } else if (success) {
39
+ stats.sessions_completed++;
40
+ } else {
41
+ stats.sessions_failed++;
42
+ }
43
+ }
44
+
45
+ export function writeHealthDashboard() {
46
+ const dispatcher = getStatus();
47
+ const dashboard = {
48
+ updated: new Date().toISOString(),
49
+ uptime_s: Math.floor((Date.now() - new Date(stats.daemon_start).getTime()) / 1000),
50
+ ...stats,
51
+ dispatcher,
52
+ };
53
+
54
+ const dir = join(AGENT_REPO_DIR, "state", "dashboards");
55
+ mkdirSync(dir, { recursive: true });
56
+ const path = join(dir, "daemon-health.yaml");
57
+
58
+ const yaml = Object.entries(dashboard)
59
+ .map(([k, v]) => {
60
+ if (typeof v === "object" && v !== null) {
61
+ return `${k}:\n${JSON.stringify(v, null, 2).split("\n").map(l => ` ${l}`).join("\n")}`;
62
+ }
63
+ return `${k}: ${v}`;
64
+ })
65
+ .join("\n");
66
+
67
+ writeFileSync(path, yaml + "\n");
68
+ }
69
+
70
+ export function getStats() {
71
+ return { ...stats };
72
+ }
@@ -0,0 +1,210 @@
1
+ /**
2
+ * Maestro — Inbox deferral utilities
3
+ *
4
+ * When the dispatcher hits a thread/channel-level dedup lock, the
5
+ * incoming message used to be marked `.processed` and dropped on the
6
+ * floor. That meant rapid-fire DMs (or thread bursts) silently lost
7
+ * every message after the first.
8
+ *
9
+ * This module replaces that with a defer-then-promote pattern:
10
+ *
11
+ * markDeferred(item, service, agentRoot)
12
+ * Renames the inbox file from `<name>` to `<name>.deferred`.
13
+ * The daemon's inbox scanner skips `.deferred` files, so the
14
+ * message is parked but not lost.
15
+ *
16
+ * promoteDeferred(channel, agentRoot)
17
+ * Called from the dispatcher's session-close release path. Finds
18
+ * every `.deferred` file in any service inbox dir whose body
19
+ * references `channel`. If exactly one is found, renames it back
20
+ * to its original name (next poll picks it up). If N>1 are found,
21
+ * keeps only the LATEST (by timestamp), promotes that one, and
22
+ * marks the others `.processed-bundled` for the audit trail —
23
+ * the latest item's `thread_context` already contains the prior
24
+ * messages as conversation history, so Claude sees everything
25
+ * and can compose a single coherent reply.
26
+ *
27
+ * The file format is the YAML emitted by the slack/gmail/calendar
28
+ * pollers (a flat top-level object with quoted scalar fields, plus an
29
+ * optional block-scalar body). We deliberately avoid pulling in a YAML
30
+ * parser — extraction of the COLUMN-0 top-level `channel_id:` and
31
+ * `timestamp:` fields is sufficient for the routing decision and keeps
32
+ * this module dependency-free. Extraction is anchored to top-level keys so
33
+ * an indented `channel_id:`/`timestamp:` inside a message body block scalar
34
+ * can never be mistaken for the real field (audit L7).
35
+ */
36
+
37
+ import { readdirSync, readFileSync, renameSync, existsSync } from "node:fs";
38
+ import { join } from "node:path";
39
+
40
+ const SERVICE_DIRS = ["slack", "gmail", "calendar", "internal", "sms", "whatsapp"];
41
+
42
+ /**
43
+ * Rename a live inbox file to its `.deferred` suffix so the scanner
44
+ * skips it until the channel/thread lock releases.
45
+ *
46
+ * Mirrors markProcessed's file-matching strategy (id or raw_ref
47
+ * substring) so we hit the same file that markProcessed would have.
48
+ *
49
+ * Idempotent: if the file is already `.deferred` or `.processed`, no-op.
50
+ *
51
+ * @returns {number} number of files renamed (usually 1, occasionally 0
52
+ * for backlog items with no inbox file)
53
+ */
54
+ export function markDeferred(item, service, agentRoot) {
55
+ if (!item || !service || !agentRoot) return 0;
56
+ const inboxDir = join(agentRoot, "state", "inbox", service);
57
+ if (!existsSync(inboxDir)) return 0;
58
+ let renamed = 0;
59
+ try {
60
+ const needle = item.id || item.raw_ref;
61
+ if (!needle) return 0;
62
+ const files = readdirSync(inboxDir).filter(
63
+ (f) => !f.endsWith(".processed") &&
64
+ !f.endsWith(".deferred") &&
65
+ !f.endsWith(".processed-bundled") &&
66
+ (f.includes(item.id || "") || f.includes(item.raw_ref || ""))
67
+ );
68
+ for (const file of files) {
69
+ renameSync(join(inboxDir, file), join(inboxDir, `${file}.deferred`));
70
+ renamed++;
71
+ }
72
+ } catch {
73
+ // Inbox dir may not exist for non-poller-backed items (e.g. backlog).
74
+ }
75
+ return renamed;
76
+ }
77
+
78
+ // Escape regex metacharacters in an interpolated field name so a field like
79
+ // `a.b` can't be read as a wildcard (audit L7).
80
+ function escapeRegExp(s) {
81
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
82
+ }
83
+
84
+ /**
85
+ * Extract a TOP-LEVEL scalar value from a poller-written inbox YAML file.
86
+ * Returns `null` if the field is absent.
87
+ *
88
+ * We only need this for `channel_id` (routing) and `timestamp`
89
+ * (latest-wins selection) — both are written as quoted single-line
90
+ * scalars by every poller.
91
+ *
92
+ * Audit L7: the previous regex used `^\s*field:` with the `m` flag, so any
93
+ * INDENTED line inside a block scalar (e.g. a message body that itself
94
+ * contains ` channel_id: D-OTHER` or ` timestamp: ...`) could match and be
95
+ * mistaken for the real top-level field — mis-routing the promote to the
96
+ * wrong channel. We now scan only the column-0 top-level region: keys with NO
97
+ * leading whitespace, stopping at the first block/indented region. A YAML
98
+ * block scalar (`key: |` / `key: >`) introduces an indented body, so once we
99
+ * see one we skip its indented continuation lines instead of matching inside
100
+ * them. The interpolated field name is regex-escaped.
101
+ */
102
+ function readScalar(body, field) {
103
+ if (typeof body !== "string") return null;
104
+ const key = escapeRegExp(field);
105
+ // Top-level key: starts at column 0 (no indentation), value on same line.
106
+ // Capture group handles optional double-quotes around the value.
107
+ const topLevel = new RegExp(`^${key}\\s*:\\s*"?([^"\\n]*?)"?\\s*$`);
108
+ // A column-0 line that opens a block scalar or a nested mapping/sequence —
109
+ // its indented continuation lines are NOT top-level and must be skipped.
110
+ const opensBlock = /^[^\s:][^:]*:\s*(?:[|>][+-]?\d*)?\s*$/;
111
+
112
+ const lines = body.split("\n");
113
+ for (let i = 0; i < lines.length; i++) {
114
+ const line = lines[i];
115
+ // Skip indented (non-top-level) lines entirely — they belong to a block
116
+ // scalar body or a nested structure, never a top-level scalar.
117
+ if (/^\s/.test(line)) continue;
118
+ if (line.trim() === "") continue;
119
+ const m = line.match(topLevel);
120
+ if (m) return m[1].trim();
121
+ // If this top-level line opens a block/nested region, the following
122
+ // indented lines are its body — they're skipped by the `^\s` guard above,
123
+ // so no extra bookkeeping is needed here. (Documented for clarity.)
124
+ void opensBlock;
125
+ }
126
+ return null;
127
+ }
128
+
129
+ /**
130
+ * Promote every `.deferred` item targeting `channel` back into the
131
+ * live inbox. Bundles bursts: if multiple deferred items exist for
132
+ * the same channel, only the latest is re-queued (the others are
133
+ * marked `.processed-bundled` since their content is already present
134
+ * in the latest item's thread_context).
135
+ *
136
+ * Called from the dispatcher's session-close path, AFTER the channel
137
+ * lock has been released, so the next poll cycle is free to re-acquire.
138
+ *
139
+ * @returns {{promoted: number, bundled: number, service: string|null}}
140
+ */
141
+ export function promoteDeferred(channel, agentRoot) {
142
+ const result = { promoted: 0, bundled: 0, service: null };
143
+ if (!channel || !agentRoot) return result;
144
+
145
+ for (const service of SERVICE_DIRS) {
146
+ const inboxDir = join(agentRoot, "state", "inbox", service);
147
+ if (!existsSync(inboxDir)) continue;
148
+
149
+ let deferredFiles;
150
+ try {
151
+ deferredFiles = readdirSync(inboxDir).filter((f) => f.endsWith(".deferred"));
152
+ } catch {
153
+ continue;
154
+ }
155
+ if (deferredFiles.length === 0) continue;
156
+
157
+ // Filter to items whose body references this channel. Reading
158
+ // every .deferred file is cheap (a handful at most) — we stay
159
+ // dependency-free by regex-scanning rather than parsing YAML.
160
+ const matches = [];
161
+ for (const file of deferredFiles) {
162
+ let body;
163
+ try {
164
+ body = readFileSync(join(inboxDir, file), "utf-8");
165
+ } catch {
166
+ continue;
167
+ }
168
+ const fileChannel = readScalar(body, "channel_id");
169
+ if (fileChannel !== channel) continue;
170
+ matches.push({
171
+ file,
172
+ timestamp: readScalar(body, "timestamp") || "",
173
+ });
174
+ }
175
+ if (matches.length === 0) continue;
176
+
177
+ // Latest-wins: lex-sort ISO timestamps, take the most recent.
178
+ matches.sort((a, b) => a.timestamp.localeCompare(b.timestamp));
179
+ const latest = matches[matches.length - 1];
180
+ const older = matches.slice(0, -1);
181
+
182
+ // Promote the latest back to live inbox.
183
+ try {
184
+ const live = latest.file.replace(/\.deferred$/, "");
185
+ renameSync(join(inboxDir, latest.file), join(inboxDir, live));
186
+ result.promoted++;
187
+ result.service = service;
188
+ } catch {
189
+ // If the rename failed (race with another process), leave it
190
+ // as .deferred — the next session-close will retry.
191
+ }
192
+
193
+ // Mark older bursts as bundled — their content is already part of
194
+ // the latest item's thread_context, so they don't need their own
195
+ // session, but we preserve the file for audit.
196
+ for (const m of older) {
197
+ try {
198
+ renameSync(
199
+ join(inboxDir, m.file),
200
+ join(inboxDir, m.file.replace(/\.deferred$/, ".processed-bundled"))
201
+ );
202
+ result.bundled++;
203
+ } catch {
204
+ // Best-effort; missing file is fine.
205
+ }
206
+ }
207
+ }
208
+
209
+ return result;
210
+ }
@@ -0,0 +1,242 @@
1
+ import { test } from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { mkdtempSync, mkdirSync, writeFileSync, readdirSync, rmSync } from "node:fs";
4
+ import { join } from "node:path";
5
+ import { tmpdir } from "node:os";
6
+
7
+ import { markDeferred, promoteDeferred } from "./inbox-deferral.mjs";
8
+
9
+ function makeAgentRoot() {
10
+ const root = mkdtempSync(join(tmpdir(), "maestro-deferral-"));
11
+ mkdirSync(join(root, "state", "inbox", "slack"), { recursive: true });
12
+ return root;
13
+ }
14
+
15
+ function writeInboxItem(root, name, { channel_id, timestamp, id = "test-id" }) {
16
+ const body = [
17
+ `id: "${id}"`,
18
+ `service: "slack"`,
19
+ `channel_id: "${channel_id}"`,
20
+ `timestamp: "${timestamp}"`,
21
+ `content: |`,
22
+ ` body`,
23
+ "",
24
+ ].join("\n");
25
+ writeFileSync(join(root, "state", "inbox", "slack", name), body);
26
+ }
27
+
28
+ test("markDeferred renames live file to .deferred", () => {
29
+ const root = makeAgentRoot();
30
+ try {
31
+ writeInboxItem(root, "msg-A.yaml", {
32
+ channel_id: "D001",
33
+ timestamp: "2026-05-13T00:00:00Z",
34
+ id: "msg-A",
35
+ });
36
+ const n = markDeferred({ id: "msg-A" }, "slack", root);
37
+ assert.equal(n, 1);
38
+ const files = readdirSync(join(root, "state", "inbox", "slack"));
39
+ assert.deepEqual(files, ["msg-A.yaml.deferred"]);
40
+ } finally {
41
+ rmSync(root, { recursive: true, force: true });
42
+ }
43
+ });
44
+
45
+ test("markDeferred is idempotent — already-deferred files are skipped", () => {
46
+ const root = makeAgentRoot();
47
+ try {
48
+ writeInboxItem(root, "msg-B.yaml.deferred", {
49
+ channel_id: "D001",
50
+ timestamp: "2026-05-13T00:00:00Z",
51
+ id: "msg-B",
52
+ });
53
+ const n = markDeferred({ id: "msg-B" }, "slack", root);
54
+ assert.equal(n, 0);
55
+ } finally {
56
+ rmSync(root, { recursive: true, force: true });
57
+ }
58
+ });
59
+
60
+ test("markDeferred is no-op when item has no matching file (e.g. backlog)", () => {
61
+ const root = makeAgentRoot();
62
+ try {
63
+ const n = markDeferred({ id: "no-such-id" }, "slack", root);
64
+ assert.equal(n, 0);
65
+ } finally {
66
+ rmSync(root, { recursive: true, force: true });
67
+ }
68
+ });
69
+
70
+ test("promoteDeferred with single item: renames back to live", () => {
71
+ const root = makeAgentRoot();
72
+ try {
73
+ writeInboxItem(root, "msg-C.yaml.deferred", {
74
+ channel_id: "D099N1JEA10",
75
+ timestamp: "2026-05-13T00:00:00Z",
76
+ id: "msg-C",
77
+ });
78
+ const r = promoteDeferred("D099N1JEA10", root);
79
+ assert.equal(r.promoted, 1);
80
+ assert.equal(r.bundled, 0);
81
+ assert.equal(r.service, "slack");
82
+ const files = readdirSync(join(root, "state", "inbox", "slack"));
83
+ assert.deepEqual(files, ["msg-C.yaml"]);
84
+ } finally {
85
+ rmSync(root, { recursive: true, force: true });
86
+ }
87
+ });
88
+
89
+ test("promoteDeferred with multiple items: keeps latest, bundles rest", () => {
90
+ const root = makeAgentRoot();
91
+ try {
92
+ writeInboxItem(root, "msg-1.yaml.deferred", {
93
+ channel_id: "D001",
94
+ timestamp: "2026-05-13T00:00:00Z",
95
+ id: "msg-1",
96
+ });
97
+ writeInboxItem(root, "msg-2.yaml.deferred", {
98
+ channel_id: "D001",
99
+ timestamp: "2026-05-13T00:01:00Z",
100
+ id: "msg-2",
101
+ });
102
+ writeInboxItem(root, "msg-3.yaml.deferred", {
103
+ channel_id: "D001",
104
+ timestamp: "2026-05-13T00:02:00Z",
105
+ id: "msg-3",
106
+ });
107
+ const r = promoteDeferred("D001", root);
108
+ assert.equal(r.promoted, 1);
109
+ assert.equal(r.bundled, 2);
110
+ const files = readdirSync(join(root, "state", "inbox", "slack")).sort();
111
+ assert.deepEqual(files, [
112
+ "msg-1.yaml.processed-bundled",
113
+ "msg-2.yaml.processed-bundled",
114
+ "msg-3.yaml", // latest survives as live
115
+ ]);
116
+ } finally {
117
+ rmSync(root, { recursive: true, force: true });
118
+ }
119
+ });
120
+
121
+ test("promoteDeferred ignores items in other channels", () => {
122
+ const root = makeAgentRoot();
123
+ try {
124
+ writeInboxItem(root, "for-A.yaml.deferred", {
125
+ channel_id: "D-A",
126
+ timestamp: "2026-05-13T00:00:00Z",
127
+ id: "for-A",
128
+ });
129
+ writeInboxItem(root, "for-B.yaml.deferred", {
130
+ channel_id: "D-B",
131
+ timestamp: "2026-05-13T00:00:00Z",
132
+ id: "for-B",
133
+ });
134
+ const r = promoteDeferred("D-A", root);
135
+ assert.equal(r.promoted, 1);
136
+ assert.equal(r.bundled, 0);
137
+ const files = readdirSync(join(root, "state", "inbox", "slack")).sort();
138
+ assert.deepEqual(files, ["for-A.yaml", "for-B.yaml.deferred"]);
139
+ } finally {
140
+ rmSync(root, { recursive: true, force: true });
141
+ }
142
+ });
143
+
144
+ test("promoteDeferred no-op when nothing to promote", () => {
145
+ const root = makeAgentRoot();
146
+ try {
147
+ const r = promoteDeferred("D999", root);
148
+ assert.equal(r.promoted, 0);
149
+ assert.equal(r.bundled, 0);
150
+ assert.equal(r.service, null);
151
+ } finally {
152
+ rmSync(root, { recursive: true, force: true });
153
+ }
154
+ });
155
+
156
+ // ---------------------------------------------------------------------------
157
+ // Audit L7 — block-scalar body must not be mis-parsed for routing fields
158
+ // ---------------------------------------------------------------------------
159
+
160
+ /**
161
+ * Write an inbox item whose `content` block scalar body itself contains lines
162
+ * that look like top-level `channel_id:` / `timestamp:` keys. A naive
163
+ * `^\s*field:` regex would match the INDENTED body line and mis-route.
164
+ */
165
+ function writeInboxItemWithTrickyBody(root, name, { channel_id, timestamp, id, bodyChannel, bodyTimestamp }) {
166
+ const lines = [
167
+ `id: "${id}"`,
168
+ `service: "slack"`,
169
+ `channel_id: "${channel_id}"`,
170
+ `timestamp: "${timestamp}"`,
171
+ `content: |`,
172
+ ` Hi team — quoting an old config below:`,
173
+ ` channel_id: ${bodyChannel}`,
174
+ ` timestamp: ${bodyTimestamp}`,
175
+ ` please review.`,
176
+ "",
177
+ ];
178
+ writeFileSync(join(root, "state", "inbox", "slack", name), lines.join("\n"));
179
+ }
180
+
181
+ test("L7: body line `channel_id:` in a block scalar does not mis-route", () => {
182
+ const root = makeAgentRoot();
183
+ try {
184
+ // Real top-level channel is D-REAL. The body mentions D-EVIL on an
185
+ // indented `channel_id:` line — promote must target D-REAL only.
186
+ writeInboxItemWithTrickyBody(root, "tricky.yaml.deferred", {
187
+ channel_id: "D-REAL",
188
+ timestamp: "2026-05-13T00:00:00Z",
189
+ id: "tricky",
190
+ bodyChannel: "D-EVIL",
191
+ bodyTimestamp: "1999-01-01T00:00:00Z",
192
+ });
193
+
194
+ // Promoting the body's channel must find NOTHING (the body line is not a
195
+ // real top-level field).
196
+ const evil = promoteDeferred("D-EVIL", root);
197
+ assert.equal(evil.promoted, 0, "must not match the indented body channel_id");
198
+ assert.equal(evil.bundled, 0);
199
+
200
+ // Promoting the genuine top-level channel must succeed.
201
+ const real = promoteDeferred("D-REAL", root);
202
+ assert.equal(real.promoted, 1, "real top-level channel_id must match");
203
+ assert.equal(real.bundled, 0);
204
+ const files = readdirSync(join(root, "state", "inbox", "slack"));
205
+ assert.deepEqual(files, ["tricky.yaml"]);
206
+ } finally {
207
+ rmSync(root, { recursive: true, force: true });
208
+ }
209
+ });
210
+
211
+ test("L7: latest-wins uses top-level timestamp, not a body `timestamp:` line", () => {
212
+ const root = makeAgentRoot();
213
+ try {
214
+ // Two items in the same channel. Item B is genuinely later by its
215
+ // top-level timestamp, but its block-scalar body contains an EARLY
216
+ // `timestamp:` line. A naive parser could read the body line, pick A as
217
+ // "latest" and bundle B. Correct behaviour: B wins (later top-level ts).
218
+ writeInboxItem(root, "A.yaml.deferred", {
219
+ channel_id: "D-TS",
220
+ timestamp: "2026-05-13T00:00:00Z",
221
+ id: "A",
222
+ });
223
+ writeInboxItemWithTrickyBody(root, "B.yaml.deferred", {
224
+ channel_id: "D-TS",
225
+ timestamp: "2026-05-13T09:00:00Z", // genuinely later
226
+ id: "B",
227
+ bodyChannel: "D-TS",
228
+ bodyTimestamp: "2000-01-01T00:00:00Z", // decoy early ts in the body
229
+ });
230
+
231
+ const r = promoteDeferred("D-TS", root);
232
+ assert.equal(r.promoted, 1);
233
+ assert.equal(r.bundled, 1);
234
+ const files = readdirSync(join(root, "state", "inbox", "slack")).sort();
235
+ assert.deepEqual(files, [
236
+ "A.yaml.processed-bundled", // earlier item bundled
237
+ "B.yaml", // later top-level timestamp survives as live
238
+ ]);
239
+ } finally {
240
+ rmSync(root, { recursive: true, force: true });
241
+ }
242
+ });
@@ -0,0 +1,149 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * test-integration.mjs — End-to-end integration tests for session lock + context compiler pipeline
4
+ */
5
+
6
+ import { describe, it, beforeEach, afterEach } from "node:test";
7
+ import assert from "node:assert/strict";
8
+ import { mkdirSync, rmSync, writeFileSync, readFileSync, existsSync } from "fs";
9
+ import { join, dirname } from "path";
10
+ import { fileURLToPath } from "url";
11
+
12
+ const __dirname = dirname(fileURLToPath(import.meta.url));
13
+ const TEST_DIR = join(__dirname, "../../.test-integration");
14
+
15
+ // Set env BEFORE importing modules. context-compiler.mjs reads its repo root
16
+ // from __TEST_AGENT_DIR (the legacy __TEST_SOPHIE_DIR name was never honoured by
17
+ // the module, so the disclosure-boundary fixtures were written here but read
18
+ // from the real repo — the cause of the end-to-end failure). Set both: the
19
+ // session-lock module reads __TEST_SESSION_DIR; the compiler reads
20
+ // __TEST_AGENT_DIR + __TEST_SESSION_DIR.
21
+ process.env.__TEST_SESSION_DIR = join(TEST_DIR, "state", "sessions");
22
+ process.env.__TEST_AGENT_DIR = TEST_DIR;
23
+ process.env.__TEST_SOPHIE_DIR = TEST_DIR; // kept for any legacy reader; harmless
24
+
25
+ function setupTestEnv() {
26
+ mkdirSync(join(TEST_DIR, "state", "sessions", "locks"), { recursive: true });
27
+ mkdirSync(join(TEST_DIR, "memory", "profiles", "users"), { recursive: true });
28
+ mkdirSync(join(TEST_DIR, "memory", "interactions", "slack", "dm-test-user"), { recursive: true });
29
+ mkdirSync(join(TEST_DIR, "policies"), { recursive: true });
30
+ writeFileSync(join(TEST_DIR, "state", "sessions", "active.json"), "{}");
31
+
32
+ writeFileSync(join(TEST_DIR, "policies", "information-barriers.yaml"), `domains:
33
+ internal-legal:
34
+ description: "Northwind legal strategy, GC advice, privilege-protected"
35
+ default_sensitivity: high
36
+ strategic-initiative:
37
+ description: "the strategic initiative M&A targets, deal terms, pipeline"
38
+ default_sensitivity: critical
39
+ public:
40
+ description: "Published information, press, website"
41
+ default_sensitivity: none
42
+ `);
43
+ }
44
+
45
+ function cleanTestEnv() {
46
+ rmSync(TEST_DIR, { recursive: true, force: true });
47
+ }
48
+
49
+ const { acquireLock, releaseLock, registerSent, checkRecentlySent } = await import("./session-lock.mjs");
50
+ const { compileContext } = await import("./context-compiler.mjs");
51
+
52
+ // ─── Integration tests ─────────────────────────────────────────────────
53
+
54
+ describe("Integration: lock → compile → prompt pipeline", () => {
55
+ beforeEach(setupTestEnv);
56
+ afterEach(cleanTestEnv);
57
+
58
+ it("acquireLock prevents duplicate processing of same item", () => {
59
+ const first = acquireLock("slack-C001-12345", { sender: "Test", channel: "C001" });
60
+ assert.equal(first.acquired, true);
61
+
62
+ const second = acquireLock("slack-C001-12345", { sender: "Test", channel: "C001" });
63
+ assert.equal(second.acquired, false);
64
+
65
+ releaseLock("slack-C001-12345");
66
+ });
67
+
68
+ it("sent-registry prevents duplicate sends across quick-reply and holding paths", () => {
69
+ // Holding message sent
70
+ registerSent("C001", "12345.6789", "holding", "s-1", "Looking into this now");
71
+
72
+ // A quick reply to the SAME channel+thread is blocked by the thread-cooldown
73
+ // rule (Rule 2): once anything has been sent to a thread, the whole thread
74
+ // cools down so the quick-reply path and the full-session path can't both
75
+ // fire into the same thread. (The original assertion expected the different
76
+ // type to be allowed — that predated Rule 2 and was stale; Rule 2 is the
77
+ // whole point of this test, "prevents duplicate sends across paths".)
78
+ const quickCheck = checkRecentlySent("C001", "12345.6789", "quick_reply");
79
+ assert.equal(quickCheck.allowed, false);
80
+ assert.match(quickCheck.reason, /thread cooldown/);
81
+
82
+ // Another holding to same channel+thread is likewise blocked.
83
+ const holdCheck = checkRecentlySent("C001", "12345.6789", "holding");
84
+ assert.equal(holdCheck.allowed, false);
85
+
86
+ // But a different, untouched thread is still free to receive a quick reply —
87
+ // the cooldown is scoped to the thread, not the whole channel (non-DM).
88
+ const otherThread = checkRecentlySent("C001", "99999.0000", "quick_reply");
89
+ assert.equal(otherThread.allowed, true);
90
+ });
91
+
92
+ it("context compiler respects disclosure boundaries end-to-end", async () => {
93
+ // Create a profile with restrictions
94
+ writeFileSync(
95
+ join(TEST_DIR, "memory", "profiles", "users", "test-user.yaml"),
96
+ `name: Test User\ninformation_boundaries:\n excluded_domains:\n - strategic-initiative\n`
97
+ );
98
+
99
+ // Create history with mixed content
100
+ const today = new Date().toISOString().split("T")[0];
101
+ writeFileSync(
102
+ join(TEST_DIR, "memory", "interactions", "slack", "dm-test-user", `${today}.jsonl`),
103
+ [
104
+ JSON.stringify({ ts: "100", from: "test-user", content: "What is the status?", received_at: "2026-04-06T10:00:00Z" }),
105
+ JSON.stringify({ ts: "101", from: "robin", content: "the strategic initiative deal terms are looking good", received_at: "2026-04-06T10:01:00Z" }),
106
+ JSON.stringify({ ts: "102", from: "robin", content: "The public website update is live", received_at: "2026-04-06T10:02:00Z" }),
107
+ ].join("\n") + "\n"
108
+ );
109
+
110
+ const { contextBlock } = await compileContext(
111
+ { sender: "Test User", channel: "D001", channel_id: "D001" },
112
+ { priority: "normal", model: "sonnet", action: "respond" },
113
+ {}
114
+ );
115
+
116
+ // Public content should be included
117
+ assert.ok(contextBlock.includes("website update is live"));
118
+ // Strategic-initiative content should be filtered. (The old assertion
119
+ // checked for "Aether deal terms" — absent from the fixture — so it was a
120
+ // tautology; assert against the actual excluded fixture text.)
121
+ assert.ok(!contextBlock.includes("strategic initiative deal terms"));
122
+ // Should have disclosure boundaries
123
+ assert.ok(contextBlock.includes("DO NOT mention"));
124
+ });
125
+
126
+ it("active sessions are visible in compiled context", async () => {
127
+ writeFileSync(
128
+ join(TEST_DIR, "state", "sessions", "active.json"),
129
+ JSON.stringify({
130
+ "s-999": {
131
+ sender: "Alice",
132
+ channel: "C002",
133
+ summary: "Preparing board pack",
134
+ model: "opus",
135
+ startTime: Date.now() - 120000,
136
+ },
137
+ })
138
+ );
139
+
140
+ const { contextBlock } = await compileContext(
141
+ { sender: "Test", channel: "C001" },
142
+ { priority: "normal", model: "sonnet", action: "respond" },
143
+ {}
144
+ );
145
+
146
+ assert.ok(contextBlock.includes("Preparing board pack"));
147
+ assert.ok(contextBlock.includes("Active Sessions"));
148
+ });
149
+ });