@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
@@ -8,17 +8,19 @@
8
8
  */
9
9
 
10
10
  import { v4 as uuid } from 'uuid';
11
- import type { Message, ContentBlock, ImageContent } from '../providers/types.js';
11
+ import type { Message, ContentBlock } from '../providers/types.js';
12
+ import type { ChannelId, TurnChannelContext } from '../channels/types.js';
12
13
  import type { ServerMessage, UsageStats, SurfaceType, SurfaceData, DynamicPageSurfaceData } from './ipc-protocol.js';
13
14
  import type { AgentLoop, CheckpointDecision, AgentEvent } from '../agent/loop.js';
14
15
  import type { Provider } from '../providers/types.js';
15
16
  import { createAssistantMessage } from '../agent/message-types.js';
16
17
  import * as conversationStore from '../memory/conversation-store.js';
18
+ import { getConversationOriginChannel } from '../memory/conversation-store.js';
17
19
  import type { PermissionPrompter } from '../permissions/prompter.js';
18
20
  import { getConfig } from '../config/loader.js';
19
21
  import { getLogger } from '../util/logger.js';
20
22
  import type { TraceEmitter } from './trace-emitter.js';
21
- import { classifySessionError, isUserCancellation, isContextTooLarge, buildSessionErrorMessage } from './session-error.js';
23
+ import { classifySessionError, isUserCancellation, buildSessionErrorMessage } from './session-error.js';
22
24
  import type { ToolProfiler } from '../events/tool-profiling-listener.js';
23
25
  import type { ContextWindowManager } from '../context/window-manager.js';
24
26
  import { getHookManager } from '../hooks/manager.js';
@@ -34,11 +36,9 @@ import {
34
36
  stripInjectedContext,
35
37
  } from './session-runtime-assembly.js';
36
38
  import { buildTemporalContext } from './date-context.js';
37
- import type { ActiveSurfaceContext, ChannelCapabilities } from './session-runtime-assembly.js';
39
+ import type { ActiveSurfaceContext, ChannelCapabilities, ChannelTurnContextParams, GuardianRuntimeContext } from './session-runtime-assembly.js';
38
40
  import {
39
41
  cleanAssistantContent,
40
- drainDirectiveDisplayBuffer,
41
- type DirectiveRequest,
42
42
  type AssistantAttachmentDraft,
43
43
  } from './assistant-attachments.js';
44
44
  import { prepareMemoryContext } from './session-memory.js';
@@ -49,8 +49,6 @@ import {
49
49
  } from './session-attachments.js';
50
50
  import { consolidateAssistantMessages } from './session-history.js';
51
51
  import { recordUsage } from './session-usage.js';
52
- import { recordRequestLog } from '../memory/llm-request-log-store.js';
53
- import { isProviderOrderingError } from './session-slash.js';
54
52
  import { repairHistory, deepRepairHistory } from './history-repair.js';
55
53
  import { stripMediaPayloadsForRetry, raceWithTimeout } from './session-media-retry.js';
56
54
  import { commitTurnChanges } from '../workspace/turn-commit.js';
@@ -58,6 +56,11 @@ import { getWorkspaceGitService } from '../workspace/git-service.js';
58
56
  import { commitAppTurnChanges } from '../memory/app-git-service.js';
59
57
  import type { UsageActor } from '../usage/actors.js';
60
58
  import type { SkillProjectionCache } from './session-skill-tools.js';
59
+ import {
60
+ createEventHandlerState,
61
+ dispatchAgentEvent,
62
+ type EventHandlerDeps,
63
+ } from './session-agent-loop-handlers.js';
61
64
 
62
65
  const log = getLogger('session-agent-loop');
63
66
 
@@ -95,6 +98,8 @@ export interface AgentLoopSessionContext {
95
98
  workspaceTopLevelContext: string | null;
96
99
  workspaceTopLevelDirty: boolean;
97
100
  channelCapabilities?: ChannelCapabilities;
101
+ commandIntent?: { type: string; payload?: string; languageCode?: string };
102
+ guardianContext?: GuardianRuntimeContext;
98
103
 
99
104
  readonly coreToolNames: Set<string>;
100
105
  allowedToolNames?: Set<string>;
@@ -111,6 +116,7 @@ export interface AgentLoopSessionContext {
111
116
  lastAttachmentWarnings: string[];
112
117
 
113
118
  hasNoClient: boolean;
119
+ readonly streamThinking: boolean;
114
120
  readonly prompter: PermissionPrompter;
115
121
  readonly queue: MessageQueue;
116
122
 
@@ -123,6 +129,7 @@ export interface AgentLoopSessionContext {
123
129
  hasQueuedMessages(): boolean;
124
130
  canHandoffAtCheckpoint(): boolean;
125
131
  drainQueue(reason: QueueDrainReason): void;
132
+ getTurnChannelContext(): TurnChannelContext | null;
126
133
  }
127
134
 
128
135
  // ── runAgentLoop ─────────────────────────────────────────────────────
@@ -142,6 +149,18 @@ export async function runAgentLoopImpl(
142
149
  const rlog = log.child({ conversationId: ctx.conversationId, requestId: reqId });
143
150
  let yieldedForHandoff = false;
144
151
 
152
+ // Capture the turn channel context *before* any awaits so a second
153
+ // message from a different channel can't overwrite it mid-flight.
154
+ // When context is unavailable (e.g. regenerate after daemon restart),
155
+ // fall back to the conversation's persisted origin channel.
156
+ const capturedTurnChannelContext: TurnChannelContext = (() => {
157
+ const live = ctx.getTurnChannelContext();
158
+ if (live) return live;
159
+ const origin = getConversationOriginChannel(ctx.conversationId);
160
+ if (origin) return { userMessageChannel: origin, assistantMessageChannel: origin };
161
+ return { userMessageChannel: 'macos' as ChannelId, assistantMessageChannel: 'macos' as ChannelId };
162
+ })();
163
+
145
164
  ctx.lastAssistantAttachments = [];
146
165
  ctx.lastAttachmentWarnings = [];
147
166
 
@@ -203,19 +222,8 @@ export async function runAgentLoopImpl(
203
222
  emitUsage(ctx, compacted.summaryInputTokens, compacted.summaryOutputTokens, compacted.summaryModel, onEvent, 'context_compactor', reqId);
204
223
  }
205
224
 
206
- let firstAssistantText = '';
207
- let exchangeInputTokens = 0;
208
- let exchangeOutputTokens = 0;
209
- let model = '';
225
+ const state = createEventHandlerState();
210
226
  let runMessages = ctx.messages;
211
- const pendingToolResults = new Map<string, { content: string; isError: boolean; contentBlocks?: ContentBlock[] }>();
212
- const persistedToolUseIds = new Set<string>();
213
- const accumulatedDirectives: DirectiveRequest[] = [];
214
- const accumulatedToolContentBlocks: ContentBlock[] = [];
215
- const directiveWarnings: string[] = [];
216
- let pendingDirectiveDisplayBuffer = '';
217
- let lastAssistantMessageId: string | undefined;
218
- let providerErrorUserMessage: string | null = null;
219
227
 
220
228
  const memoryResult = await prepareMemoryContext(
221
229
  {
@@ -234,11 +242,16 @@ export async function runAgentLoopImpl(
234
242
  );
235
243
 
236
244
  if (memoryResult.conflictClarification) {
245
+ const loopChannelMeta = {
246
+ userMessageChannel: capturedTurnChannelContext.userMessageChannel,
247
+ assistantMessageChannel: capturedTurnChannelContext.assistantMessageChannel,
248
+ };
237
249
  const assistantMessage = createAssistantMessage(memoryResult.conflictClarification);
238
250
  conversationStore.addMessage(
239
251
  ctx.conversationId,
240
252
  'assistant',
241
253
  JSON.stringify(assistantMessage.content),
254
+ loopChannelMeta,
242
255
  );
243
256
  ctx.messages.push(assistantMessage);
244
257
  onEvent({
@@ -291,11 +304,22 @@ export async function runAgentLoopImpl(
291
304
  timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone,
292
305
  });
293
306
 
307
+ // Use the channel context captured at the top of this function so it
308
+ // reflects the channel that originally sent *this* turn's message,
309
+ // even if a newer message from a different channel arrived since.
310
+ const channelTurnContext: ChannelTurnContextParams = {
311
+ turnContext: capturedTurnChannelContext,
312
+ conversationOriginChannel: getConversationOriginChannel(ctx.conversationId),
313
+ };
314
+
294
315
  runMessages = applyRuntimeInjections(runMessages, {
295
316
  softConflictInstruction,
296
317
  activeSurface,
297
318
  workspaceTopLevelContext: ctx.workspaceTopLevelContext,
298
319
  channelCapabilities: ctx.channelCapabilities ?? null,
320
+ channelCommandContext: ctx.commandIntent ?? null,
321
+ channelTurnContext,
322
+ guardianContext: ctx.guardianContext ?? null,
299
323
  temporalContext,
300
324
  });
301
325
 
@@ -307,235 +331,21 @@ export async function runAgentLoopImpl(
307
331
  runMessages = preRunRepair.messages;
308
332
  }
309
333
 
310
- let orderingErrorDetected = false;
311
- let deferredOrderingError: string | null = null;
312
- let contextTooLargeDetected = false;
313
334
  let preRunHistoryLength = runMessages.length;
314
335
 
315
- let llmCallStartedEmitted = false;
316
- const toolUseIdToName = new Map<string, string>();
317
- let currentTurnToolNames: string[] = [];
318
-
319
- const buildEventHandler = () => (event: AgentEvent) => {
320
- const emitLlmCallStartedIfNeeded = () => {
321
- if (llmCallStartedEmitted) return;
322
- llmCallStartedEmitted = true;
323
- ctx.traceEmitter.emit('llm_call_started', `LLM call to ${ctx.provider.name}`, {
324
- requestId: reqId,
325
- status: 'info',
326
- attributes: { provider: ctx.provider.name, model: model || 'unknown' },
327
- });
328
- };
329
-
330
- switch (event.type) {
331
- case 'text_delta': {
332
- emitLlmCallStartedIfNeeded();
333
- pendingDirectiveDisplayBuffer += event.text;
334
- const drained = drainDirectiveDisplayBuffer(pendingDirectiveDisplayBuffer);
335
- pendingDirectiveDisplayBuffer = drained.bufferedRemainder;
336
- if (drained.emitText.length > 0) {
337
- onEvent({ type: 'assistant_text_delta', text: drained.emitText, sessionId: ctx.conversationId });
338
- if (isFirstMessage) firstAssistantText += drained.emitText;
339
- }
340
- break;
341
- }
342
- case 'thinking_delta':
343
- emitLlmCallStartedIfNeeded();
344
- onEvent({ type: 'assistant_thinking_delta', thinking: event.thinking });
345
- break;
346
- case 'tool_use':
347
- toolUseIdToName.set(event.id, event.name);
348
- currentTurnToolNames.push(event.name);
349
- onEvent({ type: 'tool_use_start', toolName: event.name, input: event.input, sessionId: ctx.conversationId });
350
- break;
351
- case 'tool_output_chunk': {
352
- // Try to parse structured progress fields from the chunk.
353
- // Cheap pre-check: only attempt JSON.parse when the chunk looks like an object.
354
- let structured: { subType?: 'tool_start' | 'tool_complete' | 'status'; subToolName?: string; subToolInput?: string; subToolIsError?: boolean; subToolId?: string } | undefined;
355
- const trimmed = event.chunk.trimStart();
356
- if (trimmed.length > 0 && trimmed.length < 4096 && trimmed[0] === '{') {
357
- try {
358
- const parsed = JSON.parse(event.chunk);
359
- const VALID_SUB_TYPES = new Set(['tool_start', 'tool_complete', 'status']);
360
- if (parsed && typeof parsed === 'object' && typeof parsed.subType === 'string' && VALID_SUB_TYPES.has(parsed.subType)) {
361
- structured = {
362
- subType: parsed.subType as 'tool_start' | 'tool_complete' | 'status',
363
- subToolName: typeof parsed.subToolName === 'string' ? parsed.subToolName : undefined,
364
- subToolInput: typeof parsed.subToolInput === 'string' ? parsed.subToolInput : undefined,
365
- subToolIsError: typeof parsed.subToolIsError === 'boolean' ? parsed.subToolIsError : undefined,
366
- subToolId: typeof parsed.subToolId === 'string' ? parsed.subToolId : undefined,
367
- };
368
- }
369
- } catch {
370
- // Not valid JSON — pass through as plain chunk
371
- }
372
- }
373
- if (structured) {
374
- onEvent({
375
- type: 'tool_output_chunk',
376
- chunk: event.chunk,
377
- sessionId: ctx.conversationId,
378
- subType: structured.subType,
379
- subToolName: structured.subToolName,
380
- subToolInput: structured.subToolInput,
381
- subToolIsError: structured.subToolIsError,
382
- subToolId: structured.subToolId,
383
- });
384
- } else {
385
- onEvent({ type: 'tool_output_chunk', chunk: event.chunk, sessionId: ctx.conversationId });
386
- }
387
- break;
388
- }
389
- case 'input_json_delta':
390
- onEvent({ type: 'tool_input_delta', toolName: event.toolName, content: event.accumulatedJson, sessionId: ctx.conversationId });
391
- break;
392
- case 'tool_result': {
393
- const imageBlock = event.contentBlocks?.find((b): b is ImageContent => b.type === 'image');
394
- onEvent({ type: 'tool_result', toolName: '', result: event.content, isError: event.isError, diff: event.diff, status: event.status, sessionId: ctx.conversationId, imageData: imageBlock?.source.data });
395
- pendingToolResults.set(event.toolUseId, { content: event.content, isError: event.isError, contentBlocks: event.contentBlocks });
396
- {
397
- const toolName = toolUseIdToName.get(event.toolUseId);
398
- if (toolName === 'file_write' || toolName === 'bash') {
399
- ctx.markWorkspaceTopLevelDirty();
400
- } else if (toolName === 'file_edit' && !event.isError) {
401
- ctx.markWorkspaceTopLevelDirty();
402
- }
403
- }
404
- if (event.contentBlocks) {
405
- for (const cb of event.contentBlocks) {
406
- if (cb.type === 'image' || cb.type === 'file') {
407
- accumulatedToolContentBlocks.push(cb);
408
- }
409
- }
410
- }
411
- break;
412
- }
413
- case 'error':
414
- if (isProviderOrderingError(event.error.message)) {
415
- orderingErrorDetected = true;
416
- deferredOrderingError = event.error.message;
417
- } else if (isContextTooLarge(event.error.message)) {
418
- contextTooLargeDetected = true;
419
- } else {
420
- const classified = classifySessionError(event.error, { phase: 'agent_loop' });
421
- onEvent(buildSessionErrorMessage(ctx.conversationId, classified));
422
- providerErrorUserMessage = classified.userMessage;
423
- }
424
- break;
425
- case 'message_complete': {
426
- if (pendingDirectiveDisplayBuffer.length > 0) {
427
- onEvent({
428
- type: 'assistant_text_delta',
429
- text: pendingDirectiveDisplayBuffer,
430
- sessionId: ctx.conversationId,
431
- });
432
- if (isFirstMessage) firstAssistantText += pendingDirectiveDisplayBuffer;
433
- pendingDirectiveDisplayBuffer = '';
434
- }
435
- if (pendingToolResults.size > 0) {
436
- const toolResultBlocks = Array.from(pendingToolResults.entries()).map(
437
- ([toolUseId, result]) => ({
438
- type: 'tool_result',
439
- tool_use_id: toolUseId,
440
- content: result.content,
441
- is_error: result.isError,
442
- ...(result.contentBlocks ? { contentBlocks: result.contentBlocks } : {}),
443
- }),
444
- );
445
- conversationStore.addMessage(
446
- ctx.conversationId,
447
- 'user',
448
- JSON.stringify(toolResultBlocks),
449
- );
450
- for (const id of pendingToolResults.keys()) {
451
- persistedToolUseIds.add(id);
452
- }
453
- pendingToolResults.clear();
454
- }
455
- const { cleanedContent, directives: msgDirectives, warnings: msgWarnings } =
456
- cleanAssistantContent(event.message.content);
457
- accumulatedDirectives.push(...msgDirectives);
458
- directiveWarnings.push(...msgWarnings);
459
- if (msgDirectives.length > 0) {
460
- rlog.info(
461
- { parsedDirectives: msgDirectives.map(d => ({ source: d.source, path: d.path, mimeType: d.mimeType })), totalAccumulated: accumulatedDirectives.length },
462
- 'Parsed attachment directives from assistant message',
463
- );
464
- }
465
-
466
- const contentWithSurfaces: ContentBlock[] = [...cleanedContent as ContentBlock[]];
467
- for (const surface of ctx.currentTurnSurfaces) {
468
- contentWithSurfaces.push({
469
- type: 'ui_surface',
470
- surfaceId: surface.surfaceId,
471
- surfaceType: surface.surfaceType,
472
- title: surface.title,
473
- data: surface.data,
474
- actions: surface.actions,
475
- display: surface.display,
476
- } as unknown as ContentBlock);
477
- }
478
-
479
- const assistantMsg = conversationStore.addMessage(
480
- ctx.conversationId,
481
- 'assistant',
482
- JSON.stringify(contentWithSurfaces),
483
- );
484
- lastAssistantMessageId = assistantMsg.id;
485
-
486
- ctx.currentTurnSurfaces = [];
487
-
488
- const charCount = cleanedContent
489
- .filter((b) => (b as Record<string, unknown>).type === 'text')
490
- .reduce((sum: number, b) => sum + ((b as { text?: string }).text?.length ?? 0), 0);
491
- const toolUseCount = event.message.content
492
- .filter((b) => b.type === 'tool_use')
493
- .length;
494
- ctx.traceEmitter.emit('assistant_message', 'Assistant message complete', {
495
- requestId: reqId,
496
- status: 'success',
497
- attributes: { charCount, toolUseCount },
498
- });
499
- break;
500
- }
501
- case 'usage':
502
- exchangeInputTokens += event.inputTokens;
503
- exchangeOutputTokens += event.outputTokens;
504
- model = event.model;
505
-
506
- if (event.rawRequest && event.rawResponse) {
507
- try {
508
- recordRequestLog(
509
- ctx.conversationId,
510
- JSON.stringify(event.rawRequest),
511
- JSON.stringify(event.rawResponse),
512
- );
513
- } catch (err) {
514
- rlog.warn({ err }, 'Failed to persist LLM request log (non-fatal)');
515
- }
516
- }
517
-
518
- emitLlmCallStartedIfNeeded();
519
-
520
- ctx.traceEmitter.emit('llm_call_finished', `LLM call to ${ctx.provider.name} finished`, {
521
- requestId: reqId,
522
- status: 'success',
523
- attributes: {
524
- provider: ctx.provider.name,
525
- model: event.model,
526
- inputTokens: event.inputTokens,
527
- outputTokens: event.outputTokens,
528
- latencyMs: event.providerDurationMs,
529
- },
530
- });
531
- llmCallStartedEmitted = false;
532
- break;
533
- }
336
+ const deps: EventHandlerDeps = {
337
+ ctx,
338
+ onEvent,
339
+ reqId,
340
+ isFirstMessage,
341
+ rlog,
342
+ turnChannelContext: capturedTurnChannelContext,
534
343
  };
344
+ const eventHandler = (event: AgentEvent) => dispatchAgentEvent(state, deps, event);
535
345
 
536
346
  const onCheckpoint = (): CheckpointDecision => {
537
- const turnTools = currentTurnToolNames;
538
- currentTurnToolNames = [];
347
+ const turnTools = state.currentTurnToolNames;
348
+ state.currentTurnToolNames = [];
539
349
 
540
350
  if (ctx.canHandoffAtCheckpoint()) {
541
351
  const inBrowserFlow = turnTools.length > 0
@@ -552,37 +362,37 @@ export async function runAgentLoopImpl(
552
362
 
553
363
  let updatedHistory = await ctx.agentLoop.run(
554
364
  runMessages,
555
- buildEventHandler(),
365
+ eventHandler,
556
366
  abortController.signal,
557
367
  reqId,
558
368
  onCheckpoint,
559
369
  );
560
370
 
561
371
  // One-shot ordering error retry
562
- if (orderingErrorDetected && updatedHistory.length === preRunHistoryLength) {
372
+ if (state.orderingErrorDetected && updatedHistory.length === preRunHistoryLength) {
563
373
  rlog.warn({ phase: 'retry' }, 'Provider ordering error detected, attempting one-shot deep-repair retry');
564
374
  const retryRepair = deepRepairHistory(runMessages);
565
375
  runMessages = retryRepair.messages;
566
376
  preRepairMessages = retryRepair.messages;
567
377
  preRunHistoryLength = runMessages.length;
568
- orderingErrorDetected = false;
569
- deferredOrderingError = null;
378
+ state.orderingErrorDetected = false;
379
+ state.deferredOrderingError = null;
570
380
 
571
381
  updatedHistory = await ctx.agentLoop.run(
572
382
  runMessages,
573
- buildEventHandler(),
383
+ eventHandler,
574
384
  abortController.signal,
575
385
  reqId,
576
386
  onCheckpoint,
577
387
  );
578
388
 
579
- if (orderingErrorDetected) {
389
+ if (state.orderingErrorDetected) {
580
390
  rlog.error({ phase: 'retry' }, 'Deep-repair retry also failed with ordering error. Consider starting a new conversation if this persists.');
581
391
  }
582
392
  }
583
393
 
584
394
  // One-shot context-too-large recovery
585
- if (contextTooLargeDetected && updatedHistory.length === preRunHistoryLength) {
395
+ if (state.contextTooLargeDetected && updatedHistory.length === preRunHistoryLength) {
586
396
  rlog.warn({ phase: 'retry' }, 'Context too large — attempting forced compaction and retry');
587
397
  const emergencyCompact = await ctx.contextWindowManager.maybeCompact(
588
398
  ctx.messages,
@@ -617,22 +427,25 @@ export async function runAgentLoopImpl(
617
427
  activeSurface,
618
428
  workspaceTopLevelContext: ctx.workspaceTopLevelContext,
619
429
  channelCapabilities: ctx.channelCapabilities ?? null,
430
+ channelCommandContext: ctx.commandIntent ?? null,
431
+ channelTurnContext,
432
+ guardianContext: ctx.guardianContext ?? null,
620
433
  temporalContext,
621
434
  });
622
435
  preRepairMessages = runMessages;
623
436
  preRunHistoryLength = runMessages.length;
624
- contextTooLargeDetected = false;
437
+ state.contextTooLargeDetected = false;
625
438
 
626
439
  updatedHistory = await ctx.agentLoop.run(
627
440
  runMessages,
628
- buildEventHandler(),
441
+ eventHandler,
629
442
  abortController.signal,
630
443
  reqId,
631
444
  onCheckpoint,
632
445
  );
633
446
  }
634
447
 
635
- if (contextTooLargeDetected) {
448
+ if (state.contextTooLargeDetected) {
636
449
  const mediaTrimmed = stripMediaPayloadsForRetry(ctx.messages);
637
450
  if (mediaTrimmed.modified) {
638
451
  rlog.warn(
@@ -649,15 +462,18 @@ export async function runAgentLoopImpl(
649
462
  activeSurface,
650
463
  workspaceTopLevelContext: ctx.workspaceTopLevelContext,
651
464
  channelCapabilities: ctx.channelCapabilities ?? null,
465
+ channelCommandContext: ctx.commandIntent ?? null,
466
+ channelTurnContext,
467
+ guardianContext: ctx.guardianContext ?? null,
652
468
  temporalContext,
653
469
  });
654
470
  preRepairMessages = runMessages;
655
471
  preRunHistoryLength = runMessages.length;
656
- contextTooLargeDetected = false;
472
+ state.contextTooLargeDetected = false;
657
473
 
658
474
  updatedHistory = await ctx.agentLoop.run(
659
475
  runMessages,
660
- buildEventHandler(),
476
+ eventHandler,
661
477
  abortController.signal,
662
478
  reqId,
663
479
  onCheckpoint,
@@ -665,7 +481,7 @@ export async function runAgentLoopImpl(
665
481
  }
666
482
  }
667
483
 
668
- if (contextTooLargeDetected) {
484
+ if (state.contextTooLargeDetected) {
669
485
  const classified = classifySessionError(
670
486
  new Error('context_length_exceeded'),
671
487
  { phase: 'agent_loop' },
@@ -674,8 +490,8 @@ export async function runAgentLoopImpl(
674
490
  }
675
491
  }
676
492
 
677
- if (deferredOrderingError) {
678
- const classified = classifySessionError(new Error(deferredOrderingError), { phase: 'agent_loop' });
493
+ if (state.deferredOrderingError) {
494
+ const classified = classifySessionError(new Error(state.deferredOrderingError), { phase: 'agent_loop' });
679
495
  onEvent(buildSessionErrorMessage(ctx.conversationId, classified));
680
496
  }
681
497
 
@@ -684,8 +500,8 @@ export async function runAgentLoopImpl(
684
500
  const msg = updatedHistory[i];
685
501
  if (msg.role === 'user') {
686
502
  for (const block of msg.content) {
687
- if (block.type === 'tool_result' && !pendingToolResults.has(block.tool_use_id) && !persistedToolUseIds.has(block.tool_use_id)) {
688
- pendingToolResults.set(block.tool_use_id, {
503
+ if (block.type === 'tool_result' && !state.pendingToolResults.has(block.tool_use_id) && !state.persistedToolUseIds.has(block.tool_use_id)) {
504
+ state.pendingToolResults.set(block.tool_use_id, {
689
505
  content: block.content,
690
506
  isError: block.is_error ?? false,
691
507
  });
@@ -695,8 +511,8 @@ export async function runAgentLoopImpl(
695
511
  }
696
512
 
697
513
  // Flush remaining tool results
698
- if (pendingToolResults.size > 0) {
699
- const toolResultBlocks = Array.from(pendingToolResults.entries()).map(
514
+ if (state.pendingToolResults.size > 0) {
515
+ const toolResultBlocks = Array.from(state.pendingToolResults.entries()).map(
700
516
  ([toolUseId, result]) => ({
701
517
  type: 'tool_result',
702
518
  tool_use_id: toolUseId,
@@ -705,12 +521,17 @@ export async function runAgentLoopImpl(
705
521
  ...(result.contentBlocks ? { contentBlocks: result.contentBlocks } : {}),
706
522
  }),
707
523
  );
524
+ const toolResultMetadata = {
525
+ userMessageChannel: capturedTurnChannelContext.userMessageChannel,
526
+ assistantMessageChannel: capturedTurnChannelContext.assistantMessageChannel,
527
+ };
708
528
  conversationStore.addMessage(
709
529
  ctx.conversationId,
710
530
  'user',
711
531
  JSON.stringify(toolResultBlocks),
532
+ toolResultMetadata,
712
533
  );
713
- pendingToolResults.clear();
534
+ state.pendingToolResults.clear();
714
535
  }
715
536
 
716
537
  // Reconstruct history
@@ -721,17 +542,22 @@ export async function runAgentLoopImpl(
721
542
  });
722
543
 
723
544
  const hasAssistantResponse = newMessages.some((msg) => msg.role === 'assistant');
724
- if (!hasAssistantResponse && providerErrorUserMessage && !abortController.signal.aborted && !yieldedForHandoff) {
725
- const errorAssistantMessage = createAssistantMessage(providerErrorUserMessage);
545
+ if (!hasAssistantResponse && state.providerErrorUserMessage && !abortController.signal.aborted && !yieldedForHandoff) {
546
+ const errChannelMeta = {
547
+ userMessageChannel: capturedTurnChannelContext.userMessageChannel,
548
+ assistantMessageChannel: capturedTurnChannelContext.assistantMessageChannel,
549
+ };
550
+ const errorAssistantMessage = createAssistantMessage(state.providerErrorUserMessage);
726
551
  conversationStore.addMessage(
727
552
  ctx.conversationId,
728
553
  'assistant',
729
554
  JSON.stringify(errorAssistantMessage.content),
555
+ errChannelMeta,
730
556
  );
731
557
  newMessages.push(errorAssistantMessage);
732
558
  onEvent({
733
559
  type: 'assistant_text_delta',
734
- text: providerErrorUserMessage,
560
+ text: state.providerErrorUserMessage,
735
561
  sessionId: ctx.conversationId,
736
562
  });
737
563
  }
@@ -742,7 +568,7 @@ export async function runAgentLoopImpl(
742
568
  stripDynamicProfile: (msgs) => stripDynamicProfileMessages(msgs, dynamicProfile.text),
743
569
  });
744
570
 
745
- emitUsage(ctx, exchangeInputTokens, exchangeOutputTokens, model, onEvent, 'main_agent', reqId);
571
+ emitUsage(ctx, state.exchangeInputTokens, state.exchangeOutputTokens, state.model, onEvent, 'main_agent', reqId);
746
572
 
747
573
  void getHookManager().trigger('post-message', {
748
574
  sessionId: ctx.conversationId,
@@ -750,12 +576,12 @@ export async function runAgentLoopImpl(
750
576
 
751
577
  // Resolve attachments
752
578
  const attachmentResult = await resolveAssistantAttachments(
753
- accumulatedDirectives,
754
- accumulatedToolContentBlocks,
755
- directiveWarnings,
579
+ state.accumulatedDirectives,
580
+ state.accumulatedToolContentBlocks,
581
+ state.directiveWarnings,
756
582
  ctx.workingDir,
757
583
  async (filePath) => approveHostAttachmentRead(filePath, ctx.workingDir, ctx.prompter, ctx.conversationId, ctx.hasNoClient),
758
- lastAssistantMessageId,
584
+ state.lastAssistantMessageId,
759
585
  );
760
586
  const { assistantAttachments, emittedAttachments } = attachmentResult;
761
587
 
@@ -800,9 +626,10 @@ export async function runAgentLoopImpl(
800
626
  }
801
627
 
802
628
  if (isFirstMessage) {
803
- generateTitle(ctx, content, firstAssistantText).catch((err) => {
804
- log.warn({ err, conversationId: ctx.conversationId }, 'Failed to generate conversation title (non-fatal, using default title)');
805
- });
629
+ generateTitle(ctx, content, state.firstAssistantText, onEvent, abortController.signal)
630
+ .catch((err) => {
631
+ log.warn({ err, conversationId: ctx.conversationId }, 'Failed to generate conversation title (non-fatal, using default title)');
632
+ });
806
633
  }
807
634
  } catch (err) {
808
635
  const errorCtx = { phase: 'agent_loop' as const, aborted: abortController.signal.aborted };
@@ -864,6 +691,9 @@ export async function runAgentLoopImpl(
864
691
  ctx.currentActiveSurfaceId = undefined;
865
692
  ctx.allowedToolNames = undefined;
866
693
  ctx.preactivatedSkillIds = undefined;
694
+ // Channel command intents (e.g. Telegram /start) are single-turn metadata.
695
+ // Clear at turn end so they never leak into subsequent unrelated messages.
696
+ ctx.commandIntent = undefined;
867
697
 
868
698
  if (userMessageId) {
869
699
  consolidateAssistantMessages(ctx.conversationId, userMessageId);
@@ -879,13 +709,19 @@ async function generateTitle(
879
709
  ctx: Pick<AgentLoopSessionContext, 'conversationId' | 'provider'>,
880
710
  userMessage: string,
881
711
  assistantResponse: string,
712
+ onEvent: (msg: ServerMessage) => void,
713
+ sessionSignal?: AbortSignal,
882
714
  ): Promise<void> {
715
+ const config = getConfig();
883
716
  const prompt = `Generate a very short title for this conversation. Rules: at most 5 words, at most 40 characters, no quotes.\n\nUser: ${truncate(userMessage, 200, '')}\nAssistant: ${truncate(assistantResponse, 200, '')}`;
717
+ const signal = sessionSignal
718
+ ? AbortSignal.any([sessionSignal, AbortSignal.timeout(10_000)])
719
+ : AbortSignal.timeout(10_000);
884
720
  const response = await ctx.provider.sendMessage(
885
721
  [{ role: 'user', content: [{ type: 'text', text: prompt }] }],
886
722
  [],
887
723
  undefined,
888
- { config: { max_tokens: 30 } },
724
+ { config: { max_tokens: config.daemon.titleGenerationMaxTokens }, signal },
889
725
  );
890
726
 
891
727
  const textBlock = response.content.find((b) => b.type === 'text');
@@ -894,7 +730,13 @@ async function generateTitle(
894
730
  const words = title.split(/\s+/);
895
731
  if (words.length > 5) title = words.slice(0, 5).join(' ');
896
732
  if (title.length > 40) title = title.slice(0, 40).trimEnd();
733
+ if (!title) return;
897
734
  conversationStore.updateConversationTitle(ctx.conversationId, title);
735
+ onEvent({
736
+ type: 'session_title_updated',
737
+ sessionId: ctx.conversationId,
738
+ title,
739
+ });
898
740
  log.info({ conversationId: ctx.conversationId, title }, 'Auto-generated conversation title');
899
741
  }
900
742
  }
@@ -48,7 +48,7 @@ export function stripDynamicProfileMessages(messages: Message[], profileText: st
48
48
  changed = true;
49
49
  const stripped = nextText.replace(/\n{3,}/g, '\n\n').trimEnd();
50
50
  return stripped.length > 0 ? { ...block, text: stripped } : null;
51
- }).filter((block): block is NonNullable<typeof block> => block !== null);
51
+ }).filter((block): block is NonNullable<typeof block> => block != null);
52
52
  if (!changed) return messages;
53
53
  // If stripping removed all content blocks, drop the message entirely
54
54
  // to avoid sending an empty content array to the provider.