@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,497 @@
1
+ /**
2
+ * lib/model-router/spawn.mjs — the router EXECUTION layer for SESSION work.
3
+ *
4
+ * `spawnRouted(request, opts)` is what the daemon calls INSTEAD of hand-building
5
+ * `claude -p` spawn args. It ties the three substrate layers together:
6
+ *
7
+ * 1. resolveChain(request) → a RouteDecision (which model/harness, the
8
+ * session-retarget env, spawn knobs, the
9
+ * ordered failover chain).
10
+ * 2. build argv + child env → from decision.spawnArgs + decision.chosen
11
+ * (and each failover candidate's own
12
+ * envForSpawn), with §7.3 child-env
13
+ * scrubbing (no foreign *_API_KEY leaks).
14
+ * 3. failover.stepChain(...) → walk the chain, applying the 16-reason
15
+ * DISPOSITION: retry-same where allowed,
16
+ * fail over to the next candidate on a
17
+ * failover-class reason, open breakers per
18
+ * the table's cooldown scope, and STOP
19
+ * (no failover) for context_overflow /
20
+ * payload_too_large / content_policy /
21
+ * network exactly as the table says.
22
+ *
23
+ * The actual child process is NOT launched here — the daemon injects a
24
+ * `spawnImpl(candidate, { args, env, decision }) -> { ok, reason?, result? }`.
25
+ * This keeps spawn.mjs pure orchestration (no child_process, no fs, no clock of
26
+ * its own) so it is hermetically testable, mirroring the DI discipline of
27
+ * failover.mjs / resource-governor.mjs.
28
+ *
29
+ * ROUTING IS A DISPATCH-TIME DECISION (SPEC §6.2). spawnRouted owns the fast-fail
30
+ * failover loop BEFORE real work begins; it never re-routes a live session (cache
31
+ * economics — a mid-session switch silently re-bills the entire prefix). The
32
+ * kill switch (MAESTRO_ROUTER_FORCE_ANTHROPIC) is honored inside resolveChain,
33
+ * which collapses the chain to an Anthropic-only member, so spawnRouted naturally
34
+ * short-circuits to stock Anthropic with zero third-party candidates to walk.
35
+ *
36
+ * House style: ESM .mjs, Node built-ins only, never throws on the orchestration
37
+ * path (a spawnImpl that throws is caught + classified `unknown`).
38
+ *
39
+ * @module lib/model-router/spawn
40
+ */
41
+
42
+ import { resolveChain } from "./resolve.mjs";
43
+ import { stepChain } from "./failover.mjs";
44
+ import { keyFor, isOpen as healthIsOpen, open as healthOpen, close as healthClose } from "./health.mjs";
45
+ import { lookupModel } from "./catalog.mjs";
46
+ import {
47
+ nextCredential as authNextCredential,
48
+ markCredentialCoolingDown as authMarkCooling,
49
+ } from "./auth-profiles.mjs";
50
+
51
+ // Failover reasons that indicate the CREDENTIAL itself is the problem (a 429 /
52
+ // auth failure on this key) — these cool THAT credential in the pool so the next
53
+ // attempt rotates to a sibling. A backend/model-class reason (overloaded,
54
+ // server_error, …) cools the breaker, not the key.
55
+ const CRED_COOL_REASONS = new Set(["rate_limit", "auth", "auth_permanent", "billing"]);
56
+
57
+ // Env keys that the session-retarget set is allowed to introduce. Everything
58
+ // else credential-shaped is scrubbed (§7.3) before injection.
59
+ const RETARGET_KEYS = new Set([
60
+ "ANTHROPIC_BASE_URL",
61
+ "ANTHROPIC_AUTH_TOKEN",
62
+ "ANTHROPIC_API_KEY",
63
+ "ANTHROPIC_MODEL",
64
+ "ANTHROPIC_SMALL_FAST_MODEL",
65
+ "CLAUDE_CODE_ATTRIBUTION_HEADER",
66
+ "CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS",
67
+ ]);
68
+
69
+ // ---------------------------------------------------------------------------
70
+ // Child-env scrubbing (graft #3 / §7.3)
71
+ // ---------------------------------------------------------------------------
72
+
73
+ /**
74
+ * Build the child env by allowlist: start from a scrubbed copy of `baseEnv`
75
+ * with every credential-shaped var (`*_API_KEY` / `*_AUTH_TOKEN`, including the
76
+ * break-glass key) removed, then inject ONLY the resolved candidate's
77
+ * session-retarget pair (decision.envForSpawn). A session routed to a third
78
+ * party never sees Anthropic credentials, and vice versa.
79
+ *
80
+ * The Anthropic SESSION path (envForSpawn === {}) rides the stock CLI / keychain
81
+ * OAuth or a Console key the spawner owns; we keep an explicitly-injected
82
+ * ANTHROPIC_API_KEY from the caller's base env in that case ONLY if the caller
83
+ * opted in via opts.keepAnthropicApiKey (default: scrub for hygiene). When we do
84
+ * retarget (envForSpawn has ANTHROPIC_BASE_URL), ANTHROPIC_API_KEY="" is part of
85
+ * the retarget set and is injected verbatim (empty-string discipline, W:
86
+ * keychain fallthrough).
87
+ *
88
+ * @param {object} baseEnv usually process.env
89
+ * @param {object} envForSpawn decision.envForSpawn (the retarget pair) or {}
90
+ * @param {object} [opts]
91
+ * @param {boolean} [opts.keepAnthropicApiKey=false]
92
+ * @returns {object} the child env
93
+ */
94
+ export function buildChildEnv(baseEnv, envForSpawn = {}, opts = {}) {
95
+ const out = {};
96
+ const src = baseEnv && typeof baseEnv === "object" ? baseEnv : {};
97
+ for (const [k, v] of Object.entries(src)) {
98
+ if (isCredentialKey(k)) continue; // scrub all credential-shaped vars
99
+ out[k] = v;
100
+ }
101
+ // For a retarget (third-party session), force the empty-string ANTHROPIC_API_KEY
102
+ // so Claude Code does NOT fall back to keychain OAuth.
103
+ const retargeting = !!(envForSpawn && envForSpawn.ANTHROPIC_BASE_URL);
104
+ if (!retargeting && opts.keepAnthropicApiKey && typeof src.ANTHROPIC_API_KEY === "string") {
105
+ out.ANTHROPIC_API_KEY = src.ANTHROPIC_API_KEY;
106
+ }
107
+ // Inject only the retarget set (whitelisted keys).
108
+ for (const [k, v] of Object.entries(envForSpawn || {})) {
109
+ if (RETARGET_KEYS.has(k)) out[k] = v;
110
+ }
111
+ return out;
112
+ }
113
+
114
+ function isCredentialKey(k) {
115
+ return /(_API_KEY|_AUTH_TOKEN|_SECRET|_ACCESS_KEY|_PRIVATE_KEY)$/i.test(k) || k === "ANTHROPIC_API_KEY";
116
+ }
117
+
118
+ // ---------------------------------------------------------------------------
119
+ // argv construction
120
+ // ---------------------------------------------------------------------------
121
+
122
+ /**
123
+ * Build the `claude -p` argv from a RouteDecision's spawnArgs + a concrete
124
+ * candidate ref (which carries the modelFlag for this chain member). The base
125
+ * spawnArgs come from the decision (maxTurns/effort/agentsJson/bare); the
126
+ * per-candidate modelFlag overrides decision.spawnArgs.modelFlag when failing
127
+ * over to a different chain member.
128
+ *
129
+ * Argv order is deterministic so tests can assert it exactly:
130
+ * [--print, --bare?, --output-format json, --model <flag>, --max-turns <n>,
131
+ * --effort <e>?, --agents <json>?, ...extraArgs]
132
+ *
133
+ * @param {object} spawnArgs decision.spawnArgs
134
+ * @param {object} [over]
135
+ * @param {string} [over.modelFlag] per-candidate model flag override
136
+ * @param {string[]} [over.extraArgs] appended verbatim (prompt file, etc.)
137
+ * @param {string} [over.outputFormat="json"] --output-format value
138
+ * @returns {string[]}
139
+ */
140
+ export function buildSpawnArgs(spawnArgs = {}, over = {}) {
141
+ const args = ["--print"];
142
+ if (spawnArgs.bare !== false) args.push("--bare");
143
+ const outputFormat = over.outputFormat || "json";
144
+ args.push("--output-format", outputFormat);
145
+
146
+ const modelFlag = over.modelFlag || spawnArgs.modelFlag;
147
+ if (modelFlag) args.push("--model", String(modelFlag));
148
+
149
+ if (spawnArgs.maxTurns != null && Number.isFinite(Number(spawnArgs.maxTurns))) {
150
+ args.push("--max-turns", String(spawnArgs.maxTurns));
151
+ }
152
+ if (spawnArgs.effort) args.push("--effort", String(spawnArgs.effort));
153
+ if (spawnArgs.maxBudgetUsd != null && Number.isFinite(Number(spawnArgs.maxBudgetUsd))) {
154
+ args.push("--max-budget-usd", String(spawnArgs.maxBudgetUsd));
155
+ }
156
+ if (spawnArgs.agentsJson) {
157
+ const json =
158
+ typeof spawnArgs.agentsJson === "string"
159
+ ? spawnArgs.agentsJson
160
+ : JSON.stringify(spawnArgs.agentsJson);
161
+ args.push("--agents", json);
162
+ }
163
+ if (Array.isArray(over.extraArgs) && over.extraArgs.length) {
164
+ for (const a of over.extraArgs) args.push(String(a));
165
+ }
166
+ return args;
167
+ }
168
+
169
+ // ---------------------------------------------------------------------------
170
+ // Per-candidate retarget env (failover may switch backends)
171
+ // ---------------------------------------------------------------------------
172
+
173
+ /**
174
+ * Derive the session-retarget env for a chain candidate that is NOT the decision's
175
+ * chosen row (a failover target). The decision only carries envForSpawn for its
176
+ * chosen row, so when we fail over to a different ref we re-derive the retarget
177
+ * pair from that ref's catalog row. Mirrors resolve.buildEnvForSpawn for the
178
+ * session harness.
179
+ */
180
+ function envForCandidate(catalog, ref, harness, baseEnv, decision, authCtx) {
181
+ // The chosen ref reuses the decision's already-built envForSpawn verbatim ONLY
182
+ // when there's no pool to rotate over — a pooled set must re-pick per attempt so
183
+ // a failover that cooled the first key rotates to a sibling.
184
+ const isChosen = decision && decision.chosen && ref === decision.chosen.catalogRow?.ref;
185
+ const row = catalog ? lookupModel(catalog, ref) : null;
186
+ const provider = row ? row.provider : refProvider(ref);
187
+ const pooled =
188
+ authCtx && authCtx.profiles && typeof authCtx.profiles.isPooled === "function"
189
+ ? authCtx.profiles.isPooled(provider)
190
+ : false;
191
+ if (isChosen && !pooled) {
192
+ return decision.envForSpawn || {};
193
+ }
194
+ if (!row || harness !== "session") return isChosen ? decision.envForSpawn || {} : {};
195
+ if (row.provider === "anthropic") return isChosen ? decision.envForSpawn || {} : {};
196
+ const baseUrl = row.endpoints && row.endpoints.anthropic;
197
+ if (!baseUrl) return isChosen ? decision.envForSpawn || {} : {};
198
+ const out = {
199
+ ANTHROPIC_BASE_URL: baseUrl,
200
+ ANTHROPIC_API_KEY: "",
201
+ ANTHROPIC_MODEL: row.id,
202
+ ANTHROPIC_SMALL_FAST_MODEL: row.id,
203
+ CLAUDE_CODE_ATTRIBUTION_HEADER: "0",
204
+ CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS: "1",
205
+ };
206
+ // Pooled credential rotation (additive): pick the next non-cooled key, else
207
+ // fall back to the single env var exactly as before.
208
+ let tok;
209
+ let fp = null;
210
+ if (pooled && row.auth_env) {
211
+ const pick = authNextCredential(provider, {
212
+ profiles: authCtx.profiles,
213
+ now: authCtx.now,
214
+ deps: authCtx.deps,
215
+ });
216
+ if (pick && pick.key) {
217
+ tok = pick.key;
218
+ fp = pick.fingerprint;
219
+ }
220
+ }
221
+ if (typeof tok !== "string") {
222
+ tok = baseEnv && row.auth_env ? baseEnv[row.auth_env] : undefined;
223
+ }
224
+ if (typeof tok === "string") {
225
+ out.ANTHROPIC_AUTH_TOKEN = tok;
226
+ out.__credFingerprint = fp; // internal-only; scrubbed before injection
227
+ }
228
+ return out;
229
+ }
230
+
231
+ /** Provider segment of a "provider/model" ref. */
232
+ function refProvider(ref) {
233
+ return typeof ref === "string" && ref.includes("/") ? ref.split("/")[0] : ref;
234
+ }
235
+
236
+ /** The model flag for a chain candidate ref (the bare id, mirrors resolve). */
237
+ function modelFlagForRef(catalog, ref, fallbackFlag) {
238
+ const row = catalog ? lookupModel(catalog, ref) : null;
239
+ if (row && row.id) return row.id;
240
+ // ref is "provider/model"; the model segment is the flag if no row.
241
+ if (typeof ref === "string" && ref.includes("/")) return ref.split("/").slice(1).join("/");
242
+ return fallbackFlag || ref;
243
+ }
244
+
245
+ /** Build the breaker keys (backend + backend:model) for a chain candidate ref. */
246
+ function breakerKeysForRef(ref) {
247
+ if (typeof ref !== "string" || !ref.includes("/")) {
248
+ return { breakerKey: keyFor(ref || "unknown"), backendKey: keyFor(ref || "unknown") };
249
+ }
250
+ const [provider, ...rest] = ref.split("/");
251
+ const model = rest.join("/");
252
+ return { breakerKey: keyFor(provider, model), backendKey: keyFor(provider) };
253
+ }
254
+
255
+ // ---------------------------------------------------------------------------
256
+ // spawnRouted — the public entry point
257
+ // ---------------------------------------------------------------------------
258
+
259
+ /**
260
+ * Resolve a route and EXECUTE the chosen candidate via an injected spawnImpl,
261
+ * failing over along the chain per the disposition table. Returns the decision,
262
+ * the chosen candidate, the argv + child env actually built for the successful
263
+ * (or last) attempt, the failover outcome, and the per-candidate tried[].
264
+ *
265
+ * NEVER throws.
266
+ *
267
+ * @param {object} request RouteRequest (§4.3)
268
+ * @param {object} [opts]
269
+ * @param {(candidate, ctx) => ({ok, reason?, result?}|Promise<...>)} opts.spawnImpl
270
+ * REQUIRED. ctx = { args, env, decision, candidate, attemptIndex }.
271
+ * Returns { ok:true, result } on success, or { ok:false, reason } where
272
+ * reason is a FailoverReason (taxonomy.mjs). A throw is caught → unknown.
273
+ * @param {object} [opts.decision] inject a pre-resolved RouteDecision (tests);
274
+ * otherwise resolveChain(request, opts) is called.
275
+ * @param {object} [opts.config] routing config (passed to resolveChain)
276
+ * @param {object} [opts.catalog] catalog (passed to resolveChain + retarget)
277
+ * @param {object} [opts.env] base env (default process.env)
278
+ * @param {()=>number} [opts.now]
279
+ * @param {(n)=>number} [opts.rng]
280
+ * @param {object} [opts.breaker] { isOpen, open, close } — defaults to health.mjs
281
+ * @param {object} [opts.breakerDeps] deps for the default health breaker (agentRoot, stateDir, now)
282
+ * @param {boolean} [opts.allowContentReroute=false] one content_policy re-route permitted
283
+ * @param {string[]} [opts.extraArgs] appended to every candidate's argv
284
+ * @param {string} [opts.outputFormat="json"]
285
+ * @param {boolean} [opts.keepAnthropicApiKey=false]
286
+ * @param {(rec)=>void} [opts.onAttempt] side-channel per attempt
287
+ * @returns {Promise<{
288
+ * decision, chosen, args, env, outcome,
289
+ * tried: Array<{ref, reason, retry?, meta?}>,
290
+ * ok: boolean
291
+ * }>}
292
+ */
293
+ export async function spawnRouted(request = {}, opts = {}) {
294
+ const env = opts.env || process.env || {};
295
+ const decision = opts.decision || resolveChain(request, opts);
296
+
297
+ // A no-route or rules decision has no model to spawn — surface it without
298
+ // touching spawnImpl. The caller's deterministic fallback handles "rules".
299
+ if (!decision || !decision.chosen || decision.chosen.provider === "rules" || !decision.chain || !decision.chain.length) {
300
+ return {
301
+ decision,
302
+ chosen: decision ? decision.chosen : null,
303
+ args: null,
304
+ env: null,
305
+ outcome: {
306
+ ok: false,
307
+ reason: decision && decision.chosen && decision.chosen.provider === "rules" ? "rules" : "no_route",
308
+ tried: [],
309
+ soonestCooldownExpiry: null,
310
+ attempts: 0,
311
+ },
312
+ tried: [],
313
+ ok: false,
314
+ };
315
+ }
316
+
317
+ const spawnImpl = typeof opts.spawnImpl === "function" ? opts.spawnImpl : null;
318
+ if (!spawnImpl) {
319
+ return {
320
+ decision,
321
+ chosen: decision.chosen,
322
+ args: null,
323
+ env: null,
324
+ outcome: { ok: false, reason: "unknown", tried: [{ ref: decision.chosen.catalogRow?.ref ?? null, reason: "no_spawn_impl" }], soonestCooldownExpiry: null, attempts: 0 },
325
+ tried: [{ ref: decision.chosen.catalogRow?.ref ?? null, reason: "no_spawn_impl" }],
326
+ ok: false,
327
+ };
328
+ }
329
+
330
+ const catalog = resolveCatalogFromOpts(opts, decision);
331
+ const harness = decision.chosen.harness || "session";
332
+
333
+ // Build the candidate chain from decision.chain. Each candidate carries the
334
+ // breaker keys (so stepChain skips open keys + opens the right scope) and the
335
+ // per-candidate argv/env materials.
336
+ const candidates = decision.chain
337
+ .filter((c) => c && c.ref && c.transport !== "rules")
338
+ .map((c) => {
339
+ const keys = breakerKeysForRef(c.ref);
340
+ return {
341
+ ref: c.ref,
342
+ harness: c.harness || harness,
343
+ transport: c.transport,
344
+ breakerKey: keys.breakerKey,
345
+ backendKey: keys.backendKey,
346
+ };
347
+ });
348
+
349
+ if (!candidates.length) {
350
+ return {
351
+ decision,
352
+ chosen: decision.chosen,
353
+ args: null,
354
+ env: null,
355
+ outcome: { ok: false, reason: "no_route", tried: [], soonestCooldownExpiry: null, attempts: 0 },
356
+ tried: [],
357
+ ok: false,
358
+ };
359
+ }
360
+
361
+ // The breaker the failover engine drives. Default: the on-disk health.mjs.
362
+ const breaker = opts.breaker || defaultBreaker(opts);
363
+
364
+ // Auth-profile rotation context (additive; absent ⇒ single-key behavior).
365
+ // `profiles` is a loadAuthProfiles result; `deps` is the ledger I/O context.
366
+ const authCtx = opts.authProfiles
367
+ ? { profiles: opts.authProfiles, now: opts.now ? opts.now() : undefined, deps: opts.authProfileDeps }
368
+ : null;
369
+
370
+ // Capture the argv/env actually built for the LAST attempt (for the return +
371
+ // the daemon's ledger row). Updated inside the attempt fn.
372
+ let lastArgs = null;
373
+ let lastEnv = null;
374
+ let chosenCandidate = null;
375
+
376
+ const outcome = await stepChain(candidates, {
377
+ now: opts.now,
378
+ breaker,
379
+ allowContentReroute: opts.allowContentReroute === true,
380
+ onAttempt: opts.onAttempt,
381
+ attempt: async (candidate, ctx) => {
382
+ const modelFlag = modelFlagForRef(catalog, candidate.ref, decision.spawnArgs?.modelFlag);
383
+ const args = buildSpawnArgs(decision.spawnArgs, {
384
+ modelFlag,
385
+ extraArgs: opts.extraArgs,
386
+ outputFormat: opts.outputFormat,
387
+ });
388
+ // Re-derive the per-attempt clock for the auth context so a rotated pick
389
+ // sees the current time (the ctx.now from stepChain is the attempt time).
390
+ const aCtx = authCtx ? { ...authCtx, now: ctx.now } : null;
391
+ const retarget = envForCandidate(catalog, candidate.ref, candidate.harness, env, decision, aCtx);
392
+ // The fingerprint of the credential this attempt used (pooled rotation only);
393
+ // strip it from the retarget set so it can never leak into the child env.
394
+ const credFingerprint = retarget.__credFingerprint || null;
395
+ if (retarget.__credFingerprint !== undefined) delete retarget.__credFingerprint;
396
+ const childEnv = buildChildEnv(env, retarget, { keepAnthropicApiKey: opts.keepAnthropicApiKey });
397
+ lastArgs = args;
398
+ lastEnv = childEnv;
399
+
400
+ let res;
401
+ try {
402
+ res = await spawnImpl(candidate, {
403
+ args,
404
+ env: childEnv,
405
+ decision,
406
+ candidate,
407
+ attemptIndex: ctx.attemptIndex,
408
+ });
409
+ } catch (err) {
410
+ return { ok: false, reason: "unknown", meta: { thrown: String((err && err.message) || err) } };
411
+ }
412
+ res = res || { ok: false, reason: "unknown" };
413
+ if (res.ok) {
414
+ chosenCandidate = candidate;
415
+ return { ok: true, value: res.result, meta: res.meta };
416
+ }
417
+ // On a credential-class failure, cool THAT credential in the pool (not the
418
+ // whole provider — the breaker, driven by stepChain, owns backend cooldown).
419
+ // The next attempt's nextCredential() then rotates to a sibling key.
420
+ const reason = res.reason || "unknown";
421
+ if (authCtx && credFingerprint && CRED_COOL_REASONS.has(reason)) {
422
+ try {
423
+ authMarkCooling(refProvider(candidate.ref), { fingerprint: credFingerprint }, reason, null, {
424
+ now: ctx.now,
425
+ deps: authCtx.deps,
426
+ });
427
+ } catch { /* fail-open: a cooldown we can't persist degrades, never blocks */ }
428
+ }
429
+ return { ok: false, reason, meta: res.meta };
430
+ },
431
+ });
432
+
433
+ // On success, stepChain.chosen is the winning candidate; reflect it.
434
+ if (outcome.ok && outcome.chosen) chosenCandidate = outcome.chosen;
435
+
436
+ return {
437
+ decision,
438
+ chosen: chosenCandidate
439
+ ? { ...decision.chosen, ...candidateChosenView(catalog, chosenCandidate) }
440
+ : decision.chosen,
441
+ args: lastArgs,
442
+ env: lastEnv,
443
+ outcome,
444
+ tried: outcome.tried || [],
445
+ ok: outcome.ok === true,
446
+ };
447
+ }
448
+
449
+ /** A `chosen`-shaped view of a winning failover candidate (for the return). */
450
+ function candidateChosenView(catalog, candidate) {
451
+ const row = catalog ? lookupModel(catalog, candidate.ref) : null;
452
+ if (!row) return { harness: candidate.harness, transport: candidate.transport };
453
+ return {
454
+ provider: row.provider,
455
+ model: row.id,
456
+ harness: candidate.harness,
457
+ transport: candidate.transport,
458
+ catalogRow: row,
459
+ };
460
+ }
461
+
462
+ function resolveCatalogFromOpts(opts, decision) {
463
+ if (opts && opts.catalog) {
464
+ return opts.catalog.byRef ? opts.catalog : opts.catalog.catalog || opts.catalog;
465
+ }
466
+ // The decision's chosen catalogRow can seed a minimal lookup for the chosen ref,
467
+ // but failover targets need the full catalog; without it, modelFlag falls back
468
+ // to the ref's model segment and retarget env is skipped (Anthropic-safe).
469
+ return null;
470
+ }
471
+
472
+ /**
473
+ * The default breaker: a thin adapter over the on-disk health.mjs API, shaped
474
+ * the way failover.mjs expects (isOpen/open/close). Injectable for tests.
475
+ */
476
+ function defaultBreaker(opts) {
477
+ const deps = opts.breakerDeps || {};
478
+ return {
479
+ isOpen: (key, now) => healthIsOpen(key, now, deps),
480
+ open: (key, reason, ms, now) => healthOpen(key, reason, ms, now, deps),
481
+ close: (key, now) => healthClose(key, now, deps),
482
+ };
483
+ }
484
+
485
+ export const _internals = {
486
+ buildChildEnv,
487
+ buildSpawnArgs,
488
+ envForCandidate,
489
+ modelFlagForRef,
490
+ breakerKeysForRef,
491
+ isCredentialKey,
492
+ refProvider,
493
+ CRED_COOL_REASONS,
494
+ RETARGET_KEYS,
495
+ };
496
+
497
+ export default { spawnRouted, buildChildEnv, buildSpawnArgs };