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
@@ -0,0 +1,207 @@
1
+ /**
2
+ * Call recovery — reconciles in-flight calls on daemon restart.
3
+ *
4
+ * When the daemon restarts, any calls left in non-terminal states may be stale
5
+ * (the daemon crashed mid-call) or still active on the provider side. This
6
+ * module fetches the actual provider status and transitions each call
7
+ * accordingly.
8
+ */
9
+
10
+ import { getLogger } from '../util/logger.js';
11
+ import { listRecoverableCalls, updateCallSession, expirePendingQuestions } from './call-store.js';
12
+ import type { VoiceProvider } from './voice-provider.js';
13
+ import type { CallStatus } from './types.js';
14
+
15
+ type Logger = ReturnType<typeof getLogger>;
16
+
17
+ const defaultLog = getLogger('call-recovery');
18
+
19
+ /**
20
+ * Grace period (in ms) for no-SID sessions during startup recovery.
21
+ *
22
+ * A daemon crash can leave a live Twilio call without a persisted SID
23
+ * (crash after `initiateCall` succeeds but before the SID is written).
24
+ * Webhooks carrying the SID may still arrive after restart.
25
+ *
26
+ * Sessions younger than this threshold are annotated but left in their
27
+ * current non-terminal state so incoming webhooks can still deliver the
28
+ * SID and resume the call normally.
29
+ *
30
+ * Sessions older than this threshold are transitioned to `failed` to
31
+ * prevent orphan sessions from creating false "active call" state
32
+ * indefinitely. 5 minutes is long enough for any legitimate webhook
33
+ * to arrive; after that the session is considered abandoned.
34
+ */
35
+ export const NO_SID_GRACE_PERIOD_MS = 5 * 60_000;
36
+
37
+ /**
38
+ * Map a Twilio provider status string to our internal CallStatus.
39
+ * Returns the mapped status or null if the status is unrecognised.
40
+ */
41
+ function mapProviderStatus(providerStatus: string): CallStatus | null {
42
+ switch (providerStatus) {
43
+ case 'queued':
44
+ case 'ringing':
45
+ return 'ringing';
46
+ case 'in-progress':
47
+ return 'in_progress';
48
+ case 'completed':
49
+ return 'completed';
50
+ case 'failed':
51
+ case 'busy':
52
+ case 'no-answer':
53
+ case 'canceled':
54
+ return 'failed';
55
+ default:
56
+ return null;
57
+ }
58
+ }
59
+
60
+ /**
61
+ * Check whether a CallStatus is terminal (no further transitions allowed).
62
+ */
63
+ function isTerminal(status: CallStatus): boolean {
64
+ return status === 'completed' || status === 'failed' || status === 'cancelled';
65
+ }
66
+
67
+ /**
68
+ * Reconcile all non-terminal call sessions at daemon startup.
69
+ *
70
+ * For each recoverable call:
71
+ * - If it has a provider SID, fetch the current status from the provider
72
+ * and transition the call to match.
73
+ * - If no provider SID exists and the session is older than the grace
74
+ * period, transition it to `failed` to prevent orphan sessions from
75
+ * creating false "active call" state indefinitely.
76
+ * - If no provider SID exists but the session is within the grace period,
77
+ * leave it non-terminal so webhooks can still deliver the SID.
78
+ * - If the call transitions to a terminal state, expire any pending questions.
79
+ */
80
+ export async function reconcileCallsOnStartup(
81
+ provider: VoiceProvider,
82
+ log: Logger = defaultLog,
83
+ ): Promise<void> {
84
+ const recoverableCalls = listRecoverableCalls();
85
+
86
+ if (recoverableCalls.length === 0) {
87
+ log.info('No recoverable calls found at startup');
88
+ return;
89
+ }
90
+
91
+ log.info({ count: recoverableCalls.length }, 'Reconciling non-terminal calls at startup');
92
+
93
+ for (const session of recoverableCalls) {
94
+ try {
95
+ if (!session.providerCallSid) {
96
+ const sessionAgeMs = Date.now() - session.createdAt;
97
+ const isStale = sessionAgeMs >= NO_SID_GRACE_PERIOD_MS;
98
+
99
+ if (isStale) {
100
+ // Session is old enough that any legitimate webhook should
101
+ // have arrived by now. Transition to `failed` so it no
102
+ // longer appears as an active call.
103
+ log.info(
104
+ { callSessionId: session.id, previousStatus: session.status, sessionAgeMs },
105
+ 'No-SID session past grace period — failing orphan session',
106
+ );
107
+ updateCallSession(session.id, {
108
+ status: 'failed',
109
+ endedAt: Date.now(),
110
+ lastError: 'Daemon restarted before provider SID persisted; grace period expired — orphan session failed',
111
+ });
112
+ expirePendingQuestions(session.id);
113
+ } else {
114
+ // Recent session — webhooks carrying the SID may still arrive.
115
+ // Leave in its current non-terminal state.
116
+ log.info(
117
+ { callSessionId: session.id, previousStatus: session.status, sessionAgeMs },
118
+ 'Skipping recent no-SID session (within grace period, webhooks may still arrive)',
119
+ );
120
+ updateCallSession(session.id, {
121
+ lastError: 'Daemon restarted before provider SID persisted; awaiting webhook',
122
+ });
123
+ }
124
+ continue;
125
+ }
126
+
127
+ // Fetch actual status from provider
128
+ let providerStatus: string;
129
+ try {
130
+ providerStatus = await provider.getCallStatus(session.providerCallSid);
131
+ } catch (err) {
132
+ const msg = err instanceof Error ? err.message : String(err);
133
+ log.warn(
134
+ { callSessionId: session.id, callSid: session.providerCallSid, err },
135
+ 'Failed to fetch provider status during recovery — failing call',
136
+ );
137
+ updateCallSession(session.id, {
138
+ status: 'failed',
139
+ endedAt: Date.now(),
140
+ lastError: `Recovery: failed to fetch provider status: ${msg}`,
141
+ });
142
+ expirePendingQuestions(session.id);
143
+ continue;
144
+ }
145
+
146
+ const mappedStatus = mapProviderStatus(providerStatus);
147
+
148
+ if (!mappedStatus) {
149
+ log.warn(
150
+ { callSessionId: session.id, providerStatus },
151
+ 'Unrecognised provider status during recovery — failing call',
152
+ );
153
+ updateCallSession(session.id, {
154
+ status: 'failed',
155
+ endedAt: Date.now(),
156
+ lastError: `Recovery: unrecognised provider status '${providerStatus}'`,
157
+ });
158
+ expirePendingQuestions(session.id);
159
+ continue;
160
+ }
161
+
162
+ if (isTerminal(mappedStatus)) {
163
+ // Provider says the call has ended
164
+ log.info(
165
+ { callSessionId: session.id, providerStatus, mappedStatus },
166
+ 'Provider reports call ended — transitioning to terminal state',
167
+ );
168
+ updateCallSession(session.id, {
169
+ status: mappedStatus,
170
+ endedAt: Date.now(),
171
+ });
172
+ expirePendingQuestions(session.id);
173
+ } else {
174
+ // Provider says call is still active — leave it for webhooks to handle
175
+ log.info(
176
+ { callSessionId: session.id, providerStatus, mappedStatus },
177
+ 'Provider reports call still active — leaving for webhook handling',
178
+ );
179
+ }
180
+ } catch (err) {
181
+ log.error(
182
+ { callSessionId: session.id, err },
183
+ 'Unexpected error during call recovery',
184
+ );
185
+ }
186
+ }
187
+
188
+ log.info('Call recovery reconciliation complete');
189
+ }
190
+
191
+ /**
192
+ * Log a dead-letter provider event — a provider callback payload that
193
+ * could not be processed (malformed, unknown format, etc.).
194
+ *
195
+ * Rather than silently dropping these events, we log the full payload
196
+ * so operators can investigate later.
197
+ */
198
+ export function logDeadLetterEvent(
199
+ reason: string,
200
+ payload: unknown,
201
+ log: Logger = defaultLog,
202
+ ): void {
203
+ log.error(
204
+ { reason, payload },
205
+ 'Dead-letter provider event: callback could not be processed',
206
+ );
207
+ }
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Call state machine — defines allowed status transitions and validates
3
+ * all state changes in a single place.
4
+ *
5
+ * Terminal states (completed, failed, cancelled) are immutable: no further
6
+ * transitions are permitted once a call reaches one of these states.
7
+ */
8
+
9
+ import type { CallStatus } from './types.js';
10
+
11
+ // ── Transition table ─────────────────────────────────────────────────
12
+
13
+ /**
14
+ * Maps each call status to the set of statuses it may transition to.
15
+ * Terminal states map to an empty set.
16
+ */
17
+ const ALLOWED_TRANSITIONS: Record<CallStatus, Set<CallStatus>> = {
18
+ initiated: new Set<CallStatus>(['ringing', 'in_progress', 'waiting_on_user', 'completed', 'failed', 'cancelled']),
19
+ ringing: new Set<CallStatus>(['in_progress', 'waiting_on_user', 'completed', 'failed', 'cancelled']),
20
+ in_progress: new Set<CallStatus>(['waiting_on_user', 'completed', 'failed', 'cancelled']),
21
+ waiting_on_user: new Set<CallStatus>(['in_progress', 'completed', 'failed', 'cancelled']),
22
+ // Terminal states — no further transitions allowed
23
+ completed: new Set<CallStatus>(),
24
+ failed: new Set<CallStatus>(),
25
+ cancelled: new Set<CallStatus>(),
26
+ };
27
+
28
+ const TERMINAL_STATES: Set<CallStatus> = new Set(['completed', 'failed', 'cancelled']);
29
+
30
+ // ── Public API ───────────────────────────────────────────────────────
31
+
32
+ export interface TransitionResult {
33
+ valid: boolean;
34
+ reason?: string;
35
+ }
36
+
37
+ /**
38
+ * Check whether a transition from `current` to `next` is allowed.
39
+ */
40
+ export function validateTransition(current: CallStatus, next: CallStatus): TransitionResult {
41
+ if (current === next) {
42
+ return { valid: true };
43
+ }
44
+
45
+ if (isTerminalState(current)) {
46
+ return {
47
+ valid: false,
48
+ reason: `Cannot transition from terminal state '${current}'`,
49
+ };
50
+ }
51
+
52
+ const allowed = ALLOWED_TRANSITIONS[current];
53
+ if (!allowed || !allowed.has(next)) {
54
+ return {
55
+ valid: false,
56
+ reason: `Invalid transition from '${current}' to '${next}'`,
57
+ };
58
+ }
59
+
60
+ return { valid: true };
61
+ }
62
+
63
+ /**
64
+ * Returns true if the given status is a terminal (immutable) state.
65
+ */
66
+ export function isTerminalState(status: CallStatus): boolean {
67
+ return TERMINAL_STATES.has(status);
68
+ }
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Call session notifiers and orchestrator registry.
3
+ *
4
+ * Follows the same notifier pattern as watch-state.ts: module-level Maps
5
+ * with register/unregister/fire helpers keyed by conversationId.
6
+ */
7
+
8
+ import { getLogger } from '../util/logger.js';
9
+ import type { CallOrchestrator } from './call-orchestrator.js';
10
+
11
+ const log = getLogger('call-state');
12
+
13
+ // ── Question notifiers ──────────────────────────────────────────────
14
+ const questionNotifiers = new Map<string, (callSessionId: string, question: string) => void>();
15
+
16
+ export function registerCallQuestionNotifier(
17
+ conversationId: string,
18
+ callback: (callSessionId: string, question: string) => void,
19
+ ): void {
20
+ questionNotifiers.set(conversationId, callback);
21
+ }
22
+
23
+ export function unregisterCallQuestionNotifier(conversationId: string): void {
24
+ questionNotifiers.delete(conversationId);
25
+ }
26
+
27
+ export function fireCallQuestionNotifier(conversationId: string, callSessionId: string, question: string): void {
28
+ questionNotifiers.get(conversationId)?.(callSessionId, question);
29
+ }
30
+
31
+ // ── Completion notifiers ────────────────────────────────────────────
32
+ const completionNotifiers = new Map<string, (callSessionId: string) => void>();
33
+
34
+ export function registerCallCompletionNotifier(
35
+ conversationId: string,
36
+ callback: (callSessionId: string) => void,
37
+ ): void {
38
+ completionNotifiers.set(conversationId, callback);
39
+ }
40
+
41
+ export function unregisterCallCompletionNotifier(conversationId: string): void {
42
+ completionNotifiers.delete(conversationId);
43
+ }
44
+
45
+ export function fireCallCompletionNotifier(conversationId: string, callSessionId: string): void {
46
+ completionNotifiers.get(conversationId)?.(callSessionId);
47
+ }
48
+
49
+ // ── Active orchestrator registry ────────────────────────────────────
50
+ const activeCallOrchestrators = new Map<string, CallOrchestrator>();
51
+
52
+ export function registerCallOrchestrator(callSessionId: string, orchestrator: CallOrchestrator): void {
53
+ activeCallOrchestrators.set(callSessionId, orchestrator);
54
+ log.info({ callSessionId }, 'Call orchestrator registered');
55
+ }
56
+
57
+ export function unregisterCallOrchestrator(callSessionId: string): void {
58
+ activeCallOrchestrators.delete(callSessionId);
59
+ log.info({ callSessionId }, 'Call orchestrator unregistered');
60
+ }
61
+
62
+ export function getCallOrchestrator(callSessionId: string): CallOrchestrator | undefined {
63
+ return activeCallOrchestrators.get(callSessionId);
64
+ }