@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,89 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Slack Reaction Utility — Add emoji reactions to Slack messages.
4
+ *
5
+ * Used by the agent's Claude Code sessions to react to messages.
6
+ * This is a thin wrapper around the Slack reactions.add API.
7
+ *
8
+ * Usage:
9
+ * node scripts/slack-react.mjs <channel_id> <message_ts> <emoji_name>
10
+ *
11
+ * Examples:
12
+ * node scripts/slack-react.mjs C0ARASMQE1W 1775472843.039059 eyes
13
+ * node scripts/slack-react.mjs D099N1JGKRQ 1775472955.681459 white_check_mark
14
+ *
15
+ * Common emoji names:
16
+ * eyes — acknowledged / looking into it
17
+ * white_check_mark — done / approved
18
+ * thumbsup — agreed / positive
19
+ * thumbsdown — disagreed / negative
20
+ * hourglass_flowing_sand — working on it
21
+ * heavy_check_mark — completed
22
+ * warning — flagged for attention
23
+ * rocket — shipped / deployed
24
+ * brain — thinking / reviewing
25
+ *
26
+ * Exit codes:
27
+ * 0 — success
28
+ * 1 — error (missing args, API failure)
29
+ */
30
+
31
+ import { readFileSync, existsSync } from "fs";
32
+ import { join, dirname } from "path";
33
+ import { fileURLToPath } from "url";
34
+
35
+ const __dirname = dirname(fileURLToPath(import.meta.url));
36
+ const ROOT = join(__dirname, "..");
37
+
38
+ // Load .env
39
+ const envPath = join(ROOT, ".env");
40
+ if (existsSync(envPath)) {
41
+ const envContent = readFileSync(envPath, "utf-8");
42
+ for (const line of envContent.split("\n")) {
43
+ const match = line.match(/^([A-Z_][A-Z0-9_]*)=(.*)$/);
44
+ if (match) {
45
+ let val = match[2].trim();
46
+ if ((val.startsWith('"') && val.endsWith('"')) || (val.startsWith("'") && val.endsWith("'"))) {
47
+ val = val.slice(1, -1);
48
+ }
49
+ process.env[match[1]] = val;
50
+ }
51
+ }
52
+ }
53
+
54
+ const [channel, timestamp, emoji] = process.argv.slice(2);
55
+
56
+ if (!channel || !timestamp || !emoji) {
57
+ console.error("Usage: node scripts/slack-react.mjs <channel_id> <message_ts> <emoji_name>");
58
+ process.exit(1);
59
+ }
60
+
61
+ const token = process.env.SLACK_BOT_TOKEN || process.env.SLACK_USER_TOKEN || process.env.SLACK_TOKEN;
62
+ if (!token) {
63
+ console.error("Error: No Slack token found (SLACK_BOT_TOKEN, SLACK_USER_TOKEN, or SLACK_TOKEN)");
64
+ process.exit(1);
65
+ }
66
+
67
+ try {
68
+ const res = await fetch("https://slack.com/api/reactions.add", {
69
+ method: "POST",
70
+ headers: {
71
+ Authorization: `Bearer ${token}`,
72
+ "Content-Type": "application/json",
73
+ },
74
+ body: JSON.stringify({ channel, timestamp, name: emoji }),
75
+ });
76
+ const body = await res.json();
77
+
78
+ if (body.ok) {
79
+ console.log(`Reacted with :${emoji}: on ${channel}/${timestamp}`);
80
+ } else if (body.error === "already_reacted") {
81
+ console.log(`Already reacted with :${emoji}: (no-op)`);
82
+ } else {
83
+ console.error(`Slack API error: ${body.error}`);
84
+ process.exit(1);
85
+ }
86
+ } catch (err) {
87
+ console.error(`Error: ${err.message}`);
88
+ process.exit(1);
89
+ }
@@ -0,0 +1,232 @@
1
+ #!/bin/bash
2
+ # slack-responded.sh — Atomic response deduplication for Slack messages
3
+ # Prevents duplicate responses from concurrent sessions using mkdir-based locking
4
+ #
5
+ # The key insight: `mkdir` is atomic on POSIX systems. Two concurrent processes
6
+ # calling `mkdir` on the same path will have exactly one succeed and one fail.
7
+ # This eliminates the race condition in the old check-then-write approach.
8
+ #
9
+ # Usage:
10
+ # ./scripts/slack-responded.sh acquire <channel_id> <message_ts> [session_id]
11
+ # → exits 0 if lock acquired (you should send), 1 if already locked (skip)
12
+ # → On success, creates lock dir + metadata file atomically
13
+ #
14
+ # ./scripts/slack-responded.sh confirm <channel_id> <message_ts> [message_preview]
15
+ # → Called after successful send to record details in the lock
16
+ #
17
+ # ./scripts/slack-responded.sh check <message_ts> [channel_id]
18
+ # → exits 0 if already handled, 1 if not (backward-compatible)
19
+ #
20
+ # ./scripts/slack-responded.sh mark <message_ts>
21
+ # → marks message as handled (backward-compatible, legacy)
22
+ #
23
+ # ./scripts/slack-responded.sh clean
24
+ # → purge entries older than 24h
25
+ set -e
26
+
27
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
28
+ AGENT_REPO_DIR="${AGENT_DIR:-$(cd "$SCRIPT_DIR/.." && pwd)}"
29
+
30
+ LOCK_BASE_DIR="$AGENT_REPO_DIR/state/locks/slack-response"
31
+ LEGACY_DIR="$AGENT_REPO_DIR/state/slack-responded"
32
+ LOCK_TTL_MINUTES=1440 # 24 hours — thread-level dedup needs long TTL to prevent cross-session duplicates
33
+
34
+ mkdir -p "$LOCK_BASE_DIR"
35
+ mkdir -p "$LEGACY_DIR"
36
+
37
+ ACTION="${1:-}"
38
+ TIMESTAMP=$(date -u +%Y-%m-%dT%H:%M:%SZ)
39
+
40
+ # Helper: check if a lock path (directory or file) is still valid (< LOCK_TTL_MINUTES old)
41
+ lock_is_valid() {
42
+ local lock_path="$1"
43
+ if [ ! -e "$lock_path" ]; then
44
+ return 1
45
+ fi
46
+ # Check age of lock path (works for both directories and files)
47
+ local lock_age
48
+ if [[ "$(uname)" == "Darwin" ]]; then
49
+ lock_age=$(( ( $(date +%s) - $(stat -f %m "$lock_path") ) / 60 ))
50
+ else
51
+ lock_age=$(( ( $(date +%s) - $(stat -c %Y "$lock_path") ) / 60 ))
52
+ fi
53
+ [ "$lock_age" -lt "$LOCK_TTL_MINUTES" ]
54
+ }
55
+
56
+ case "$ACTION" in
57
+ acquire)
58
+ # acquire <channel_id> <message_ts> [session_id]
59
+ # Attempts to atomically acquire a response lock.
60
+ # Returns 0 if lock acquired (caller should proceed to send).
61
+ # Returns 1 if lock already held (caller should skip sending).
62
+ CHANNEL="${2:?Channel ID required}"
63
+ MSG_TS="${3:?Message TS required}"
64
+ SESSION_ID="${4:-unknown}"
65
+
66
+ LOCK_DIR="$LOCK_BASE_DIR/${CHANNEL}-${MSG_TS}"
67
+
68
+ # Check for existing valid lock
69
+ if lock_is_valid "$LOCK_DIR"; then
70
+ echo "LOCKED: Response already claimed for $CHANNEL:$MSG_TS"
71
+ exit 1
72
+ fi
73
+
74
+ # If stale lock exists, remove it
75
+ if [ -d "$LOCK_DIR" ]; then
76
+ rm -rf "$LOCK_DIR"
77
+ fi
78
+
79
+ # Atomic lock acquisition via mkdir
80
+ # mkdir will fail if another process creates it between our check and this call
81
+ if mkdir "$LOCK_DIR" 2>/dev/null; then
82
+ # Lock acquired — write metadata
83
+ cat > "$LOCK_DIR/meta" << EOF
84
+ acquired: "$TIMESTAMP"
85
+ session: "$SESSION_ID"
86
+ channel: "$CHANNEL"
87
+ message_ts: "$MSG_TS"
88
+ status: acquired
89
+ EOF
90
+ # Also write legacy marker for backward compatibility
91
+ echo "$TIMESTAMP" > "$LEGACY_DIR/$MSG_TS"
92
+ echo "ACQUIRED"
93
+ exit 0
94
+ else
95
+ # Another process beat us to it
96
+ echo "LOCKED: Response already claimed for $CHANNEL:$MSG_TS (race lost)"
97
+ exit 1
98
+ fi
99
+ ;;
100
+
101
+ confirm)
102
+ # confirm <channel_id> <message_ts> [message_preview]
103
+ # Called after a successful send to record what was sent.
104
+ CHANNEL="${2:?Channel ID required}"
105
+ MSG_TS="${3:?Message TS required}"
106
+ PREVIEW="${4:-}"
107
+
108
+ LOCK_DIR="$LOCK_BASE_DIR/${CHANNEL}-${MSG_TS}"
109
+
110
+ if [ -d "$LOCK_DIR" ]; then
111
+ # Preserve the original acquisition timestamp
112
+ ORIG_ACQUIRED=$(grep 'acquired:' "$LOCK_DIR/meta" 2>/dev/null | head -1 | sed 's/^acquired: *//' | tr -d '"' || echo "$TIMESTAMP")
113
+ if [ -z "$ORIG_ACQUIRED" ]; then ORIG_ACQUIRED="$TIMESTAMP"; fi
114
+ SAFE_PREVIEW=$(echo "$PREVIEW" | head -c 200 | tr '"' "'")
115
+ cat > "$LOCK_DIR/meta" << EOF
116
+ acquired: "$ORIG_ACQUIRED"
117
+ confirmed: "$TIMESTAMP"
118
+ channel: "$CHANNEL"
119
+ message_ts: "$MSG_TS"
120
+ status: confirmed
121
+ preview: "$SAFE_PREVIEW"
122
+ EOF
123
+ fi
124
+ # Update legacy marker
125
+ echo "$TIMESTAMP" > "$LEGACY_DIR/$MSG_TS"
126
+ echo "CONFIRMED"
127
+ ;;
128
+
129
+ check)
130
+ # check <message_ts> [channel_id]
131
+ # Backward-compatible check. Returns 0 if already handled, 1 if not.
132
+ MSG_TS="${2:?Message TS required}"
133
+ CHANNEL="${3:-}"
134
+
135
+ # If channel is provided, check the atomic lock first
136
+ if [ -n "$CHANNEL" ]; then
137
+ LOCK_DIR="$LOCK_BASE_DIR/${CHANNEL}-${MSG_TS}"
138
+ if lock_is_valid "$LOCK_DIR"; then
139
+ exit 0 # Already handled
140
+ fi
141
+ fi
142
+
143
+ # Also check any lock matching this message_ts (across all channels)
144
+ for lock in "$LOCK_BASE_DIR"/*-"${MSG_TS}"; do
145
+ if [ -d "$lock" ] && lock_is_valid "$lock"; then
146
+ exit 0 # Already handled
147
+ fi
148
+ done
149
+
150
+ # Fall back to legacy flat-file check
151
+ if [ -f "$LEGACY_DIR/$MSG_TS" ]; then
152
+ if lock_is_valid "$LEGACY_DIR/$MSG_TS"; then
153
+ exit 0 # Recently handled
154
+ fi
155
+ # Legacy file exists but is old — still treat as handled for backward compat
156
+ exit 0
157
+ fi
158
+
159
+ exit 1 # Not yet handled
160
+ ;;
161
+
162
+ release)
163
+ # release <channel_id> <message_ts> [session_id]
164
+ # Releases an unconfirmed lock so a legitimate retry isn't blocked by a
165
+ # 24h TTL. Called by slack-send.sh's exit trap when a send fails or the
166
+ # script crashes after acquiring the lock but before confirming the send.
167
+ #
168
+ # Safety:
169
+ # - Refuse to release if the lock has been confirmed (real send happened)
170
+ # - If session_id is provided, only release locks owned by that session
171
+ # (prevents a buggy caller from blowing away a sibling's in-flight lock)
172
+ #
173
+ # Lock metadata is stored in `meta` (acquire/confirm both write to it)
174
+ # using the format produced by the acquire/confirm sections above:
175
+ # session: "<id>"
176
+ # status: acquired (or confirmed)
177
+ CHANNEL="${2:?Channel ID required}"
178
+ MSG_TS="${3:?Message TS required}"
179
+ SESSION_ID="${4:-unknown}"
180
+
181
+ LOCK_DIR="$LOCK_BASE_DIR/${CHANNEL}-${MSG_TS}"
182
+ META="$LOCK_DIR/meta"
183
+
184
+ if [ ! -d "$LOCK_DIR" ]; then
185
+ echo "NOT_HELD"
186
+ exit 0
187
+ fi
188
+
189
+ if [ -f "$META" ] && grep -qE "^status:[[:space:]]*confirmed" "$META" 2>/dev/null; then
190
+ echo "ALREADY_CONFIRMED"
191
+ exit 0
192
+ fi
193
+
194
+ if [ -f "$META" ] && [ "$SESSION_ID" != "unknown" ]; then
195
+ if ! grep -qE "^session:[[:space:]]*\"?${SESSION_ID}\"?[[:space:]]*$" "$META" 2>/dev/null; then
196
+ echo "NOT_OWNED"
197
+ exit 0
198
+ fi
199
+ fi
200
+
201
+ rm -rf "$LOCK_DIR"
202
+ echo "RELEASED"
203
+ ;;
204
+
205
+ mark)
206
+ # mark <message_ts> — backward-compatible mark
207
+ MSG_TS="${2:?Message TS required}"
208
+ echo "$TIMESTAMP" > "$LEGACY_DIR/$MSG_TS"
209
+ ;;
210
+
211
+ clean)
212
+ # Purge entries older than 24 hours
213
+ # Clean lock directories
214
+ find "$LOCK_BASE_DIR" -maxdepth 1 -type d -mmin +1440 -exec rm -rf {} + 2>/dev/null || true
215
+ # Clean legacy flat files
216
+ find "$LEGACY_DIR" -type f -mmin +1440 -delete 2>/dev/null || true
217
+ echo "Cleaned entries older than 24h"
218
+ ;;
219
+
220
+ *)
221
+ echo "Usage: slack-responded.sh {acquire|confirm|release|check|mark|clean} [args...]"
222
+ echo ""
223
+ echo "Commands:"
224
+ echo " acquire <channel> <msg_ts> [session_id] — Atomically claim a response lock"
225
+ echo " confirm <channel> <msg_ts> [preview] — Record successful send"
226
+ echo " release <channel> <msg_ts> [session_id] — Release unconfirmed lock (send failed)"
227
+ echo " check <msg_ts> [channel] — Check if already handled"
228
+ echo " mark <msg_ts> — Legacy: mark as handled"
229
+ echo " clean — Purge old entries"
230
+ exit 2
231
+ ;;
232
+ esac
@@ -0,0 +1,287 @@
1
+ #!/bin/bash
2
+ # slack-send.sh — Send Slack message as the running agent via User OAuth Token (xoxp-)
3
+ # Messages appear as the agent's own Slack user, NOT the bot app.
4
+ # Usage: ./scripts/slack-send.sh <channel_id> <message> [--thread_ts <ts>]
5
+ set -e
6
+
7
+ # Resolve the agent repo dir so we can source env files regardless of cwd.
8
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
9
+ AGENT_REPO_DIR="${AGENT_DIR:-$(cd "$SCRIPT_DIR/.." && pwd)}"
10
+
11
+ # Load identity vars (AGENT_FIRST_NAME, AGENT_FULL_NAME, AGENT_EMAIL, …) so
12
+ # nothing in this script needs to hardcode the agent's name or paths.
13
+ if [ -f "$AGENT_REPO_DIR/config/agent.env" ]; then
14
+ # shellcheck disable=SC1091
15
+ source "$AGENT_REPO_DIR/config/agent.env"
16
+ fi
17
+
18
+ # Load secrets from the agent's .env (must live alongside agent.env).
19
+ if [ -f "$AGENT_REPO_DIR/.env" ]; then
20
+ # shellcheck disable=SC1091
21
+ source "$AGENT_REPO_DIR/.env"
22
+ fi
23
+
24
+ # ENFORCEMENT: Must use User Token (xoxp-), never Bot Token (xoxb-).
25
+ # Per CEO directive: all Slack sends must come from user scope so messages
26
+ # appear as the running agent, not a generic bot app.
27
+ SLACK_SEND_TOKEN="${SLACK_USER_TOKEN}"
28
+ if [ -z "$SLACK_SEND_TOKEN" ]; then
29
+ echo "ERROR: SLACK_USER_TOKEN not set in .env — cannot send as user scope" >&2
30
+ exit 1
31
+ fi
32
+ if [[ "$SLACK_SEND_TOKEN" == xoxb-* ]]; then
33
+ echo "ERROR: SLACK_SEND_TOKEN is a bot token (xoxb-). Must use user token (xoxp-)." >&2
34
+ exit 1
35
+ fi
36
+
37
+ CHANNEL="$1"
38
+ MESSAGE="$2"
39
+ THREAD_TS=""
40
+ RESPONDING_TO=""
41
+ SKIP_TYPING=""
42
+
43
+ # Parse optional flags
44
+ shift 2 2>/dev/null || true
45
+ while [[ $# -gt 0 ]]; do
46
+ case "$1" in
47
+ --thread_ts)
48
+ THREAD_TS="$2"
49
+ shift 2
50
+ ;;
51
+ --responding_to)
52
+ RESPONDING_TO="$2"
53
+ shift 2
54
+ ;;
55
+ --no-typing)
56
+ SKIP_TYPING="1"
57
+ shift
58
+ ;;
59
+ *)
60
+ shift
61
+ ;;
62
+ esac
63
+ done
64
+
65
+ # Allow env override to disable typing globally
66
+ if [ "${SLACK_TYPING_ENABLED:-1}" = "0" ]; then
67
+ SKIP_TYPING="1"
68
+ fi
69
+
70
+ if [ -z "$CHANNEL" ] || [ -z "$MESSAGE" ]; then
71
+ echo "Usage: slack-send.sh <channel_id> <message> [--thread_ts <ts>] [--responding_to <ts>]"
72
+ exit 1
73
+ fi
74
+
75
+ # Auto-detect --responding_to from the inbox if the caller didn't pass it.
76
+ # Protects against operator-vs-daemon duplicate replies: when a human-prompted
77
+ # session replies via this script, it should claim the dedup lock so the
78
+ # daemon's session (which always passes --responding_to) finds the lock
79
+ # already taken and skips its own send. Without auto-detect, two reply
80
+ # sources race and the user gets duplicate substantive responses (observed
81
+ # on 2026-05-12 when operator + daemon both replied to a CEO DM 3 min apart).
82
+ #
83
+ # Strategy: scan state/inbox/slack/ for the most recent unprocessed YAML
84
+ # whose body references this channel; extract its raw_ref ts as RESPONDING_TO.
85
+ # Operator can override by passing --responding_to explicitly, or skip auto-
86
+ # detect with --responding_to "" (proactive send not tied to an inbox item).
87
+ if [ -z "$RESPONDING_TO" ] && [[ "$CHANNEL" =~ ^[CD][A-Z0-9]+$ ]]; then
88
+ INBOX_DIR="$AGENT_REPO_DIR/state/inbox/slack"
89
+ if [ -d "$INBOX_DIR" ]; then
90
+ LATEST_INBOX=$(
91
+ ls -t "$INBOX_DIR"/*.yaml 2>/dev/null |
92
+ xargs -I{} grep -l "channel_id: \"$CHANNEL\"" {} 2>/dev/null |
93
+ head -1
94
+ )
95
+ if [ -n "$LATEST_INBOX" ]; then
96
+ AUTO_TS=$(grep "^raw_ref:" "$LATEST_INBOX" 2>/dev/null | sed -E 's/.*:([0-9]+\.[0-9]+)".*/\1/')
97
+ if [ -n "$AUTO_TS" ]; then
98
+ RESPONDING_TO="$AUTO_TS"
99
+ echo "[slack-send] auto-detected --responding_to=$RESPONDING_TO from $(basename "$LATEST_INBOX")" >&2
100
+ fi
101
+ fi
102
+ fi
103
+ fi
104
+
105
+ # Dedup check: if --responding_to is set, use atomic locking to prevent concurrent sends
106
+ DEDUP_ACQUIRED=""
107
+ if [ -n "$RESPONDING_TO" ]; then
108
+ # Atomic lock acquisition (mkdir-based, race-safe).
109
+ # This is the primary dedup mechanism. mkdir is atomic on POSIX — if two sessions
110
+ # race to respond to the same message, exactly one will succeed and the other will
111
+ # get LOCKED and skip. This eliminates the check-then-write race condition.
112
+ # AGENT_SESSION_ID is the per-agent session identifier (used to be SOPHIE_SESSION_ID,
113
+ # RAVI_SESSION_ID, etc.); $$ is the fallback when running outside a daemon-spawned shell.
114
+ SESSION_ID="${AGENT_SESSION_ID:-${SOPHIE_SESSION_ID:-${RAVI_SESSION_ID:-$$}}}"
115
+ ACQUIRE_RESULT=$("$SCRIPT_DIR/slack-responded.sh" acquire "$CHANNEL" "$RESPONDING_TO" "$SESSION_ID" 2>/dev/null) || true
116
+ if [ "$ACQUIRE_RESULT" != "ACQUIRED" ]; then
117
+ echo "DEDUP_SKIP"
118
+ exit 0
119
+ fi
120
+ DEDUP_ACQUIRED="1"
121
+
122
+ # Lock-release safety net. If this script exits BEFORE confirming the
123
+ # lock (e.g. validation failed, Slack API errored, python script crashed
124
+ # during message conversion, caller killed us), we must release the lock
125
+ # so the next legitimate retry isn't blocked for 24 hours. The trap is
126
+ # cleared after the post-send confirm; if the confirm runs, we leave
127
+ # the lock in place (it's now a real send marker, not a placeholder).
128
+ cleanup_unconfirmed_lock() {
129
+ local exit_code=$?
130
+ if [ -n "$DEDUP_ACQUIRED" ] && [ -z "$DEDUP_CONFIRMED" ]; then
131
+ "$SCRIPT_DIR/slack-responded.sh" release "$CHANNEL" "$RESPONDING_TO" "$SESSION_ID" 2>/dev/null || true
132
+ fi
133
+ return "$exit_code"
134
+ }
135
+ trap cleanup_unconfirmed_lock EXIT
136
+ fi
137
+
138
+ # Show typing indicator before sending (non-blocking, fire-and-forget)
139
+ # This makes the agent's presence feel human — the "X is typing..." dots appear
140
+ # before the message arrives. Requires rtm:stream scope on user token; degrades gracefully.
141
+ TYPING_DURATION=2500
142
+ MSG_LEN=${#MESSAGE}
143
+ if [ "$MSG_LEN" -gt 500 ]; then
144
+ TYPING_DURATION=4000
145
+ elif [ "$MSG_LEN" -gt 200 ]; then
146
+ TYPING_DURATION=3000
147
+ elif [ "$MSG_LEN" -lt 50 ]; then
148
+ TYPING_DURATION=1500
149
+ fi
150
+
151
+ # Run typing indicator synchronously. The script connects via WebSocket, sends typing
152
+ # events for TYPING_DURATION ms, then exits. If rtm:stream scope is missing, it exits
153
+ # immediately with no delay — the message sends without the typing indicator.
154
+ if [ -z "$SKIP_TYPING" ] && [ -f "$SCRIPT_DIR/slack-typing.mjs" ]; then
155
+ /opt/homebrew/bin/node "$SCRIPT_DIR/slack-typing.mjs" "$CHANNEL" "$TYPING_DURATION" 2>/dev/null || true
156
+ fi
157
+
158
+ # ── Pre-draft context enrichment (Phase 7) ─────────────────────
159
+ # Look up recipient in entity index before sending. Advisory only — never blocks.
160
+ # Logs context to logs/audit/{date}-pre-draft-lookups.jsonl for audit trail.
161
+ if [ -f "$SCRIPT_DIR/pre_draft_lookup.py" ]; then
162
+ python3 "$SCRIPT_DIR/pre_draft_lookup.py" --recipient "$CHANNEL" --type slack --channel "$CHANNEL" 2>/dev/null >/dev/null || true
163
+ fi
164
+
165
+ # ── Pre-send factual validation ─────────────────────────────────
166
+ # Checks relationship claims, AI disclosure, in-person scheduling, title accuracy
167
+ # Blocks send if critical issues found. Fail-open on errors.
168
+ if [ ! -f "$SCRIPT_DIR/validate_outbound.py" ]; then
169
+ echo "BLOCKED: outbound validator (validate_outbound.py) missing — refusing Slack send (fail closed)." >&2
170
+ exit 1
171
+ fi
172
+ if true; then
173
+ VALIDATION_RESULT=$(echo "$MESSAGE" | python3 "$SCRIPT_DIR/validate_outbound.py" --type slack --recipient "$CHANNEL" --quiet 2>/dev/null) || VALIDATION_EXIT=$?
174
+ VALIDATION_EXIT=${VALIDATION_EXIT:-0}
175
+ if [ "$VALIDATION_EXIT" = "1" ]; then
176
+ BLOCKERS=$(echo "$VALIDATION_RESULT" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('metadata',{}).get('blockers',0))" 2>/dev/null) || BLOCKERS=0
177
+ if [ "$BLOCKERS" -gt 0 ]; then
178
+ echo "VALIDATION_BLOCK: $BLOCKERS issue(s) found in Slack message to $CHANNEL"
179
+ echo "$VALIDATION_RESULT" | python3 -c "
180
+ import json,sys
181
+ d=json.load(sys.stdin)
182
+ for i in d.get('issues',[]):
183
+ if i.get('severity')=='block':
184
+ print(f\" [{i['type']}] {i['description']}\")
185
+ if i.get('suggestion'): print(f\" -> {i['suggestion']}\")
186
+ " 2>/dev/null || true
187
+ echo "Fix the above issues before sending." >&2
188
+ exit 1
189
+ fi
190
+ fi
191
+ fi
192
+
193
+ # ── Pre-send policy gate (shared send-gate chokepoint) ──────────────
194
+ # Unified policy layer ABOVE validate_outbound.py (Layer 0): banned phrases,
195
+ # AI-disclosure, information barriers, and the recipient allowlist, all enforced
196
+ # in lib/comms/send-gate.mjs so every send path screens identically. Slack
197
+ # channel/DM ids are internal by construction, so an FS fault fails OPEN here;
198
+ # external recipients fail CLOSED (see the module header).
199
+ SEND_GATE="$AGENT_REPO_DIR/lib/comms/send-gate.mjs"
200
+ if [ ! -f "$SEND_GATE" ]; then
201
+ # Try the maestro package location if running from a consuming agent repo.
202
+ SEND_GATE="$AGENT_REPO_DIR/node_modules/@cohortapp/agent-sdk/lib/comms/send-gate.mjs"
203
+ fi
204
+ if [ -f "$SEND_GATE" ]; then
205
+ NODE_BIN="${MAESTRO_NODE_BIN:-/opt/homebrew/bin/node}"
206
+ command -v "$NODE_BIN" >/dev/null 2>&1 || NODE_BIN="node"
207
+ # Operational sends are replies by default (no proactive-disclosure duty);
208
+ # set MAESTRO_SEND_FIRST_CONTACT=true for a genuine first-contact send. The
209
+ # truthfulness invariant (never deny being an AI) applies regardless.
210
+ GATE_REASON=$(printf '%s' "$MESSAGE" | AGENT_DIR="$AGENT_REPO_DIR" "$NODE_BIN" "$SEND_GATE" \
211
+ --channel slack --recipient "$CHANNEL" --agent-root "$AGENT_REPO_DIR" \
212
+ --first-contact "${MAESTRO_SEND_FIRST_CONTACT:-false}" 2>&1) || GATE_EXIT=$?
213
+ GATE_EXIT=${GATE_EXIT:-0}
214
+ if [ "$GATE_EXIT" != "0" ]; then
215
+ echo "SEND_GATE_BLOCK: Slack message to $CHANNEL blocked by policy gate" >&2
216
+ echo "$GATE_REASON" >&2
217
+ exit 1
218
+ fi
219
+ fi
220
+
221
+ # Convert markdown to Slack mrkdwn format before sending
222
+ # Slack uses its own mrkdwn syntax which differs from standard markdown:
223
+ # Bold: *text* (not **text**)
224
+ # Italic: _text_ (same as markdown)
225
+ # Strikethrough: ~text~ (not ~~text~~)
226
+ # Links: <url|text> (not [text](url))
227
+ # Headings: not supported — converted to *bold* text
228
+ # Images: not supported — converted to <url|alt> links
229
+ # HR (---): converted to Unicode line separator
230
+ MESSAGE=$(python3 -c "
231
+ import sys, re
232
+ msg = sys.argv[1]
233
+ # 1. Convert markdown images ![alt](url) to <url|alt> (strip the !)
234
+ msg = re.sub(r'!\[([^\]]*)\]\(([^)]+)\)', r'<\2|\1>', msg)
235
+ # 2. Convert ***bold italic*** to *_bold italic_* (must come before ** rule)
236
+ msg = re.sub(r'\*\*\*(.+?)\*\*\*', r'*_\1_*', msg)
237
+ # 3. Convert **bold** to *bold* (markdown bold to Slack bold)
238
+ msg = re.sub(r'\*\*(.+?)\*\*', r'*\1*', msg)
239
+ # 4. Convert __bold__ to *bold* (underscore bold variant)
240
+ msg = re.sub(r'__(.+?)__', r'*\1*', msg)
241
+ # 5. Convert ~~strikethrough~~ to ~strikethrough~ (Slack format)
242
+ msg = re.sub(r'~~(.+?)~~', r'~\1~', msg)
243
+ # 6. Convert [text](url) to <url|text> (markdown links to Slack links)
244
+ msg = re.sub(r'(?<!!)\[([^\]]+)\]\(([^)]+)\)', r'<\2|\1>', msg)
245
+ # 7. Convert headings to bold text (process longer prefixes first)
246
+ msg = re.sub(r'^####\s+(.+)$', r'*\1*', msg, flags=re.MULTILINE)
247
+ msg = re.sub(r'^###\s+(.+)$', r'*\1*', msg, flags=re.MULTILINE)
248
+ msg = re.sub(r'^##\s+(.+)$', r'*\1*', msg, flags=re.MULTILINE)
249
+ msg = re.sub(r'^#\s+(.+)$', r'*\1*', msg, flags=re.MULTILINE)
250
+ # 8. Convert horizontal rules (---, ***, ___) to Unicode line
251
+ msg = re.sub(r'^[-*_]{3,}\s*$', '───────────────────────────', msg, flags=re.MULTILINE)
252
+ print(msg)
253
+ " "$MESSAGE")
254
+
255
+ # Escape message for JSON
256
+ ESCAPED=$(python3 -c "import json,sys; print(json.dumps(sys.argv[1]))" "$MESSAGE")
257
+
258
+ # Build JSON payload
259
+ if [ -n "$THREAD_TS" ]; then
260
+ PAYLOAD="{\"channel\":\"$CHANNEL\",\"text\":$ESCAPED,\"thread_ts\":\"$THREAD_TS\"}"
261
+ else
262
+ PAYLOAD="{\"channel\":\"$CHANNEL\",\"text\":$ESCAPED}"
263
+ fi
264
+
265
+ RESULT=$(curl -s -X POST "https://slack.com/api/chat.postMessage" \
266
+ -H "Authorization: Bearer $SLACK_SEND_TOKEN" \
267
+ -H "Content-Type: application/json" \
268
+ -d "$PAYLOAD")
269
+
270
+ STATUS=$(echo "$RESULT" | python3 -c "import json,sys; d=json.load(sys.stdin); print('OK' if d['ok'] else d.get('error','fail'))")
271
+ echo "$STATUS"
272
+
273
+ # If we get a token_revoked or invalid_auth error, surface it clearly
274
+ if [ "$STATUS" = "invalid_auth" ] || [ "$STATUS" = "token_revoked" ] || [ "$STATUS" = "not_authed" ]; then
275
+ echo "ERROR: User token auth failed ($STATUS). Check SLACK_USER_TOKEN in .env" >&2
276
+ exit 1
277
+ fi
278
+
279
+ # Post-send: confirm the response lock with message details (audit trail)
280
+ # and mark the trap as fulfilled so the exit-handler does NOT release the
281
+ # now-legitimate confirmation marker. Any non-OK status leaves DEDUP_CONFIRMED
282
+ # unset, so the EXIT trap releases the lock for legitimate retry.
283
+ if [ "$STATUS" = "OK" ] && [ -n "$RESPONDING_TO" ] && [ -n "$DEDUP_ACQUIRED" ]; then
284
+ PREVIEW=$(echo "$MESSAGE" | head -c 200)
285
+ "$SCRIPT_DIR/slack-responded.sh" confirm "$CHANNEL" "$RESPONDING_TO" "$PREVIEW" 2>/dev/null || true
286
+ DEDUP_CONFIRMED="1"
287
+ fi