vellum 0.2.0 → 0.2.2

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 (361) hide show
  1. package/README.md +15 -2
  2. package/bun.lock +5 -2
  3. package/package.json +4 -2
  4. package/scripts/capture-x-graphql.ts +562 -0
  5. package/scripts/ipc/check-swift-decoder-drift.ts +2 -1
  6. package/scripts/test.sh +5 -0
  7. package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +161 -34
  8. package/src/__tests__/account-registry.test.ts +2 -1
  9. package/src/__tests__/agent-heartbeat-service.test.ts +250 -0
  10. package/src/__tests__/app-bundler.test.ts +12 -33
  11. package/src/__tests__/asset-materialize-tool.test.ts +16 -15
  12. package/src/__tests__/asset-search-tool.test.ts +23 -22
  13. package/src/__tests__/attachments-store.test.ts +56 -127
  14. package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +5 -4
  15. package/src/__tests__/browser-skill-endstate.test.ts +5 -8
  16. package/src/__tests__/call-bridge.test.ts +385 -0
  17. package/src/__tests__/call-constants.test.ts +40 -0
  18. package/src/__tests__/call-orchestrator.test.ts +454 -0
  19. package/src/__tests__/call-recovery.test.ts +518 -0
  20. package/src/__tests__/call-routes-http.test.ts +459 -0
  21. package/src/__tests__/call-state-machine.test.ts +143 -0
  22. package/src/__tests__/call-state.test.ts +133 -0
  23. package/src/__tests__/call-store.test.ts +691 -0
  24. package/src/__tests__/cli-discover.test.ts +1 -1
  25. package/src/__tests__/commit-message-enrichment-service.test.ts +550 -0
  26. package/src/__tests__/compaction.benchmark.test.ts +176 -0
  27. package/src/__tests__/computer-use-tools.test.ts +250 -0
  28. package/src/__tests__/config-schema.test.ts +348 -3
  29. package/src/__tests__/conflict-store.test.ts +2 -1
  30. package/src/__tests__/contacts-tools.test.ts +331 -0
  31. package/src/__tests__/conversation-store.test.ts +30 -32
  32. package/src/__tests__/credential-security-invariants.test.ts +4 -0
  33. package/src/__tests__/date-context.test.ts +373 -0
  34. package/src/__tests__/db-schedule-syntax-migration.test.ts +129 -0
  35. package/src/__tests__/doordash-session.test.ts +9 -0
  36. package/src/__tests__/fixtures/media-reuse-fixtures.ts +3 -3
  37. package/src/__tests__/followup-tools.test.ts +303 -0
  38. package/src/__tests__/handlers-twitter-config.test.ts +718 -0
  39. package/src/__tests__/intent-routing.test.ts +64 -57
  40. package/src/__tests__/ipc-roundtrip.benchmark.test.ts +237 -0
  41. package/src/__tests__/ipc-snapshot.test.ts +96 -28
  42. package/src/__tests__/llm-usage-store.test.ts +3 -8
  43. package/src/__tests__/media-generate-image.test.ts +1 -1
  44. package/src/__tests__/media-reuse-story.e2e.test.ts +7 -7
  45. package/src/__tests__/memory-retrieval.benchmark.test.ts +430 -0
  46. package/src/__tests__/parallel-tool.benchmark.test.ts +294 -0
  47. package/src/__tests__/playbook-tools.test.ts +342 -0
  48. package/src/__tests__/profile-compiler.test.ts +2 -1
  49. package/src/__tests__/provider-streaming.benchmark.test.ts +773 -0
  50. package/src/__tests__/recurrence-engine-rruleset.test.ts +78 -0
  51. package/src/__tests__/recurrence-engine.test.ts +69 -0
  52. package/src/__tests__/recurrence-types.test.ts +71 -0
  53. package/src/__tests__/registry.test.ts +17 -10
  54. package/src/__tests__/relay-server.test.ts +633 -0
  55. package/src/__tests__/reminder-store.test.ts +6 -3
  56. package/src/__tests__/reminder.test.ts +43 -77
  57. package/src/__tests__/run-orchestrator-assistant-events.test.ts +222 -0
  58. package/src/__tests__/run-orchestrator.test.ts +7 -7
  59. package/src/__tests__/runtime-attachment-metadata.test.ts +19 -20
  60. package/src/__tests__/runtime-runs-http.test.ts +5 -23
  61. package/src/__tests__/runtime-runs.test.ts +11 -11
  62. package/src/__tests__/schedule-store.test.ts +482 -0
  63. package/src/__tests__/schedule-tools.test.ts +700 -0
  64. package/src/__tests__/scheduler-recurrence.test.ts +329 -0
  65. package/src/__tests__/server-history-render.test.ts +14 -13
  66. package/src/__tests__/session-error.test.ts +28 -0
  67. package/src/__tests__/session-init.benchmark.test.ts +462 -0
  68. package/src/__tests__/session-queue.test.ts +89 -16
  69. package/src/__tests__/session-runtime-assembly.test.ts +161 -0
  70. package/src/__tests__/session-surfaces-task-progress.test.ts +104 -0
  71. package/src/__tests__/signup-e2e.test.ts +2 -1
  72. package/src/__tests__/skill-projection.benchmark.test.ts +328 -0
  73. package/src/__tests__/skill-script-runner.test.ts +159 -0
  74. package/src/__tests__/speaker-identification.test.ts +52 -0
  75. package/src/__tests__/subagent-manager-notify.test.ts +42 -10
  76. package/src/__tests__/subagent-tools.test.ts +141 -41
  77. package/src/__tests__/task-compiler.test.ts +2 -1
  78. package/src/__tests__/task-runner.test.ts +2 -1
  79. package/src/__tests__/task-scheduler.test.ts +2 -1
  80. package/src/__tests__/task-tools.test.ts +49 -56
  81. package/src/__tests__/tool-audit-listener.test.ts +1 -0
  82. package/src/__tests__/tool-domain-event-publisher.test.ts +2 -0
  83. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +500 -0
  84. package/src/__tests__/tool-executor.test.ts +13 -17
  85. package/src/__tests__/turn-commit.test.ts +273 -2
  86. package/src/__tests__/twilio-provider.test.ts +143 -0
  87. package/src/__tests__/twilio-routes.test.ts +789 -0
  88. package/src/__tests__/twitter-auth-handler.test.ts +581 -0
  89. package/src/__tests__/view-image-tool.test.ts +217 -0
  90. package/src/__tests__/workspace-git-service.test.ts +403 -0
  91. package/src/__tests__/workspace-heartbeat-service.test.ts +141 -2
  92. package/src/agent-heartbeat/agent-heartbeat-service.ts +155 -0
  93. package/src/bundler/app-bundler.ts +35 -14
  94. package/src/calls/call-bridge.ts +95 -0
  95. package/src/calls/call-constants.ts +48 -0
  96. package/src/calls/call-domain.ts +276 -0
  97. package/src/calls/call-orchestrator.ts +390 -0
  98. package/src/calls/call-recovery.ts +207 -0
  99. package/src/calls/call-state-machine.ts +68 -0
  100. package/src/calls/call-state.ts +64 -0
  101. package/src/calls/call-store.ts +416 -0
  102. package/src/calls/relay-server.ts +335 -0
  103. package/src/calls/speaker-identification.ts +213 -0
  104. package/src/calls/twilio-config.ts +34 -0
  105. package/src/calls/twilio-provider.ts +173 -0
  106. package/src/calls/twilio-routes.ts +250 -0
  107. package/src/calls/types.ts +37 -0
  108. package/src/calls/voice-provider.ts +14 -0
  109. package/src/cli/config-commands.ts +334 -0
  110. package/src/cli/core-commands.ts +776 -0
  111. package/src/cli/doordash.ts +256 -25
  112. package/src/cli/ipc-client.ts +82 -0
  113. package/src/cli/map.ts +246 -0
  114. package/src/cli/twitter.ts +575 -0
  115. package/src/cli.ts +7 -5
  116. package/src/commands/__tests__/cc-command-registry.test.ts +319 -0
  117. package/src/commands/cc-command-registry.ts +209 -0
  118. package/src/config/bundled-skills/contacts/SKILL.md +39 -0
  119. package/src/config/bundled-skills/contacts/TOOLS.json +122 -0
  120. package/src/config/bundled-skills/contacts/tools/contact-merge.ts +9 -0
  121. package/src/config/bundled-skills/contacts/tools/contact-search.ts +9 -0
  122. package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +9 -0
  123. package/src/config/bundled-skills/document/SKILL.md +18 -0
  124. package/src/config/bundled-skills/document/TOOLS.json +53 -0
  125. package/src/config/bundled-skills/document/tools/document-create.ts +9 -0
  126. package/src/config/bundled-skills/document/tools/document-update.ts +9 -0
  127. package/src/config/bundled-skills/doordash/SKILL.md +163 -0
  128. package/src/config/bundled-skills/followups/SKILL.md +32 -0
  129. package/src/config/bundled-skills/followups/TOOLS.json +100 -0
  130. package/src/config/bundled-skills/followups/tools/followup-create.ts +9 -0
  131. package/src/config/bundled-skills/followups/tools/followup-list.ts +9 -0
  132. package/src/config/bundled-skills/followups/tools/followup-resolve.ts +9 -0
  133. package/src/config/bundled-skills/image-studio/TOOLS.json +2 -2
  134. package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +2 -24
  135. package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +2 -1
  136. package/src/config/bundled-skills/playbooks/SKILL.md +31 -0
  137. package/src/config/bundled-skills/playbooks/TOOLS.json +126 -0
  138. package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +9 -0
  139. package/src/config/bundled-skills/playbooks/tools/playbook-delete.ts +9 -0
  140. package/src/config/bundled-skills/playbooks/tools/playbook-list.ts +9 -0
  141. package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +9 -0
  142. package/src/config/bundled-skills/reminder/SKILL.md +20 -0
  143. package/src/config/bundled-skills/reminder/TOOLS.json +67 -0
  144. package/src/config/bundled-skills/reminder/tools/reminder-cancel.ts +9 -0
  145. package/src/config/bundled-skills/reminder/tools/reminder-create.ts +9 -0
  146. package/src/config/bundled-skills/reminder/tools/reminder-list.ts +9 -0
  147. package/src/config/bundled-skills/schedule/SKILL.md +74 -0
  148. package/src/config/bundled-skills/schedule/TOOLS.json +135 -0
  149. package/src/config/bundled-skills/schedule/tools/schedule-create.ts +9 -0
  150. package/src/config/bundled-skills/schedule/tools/schedule-delete.ts +9 -0
  151. package/src/config/bundled-skills/schedule/tools/schedule-list.ts +9 -0
  152. package/src/config/bundled-skills/schedule/tools/schedule-update.ts +9 -0
  153. package/src/config/bundled-skills/subagent/SKILL.md +25 -0
  154. package/src/config/bundled-skills/subagent/TOOLS.json +107 -0
  155. package/src/config/bundled-skills/subagent/tools/subagent-abort.ts +9 -0
  156. package/src/config/bundled-skills/subagent/tools/subagent-message.ts +9 -0
  157. package/src/config/bundled-skills/subagent/tools/subagent-read.ts +9 -0
  158. package/src/config/bundled-skills/subagent/tools/subagent-spawn.ts +9 -0
  159. package/src/config/bundled-skills/subagent/tools/subagent-status.ts +9 -0
  160. package/src/config/bundled-skills/tasks/SKILL.md +28 -0
  161. package/src/config/bundled-skills/tasks/TOOLS.json +256 -0
  162. package/src/config/bundled-skills/tasks/tools/task-delete.ts +9 -0
  163. package/src/config/bundled-skills/tasks/tools/task-list-add.ts +9 -0
  164. package/src/config/bundled-skills/tasks/tools/task-list-remove.ts +9 -0
  165. package/src/config/bundled-skills/tasks/tools/task-list-show.ts +9 -0
  166. package/src/config/bundled-skills/tasks/tools/task-list-update.ts +9 -0
  167. package/src/config/bundled-skills/tasks/tools/task-list.ts +9 -0
  168. package/src/config/bundled-skills/tasks/tools/task-run.ts +9 -0
  169. package/src/config/bundled-skills/tasks/tools/task-save.ts +9 -0
  170. package/src/config/bundled-skills/twitter/SKILL.md +134 -0
  171. package/src/config/bundled-skills/watcher/SKILL.md +27 -0
  172. package/src/config/bundled-skills/watcher/TOOLS.json +147 -0
  173. package/src/config/bundled-skills/watcher/tools/watcher-create.ts +9 -0
  174. package/src/config/bundled-skills/watcher/tools/watcher-delete.ts +9 -0
  175. package/src/config/bundled-skills/watcher/tools/watcher-digest.ts +9 -0
  176. package/src/config/bundled-skills/watcher/tools/watcher-list.ts +9 -0
  177. package/src/config/bundled-skills/watcher/tools/watcher-update.ts +9 -0
  178. package/src/config/defaults.ts +44 -0
  179. package/src/config/loader.ts +4 -1
  180. package/src/config/schema.ts +218 -1
  181. package/src/config/system-prompt.ts +100 -6
  182. package/src/config/templates/IDENTITY.md +7 -0
  183. package/src/config/types.ts +5 -0
  184. package/src/contacts/contact-store.ts +4 -4
  185. package/src/daemon/assistant-attachments.ts +10 -0
  186. package/src/daemon/classifier.ts +3 -1
  187. package/src/daemon/computer-use-session.ts +3 -1
  188. package/src/daemon/date-context.ts +136 -0
  189. package/src/daemon/handlers/apps.ts +16 -1
  190. package/src/daemon/handlers/browser.ts +54 -0
  191. package/src/daemon/handlers/computer-use.ts +7 -1
  192. package/src/daemon/handlers/config.ts +192 -4
  193. package/src/daemon/handlers/diagnostics.ts +5 -1
  194. package/src/daemon/handlers/documents.ts +18 -29
  195. package/src/daemon/handlers/home-base.ts +5 -1
  196. package/src/daemon/handlers/index.ts +40 -271
  197. package/src/daemon/handlers/misc.ts +9 -1
  198. package/src/daemon/handlers/publish.ts +6 -1
  199. package/src/daemon/handlers/sessions.ts +65 -12
  200. package/src/daemon/handlers/shared.ts +36 -1
  201. package/src/daemon/handlers/signing.ts +37 -0
  202. package/src/daemon/handlers/skills.ts +20 -6
  203. package/src/daemon/handlers/subagents.ts +8 -3
  204. package/src/daemon/handlers/twitter-auth.ts +169 -0
  205. package/src/daemon/handlers/work-items.ts +495 -39
  206. package/src/daemon/ipc-contract-inventory.json +40 -4
  207. package/src/daemon/ipc-contract.ts +185 -37
  208. package/src/daemon/ipc-protocol.ts +7 -2
  209. package/src/daemon/lifecycle.ts +48 -5
  210. package/src/daemon/main.ts +10 -4
  211. package/src/daemon/ride-shotgun-handler.ts +74 -10
  212. package/src/daemon/server.ts +144 -29
  213. package/src/daemon/session-agent-loop.ts +887 -0
  214. package/src/daemon/session-attachments.ts +28 -5
  215. package/src/daemon/session-error.ts +24 -3
  216. package/src/daemon/session-lifecycle.ts +147 -0
  217. package/src/daemon/session-media-retry.ts +147 -0
  218. package/src/daemon/session-messaging.ts +145 -0
  219. package/src/daemon/session-notifiers.ts +164 -0
  220. package/src/daemon/session-process.ts +2 -2
  221. package/src/daemon/session-queue-manager.ts +1 -0
  222. package/src/daemon/session-runtime-assembly.ts +52 -0
  223. package/src/daemon/session-skill-tools.ts +124 -5
  224. package/src/daemon/session-slash.ts +3 -0
  225. package/src/daemon/session-surfaces.ts +77 -2
  226. package/src/daemon/session-tool-setup.ts +222 -2
  227. package/src/daemon/session-usage.ts +0 -2
  228. package/src/daemon/session.ts +114 -1365
  229. package/src/daemon/video-thumbnail.ts +60 -0
  230. package/src/doordash/client.ts +121 -27
  231. package/src/doordash/queries.ts +1 -2
  232. package/src/export/formatter.ts +3 -1
  233. package/src/followups/followup-store.ts +4 -2
  234. package/src/followups/types.ts +6 -0
  235. package/src/hooks/templates.ts +1 -1
  236. package/src/index.ts +32 -1151
  237. package/src/media/gemini-image-service.ts +1 -1
  238. package/src/memory/attachments-store.ts +28 -83
  239. package/src/memory/channel-delivery-store.ts +7 -21
  240. package/src/memory/clarification-resolver.ts +6 -5
  241. package/src/memory/contradiction-checker.ts +3 -2
  242. package/src/memory/conversation-key-store.ts +10 -29
  243. package/src/memory/conversation-store.ts +2 -1
  244. package/src/memory/db.ts +362 -2
  245. package/src/memory/entity-extractor.ts +6 -3
  246. package/src/memory/items-extractor.ts +5 -4
  247. package/src/memory/jobs-store.ts +3 -2
  248. package/src/memory/llm-usage-store.ts +1 -2
  249. package/src/memory/runs-store.ts +1 -2
  250. package/src/memory/schema.ts +65 -2
  251. package/src/messaging/style-analyzer.ts +3 -2
  252. package/src/messaging/thread-summarizer.ts +8 -12
  253. package/src/messaging/triage-engine.ts +4 -2
  254. package/src/providers/openrouter/client.ts +20 -0
  255. package/src/providers/registry.ts +8 -0
  256. package/src/runtime/http-server.ts +277 -25
  257. package/src/runtime/http-types.ts +0 -2
  258. package/src/runtime/routes/attachment-routes.ts +5 -6
  259. package/src/runtime/routes/call-routes.ts +140 -0
  260. package/src/runtime/routes/channel-routes.ts +12 -19
  261. package/src/runtime/routes/conversation-routes.ts +5 -9
  262. package/src/runtime/routes/run-routes.ts +4 -8
  263. package/src/runtime/run-orchestrator.ts +39 -6
  264. package/src/schedule/recurrence-engine.ts +138 -0
  265. package/src/schedule/recurrence-types.ts +67 -0
  266. package/src/schedule/schedule-store.ts +102 -57
  267. package/src/schedule/scheduler.ts +9 -6
  268. package/src/security/oauth2.ts +29 -4
  269. package/src/security/secret-allowlist.ts +46 -0
  270. package/src/skills/clawhub.ts +1 -1
  271. package/src/subagent/manager.ts +40 -8
  272. package/src/swarm/backend-claude-code.ts +64 -9
  273. package/src/swarm/worker-prompts.ts +2 -1
  274. package/src/tasks/SPEC.md +34 -28
  275. package/src/tasks/ephemeral-permissions.ts +16 -7
  276. package/src/tasks/task-compiler.ts +5 -4
  277. package/src/tasks/task-runner.ts +10 -5
  278. package/src/tasks/task-scheduler.ts +1 -1
  279. package/src/tasks/tool-sanitizer.ts +36 -0
  280. package/src/tools/assets/search.ts +4 -4
  281. package/src/tools/browser/api-map.ts +220 -0
  282. package/src/tools/browser/auto-navigate.ts +270 -0
  283. package/src/tools/browser/browser-execution.ts +2 -1
  284. package/src/tools/browser/browser-manager.ts +2 -2
  285. package/src/tools/browser/network-recorder.ts +5 -4
  286. package/src/tools/browser/x-auto-navigate.ts +207 -0
  287. package/src/tools/calls/call-end.ts +67 -0
  288. package/src/tools/calls/call-start.ts +73 -0
  289. package/src/tools/calls/call-status.ts +81 -0
  290. package/src/tools/claude-code/claude-code.ts +77 -11
  291. package/src/tools/contacts/contact-merge.ts +46 -78
  292. package/src/tools/contacts/contact-search.ts +35 -79
  293. package/src/tools/contacts/contact-upsert.ts +35 -108
  294. package/src/tools/credentials/vault.ts +21 -5
  295. package/src/tools/document/document-tool.ts +71 -144
  296. package/src/tools/executor.ts +129 -10
  297. package/src/tools/followups/followup_create.ts +46 -88
  298. package/src/tools/followups/followup_list.ts +34 -74
  299. package/src/tools/followups/followup_resolve.ts +31 -66
  300. package/src/tools/host-terminal/cli-discover.ts +2 -1
  301. package/src/tools/host-terminal/host-shell.ts +10 -0
  302. package/src/tools/memory/handlers.ts +5 -4
  303. package/src/tools/network/__tests__/web-search.test.ts +427 -0
  304. package/src/tools/network/script-proxy/__tests__/logging.test.ts +248 -0
  305. package/src/tools/network/script-proxy/__tests__/policy.test.ts +234 -0
  306. package/src/tools/network/script-proxy/__tests__/router.test.ts +76 -0
  307. package/src/tools/network/web-fetch.ts +18 -6
  308. package/src/tools/playbooks/index.ts +4 -5
  309. package/src/tools/playbooks/playbook-create.ts +3 -47
  310. package/src/tools/playbooks/playbook-delete.ts +1 -25
  311. package/src/tools/playbooks/playbook-list.ts +1 -28
  312. package/src/tools/playbooks/playbook-update.ts +3 -51
  313. package/src/tools/registry.ts +2 -4
  314. package/src/tools/reminder/reminder.ts +5 -78
  315. package/src/tools/schedule/create.ts +69 -74
  316. package/src/tools/schedule/delete.ts +21 -47
  317. package/src/tools/schedule/list.ts +55 -74
  318. package/src/tools/schedule/update.ts +77 -84
  319. package/src/tools/subagent/abort.ts +29 -58
  320. package/src/tools/subagent/message.ts +30 -63
  321. package/src/tools/subagent/read.ts +53 -84
  322. package/src/tools/subagent/spawn.ts +43 -82
  323. package/src/tools/subagent/status.ts +42 -71
  324. package/src/tools/swarm/delegate.ts +2 -1
  325. package/src/tools/tasks/index.ts +8 -6
  326. package/src/tools/tasks/task-delete.ts +69 -56
  327. package/src/tools/tasks/task-list.ts +31 -52
  328. package/src/tools/tasks/task-run.ts +74 -102
  329. package/src/tools/tasks/task-save.ts +33 -65
  330. package/src/tools/tasks/work-item-enqueue.ts +192 -134
  331. package/src/tools/tasks/work-item-list.ts +33 -78
  332. package/src/tools/tasks/work-item-remove.ts +60 -0
  333. package/src/tools/tasks/work-item-update.ts +114 -0
  334. package/src/tools/terminal/backends/native.ts +3 -1
  335. package/src/tools/tool-manifest.ts +20 -74
  336. package/src/tools/types.ts +6 -0
  337. package/src/tools/ui-surface/definitions.ts +6 -1
  338. package/src/tools/watch/screen-watch.ts +3 -1
  339. package/src/tools/watcher/create.ts +52 -98
  340. package/src/tools/watcher/delete.ts +20 -46
  341. package/src/tools/watcher/digest.ts +36 -70
  342. package/src/tools/watcher/list.ts +49 -79
  343. package/src/tools/watcher/update.ts +45 -91
  344. package/src/twitter/client.ts +690 -0
  345. package/src/twitter/session.ts +91 -0
  346. package/src/usage/types.ts +0 -1
  347. package/src/util/truncate.ts +6 -0
  348. package/src/watcher/providers/slack.ts +2 -1
  349. package/src/watcher/watcher-store.ts +3 -2
  350. package/src/work-items/work-item-store.ts +236 -2
  351. package/src/workspace/commit-message-enrichment-service.ts +284 -0
  352. package/src/workspace/commit-message-provider.ts +95 -0
  353. package/src/workspace/git-service.ts +272 -52
  354. package/src/workspace/heartbeat-service.ts +70 -13
  355. package/src/workspace/provider-commit-message-generator.ts +242 -0
  356. package/src/workspace/turn-commit.ts +100 -51
  357. package/src/tools/contacts/index.ts +0 -4
  358. package/src/tools/document/index.ts +0 -5
  359. package/src/tools/followups/index.ts +0 -3
  360. package/src/tools/subagent/index.ts +0 -5
  361. /package/src/__tests__/{memory-context-benchmark.test.ts → memory-context-benchmark.benchmark.test.ts} +0 -0
@@ -7,7 +7,7 @@ import { validateAttachmentUpload, AttachmentUploadError } from '../../memory/at
7
7
  /** 30 MB — base64-encoded 20 MB attachment ≈ 27 MB plus JSON wrapper overhead. */
8
8
  const MAX_UPLOAD_BODY_BYTES = 30 * 1024 * 1024;
9
9
 
10
- export async function handleUploadAttachment(assistantId: string, req: Request): Promise<Response> {
10
+ export async function handleUploadAttachment(req: Request): Promise<Response> {
11
11
  const rawBody = await req.arrayBuffer();
12
12
  if (rawBody.byteLength > MAX_UPLOAD_BODY_BYTES) {
13
13
  return Response.json(
@@ -56,7 +56,6 @@ export async function handleUploadAttachment(assistantId: string, req: Request):
56
56
  let attachment: attachmentsStore.StoredAttachment;
57
57
  try {
58
58
  attachment = attachmentsStore.uploadAttachment(
59
- assistantId,
60
59
  filename,
61
60
  mimeType,
62
61
  data,
@@ -78,7 +77,7 @@ export async function handleUploadAttachment(assistantId: string, req: Request):
78
77
  });
79
78
  }
80
79
 
81
- export async function handleDeleteAttachment(assistantId: string, req: Request): Promise<Response> {
80
+ export async function handleDeleteAttachment(req: Request): Promise<Response> {
82
81
  let body: { attachmentId?: string };
83
82
  try {
84
83
  body = await req.json() as { attachmentId?: string };
@@ -98,7 +97,7 @@ export async function handleDeleteAttachment(assistantId: string, req: Request):
98
97
  );
99
98
  }
100
99
 
101
- const result = attachmentsStore.deleteAttachment(assistantId, attachmentId);
100
+ const result = attachmentsStore.deleteAttachment(attachmentId);
102
101
 
103
102
  if (result === 'not_found') {
104
103
  return Response.json(
@@ -117,8 +116,8 @@ export async function handleDeleteAttachment(assistantId: string, req: Request):
117
116
  return new Response(null, { status: 204 });
118
117
  }
119
118
 
120
- export function handleGetAttachment(assistantId: string, attachmentId: string): Response {
121
- const attachment = attachmentsStore.getAttachmentById(assistantId, attachmentId);
119
+ export function handleGetAttachment(attachmentId: string): Response {
120
+ const attachment = attachmentsStore.getAttachmentById(attachmentId);
122
121
  if (!attachment) {
123
122
  return Response.json({ error: 'Attachment not found' }, { status: 404 });
124
123
  }
@@ -0,0 +1,140 @@
1
+ /**
2
+ * Runtime HTTP route handlers for the call API.
3
+ *
4
+ * POST /v1/calls/start — initiate a new call
5
+ * GET /v1/calls/:callSessionId — get call status
6
+ * POST /v1/calls/:callSessionId/cancel — cancel a call
7
+ * POST /v1/calls/:callSessionId/answer — answer a pending question
8
+ */
9
+
10
+ import { startCall, getCallStatus, cancelCall, answerCall } from '../../calls/call-domain.js';
11
+ import { getConfig } from '../../config/loader.js';
12
+
13
+ /**
14
+ * POST /v1/calls/start
15
+ *
16
+ * Body: { phoneNumber: string; task: string; context?: string; conversationId: string }
17
+ */
18
+ export async function handleStartCall(req: Request): Promise<Response> {
19
+ if (!getConfig().calls.enabled) {
20
+ return Response.json(
21
+ { error: 'Calls feature is disabled via configuration. Set calls.enabled to true to use this feature.' },
22
+ { status: 403 },
23
+ );
24
+ }
25
+
26
+ let body: {
27
+ phoneNumber?: string;
28
+ task?: string;
29
+ context?: string;
30
+ conversationId?: string;
31
+ };
32
+ try {
33
+ body = await req.json() as typeof body;
34
+ } catch {
35
+ return Response.json({ error: 'Invalid JSON in request body' }, { status: 400 });
36
+ }
37
+
38
+ if (!body.conversationId) {
39
+ return Response.json({ error: 'conversationId is required' }, { status: 400 });
40
+ }
41
+
42
+ const result = await startCall({
43
+ phoneNumber: body.phoneNumber ?? '',
44
+ task: body.task ?? '',
45
+ context: body.context,
46
+ conversationId: body.conversationId,
47
+ });
48
+
49
+ if (!result.ok) {
50
+ return Response.json({ error: result.error }, { status: result.status ?? 500 });
51
+ }
52
+
53
+ return Response.json({
54
+ callSessionId: result.session.id,
55
+ callSid: result.callSid,
56
+ status: result.session.status,
57
+ toNumber: result.session.toNumber,
58
+ fromNumber: result.session.fromNumber,
59
+ }, { status: 201 });
60
+ }
61
+
62
+ /**
63
+ * GET /v1/calls/:callSessionId
64
+ */
65
+ export function handleGetCallStatus(callSessionId: string): Response {
66
+ const result = getCallStatus(callSessionId);
67
+
68
+ if (!result.ok) {
69
+ return Response.json({ error: result.error }, { status: result.status ?? 500 });
70
+ }
71
+
72
+ const { session } = result;
73
+ return Response.json({
74
+ callSessionId: session.id,
75
+ conversationId: session.conversationId,
76
+ status: session.status,
77
+ toNumber: session.toNumber,
78
+ fromNumber: session.fromNumber,
79
+ provider: session.provider,
80
+ providerCallSid: session.providerCallSid,
81
+ task: session.task,
82
+ startedAt: session.startedAt ? new Date(session.startedAt).toISOString() : null,
83
+ endedAt: session.endedAt ? new Date(session.endedAt).toISOString() : null,
84
+ lastError: session.lastError,
85
+ pendingQuestion: result.pendingQuestion ?? null,
86
+ createdAt: new Date(session.createdAt).toISOString(),
87
+ updatedAt: new Date(session.updatedAt).toISOString(),
88
+ });
89
+ }
90
+
91
+ /**
92
+ * POST /v1/calls/:callSessionId/cancel
93
+ *
94
+ * Body: { reason?: string }
95
+ */
96
+ export async function handleCancelCall(req: Request, callSessionId: string): Promise<Response> {
97
+ let reason: string | undefined;
98
+ try {
99
+ const body = await req.json() as { reason?: string };
100
+ reason = body.reason;
101
+ } catch {
102
+ // Empty body is fine
103
+ }
104
+
105
+ const result = await cancelCall({ callSessionId, reason });
106
+
107
+ if (!result.ok) {
108
+ return Response.json({ error: result.error }, { status: result.status ?? 500 });
109
+ }
110
+
111
+ return Response.json({
112
+ callSessionId: result.session.id,
113
+ status: result.session.status,
114
+ });
115
+ }
116
+
117
+ /**
118
+ * POST /v1/calls/:callSessionId/answer
119
+ *
120
+ * Body: { answer: string }
121
+ */
122
+ export async function handleAnswerCall(req: Request, callSessionId: string): Promise<Response> {
123
+ let body: { answer?: string };
124
+ try {
125
+ body = await req.json() as typeof body;
126
+ } catch {
127
+ return Response.json({ error: 'Invalid JSON in request body' }, { status: 400 });
128
+ }
129
+
130
+ const result = await answerCall({
131
+ callSessionId,
132
+ answer: body.answer ?? '',
133
+ });
134
+
135
+ if (!result.ok) {
136
+ return Response.json({ error: result.error }, { status: result.status ?? 500 });
137
+ }
138
+
139
+ return Response.json({ ok: true, questionId: result.questionId });
140
+ }
@@ -18,7 +18,7 @@ import type {
18
18
 
19
19
  const log = getLogger('runtime-http');
20
20
 
21
- export async function handleDeleteConversation(assistantId: string, req: Request): Promise<Response> {
21
+ export async function handleDeleteConversation(req: Request): Promise<Response> {
22
22
  const body = await req.json() as {
23
23
  sourceChannel?: string;
24
24
  externalChatId?: string;
@@ -34,13 +34,12 @@ export async function handleDeleteConversation(assistantId: string, req: Request
34
34
  }
35
35
 
36
36
  const conversationKey = `${sourceChannel}:${externalChatId}`;
37
- deleteConversationKey(assistantId, conversationKey);
37
+ deleteConversationKey(conversationKey);
38
38
 
39
39
  return Response.json({ ok: true });
40
40
  }
41
41
 
42
42
  export async function handleChannelInbound(
43
- assistantId: string,
44
43
  req: Request,
45
44
  processMessage?: MessageProcessor,
46
45
  ): Promise<Response> {
@@ -90,7 +89,7 @@ export async function handleChannelInbound(
90
89
  }
91
90
 
92
91
  if (hasAttachments) {
93
- const resolved = attachmentsStore.getAttachmentsByIds(assistantId, attachmentIds);
92
+ const resolved = attachmentsStore.getAttachmentsByIds(attachmentIds);
94
93
  if (resolved.length !== attachmentIds.length) {
95
94
  const resolvedIds = new Set(resolved.map((a) => a.id));
96
95
  const missing = attachmentIds.filter((id) => !resolvedIds.has(id));
@@ -113,7 +112,6 @@ export async function handleChannelInbound(
113
112
  if (isEdit && sourceMessageId) {
114
113
  // Dedup the edit event itself (retried edited_message webhooks)
115
114
  const editResult = channelDeliveryStore.recordInbound(
116
- assistantId,
117
115
  sourceChannel,
118
116
  externalChatId,
119
117
  externalMessageId,
@@ -137,7 +135,6 @@ export async function handleChannelInbound(
137
135
  let original: { messageId: string; conversationId: string } | null = null;
138
136
  for (let attempt = 0; attempt <= EDIT_LOOKUP_RETRIES; attempt++) {
139
137
  original = channelDeliveryStore.findMessageBySourceId(
140
- assistantId,
141
138
  sourceChannel,
142
139
  externalChatId,
143
140
  sourceMessageId,
@@ -145,7 +142,7 @@ export async function handleChannelInbound(
145
142
  if (original) break;
146
143
  if (attempt < EDIT_LOOKUP_RETRIES) {
147
144
  log.info(
148
- { assistantId, sourceMessageId, attempt: attempt + 1, maxAttempts: EDIT_LOOKUP_RETRIES },
145
+ { assistantId: "self", sourceMessageId, attempt: attempt + 1, maxAttempts: EDIT_LOOKUP_RETRIES },
149
146
  'Original message not linked yet, retrying edit lookup',
150
147
  );
151
148
  await new Promise((resolve) => setTimeout(resolve, EDIT_LOOKUP_DELAY_MS));
@@ -155,12 +152,12 @@ export async function handleChannelInbound(
155
152
  if (original) {
156
153
  conversationStore.updateMessageContent(original.messageId, content ?? '');
157
154
  log.info(
158
- { assistantId, sourceMessageId, messageId: original.messageId },
155
+ { assistantId: "self", sourceMessageId, messageId: original.messageId },
159
156
  'Updated message content from edited_message',
160
157
  );
161
158
  } else {
162
159
  log.warn(
163
- { assistantId, sourceChannel, externalChatId, sourceMessageId },
160
+ { assistantId: "self", sourceChannel, externalChatId, sourceMessageId },
164
161
  'Could not find original message for edit after retries, ignoring',
165
162
  );
166
163
  }
@@ -174,7 +171,6 @@ export async function handleChannelInbound(
174
171
 
175
172
  // ── New message path ──
176
173
  const result = channelDeliveryStore.recordInbound(
177
- assistantId,
178
174
  sourceChannel,
179
175
  externalChatId,
180
176
  externalMessageId,
@@ -221,7 +217,6 @@ export async function handleChannelInbound(
221
217
  }
222
218
 
223
219
  const { messageId: userMessageId } = await processMessage(
224
- assistantId,
225
220
  result.conversationId,
226
221
  content ?? '',
227
222
  hasAttachments ? attachmentIds : undefined,
@@ -241,7 +236,6 @@ export async function handleChannelInbound(
241
236
  } catch (err) {
242
237
  // Secret ingress blocks are not retryable — let the top-level handler return 422
243
238
  if (err instanceof IngressBlockedError) throw err;
244
- console.error(`[runtime-http] Processing failed`, err);
245
239
  log.error({ err, conversationId: result.conversationId }, 'Failed to process channel inbound message');
246
240
  channelDeliveryStore.recordProcessingFailure(result.eventId, err);
247
241
  }
@@ -259,7 +253,7 @@ export async function handleChannelInbound(
259
253
  try { parsed = JSON.parse(msgs[i].content); } catch { parsed = msgs[i].content; }
260
254
  const rendered = renderHistoryContent(parsed);
261
255
 
262
- const linked = attachmentsStore.getAttachmentMetadataForMessage(msgs[i].id, assistantId);
256
+ const linked = attachmentsStore.getAttachmentMetadataForMessage(msgs[i].id);
263
257
  const replyAttachments: RuntimeAttachmentMetadata[] = linked.map((a) => ({
264
258
  id: a.id,
265
259
  filename: a.originalFilename,
@@ -291,12 +285,12 @@ export async function handleChannelInbound(
291
285
  });
292
286
  }
293
287
 
294
- export function handleListDeadLetters(assistantId: string): Response {
295
- const events = channelDeliveryStore.getDeadLetterEvents(assistantId);
288
+ export function handleListDeadLetters(): Response {
289
+ const events = channelDeliveryStore.getDeadLetterEvents();
296
290
  return Response.json({ events });
297
291
  }
298
292
 
299
- export async function handleReplayDeadLetters(assistantId: string, req: Request): Promise<Response> {
293
+ export async function handleReplayDeadLetters(req: Request): Promise<Response> {
300
294
  const body = await req.json() as { eventIds?: string[] };
301
295
  const eventIds = body.eventIds;
302
296
 
@@ -304,11 +298,11 @@ export async function handleReplayDeadLetters(assistantId: string, req: Request)
304
298
  return Response.json({ error: 'eventIds array is required' }, { status: 400 });
305
299
  }
306
300
 
307
- const replayed = channelDeliveryStore.replayDeadLetters(assistantId, eventIds);
301
+ const replayed = channelDeliveryStore.replayDeadLetters(eventIds);
308
302
  return Response.json({ replayed });
309
303
  }
310
304
 
311
- export async function handleChannelDeliveryAck(assistantId: string, req: Request): Promise<Response> {
305
+ export async function handleChannelDeliveryAck(req: Request): Promise<Response> {
312
306
  const body = await req.json() as {
313
307
  sourceChannel?: string;
314
308
  externalChatId?: string;
@@ -328,7 +322,6 @@ export async function handleChannelDeliveryAck(assistantId: string, req: Request
328
322
  }
329
323
 
330
324
  const acked = channelDeliveryStore.acknowledgeDelivery(
331
- assistantId,
332
325
  sourceChannel,
333
326
  externalChatId,
334
327
  externalMessageId,
@@ -43,7 +43,6 @@ function getInterfaceFilesWithMtimes(interfacesDir: string | null): Array<{ path
43
43
  }
44
44
 
45
45
  export function handleListMessages(
46
- assistantId: string,
47
46
  url: URL,
48
47
  interfacesDir: string | null,
49
48
  ): Response {
@@ -55,7 +54,7 @@ export function handleListMessages(
55
54
  );
56
55
  }
57
56
 
58
- const mapping = getConversationByKey(assistantId, conversationKey);
57
+ const mapping = getConversationByKey(conversationKey);
59
58
  if (!mapping) {
60
59
  return Response.json({ messages: [] });
61
60
  }
@@ -90,7 +89,7 @@ export function handleListMessages(
90
89
  const messages: RuntimeMessagePayload[] = merged.map((m) => {
91
90
  let msgAttachments: RuntimeAttachmentMetadata[] = [];
92
91
  if (m.role === 'assistant' && m.id) {
93
- const linked = attachmentsStore.getAttachmentMetadataForMessage(m.id, assistantId);
92
+ const linked = attachmentsStore.getAttachmentMetadataForMessage(m.id);
94
93
  if (linked.length > 0) {
95
94
  msgAttachments = linked.map((a) => ({
96
95
  id: a.id,
@@ -129,7 +128,6 @@ export function handleListMessages(
129
128
  }
130
129
 
131
130
  export async function handleSendMessage(
132
- assistantId: string,
133
131
  req: Request,
134
132
  deps: {
135
133
  processMessage?: MessageProcessor;
@@ -172,7 +170,7 @@ export async function handleSendMessage(
172
170
 
173
171
  // Validate that all attachment IDs resolve
174
172
  if (hasAttachments) {
175
- const resolved = attachmentsStore.getAttachmentsByIds(assistantId, attachmentIds);
173
+ const resolved = attachmentsStore.getAttachmentsByIds(attachmentIds);
176
174
  if (resolved.length !== attachmentIds.length) {
177
175
  const resolvedIds = new Set(resolved.map((a) => a.id));
178
176
  const missing = attachmentIds.filter((id) => !resolvedIds.has(id));
@@ -183,7 +181,7 @@ export async function handleSendMessage(
183
181
  }
184
182
  }
185
183
 
186
- const mapping = getOrCreateConversation(assistantId, conversationKey);
184
+ const mapping = getOrCreateConversation(conversationKey);
187
185
 
188
186
  const processor = deps.persistAndProcessMessage ?? deps.processMessage;
189
187
  if (!processor) {
@@ -192,7 +190,6 @@ export async function handleSendMessage(
192
190
 
193
191
  try {
194
192
  const result = await processor(
195
- assistantId,
196
193
  mapping.conversationId,
197
194
  content ?? '',
198
195
  hasAttachments ? attachmentIds : undefined,
@@ -236,7 +233,6 @@ async function generateLlmSuggestion(provider: Provider, assistantText: string):
236
233
  }
237
234
 
238
235
  export async function handleGetSuggestion(
239
- assistantId: string,
240
236
  url: URL,
241
237
  deps: {
242
238
  suggestionCache: Map<string, string>;
@@ -251,7 +247,7 @@ export async function handleGetSuggestion(
251
247
  );
252
248
  }
253
249
 
254
- const mapping = getConversationByKey(assistantId, conversationKey);
250
+ const mapping = getConversationByKey(conversationKey);
255
251
  if (!mapping) {
256
252
  return Response.json({ suggestion: null, messageId: null, source: 'none' as const });
257
253
  }
@@ -11,7 +11,6 @@ import type { RunOrchestrator } from '../run-orchestrator.js';
11
11
  const log = getLogger('runtime-http');
12
12
 
13
13
  export async function handleCreateRun(
14
- assistantId: string,
15
14
  req: Request,
16
15
  runOrchestrator: RunOrchestrator,
17
16
  ): Promise<Response> {
@@ -39,7 +38,7 @@ export async function handleCreateRun(
39
38
  }
40
39
 
41
40
  if (hasAttachments) {
42
- const resolved = attachmentsStore.getAttachmentsByIds(assistantId, attachmentIds);
41
+ const resolved = attachmentsStore.getAttachmentsByIds(attachmentIds);
43
42
  if (resolved.length !== attachmentIds.length) {
44
43
  const resolvedIds = new Set(resolved.map((a) => a.id));
45
44
  const missing = attachmentIds.filter((id) => !resolvedIds.has(id));
@@ -50,11 +49,10 @@ export async function handleCreateRun(
50
49
  }
51
50
  }
52
51
 
53
- const mapping = getOrCreateConversation(assistantId, conversationKey);
52
+ const mapping = getOrCreateConversation(conversationKey);
54
53
 
55
54
  try {
56
55
  const run = await runOrchestrator.startRun(
57
- assistantId,
58
56
  mapping.conversationId,
59
57
  content ?? '',
60
58
  hasAttachments ? attachmentIds : undefined,
@@ -77,12 +75,11 @@ export async function handleCreateRun(
77
75
  }
78
76
 
79
77
  export function handleGetRun(
80
- assistantId: string,
81
78
  runId: string,
82
79
  runOrchestrator: RunOrchestrator,
83
80
  ): Response {
84
81
  const run = runOrchestrator.getRun(runId);
85
- if (!run || run.assistantId !== assistantId) {
82
+ if (!run) {
86
83
  return Response.json({ error: 'Run not found' }, { status: 404 });
87
84
  }
88
85
 
@@ -98,13 +95,12 @@ export function handleGetRun(
98
95
  }
99
96
 
100
97
  export async function handleRunDecision(
101
- assistantId: string,
102
98
  runId: string,
103
99
  req: Request,
104
100
  runOrchestrator: RunOrchestrator,
105
101
  ): Promise<Response> {
106
102
  const run = runOrchestrator.getRun(runId);
107
- if (!run || run.assistantId !== assistantId) {
103
+ if (!run) {
108
104
  return Response.json({ error: 'Run not found' }, { status: 404 });
109
105
  }
110
106
 
@@ -14,10 +14,13 @@ import * as runsStore from '../memory/runs-store.js';
14
14
  import type { Run } from '../memory/runs-store.js';
15
15
  import type { Session } from '../daemon/session.js';
16
16
  import type { ServerMessage } from '../daemon/ipc-protocol.js';
17
+ import { resolveChannelCapabilities } from '../daemon/session-runtime-assembly.js';
17
18
  import type { UserDecision } from '../permissions/types.js';
18
19
  import { checkIngressForSecrets } from '../security/secret-ingress.js';
19
20
  import { IngressBlockedError } from '../util/errors.js';
20
21
  import { getLogger } from '../util/logger.js';
22
+ import { assistantEventHub } from './assistant-event-hub.js';
23
+ import { buildAssistantEvent } from './assistant-event.js';
21
24
 
22
25
  const log = getLogger('run-orchestrator');
23
26
 
@@ -32,7 +35,7 @@ interface PendingRunState {
32
35
 
33
36
  export interface RunOrchestratorDeps {
34
37
  getOrCreateSession: (conversationId: string) => Promise<Session>;
35
- resolveAttachments: (assistantId: string, attachmentIds: string[]) => Array<{
38
+ resolveAttachments: (attachmentIds: string[]) => Array<{
36
39
  id: string;
37
40
  filename: string;
38
41
  mimeType: string;
@@ -64,7 +67,6 @@ export class RunOrchestrator {
64
67
  * and fire the agent loop in the background.
65
68
  */
66
69
  async startRun(
67
- assistantId: string,
68
70
  conversationId: string,
69
71
  content: string,
70
72
  attachmentIds?: string[],
@@ -82,15 +84,34 @@ export class RunOrchestrator {
82
84
  }
83
85
 
84
86
  const attachments = attachmentIds
85
- ? this.deps.resolveAttachments(assistantId, attachmentIds)
87
+ ? this.deps.resolveAttachments(attachmentIds)
86
88
  : [];
87
89
 
88
90
  const requestId = crypto.randomUUID();
89
91
  const messageId = session.persistUserMessage(content, attachments, requestId);
90
- const run = runsStore.createRun(assistantId, conversationId, messageId);
92
+ const run = runsStore.createRun(conversationId, messageId);
93
+
94
+ // Runs are always HTTP-originated; set channel capabilities so the attachment
95
+ // scope heuristic resolves to 'self' rather than 'local-assistant'.
96
+ session.setChannelCapabilities(resolveChannelCapabilities('http-api'));
97
+
98
+ // Serialized publish chain so hub subscribers observe events in order.
99
+ let hubChain: Promise<void> = Promise.resolve();
100
+ const publishToHub = (msg: ServerMessage): void => {
101
+ const msgRecord = msg as unknown as Record<string, unknown>;
102
+ const msgSessionId =
103
+ 'sessionId' in msg && typeof msgRecord.sessionId === 'string'
104
+ ? (msgRecord.sessionId as string)
105
+ : undefined;
106
+ const resolvedSessionId = msgSessionId ?? conversationId;
107
+ const event = buildAssistantEvent('self', msg, resolvedSessionId);
108
+ hubChain = hubChain
109
+ .then(() => assistantEventHub.publish(event))
110
+ .catch((err: unknown) => {
111
+ log.warn({ err }, 'assistant-events hub subscriber threw during HTTP run');
112
+ });
113
+ };
91
114
 
92
- // Set the assistant ID so attachments are scoped correctly.
93
- session.setAssistantId(assistantId);
94
115
 
95
116
  // Hook into session to intercept confirmation_request events.
96
117
  // When the prompter sends a confirmation_request, we record it in the
@@ -118,11 +139,17 @@ export class RunOrchestrator {
118
139
  session,
119
140
  });
120
141
  }
142
+ // Mirror every outbound message to the assistant-events hub so SSE
143
+ // subscribers receive the same payload parity as IPC clients.
144
+ publishToHub(msg);
121
145
  });
122
146
 
123
147
  // Fire-and-forget the agent loop
124
148
  const cleanup = () => {
125
149
  this.pending.delete(run.id);
150
+ // Reset channel capabilities so a subsequent IPC/desktop session on the
151
+ // same conversation is not incorrectly treated as an HTTP-API client.
152
+ session.setChannelCapabilities(null);
126
153
  // Reset the session's client callback to a no-op so the stale
127
154
  // closure doesn't intercept events from future runs on the same session.
128
155
  // Set hasNoClient=true here since the run is done and no HTTP caller
@@ -136,6 +163,12 @@ export class RunOrchestrator {
136
163
  } else if (msg.type === 'session_error') {
137
164
  lastError = msg.userMessage;
138
165
  }
166
+ // Mirror agent-loop events (assistant_text_delta, message_complete,
167
+ // tool_use_start, tool_result, etc.) to the hub. These travel through
168
+ // the onEvent path, distinct from the updateClient path used by the
169
+ // prompter (confirmation_request). Both paths must publish so SSE
170
+ // consumers receive the full response stream.
171
+ publishToHub(msg);
139
172
  }).then(() => {
140
173
  if (lastError) {
141
174
  log.error({ runId: run.id, error: lastError }, 'Run failed (error event from agent loop)');