@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,232 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ # ---------------------------------------------------------------------------
5
+ # Maestro Huddle — Slack Launcher with CDP
6
+ #
7
+ # Launches (or relaunches) the Slack desktop app with Chrome DevTools Protocol
8
+ # enabled on port 9222. This allows Playwright/Puppeteer to connect and
9
+ # control the Slack UI for huddle automation.
10
+ #
11
+ # Usage:
12
+ # ./launch-slack.sh # Launch/relaunch Slack with CDP
13
+ # ./launch-slack.sh --check # Check if CDP is available
14
+ # ./launch-slack.sh --kill # Kill Slack entirely
15
+ # ---------------------------------------------------------------------------
16
+
17
+ CDP_PORT="${CDP_PORT:-9222}"
18
+ CDP_URL="http://localhost:${CDP_PORT}"
19
+ SLACK_APP="/Applications/Slack.app"
20
+ SLACK_BINARY="${SLACK_APP}/Contents/MacOS/Slack"
21
+
22
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
23
+ LOG_FILE="${SCRIPT_DIR}/../../logs/huddle/launch-slack.log"
24
+ PID_FILE="${SCRIPT_DIR}/../../state/huddle/slack-cdp.pid"
25
+
26
+ mkdir -p "$(dirname "$LOG_FILE")" "$(dirname "$PID_FILE")"
27
+
28
+ log() { echo "[$(date '+%H:%M:%S')] $*" | tee -a "$LOG_FILE"; }
29
+ err() { echo "[$(date '+%H:%M:%S')] ERROR: $*" | tee -a "$LOG_FILE" >&2; }
30
+ ok() { echo "[$(date '+%H:%M:%S')] ✓ $*" | tee -a "$LOG_FILE"; }
31
+
32
+ # ---------------------------------------------------------------------------
33
+ # CDP connectivity check
34
+ # ---------------------------------------------------------------------------
35
+
36
+ check_cdp() {
37
+ local response
38
+ response=$(curl -s --connect-timeout 3 "${CDP_URL}/json/version" 2>/dev/null || echo "")
39
+
40
+ if [ -n "$response" ] && echo "$response" | python3 -c "import sys,json; json.load(sys.stdin)" 2>/dev/null; then
41
+ return 0
42
+ fi
43
+ return 1
44
+ }
45
+
46
+ list_cdp_targets() {
47
+ local targets
48
+ targets=$(curl -s --connect-timeout 3 "${CDP_URL}/json" 2>/dev/null || echo "[]")
49
+ echo "$targets"
50
+ }
51
+
52
+ # ---------------------------------------------------------------------------
53
+ # Slack process management
54
+ # ---------------------------------------------------------------------------
55
+
56
+ is_slack_running() {
57
+ pgrep -x "Slack" &>/dev/null
58
+ }
59
+
60
+ get_slack_pid() {
61
+ pgrep -x "Slack" | head -1
62
+ }
63
+
64
+ kill_slack() {
65
+ if is_slack_running; then
66
+ log "Stopping Slack..."
67
+ osascript -e 'tell application "Slack" to quit' 2>/dev/null || true
68
+
69
+ # Wait up to 10 seconds for graceful shutdown
70
+ local i=0
71
+ while is_slack_running && [ $i -lt 10 ]; do
72
+ sleep 1
73
+ i=$((i + 1))
74
+ done
75
+
76
+ # Force kill if still running
77
+ if is_slack_running; then
78
+ log "Force killing Slack..."
79
+ pkill -9 -x "Slack" 2>/dev/null || true
80
+ sleep 2
81
+ fi
82
+
83
+ if ! is_slack_running; then
84
+ ok "Slack stopped"
85
+ else
86
+ err "Failed to stop Slack"
87
+ return 1
88
+ fi
89
+ else
90
+ log "Slack not running"
91
+ fi
92
+ }
93
+
94
+ # ---------------------------------------------------------------------------
95
+ # Launch Slack with CDP
96
+ # ---------------------------------------------------------------------------
97
+
98
+ launch_slack_cdp() {
99
+ # Check if CDP is already available (Slack running with correct flag)
100
+ if check_cdp; then
101
+ ok "Slack already running with CDP on port ${CDP_PORT}"
102
+ show_status
103
+ return 0
104
+ fi
105
+
106
+ # If Slack is running but CDP isn't available, need to restart
107
+ if is_slack_running; then
108
+ log "Slack running but CDP not available — restarting with --remote-debugging-port=${CDP_PORT}"
109
+ kill_slack
110
+ sleep 2
111
+ fi
112
+
113
+ # Check port availability
114
+ if lsof -i ":${CDP_PORT}" &>/dev/null; then
115
+ err "Port ${CDP_PORT} is already in use by another process"
116
+ lsof -i ":${CDP_PORT}" | head -5 | while read -r line; do
117
+ log " $line"
118
+ done
119
+ return 1
120
+ fi
121
+
122
+ # Launch Slack with CDP
123
+ log "Launching Slack with CDP on port ${CDP_PORT}..."
124
+ open -a "Slack" --args --remote-debugging-port="${CDP_PORT}"
125
+
126
+ # Wait for Slack to start and CDP to become available
127
+ local i=0
128
+ local max_wait=30
129
+ while [ $i -lt $max_wait ]; do
130
+ sleep 1
131
+ i=$((i + 1))
132
+
133
+ if check_cdp; then
134
+ ok "Slack launched with CDP — port ${CDP_PORT} ready"
135
+
136
+ # Save PID
137
+ local pid
138
+ pid=$(get_slack_pid)
139
+ echo "$pid" > "$PID_FILE"
140
+ log "Slack PID: ${pid}"
141
+
142
+ show_status
143
+ return 0
144
+ fi
145
+
146
+ # Progress indicator every 5 seconds
147
+ if [ $((i % 5)) -eq 0 ]; then
148
+ log "Waiting for CDP... (${i}/${max_wait}s)"
149
+ fi
150
+ done
151
+
152
+ err "Timed out waiting for CDP on port ${CDP_PORT} after ${max_wait}s"
153
+ err "Slack may have launched without the CDP flag"
154
+
155
+ # Check if Slack is at least running
156
+ if is_slack_running; then
157
+ log "Slack is running (PID: $(get_slack_pid)) but CDP is not responding"
158
+ log "Try: kill Slack manually, then rerun this script"
159
+ fi
160
+
161
+ return 1
162
+ }
163
+
164
+ # ---------------------------------------------------------------------------
165
+ # Status
166
+ # ---------------------------------------------------------------------------
167
+
168
+ show_status() {
169
+ log ""
170
+ log "=== Slack CDP Status ==="
171
+
172
+ if ! is_slack_running; then
173
+ log " Slack: NOT RUNNING"
174
+ return 1
175
+ fi
176
+
177
+ local pid
178
+ pid=$(get_slack_pid)
179
+ log " Slack PID: ${pid}"
180
+
181
+ if check_cdp; then
182
+ log " CDP: AVAILABLE at ${CDP_URL}"
183
+
184
+ # Count debuggable targets
185
+ local targets
186
+ targets=$(list_cdp_targets)
187
+ local count
188
+ count=$(echo "$targets" | python3 -c "import sys,json; print(len(json.load(sys.stdin)))" 2>/dev/null || echo "?")
189
+ log " Targets: ${count} debuggable pages"
190
+
191
+ # Show version info
192
+ local version
193
+ version=$(curl -s "${CDP_URL}/json/version" 2>/dev/null | python3 -c "import sys,json; d=json.load(sys.stdin); print(f\" Browser: {d.get('Browser','?')}\")" 2>/dev/null || true)
194
+ [ -n "$version" ] && log "$version"
195
+ else
196
+ log " CDP: NOT AVAILABLE"
197
+ log " Slack is running but was not launched with --remote-debugging-port"
198
+ fi
199
+
200
+ log ""
201
+ }
202
+
203
+ # ---------------------------------------------------------------------------
204
+ # Main
205
+ # ---------------------------------------------------------------------------
206
+
207
+ main() {
208
+ log ""
209
+ log "=========================================="
210
+ log " Maestro Huddle — Slack Launcher"
211
+ log " $(date)"
212
+ log "=========================================="
213
+
214
+ case "${1:-launch}" in
215
+ --check|check)
216
+ show_status
217
+ ;;
218
+ --kill|kill)
219
+ kill_slack
220
+ rm -f "$PID_FILE"
221
+ ;;
222
+ --launch|launch|"")
223
+ launch_slack_cdp
224
+ ;;
225
+ *)
226
+ echo "Usage: $0 [--launch|--check|--kill]"
227
+ exit 1
228
+ ;;
229
+ esac
230
+ }
231
+
232
+ main "$@"
@@ -0,0 +1,462 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Maestro Huddle — OpenAI Realtime Audio Bridge
4
+ *
5
+ * Drop-in replacement for the Deepgram + Claude + ElevenLabs pipeline in
6
+ * audio-bridge.mjs. Captures audio from BlackHole 2ch (Slack speaker tap),
7
+ * streams it to OpenAI Realtime (single speech-to-speech model), and plays
8
+ * the response back through BlackHole 16ch (Slack microphone). One model,
9
+ * one WebSocket, one lower-latency path — same brain that drives PSTN.
10
+ *
11
+ * Why a parallel module rather than modifying the existing bridge: the
12
+ * existing bridge is deeply coupled to Deepgram event semantics (interim /
13
+ * final transcripts, utterance boundaries) and to the discrete-turn
14
+ * conversation loop in huddle-server.mjs. The Realtime API replaces both
15
+ * with a duplex audio stream + native barge-in. Keeping them side-by-side
16
+ * lets operators switch with HUDDLE_BRAIN=openai-realtime.
17
+ *
18
+ * Event compatibility surface (same names as AudioBridge so huddle-server
19
+ * needs minimal changes):
20
+ * - 'capture:started' / 'capture:stopped'
21
+ * - 'speech:start' when the model starts replying
22
+ * - 'speech:end' when the model finishes a response
23
+ * - 'transcript' {text, role: 'user'|'assistant'} — full turn
24
+ * - 'transcript:interim' {text} — partial transcript (user side only)
25
+ * - 'tts:start' when audio playback begins
26
+ * - 'tts:end' when audio playback ends
27
+ * - 'tool:call' {name, arguments} — tool was invoked
28
+ * - 'error' {component, message}
29
+ *
30
+ * Public methods (signature-compatible with AudioBridge):
31
+ * start() — connect + start audio capture
32
+ * stop() — flush + tear down
33
+ * speak(text) — make the model say this text now
34
+ * (translated to conversation.item.create +
35
+ * response.create with explicit instructions)
36
+ *
37
+ * Env:
38
+ * OPENAI_API_KEY — required
39
+ * HUDDLE_CAPTURE_DEVICE — default "BlackHole 2ch"
40
+ * HUDDLE_PLAYBACK_DEVICE — default "BlackHole 16ch"
41
+ * HUDDLE_REALTIME_MODEL — default "gpt-realtime-2"
42
+ * HUDDLE_REALTIME_VOICE — default "cedar"
43
+ * AGENT_ROOT — where context-loader reads from
44
+ */
45
+
46
+ import { EventEmitter } from "node:events";
47
+ import { spawn, execFileSync } from "node:child_process";
48
+ import { WebSocket } from "ws";
49
+
50
+ const CAPTURE_DEVICE = process.env.HUDDLE_CAPTURE_DEVICE || "BlackHole 2ch";
51
+ const PLAYBACK_DEVICE = process.env.HUDDLE_PLAYBACK_DEVICE || "BlackHole 16ch";
52
+
53
+ // 24kHz PCM16 is OpenAI Realtime's recommended format. The huddle bridge
54
+ // captures at 24kHz mono so no resampling is needed at the WS boundary.
55
+ const SAMPLE_RATE = 24000;
56
+ const BIT_DEPTH = 16;
57
+ const CHANNELS = 1;
58
+
59
+ const MODEL = process.env.HUDDLE_REALTIME_MODEL || "gpt-realtime-2";
60
+ const VOICE = process.env.HUDDLE_REALTIME_VOICE || "cedar";
61
+
62
+ // We chunk capture into ~40ms windows (1920 bytes at 24kHz mono 16-bit).
63
+ // Smaller is more responsive; larger is fewer WS frames. 40ms is the sweet
64
+ // spot — well under typical end-of-turn detection latency.
65
+ const CAPTURE_CHUNK_BYTES = 1920;
66
+
67
+ /**
68
+ * @param {object} options
69
+ * @param {string} [options.apiKey] OpenAI API key (defaults to env)
70
+ * @param {string} [options.captureDevice] BlackHole capture device
71
+ * @param {string} [options.playbackDevice] BlackHole playback device
72
+ * @param {string} [options.agentRoot] For context-loader (identity + RAG)
73
+ * @param {string} [options.voice] Realtime voice (default cedar)
74
+ * @param {string} [options.peer] Caller identifier (slack_id / phone / email)
75
+ * @param {string} [options.brief] What the agent is supposed to accomplish
76
+ * @param {Array} [options.tools] OpenAI Realtime tool definitions
77
+ * @param {(call) => Promise<string>} [options.executeTool]
78
+ * Tool execution handler (returns result text)
79
+ */
80
+ export class OpenAIRealtimeHuddleBridge extends EventEmitter {
81
+ constructor(options = {}) {
82
+ super();
83
+ this.apiKey = options.apiKey || process.env.OPENAI_API_KEY || "";
84
+ if (!this.apiKey) throw new Error("OPENAI_API_KEY required for huddle realtime bridge");
85
+ this.captureDevice = options.captureDevice || CAPTURE_DEVICE;
86
+ this.playbackDevice = options.playbackDevice || PLAYBACK_DEVICE;
87
+ this.agentRoot = options.agentRoot || process.env.AGENT_ROOT || process.cwd();
88
+ this.voice = options.voice || VOICE;
89
+ this.peer = options.peer || null;
90
+ this.brief = options.brief || null;
91
+ this.tools = options.tools || [];
92
+ this.executeTool = options.executeTool || null;
93
+
94
+ // Audio pipeline state
95
+ this.captureProcess = null;
96
+ this.playbackProcess = null;
97
+ this.ws = null;
98
+ this.isCapturing = false;
99
+ this.responseInProgress = false;
100
+ this.currentTranscript = ""; // user side, accumulating
101
+ this.currentResponse = ""; // model side, accumulating
102
+ this.pendingFunctionCalls = new Map(); // call_id -> { name, args }
103
+ }
104
+
105
+ // ─────────────────────────────────────────────────────────────────────────
106
+ // Lifecycle
107
+ // ─────────────────────────────────────────────────────────────────────────
108
+
109
+ /**
110
+ * Probe macOS for the BlackHole capture + playback devices via
111
+ * SwitchAudioSource. Same signature as AudioBridge.checkDevices so the
112
+ * huddle-server doesn't care which brain is in use.
113
+ */
114
+ async checkDevices() {
115
+ const result = { capture: false, playback: false };
116
+ try {
117
+ const devices = execFileSync("SwitchAudioSource", ["-a"], { encoding: "utf8" });
118
+ result.capture = devices.includes(this.captureDevice);
119
+ result.playback = devices.includes(this.playbackDevice);
120
+ } catch { /* SwitchAudioSource not installed */ }
121
+ return result;
122
+ }
123
+
124
+ async start() {
125
+ if (this.isCapturing) return;
126
+
127
+ // Build the full instructions block via context-loader — same one the
128
+ // PSTN / outbound paths use. Identity + queues + decisions + team + RAG.
129
+ let instructions;
130
+ try {
131
+ const ctx = await import("../../lib/voice/context-loader.mjs");
132
+ instructions = await ctx.buildContextualInstructions({
133
+ agentRoot: this.agentRoot,
134
+ peer: this.peer,
135
+ callKind: "inbound",
136
+ brief: this.brief,
137
+ });
138
+ } catch (err) {
139
+ this.emit("error", { component: "context-loader", message: err.message });
140
+ instructions = `You are an AI executive operator. Reply briefly in voice mode.`;
141
+ }
142
+
143
+ await this._connectRealtime(instructions);
144
+ await this._startPlayback();
145
+ await this._startCapture();
146
+
147
+ this.isCapturing = true;
148
+ this.emit("capture:started");
149
+ }
150
+
151
+ async stop() {
152
+ this.isCapturing = false;
153
+
154
+ if (this.captureProcess) {
155
+ try { this.captureProcess.kill("SIGTERM"); } catch { /* */ }
156
+ this.captureProcess = null;
157
+ }
158
+ if (this.playbackProcess) {
159
+ try { this.playbackProcess.stdin?.end(); } catch { /* */ }
160
+ try { this.playbackProcess.kill("SIGTERM"); } catch { /* */ }
161
+ this.playbackProcess = null;
162
+ }
163
+ if (this.ws) {
164
+ try { this.ws.close(); } catch { /* */ }
165
+ this.ws = null;
166
+ }
167
+ this.emit("capture:stopped");
168
+ }
169
+
170
+ /**
171
+ * Force the model to say a specific line. Used by huddle-server.mjs for
172
+ * the initial greeting and the closing farewell. Translates to a
173
+ * conversation.item.create with a system-style message + response.create
174
+ * with explicit instructions — the model paraphrases but stays on-topic.
175
+ */
176
+ async speak(text) {
177
+ if (!text?.trim() || !this.ws || this.ws.readyState !== WebSocket.OPEN) return;
178
+
179
+ this._sendWs({
180
+ type: "conversation.item.create",
181
+ item: {
182
+ type: "message",
183
+ role: "user",
184
+ content: [{
185
+ type: "input_text",
186
+ text: `[system instruction] On your next turn, say exactly: "${text}". After saying it, await the other party's response.`,
187
+ }],
188
+ },
189
+ });
190
+ this._sendWs({ type: "response.create" });
191
+ }
192
+
193
+ // ─────────────────────────────────────────────────────────────────────────
194
+ // Realtime WebSocket
195
+ // ─────────────────────────────────────────────────────────────────────────
196
+
197
+ async _connectRealtime(instructions) {
198
+ const url = `wss://api.openai.com/v1/realtime?model=${encodeURIComponent(MODEL)}`;
199
+ this.ws = new WebSocket(url, {
200
+ headers: { Authorization: `Bearer ${this.apiKey}` },
201
+ });
202
+
203
+ await new Promise((resolveOpen, rejectOpen) => {
204
+ this.ws.once("open", resolveOpen);
205
+ this.ws.once("error", rejectOpen);
206
+ });
207
+
208
+ // Push initial session config (GA schema: nested audio.input/output).
209
+ this._sendWs({
210
+ type: "session.update",
211
+ session: {
212
+ type: "realtime",
213
+ output_modalities: ["audio"],
214
+ instructions,
215
+ audio: {
216
+ input: {
217
+ format: { type: "audio/pcm", rate: SAMPLE_RATE },
218
+ turn_detection: {
219
+ type: "server_vad",
220
+ threshold: 0.55,
221
+ prefix_padding_ms: 300,
222
+ silence_duration_ms: 700,
223
+ },
224
+ transcription: { model: "whisper-1" },
225
+ },
226
+ output: {
227
+ format: { type: "audio/pcm", rate: SAMPLE_RATE },
228
+ voice: this.voice,
229
+ },
230
+ },
231
+ tools: this.tools,
232
+ tool_choice: this.tools.length ? "auto" : "none",
233
+ },
234
+ });
235
+
236
+ this.ws.on("message", (data) => this._onWsMessage(data));
237
+ this.ws.on("close", () => { this.responseInProgress = false; });
238
+ this.ws.on("error", (err) => {
239
+ this.emit("error", { component: "openai-realtime", message: err.message });
240
+ });
241
+ }
242
+
243
+ _sendWs(obj) {
244
+ if (!this.ws || this.ws.readyState !== WebSocket.OPEN) return;
245
+ try { this.ws.send(JSON.stringify(obj)); } catch { /* */ }
246
+ }
247
+
248
+ _onWsMessage(data) {
249
+ let evt;
250
+ try { evt = JSON.parse(data.toString()); }
251
+ catch { return; }
252
+
253
+ switch (evt.type) {
254
+ case "response.created":
255
+ this.responseInProgress = true;
256
+ this.currentResponse = "";
257
+ this.emit("speech:start");
258
+ break;
259
+
260
+ case "response.audio.delta":
261
+ case "response.output_audio.delta": {
262
+ const b64 = evt.delta || evt.audio || "";
263
+ if (b64 && this.playbackProcess?.stdin) {
264
+ const pcm = Buffer.from(b64, "base64");
265
+ try { this.playbackProcess.stdin.write(pcm); } catch { /* */ }
266
+ }
267
+ break;
268
+ }
269
+
270
+ case "response.audio_transcript.delta":
271
+ case "response.output_audio_transcript.delta":
272
+ if (evt.delta) this.currentResponse += evt.delta;
273
+ break;
274
+
275
+ case "response.audio_transcript.done":
276
+ case "response.output_audio_transcript.done":
277
+ if (this.currentResponse.trim()) {
278
+ this.emit("transcript", { role: "assistant", text: this.currentResponse.trim() });
279
+ }
280
+ break;
281
+
282
+ case "response.done":
283
+ case "response.completed":
284
+ this.responseInProgress = false;
285
+ this.emit("speech:end");
286
+ break;
287
+
288
+ case "conversation.item.input_audio_transcription.delta":
289
+ if (evt.delta) {
290
+ this.currentTranscript += evt.delta;
291
+ this.emit("transcript:interim", { text: this.currentTranscript });
292
+ }
293
+ break;
294
+
295
+ case "conversation.item.input_audio_transcription.completed":
296
+ if (evt.transcript) {
297
+ this.emit("transcript", { role: "user", text: evt.transcript });
298
+ }
299
+ this.currentTranscript = "";
300
+ break;
301
+
302
+ case "input_audio_buffer.speech_started":
303
+ // User started talking — request a cancel if the model is mid-reply.
304
+ // OpenAI's server_vad does the barge-in detection; this just stops
305
+ // the audio playback eagerly so the user's voice isn't trampled.
306
+ if (this.responseInProgress) {
307
+ this._sendWs({ type: "response.cancel" });
308
+ }
309
+ // Drain the playback buffer.
310
+ if (this.playbackProcess?.stdin) {
311
+ // sox stdin has no flush API; emit a tone of silence to unblock.
312
+ // The next response.audio.delta will pick up immediately.
313
+ }
314
+ break;
315
+
316
+ // Function calling (live tool use during huddle)
317
+ case "response.function_call_arguments.done": {
318
+ const callId = evt.call_id;
319
+ const name = evt.name;
320
+ const args = evt.arguments;
321
+ this.emit("tool:call", { name, arguments: args, call_id: callId });
322
+ this._handleFunctionCall(callId, name, args).catch((err) => {
323
+ this.emit("error", { component: "tool-call", message: err.message });
324
+ });
325
+ break;
326
+ }
327
+
328
+ case "error":
329
+ this.emit("error", {
330
+ component: "openai-realtime",
331
+ message: evt.error?.message || JSON.stringify(evt),
332
+ });
333
+ break;
334
+
335
+ default:
336
+ // many event types we don't need: response.created subtypes, audio
337
+ // buffer state etc. silent ignore.
338
+ break;
339
+ }
340
+ }
341
+
342
+ async _handleFunctionCall(callId, name, argsJson) {
343
+ if (!this.executeTool) {
344
+ // No executor wired — return a stub so the model doesn't hang.
345
+ this._sendFunctionResult(callId, JSON.stringify({
346
+ ok: false,
347
+ error: "no_tool_executor_wired_in_huddle_bridge",
348
+ }));
349
+ return;
350
+ }
351
+ let parsed = {};
352
+ try { parsed = JSON.parse(argsJson || "{}"); } catch { /* */ }
353
+ let result;
354
+ try {
355
+ result = await this.executeTool({ name, arguments: parsed, call_id: callId });
356
+ } catch (err) {
357
+ result = { ok: false, error: err.message };
358
+ }
359
+ this._sendFunctionResult(callId, typeof result === "string" ? result : JSON.stringify(result));
360
+ }
361
+
362
+ _sendFunctionResult(callId, output) {
363
+ this._sendWs({
364
+ type: "conversation.item.create",
365
+ item: {
366
+ type: "function_call_output",
367
+ call_id: callId,
368
+ output,
369
+ },
370
+ });
371
+ this._sendWs({ type: "response.create" });
372
+ }
373
+
374
+ // ─────────────────────────────────────────────────────────────────────────
375
+ // Audio capture (BlackHole 2ch → sox → WS append)
376
+ // ─────────────────────────────────────────────────────────────────────────
377
+
378
+ async _startCapture() {
379
+ // sox -t coreaudio "BlackHole 2ch" -t raw -r 24000 -e signed-integer -b 16 -c 1 -
380
+ // streams raw little-endian PCM16 mono at 24kHz to stdout. We split the
381
+ // stream into ~40ms windows + base64-encode for OpenAI's
382
+ // input_audio_buffer.append messages.
383
+ this.captureProcess = spawn("sox", [
384
+ "-t", "coreaudio", this.captureDevice,
385
+ "-t", "raw",
386
+ "-r", String(SAMPLE_RATE),
387
+ "-e", "signed-integer",
388
+ "-b", String(BIT_DEPTH),
389
+ "-c", String(CHANNELS),
390
+ "-",
391
+ ], { stdio: ["ignore", "pipe", "pipe"] });
392
+
393
+ let leftover = Buffer.alloc(0);
394
+ this.captureProcess.stdout.on("data", (chunk) => {
395
+ if (!this.ws || this.ws.readyState !== WebSocket.OPEN) return;
396
+ // Concatenate with any trailing fractional-frame bytes from last tick
397
+ // and re-slice into clean CHUNK_BYTES windows.
398
+ const combined = leftover.length ? Buffer.concat([leftover, chunk]) : chunk;
399
+ const usable = Math.floor(combined.length / CAPTURE_CHUNK_BYTES) * CAPTURE_CHUNK_BYTES;
400
+ for (let off = 0; off < usable; off += CAPTURE_CHUNK_BYTES) {
401
+ const slice = combined.slice(off, off + CAPTURE_CHUNK_BYTES);
402
+ this._sendWs({
403
+ type: "input_audio_buffer.append",
404
+ audio: slice.toString("base64"),
405
+ });
406
+ }
407
+ leftover = combined.slice(usable);
408
+ });
409
+ this.captureProcess.stderr.on("data", (chunk) => {
410
+ const line = chunk.toString().trim();
411
+ if (line && !/^In:/.test(line)) {
412
+ // sox emits progress lines on stderr; ignore those.
413
+ this.emit("error", { component: "sox-capture", message: line });
414
+ }
415
+ });
416
+ this.captureProcess.on("exit", (code, signal) => {
417
+ if (this.isCapturing && code !== 0 && signal !== "SIGTERM") {
418
+ this.emit("error", {
419
+ component: "sox-capture",
420
+ message: `capture exited code=${code} signal=${signal}`,
421
+ });
422
+ }
423
+ });
424
+ }
425
+
426
+ // ─────────────────────────────────────────────────────────────────────────
427
+ // Audio playback (WS audio.delta → sox stdin → BlackHole 16ch)
428
+ // ─────────────────────────────────────────────────────────────────────────
429
+
430
+ async _startPlayback() {
431
+ // sox reads raw PCM16 from stdin and outputs it on the coreaudio
432
+ // playback device. The continuous-stdin pipe keeps the device live so
433
+ // each audio.delta plays immediately (no per-chunk device-open latency).
434
+ this.playbackProcess = spawn("sox", [
435
+ "-t", "raw",
436
+ "-r", String(SAMPLE_RATE),
437
+ "-e", "signed-integer",
438
+ "-b", String(BIT_DEPTH),
439
+ "-c", String(CHANNELS),
440
+ "-",
441
+ "-t", "coreaudio", this.playbackDevice,
442
+ ], { stdio: ["pipe", "ignore", "pipe"] });
443
+
444
+ this.playbackProcess.stderr.on("data", (chunk) => {
445
+ const line = chunk.toString().trim();
446
+ if (line && !/^In:/.test(line)) {
447
+ this.emit("error", { component: "sox-playback", message: line });
448
+ }
449
+ });
450
+ this.playbackProcess.on("exit", (code, signal) => {
451
+ if (this.isCapturing && code !== 0 && signal !== "SIGTERM") {
452
+ this.emit("error", {
453
+ component: "sox-playback",
454
+ message: `playback exited code=${code} signal=${signal}`,
455
+ });
456
+ }
457
+ });
458
+ this.emit("tts:start");
459
+ }
460
+ }
461
+
462
+ export default OpenAIRealtimeHuddleBridge;