@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,202 @@
1
+ /**
2
+ * lib/util/fetch-timeout.test.mjs
3
+ *
4
+ * node:test coverage for fetchWithTimeout:
5
+ * - a fast request passes through and returns the Response
6
+ * - a slow request aborts after the timeout with a typed FetchTimeoutError
7
+ * - a caller-supplied AbortSignal abort surfaces as the caller's error, not a
8
+ * timeout, and the timer is cleared
9
+ * - timeoutMs <= 0 disables the timeout (pass-through, no internal signal)
10
+ * - init is forwarded; the internal abort signal is injected
11
+ *
12
+ * Run: cd /Users/layla/maestro && node --test lib/util/fetch-timeout.test.mjs
13
+ */
14
+
15
+ import test from "node:test";
16
+ import assert from "node:assert/strict";
17
+
18
+ import {
19
+ fetchWithTimeout,
20
+ FetchTimeoutError,
21
+ DEFAULT_TIMEOUT_MS,
22
+ } from "./fetch-timeout.mjs";
23
+
24
+ /**
25
+ * A fetch stub that resolves after `delayMs`, but rejects with an AbortError if
26
+ * the provided signal aborts first — mirroring real `fetch` abort semantics.
27
+ *
28
+ * @param {number} delayMs
29
+ * @param {*} [response]
30
+ */
31
+ function slowFetch(delayMs, response = { ok: true, status: 200 }) {
32
+ return (url, init) =>
33
+ new Promise((resolve, reject) => {
34
+ const signal = init && init.signal;
35
+ const t = setTimeout(() => resolve(response), delayMs);
36
+ if (signal) {
37
+ if (signal.aborted) {
38
+ clearTimeout(t);
39
+ const e = new Error("aborted");
40
+ e.name = "AbortError";
41
+ reject(e);
42
+ return;
43
+ }
44
+ signal.addEventListener(
45
+ "abort",
46
+ () => {
47
+ clearTimeout(t);
48
+ const e = new Error("aborted");
49
+ e.name = "AbortError";
50
+ reject(e);
51
+ },
52
+ { once: true }
53
+ );
54
+ }
55
+ });
56
+ }
57
+
58
+ /* ------------------------------- pass-through ----------------------------- */
59
+
60
+ test("fast request passes through and returns the response", async () => {
61
+ const resp = { ok: true, status: 200, body: "hi" };
62
+ const out = await fetchWithTimeout("https://x/", {}, 1000, {
63
+ fetchImpl: slowFetch(0, resp),
64
+ });
65
+ assert.equal(out, resp);
66
+ });
67
+
68
+ test("init is forwarded and an abort signal is injected", async () => {
69
+ let seenInit = null;
70
+ const fetchImpl = (url, init) => {
71
+ seenInit = init;
72
+ return Promise.resolve({ ok: true });
73
+ };
74
+ await fetchWithTimeout(
75
+ "https://x/",
76
+ { method: "POST", headers: { "x-test": "1" } },
77
+ 1000,
78
+ { fetchImpl }
79
+ );
80
+ assert.equal(seenInit.method, "POST");
81
+ assert.equal(seenInit.headers["x-test"], "1");
82
+ assert.ok(seenInit.signal, "internal AbortSignal should be injected into init");
83
+ assert.equal(seenInit.signal.aborted, false);
84
+ });
85
+
86
+ /* --------------------------------- timeout -------------------------------- */
87
+
88
+ test("slow request aborts after the timeout with a typed error", async () => {
89
+ await assert.rejects(
90
+ () =>
91
+ fetchWithTimeout("https://slow/", {}, 20, {
92
+ fetchImpl: slowFetch(10_000),
93
+ }),
94
+ (err) => {
95
+ assert.ok(err instanceof FetchTimeoutError, `wrong type: ${err && err.name}`);
96
+ assert.equal(err.name, "FetchTimeoutError");
97
+ assert.equal(err.code, "ETIMEDOUT");
98
+ assert.equal(err.timeoutMs, 20);
99
+ assert.ok(String(err.message).includes("timed out"), err.message);
100
+ assert.ok(String(err.message).includes("slow"), "url should appear in message");
101
+ return true;
102
+ }
103
+ );
104
+ });
105
+
106
+ /* --------------------------- caller abort vs timeout ---------------------- */
107
+
108
+ test("caller-supplied abort surfaces as the caller error, not a timeout", async () => {
109
+ const ac = new AbortController();
110
+ // Abort well before the (long) timeout would fire.
111
+ setTimeout(() => ac.abort(new Error("caller-cancelled")), 5);
112
+
113
+ await assert.rejects(
114
+ () =>
115
+ fetchWithTimeout("https://x/", { signal: ac.signal }, 10_000, {
116
+ fetchImpl: slowFetch(10_000),
117
+ }),
118
+ (err) => {
119
+ assert.ok(!(err instanceof FetchTimeoutError), "must not be a timeout error");
120
+ assert.notEqual(err.code, "ETIMEDOUT");
121
+ return true;
122
+ }
123
+ );
124
+ });
125
+
126
+ test("an already-aborted caller signal aborts immediately", async () => {
127
+ const ac = new AbortController();
128
+ ac.abort(new Error("pre-aborted"));
129
+ await assert.rejects(
130
+ () =>
131
+ fetchWithTimeout("https://x/", { signal: ac.signal }, 10_000, {
132
+ fetchImpl: slowFetch(10_000),
133
+ }),
134
+ (err) => {
135
+ assert.ok(!(err instanceof FetchTimeoutError));
136
+ return true;
137
+ }
138
+ );
139
+ });
140
+
141
+ /* ------------------------------ disabled timeout -------------------------- */
142
+
143
+ test("non-positive timeout disables timing out (plain pass-through)", async () => {
144
+ let seenInit = null;
145
+ const fetchImpl = (url, init) => {
146
+ seenInit = init;
147
+ return Promise.resolve({ ok: true });
148
+ };
149
+ const out = await fetchWithTimeout("https://x/", { method: "GET" }, 0, {
150
+ fetchImpl,
151
+ });
152
+ assert.equal(out.ok, true);
153
+ // Pass-through leaves init untouched (no injected signal).
154
+ assert.equal(seenInit.method, "GET");
155
+ assert.equal(seenInit.signal, undefined);
156
+ });
157
+
158
+ /* -------------------------------- exports --------------------------------- */
159
+
160
+ test("exposes a sane default timeout constant", () => {
161
+ assert.equal(typeof DEFAULT_TIMEOUT_MS, "number");
162
+ assert.ok(DEFAULT_TIMEOUT_MS > 0);
163
+ });
164
+
165
+ test("a non-function fetchImpl falls back to the global fetch", async () => {
166
+ // resolveFetch ignores a non-function fetchImpl and uses the global. We stub
167
+ // the global so this is deterministic and never hits the network.
168
+ const original = globalThis.fetch;
169
+ let used = false;
170
+ globalThis.fetch = () => {
171
+ used = true;
172
+ return Promise.resolve({ ok: true });
173
+ };
174
+ try {
175
+ const out = await fetchWithTimeout("https://x/", {}, 1000, { fetchImpl: 123 });
176
+ assert.equal(out.ok, true);
177
+ assert.equal(used, true);
178
+ } finally {
179
+ globalThis.fetch = original;
180
+ }
181
+ });
182
+
183
+ test("throws a clear error when no fetch implementation is available", async () => {
184
+ const original = globalThis.fetch;
185
+ // Remove the global so neither an injected nor a global fetch exists.
186
+ delete globalThis.fetch;
187
+ try {
188
+ await assert.rejects(
189
+ () => fetchWithTimeout("https://x/", {}, 1000),
190
+ (err) => {
191
+ assert.ok(err instanceof TypeError, `expected TypeError, got ${err && err.name}`);
192
+ assert.ok(
193
+ String(err.message).includes("no fetch implementation"),
194
+ err.message
195
+ );
196
+ return true;
197
+ }
198
+ );
199
+ } finally {
200
+ globalThis.fetch = original;
201
+ }
202
+ });
@@ -0,0 +1,343 @@
1
+ /**
2
+ * lib/util/reconnect.mjs — a shared bounded-backoff reconnect supervisor.
3
+ *
4
+ * The audit's silent-failure epidemic (gaps-product-quality P0-6) flagged the
5
+ * channel layer's reconnect logic as a one-line death: "Baileys reconnect is one
6
+ * swallowed attempt then permanent death; Telegram long-poll death is one warn
7
+ * with healthCheck() green." A live connection that dies and never comes back —
8
+ * with nothing surfacing the transition — is the worst kind of silent failure:
9
+ * the agent looks healthy and is deaf.
10
+ *
11
+ * The correct pattern already exists in lib/voice/openai-realtime.mjs (bounded
12
+ * reconnect, deterministic teardown on exhaustion, never goes silent). This
13
+ * module lifts that pattern into a standalone, transport-agnostic supervisor the
14
+ * daemon and every channel adapter can adopt:
15
+ *
16
+ * superviseConnection({ connect, isHealthy, onState, ... })
17
+ *
18
+ * It owns the loop: open the connection, watch its health, and on a drop
19
+ * reconnect with DECORRELATED jittered backoff (the same AWS-blog algorithm as
20
+ * lib/rate-guard.mjs — each delay is a uniform pick in [base, prev*factor],
21
+ * capped at maxBackoffMs) until it recovers or the attempt budget is exhausted.
22
+ * Every transition is surfaced through `onState` (connecting | up | down |
23
+ * giving-up) so a human/doctor/counter always sees the state — it NEVER silently
24
+ * dies.
25
+ *
26
+ * Everything is injectable — `connect`, `isHealthy`, the clock (`now`),
27
+ * `setTimeout`/`clearTimeout`, and the RNG — so the whole backoff-and-recover
28
+ * lifecycle is testable with no real sockets and no real time.
29
+ *
30
+ * Constraints (CLAUDE.md): ESM, Node built-ins only, fail-open, injectable seams.
31
+ *
32
+ * @module lib/util/reconnect
33
+ */
34
+
35
+ /** Connection lifecycle states surfaced through `onState`. Frozen as a contract. */
36
+ export const CONN_STATE = Object.freeze({
37
+ CONNECTING: "connecting",
38
+ UP: "up",
39
+ DOWN: "down",
40
+ GIVING_UP: "giving-up",
41
+ });
42
+
43
+ /** Default floor of every backoff window (ms). */
44
+ export const DEFAULT_BASE_BACKOFF_MS = 500;
45
+ /** Default ceiling on any single backoff window (ms). */
46
+ export const DEFAULT_MAX_BACKOFF_MS = 30_000;
47
+ /** Default multiplier applied to the previous backoff to size the next jitter range. */
48
+ export const DEFAULT_BACKOFF_FACTOR = 3;
49
+
50
+ /**
51
+ * Default RNG → uniform float in [0, 1). Tests inject a deterministic one; the
52
+ * default is fine for production jitter. We avoid Math.random only for parity
53
+ * with the rest of the codebase's "deterministic-in-tests" discipline.
54
+ * @returns {number}
55
+ */
56
+ function defaultRng() {
57
+ return Math.random();
58
+ }
59
+
60
+ /**
61
+ * Decorrelated-jitter backoff (AWS architecture blog), identical in shape to
62
+ * lib/rate-guard.mjs:decorrelatedBackoff so the fleet backs off the same way
63
+ * everywhere. The next delay is a uniform random pick in
64
+ * [base, max(base, prevBackoff * factor)]
65
+ * capped at `max`. With prev=0 the range collapses to [base, base].
66
+ *
67
+ * Exported so callers/tests can assert the bounds with an injected rng.
68
+ *
69
+ * @param {number} prevBackoffMs the previous backoff window (0 on first attempt)
70
+ * @param {object} [opts]
71
+ * @param {number} [opts.base] floor (default DEFAULT_BASE_BACKOFF_MS)
72
+ * @param {number} [opts.max] ceiling (default DEFAULT_MAX_BACKOFF_MS)
73
+ * @param {number} [opts.factor] growth multiplier (default DEFAULT_BACKOFF_FACTOR)
74
+ * @param {() => number} [opts.rng] uniform [0,1) source (default Math.random)
75
+ * @returns {number} the next backoff in ms (integer, within [base, max])
76
+ */
77
+ export function decorrelatedBackoff(prevBackoffMs, opts = {}) {
78
+ const base = posNum(opts.base, DEFAULT_BASE_BACKOFF_MS);
79
+ const max = posNum(opts.max, DEFAULT_MAX_BACKOFF_MS);
80
+ const factor = posNum(opts.factor, DEFAULT_BACKOFF_FACTOR);
81
+ const prev = Number.isFinite(Number(prevBackoffMs)) ? Math.max(0, Number(prevBackoffMs)) : 0;
82
+ const rng = typeof opts.rng === "function" ? opts.rng : defaultRng;
83
+
84
+ const hi = Math.max(base, prev * factor);
85
+ const r = rng();
86
+ const clampedR = r < 0 ? 0 : r >= 1 ? 0.999999 : r;
87
+ const pick = base + clampedR * (hi - base);
88
+ return Math.min(max, Math.max(base, Math.round(pick)));
89
+ }
90
+
91
+ function posNum(v, dflt) {
92
+ const n = Number(v);
93
+ return Number.isFinite(n) && n > 0 ? n : dflt;
94
+ }
95
+
96
+ /**
97
+ * Supervise a connection: open it, and whenever it drops, reconnect with
98
+ * bounded decorrelated-jitter backoff — surfacing every state transition so the
99
+ * connection can never silently die.
100
+ *
101
+ * Lifecycle, expressed through `onState(state, info)`:
102
+ * - `connecting` — about to (re)open. `info` = { attempt, delayMs?, reason? }
103
+ * - `up` — connect() resolved; the connection is live. `info` =
104
+ * { attempt, reconnected }
105
+ * - `down` — a live connection dropped (reported via `notifyDown`, an
106
+ * `isHealthy` poll returning false, or a failed connect).
107
+ * `info` = { attempt, reason }
108
+ * - `giving-up` — the attempt budget was exhausted; the supervisor has torn
109
+ * down deterministically. `info` = { attempts, reason }
110
+ *
111
+ * `connect()` is called for each (re)connect attempt and must resolve when the
112
+ * connection is established (it may return a handle; whatever it returns is
113
+ * passed back as `controller.connection`). A rejection is treated as a failed
114
+ * attempt → backoff → retry, exactly like a drop.
115
+ *
116
+ * Health is watched two ways, either or both optional:
117
+ * - the caller drives it explicitly by calling `controller.notifyDown(reason)`
118
+ * when its own listener observes a socket close (the channel-adapter path);
119
+ * - and/or this supervisor polls `isHealthy()` every `healthIntervalMs` and
120
+ * treats a falsey result as a drop (the daemon health-sweep path).
121
+ *
122
+ * The returned controller lets the owner stop the supervisor cleanly:
123
+ * - `controller.notifyDown(reason)` — report an observed drop (idempotent while
124
+ * a reconnect is already in flight).
125
+ * - `controller.stop(reason)` — deliberate shutdown: cancels any pending
126
+ * backoff timer and the health poll; a subsequent drop does NOT reconnect.
127
+ * - `controller.state` — the current CONN_STATE.
128
+ * - `controller.connection` — the most recent value `connect` resolved.
129
+ *
130
+ * @param {object} cfg
131
+ * @param {() => Promise<any>|any} cfg.connect open the connection; resolves when live.
132
+ * @param {() => Promise<boolean>|boolean} [cfg.isHealthy] optional liveness poll.
133
+ * @param {(state: string, info: object) => void} [cfg.onState] transition sink.
134
+ * @param {number} [cfg.maxAttempts] consecutive failed attempts before giving up
135
+ * (default 8). A successful connect resets the counter.
136
+ * @param {number} [cfg.baseBackoffMs] backoff floor (default DEFAULT_BASE_BACKOFF_MS).
137
+ * @param {number} [cfg.maxBackoffMs] backoff ceiling (default DEFAULT_MAX_BACKOFF_MS).
138
+ * @param {number} [cfg.backoffFactor] growth multiplier (default DEFAULT_BACKOFF_FACTOR).
139
+ * @param {boolean} [cfg.jitter] apply decorrelated jitter (default true). When
140
+ * false, the delay is the deterministic `prev*factor` (capped) — useful
141
+ * for tests / strict ordering.
142
+ * @param {number} [cfg.healthIntervalMs] poll `isHealthy` this often (0/absent →
143
+ * no polling; rely on notifyDown).
144
+ * @param {() => number} [cfg.now] clock (default Date.now) — for info timestamps.
145
+ * @param {(fn: Function, ms: number) => any} [cfg.setTimeout] default global.
146
+ * @param {(handle: any) => void} [cfg.clearTimeout] default global.
147
+ * @param {() => number} [cfg.rng] uniform [0,1) for jitter (default Math.random).
148
+ * @returns {{ notifyDown:(reason?:string)=>void, stop:(reason?:string)=>void,
149
+ * readonly state:string, readonly connection:any, readonly attempts:number,
150
+ * started:Promise<void> }}
151
+ */
152
+ export function superviseConnection(cfg = {}) {
153
+ if (!cfg || typeof cfg.connect !== "function") {
154
+ throw new TypeError("superviseConnection: cfg.connect must be a function");
155
+ }
156
+
157
+ const connect = cfg.connect;
158
+ const isHealthy = typeof cfg.isHealthy === "function" ? cfg.isHealthy : null;
159
+ const onState = typeof cfg.onState === "function" ? cfg.onState : () => {};
160
+ const maxAttempts = posNum(cfg.maxAttempts, 8);
161
+ const base = posNum(cfg.baseBackoffMs, DEFAULT_BASE_BACKOFF_MS);
162
+ const max = posNum(cfg.maxBackoffMs, DEFAULT_MAX_BACKOFF_MS);
163
+ const factor = posNum(cfg.backoffFactor, DEFAULT_BACKOFF_FACTOR);
164
+ const jitter = cfg.jitter !== false;
165
+ const healthIntervalMs = Number(cfg.healthIntervalMs) > 0 ? Number(cfg.healthIntervalMs) : 0;
166
+ const now = typeof cfg.now === "function" ? cfg.now : Date.now;
167
+ const setT = typeof cfg.setTimeout === "function" ? cfg.setTimeout : setTimeout;
168
+ const clearT = typeof cfg.clearTimeout === "function" ? cfg.clearTimeout : clearTimeout;
169
+ const rng = typeof cfg.rng === "function" ? cfg.rng : defaultRng;
170
+
171
+ // Mutable supervisor state.
172
+ let state = CONN_STATE.CONNECTING;
173
+ let connection = null;
174
+ let failedAttempts = 0; // consecutive failed connect attempts
175
+ let lastBackoffMs = 0; // feeds the decorrelated-jitter recurrence
176
+ let reconnecting = false; // a connect attempt is in flight
177
+ let stopped = false; // deliberate shutdown — no further reconnects
178
+ let backoffTimer = null;
179
+ let healthTimer = null;
180
+
181
+ /** Emit a transition, isolating a throwing sink so it can't break the loop. */
182
+ function emit(next, info) {
183
+ state = next;
184
+ try {
185
+ onState(next, { ...info, ts: new Date(now()).toISOString(), state: next });
186
+ } catch {
187
+ /* a buggy state sink must never crash the supervisor */
188
+ }
189
+ }
190
+
191
+ function clearBackoff() {
192
+ if (backoffTimer != null) {
193
+ try { clearT(backoffTimer); } catch { /* */ }
194
+ backoffTimer = null;
195
+ }
196
+ }
197
+
198
+ function clearHealth() {
199
+ if (healthTimer != null) {
200
+ try { clearT(healthTimer); } catch { /* */ }
201
+ healthTimer = null;
202
+ }
203
+ }
204
+
205
+ /** Next backoff window: decorrelated jitter, or deterministic if jitter off. */
206
+ function nextDelay() {
207
+ if (jitter) {
208
+ lastBackoffMs = decorrelatedBackoff(lastBackoffMs, { base, max, factor, rng });
209
+ } else {
210
+ const grown = lastBackoffMs > 0 ? lastBackoffMs * factor : base;
211
+ lastBackoffMs = Math.min(max, Math.max(base, Math.round(grown)));
212
+ }
213
+ return lastBackoffMs;
214
+ }
215
+
216
+ /** Schedule the periodic health poll (no-op if polling disabled / stopped). */
217
+ function scheduleHealthPoll() {
218
+ if (!isHealthy || healthIntervalMs <= 0 || stopped) return;
219
+ clearHealth();
220
+ healthTimer = setT(runHealthPoll, healthIntervalMs);
221
+ if (healthTimer && typeof healthTimer.unref === "function") healthTimer.unref();
222
+ }
223
+
224
+ async function runHealthPoll() {
225
+ healthTimer = null;
226
+ if (stopped || state !== CONN_STATE.UP) return;
227
+ let healthy = true;
228
+ try {
229
+ healthy = await isHealthy();
230
+ } catch {
231
+ healthy = false; // a throwing health check is a failing health check
232
+ }
233
+ if (stopped) return;
234
+ if (!healthy) {
235
+ // An unhealthy live connection is a drop — kick the reconnect loop.
236
+ reportDown("health-check-failed");
237
+ } else {
238
+ scheduleHealthPoll();
239
+ }
240
+ }
241
+
242
+ /** One connect attempt. On success → up; on failure → backoff/give-up. */
243
+ async function attemptConnect(reason) {
244
+ if (stopped) return;
245
+ reconnecting = true;
246
+ const attempt = failedAttempts + 1;
247
+ // We're actively opening the socket now → connecting.
248
+ emit(CONN_STATE.CONNECTING, { attempt, reason: reason || null });
249
+ try {
250
+ connection = await connect();
251
+ if (stopped) return; // stopped mid-connect → leave the just-opened handle to the caller's stop path
252
+ // Live again: reset the failure budget + backoff recurrence.
253
+ failedAttempts = 0;
254
+ lastBackoffMs = 0;
255
+ reconnecting = false;
256
+ const reconnected = reason != null;
257
+ emit(CONN_STATE.UP, { attempt, reconnected });
258
+ scheduleHealthPoll();
259
+ } catch (err) {
260
+ reconnecting = false;
261
+ if (stopped) return;
262
+ failedAttempts += 1;
263
+ const why = errMsg(err) || reason || "connect-failed";
264
+ // A failed attempt is a drop: schedule the next backoff (or give up). The
265
+ // down/giving-up transition is emitted by scheduleBackoff so there's one
266
+ // owner of that decision.
267
+ scheduleBackoff(why);
268
+ }
269
+ }
270
+
271
+ /**
272
+ * Arm the backoff timer for the next attempt, or give up if exhausted. This
273
+ * is the single owner of the `down` and `giving-up` transitions: while the
274
+ * backoff timer is pending the connection is DOWN (state stays `down`,
275
+ * carrying the delayMs so an observer knows when the retry fires), and an
276
+ * exhausted budget is the only path to `giving-up`.
277
+ */
278
+ function scheduleBackoff(reason) {
279
+ if (stopped) return;
280
+ if (failedAttempts >= maxAttempts) {
281
+ giveUp(reason);
282
+ return;
283
+ }
284
+ const delayMs = nextDelay();
285
+ emit(CONN_STATE.DOWN, { attempt: failedAttempts + 1, delayMs, reason });
286
+ clearBackoff();
287
+ backoffTimer = setT(() => {
288
+ backoffTimer = null;
289
+ return attemptConnect(reason); // return the promise so an awaiting timer driver chains the connect
290
+ }, delayMs);
291
+ if (backoffTimer && typeof backoffTimer.unref === "function") backoffTimer.unref();
292
+ }
293
+
294
+ /** Deterministic teardown after exhausting the attempt budget. */
295
+ function giveUp(reason) {
296
+ clearBackoff();
297
+ clearHealth();
298
+ emit(CONN_STATE.GIVING_UP, { attempts: failedAttempts, reason: reason || "exhausted" });
299
+ }
300
+
301
+ /**
302
+ * Handle an observed drop of a live connection. Idempotent while a reconnect
303
+ * is already in flight (a flurry of close events collapses to one cycle).
304
+ */
305
+ function reportDown(reason) {
306
+ if (stopped) return;
307
+ if (reconnecting) return; // already cycling — don't stack attempts
308
+ if (state === CONN_STATE.GIVING_UP) return; // already terminal
309
+ if (state === CONN_STATE.DOWN && backoffTimer != null) return; // backoff already pending
310
+ clearHealth();
311
+ scheduleBackoff(reason || "connection-dropped");
312
+ }
313
+
314
+ // Kick off the first connect immediately.
315
+ const started = attemptConnect(null);
316
+
317
+ return {
318
+ notifyDown(reason) { reportDown(reason); },
319
+ stop(reason) {
320
+ if (stopped) return;
321
+ stopped = true;
322
+ reconnecting = false;
323
+ clearBackoff();
324
+ clearHealth();
325
+ // We don't emit a CONN_STATE here — stop is the OWNER's deliberate action,
326
+ // not a connection transition. The owner already knows it stopped.
327
+ void reason;
328
+ },
329
+ get state() { return state; },
330
+ get connection() { return connection; },
331
+ get attempts() { return failedAttempts; },
332
+ started,
333
+ };
334
+ }
335
+
336
+ /** Coerce any thrown value to a short message string. */
337
+ function errMsg(err) {
338
+ if (!err) return "";
339
+ if (typeof err === "string") return err;
340
+ return err.message || String(err);
341
+ }
342
+
343
+ export default { superviseConnection, decorrelatedBackoff, CONN_STATE };