@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,159 @@
1
+ /**
2
+ * sections/recovery.mjs — reboot/outage recovery + resource governance (WS4, order 85).
3
+ *
4
+ * Cross-workstream seam (design §3.2): registered by dropping this file into
5
+ * sections/ (no core edit). It installs the recovery config the resource
6
+ * governor / watchdog / budget guard read, and verifies the self-healing
7
+ * recovery wiring (watchdog script + heartbeat, governor sizing, pmset
8
+ * autorestart). Order 85 (after learning/80, before verify/90) per design §3.2.
9
+ *
10
+ * Cardinal rule surfaced to the operator: the ONLY thing that permanently
11
+ * stops the agent is a HUMAN writing .emergency-stop. Everything here is about
12
+ * degrading gracefully and recovering automatically — never bricking.
13
+ *
14
+ * Every hook is non-blocking under --headless (detect/apply/verify never read
15
+ * the TTY; prompt honours `io` headless resolution). Idempotent.
16
+ *
17
+ * @module lib/setup/sections/recovery
18
+ */
19
+
20
+ "use strict";
21
+
22
+ import { join } from "node:path";
23
+ import { existsSync, readFileSync, writeFileSync, mkdirSync, copyFileSync, statSync } from "node:fs";
24
+ import { execFileSync } from "node:child_process";
25
+ import os from "node:os";
26
+ import * as io from "../io.mjs";
27
+
28
+ const CONFIG_REL = join("config", "recovery.yaml");
29
+ const WATCHDOG_REL = join("scripts", "watchdog", "memory-watchdog.sh");
30
+
31
+ /** Write config/recovery.yaml from the scaffold (or a minimal stub). Idempotent. */
32
+ function ensureConfig(ctx) {
33
+ const dst = join(ctx.agentRoot, CONFIG_REL);
34
+ if (existsSync(dst)) return; // never clobber an operator-customised file
35
+ mkdirSync(join(ctx.agentRoot, "config"), { recursive: true });
36
+ const scaffold = join(ctx.maestroRoot, "scaffold", "config", "recovery.yaml");
37
+ if (existsSync(scaffold)) {
38
+ try { copyFileSync(scaffold, dst); return; } catch { /* fall through to stub */ }
39
+ }
40
+ writeFileSync(dst, [
41
+ "# config/recovery.yaml — Reboot/outage recovery + resource governance (WS4)",
42
+ "# Defaults apply when keys are omitted. The watchdog NEVER writes",
43
+ "# .emergency-stop — that flag is human-only.",
44
+ "governor:",
45
+ " ram_fraction: 0.60",
46
+ "budget:",
47
+ " daily_spend_cap_usd: 25",
48
+ "pmset_autorestart_required: true",
49
+ "",
50
+ ].join("\n"));
51
+ }
52
+
53
+ /** Is pmset autorestart enabled? Returns true/false/null (unknown / non-mac). */
54
+ function pmsetAutorestart() {
55
+ try {
56
+ const out = execFileSync("pmset", ["-g"], { encoding: "utf-8" });
57
+ const m = out.match(/\bautorestart\s+(\d)/);
58
+ if (!m) return null;
59
+ return m[1] === "1";
60
+ } catch {
61
+ return null; // pmset unavailable (non-mac / CI) — soft signal
62
+ }
63
+ }
64
+
65
+ /** Compute the governor's dynamicMax for this host (sizing sanity in verify). */
66
+ function computeDynamicMax() {
67
+ try {
68
+ const totalmem = os.totalmem();
69
+ const cpus = Math.max(1, os.cpus().length || 1);
70
+ const byRam = Math.floor((totalmem * 0.6) / (750 * 1024 * 1024));
71
+ return Math.min(Math.min(10, cpus), Math.max(1, byRam));
72
+ } catch { return null; }
73
+ }
74
+
75
+ export default {
76
+ id: "recovery",
77
+ label: "Recovery & resource governance",
78
+ order: 85,
79
+
80
+ async detect(ctx) {
81
+ const missing = [];
82
+ if (!existsSync(join(ctx.agentRoot, CONFIG_REL))) missing.push("config/recovery.yaml");
83
+ if (!existsSync(join(ctx.agentRoot, WATCHDOG_REL))) missing.push("scripts/watchdog/memory-watchdog.sh");
84
+ if (missing.length === 0) return { status: "complete", summary: "recovery + governance configured" };
85
+ if (missing.length === 2) return { status: "unconfigured", missing };
86
+ return { status: "partial", summary: "recovery partially configured", missing };
87
+ },
88
+
89
+ async prompt(ctx) {
90
+ // The only operator choice worth surfacing is the daily spend cap; the rest
91
+ // are sensible self-healing defaults. Headless → uses the pre-supplied
92
+ // answer or leaves the default.
93
+ const cap = await io.text(ctx, {
94
+ message: "Daily Claude spend cap (USD) before the agent goes essential-only (inbox-only)?",
95
+ key: "dailySpendCapUsd",
96
+ default: "25",
97
+ });
98
+ ctx.answers.dailySpendCapUsd = cap;
99
+ },
100
+
101
+ async apply(ctx) {
102
+ ensureConfig(ctx);
103
+ // If the operator set a cap, fold it into the config (best-effort line edit;
104
+ // the env var DAILY_SPEND_CAP_USD also overrides at runtime).
105
+ const cap = ctx.answers && ctx.answers.dailySpendCapUsd;
106
+ if (cap && /^\d+(\.\d+)?$/.test(String(cap))) {
107
+ try {
108
+ const p = join(ctx.agentRoot, CONFIG_REL);
109
+ let body = readFileSync(p, "utf-8");
110
+ if (/daily_spend_cap_usd:/.test(body)) {
111
+ body = body.replace(/(\n\s*daily_spend_cap_usd:\s*)\S+/, `$1${cap}`);
112
+ } else {
113
+ body += `\nbudget:\n daily_spend_cap_usd: ${cap}\n`;
114
+ }
115
+ writeFileSync(p, body);
116
+ } catch { /* fail-open */ }
117
+ }
118
+ // Ensure the runtime state dirs the governance layer writes to exist so the
119
+ // first watchdog/governor run doesn't race a missing directory.
120
+ for (const d of ["state", "state/sessions/resume-pending", "state/rate-limits", "state/cost-tracking"]) {
121
+ try { mkdirSync(join(ctx.agentRoot, d), { recursive: true }); } catch { /* */ }
122
+ }
123
+ },
124
+
125
+ async verify(ctx) {
126
+ const checks = [];
127
+
128
+ const cfgOk = existsSync(join(ctx.agentRoot, CONFIG_REL));
129
+ checks.push({ name: "config/recovery.yaml present", ok: cfgOk, remedy: cfgOk ? undefined : "run `maestro setup --only recovery`" });
130
+
131
+ const wdPath = join(ctx.agentRoot, WATCHDOG_REL);
132
+ const wdOk = existsSync(wdPath);
133
+ checks.push({ name: "memory-watchdog.sh installed", ok: wdOk, remedy: wdOk ? undefined : "run `maestro upgrade` to install the watchdog" });
134
+
135
+ // The watchdog must NOT write .emergency-stop on OOM (the brick fix). Cheap
136
+ // static guard: the OOM path's run_watchdog body must not echo into the flag.
137
+ if (wdOk) {
138
+ let safe = true;
139
+ try {
140
+ const body = readFileSync(wdPath, "utf-8");
141
+ // A *write* to .emergency-stop would look like `> "...emergency-stop"`.
142
+ safe = !/>\s*"?\$?\{?[A-Z_]*\}?[^"\n]*\.emergency-stop"?/.test(body);
143
+ } catch { /* unreadable — leave safe=true, doctor covers runtime */ }
144
+ checks.push({ name: "watchdog does not write .emergency-stop on OOM (human-only)", ok: safe, remedy: safe ? undefined : "watchdog still writes .emergency-stop — run `maestro upgrade`" });
145
+ }
146
+
147
+ // Governor sizing sanity (informational; always ok).
148
+ const dMax = computeDynamicMax();
149
+ checks.push({ name: `governor dynamicMax=${dMax ?? "?"} for this host`, ok: true });
150
+
151
+ // pmset autorestart — SOFT check (null on non-mac/CI is tolerated).
152
+ const ar = pmsetAutorestart();
153
+ if (ar === true) checks.push({ name: "pmset autorestart=1 (recovers from power loss)", ok: true });
154
+ else if (ar === false) checks.push({ name: "pmset autorestart enabled", ok: false, remedy: "sudo pmset -a autorestart 1" });
155
+ else checks.push({ name: "pmset autorestart (skipped — non-mac/CI)", ok: true });
156
+
157
+ return { ok: checks.every((c) => c.ok), checks };
158
+ },
159
+ };
@@ -0,0 +1,98 @@
1
+ /**
2
+ * recovery.test.mjs — WS4 setup section (order 85) tests.
3
+ * Run: node --test lib/setup/sections/recovery.test.mjs
4
+ */
5
+ "use strict";
6
+
7
+ import { test } from "node:test";
8
+ import assert from "node:assert/strict";
9
+ import { mkdtempSync, existsSync, readFileSync, writeFileSync, mkdirSync, copyFileSync } from "node:fs";
10
+ import { tmpdir } from "node:os";
11
+ import { join, dirname } from "node:path";
12
+ import { fileURLToPath } from "node:url";
13
+
14
+ import recovery from "./recovery.mjs";
15
+ import * as io from "../io.mjs";
16
+
17
+ const MAESTRO_ROOT = join(dirname(fileURLToPath(import.meta.url)), "..", "..", "..");
18
+
19
+ function tmpRoot() {
20
+ const root = mkdtempSync(join(tmpdir(), "maestro-recsec-"));
21
+ mkdirSync(join(root, "config"), { recursive: true });
22
+ return root;
23
+ }
24
+ function ctxFor(root, answers = {}) {
25
+ return { agentRoot: root, maestroRoot: MAESTRO_ROOT, flags: { headless: true }, answers, io };
26
+ }
27
+ /** Copy the real watchdog into the test root so verify checks the real body. */
28
+ function installWatchdog(root) {
29
+ mkdirSync(join(root, "scripts/watchdog"), { recursive: true });
30
+ copyFileSync(join(MAESTRO_ROOT, "scripts/watchdog/memory-watchdog.sh"), join(root, "scripts/watchdog/memory-watchdog.sh"));
31
+ }
32
+
33
+ test("section shape: id/order=85/hooks", () => {
34
+ assert.equal(recovery.id, "recovery");
35
+ assert.equal(recovery.order, 85);
36
+ for (const h of ["detect", "prompt", "apply", "verify"]) assert.equal(typeof recovery[h], "function");
37
+ });
38
+
39
+ test("detect: unconfigured before apply", async () => {
40
+ const root = tmpRoot();
41
+ const before = await recovery.detect(ctxFor(root));
42
+ assert.equal(before.status, "unconfigured");
43
+ assert.ok(before.missing.includes("config/recovery.yaml"));
44
+ });
45
+
46
+ test("apply writes config/recovery.yaml from scaffold + seeds state dirs", async () => {
47
+ const root = tmpRoot();
48
+ await recovery.apply(ctxFor(root, {}));
49
+ assert.ok(existsSync(join(root, "config", "recovery.yaml")));
50
+ // Runtime state dirs the governance layer writes to are pre-created.
51
+ assert.ok(existsSync(join(root, "state/sessions/resume-pending")));
52
+ assert.ok(existsSync(join(root, "state/rate-limits")));
53
+ // The scaffold carries the governance knobs.
54
+ const body = readFileSync(join(root, "config", "recovery.yaml"), "utf8");
55
+ assert.match(body, /ram_fraction/);
56
+ assert.match(body, /daily_spend_cap_usd/);
57
+ });
58
+
59
+ test("apply folds the operator's daily spend cap into the config", async () => {
60
+ const root = tmpRoot();
61
+ await recovery.apply(ctxFor(root, { dailySpendCapUsd: "40" }));
62
+ const body = readFileSync(join(root, "config", "recovery.yaml"), "utf8");
63
+ assert.match(body, /daily_spend_cap_usd:\s*40/);
64
+ });
65
+
66
+ test("apply is idempotent — never clobbers an existing recovery.yaml", async () => {
67
+ const root = tmpRoot();
68
+ writeFileSync(join(root, "config", "recovery.yaml"), "# custom\nbudget:\n daily_spend_cap_usd: 99\n");
69
+ await recovery.apply(ctxFor(root, { dailySpendCapUsd: "40" }));
70
+ const body = readFileSync(join(root, "config", "recovery.yaml"), "utf8");
71
+ // The operator's existing file is preserved (cap line edited in place, custom comment kept).
72
+ assert.match(body, /# custom/);
73
+ } );
74
+
75
+ test("verify passes when config + watchdog present and watchdog is OOM-brick-safe", async () => {
76
+ const root = tmpRoot();
77
+ await recovery.apply(ctxFor(root, {}));
78
+ installWatchdog(root);
79
+ const res = await recovery.verify(ctxFor(root));
80
+ const byName = Object.fromEntries(res.checks.map((c) => [c.name, c.ok]));
81
+ assert.equal(byName["config/recovery.yaml present"], true);
82
+ assert.equal(byName["memory-watchdog.sh installed"], true);
83
+ // The crux: the installed watchdog must NOT write .emergency-stop on OOM.
84
+ assert.equal(byName["watchdog does not write .emergency-stop on OOM (human-only)"], true);
85
+ });
86
+
87
+ test("verify flags a watchdog that DOES write .emergency-stop on OOM (regression guard)", async () => {
88
+ const root = tmpRoot();
89
+ await recovery.apply(ctxFor(root, {}));
90
+ mkdirSync(join(root, "scripts/watchdog"), { recursive: true });
91
+ // A legacy-style watchdog that bricks on OOM.
92
+ writeFileSync(join(root, "scripts/watchdog/memory-watchdog.sh"),
93
+ `#!/bin/bash\nMAESTRO_DIR=/x\necho "OOM" > "$MAESTRO_DIR/.emergency-stop"\n`);
94
+ const res = await recovery.verify(ctxFor(root));
95
+ const check = res.checks.find((c) => /does not write .emergency-stop/.test(c.name));
96
+ assert.equal(check.ok, false, "must flag a watchdog that writes the human-only flag");
97
+ assert.ok(check.remedy);
98
+ });
@@ -0,0 +1,132 @@
1
+ /**
2
+ * sections/tools.mjs — select channels + MCP servers to enable. (order 50)
3
+ *
4
+ * Records selections only (per WS1 scope): channel choices land as commented
5
+ * .env key stubs the operator fills in, and MCP selections are recorded in
6
+ * config/mcp-servers.yaml's `enabled` list. The per-channel deep wiring (Socket
7
+ * Mode, Baileys auth, relay) is WS2's comms section.
8
+ *
9
+ * @module lib/setup/sections/tools
10
+ */
11
+
12
+ "use strict";
13
+
14
+ import { join } from "node:path";
15
+ import { existsSync, readFileSync, writeFileSync, renameSync, appendFileSync, mkdirSync } from "node:fs";
16
+ import * as io from "../io.mjs";
17
+
18
+ const CHANNELS = [
19
+ { value: "slack", label: "Slack", hint: "DMs, channels, threads" },
20
+ { value: "gmail", label: "Email (Gmail)", hint: "IMAP + send" },
21
+ { value: "whatsapp", label: "WhatsApp", hint: "Baileys" },
22
+ { value: "telegram", label: "Telegram", hint: "grammY (WS2)" },
23
+ { value: "sms", label: "SMS", hint: "Twilio" },
24
+ { value: "voice", label: "Voice / phone", hint: "Realtime" },
25
+ ];
26
+
27
+ /** .env stub keys we drop (commented) per selected channel. */
28
+ const CHANNEL_ENV_STUBS = {
29
+ slack: ["# SLACK_USER_TOKEN=", "# SLACK_BOT_TOKEN=", "# SLACK_APP_LEVEL_TOKEN=xapp-1-…"],
30
+ gmail: ["# GMAIL_APP_PASSWORD="],
31
+ sms: ["# TWILIO_ACCOUNT_SID=", "# TWILIO_AUTH_TOKEN=", "# TWILIO_FROM_NUMBER="],
32
+ whatsapp: ["# WHATSAPP_ENABLED=1"],
33
+ telegram: ["# TELEGRAM_BOT_TOKEN="],
34
+ voice: ["# VOICE_ENABLED=1"],
35
+ };
36
+
37
+ function readMcpServers(agentRoot) {
38
+ const p = join(agentRoot, "config", "mcp-servers.yaml");
39
+ return existsSync(p) ? readFileSync(p, "utf-8") : "";
40
+ }
41
+
42
+ export default {
43
+ id: "tools",
44
+ label: "Channels & MCP servers",
45
+ order: 50,
46
+
47
+ async detect(ctx) {
48
+ const recorded = readSelections(ctx.agentRoot);
49
+ if (!recorded) return { status: "unconfigured", missing: ["no selections recorded"] };
50
+ return { status: "partial", summary: `channels: ${(recorded.channels || []).join(", ") || "none"}` };
51
+ },
52
+
53
+ async prompt(ctx) {
54
+ const channels = await io.multiselect(ctx, {
55
+ message: "Which messaging channels will this agent use?",
56
+ key: "channels",
57
+ choices: CHANNELS,
58
+ default: [],
59
+ });
60
+ // Offer MCP servers the role declared (from generate-capability), if present.
61
+ const declared = parseDeclaredMcp(readMcpServers(ctx.agentRoot));
62
+ let mcp = [];
63
+ if (declared.length) {
64
+ mcp = await io.multiselect(ctx, {
65
+ message: "Enable which MCP servers now?",
66
+ key: "mcp",
67
+ choices: declared.map((m) => ({ value: m, label: m })),
68
+ default: [],
69
+ });
70
+ }
71
+ ctx.answers.tools = { channels, mcp };
72
+ },
73
+
74
+ async apply(ctx) {
75
+ const a = (ctx.answers && ctx.answers.tools) || pull(ctx);
76
+ if (!a) return;
77
+ // 1. Record selections sidecar (durable, machine-readable).
78
+ writeSelections(ctx.agentRoot, { channels: a.channels || [], mcp: a.mcp || [], recordedAt: new Date().toISOString() });
79
+ // 2. Drop commented .env stubs for each selected channel (never overwrite a set key).
80
+ const envPath = join(ctx.agentRoot, ".env");
81
+ const env = existsSync(envPath) ? readFileSync(envPath, "utf-8") : "";
82
+ const additions = [];
83
+ for (const ch of a.channels || []) {
84
+ for (const stub of CHANNEL_ENV_STUBS[ch] || []) {
85
+ const realKey = stub.replace(/^#\s*/, "").split("=")[0];
86
+ if (!new RegExp(`^${realKey}=.+`, "m").test(env) && !env.includes(stub)) additions.push(stub);
87
+ }
88
+ }
89
+ if (additions.length) {
90
+ const prefix = env.endsWith("\n") || env === "" ? "" : "\n";
91
+ appendFileSync(envPath, `${prefix}# ── channel stubs (maestro setup; fill in) ──\n${additions.join("\n")}\n`);
92
+ }
93
+ },
94
+
95
+ async verify(ctx) {
96
+ const recorded = readSelections(ctx.agentRoot);
97
+ const ok = !!recorded;
98
+ return { ok: true, checks: [{ name: `channel/MCP selections recorded (${ok ? (recorded.channels || []).length : 0})`, ok: true }] };
99
+ },
100
+ };
101
+
102
+ function selectionsPath(agentRoot) { return join(agentRoot, "state", "setup", "selections.json"); }
103
+
104
+ function readSelections(agentRoot) {
105
+ const p = selectionsPath(agentRoot);
106
+ try { return existsSync(p) ? JSON.parse(readFileSync(p, "utf-8")) : null; } catch { return null; }
107
+ }
108
+ function writeSelections(agentRoot, obj) {
109
+ const p = selectionsPath(agentRoot);
110
+ mkdirSync(join(agentRoot, "state", "setup"), { recursive: true });
111
+ const tmp = `${p}.${process.pid}.${Date.now()}.tmp`;
112
+ writeFileSync(tmp, JSON.stringify(obj, null, 2) + "\n", "utf-8");
113
+ renameSync(tmp, p);
114
+ }
115
+
116
+ /** Pull MCP server ids declared by generate-capability (best-effort line scan). */
117
+ function parseDeclaredMcp(body) {
118
+ if (!body) return [];
119
+ const out = [];
120
+ for (const line of body.split("\n")) {
121
+ const m = line.match(/^\s*-\s*(?:id|name):\s*([A-Za-z0-9_.-]+)/);
122
+ if (m) out.push(m[1]);
123
+ }
124
+ return [...new Set(out)];
125
+ }
126
+
127
+ function pull(ctx) {
128
+ const ans = ctx.answers || {};
129
+ if (ans.tools) return ans.tools;
130
+ if ("channels" in ans || "mcp" in ans) return { channels: ans.channels || [], mcp: ans.mcp || [] };
131
+ return null;
132
+ }
@@ -0,0 +1,97 @@
1
+ /**
2
+ * sections/verify.mjs — final verification + capability summary table. (order 90)
3
+ *
4
+ * apply() is a no-op (verification writes nothing). verify() aggregates every
5
+ * other section's verify() result + the Claude API probe + the completeness
6
+ * gate, then builds an N/M capability table with a remedy line per failure.
7
+ *
8
+ * HARD failures (cause setup to exit non-zero) are limited to:
9
+ * - no identity / config/agent.json missing
10
+ * - unresolvable archetype
11
+ * - Claude API unreachable
12
+ * Everything else is reported but non-fatal (channels optional, enrichment
13
+ * best-effort), per design §4 "Exit non-zero only on hard failures".
14
+ *
15
+ * @module lib/setup/sections/verify
16
+ */
17
+
18
+ "use strict";
19
+
20
+ import { probeClaude } from "../claude-probe.mjs";
21
+ import { assertComplete } from "../completeness.mjs";
22
+ import * as io from "../io.mjs";
23
+
24
+ export default {
25
+ id: "verify",
26
+ label: "Verification & capability summary",
27
+ order: 90,
28
+
29
+ async detect() {
30
+ // verify always runs (it has no persisted "done" state of its own).
31
+ return { status: "partial", summary: "runs the live capability probe" };
32
+ },
33
+
34
+ async prompt() { /* none */ },
35
+
36
+ async apply() { /* verification is read-only */ },
37
+
38
+ /**
39
+ * Aggregate verification. Optionally pass the live section list via
40
+ * `ctx.sections` so we can run each section's verify(); otherwise we still
41
+ * run the core probes (completeness + Claude API).
42
+ */
43
+ async verify(ctx) {
44
+ const rows = [];
45
+
46
+ // Per-section verify (skip self to avoid recursion).
47
+ const sections = (ctx.sections || []).filter((s) => s.id !== "verify");
48
+ for (const s of sections) {
49
+ try {
50
+ const r = await s.verify(ctx);
51
+ for (const c of r.checks || []) rows.push({ section: s.id, ...c });
52
+ } catch (e) {
53
+ rows.push({ section: s.id, name: `${s.id} verify`, ok: false, remedy: `verify threw: ${e && e.message ? e.message : e}` });
54
+ }
55
+ }
56
+
57
+ // Core probes (always, even if no sections passed in).
58
+ const complete = await assertComplete(ctx.agentRoot);
59
+ const probe = probeClaude(ctx.agentRoot);
60
+
61
+ // Hard-fail conditions.
62
+ const noIdentity = complete.missing.some((m) => m.artifact === "config/agent.json");
63
+ const archetypeOk = complete.archetypeOk;
64
+ const hardFail = noIdentity || !archetypeOk || !probe.ok;
65
+
66
+ return {
67
+ ok: !hardFail,
68
+ hardFail,
69
+ rows,
70
+ complete,
71
+ probe,
72
+ checks: rows,
73
+ };
74
+ },
75
+
76
+ /**
77
+ * Render the capability table to stdout. Separated from verify() so the
78
+ * runner can call it; pure presentation.
79
+ * @param {{rows:Array, complete:object, probe:object}} result
80
+ */
81
+ renderTable(result) {
82
+ const rows = result.rows || [];
83
+ const passed = rows.filter((r) => r.ok).length;
84
+ io.note("");
85
+ io.note(`Capability summary — ${passed}/${rows.length} checks passing`);
86
+ for (const r of rows) {
87
+ const mark = r.ok ? " ✓" : " ✗";
88
+ const where = r.section ? ` [${r.section}]` : "";
89
+ io.note(`${mark} ${r.name}${where}`);
90
+ if (!r.ok && r.remedy) io.note(` ↳ ${r.remedy}`, { dim: true });
91
+ }
92
+ if (result.probe && !result.probe.ok && result.probe.remedy) {
93
+ io.note(` ✗ ${result.probe.name}`);
94
+ io.note(` ↳ ${result.probe.remedy}`, { dim: true });
95
+ }
96
+ },
97
+ };
@@ -0,0 +1,205 @@
1
+ /**
2
+ * lib/setup/sot.mjs — single-source-of-truth helpers for agent identity.
3
+ *
4
+ * `config/agent.json` is the canonical store. `config/agent.ts` is a generated
5
+ * thin re-export wrapper over it (the migrate-agent-to-sot.mjs end-state); this
6
+ * module owns reading/merging the JSON and (re)writing the wrapper idempotently.
7
+ *
8
+ * readAgentJson(agentRoot) → object | null
9
+ * mergeAgentJson(agentRoot, patch) → merged object (deep merge-by-key, atomic)
10
+ * ensureSotWrapper(agentRoot) → { wrote, path } (writes config/agent.ts iff needed)
11
+ * isWrapper(tsSource) → boolean (does this .ts re-export agent.json?)
12
+ * WRAPPER_TEMPLATE → the canonical wrapper body
13
+ *
14
+ * Atomic writes (tmp + rename). `mergeAgentJson` deep-merges plain objects and
15
+ * never drops keys the patch did not mention.
16
+ *
17
+ * @module lib/setup/sot
18
+ */
19
+
20
+ "use strict";
21
+
22
+ import { existsSync, readFileSync, writeFileSync, renameSync, mkdirSync } from "node:fs";
23
+ import { join, dirname } from "node:path";
24
+
25
+ const JSON_REL = "config/agent.json";
26
+ const TS_REL = "config/agent.ts";
27
+
28
+ /** Path helpers. */
29
+ export function agentJsonPath(agentRoot) { return join(agentRoot, JSON_REL); }
30
+ export function agentTsPath(agentRoot) { return join(agentRoot, TS_REL); }
31
+
32
+ /**
33
+ * Read config/agent.json. Returns null when missing; throws on malformed JSON
34
+ * (a corrupt SoT is a real error the caller should surface, unlike the
35
+ * fail-soft progress store).
36
+ * @param {string} agentRoot
37
+ * @returns {object|null}
38
+ */
39
+ export function readAgentJson(agentRoot) {
40
+ const p = agentJsonPath(agentRoot);
41
+ if (!existsSync(p)) return null;
42
+ return JSON.parse(readFileSync(p, "utf-8"));
43
+ }
44
+
45
+ function writeAtomic(path, body) {
46
+ mkdirSync(dirname(path), { recursive: true });
47
+ const tmp = `${path}.${process.pid}.${Date.now()}.tmp`;
48
+ writeFileSync(tmp, body, "utf-8");
49
+ renameSync(tmp, path);
50
+ }
51
+
52
+ /** Write config/agent.json atomically (stable 2-space JSON + trailing newline). */
53
+ export function writeAgentJson(agentRoot, obj) {
54
+ writeAtomic(agentJsonPath(agentRoot), JSON.stringify(obj, null, 2) + "\n");
55
+ return obj;
56
+ }
57
+
58
+ /**
59
+ * Deep merge: plain objects merge key-by-key (patch wins per leaf), arrays and
60
+ * scalars are replaced wholesale by the patch. Unrelated existing keys are
61
+ * always preserved. Returns a new object; inputs are not mutated.
62
+ * @param {object} base @param {object} patch @returns {object}
63
+ */
64
+ export function deepMergeByKey(base, patch) {
65
+ if (patch === null || patch === undefined) return clone(base);
66
+ if (Array.isArray(base) || Array.isArray(patch)) return clone(patch);
67
+ if (typeof base !== "object" || typeof patch !== "object") return clone(patch);
68
+ const out = clone(base);
69
+ for (const k of Object.keys(patch)) {
70
+ const b = out[k];
71
+ const p = patch[k];
72
+ if (b && p && typeof b === "object" && typeof p === "object" && !Array.isArray(b) && !Array.isArray(p)) {
73
+ out[k] = deepMergeByKey(b, p);
74
+ } else if (p !== undefined) {
75
+ out[k] = clone(p);
76
+ }
77
+ }
78
+ return out;
79
+ }
80
+
81
+ function clone(v) { return v === undefined ? undefined : JSON.parse(JSON.stringify(v)); }
82
+
83
+ /**
84
+ * Merge a patch into config/agent.json (creating it from the patch if absent),
85
+ * writing atomically. Never drops unrelated fields.
86
+ * @param {string} agentRoot
87
+ * @param {object} patch
88
+ * @returns {object} the merged object
89
+ */
90
+ export function mergeAgentJson(agentRoot, patch) {
91
+ const current = readAgentJson(agentRoot) || {};
92
+ const merged = deepMergeByKey(current, patch || {});
93
+ return writeAgentJson(agentRoot, merged);
94
+ }
95
+
96
+ /**
97
+ * Heuristic: is the supplied agent.ts source a thin wrapper that re-exports
98
+ * from ./agent.json (rather than a standalone literal `export const agent = {…}`)?
99
+ * @param {string} tsSource
100
+ * @returns {boolean}
101
+ */
102
+ export function isWrapper(tsSource) {
103
+ if (typeof tsSource !== "string") return false;
104
+ const importsJson = /import\s+\w+\s+from\s+['"]\.\/agent\.json['"]/.test(tsSource);
105
+ // A standalone literal assigns an inline object to `agent`.
106
+ const inlineLiteral = /export\s+const\s+agent\s*(?::[^=]+)?=\s*\{/.test(tsSource);
107
+ return importsJson && !inlineLiteral;
108
+ }
109
+
110
+ /**
111
+ * Ensure config/agent.ts is the thin wrapper. Idempotent: if it already is a
112
+ * wrapper, no write occurs. Returns { wrote, path }.
113
+ * @param {string} agentRoot
114
+ * @returns {{wrote:boolean, path:string}}
115
+ */
116
+ export function ensureSotWrapper(agentRoot) {
117
+ const p = agentTsPath(agentRoot);
118
+ if (existsSync(p)) {
119
+ const src = readFileSync(p, "utf-8");
120
+ if (isWrapper(src)) return { wrote: false, path: p };
121
+ }
122
+ writeAtomic(p, WRAPPER_TEMPLATE);
123
+ return { wrote: true, path: p };
124
+ }
125
+
126
+ /**
127
+ * The canonical thin-wrapper body for config/agent.ts. Mirrors
128
+ * scripts/setup/migrate-agent-to-sot.mjs `inlineWrapper()` so a repo migrated by
129
+ * either path ends up byte-identical.
130
+ */
131
+ export const WRAPPER_TEMPLATE = `/**
132
+ * Maestro — Agent Configuration (TypeScript wrapper)
133
+ *
134
+ * The canonical source of truth is \`config/agent.json\`. Edit that file,
135
+ * then run \`node scripts/setup/generate-agent-env.mjs\` to regenerate the
136
+ * shell environment file.
137
+ *
138
+ * This wrapper exposes typed access plus a few computed convenience values.
139
+ */
140
+
141
+ import agentData from './agent.json' with { type: 'json' };
142
+
143
+ export interface VoiceMode {
144
+ id: string;
145
+ label: string;
146
+ description: string;
147
+ }
148
+
149
+ export interface PrincipalConfig {
150
+ firstName: string;
151
+ lastName: string;
152
+ fullName: string;
153
+ title: string;
154
+ email: string;
155
+ slackMemberId: string;
156
+ }
157
+
158
+ export interface AgentConfig {
159
+ firstName: string;
160
+ lastName: string;
161
+ fullName: string;
162
+ title: string;
163
+ archetype: string;
164
+
165
+ email: string;
166
+ phone: string;
167
+ slackMemberId: string;
168
+
169
+ company: string;
170
+ companyDomain: string;
171
+ companyDescription: string;
172
+
173
+ principal: PrincipalConfig;
174
+
175
+ machineName: string;
176
+ repoSlug: string;
177
+ launchdLabelPrefix: string;
178
+
179
+ timezone: string;
180
+ locale: string;
181
+
182
+ schedule: {
183
+ morningBrief: string;
184
+ commsTriage: string[];
185
+ eveningWrap: string;
186
+ overnightMonitoring: string;
187
+ };
188
+
189
+ communication: {
190
+ defaultTone: string;
191
+ externalTone: string;
192
+ voiceModes: VoiceMode[];
193
+ };
194
+
195
+ responsibilities: string[];
196
+ operatingPrinciples: string[];
197
+ }
198
+
199
+ export const agent: AgentConfig = agentData as AgentConfig;
200
+
201
+ export const AGENT_UPPER = agent.firstName.toUpperCase();
202
+ export const AGENT_LOWER = agent.firstName.toLowerCase();
203
+ export const REPO_DIR = \`~/\${agent.repoSlug}\`;
204
+ export const EMAIL_SIGNATURE = \`\${agent.fullName} | \${agent.title} | \${agent.company}\`;
205
+ `;