@vellumai/assistant 0.3.4 → 0.3.6

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 (506) hide show
  1. package/Dockerfile +2 -0
  2. package/README.md +88 -2
  3. package/eslint.config.mjs +31 -0
  4. package/package.json +1 -1
  5. package/scripts/ipc/check-swift-decoder-drift.ts +4 -1
  6. package/scripts/ipc/generate-swift.ts +31 -2
  7. package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +438 -1
  8. package/src/__tests__/approval-conversation-turn.test.ts +214 -0
  9. package/src/__tests__/approval-hardcoded-copy-guard.test.ts +41 -0
  10. package/src/__tests__/approval-message-composer.test.ts +253 -0
  11. package/src/__tests__/browser-manager.test.ts +1 -0
  12. package/src/__tests__/call-conversation-messages.test.ts +130 -0
  13. package/src/__tests__/call-domain.test.ts +12 -2
  14. package/src/__tests__/call-orchestrator.test.ts +799 -249
  15. package/src/__tests__/call-pointer-messages.test.ts +148 -0
  16. package/src/__tests__/call-recovery.test.ts +3 -0
  17. package/src/__tests__/call-routes-http.test.ts +32 -2
  18. package/src/__tests__/call-store.test.ts +3 -0
  19. package/src/__tests__/channel-approval-routes.test.ts +1277 -98
  20. package/src/__tests__/channel-approval.test.ts +37 -0
  21. package/src/__tests__/channel-approvals.test.ts +36 -50
  22. package/src/__tests__/channel-guardian.test.ts +630 -22
  23. package/src/__tests__/channel-readiness-service.test.ts +324 -0
  24. package/src/__tests__/checker.test.ts +14 -7
  25. package/src/__tests__/clarification-resolver.test.ts +44 -24
  26. package/src/__tests__/commit-message-enrichment-service.test.ts +9 -4
  27. package/src/__tests__/computer-use-session-working-dir.test.ts +8 -0
  28. package/src/__tests__/config-schema.test.ts +14 -8
  29. package/src/__tests__/context-window-manager.test.ts +30 -2
  30. package/src/__tests__/contradiction-checker.test.ts +20 -5
  31. package/src/__tests__/credential-security-invariants.test.ts +7 -2
  32. package/src/__tests__/daemon-lifecycle.test.ts +13 -12
  33. package/src/__tests__/db-migration-rollback.test.ts +752 -0
  34. package/src/__tests__/dictation-mode-detection.test.ts +63 -0
  35. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +2 -0
  36. package/src/__tests__/entity-search.test.ts +615 -0
  37. package/src/__tests__/fuzzy-match-property.test.ts +5 -5
  38. package/src/__tests__/guardian-action-store.test.ts +123 -0
  39. package/src/__tests__/guardian-action-sweep.test.ts +277 -0
  40. package/src/__tests__/guardian-dispatch.test.ts +389 -0
  41. package/src/__tests__/guardian-question-copy.test.ts +47 -0
  42. package/src/__tests__/handlers-telegram-config.test.ts +4 -2
  43. package/src/__tests__/handlers-twilio-config.test.ts +533 -0
  44. package/src/__tests__/intent-routing.test.ts +2 -0
  45. package/src/__tests__/ipc-snapshot.test.ts +291 -1
  46. package/src/__tests__/memory-upsert-concurrency.test.ts +828 -0
  47. package/src/__tests__/messaging-send-tool.test.ts +65 -0
  48. package/src/__tests__/model-intents.test.ts +96 -0
  49. package/src/__tests__/no-direct-anthropic-sdk-imports.test.ts +42 -0
  50. package/src/__tests__/oauth2-gateway-transport.test.ts +130 -0
  51. package/src/__tests__/onboarding-starter-tasks.test.ts +2 -0
  52. package/src/__tests__/provider-commit-message-generator.test.ts +89 -13
  53. package/src/__tests__/provider-error-scenarios.test.ts +621 -0
  54. package/src/__tests__/provider-fail-open-selection.test.ts +119 -0
  55. package/src/__tests__/qdrant-manager.test.ts +27 -20
  56. package/src/__tests__/relay-server.test.ts +779 -40
  57. package/src/__tests__/run-orchestrator-assistant-events.test.ts +6 -0
  58. package/src/__tests__/run-orchestrator.test.ts +42 -4
  59. package/src/__tests__/runtime-runs-http.test.ts +17 -1
  60. package/src/__tests__/runtime-runs.test.ts +16 -0
  61. package/src/__tests__/schedule-store.test.ts +18 -4
  62. package/src/__tests__/scheduler-recurrence.test.ts +13 -4
  63. package/src/__tests__/session-abort-tool-results.test.ts +6 -0
  64. package/src/__tests__/session-agent-loop.test.ts +857 -0
  65. package/src/__tests__/session-conflict-gate.test.ts +6 -0
  66. package/src/__tests__/session-pre-run-repair.test.ts +6 -0
  67. package/src/__tests__/session-profile-injection.test.ts +6 -0
  68. package/src/__tests__/session-provider-retry-repair.test.ts +6 -0
  69. package/src/__tests__/session-queue.test.ts +6 -0
  70. package/src/__tests__/session-runtime-assembly.test.ts +321 -13
  71. package/src/__tests__/session-slash-known.test.ts +6 -0
  72. package/src/__tests__/session-slash-queue.test.ts +6 -0
  73. package/src/__tests__/session-slash-unknown.test.ts +6 -0
  74. package/src/__tests__/session-surfaces-task-progress.test.ts +2 -0
  75. package/src/__tests__/session-tool-setup-app-refresh.test.ts +1 -0
  76. package/src/__tests__/session-tool-setup-memory-scope.test.ts +1 -0
  77. package/src/__tests__/session-tool-setup-side-effect-flag.test.ts +1 -0
  78. package/src/__tests__/session-workspace-injection.test.ts +6 -0
  79. package/src/__tests__/session-workspace-tool-tracking.test.ts +6 -0
  80. package/src/__tests__/skills.test.ts +2 -0
  81. package/src/__tests__/sms-messaging-provider.test.ts +126 -0
  82. package/src/__tests__/starter-task-flow.test.ts +2 -0
  83. package/src/__tests__/swarm-dag-pathological.test.ts +535 -0
  84. package/src/__tests__/system-prompt.test.ts +2 -0
  85. package/src/__tests__/task-management-tools.test.ts +2 -2
  86. package/src/__tests__/task-runner.test.ts +14 -4
  87. package/src/__tests__/terminal-tools.test.ts +25 -19
  88. package/src/__tests__/tool-execution-abort-cleanup.test.ts +545 -0
  89. package/src/__tests__/tool-executor-shell-integration.test.ts +11 -11
  90. package/src/__tests__/tool-executor.test.ts +23 -24
  91. package/src/__tests__/trust-store.test.ts +3 -3
  92. package/src/__tests__/twilio-rest.test.ts +29 -0
  93. package/src/__tests__/twilio-routes-elevenlabs.test.ts +3 -0
  94. package/src/__tests__/twilio-routes-twiml.test.ts +11 -0
  95. package/src/__tests__/twilio-routes.test.ts +167 -11
  96. package/src/__tests__/twitter-cli-error-shaping.test.ts +2 -2
  97. package/src/__tests__/user-reference.test.ts +2 -0
  98. package/src/__tests__/voice-quality.test.ts +222 -0
  99. package/src/__tests__/web-search.test.ts +46 -30
  100. package/src/__tests__/work-item-output.test.ts +110 -0
  101. package/src/agent/loop.ts +1 -1
  102. package/src/agent-heartbeat/agent-heartbeat-service.ts +2 -10
  103. package/src/amazon/client.ts +1418 -0
  104. package/src/amazon/request-extractor.ts +135 -0
  105. package/src/amazon/session.ts +109 -0
  106. package/src/autonomy/autonomy-store.ts +5 -5
  107. package/src/browser-extension-relay/client.ts +124 -0
  108. package/src/browser-extension-relay/protocol.ts +63 -0
  109. package/src/browser-extension-relay/server.ts +177 -0
  110. package/src/bundler/app-bundler.ts +3 -3
  111. package/src/bundler/bundle-signer.ts +1 -1
  112. package/src/bundler/signature-verifier.ts +1 -1
  113. package/src/calls/call-conversation-messages.ts +33 -0
  114. package/src/calls/call-domain.ts +114 -10
  115. package/src/calls/call-orchestrator.ts +268 -59
  116. package/src/calls/call-pointer-messages.ts +53 -0
  117. package/src/calls/call-recovery.ts +3 -8
  118. package/src/calls/call-store.ts +69 -87
  119. package/src/calls/elevenlabs-config.ts +3 -2
  120. package/src/calls/guardian-action-sweep.ts +105 -0
  121. package/src/calls/guardian-dispatch.ts +203 -0
  122. package/src/calls/guardian-question-copy.ts +133 -0
  123. package/src/calls/relay-server.ts +466 -8
  124. package/src/calls/speaker-identification.ts +1 -1
  125. package/src/calls/twilio-config.ts +22 -14
  126. package/src/calls/twilio-provider.ts +6 -4
  127. package/src/calls/twilio-rest.ts +308 -7
  128. package/src/calls/twilio-routes.ts +65 -12
  129. package/src/calls/types.ts +3 -1
  130. package/src/channels/types.ts +25 -0
  131. package/src/cli/amazon.ts +815 -0
  132. package/src/cli/config-commands.ts +2 -2
  133. package/src/cli/core-commands.ts +4 -3
  134. package/src/cli/influencer.ts +244 -0
  135. package/src/cli/map.ts +89 -6
  136. package/src/cli.ts +1 -1
  137. package/src/config/agent-schema.ts +171 -0
  138. package/src/config/bundled-skills/amazon/SKILL.md +127 -0
  139. package/src/config/bundled-skills/amazon/icon.svg +13 -0
  140. package/src/config/bundled-skills/api-mapping/SKILL.md +78 -0
  141. package/src/config/bundled-skills/browser/SKILL.md +1 -0
  142. package/src/config/bundled-skills/browser/TOOLS.json +17 -0
  143. package/src/config/bundled-skills/browser/tools/browser-wait-for-download.ts +25 -0
  144. package/src/config/bundled-skills/doordash/SKILL.md +51 -51
  145. package/src/config/bundled-skills/email-setup/SKILL.md +14 -5
  146. package/src/config/bundled-skills/google-oauth-setup/SKILL.md +183 -0
  147. package/src/config/bundled-skills/influencer/SKILL.md +144 -0
  148. package/src/config/bundled-skills/knowledge-graph/SKILL.md +15 -0
  149. package/src/config/bundled-skills/knowledge-graph/TOOLS.json +56 -0
  150. package/src/config/bundled-skills/knowledge-graph/tools/graph-query.ts +185 -0
  151. package/src/config/bundled-skills/macos-automation/icon.svg +12 -0
  152. package/src/config/bundled-skills/media-processing/SKILL.md +176 -0
  153. package/src/config/bundled-skills/media-processing/TOOLS.json +230 -0
  154. package/src/config/bundled-skills/media-processing/__tests__/concurrency-pool.test.ts +77 -0
  155. package/src/config/bundled-skills/media-processing/__tests__/cost-tracker.test.ts +69 -0
  156. package/src/config/bundled-skills/media-processing/__tests__/preprocess.test.ts +303 -0
  157. package/src/config/bundled-skills/media-processing/services/concurrency-pool.ts +55 -0
  158. package/src/config/bundled-skills/media-processing/services/cost-tracker.ts +86 -0
  159. package/src/config/bundled-skills/media-processing/services/gemini-map.ts +339 -0
  160. package/src/config/bundled-skills/media-processing/services/preprocess.ts +551 -0
  161. package/src/config/bundled-skills/media-processing/services/processing-pipeline.ts +259 -0
  162. package/src/config/bundled-skills/media-processing/services/reduce.ts +197 -0
  163. package/src/config/bundled-skills/media-processing/tools/analyze-keyframes.ts +136 -0
  164. package/src/config/bundled-skills/media-processing/tools/extract-keyframes.ts +59 -0
  165. package/src/config/bundled-skills/media-processing/tools/generate-clip.ts +195 -0
  166. package/src/config/bundled-skills/media-processing/tools/ingest-media.ts +197 -0
  167. package/src/config/bundled-skills/media-processing/tools/media-diagnostics.ts +143 -0
  168. package/src/config/bundled-skills/media-processing/tools/media-status.ts +75 -0
  169. package/src/config/bundled-skills/media-processing/tools/query-media-events.ts +65 -0
  170. package/src/config/bundled-skills/messaging/SKILL.md +33 -8
  171. package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +4 -7
  172. package/src/config/bundled-skills/messaging/tools/messaging-reply.ts +2 -1
  173. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +5 -1
  174. package/src/config/bundled-skills/phone-calls/SKILL.md +88 -23
  175. package/src/config/bundled-skills/twitter/SKILL.md +19 -3
  176. package/src/config/bundled-skills/twitter/icon.svg +14 -0
  177. package/src/config/bundled-tool-registry.ts +310 -0
  178. package/src/config/calls-schema.ts +181 -0
  179. package/src/config/core-schema.ts +309 -0
  180. package/src/config/defaults.ts +28 -3
  181. package/src/config/env-registry.ts +162 -0
  182. package/src/config/env.ts +175 -0
  183. package/src/config/loader.ts +6 -6
  184. package/src/config/memory-schema.ts +528 -0
  185. package/src/config/sandbox-schema.ts +55 -0
  186. package/src/config/schema.ts +158 -1133
  187. package/src/config/skill-state.ts +1 -1
  188. package/src/config/skills-schema.ts +32 -0
  189. package/src/config/skills.ts +35 -24
  190. package/src/config/system-prompt.ts +131 -56
  191. package/src/config/templates/IDENTITY.md +2 -2
  192. package/src/config/templates/SOUL.md +1 -1
  193. package/src/config/types.ts +1 -0
  194. package/src/config/user-reference.ts +4 -9
  195. package/src/config/vellum-skills/catalog.json +6 -7
  196. package/src/config/vellum-skills/chatgpt-import/tools/chatgpt-import.ts +5 -1
  197. package/src/config/vellum-skills/slack-oauth-setup/SKILL.md +4 -3
  198. package/src/config/vellum-skills/sms-setup/SKILL.md +216 -0
  199. package/src/config/vellum-skills/twilio-setup/SKILL.md +40 -8
  200. package/src/context/window-manager.ts +27 -7
  201. package/src/daemon/approval-generators.ts +186 -0
  202. package/src/daemon/approved-devices-store.ts +140 -0
  203. package/src/daemon/assistant-attachments.ts +1 -1
  204. package/src/daemon/classifier.ts +35 -32
  205. package/src/daemon/config-watcher.ts +1 -1
  206. package/src/daemon/daemon-control.ts +217 -0
  207. package/src/daemon/handlers/apps.ts +2 -3
  208. package/src/daemon/handlers/config-channels.ts +158 -0
  209. package/src/daemon/handlers/config-inbox.ts +540 -0
  210. package/src/daemon/handlers/config-ingress.ts +231 -0
  211. package/src/daemon/handlers/config-integrations.ts +258 -0
  212. package/src/daemon/handlers/config-model.ts +143 -0
  213. package/src/daemon/handlers/config-parental.ts +163 -0
  214. package/src/daemon/handlers/config-scheduling.ts +172 -0
  215. package/src/daemon/handlers/config-slack.ts +92 -0
  216. package/src/daemon/handlers/config-telegram.ts +301 -0
  217. package/src/daemon/handlers/config-tools.ts +177 -0
  218. package/src/daemon/handlers/config-trust.ts +104 -0
  219. package/src/daemon/handlers/config-twilio.ts +1080 -0
  220. package/src/daemon/handlers/config.ts +53 -1689
  221. package/src/daemon/handlers/diagnostics.ts +1 -1
  222. package/src/daemon/handlers/dictation.ts +180 -0
  223. package/src/daemon/handlers/documents.ts +18 -32
  224. package/src/daemon/handlers/identity.ts +14 -23
  225. package/src/daemon/handlers/index.ts +11 -0
  226. package/src/daemon/handlers/misc.ts +3 -5
  227. package/src/daemon/handlers/pairing.ts +98 -0
  228. package/src/daemon/handlers/sessions.ts +56 -5
  229. package/src/daemon/handlers/shared.ts +6 -1
  230. package/src/daemon/handlers/skills.ts +1 -1
  231. package/src/daemon/handlers/twitter-auth.ts +2 -0
  232. package/src/daemon/handlers/work-items.ts +17 -9
  233. package/src/daemon/handlers/workspace-files.ts +4 -3
  234. package/src/daemon/install-cli-launchers.ts +113 -0
  235. package/src/daemon/ipc-contract/apps.ts +356 -0
  236. package/src/daemon/ipc-contract/browser.ts +74 -0
  237. package/src/daemon/ipc-contract/computer-use.ts +151 -0
  238. package/src/daemon/ipc-contract/diagnostics.ts +56 -0
  239. package/src/daemon/ipc-contract/documents.ts +74 -0
  240. package/src/daemon/ipc-contract/inbox.ts +209 -0
  241. package/src/daemon/ipc-contract/integrations.ts +284 -0
  242. package/src/daemon/ipc-contract/memory.ts +48 -0
  243. package/src/daemon/ipc-contract/messages.ts +211 -0
  244. package/src/daemon/ipc-contract/pairing.ts +45 -0
  245. package/src/daemon/ipc-contract/parental-control.ts +95 -0
  246. package/src/daemon/ipc-contract/schedules.ts +97 -0
  247. package/src/daemon/ipc-contract/sessions.ts +315 -0
  248. package/src/daemon/ipc-contract/shared.ts +42 -0
  249. package/src/daemon/ipc-contract/skills.ts +120 -0
  250. package/src/daemon/ipc-contract/subagents.ts +58 -0
  251. package/src/daemon/ipc-contract/surfaces.ts +250 -0
  252. package/src/daemon/ipc-contract/trust.ts +60 -0
  253. package/src/daemon/ipc-contract/work-items.ts +225 -0
  254. package/src/daemon/ipc-contract/workspace.ts +113 -0
  255. package/src/daemon/ipc-contract-inventory.json +70 -0
  256. package/src/daemon/ipc-contract-inventory.ts +55 -29
  257. package/src/daemon/ipc-contract.ts +229 -2426
  258. package/src/daemon/ipc-protocol.ts +1 -1
  259. package/src/daemon/ipc-validate.ts +7 -0
  260. package/src/daemon/lifecycle.ts +97 -377
  261. package/src/daemon/pairing-store.ts +177 -0
  262. package/src/daemon/providers-setup.ts +43 -0
  263. package/src/daemon/ride-shotgun-handler.ts +68 -3
  264. package/src/daemon/server.ts +66 -46
  265. package/src/daemon/session-agent-loop-handlers.ts +421 -0
  266. package/src/daemon/session-agent-loop.ts +117 -275
  267. package/src/daemon/session-dynamic-profile.ts +1 -1
  268. package/src/daemon/session-history.ts +1 -1
  269. package/src/daemon/session-media-retry.ts +1 -1
  270. package/src/daemon/session-messaging.ts +37 -2
  271. package/src/daemon/session-notifiers.ts +5 -25
  272. package/src/daemon/session-process.ts +99 -59
  273. package/src/daemon/session-queue-manager.ts +96 -4
  274. package/src/daemon/session-runtime-assembly.ts +199 -10
  275. package/src/daemon/session-surfaces.ts +19 -4
  276. package/src/daemon/session-tool-setup.ts +30 -30
  277. package/src/daemon/session-workspace.ts +1 -1
  278. package/src/daemon/session.ts +35 -2
  279. package/src/daemon/shutdown-handlers.ts +122 -0
  280. package/src/daemon/trace-emitter.ts +1 -1
  281. package/src/daemon/watch-handler.ts +36 -33
  282. package/src/doordash/cart-queries.ts +787 -0
  283. package/src/doordash/client.ts +144 -127
  284. package/src/doordash/order-queries.ts +85 -0
  285. package/src/doordash/queries.ts +10 -1308
  286. package/src/doordash/search-queries.ts +203 -0
  287. package/src/doordash/session.ts +3 -2
  288. package/src/doordash/store-queries.ts +246 -0
  289. package/src/doordash/types.ts +367 -0
  290. package/src/email/providers/agentmail.ts +2 -1
  291. package/src/email/providers/index.ts +3 -2
  292. package/src/email/service.ts +3 -2
  293. package/src/errors.ts +43 -0
  294. package/src/home-base/prebuilt/seed.ts +1 -1
  295. package/src/hooks/cli.ts +6 -5
  296. package/src/hooks/config.ts +6 -8
  297. package/src/hooks/discovery.ts +6 -5
  298. package/src/hooks/manager.ts +4 -3
  299. package/src/hooks/runner.ts +2 -2
  300. package/src/hooks/templates.ts +5 -5
  301. package/src/inbound/public-ingress-urls.ts +6 -4
  302. package/src/index.ts +4 -2
  303. package/src/influencer/client.ts +1104 -0
  304. package/src/instrument.ts +4 -3
  305. package/src/logfire.ts +4 -3
  306. package/src/memory/admin.ts +25 -35
  307. package/src/memory/attachments-store.ts +4 -7
  308. package/src/memory/channel-delivery-store.ts +30 -1
  309. package/src/memory/channel-guardian-store.ts +202 -2
  310. package/src/memory/clarification-resolver.ts +37 -33
  311. package/src/memory/conflict-store.ts +67 -61
  312. package/src/memory/contradiction-checker.ts +141 -117
  313. package/src/memory/conversation-store.ts +335 -51
  314. package/src/memory/db-connection.ts +27 -4
  315. package/src/memory/db-init.ts +265 -4
  316. package/src/memory/db.ts +14 -1
  317. package/src/memory/embedding-backend.ts +27 -5
  318. package/src/memory/embedding-ollama.ts +2 -1
  319. package/src/memory/entity-extractor.ts +38 -35
  320. package/src/memory/guardian-action-store.ts +430 -0
  321. package/src/memory/inbox-escalation-projection.ts +59 -0
  322. package/src/memory/inbox-thread-store.ts +218 -0
  323. package/src/memory/ingress-invite-store.ts +338 -0
  324. package/src/memory/ingress-member-store.ts +350 -0
  325. package/src/memory/items-extractor.ts +91 -97
  326. package/src/memory/job-handlers/index-maintenance.ts +3 -3
  327. package/src/memory/job-handlers/media-processing.ts +69 -0
  328. package/src/memory/job-handlers/summarization.ts +32 -26
  329. package/src/memory/job-utils.ts +3 -10
  330. package/src/memory/jobs-store.ts +8 -10
  331. package/src/memory/jobs-worker.ts +55 -36
  332. package/src/memory/media-store.ts +759 -0
  333. package/src/memory/migrations/001-job-deferrals.ts +45 -0
  334. package/src/memory/migrations/002-tool-invocations-fk.ts +43 -0
  335. package/src/memory/migrations/003-memory-fts-backfill.ts +24 -0
  336. package/src/memory/migrations/004-entity-relation-dedup.ts +87 -0
  337. package/src/memory/migrations/005-fingerprint-scope-unique.ts +80 -0
  338. package/src/memory/migrations/006-scope-salted-fingerprints.ts +62 -0
  339. package/src/memory/migrations/007-assistant-id-to-self.ts +254 -0
  340. package/src/memory/migrations/008-remove-assistant-id-columns.ts +208 -0
  341. package/src/memory/migrations/009-llm-usage-events-drop-assistant-id.ts +83 -0
  342. package/src/memory/migrations/010-ext-conv-bindings-channel-chat-unique.ts +56 -0
  343. package/src/memory/migrations/011-call-sessions-provider-sid-dedup.ts +63 -0
  344. package/src/memory/migrations/012-call-sessions-add-initiated-from.ts +19 -0
  345. package/src/memory/migrations/013-guardian-action-tables.ts +68 -0
  346. package/src/memory/migrations/014-backfill-inbox-thread-state.ts +76 -0
  347. package/src/memory/migrations/015-drop-active-search-index.ts +27 -0
  348. package/src/memory/migrations/016-memory-segments-indexes.ts +11 -0
  349. package/src/memory/migrations/017-memory-items-indexes.ts +10 -0
  350. package/src/memory/migrations/018-remaining-table-indexes.ts +13 -0
  351. package/src/memory/migrations/index.ts +24 -0
  352. package/src/memory/migrations/registry.ts +79 -0
  353. package/src/memory/migrations/validate-migration-state.ts +69 -0
  354. package/src/memory/qdrant-manager.ts +49 -8
  355. package/src/memory/query-builder.ts +1 -1
  356. package/src/memory/raw-query.ts +119 -0
  357. package/src/memory/recall-cache.ts +4 -1
  358. package/src/memory/retriever.ts +165 -47
  359. package/src/memory/schema-migration.ts +25 -984
  360. package/src/memory/schema.ts +228 -7
  361. package/src/memory/search/entity.ts +205 -31
  362. package/src/memory/search/lexical.ts +81 -52
  363. package/src/memory/search/ranking.ts +27 -23
  364. package/src/memory/search/semantic.ts +157 -19
  365. package/src/memory/search/types.ts +24 -0
  366. package/src/memory/shared-app-links-store.ts +4 -5
  367. package/src/memory/validation.ts +19 -0
  368. package/src/messaging/draft-store.ts +5 -6
  369. package/src/messaging/provider-types.ts +2 -0
  370. package/src/messaging/providers/sms/adapter.ts +201 -0
  371. package/src/messaging/providers/sms/client.ts +93 -0
  372. package/src/messaging/providers/sms/types.ts +7 -0
  373. package/src/messaging/providers/telegram-bot/adapter.ts +2 -5
  374. package/src/messaging/providers/whatsapp/adapter.ts +136 -0
  375. package/src/messaging/providers/whatsapp/client.ts +67 -0
  376. package/src/messaging/style-analyzer.ts +5 -4
  377. package/src/messaging/thread-summarizer.ts +61 -69
  378. package/src/messaging/triage-engine.ts +62 -71
  379. package/src/migrations/config-merge.ts +53 -0
  380. package/src/migrations/data-layout.ts +68 -0
  381. package/src/migrations/data-merge.ts +33 -0
  382. package/src/migrations/hooks-merge.ts +90 -0
  383. package/src/migrations/index.ts +6 -0
  384. package/src/migrations/log.ts +23 -0
  385. package/src/migrations/skills-merge.ts +33 -0
  386. package/src/migrations/workspace-layout.ts +79 -0
  387. package/src/permissions/checker.ts +133 -11
  388. package/src/permissions/prompter.ts +14 -0
  389. package/src/permissions/shell-identity.ts +31 -1
  390. package/src/permissions/trust-store.ts +21 -1
  391. package/src/providers/anthropic/client.ts +4 -4
  392. package/src/providers/failover.ts +2 -2
  393. package/src/providers/model-intents.ts +70 -0
  394. package/src/providers/ollama/client.ts +2 -1
  395. package/src/providers/provider-send-message.ts +176 -0
  396. package/src/providers/registry.ts +71 -30
  397. package/src/providers/retry.ts +35 -1
  398. package/src/providers/types.ts +12 -1
  399. package/src/runtime/approval-conversation-turn.ts +97 -0
  400. package/src/runtime/approval-message-composer.ts +253 -0
  401. package/src/runtime/channel-approval-parser.ts +36 -2
  402. package/src/runtime/channel-approvals.ts +11 -24
  403. package/src/runtime/channel-guardian-service.ts +88 -21
  404. package/src/runtime/channel-readiness-service.ts +418 -0
  405. package/src/runtime/channel-readiness-types.ts +35 -0
  406. package/src/runtime/channel-retry-sweep.ts +184 -0
  407. package/src/runtime/guardian-context-resolver.ts +108 -0
  408. package/src/runtime/http-server.ts +275 -717
  409. package/src/runtime/http-types.ts +59 -3
  410. package/src/runtime/middleware/auth.ts +116 -0
  411. package/src/runtime/middleware/error-handler.ts +33 -0
  412. package/src/runtime/middleware/twilio-validation.ts +127 -0
  413. package/src/runtime/routes/app-routes.ts +1 -1
  414. package/src/runtime/routes/call-routes.ts +51 -7
  415. package/src/runtime/routes/channel-delivery-routes.ts +170 -0
  416. package/src/runtime/routes/channel-guardian-routes.ts +1191 -0
  417. package/src/runtime/routes/channel-inbound-routes.ts +1152 -0
  418. package/src/runtime/routes/channel-route-shared.ts +144 -0
  419. package/src/runtime/routes/channel-routes.ts +32 -1588
  420. package/src/runtime/routes/conversation-routes.ts +50 -7
  421. package/src/runtime/routes/events-routes.ts +2 -2
  422. package/src/runtime/routes/identity-routes.ts +126 -0
  423. package/src/runtime/routes/pairing-routes.ts +143 -0
  424. package/src/runtime/routes/run-routes.ts +15 -1
  425. package/src/runtime/run-orchestrator.ts +86 -35
  426. package/src/schedule/schedule-store.ts +36 -32
  427. package/src/schedule/scheduler.ts +3 -3
  428. package/src/security/encrypted-store.ts +5 -7
  429. package/src/security/oauth2.ts +45 -15
  430. package/src/security/parental-control-store.ts +183 -0
  431. package/src/security/secret-allowlist.ts +4 -3
  432. package/src/security/secret-scanner.ts +5 -5
  433. package/src/security/secure-keys.ts +1 -1
  434. package/src/security/token-manager.ts +3 -2
  435. package/src/services/vercel-deploy.ts +6 -2
  436. package/src/skills/tool-manifest.ts +3 -3
  437. package/src/skills/vellum-catalog-remote.ts +75 -16
  438. package/src/slack/slack-webhook.ts +2 -1
  439. package/src/swarm/orchestrator.ts +92 -1
  440. package/src/swarm/router-planner.ts +6 -9
  441. package/src/swarm/worker-prompts.ts +9 -12
  442. package/src/tasks/task-compiler.ts +19 -28
  443. package/src/tasks/task-runner.ts +1 -1
  444. package/src/tools/assets/materialize.ts +2 -2
  445. package/src/tools/assets/search.ts +15 -14
  446. package/src/tools/browser/__tests__/auth-detector.test.ts +1 -0
  447. package/src/tools/browser/auto-navigate.ts +1 -0
  448. package/src/tools/browser/browser-execution.ts +10 -1
  449. package/src/tools/browser/browser-manager.ts +119 -4
  450. package/src/tools/browser/network-recorder.ts +5 -0
  451. package/src/tools/calls/call-start.ts +1 -0
  452. package/src/tools/credentials/broker.ts +11 -2
  453. package/src/tools/credentials/metadata-store.ts +18 -14
  454. package/src/tools/credentials/post-connect-hooks.ts +61 -0
  455. package/src/tools/credentials/vault.ts +49 -23
  456. package/src/tools/execution-target.ts +11 -1
  457. package/src/tools/executor.ts +68 -9
  458. package/src/tools/host-terminal/cli-discover.ts +1 -1
  459. package/src/tools/network/script-proxy/http-forwarder.ts +1 -1
  460. package/src/tools/network/script-proxy/mitm-handler.ts +1 -1
  461. package/src/tools/network/script-proxy/server.ts +1 -1
  462. package/src/tools/network/script-proxy/session-manager.ts +6 -5
  463. package/src/tools/network/web-fetch.ts +18 -2
  464. package/src/tools/network/web-search.ts +8 -4
  465. package/src/tools/reminder/reminder-store.ts +14 -15
  466. package/src/tools/schedule/create.ts +1 -0
  467. package/src/tools/schedule/list.ts +2 -1
  468. package/src/tools/shared/filesystem/file-ops-service.ts +5 -7
  469. package/src/tools/skills/skill-script-runner.ts +24 -9
  470. package/src/tools/skills/skill-tool-factory.ts +1 -0
  471. package/src/tools/tasks/work-item-enqueue.ts +2 -2
  472. package/src/tools/terminal/evaluate-typescript.ts +21 -12
  473. package/src/tools/terminal/parser.ts +50 -0
  474. package/src/tools/types.ts +2 -0
  475. package/src/tools/watcher/delete.ts +6 -0
  476. package/src/tools/weather/service.ts +1 -1
  477. package/src/twitter/client.ts +190 -24
  478. package/src/twitter/router.ts +1 -1
  479. package/src/twitter/session.ts +4 -3
  480. package/src/util/clipboard.ts +1 -1
  481. package/src/util/errors.ts +65 -8
  482. package/src/util/fs.ts +40 -0
  483. package/src/util/json.ts +10 -0
  484. package/src/util/log-redact.ts +189 -0
  485. package/src/util/logger.ts +19 -17
  486. package/src/util/object.ts +3 -0
  487. package/src/util/platform.ts +105 -363
  488. package/src/util/pricing.ts +1 -1
  489. package/src/util/promise-guard.ts +1 -1
  490. package/src/util/retry.ts +19 -0
  491. package/src/util/row-mapper.ts +79 -0
  492. package/src/util/silently.ts +21 -0
  493. package/src/watcher/engine.ts +5 -1
  494. package/src/watcher/provider-types.ts +20 -0
  495. package/src/watcher/providers/github.ts +156 -0
  496. package/src/watcher/providers/gmail.ts +1 -0
  497. package/src/watcher/providers/google-calendar.ts +1 -0
  498. package/src/watcher/providers/linear.ts +460 -0
  499. package/src/watcher/providers/slack.ts +1 -0
  500. package/src/work-items/work-item-runner.ts +1 -1
  501. package/src/workspace/git-service.ts +1 -1
  502. package/src/workspace/provider-commit-message-generator.ts +51 -22
  503. package/src/__tests__/call-bridge.test.ts +0 -517
  504. package/src/__tests__/session-process-bridge.test.ts +0 -244
  505. package/src/calls/call-bridge.ts +0 -168
  506. package/src/config/vellum-skills/google-oauth-setup/SKILL.md +0 -199
@@ -14,7 +14,7 @@ const log = getLogger('session-history');
14
14
 
15
15
  function isUndoableUserMessage(message: Message): boolean {
16
16
  if (message.role !== 'user') return false;
17
- if (getSummaryFromContextMessage(message) !== null) return false;
17
+ if (getSummaryFromContextMessage(message) != null) return false;
18
18
  // A user message is undoable if it contains user-authored content (non-tool_result
19
19
  // blocks). Messages that contain ONLY tool_result blocks (e.g. automated tool
20
20
  // responses) are not undoable. Messages that have both tool_result and text blocks
@@ -17,7 +17,7 @@ export function stripMediaPayloadsForRetry(messages: Message[]): { messages: Mes
17
17
  for (let i = messages.length - 1; i >= 0; i--) {
18
18
  const msg = messages[i];
19
19
  if (msg.role !== 'user') continue;
20
- if (getSummaryFromContextMessage(msg) !== null) continue;
20
+ if (getSummaryFromContextMessage(msg) != null) continue;
21
21
  if (isToolResultOnlyMessage(msg)) continue;
22
22
  latestUserIndex = i;
23
23
  break;
@@ -7,6 +7,8 @@
7
7
 
8
8
  import { v4 as uuid } from 'uuid';
9
9
  import type { Message } from '../providers/types.js';
10
+ import type { TurnChannelContext } from '../channels/types.js';
11
+ import { parseChannelId } from '../channels/types.js';
10
12
  import type { ServerMessage, UserMessageAttachment } from './ipc-protocol.js';
11
13
  import { createUserMessage } from '../agent/message-types.js';
12
14
  import * as conversationStore from '../memory/conversation-store.js';
@@ -25,6 +27,15 @@ export interface MessagingSessionContext {
25
27
  abortController: AbortController | null;
26
28
  currentRequestId?: string;
27
29
  readonly queue: MessageQueue;
30
+ getTurnChannelContext(): TurnChannelContext | null;
31
+ }
32
+
33
+ function extractTurnChannelContext(metadata?: Record<string, unknown>): TurnChannelContext | null {
34
+ if (!metadata) return null;
35
+ const userMessageChannel = parseChannelId(metadata.userMessageChannel);
36
+ const assistantMessageChannel = parseChannelId(metadata.assistantMessageChannel);
37
+ if (!userMessageChannel || !assistantMessageChannel) return null;
38
+ return { userMessageChannel, assistantMessageChannel };
28
39
  }
29
40
 
30
41
  // ── enqueueMessage ───────────────────────────────────────────────────
@@ -43,7 +54,18 @@ export function enqueueMessage(
43
54
  return { queued: false, requestId };
44
55
  }
45
56
 
46
- const pushed = ctx.queue.push({ content, attachments, requestId, onEvent, activeSurfaceId, currentPage, metadata });
57
+ const turnChannelContext = extractTurnChannelContext(metadata) ?? ctx.getTurnChannelContext() ?? undefined;
58
+ const pushed = ctx.queue.push({
59
+ content,
60
+ attachments,
61
+ requestId,
62
+ onEvent,
63
+ activeSurfaceId,
64
+ currentPage,
65
+ metadata,
66
+ turnChannelContext,
67
+ queuedAt: Date.now(),
68
+ });
47
69
  if (!pushed) {
48
70
  return { queued: false, rejected: true, requestId };
49
71
  }
@@ -82,13 +104,26 @@ export function persistUserMessage(
82
104
  ctx.messages.push(userMessage);
83
105
 
84
106
  try {
107
+ const turnCtx = extractTurnChannelContext(metadata) ?? ctx.getTurnChannelContext();
108
+ const mergedMetadata = turnCtx
109
+ ? {
110
+ ...(metadata ?? {}),
111
+ userMessageChannel: turnCtx.userMessageChannel,
112
+ assistantMessageChannel: turnCtx.assistantMessageChannel,
113
+ }
114
+ : metadata;
115
+
85
116
  const persistedUserMessage = conversationStore.addMessage(
86
117
  ctx.conversationId,
87
118
  'user',
88
119
  JSON.stringify(userMessage.content),
89
- metadata,
120
+ mergedMetadata,
90
121
  );
91
122
 
123
+ if (turnCtx) {
124
+ conversationStore.setConversationOriginChannelIfUnset(ctx.conversationId, turnCtx.userMessageChannel);
125
+ }
126
+
92
127
  if (!persistedUserMessage.id) {
93
128
  throw new Error('Failed to persist user message');
94
129
  }
@@ -30,7 +30,8 @@ import {
30
30
  registerCallCompletionNotifier,
31
31
  unregisterCallCompletionNotifier,
32
32
  } from '../calls/call-state.js';
33
- import { getCallSession, getCallEvents } from '../calls/call-store.js';
33
+ import { getCallSession } from '../calls/call-store.js';
34
+ import { buildCallCompletionMessage } from '../calls/call-conversation-messages.js';
34
35
 
35
36
  /**
36
37
  * Subset of Session state that notifier callbacks need to read at
@@ -95,12 +96,13 @@ export function registerSessionNotifiers(
95
96
  registerCallQuestionNotifier(conversationId, (callSessionId: string, question: string) => {
96
97
  const callSession = getCallSession(callSessionId);
97
98
  const callee = callSession?.toNumber ?? 'the caller';
98
- const questionText = `**Live call question** (to ${callee}):\n\n${question}\n\n_Reply in this thread to answer. Your next message will be treated as the answer to this question. Once answered, you can send messages to steer the conversation._`;
99
+ const questionText = `**Live call question** (to ${callee}):\n\n${question}\n\n_Use the call answer API to respond._`;
99
100
 
100
101
  conversationStore.addMessage(
101
102
  conversationId,
102
103
  'assistant',
103
104
  JSON.stringify([{ type: 'text', text: questionText }]),
105
+ { userMessageChannel: 'voice', assistantMessageChannel: 'voice' },
104
106
  );
105
107
 
106
108
  ctx.messages.push(createAssistantMessage(questionText));
@@ -122,14 +124,6 @@ export function registerSessionNotifiers(
122
124
  const speakerLabel = speaker === 'caller' ? 'Caller' : 'Assistant';
123
125
  const transcriptText = `**Live call transcript**\n${speakerLabel}: ${text}`;
124
126
 
125
- conversationStore.addMessage(
126
- conversationId,
127
- 'assistant',
128
- JSON.stringify([{ type: 'text', text: transcriptText }]),
129
- );
130
-
131
- ctx.messages.push(createAssistantMessage(transcriptText));
132
-
133
127
  ctx.sendToClient({
134
128
  type: 'assistant_text_delta',
135
129
  text: transcriptText,
@@ -143,21 +137,7 @@ export function registerSessionNotifiers(
143
137
  );
144
138
 
145
139
  registerCallCompletionNotifier(conversationId, (callSessionId: string) => {
146
- const callSession = getCallSession(callSessionId);
147
- const events = getCallEvents(callSessionId);
148
- const duration = callSession?.endedAt && callSession?.startedAt
149
- ? Math.round((callSession.endedAt - callSession.startedAt) / 1000)
150
- : null;
151
- const durationStr = duration !== null ? ` (${duration}s)` : '';
152
- const summaryText = `**Call completed**${durationStr}. ${events.length} event(s) recorded.`;
153
-
154
- conversationStore.addMessage(
155
- conversationId,
156
- 'assistant',
157
- JSON.stringify([{ type: 'text', text: summaryText }]),
158
- );
159
-
160
- ctx.messages.push(createAssistantMessage(summaryText));
140
+ const summaryText = buildCallCompletionMessage(callSessionId);
161
141
 
162
142
  ctx.sendToClient({
163
143
  type: 'assistant_text_delta',
@@ -7,6 +7,8 @@
7
7
  */
8
8
 
9
9
  import type { Message } from '../providers/types.js';
10
+ import type { TurnChannelContext } from '../channels/types.js';
11
+ import { parseChannelId } from '../channels/types.js';
10
12
  import type { ServerMessage, UserMessageAttachment } from './ipc-protocol.js';
11
13
  import type { UsageStats } from './ipc-contract.js';
12
14
  import type { MessageQueue } from './session-queue-manager.js';
@@ -14,10 +16,15 @@ import type { QueueDrainReason } from './session-queue-manager.js';
14
16
  import type { TraceEmitter } from './trace-emitter.js';
15
17
  import { createUserMessage, createAssistantMessage } from '../agent/message-types.js';
16
18
  import * as conversationStore from '../memory/conversation-store.js';
19
+ import {
20
+ getPendingDeliveryByConversation,
21
+ getGuardianActionRequest,
22
+ resolveGuardianActionRequest,
23
+ } from '../memory/guardian-action-store.js';
24
+ import { answerCall } from '../calls/call-domain.js';
17
25
  import { resolveSlash, type SlashContext } from './session-slash.js';
18
26
  import { getConfig } from '../config/loader.js';
19
27
  import { getLogger } from '../util/logger.js';
20
- import { tryRouteCallMessage } from '../calls/call-bridge.js';
21
28
 
22
29
  const log = getLogger('session-process');
23
30
 
@@ -68,6 +75,24 @@ export interface ProcessSessionContext {
68
75
  onEvent: (msg: ServerMessage) => void,
69
76
  options?: { skipPreMessageRollback?: boolean },
70
77
  ): Promise<void>;
78
+ getTurnChannelContext(): TurnChannelContext | null;
79
+ setTurnChannelContext(ctx: TurnChannelContext): void;
80
+ }
81
+
82
+ function resolveQueuedTurnContext(
83
+ queued: { turnChannelContext?: TurnChannelContext; metadata?: Record<string, unknown> },
84
+ fallback: TurnChannelContext | null,
85
+ ): TurnChannelContext | null {
86
+ if (queued.turnChannelContext) return queued.turnChannelContext;
87
+ const metadata = queued.metadata;
88
+ if (metadata) {
89
+ const userMessageChannel = parseChannelId(metadata.userMessageChannel);
90
+ const assistantMessageChannel = parseChannelId(metadata.assistantMessageChannel);
91
+ if (userMessageChannel && assistantMessageChannel) {
92
+ return { userMessageChannel, assistantMessageChannel };
93
+ }
94
+ }
95
+ return fallback;
71
96
  }
72
97
 
73
98
  /** Build a SlashContext from the current session state and config. */
@@ -112,6 +137,11 @@ export function drainQueue(session: ProcessSessionContext, reason: QueueDrainRea
112
137
  requestId: next.requestId,
113
138
  });
114
139
 
140
+ const queuedTurnCtx = resolveQueuedTurnContext(next, session.getTurnChannelContext());
141
+ if (queuedTurnCtx) {
142
+ session.setTurnChannelContext(queuedTurnCtx);
143
+ }
144
+
115
145
  // Resolve slash commands for queued messages
116
146
  const slashResult = resolveSlash(next.content, buildSlashContext(session));
117
147
 
@@ -120,11 +150,15 @@ export function drainQueue(session: ProcessSessionContext, reason: QueueDrainRea
120
150
  // failed write never leaves an unpersisted message in memory.
121
151
  if (slashResult.kind === 'unknown') {
122
152
  try {
153
+ const drainChannelMeta = queuedTurnCtx
154
+ ? { userMessageChannel: queuedTurnCtx.userMessageChannel, assistantMessageChannel: queuedTurnCtx.assistantMessageChannel }
155
+ : undefined;
123
156
  const userMsg = createUserMessage(next.content, next.attachments);
124
157
  conversationStore.addMessage(
125
158
  session.conversationId,
126
159
  'user',
127
160
  JSON.stringify(userMsg.content),
161
+ drainChannelMeta,
128
162
  );
129
163
  session.messages.push(userMsg);
130
164
 
@@ -133,6 +167,7 @@ export function drainQueue(session: ProcessSessionContext, reason: QueueDrainRea
133
167
  session.conversationId,
134
168
  'assistant',
135
169
  JSON.stringify(assistantMsg.content),
170
+ drainChannelMeta,
136
171
  );
137
172
  session.messages.push(assistantMsg);
138
173
 
@@ -197,49 +232,15 @@ export function drainQueue(session: ProcessSessionContext, reason: QueueDrainRea
197
232
  session.currentPage = next.currentPage;
198
233
 
199
234
  // Fire-and-forget: persistUserMessage set session.processing = true
200
- // so subsequent messages will still be enqueued. Route through the call
201
- // bridge first — if consumed, skip agent processing and continue draining.
235
+ // so subsequent messages will still be enqueued.
202
236
  // runAgentLoop's finally block will call drainQueue when this run completes.
203
- routeOrProcess(session, resolvedContent, userMessageId, next).catch((err) => {
237
+ session.runAgentLoop(resolvedContent, userMessageId, next.onEvent).catch((err) => {
204
238
  const message = err instanceof Error ? err.message : String(err);
205
239
  log.error({ err, conversationId: session.conversationId, requestId: next.requestId }, 'Error processing queued message');
206
240
  next.onEvent({ type: 'error', message: `Failed to process queued message: ${message}` });
207
241
  });
208
242
  }
209
243
 
210
- /**
211
- * Try the call bridge first; if not consumed, run the agent loop.
212
- * Used by drainQueue to handle the async bridge check in fire-and-forget mode.
213
- */
214
- async function routeOrProcess(
215
- session: ProcessSessionContext,
216
- content: string,
217
- userMessageId: string,
218
- next: { onEvent: (msg: ServerMessage) => void; requestId: string },
219
- ): Promise<void> {
220
- try {
221
- const bridgeResult = await tryRouteCallMessage(session.conversationId, content, userMessageId);
222
- if (bridgeResult.handled) {
223
- session.preactivatedSkillIds = undefined;
224
- session.processing = false;
225
- session.abortController = null;
226
- session.currentRequestId = undefined;
227
- log.info({ conversationId: session.conversationId, userMessageId }, 'Queued message consumed by call bridge, skipping agent loop');
228
- if (bridgeResult.userFacingText) {
229
- next.onEvent({ type: 'assistant_text_delta', text: bridgeResult.userFacingText });
230
- }
231
- next.onEvent({ type: 'message_complete', sessionId: session.conversationId });
232
- // runAgentLoop never ran so its finally block won't drain — continue manually
233
- drainQueue(session);
234
- return;
235
- }
236
- } catch (err) {
237
- log.warn({ err, conversationId: session.conversationId }, 'Call bridge check failed (non-fatal), proceeding with agent loop');
238
- }
239
-
240
- await session.runAgentLoop(content, userMessageId, next.onEvent);
241
- }
242
-
243
244
  // ── processMessage ───────────────────────────────────────────────────
244
245
 
245
246
  /**
@@ -258,6 +259,61 @@ export async function processMessage(
258
259
  session.currentActiveSurfaceId = activeSurfaceId;
259
260
  session.currentPage = currentPage;
260
261
 
262
+ // ── Guardian action answer interception (mac channel) ──
263
+ // If this conversation has a pending guardian action delivery, treat the
264
+ // user message as the guardian's answer instead of running the agent loop.
265
+ const guardianDelivery = getPendingDeliveryByConversation(session.conversationId);
266
+ if (guardianDelivery) {
267
+ const guardianRequest = getGuardianActionRequest(guardianDelivery.requestId);
268
+ if (guardianRequest && guardianRequest.status === 'pending') {
269
+ const guardianChannelMeta = { userMessageChannel: 'macos' as const, assistantMessageChannel: 'macos' as const };
270
+ const userMsg = createUserMessage(content, attachments);
271
+ const persisted = conversationStore.addMessage(
272
+ session.conversationId,
273
+ 'user',
274
+ JSON.stringify(userMsg.content),
275
+ guardianChannelMeta,
276
+ );
277
+ session.messages.push(userMsg);
278
+
279
+ // Attempt to deliver the answer to the call first. Only resolve
280
+ // the guardian action request if answerCall succeeds, so that a
281
+ // failed delivery leaves the request pending for retry from
282
+ // another channel.
283
+ const answerResult = await answerCall({ callSessionId: guardianRequest.callSessionId, answer: content });
284
+
285
+ if ('ok' in answerResult && answerResult.ok) {
286
+ const resolved = resolveGuardianActionRequest(guardianRequest.id, content, 'macos');
287
+ const replyText = resolved
288
+ ? 'Your answer has been relayed to the call.'
289
+ : 'This question has already been answered from another channel.';
290
+ const replyMsg = createAssistantMessage(replyText);
291
+ conversationStore.addMessage(
292
+ session.conversationId,
293
+ 'assistant',
294
+ JSON.stringify(replyMsg.content),
295
+ guardianChannelMeta,
296
+ );
297
+ session.messages.push(replyMsg);
298
+ onEvent({ type: 'assistant_text_delta', text: replyText });
299
+ } else {
300
+ const errorDetail = 'error' in answerResult ? answerResult.error : 'Unknown error';
301
+ log.warn({ callSessionId: guardianRequest.callSessionId, error: errorDetail }, 'answerCall failed for mac guardian answer');
302
+ const failMsg = createAssistantMessage('Failed to deliver your answer to the call. Please try again.');
303
+ conversationStore.addMessage(
304
+ session.conversationId,
305
+ 'assistant',
306
+ JSON.stringify(failMsg.content),
307
+ guardianChannelMeta,
308
+ );
309
+ session.messages.push(failMsg);
310
+ onEvent({ type: 'assistant_text_delta', text: 'Failed to deliver your answer to the call. Please try again.' });
311
+ }
312
+ onEvent({ type: 'message_complete', sessionId: session.conversationId });
313
+ return persisted.id;
314
+ }
315
+ }
316
+
261
317
  // Resolve slash commands before persistence
262
318
  const slashResult = resolveSlash(content, buildSlashContext(session));
263
319
 
@@ -265,11 +321,16 @@ export async function processMessage(
265
321
  // messageId is real. Persist each message before pushing to session.messages
266
322
  // so that a failed write never leaves an unpersisted message in memory.
267
323
  if (slashResult.kind === 'unknown') {
324
+ const pmTurnCtx = session.getTurnChannelContext();
325
+ const pmChannelMeta = pmTurnCtx
326
+ ? { userMessageChannel: pmTurnCtx.userMessageChannel, assistantMessageChannel: pmTurnCtx.assistantMessageChannel }
327
+ : undefined;
268
328
  const userMsg = createUserMessage(content, attachments);
269
329
  const persisted = conversationStore.addMessage(
270
330
  session.conversationId,
271
331
  'user',
272
332
  JSON.stringify(userMsg.content),
333
+ pmChannelMeta,
273
334
  );
274
335
  session.messages.push(userMsg);
275
336
 
@@ -278,6 +339,7 @@ export async function processMessage(
278
339
  session.conversationId,
279
340
  'assistant',
280
341
  JSON.stringify(assistantMsg.content),
342
+ pmChannelMeta,
281
343
  );
282
344
  session.messages.push(assistantMsg);
283
345
 
@@ -313,28 +375,6 @@ export async function processMessage(
313
375
  return '';
314
376
  }
315
377
 
316
- // Route through the call bridge before the agent loop. When the bridge
317
- // consumes the message (answer or instruction), skip agent processing.
318
- try {
319
- const bridgeResult = await tryRouteCallMessage(session.conversationId, resolvedContent, userMessageId);
320
- if (bridgeResult.handled) {
321
- session.preactivatedSkillIds = undefined;
322
- session.processing = false;
323
- session.abortController = null;
324
- session.currentRequestId = undefined;
325
- log.info({ conversationId: session.conversationId, userMessageId }, 'IPC message consumed by call bridge, skipping agent loop');
326
- if (bridgeResult.userFacingText) {
327
- onEvent({ type: 'assistant_text_delta', text: bridgeResult.userFacingText });
328
- }
329
- onEvent({ type: 'message_complete', sessionId: session.conversationId });
330
- // runAgentLoop never ran so its finally block won't drain — continue manually
331
- drainQueue(session);
332
- return userMessageId;
333
- }
334
- } catch (err) {
335
- log.warn({ err, conversationId: session.conversationId }, 'Call bridge check failed (non-fatal), proceeding with agent loop');
336
- }
337
-
338
378
  await session.runAgentLoop(resolvedContent, userMessageId, onEvent);
339
379
  return userMessageId;
340
380
  }
@@ -6,6 +6,10 @@
6
6
  */
7
7
 
8
8
  import type { ServerMessage, UserMessageAttachment } from './ipc-protocol.js';
9
+ import type { TurnChannelContext } from '../channels/types.js';
10
+ import { getLogger } from '../util/logger.js';
11
+
12
+ const log = getLogger('session-queue');
9
13
 
10
14
  export interface QueuedMessage {
11
15
  content: string;
@@ -15,9 +19,15 @@ export interface QueuedMessage {
15
19
  activeSurfaceId?: string;
16
20
  currentPage?: string;
17
21
  metadata?: Record<string, unknown>;
22
+ turnChannelContext?: TurnChannelContext;
23
+ /** Timestamp (ms) when the message was enqueued. */
24
+ queuedAt: number;
18
25
  }
19
26
 
20
27
  export const MAX_QUEUE_DEPTH = 10;
28
+ /** Messages older than this (ms) are auto-expired from the queue. */
29
+ export const DEFAULT_MAX_WAIT_MS = 60_000;
30
+ const CAPACITY_WARNING_THRESHOLD = 0.8;
21
31
 
22
32
  /**
23
33
  * Describes why a queued message was promoted from the queue.
@@ -35,27 +45,77 @@ export interface QueuePolicy {
35
45
  checkpointHandoffEnabled: boolean;
36
46
  }
37
47
 
48
+ export interface QueueMetrics {
49
+ currentDepth: number;
50
+ totalDropped: number;
51
+ totalExpired: number;
52
+ /** Average wait time (ms) of dequeued messages. 0 when no messages have been dequeued. */
53
+ averageWaitMs: number;
54
+ }
55
+
38
56
  /**
39
57
  * Typed wrapper around the queued-message array.
40
58
  *
41
- * Session owns one instance; the wrapper handles capacity checks and
42
- * iteration so the rest of Session doesn't touch the raw array.
59
+ * Session owns one instance; the wrapper handles capacity checks,
60
+ * expiry, metrics, and iteration so the rest of Session doesn't
61
+ * touch the raw array.
43
62
  */
44
63
  export class MessageQueue {
45
64
  private items: QueuedMessage[] = [];
65
+ private maxWaitMs: number;
66
+ private droppedCount = 0;
67
+ private expiredCount = 0;
68
+ private totalWaitMs = 0;
69
+ private dequeuedCount = 0;
70
+ private capacityWarned = false;
71
+
72
+ constructor(maxWaitMs: number = DEFAULT_MAX_WAIT_MS) {
73
+ this.maxWaitMs = maxWaitMs;
74
+ }
46
75
 
47
76
  push(item: QueuedMessage): boolean {
48
- if (this.items.length >= MAX_QUEUE_DEPTH) return false;
77
+ this.expireStale();
78
+
79
+ if (this.items.length >= MAX_QUEUE_DEPTH) {
80
+ this.droppedCount++;
81
+ item.onEvent({
82
+ type: 'error',
83
+ message: 'Message queue is full. Please wait for current messages to be processed.',
84
+ category: 'queue_full',
85
+ });
86
+ return false;
87
+ }
88
+
89
+ item.queuedAt = Date.now();
49
90
  this.items.push(item);
91
+
92
+ const ratio = this.items.length / MAX_QUEUE_DEPTH;
93
+ if (ratio >= CAPACITY_WARNING_THRESHOLD && !this.capacityWarned) {
94
+ this.capacityWarned = true;
95
+ log.warn({ depth: this.items.length, max: MAX_QUEUE_DEPTH }, 'Queue nearing capacity');
96
+ } else if (ratio < CAPACITY_WARNING_THRESHOLD) {
97
+ this.capacityWarned = false;
98
+ }
99
+
50
100
  return true;
51
101
  }
52
102
 
53
103
  shift(): QueuedMessage | undefined {
54
- return this.items.shift();
104
+ this.expireStale();
105
+ const item = this.items.shift();
106
+ if (item) {
107
+ this.dequeuedCount++;
108
+ this.totalWaitMs += Date.now() - item.queuedAt;
109
+ }
110
+ if (this.items.length / MAX_QUEUE_DEPTH < CAPACITY_WARNING_THRESHOLD) {
111
+ this.capacityWarned = false;
112
+ }
113
+ return item;
55
114
  }
56
115
 
57
116
  clear(): void {
58
117
  this.items = [];
118
+ this.capacityWarned = false;
59
119
  }
60
120
 
61
121
  get length(): number {
@@ -76,6 +136,38 @@ export class MessageQueue {
76
136
  return this.items.splice(idx, 1)[0];
77
137
  }
78
138
 
139
+ getMetrics(): QueueMetrics {
140
+ return {
141
+ currentDepth: this.items.length,
142
+ totalDropped: this.droppedCount,
143
+ totalExpired: this.expiredCount,
144
+ averageWaitMs: this.dequeuedCount > 0 ? this.totalWaitMs / this.dequeuedCount : 0,
145
+ };
146
+ }
147
+
148
+ /** Remove messages that have been waiting longer than maxWaitMs. */
149
+ private expireStale(): void {
150
+ const now = Date.now();
151
+ const cutoff = now - this.maxWaitMs;
152
+ const before = this.items.length;
153
+ this.items = this.items.filter((item) => {
154
+ if (item.queuedAt < cutoff) {
155
+ this.expiredCount++;
156
+ log.warn({ requestId: item.requestId, waitMs: now - item.queuedAt }, 'Expiring stale queued message');
157
+ item.onEvent({
158
+ type: 'error',
159
+ message: 'Your queued message was dropped because it waited too long in the queue.',
160
+ category: 'queue_expired',
161
+ });
162
+ return false;
163
+ }
164
+ return true;
165
+ });
166
+ if (this.items.length < before && this.items.length / MAX_QUEUE_DEPTH < CAPACITY_WARNING_THRESHOLD) {
167
+ this.capacityWarned = false;
168
+ }
169
+ }
170
+
79
171
  [Symbol.iterator](): Iterator<QueuedMessage> {
80
172
  return this.items[Symbol.iterator]();
81
173
  }