@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,677 @@
1
+ /**
2
+ * Maestro — Model router
3
+ *
4
+ * Routes each `claude --print` spawn (and any other Anthropic-Messages
5
+ * caller) to the cheapest backend that satisfies the request's declared
6
+ * capability needs. Backends are configured via `config/model-routing.yaml`
7
+ * in the agent repo; if no config is present, the router is inert and the
8
+ * existing Claude-CLI-on-Max-subscription behaviour is preserved verbatim.
9
+ *
10
+ * Why this exists:
11
+ * - Anthropic Sonnet 4.6 / Opus 4.7 are still the quality ceiling for
12
+ * agent loops, but background work (classification, log triage,
13
+ * scheduled summaries) does NOT need them.
14
+ * - Moonshot exposes a native `/anthropic` Messages endpoint, so
15
+ * Kimi K2.6 is a literal drop-in for Claude Code — no router,
16
+ * no translation, no tool-use degradation.
17
+ * - Qwen3-Coder via OpenRouter is one router-hop away and significantly
18
+ * cheaper; usable for agents that don't need extended-thinking or
19
+ * parallel tool calls.
20
+ *
21
+ * What this is NOT:
22
+ * - Not a wire-format translator. We rely on the backend speaking
23
+ * Anthropic Messages directly (Moonshot, Anthropic, NIM) OR a thin
24
+ * OpenAI-compat shim that Claude Code's existing
25
+ * ANTHROPIC_BASE_URL/AUTH_TOKEN/MODEL knobs already handle.
26
+ * - Not a model wrapper. We only emit env vars for the spawned child;
27
+ * the existing `claude` binary does the actual HTTP I/O.
28
+ *
29
+ * Public API:
30
+ * loadRoutingConfig(agentRoot) → parsed YAML or null if absent
31
+ * resolveBackend(request, opts) → { name, envForSpawn, model, … }
32
+ * describeBackend(name, opts) → human-readable backend summary
33
+ * listBackends(opts) → array of backend names
34
+ * estimateCost(request, opts) → rough $ for a given req
35
+ *
36
+ * Caller integration: in dispatcher.mjs / responder.mjs / classifier.mjs,
37
+ * compute a small `AgentRequest` based on the item being processed and
38
+ * merge the returned `envForSpawn` into the child env. Returning a null
39
+ * backend (no config) means "use whatever you used before".
40
+ *
41
+ * Kill switch:
42
+ * - MAESTRO_ROUTER_FORCE_ANTHROPIC=1 (or "true") short-circuits ALL
43
+ * resolution to the stock Anthropic-CLI behaviour — the same path as
44
+ * when no routing config is present (resolveBackend → null, callers
45
+ * fall through to keychain-OAuth `claude --print`). This is the operator
46
+ * escape hatch when a routed backend misbehaves fleet-wide; it overrides
47
+ * any policy without editing config.
48
+ *
49
+ * Spec / risk notes (May 2026):
50
+ * - ANTHROPIC_API_KEY MUST be set to the empty string when routing to a
51
+ * non-Anthropic base URL; merely unsetting it makes Claude Code fall
52
+ * back to its keychain OAuth and authenticate against api.anthropic.com.
53
+ * - CLAUDE_CODE_ATTRIBUTION_HEADER=0 strips the system-prompt attribution
54
+ * block so a third-party gateway's prompt-cache key isn't poisoned.
55
+ * - Some OpenRouter providers return empty tool_use.input for Qwen3-Coder
56
+ * (musistudio/claude-code-router#409). We classify those as
57
+ * `degraded_tool_use: true` and route agents needing reliable tool use
58
+ * away from them.
59
+ * - A backend whose required `auth_env` is unset/empty is SKIPPED at
60
+ * resolve time (treated as unavailable, chain continues) so we never
61
+ * spawn a non-Anthropic backend with an empty credential.
62
+ */
63
+
64
+ import { existsSync, readFileSync } from "node:fs";
65
+ import { join, resolve } from "node:path";
66
+
67
+ // v2 surface — the resolveChain "brain" + strict config validation. Imported as
68
+ // a sibling module so the v1 API above stays byte-compatible; resolveChain is
69
+ // purely additive (SPEC §3). See lib/model-router/resolve.mjs.
70
+ import { resolveChain, validateRoutingConfig } from "./model-router/resolve.mjs";
71
+ // Execution lane (additive, SPEC §6.2/§6.3): the session spawn primitive,
72
+ // the governed cheap one-shot, and the cheap-model tool-call repair. Imported as
73
+ // sibling modules so the v1 + resolveChain surfaces stay byte-compatible.
74
+ import { spawnRouted } from "./model-router/spawn.mjs";
75
+ import { llmTask } from "./model-router/llm-task.mjs";
76
+ import { repairToolCalls } from "./model-router/repair.mjs";
77
+
78
+ // We accept either YAML or JSON. js-yaml is optional; the loader gracefully
79
+ // degrades to JSON-only if it isn't installed.
80
+ let yamlParse = null;
81
+ try {
82
+ const y = await import("js-yaml");
83
+ yamlParse = (text) => y.load(text);
84
+ } catch {
85
+ yamlParse = null;
86
+ }
87
+
88
+ // ---------------------------------------------------------------------------
89
+ // Defaults — used when no config exists OR when a config has gaps.
90
+ // Anthropic-only fallback so existing agents keep working unchanged.
91
+ // ---------------------------------------------------------------------------
92
+
93
+ const ANTHROPIC_DEFAULT = Object.freeze({
94
+ transport: "anthropic-cli", // special: do nothing (current behaviour)
95
+ base_url: "https://api.anthropic.com",
96
+ auth_env: null, // null → use the keychain-resident OAuth token
97
+ capabilities: [
98
+ "thinking",
99
+ "vision",
100
+ "prompt_cache_1h",
101
+ "tool_use",
102
+ "parallel_tools",
103
+ "long_context_1m",
104
+ ],
105
+ pricing: { input_per_m: 3.0, output_per_m: 15.0 }, // Sonnet 4.6 ballpark
106
+ models: {
107
+ classifier: "claude-haiku-4-5-20251001",
108
+ default: "claude-sonnet-4-6",
109
+ premium: "claude-opus-4-7",
110
+ fast: "claude-haiku-4-5-20251001",
111
+ },
112
+ });
113
+
114
+ const DEFAULT_CONFIG = Object.freeze({
115
+ backends: { anthropic: ANTHROPIC_DEFAULT },
116
+ routing_policy: [{ default: true, backend: "anthropic" }],
117
+ fallback_to_anthropic: true,
118
+ strip_attribution_header: true,
119
+ });
120
+
121
+ /**
122
+ * The Anthropic-only default routing config (audit L21).
123
+ *
124
+ * Returns a normalised, deep-cloned copy of {@link DEFAULT_CONFIG} so callers
125
+ * that explicitly want "the built-in Anthropic-CLI default" — rather than the
126
+ * `null` that `loadRoutingConfig` returns when no config file is present — have
127
+ * a concrete config object to pass into `resolveBackend({}, { config })`.
128
+ *
129
+ * This is intentionally an OPT-IN accessor. The default search path
130
+ * (`loadRoutingConfig` returning `null` when no file exists) is preserved
131
+ * verbatim so existing callers keep falling through to "current Claude CLI
132
+ * behaviour"; nothing about that contract changes.
133
+ *
134
+ * @returns {object} A fresh, normalised default config (safe to mutate).
135
+ */
136
+ export function defaultRoutingConfig() {
137
+ // Normalise through the same path as a file-loaded config so the returned
138
+ // shape (clones of ANTHROPIC_DEFAULT, derived booleans, etc.) is identical.
139
+ return normaliseConfig(
140
+ {
141
+ backends: { anthropic: cloneBackend(ANTHROPIC_DEFAULT) },
142
+ routing_policy: [{ default: true, backend: "anthropic" }],
143
+ fallback_to_anthropic: DEFAULT_CONFIG.fallback_to_anthropic,
144
+ strip_attribution_header: DEFAULT_CONFIG.strip_attribution_header,
145
+ },
146
+ { sourcePath: "<default>" }
147
+ );
148
+ }
149
+
150
+ // ---------------------------------------------------------------------------
151
+ // Config loader
152
+ // ---------------------------------------------------------------------------
153
+
154
+ export const CONFIG_RELATIVE_PATH = "config/model-routing.yaml";
155
+
156
+ /**
157
+ * Resolve the active routing config. Search order:
158
+ * 1. opts.configPath (explicit)
159
+ * 2. $MAESTRO_ROUTING_CONFIG env var
160
+ * 3. <agentRoot>/config/model-routing.yaml
161
+ * 4. <agentRoot>/config/model-routing.json
162
+ *
163
+ * Returns null when no config exists so callers can fall through to
164
+ * "current behaviour". Throws on parse error so misconfiguration is loud.
165
+ *
166
+ * @param {string} [agentRoot]
167
+ * @param {{ configPath?: string, useDefault?: boolean }} [opts]
168
+ * When `useDefault` is true, returns {@link defaultRoutingConfig} instead of
169
+ * null if no config file is found (opt-in; default false preserves the
170
+ * null-when-absent contract).
171
+ */
172
+ export function loadRoutingConfig(agentRoot, opts = {}) {
173
+ const root = resolve(
174
+ agentRoot ||
175
+ process.env.AGENT_ROOT ||
176
+ process.env.AGENT_DIR ||
177
+ process.cwd()
178
+ );
179
+ const candidates = [
180
+ opts.configPath,
181
+ process.env.MAESTRO_ROUTING_CONFIG,
182
+ join(root, CONFIG_RELATIVE_PATH),
183
+ join(root, "config/model-routing.json"),
184
+ ].filter(Boolean);
185
+
186
+ for (const path of candidates) {
187
+ if (!existsSync(path)) continue;
188
+ const raw = readFileSync(path, "utf-8");
189
+ const parsed = path.endsWith(".json")
190
+ ? JSON.parse(raw)
191
+ : parseYaml(raw, path);
192
+ return normaliseConfig(parsed, { sourcePath: path });
193
+ }
194
+ // Opt-in: callers that want the built-in Anthropic default rather than null.
195
+ if (opts.useDefault) return defaultRoutingConfig();
196
+ return null;
197
+ }
198
+
199
+ function parseYaml(text, sourcePath) {
200
+ if (!yamlParse) {
201
+ throw new Error(
202
+ `[model-router] ${sourcePath} is YAML but js-yaml is not installed. ` +
203
+ "Install it (`npm i js-yaml`) or convert the config to JSON."
204
+ );
205
+ }
206
+ try {
207
+ return yamlParse(text);
208
+ } catch (err) {
209
+ throw new Error(`[model-router] failed to parse ${sourcePath}: ${err.message}`);
210
+ }
211
+ }
212
+
213
+ function normaliseConfig(raw, { sourcePath } = {}) {
214
+ if (!raw || typeof raw !== "object") {
215
+ throw new Error(`[model-router] config at ${sourcePath} is empty or not an object`);
216
+ }
217
+ // ── Policy v2 ─────────────────────────────────────────────────────────────
218
+ // A `schema_version: 2` config drives the new resolveChain brain (catalog
219
+ // rows + chains + task/data classes + lanes), NOT the v1 backend resolver.
220
+ // We pass it through largely verbatim (resolveChain validates it strictly via
221
+ // validateRoutingConfig); we only fill in the few defaults the brain reads.
222
+ if (raw.schema_version === 2) {
223
+ return normaliseConfigV2(raw, sourcePath);
224
+ }
225
+ const backends = { ...(raw.backends || {}) };
226
+ if (!Object.keys(backends).length) {
227
+ throw new Error(`[model-router] config at ${sourcePath} declares no backends`);
228
+ }
229
+ // Ensure an anthropic backend always exists as the safety net unless the
230
+ // operator has explicitly opted out via fallback_to_anthropic: false.
231
+ if (!backends.anthropic && raw.fallback_to_anthropic !== false) {
232
+ backends.anthropic = cloneBackend(ANTHROPIC_DEFAULT);
233
+ }
234
+
235
+ // Defensive normalisation of each backend.
236
+ for (const [name, b] of Object.entries(backends)) {
237
+ if (!b.transport) {
238
+ throw new Error(`[model-router] backend "${name}" missing transport`);
239
+ }
240
+ b.capabilities = Array.isArray(b.capabilities) ? b.capabilities : [];
241
+ b.models = b.models || {};
242
+ b.pricing = b.pricing || {};
243
+ }
244
+
245
+ const policy = Array.isArray(raw.routing_policy) && raw.routing_policy.length
246
+ ? raw.routing_policy
247
+ : [{ default: true, backend: Object.keys(backends)[0] }];
248
+
249
+ return {
250
+ backends,
251
+ routing_policy: policy,
252
+ fallback_to_anthropic: raw.fallback_to_anthropic !== false,
253
+ strip_attribution_header: raw.strip_attribution_header !== false,
254
+ disable_experimental_betas: raw.disable_experimental_betas !== false,
255
+ source_path: sourcePath,
256
+ };
257
+ }
258
+
259
+ /**
260
+ * Normalise a policy-v2 config. The v2 brain (resolveChain) consults the catalog
261
+ * for model rows; the config only carries policy: aliases, data-class backends,
262
+ * the ordered routing_policy chain rules, the budget ladder, and defaults. We
263
+ * keep the raw shape and only stamp defaults the brain relies on so a sparse
264
+ * config still resolves. Strict validation is the job of validateRoutingConfig.
265
+ */
266
+ function normaliseConfigV2(raw, sourcePath) {
267
+ const defaults = raw.defaults && typeof raw.defaults === "object" ? { ...raw.defaults } : {};
268
+ if (defaults.needs_tool_use_for_sessions === undefined) defaults.needs_tool_use_for_sessions = true;
269
+ if (defaults.data_class === undefined) defaults.data_class = "sensitive";
270
+ if (defaults.cache_ttl === undefined) defaults.cache_ttl = "1h";
271
+ return {
272
+ schema_version: 2,
273
+ aliases: raw.aliases && typeof raw.aliases === "object" ? { ...raw.aliases } : {},
274
+ defaults,
275
+ backends: raw.backends && typeof raw.backends === "object" ? { ...raw.backends } : {},
276
+ routing_policy: Array.isArray(raw.routing_policy) ? raw.routing_policy : [],
277
+ budget_ladder: raw.budget_ladder && typeof raw.budget_ladder === "object" ? { ...raw.budget_ladder } : {},
278
+ fallback_to_anthropic: raw.fallback_to_anthropic !== false,
279
+ strip_attribution_header: raw.strip_attribution_header !== false,
280
+ disable_experimental_betas: raw.disable_experimental_betas !== false,
281
+ source_path: sourcePath,
282
+ };
283
+ }
284
+
285
+ // ---------------------------------------------------------------------------
286
+ // Resolver
287
+ // ---------------------------------------------------------------------------
288
+
289
+ /**
290
+ * @typedef {Object} AgentRequest
291
+ * @property {string} [agent_role] Maestro action role tag, e.g. "classifier", "responder", "ceo_pre_pass"
292
+ * @property {boolean} [needs_thinking] Extended-thinking required
293
+ * @property {boolean} [needs_vision] Vision input required
294
+ * @property {boolean} [needs_long_context] >200k token context window required
295
+ * @property {boolean} [needs_tool_use] Tool/function calling required
296
+ * @property {boolean} [needs_parallel_tools] Multi-tool-per-turn required
297
+ * @property {string} [tier] "classifier" | "default" | "premium" | "fast"
298
+ * @property {string} [model_hint] "haiku" | "sonnet" | "opus" — coarse legacy tier
299
+ * @property {number} [token_estimate] Approximate input token count
300
+ * @property {string} [source] "inbox" | "backlog" | "cadence" | "voice" | etc
301
+ * @property {string} [priority] "critical" | "high" | "normal"
302
+ */
303
+
304
+ /**
305
+ * Resolve a backend for the given request. Returns null when no config is
306
+ * loaded so callers can fall through to "use current Claude CLI behaviour".
307
+ *
308
+ * @param {AgentRequest} request
309
+ * @param {{ config?: object, agentRoot?: string, env?: object }} opts
310
+ * @returns {null | ResolvedBackend}
311
+ */
312
+ export function resolveBackend(request = {}, opts = {}) {
313
+ const env = opts.env || process.env;
314
+ // Kill switch: force stock Anthropic-CLI behaviour regardless of policy.
315
+ // Returning null is the same fall-through path callers take when no config
316
+ // is present, so this is a true escape hatch — no env injection, no routing.
317
+ if (forceAnthropic(env)) return null;
318
+
319
+ const config = opts.config || loadRoutingConfig(opts.agentRoot);
320
+ if (!config) return null;
321
+
322
+ const tried = [];
323
+ const chosen = pickPolicyRule(config.routing_policy, request);
324
+ const backendOrder = [chosen.backend].concat(
325
+ Array.isArray(chosen.fallback) ? chosen.fallback : []
326
+ );
327
+
328
+ for (const backendName of backendOrder) {
329
+ const backend = config.backends[backendName];
330
+ if (!backend) {
331
+ tried.push({ backend: backendName, ok: false, reason: "unknown_backend" });
332
+ continue;
333
+ }
334
+ const supports = backendSupports(backend, request);
335
+ if (!supports.ok) {
336
+ tried.push({ backend: backendName, ok: false, reason: supports.reason });
337
+ continue;
338
+ }
339
+ // A backend with a configured-but-absent credential is unavailable, not a
340
+ // candidate to spawn with an empty token (which would silently fail auth
341
+ // at runtime, burn retries, and poison the breaker). Skip and continue.
342
+ if (!hasAuth(backend, env)) {
343
+ tried.push({ backend: backendName, ok: false, reason: "missing_auth_env" });
344
+ continue;
345
+ }
346
+ return buildResolved({
347
+ name: backendName,
348
+ backend,
349
+ request,
350
+ config,
351
+ ruleMeta: chosen,
352
+ env,
353
+ tried,
354
+ });
355
+ }
356
+
357
+ // Safety net: anthropic-cli needs no auth_env (keychain OAuth), so the
358
+ // hasAuth gate above never excludes it here.
359
+ if (config.fallback_to_anthropic && config.backends.anthropic) {
360
+ return buildResolved({
361
+ name: "anthropic",
362
+ backend: config.backends.anthropic,
363
+ request,
364
+ config,
365
+ ruleMeta: { backend: "anthropic", fallback_reason: "no_compatible_backend" },
366
+ env,
367
+ tried,
368
+ });
369
+ }
370
+
371
+ // No fallback declared and nothing satisfied the request.
372
+ return null;
373
+ }
374
+
375
+ /**
376
+ * @typedef {Object} ResolvedBackend
377
+ * @property {string} name Backend key
378
+ * @property {string} transport "anthropic-cli" | "anthropic-native" | "openai-compat"
379
+ * @property {string} model Concrete model string for ANTHROPIC_MODEL
380
+ * @property {string} fastModel Concrete model for ANTHROPIC_SMALL_FAST_MODEL
381
+ * @property {Object<string,string>} envForSpawn Env vars to merge into the child env
382
+ * @property {Array<object>} tried Diagnostic record of attempts
383
+ * @property {object} capabilities Capability flags actually granted
384
+ * @property {object} pricing Pricing info for cost tracking
385
+ * @property {string} [rule] Matched policy rule (debug)
386
+ */
387
+
388
+ function buildResolved({ name, backend, request, config, ruleMeta, env, tried }) {
389
+ const tier = request.tier || mapHintToTier(request.model_hint) || "default";
390
+ const model = backend.models[tier] || backend.models.default;
391
+ const fastModel = backend.models.fast || backend.models.classifier || model;
392
+
393
+ const envForSpawn = {};
394
+
395
+ if (backend.transport === "anthropic-cli") {
396
+ // Special case: preserve current `claude --print` behaviour exactly.
397
+ // We don't touch ANTHROPIC_BASE_URL/AUTH_TOKEN/API_KEY; Claude Code
398
+ // will use its keychain-resident OAuth token (Max subscription).
399
+ // We DO honour the request's tier via --model (caller passes it on
400
+ // the CLI), but we don't need to inject anything into the env.
401
+ } else {
402
+ // For any non-Anthropic backend, we must:
403
+ // 1. Point Claude Code at the new base URL
404
+ // 2. Provide the auth token
405
+ // 3. Set ANTHROPIC_API_KEY to the empty string (NOT unset) so
406
+ // Claude Code skips its OAuth-with-Anthropic flow.
407
+ // 4. Optionally strip the attribution header so the backend's
408
+ // prompt cache isn't poisoned.
409
+ envForSpawn.ANTHROPIC_BASE_URL = backend.base_url;
410
+ if (backend.auth_env) {
411
+ const token = env[backend.auth_env] || "";
412
+ envForSpawn.ANTHROPIC_AUTH_TOKEN = token;
413
+ }
414
+ envForSpawn.ANTHROPIC_API_KEY = "";
415
+ if (model) envForSpawn.ANTHROPIC_MODEL = model;
416
+ if (fastModel) envForSpawn.ANTHROPIC_SMALL_FAST_MODEL = fastModel;
417
+ if (config.strip_attribution_header) {
418
+ envForSpawn.CLAUDE_CODE_ATTRIBUTION_HEADER = "0";
419
+ }
420
+ if (config.disable_experimental_betas) {
421
+ envForSpawn.CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS = "1";
422
+ }
423
+ }
424
+
425
+ return {
426
+ name,
427
+ transport: backend.transport,
428
+ model,
429
+ fastModel,
430
+ envForSpawn,
431
+ tried,
432
+ capabilities: indexCapabilities(backend.capabilities),
433
+ pricing: backend.pricing || {},
434
+ rule: ruleMeta?.match ? JSON.stringify(ruleMeta.match) : "default",
435
+ fallback_reason: ruleMeta?.fallback_reason || null,
436
+ config_source: config.source_path || null,
437
+ };
438
+ }
439
+
440
+ // ---------------------------------------------------------------------------
441
+ // Policy + capability matching
442
+ // ---------------------------------------------------------------------------
443
+
444
+ function pickPolicyRule(rules, request) {
445
+ // Rules are evaluated top-to-bottom; first match wins. A rule with
446
+ // `default: true` always matches and acts as the terminal fallback.
447
+ for (const rule of rules) {
448
+ if (rule.default) return rule;
449
+ if (matchesRule(rule.match || {}, request)) return rule;
450
+ }
451
+ // No rule matched — synthesize a default pointing to the first backend.
452
+ return { default: true, backend: rules[0]?.backend, synthesized: true };
453
+ }
454
+
455
+ function matchesRule(match, req) {
456
+ for (const [key, expected] of Object.entries(match)) {
457
+ if (key === "token_estimate_gte") {
458
+ if ((req.token_estimate ?? 0) < expected) return false;
459
+ continue;
460
+ }
461
+ if (key === "token_estimate_lt") {
462
+ if ((req.token_estimate ?? 0) >= expected) return false;
463
+ continue;
464
+ }
465
+ if (key === "needs_any") {
466
+ const needs = Array.isArray(expected) ? expected : [expected];
467
+ if (!needs.some((n) => req[n])) return false;
468
+ continue;
469
+ }
470
+ if (key === "agent_role_in") {
471
+ if (!Array.isArray(expected) || !expected.includes(req.agent_role)) return false;
472
+ continue;
473
+ }
474
+ if (req[key] !== expected) return false;
475
+ }
476
+ return true;
477
+ }
478
+
479
+ function backendSupports(backend, req) {
480
+ const caps = indexCapabilities(backend.capabilities);
481
+ if (req.needs_thinking && !caps.thinking) return { ok: false, reason: "no_thinking" };
482
+ if (req.needs_vision && !caps.vision) return { ok: false, reason: "no_vision" };
483
+ if (req.needs_tool_use && !caps.tool_use) return { ok: false, reason: "no_tool_use" };
484
+ if (req.needs_parallel_tools && !caps.parallel_tools)
485
+ return { ok: false, reason: "no_parallel_tools" };
486
+ if (req.needs_long_context) {
487
+ if (!caps.long_context_1m && !caps.long_context_262k && !caps.long_context_200k) {
488
+ return { ok: false, reason: "no_long_context" };
489
+ }
490
+ }
491
+ // Reject backends explicitly marked degraded for the requested capability.
492
+ if (req.needs_tool_use && caps.degraded_tool_use) {
493
+ return { ok: false, reason: "degraded_tool_use" };
494
+ }
495
+ return { ok: true };
496
+ }
497
+
498
+ function indexCapabilities(arr) {
499
+ const out = {};
500
+ for (const c of arr || []) out[c] = true;
501
+ return out;
502
+ }
503
+
504
+ /**
505
+ * True when the operator kill switch is set. Accepts "1" or "true" so it
506
+ * matches the other CLAUDE_CODE_* / GOV_* boolean env conventions in maestro.
507
+ */
508
+ function forceAnthropic(env) {
509
+ const v = env?.MAESTRO_ROUTER_FORCE_ANTHROPIC;
510
+ return v === "1" || v === "true";
511
+ }
512
+
513
+ /**
514
+ * A backend is auth-available when it either needs no token (anthropic-cli
515
+ * keychain OAuth, or an explicit `auth_env: null` local server) or its named
516
+ * auth env var is present and non-empty. An empty/whitespace token counts as
517
+ * absent — spawning with it would just fail auth at runtime.
518
+ */
519
+ function hasAuth(backend, env) {
520
+ if (!backend.auth_env) return true; // null/absent → no token required
521
+ const token = env?.[backend.auth_env];
522
+ return typeof token === "string" && token.trim() !== "";
523
+ }
524
+
525
+ function cloneBackend(b) {
526
+ return {
527
+ ...b,
528
+ capabilities: Array.isArray(b.capabilities) ? [...b.capabilities] : [],
529
+ models: { ...(b.models || {}) },
530
+ pricing: { ...(b.pricing || {}) },
531
+ };
532
+ }
533
+
534
+ function mapHintToTier(hint) {
535
+ if (!hint) return null;
536
+ const h = String(hint).toLowerCase();
537
+ if (h === "haiku") return "fast";
538
+ if (h === "sonnet") return "default";
539
+ if (h === "opus") return "premium";
540
+ if (h === "classifier") return "fast";
541
+ if (h === "premium" || h === "default" || h === "fast" || h === "classifier") return h;
542
+ return null;
543
+ }
544
+
545
+ // ---------------------------------------------------------------------------
546
+ // Cost estimation (rough — pricing differs by region and version)
547
+ // ---------------------------------------------------------------------------
548
+
549
+ /**
550
+ * Estimate the dollar cost for a request given the resolved backend.
551
+ * Treats pricing as $ per million tokens. Returns null if we lack data.
552
+ */
553
+ export function estimateCost(request, resolved) {
554
+ if (!resolved || !resolved.pricing) return null;
555
+ const inTokens = request.token_estimate ?? 0;
556
+ const outTokens = request.token_estimate_out ?? Math.floor(inTokens * 0.5);
557
+ const { input_per_m, output_per_m, cache_hit_per_m } = resolved.pricing;
558
+ if (input_per_m == null || output_per_m == null) return null;
559
+ const cacheHits = request.cache_hit_ratio ?? 0;
560
+ const inputUsd =
561
+ ((inTokens * (1 - cacheHits)) / 1e6) * input_per_m +
562
+ (cache_hit_per_m
563
+ ? ((inTokens * cacheHits) / 1e6) * cache_hit_per_m
564
+ : 0);
565
+ const outputUsd = (outTokens / 1e6) * output_per_m;
566
+ return Number((inputUsd + outputUsd).toFixed(6));
567
+ }
568
+
569
+ // ---------------------------------------------------------------------------
570
+ // Introspection helpers
571
+ // ---------------------------------------------------------------------------
572
+
573
+ export function listBackends(opts = {}) {
574
+ const config = opts.config || loadRoutingConfig(opts.agentRoot);
575
+ if (!config) return ["anthropic"];
576
+ return Object.keys(config.backends);
577
+ }
578
+
579
+ export function describeBackend(name, opts = {}) {
580
+ const config = opts.config || loadRoutingConfig(opts.agentRoot);
581
+ const backend = config?.backends?.[name];
582
+ if (!backend) return null;
583
+ return {
584
+ name,
585
+ transport: backend.transport,
586
+ base_url: backend.base_url || null,
587
+ capabilities: indexCapabilities(backend.capabilities),
588
+ models: { ...backend.models },
589
+ pricing: { ...backend.pricing },
590
+ };
591
+ }
592
+
593
+ /**
594
+ * Build the CLI `--model` flag value for a resolved backend. For
595
+ * anthropic-cli transport this is the bare model string (haiku/sonnet/opus
596
+ * shorthand or a fully-qualified id). For non-Anthropic transports we pass
597
+ * the concrete model string because Claude Code propagates it directly.
598
+ */
599
+ export function modelFlagFor(resolved, request = {}) {
600
+ if (!resolved) return modelFlagFromHint(request);
601
+ const tier = request.tier || mapHintToTier(request.model_hint) || "default";
602
+ // For anthropic-cli, prefer the human-readable shorthand because the CLI
603
+ // accepts "sonnet" / "opus" / "haiku" and Anthropic resolves them to the
604
+ // current default version, which is what existing maestro code relies on.
605
+ if (resolved.transport === "anthropic-cli") {
606
+ if (tier === "fast" || tier === "classifier") return "haiku";
607
+ if (tier === "premium") return "opus";
608
+ if (tier === "default") return "sonnet";
609
+ return resolved.model;
610
+ }
611
+ return resolved.model;
612
+ }
613
+
614
+ function modelFlagFromHint(req) {
615
+ const tier = req?.tier || mapHintToTier(req?.model_hint) || "default";
616
+ if (tier === "fast" || tier === "classifier") return "haiku";
617
+ if (tier === "premium") return "opus";
618
+ return "sonnet";
619
+ }
620
+
621
+ // ---------------------------------------------------------------------------
622
+ // Convenience: derive an AgentRequest from a maestro classifier result.
623
+ // Keeps the dispatcher's call-site one-liner-clean.
624
+ // ---------------------------------------------------------------------------
625
+
626
+ export function requestFromClassifierResult(classResult = {}, opts = {}) {
627
+ const req = {
628
+ agent_role: opts.role || "responder",
629
+ source: opts.source || "inbox",
630
+ priority: classResult.priority || "normal",
631
+ model_hint: classResult.model || null, // "opus" | "sonnet"
632
+ };
633
+ // Dispatcher spawns are full Claude Code agent loops with the complete tool
634
+ // surface, so tool use is required by construction. Default needs_tool_use
635
+ // to TRUE for session work and let one-shot lookups opt out explicitly
636
+ // (opts.oneShot: true) — otherwise a normal-priority item could match a
637
+ // `degraded_tool_use` backend and the capability gate would never fire.
638
+ req.needs_tool_use = opts.oneShot ? false : true;
639
+ // Critical / opus → also needs extended thinking.
640
+ if (classResult.priority === "critical" || classResult.model === "opus") {
641
+ req.needs_thinking = true;
642
+ req.needs_tool_use = true;
643
+ }
644
+ if (opts.token_estimate) req.token_estimate = opts.token_estimate;
645
+ return req;
646
+ }
647
+
648
+ // ---------------------------------------------------------------------------
649
+ // v2 surface (additive) — re-export the resolveChain brain + validator so
650
+ // callers import everything from "@cohortapp/agent-sdk/model-router".
651
+ // ---------------------------------------------------------------------------
652
+
653
+ export { resolveChain, validateRoutingConfig };
654
+ // Execution lane re-exports (additive). spawnRouted is the daemon's session
655
+ // primitive; llmTask the sanctioned cheap one-shot; repairToolCalls promotes
656
+ // cheap-model text/JSON tool-call leakage into native tool_use blocks.
657
+ export { spawnRouted, llmTask, repairToolCalls };
658
+
659
+ // Default export: the resolver itself, for ergonomic `import router from`.
660
+ export default {
661
+ loadRoutingConfig,
662
+ defaultRoutingConfig,
663
+ resolveBackend,
664
+ estimateCost,
665
+ listBackends,
666
+ describeBackend,
667
+ modelFlagFor,
668
+ requestFromClassifierResult,
669
+ CONFIG_RELATIVE_PATH,
670
+ // v2:
671
+ resolveChain,
672
+ validateRoutingConfig,
673
+ // execution lane:
674
+ spawnRouted,
675
+ llmTask,
676
+ repairToolCalls,
677
+ };