@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,532 @@
1
+ /**
2
+ * Maestro — OpenAI Realtime Voice Cloud Relay (Railway)
3
+ *
4
+ * Public HTTPS service deployed on Railway that bridges OpenAI's
5
+ * `realtime.call.incoming` webhook into a Mac-mini-resident maestro
6
+ * daemon over a buffer-and-poll channel.
7
+ *
8
+ * Why this exists: cloudflared "trycloudflare.com" URLs are ephemeral
9
+ * (rotate on every restart). For a production OpenAI webhook config
10
+ * we need a stable URL that never changes.
11
+ *
12
+ * Architecture:
13
+ * 1. OpenAI POSTs realtime.call.incoming → this service
14
+ * 2. Service verifies signature (HMAC-SHA256 against OPENAI_WEBHOOK_SECRET)
15
+ * 3. Service buffers the event in memory (TTL 10 min, dedup by call_id)
16
+ * 4. Service returns 200 immediately so OpenAI's call setup proceeds
17
+ * 5. Local Mac polls /openai/realtime/calls every 2-5s, sees new
18
+ * events, calls OpenAI's /v1/realtime/calls/<id>/accept directly
19
+ * from the Mac, then opens the WebSocket from the Mac. Tool
20
+ * bridge / RAG / context-loading all happen on the Mac with
21
+ * zero round-trips through Railway.
22
+ *
23
+ * Endpoints:
24
+ * POST /openai/realtime/incoming — OpenAI webhook receiver. Verifies
25
+ * signature, buffers event, ack 200.
26
+ * GET /openai/realtime/calls — Drain buffer (auth-required: bearer
27
+ * VOICE_RELAY_POLL_TOKEN).
28
+ * POST /openai/realtime/test — Echo back; for connectivity probes.
29
+ * GET /health — Liveness probe.
30
+ *
31
+ * Env (set via Railway dashboard or `railway variables`):
32
+ * PORT — provided by Railway
33
+ * OPENAI_WEBHOOK_SECRET — copy from OpenAI dashboard "signing secret"
34
+ * VOICE_RELAY_POLL_TOKEN — random hex; the Mac uses this as bearer
35
+ * BUFFER_TTL_MS — optional, default 600000 (10 min)
36
+ */
37
+
38
+ import http from "node:http";
39
+ import crypto from "node:crypto";
40
+ import { WebSocketServer, WebSocket } from "ws";
41
+
42
+ const PORT = parseInt(process.env.PORT || "8080", 10);
43
+ const OPENAI_WEBHOOK_SECRET = process.env.OPENAI_WEBHOOK_SECRET || "";
44
+ const POLL_TOKEN = process.env.VOICE_RELAY_POLL_TOKEN || "";
45
+ const OPENAI_API_KEY = process.env.OPENAI_API_KEY || "";
46
+ const VOICE_TWIML_TOKEN = process.env.VOICE_TWIML_TOKEN || ""; // bearer for /twilio/twiml endpoints
47
+ const BUFFER_TTL_MS = parseInt(process.env.BUFFER_TTL_MS || "600000", 10);
48
+ const MAX_BUFFER_SIZE = parseInt(process.env.MAX_BUFFER_SIZE || "500", 10);
49
+
50
+ if (!OPENAI_WEBHOOK_SECRET) console.warn("[warn] OPENAI_WEBHOOK_SECRET not set — webhook signature verification disabled");
51
+ if (!POLL_TOKEN) console.warn("[warn] VOICE_RELAY_POLL_TOKEN not set — poll endpoint is unauthenticated");
52
+ if (!OPENAI_API_KEY) console.warn("[warn] OPENAI_API_KEY not set — outbound Media Streams bridge disabled");
53
+
54
+ // ── In-memory buffer ───────────────────────────────────────────────────────
55
+
56
+ class EventBuffer {
57
+ constructor(name) { this.name = name; this.events = []; this.seen = new Map(); }
58
+ push(event) {
59
+ const id = event.id;
60
+ if (id && this.seen.has(id)) return false;
61
+ if (id) this.seen.set(id, Date.now());
62
+ this.events.push(event);
63
+ this.gc();
64
+ return true;
65
+ }
66
+ drain() { return this.events.splice(0, this.events.length); }
67
+ gc() {
68
+ const now = Date.now();
69
+ while (this.events.length && now - new Date(this.events[0].received_at).getTime() > BUFFER_TTL_MS) {
70
+ this.events.shift();
71
+ }
72
+ while (this.events.length > MAX_BUFFER_SIZE) this.events.shift();
73
+ for (const [id, ts] of this.seen) if (now - ts > BUFFER_TTL_MS) this.seen.delete(id);
74
+ }
75
+ }
76
+ const buffer = new EventBuffer("openai_voice");
77
+ setInterval(() => buffer.gc(), 60000).unref();
78
+
79
+ // ── HTTP helpers ───────────────────────────────────────────────────────────
80
+
81
+ function readBody(req) {
82
+ return new Promise((resolve, reject) => {
83
+ let data = "";
84
+ req.setEncoding("utf-8");
85
+ req.on("data", (c) => { data += c; });
86
+ req.on("end", () => resolve(data));
87
+ req.on("error", reject);
88
+ });
89
+ }
90
+ function json(res, status, payload) {
91
+ res.writeHead(status, { "content-type": "application/json" });
92
+ res.end(JSON.stringify(payload));
93
+ }
94
+ function text(res, status, body) {
95
+ res.writeHead(status, { "content-type": "text/plain" });
96
+ res.end(body);
97
+ }
98
+
99
+ // ── OpenAI webhook signature verification ──────────────────────────────────
100
+ // OpenAI sends `openai-signature: <hex>` (or sometimes `webhook-signature:
101
+ // t=<ts>,v1=<hex>` Stripe-style). We accept either.
102
+
103
+ function verifyOpenAISignature(rawBody, headers) {
104
+ if (!OPENAI_WEBHOOK_SECRET) return true; // dev mode
105
+ const headerSig = headers["openai-signature"] || headers["webhook-signature"] || "";
106
+ if (!headerSig) return false;
107
+ let providedHex = headerSig;
108
+ if (headerSig.includes(",")) {
109
+ const parts = Object.fromEntries(
110
+ headerSig.split(",").map((kv) => kv.split("=").map((s) => s.trim()))
111
+ );
112
+ providedHex = parts.v1 || parts.v0 || "";
113
+ }
114
+ const expected = crypto.createHmac("sha256", OPENAI_WEBHOOK_SECRET).update(rawBody || "").digest("hex");
115
+ if (providedHex.length !== expected.length) return false;
116
+ try {
117
+ return crypto.timingSafeEqual(Buffer.from(providedHex), Buffer.from(expected));
118
+ } catch {
119
+ return false;
120
+ }
121
+ }
122
+
123
+ function checkPollAuth(req, res) {
124
+ if (!POLL_TOKEN) return true;
125
+ const auth = req.headers["authorization"] || "";
126
+ if (auth === `Bearer ${POLL_TOKEN}`) return true;
127
+ text(res, 401, "unauthorized");
128
+ return false;
129
+ }
130
+
131
+ // ── Handlers ───────────────────────────────────────────────────────────────
132
+
133
+ async function handleIncoming(req, res) {
134
+ const rawBody = await readBody(req);
135
+ if (!verifyOpenAISignature(rawBody, req.headers)) {
136
+ console.warn("[openai-voice] signature mismatch");
137
+ return json(res, 403, { ok: false, error: "openai_signature_invalid" });
138
+ }
139
+ let parsed;
140
+ try { parsed = JSON.parse(rawBody); }
141
+ catch { return json(res, 400, { ok: false, error: "invalid_json" }); }
142
+
143
+ const callId = parsed.data?.call_id || parsed.call_id || `nocallid-${Date.now()}`;
144
+ const eventType = parsed.type || "unknown";
145
+
146
+ // Critical-path: OpenAI gives us roughly 5 seconds to POST /accept on a
147
+ // realtime.call.incoming webhook. The Mac-mini poller runs at ~750ms
148
+ // intervals, plus there's a ~50–200ms hop home, plus accept latency —
149
+ // worst-case we miss the window. So accept FROM RAILWAY immediately;
150
+ // the local handler then just attaches the event WebSocket whenever
151
+ // its next poll picks the event up. Audio is bridged inside OpenAI, so
152
+ // the local WS attach can lag by hundreds of ms without harm.
153
+ let acceptedByRelay = false;
154
+ let acceptError = null;
155
+ if (eventType === "realtime.call.incoming" && OPENAI_API_KEY && callId && !callId.startsWith("nocallid-")) {
156
+ try {
157
+ const acceptResp = await fetch(`https://api.openai.com/v1/realtime/calls/${encodeURIComponent(callId)}/accept`, {
158
+ method: "POST",
159
+ headers: { Authorization: `Bearer ${OPENAI_API_KEY}` },
160
+ });
161
+ if (acceptResp.ok) {
162
+ acceptedByRelay = true;
163
+ console.log(`[openai-voice] relay-accepted call=${callId} (status=${acceptResp.status})`);
164
+ } else {
165
+ acceptError = `accept ${acceptResp.status}: ${(await acceptResp.text()).slice(0, 200)}`;
166
+ console.warn(`[openai-voice] relay-accept failed call=${callId}: ${acceptError}`);
167
+ }
168
+ } catch (err) {
169
+ acceptError = err.message;
170
+ console.warn(`[openai-voice] relay-accept threw call=${callId}: ${err.message}`);
171
+ }
172
+ }
173
+
174
+ const accepted = buffer.push({
175
+ id: `${callId}-${eventType}`,
176
+ received_at: new Date().toISOString(),
177
+ event_type: eventType,
178
+ call_id: callId,
179
+ accepted_by_relay: acceptedByRelay,
180
+ accept_error: acceptError,
181
+ // Store the parsed object for human inspection AND the raw body
182
+ // bytes so the local poller can re-verify HMAC against what OpenAI
183
+ // actually signed (re-stringifying parsed loses key-order/whitespace
184
+ // and breaks signature validation downstream).
185
+ payload: parsed,
186
+ raw_body: rawBody,
187
+ });
188
+ console.log(`[openai-voice] ${accepted ? "buffered" : "dedup"} ${eventType} call=${callId} accepted_by_relay=${acceptedByRelay}`);
189
+ return json(res, 200, { ok: true, dedup: !accepted, call_id: callId, accepted_by_relay: acceptedByRelay });
190
+ }
191
+
192
+ function handlePoll(req, res) {
193
+ if (!checkPollAuth(req, res)) return;
194
+ const events = buffer.drain();
195
+ return json(res, 200, {
196
+ ok: true,
197
+ count: events.length,
198
+ events,
199
+ server_time: new Date().toISOString(),
200
+ });
201
+ }
202
+
203
+ function handleTest(req, res) {
204
+ return json(res, 200, {
205
+ ok: true,
206
+ service: "openai-voice-cloud-relay",
207
+ signature_required: !!OPENAI_WEBHOOK_SECRET,
208
+ poll_auth_required: !!POLL_TOKEN,
209
+ server_time: new Date().toISOString(),
210
+ });
211
+ }
212
+
213
+ function handleHealth(req, res) {
214
+ return json(res, 200, {
215
+ ok: true,
216
+ service: "openai-voice-cloud-relay",
217
+ uptime_sec: Math.round(process.uptime()),
218
+ buffer: { events: buffer.events.length, seen: buffer.seen.size },
219
+ bridges: { active: activeBridges.size },
220
+ config: {
221
+ signature_required: !!OPENAI_WEBHOOK_SECRET,
222
+ poll_auth_required: !!POLL_TOKEN,
223
+ twiml_auth_required: !!VOICE_TWIML_TOKEN,
224
+ openai_key_loaded: !!OPENAI_API_KEY,
225
+ media_streams_enabled: !!OPENAI_API_KEY,
226
+ buffer_ttl_ms: BUFFER_TTL_MS,
227
+ },
228
+ server_time: new Date().toISOString(),
229
+ });
230
+ }
231
+
232
+ // ── Outbound bridge state ──────────────────────────────────────────────────
233
+ // When a call is queued via /twilio/queue-outbound, we store its brief +
234
+ // session config keyed by callSid (Twilio's identifier) so the WebSocket
235
+ // handler can fetch it when Twilio connects with that callSid.
236
+ const queuedOutbound = new Map(); // callSid → { brief, voice, instructions, identity }
237
+ const QUEUED_TTL_MS = 5 * 60_000;
238
+ function gcQueued() {
239
+ const now = Date.now();
240
+ for (const [k, v] of queuedOutbound) {
241
+ if (now - v.queued_at > QUEUED_TTL_MS) queuedOutbound.delete(k);
242
+ }
243
+ }
244
+ setInterval(gcQueued, 60_000).unref();
245
+
246
+ // Active bridges (Twilio WS ↔ OpenAI WS), keyed by Twilio streamSid.
247
+ const activeBridges = new Map();
248
+
249
+ // ── Twilio outbound endpoints ──────────────────────────────────────────────
250
+
251
+ function checkTwimlAuth(req, res, url) {
252
+ // Bearer token via Authorization header OR ?token= query param (Twilio
253
+ // may not set Authorization on TwiML callbacks, so query param is the
254
+ // fallback).
255
+ if (!VOICE_TWIML_TOKEN) return true;
256
+ const tokenQ = url.searchParams.get("token");
257
+ const auth = req.headers["authorization"] || "";
258
+ if (auth === `Bearer ${VOICE_TWIML_TOKEN}`) return true;
259
+ if (tokenQ && tokenQ === VOICE_TWIML_TOKEN) return true;
260
+ text(res, 401, "unauthorized");
261
+ return false;
262
+ }
263
+
264
+ /**
265
+ * POST /twilio/queue-outbound
266
+ * Body: { call_sid, brief, voice?, identity?, session?, peer?, peer_name? }
267
+ *
268
+ * Called by the LOCAL agent right before / after it tells Twilio to dial
269
+ * the user's PSTN number. Stores the session config keyed by the Twilio
270
+ * call_sid so when Twilio connects to /twilio/stream with that streamSid,
271
+ * we know what to inject as session instructions.
272
+ */
273
+ async function handleQueueOutbound(req, res, url) {
274
+ if (!checkTwimlAuth(req, res, url)) return;
275
+ const rawBody = await readBody(req);
276
+ let parsed;
277
+ try { parsed = JSON.parse(rawBody); }
278
+ catch { return json(res, 400, { ok: false, error: "invalid_json" }); }
279
+
280
+ const callSid = parsed.call_sid;
281
+ if (!callSid) return json(res, 400, { ok: false, error: "missing call_sid" });
282
+ queuedOutbound.set(callSid, { ...parsed, queued_at: Date.now() });
283
+ console.log(`[twilio-outbound] queued config for call_sid=${callSid} (${parsed.peer_name || parsed.peer || "?"})`);
284
+ return json(res, 200, { ok: true, call_sid: callSid });
285
+ }
286
+
287
+ /**
288
+ * GET/POST /twilio/twiml
289
+ * Returns TwiML that tells Twilio to <Connect><Stream> to our WSS endpoint.
290
+ * Called by Twilio when an outbound call is answered (we set this URL as
291
+ * the call's TwiML when invoking the Twilio Voice API).
292
+ */
293
+ async function handleTwiml(req, res, url) {
294
+ if (!checkTwimlAuth(req, res, url)) return;
295
+ const host = req.headers["x-forwarded-host"] || req.headers.host;
296
+ const proto = (req.headers["x-forwarded-proto"] || "https") === "http" ? "ws" : "wss";
297
+ const streamUrl = `${proto}://${host}/twilio/stream${VOICE_TWIML_TOKEN ? `?token=${encodeURIComponent(VOICE_TWIML_TOKEN)}` : ""}`;
298
+ const twiml = `<?xml version="1.0" encoding="UTF-8"?>
299
+ <Response>
300
+ <Connect>
301
+ <Stream url="${streamUrl.replace(/&/g, "&amp;")}" />
302
+ </Connect>
303
+ </Response>`;
304
+ console.log(`[twilio-twiml] served TwiML (req from ${req.socket.remoteAddress}, ua=${(req.headers["user-agent"] || "?").slice(0, 60)})`);
305
+ res.writeHead(200, { "content-type": "text/xml" });
306
+ res.end(twiml);
307
+ }
308
+
309
+ // ── Server ────────────────────────────────────────────────────────────────
310
+
311
+ const server = http.createServer(async (req, res) => {
312
+ try {
313
+ const url = new URL(req.url, `http://${req.headers.host || "localhost"}`);
314
+ const path = url.pathname;
315
+ if (req.method === "POST" && path === "/openai/realtime/incoming") return handleIncoming(req, res);
316
+ if (req.method === "GET" && path === "/openai/realtime/calls") return handlePoll(req, res);
317
+ if (req.method === "POST" && path === "/openai/realtime/test") return handleTest(req, res);
318
+ if (req.method === "POST" && path === "/twilio/queue-outbound") return handleQueueOutbound(req, res, url);
319
+ if ((req.method === "GET" || req.method === "POST") && path === "/twilio/twiml") return handleTwiml(req, res, url);
320
+ if (req.method === "GET" && (path === "/health" || path === "/")) return handleHealth(req, res);
321
+ return text(res, 404, "not found");
322
+ } catch (err) {
323
+ console.error("[error]", err);
324
+ return text(res, 500, "internal error");
325
+ }
326
+ });
327
+
328
+ // ── WebSocket: Twilio Media Streams ⇄ OpenAI Realtime ──────────────────────
329
+ // Twilio opens a WSS connection to /twilio/stream. We:
330
+ // 1. Wait for Twilio's `start` event (gives us the streamSid + callSid)
331
+ // 2. Look up the queued session config for that callSid
332
+ // 3. Open a WSS to OpenAI's realtime endpoint
333
+ // 4. session.update with input/output audio = g711_ulaw + the queued
334
+ // instructions / tools
335
+ // 5. Bridge audio frames bidirectionally (no transcoding — both sides
336
+ // speak g711_ulaw natively)
337
+
338
+ const wss = new WebSocketServer({ noServer: true });
339
+
340
+ server.on("upgrade", (req, socket, head) => {
341
+ const url = new URL(req.url, `http://${req.headers.host || "localhost"}`);
342
+ if (url.pathname !== "/twilio/stream") {
343
+ socket.destroy();
344
+ return;
345
+ }
346
+ // Twilio Media Streams strips query params on WS upgrade, so we can't
347
+ // require ?token=. The endpoint is rate-limited by OpenAI's per-minute
348
+ // billing for any connection that does land here, and we log
349
+ // remoteAddress + user-agent so abuse is detectable. The TwiML
350
+ // endpoint that publishes the stream URL still requires
351
+ // VOICE_TWIML_TOKEN, so a malicious caller would need to know
352
+ // the WSS URL out-of-band to abuse this endpoint.
353
+ console.log(`[bridge] WS upgrade from ${req.socket.remoteAddress} ua=${(req.headers["user-agent"] || "?").slice(0, 60)} path=${url.pathname} qs=${url.search}`);
354
+ wss.handleUpgrade(req, socket, head, (ws) => {
355
+ handleTwilioStream(ws, req);
356
+ });
357
+ });
358
+
359
+ function handleTwilioStream(twilioWs, req) {
360
+ if (!OPENAI_API_KEY) {
361
+ console.warn("[bridge] cannot bridge — OPENAI_API_KEY not set");
362
+ twilioWs.close(1011, "openai key missing");
363
+ return;
364
+ }
365
+
366
+ let openaiWs = null;
367
+ let streamSid = null;
368
+ let callSid = null;
369
+ let openaiReady = false;
370
+ let bufferedFrames = []; // Twilio frames received before OpenAI is ready
371
+ let responseInProgress = false; // tracks whether a response.create is mid-flight
372
+
373
+ console.log(`[bridge] Twilio Media Stream connected from ${req.socket.remoteAddress}`);
374
+
375
+ const cleanup = (reason) => {
376
+ if (openaiWs && openaiWs.readyState !== WebSocket.CLOSED) {
377
+ try { openaiWs.close(1000, reason); } catch {}
378
+ }
379
+ if (twilioWs && twilioWs.readyState !== WebSocket.CLOSED) {
380
+ try { twilioWs.close(1000, reason); } catch {}
381
+ }
382
+ if (streamSid) activeBridges.delete(streamSid);
383
+ console.log(`[bridge] closed (${reason}) stream=${streamSid || "?"} call=${callSid || "?"}`);
384
+ };
385
+
386
+ twilioWs.on("error", (err) => {
387
+ console.error(`[bridge] twilio ws error: ${err.message}`);
388
+ });
389
+
390
+ twilioWs.on("close", () => cleanup("twilio_closed"));
391
+
392
+ twilioWs.on("message", (raw) => {
393
+ let evt;
394
+ try { evt = JSON.parse(raw.toString("utf-8")); }
395
+ catch { return; }
396
+
397
+ if (evt.event === "start") {
398
+ streamSid = evt.start?.streamSid;
399
+ callSid = evt.start?.callSid;
400
+ activeBridges.set(streamSid, { callSid, started: Date.now() });
401
+ console.log(`[bridge] start stream=${streamSid} call=${callSid}`);
402
+ const queued = queuedOutbound.get(callSid) || {};
403
+ openOpenAIWs(queued);
404
+ } else if (evt.event === "media") {
405
+ // Twilio sends 20ms of µ-law @ 8kHz, base64-encoded.
406
+ // OpenAI accepts the same bytes when session.audio.input.format = g711_ulaw.
407
+ const payload = evt.media?.payload;
408
+ if (!payload) return;
409
+ if (openaiReady) {
410
+ openaiWs.send(JSON.stringify({ type: "input_audio_buffer.append", audio: payload }));
411
+ } else {
412
+ bufferedFrames.push(payload);
413
+ if (bufferedFrames.length > 1500) bufferedFrames.shift(); // ~30s cap
414
+ }
415
+ } else if (evt.event === "stop") {
416
+ console.log(`[bridge] twilio stop stream=${streamSid}`);
417
+ // Don't close OpenAI yet — let the model finish current response if any
418
+ setTimeout(() => cleanup("twilio_stop"), 1500);
419
+ } else if (evt.event === "mark") {
420
+ // Twilio playback marker (we don't use these but log for debugging)
421
+ }
422
+ });
423
+
424
+ function openOpenAIWs(queued) {
425
+ const model = queued.model || "gpt-realtime-2";
426
+ const url = `wss://api.openai.com/v1/realtime?model=${encodeURIComponent(model)}`;
427
+ openaiWs = new WebSocket(url, {
428
+ headers: { Authorization: `Bearer ${OPENAI_API_KEY}` },
429
+ });
430
+
431
+ openaiWs.on("open", () => {
432
+ console.log(`[bridge] openai ws open model=${model}`);
433
+ // Send session config — use g711_ulaw on both sides so we don't transcode.
434
+ const sessionUpdate = buildSessionUpdate(queued);
435
+ openaiWs.send(JSON.stringify(sessionUpdate));
436
+ openaiReady = true;
437
+ // Drain any frames Twilio sent before OpenAI was ready
438
+ for (const payload of bufferedFrames) {
439
+ openaiWs.send(JSON.stringify({ type: "input_audio_buffer.append", audio: payload }));
440
+ }
441
+ bufferedFrames = [];
442
+ // Nudge the model to greet first.
443
+ openaiWs.send(JSON.stringify({ type: "response.create" }));
444
+ });
445
+
446
+ openaiWs.on("error", (err) => {
447
+ console.error(`[bridge] openai ws error: ${err.message}`);
448
+ });
449
+
450
+ openaiWs.on("close", (code, reason) => {
451
+ console.log(`[bridge] openai ws closed code=${code} reason=${reason}`);
452
+ cleanup("openai_closed");
453
+ });
454
+
455
+ openaiWs.on("message", (raw) => {
456
+ let evt;
457
+ try { evt = JSON.parse(raw.toString("utf-8")); }
458
+ catch { return; }
459
+
460
+ // Forward the model's audio back to Twilio.
461
+ if (evt.type === "response.audio.delta" || evt.type === "response.output_audio.delta") {
462
+ if (twilioWs.readyState === WebSocket.OPEN && streamSid && evt.delta) {
463
+ twilioWs.send(JSON.stringify({
464
+ event: "media",
465
+ streamSid,
466
+ media: { payload: evt.delta },
467
+ }));
468
+ }
469
+ } else if (evt.type === "input_audio_buffer.speech_started") {
470
+ // User started speaking → barge-in: tell Twilio to clear playback buffer.
471
+ // Only send response.cancel if a response is ACTUALLY running, otherwise
472
+ // OpenAI rejects with "response_cancel_not_active" (harmless but noisy).
473
+ if (twilioWs.readyState === WebSocket.OPEN && streamSid) {
474
+ twilioWs.send(JSON.stringify({ event: "clear", streamSid }));
475
+ }
476
+ if (responseInProgress) {
477
+ openaiWs.send(JSON.stringify({ type: "response.cancel" }));
478
+ responseInProgress = false;
479
+ }
480
+ } else if (evt.type === "response.created") {
481
+ responseInProgress = true;
482
+ } else if (evt.type === "error") {
483
+ console.error(`[bridge] openai error event: ${JSON.stringify(evt).slice(0, 300)}`);
484
+ } else if (evt.type === "session.created" || evt.type === "session.updated") {
485
+ // Quiet — no action needed
486
+ } else if (evt.type === "response.done") {
487
+ responseInProgress = false;
488
+ }
489
+ });
490
+ }
491
+ }
492
+
493
+ function buildSessionUpdate(queued) {
494
+ const voice = queued.voice || "cedar";
495
+ const identity = queued.identity || {};
496
+ // PREFER `instructions` (the full pre-loaded contextual prompt the
497
+ // local agent shipped — includes identity, memory, RAG, peer info,
498
+ // voice-mode rules) over the bare `brief`. The `brief` describes
499
+ // the task; `instructions` describes the whole agent.
500
+ const instructions = queued.instructions || queued.brief ||
501
+ `You are ${identity.name || "the agent"}, ${identity.role || "an executive operator"} at ${identity.company || "Northwind"}. Reply briefly in your voice.`;
502
+ return {
503
+ type: "session.update",
504
+ session: {
505
+ type: "realtime",
506
+ output_modalities: ["audio"],
507
+ instructions,
508
+ audio: {
509
+ input: {
510
+ format: { type: "audio/pcmu" }, // g711_ulaw — matches Twilio
511
+ turn_detection: {
512
+ type: "server_vad",
513
+ threshold: 0.5,
514
+ prefix_padding_ms: 300,
515
+ silence_duration_ms: 500,
516
+ },
517
+ },
518
+ output: {
519
+ format: { type: "audio/pcmu" },
520
+ voice,
521
+ },
522
+ },
523
+ tools: queued.tools || [],
524
+ tool_choice: queued.tool_choice || "auto",
525
+ },
526
+ };
527
+ }
528
+
529
+ server.listen(PORT, () => {
530
+ console.log(`[start] openai-voice-cloud-relay listening on :${PORT}`);
531
+ console.log(`[start] sig=${!!OPENAI_WEBHOOK_SECRET} poll_auth=${!!POLL_TOKEN} openai_key=${!!OPENAI_API_KEY} twiml_auth=${!!VOICE_TWIML_TOKEN}`);
532
+ });