@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,554 @@
1
+ /**
2
+ * lib/comms/send-gate.mjs — the ONE shared outbound send-gate chokepoint.
3
+ *
4
+ * Why this exists
5
+ * ---------------
6
+ * The pre-send audit hook (scripts/hooks/pre-send-audit.sh) historically gated
7
+ * exactly one path: the `gmail_create_draft` tool. Every other real send path —
8
+ * scripts/slack-send.sh, scripts/send-whatsapp.sh, scripts/send-sms.sh,
9
+ * scripts/send-email.sh, the daemon responder, and BaseAdapter._send — bypassed
10
+ * it (gaps-product-quality P0-3). This module is the single policy layer those
11
+ * paths now share, so banned-phrase screening, AI-disclosure rules, information
12
+ * barriers, and the recipient allowlist are enforced in ONE place instead of
13
+ * being re-implemented (or skipped) per script.
14
+ *
15
+ * Relationship to validate_outbound.py (Layer 0)
16
+ * ----------------------------------------------
17
+ * The Python validator (relationship/title/possessive-claim checks) stays as the
18
+ * Layer-0 call inside each send script. send-gate is the unified POLICY layer
19
+ * ABOVE it: the two run in series and a block from either stops the send.
20
+ *
21
+ * Fail posture (mirrors scripts/validate_outbound semantics)
22
+ * ----------------------------------------------------------
23
+ * The house idiom is "fail-open + silence", but for outbound governance that is
24
+ * the wrong default for EXTERNAL recipients: an unreadable policy file must not
25
+ * silently open the perimeter to outsiders. So:
26
+ * - external recipient + a required policy file is unreadable/malformed →
27
+ * fail CLOSED (allow:false, reason explains which file).
28
+ * - internal recipient + the same fault → fail OPEN (allow:true) so an
29
+ * internal ops message is never blocked by a transient FS fault.
30
+ * A recipient is classified external unless it provably matches an internal
31
+ * signal (Slack/Telegram workspace ids, or an email whose domain is on the
32
+ * configured internal_domains list). Default-deny: unknown ⇒ external.
33
+ *
34
+ * Determinism / testability
35
+ * --------------------------
36
+ * screenOutbound is pure-ish: it reads only the policy files and never sends.
37
+ * The filesystem reader is injectable (`opts.fs = { readFileSync, existsSync }`)
38
+ * so tests drive every branch — including "unreadable policy" — without touching
39
+ * disk. js-yaml is loaded lazily (the package's only YAML dep), matching
40
+ * lib/learning/config.mjs and lib/collective/config.mjs.
41
+ *
42
+ * CLI form (consumed by the shell send scripts)
43
+ * ---------------------------------------------
44
+ * echo "message text" | node lib/comms/send-gate.mjs \
45
+ * --channel slack --recipient C0123 [--agent-root /path]
46
+ * exit 0 → allow; exit 1 → BLOCK (reason on stderr); prints redactedText (if
47
+ * any) on stdout. Any unexpected internal error for an EXTERNAL recipient also
48
+ * exits 1 (fail closed); for an internal recipient it exits 0 (fail open).
49
+ *
50
+ * @module lib/comms/send-gate
51
+ */
52
+
53
+ "use strict";
54
+
55
+ import {
56
+ readFileSync as nodeReadFileSync,
57
+ existsSync as nodeExistsSync,
58
+ } from "node:fs";
59
+ import { join } from "node:path";
60
+
61
+ /* ─────────────────────────── policy file locations ───────────────────────── */
62
+
63
+ const POLICY = Object.freeze({
64
+ style: "policies/communication-style.md",
65
+ disclosure: "policies/ai-disclosure.yaml",
66
+ barriers: "policies/information-barriers.yaml",
67
+ });
68
+
69
+ /* ───────────────────────────── banned phrases ────────────────────────────── */
70
+ // Source of truth: policies/communication-style.md §"Default tone" rule 2.
71
+ // Kept in sync with scripts/hooks/pre-send-audit.sh BANNED_SUBSTRINGS so both
72
+ // gates reject the same set. Matching is case-insensitive, substring-based.
73
+ export const BANNED_SUBSTRINGS = Object.freeze([
74
+ "I'm here to help",
75
+ "I am here to help",
76
+ "Let me know if you need anything else",
77
+ "I hope this helps",
78
+ "Feel free to ask if you have any questions",
79
+ "As an AI",
80
+ "As a language model",
81
+ "I should clarify that I am",
82
+ "Here's a comprehensive overview of",
83
+ "Here is a comprehensive overview of",
84
+ "I'd be happy to",
85
+ "I would be happy to",
86
+ ]);
87
+
88
+ // Opener-only fillers: legitimate mid-sentence, banned as a line/message opener.
89
+ export const BANNED_OPENERS = Object.freeze([
90
+ "Certainly!",
91
+ "Absolutely!",
92
+ "Great question!",
93
+ ]);
94
+
95
+ /* ────────────────────────── recipient classification ─────────────────────── */
96
+
97
+ /**
98
+ * Decide whether a recipient is INTERNAL (workspace member / internal domain) or
99
+ * EXTERNAL (everyone else). Default-deny: anything unrecognised is external, so
100
+ * the fail-CLOSED branch covers the unknown case.
101
+ *
102
+ * @param {string} channel slack|telegram|whatsapp|sms|email|voice|document
103
+ * @param {string} recipient
104
+ * @param {string[]} internalDomains lower-cased email domains treated internal
105
+ * @returns {"internal"|"external"}
106
+ */
107
+ export function classifyRecipient(channel, recipient, internalDomains = []) {
108
+ const r = String(recipient || "").trim();
109
+ if (!r) return "external"; // no recipient ⇒ treat conservatively
110
+
111
+ switch (channel) {
112
+ case "slack":
113
+ case "telegram":
114
+ // Slack channel/DM ids (C…, D…, G…, U…) and Telegram chat ids live inside
115
+ // the agent's own workspace → internal by construction. A bare email/phone
116
+ // passed on these channels is ambiguous, so fall through to the domain test.
117
+ if (/^[CDGU][A-Z0-9]{6,}$/.test(r)) return "internal"; // Slack id
118
+ if (/^-?\d{5,}$/.test(r)) return "internal"; // Telegram numeric chat id
119
+ break;
120
+ default:
121
+ break;
122
+ }
123
+
124
+ // Email: internal only if its domain is explicitly allow-listed as internal.
125
+ const at = r.lastIndexOf("@");
126
+ if (at > 0 && at < r.length - 1) {
127
+ const domain = r.slice(at + 1).toLowerCase();
128
+ for (const d of internalDomains) {
129
+ const dn = String(d || "").toLowerCase().replace(/^@/, "");
130
+ if (dn && (domain === dn || domain.endsWith("." + dn))) return "internal";
131
+ }
132
+ return "external";
133
+ }
134
+
135
+ // Phone numbers (whatsapp/sms) and everything else: external by default.
136
+ return "external";
137
+ }
138
+
139
+ /* ─────────────────────────────── helpers ─────────────────────────────────── */
140
+
141
+ function makeFs(injected) {
142
+ return {
143
+ readFileSync: injected?.readFileSync || nodeReadFileSync,
144
+ existsSync: injected?.existsSync || nodeExistsSync,
145
+ };
146
+ }
147
+
148
+ let _yaml = null;
149
+ async function getYaml() {
150
+ if (_yaml) return _yaml;
151
+ const mod = await import("js-yaml");
152
+ _yaml = mod.default || mod;
153
+ return _yaml;
154
+ }
155
+
156
+ /**
157
+ * Read a policy file. Returns { ok, text } — ok:false means unreadable/missing,
158
+ * which the caller maps to fail-open (internal) or fail-closed (external).
159
+ */
160
+ function readPolicy(fs, agentRoot, rel) {
161
+ const path = join(agentRoot, rel);
162
+ try {
163
+ if (!fs.existsSync(path)) return { ok: false, text: "", path };
164
+ const text = fs.readFileSync(path, "utf-8");
165
+ return { ok: true, text, path };
166
+ } catch {
167
+ return { ok: false, text: "", path };
168
+ }
169
+ }
170
+
171
+ function blocked(reason, extra = {}) {
172
+ return { allow: false, reason, ...extra };
173
+ }
174
+ function allowed(extra = {}) {
175
+ return { allow: true, reason: "", ...extra };
176
+ }
177
+
178
+ /* ───────────────────────── individual screen layers ──────────────────────── */
179
+
180
+ /**
181
+ * (a) Banned-phrase / AI-tell screen. Pure; no IO. Returns a block reason string
182
+ * or null. Sourced from policies/communication-style.md (constant above).
183
+ */
184
+ export function screenBannedPhrases(text) {
185
+ const lc = String(text || "").toLowerCase();
186
+ for (const phrase of BANNED_SUBSTRINGS) {
187
+ if (lc.includes(phrase.toLowerCase())) {
188
+ return `banned AI-tell phrase "${phrase}" (policies/communication-style.md)`;
189
+ }
190
+ }
191
+ // Opener-only: at message start or at the start of any line.
192
+ for (const opener of BANNED_OPENERS) {
193
+ const re = new RegExp(
194
+ "(^|[\\r\\n])\\s*" + opener.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"),
195
+ "i",
196
+ );
197
+ if (re.test(String(text || ""))) {
198
+ return `banned filler opener "${opener}" (policies/communication-style.md)`;
199
+ }
200
+ }
201
+ return null;
202
+ }
203
+
204
+ /**
205
+ * (b) AI-disclosure screen. Resolves the active posture for the recipient's
206
+ * jurisdiction from ai-disclosure.yaml and enforces two things:
207
+ * 1. The non-overridable truthfulness invariant: an outbound message must
208
+ * never DENY being an AI (e.g. "I am not a bot", "I'm a real person").
209
+ * This holds under every posture — concealment must never become a lie.
210
+ * 2. Where the active posture requires proactive disclosure / a machine-
211
+ * readable marking, a first-contact message that conceals it is blocked.
212
+ *
213
+ * @returns {string|null} block reason, or null if disclosure rules are satisfied
214
+ */
215
+ export function screenDisclosure(text, { policy, jurisdiction, firstContact }) {
216
+ const lc = String(text || "").toLowerCase();
217
+
218
+ // 1. Truthfulness invariant — locked, applies under every posture. A message
219
+ // that explicitly denies AI nature is never allowed to leave.
220
+ const denials = [
221
+ "i am not an ai",
222
+ "i'm not an ai",
223
+ "i am not a bot",
224
+ "i'm not a bot",
225
+ "i am a real person",
226
+ "i'm a real person",
227
+ "i am a human",
228
+ "i'm a human",
229
+ "no, i'm not a bot",
230
+ "no i am not a bot",
231
+ "i am not a robot",
232
+ "i'm not a robot",
233
+ ];
234
+ for (const d of denials) {
235
+ if (lc.includes(d)) {
236
+ return `AI-disclosure invariant: message denies AI nature ("${d}") — concealment may never become a lie (policies/ai-disclosure.yaml)`;
237
+ }
238
+ }
239
+
240
+ if (!policy) return null; // policy already handled upstream (fail posture)
241
+
242
+ // 2. Resolve posture for this jurisdiction (fail-safe = default_posture).
243
+ const postures = policy.posture || {};
244
+ const jurisdictions = policy.jurisdictions || {};
245
+ const jKey = jurisdiction && jurisdictions[jurisdiction] ? jurisdiction : "default";
246
+ let postureName =
247
+ (jurisdictions[jKey] && jurisdictions[jKey].posture) ||
248
+ policy.default_posture ||
249
+ "disclose-always";
250
+
251
+ // concealed-where-legal collapses to disclose-always unless governance signed
252
+ // it off for this jurisdiction (resolution.unsigned_concealed_posture).
253
+ if (postureName === "concealed-where-legal") {
254
+ const signoff = policy.governance && policy.governance.sign_off;
255
+ const signedJur =
256
+ signoff &&
257
+ Array.isArray(signoff.jurisdictions) &&
258
+ signoff.approver &&
259
+ signoff.jurisdictions.includes(jurisdiction);
260
+ if (!signedJur) postureName = "disclose-always";
261
+ }
262
+
263
+ const posture = postures[postureName] || {};
264
+
265
+ // Only first-contact messages carry the proactive-disclosure duty; a reply in
266
+ // an ongoing thread does not have to re-disclose every turn.
267
+ if (posture.proactive_disclosure && firstContact) {
268
+ const discloses =
269
+ /\bai\b/i.test(String(text || "")) ||
270
+ lc.includes("automated") ||
271
+ lc.includes("assistant working") ||
272
+ lc.includes("on behalf of");
273
+ if (!discloses) {
274
+ return `AI-disclosure required: posture "${postureName}" (${jKey}) mandates proactive disclosure on first contact, message conceals it (policies/ai-disclosure.yaml)`;
275
+ }
276
+ }
277
+ return null;
278
+ }
279
+
280
+ /**
281
+ * (c) Information-barrier screen. Reads an OPTIONAL `barriers:` section from
282
+ * information-barriers.yaml. Each barrier wall is:
283
+ * - recipients: [exact recipient strings] (optional)
284
+ * - recipient_pattern: "regex" (optional)
285
+ * - recipient_class: "external"|"internal" (optional)
286
+ * - blocked_topics / deny_keywords: [case-insensitive substrings] (required)
287
+ * - reason: "human-readable wall name" (optional)
288
+ * A message to a matching recipient that contains a blocked topic is denied.
289
+ * When no `barriers:` section is present the screen is a no-op (no walls ⇒
290
+ * nothing to block) — the existing taxonomy-only file stays valid.
291
+ *
292
+ * @returns {string|null} block reason, or null
293
+ */
294
+ export function screenBarriers(text, { policy, recipient, recipientClass }) {
295
+ if (!policy || !Array.isArray(policy.barriers)) return null;
296
+ const lc = String(text || "").toLowerCase();
297
+ const r = String(recipient || "");
298
+
299
+ for (const wall of policy.barriers) {
300
+ if (!wall || typeof wall !== "object") continue;
301
+ const topics = wall.blocked_topics || wall.deny_keywords || [];
302
+ if (!Array.isArray(topics) || topics.length === 0) continue;
303
+
304
+ let appliesToRecipient = false;
305
+ if (Array.isArray(wall.recipients) && wall.recipients.includes(r)) {
306
+ appliesToRecipient = true;
307
+ }
308
+ if (!appliesToRecipient && wall.recipient_pattern) {
309
+ try {
310
+ if (new RegExp(wall.recipient_pattern).test(r)) appliesToRecipient = true;
311
+ } catch {
312
+ // A malformed pattern is a policy authoring error; treat the wall as
313
+ // applying (fail toward blocking) so a broken wall can't silently open.
314
+ appliesToRecipient = true;
315
+ }
316
+ }
317
+ if (!appliesToRecipient && wall.recipient_class) {
318
+ if (wall.recipient_class === recipientClass) appliesToRecipient = true;
319
+ }
320
+ // A wall with NO recipient predicate at all applies to everyone.
321
+ if (
322
+ !appliesToRecipient &&
323
+ !Array.isArray(wall.recipients) &&
324
+ !wall.recipient_pattern &&
325
+ !wall.recipient_class
326
+ ) {
327
+ appliesToRecipient = true;
328
+ }
329
+ if (!appliesToRecipient) continue;
330
+
331
+ for (const topic of topics) {
332
+ const t = String(topic || "").toLowerCase().trim();
333
+ if (t && lc.includes(t)) {
334
+ const name = wall.reason || wall.name || "information barrier";
335
+ return `information barrier "${name}": topic "${topic}" walled from recipient (policies/information-barriers.yaml)`;
336
+ }
337
+ }
338
+ }
339
+ return null;
340
+ }
341
+
342
+ /**
343
+ * (d) Recipient allowlist. Only enforced when `allowlist` is a non-empty array.
344
+ * An empty / undefined allowlist means "not configured" → no restriction (the
345
+ * pre-send-audit hook has the same opt-in semantics).
346
+ *
347
+ * @returns {string|null} block reason, or null
348
+ */
349
+ export function screenAllowlist(recipient, allowlist) {
350
+ if (!Array.isArray(allowlist) || allowlist.length === 0) return null;
351
+ const r = String(recipient || "").trim();
352
+ if (!r) return `recipient missing but a send allowlist is configured`;
353
+ for (const entry of allowlist) {
354
+ if (String(entry || "").trim() === r) return null;
355
+ }
356
+ return `recipient "${r}" is not on the configured send allowlist`;
357
+ }
358
+
359
+ /* ──────────────────────────── main entry point ───────────────────────────── */
360
+
361
+ /**
362
+ * Screen an outbound message at the one shared chokepoint.
363
+ *
364
+ * @param {object} args
365
+ * @param {string} args.channel slack|telegram|whatsapp|sms|email|voice|document
366
+ * @param {string} args.recipient channel id / email / phone / handle
367
+ * @param {string} args.text the outbound message body
368
+ * @param {string} [args.agentRoot=process.cwd()] repo root holding policies/
369
+ * @param {string} [args.jurisdiction] recipient jurisdiction key (EU, US-CA, …)
370
+ * @param {boolean} [args.firstContact=true] is this the first message in the convo
371
+ * @param {string[]} [args.internalDomains] email domains treated as internal
372
+ * @param {string[]} [args.allowlist] recipient allowlist (opt-in)
373
+ * @param {object} [args.fs] injected { readFileSync, existsSync } for tests
374
+ * @returns {Promise<{allow:boolean, reason:string, redactedText?:string}>}
375
+ */
376
+ export async function screenOutbound({
377
+ channel,
378
+ recipient,
379
+ text,
380
+ agentRoot = process.cwd(),
381
+ jurisdiction = undefined,
382
+ firstContact = true,
383
+ internalDomains = [],
384
+ allowlist = undefined,
385
+ fs: injectedFs = undefined,
386
+ } = {}) {
387
+ const fs = makeFs(injectedFs);
388
+ const recipientClass = classifyRecipient(channel, recipient, internalDomains);
389
+ const isExternal = recipientClass === "external";
390
+
391
+ // failPolicy: for a missing/unreadable REQUIRED policy file. External → block,
392
+ // internal → open (so transient FS faults never wall off internal ops chatter).
393
+ const failPolicy = (file) =>
394
+ isExternal
395
+ ? blocked(
396
+ `outbound policy "${file}" unreadable — failing closed for external recipient (mirrors validate_outbound)`,
397
+ )
398
+ : allowed();
399
+
400
+ try {
401
+ // (a) Banned phrases — pure, sourced from communication-style.md. If that
402
+ // file is unreadable we still apply the embedded constant list (the SoT
403
+ // is mirrored here), so a missing style file does NOT open the AI-tell
404
+ // gate; it only loses the ability to refresh the list.
405
+ const bannedReason = screenBannedPhrases(text);
406
+ if (bannedReason) return blocked(bannedReason);
407
+
408
+ // (b) AI-disclosure. The truthfulness invariant is enforced from the
409
+ // embedded denial list even without the file; posture resolution needs
410
+ // the file. Missing file → invariant still runs; posture defaults apply.
411
+ let disclosurePolicy = null;
412
+ const disc = readPolicy(fs, agentRoot, POLICY.disclosure);
413
+ if (disc.ok) {
414
+ try {
415
+ const yaml = await getYaml();
416
+ disclosurePolicy = yaml.load(disc.text) || null;
417
+ } catch {
418
+ // Malformed disclosure YAML: required policy → fail per posture.
419
+ const fp = failPolicy(POLICY.disclosure);
420
+ if (!fp.allow) return fp;
421
+ }
422
+ } else {
423
+ const fp = failPolicy(POLICY.disclosure);
424
+ if (!fp.allow) return fp;
425
+ }
426
+ const disclosureReason = screenDisclosure(text, {
427
+ policy: disclosurePolicy,
428
+ jurisdiction,
429
+ firstContact,
430
+ });
431
+ if (disclosureReason) return blocked(disclosureReason);
432
+
433
+ // (c) Information barriers (recipient/topic walls). Optional section.
434
+ let barriersPolicy = null;
435
+ const bar = readPolicy(fs, agentRoot, POLICY.barriers);
436
+ if (bar.ok) {
437
+ try {
438
+ const yaml = await getYaml();
439
+ barriersPolicy = yaml.load(bar.text) || null;
440
+ } catch {
441
+ const fp = failPolicy(POLICY.barriers);
442
+ if (!fp.allow) return fp;
443
+ }
444
+ } else {
445
+ const fp = failPolicy(POLICY.barriers);
446
+ if (!fp.allow) return fp;
447
+ }
448
+ const barrierReason = screenBarriers(text, {
449
+ policy: barriersPolicy,
450
+ recipient,
451
+ recipientClass,
452
+ });
453
+ if (barrierReason) return blocked(barrierReason);
454
+
455
+ // (d) Recipient allowlist (opt-in).
456
+ const allowReason = screenAllowlist(recipient, allowlist);
457
+ if (allowReason) return blocked(allowReason);
458
+
459
+ return allowed();
460
+ } catch (err) {
461
+ // Unexpected fault: fail closed for external, open for internal.
462
+ if (isExternal) {
463
+ return blocked(
464
+ `send-gate internal error — failing closed for external recipient: ${err && err.message ? err.message : String(err)}`,
465
+ );
466
+ }
467
+ return allowed();
468
+ }
469
+ }
470
+
471
+ /* ─────────────────────────────────── CLI ─────────────────────────────────── */
472
+
473
+ /**
474
+ * Parse `--flag value` / `--flag=value` argv into a plain object. Repeated flags
475
+ * (e.g. --internal-domain) accumulate into an array under the camelCase key.
476
+ */
477
+ function parseArgs(argv) {
478
+ const out = {};
479
+ for (let i = 0; i < argv.length; i++) {
480
+ const a = argv[i];
481
+ if (!a.startsWith("--")) continue;
482
+ let key = a.slice(2);
483
+ let val;
484
+ const eq = key.indexOf("=");
485
+ if (eq >= 0) {
486
+ val = key.slice(eq + 1);
487
+ key = key.slice(0, eq);
488
+ } else if (i + 1 < argv.length && !argv[i + 1].startsWith("--")) {
489
+ val = argv[++i];
490
+ } else {
491
+ val = "true";
492
+ }
493
+ const camel = key.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
494
+ if (camel in out) {
495
+ out[camel] = Array.isArray(out[camel]) ? out[camel].concat(val) : [out[camel], val];
496
+ } else {
497
+ out[camel] = val;
498
+ }
499
+ }
500
+ return out;
501
+ }
502
+
503
+ function readStdin() {
504
+ try {
505
+ return nodeReadFileSync(0, "utf-8");
506
+ } catch {
507
+ return "";
508
+ }
509
+ }
510
+
511
+ async function main(argv) {
512
+ const args = parseArgs(argv);
513
+ const channel = args.channel || "";
514
+ const recipient = args.recipient || "";
515
+ if (!channel) {
516
+ process.stderr.write("BLOCKED: --channel is required\n");
517
+ process.exit(1);
518
+ }
519
+ // text precedence: --text flag, else stdin.
520
+ const text = args.text != null ? args.text : readStdin();
521
+ const internalDomains = args.internalDomain
522
+ ? [].concat(args.internalDomain)
523
+ : (process.env.MAESTRO_INTERNAL_DOMAINS || "")
524
+ .split(",")
525
+ .map((s) => s.trim())
526
+ .filter(Boolean);
527
+ const allowlist = args.allow ? [].concat(args.allow) : undefined;
528
+ const firstContact = args.firstContact === "false" ? false : true;
529
+
530
+ const result = await screenOutbound({
531
+ channel,
532
+ recipient,
533
+ text,
534
+ agentRoot: args.agentRoot || process.env.AGENT_DIR || process.cwd(),
535
+ jurisdiction: args.jurisdiction,
536
+ firstContact,
537
+ internalDomains,
538
+ allowlist,
539
+ });
540
+
541
+ if (result.allow) {
542
+ if (result.redactedText) process.stdout.write(result.redactedText);
543
+ process.exit(0);
544
+ }
545
+ process.stderr.write(`BLOCKED: ${result.reason}\n`);
546
+ process.exit(1);
547
+ }
548
+
549
+ // Run as CLI only when invoked directly (not when imported).
550
+ const invokedDirectly =
551
+ process.argv[1] && process.argv[1].endsWith("send-gate.mjs");
552
+ if (invokedDirectly) {
553
+ main(process.argv.slice(2));
554
+ }