@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,893 @@
1
+ /**
2
+ * lib/model-router/taxonomy.mjs — FailoverReason taxonomy + classifiers.
3
+ *
4
+ * The closed, 16-value `FAILOVER_REASONS` enum and its `DISPOSITION` table are
5
+ * the *contract* between every outcome-producing surface (CLI stream-json,
6
+ * result-json subtype, stderr, direct-api HTTP) and `failover.mjs`. The
7
+ * disposition column semantics — retrySame, failoverNext, cooldownScope,
8
+ * cooldownMs|stepped, opensBreaker — encode the load-bearing rules from the
9
+ * router SPEC §4.5 *as data*, not as branching code, so the failover engine is
10
+ * a pure table-walk.
11
+ *
12
+ * Four classifiers map representative real-world inputs onto a FailoverReason:
13
+ * - classifyCliEvent(ev) — the closed `claude -p` stream-json
14
+ * system/api_retry enum, mapped 1:1.
15
+ * - classifyResultSubtype(s) — the result-json terminal subtype.
16
+ * - classifyStderr(text) — extends rate-guard.classifyStderr with the
17
+ * context-overflow + content-policy signals.
18
+ * - classifyHttp(status, body) — direct-api HTTP status + body-pattern
19
+ * pipeline (billing-vs-rate-limit lists,
20
+ * OpenRouter nested metadata.raw unwrap),
21
+ * ported from Hermes error_classifier.py and
22
+ * collapsed onto the closed 16-reason enum.
23
+ *
24
+ * House style (cadence-bus / rate-guard / model-router): ESM, Node built-ins
25
+ * only, pure functions, never throw on bad input — an unclassifiable signal
26
+ * falls through to `unknown` (retry-with-backoff), never an exception.
27
+ *
28
+ * NOTE: an export named `classifyResultالالسubtype` appears in the brief as a
29
+ * mojibake'd spelling of `classifyResultSubtype`; we export the clean ASCII
30
+ * name and an alias under that exact byte sequence so either spelling resolves.
31
+ */
32
+
33
+ import { classifyStderr as rateGuardClassifyStderr } from "../rate-guard.mjs";
34
+
35
+ // ---------------------------------------------------------------------------
36
+ // The closed 16-value enum. Order is the SPEC §4.5 order; do not reorder
37
+ // without updating every fixture corpus that asserts membership.
38
+ // ---------------------------------------------------------------------------
39
+
40
+ /** @typedef {(
41
+ * "auth"|"auth_permanent"|"billing"|"rate_limit"|"overloaded"|"server_error"|
42
+ * "timeout"|"model_not_found"|"context_overflow"|"payload_too_large"|
43
+ * "content_policy"|"provider_policy"|"format"|"empty_response"|"network"|"unknown"
44
+ * )} FailoverReason */
45
+
46
+ export const FAILOVER_REASONS = Object.freeze([
47
+ "auth",
48
+ "auth_permanent",
49
+ "billing",
50
+ "rate_limit",
51
+ "overloaded",
52
+ "server_error",
53
+ "timeout",
54
+ "model_not_found",
55
+ "context_overflow",
56
+ "payload_too_large",
57
+ "content_policy",
58
+ "provider_policy",
59
+ "format",
60
+ "empty_response",
61
+ "network",
62
+ "unknown",
63
+ ]);
64
+
65
+ const REASON_SET = new Set(FAILOVER_REASONS);
66
+
67
+ /** True iff `r` is a member of the closed enum. */
68
+ export function isFailoverReason(r) {
69
+ return REASON_SET.has(r);
70
+ }
71
+
72
+ /** Coerce an arbitrary value to a FailoverReason, defaulting to `unknown`. */
73
+ export function asFailoverReason(r) {
74
+ return REASON_SET.has(r) ? r : "unknown";
75
+ }
76
+
77
+ // ---------------------------------------------------------------------------
78
+ // Cooldown step ladders (ms). `rate_limit` is stepped 30s → 1m → 5m, indexed
79
+ // by the breaker's strike count; `overloaded`/`server_error` use a flat 60s.
80
+ // Exported so failover.mjs + health.mjs agree on the exact ladder.
81
+ // ---------------------------------------------------------------------------
82
+
83
+ const SECOND = 1000;
84
+ const MINUTE = 60 * SECOND;
85
+ const HOUR = 60 * MINUTE;
86
+
87
+ /** rate_limit cooldown ladder: 30s → 1m → 5m, then sticks at 5m. */
88
+ export const RATE_LIMIT_STEPS_MS = Object.freeze([30 * SECOND, 1 * MINUTE, 5 * MINUTE]);
89
+
90
+ /**
91
+ * Resolve a stepped cooldown for `reason` at a given (0-based) strike count.
92
+ * Returns null for reasons whose cooldown is not stepped (callers read the
93
+ * flat `cooldownMs` instead). Non-stepped / no-cooldown reasons → null.
94
+ *
95
+ * @param {FailoverReason} reason
96
+ * @param {number} [strike=0] 0-based count of prior consecutive hits for this key.
97
+ * @returns {number|null} cooldown ms, or null if this reason isn't stepped.
98
+ */
99
+ export function steppedCooldownMs(reason, strike = 0) {
100
+ const d = DISPOSITION[reason];
101
+ if (!d || !Array.isArray(d.stepped) || d.stepped.length === 0) return null;
102
+ const i = Math.max(0, Math.min(d.stepped.length - 1, Math.floor(strike)));
103
+ return d.stepped[i];
104
+ }
105
+
106
+ /**
107
+ * The effective cooldown (ms) for a reason at a given strike count. Prefers the
108
+ * stepped ladder when present, else the flat `cooldownMs`. 0 ⇒ no cooldown.
109
+ * Operator-gated reasons (`auth_permanent`) return Infinity — they cool only on
110
+ * an explicit operator clear, never on a wall-clock timer.
111
+ *
112
+ * @param {FailoverReason} reason
113
+ * @param {number} [strike=0]
114
+ * @returns {number} ms (0 = none, Infinity = until-operator)
115
+ */
116
+ export function cooldownMsFor(reason, strike = 0) {
117
+ const d = DISPOSITION[reason];
118
+ if (!d) return 0;
119
+ if (d.cooldownMs === Infinity) return Infinity;
120
+ const stepped = steppedCooldownMs(reason, strike);
121
+ if (stepped != null) return stepped;
122
+ return d.cooldownMs || 0;
123
+ }
124
+
125
+ // ---------------------------------------------------------------------------
126
+ // DISPOSITION — the contract, encoded as data (SPEC §4.5 disposition table).
127
+ //
128
+ // Per reason:
129
+ // retrySame — may we retry the SAME candidate? false | true (=once)
130
+ // | "jittered" (once, jittered) | "backoff" (once, backoff)
131
+ // | "cli" (the CLI already retried server_error ≤10× internally;
132
+ // we do not re-retry the same candidate ourselves).
133
+ // failoverNext — may we advance to the next candidate in the chain?
134
+ // cooldownScope — which breaker key cools: none | backend | backend:model |
135
+ // model-id | operator
136
+ // cooldownMs — flat cooldown ms (0 = none, Infinity = until operator)
137
+ // stepped — when present, a ms ladder indexed by strike count; takes
138
+ // precedence over cooldownMs.
139
+ // opensBreaker — does a hit open / advance the breaker for cooldownScope?
140
+ //
141
+ // Load-bearing invariants (asserted in the tests, cell-by-cell):
142
+ // - context_overflow: NEVER fails over, no cooldown, no breaker.
143
+ // - network: NEVER fails over, no cooldown, opensBreaker=false (DEFER-style;
144
+ // burns no retry budget — the chain is left intact and re-queued).
145
+ // - payload_too_large: no failover (shrink path), no cooldown.
146
+ // - content_policy: no failover on the SAME content (one Anthropic-only
147
+ // re-route is permitted by failover.mjs, governed by opts, not here).
148
+ // - rate_limit: cooldownScope backend:model (Haiku flows while Sonnet is dry),
149
+ // stepped 30s→1m→5m.
150
+ // - billing: disambiguated from rate_limit by the classifier pattern lists;
151
+ // cools the whole backend for 6h.
152
+ // - auth_permanent: cools until operator (Infinity).
153
+ // ---------------------------------------------------------------------------
154
+
155
+ export const DISPOSITION = Object.freeze({
156
+ auth: Object.freeze({
157
+ retrySame: false,
158
+ failoverNext: true,
159
+ cooldownScope: "backend",
160
+ cooldownMs: 5 * MINUTE,
161
+ opensBreaker: true,
162
+ }),
163
+ auth_permanent: Object.freeze({
164
+ retrySame: false,
165
+ failoverNext: true,
166
+ cooldownScope: "operator",
167
+ cooldownMs: Infinity, // until an operator clears it
168
+ opensBreaker: true,
169
+ }),
170
+ billing: Object.freeze({
171
+ retrySame: false,
172
+ failoverNext: true,
173
+ cooldownScope: "backend",
174
+ cooldownMs: 6 * HOUR,
175
+ opensBreaker: true,
176
+ }),
177
+ rate_limit: Object.freeze({
178
+ retrySame: false,
179
+ failoverNext: true,
180
+ cooldownScope: "backend:model", // model-scoped: Haiku flows while Sonnet is dry
181
+ cooldownMs: RATE_LIMIT_STEPS_MS[0],
182
+ stepped: RATE_LIMIT_STEPS_MS, // 30s → 1m → 5m
183
+ opensBreaker: true,
184
+ }),
185
+ overloaded: Object.freeze({
186
+ retrySame: "jittered", // once, jittered
187
+ failoverNext: true,
188
+ cooldownScope: "backend:model", // 529 is per-model capacity
189
+ cooldownMs: 60 * SECOND,
190
+ opensBreaker: true,
191
+ }),
192
+ server_error: Object.freeze({
193
+ retrySame: "cli", // CLI retries ≤10× internally; we don't re-retry, just failover after
194
+ failoverNext: true,
195
+ cooldownScope: "backend:model",
196
+ cooldownMs: 60 * SECOND,
197
+ opensBreaker: true,
198
+ }),
199
+ timeout: Object.freeze({
200
+ retrySame: true, // once
201
+ failoverNext: true,
202
+ cooldownScope: "none",
203
+ cooldownMs: 0,
204
+ opensBreaker: false,
205
+ }),
206
+ model_not_found: Object.freeze({
207
+ retrySame: false,
208
+ failoverNext: true,
209
+ cooldownScope: "model-id", // catalog-drift signal → doctor warning
210
+ cooldownMs: Infinity, // permanent for this model id until catalog fix
211
+ opensBreaker: true,
212
+ }),
213
+ context_overflow: Object.freeze({
214
+ retrySame: false,
215
+ failoverNext: false, // NEVER fails over — smaller-ctx fallback fails worse
216
+ cooldownScope: "none",
217
+ cooldownMs: 0,
218
+ opensBreaker: false,
219
+ }),
220
+ payload_too_large: Object.freeze({
221
+ retrySame: false,
222
+ failoverNext: false, // shrink path; failover to another model won't help
223
+ cooldownScope: "none",
224
+ cooldownMs: 0,
225
+ opensBreaker: false,
226
+ }),
227
+ content_policy: Object.freeze({
228
+ retrySame: false,
229
+ failoverNext: false, // deterministic refusal sticky across resume (same content)
230
+ cooldownScope: "none",
231
+ cooldownMs: 0,
232
+ opensBreaker: false,
233
+ }),
234
+ provider_policy: Object.freeze({
235
+ retrySame: false,
236
+ failoverNext: true,
237
+ cooldownScope: "backend",
238
+ cooldownMs: 1 * HOUR,
239
+ opensBreaker: true,
240
+ }),
241
+ format: Object.freeze({
242
+ retrySame: false,
243
+ failoverNext: true, // compat bug — logged with compat-flag context
244
+ cooldownScope: "none",
245
+ cooldownMs: 0,
246
+ opensBreaker: false,
247
+ }),
248
+ empty_response: Object.freeze({
249
+ retrySame: true, // once
250
+ failoverNext: true,
251
+ cooldownScope: "none",
252
+ cooldownMs: 0,
253
+ opensBreaker: false,
254
+ }),
255
+ network: Object.freeze({
256
+ retrySame: false,
257
+ failoverNext: false, // DEFER-style: offline; re-queued, burns no retry budget
258
+ cooldownScope: "none",
259
+ cooldownMs: 0,
260
+ opensBreaker: false, // breaker NOT opened
261
+ }),
262
+ unknown: Object.freeze({
263
+ retrySame: "backoff", // once, backoff
264
+ failoverNext: true,
265
+ cooldownScope: "none",
266
+ cooldownMs: 0,
267
+ opensBreaker: false,
268
+ }),
269
+ });
270
+
271
+ // Frozen, so a buggy consumer cannot mutate the contract at runtime.
272
+ Object.freeze(DISPOSITION);
273
+
274
+ // ---------------------------------------------------------------------------
275
+ // 1. CLI stream-json classifier — closed `claude -p` system/api_retry enum.
276
+ // Mapped 1:1 onto the FailoverReason enum. This is free signal the
277
+ // dispatcher currently discards (SPEC §4.5 source 1).
278
+ // ---------------------------------------------------------------------------
279
+
280
+ /**
281
+ * The closed CLI api_retry/error enum → FailoverReason, 1:1.
282
+ * authentication_failed → auth
283
+ * oauth_org_not_allowed → auth_permanent
284
+ * billing_error → billing
285
+ * rate_limit → rate_limit
286
+ * overloaded → overloaded
287
+ * invalid_request → format
288
+ * model_not_found → model_not_found
289
+ * server_error → server_error
290
+ * max_output_tokens → context_overflow (output hit the model ceiling)
291
+ * unknown → unknown
292
+ */
293
+ const CLI_EVENT_MAP = Object.freeze({
294
+ authentication_failed: "auth",
295
+ oauth_org_not_allowed: "auth_permanent",
296
+ billing_error: "billing",
297
+ rate_limit: "rate_limit",
298
+ overloaded: "overloaded",
299
+ invalid_request: "format",
300
+ model_not_found: "model_not_found",
301
+ server_error: "server_error",
302
+ max_output_tokens: "context_overflow",
303
+ unknown: "unknown",
304
+ });
305
+
306
+ /**
307
+ * Classify a `claude -p --output-format stream-json` event. Accepts either the
308
+ * raw event object (`{type:"system", subtype:"api_retry", error:"rate_limit"}`)
309
+ * or a bare enum string. Unknown event shapes → null (caller may fall through
310
+ * to stderr/result-subtype classification); a recognized-but-unmapped enum
311
+ * value → `unknown`.
312
+ *
313
+ * @param {object|string} ev
314
+ * @returns {FailoverReason|null}
315
+ */
316
+ export function classifyCliEvent(ev) {
317
+ if (ev == null) return null;
318
+ let code = null;
319
+ if (typeof ev === "string") {
320
+ code = ev;
321
+ } else if (typeof ev === "object") {
322
+ // The api_retry event carries the reason in `error` (or `reason`/`code`);
323
+ // a result event uses `subtype`. An explicit api_error event may nest the
324
+ // code under error.type / error.code. Prefer scalar fields, then unwrap a
325
+ // nested error object.
326
+ if (typeof ev.error === "string") code = ev.error;
327
+ else if (typeof ev.reason === "string") code = ev.reason;
328
+ else if (typeof ev.code === "string") code = ev.code;
329
+ else if (ev.error && typeof ev.error === "object") {
330
+ code = ev.error.type || ev.error.code || null;
331
+ }
332
+ }
333
+ if (typeof code !== "string" || !code) return null;
334
+ const key = code.trim().toLowerCase();
335
+ if (Object.prototype.hasOwnProperty.call(CLI_EVENT_MAP, key)) {
336
+ return CLI_EVENT_MAP[key];
337
+ }
338
+ // A recognized api_retry event with an out-of-enum code is still a failure
339
+ // signal — bucket it as unknown rather than dropping it.
340
+ if (
341
+ typeof ev === "object" &&
342
+ (ev.subtype === "api_retry" || ev.subtype === "api_error" || ev.type === "system")
343
+ ) {
344
+ return "unknown";
345
+ }
346
+ return null;
347
+ }
348
+
349
+ // ---------------------------------------------------------------------------
350
+ // 2. Result-json subtype classifier (SPEC §4.5 source 2).
351
+ // error_max_turns / error_max_budget_usd → resumable budget stops; we map
352
+ // those to `unknown` for failover purposes (the caller resumes rather than
353
+ // failing over — these are not provider faults). The exported export name is
354
+ // duplicated under the brief's mojibake spelling for resolve-lane safety.
355
+ // ---------------------------------------------------------------------------
356
+
357
+ const RESULT_SUBTYPE_MAP = Object.freeze({
358
+ success: "ok",
359
+ error_max_turns: "unknown", // resumable budget stop — not a provider fault
360
+ error_max_budget_usd: "unknown", // resumable budget stop
361
+ error_during_execution: "server_error",
362
+ error_max_output_tokens: "context_overflow",
363
+ });
364
+
365
+ /**
366
+ * Classify a result-json terminal `subtype`. Returns "ok" for the success
367
+ * subtype (so callers can branch without a separate success path), a
368
+ * FailoverReason for the error subtypes, or null when the subtype is unknown.
369
+ *
370
+ * @param {string} subtype
371
+ * @returns {FailoverReason|"ok"|null}
372
+ */
373
+ export function classifyResultSubtype(subtype) {
374
+ if (typeof subtype !== "string" || !subtype) return null;
375
+ const key = subtype.trim().toLowerCase();
376
+ if (Object.prototype.hasOwnProperty.call(RESULT_SUBTYPE_MAP, key)) {
377
+ return RESULT_SUBTYPE_MAP[key];
378
+ }
379
+ // Any other `error_*` subtype is a real failure of unknown kind.
380
+ if (key.startsWith("error")) return "unknown";
381
+ return null;
382
+ }
383
+
384
+ // Alias under the exact (mojibake) byte sequence used in the component brief,
385
+ // so the resolve lane resolves either spelling. Both point at the same fn.
386
+ export { classifyResultSubtype as classifyResultالالسubtype };
387
+
388
+ // ---------------------------------------------------------------------------
389
+ // 3. stderr classifier — extends rate-guard.classifyStderr (SPEC §4.5 source 3)
390
+ // "Prompt is too long" → context_overflow
391
+ // usage-policy refusal → content_policy
392
+ // (rate-guard's 429/overload) → rate_limit
393
+ // ---------------------------------------------------------------------------
394
+
395
+ const STDERR_CONTEXT_OVERFLOW = [
396
+ "prompt is too long",
397
+ "prompt exceeds max length",
398
+ "context length exceeded",
399
+ "maximum context",
400
+ "too many tokens",
401
+ "input is too long",
402
+ "exceeds the context window",
403
+ ];
404
+
405
+ const STDERR_CONTENT_POLICY = [
406
+ "violates our usage policies",
407
+ "violates anthropic's usage policies",
408
+ "your request was flagged by",
409
+ "prompt was flagged by our safety",
410
+ "responses cannot be generated due to safety",
411
+ "usage policy",
412
+ "content_filter",
413
+ ];
414
+
415
+ const STDERR_AUTH = [
416
+ "invalid api key",
417
+ "invalid x-api-key",
418
+ "authentication_error",
419
+ "could not resolve authentication",
420
+ "oauth token",
421
+ "401 unauthorized",
422
+ ];
423
+
424
+ const STDERR_BILLING = [
425
+ "credit balance is too low",
426
+ "insufficient credits",
427
+ "payment required",
428
+ "billing",
429
+ "exceeded your current quota",
430
+ ];
431
+
432
+ const STDERR_NETWORK = [
433
+ "getaddrinfo enotfound",
434
+ "econnrefused",
435
+ "econnreset",
436
+ "etimedout",
437
+ "network is unreachable",
438
+ "socket hang up",
439
+ "enetdown",
440
+ "enetunreach",
441
+ "ehostunreach",
442
+ "failed to fetch",
443
+ "fetch failed",
444
+ ];
445
+
446
+ /**
447
+ * Classify a captured stderr/text blob. Extends `rate-guard.classifyStderr`
448
+ * (which returns a boolean for the 429/overload family) into the full enum.
449
+ * Returns null when nothing matches (caller may fall through to other sources).
450
+ *
451
+ * Order matters: content-overflow and content-policy are checked before the
452
+ * coarse rate-guard match so a 429 string that also says "prompt is too long"
453
+ * resolves to the more actionable reason.
454
+ *
455
+ * @param {string} text
456
+ * @returns {FailoverReason|null}
457
+ */
458
+ export function classifyStderr(text) {
459
+ if (typeof text !== "string" || !text) return null;
460
+ const t = text.toLowerCase();
461
+
462
+ if (STDERR_CONTEXT_OVERFLOW.some((p) => t.includes(p))) return "context_overflow";
463
+ if (STDERR_CONTENT_POLICY.some((p) => t.includes(p))) return "content_policy";
464
+ if (STDERR_AUTH.some((p) => t.includes(p))) return "auth";
465
+ if (STDERR_BILLING.some((p) => t.includes(p))) return "billing";
466
+ if (STDERR_NETWORK.some((p) => t.includes(p))) return "network";
467
+
468
+ // Defer to the shared rate-guard breaker for the 429 / overloaded family so
469
+ // one pattern set covers every spawn source.
470
+ if (rateGuardClassifyStderr(text)) {
471
+ // "overloaded" is per-model capacity; a bare 429/rate-limit is rate_limit.
472
+ if (/\boverloaded\b|529/i.test(text)) return "overloaded";
473
+ return "rate_limit";
474
+ }
475
+
476
+ return null;
477
+ }
478
+
479
+ // ---------------------------------------------------------------------------
480
+ // 4. Direct-api HTTP classifier — ported from Hermes error_classifier.py and
481
+ // collapsed onto the closed 16-reason enum (SPEC §4.5 source 4).
482
+ //
483
+ // Mapping of Hermes' finer-grained reasons onto our enum:
484
+ // image_too_large / multimodal_tool_content / invalid_encrypted /
485
+ // thinking_signature / format_error / *_validation → format
486
+ // provider_policy_blocked → provider_policy
487
+ // content_policy_blocked → content_policy
488
+ // long_context_tier → rate_limit
489
+ // (everything else maps to the same-named reason)
490
+ // ---------------------------------------------------------------------------
491
+
492
+ const BILLING_PATTERNS = [
493
+ "insufficient credits",
494
+ "insufficient_quota",
495
+ "insufficient balance",
496
+ "credit balance",
497
+ "credit balance is too low",
498
+ "credits exhausted",
499
+ "credits have been exhausted",
500
+ "no usable credits",
501
+ "top up your credits",
502
+ "payment required",
503
+ "billing hard limit",
504
+ "exceeded your current quota",
505
+ "account is deactivated",
506
+ "plan does not include",
507
+ "out of funds",
508
+ "run out of funds",
509
+ "balance_depleted",
510
+ "model_not_supported_on_free_tier",
511
+ "not available on the free tier",
512
+ "key limit exceeded",
513
+ "spending limit",
514
+ ];
515
+
516
+ const RATE_LIMIT_PATTERNS = [
517
+ "rate limit",
518
+ "rate_limit",
519
+ "too many requests",
520
+ "throttled",
521
+ "requests per minute",
522
+ "tokens per minute",
523
+ "requests per day",
524
+ "try again in",
525
+ "please retry after",
526
+ "resource_exhausted",
527
+ "rate increased too quickly",
528
+ "throttlingexception",
529
+ "too many concurrent requests",
530
+ "servicequotaexceededexception",
531
+ ];
532
+
533
+ const USAGE_LIMIT_PATTERNS = ["usage limit", "quota", "limit exceeded", "key limit exceeded"];
534
+ const USAGE_LIMIT_TRANSIENT = [
535
+ "try again",
536
+ "retry",
537
+ "resets at",
538
+ "reset in",
539
+ "wait",
540
+ "requests remaining",
541
+ "periodic",
542
+ "window",
543
+ ];
544
+
545
+ const CONTEXT_OVERFLOW_PATTERNS = [
546
+ "context length",
547
+ "context size",
548
+ "maximum context",
549
+ "token limit",
550
+ "too many tokens",
551
+ "reduce the length",
552
+ "exceeds the limit",
553
+ "context window",
554
+ "prompt is too long",
555
+ "prompt exceeds max length",
556
+ "maximum number of tokens",
557
+ "exceeds the max_model_len",
558
+ "max_model_len",
559
+ "input is too long",
560
+ "maximum model length",
561
+ "context length exceeded",
562
+ "context_length_exceeded",
563
+ "max_tokens_exceeded",
564
+ ];
565
+
566
+ const MODEL_NOT_FOUND_PATTERNS = [
567
+ "is not a valid model",
568
+ "invalid model",
569
+ "model not found",
570
+ "model_not_found",
571
+ "model_not_available",
572
+ "does not exist",
573
+ "no such model",
574
+ "unknown model",
575
+ "unsupported model",
576
+ ];
577
+
578
+ const REQUEST_VALIDATION_PATTERNS = [
579
+ "unknown parameter",
580
+ "unsupported parameter",
581
+ "unrecognized request argument",
582
+ "unknown_parameter",
583
+ "unsupported_parameter",
584
+ ];
585
+
586
+ const PROVIDER_POLICY_PATTERNS = [
587
+ "no endpoints available matching your guardrail",
588
+ "no endpoints available matching your data policy",
589
+ "no endpoints found matching your data policy",
590
+ ];
591
+
592
+ const CONTENT_POLICY_PATTERNS = [
593
+ "flagged for possible cybersecurity risk",
594
+ "trusted access for cyber",
595
+ "violates our usage policies",
596
+ "violates openai's usage policies",
597
+ "your request was flagged by",
598
+ "prompt was flagged by our safety",
599
+ "responses cannot be generated due to safety",
600
+ "content_filter",
601
+ "responsibleaipolicyviolation",
602
+ ];
603
+
604
+ const AUTH_PATTERNS = [
605
+ "invalid api key",
606
+ "invalid_api_key",
607
+ "authentication",
608
+ "unauthorized",
609
+ "forbidden",
610
+ "invalid token",
611
+ "token expired",
612
+ "token revoked",
613
+ "access denied",
614
+ ];
615
+
616
+ const PAYLOAD_TOO_LARGE_PATTERNS = [
617
+ "request entity too large",
618
+ "payload too large",
619
+ "error code: 413",
620
+ ];
621
+
622
+ const NETWORK_PATTERNS = [
623
+ "getaddrinfo enotfound",
624
+ "econnrefused",
625
+ "econnreset",
626
+ "etimedout",
627
+ "network is unreachable",
628
+ "socket hang up",
629
+ "enetdown",
630
+ "enetunreach",
631
+ "ehostunreach",
632
+ "fetch failed",
633
+ "failed to fetch",
634
+ "connection refused",
635
+ "connection reset",
636
+ ];
637
+
638
+ const TIMEOUT_PATTERNS = [
639
+ "timed out",
640
+ "turn timed out",
641
+ "request timed out",
642
+ "deadline exceeded",
643
+ "operation timed out",
644
+ "upstream timed out",
645
+ ];
646
+
647
+ const EMPTY_RESPONSE_PATTERNS = [
648
+ "empty response",
649
+ "no content returned",
650
+ "no completion returned",
651
+ "received an empty",
652
+ ];
653
+
654
+ const hasAny = (text, list) => list.some((p) => text.includes(p));
655
+
656
+ /**
657
+ * Flatten an HTTP error body (object or string) into one lowercased string,
658
+ * unwrapping OpenRouter's nested `error.metadata.raw` JSON so the real upstream
659
+ * message (e.g. "context length exceeded") is matched even when OpenRouter
660
+ * wraps it as `{"error":{"message":"Provider returned error","metadata":{"raw":"…"}}}`.
661
+ *
662
+ * @param {object|string|null} body
663
+ * @returns {{ text: string, code: string }}
664
+ */
665
+ export function flattenHttpBody(body) {
666
+ const parts = [];
667
+ let code = "";
668
+ if (body == null) return { text: "", code: "" };
669
+ if (typeof body === "string") {
670
+ parts.push(body);
671
+ return { text: parts.join(" ").toLowerCase(), code: "" };
672
+ }
673
+ if (typeof body !== "object") return { text: String(body).toLowerCase(), code: "" };
674
+
675
+ const err = body.error && typeof body.error === "object" ? body.error : null;
676
+ if (err) {
677
+ if (typeof err.message === "string") parts.push(err.message);
678
+ if (typeof err.code === "string") code ||= err.code;
679
+ else if (typeof err.type === "string") code ||= err.type;
680
+ // OpenRouter nested metadata.raw unwrap.
681
+ const meta = err.metadata && typeof err.metadata === "object" ? err.metadata : null;
682
+ if (meta && typeof meta.raw === "string" && meta.raw.trim()) {
683
+ parts.push(meta.raw); // include the raw string itself
684
+ try {
685
+ const inner = JSON.parse(meta.raw);
686
+ const innerErr = inner && typeof inner === "object" ? inner.error : null;
687
+ if (innerErr && typeof innerErr === "object") {
688
+ if (typeof innerErr.message === "string") parts.push(innerErr.message);
689
+ if (!code && typeof innerErr.code === "string") code = innerErr.code;
690
+ } else if (inner && typeof inner.message === "string") {
691
+ parts.push(inner.message);
692
+ }
693
+ } catch {
694
+ /* raw wasn't JSON — the string is already in parts */
695
+ }
696
+ }
697
+ }
698
+ if (typeof body.message === "string") parts.push(body.message);
699
+ if (!code && typeof body.code === "string") code = body.code;
700
+ if (!code && typeof body.error_code === "string") code = body.error_code;
701
+
702
+ return { text: parts.join(" ").toLowerCase(), code: String(code || "").toLowerCase() };
703
+ }
704
+
705
+ // Unambiguous throttling phrases — when present, the signal is a rate limit
706
+ // regardless of a co-occurring generic "limit exceeded" usage phrase.
707
+ const UNAMBIGUOUS_RATE_LIMIT = [
708
+ "rate limit",
709
+ "rate_limit",
710
+ "too many requests",
711
+ "throttled",
712
+ "throttlingexception",
713
+ "requests per minute",
714
+ "tokens per minute",
715
+ "requests per day",
716
+ ];
717
+
718
+ /**
719
+ * Disambiguate a usage/payment signal: a transient "try again in 5m" usage
720
+ * limit is rate_limit, a confirmed balance/credit exhaustion is billing. An
721
+ * unambiguous throttling phrase ("rate limit", "too many requests") always
722
+ * wins over the generic usage-limit family (which also matches "limit
723
+ * exceeded", a substring of "rate limit exceeded").
724
+ */
725
+ function disambiguateBilling(text) {
726
+ if (hasAny(text, UNAMBIGUOUS_RATE_LIMIT)) return "rate_limit";
727
+ const usage = hasAny(text, USAGE_LIMIT_PATTERNS);
728
+ const transient = hasAny(text, USAGE_LIMIT_TRANSIENT);
729
+ if (usage && transient) return "rate_limit";
730
+ if (hasAny(text, BILLING_PATTERNS)) return "billing";
731
+ if (usage) return "billing"; // usage limit without a transient signal ⇒ exhaustion
732
+ return null;
733
+ }
734
+
735
+ /**
736
+ * Classify a direct-api HTTP outcome onto the closed 16-reason enum.
737
+ *
738
+ * @param {number|null|undefined} status HTTP status (null when transport-level)
739
+ * @param {object|string|null} [body] parsed JSON body or raw text
740
+ * @returns {FailoverReason}
741
+ */
742
+ export function classifyHttp(status, body) {
743
+ const { text, code } = flattenHttpBody(body);
744
+ const st = Number.isFinite(status) ? Number(status) : null;
745
+
746
+ // ── 1. Body-pattern signals that override status (deterministic refusals,
747
+ // OpenRouter policy blocks, content policy) ─────────────────────────
748
+ if (hasAny(text, CONTENT_POLICY_PATTERNS)) return "content_policy";
749
+ if (hasAny(text, PROVIDER_POLICY_PATTERNS)) return "provider_policy";
750
+
751
+ // Structured error codes (highest-confidence signal in the body).
752
+ if (code) {
753
+ if (["resource_exhausted", "throttled", "rate_limit_exceeded"].includes(code))
754
+ return "rate_limit";
755
+ if (
756
+ [
757
+ "insufficient_quota",
758
+ "billing_not_active",
759
+ "payment_required",
760
+ "insufficient_credits",
761
+ "no_usable_credits",
762
+ "balance_depleted",
763
+ "model_not_supported_on_free_tier",
764
+ ].includes(code)
765
+ )
766
+ return "billing";
767
+ if (["model_not_found", "model_not_available", "invalid_model"].includes(code))
768
+ return "model_not_found";
769
+ if (["context_length_exceeded", "max_tokens_exceeded"].includes(code))
770
+ return "context_overflow";
771
+ }
772
+
773
+ // ── 2. Status-code classification with message-aware refinement ───────────
774
+ if (st != null) {
775
+ if (st === 401) return "auth";
776
+ if (st === 403) {
777
+ // OpenRouter 403 "key limit exceeded" is billing, not auth.
778
+ if (disambiguateBilling(text) === "billing") return "billing";
779
+ if (hasAny(text, BILLING_PATTERNS)) return "billing";
780
+ return "auth";
781
+ }
782
+ if (st === 402) {
783
+ return disambiguateBilling(text) || "billing";
784
+ }
785
+ if (st === 404) {
786
+ if (hasAny(text, BILLING_PATTERNS)) return "billing";
787
+ if (hasAny(text, MODEL_NOT_FOUND_PATTERNS)) return "model_not_found";
788
+ // Generic 404 (often a misconfigured endpoint) — surface as unknown,
789
+ // not a misleading model_not_found that silently fails over.
790
+ return "unknown";
791
+ }
792
+ if (st === 413) return "payload_too_large";
793
+ if (st === 429) return "rate_limit";
794
+ if (st === 400) return classify400(text, code);
795
+ if (st === 408) return "timeout";
796
+ if (st === 500 || st === 502) {
797
+ // Some gateways return request-validation errors as 5xx — fail fast.
798
+ if (hasAny(text, REQUEST_VALIDATION_PATTERNS) || code === "invalid_request_error")
799
+ return "format";
800
+ return "server_error";
801
+ }
802
+ if (st === 503 || st === 529) return "overloaded";
803
+ if (st >= 400 && st < 500) return "format";
804
+ if (st >= 500 && st < 600) return "server_error";
805
+ }
806
+
807
+ // ── 3. Message-only classification (transport-level, no status) ───────────
808
+ const m = classifyByMessage(text);
809
+ if (m) return m;
810
+
811
+ // ── 4. Fallback ───────────────────────────────────────────────────────────
812
+ return "unknown";
813
+ }
814
+
815
+ /** 400-body refinement, mirroring Hermes _classify_400 collapsed to our enum. */
816
+ function classify400(text, code) {
817
+ // Request-validation (unsupported/unknown parameter) is deterministic format
818
+ // error — must be checked before context_overflow because "max_tokens" is in
819
+ // both pattern families.
820
+ const reqVal = REQUEST_VALIDATION_PATTERNS.filter((p) => p !== "invalid_request_error");
821
+ if (hasAny(text, reqVal) || code === "unknown_parameter" || code === "unsupported_parameter")
822
+ return "format";
823
+ if (hasAny(text, CONTEXT_OVERFLOW_PATTERNS)) return "context_overflow";
824
+ if (hasAny(text, PROVIDER_POLICY_PATTERNS)) return "provider_policy";
825
+ if (hasAny(text, MODEL_NOT_FOUND_PATTERNS)) return "model_not_found";
826
+ if (hasAny(text, RATE_LIMIT_PATTERNS)) return "rate_limit";
827
+ if (hasAny(text, BILLING_PATTERNS)) return "billing";
828
+ return "format";
829
+ }
830
+
831
+ /** Pure message-pattern classification (no status code present). */
832
+ function classifyByMessage(text) {
833
+ if (!text) return null;
834
+ if (hasAny(text, PAYLOAD_TOO_LARGE_PATTERNS)) return "payload_too_large";
835
+ const bill = disambiguateBilling(text);
836
+ if (bill) return bill;
837
+ if (hasAny(text, RATE_LIMIT_PATTERNS)) return "rate_limit";
838
+ if (hasAny(text, CONTEXT_OVERFLOW_PATTERNS)) return "context_overflow";
839
+ if (hasAny(text, AUTH_PATTERNS)) return "auth";
840
+ if (hasAny(text, PROVIDER_POLICY_PATTERNS)) return "provider_policy";
841
+ if (hasAny(text, MODEL_NOT_FOUND_PATTERNS)) return "model_not_found";
842
+ if (hasAny(text, NETWORK_PATTERNS)) return "network";
843
+ if (hasAny(text, TIMEOUT_PATTERNS)) return "timeout";
844
+ if (hasAny(text, EMPTY_RESPONSE_PATTERNS)) return "empty_response";
845
+ return null;
846
+ }
847
+
848
+ /**
849
+ * Classify a thrown transport-level error (no HTTP response). Recognizes
850
+ * Node fetch/undici/dns error codes as `network`, AbortError/timeouts as
851
+ * `timeout`, else falls through to message classification → unknown.
852
+ *
853
+ * @param {Error|object} err
854
+ * @returns {FailoverReason}
855
+ */
856
+ export function classifyTransportError(err) {
857
+ if (!err) return "unknown";
858
+ const code = String(err.code || err.errno || "").toUpperCase();
859
+ const name = String(err.name || "");
860
+ const msg = String(err.message || err).toLowerCase();
861
+ const NET_CODES = new Set([
862
+ "ENOTFOUND",
863
+ "ECONNREFUSED",
864
+ "ECONNRESET",
865
+ "ETIMEDOUT",
866
+ "ENETDOWN",
867
+ "ENETUNREACH",
868
+ "EHOSTUNREACH",
869
+ "EAI_AGAIN",
870
+ "UND_ERR_SOCKET",
871
+ "UND_ERR_CONNECT_TIMEOUT",
872
+ ]);
873
+ if (code === "ETIMEDOUT" || code === "UND_ERR_CONNECT_TIMEOUT") return "timeout";
874
+ if (NET_CODES.has(code)) return "network";
875
+ if (name === "AbortError" || name === "TimeoutError" || hasAny(msg, TIMEOUT_PATTERNS))
876
+ return "timeout";
877
+ if (hasAny(msg, NETWORK_PATTERNS)) return "network";
878
+ const m = classifyByMessage(msg);
879
+ return m || "unknown";
880
+ }
881
+
882
+ export const _internals = {
883
+ CLI_EVENT_MAP,
884
+ RESULT_SUBTYPE_MAP,
885
+ BILLING_PATTERNS,
886
+ RATE_LIMIT_PATTERNS,
887
+ CONTEXT_OVERFLOW_PATTERNS,
888
+ CONTENT_POLICY_PATTERNS,
889
+ PROVIDER_POLICY_PATTERNS,
890
+ classify400,
891
+ classifyByMessage,
892
+ disambiguateBilling,
893
+ };