vellum 0.2.1 → 0.2.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 (361) hide show
  1. package/README.md +15 -2
  2. package/bun.lock +71 -100
  3. package/package.json +5 -3
  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 +133 -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__/asset-materialize-tool.test.ts +16 -15
  11. package/src/__tests__/asset-search-tool.test.ts +23 -22
  12. package/src/__tests__/attachments-store.test.ts +56 -127
  13. package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +5 -4
  14. package/src/__tests__/browser-skill-endstate.test.ts +4 -3
  15. package/src/__tests__/call-bridge.test.ts +385 -0
  16. package/src/__tests__/call-constants.test.ts +40 -0
  17. package/src/__tests__/call-orchestrator.test.ts +130 -4
  18. package/src/__tests__/call-recovery.test.ts +518 -0
  19. package/src/__tests__/call-routes-http.test.ts +459 -0
  20. package/src/__tests__/call-state-machine.test.ts +143 -0
  21. package/src/__tests__/call-store.test.ts +216 -1
  22. package/src/__tests__/cli-discover.test.ts +1 -1
  23. package/src/__tests__/commit-message-enrichment-service.test.ts +148 -7
  24. package/src/__tests__/compaction.benchmark.test.ts +176 -0
  25. package/src/__tests__/computer-use-tools.test.ts +250 -0
  26. package/src/__tests__/config-schema.test.ts +305 -3
  27. package/src/__tests__/conflict-store.test.ts +2 -1
  28. package/src/__tests__/contacts-tools.test.ts +331 -0
  29. package/src/__tests__/conversation-store.test.ts +30 -32
  30. package/src/__tests__/credential-security-invariants.test.ts +4 -0
  31. package/src/__tests__/date-context.test.ts +373 -0
  32. package/src/__tests__/db-schedule-syntax-migration.test.ts +129 -0
  33. package/src/__tests__/fixtures/media-reuse-fixtures.ts +3 -3
  34. package/src/__tests__/followup-tools.test.ts +303 -0
  35. package/src/__tests__/handlers-twilio-config.test.ts +221 -0
  36. package/src/__tests__/handlers-twitter-config.test.ts +718 -0
  37. package/src/__tests__/intent-routing.test.ts +64 -57
  38. package/src/__tests__/ipc-roundtrip.benchmark.test.ts +237 -0
  39. package/src/__tests__/ipc-snapshot.test.ts +71 -28
  40. package/src/__tests__/llm-usage-store.test.ts +3 -8
  41. package/src/__tests__/media-generate-image.test.ts +1 -1
  42. package/src/__tests__/media-reuse-story.e2e.test.ts +7 -7
  43. package/src/__tests__/memory-regressions.test.ts +100 -2
  44. package/src/__tests__/memory-retrieval.benchmark.test.ts +430 -0
  45. package/src/__tests__/parallel-tool.benchmark.test.ts +294 -0
  46. package/src/__tests__/playbook-tools.test.ts +342 -0
  47. package/src/__tests__/profile-compiler.test.ts +2 -1
  48. package/src/__tests__/provider-commit-message-generator.test.ts +303 -0
  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 +5 -3
  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 +8 -4
  58. package/src/__tests__/run-orchestrator.test.ts +4 -4
  59. package/src/__tests__/runtime-attachment-metadata.test.ts +7 -6
  60. package/src/__tests__/runtime-runs-http.test.ts +4 -4
  61. package/src/__tests__/runtime-runs.test.ts +4 -4
  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-conflict-gate.test.ts +28 -25
  67. package/src/__tests__/session-error.test.ts +28 -0
  68. package/src/__tests__/session-init.benchmark.test.ts +462 -0
  69. package/src/__tests__/session-queue.test.ts +71 -48
  70. package/src/__tests__/session-runtime-assembly.test.ts +161 -0
  71. package/src/__tests__/session-surfaces-task-progress.test.ts +104 -0
  72. package/src/__tests__/signup-e2e.test.ts +2 -1
  73. package/src/__tests__/skill-projection.benchmark.test.ts +328 -0
  74. package/src/__tests__/skill-script-runner.test.ts +159 -0
  75. package/src/__tests__/speaker-identification.test.ts +52 -0
  76. package/src/__tests__/subagent-manager-notify.test.ts +42 -10
  77. package/src/__tests__/subagent-tools.test.ts +141 -41
  78. package/src/__tests__/task-compiler.test.ts +2 -1
  79. package/src/__tests__/task-runner.test.ts +2 -1
  80. package/src/__tests__/task-scheduler.test.ts +2 -1
  81. package/src/__tests__/task-tools.test.ts +49 -56
  82. package/src/__tests__/tool-audit-listener.test.ts +1 -0
  83. package/src/__tests__/tool-domain-event-publisher.test.ts +2 -0
  84. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +500 -0
  85. package/src/__tests__/tool-executor.test.ts +13 -17
  86. package/src/__tests__/turn-commit.test.ts +218 -3
  87. package/src/__tests__/twilio-provider.test.ts +143 -0
  88. package/src/__tests__/twilio-routes.test.ts +789 -0
  89. package/src/__tests__/twitter-auth-handler.test.ts +581 -0
  90. package/src/__tests__/view-image-tool.test.ts +217 -0
  91. package/src/__tests__/workspace-git-service.test.ts +186 -0
  92. package/src/__tests__/workspace-heartbeat-service.test.ts +13 -3
  93. package/src/agent-heartbeat/agent-heartbeat-service.ts +155 -0
  94. package/src/bundler/app-bundler.ts +12 -8
  95. package/src/calls/__tests__/twilio-webhook-urls.test.ts +162 -0
  96. package/src/calls/call-bridge.ts +95 -0
  97. package/src/calls/call-constants.ts +43 -5
  98. package/src/calls/call-domain.ts +276 -0
  99. package/src/calls/call-orchestrator.ts +43 -17
  100. package/src/calls/call-recovery.ts +207 -0
  101. package/src/calls/call-state-machine.ts +68 -0
  102. package/src/calls/call-store.ts +192 -5
  103. package/src/calls/relay-server.ts +41 -4
  104. package/src/calls/speaker-identification.ts +213 -0
  105. package/src/calls/twilio-config.ts +8 -8
  106. package/src/calls/twilio-provider.ts +13 -9
  107. package/src/calls/twilio-routes.ts +90 -76
  108. package/src/calls/twilio-webhook-urls.ts +50 -0
  109. package/src/calls/types.ts +1 -1
  110. package/src/cli/config-commands.ts +334 -0
  111. package/src/cli/core-commands.ts +776 -0
  112. package/src/cli/doordash.ts +251 -1
  113. package/src/cli/ipc-client.ts +82 -0
  114. package/src/cli/map.ts +270 -0
  115. package/src/cli/twitter.ts +575 -0
  116. package/src/cli.ts +7 -5
  117. package/src/commands/__tests__/cc-command-registry.test.ts +319 -0
  118. package/src/commands/cc-command-registry.ts +209 -0
  119. package/src/config/bundled-skills/contacts/SKILL.md +39 -0
  120. package/src/config/bundled-skills/contacts/TOOLS.json +122 -0
  121. package/src/config/bundled-skills/contacts/tools/contact-merge.ts +9 -0
  122. package/src/config/bundled-skills/contacts/tools/contact-search.ts +9 -0
  123. package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +9 -0
  124. package/src/config/bundled-skills/document/SKILL.md +18 -0
  125. package/src/config/bundled-skills/document/TOOLS.json +53 -0
  126. package/src/config/bundled-skills/document/tools/document-create.ts +9 -0
  127. package/src/config/bundled-skills/document/tools/document-update.ts +9 -0
  128. package/src/config/bundled-skills/doordash/SKILL.md +82 -23
  129. package/src/config/bundled-skills/followups/SKILL.md +32 -0
  130. package/src/config/bundled-skills/followups/TOOLS.json +100 -0
  131. package/src/config/bundled-skills/followups/tools/followup-create.ts +9 -0
  132. package/src/config/bundled-skills/followups/tools/followup-list.ts +9 -0
  133. package/src/config/bundled-skills/followups/tools/followup-resolve.ts +9 -0
  134. package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +1 -23
  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 +34 -0
  179. package/src/config/loader.ts +4 -1
  180. package/src/config/schema.ts +165 -1
  181. package/src/config/system-prompt.ts +61 -16
  182. package/src/config/templates/IDENTITY.md +7 -0
  183. package/src/config/types.ts +4 -0
  184. package/src/config/vellum-skills/telegram-setup/SKILL.md +1 -5
  185. package/src/contacts/contact-store.ts +4 -4
  186. package/src/daemon/assistant-attachments.ts +10 -0
  187. package/src/daemon/classifier.ts +3 -1
  188. package/src/daemon/computer-use-session.ts +3 -1
  189. package/src/daemon/date-context.ts +136 -0
  190. package/src/daemon/handlers/apps.ts +16 -1
  191. package/src/daemon/handlers/browser.ts +54 -0
  192. package/src/daemon/handlers/computer-use.ts +7 -1
  193. package/src/daemon/handlers/config.ts +205 -5
  194. package/src/daemon/handlers/diagnostics.ts +5 -1
  195. package/src/daemon/handlers/documents.ts +18 -29
  196. package/src/daemon/handlers/home-base.ts +5 -1
  197. package/src/daemon/handlers/index.ts +40 -277
  198. package/src/daemon/handlers/misc.ts +9 -1
  199. package/src/daemon/handlers/publish.ts +6 -1
  200. package/src/daemon/handlers/sessions.ts +65 -12
  201. package/src/daemon/handlers/shared.ts +36 -1
  202. package/src/daemon/handlers/signing.ts +37 -0
  203. package/src/daemon/handlers/skills.ts +20 -6
  204. package/src/daemon/handlers/subagents.ts +8 -3
  205. package/src/daemon/handlers/twitter-auth.ts +169 -0
  206. package/src/daemon/handlers/work-items.ts +384 -68
  207. package/src/daemon/ipc-contract-inventory.json +32 -4
  208. package/src/daemon/ipc-contract.ts +156 -37
  209. package/src/daemon/ipc-protocol.ts +7 -2
  210. package/src/daemon/lifecycle.ts +21 -0
  211. package/src/daemon/main.ts +10 -4
  212. package/src/daemon/ride-shotgun-handler.ts +75 -10
  213. package/src/daemon/server.ts +143 -26
  214. package/src/daemon/session-agent-loop.ts +922 -0
  215. package/src/daemon/session-attachments.ts +28 -5
  216. package/src/daemon/session-conflict-gate.ts +18 -109
  217. package/src/daemon/session-error.ts +24 -3
  218. package/src/daemon/session-lifecycle.ts +147 -0
  219. package/src/daemon/session-media-retry.ts +147 -0
  220. package/src/daemon/session-messaging.ts +145 -0
  221. package/src/daemon/session-notifiers.ts +164 -0
  222. package/src/daemon/session-process.ts +2 -2
  223. package/src/daemon/session-queue-manager.ts +1 -0
  224. package/src/daemon/session-runtime-assembly.ts +52 -0
  225. package/src/daemon/session-skill-tools.ts +124 -5
  226. package/src/daemon/session-slash.ts +3 -0
  227. package/src/daemon/session-surfaces.ts +77 -2
  228. package/src/daemon/session-tool-setup.ts +216 -2
  229. package/src/daemon/session-usage.ts +0 -2
  230. package/src/daemon/session.ts +114 -1404
  231. package/src/daemon/video-thumbnail.ts +60 -0
  232. package/src/doordash/client.ts +121 -27
  233. package/src/doordash/queries.ts +1 -2
  234. package/src/export/formatter.ts +3 -1
  235. package/src/followups/followup-store.ts +4 -2
  236. package/src/followups/types.ts +6 -0
  237. package/src/hooks/templates.ts +1 -1
  238. package/src/index.ts +32 -1153
  239. package/src/memory/attachments-store.ts +28 -83
  240. package/src/memory/channel-delivery-store.ts +7 -21
  241. package/src/memory/clarification-resolver.ts +6 -5
  242. package/src/memory/conflict-intent.ts +114 -0
  243. package/src/memory/contradiction-checker.ts +3 -2
  244. package/src/memory/conversation-key-store.ts +10 -29
  245. package/src/memory/conversation-store.ts +2 -1
  246. package/src/memory/db.ts +96 -2
  247. package/src/memory/entity-extractor.ts +6 -3
  248. package/src/memory/items-extractor.ts +5 -4
  249. package/src/memory/job-handlers/conflict.ts +23 -1
  250. package/src/memory/jobs-store.ts +3 -2
  251. package/src/memory/llm-usage-store.ts +1 -2
  252. package/src/memory/runs-store.ts +1 -2
  253. package/src/memory/schema.ts +23 -2
  254. package/src/messaging/style-analyzer.ts +3 -2
  255. package/src/messaging/thread-summarizer.ts +8 -12
  256. package/src/messaging/triage-engine.ts +4 -2
  257. package/src/providers/openrouter/client.ts +20 -0
  258. package/src/providers/registry.ts +8 -0
  259. package/src/runtime/gateway-client.ts +36 -0
  260. package/src/runtime/http-server.ts +166 -22
  261. package/src/runtime/routes/attachment-routes.ts +2 -3
  262. package/src/runtime/routes/call-routes.ts +140 -0
  263. package/src/runtime/routes/channel-routes.ts +125 -88
  264. package/src/runtime/routes/conversation-routes.ts +5 -5
  265. package/src/runtime/routes/run-routes.ts +2 -2
  266. package/src/runtime/run-orchestrator.ts +9 -3
  267. package/src/schedule/recurrence-engine.ts +138 -0
  268. package/src/schedule/recurrence-types.ts +67 -0
  269. package/src/schedule/schedule-store.ts +102 -57
  270. package/src/schedule/scheduler.ts +9 -6
  271. package/src/security/oauth2.ts +29 -4
  272. package/src/security/secret-allowlist.ts +46 -0
  273. package/src/skills/clawhub.ts +1 -1
  274. package/src/subagent/manager.ts +40 -8
  275. package/src/swarm/backend-claude-code.ts +64 -9
  276. package/src/swarm/worker-prompts.ts +2 -1
  277. package/src/tasks/SPEC.md +34 -28
  278. package/src/tasks/ephemeral-permissions.ts +16 -7
  279. package/src/tasks/task-compiler.ts +5 -4
  280. package/src/tasks/task-runner.ts +10 -5
  281. package/src/tasks/task-scheduler.ts +1 -1
  282. package/src/tasks/tool-sanitizer.ts +36 -0
  283. package/src/tools/assets/search.ts +4 -4
  284. package/src/tools/browser/api-map.ts +293 -0
  285. package/src/tools/browser/auto-navigate.ts +270 -0
  286. package/src/tools/browser/browser-execution.ts +2 -1
  287. package/src/tools/browser/browser-manager.ts +2 -2
  288. package/src/tools/browser/network-recorder.ts +5 -4
  289. package/src/tools/browser/x-auto-navigate.ts +207 -0
  290. package/src/tools/calls/call-end.ts +17 -67
  291. package/src/tools/calls/call-start.ts +24 -85
  292. package/src/tools/calls/call-status.ts +35 -51
  293. package/src/tools/claude-code/claude-code.ts +207 -11
  294. package/src/tools/contacts/contact-merge.ts +46 -78
  295. package/src/tools/contacts/contact-search.ts +35 -79
  296. package/src/tools/contacts/contact-upsert.ts +35 -108
  297. package/src/tools/credentials/vault.ts +20 -4
  298. package/src/tools/document/document-tool.ts +71 -144
  299. package/src/tools/executor.ts +129 -10
  300. package/src/tools/followups/followup_create.ts +46 -88
  301. package/src/tools/followups/followup_list.ts +34 -74
  302. package/src/tools/followups/followup_resolve.ts +31 -66
  303. package/src/tools/host-terminal/cli-discover.ts +2 -1
  304. package/src/tools/host-terminal/host-shell.ts +10 -0
  305. package/src/tools/memory/handlers.ts +5 -4
  306. package/src/tools/network/__tests__/web-search.test.ts +427 -0
  307. package/src/tools/network/script-proxy/__tests__/logging.test.ts +248 -0
  308. package/src/tools/network/script-proxy/__tests__/policy.test.ts +234 -0
  309. package/src/tools/network/script-proxy/__tests__/router.test.ts +76 -0
  310. package/src/tools/network/web-fetch.ts +18 -6
  311. package/src/tools/playbooks/index.ts +4 -5
  312. package/src/tools/playbooks/playbook-create.ts +3 -47
  313. package/src/tools/playbooks/playbook-delete.ts +1 -25
  314. package/src/tools/playbooks/playbook-list.ts +1 -28
  315. package/src/tools/playbooks/playbook-update.ts +3 -51
  316. package/src/tools/reminder/reminder.ts +5 -78
  317. package/src/tools/schedule/create.ts +69 -74
  318. package/src/tools/schedule/delete.ts +21 -47
  319. package/src/tools/schedule/list.ts +55 -74
  320. package/src/tools/schedule/update.ts +77 -84
  321. package/src/tools/subagent/abort.ts +29 -58
  322. package/src/tools/subagent/message.ts +30 -63
  323. package/src/tools/subagent/read.ts +53 -84
  324. package/src/tools/subagent/spawn.ts +43 -82
  325. package/src/tools/subagent/status.ts +42 -71
  326. package/src/tools/swarm/delegate.ts +2 -1
  327. package/src/tools/tasks/index.ts +8 -8
  328. package/src/tools/tasks/task-delete.ts +60 -88
  329. package/src/tools/tasks/task-list.ts +31 -52
  330. package/src/tools/tasks/task-run.ts +72 -108
  331. package/src/tools/tasks/task-save.ts +33 -65
  332. package/src/tools/tasks/work-item-enqueue.ts +183 -215
  333. package/src/tools/tasks/work-item-list.ts +33 -63
  334. package/src/tools/tasks/work-item-remove.ts +45 -97
  335. package/src/tools/tasks/work-item-update.ts +91 -163
  336. package/src/tools/terminal/backends/native.ts +3 -1
  337. package/src/tools/tool-manifest.ts +0 -62
  338. package/src/tools/types.ts +6 -0
  339. package/src/tools/ui-surface/definitions.ts +3 -1
  340. package/src/tools/watch/screen-watch.ts +3 -1
  341. package/src/tools/watcher/create.ts +52 -98
  342. package/src/tools/watcher/delete.ts +20 -46
  343. package/src/tools/watcher/digest.ts +36 -70
  344. package/src/tools/watcher/list.ts +49 -79
  345. package/src/tools/watcher/update.ts +45 -91
  346. package/src/twitter/client.ts +690 -0
  347. package/src/twitter/session.ts +91 -0
  348. package/src/usage/types.ts +0 -1
  349. package/src/util/truncate.ts +6 -0
  350. package/src/watcher/providers/slack.ts +2 -1
  351. package/src/watcher/watcher-store.ts +3 -2
  352. package/src/work-items/work-item-store.ts +27 -2
  353. package/src/workspace/commit-message-enrichment-service.ts +31 -7
  354. package/src/workspace/git-service.ts +87 -22
  355. package/src/workspace/provider-commit-message-generator.ts +269 -0
  356. package/src/workspace/turn-commit.ts +62 -3
  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
@@ -10,8 +10,9 @@ import { resolve } from 'node:path';
10
10
  import { timingSafeEqual } from 'node:crypto';
11
11
  import { ConfigError, IngressBlockedError } from '../util/errors.js';
12
12
  import { getLogger } from '../util/logger.js';
13
- import { getSecureKey } from '../security/secure-keys.js';
14
13
  import { TwilioConversationRelayProvider } from '../calls/twilio-provider.js';
14
+ import { loadConfig } from '../config/loader.js';
15
+ import { getWebhookBaseUrl } from '../calls/twilio-webhook-urls.js';
15
16
  import type { RunOrchestrator } from './run-orchestrator.js';
16
17
 
17
18
  // Route handlers — grouped by domain
@@ -39,6 +40,10 @@ import {
39
40
  handleReplayDeadLetters,
40
41
  } from './routes/channel-routes.js';
41
42
  import * as channelDeliveryStore from '../memory/channel-delivery-store.js';
43
+ import * as conversationStore from '../memory/conversation-store.js';
44
+ import * as attachmentsStore from '../memory/attachments-store.js';
45
+ import { renderHistoryContent } from '../daemon/handlers.js';
46
+ import { deliverChannelReply } from './gateway-client.js';
42
47
  import {
43
48
  handleServePage,
44
49
  handleShareApp,
@@ -47,11 +52,16 @@ import {
47
52
  handleDeleteSharedApp,
48
53
  } from './routes/app-routes.js';
49
54
  import { handleAddSecret } from './routes/secret-routes.js';
55
+ import {
56
+ handleStartCall,
57
+ handleGetCallStatus,
58
+ handleCancelCall,
59
+ handleAnswerCall,
60
+ } from './routes/call-routes.js';
50
61
  import {
51
62
  handleVoiceWebhook,
52
63
  handleStatusCallback,
53
64
  handleConnectAction,
54
- handleCallAnswer,
55
65
  } from '../calls/twilio-routes.js';
56
66
  import { RelayConnection, activeRelayConnections } from '../calls/relay-server.js';
57
67
  import type { RelayWebSocketData } from '../calls/relay-server.js';
@@ -113,25 +123,50 @@ function getDiskSpaceInfo(): DiskSpaceInfo | null {
113
123
  */
114
124
  const TWILIO_WEBHOOK_RE = /^\/v1\/(?:assistants\/[^/]+\/)?calls\/twilio\/(.+)$/;
115
125
 
126
+ /**
127
+ * Gateway-compatible Twilio webhook paths:
128
+ * /webhooks/twilio/<subpath>
129
+ *
130
+ * Maps gateway path segments to the internal subpath names used by the
131
+ * dispatcher below (e.g. "voice" -> "voice-webhook").
132
+ */
133
+ const TWILIO_GATEWAY_WEBHOOK_RE = /^\/webhooks\/twilio\/(.+)$/;
134
+ const GATEWAY_SUBPATH_MAP: Record<string, string> = {
135
+ voice: 'voice-webhook',
136
+ status: 'status',
137
+ 'connect-action': 'connect-action',
138
+ };
139
+
116
140
  /**
117
141
  * Validate a Twilio webhook request's X-Twilio-Signature header.
118
142
  *
119
143
  * Returns the raw body text on success so callers can reconstruct the Request
120
144
  * for downstream handlers (which also need to read the body).
121
145
  * Returns a 403 Response if signature validation fails.
122
- * If the Twilio auth token is not configured, skips validation with a warning.
146
+ *
147
+ * Fail-closed: if the auth token is not configured, the request is rejected
148
+ * with 403 rather than silently skipping validation. An explicit local-dev
149
+ * bypass is available via TWILIO_WEBHOOK_VALIDATION_DISABLED=true.
123
150
  */
124
151
  async function validateTwilioWebhook(
125
152
  req: Request,
126
153
  ): Promise<{ body: string } | Response> {
127
154
  const rawBody = await req.text();
128
- const authToken = getSecureKey('twilio_auth_token');
129
155
 
130
- if (!authToken) {
131
- log.warn('Twilio auth token not configured — skipping webhook signature validation');
156
+ // Allow explicit local-dev bypass — must be exactly "true"
157
+ if (process.env.TWILIO_WEBHOOK_VALIDATION_DISABLED === 'true') {
158
+ log.warn('Twilio webhook signature validation explicitly disabled via TWILIO_WEBHOOK_VALIDATION_DISABLED');
132
159
  return { body: rawBody };
133
160
  }
134
161
 
162
+ const authToken = TwilioConversationRelayProvider.getAuthToken();
163
+
164
+ // Fail-closed: reject if no auth token is configured
165
+ if (!authToken) {
166
+ log.error('Twilio auth token not configured — rejecting webhook request (fail-closed)');
167
+ return Response.json({ error: 'Forbidden' }, { status: 403 });
168
+ }
169
+
135
170
  const signature = req.headers.get('x-twilio-signature');
136
171
  if (!signature) {
137
172
  log.warn('Twilio webhook request missing X-Twilio-Signature header');
@@ -149,16 +184,22 @@ async function validateTwilioWebhook(
149
184
  // Behind proxies/gateways, req.url is the local server URL (e.g.
150
185
  // http://127.0.0.1:7821/...) which differs from the public URL Twilio
151
186
  // used to compute the HMAC-SHA1 signature.
152
- const publicBaseUrl = process.env.TWILIO_WEBHOOK_BASE_URL;
187
+ let publicBaseUrl: string | undefined;
188
+ try {
189
+ publicBaseUrl = getWebhookBaseUrl(loadConfig());
190
+ } catch {
191
+ // No webhook base URL configured — fall back to using req.url as-is
192
+ }
153
193
  const parsedUrl = new URL(req.url);
154
194
  const publicUrl = publicBaseUrl
155
- ? publicBaseUrl.replace(/\/$/, '') + parsedUrl.pathname + parsedUrl.search
195
+ ? publicBaseUrl + parsedUrl.pathname + parsedUrl.search
156
196
  : req.url;
157
197
 
158
198
  const isValid = TwilioConversationRelayProvider.verifyWebhookSignature(
159
199
  publicUrl,
160
200
  params,
161
201
  signature,
202
+ authToken,
162
203
  );
163
204
 
164
205
  if (!isValid) {
@@ -302,11 +343,18 @@ export class RuntimeHttpServer {
302
343
 
303
344
  // ── Twilio webhook endpoints — before auth check because Twilio
304
345
  // webhook POSTs don't include bearer tokens.
305
- // Supports both /v1/calls/twilio/* and /v1/assistants/:id/calls/twilio/*
346
+ // Supports /v1/calls/twilio/*, /v1/assistants/:id/calls/twilio/*,
347
+ // and gateway-compatible /webhooks/twilio/* paths.
306
348
  // Validates X-Twilio-Signature to prevent unauthorized access. ──
307
349
  const twilioMatch = path.match(TWILIO_WEBHOOK_RE);
308
- if (twilioMatch && req.method === 'POST') {
309
- const twilioSubpath = twilioMatch[1];
350
+ const gatewayTwilioMatch = !twilioMatch ? path.match(TWILIO_GATEWAY_WEBHOOK_RE) : null;
351
+ const resolvedTwilioSubpath = twilioMatch
352
+ ? twilioMatch[1]
353
+ : gatewayTwilioMatch
354
+ ? GATEWAY_SUBPATH_MAP[gatewayTwilioMatch[1]]
355
+ : null;
356
+ if (resolvedTwilioSubpath && req.method === 'POST') {
357
+ const twilioSubpath = resolvedTwilioSubpath;
310
358
 
311
359
  // Validate Twilio request signature before dispatching
312
360
  const validation = await validateTwilioWebhook(req);
@@ -335,17 +383,6 @@ export class RuntimeHttpServer {
335
383
  }
336
384
  }
337
385
 
338
- // ── Call answer endpoint — behind auth gate ──────────────────────
339
- const callAnswerMatch = path.match(/^\/v1\/calls\/([^/]+)\/answer$/);
340
- if (callAnswerMatch && req.method === 'POST') {
341
- try {
342
- return await handleCallAnswer(req, callAnswerMatch[1]);
343
- } catch (err) {
344
- log.error({ err, callSessionId: callAnswerMatch[1] }, 'Runtime HTTP handler error answering call');
345
- return Response.json({ error: 'Internal server error' }, { status: 500 });
346
- }
347
- }
348
-
349
386
  // Serve shareable app pages
350
387
  const pagesMatch = path.match(/^\/pages\/([^/]+)$/);
351
388
  if (pagesMatch && req.method === 'GET') {
@@ -529,6 +566,68 @@ export class RuntimeHttpServer {
529
566
  return await handleReplayDeadLetters(req);
530
567
  }
531
568
 
569
+ // ── Call API routes ───────────────────────────────────────────
570
+ if (endpoint === 'calls/start' && req.method === 'POST') {
571
+ return await handleStartCall(req);
572
+ }
573
+
574
+ // Match calls/:callSessionId and calls/:callSessionId/cancel, calls/:callSessionId/answer
575
+ const callsMatch = endpoint.match(/^calls\/([^/]+?)(\/cancel|\/answer)?$/);
576
+ if (callsMatch) {
577
+ const callSessionId = callsMatch[1];
578
+ // Skip known sub-paths that are handled elsewhere (twilio, relay)
579
+ if (callSessionId !== 'twilio' && callSessionId !== 'relay' && callSessionId !== 'start') {
580
+ if (callsMatch[2] === '/cancel' && req.method === 'POST') {
581
+ return await handleCancelCall(req, callSessionId);
582
+ }
583
+ if (callsMatch[2] === '/answer' && req.method === 'POST') {
584
+ return await handleAnswerCall(req, callSessionId);
585
+ }
586
+ if (!callsMatch[2] && req.method === 'GET') {
587
+ return handleGetCallStatus(callSessionId);
588
+ }
589
+ }
590
+ }
591
+
592
+ // ── Internal Twilio forwarding endpoints (gateway → runtime) ──
593
+ // These accept JSON payloads from the gateway (which already validated
594
+ // the Twilio signature) and reconstruct requests for the existing
595
+ // Twilio route handlers.
596
+ if (endpoint === 'internal/twilio/voice-webhook' && req.method === 'POST') {
597
+ const json = await req.json() as { params: Record<string, string>; originalUrl?: string };
598
+ const formBody = new URLSearchParams(json.params).toString();
599
+ // Reconstruct request URL: keep the original URL query string (callSessionId)
600
+ const reconstructedUrl = json.originalUrl ?? req.url;
601
+ const fakeReq = new Request(reconstructedUrl, {
602
+ method: 'POST',
603
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
604
+ body: formBody,
605
+ });
606
+ return await handleVoiceWebhook(fakeReq);
607
+ }
608
+
609
+ if (endpoint === 'internal/twilio/status' && req.method === 'POST') {
610
+ const json = await req.json() as { params: Record<string, string> };
611
+ const formBody = new URLSearchParams(json.params).toString();
612
+ const fakeReq = new Request(req.url, {
613
+ method: 'POST',
614
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
615
+ body: formBody,
616
+ });
617
+ return await handleStatusCallback(fakeReq);
618
+ }
619
+
620
+ if (endpoint === 'internal/twilio/connect-action' && req.method === 'POST') {
621
+ const json = await req.json() as { params: Record<string, string> };
622
+ const formBody = new URLSearchParams(json.params).toString();
623
+ const fakeReq = new Request(req.url, {
624
+ method: 'POST',
625
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
626
+ body: formBody,
627
+ });
628
+ return await handleConnectAction(fakeReq);
629
+ }
630
+
532
631
  return Response.json({ error: 'Not found', source: 'runtime' }, { status: 404 });
533
632
  } catch (err) {
534
633
  if (err instanceof IngressBlockedError) {
@@ -607,6 +706,18 @@ export class RuntimeHttpServer {
607
706
  channelDeliveryStore.linkMessage(event.id, userMessageId);
608
707
  channelDeliveryStore.markProcessed(event.id);
609
708
  log.info({ eventId: event.id }, 'Successfully replayed failed channel event');
709
+
710
+ const replyCallbackUrl = typeof payload.replyCallbackUrl === 'string'
711
+ ? payload.replyCallbackUrl
712
+ : undefined;
713
+ if (replyCallbackUrl) {
714
+ const externalChatId = typeof payload.externalChatId === 'string'
715
+ ? payload.externalChatId
716
+ : undefined;
717
+ if (externalChatId) {
718
+ await this.deliverReplyViaCallback(event.conversationId, externalChatId, replyCallbackUrl);
719
+ }
720
+ }
610
721
  } catch (err) {
611
722
  log.error({ err, eventId: event.id }, 'Retry failed for channel event');
612
723
  channelDeliveryStore.recordProcessingFailure(event.id, err);
@@ -614,6 +725,39 @@ export class RuntimeHttpServer {
614
725
  }
615
726
  }
616
727
 
728
+ private async deliverReplyViaCallback(
729
+ conversationId: string,
730
+ externalChatId: string,
731
+ callbackUrl: string,
732
+ ): Promise<void> {
733
+ const msgs = conversationStore.getMessages(conversationId);
734
+ for (let i = msgs.length - 1; i >= 0; i--) {
735
+ if (msgs[i].role === 'assistant') {
736
+ let parsed: unknown;
737
+ try { parsed = JSON.parse(msgs[i].content); } catch { parsed = msgs[i].content; }
738
+ const rendered = renderHistoryContent(parsed);
739
+
740
+ const linked = attachmentsStore.getAttachmentMetadataForMessage(msgs[i].id);
741
+ const replyAttachments = linked.map((a) => ({
742
+ id: a.id,
743
+ filename: a.originalFilename,
744
+ mimeType: a.mimeType,
745
+ sizeBytes: a.sizeBytes,
746
+ kind: a.kind,
747
+ }));
748
+
749
+ if (rendered.text || replyAttachments.length > 0) {
750
+ await deliverChannelReply(callbackUrl, {
751
+ chatId: externalChatId,
752
+ text: rendered.text || undefined,
753
+ attachments: replyAttachments.length > 0 ? replyAttachments : undefined,
754
+ });
755
+ }
756
+ break;
757
+ }
758
+ }
759
+ }
760
+
617
761
  private handleHealth(): Response {
618
762
  return Response.json({
619
763
  status: 'healthy',
@@ -56,7 +56,6 @@ export async function handleUploadAttachment(req: Request): Promise<Response> {
56
56
  let attachment: attachmentsStore.StoredAttachment;
57
57
  try {
58
58
  attachment = attachmentsStore.uploadAttachment(
59
- "self",
60
59
  filename,
61
60
  mimeType,
62
61
  data,
@@ -98,7 +97,7 @@ export async function handleDeleteAttachment(req: Request): Promise<Response> {
98
97
  );
99
98
  }
100
99
 
101
- const result = attachmentsStore.deleteAttachment("self", attachmentId);
100
+ const result = attachmentsStore.deleteAttachment(attachmentId);
102
101
 
103
102
  if (result === 'not_found') {
104
103
  return Response.json(
@@ -118,7 +117,7 @@ export async function handleDeleteAttachment(req: Request): Promise<Response> {
118
117
  }
119
118
 
120
119
  export function handleGetAttachment(attachmentId: string): Response {
121
- const attachment = attachmentsStore.getAttachmentById("self", attachmentId);
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
+ }