@vellumai/assistant 0.3.5 → 0.3.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (487) hide show
  1. package/README.md +51 -0
  2. package/eslint.config.mjs +31 -0
  3. package/package.json +1 -1
  4. package/scripts/ipc/check-swift-decoder-drift.ts +4 -1
  5. package/scripts/ipc/generate-swift.ts +18 -2
  6. package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +338 -1
  7. package/src/__tests__/approval-conversation-turn.test.ts +214 -0
  8. package/src/__tests__/browser-manager.test.ts +1 -0
  9. package/src/__tests__/call-conversation-messages.test.ts +130 -0
  10. package/src/__tests__/call-orchestrator.test.ts +752 -271
  11. package/src/__tests__/call-pointer-messages.test.ts +148 -0
  12. package/src/__tests__/call-recovery.test.ts +3 -0
  13. package/src/__tests__/call-routes-http.test.ts +5 -0
  14. package/src/__tests__/call-store.test.ts +3 -0
  15. package/src/__tests__/channel-approval-routes.test.ts +1260 -85
  16. package/src/__tests__/channel-approval.test.ts +37 -0
  17. package/src/__tests__/channel-approvals.test.ts +4 -65
  18. package/src/__tests__/channel-guardian.test.ts +556 -0
  19. package/src/__tests__/channel-readiness-service.test.ts +74 -7
  20. package/src/__tests__/checker.test.ts +14 -7
  21. package/src/__tests__/clarification-resolver.test.ts +44 -24
  22. package/src/__tests__/commit-message-enrichment-service.test.ts +9 -4
  23. package/src/__tests__/computer-use-session-working-dir.test.ts +8 -0
  24. package/src/__tests__/config-schema.test.ts +12 -7
  25. package/src/__tests__/context-window-manager.test.ts +30 -2
  26. package/src/__tests__/contradiction-checker.test.ts +20 -5
  27. package/src/__tests__/credential-security-invariants.test.ts +6 -2
  28. package/src/__tests__/db-migration-rollback.test.ts +752 -0
  29. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +2 -0
  30. package/src/__tests__/fuzzy-match-property.test.ts +5 -5
  31. package/src/__tests__/guardian-action-store.test.ts +123 -0
  32. package/src/__tests__/guardian-action-sweep.test.ts +277 -0
  33. package/src/__tests__/guardian-dispatch.test.ts +389 -0
  34. package/src/__tests__/guardian-question-copy.test.ts +47 -0
  35. package/src/__tests__/handlers-telegram-config.test.ts +4 -2
  36. package/src/__tests__/handlers-twilio-config.test.ts +126 -0
  37. package/src/__tests__/intent-routing.test.ts +2 -0
  38. package/src/__tests__/ipc-snapshot.test.ts +228 -1
  39. package/src/__tests__/memory-upsert-concurrency.test.ts +828 -0
  40. package/src/__tests__/model-intents.test.ts +96 -0
  41. package/src/__tests__/no-direct-anthropic-sdk-imports.test.ts +42 -0
  42. package/src/__tests__/oauth2-gateway-transport.test.ts +130 -0
  43. package/src/__tests__/onboarding-starter-tasks.test.ts +2 -0
  44. package/src/__tests__/provider-commit-message-generator.test.ts +89 -13
  45. package/src/__tests__/provider-error-scenarios.test.ts +621 -0
  46. package/src/__tests__/provider-fail-open-selection.test.ts +119 -0
  47. package/src/__tests__/qdrant-manager.test.ts +27 -20
  48. package/src/__tests__/relay-server.test.ts +779 -40
  49. package/src/__tests__/run-orchestrator-assistant-events.test.ts +2 -0
  50. package/src/__tests__/run-orchestrator.test.ts +20 -4
  51. package/src/__tests__/runtime-runs-http.test.ts +17 -1
  52. package/src/__tests__/runtime-runs.test.ts +16 -0
  53. package/src/__tests__/schedule-store.test.ts +18 -4
  54. package/src/__tests__/scheduler-recurrence.test.ts +13 -4
  55. package/src/__tests__/session-abort-tool-results.test.ts +6 -0
  56. package/src/__tests__/session-agent-loop.test.ts +857 -0
  57. package/src/__tests__/session-conflict-gate.test.ts +6 -0
  58. package/src/__tests__/session-pre-run-repair.test.ts +6 -0
  59. package/src/__tests__/session-profile-injection.test.ts +6 -0
  60. package/src/__tests__/session-provider-retry-repair.test.ts +6 -0
  61. package/src/__tests__/session-queue.test.ts +6 -0
  62. package/src/__tests__/session-runtime-assembly.test.ts +237 -13
  63. package/src/__tests__/session-slash-known.test.ts +6 -0
  64. package/src/__tests__/session-slash-queue.test.ts +6 -0
  65. package/src/__tests__/session-slash-unknown.test.ts +6 -0
  66. package/src/__tests__/session-surfaces-task-progress.test.ts +2 -0
  67. package/src/__tests__/session-tool-setup-app-refresh.test.ts +1 -0
  68. package/src/__tests__/session-tool-setup-memory-scope.test.ts +1 -0
  69. package/src/__tests__/session-tool-setup-side-effect-flag.test.ts +1 -0
  70. package/src/__tests__/session-workspace-injection.test.ts +6 -0
  71. package/src/__tests__/session-workspace-tool-tracking.test.ts +6 -0
  72. package/src/__tests__/skills.test.ts +2 -0
  73. package/src/__tests__/sms-messaging-provider.test.ts +2 -1
  74. package/src/__tests__/starter-task-flow.test.ts +2 -0
  75. package/src/__tests__/swarm-dag-pathological.test.ts +535 -0
  76. package/src/__tests__/system-prompt.test.ts +2 -0
  77. package/src/__tests__/task-management-tools.test.ts +2 -2
  78. package/src/__tests__/task-runner.test.ts +14 -4
  79. package/src/__tests__/terminal-tools.test.ts +25 -19
  80. package/src/__tests__/tool-execution-abort-cleanup.test.ts +545 -0
  81. package/src/__tests__/tool-executor-shell-integration.test.ts +11 -11
  82. package/src/__tests__/tool-executor.test.ts +23 -24
  83. package/src/__tests__/trust-store.test.ts +3 -3
  84. package/src/__tests__/twilio-rest.test.ts +29 -0
  85. package/src/__tests__/twilio-routes-elevenlabs.test.ts +3 -0
  86. package/src/__tests__/twilio-routes-twiml.test.ts +11 -0
  87. package/src/__tests__/twilio-routes.test.ts +141 -21
  88. package/src/__tests__/user-reference.test.ts +2 -0
  89. package/src/__tests__/voice-quality.test.ts +222 -0
  90. package/src/__tests__/web-search.test.ts +45 -29
  91. package/src/agent/loop.ts +1 -1
  92. package/src/agent-heartbeat/agent-heartbeat-service.ts +2 -10
  93. package/src/amazon/client.ts +1418 -0
  94. package/src/amazon/request-extractor.ts +135 -0
  95. package/src/amazon/session.ts +109 -0
  96. package/src/autonomy/autonomy-store.ts +5 -5
  97. package/src/browser-extension-relay/client.ts +124 -0
  98. package/src/browser-extension-relay/protocol.ts +63 -0
  99. package/src/browser-extension-relay/server.ts +177 -0
  100. package/src/bundler/app-bundler.ts +3 -3
  101. package/src/bundler/bundle-signer.ts +1 -1
  102. package/src/bundler/signature-verifier.ts +1 -1
  103. package/src/calls/call-conversation-messages.ts +33 -0
  104. package/src/calls/call-domain.ts +106 -5
  105. package/src/calls/call-orchestrator.ts +252 -54
  106. package/src/calls/call-pointer-messages.ts +53 -0
  107. package/src/calls/call-recovery.ts +3 -8
  108. package/src/calls/call-store.ts +69 -87
  109. package/src/calls/elevenlabs-config.ts +3 -2
  110. package/src/calls/guardian-action-sweep.ts +105 -0
  111. package/src/calls/guardian-dispatch.ts +203 -0
  112. package/src/calls/guardian-question-copy.ts +133 -0
  113. package/src/calls/relay-server.ts +466 -8
  114. package/src/calls/speaker-identification.ts +1 -1
  115. package/src/calls/twilio-config.ts +7 -5
  116. package/src/calls/twilio-provider.ts +6 -4
  117. package/src/calls/twilio-rest.ts +40 -15
  118. package/src/calls/twilio-routes.ts +60 -45
  119. package/src/calls/types.ts +3 -1
  120. package/src/channels/types.ts +25 -0
  121. package/src/cli/amazon.ts +815 -0
  122. package/src/cli/config-commands.ts +2 -2
  123. package/src/cli/core-commands.ts +4 -3
  124. package/src/cli/influencer.ts +244 -0
  125. package/src/cli/map.ts +89 -6
  126. package/src/cli.ts +1 -1
  127. package/src/config/agent-schema.ts +171 -0
  128. package/src/config/bundled-skills/amazon/SKILL.md +127 -0
  129. package/src/config/bundled-skills/amazon/icon.svg +13 -0
  130. package/src/config/bundled-skills/api-mapping/SKILL.md +78 -0
  131. package/src/config/bundled-skills/browser/SKILL.md +1 -0
  132. package/src/config/bundled-skills/browser/TOOLS.json +17 -0
  133. package/src/config/bundled-skills/browser/tools/browser-wait-for-download.ts +25 -0
  134. package/src/config/bundled-skills/doordash/SKILL.md +51 -51
  135. package/src/config/bundled-skills/email-setup/SKILL.md +14 -5
  136. package/src/config/bundled-skills/google-oauth-setup/SKILL.md +183 -0
  137. package/src/config/bundled-skills/influencer/SKILL.md +144 -0
  138. package/src/config/bundled-skills/macos-automation/icon.svg +12 -0
  139. package/src/config/bundled-skills/media-processing/SKILL.md +72 -95
  140. package/src/config/bundled-skills/media-processing/TOOLS.json +57 -147
  141. package/src/config/bundled-skills/media-processing/__tests__/concurrency-pool.test.ts +77 -0
  142. package/src/config/bundled-skills/media-processing/__tests__/cost-tracker.test.ts +69 -0
  143. package/src/config/bundled-skills/media-processing/__tests__/preprocess.test.ts +303 -0
  144. package/src/config/bundled-skills/media-processing/services/concurrency-pool.ts +55 -0
  145. package/src/config/bundled-skills/media-processing/services/cost-tracker.ts +86 -0
  146. package/src/config/bundled-skills/media-processing/services/gemini-map.ts +339 -0
  147. package/src/config/bundled-skills/media-processing/services/preprocess.ts +551 -0
  148. package/src/config/bundled-skills/media-processing/services/processing-pipeline.ts +7 -9
  149. package/src/config/bundled-skills/media-processing/services/reduce.ts +197 -0
  150. package/src/config/bundled-skills/media-processing/tools/analyze-keyframes.ts +88 -253
  151. package/src/config/bundled-skills/media-processing/tools/extract-keyframes.ts +22 -153
  152. package/src/config/bundled-skills/media-processing/tools/generate-clip.ts +2 -2
  153. package/src/config/bundled-skills/media-processing/tools/media-diagnostics.ts +28 -51
  154. package/src/config/bundled-skills/media-processing/tools/query-media-events.ts +35 -270
  155. package/src/config/bundled-skills/messaging/SKILL.md +12 -2
  156. package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +4 -7
  157. package/src/config/bundled-skills/messaging/tools/messaging-reply.ts +2 -1
  158. package/src/config/bundled-skills/phone-calls/SKILL.md +86 -21
  159. package/src/config/bundled-skills/twitter/icon.svg +14 -0
  160. package/src/config/bundled-tool-registry.ts +310 -0
  161. package/src/config/calls-schema.ts +181 -0
  162. package/src/config/core-schema.ts +309 -0
  163. package/src/config/defaults.ts +27 -3
  164. package/src/config/env-registry.ts +169 -0
  165. package/src/config/env.ts +175 -0
  166. package/src/config/loader.ts +6 -6
  167. package/src/config/memory-schema.ts +528 -0
  168. package/src/config/sandbox-schema.ts +55 -0
  169. package/src/config/schema.ts +157 -1138
  170. package/src/config/skill-state.ts +1 -1
  171. package/src/config/skills-schema.ts +32 -0
  172. package/src/config/skills.ts +35 -24
  173. package/src/config/system-prompt.ts +107 -56
  174. package/src/config/templates/SOUL.md +1 -1
  175. package/src/config/types.ts +1 -0
  176. package/src/config/user-reference.ts +4 -9
  177. package/src/config/vellum-skills/catalog.json +0 -7
  178. package/src/config/vellum-skills/chatgpt-import/tools/chatgpt-import.ts +5 -1
  179. package/src/config/vellum-skills/slack-oauth-setup/SKILL.md +1 -0
  180. package/src/config/vellum-skills/sms-setup/SKILL.md +112 -14
  181. package/src/context/window-manager.ts +27 -7
  182. package/src/daemon/approval-generators.ts +186 -0
  183. package/src/daemon/approved-devices-store.ts +140 -0
  184. package/src/daemon/assistant-attachments.ts +1 -1
  185. package/src/daemon/classifier.ts +35 -32
  186. package/src/daemon/config-watcher.ts +1 -1
  187. package/src/daemon/daemon-control.ts +254 -0
  188. package/src/daemon/handlers/apps.ts +2 -3
  189. package/src/daemon/handlers/config-channels.ts +158 -0
  190. package/src/daemon/handlers/config-inbox.ts +540 -0
  191. package/src/daemon/handlers/config-ingress.ts +231 -0
  192. package/src/daemon/handlers/config-integrations.ts +258 -0
  193. package/src/daemon/handlers/config-model.ts +143 -0
  194. package/src/daemon/handlers/config-parental.ts +163 -0
  195. package/src/daemon/handlers/config-scheduling.ts +172 -0
  196. package/src/daemon/handlers/config-slack.ts +92 -0
  197. package/src/daemon/handlers/config-telegram.ts +301 -0
  198. package/src/daemon/handlers/config-tools.ts +177 -0
  199. package/src/daemon/handlers/config-trust.ts +104 -0
  200. package/src/daemon/handlers/config-twilio.ts +1080 -0
  201. package/src/daemon/handlers/config.ts +53 -2463
  202. package/src/daemon/handlers/diagnostics.ts +1 -1
  203. package/src/daemon/handlers/dictation.ts +4 -6
  204. package/src/daemon/handlers/documents.ts +18 -32
  205. package/src/daemon/handlers/index.ts +9 -0
  206. package/src/daemon/handlers/misc.ts +3 -5
  207. package/src/daemon/handlers/pairing.ts +98 -0
  208. package/src/daemon/handlers/sessions.ts +74 -5
  209. package/src/daemon/handlers/shared.ts +3 -1
  210. package/src/daemon/handlers/skills.ts +1 -1
  211. package/src/daemon/handlers/twitter-auth.ts +2 -0
  212. package/src/daemon/handlers/work-items.ts +2 -2
  213. package/src/daemon/handlers/workspace-files.ts +4 -3
  214. package/src/daemon/install-cli-launchers.ts +113 -0
  215. package/src/daemon/ipc-contract/apps.ts +356 -0
  216. package/src/daemon/ipc-contract/browser.ts +74 -0
  217. package/src/daemon/ipc-contract/computer-use.ts +151 -0
  218. package/src/daemon/ipc-contract/diagnostics.ts +56 -0
  219. package/src/daemon/ipc-contract/documents.ts +74 -0
  220. package/src/daemon/ipc-contract/inbox.ts +209 -0
  221. package/src/daemon/ipc-contract/integrations.ts +284 -0
  222. package/src/daemon/ipc-contract/memory.ts +48 -0
  223. package/src/daemon/ipc-contract/messages.ts +211 -0
  224. package/src/daemon/ipc-contract/pairing.ts +45 -0
  225. package/src/daemon/ipc-contract/parental-control.ts +95 -0
  226. package/src/daemon/ipc-contract/schedules.ts +97 -0
  227. package/src/daemon/ipc-contract/sessions.ts +321 -0
  228. package/src/daemon/ipc-contract/shared.ts +42 -0
  229. package/src/daemon/ipc-contract/skills.ts +120 -0
  230. package/src/daemon/ipc-contract/subagents.ts +58 -0
  231. package/src/daemon/ipc-contract/surfaces.ts +250 -0
  232. package/src/daemon/ipc-contract/trust.ts +60 -0
  233. package/src/daemon/ipc-contract/work-items.ts +225 -0
  234. package/src/daemon/ipc-contract/workspace.ts +113 -0
  235. package/src/daemon/ipc-contract-inventory.json +62 -0
  236. package/src/daemon/ipc-contract-inventory.ts +55 -29
  237. package/src/daemon/ipc-contract.ts +227 -2527
  238. package/src/daemon/ipc-protocol.ts +1 -1
  239. package/src/daemon/ipc-validate.ts +7 -0
  240. package/src/daemon/lifecycle.ts +97 -379
  241. package/src/daemon/pairing-store.ts +177 -0
  242. package/src/daemon/providers-setup.ts +43 -0
  243. package/src/daemon/ride-shotgun-handler.ts +67 -2
  244. package/src/daemon/server.ts +60 -44
  245. package/src/daemon/session-agent-loop-handlers.ts +421 -0
  246. package/src/daemon/session-agent-loop.ts +113 -275
  247. package/src/daemon/session-dynamic-profile.ts +1 -1
  248. package/src/daemon/session-history.ts +1 -1
  249. package/src/daemon/session-media-retry.ts +1 -1
  250. package/src/daemon/session-messaging.ts +37 -2
  251. package/src/daemon/session-notifiers.ts +5 -25
  252. package/src/daemon/session-process.ts +99 -59
  253. package/src/daemon/session-queue-manager.ts +98 -4
  254. package/src/daemon/session-runtime-assembly.ts +149 -15
  255. package/src/daemon/session-surfaces.ts +26 -4
  256. package/src/daemon/session-tool-setup.ts +28 -30
  257. package/src/daemon/session-workspace.ts +1 -1
  258. package/src/daemon/session.ts +24 -1
  259. package/src/daemon/shutdown-handlers.ts +122 -0
  260. package/src/daemon/trace-emitter.ts +1 -1
  261. package/src/daemon/watch-handler.ts +36 -33
  262. package/src/doordash/cart-queries.ts +787 -0
  263. package/src/doordash/client.ts +144 -127
  264. package/src/doordash/order-queries.ts +85 -0
  265. package/src/doordash/queries.ts +10 -1308
  266. package/src/doordash/search-queries.ts +203 -0
  267. package/src/doordash/session.ts +3 -2
  268. package/src/doordash/store-queries.ts +246 -0
  269. package/src/doordash/types.ts +367 -0
  270. package/src/email/providers/agentmail.ts +2 -1
  271. package/src/email/providers/index.ts +3 -2
  272. package/src/email/service.ts +3 -2
  273. package/src/errors.ts +43 -0
  274. package/src/home-base/prebuilt/seed.ts +1 -1
  275. package/src/hooks/cli.ts +6 -5
  276. package/src/hooks/config.ts +6 -8
  277. package/src/hooks/discovery.ts +6 -5
  278. package/src/hooks/manager.ts +4 -3
  279. package/src/hooks/runner.ts +2 -2
  280. package/src/hooks/templates.ts +5 -5
  281. package/src/inbound/public-ingress-urls.ts +3 -1
  282. package/src/index.ts +4 -2
  283. package/src/influencer/client.ts +1104 -0
  284. package/src/instrument.ts +4 -3
  285. package/src/logfire.ts +4 -3
  286. package/src/memory/admin.ts +25 -35
  287. package/src/memory/attachments-store.ts +4 -7
  288. package/src/memory/channel-delivery-store.ts +30 -1
  289. package/src/memory/channel-guardian-store.ts +200 -1
  290. package/src/memory/clarification-resolver.ts +37 -33
  291. package/src/memory/conflict-store.ts +67 -61
  292. package/src/memory/contradiction-checker.ts +141 -117
  293. package/src/memory/conversation-store.ts +335 -51
  294. package/src/memory/db-connection.ts +27 -4
  295. package/src/memory/db-init.ts +121 -4
  296. package/src/memory/db.ts +14 -1
  297. package/src/memory/embedding-backend.ts +27 -5
  298. package/src/memory/embedding-ollama.ts +2 -1
  299. package/src/memory/entity-extractor.ts +38 -35
  300. package/src/memory/guardian-action-store.ts +430 -0
  301. package/src/memory/inbox-escalation-projection.ts +59 -0
  302. package/src/memory/inbox-thread-store.ts +218 -0
  303. package/src/memory/ingress-invite-store.ts +338 -0
  304. package/src/memory/ingress-member-store.ts +350 -0
  305. package/src/memory/items-extractor.ts +91 -97
  306. package/src/memory/job-handlers/index-maintenance.ts +3 -3
  307. package/src/memory/job-handlers/media-processing.ts +11 -42
  308. package/src/memory/job-handlers/summarization.ts +32 -26
  309. package/src/memory/job-utils.ts +3 -10
  310. package/src/memory/jobs-store.ts +6 -9
  311. package/src/memory/jobs-worker.ts +51 -36
  312. package/src/memory/migrations/001-job-deferrals.ts +45 -0
  313. package/src/memory/migrations/002-tool-invocations-fk.ts +43 -0
  314. package/src/memory/migrations/003-memory-fts-backfill.ts +24 -0
  315. package/src/memory/migrations/004-entity-relation-dedup.ts +87 -0
  316. package/src/memory/migrations/005-fingerprint-scope-unique.ts +80 -0
  317. package/src/memory/migrations/006-scope-salted-fingerprints.ts +62 -0
  318. package/src/memory/migrations/007-assistant-id-to-self.ts +254 -0
  319. package/src/memory/migrations/008-remove-assistant-id-columns.ts +208 -0
  320. package/src/memory/migrations/009-llm-usage-events-drop-assistant-id.ts +83 -0
  321. package/src/memory/migrations/010-ext-conv-bindings-channel-chat-unique.ts +56 -0
  322. package/src/memory/migrations/011-call-sessions-provider-sid-dedup.ts +63 -0
  323. package/src/memory/migrations/012-call-sessions-add-initiated-from.ts +19 -0
  324. package/src/memory/migrations/013-guardian-action-tables.ts +68 -0
  325. package/src/memory/migrations/014-backfill-inbox-thread-state.ts +76 -0
  326. package/src/memory/migrations/015-drop-active-search-index.ts +27 -0
  327. package/src/memory/migrations/016-memory-segments-indexes.ts +11 -0
  328. package/src/memory/migrations/017-memory-items-indexes.ts +12 -0
  329. package/src/memory/migrations/018-remaining-table-indexes.ts +13 -0
  330. package/src/memory/migrations/index.ts +24 -0
  331. package/src/memory/migrations/registry.ts +79 -0
  332. package/src/memory/migrations/validate-migration-state.ts +69 -0
  333. package/src/memory/qdrant-manager.ts +49 -8
  334. package/src/memory/query-builder.ts +1 -1
  335. package/src/memory/raw-query.ts +119 -0
  336. package/src/memory/recall-cache.ts +4 -1
  337. package/src/memory/retriever.ts +163 -47
  338. package/src/memory/schema-migration.ts +25 -984
  339. package/src/memory/schema.ts +130 -7
  340. package/src/memory/search/entity.ts +10 -19
  341. package/src/memory/search/lexical.ts +81 -52
  342. package/src/memory/search/ranking.ts +21 -22
  343. package/src/memory/search/semantic.ts +157 -19
  344. package/src/memory/shared-app-links-store.ts +4 -5
  345. package/src/memory/validation.ts +19 -0
  346. package/src/messaging/draft-store.ts +5 -6
  347. package/src/messaging/providers/sms/adapter.ts +3 -6
  348. package/src/messaging/providers/telegram-bot/adapter.ts +2 -5
  349. package/src/messaging/providers/whatsapp/adapter.ts +136 -0
  350. package/src/messaging/providers/whatsapp/client.ts +67 -0
  351. package/src/messaging/style-analyzer.ts +5 -4
  352. package/src/messaging/thread-summarizer.ts +61 -69
  353. package/src/messaging/triage-engine.ts +62 -71
  354. package/src/migrations/config-merge.ts +53 -0
  355. package/src/migrations/data-layout.ts +68 -0
  356. package/src/migrations/data-merge.ts +33 -0
  357. package/src/migrations/hooks-merge.ts +90 -0
  358. package/src/migrations/index.ts +6 -0
  359. package/src/migrations/log.ts +23 -0
  360. package/src/migrations/skills-merge.ts +33 -0
  361. package/src/migrations/workspace-layout.ts +79 -0
  362. package/src/permissions/checker.ts +126 -11
  363. package/src/permissions/prompter.ts +14 -0
  364. package/src/permissions/shell-identity.ts +31 -1
  365. package/src/permissions/trust-store.ts +21 -1
  366. package/src/providers/anthropic/client.ts +4 -4
  367. package/src/providers/failover.ts +2 -2
  368. package/src/providers/model-intents.ts +70 -0
  369. package/src/providers/ollama/client.ts +2 -1
  370. package/src/providers/provider-send-message.ts +176 -0
  371. package/src/providers/registry.ts +71 -30
  372. package/src/providers/retry.ts +35 -1
  373. package/src/providers/types.ts +12 -1
  374. package/src/runtime/approval-conversation-turn.ts +97 -0
  375. package/src/runtime/approval-message-composer.ts +115 -5
  376. package/src/runtime/assistant-event-hub.ts +3 -1
  377. package/src/runtime/channel-approval-parser.ts +36 -2
  378. package/src/runtime/channel-approvals.ts +0 -21
  379. package/src/runtime/channel-guardian-service.ts +48 -7
  380. package/src/runtime/channel-readiness-service.ts +160 -34
  381. package/src/runtime/channel-readiness-types.ts +10 -4
  382. package/src/runtime/channel-retry-sweep.ts +184 -0
  383. package/src/runtime/guardian-context-resolver.ts +108 -0
  384. package/src/runtime/http-server.ts +289 -745
  385. package/src/runtime/http-types.ts +56 -3
  386. package/src/runtime/middleware/auth.ts +116 -0
  387. package/src/runtime/middleware/error-handler.ts +33 -0
  388. package/src/runtime/middleware/twilio-validation.ts +127 -0
  389. package/src/runtime/routes/app-routes.ts +1 -1
  390. package/src/runtime/routes/call-routes.ts +49 -6
  391. package/src/runtime/routes/channel-delivery-routes.ts +170 -0
  392. package/src/runtime/routes/channel-guardian-routes.ts +1191 -0
  393. package/src/runtime/routes/channel-inbound-routes.ts +1152 -0
  394. package/src/runtime/routes/channel-route-shared.ts +144 -0
  395. package/src/runtime/routes/channel-routes.ts +32 -1634
  396. package/src/runtime/routes/conversation-routes.ts +50 -7
  397. package/src/runtime/routes/events-routes.ts +2 -2
  398. package/src/runtime/routes/identity-routes.ts +126 -0
  399. package/src/runtime/routes/pairing-routes.ts +144 -0
  400. package/src/runtime/routes/run-routes.ts +15 -1
  401. package/src/runtime/run-orchestrator.ts +52 -34
  402. package/src/schedule/schedule-store.ts +36 -32
  403. package/src/schedule/scheduler.ts +3 -3
  404. package/src/security/encrypted-store.ts +5 -7
  405. package/src/security/oauth2.ts +45 -15
  406. package/src/security/parental-control-store.ts +183 -0
  407. package/src/security/secret-allowlist.ts +4 -3
  408. package/src/security/secret-scanner.ts +5 -5
  409. package/src/security/secure-keys.ts +1 -1
  410. package/src/security/token-manager.ts +3 -2
  411. package/src/services/vercel-deploy.ts +6 -2
  412. package/src/skills/tool-manifest.ts +3 -3
  413. package/src/skills/vellum-catalog-remote.ts +75 -16
  414. package/src/slack/slack-webhook.ts +2 -1
  415. package/src/swarm/orchestrator.ts +92 -1
  416. package/src/swarm/router-planner.ts +6 -9
  417. package/src/swarm/worker-prompts.ts +9 -12
  418. package/src/tasks/task-compiler.ts +19 -28
  419. package/src/tasks/task-runner.ts +1 -1
  420. package/src/tools/assets/search.ts +15 -14
  421. package/src/tools/browser/__tests__/auth-detector.test.ts +1 -0
  422. package/src/tools/browser/auto-navigate.ts +1 -0
  423. package/src/tools/browser/browser-execution.ts +13 -1
  424. package/src/tools/browser/browser-manager.ts +119 -4
  425. package/src/tools/browser/network-recorder.ts +5 -0
  426. package/src/tools/credentials/broker.ts +11 -2
  427. package/src/tools/credentials/metadata-store.ts +18 -14
  428. package/src/tools/credentials/post-connect-hooks.ts +61 -0
  429. package/src/tools/credentials/vault.ts +49 -23
  430. package/src/tools/executor.ts +80 -18
  431. package/src/tools/host-terminal/cli-discover.ts +1 -1
  432. package/src/tools/network/script-proxy/http-forwarder.ts +1 -1
  433. package/src/tools/network/script-proxy/mitm-handler.ts +1 -1
  434. package/src/tools/network/script-proxy/server.ts +1 -1
  435. package/src/tools/network/script-proxy/session-manager.ts +6 -5
  436. package/src/tools/network/web-fetch.ts +18 -2
  437. package/src/tools/network/web-search.ts +7 -3
  438. package/src/tools/reminder/reminder-store.ts +14 -15
  439. package/src/tools/schedule/create.ts +1 -0
  440. package/src/tools/schedule/list.ts +2 -1
  441. package/src/tools/shared/filesystem/file-ops-service.ts +5 -7
  442. package/src/tools/skills/skill-script-runner.ts +24 -9
  443. package/src/tools/skills/skill-tool-factory.ts +1 -0
  444. package/src/tools/tasks/work-item-enqueue.ts +2 -2
  445. package/src/tools/terminal/evaluate-typescript.ts +21 -12
  446. package/src/tools/terminal/parser.ts +50 -0
  447. package/src/tools/watcher/delete.ts +6 -0
  448. package/src/tools/weather/service.ts +1 -1
  449. package/src/twitter/client.ts +190 -24
  450. package/src/twitter/session.ts +4 -3
  451. package/src/util/clipboard.ts +1 -1
  452. package/src/util/errors.ts +65 -8
  453. package/src/util/fs.ts +40 -0
  454. package/src/util/json.ts +10 -0
  455. package/src/util/log-redact.ts +189 -0
  456. package/src/util/logger.ts +25 -18
  457. package/src/util/object.ts +3 -0
  458. package/src/util/platform.ts +72 -365
  459. package/src/util/pricing.ts +1 -1
  460. package/src/util/promise-guard.ts +1 -1
  461. package/src/util/retry.ts +19 -0
  462. package/src/util/row-mapper.ts +79 -0
  463. package/src/util/silently.ts +21 -0
  464. package/src/watcher/engine.ts +5 -1
  465. package/src/watcher/provider-types.ts +20 -0
  466. package/src/watcher/providers/github.ts +156 -0
  467. package/src/watcher/providers/gmail.ts +1 -0
  468. package/src/watcher/providers/google-calendar.ts +1 -0
  469. package/src/watcher/providers/linear.ts +460 -0
  470. package/src/watcher/providers/slack.ts +1 -0
  471. package/src/work-items/work-item-runner.ts +1 -1
  472. package/src/workspace/git-service.ts +1 -1
  473. package/src/workspace/provider-commit-message-generator.ts +51 -22
  474. package/src/__tests__/call-bridge.test.ts +0 -517
  475. package/src/__tests__/session-process-bridge.test.ts +0 -244
  476. package/src/calls/call-bridge.ts +0 -168
  477. package/src/config/bundled-skills/media-processing/services/capability-registry.ts +0 -137
  478. package/src/config/bundled-skills/media-processing/services/event-detection-service.ts +0 -280
  479. package/src/config/bundled-skills/media-processing/services/feedback-aggregation.ts +0 -144
  480. package/src/config/bundled-skills/media-processing/services/feedback-store.ts +0 -136
  481. package/src/config/bundled-skills/media-processing/services/retrieval-service.ts +0 -95
  482. package/src/config/bundled-skills/media-processing/services/timeline-service.ts +0 -267
  483. package/src/config/bundled-skills/media-processing/tools/detect-events.ts +0 -110
  484. package/src/config/bundled-skills/media-processing/tools/recalibrate.ts +0 -235
  485. package/src/config/bundled-skills/media-processing/tools/select-tracking-profile.ts +0 -142
  486. package/src/config/bundled-skills/media-processing/tools/submit-feedback.ts +0 -150
  487. package/src/config/vellum-skills/google-oauth-setup/SKILL.md +0 -199
@@ -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, GuardianRuntimeContext } 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,7 @@ export interface AgentLoopSessionContext {
95
98
  workspaceTopLevelContext: string | null;
96
99
  workspaceTopLevelDirty: boolean;
97
100
  channelCapabilities?: ChannelCapabilities;
101
+ commandIntent?: { type: string; payload?: string; languageCode?: string };
98
102
  guardianContext?: GuardianRuntimeContext;
99
103
 
100
104
  readonly coreToolNames: Set<string>;
@@ -112,6 +116,7 @@ export interface AgentLoopSessionContext {
112
116
  lastAttachmentWarnings: string[];
113
117
 
114
118
  hasNoClient: boolean;
119
+ readonly streamThinking: boolean;
115
120
  readonly prompter: PermissionPrompter;
116
121
  readonly queue: MessageQueue;
117
122
 
@@ -124,6 +129,7 @@ export interface AgentLoopSessionContext {
124
129
  hasQueuedMessages(): boolean;
125
130
  canHandoffAtCheckpoint(): boolean;
126
131
  drainQueue(reason: QueueDrainReason): void;
132
+ getTurnChannelContext(): TurnChannelContext | null;
127
133
  }
128
134
 
129
135
  // ── runAgentLoop ─────────────────────────────────────────────────────
@@ -143,6 +149,18 @@ export async function runAgentLoopImpl(
143
149
  const rlog = log.child({ conversationId: ctx.conversationId, requestId: reqId });
144
150
  let yieldedForHandoff = false;
145
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
+
146
164
  ctx.lastAssistantAttachments = [];
147
165
  ctx.lastAttachmentWarnings = [];
148
166
 
@@ -204,19 +222,8 @@ export async function runAgentLoopImpl(
204
222
  emitUsage(ctx, compacted.summaryInputTokens, compacted.summaryOutputTokens, compacted.summaryModel, onEvent, 'context_compactor', reqId);
205
223
  }
206
224
 
207
- let firstAssistantText = '';
208
- let exchangeInputTokens = 0;
209
- let exchangeOutputTokens = 0;
210
- let model = '';
225
+ const state = createEventHandlerState();
211
226
  let runMessages = ctx.messages;
212
- const pendingToolResults = new Map<string, { content: string; isError: boolean; contentBlocks?: ContentBlock[] }>();
213
- const persistedToolUseIds = new Set<string>();
214
- const accumulatedDirectives: DirectiveRequest[] = [];
215
- const accumulatedToolContentBlocks: ContentBlock[] = [];
216
- const directiveWarnings: string[] = [];
217
- let pendingDirectiveDisplayBuffer = '';
218
- let lastAssistantMessageId: string | undefined;
219
- let providerErrorUserMessage: string | null = null;
220
227
 
221
228
  const memoryResult = await prepareMemoryContext(
222
229
  {
@@ -235,11 +242,16 @@ export async function runAgentLoopImpl(
235
242
  );
236
243
 
237
244
  if (memoryResult.conflictClarification) {
245
+ const loopChannelMeta = {
246
+ userMessageChannel: capturedTurnChannelContext.userMessageChannel,
247
+ assistantMessageChannel: capturedTurnChannelContext.assistantMessageChannel,
248
+ };
238
249
  const assistantMessage = createAssistantMessage(memoryResult.conflictClarification);
239
250
  conversationStore.addMessage(
240
251
  ctx.conversationId,
241
252
  'assistant',
242
253
  JSON.stringify(assistantMessage.content),
254
+ loopChannelMeta,
243
255
  );
244
256
  ctx.messages.push(assistantMessage);
245
257
  onEvent({
@@ -292,11 +304,21 @@ export async function runAgentLoopImpl(
292
304
  timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone,
293
305
  });
294
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
+
295
315
  runMessages = applyRuntimeInjections(runMessages, {
296
316
  softConflictInstruction,
297
317
  activeSurface,
298
318
  workspaceTopLevelContext: ctx.workspaceTopLevelContext,
299
319
  channelCapabilities: ctx.channelCapabilities ?? null,
320
+ channelCommandContext: ctx.commandIntent ?? null,
321
+ channelTurnContext,
300
322
  guardianContext: ctx.guardianContext ?? null,
301
323
  temporalContext,
302
324
  });
@@ -309,235 +331,21 @@ export async function runAgentLoopImpl(
309
331
  runMessages = preRunRepair.messages;
310
332
  }
311
333
 
312
- let orderingErrorDetected = false;
313
- let deferredOrderingError: string | null = null;
314
- let contextTooLargeDetected = false;
315
334
  let preRunHistoryLength = runMessages.length;
316
335
 
317
- let llmCallStartedEmitted = false;
318
- const toolUseIdToName = new Map<string, string>();
319
- let currentTurnToolNames: string[] = [];
320
-
321
- const buildEventHandler = () => (event: AgentEvent) => {
322
- const emitLlmCallStartedIfNeeded = () => {
323
- if (llmCallStartedEmitted) return;
324
- llmCallStartedEmitted = true;
325
- ctx.traceEmitter.emit('llm_call_started', `LLM call to ${ctx.provider.name}`, {
326
- requestId: reqId,
327
- status: 'info',
328
- attributes: { provider: ctx.provider.name, model: model || 'unknown' },
329
- });
330
- };
331
-
332
- switch (event.type) {
333
- case 'text_delta': {
334
- emitLlmCallStartedIfNeeded();
335
- pendingDirectiveDisplayBuffer += event.text;
336
- const drained = drainDirectiveDisplayBuffer(pendingDirectiveDisplayBuffer);
337
- pendingDirectiveDisplayBuffer = drained.bufferedRemainder;
338
- if (drained.emitText.length > 0) {
339
- onEvent({ type: 'assistant_text_delta', text: drained.emitText, sessionId: ctx.conversationId });
340
- if (isFirstMessage) firstAssistantText += drained.emitText;
341
- }
342
- break;
343
- }
344
- case 'thinking_delta':
345
- emitLlmCallStartedIfNeeded();
346
- onEvent({ type: 'assistant_thinking_delta', thinking: event.thinking });
347
- break;
348
- case 'tool_use':
349
- toolUseIdToName.set(event.id, event.name);
350
- currentTurnToolNames.push(event.name);
351
- onEvent({ type: 'tool_use_start', toolName: event.name, input: event.input, sessionId: ctx.conversationId });
352
- break;
353
- case 'tool_output_chunk': {
354
- // Try to parse structured progress fields from the chunk.
355
- // Cheap pre-check: only attempt JSON.parse when the chunk looks like an object.
356
- let structured: { subType?: 'tool_start' | 'tool_complete' | 'status'; subToolName?: string; subToolInput?: string; subToolIsError?: boolean; subToolId?: string } | undefined;
357
- const trimmed = event.chunk.trimStart();
358
- if (trimmed.length > 0 && trimmed.length < 4096 && trimmed[0] === '{') {
359
- try {
360
- const parsed = JSON.parse(event.chunk);
361
- const VALID_SUB_TYPES = new Set(['tool_start', 'tool_complete', 'status']);
362
- if (parsed && typeof parsed === 'object' && typeof parsed.subType === 'string' && VALID_SUB_TYPES.has(parsed.subType)) {
363
- structured = {
364
- subType: parsed.subType as 'tool_start' | 'tool_complete' | 'status',
365
- subToolName: typeof parsed.subToolName === 'string' ? parsed.subToolName : undefined,
366
- subToolInput: typeof parsed.subToolInput === 'string' ? parsed.subToolInput : undefined,
367
- subToolIsError: typeof parsed.subToolIsError === 'boolean' ? parsed.subToolIsError : undefined,
368
- subToolId: typeof parsed.subToolId === 'string' ? parsed.subToolId : undefined,
369
- };
370
- }
371
- } catch {
372
- // Not valid JSON — pass through as plain chunk
373
- }
374
- }
375
- if (structured) {
376
- onEvent({
377
- type: 'tool_output_chunk',
378
- chunk: event.chunk,
379
- sessionId: ctx.conversationId,
380
- subType: structured.subType,
381
- subToolName: structured.subToolName,
382
- subToolInput: structured.subToolInput,
383
- subToolIsError: structured.subToolIsError,
384
- subToolId: structured.subToolId,
385
- });
386
- } else {
387
- onEvent({ type: 'tool_output_chunk', chunk: event.chunk, sessionId: ctx.conversationId });
388
- }
389
- break;
390
- }
391
- case 'input_json_delta':
392
- onEvent({ type: 'tool_input_delta', toolName: event.toolName, content: event.accumulatedJson, sessionId: ctx.conversationId });
393
- break;
394
- case 'tool_result': {
395
- const imageBlock = event.contentBlocks?.find((b): b is ImageContent => b.type === 'image');
396
- onEvent({ type: 'tool_result', toolName: '', result: event.content, isError: event.isError, diff: event.diff, status: event.status, sessionId: ctx.conversationId, imageData: imageBlock?.source.data });
397
- pendingToolResults.set(event.toolUseId, { content: event.content, isError: event.isError, contentBlocks: event.contentBlocks });
398
- {
399
- const toolName = toolUseIdToName.get(event.toolUseId);
400
- if (toolName === 'file_write' || toolName === 'bash') {
401
- ctx.markWorkspaceTopLevelDirty();
402
- } else if (toolName === 'file_edit' && !event.isError) {
403
- ctx.markWorkspaceTopLevelDirty();
404
- }
405
- }
406
- if (event.contentBlocks) {
407
- for (const cb of event.contentBlocks) {
408
- if (cb.type === 'image' || cb.type === 'file') {
409
- accumulatedToolContentBlocks.push(cb);
410
- }
411
- }
412
- }
413
- break;
414
- }
415
- case 'error':
416
- if (isProviderOrderingError(event.error.message)) {
417
- orderingErrorDetected = true;
418
- deferredOrderingError = event.error.message;
419
- } else if (isContextTooLarge(event.error.message)) {
420
- contextTooLargeDetected = true;
421
- } else {
422
- const classified = classifySessionError(event.error, { phase: 'agent_loop' });
423
- onEvent(buildSessionErrorMessage(ctx.conversationId, classified));
424
- providerErrorUserMessage = classified.userMessage;
425
- }
426
- break;
427
- case 'message_complete': {
428
- if (pendingDirectiveDisplayBuffer.length > 0) {
429
- onEvent({
430
- type: 'assistant_text_delta',
431
- text: pendingDirectiveDisplayBuffer,
432
- sessionId: ctx.conversationId,
433
- });
434
- if (isFirstMessage) firstAssistantText += pendingDirectiveDisplayBuffer;
435
- pendingDirectiveDisplayBuffer = '';
436
- }
437
- if (pendingToolResults.size > 0) {
438
- const toolResultBlocks = Array.from(pendingToolResults.entries()).map(
439
- ([toolUseId, result]) => ({
440
- type: 'tool_result',
441
- tool_use_id: toolUseId,
442
- content: result.content,
443
- is_error: result.isError,
444
- ...(result.contentBlocks ? { contentBlocks: result.contentBlocks } : {}),
445
- }),
446
- );
447
- conversationStore.addMessage(
448
- ctx.conversationId,
449
- 'user',
450
- JSON.stringify(toolResultBlocks),
451
- );
452
- for (const id of pendingToolResults.keys()) {
453
- persistedToolUseIds.add(id);
454
- }
455
- pendingToolResults.clear();
456
- }
457
- const { cleanedContent, directives: msgDirectives, warnings: msgWarnings } =
458
- cleanAssistantContent(event.message.content);
459
- accumulatedDirectives.push(...msgDirectives);
460
- directiveWarnings.push(...msgWarnings);
461
- if (msgDirectives.length > 0) {
462
- rlog.info(
463
- { parsedDirectives: msgDirectives.map(d => ({ source: d.source, path: d.path, mimeType: d.mimeType })), totalAccumulated: accumulatedDirectives.length },
464
- 'Parsed attachment directives from assistant message',
465
- );
466
- }
467
-
468
- const contentWithSurfaces: ContentBlock[] = [...cleanedContent as ContentBlock[]];
469
- for (const surface of ctx.currentTurnSurfaces) {
470
- contentWithSurfaces.push({
471
- type: 'ui_surface',
472
- surfaceId: surface.surfaceId,
473
- surfaceType: surface.surfaceType,
474
- title: surface.title,
475
- data: surface.data,
476
- actions: surface.actions,
477
- display: surface.display,
478
- } as unknown as ContentBlock);
479
- }
480
-
481
- const assistantMsg = conversationStore.addMessage(
482
- ctx.conversationId,
483
- 'assistant',
484
- JSON.stringify(contentWithSurfaces),
485
- );
486
- lastAssistantMessageId = assistantMsg.id;
487
-
488
- ctx.currentTurnSurfaces = [];
489
-
490
- const charCount = cleanedContent
491
- .filter((b) => (b as Record<string, unknown>).type === 'text')
492
- .reduce((sum: number, b) => sum + ((b as { text?: string }).text?.length ?? 0), 0);
493
- const toolUseCount = event.message.content
494
- .filter((b) => b.type === 'tool_use')
495
- .length;
496
- ctx.traceEmitter.emit('assistant_message', 'Assistant message complete', {
497
- requestId: reqId,
498
- status: 'success',
499
- attributes: { charCount, toolUseCount },
500
- });
501
- break;
502
- }
503
- case 'usage':
504
- exchangeInputTokens += event.inputTokens;
505
- exchangeOutputTokens += event.outputTokens;
506
- model = event.model;
507
-
508
- if (event.rawRequest && event.rawResponse) {
509
- try {
510
- recordRequestLog(
511
- ctx.conversationId,
512
- JSON.stringify(event.rawRequest),
513
- JSON.stringify(event.rawResponse),
514
- );
515
- } catch (err) {
516
- rlog.warn({ err }, 'Failed to persist LLM request log (non-fatal)');
517
- }
518
- }
519
-
520
- emitLlmCallStartedIfNeeded();
521
-
522
- ctx.traceEmitter.emit('llm_call_finished', `LLM call to ${ctx.provider.name} finished`, {
523
- requestId: reqId,
524
- status: 'success',
525
- attributes: {
526
- provider: ctx.provider.name,
527
- model: event.model,
528
- inputTokens: event.inputTokens,
529
- outputTokens: event.outputTokens,
530
- latencyMs: event.providerDurationMs,
531
- },
532
- });
533
- llmCallStartedEmitted = false;
534
- break;
535
- }
336
+ const deps: EventHandlerDeps = {
337
+ ctx,
338
+ onEvent,
339
+ reqId,
340
+ isFirstMessage,
341
+ rlog,
342
+ turnChannelContext: capturedTurnChannelContext,
536
343
  };
344
+ const eventHandler = (event: AgentEvent) => dispatchAgentEvent(state, deps, event);
537
345
 
538
346
  const onCheckpoint = (): CheckpointDecision => {
539
- const turnTools = currentTurnToolNames;
540
- currentTurnToolNames = [];
347
+ const turnTools = state.currentTurnToolNames;
348
+ state.currentTurnToolNames = [];
541
349
 
542
350
  if (ctx.canHandoffAtCheckpoint()) {
543
351
  const inBrowserFlow = turnTools.length > 0
@@ -554,37 +362,37 @@ export async function runAgentLoopImpl(
554
362
 
555
363
  let updatedHistory = await ctx.agentLoop.run(
556
364
  runMessages,
557
- buildEventHandler(),
365
+ eventHandler,
558
366
  abortController.signal,
559
367
  reqId,
560
368
  onCheckpoint,
561
369
  );
562
370
 
563
371
  // One-shot ordering error retry
564
- if (orderingErrorDetected && updatedHistory.length === preRunHistoryLength) {
372
+ if (state.orderingErrorDetected && updatedHistory.length === preRunHistoryLength) {
565
373
  rlog.warn({ phase: 'retry' }, 'Provider ordering error detected, attempting one-shot deep-repair retry');
566
374
  const retryRepair = deepRepairHistory(runMessages);
567
375
  runMessages = retryRepair.messages;
568
376
  preRepairMessages = retryRepair.messages;
569
377
  preRunHistoryLength = runMessages.length;
570
- orderingErrorDetected = false;
571
- deferredOrderingError = null;
378
+ state.orderingErrorDetected = false;
379
+ state.deferredOrderingError = null;
572
380
 
573
381
  updatedHistory = await ctx.agentLoop.run(
574
382
  runMessages,
575
- buildEventHandler(),
383
+ eventHandler,
576
384
  abortController.signal,
577
385
  reqId,
578
386
  onCheckpoint,
579
387
  );
580
388
 
581
- if (orderingErrorDetected) {
389
+ if (state.orderingErrorDetected) {
582
390
  rlog.error({ phase: 'retry' }, 'Deep-repair retry also failed with ordering error. Consider starting a new conversation if this persists.');
583
391
  }
584
392
  }
585
393
 
586
394
  // One-shot context-too-large recovery
587
- if (contextTooLargeDetected && updatedHistory.length === preRunHistoryLength) {
395
+ if (state.contextTooLargeDetected && updatedHistory.length === preRunHistoryLength) {
588
396
  rlog.warn({ phase: 'retry' }, 'Context too large — attempting forced compaction and retry');
589
397
  const emergencyCompact = await ctx.contextWindowManager.maybeCompact(
590
398
  ctx.messages,
@@ -619,23 +427,25 @@ export async function runAgentLoopImpl(
619
427
  activeSurface,
620
428
  workspaceTopLevelContext: ctx.workspaceTopLevelContext,
621
429
  channelCapabilities: ctx.channelCapabilities ?? null,
430
+ channelCommandContext: ctx.commandIntent ?? null,
431
+ channelTurnContext,
622
432
  guardianContext: ctx.guardianContext ?? null,
623
433
  temporalContext,
624
434
  });
625
435
  preRepairMessages = runMessages;
626
436
  preRunHistoryLength = runMessages.length;
627
- contextTooLargeDetected = false;
437
+ state.contextTooLargeDetected = false;
628
438
 
629
439
  updatedHistory = await ctx.agentLoop.run(
630
440
  runMessages,
631
- buildEventHandler(),
441
+ eventHandler,
632
442
  abortController.signal,
633
443
  reqId,
634
444
  onCheckpoint,
635
445
  );
636
446
  }
637
447
 
638
- if (contextTooLargeDetected) {
448
+ if (state.contextTooLargeDetected) {
639
449
  const mediaTrimmed = stripMediaPayloadsForRetry(ctx.messages);
640
450
  if (mediaTrimmed.modified) {
641
451
  rlog.warn(
@@ -652,16 +462,18 @@ export async function runAgentLoopImpl(
652
462
  activeSurface,
653
463
  workspaceTopLevelContext: ctx.workspaceTopLevelContext,
654
464
  channelCapabilities: ctx.channelCapabilities ?? null,
465
+ channelCommandContext: ctx.commandIntent ?? null,
466
+ channelTurnContext,
655
467
  guardianContext: ctx.guardianContext ?? null,
656
468
  temporalContext,
657
469
  });
658
470
  preRepairMessages = runMessages;
659
471
  preRunHistoryLength = runMessages.length;
660
- contextTooLargeDetected = false;
472
+ state.contextTooLargeDetected = false;
661
473
 
662
474
  updatedHistory = await ctx.agentLoop.run(
663
475
  runMessages,
664
- buildEventHandler(),
476
+ eventHandler,
665
477
  abortController.signal,
666
478
  reqId,
667
479
  onCheckpoint,
@@ -669,7 +481,7 @@ export async function runAgentLoopImpl(
669
481
  }
670
482
  }
671
483
 
672
- if (contextTooLargeDetected) {
484
+ if (state.contextTooLargeDetected) {
673
485
  const classified = classifySessionError(
674
486
  new Error('context_length_exceeded'),
675
487
  { phase: 'agent_loop' },
@@ -678,8 +490,8 @@ export async function runAgentLoopImpl(
678
490
  }
679
491
  }
680
492
 
681
- if (deferredOrderingError) {
682
- const classified = classifySessionError(new Error(deferredOrderingError), { phase: 'agent_loop' });
493
+ if (state.deferredOrderingError) {
494
+ const classified = classifySessionError(new Error(state.deferredOrderingError), { phase: 'agent_loop' });
683
495
  onEvent(buildSessionErrorMessage(ctx.conversationId, classified));
684
496
  }
685
497
 
@@ -688,8 +500,8 @@ export async function runAgentLoopImpl(
688
500
  const msg = updatedHistory[i];
689
501
  if (msg.role === 'user') {
690
502
  for (const block of msg.content) {
691
- if (block.type === 'tool_result' && !pendingToolResults.has(block.tool_use_id) && !persistedToolUseIds.has(block.tool_use_id)) {
692
- 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, {
693
505
  content: block.content,
694
506
  isError: block.is_error ?? false,
695
507
  });
@@ -699,8 +511,8 @@ export async function runAgentLoopImpl(
699
511
  }
700
512
 
701
513
  // Flush remaining tool results
702
- if (pendingToolResults.size > 0) {
703
- const toolResultBlocks = Array.from(pendingToolResults.entries()).map(
514
+ if (state.pendingToolResults.size > 0) {
515
+ const toolResultBlocks = Array.from(state.pendingToolResults.entries()).map(
704
516
  ([toolUseId, result]) => ({
705
517
  type: 'tool_result',
706
518
  tool_use_id: toolUseId,
@@ -709,12 +521,17 @@ export async function runAgentLoopImpl(
709
521
  ...(result.contentBlocks ? { contentBlocks: result.contentBlocks } : {}),
710
522
  }),
711
523
  );
524
+ const toolResultMetadata = {
525
+ userMessageChannel: capturedTurnChannelContext.userMessageChannel,
526
+ assistantMessageChannel: capturedTurnChannelContext.assistantMessageChannel,
527
+ };
712
528
  conversationStore.addMessage(
713
529
  ctx.conversationId,
714
530
  'user',
715
531
  JSON.stringify(toolResultBlocks),
532
+ toolResultMetadata,
716
533
  );
717
- pendingToolResults.clear();
534
+ state.pendingToolResults.clear();
718
535
  }
719
536
 
720
537
  // Reconstruct history
@@ -725,17 +542,22 @@ export async function runAgentLoopImpl(
725
542
  });
726
543
 
727
544
  const hasAssistantResponse = newMessages.some((msg) => msg.role === 'assistant');
728
- if (!hasAssistantResponse && providerErrorUserMessage && !abortController.signal.aborted && !yieldedForHandoff) {
729
- 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);
730
551
  conversationStore.addMessage(
731
552
  ctx.conversationId,
732
553
  'assistant',
733
554
  JSON.stringify(errorAssistantMessage.content),
555
+ errChannelMeta,
734
556
  );
735
557
  newMessages.push(errorAssistantMessage);
736
558
  onEvent({
737
559
  type: 'assistant_text_delta',
738
- text: providerErrorUserMessage,
560
+ text: state.providerErrorUserMessage,
739
561
  sessionId: ctx.conversationId,
740
562
  });
741
563
  }
@@ -746,7 +568,7 @@ export async function runAgentLoopImpl(
746
568
  stripDynamicProfile: (msgs) => stripDynamicProfileMessages(msgs, dynamicProfile.text),
747
569
  });
748
570
 
749
- emitUsage(ctx, exchangeInputTokens, exchangeOutputTokens, model, onEvent, 'main_agent', reqId);
571
+ emitUsage(ctx, state.exchangeInputTokens, state.exchangeOutputTokens, state.model, onEvent, 'main_agent', reqId);
750
572
 
751
573
  void getHookManager().trigger('post-message', {
752
574
  sessionId: ctx.conversationId,
@@ -754,12 +576,12 @@ export async function runAgentLoopImpl(
754
576
 
755
577
  // Resolve attachments
756
578
  const attachmentResult = await resolveAssistantAttachments(
757
- accumulatedDirectives,
758
- accumulatedToolContentBlocks,
759
- directiveWarnings,
579
+ state.accumulatedDirectives,
580
+ state.accumulatedToolContentBlocks,
581
+ state.directiveWarnings,
760
582
  ctx.workingDir,
761
583
  async (filePath) => approveHostAttachmentRead(filePath, ctx.workingDir, ctx.prompter, ctx.conversationId, ctx.hasNoClient),
762
- lastAssistantMessageId,
584
+ state.lastAssistantMessageId,
763
585
  );
764
586
  const { assistantAttachments, emittedAttachments } = attachmentResult;
765
587
 
@@ -804,9 +626,10 @@ export async function runAgentLoopImpl(
804
626
  }
805
627
 
806
628
  if (isFirstMessage) {
807
- generateTitle(ctx, content, firstAssistantText).catch((err) => {
808
- log.warn({ err, conversationId: ctx.conversationId }, 'Failed to generate conversation title (non-fatal, using default title)');
809
- });
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
+ });
810
633
  }
811
634
  } catch (err) {
812
635
  const errorCtx = { phase: 'agent_loop' as const, aborted: abortController.signal.aborted };
@@ -868,6 +691,9 @@ export async function runAgentLoopImpl(
868
691
  ctx.currentActiveSurfaceId = undefined;
869
692
  ctx.allowedToolNames = undefined;
870
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;
871
697
 
872
698
  if (userMessageId) {
873
699
  consolidateAssistantMessages(ctx.conversationId, userMessageId);
@@ -883,13 +709,19 @@ async function generateTitle(
883
709
  ctx: Pick<AgentLoopSessionContext, 'conversationId' | 'provider'>,
884
710
  userMessage: string,
885
711
  assistantResponse: string,
712
+ onEvent: (msg: ServerMessage) => void,
713
+ sessionSignal?: AbortSignal,
886
714
  ): Promise<void> {
715
+ const config = getConfig();
887
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);
888
720
  const response = await ctx.provider.sendMessage(
889
721
  [{ role: 'user', content: [{ type: 'text', text: prompt }] }],
890
722
  [],
891
723
  undefined,
892
- { config: { max_tokens: 30 } },
724
+ { config: { max_tokens: config.daemon.titleGenerationMaxTokens }, signal },
893
725
  );
894
726
 
895
727
  const textBlock = response.content.find((b) => b.type === 'text');
@@ -898,7 +730,13 @@ async function generateTitle(
898
730
  const words = title.split(/\s+/);
899
731
  if (words.length > 5) title = words.slice(0, 5).join(' ');
900
732
  if (title.length > 40) title = title.slice(0, 40).trimEnd();
733
+ if (!title) return;
901
734
  conversationStore.updateConversationTitle(ctx.conversationId, title);
735
+ onEvent({
736
+ type: 'session_title_updated',
737
+ sessionId: ctx.conversationId,
738
+ title,
739
+ });
902
740
  log.info({ conversationId: ctx.conversationId, title }, 'Auto-generated conversation title');
903
741
  }
904
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.
@@ -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