@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,194 @@
1
+ /**
2
+ * check.test.mjs — node:test coverage for the maestro CI guard scripts.
3
+ *
4
+ * Exercises the pure helpers (so logic is verified deterministically without
5
+ * depending on repo state) and asserts the aggregator + each guard return 0
6
+ * on the current, known-clean repo.
7
+ *
8
+ * Run: `node --test scripts/ci/check.test.mjs`
9
+ *
10
+ * @module scripts/ci/check.test
11
+ */
12
+
13
+ "use strict";
14
+
15
+ import { test } from "node:test";
16
+ import assert from "node:assert/strict";
17
+ import { mkdtemp, writeFile, mkdir, rm } from "node:fs/promises";
18
+ import { tmpdir } from "node:os";
19
+ import path from "node:path";
20
+
21
+ import {
22
+ lineHasConflictMarker,
23
+ scanContentForMarkers,
24
+ findConflictMarkers,
25
+ run as runConflictMarkers,
26
+ } from "./check-no-conflict-markers.mjs";
27
+ import {
28
+ collectExportTargets,
29
+ flattenExports,
30
+ checkExportsExist,
31
+ run as runExportsExist,
32
+ } from "./check-exports-exist.mjs";
33
+ import {
34
+ isGlob,
35
+ checkFilesExist,
36
+ run as runFilesExist,
37
+ } from "./check-files-exist.mjs";
38
+ import {
39
+ scanText,
40
+ run as runNoConfidential,
41
+ } from "./check-no-confidential.mjs";
42
+ import { runAll, run as runAggregator, CHECKS } from "./check.mjs";
43
+
44
+ // --- conflict markers: pure line matcher ---
45
+
46
+ test("lineHasConflictMarker matches the four marker kinds", () => {
47
+ assert.equal(lineHasConflictMarker("<<<<<<< HEAD"), true);
48
+ assert.equal(lineHasConflictMarker("======="), true);
49
+ assert.equal(lineHasConflictMarker(">>>>>>> feature/x"), true);
50
+ assert.equal(lineHasConflictMarker("||||||| merged common ancestors"), true);
51
+ assert.equal(lineHasConflictMarker("======= \r".replace(" \r", "\r")), true); // CRLF bare marker
52
+ });
53
+
54
+ test("lineHasConflictMarker ignores look-alikes", () => {
55
+ assert.equal(lineHasConflictMarker("======== eight equals is a markdown rule"), false);
56
+ assert.equal(lineHasConflictMarker("===="), false);
57
+ assert.equal(lineHasConflictMarker("some =======text inline"), false);
58
+ assert.equal(lineHasConflictMarker(">>>>>>>>"), false);
59
+ assert.equal(lineHasConflictMarker(""), false);
60
+ assert.equal(lineHasConflictMarker(42), false);
61
+ });
62
+
63
+ test("scanContentForMarkers reports 1-based line numbers", () => {
64
+ const content = ["ok line", "<<<<<<< HEAD", "mine", "=======", "theirs", ">>>>>>> branch"].join("\n");
65
+ const hits = scanContentForMarkers(content);
66
+ assert.deepEqual(hits.map((h) => h.line), [2, 4, 6]);
67
+ assert.equal(hits[0].text, "<<<<<<< HEAD");
68
+ });
69
+
70
+ test("scanContentForMarkers clean content yields nothing", () => {
71
+ assert.deepEqual(scanContentForMarkers("nothing\nto\nsee\n"), []);
72
+ assert.deepEqual(scanContentForMarkers(""), []);
73
+ });
74
+
75
+ // --- exports flattening ---
76
+
77
+ test("collectExportTargets handles string, array, nested conditions, and null", () => {
78
+ assert.deepEqual(collectExportTargets("./a.js", "./a"), [{ subpath: "./a", target: "./a.js" }]);
79
+ assert.deepEqual(collectExportTargets(null, "./blocked"), []);
80
+ const nested = { import: "./x.mjs", require: { default: "./x.cjs" } };
81
+ const got = collectExportTargets(nested, "./x").map((t) => t.target).sort();
82
+ assert.deepEqual(got, ["./x.cjs", "./x.mjs"]);
83
+ const arr = ["./first.js", { import: "./second.mjs" }];
84
+ const gotArr = collectExportTargets(arr, "./y").map((t) => t.target).sort();
85
+ assert.deepEqual(gotArr, ["./first.js", "./second.mjs"]);
86
+ });
87
+
88
+ test("flattenExports handles sugar string form and map form", () => {
89
+ assert.deepEqual(flattenExports("./index.js"), [{ subpath: ".", target: "./index.js" }]);
90
+ assert.deepEqual(flattenExports(undefined), []);
91
+ const pairs = flattenExports({ ".": "./i.js", "./t": "./t.js" });
92
+ assert.deepEqual(pairs.map((p) => p.target).sort(), ["./i.js", "./t.js"]);
93
+ });
94
+
95
+ // --- exports/files existence against a synthetic fixture repo ---
96
+
97
+ async function makeFixtureRepo() {
98
+ const dir = await mkdtemp(path.join(tmpdir(), "maestro-ci-"));
99
+ await mkdir(path.join(dir, "lib"), { recursive: true });
100
+ await writeFile(path.join(dir, "lib", "real.js"), "export const x = 1;\n");
101
+ await writeFile(path.join(dir, "README.md"), "# fixture\n");
102
+ return dir;
103
+ }
104
+
105
+ test("checkExportsExist passes when targets exist, fails when missing", async () => {
106
+ const dir = await makeFixtureRepo();
107
+ try {
108
+ const good = await checkExportsExist({ cwd: dir, pkg: { exports: { ".": "./lib/real.js" } } });
109
+ assert.equal(good.ok, true);
110
+ assert.equal(good.checked, 1);
111
+
112
+ const bad = await checkExportsExist({ cwd: dir, pkg: { exports: { "./tts": "./lib/missing.mjs" } } });
113
+ assert.equal(bad.ok, false);
114
+ assert.equal(bad.missing.length, 1);
115
+ assert.equal(bad.missing[0].subpath, "./tts");
116
+
117
+ // null target is a deliberate block, not a missing file.
118
+ const blocked = await checkExportsExist({ cwd: dir, pkg: { exports: { "./x": null } } });
119
+ assert.equal(blocked.ok, true);
120
+ assert.equal(blocked.checked, 0);
121
+ } finally {
122
+ await rm(dir, { recursive: true, force: true });
123
+ }
124
+ });
125
+
126
+ test("isGlob detects metacharacters", () => {
127
+ assert.equal(isGlob("lib/"), false);
128
+ assert.equal(isGlob("README.md"), false);
129
+ assert.equal(isGlob("lib/**/*.js"), true);
130
+ assert.equal(isGlob("file?.js"), true);
131
+ });
132
+
133
+ test("checkFilesExist passes for present entries, fails for missing, skips globs", async () => {
134
+ const dir = await makeFixtureRepo();
135
+ try {
136
+ const good = await checkFilesExist({ cwd: dir, pkg: { files: ["lib/", "README.md"] } });
137
+ assert.equal(good.ok, true);
138
+ assert.equal(good.checked, 2);
139
+
140
+ const bad = await checkFilesExist({ cwd: dir, pkg: { files: ["lib/", "nope/"] } });
141
+ assert.equal(bad.ok, false);
142
+ assert.equal(bad.missing[0].entry, "nope/");
143
+
144
+ const globby = await checkFilesExist({ cwd: dir, pkg: { files: ["lib/**/*.js"] } });
145
+ assert.equal(globby.ok, true);
146
+ assert.equal(globby.skipped.length, 1);
147
+ assert.equal(globby.checked, 0);
148
+ } finally {
149
+ await rm(dir, { recursive: true, force: true });
150
+ }
151
+ });
152
+
153
+ // --- confidential-marker scanner: pure text matcher ---
154
+
155
+ test("scanText flags denylisted markers and ignores clean text", () => {
156
+ const patterns = [
157
+ { label: "target", re: /\bAcme Capital\b/i },
158
+ { label: "id", re: /\bZZ9999\b/ },
159
+ ];
160
+ assert.deepEqual(scanText("nothing to see here", patterns), []);
161
+ assert.deepEqual(scanText("", patterns), []);
162
+ assert.deepEqual(scanText("some text", []), []);
163
+ assert.deepEqual(scanText(42, patterns), []);
164
+ const hits = scanText("we are pursuing acme capital (ref ZZ9999)", patterns);
165
+ assert.deepEqual(hits.map((h) => h.label).sort(), ["id", "target"]);
166
+ });
167
+
168
+ // --- conflict scan against a synthetic git-less dir is not possible (needs git);
169
+ // instead verify findConflictMarkers throws gracefully only via real repo run below.
170
+
171
+ // --- live runs against the CURRENT maestro repo: must all be green ---
172
+
173
+ test("each guard run() returns 0 on the current repo", async () => {
174
+ assert.equal(await runConflictMarkers(), 0);
175
+ assert.equal(await runExportsExist(), 0);
176
+ assert.equal(await runFilesExist(), 0);
177
+ assert.equal(await runNoConfidential(), 0);
178
+ });
179
+
180
+ test("findConflictMarkers finds nothing in the current repo", async () => {
181
+ const offenders = await findConflictMarkers();
182
+ assert.deepEqual(offenders, []);
183
+ });
184
+
185
+ test("aggregator runAll reports all checks passed on the current repo", async () => {
186
+ const { ok, results } = await runAll();
187
+ assert.equal(ok, true);
188
+ assert.equal(results.length, CHECKS.length);
189
+ assert.ok(results.every((r) => r.passed));
190
+ });
191
+
192
+ test("aggregator run() exits 0 on the current repo", async () => {
193
+ assert.equal(await runAggregator(), 0);
194
+ });
@@ -0,0 +1,82 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * run-coverage.mjs — coverage variant of the maestro test runner (audit L31).
4
+ *
5
+ * Uses the SAME test discovery as scripts/ci/run-tests.mjs (every `*.test.mjs`
6
+ * under lib/, scripts/, and bin/), but invokes `node --test` with
7
+ * `--experimental-test-coverage` so contributors can see line/branch/function
8
+ * coverage for the suite.
9
+ *
10
+ * This is intentionally NOT wired into `npm test` or the CI publish gate: the
11
+ * experimental coverage reporter is comparatively slow and its output format is
12
+ * not yet stable across Node minors, so gating CI on it would add flakiness for
13
+ * no safety gain. It is a developer convenience — run it locally with
14
+ * `npm run test:coverage`.
15
+ *
16
+ * Node builtins only (maestro is "type":"module", near-zero-dep by design).
17
+ * Requires Node 20+ (already enforced via package.json engines), which is where
18
+ * `--experimental-test-coverage` is available.
19
+ *
20
+ * Usage: `node scripts/ci/run-coverage.mjs`
21
+ * exit 0 → all tests passed; non-zero → at least one failed (or none found).
22
+ *
23
+ * @module scripts/ci/run-coverage
24
+ */
25
+
26
+ import { readdirSync } from "node:fs";
27
+ import { join, dirname } from "node:path";
28
+ import { fileURLToPath } from "node:url";
29
+ import { spawnSync } from "node:child_process";
30
+
31
+ const REPO_ROOT = join(dirname(fileURLToPath(import.meta.url)), "..", "..");
32
+ const ROOTS = ["lib", "scripts", "bin"];
33
+ const SKIP_DIRS = new Set(["node_modules", ".git", "state", "logs", "audit"]);
34
+
35
+ /**
36
+ * Recursively collect `*.test.mjs` files under `dir` (absolute). Mirrors the
37
+ * discovery in run-tests.mjs so coverage runs over the exact same set.
38
+ * @param {string} dir
39
+ * @returns {string[]}
40
+ */
41
+ function findTests(dir) {
42
+ let out = [];
43
+ let entries;
44
+ try {
45
+ entries = readdirSync(dir, { withFileTypes: true });
46
+ } catch {
47
+ return out; // missing root dir is fine
48
+ }
49
+ for (const entry of entries) {
50
+ if (entry.isDirectory()) {
51
+ if (SKIP_DIRS.has(entry.name)) continue;
52
+ out = out.concat(findTests(join(dir, entry.name)));
53
+ } else if (entry.isFile() && entry.name.endsWith(".test.mjs")) {
54
+ out.push(join(dir, entry.name));
55
+ }
56
+ }
57
+ return out;
58
+ }
59
+
60
+ const files = ROOTS.flatMap((r) => findTests(join(REPO_ROOT, r))).sort();
61
+
62
+ if (files.length === 0) {
63
+ console.error("run-coverage: no *.test.mjs files found — refusing to report success.");
64
+ process.exit(1);
65
+ }
66
+
67
+ console.log(`run-coverage: discovered ${files.length} test file(s).`);
68
+
69
+ const result = spawnSync(
70
+ process.execPath,
71
+ ["--test", "--experimental-test-coverage", ...files],
72
+ {
73
+ cwd: REPO_ROOT,
74
+ stdio: "inherit",
75
+ },
76
+ );
77
+
78
+ if (result.error) {
79
+ console.error("run-coverage: failed to launch node --test:", result.error.message);
80
+ process.exit(1);
81
+ }
82
+ process.exit(result.status ?? 1);
@@ -0,0 +1,71 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * run-tests.mjs — discovery test runner for maestro.
4
+ *
5
+ * Recursively finds every `*.test.mjs` under lib/, scripts/, and bin/ and runs
6
+ * them with `node --test`. Replaces the previous hand-enumerated file list in
7
+ * package.json, which silently excluded any new test until it was manually
8
+ * appended (audit L25). A renamed/deleted file can no longer error the whole
9
+ * invocation, and a newly added test is picked up automatically.
10
+ *
11
+ * Node builtins only (maestro is "type":"module", near-zero-dep by design).
12
+ *
13
+ * Usage: `node scripts/ci/run-tests.mjs`
14
+ * exit 0 → all tests passed; non-zero → at least one failed (or none found).
15
+ *
16
+ * @module scripts/ci/run-tests
17
+ */
18
+
19
+ import { readdirSync } from "node:fs";
20
+ import { join, dirname } from "node:path";
21
+ import { fileURLToPath } from "node:url";
22
+ import { spawnSync } from "node:child_process";
23
+
24
+ const REPO_ROOT = join(dirname(fileURLToPath(import.meta.url)), "..", "..");
25
+ const ROOTS = ["lib", "scripts", "bin", "services"];
26
+ const SKIP_DIRS = new Set(["node_modules", ".git", "state", "logs", "audit"]);
27
+
28
+ /**
29
+ * Recursively collect `*.test.mjs` files under `dir` (absolute), returning
30
+ * paths relative to REPO_ROOT for stable, readable output.
31
+ * @param {string} dir
32
+ * @returns {string[]}
33
+ */
34
+ function findTests(dir) {
35
+ let out = [];
36
+ let entries;
37
+ try {
38
+ entries = readdirSync(dir, { withFileTypes: true });
39
+ } catch {
40
+ return out; // missing root dir is fine
41
+ }
42
+ for (const entry of entries) {
43
+ if (entry.isDirectory()) {
44
+ if (SKIP_DIRS.has(entry.name)) continue;
45
+ out = out.concat(findTests(join(dir, entry.name)));
46
+ } else if (entry.isFile() && entry.name.endsWith(".test.mjs")) {
47
+ out.push(join(dir, entry.name));
48
+ }
49
+ }
50
+ return out;
51
+ }
52
+
53
+ const files = ROOTS.flatMap((r) => findTests(join(REPO_ROOT, r))).sort();
54
+
55
+ if (files.length === 0) {
56
+ console.error("run-tests: no *.test.mjs files found — refusing to report success.");
57
+ process.exit(1);
58
+ }
59
+
60
+ console.log(`run-tests: discovered ${files.length} test file(s).`);
61
+
62
+ const result = spawnSync(process.execPath, ["--test", ...files], {
63
+ cwd: REPO_ROOT,
64
+ stdio: "inherit",
65
+ });
66
+
67
+ if (result.error) {
68
+ console.error("run-tests: failed to launch node --test:", result.error.message);
69
+ process.exit(1);
70
+ }
71
+ process.exit(result.status ?? 1);
@@ -0,0 +1,59 @@
1
+ # Cloud Relay — Slack Events to Mac mini
2
+
3
+ Public HTTPS service that proxies Slack Events API webhooks into a
4
+ Mac-mini-resident maestro daemon over Server-Sent Events.
5
+
6
+ ## Why
7
+
8
+ Slack Socket Mode only delivers events for the **bot user** that owns the
9
+ app. DMs sent to the agent's underlying **user account** (e.g.,
10
+ `U099N1JE0LA` for Ravi) are invisible to Socket Mode — the bot is not a
11
+ member of those conversations. To get real-time push for those DMs we
12
+ need user-scope event subscriptions, which require an HTTPS endpoint, and
13
+ Mac minis don't have public IPs. This relay closes the gap.
14
+
15
+ ## Architecture
16
+
17
+ ```
18
+ Slack ──HTTPS POST──▶ Railway (this relay) ──SSE──▶ Mac mini (slack-cloud-relay-client.mjs)
19
+ │ │
20
+ │ └──▶ writeInboxItem() → state/inbox/slack/
21
+ │
22
+ └── verifies x-slack-signature, fans events to all SSE clients
23
+ ```
24
+
25
+ ## Deploy
26
+
27
+ ```
28
+ cd scripts/cloud-relay
29
+ railway init
30
+ railway up
31
+ railway variables set \
32
+ SLACK_SIGNING_SECRET=<from Slack app Basic Information> \
33
+ RELAY_SHARED_SECRET=<long random string, share with Mac mini>
34
+ railway domain # → returns https://<project>.up.railway.app
35
+ ```
36
+
37
+ Then in the Slack app config:
38
+ 1. Event Subscriptions → Enable Events → set Request URL to
39
+ `https://<project>.up.railway.app/slack/events`.
40
+ 2. Subscribe to **User events** (`message.im`, `message.mpim`,
41
+ `message.channels`, `message.groups`, `app_mention`).
42
+ 3. Save changes and reinstall the app.
43
+
44
+ Set the Mac mini's `.env`:
45
+ ```
46
+ CLOUD_RELAY_URL=https://<project>.up.railway.app
47
+ RELAY_SHARED_SECRET=<same string as on Railway>
48
+ ```
49
+
50
+ ## Endpoints
51
+
52
+ - `POST /slack/events` — Slack Events API. Verifies `x-slack-signature`
53
+ using `SLACK_SIGNING_SECRET`; responds to `url_verification` challenges;
54
+ fans event callbacks out to connected SSE clients.
55
+ - `GET /events/stream` — SSE stream. Requires
56
+ `Authorization: Bearer <RELAY_SHARED_SECRET>`. New connections receive
57
+ the last 100 events as catch-up; heartbeats every 25 s keep the stream
58
+ alive through proxies.
59
+ - `GET /health` — Liveness probe (returns connection + queue counts).
@@ -0,0 +1,233 @@
1
+ /**
2
+ * Maestro — Slack Events Cloud Relay
3
+ *
4
+ * Public HTTPS service (deployed on Railway) that bridges Slack user-scope
5
+ * Events API webhooks into a Mac-mini-resident maestro daemon over SSE.
6
+ *
7
+ * Why this exists: Socket Mode is bot-scoped, so DMs sent to the agent's
8
+ * USER account (not its bot user) are invisible to a Socket Mode listener.
9
+ * To get real-time push for those DMs, the Slack app needs user-scope event
10
+ * subscriptions, which require an HTTPS webhook URL — and Mac minis don't
11
+ * have public IPs. This relay sits in front: Slack POSTs events here, the
12
+ * Mac mini holds an authenticated SSE connection, and events get pushed
13
+ * back over the open stream in milliseconds.
14
+ *
15
+ * Endpoints:
16
+ * POST /slack/events — Slack Events API receiver. Verifies the v0
17
+ * HMAC signature, handles url_verification on
18
+ * registration, fans event_callback envelopes
19
+ * out to every connected SSE client.
20
+ * GET /events/stream — Server-Sent Events feed for Mac-mini clients
21
+ * holding an open connection. Bearer-token
22
+ * authenticated against RELAY_SHARED_SECRET.
23
+ * New connections receive the last ~100 events
24
+ * so a brief disconnect doesn't drop messages.
25
+ * GET /health — Liveness probe used by Railway / external
26
+ * uptime monitors.
27
+ *
28
+ * Env (all required):
29
+ * PORT — provided by Railway
30
+ * SLACK_SIGNING_SECRET — from Slack app "Basic Information" page
31
+ * RELAY_SHARED_SECRET — operator-chosen long random string; the Mac
32
+ * mini client must send the same value in its
33
+ * Authorization: Bearer <...> header
34
+ */
35
+
36
+ import http from "node:http";
37
+ import crypto from "node:crypto";
38
+
39
+ const PORT = parseInt(process.env.PORT || "3100", 10);
40
+ const SIGNING_SECRET = process.env.SLACK_SIGNING_SECRET || "";
41
+ const RELAY_SHARED_SECRET = process.env.RELAY_SHARED_SECRET || "";
42
+
43
+ if (!SIGNING_SECRET) {
44
+ console.error("FATAL: SLACK_SIGNING_SECRET env var is required");
45
+ process.exit(78);
46
+ }
47
+ if (!RELAY_SHARED_SECRET) {
48
+ console.error("FATAL: RELAY_SHARED_SECRET env var is required");
49
+ process.exit(78);
50
+ }
51
+
52
+ // Connected Mac-mini SSE clients (typically 1, but designed for fan-out).
53
+ const sseClients = new Set();
54
+
55
+ // Bounded ring buffer of recent events for reconnect replay. Slack-side
56
+ // "retry on 5xx" already gives us at-least-once delivery, so this is just
57
+ // for the case where the Mac mini's SSE connection drops between an event
58
+ // arriving and being acked: when it reconnects within ~5 minutes it gets
59
+ // the missing events replayed instead of waiting for Slack's next retry.
60
+ const REPLAY_CAPACITY = 100;
61
+ const eventQueue = [];
62
+
63
+ function log(level, message, data) {
64
+ const ts = new Date().toISOString();
65
+ const line = JSON.stringify({ ts, level, message, ...(data || {}) });
66
+ if (level === "error") console.error(line);
67
+ else console.log(line);
68
+ }
69
+
70
+ /**
71
+ * Verify Slack's v0 HMAC signature. Per Slack docs:
72
+ * sig_basestring = "v0:" + timestamp + ":" + raw_body
73
+ * expected = "v0=" + hex(hmac_sha256(SIGNING_SECRET, sig_basestring))
74
+ * Also enforce the 5-minute timestamp window to block replay attacks.
75
+ */
76
+ function verifySlackSignature(timestamp, body, signature) {
77
+ if (!timestamp || !signature) return false;
78
+ const tsNum = parseInt(timestamp, 10);
79
+ if (!Number.isFinite(tsNum)) return false;
80
+ const ageSec = Math.abs(Date.now() / 1000 - tsNum);
81
+ if (ageSec > 5 * 60) return false;
82
+ const basestring = `v0:${timestamp}:${body}`;
83
+ const expected = `v0=${crypto.createHmac("sha256", SIGNING_SECRET).update(basestring).digest("hex")}`;
84
+ if (expected.length !== signature.length) return false;
85
+ try {
86
+ return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
87
+ } catch {
88
+ return false;
89
+ }
90
+ }
91
+
92
+ function fanOut(envelope) {
93
+ eventQueue.push(envelope);
94
+ if (eventQueue.length > REPLAY_CAPACITY) eventQueue.shift();
95
+ const data = `data: ${JSON.stringify(envelope)}\n\n`;
96
+ const dead = [];
97
+ for (const client of sseClients) {
98
+ try {
99
+ client.write(data);
100
+ } catch {
101
+ dead.push(client);
102
+ }
103
+ }
104
+ for (const c of dead) sseClients.delete(c);
105
+ log("info", "fanned-out", { sse_clients: sseClients.size, queue_size: eventQueue.length });
106
+ }
107
+
108
+ const server = http.createServer((req, res) => {
109
+ // Health probe
110
+ if (req.method === "GET" && req.url === "/health") {
111
+ res.writeHead(200, { "Content-Type": "application/json" });
112
+ res.end(JSON.stringify({
113
+ ok: true,
114
+ sse_clients: sseClients.size,
115
+ queue_size: eventQueue.length,
116
+ uptime_s: Math.round(process.uptime()),
117
+ }));
118
+ return;
119
+ }
120
+
121
+ // Slack Events API endpoint
122
+ if (req.method === "POST" && req.url === "/slack/events") {
123
+ let body = "";
124
+ req.on("data", (chunk) => { body += chunk; });
125
+ req.on("end", () => {
126
+ const ts = req.headers["x-slack-request-timestamp"];
127
+ const sig = req.headers["x-slack-signature"];
128
+
129
+ if (!verifySlackSignature(ts, body, sig)) {
130
+ log("warn", "rejected-bad-signature", { ts_header: !!ts });
131
+ res.writeHead(401, { "Content-Type": "text/plain" });
132
+ res.end("invalid signature");
133
+ return;
134
+ }
135
+
136
+ let payload;
137
+ try {
138
+ payload = JSON.parse(body);
139
+ } catch {
140
+ res.writeHead(400, { "Content-Type": "text/plain" });
141
+ res.end("bad json");
142
+ return;
143
+ }
144
+
145
+ // One-time URL verification when the operator first registers the
146
+ // webhook in the Slack app config. Slack expects an echo of the
147
+ // `challenge` field within 3 seconds.
148
+ if (payload.type === "url_verification") {
149
+ log("info", "url-verification-challenge", { challenge_len: (payload.challenge || "").length });
150
+ res.writeHead(200, { "Content-Type": "application/json" });
151
+ res.end(JSON.stringify({ challenge: payload.challenge }));
152
+ return;
153
+ }
154
+
155
+ // Event callback. Slack requires a 200 within 3 seconds or it
156
+ // retries, so ack immediately and fan out after.
157
+ res.writeHead(200, { "Content-Type": "text/plain" });
158
+ res.end("ok");
159
+ if (payload.type === "event_callback") {
160
+ fanOut(payload);
161
+ } else {
162
+ log("info", "ignored-envelope-type", { type: payload.type });
163
+ }
164
+ });
165
+ req.on("error", (err) => {
166
+ log("error", "request-error", { error: err.message });
167
+ });
168
+ return;
169
+ }
170
+
171
+ // SSE stream to Mac mini
172
+ if (req.method === "GET" && req.url === "/events/stream") {
173
+ const auth = req.headers.authorization || "";
174
+ const want = `Bearer ${RELAY_SHARED_SECRET}`;
175
+ if (auth.length !== want.length || !crypto.timingSafeEqual(Buffer.from(auth), Buffer.from(want))) {
176
+ res.writeHead(401, { "Content-Type": "text/plain" });
177
+ res.end("unauthorized");
178
+ return;
179
+ }
180
+
181
+ res.writeHead(200, {
182
+ "Content-Type": "text/event-stream",
183
+ "Cache-Control": "no-cache, no-transform",
184
+ "Connection": "keep-alive",
185
+ "X-Accel-Buffering": "no",
186
+ });
187
+ res.write(": connected\n\n");
188
+ sseClients.add(res);
189
+ log("info", "sse-client-connected", { sse_clients: sseClients.size, remote: req.socket.remoteAddress });
190
+
191
+ // Replay the recent backlog so a brief disconnect doesn't drop events.
192
+ for (const ev of eventQueue) {
193
+ try { res.write(`data: ${JSON.stringify(ev)}\n\n`); } catch { /* */ }
194
+ }
195
+
196
+ // Heartbeat — many proxies idle-timeout long-lived streams; a comment
197
+ // line every 25s keeps the connection visibly alive without polluting
198
+ // the event payloads.
199
+ const hb = setInterval(() => {
200
+ try {
201
+ res.write(": hb\n\n");
202
+ } catch {
203
+ clearInterval(hb);
204
+ }
205
+ }, 25_000);
206
+
207
+ req.on("close", () => {
208
+ sseClients.delete(res);
209
+ clearInterval(hb);
210
+ log("info", "sse-client-disconnected", { sse_clients: sseClients.size });
211
+ });
212
+ return;
213
+ }
214
+
215
+ res.writeHead(404, { "Content-Type": "text/plain" });
216
+ res.end("not found");
217
+ });
218
+
219
+ server.listen(PORT, () => {
220
+ log("info", "cloud-relay-listening", { port: PORT });
221
+ });
222
+
223
+ // Graceful shutdown on SIGTERM (Railway sends this on redeploy / scale-down).
224
+ const shutdown = (signal) => {
225
+ log("info", "shutdown-requested", { signal });
226
+ for (const client of sseClients) {
227
+ try { client.end(); } catch { /* */ }
228
+ }
229
+ server.close(() => process.exit(0));
230
+ setTimeout(() => process.exit(0), 5_000).unref();
231
+ };
232
+ process.on("SIGTERM", () => shutdown("SIGTERM"));
233
+ process.on("SIGINT", () => shutdown("SIGINT"));
@@ -0,0 +1,15 @@
1
+ {
2
+ "name": "@cohortapp/agent-sdk-cloud-relay",
3
+ "version": "1.0.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "description": "Public HTTPS relay that proxies Slack Events API into a Mac-mini-resident maestro daemon over SSE.",
7
+ "main": "index.mjs",
8
+ "scripts": {
9
+ "start": "node index.mjs"
10
+ },
11
+ "engines": {
12
+ "node": ">=20"
13
+ },
14
+ "license": "UNLICENSED"
15
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "$schema": "https://railway.com/railway.schema.json",
3
+ "build": {
4
+ "builder": "NIXPACKS"
5
+ },
6
+ "deploy": {
7
+ "startCommand": "node index.mjs",
8
+ "healthcheckPath": "/health",
9
+ "healthcheckTimeout": 100,
10
+ "restartPolicyType": "ON_FAILURE",
11
+ "restartPolicyMaxRetries": 10
12
+ }
13
+ }