@vellumai/assistant 0.3.5 → 0.3.7

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 (487) hide show
  1. package/README.md +51 -0
  2. package/eslint.config.mjs +31 -0
  3. package/package.json +1 -1
  4. package/scripts/ipc/check-swift-decoder-drift.ts +4 -1
  5. package/scripts/ipc/generate-swift.ts +18 -2
  6. package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +338 -1
  7. package/src/__tests__/approval-conversation-turn.test.ts +214 -0
  8. package/src/__tests__/browser-manager.test.ts +1 -0
  9. package/src/__tests__/call-conversation-messages.test.ts +130 -0
  10. package/src/__tests__/call-orchestrator.test.ts +752 -271
  11. package/src/__tests__/call-pointer-messages.test.ts +148 -0
  12. package/src/__tests__/call-recovery.test.ts +3 -0
  13. package/src/__tests__/call-routes-http.test.ts +5 -0
  14. package/src/__tests__/call-store.test.ts +3 -0
  15. package/src/__tests__/channel-approval-routes.test.ts +1260 -85
  16. package/src/__tests__/channel-approval.test.ts +37 -0
  17. package/src/__tests__/channel-approvals.test.ts +4 -65
  18. package/src/__tests__/channel-guardian.test.ts +556 -0
  19. package/src/__tests__/channel-readiness-service.test.ts +74 -7
  20. package/src/__tests__/checker.test.ts +14 -7
  21. package/src/__tests__/clarification-resolver.test.ts +44 -24
  22. package/src/__tests__/commit-message-enrichment-service.test.ts +9 -4
  23. package/src/__tests__/computer-use-session-working-dir.test.ts +8 -0
  24. package/src/__tests__/config-schema.test.ts +12 -7
  25. package/src/__tests__/context-window-manager.test.ts +30 -2
  26. package/src/__tests__/contradiction-checker.test.ts +20 -5
  27. package/src/__tests__/credential-security-invariants.test.ts +6 -2
  28. package/src/__tests__/db-migration-rollback.test.ts +752 -0
  29. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +2 -0
  30. package/src/__tests__/fuzzy-match-property.test.ts +5 -5
  31. package/src/__tests__/guardian-action-store.test.ts +123 -0
  32. package/src/__tests__/guardian-action-sweep.test.ts +277 -0
  33. package/src/__tests__/guardian-dispatch.test.ts +389 -0
  34. package/src/__tests__/guardian-question-copy.test.ts +47 -0
  35. package/src/__tests__/handlers-telegram-config.test.ts +4 -2
  36. package/src/__tests__/handlers-twilio-config.test.ts +126 -0
  37. package/src/__tests__/intent-routing.test.ts +2 -0
  38. package/src/__tests__/ipc-snapshot.test.ts +228 -1
  39. package/src/__tests__/memory-upsert-concurrency.test.ts +828 -0
  40. package/src/__tests__/model-intents.test.ts +96 -0
  41. package/src/__tests__/no-direct-anthropic-sdk-imports.test.ts +42 -0
  42. package/src/__tests__/oauth2-gateway-transport.test.ts +130 -0
  43. package/src/__tests__/onboarding-starter-tasks.test.ts +2 -0
  44. package/src/__tests__/provider-commit-message-generator.test.ts +89 -13
  45. package/src/__tests__/provider-error-scenarios.test.ts +621 -0
  46. package/src/__tests__/provider-fail-open-selection.test.ts +119 -0
  47. package/src/__tests__/qdrant-manager.test.ts +27 -20
  48. package/src/__tests__/relay-server.test.ts +779 -40
  49. package/src/__tests__/run-orchestrator-assistant-events.test.ts +2 -0
  50. package/src/__tests__/run-orchestrator.test.ts +20 -4
  51. package/src/__tests__/runtime-runs-http.test.ts +17 -1
  52. package/src/__tests__/runtime-runs.test.ts +16 -0
  53. package/src/__tests__/schedule-store.test.ts +18 -4
  54. package/src/__tests__/scheduler-recurrence.test.ts +13 -4
  55. package/src/__tests__/session-abort-tool-results.test.ts +6 -0
  56. package/src/__tests__/session-agent-loop.test.ts +857 -0
  57. package/src/__tests__/session-conflict-gate.test.ts +6 -0
  58. package/src/__tests__/session-pre-run-repair.test.ts +6 -0
  59. package/src/__tests__/session-profile-injection.test.ts +6 -0
  60. package/src/__tests__/session-provider-retry-repair.test.ts +6 -0
  61. package/src/__tests__/session-queue.test.ts +6 -0
  62. package/src/__tests__/session-runtime-assembly.test.ts +237 -13
  63. package/src/__tests__/session-slash-known.test.ts +6 -0
  64. package/src/__tests__/session-slash-queue.test.ts +6 -0
  65. package/src/__tests__/session-slash-unknown.test.ts +6 -0
  66. package/src/__tests__/session-surfaces-task-progress.test.ts +2 -0
  67. package/src/__tests__/session-tool-setup-app-refresh.test.ts +1 -0
  68. package/src/__tests__/session-tool-setup-memory-scope.test.ts +1 -0
  69. package/src/__tests__/session-tool-setup-side-effect-flag.test.ts +1 -0
  70. package/src/__tests__/session-workspace-injection.test.ts +6 -0
  71. package/src/__tests__/session-workspace-tool-tracking.test.ts +6 -0
  72. package/src/__tests__/skills.test.ts +2 -0
  73. package/src/__tests__/sms-messaging-provider.test.ts +2 -1
  74. package/src/__tests__/starter-task-flow.test.ts +2 -0
  75. package/src/__tests__/swarm-dag-pathological.test.ts +535 -0
  76. package/src/__tests__/system-prompt.test.ts +2 -0
  77. package/src/__tests__/task-management-tools.test.ts +2 -2
  78. package/src/__tests__/task-runner.test.ts +14 -4
  79. package/src/__tests__/terminal-tools.test.ts +25 -19
  80. package/src/__tests__/tool-execution-abort-cleanup.test.ts +545 -0
  81. package/src/__tests__/tool-executor-shell-integration.test.ts +11 -11
  82. package/src/__tests__/tool-executor.test.ts +23 -24
  83. package/src/__tests__/trust-store.test.ts +3 -3
  84. package/src/__tests__/twilio-rest.test.ts +29 -0
  85. package/src/__tests__/twilio-routes-elevenlabs.test.ts +3 -0
  86. package/src/__tests__/twilio-routes-twiml.test.ts +11 -0
  87. package/src/__tests__/twilio-routes.test.ts +141 -21
  88. package/src/__tests__/user-reference.test.ts +2 -0
  89. package/src/__tests__/voice-quality.test.ts +222 -0
  90. package/src/__tests__/web-search.test.ts +45 -29
  91. package/src/agent/loop.ts +1 -1
  92. package/src/agent-heartbeat/agent-heartbeat-service.ts +2 -10
  93. package/src/amazon/client.ts +1418 -0
  94. package/src/amazon/request-extractor.ts +135 -0
  95. package/src/amazon/session.ts +109 -0
  96. package/src/autonomy/autonomy-store.ts +5 -5
  97. package/src/browser-extension-relay/client.ts +124 -0
  98. package/src/browser-extension-relay/protocol.ts +63 -0
  99. package/src/browser-extension-relay/server.ts +177 -0
  100. package/src/bundler/app-bundler.ts +3 -3
  101. package/src/bundler/bundle-signer.ts +1 -1
  102. package/src/bundler/signature-verifier.ts +1 -1
  103. package/src/calls/call-conversation-messages.ts +33 -0
  104. package/src/calls/call-domain.ts +106 -5
  105. package/src/calls/call-orchestrator.ts +252 -54
  106. package/src/calls/call-pointer-messages.ts +53 -0
  107. package/src/calls/call-recovery.ts +3 -8
  108. package/src/calls/call-store.ts +69 -87
  109. package/src/calls/elevenlabs-config.ts +3 -2
  110. package/src/calls/guardian-action-sweep.ts +105 -0
  111. package/src/calls/guardian-dispatch.ts +203 -0
  112. package/src/calls/guardian-question-copy.ts +133 -0
  113. package/src/calls/relay-server.ts +466 -8
  114. package/src/calls/speaker-identification.ts +1 -1
  115. package/src/calls/twilio-config.ts +7 -5
  116. package/src/calls/twilio-provider.ts +6 -4
  117. package/src/calls/twilio-rest.ts +40 -15
  118. package/src/calls/twilio-routes.ts +60 -45
  119. package/src/calls/types.ts +3 -1
  120. package/src/channels/types.ts +25 -0
  121. package/src/cli/amazon.ts +815 -0
  122. package/src/cli/config-commands.ts +2 -2
  123. package/src/cli/core-commands.ts +4 -3
  124. package/src/cli/influencer.ts +244 -0
  125. package/src/cli/map.ts +89 -6
  126. package/src/cli.ts +1 -1
  127. package/src/config/agent-schema.ts +171 -0
  128. package/src/config/bundled-skills/amazon/SKILL.md +127 -0
  129. package/src/config/bundled-skills/amazon/icon.svg +13 -0
  130. package/src/config/bundled-skills/api-mapping/SKILL.md +78 -0
  131. package/src/config/bundled-skills/browser/SKILL.md +1 -0
  132. package/src/config/bundled-skills/browser/TOOLS.json +17 -0
  133. package/src/config/bundled-skills/browser/tools/browser-wait-for-download.ts +25 -0
  134. package/src/config/bundled-skills/doordash/SKILL.md +51 -51
  135. package/src/config/bundled-skills/email-setup/SKILL.md +14 -5
  136. package/src/config/bundled-skills/google-oauth-setup/SKILL.md +183 -0
  137. package/src/config/bundled-skills/influencer/SKILL.md +144 -0
  138. package/src/config/bundled-skills/macos-automation/icon.svg +12 -0
  139. package/src/config/bundled-skills/media-processing/SKILL.md +72 -95
  140. package/src/config/bundled-skills/media-processing/TOOLS.json +57 -147
  141. package/src/config/bundled-skills/media-processing/__tests__/concurrency-pool.test.ts +77 -0
  142. package/src/config/bundled-skills/media-processing/__tests__/cost-tracker.test.ts +69 -0
  143. package/src/config/bundled-skills/media-processing/__tests__/preprocess.test.ts +303 -0
  144. package/src/config/bundled-skills/media-processing/services/concurrency-pool.ts +55 -0
  145. package/src/config/bundled-skills/media-processing/services/cost-tracker.ts +86 -0
  146. package/src/config/bundled-skills/media-processing/services/gemini-map.ts +339 -0
  147. package/src/config/bundled-skills/media-processing/services/preprocess.ts +551 -0
  148. package/src/config/bundled-skills/media-processing/services/processing-pipeline.ts +7 -9
  149. package/src/config/bundled-skills/media-processing/services/reduce.ts +197 -0
  150. package/src/config/bundled-skills/media-processing/tools/analyze-keyframes.ts +88 -253
  151. package/src/config/bundled-skills/media-processing/tools/extract-keyframes.ts +22 -153
  152. package/src/config/bundled-skills/media-processing/tools/generate-clip.ts +2 -2
  153. package/src/config/bundled-skills/media-processing/tools/media-diagnostics.ts +28 -51
  154. package/src/config/bundled-skills/media-processing/tools/query-media-events.ts +35 -270
  155. package/src/config/bundled-skills/messaging/SKILL.md +12 -2
  156. package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +4 -7
  157. package/src/config/bundled-skills/messaging/tools/messaging-reply.ts +2 -1
  158. package/src/config/bundled-skills/phone-calls/SKILL.md +86 -21
  159. package/src/config/bundled-skills/twitter/icon.svg +14 -0
  160. package/src/config/bundled-tool-registry.ts +310 -0
  161. package/src/config/calls-schema.ts +181 -0
  162. package/src/config/core-schema.ts +309 -0
  163. package/src/config/defaults.ts +27 -3
  164. package/src/config/env-registry.ts +169 -0
  165. package/src/config/env.ts +175 -0
  166. package/src/config/loader.ts +6 -6
  167. package/src/config/memory-schema.ts +528 -0
  168. package/src/config/sandbox-schema.ts +55 -0
  169. package/src/config/schema.ts +157 -1138
  170. package/src/config/skill-state.ts +1 -1
  171. package/src/config/skills-schema.ts +32 -0
  172. package/src/config/skills.ts +35 -24
  173. package/src/config/system-prompt.ts +107 -56
  174. package/src/config/templates/SOUL.md +1 -1
  175. package/src/config/types.ts +1 -0
  176. package/src/config/user-reference.ts +4 -9
  177. package/src/config/vellum-skills/catalog.json +0 -7
  178. package/src/config/vellum-skills/chatgpt-import/tools/chatgpt-import.ts +5 -1
  179. package/src/config/vellum-skills/slack-oauth-setup/SKILL.md +1 -0
  180. package/src/config/vellum-skills/sms-setup/SKILL.md +112 -14
  181. package/src/context/window-manager.ts +27 -7
  182. package/src/daemon/approval-generators.ts +186 -0
  183. package/src/daemon/approved-devices-store.ts +140 -0
  184. package/src/daemon/assistant-attachments.ts +1 -1
  185. package/src/daemon/classifier.ts +35 -32
  186. package/src/daemon/config-watcher.ts +1 -1
  187. package/src/daemon/daemon-control.ts +254 -0
  188. package/src/daemon/handlers/apps.ts +2 -3
  189. package/src/daemon/handlers/config-channels.ts +158 -0
  190. package/src/daemon/handlers/config-inbox.ts +540 -0
  191. package/src/daemon/handlers/config-ingress.ts +231 -0
  192. package/src/daemon/handlers/config-integrations.ts +258 -0
  193. package/src/daemon/handlers/config-model.ts +143 -0
  194. package/src/daemon/handlers/config-parental.ts +163 -0
  195. package/src/daemon/handlers/config-scheduling.ts +172 -0
  196. package/src/daemon/handlers/config-slack.ts +92 -0
  197. package/src/daemon/handlers/config-telegram.ts +301 -0
  198. package/src/daemon/handlers/config-tools.ts +177 -0
  199. package/src/daemon/handlers/config-trust.ts +104 -0
  200. package/src/daemon/handlers/config-twilio.ts +1080 -0
  201. package/src/daemon/handlers/config.ts +53 -2463
  202. package/src/daemon/handlers/diagnostics.ts +1 -1
  203. package/src/daemon/handlers/dictation.ts +4 -6
  204. package/src/daemon/handlers/documents.ts +18 -32
  205. package/src/daemon/handlers/index.ts +9 -0
  206. package/src/daemon/handlers/misc.ts +3 -5
  207. package/src/daemon/handlers/pairing.ts +98 -0
  208. package/src/daemon/handlers/sessions.ts +74 -5
  209. package/src/daemon/handlers/shared.ts +3 -1
  210. package/src/daemon/handlers/skills.ts +1 -1
  211. package/src/daemon/handlers/twitter-auth.ts +2 -0
  212. package/src/daemon/handlers/work-items.ts +2 -2
  213. package/src/daemon/handlers/workspace-files.ts +4 -3
  214. package/src/daemon/install-cli-launchers.ts +113 -0
  215. package/src/daemon/ipc-contract/apps.ts +356 -0
  216. package/src/daemon/ipc-contract/browser.ts +74 -0
  217. package/src/daemon/ipc-contract/computer-use.ts +151 -0
  218. package/src/daemon/ipc-contract/diagnostics.ts +56 -0
  219. package/src/daemon/ipc-contract/documents.ts +74 -0
  220. package/src/daemon/ipc-contract/inbox.ts +209 -0
  221. package/src/daemon/ipc-contract/integrations.ts +284 -0
  222. package/src/daemon/ipc-contract/memory.ts +48 -0
  223. package/src/daemon/ipc-contract/messages.ts +211 -0
  224. package/src/daemon/ipc-contract/pairing.ts +45 -0
  225. package/src/daemon/ipc-contract/parental-control.ts +95 -0
  226. package/src/daemon/ipc-contract/schedules.ts +97 -0
  227. package/src/daemon/ipc-contract/sessions.ts +321 -0
  228. package/src/daemon/ipc-contract/shared.ts +42 -0
  229. package/src/daemon/ipc-contract/skills.ts +120 -0
  230. package/src/daemon/ipc-contract/subagents.ts +58 -0
  231. package/src/daemon/ipc-contract/surfaces.ts +250 -0
  232. package/src/daemon/ipc-contract/trust.ts +60 -0
  233. package/src/daemon/ipc-contract/work-items.ts +225 -0
  234. package/src/daemon/ipc-contract/workspace.ts +113 -0
  235. package/src/daemon/ipc-contract-inventory.json +62 -0
  236. package/src/daemon/ipc-contract-inventory.ts +55 -29
  237. package/src/daemon/ipc-contract.ts +227 -2527
  238. package/src/daemon/ipc-protocol.ts +1 -1
  239. package/src/daemon/ipc-validate.ts +7 -0
  240. package/src/daemon/lifecycle.ts +97 -379
  241. package/src/daemon/pairing-store.ts +177 -0
  242. package/src/daemon/providers-setup.ts +43 -0
  243. package/src/daemon/ride-shotgun-handler.ts +67 -2
  244. package/src/daemon/server.ts +60 -44
  245. package/src/daemon/session-agent-loop-handlers.ts +421 -0
  246. package/src/daemon/session-agent-loop.ts +113 -275
  247. package/src/daemon/session-dynamic-profile.ts +1 -1
  248. package/src/daemon/session-history.ts +1 -1
  249. package/src/daemon/session-media-retry.ts +1 -1
  250. package/src/daemon/session-messaging.ts +37 -2
  251. package/src/daemon/session-notifiers.ts +5 -25
  252. package/src/daemon/session-process.ts +99 -59
  253. package/src/daemon/session-queue-manager.ts +98 -4
  254. package/src/daemon/session-runtime-assembly.ts +149 -15
  255. package/src/daemon/session-surfaces.ts +26 -4
  256. package/src/daemon/session-tool-setup.ts +28 -30
  257. package/src/daemon/session-workspace.ts +1 -1
  258. package/src/daemon/session.ts +24 -1
  259. package/src/daemon/shutdown-handlers.ts +122 -0
  260. package/src/daemon/trace-emitter.ts +1 -1
  261. package/src/daemon/watch-handler.ts +36 -33
  262. package/src/doordash/cart-queries.ts +787 -0
  263. package/src/doordash/client.ts +144 -127
  264. package/src/doordash/order-queries.ts +85 -0
  265. package/src/doordash/queries.ts +10 -1308
  266. package/src/doordash/search-queries.ts +203 -0
  267. package/src/doordash/session.ts +3 -2
  268. package/src/doordash/store-queries.ts +246 -0
  269. package/src/doordash/types.ts +367 -0
  270. package/src/email/providers/agentmail.ts +2 -1
  271. package/src/email/providers/index.ts +3 -2
  272. package/src/email/service.ts +3 -2
  273. package/src/errors.ts +43 -0
  274. package/src/home-base/prebuilt/seed.ts +1 -1
  275. package/src/hooks/cli.ts +6 -5
  276. package/src/hooks/config.ts +6 -8
  277. package/src/hooks/discovery.ts +6 -5
  278. package/src/hooks/manager.ts +4 -3
  279. package/src/hooks/runner.ts +2 -2
  280. package/src/hooks/templates.ts +5 -5
  281. package/src/inbound/public-ingress-urls.ts +3 -1
  282. package/src/index.ts +4 -2
  283. package/src/influencer/client.ts +1104 -0
  284. package/src/instrument.ts +4 -3
  285. package/src/logfire.ts +4 -3
  286. package/src/memory/admin.ts +25 -35
  287. package/src/memory/attachments-store.ts +4 -7
  288. package/src/memory/channel-delivery-store.ts +30 -1
  289. package/src/memory/channel-guardian-store.ts +200 -1
  290. package/src/memory/clarification-resolver.ts +37 -33
  291. package/src/memory/conflict-store.ts +67 -61
  292. package/src/memory/contradiction-checker.ts +141 -117
  293. package/src/memory/conversation-store.ts +335 -51
  294. package/src/memory/db-connection.ts +27 -4
  295. package/src/memory/db-init.ts +121 -4
  296. package/src/memory/db.ts +14 -1
  297. package/src/memory/embedding-backend.ts +27 -5
  298. package/src/memory/embedding-ollama.ts +2 -1
  299. package/src/memory/entity-extractor.ts +38 -35
  300. package/src/memory/guardian-action-store.ts +430 -0
  301. package/src/memory/inbox-escalation-projection.ts +59 -0
  302. package/src/memory/inbox-thread-store.ts +218 -0
  303. package/src/memory/ingress-invite-store.ts +338 -0
  304. package/src/memory/ingress-member-store.ts +350 -0
  305. package/src/memory/items-extractor.ts +91 -97
  306. package/src/memory/job-handlers/index-maintenance.ts +3 -3
  307. package/src/memory/job-handlers/media-processing.ts +11 -42
  308. package/src/memory/job-handlers/summarization.ts +32 -26
  309. package/src/memory/job-utils.ts +3 -10
  310. package/src/memory/jobs-store.ts +6 -9
  311. package/src/memory/jobs-worker.ts +51 -36
  312. package/src/memory/migrations/001-job-deferrals.ts +45 -0
  313. package/src/memory/migrations/002-tool-invocations-fk.ts +43 -0
  314. package/src/memory/migrations/003-memory-fts-backfill.ts +24 -0
  315. package/src/memory/migrations/004-entity-relation-dedup.ts +87 -0
  316. package/src/memory/migrations/005-fingerprint-scope-unique.ts +80 -0
  317. package/src/memory/migrations/006-scope-salted-fingerprints.ts +62 -0
  318. package/src/memory/migrations/007-assistant-id-to-self.ts +254 -0
  319. package/src/memory/migrations/008-remove-assistant-id-columns.ts +208 -0
  320. package/src/memory/migrations/009-llm-usage-events-drop-assistant-id.ts +83 -0
  321. package/src/memory/migrations/010-ext-conv-bindings-channel-chat-unique.ts +56 -0
  322. package/src/memory/migrations/011-call-sessions-provider-sid-dedup.ts +63 -0
  323. package/src/memory/migrations/012-call-sessions-add-initiated-from.ts +19 -0
  324. package/src/memory/migrations/013-guardian-action-tables.ts +68 -0
  325. package/src/memory/migrations/014-backfill-inbox-thread-state.ts +76 -0
  326. package/src/memory/migrations/015-drop-active-search-index.ts +27 -0
  327. package/src/memory/migrations/016-memory-segments-indexes.ts +11 -0
  328. package/src/memory/migrations/017-memory-items-indexes.ts +12 -0
  329. package/src/memory/migrations/018-remaining-table-indexes.ts +13 -0
  330. package/src/memory/migrations/index.ts +24 -0
  331. package/src/memory/migrations/registry.ts +79 -0
  332. package/src/memory/migrations/validate-migration-state.ts +69 -0
  333. package/src/memory/qdrant-manager.ts +49 -8
  334. package/src/memory/query-builder.ts +1 -1
  335. package/src/memory/raw-query.ts +119 -0
  336. package/src/memory/recall-cache.ts +4 -1
  337. package/src/memory/retriever.ts +163 -47
  338. package/src/memory/schema-migration.ts +25 -984
  339. package/src/memory/schema.ts +130 -7
  340. package/src/memory/search/entity.ts +10 -19
  341. package/src/memory/search/lexical.ts +81 -52
  342. package/src/memory/search/ranking.ts +21 -22
  343. package/src/memory/search/semantic.ts +157 -19
  344. package/src/memory/shared-app-links-store.ts +4 -5
  345. package/src/memory/validation.ts +19 -0
  346. package/src/messaging/draft-store.ts +5 -6
  347. package/src/messaging/providers/sms/adapter.ts +3 -6
  348. package/src/messaging/providers/telegram-bot/adapter.ts +2 -5
  349. package/src/messaging/providers/whatsapp/adapter.ts +136 -0
  350. package/src/messaging/providers/whatsapp/client.ts +67 -0
  351. package/src/messaging/style-analyzer.ts +5 -4
  352. package/src/messaging/thread-summarizer.ts +61 -69
  353. package/src/messaging/triage-engine.ts +62 -71
  354. package/src/migrations/config-merge.ts +53 -0
  355. package/src/migrations/data-layout.ts +68 -0
  356. package/src/migrations/data-merge.ts +33 -0
  357. package/src/migrations/hooks-merge.ts +90 -0
  358. package/src/migrations/index.ts +6 -0
  359. package/src/migrations/log.ts +23 -0
  360. package/src/migrations/skills-merge.ts +33 -0
  361. package/src/migrations/workspace-layout.ts +79 -0
  362. package/src/permissions/checker.ts +126 -11
  363. package/src/permissions/prompter.ts +14 -0
  364. package/src/permissions/shell-identity.ts +31 -1
  365. package/src/permissions/trust-store.ts +21 -1
  366. package/src/providers/anthropic/client.ts +4 -4
  367. package/src/providers/failover.ts +2 -2
  368. package/src/providers/model-intents.ts +70 -0
  369. package/src/providers/ollama/client.ts +2 -1
  370. package/src/providers/provider-send-message.ts +176 -0
  371. package/src/providers/registry.ts +71 -30
  372. package/src/providers/retry.ts +35 -1
  373. package/src/providers/types.ts +12 -1
  374. package/src/runtime/approval-conversation-turn.ts +97 -0
  375. package/src/runtime/approval-message-composer.ts +115 -5
  376. package/src/runtime/assistant-event-hub.ts +3 -1
  377. package/src/runtime/channel-approval-parser.ts +36 -2
  378. package/src/runtime/channel-approvals.ts +0 -21
  379. package/src/runtime/channel-guardian-service.ts +48 -7
  380. package/src/runtime/channel-readiness-service.ts +160 -34
  381. package/src/runtime/channel-readiness-types.ts +10 -4
  382. package/src/runtime/channel-retry-sweep.ts +184 -0
  383. package/src/runtime/guardian-context-resolver.ts +108 -0
  384. package/src/runtime/http-server.ts +289 -745
  385. package/src/runtime/http-types.ts +56 -3
  386. package/src/runtime/middleware/auth.ts +116 -0
  387. package/src/runtime/middleware/error-handler.ts +33 -0
  388. package/src/runtime/middleware/twilio-validation.ts +127 -0
  389. package/src/runtime/routes/app-routes.ts +1 -1
  390. package/src/runtime/routes/call-routes.ts +49 -6
  391. package/src/runtime/routes/channel-delivery-routes.ts +170 -0
  392. package/src/runtime/routes/channel-guardian-routes.ts +1191 -0
  393. package/src/runtime/routes/channel-inbound-routes.ts +1152 -0
  394. package/src/runtime/routes/channel-route-shared.ts +144 -0
  395. package/src/runtime/routes/channel-routes.ts +32 -1634
  396. package/src/runtime/routes/conversation-routes.ts +50 -7
  397. package/src/runtime/routes/events-routes.ts +2 -2
  398. package/src/runtime/routes/identity-routes.ts +126 -0
  399. package/src/runtime/routes/pairing-routes.ts +144 -0
  400. package/src/runtime/routes/run-routes.ts +15 -1
  401. package/src/runtime/run-orchestrator.ts +52 -34
  402. package/src/schedule/schedule-store.ts +36 -32
  403. package/src/schedule/scheduler.ts +3 -3
  404. package/src/security/encrypted-store.ts +5 -7
  405. package/src/security/oauth2.ts +45 -15
  406. package/src/security/parental-control-store.ts +183 -0
  407. package/src/security/secret-allowlist.ts +4 -3
  408. package/src/security/secret-scanner.ts +5 -5
  409. package/src/security/secure-keys.ts +1 -1
  410. package/src/security/token-manager.ts +3 -2
  411. package/src/services/vercel-deploy.ts +6 -2
  412. package/src/skills/tool-manifest.ts +3 -3
  413. package/src/skills/vellum-catalog-remote.ts +75 -16
  414. package/src/slack/slack-webhook.ts +2 -1
  415. package/src/swarm/orchestrator.ts +92 -1
  416. package/src/swarm/router-planner.ts +6 -9
  417. package/src/swarm/worker-prompts.ts +9 -12
  418. package/src/tasks/task-compiler.ts +19 -28
  419. package/src/tasks/task-runner.ts +1 -1
  420. package/src/tools/assets/search.ts +15 -14
  421. package/src/tools/browser/__tests__/auth-detector.test.ts +1 -0
  422. package/src/tools/browser/auto-navigate.ts +1 -0
  423. package/src/tools/browser/browser-execution.ts +13 -1
  424. package/src/tools/browser/browser-manager.ts +119 -4
  425. package/src/tools/browser/network-recorder.ts +5 -0
  426. package/src/tools/credentials/broker.ts +11 -2
  427. package/src/tools/credentials/metadata-store.ts +18 -14
  428. package/src/tools/credentials/post-connect-hooks.ts +61 -0
  429. package/src/tools/credentials/vault.ts +49 -23
  430. package/src/tools/executor.ts +80 -18
  431. package/src/tools/host-terminal/cli-discover.ts +1 -1
  432. package/src/tools/network/script-proxy/http-forwarder.ts +1 -1
  433. package/src/tools/network/script-proxy/mitm-handler.ts +1 -1
  434. package/src/tools/network/script-proxy/server.ts +1 -1
  435. package/src/tools/network/script-proxy/session-manager.ts +6 -5
  436. package/src/tools/network/web-fetch.ts +18 -2
  437. package/src/tools/network/web-search.ts +7 -3
  438. package/src/tools/reminder/reminder-store.ts +14 -15
  439. package/src/tools/schedule/create.ts +1 -0
  440. package/src/tools/schedule/list.ts +2 -1
  441. package/src/tools/shared/filesystem/file-ops-service.ts +5 -7
  442. package/src/tools/skills/skill-script-runner.ts +24 -9
  443. package/src/tools/skills/skill-tool-factory.ts +1 -0
  444. package/src/tools/tasks/work-item-enqueue.ts +2 -2
  445. package/src/tools/terminal/evaluate-typescript.ts +21 -12
  446. package/src/tools/terminal/parser.ts +50 -0
  447. package/src/tools/watcher/delete.ts +6 -0
  448. package/src/tools/weather/service.ts +1 -1
  449. package/src/twitter/client.ts +190 -24
  450. package/src/twitter/session.ts +4 -3
  451. package/src/util/clipboard.ts +1 -1
  452. package/src/util/errors.ts +65 -8
  453. package/src/util/fs.ts +40 -0
  454. package/src/util/json.ts +10 -0
  455. package/src/util/log-redact.ts +189 -0
  456. package/src/util/logger.ts +25 -18
  457. package/src/util/object.ts +3 -0
  458. package/src/util/platform.ts +72 -365
  459. package/src/util/pricing.ts +1 -1
  460. package/src/util/promise-guard.ts +1 -1
  461. package/src/util/retry.ts +19 -0
  462. package/src/util/row-mapper.ts +79 -0
  463. package/src/util/silently.ts +21 -0
  464. package/src/watcher/engine.ts +5 -1
  465. package/src/watcher/provider-types.ts +20 -0
  466. package/src/watcher/providers/github.ts +156 -0
  467. package/src/watcher/providers/gmail.ts +1 -0
  468. package/src/watcher/providers/google-calendar.ts +1 -0
  469. package/src/watcher/providers/linear.ts +460 -0
  470. package/src/watcher/providers/slack.ts +1 -0
  471. package/src/work-items/work-item-runner.ts +1 -1
  472. package/src/workspace/git-service.ts +1 -1
  473. package/src/workspace/provider-commit-message-generator.ts +51 -22
  474. package/src/__tests__/call-bridge.test.ts +0 -517
  475. package/src/__tests__/session-process-bridge.test.ts +0 -244
  476. package/src/calls/call-bridge.ts +0 -168
  477. package/src/config/bundled-skills/media-processing/services/capability-registry.ts +0 -137
  478. package/src/config/bundled-skills/media-processing/services/event-detection-service.ts +0 -280
  479. package/src/config/bundled-skills/media-processing/services/feedback-aggregation.ts +0 -144
  480. package/src/config/bundled-skills/media-processing/services/feedback-store.ts +0 -136
  481. package/src/config/bundled-skills/media-processing/services/retrieval-service.ts +0 -95
  482. package/src/config/bundled-skills/media-processing/services/timeline-service.ts +0 -267
  483. package/src/config/bundled-skills/media-processing/tools/detect-events.ts +0 -110
  484. package/src/config/bundled-skills/media-processing/tools/recalibrate.ts +0 -235
  485. package/src/config/bundled-skills/media-processing/tools/select-tracking-profile.ts +0 -142
  486. package/src/config/bundled-skills/media-processing/tools/submit-feedback.ts +0 -150
  487. package/src/config/vellum-skills/google-oauth-setup/SKILL.md +0 -199
@@ -1,56 +1,53 @@
1
- import { eq, and, notInArray, desc } from 'drizzle-orm';
1
+ import { eq, and, or, notInArray, desc } from 'drizzle-orm';
2
2
  import { v4 as uuid } from 'uuid';
3
- import { getDb } from '../memory/db.js';
3
+ import { getDb, rawChanges, rawGet, rawRun } from '../memory/db.js';
4
4
  import { callSessions, callEvents, callPendingQuestions } from '../memory/schema.js';
5
5
  import type { CallSession, CallEvent, CallPendingQuestion, CallEventType, CallStatus } from './types.js';
6
6
  import { validateTransition } from './call-state-machine.js';
7
7
  import { getLogger } from '../util/logger.js';
8
+ import { createRowMapper, cast } from '../util/row-mapper.js';
8
9
 
9
10
  const log = getLogger('call-store');
10
11
 
11
12
  // ── Helpers ──────────────────────────────────────────────────────────
12
13
 
13
- function parseCallSession(row: typeof callSessions.$inferSelect): CallSession {
14
- return {
15
- id: row.id,
16
- conversationId: row.conversationId,
17
- provider: row.provider,
18
- providerCallSid: row.providerCallSid,
19
- fromNumber: row.fromNumber,
20
- toNumber: row.toNumber,
21
- task: row.task,
22
- status: row.status as CallSession['status'],
23
- callerIdentityMode: row.callerIdentityMode,
24
- callerIdentitySource: row.callerIdentitySource,
25
- startedAt: row.startedAt,
26
- endedAt: row.endedAt,
27
- lastError: row.lastError,
28
- createdAt: row.createdAt,
29
- updatedAt: row.updatedAt,
30
- };
31
- }
32
-
33
- function parseCallEvent(row: typeof callEvents.$inferSelect): CallEvent {
34
- return {
35
- id: row.id,
36
- callSessionId: row.callSessionId,
37
- eventType: row.eventType as CallEvent['eventType'],
38
- payloadJson: row.payloadJson,
39
- createdAt: row.createdAt,
40
- };
41
- }
42
-
43
- function parsePendingQuestion(row: typeof callPendingQuestions.$inferSelect): CallPendingQuestion {
44
- return {
45
- id: row.id,
46
- callSessionId: row.callSessionId,
47
- questionText: row.questionText,
48
- status: row.status as CallPendingQuestion['status'],
49
- askedAt: row.askedAt,
50
- answeredAt: row.answeredAt,
51
- answerText: row.answerText,
52
- };
53
- }
14
+ const parseCallSession = createRowMapper<typeof callSessions.$inferSelect, CallSession>({
15
+ id: 'id',
16
+ conversationId: 'conversationId',
17
+ provider: 'provider',
18
+ providerCallSid: 'providerCallSid',
19
+ fromNumber: 'fromNumber',
20
+ toNumber: 'toNumber',
21
+ task: 'task',
22
+ status: { from: 'status', transform: cast<CallSession['status']>() },
23
+ callerIdentityMode: 'callerIdentityMode',
24
+ callerIdentitySource: 'callerIdentitySource',
25
+ assistantId: 'assistantId',
26
+ initiatedFromConversationId: 'initiatedFromConversationId',
27
+ startedAt: 'startedAt',
28
+ endedAt: 'endedAt',
29
+ lastError: 'lastError',
30
+ createdAt: 'createdAt',
31
+ updatedAt: 'updatedAt',
32
+ });
33
+
34
+ const parseCallEvent = createRowMapper<typeof callEvents.$inferSelect, CallEvent>({
35
+ id: 'id',
36
+ callSessionId: 'callSessionId',
37
+ eventType: { from: 'eventType', transform: cast<CallEvent['eventType']>() },
38
+ payloadJson: 'payloadJson',
39
+ createdAt: 'createdAt',
40
+ });
41
+
42
+ const parsePendingQuestion = createRowMapper<typeof callPendingQuestions.$inferSelect, CallPendingQuestion>({
43
+ id: 'id',
44
+ callSessionId: 'callSessionId',
45
+ questionText: 'questionText',
46
+ status: { from: 'status', transform: cast<CallPendingQuestion['status']>() },
47
+ askedAt: 'askedAt',
48
+ answeredAt: 'answeredAt',
49
+ answerText: 'answerText',
50
+ });
54
51
 
55
52
  // ── Call Sessions ────────────────────────────────────────────────────
56
53
 
@@ -62,6 +59,8 @@ export function createCallSession(opts: {
62
59
  task?: string;
63
60
  callerIdentityMode?: string;
64
61
  callerIdentitySource?: string;
62
+ assistantId?: string;
63
+ initiatedFromConversationId?: string;
65
64
  }): CallSession {
66
65
  const db = getDb();
67
66
  const now = Date.now();
@@ -76,6 +75,8 @@ export function createCallSession(opts: {
76
75
  status: 'initiated' as const,
77
76
  callerIdentityMode: opts.callerIdentityMode ?? null,
78
77
  callerIdentitySource: opts.callerIdentitySource ?? null,
78
+ assistantId: opts.assistantId ?? null,
79
+ initiatedFromConversationId: opts.initiatedFromConversationId ?? null,
79
80
  startedAt: null,
80
81
  endedAt: null,
81
82
  lastError: null,
@@ -111,7 +112,10 @@ export function getActiveCallSessionForConversation(conversationId: string): Cal
111
112
  .from(callSessions)
112
113
  .where(
113
114
  and(
114
- eq(callSessions.conversationId, conversationId),
115
+ or(
116
+ eq(callSessions.conversationId, conversationId),
117
+ eq(callSessions.initiatedFromConversationId, conversationId),
118
+ ),
115
119
  notInArray(callSessions.status, ['completed', 'failed', 'cancelled']),
116
120
  ),
117
121
  )
@@ -123,7 +127,7 @@ export function getActiveCallSessionForConversation(conversationId: string): Cal
123
127
 
124
128
  export function updateCallSession(
125
129
  id: string,
126
- updates: Partial<Pick<CallSession, 'status' | 'providerCallSid' | 'startedAt' | 'endedAt' | 'lastError'>>,
130
+ updates: Partial<Pick<CallSession, 'status' | 'providerCallSid' | 'startedAt' | 'endedAt' | 'lastError' | 'conversationId' | 'initiatedFromConversationId'>>,
127
131
  ): void {
128
132
  const db = getDb();
129
133
 
@@ -246,10 +250,7 @@ export function answerPendingQuestion(id: string, answerText: string): void {
246
250
  ),
247
251
  )
248
252
  .run();
249
- // Drizzle's .run() returns void for bun:sqlite, so check affected rows via raw client.
250
- const raw = (db as unknown as { $client: import('bun:sqlite').Database }).$client;
251
- const changes = raw.query('SELECT changes() as c').get() as { c: number };
252
- if (changes.c === 0) {
253
+ if (rawChanges() === 0) {
253
254
  log.warn({ questionId: id }, 'answerPendingQuestion: no rows updated — question may have already been answered or expired');
254
255
  }
255
256
  }
@@ -292,13 +293,7 @@ export function buildCallbackDedupeKey(
292
293
  * Returns true if the key already exists, false otherwise.
293
294
  */
294
295
  export function isCallbackProcessed(dedupeKey: string): boolean {
295
- const db = getDb();
296
- const raw = (db as unknown as { $client: import('bun:sqlite').Database }).$client;
297
-
298
- const row = raw.query(
299
- `SELECT 1 FROM processed_callbacks WHERE dedupe_key = ?`,
300
- ).get(dedupeKey);
301
- return row != null;
296
+ return rawGet<{ 1: number }>(`SELECT 1 FROM processed_callbacks WHERE dedupe_key = ?`, dedupeKey) != null;
302
297
  }
303
298
 
304
299
  /**
@@ -312,12 +307,10 @@ export function recordProcessedCallback(
312
307
  dedupeKey: string,
313
308
  callSessionId: string,
314
309
  ): void {
315
- const db = getDb();
316
- const raw = (db as unknown as { $client: import('bun:sqlite').Database }).$client;
317
-
318
- raw.query(
310
+ rawRun(
319
311
  `INSERT OR IGNORE INTO processed_callbacks (id, dedupe_key, call_session_id, created_at) VALUES (?, ?, ?, ?)`,
320
- ).run(uuid(), dedupeKey, callSessionId, Date.now());
312
+ uuid(), dedupeKey, callSessionId, Date.now(),
313
+ );
321
314
  }
322
315
 
323
316
  /**
@@ -334,15 +327,10 @@ export function tryRecordProcessedCallback(
334
327
  dedupeKey: string,
335
328
  callSessionId: string,
336
329
  ): boolean {
337
- const db = getDb();
338
- const raw = (db as unknown as { $client: import('bun:sqlite').Database }).$client;
339
-
340
- raw.query(
330
+ return rawRun(
341
331
  `INSERT OR IGNORE INTO processed_callbacks (id, dedupe_key, call_session_id, created_at) VALUES (?, ?, ?, ?)`,
342
- ).run(uuid(), dedupeKey, callSessionId, Date.now());
343
-
344
- const changes = raw.query('SELECT changes() as c').get() as { c: number };
345
- return changes.c > 0;
332
+ uuid(), dedupeKey, callSessionId, Date.now(),
333
+ ) > 0;
346
334
  }
347
335
 
348
336
  /**
@@ -362,20 +350,18 @@ export function tryRecordProcessedCallback(
362
350
  * finalize a claim that was reclaimed by handler B after expiry.
363
351
  */
364
352
  export function claimCallback(dedupeKey: string, callSessionId: string): string | null {
365
- const db = getDb();
366
- const raw = (db as unknown as { $client: import('bun:sqlite').Database }).$client;
367
-
368
353
  // Clear any expired orphaned claims so they can be reprocessed
369
- raw.query(
354
+ rawRun(
370
355
  `DELETE FROM processed_callbacks WHERE dedupe_key = ? AND created_at < ?`,
371
- ).run(dedupeKey, Date.now() - CLAIM_EXPIRY_MS);
356
+ dedupeKey, Date.now() - CLAIM_EXPIRY_MS,
357
+ );
372
358
 
373
359
  const claimId = uuid();
374
- raw.query(
360
+ const changes = rawRun(
375
361
  `INSERT OR IGNORE INTO processed_callbacks (id, dedupe_key, call_session_id, claim_id, created_at) VALUES (?, ?, ?, ?, ?)`,
376
- ).run(uuid(), dedupeKey, callSessionId, claimId, Date.now());
377
- const changes = raw.query('SELECT changes() as c').get() as { c: number };
378
- return changes.c > 0 ? claimId : null;
362
+ uuid(), dedupeKey, callSessionId, claimId, Date.now(),
363
+ );
364
+ return changes > 0 ? claimId : null;
379
365
  }
380
366
 
381
367
  /**
@@ -386,9 +372,7 @@ export function claimCallback(dedupeKey: string, callSessionId: string): string
386
372
  * handler A from releasing a claim that was reclaimed by handler B.
387
373
  */
388
374
  export function releaseCallbackClaim(dedupeKey: string, claimId: string): void {
389
- const db = getDb();
390
- const raw = (db as unknown as { $client: import('bun:sqlite').Database }).$client;
391
- raw.query(`DELETE FROM processed_callbacks WHERE dedupe_key = ? AND claim_id = ?`).run(dedupeKey, claimId);
375
+ rawRun(`DELETE FROM processed_callbacks WHERE dedupe_key = ? AND claim_id = ?`, dedupeKey, claimId);
392
376
  }
393
377
 
394
378
  /**
@@ -406,15 +390,13 @@ export function releaseCallbackClaim(dedupeKey: string, claimId: string): void {
406
390
  * else, so duplicate processing may occur on later retries.
407
391
  */
408
392
  export function finalizeCallbackClaim(dedupeKey: string, claimId: string): boolean {
409
- const db = getDb();
410
- const raw = (db as unknown as { $client: import('bun:sqlite').Database }).$client;
411
393
  // Set created_at far in the future so expiry check never matches
412
394
  const NEVER_EXPIRE = Date.now() + 100 * 365 * 24 * 60 * 60 * 1000; // ~100 years
413
- raw.query(
395
+ const changes = rawRun(
414
396
  `UPDATE processed_callbacks SET created_at = ? WHERE dedupe_key = ? AND claim_id = ?`,
415
- ).run(NEVER_EXPIRE, dedupeKey, claimId);
416
- const changes = raw.query('SELECT changes() as c').get() as { c: number };
417
- if (changes.c === 0) {
397
+ NEVER_EXPIRE, dedupeKey, claimId,
398
+ );
399
+ if (changes === 0) {
418
400
  log.warn({ dedupeKey, claimId }, 'finalizeCallbackClaim: claim was lost — another handler reclaimed this key after expiry');
419
401
  return false;
420
402
  }
@@ -1,5 +1,6 @@
1
1
  import { getConfig } from '../config/loader.js';
2
2
  import { getSecureKey } from '../security/secure-keys.js';
3
+ import { ConfigError } from '../util/errors.js';
3
4
 
4
5
  export interface ElevenLabsConfig {
5
6
  apiKey: string;
@@ -14,12 +15,12 @@ export function getElevenLabsConfig(): ElevenLabsConfig {
14
15
 
15
16
  const apiKey = getSecureKey('credential:elevenlabs:api_key') ?? '';
16
17
  if (!apiKey) {
17
- throw new Error('ElevenLabs API key is not configured. Set credential:elevenlabs:api_key in the secure key store.');
18
+ throw new ConfigError('ElevenLabs API key is not configured. Set credential:elevenlabs:api_key in the secure key store.');
18
19
  }
19
20
 
20
21
  const agentId = voice.elevenlabs.agentId;
21
22
  if (!agentId) {
22
- throw new Error('ElevenLabs agent ID is not configured. Set calls.voice.elevenlabs.agentId in config.');
23
+ throw new ConfigError('ElevenLabs agent ID is not configured. Set calls.voice.elevenlabs.agentId in config.');
23
24
  }
24
25
 
25
26
  return {
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Periodic sweep for expired guardian action requests.
3
+ *
4
+ * Runs on a 60-second interval. When a request has passed its expiresAt
5
+ * timestamp:
6
+ * 1. Expires the request and all its deliveries in the store
7
+ * 2. Expires the associated pending question so the call-side timeout fires
8
+ * 3. Sends expiry notices to external delivery destinations (telegram, sms)
9
+ * 4. Adds an expiry message to mac guardian thread conversations
10
+ */
11
+
12
+ import { getLogger } from '../util/logger.js';
13
+ import {
14
+ getExpiredGuardianActionRequests,
15
+ expireGuardianActionRequest,
16
+ getDeliveriesByRequestId,
17
+ } from '../memory/guardian-action-store.js';
18
+ import { expirePendingQuestions } from './call-store.js';
19
+ import { deliverChannelReply } from '../runtime/gateway-client.js';
20
+ import { addMessage } from '../memory/conversation-store.js';
21
+
22
+ const log = getLogger('guardian-action-sweep');
23
+
24
+ const SWEEP_INTERVAL_MS = 60_000;
25
+
26
+ let sweepTimer: ReturnType<typeof setInterval> | null = null;
27
+
28
+ /**
29
+ * Sweep expired guardian action requests and clean up.
30
+ */
31
+ export function sweepExpiredGuardianActions(
32
+ gatewayBaseUrl: string,
33
+ bearerToken?: string,
34
+ ): void {
35
+ const expired = getExpiredGuardianActionRequests();
36
+
37
+ for (const request of expired) {
38
+ // Capture deliveries before expiring (since expiry changes their status)
39
+ const deliveries = getDeliveriesByRequestId(request.id);
40
+
41
+ // Expire the request and all deliveries
42
+ expireGuardianActionRequest(request.id);
43
+
44
+ // Expire associated pending questions
45
+ expirePendingQuestions(request.callSessionId);
46
+
47
+ log.info(
48
+ { requestId: request.id, callSessionId: request.callSessionId },
49
+ 'Expired guardian action request',
50
+ );
51
+
52
+ // Send expiry notices to each delivery destination
53
+ for (const delivery of deliveries) {
54
+ if (delivery.status !== 'sent' && delivery.status !== 'pending') continue;
55
+
56
+ if ((delivery.destinationChannel === 'macos' || delivery.destinationChannel === 'mac') && delivery.destinationConversationId) {
57
+ // Add expiry message to mac guardian thread
58
+ addMessage(
59
+ delivery.destinationConversationId,
60
+ 'assistant',
61
+ JSON.stringify([{ type: 'text', text: 'This guardian question has expired without a response.' }]),
62
+ { userMessageChannel: 'voice', assistantMessageChannel: 'macos' },
63
+ );
64
+ } else if (delivery.destinationChatId) {
65
+ // External channel — send expiry notice
66
+ const deliverUrl = `${gatewayBaseUrl}/deliver/${delivery.destinationChannel}`;
67
+ void (async () => {
68
+ try {
69
+ await deliverChannelReply(deliverUrl, {
70
+ chatId: delivery.destinationChatId!,
71
+ text: 'The guardian question has expired without a response. The call has moved on.',
72
+ assistantId: request.assistantId,
73
+ }, bearerToken);
74
+ } catch (err) {
75
+ log.error(
76
+ { err, deliveryId: delivery.id, channel: delivery.destinationChannel },
77
+ 'Failed to deliver guardian action expiry notice',
78
+ );
79
+ }
80
+ })();
81
+ }
82
+ }
83
+ }
84
+ }
85
+
86
+ export function startGuardianActionSweep(
87
+ gatewayBaseUrl: string,
88
+ bearerToken?: string,
89
+ ): void {
90
+ if (sweepTimer) return;
91
+ sweepTimer = setInterval(() => {
92
+ try {
93
+ sweepExpiredGuardianActions(gatewayBaseUrl, bearerToken);
94
+ } catch (err) {
95
+ log.error({ err }, 'Guardian action sweep failed');
96
+ }
97
+ }, SWEEP_INTERVAL_MS);
98
+ }
99
+
100
+ export function stopGuardianActionSweep(): void {
101
+ if (sweepTimer) {
102
+ clearInterval(sweepTimer);
103
+ sweepTimer = null;
104
+ }
105
+ }
@@ -0,0 +1,203 @@
1
+ /**
2
+ * Guardian dispatch engine for cross-channel voice calls.
3
+ *
4
+ * When a call orchestrator detects ASK_GUARDIAN, this module:
5
+ * 1. Creates a guardian_action_request
6
+ * 2. Determines delivery destinations (telegram, sms, macos)
7
+ * 3. Creates guardian_action_delivery rows for each destination
8
+ * 4. Sends HTTP POST to gateway for external channels
9
+ * 5. Emits IPC events for the mac channel
10
+ */
11
+
12
+ import { getLogger } from '../util/logger.js';
13
+ import { getGatewayInternalBaseUrl } from '../config/env.js';
14
+ import { getActiveBinding } from '../memory/channel-guardian-store.js';
15
+ import {
16
+ createGuardianActionRequest,
17
+ createGuardianActionDelivery,
18
+ updateDeliveryStatus,
19
+ } from '../memory/guardian-action-store.js';
20
+ import { deliverChannelReply } from '../runtime/gateway-client.js';
21
+ import { getUserConsultationTimeoutMs } from './call-constants.js';
22
+ import { getOrCreateConversation } from '../memory/conversation-key-store.js';
23
+ import { addMessage } from '../memory/conversation-store.js';
24
+ import type { CallPendingQuestion } from './types.js';
25
+ import { readHttpToken } from '../util/platform.js';
26
+ import type { ServerMessage } from '../daemon/ipc-contract.js';
27
+ import { generateGuardianCopy } from './guardian-question-copy.js';
28
+
29
+ const log = getLogger('guardian-dispatch');
30
+
31
+ /** Resolve the gateway base URL for internal delivery callbacks. */
32
+ function getGatewayBaseUrl(): string {
33
+ return getGatewayInternalBaseUrl();
34
+ }
35
+
36
+ export interface GuardianDispatchParams {
37
+ callSessionId: string;
38
+ conversationId: string;
39
+ assistantId: string;
40
+ pendingQuestion: CallPendingQuestion;
41
+ /** Broadcast function to emit IPC events to connected clients. */
42
+ broadcast?: (msg: ServerMessage) => void;
43
+ }
44
+
45
+ /**
46
+ * Dispatch a guardian action request to all configured channels.
47
+ * Fire-and-forget: errors are logged but do not propagate.
48
+ */
49
+ export async function dispatchGuardianQuestion(params: GuardianDispatchParams): Promise<void> {
50
+ const {
51
+ callSessionId,
52
+ conversationId,
53
+ assistantId,
54
+ pendingQuestion,
55
+ broadcast,
56
+ } = params;
57
+
58
+ try {
59
+ const expiresAt = Date.now() + getUserConsultationTimeoutMs();
60
+
61
+ // Create the action request
62
+ const request = createGuardianActionRequest({
63
+ assistantId,
64
+ kind: 'ask_guardian',
65
+ sourceChannel: 'voice',
66
+ sourceConversationId: conversationId,
67
+ callSessionId,
68
+ pendingQuestionId: pendingQuestion.id,
69
+ questionText: pendingQuestion.questionText,
70
+ expiresAt,
71
+ });
72
+
73
+ log.info(
74
+ { requestId: request.id, requestCode: request.requestCode, callSessionId },
75
+ 'Created guardian action request',
76
+ );
77
+
78
+ // Determine delivery destinations
79
+ const destinations: Array<{
80
+ channel: string;
81
+ chatId?: string;
82
+ externalUserId?: string;
83
+ }> = [];
84
+
85
+ // Telegram guardian binding
86
+ const telegramBinding = getActiveBinding(assistantId, 'telegram');
87
+ if (telegramBinding) {
88
+ destinations.push({
89
+ channel: 'telegram',
90
+ chatId: telegramBinding.guardianDeliveryChatId,
91
+ externalUserId: telegramBinding.guardianExternalUserId,
92
+ });
93
+ }
94
+
95
+ // SMS guardian binding
96
+ const smsBinding = getActiveBinding(assistantId, 'sms');
97
+ if (smsBinding) {
98
+ destinations.push({
99
+ channel: 'sms',
100
+ chatId: smsBinding.guardianDeliveryChatId,
101
+ externalUserId: smsBinding.guardianExternalUserId,
102
+ });
103
+ }
104
+
105
+ // Mac (internal) delivery — always created
106
+ destinations.push({ channel: 'macos' });
107
+
108
+ // Start LLM copy generation concurrently — only awaited in the macOS branch
109
+ // so external channels (Telegram, SMS) dispatch without LLM latency.
110
+ const guardianCopyPromise = generateGuardianCopy(
111
+ pendingQuestion.questionText,
112
+ request.requestCode,
113
+ );
114
+
115
+ // Create delivery rows and dispatch
116
+ for (const dest of destinations) {
117
+ if (dest.channel === 'macos') {
118
+ // Create conversation and delivery row synchronously so they exist
119
+ // before awaiting LLM copy — prevents a race where an external channel
120
+ // reply resolves the request before the macOS delivery is created.
121
+ const macConvKey = `asst:${assistantId}:guardian:request:${request.id}`;
122
+ const { conversationId: macConversationId } = getOrCreateConversation(macConvKey);
123
+
124
+ const delivery = createGuardianActionDelivery({
125
+ requestId: request.id,
126
+ destinationChannel: 'macos',
127
+ destinationConversationId: macConversationId,
128
+ });
129
+
130
+ // Now await LLM-generated copy for the message content and thread title
131
+ const guardianCopy = await guardianCopyPromise;
132
+
133
+ // Add the guardian question as the initial message in the thread
134
+ addMessage(
135
+ macConversationId,
136
+ 'assistant',
137
+ JSON.stringify([{ type: 'text', text: guardianCopy.initialMessage }]),
138
+ { userMessageChannel: 'voice', assistantMessageChannel: 'macos' },
139
+ );
140
+
141
+ // Emit IPC event for the mac client with the server-created conversation
142
+ if (broadcast) {
143
+ broadcast({
144
+ type: 'guardian_request_thread_created',
145
+ conversationId: macConversationId,
146
+ requestId: request.id,
147
+ callSessionId,
148
+ title: guardianCopy.threadTitle,
149
+ questionText: request.questionText,
150
+ } as ServerMessage);
151
+ }
152
+ updateDeliveryStatus(delivery.id, 'sent');
153
+ log.info({ deliveryId: delivery.id, channel: 'macos', macConversationId }, 'Mac guardian delivery emitted');
154
+ } else {
155
+ const delivery = createGuardianActionDelivery({
156
+ requestId: request.id,
157
+ destinationChannel: dest.channel,
158
+ destinationChatId: dest.chatId,
159
+ destinationExternalUserId: dest.externalUserId,
160
+ });
161
+ // External channel — POST to gateway
162
+ void deliverToExternalChannel(delivery.id, dest.channel, dest.chatId!, request.questionText, request.requestCode, assistantId, readHttpToken() ?? undefined);
163
+ }
164
+ }
165
+ } catch (err) {
166
+ log.error({ err, callSessionId }, 'Failed to dispatch guardian question');
167
+ }
168
+ }
169
+
170
+ async function deliverToExternalChannel(
171
+ deliveryId: string,
172
+ channel: string,
173
+ chatId: string,
174
+ questionText: string,
175
+ requestCode: string,
176
+ assistantId: string,
177
+ bearerToken?: string,
178
+ ): Promise<void> {
179
+ const gatewayBase = getGatewayBaseUrl();
180
+ const deliverUrl = `${gatewayBase}/deliver/${channel}`;
181
+
182
+ const messageText = [
183
+ `Your assistant needs your input during a phone call.`,
184
+ ``,
185
+ `Question: ${questionText}`,
186
+ ``,
187
+ `Reply to this message with your answer. (ref: ${requestCode})`,
188
+ ].join('\n');
189
+
190
+ try {
191
+ await deliverChannelReply(deliverUrl, {
192
+ chatId,
193
+ text: messageText,
194
+ assistantId,
195
+ }, bearerToken);
196
+ updateDeliveryStatus(deliveryId, 'sent');
197
+ log.info({ deliveryId, channel, chatId }, 'External guardian delivery sent');
198
+ } catch (err) {
199
+ const errorMsg = err instanceof Error ? err.message : String(err);
200
+ updateDeliveryStatus(deliveryId, 'failed', errorMsg);
201
+ log.error({ err, deliveryId, channel, chatId }, 'External guardian delivery failed');
202
+ }
203
+ }