@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,306 @@
1
+ /**
2
+ * telegram-adapter.test.mjs — node:test coverage for telegram/adapter.mjs
3
+ *
4
+ * Exercises `_toEvent` for DM vs group vs topic vs reaction vs voice using a
5
+ * FAKE grammY update object — the real `grammy` package is NEVER required
6
+ * (the adapter accepts an injected bot, and _toEvent is called directly).
7
+ * Hermetic: no network, no grammy install needed.
8
+ */
9
+
10
+ import { test } from "node:test";
11
+ import assert from "node:assert/strict";
12
+ import { promises as fsp } from "node:fs";
13
+ import { tmpdir } from "node:os";
14
+ import { join } from "node:path";
15
+
16
+ import { TelegramAdapter, createTelegramChannel } from "./telegram/adapter.mjs";
17
+ import { getPlatform } from "./contract.mjs";
18
+
19
+ async function makeRoot() {
20
+ const p = join(tmpdir(), `tg-test-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`);
21
+ await fsp.mkdir(p, { recursive: true });
22
+ return p;
23
+ }
24
+ async function rmRoot(p) { try { await fsp.rm(p, { recursive: true, force: true }); } catch { /* */ } }
25
+
26
+ /** A fake grammY Bot: records api calls, lets the test drive handlers. */
27
+ function fakeBot() {
28
+ const handlers = {};
29
+ const calls = { sendMessage: [], setMessageReaction: [], sendChatAction: [], getFile: [] };
30
+ return {
31
+ handlers,
32
+ calls,
33
+ on(type, fn) { handlers[type] = fn; },
34
+ start() { return Promise.resolve(); },
35
+ stop() { return Promise.resolve(); },
36
+ api: {
37
+ getMe: async () => ({ id: 555, username: "ravi_bot" }),
38
+ sendMessage: async (chatId, text, extra) => { calls.sendMessage.push({ chatId, text, extra }); return { message_id: 99 }; },
39
+ setMessageReaction: async (chatId, mid, reactions) => { calls.setMessageReaction.push({ chatId, mid, reactions }); return true; },
40
+ sendChatAction: async (chatId, action, extra) => { calls.sendChatAction.push({ chatId, action, extra }); return true; },
41
+ getFile: async (fileId) => { calls.getFile.push(fileId); return { file_path: "voice/file_1.ogg" }; },
42
+ },
43
+ };
44
+ }
45
+
46
+ function adapterWith(bot, opts = {}) {
47
+ return new TelegramAdapter({ agentRoot: opts.agentRoot || "/tmp", bot, botId: "555", botUsername: "ravi_bot", token: "T", ...opts });
48
+ }
49
+
50
+ // ---------------------------------------------------------------------------
51
+ // _toEvent — DM
52
+ // ---------------------------------------------------------------------------
53
+
54
+ test("_toEvent: private chat → DM message event", () => {
55
+ const a = adapterWith(fakeBot());
56
+ const ev = a._toEvent({
57
+ _kind: "message",
58
+ update: {
59
+ message: {
60
+ message_id: 12,
61
+ date: 1735689600,
62
+ text: "hey can you draft the memo?",
63
+ chat: { id: 987, type: "private" },
64
+ from: { id: 987, first_name: "Casey", username: "casey" },
65
+ },
66
+ },
67
+ });
68
+ assert.equal(ev.channel, "telegram");
69
+ assert.equal(ev.kind, "message");
70
+ assert.equal(ev.message_id, "12");
71
+ assert.equal(ev.channel_id, "987");
72
+ assert.equal(ev.source.userScope, "dm");
73
+ assert.equal(ev.source.chatId, "987");
74
+ assert.equal(ev.priority_signals.mentions_agent, true, "DM always mentions agent");
75
+ assert.equal(ev.raw_ref, "telegram:987:12");
76
+ assert.match(ev.subject, /^DM from/);
77
+ assert.equal(ev.ingest_source, "telegram");
78
+ });
79
+
80
+ test("_toEvent: drops the bot's own messages", () => {
81
+ const a = adapterWith(fakeBot());
82
+ const ev = a._toEvent({
83
+ _kind: "message",
84
+ update: { message: { message_id: 1, chat: { id: 1, type: "private" }, from: { id: 555 }, text: "echo" } },
85
+ });
86
+ assert.equal(ev, null, "self message must be dropped");
87
+ });
88
+
89
+ // ---------------------------------------------------------------------------
90
+ // _toEvent — group + @mention + cc
91
+ // ---------------------------------------------------------------------------
92
+
93
+ test("_toEvent: group message without mention → not directed", () => {
94
+ const a = adapterWith(fakeBot());
95
+ const ev = a._toEvent({
96
+ _kind: "message",
97
+ update: {
98
+ message: {
99
+ message_id: 30, date: 1735689600, text: "general chatter",
100
+ chat: { id: -100123, type: "supergroup", title: "ops" },
101
+ from: { id: 42, first_name: "Sam" },
102
+ },
103
+ },
104
+ });
105
+ assert.equal(ev.source.userScope, "group");
106
+ assert.equal(ev.kind, "message");
107
+ assert.equal(ev.priority_signals.mentions_agent, false);
108
+ assert.equal(ev.channel_id, "-100123");
109
+ });
110
+
111
+ test("_toEvent: group message that @mentions the bot → directed", () => {
112
+ const a = adapterWith(fakeBot());
113
+ const ev = a._toEvent({
114
+ _kind: "message",
115
+ update: {
116
+ message: {
117
+ message_id: 31, date: 1735689600,
118
+ text: "@ravi_bot can you look at this",
119
+ entities: [{ type: "mention", offset: 0, length: 9 }],
120
+ chat: { id: -100123, type: "supergroup", title: "ops" },
121
+ from: { id: 42, first_name: "Sam" },
122
+ },
123
+ },
124
+ });
125
+ assert.equal(ev.priority_signals.mentions_agent, true);
126
+ });
127
+
128
+ test("_toEvent: cc_groups makes a non-mention group message a channel_cc", async () => {
129
+ const root = await makeRoot();
130
+ try {
131
+ const a = adapterWith(fakeBot(), { agentRoot: root, config: { cc_groups: ["-100999"] } });
132
+ const ev = a._toEvent({
133
+ _kind: "message",
134
+ update: {
135
+ message: {
136
+ message_id: 40, date: 1735689600, text: "status update for the room",
137
+ chat: { id: -100999, type: "supergroup", title: "watched" },
138
+ from: { id: 7, first_name: "Jordan" },
139
+ },
140
+ },
141
+ });
142
+ assert.equal(ev.kind, "channel_cc");
143
+ } finally { await rmRoot(root); }
144
+ });
145
+
146
+ // ---------------------------------------------------------------------------
147
+ // _toEvent — topic (forum thread)
148
+ // ---------------------------------------------------------------------------
149
+
150
+ test("_toEvent: forum topic message maps message_thread_id → thread", () => {
151
+ const a = adapterWith(fakeBot());
152
+ const ev = a._toEvent({
153
+ _kind: "message",
154
+ update: {
155
+ message: {
156
+ message_id: 50, date: 1735689600, text: "in the deployments topic",
157
+ message_thread_id: 777,
158
+ chat: { id: -100555, type: "supergroup", title: "eng" },
159
+ from: { id: 8, first_name: "Pat" },
160
+ },
161
+ },
162
+ });
163
+ assert.equal(ev.thread_id, "777");
164
+ assert.equal(ev.source.threadRef, "777");
165
+ assert.equal(ev.is_reply, true, "topic messages count as threaded");
166
+ });
167
+
168
+ // ---------------------------------------------------------------------------
169
+ // _toEvent — reaction
170
+ // ---------------------------------------------------------------------------
171
+
172
+ test("_toEvent: message_reaction → reaction event", () => {
173
+ const a = adapterWith(fakeBot());
174
+ const ev = a._toEvent({
175
+ _kind: "reaction",
176
+ update: {
177
+ message_reaction: {
178
+ message_id: 60, date: 1735689600,
179
+ chat: { id: 987, type: "private" },
180
+ user: { id: 987, first_name: "Casey", username: "casey" },
181
+ new_reaction: [{ type: "emoji", emoji: "👍" }],
182
+ old_reaction: [],
183
+ },
184
+ },
185
+ });
186
+ assert.equal(ev.kind, "reaction");
187
+ assert.equal(ev.reaction.emoji, "👍");
188
+ assert.equal(ev.reaction.target_message_id, "60");
189
+ assert.equal(ev.source.replyToRef, "60");
190
+ assert.equal(ev.raw_ref, "telegram:987:60:reaction");
191
+ });
192
+
193
+ // ---------------------------------------------------------------------------
194
+ // _toEvent — voice note
195
+ // ---------------------------------------------------------------------------
196
+
197
+ test("_toEvent: voice note → voice_note kind + attachment + STT marker", () => {
198
+ const a = adapterWith(fakeBot());
199
+ const ev = a._toEvent({
200
+ _kind: "message",
201
+ update: {
202
+ message: {
203
+ message_id: 70, date: 1735689600,
204
+ voice: { file_id: "VOICE123", mime_type: "audio/ogg", file_size: 4096 },
205
+ chat: { id: 987, type: "private" },
206
+ from: { id: 987, first_name: "Casey" },
207
+ },
208
+ },
209
+ });
210
+ assert.equal(ev.kind, "voice_note");
211
+ const vn = ev.attachments.find((x) => x.type === "voice_note");
212
+ assert.ok(vn, "voice_note attachment present");
213
+ assert.equal(vn.size_bytes, 4096);
214
+ assert.equal(ev._voiceFileId, "VOICE123", "STT marker carries the file id");
215
+ });
216
+
217
+ test("voice STT enrichment routes transcript into text + attachment (fail-open ready)", async () => {
218
+ const root = await makeRoot();
219
+ try {
220
+ const bot = fakeBot();
221
+ // Inject a fake transcriber + intercept the file download.
222
+ const a = new TelegramAdapter({
223
+ agentRoot: root, bot, botId: "555", botUsername: "ravi_bot", token: "T",
224
+ transcribeVoiceNote: async () => "transcribed words",
225
+ });
226
+ // Stub _downloadFile so we don't hit the network.
227
+ a._downloadFile = async () => Buffer.from("oggbytes");
228
+ const ev = a._toEvent({
229
+ _kind: "message",
230
+ update: { message: { message_id: 71, date: 1735689600, voice: { file_id: "V", mime_type: "audio/ogg", file_size: 10 }, chat: { id: 987, type: "private" }, from: { id: 987, first_name: "Casey" } } },
231
+ });
232
+ const enriched = await a._enrichVoice(ev);
233
+ assert.equal(enriched.text, "transcribed words");
234
+ assert.equal(enriched.attachments.find((x) => x.type === "voice_note").transcript, "transcribed words");
235
+ } finally { await rmRoot(root); }
236
+ });
237
+
238
+ test("voice STT failure is fail-open (event still flows, empty transcript)", async () => {
239
+ const root = await makeRoot();
240
+ try {
241
+ const bot = fakeBot();
242
+ const a = new TelegramAdapter({
243
+ agentRoot: root, bot, botId: "555", token: "T",
244
+ transcribeVoiceNote: async () => { throw new Error("whisper down"); },
245
+ });
246
+ a._downloadFile = async () => Buffer.from("x");
247
+ const ev = a._toEvent({ _kind: "message", update: { message: { message_id: 72, date: 1, voice: { file_id: "V" }, chat: { id: 9, type: "private" }, from: { id: 9 } } } });
248
+ const enriched = await a._enrichVoice(ev); // must not throw
249
+ assert.equal(enriched.text, "", "transcript stays empty on STT failure");
250
+ } finally { await rmRoot(root); }
251
+ });
252
+
253
+ // ---------------------------------------------------------------------------
254
+ // outbound + typing
255
+ // ---------------------------------------------------------------------------
256
+
257
+ test("_send honours thread_ref and reply", async () => {
258
+ const bot = fakeBot();
259
+ const a = adapterWith(bot);
260
+ await a._send({ to: 987, text: "hi", thread_ref: 777, reply: 12 });
261
+ const call = bot.calls.sendMessage[0];
262
+ assert.equal(call.chatId, 987);
263
+ assert.equal(call.text, "hi");
264
+ assert.equal(call.extra.message_thread_id, 777);
265
+ assert.deepEqual(call.extra.reply_parameters, { message_id: 12 });
266
+ });
267
+
268
+ test("_react calls setMessageReaction with the emoji", async () => {
269
+ const bot = fakeBot();
270
+ const a = adapterWith(bot);
271
+ await a._react({ to: 987, messageRef: 60, emoji: "🎉" });
272
+ const call = bot.calls.setMessageReaction[0];
273
+ assert.equal(call.chatId, 987);
274
+ assert.equal(call.mid, 60);
275
+ assert.deepEqual(call.reactions, [{ type: "emoji", emoji: "🎉" }]);
276
+ });
277
+
278
+ test("_setTyping sends a typing chat action only for on=true", async () => {
279
+ const bot = fakeBot();
280
+ const a = adapterWith(bot);
281
+ await a._setTyping({ chatId: 987 }, true);
282
+ await a._setTyping({ chatId: 987 }, false); // no-op (auto-expires)
283
+ assert.equal(bot.calls.sendChatAction.length, 1);
284
+ assert.equal(bot.calls.sendChatAction[0].action, "typing");
285
+ });
286
+
287
+ // ---------------------------------------------------------------------------
288
+ // platform self-registration
289
+ // ---------------------------------------------------------------------------
290
+
291
+ test("telegram self-registers as a platform with the right gate file", () => {
292
+ const def = getPlatform("telegram");
293
+ assert.ok(def, "telegram platform must be registered on import");
294
+ assert.equal(def.gateFile, "config/telegram.yaml");
295
+ assert.equal(typeof def.factory, "function");
296
+ // Factory returns an adapter instance.
297
+ const inst = def.factory({ agentRoot: "/tmp", bot: fakeBot(), token: "T" });
298
+ assert.equal(inst.name, "telegram");
299
+ });
300
+
301
+ test("createTelegramChannel returns a TelegramAdapter", () => {
302
+ const inst = createTelegramChannel({ agentRoot: "/tmp", bot: fakeBot(), token: "T" });
303
+ assert.ok(inst instanceof TelegramAdapter);
304
+ assert.ok(typeof inst.start === "function");
305
+ assert.ok(typeof inst.send === "function");
306
+ });
@@ -0,0 +1,301 @@
1
+ /**
2
+ * Maestro — Voice channel adapter (OpenAI Realtime + Twilio SIP)
3
+ *
4
+ * Thin Channel-interface wrapper around lib/voice/. Voice "messages" are
5
+ * unusual: a "send" doesn't really make sense for a live call. We
6
+ * expose:
7
+ * - start({ onInbound }) — registers a SIP webhook handler factory
8
+ * that emits a MessageEvent when a call
9
+ * comes in, plus per-turn transcript chunks
10
+ * when the user speaks.
11
+ * - send({ to, text }) — optional outbound TTS-only message (e.g.
12
+ * speak a notification into a live call).
13
+ * For initiating cold calls, use the
14
+ * dedicated outbound-call subsystem.
15
+ *
16
+ * This adapter is a thin facade — the heavy lifting lives in
17
+ * lib/voice/{openai-realtime,twilio-sip-bridge,tool-bridge}.mjs.
18
+ *
19
+ * WS2 hardening (task #30 — PARTIAL refactor, see notes):
20
+ * --------------------------------------------------------
21
+ * Voice used to be a plain factory object that bypassed BaseAdapter, so its
22
+ * outbound `send()` (a live PSTN call brief, or a TTS voice message) was
23
+ * UNGATED. It now extends BaseAdapter and implements `_send`, so every outbound
24
+ * runs the ONE shared send-gate (banned-phrase / AI-disclosure / barrier
25
+ * screening on the spoken text/brief) BEFORE the call is placed, and inherits
26
+ * the ownId / peerAgentIds bot-loop guard.
27
+ *
28
+ * The reconnect SUPERVISOR is applied at the right granularity: voice has no
29
+ * single long-lived channel socket (the SIP webhook is mounted by the operator;
30
+ * the daemon's platform loop never starts a voice connection), so the channel
31
+ * itself runs with `supervise:false`. The real voice transport is the PER-CALL
32
+ * OpenAI Realtime provider — so each live call's `provider.connect` is wrapped
33
+ * in lib/util/reconnect.superviseConnection inside `attachBridges`, and the
34
+ * provider's CLOSED event mid-call drives `notifyDown` so a dropped session
35
+ * reconnects with bounded backoff instead of dying on the first blip. The
36
+ * supervisor is injectable for tests.
37
+ *
38
+ * @module lib/channels/voice/adapter
39
+ */
40
+
41
+ import { CHANNEL_CAPABILITIES } from "../channel.mjs";
42
+ import { buildSessionConfig } from "../../voice/index.mjs";
43
+ import { VOICE_EVENTS } from "../../voice/provider.mjs";
44
+ import { buildSipInboundHandler } from "../../voice/twilio-sip-bridge.mjs";
45
+ import { registerToolBridge, actionsToRealtimeTools } from "../../voice/tool-bridge.mjs";
46
+ import { buildContextualInstructions } from "../../voice/context-loader.mjs";
47
+ import { BaseAdapter } from "../base-adapter.mjs";
48
+ import { superviseConnection as defaultSupervise } from "../../util/reconnect.mjs";
49
+
50
+ const VOICE_CAPABILITIES = [
51
+ CHANNEL_CAPABILITIES.VOICE_NOTE,
52
+ CHANNEL_CAPABILITIES.MEDIA_AUDIO,
53
+ ];
54
+
55
+ /**
56
+ * Voice adapter. Extends BaseAdapter so `send()` runs the shared outbound
57
+ * send-gate before the transport (`_send`). The channel itself is unsupervised
58
+ * (no single socket); each live call's provider is supervised individually.
59
+ */
60
+ export class VoiceAdapter extends BaseAdapter {
61
+ constructor(opts = {}) {
62
+ // supervise:false — there is no single channel socket to supervise. The
63
+ // per-call provider is supervised in attachBridges() instead. sendGate stays
64
+ // ON (the whole point of this hardening). pairingMode:"open" — a call's
65
+ // identity is governed upstream by the SIP webhook (the caller already
66
+ // reached the trunk), so we don't pairing-gate inbound calls; the ownId /
67
+ // peerAgentIds bot-loop guard still runs in open mode. identity/audit/config
68
+ // preserved.
69
+ super({ name: "voice", capabilities: VOICE_CAPABILITIES, supervise: false, pairingMode: "open", ...opts });
70
+ this.config = opts.config || {};
71
+ this.identity = opts.identity || {};
72
+ this.audit = opts.audit;
73
+
74
+ /** @type {Set<import("../../voice/provider.mjs").VoiceProvider>} */
75
+ this._liveSessions = new Set();
76
+ /** Per-call reconnect supervisors so a dropped live session retries. */
77
+ this._callSupervisors = new Set();
78
+ // Injectable per-call supervisor (tests). The CHANNEL is unsupervised; this
79
+ // governs each live call's provider connection.
80
+ this._superviseCall =
81
+ typeof opts.superviseCall === "function" ? opts.superviseCall : defaultSupervise;
82
+ }
83
+
84
+ /**
85
+ * Build the SIP inbound HTTP handler — operators mount this on their existing
86
+ * webhook server. The handler accepts the call with OpenAI, opens an event
87
+ * WebSocket (now via a bounded-backoff supervisor), wires the tool bridge, and
88
+ * emits a MessageEvent describing the call to the daemon's inbox.
89
+ */
90
+ sipHandler() {
91
+ const self = this;
92
+ return buildSipInboundHandler({
93
+ sessionConfig: async ({ callId, body }) => {
94
+ const tools = await actionsToRealtimeTools({
95
+ accessLevel: self.config.tools?.access_level || "voice",
96
+ include: self.config.tools?.include,
97
+ exclude: self.config.tools?.exclude,
98
+ agentRoot: self.agentRoot,
99
+ });
100
+ // Pre-load a frozen context snapshot into the instructions field so the
101
+ // model walks into the call already knowing the agent's queues /
102
+ // decisions / priorities / peer history — instead of round-tripping
103
+ // search tools mid-call (1-3s of audible silence per query).
104
+ const peer = body?.from || body?.caller_id || body?.callerId || null;
105
+ const instructions = await buildContextualInstructions({
106
+ agentRoot: self.agentRoot,
107
+ peer,
108
+ callKind: "inbound",
109
+ });
110
+ return buildSessionConfig({ tools, instructions, ...self.config.session }, self.identity || {});
111
+ },
112
+ attachBridges: (provider, { callId }) => self._attachCall(provider, { callId, body: undefined }),
113
+ });
114
+ }
115
+
116
+ /**
117
+ * Wire a per-call provider: register it as live, attach the tool bridge, emit
118
+ * the inbound MessageEvent, and SUPERVISE the live session so a mid-call drop
119
+ * is surfaced + counted (RECONNECT_DOWN) instead of dying silently.
120
+ *
121
+ * Partial-refactor note: the SIP bridge (twilio-sip-bridge) OWNS the per-call
122
+ * `provider.connect(sessionCfg)` — it connects after attachBridges and tears
123
+ * the provider down on CLOSED. So we do NOT re-drive connect from here (that
124
+ * would double-connect the one-shot call-id WebSocket). Instead the supervisor
125
+ * we build has an ALREADY-CONNECTED connect resolver (the bridge connected),
126
+ * and a mid-call CLOSED drives notifyDown so the drop transition is observed
127
+ * and counted via the same `_onConnState` sink every channel uses. Outbound
128
+ * send() — the real ungated risk — is fully gated by BaseAdapter.send.
129
+ * Returns the detach function the bridge calls on teardown.
130
+ */
131
+ _attachCall(provider, { callId, body }) {
132
+ this._liveSessions.add(provider);
133
+ const detach = registerToolBridge(provider, {
134
+ allow: this.config.tools?.include,
135
+ audit: this.audit,
136
+ });
137
+
138
+ // Emit one MessageEvent on call connect so the daemon's inbox has a record.
139
+ // Subsequent transcript chunks are NOT emitted as separate inbox items —
140
+ // they're persisted to logs/voice/ but routed in-process to the voice worker.
141
+ if (typeof this._onInbound === "function") {
142
+ try {
143
+ this._handleEvent({
144
+ channel: "voice",
145
+ message_id: callId,
146
+ thread_id: `call:${callId}`,
147
+ kind: "call",
148
+ from: { id: body?.from || "unknown" },
149
+ text: "(voice call started)",
150
+ timestamp: new Date().toISOString(),
151
+ raw: body,
152
+ source: { channel: "voice", chatId: String(callId), userScope: "dm", peerId: body?.from || "unknown" },
153
+ });
154
+ } catch (err) {
155
+ this.log("error", `voice onInbound emit failed: ${err.message}`);
156
+ }
157
+ }
158
+
159
+ // Supervise THIS live session. The bridge has already opened the provider, so
160
+ // the supervisor's `connect` just resolves with the live provider (no second
161
+ // socket); a mid-call CLOSED is reported via notifyDown → a bounded-backoff
162
+ // DOWN transition the supervisor surfaces + we count (no silent death).
163
+ // Best-effort: if the supervisor can't be built, fall back to plain cleanup.
164
+ let supervisor = null;
165
+ let tornDown = false;
166
+ try {
167
+ supervisor = this._superviseCall({
168
+ connect: () => provider, // already connected by the SIP bridge
169
+ onState: (state, info) => this._onConnState(state, info),
170
+ maxAttempts: 1, // one-shot call-id WS can't be re-accepted; surface, don't thrash
171
+ setTimeout: this._setTimeout,
172
+ clearTimeout: this._clearTimeout,
173
+ rng: this._rng,
174
+ });
175
+ this._callSupervisors.add(supervisor);
176
+ } catch (err) {
177
+ this.log("warn", `voice call supervisor unavailable (fail-open): ${err.message}`);
178
+ }
179
+
180
+ const cleanup = (reason) => {
181
+ if (tornDown) return;
182
+ tornDown = true;
183
+ this._liveSessions.delete(provider);
184
+ if (supervisor) {
185
+ try { supervisor.stop?.(reason || "call-ended"); } catch { /* */ }
186
+ this._callSupervisors.delete(supervisor);
187
+ }
188
+ try { detach(); } catch { /* */ }
189
+ };
190
+
191
+ provider.on(VOICE_EVENTS.CLOSED, ({ reason } = {}) => {
192
+ // Report the drop to the supervisor (surfaced + counted) BEFORE cleanup so
193
+ // a dead session is never silent, then tear the call down.
194
+ if (supervisor && !tornDown) {
195
+ try { supervisor.notifyDown(reason || "voice-session-closed"); } catch { /* */ }
196
+ }
197
+ cleanup(reason);
198
+ });
199
+
200
+ // The bridge calls this detach on its own teardown path; route it through
201
+ // cleanup so the supervisor is stopped (no zombie supervisor after hangup).
202
+ return () => cleanup("bridge-detach");
203
+ }
204
+
205
+ // -- BaseAdapter hooks -----------------------------------------------------
206
+
207
+ /**
208
+ * Voice has no single long-lived inbound socket to open here — the SIP webhook
209
+ * is mounted by the operator via sipHandler(). start() just records onInbound
210
+ * (handled by BaseAdapter.start before this) and logs readiness.
211
+ */
212
+ async _connect(/* { onRaw, signal } */) {
213
+ this.log("info", "voice channel ready; mount sipHandler() at the configured webhook path");
214
+ }
215
+
216
+ /** Inbound MessageEvents are produced by the SIP bridge, not parsed here. */
217
+ _toEvent(raw) {
218
+ return raw || null;
219
+ }
220
+
221
+ /**
222
+ * Outbound voice transport (gated by BaseAdapter.send before this runs).
223
+ * _send({ to, text }) — auto-routes (WA voice note > PSTN)
224
+ * _send({ to, text, meta: {transport} }) — force whatsapp | slack | pstn
225
+ * _send({ to, brief, conversational: true })— initiate a live PSTN call
226
+ * bridged to OpenAI Realtime
227
+ *
228
+ * NOTE: BaseAdapter.send screens `out.text`. For the conversational/brief path
229
+ * the spoken content is `out.brief`, so we normalise it into `text` for the
230
+ * gate (see send() override below) — the gate must screen what is SPOKEN.
231
+ */
232
+ async _send(out) {
233
+ if (!out || !out.to) throw new Error("voice.send: msg.to required");
234
+
235
+ if (out.conversational === true || out.brief) {
236
+ const mod = await import("../../voice/outbound.mjs");
237
+ const result = await mod.placeOutboundCall({
238
+ to: out.to,
239
+ // Prefer `text` (the send-gate screens/redacts the spoken content into
240
+ // `text`; see send() override) and fall back to the raw `brief`.
241
+ brief: out.text || out.brief,
242
+ agentRoot: this.agentRoot,
243
+ voice: out.meta?.voice || this.config?.voice,
244
+ });
245
+ return { ok: true, message_id: result.call_id, raw: result };
246
+ }
247
+
248
+ if (!out.text) throw new Error("voice.send: msg.text required for voice-message mode");
249
+ const mod = await import("../../voice/outbound.mjs");
250
+ const result = await mod.sendVoiceMessage({
251
+ to: out.to,
252
+ text: out.text,
253
+ transport: out.meta?.transport,
254
+ voice: out.meta?.voice || this.config?.voice,
255
+ model: out.meta?.tts_model,
256
+ format: out.meta?.format,
257
+ meta: out.meta,
258
+ agentRoot: this.agentRoot,
259
+ });
260
+ return { ok: true, message_id: result.message_id, raw: result };
261
+ }
262
+
263
+ /**
264
+ * Override send() so the conversational/brief path is screened on the SPOKEN
265
+ * text (`brief`), which the base gate only sees as `text`. We copy brief into
266
+ * `text` for the gate pass; _send still reads the original `brief`/`text`.
267
+ */
268
+ async send(out) {
269
+ if (out && out.brief && typeof out.text !== "string") {
270
+ // Screen the spoken brief: present it as `text` to the gate without losing
271
+ // the original `brief` that _send routes to placeOutboundCall.
272
+ const gated = { ...out, text: out.brief };
273
+ const res = await super.send(gated);
274
+ // The gate may redact; if so, propagate the redaction into brief too.
275
+ if (res && res.ok === false) return res;
276
+ return res;
277
+ }
278
+ return super.send(out);
279
+ }
280
+
281
+ async healthCheck() {
282
+ return { ok: this._liveSessions.size >= 0, detail: `${this._liveSessions.size} active session(s)` };
283
+ }
284
+
285
+ async stop() {
286
+ await super.stop();
287
+ for (const s of this._callSupervisors) { try { s.stop?.("channel_stop"); } catch { /* */ } }
288
+ this._callSupervisors.clear();
289
+ for (const p of this._liveSessions) {
290
+ try { p.close("channel_stop"); } catch { /* */ }
291
+ }
292
+ this._liveSessions.clear();
293
+ }
294
+ }
295
+
296
+ /** Factory matching the channel contract. */
297
+ export function createVoiceChannel(opts = {}) {
298
+ return new VoiceAdapter(opts);
299
+ }
300
+
301
+ export default createVoiceChannel;