@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,295 @@
1
+ /**
2
+ * session-router.test.mjs — node:test coverage for the session router.
3
+ *
4
+ * No real subprocess spawns, no real disk writes outside os.tmpdir().
5
+ * Each test creates its own registry path under tmpdir and cleans up.
6
+ */
7
+
8
+ import { test } from "node:test";
9
+ import assert from "node:assert/strict";
10
+ import { promises as fsp } from "fs";
11
+ import { tmpdir } from "os";
12
+ import { join } from "path";
13
+
14
+ import { routingKey, createRouter } from "./session-router.mjs";
15
+
16
+ function tmpRegistryPath(suffix = "") {
17
+ const name = `session-router-test-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}${suffix}.json`;
18
+ return join(tmpdir(), name);
19
+ }
20
+
21
+ async function safeUnlink(path) {
22
+ try {
23
+ await fsp.unlink(path);
24
+ } catch {
25
+ /* ignore */
26
+ }
27
+ }
28
+
29
+ // ---------------------------------------------------------------------------
30
+ // 1. routingKey — one assertion per row of memo §4.2.
31
+ // ---------------------------------------------------------------------------
32
+
33
+ test("routingKey — slack threaded message", () => {
34
+ const k = routingKey({
35
+ source: "slack",
36
+ channel: "C0123ABC",
37
+ thread_ts: "1777283277.000100",
38
+ });
39
+ assert.equal(k, "slack:C0123ABC:1777283277.000100");
40
+ });
41
+
42
+ test("routingKey — slack DM (no thread)", () => {
43
+ const k = routingKey({
44
+ source: "slack",
45
+ channel: "D099N1JGKRQ",
46
+ });
47
+ assert.equal(k, "slack:D099N1JGKRQ");
48
+ });
49
+
50
+ test("routingKey — slack channel non-DM (no thread)", () => {
51
+ const k = routingKey({
52
+ source: "slack",
53
+ channel: "C0123ABC",
54
+ ts: "1777283277.000200",
55
+ });
56
+ assert.equal(k, "slack:C0123ABC:1777283277.000200");
57
+ });
58
+
59
+ test("routingKey — gmail", () => {
60
+ const k = routingKey({ source: "gmail", thread_id: "1818abc" });
61
+ assert.equal(k, "gmail:1818abc");
62
+ });
63
+
64
+ test("routingKey — calendar", () => {
65
+ const k = routingKey({ source: "calendar", event_id: "evt_42" });
66
+ assert.equal(k, "calendar:evt_42");
67
+ });
68
+
69
+ test("routingKey — internal queue item", () => {
70
+ const k = routingKey({ source: "internal", id: "ar-20260427-001" });
71
+ assert.equal(k, "internal:ar-20260427-001");
72
+ });
73
+
74
+ test("routingKey — backlog with topic_slug", () => {
75
+ const k = routingKey({
76
+ source: "backlog",
77
+ topic_slug: "session-router-step2",
78
+ id: "as-20260427-x",
79
+ });
80
+ assert.equal(k, "backlog:session-router-step2");
81
+ });
82
+
83
+ test("routingKey — backlog without topic_slug falls back to internal:<id>", () => {
84
+ const k = routingKey({ source: "backlog", id: "as-20260427-fallback" });
85
+ assert.equal(k, "internal:as-20260427-fallback");
86
+ });
87
+
88
+ // ---------------------------------------------------------------------------
89
+ // 2. route() — missing key returns EPHEMERAL.
90
+ // ---------------------------------------------------------------------------
91
+
92
+ test("route — missing key returns EPHEMERAL", async (t) => {
93
+ const path = tmpRegistryPath("-missing");
94
+ t.after(() => safeUnlink(path));
95
+ const router = await createRouter({ registryPath: path });
96
+ const decision = router.route("slack:CNEVERSEEN:0");
97
+ assert.deepEqual(decision, { decision: "EPHEMERAL", resumeId: null });
98
+ });
99
+
100
+ // ---------------------------------------------------------------------------
101
+ // 3. route() — past-TTL entry returns EPHEMERAL_REPLACE.
102
+ // ---------------------------------------------------------------------------
103
+
104
+ test("route — past-TTL entry returns EPHEMERAL_REPLACE", async (t) => {
105
+ const path = tmpRegistryPath("-ttl");
106
+ t.after(() => safeUnlink(path));
107
+ let clock = 1_000_000_000_000;
108
+ const router = await createRouter({
109
+ registryPath: path,
110
+ ttlSeconds: 60,
111
+ now: () => clock,
112
+ });
113
+ await router.touch("slack:DAAA:1", { claudeSessionId: "cli-1", model: "sonnet" });
114
+ // Advance clock past TTL.
115
+ clock += 61 * 1000;
116
+ const decision = router.route("slack:DAAA:1");
117
+ assert.equal(decision.decision, "EPHEMERAL_REPLACE");
118
+ assert.equal(decision.resumeId, null);
119
+ });
120
+
121
+ // ---------------------------------------------------------------------------
122
+ // 4. route() — live, fresh, exit_code 0 returns RESUME with the stored id.
123
+ // ---------------------------------------------------------------------------
124
+
125
+ test("route — live fresh entry returns RESUME with stored claude_session_id", async (t) => {
126
+ const path = tmpRegistryPath("-resume");
127
+ t.after(() => safeUnlink(path));
128
+ let clock = 2_000_000_000_000;
129
+ const router = await createRouter({
130
+ registryPath: path,
131
+ ttlSeconds: 600,
132
+ now: () => clock,
133
+ });
134
+ await router.touch("slack:DRESUME:1", {
135
+ claudeSessionId: "cli-resume-xyz",
136
+ model: "sonnet",
137
+ });
138
+ clock += 30_000; // 30s — well within TTL.
139
+ const decision = router.route("slack:DRESUME:1");
140
+ assert.equal(decision.decision, "RESUME");
141
+ assert.equal(decision.resumeId, "cli-resume-xyz");
142
+ });
143
+
144
+ // ---------------------------------------------------------------------------
145
+ // 5. route() — non-zero last_exit_code returns EPHEMERAL_REPLACE.
146
+ // ---------------------------------------------------------------------------
147
+
148
+ test("route — non-zero last_exit_code returns EPHEMERAL_REPLACE", async (t) => {
149
+ const path = tmpRegistryPath("-exitcode");
150
+ t.after(() => safeUnlink(path));
151
+ let clock = 3_000_000_000_000;
152
+ const router = await createRouter({
153
+ registryPath: path,
154
+ ttlSeconds: 600,
155
+ now: () => clock,
156
+ });
157
+ await router.touch("slack:DEXIT:1", {
158
+ claudeSessionId: "cli-exit",
159
+ model: "sonnet",
160
+ });
161
+ await router.recordExit("slack:DEXIT:1", 1);
162
+ clock += 5_000;
163
+ const decision = router.route("slack:DEXIT:1");
164
+ assert.equal(decision.decision, "EPHEMERAL_REPLACE");
165
+ assert.equal(decision.resumeId, null);
166
+ });
167
+
168
+ // ---------------------------------------------------------------------------
169
+ // 6. touch() — LRU cap enforced; oldest entry evicted on overflow.
170
+ // ---------------------------------------------------------------------------
171
+
172
+ test("touch — LRU cap evicts oldest when at capacity", async (t) => {
173
+ const path = tmpRegistryPath("-lru");
174
+ t.after(() => safeUnlink(path));
175
+ let clock = 4_000_000_000_000;
176
+ const router = await createRouter({
177
+ registryPath: path,
178
+ ttlSeconds: 3600,
179
+ maxLiveSessions: 3,
180
+ now: () => clock,
181
+ });
182
+ for (let i = 1; i <= 3; i++) {
183
+ await router.touch(`slack:DLRU${i}`, {
184
+ claudeSessionId: `cli-${i}`,
185
+ model: "sonnet",
186
+ });
187
+ clock += 1000;
188
+ }
189
+ // At cap. Add a fourth.
190
+ await router.touch("slack:DLRU4", {
191
+ claudeSessionId: "cli-4",
192
+ model: "sonnet",
193
+ });
194
+ const snap = router._readForTests();
195
+ assert.equal(snap.lru.length, 3, "lru should still hold 3 keys");
196
+ assert.ok(!("slack:DLRU1" in snap.sessions), "oldest (DLRU1) evicted");
197
+ assert.ok("slack:DLRU2" in snap.sessions);
198
+ assert.ok("slack:DLRU3" in snap.sessions);
199
+ assert.ok("slack:DLRU4" in snap.sessions);
200
+ });
201
+
202
+ // ---------------------------------------------------------------------------
203
+ // 7. recordExit() — non-zero code flips status to "killed".
204
+ // ---------------------------------------------------------------------------
205
+
206
+ test("recordExit — non-zero code flips status to killed", async (t) => {
207
+ const path = tmpRegistryPath("-killed");
208
+ t.after(() => safeUnlink(path));
209
+ const router = await createRouter({ registryPath: path });
210
+ await router.touch("slack:DKILL:1", {
211
+ claudeSessionId: "cli-kill",
212
+ model: "sonnet",
213
+ });
214
+ await router.recordExit("slack:DKILL:1", 137);
215
+ const snap = router._readForTests();
216
+ assert.equal(snap.sessions["slack:DKILL:1"].status, "killed");
217
+ assert.equal(snap.sessions["slack:DKILL:1"].last_exit_code, 137);
218
+ });
219
+
220
+ // ---------------------------------------------------------------------------
221
+ // 8. evictExpired() — removes only stale entries, returns count.
222
+ // ---------------------------------------------------------------------------
223
+
224
+ test("evictExpired — removes only stale entries and returns count", async (t) => {
225
+ const path = tmpRegistryPath("-evict");
226
+ t.after(() => safeUnlink(path));
227
+ let clock = 5_000_000_000_000;
228
+ const router = await createRouter({
229
+ registryPath: path,
230
+ ttlSeconds: 60,
231
+ now: () => clock,
232
+ });
233
+ await router.touch("slack:DEVICT:OLD", {
234
+ claudeSessionId: "cli-old",
235
+ model: "sonnet",
236
+ });
237
+ // Advance past TTL.
238
+ clock += 120_000;
239
+ await router.touch("slack:DEVICT:NEW", {
240
+ claudeSessionId: "cli-new",
241
+ model: "sonnet",
242
+ });
243
+ // Now: OLD is stale (last_used_at = clock - 120s, ttl = 60s),
244
+ // NEW is fresh.
245
+ const evicted = await router.evictExpired();
246
+ assert.equal(evicted, 1, "one entry should have been evicted");
247
+ const snap = router._readForTests();
248
+ assert.ok(!("slack:DEVICT:OLD" in snap.sessions));
249
+ assert.ok("slack:DEVICT:NEW" in snap.sessions);
250
+ });
251
+
252
+ // ---------------------------------------------------------------------------
253
+ // 9. Registry round-trip — touch two keys, re-create router, state restored.
254
+ // ---------------------------------------------------------------------------
255
+
256
+ test("registry round-trip — second router instance reads persisted state", async (t) => {
257
+ const path = tmpRegistryPath("-roundtrip");
258
+ t.after(() => safeUnlink(path));
259
+ let clock = 6_000_000_000_000;
260
+
261
+ const r1 = await createRouter({
262
+ registryPath: path,
263
+ ttlSeconds: 3600,
264
+ now: () => clock,
265
+ });
266
+ await r1.touch("gmail:thread-A", {
267
+ claudeSessionId: "cli-A",
268
+ model: "sonnet",
269
+ });
270
+ clock += 1000;
271
+ await r1.touch("gmail:thread-B", {
272
+ claudeSessionId: "cli-B",
273
+ model: "opus",
274
+ });
275
+
276
+ // Fresh router instance against the same path.
277
+ const r2 = await createRouter({
278
+ registryPath: path,
279
+ ttlSeconds: 3600,
280
+ now: () => clock,
281
+ });
282
+ const snap = r2._readForTests();
283
+ assert.equal(Object.keys(snap.sessions).length, 2);
284
+ assert.equal(snap.sessions["gmail:thread-A"].claude_session_id, "cli-A");
285
+ assert.equal(snap.sessions["gmail:thread-B"].claude_session_id, "cli-B");
286
+ assert.equal(snap.sessions["gmail:thread-B"].model, "opus");
287
+ // Both keys present in the LRU array.
288
+ assert.ok(snap.lru.includes("gmail:thread-A"));
289
+ assert.ok(snap.lru.includes("gmail:thread-B"));
290
+
291
+ // route() should return RESUME for the persisted entry.
292
+ const dec = r2.route("gmail:thread-A");
293
+ assert.equal(dec.decision, "RESUME");
294
+ assert.equal(dec.resumeId, "cli-A");
295
+ });
@@ -0,0 +1,275 @@
1
+ #!/usr/bin/env node
2
+ // =============================================================================
3
+ // Maestro Daemon — Reactive event-driven message processor
4
+ // =============================================================================
5
+ //
6
+ // Generic entry point that loads agent identity from config/agent.json and
7
+ // delegates to the core daemon logic. This file is agent-name-agnostic.
8
+ //
9
+ // Lifecycle:
10
+ // 1. Honour .emergency-stop BEFORE doing anything (don't acquire singleton
11
+ // lock, don't start consumer, don't import the core daemon). Stops the
12
+ // launchd restart treadmill cold.
13
+ // 2. Acquire the daemon singleton lock so only one instance runs.
14
+ // 3. Start the cadence consumer (state/cadence-bus/ drain loop).
15
+ // 4. Import the core daemon (agent-daemon.mjs canonical, with fallbacks
16
+ // to a legacy <firstName>-daemon.mjs for back-compat).
17
+ //
18
+ // Run: node scripts/daemon/maestro-daemon.mjs
19
+ // Install: launchd plist with KeepAlive.SuccessfulExit: false (clean exits
20
+ // during emergency-stop should NOT trigger restart; non-zero exits should).
21
+ // =============================================================================
22
+
23
+ import { resolve, dirname, join } from "node:path";
24
+ import { fileURLToPath } from "node:url";
25
+ import { existsSync, readFileSync, readdirSync } from "node:fs";
26
+ import { installUnhandledHandlers } from "../../lib/util/unhandled.mjs";
27
+ import { readPeers, readIdentity } from "../../lib/org/registry.mjs";
28
+ import { bump as bumpCounter } from "../../lib/diagnostics/counters.mjs";
29
+
30
+ const __dirname = dirname(fileURLToPath(import.meta.url));
31
+ const AGENT_DIR = process.env.AGENT_DIR || resolve(__dirname, "../..");
32
+
33
+ process.env.AGENT_DIR = AGENT_DIR;
34
+ process.env.AGENT_ROOT = AGENT_DIR;
35
+
36
+ // Never die silently (P0-6): a stray unhandled rejection / uncaught exception is
37
+ // logged + counted, not a silent process death. Rejections keep the daemon
38
+ // running; an uncaught exception logs+flushes then lets launchd restart us.
39
+ installUnhandledHandlers({
40
+ bump: (name, attrs) => { try { bumpCounter(name, { ...attrs, agentRoot: AGENT_DIR }); } catch { /* never throw from the handler */ } },
41
+ onEvent: (e) => console.error(`[DAEMON] ${e.kind}: ${e.error && e.error.message}`),
42
+ });
43
+
44
+ // ---------------------------------------------------------------------------
45
+ // 1. Emergency-stop gate
46
+ // ---------------------------------------------------------------------------
47
+ // If the operator dropped .emergency-stop, we exit cleanly (code 0). With
48
+ // KeepAlive.SuccessfulExit:false this means launchd will NOT restart us
49
+ // until the flag is lifted + the plist is reloaded by resume-operations.sh.
50
+ // Older plists with plain KeepAlive:true will still restart, so we hold
51
+ // the process for 30s before exiting to give launchd's ThrottleInterval
52
+ // something to throttle.
53
+ if (existsSync(join(AGENT_DIR, ".emergency-stop"))) {
54
+ console.error("[DAEMON] .emergency-stop flag present — refusing to start.");
55
+ console.error("[DAEMON] Lift with: scripts/resume-operations.sh");
56
+ await new Promise((r) => setTimeout(r, 30_000));
57
+ process.exit(0);
58
+ }
59
+
60
+ // ---------------------------------------------------------------------------
61
+ // 2. Singleton guard
62
+ // ---------------------------------------------------------------------------
63
+ try {
64
+ const { acquireLock } = await import("../../lib/singleton.js");
65
+ if (!acquireLock("daemon")) {
66
+ console.log("[DAEMON] Already running — exiting");
67
+ process.exit(0);
68
+ }
69
+ } catch { /* singleton lib missing — proceed without lock */ }
70
+
71
+ // ---------------------------------------------------------------------------
72
+ // 3. Cadence consumer (cadence bus drain loop)
73
+ // ---------------------------------------------------------------------------
74
+ // Start alongside the reactive event loop. This is the single persistent
75
+ // owner of cadence housekeeping — launchd plists enqueue cadence ticks
76
+ // onto state/cadence-bus/ and the consumer drains them here, either
77
+ // handling them inline or escalating to a sub-session when warranted.
78
+ // Failure to start the consumer must NOT take the reactive daemon down.
79
+ try {
80
+ const { startConsumer } = await import("./cadence-consumer.mjs");
81
+ const consumer = startConsumer({ agentRoot: AGENT_DIR });
82
+ const shutdown = async (sig) => {
83
+ try { await consumer.stop(); } catch { /* ignore */ }
84
+ console.log(`[DAEMON] cadence consumer stopped (${sig})`);
85
+ };
86
+ process.on("SIGTERM", () => shutdown("SIGTERM"));
87
+ process.on("SIGINT", () => shutdown("SIGINT"));
88
+ } catch (err) {
89
+ console.error(`[DAEMON] cadence consumer failed to start: ${err.message}`);
90
+ // Reactive daemon continues. Doctor / healthcheck will surface this.
91
+ }
92
+
93
+ // ---------------------------------------------------------------------------
94
+ // 3b. Channel bus (1.12.0+; generic platform loop in 1.15.0/WS2)
95
+ // ---------------------------------------------------------------------------
96
+ // WS2: instead of a hardcoded voice+whatsapp block, we iterate the
97
+ // self-registered PLATFORMS (lib/channels/contract.mjs definePlatform) and
98
+ // start every platform whose gate file exists under the agent dir. Each
99
+ // channel's onInbound writes a byte-compatible inbox item via
100
+ // `writeInboxItem(service, eventToInboxItem(ev))` so new channels flow
101
+ // through the unchanged poll()/classify/dispatch path. Failures here MUST
102
+ // NOT take the reactive daemon down.
103
+ try {
104
+ const channelsModule = resolve(__dirname, "../../lib/channels/index.mjs");
105
+ if (existsSync(channelsModule)) {
106
+ const { listPlatforms, eventToInboxItem } = await import(`file://${channelsModule}`);
107
+ const { writeInboxItem } = await import(`file://${resolve(__dirname, "../poller/utils.mjs")}`);
108
+ // Single-owner rule (H2): the shared SoT for which platforms the daemon must
109
+ // NOT start (they have a dedicated launchd owner — e.g. slack-socket).
110
+ const { isDaemonManagedPlatform } = await import(`file://${resolve(__dirname, "../../lib/channels/contract.mjs")}`);
111
+
112
+ // Importing the adapters runs their definePlatform() side-effects. Each is
113
+ // optional (dynamic deps); a failed import just means that platform isn't
114
+ // available and is skipped.
115
+ for (const mod of ["./slack/adapter.mjs", "./telegram/adapter.mjs", "./whatsapp/adapter-baileys.mjs", "./orgmail/adapter.mjs"]) {
116
+ try { await import(`file://${resolve(__dirname, "../../lib/channels", mod)}`); }
117
+ catch (err) { console.warn(`[DAEMON] channel-bus: ${mod} not loadable — ${err.message}`); }
118
+ }
119
+
120
+ const makeOnInbound = (service) => (ev) => {
121
+ try {
122
+ writeInboxItem(service, eventToInboxItem(ev));
123
+ } catch (err) {
124
+ console.warn(`[DAEMON] channel-bus inbox write failed (${service}): ${err.message}`);
125
+ }
126
+ };
127
+
128
+ // Register started adapters with the typing registry so the dispatcher can
129
+ // drive a "typing…" heartbeat on the originating channel during a session.
130
+ let registerTypingAdapter = null;
131
+ try {
132
+ ({ registerTypingAdapter } = await import(`file://${resolve(__dirname, "./typing-registry.mjs")}`));
133
+ } catch { /* typing is optional */ }
134
+
135
+ // SINGLE-OWNER RULE (H2): some platforms have a DEDICATED launchd process
136
+ // that owns their real-time connection — the daemon must NOT also start
137
+ // them here, or the channel is ingested twice (every DM answered twice).
138
+ // Slack's owner is `ai.maestro.<agent>-slack-socket` (generate-plists.sh),
139
+ // which runs scripts/poller/slack-socket-mode.mjs and reads config/slack.yaml
140
+ // itself for reactions/cc. This generic loop is only for daemon-owned
141
+ // channels (telegram / whatsapp / voice) that have NO dedicated plist.
142
+ // The exclusion SoT is lib/channels/contract.mjs (isDaemonManagedPlatform).
143
+ for (const platform of listPlatforms()) {
144
+ if (!isDaemonManagedPlatform(platform.name)) {
145
+ console.log(`[DAEMON] channel-bus: ${platform.name} skipped — owned by its dedicated launchd process (single-owner rule)`);
146
+ continue;
147
+ }
148
+ const gatePath = join(AGENT_DIR, platform.gateFile);
149
+ if (!existsSync(gatePath)) continue;
150
+ try {
151
+ // Peer registry → bot-loop guard (P0-5): pass this agent's own id + the
152
+ // set of peer-agent ids so BaseAdapter._isOwnOrPeer won't auto-reply to a
153
+ // sibling agent in a shared channel (reply-storm prevention). Best-effort:
154
+ // ids are matched per-platform where known; a no-match is simply not guarded.
155
+ const peers = readPeers(AGENT_DIR) || [];
156
+ const self = readIdentity(AGENT_DIR) || {};
157
+ const peerAgentIds = new Set(
158
+ peers.flatMap((p) => [p.slackId, p.telegramId, p.id, p.whatsapp, p.handle].filter(Boolean)),
159
+ );
160
+ const ownId = self.slackId || self.id || self.repoSlug || "";
161
+ const channel = platform.factory({ agentRoot: AGENT_DIR, ownId, peerAgentIds });
162
+ if (registerTypingAdapter) { try { registerTypingAdapter(platform.name, channel); } catch { /* */ } }
163
+ // start() returns once the underlying transport is constructed (socket
164
+ // built / long-poll launched) — not when fully authenticated — so we
165
+ // don't block the daemon boot on it.
166
+ Promise.resolve(channel.start({ onInbound: makeOnInbound(platform.name) }))
167
+ .catch((err) => console.warn(`[DAEMON] channel-bus: ${platform.name}.start() failed — ${err.message}`));
168
+ console.log(`[DAEMON] channel-bus: ${platform.name} channel started (gate: ${platform.gateFile})`);
169
+ } catch (err) {
170
+ console.warn(`[DAEMON] channel-bus: ${platform.name} channel skipped — ${err.message}`);
171
+ }
172
+ }
173
+ }
174
+ } catch (err) {
175
+ console.warn(`[DAEMON] channel-bus initialization failed: ${err.message}`);
176
+ }
177
+
178
+ // ---------------------------------------------------------------------------
179
+ // 3b'. Org mesh runtime membership (org-connect)
180
+ // ---------------------------------------------------------------------------
181
+ // Make this agent actually JOIN the Cohort org mesh at runtime: register at
182
+ // boot, presence-beat (~30s), and act on the server's directives — the kill
183
+ // switch ("halt" → .emergency-stop + stop), policy-stale refetch, directory
184
+ // resync, token rotation. A no-op when the org integration is disabled, and
185
+ // fully fail-open (org down ⇒ the daemon runs standalone). Failure here MUST NOT
186
+ // take the reactive daemon down.
187
+ try {
188
+ // Org enrollment lives in config/org.yaml (written by `maestro setup` /
189
+ // lib/setup/sections/org.mjs, read by lib/org/client.mjs) — NOT
190
+ // config/collective.yaml. Use the canonical org-config loader so an
191
+ // enrolled agent's mesh actually connects.
192
+ const { connectOrgMesh } = await import("../../lib/org/mesh.mjs");
193
+ const { loadOrgConfig } = await import("../../lib/org/client.mjs");
194
+ const cfg = loadOrgConfig(AGENT_DIR);
195
+ const orgMesh = await connectOrgMesh({ agentRoot: AGENT_DIR, cfg });
196
+ if (orgMesh && orgMesh.isEnabled) console.log("[DAEMON] org-mesh connected");
197
+ const stopMesh = (sig) => {
198
+ try { orgMesh.stop(); } catch { /* ignore */ }
199
+ if (orgMesh && orgMesh.isEnabled) console.log(`[DAEMON] org-mesh stopped (${sig})`);
200
+ };
201
+ process.on("SIGTERM", () => stopMesh("SIGTERM"));
202
+ process.on("SIGINT", () => stopMesh("SIGINT"));
203
+ } catch (err) {
204
+ console.warn(`[DAEMON] org-mesh init failed (running standalone): ${err.message}`);
205
+ }
206
+
207
+ // ---------------------------------------------------------------------------
208
+ // 3c. Model router config validation (1.12.0+, advisory)
209
+ // ---------------------------------------------------------------------------
210
+ // If config/model-routing.yaml is active, validate it loads cleanly at
211
+ // daemon startup so a typo doesn't silently break every spawn. We don't
212
+ // reject startup on failure — dispatcher's resolver also catches this
213
+ // and falls back to the Anthropic CLI default.
214
+ try {
215
+ if (existsSync(join(AGENT_DIR, "config/model-routing.yaml"))) {
216
+ const routerModule = resolve(__dirname, "../../lib/model-router.mjs");
217
+ if (existsSync(routerModule)) {
218
+ const { loadRoutingConfig, listBackends } = await import(`file://${routerModule}`);
219
+ const cfg = loadRoutingConfig(AGENT_DIR);
220
+ if (cfg) {
221
+ const backends = listBackends({ config: cfg });
222
+ console.log(`[DAEMON] model-router active (${backends.length} backend(s): ${backends.join(", ")})`);
223
+ } else {
224
+ console.warn("[DAEMON] model-router config present but loader returned null");
225
+ }
226
+ }
227
+ }
228
+ } catch (err) {
229
+ console.warn(`[DAEMON] model-router validation failed (spawn will fall back to default): ${err.message}`);
230
+ }
231
+
232
+ // ---------------------------------------------------------------------------
233
+ // 4. Core daemon import
234
+ // ---------------------------------------------------------------------------
235
+ // Resolve the core daemon module. Try, in order:
236
+ // 1. ./agent-daemon.mjs — canonical filename (1.9.4+)
237
+ // 2. ./<firstName>-daemon.mjs — legacy per-agent rename (pre-1.9.4)
238
+ // 3. The first scripts/daemon/*-daemon.mjs that isn't this file (catches any
239
+ // other legacy-named core daemon still on disk in an old agent repo)
240
+ function resolveCoreDaemon() {
241
+ const localCandidates = [
242
+ resolve(__dirname, "agent-daemon.mjs"),
243
+ ];
244
+
245
+ try {
246
+ const agentJson = join(AGENT_DIR, "config", "agent.json");
247
+ if (existsSync(agentJson)) {
248
+ const { firstName } = JSON.parse(readFileSync(agentJson, "utf-8"));
249
+ if (firstName && typeof firstName === "string") {
250
+ const lower = firstName.toLowerCase();
251
+ localCandidates.push(resolve(__dirname, `${lower}-daemon.mjs`));
252
+ }
253
+ }
254
+ } catch { /* identity unavailable — fall through */ }
255
+
256
+ for (const p of localCandidates) if (existsSync(p)) return p;
257
+
258
+ // Final fallback: scan the directory for *-daemon.mjs (excluding self).
259
+ try {
260
+ for (const name of readdirSync(__dirname)) {
261
+ if (name === "maestro-daemon.mjs") continue;
262
+ if (name.endsWith("-daemon.mjs")) return resolve(__dirname, name);
263
+ }
264
+ } catch { /* */ }
265
+
266
+ return null;
267
+ }
268
+
269
+ const coreDaemon = resolveCoreDaemon();
270
+ if (!coreDaemon) {
271
+ console.error("[DAEMON] could not locate a core daemon module under scripts/daemon/. Expected agent-daemon.mjs (canonical) or a legacy <firstName>-daemon.mjs.");
272
+ process.exit(78);
273
+ }
274
+ // Import and run the daemon (handles its own .env loading).
275
+ await import(coreDaemon);