@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,100 @@
1
+ /**
2
+ * Maestro — Voice subsystem (provider-agnostic facade)
3
+ *
4
+ * Exports the `VoiceProvider` interface and the concrete implementations
5
+ * we ship. Today: OpenAI Realtime. Tomorrow: Gemini Live, Claude Voice
6
+ * (when GA). The interface is small on purpose:
7
+ *
8
+ * connect(session) → opens the upstream session
9
+ * appendAudio(pcm16Chunk) → streams an audio chunk to the model
10
+ * commitTurn() → tells the model the user's turn is over
11
+ * interrupt() → barge-in: stop current model output
12
+ * sendToolResult(callId, output) → returns a tool-call result + resumes
13
+ * sendText(text) → optional: send user text directly
14
+ * close() → tear the session down
15
+ * on(event, handler) → audio.delta, transcript.delta, tool.call, …
16
+ *
17
+ * The maestro daemon never imports this directly; instead, the
18
+ * `voice-handler.mjs` daemon worker creates a provider via createProvider()
19
+ * and pipes between the channel (Twilio SIP, browser WebRTC, etc.) and the
20
+ * provider. That keeps the audio plumbing out of the core daemon and makes
21
+ * the provider swappable per agent.
22
+ */
23
+
24
+ import { VoiceProvider, VOICE_EVENTS } from "./provider.mjs";
25
+ import { createOpenAIRealtimeProvider } from "./openai-realtime.mjs";
26
+
27
+ export { VoiceProvider, VOICE_EVENTS };
28
+
29
+ /**
30
+ * Factory for the configured provider. Reads `provider` from the session
31
+ * config and instantiates the matching implementation. Used by the
32
+ * daemon's voice worker so we never hard-code OpenAI.
33
+ *
34
+ * Supported provider names:
35
+ * "openai-realtime" (default) → OpenAI gpt-realtime-2 over WebSocket
36
+ * "openai-realtime-webrtc" → same model, WebRTC transport
37
+ * "gemini-live" → stub (throws not_implemented)
38
+ *
39
+ * @param {string} name
40
+ * @param {object} opts
41
+ * @returns {VoiceProvider}
42
+ */
43
+ export function createProvider(name, opts = {}) {
44
+ switch ((name || "openai-realtime").toLowerCase()) {
45
+ case "openai-realtime":
46
+ case "openai-realtime-ws":
47
+ return createOpenAIRealtimeProvider({ transport: "ws", ...opts });
48
+ case "openai-realtime-webrtc":
49
+ return createOpenAIRealtimeProvider({ transport: "webrtc", ...opts });
50
+ case "gemini-live":
51
+ throw new Error("provider 'gemini-live' is stubbed — see lib/voice/gemini-live.mjs (planned 1.13.0)");
52
+ case "claude-voice":
53
+ throw new Error("provider 'claude-voice' is unavailable — Anthropic has not yet shipped a streaming voice API");
54
+ default:
55
+ throw new Error(`unknown voice provider: ${name}`);
56
+ }
57
+ }
58
+
59
+ /**
60
+ * Helper: build a Realtime session config from a partial spec + agent
61
+ * identity. Centralises the defaults so every caller doesn't reinvent
62
+ * them.
63
+ */
64
+ export function buildSessionConfig(partial = {}, identity = {}) {
65
+ const voice = partial.voice || "cedar"; // GA-only voice from May 2026 launch
66
+ const instructions = partial.instructions ||
67
+ `You are ${identity.name || "this agent"}, ${identity.role || "an executive operator"} at ${identity.company || "the company"}. ` +
68
+ `Reply briefly in ${identity.name || "first-person"} voice. Honour the same action-classification policy as the Slack/Gmail channels — verbally confirm any high-risk action before invoking its tool.`;
69
+ return {
70
+ type: "realtime",
71
+ model: partial.model || "gpt-realtime-2",
72
+ output_modalities: partial.output_modalities || ["audio"],
73
+ instructions,
74
+ voice,
75
+ input_audio_format: partial.input_audio_format || "pcm16",
76
+ output_audio_format: partial.output_audio_format || "pcm16",
77
+ input_audio_transcription: partial.input_audio_transcription ?? {
78
+ model: "gpt-realtime-whisper",
79
+ },
80
+ turn_detection: partial.turn_detection ?? {
81
+ type: "server_vad",
82
+ threshold: 0.5,
83
+ prefix_padding_ms: 300,
84
+ silence_duration_ms: 500,
85
+ },
86
+ tools: partial.tools || [],
87
+ tool_choice: partial.tool_choice || "auto",
88
+ };
89
+ }
90
+
91
+ export { createOpenAIRealtimeProvider } from "./openai-realtime.mjs";
92
+ export { actionsToRealtimeTools, registerToolBridge } from "./tool-bridge.mjs";
93
+ export { rotateSessionIfStale, SESSION_ROTATION_DEFAULTS } from "./session-rotation.mjs";
94
+
95
+ export default {
96
+ VoiceProvider,
97
+ VOICE_EVENTS,
98
+ createProvider,
99
+ buildSessionConfig,
100
+ };
@@ -0,0 +1,510 @@
1
+ /**
2
+ * Maestro — OpenAI Realtime voice provider (May 2026 GA)
3
+ *
4
+ * Thin, typed-by-convention wrapper around OpenAI's Realtime API. Emits
5
+ * the events declared in `lib/voice/index.mjs:VOICE_EVENTS` so the daemon's
6
+ * voice worker can stay provider-agnostic.
7
+ *
8
+ * Two transports:
9
+ * - WebSocket (server-side agents) — wss://api.openai.com/v1/realtime
10
+ * - WebRTC (browser/low-latency) — POST /v1/realtime/calls
11
+ *
12
+ * Audio is PCM16 mono @ 24 kHz on the WebSocket path; WebRTC negotiates
13
+ * Opus automatically. For µ-law (Twilio Voice / SIP bridges), set the
14
+ * session input/output audio_format to "g711_ulaw" and route µ-law
15
+ * frames through directly — no resampling on our side.
16
+ *
17
+ * Implementation notes:
18
+ * - We don't take a hard dependency on the `openai` npm package. If it
19
+ * is installed, we use its typed OpenAIRealtimeWS helper. Otherwise we
20
+ * fall back to a thin `ws` client. Either way, the public interface
21
+ * here is identical.
22
+ * - For barge-in (user interrupts the agent), the caller MUST emit
23
+ * `interrupt()` from a USER_SPEECH_STARTED handler, AND must track how
24
+ * many ms of agent audio actually played before the interruption so
25
+ * `conversation.item.truncate` can keep the model's internal
26
+ * transcript in sync with what the human heard. Skipping the truncate
27
+ * is the #1 production bug per the OpenAI docs.
28
+ *
29
+ * Auth:
30
+ * - OPENAI_API_KEY (required)
31
+ * - OPENAI_SAFETY_IDENTIFIER (optional; hashed user ID for safety telemetry)
32
+ *
33
+ * Refs:
34
+ * https://developers.openai.com/api/docs/guides/realtime-conversations
35
+ * https://developers.openai.com/api/docs/guides/realtime-vad
36
+ */
37
+
38
+ import { VoiceProvider, VOICE_EVENTS } from "./provider.mjs";
39
+
40
+ export const OPENAI_REALTIME_WS_URL = "wss://api.openai.com/v1/realtime";
41
+ export const OPENAI_REALTIME_DEFAULT_MODEL = "gpt-realtime-2";
42
+
43
+ // WebSocket.readyState OPEN value (same for browser WebSocket and the `ws`
44
+ // package). We compare numerically rather than importing a constructor so the
45
+ // SDK / `ws` / injected-fake socket paths all work uniformly.
46
+ const WS_OPEN = 1;
47
+
48
+ // Default backpressure ceiling (bytes). When socket.bufferedAmount climbs above
49
+ // this, the kernel/ws send buffer is backing up faster than the wire can drain
50
+ // — typically because the upstream stalled. Rather than queueing unbounded
51
+ // audio (which adds latency the user hears as lag and can OOM a long call), we
52
+ // drop new *audio append* frames until the buffer drains (drop-oldest-style for
53
+ // a live stream). Control frames are never dropped. Tunable via
54
+ // opts.maxBufferedBytes. 0 disables the check.
55
+ const DEFAULT_MAX_BUFFERED_BYTES = 1 << 20; // 1 MiB
56
+
57
+ // Lazy-load WebSocket. We prefer the official `openai` SDK helper if
58
+ // available, but fall back to plain `ws` so the framework doesn't force
59
+ // the user to install a new dependency until they actually opt in.
60
+ async function loadWsCtor() {
61
+ try {
62
+ const sdk = await import("openai/realtime/ws");
63
+ return { type: "sdk", ctor: sdk.OpenAIRealtimeWS };
64
+ } catch { /* fall through */ }
65
+ try {
66
+ const wsMod = await import("ws");
67
+ const WebSocket = wsMod.default || wsMod.WebSocket;
68
+ return { type: "ws", ctor: WebSocket };
69
+ } catch (err) {
70
+ throw new Error(
71
+ "Neither `openai/realtime/ws` nor `ws` is installed. " +
72
+ "Install one: `npm i openai` (recommended) or `npm i ws`."
73
+ );
74
+ }
75
+ }
76
+
77
+ class OpenAIRealtimeProvider extends VoiceProvider {
78
+ constructor(opts = {}) {
79
+ super(opts);
80
+ this.transport = opts.transport || "ws";
81
+ this.model = opts.model || OPENAI_REALTIME_DEFAULT_MODEL;
82
+ this.apiKey = opts.apiKey || process.env.OPENAI_API_KEY;
83
+ this.safetyIdentifier = opts.safetyIdentifier || process.env.OPENAI_SAFETY_IDENTIFIER;
84
+ this.socket = null; // underlying WS instance
85
+ this.sessionConfig = null;
86
+ this.sessionId = null; // server-assigned session id
87
+ this.responseId = null; // current in-flight response (if any)
88
+ this.lastAgentAudioStartedAt = null; // ms since-connect when agent started speaking
89
+ this.lastAgentAudioPlayedMs = 0; // ms of agent audio the user has heard
90
+ this._connectedAt = null;
91
+ this._turnAudioStartedAt = null;
92
+
93
+ // ── Reconnect / upstream-drop handling (H4) ──────────────────────────
94
+ // close() flips this so _onClose can tell a deliberate client close
95
+ // apart from an unexpected upstream socket drop. Only the latter
96
+ // triggers a bounded reconnect.
97
+ this._clientClosed = false;
98
+ this._reconnecting = false;
99
+ this._reconnectAttempts = 0;
100
+ this.maxReconnectAttempts = opts.maxReconnectAttempts ?? 3;
101
+ this.reconnectBaseDelayMs = opts.reconnectBaseDelayMs ?? 250;
102
+ // Seams so tests can drive reconnect without real sockets/timers.
103
+ this._setTimeout = opts.setTimeout || setTimeout;
104
+ this._clearTimeout = opts.clearTimeout || clearTimeout;
105
+ this._reconnectTimer = null;
106
+ // Optional injected WebSocket constructor — when provided, _connectWebSocket
107
+ // uses it instead of dynamically loading `openai`/`ws`. Lets tests drive a
108
+ // fake socket with no real network. Shape: (url, { headers }) => socketLike.
109
+ this._wsCtorOverride = opts.wsCtor || null;
110
+
111
+ // ── Backpressure (L12) ───────────────────────────────────────────────
112
+ // Ceiling on socket.bufferedAmount before we start dropping *audio* frames.
113
+ // Control frames (session.update, response.*, tool results) always go out.
114
+ this.maxBufferedBytes = opts.maxBufferedBytes ?? DEFAULT_MAX_BUFFERED_BYTES;
115
+ // Count of audio frames dropped due to backpressure / non-open socket, for
116
+ // observability. Not part of any wire protocol.
117
+ this.droppedAudioFrames = 0;
118
+ }
119
+
120
+ /** Open the upstream session. Awaits the server's session.created event. */
121
+ async connect(sessionConfig) {
122
+ if (this.state !== "idle" && this.state !== "closed") {
123
+ throw new Error(`connect() called from invalid state: ${this.state}`);
124
+ }
125
+ if (!this.apiKey) {
126
+ throw new Error("OPENAI_API_KEY not set");
127
+ }
128
+ this.sessionConfig = sessionConfig;
129
+
130
+ if (this.transport === "ws") {
131
+ await this._connectWebSocket();
132
+ } else if (this.transport === "webrtc") {
133
+ throw new Error("WebRTC transport in this provider is not wired in yet — use 'ws' for server-side agents");
134
+ } else {
135
+ throw new Error(`unknown transport: ${this.transport}`);
136
+ }
137
+
138
+ // Send the initial session.update so the model knows our voice / tools / VAD.
139
+ this._send({
140
+ type: "session.update",
141
+ session: sessionConfig,
142
+ });
143
+ this.state = "connected";
144
+ this.emit(VOICE_EVENTS.CONNECTED, { transport: this.transport, model: this.model });
145
+ }
146
+
147
+ _connectWebSocket() {
148
+ const loader = this._wsCtorOverride
149
+ ? Promise.resolve({ type: "factory", ctor: this._wsCtorOverride })
150
+ : loadWsCtor();
151
+ return loader.then(({ type, ctor }) => {
152
+ return new Promise((resolve, reject) => {
153
+ const url = `${OPENAI_REALTIME_WS_URL}?model=${encodeURIComponent(this.model)}`;
154
+ const headers = {
155
+ Authorization: `Bearer ${this.apiKey}`,
156
+ "OpenAI-Beta": "realtime=v1",
157
+ };
158
+ if (this.safetyIdentifier) {
159
+ headers["OpenAI-Safety-Identifier"] = this.safetyIdentifier;
160
+ }
161
+
162
+ let socket;
163
+ if (type === "sdk") {
164
+ // The official OpenAIRealtimeWS instance manages its own WebSocket;
165
+ // its .socket property is the raw WS. We wire to the underlying.
166
+ const rt = new ctor({ model: this.model, apiKey: this.apiKey });
167
+ socket = rt.socket;
168
+ this._sdkRt = rt;
169
+ } else if (type === "factory") {
170
+ // Injected socket factory (tests): called as a plain function,
171
+ // NOT with `new`, so a fake socket can be returned directly.
172
+ socket = ctor(url, { headers });
173
+ } else {
174
+ socket = new ctor(url, { headers });
175
+ }
176
+
177
+ this.socket = socket;
178
+ this._connectedAt = Date.now();
179
+
180
+ const onOpen = () => resolve();
181
+ const onError = (err) => reject(err instanceof Error ? err : new Error(String(err?.message || err)));
182
+ if (typeof socket.addEventListener === "function") {
183
+ socket.addEventListener("open", onOpen, { once: true });
184
+ socket.addEventListener("error", onError, { once: true });
185
+ } else {
186
+ socket.once?.("open", onOpen);
187
+ socket.once?.("error", onError);
188
+ }
189
+
190
+ const handleMessage = (raw) => this._onServerEvent(raw);
191
+ if (typeof socket.addEventListener === "function") {
192
+ socket.addEventListener("message", (evt) => handleMessage(evt.data));
193
+ socket.addEventListener("close", (evt) => this._onClose(evt.reason || "closed"));
194
+ socket.addEventListener("error", (evt) => this.emit(VOICE_EVENTS.ERROR, evt));
195
+ } else {
196
+ socket.on?.("message", handleMessage);
197
+ socket.on?.("close", (code, reason) => this._onClose(reason || `code ${code}`));
198
+ socket.on?.("error", (err) => this.emit(VOICE_EVENTS.ERROR, err));
199
+ }
200
+ });
201
+ });
202
+ }
203
+
204
+ /** Stream PCM16 audio (base64-encoded chunk OR raw Buffer/Uint8Array). */
205
+ appendAudio(pcm16) {
206
+ if (this.state !== "connected" && this.state !== "speaking" && this.state !== "listening") return;
207
+ // Backpressure: if the socket's send buffer is already backed up, drop this
208
+ // audio frame instead of queueing it. Audio is a live stream — a stale
209
+ // chunk delivered late is worse than a dropped one, and unbounded queueing
210
+ // adds audible latency and can exhaust memory on a long stalled call. Only
211
+ // audio is shed; control frames in _send() are never dropped.
212
+ if (this._isBackpressured()) {
213
+ this.droppedAudioFrames += 1;
214
+ return;
215
+ }
216
+ const b64 = typeof pcm16 === "string" ? pcm16 : Buffer.from(pcm16).toString("base64");
217
+ this._send({ type: "input_audio_buffer.append", audio: b64 });
218
+ }
219
+
220
+ /**
221
+ * True when the underlying socket is not OPEN, or its send buffer has grown
222
+ * past maxBufferedBytes (i.e. we're writing faster than the wire drains).
223
+ * Robust to fake/SDK sockets that don't expose readyState/bufferedAmount.
224
+ * @returns {boolean}
225
+ */
226
+ _isBackpressured() {
227
+ const s = this.socket;
228
+ if (!s) return true;
229
+ if (typeof s.readyState === "number" && s.readyState !== WS_OPEN) return true;
230
+ if (
231
+ this.maxBufferedBytes > 0 &&
232
+ typeof s.bufferedAmount === "number" &&
233
+ s.bufferedAmount > this.maxBufferedBytes
234
+ ) {
235
+ return true;
236
+ }
237
+ return false;
238
+ }
239
+
240
+ /** Commit the current audio buffer as one user turn. */
241
+ commitTurn() {
242
+ this._send({ type: "input_audio_buffer.commit" });
243
+ this._send({ type: "response.create" }); // ask the model to respond
244
+ }
245
+
246
+ /** Send a text-only user message (no audio). */
247
+ sendText(text) {
248
+ this._send({
249
+ type: "conversation.item.create",
250
+ item: {
251
+ type: "message",
252
+ role: "user",
253
+ content: [{ type: "input_text", text }],
254
+ },
255
+ });
256
+ this._send({ type: "response.create" });
257
+ }
258
+
259
+ /**
260
+ * Barge-in. The caller passes the number of milliseconds of agent audio
261
+ * the user actually heard before interrupting; we truncate the model's
262
+ * server-side conversation item to match so subsequent turns don't
263
+ * "remember" content the human never heard.
264
+ */
265
+ interrupt(opts = {}) {
266
+ const playedMs = Number(opts.playedMs ?? this.lastAgentAudioPlayedMs ?? 0);
267
+ if (this.responseId) {
268
+ this._send({ type: "response.cancel" });
269
+ }
270
+ if (opts.itemId && playedMs > 0) {
271
+ this._send({
272
+ type: "conversation.item.truncate",
273
+ item_id: opts.itemId,
274
+ content_index: 0,
275
+ audio_end_ms: playedMs,
276
+ });
277
+ }
278
+ this.responseId = null;
279
+ }
280
+
281
+ /**
282
+ * Return a tool-call result and resume the model. Output must be a
283
+ * JSON-serialisable value (object or scalar) — we stringify it for you.
284
+ */
285
+ sendToolResult(callId, output) {
286
+ if (!callId) throw new Error("sendToolResult requires a call_id");
287
+ this._send({
288
+ type: "conversation.item.create",
289
+ item: {
290
+ type: "function_call_output",
291
+ call_id: callId,
292
+ output: typeof output === "string" ? output : JSON.stringify(output),
293
+ },
294
+ });
295
+ this._send({ type: "response.create" });
296
+ }
297
+
298
+ /** Close the upstream session cleanly. */
299
+ close(reason) {
300
+ // Mark this as a deliberate, client-initiated close so an ensuing
301
+ // socket 'close' event is NOT treated as an upstream drop (H4).
302
+ this._clientClosed = true;
303
+ if (this._reconnectTimer) {
304
+ try { this._clearTimeout(this._reconnectTimer); } catch { /* */ }
305
+ this._reconnectTimer = null;
306
+ }
307
+ this._reconnecting = false;
308
+ if (this.state === "closed") return;
309
+ try { this.socket?.close?.(); } catch { /* ignore */ }
310
+ this.state = "closed";
311
+ this.emit(VOICE_EVENTS.CLOSED, { reason: reason || "client_close" });
312
+ }
313
+
314
+ // ── Internals ────────────────────────────────────────────────────────────
315
+
316
+ _send(payload) {
317
+ const s = this.socket;
318
+ if (!s || this.state === "closed") return;
319
+ // Don't write to a socket that isn't OPEN (CONNECTING / CLOSING / CLOSED):
320
+ // socket.send() would otherwise throw on the `ws` package. We still allow
321
+ // sends in the "reconnecting" state because _attemptReconnect re-seeds
322
+ // session.update on a freshly-opened socket (readyState OPEN) before
323
+ // flipping state back to "connected". Fake/SDK sockets without a numeric
324
+ // readyState fall through to the send (preserving existing behaviour).
325
+ if (typeof s.readyState === "number" && s.readyState !== WS_OPEN) return;
326
+ try {
327
+ s.send(JSON.stringify(payload));
328
+ } catch (err) {
329
+ this.emit(VOICE_EVENTS.ERROR, err);
330
+ }
331
+ }
332
+
333
+ _onClose(reason) {
334
+ // A deliberate client close() — keep the existing behaviour: tear down
335
+ // and emit CLOSED exactly once. close() already set state=closed and
336
+ // emitted CLOSED, so don't double-emit.
337
+ if (this._clientClosed) {
338
+ this.state = "closed";
339
+ return;
340
+ }
341
+ // Unexpected upstream drop. Don't silently flip to "closed" (which would
342
+ // make appendAudio/_send early-return → dead air). Attempt a bounded
343
+ // reconnect; only on exhaustion do we tear down deterministically (H4).
344
+ this._scheduleReconnect(reason);
345
+ }
346
+
347
+ /**
348
+ * Schedule a bounded reconnect after an upstream drop. Backs off
349
+ * exponentially from reconnectBaseDelayMs. On exhaustion, tears down
350
+ * deterministically with a clear error + CLOSED event rather than going
351
+ * silent. No-ops if a client close() already happened.
352
+ */
353
+ _scheduleReconnect(reason) {
354
+ if (this._clientClosed) return;
355
+ if (this._reconnecting) return; // a reconnect cycle is already in flight
356
+ if (this._reconnectAttempts >= this.maxReconnectAttempts) {
357
+ this._failReconnect(reason);
358
+ return;
359
+ }
360
+ this._reconnecting = true;
361
+ this.state = "reconnecting";
362
+ const attempt = this._reconnectAttempts + 1;
363
+ const delay = this.reconnectBaseDelayMs * Math.pow(2, this._reconnectAttempts);
364
+ this.emit(VOICE_EVENTS.ERROR, new Error(
365
+ `upstream session dropped (${reason}); reconnect attempt ${attempt}/${this.maxReconnectAttempts} in ${delay}ms`,
366
+ ));
367
+ this._reconnectTimer = this._setTimeout(() => {
368
+ this._reconnectTimer = null;
369
+ this._attemptReconnect(reason);
370
+ }, delay);
371
+ if (this._reconnectTimer && typeof this._reconnectTimer.unref === "function") {
372
+ this._reconnectTimer.unref();
373
+ }
374
+ }
375
+
376
+ /** Perform one reconnect attempt: reopen WS + re-send the stored config. */
377
+ async _attemptReconnect(reason) {
378
+ if (this._clientClosed) { this._reconnecting = false; return; }
379
+ this._reconnectAttempts += 1;
380
+ try {
381
+ this.socket = null;
382
+ await this._connectWebSocket();
383
+ // Re-seed the new socket with the session config the model needs.
384
+ if (this.sessionConfig) {
385
+ this._send({ type: "session.update", session: this.sessionConfig });
386
+ }
387
+ // Back to a live state — clear the reconnect bookkeeping.
388
+ this.state = "connected";
389
+ this._reconnecting = false;
390
+ this._reconnectAttempts = 0;
391
+ this.emit(VOICE_EVENTS.CONNECTED, {
392
+ transport: this.transport,
393
+ model: this.model,
394
+ reconnected: true,
395
+ });
396
+ } catch (err) {
397
+ // This attempt failed — schedule the next one (or fail if exhausted).
398
+ this._reconnecting = false;
399
+ if (this._reconnectAttempts >= this.maxReconnectAttempts) {
400
+ this._failReconnect(err?.message || reason);
401
+ } else {
402
+ this._scheduleReconnect(err?.message || reason);
403
+ }
404
+ }
405
+ }
406
+
407
+ /** Give up after exhausting reconnects: deterministic teardown + error. */
408
+ _failReconnect(reason) {
409
+ this._reconnecting = false;
410
+ if (this._reconnectTimer) {
411
+ try { this._clearTimeout(this._reconnectTimer); } catch { /* */ }
412
+ this._reconnectTimer = null;
413
+ }
414
+ this.state = "closed";
415
+ this.emit(VOICE_EVENTS.ERROR, new Error(
416
+ `upstream session lost and reconnect exhausted after ${this.maxReconnectAttempts} attempts (${reason})`,
417
+ ));
418
+ this.emit(VOICE_EVENTS.CLOSED, { reason: `upstream_drop_reconnect_exhausted: ${reason}` });
419
+ }
420
+
421
+ _onServerEvent(raw) {
422
+ let evt;
423
+ try {
424
+ evt = typeof raw === "string" ? JSON.parse(raw) : JSON.parse(Buffer.from(raw).toString("utf-8"));
425
+ } catch (err) {
426
+ this.emit(VOICE_EVENTS.ERROR, new Error(`bad server frame: ${err.message}`));
427
+ return;
428
+ }
429
+ // Re-emit using our normalised event names. We pass through anything
430
+ // unrecognised under its native `type` so downstream consumers can
431
+ // still observe vendor-specific events.
432
+ switch (evt.type) {
433
+ case "session.created":
434
+ this.sessionId = evt.session?.id || null;
435
+ this.emit(VOICE_EVENTS.SESSION_CREATED, evt.session || {});
436
+ break;
437
+ case "input_audio_buffer.speech_started":
438
+ this.emit(VOICE_EVENTS.USER_SPEECH_STARTED, evt);
439
+ break;
440
+ case "input_audio_buffer.speech_stopped":
441
+ this.emit(VOICE_EVENTS.USER_SPEECH_STOPPED, evt);
442
+ break;
443
+ case "response.created":
444
+ this.responseId = evt.response?.id || null;
445
+ break;
446
+ case "response.audio.delta":
447
+ if (!this._turnAudioStartedAt) this._turnAudioStartedAt = Date.now();
448
+ this.emit(VOICE_EVENTS.AUDIO_DELTA, {
449
+ delta: evt.delta,
450
+ item_id: evt.item_id,
451
+ response_id: evt.response_id,
452
+ });
453
+ break;
454
+ case "response.audio.done":
455
+ this._turnAudioStartedAt = null;
456
+ this.emit(VOICE_EVENTS.AUDIO_DONE, evt);
457
+ break;
458
+ case "response.audio_transcript.delta":
459
+ case "response.output_text.delta":
460
+ this.emit(VOICE_EVENTS.TRANSCRIPT_DELTA, {
461
+ delta: evt.delta,
462
+ role: evt.type.includes("audio_transcript") ? "assistant_voice" : "assistant_text",
463
+ item_id: evt.item_id,
464
+ response_id: evt.response_id,
465
+ });
466
+ break;
467
+ case "response.audio_transcript.done":
468
+ case "response.output_text.done":
469
+ this.emit(VOICE_EVENTS.TRANSCRIPT_DONE, evt);
470
+ break;
471
+ case "response.function_call_arguments.delta":
472
+ this.emit(VOICE_EVENTS.TOOL_CALL_DELTA, {
473
+ call_id: evt.call_id,
474
+ name: evt.name,
475
+ delta: evt.delta,
476
+ });
477
+ break;
478
+ case "response.function_call_arguments.done":
479
+ this.emit(VOICE_EVENTS.TOOL_CALL, {
480
+ call_id: evt.call_id,
481
+ name: evt.name,
482
+ arguments: safeParseJson(evt.arguments),
483
+ response_id: evt.response_id,
484
+ });
485
+ break;
486
+ case "response.done":
487
+ this.responseId = null;
488
+ this.emit(VOICE_EVENTS.TURN_DONE, evt.response || {});
489
+ break;
490
+ case "error":
491
+ this.emit(VOICE_EVENTS.ERROR, new Error(evt.error?.message || "unknown"));
492
+ break;
493
+ default:
494
+ // Surface as a generic event so callers wanting raw access can subscribe.
495
+ this.emit(evt.type, evt);
496
+ }
497
+ }
498
+ }
499
+
500
+ function safeParseJson(s) {
501
+ if (typeof s !== "string") return s;
502
+ try { return JSON.parse(s); } catch { return { _raw: s }; }
503
+ }
504
+
505
+ export function createOpenAIRealtimeProvider(opts = {}) {
506
+ return new OpenAIRealtimeProvider(opts);
507
+ }
508
+
509
+ export { OpenAIRealtimeProvider };
510
+ export default createOpenAIRealtimeProvider;