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
@@ -1,51 +1,33 @@
1
- import { describe, test, expect, beforeEach } from 'bun:test';
2
- import { initializeDb } from '../memory/db.js';
3
- import { getDb } from '../memory/db.js';
1
+ import { afterAll, describe, test, expect, beforeEach } from 'bun:test';
2
+ import { initializeDb, getDb, resetDb } from '../memory/db.js';
4
3
  import { reminders } from '../memory/schema.js';
5
- import { reminderTool } from '../tools/reminder/reminder.js';
4
+ import { executeReminderCreate, executeReminderList, executeReminderCancel } from '../tools/reminder/reminder.js';
6
5
  import { claimDueReminders } from '../tools/reminder/reminder-store.js';
7
6
 
8
7
  initializeDb();
9
8
 
10
- const dummyContext = {
11
- workingDir: '/tmp',
12
- sessionId: 'test-session',
13
- conversationId: 'test-conversation',
14
- };
15
-
16
9
  function clearReminders() {
17
10
  getDb().delete(reminders).run();
18
11
  }
19
12
 
13
+ afterAll(() => {
14
+ resetDb();
15
+ });
16
+
20
17
  describe('reminder tool', () => {
21
18
  beforeEach(() => {
22
19
  clearReminders();
23
20
  });
24
21
 
25
- // ── action validation ───────────────────────────────────────────────
26
-
27
- test('rejects missing action', async () => {
28
- const result = await reminderTool.execute({}, dummyContext);
29
- expect(result.isError).toBe(true);
30
- expect(result.content).toContain('action is required');
31
- });
32
-
33
- test('rejects unknown action', async () => {
34
- const result = await reminderTool.execute({ action: 'explode' }, dummyContext);
35
- expect(result.isError).toBe(true);
36
- expect(result.content).toContain('Unknown action');
37
- });
38
-
39
22
  // ── create ──────────────────────────────────────────────────────────
40
23
 
41
24
  test('create with valid future ISO timestamp succeeds', async () => {
42
25
  const future = new Date(Date.now() + 60_000).toISOString();
43
- const result = await reminderTool.execute({
44
- action: 'create',
26
+ const result = executeReminderCreate({
45
27
  fire_at: future,
46
28
  label: 'Call Sidd',
47
29
  message: 'Remember to call Sidd',
48
- }, dummyContext);
30
+ });
49
31
 
50
32
  expect(result.isError).toBe(false);
51
33
  expect(result.content).toContain('Reminder created');
@@ -55,48 +37,44 @@ describe('reminder tool', () => {
55
37
 
56
38
  test('create with past timestamp returns error', async () => {
57
39
  const past = new Date(Date.now() - 60_000).toISOString();
58
- const result = await reminderTool.execute({
59
- action: 'create',
40
+ const result = executeReminderCreate({
60
41
  fire_at: past,
61
42
  label: 'Too late',
62
43
  message: 'This is in the past',
63
- }, dummyContext);
44
+ });
64
45
 
65
46
  expect(result.isError).toBe(true);
66
47
  expect(result.content).toContain('must be in the future');
67
48
  });
68
49
 
69
50
  test('create with invalid timestamp string returns error', async () => {
70
- const result = await reminderTool.execute({
71
- action: 'create',
51
+ const result = executeReminderCreate({
72
52
  fire_at: 'not-a-date',
73
53
  label: 'Bad date',
74
54
  message: 'This has a bad date',
75
- }, dummyContext);
55
+ });
76
56
 
77
57
  expect(result.isError).toBe(true);
78
58
  expect(result.content).toContain('Invalid timestamp');
79
59
  });
80
60
 
81
61
  test('create rejects non-ISO date formats like MM/DD/YYYY', async () => {
82
- const result = await reminderTool.execute({
83
- action: 'create',
62
+ const result = executeReminderCreate({
84
63
  fire_at: '03/04/2027',
85
64
  label: 'Ambiguous date',
86
65
  message: 'This format is locale-dependent',
87
- }, dummyContext);
66
+ });
88
67
 
89
68
  expect(result.isError).toBe(true);
90
69
  expect(result.content).toContain('Invalid timestamp');
91
70
  });
92
71
 
93
72
  test('create rejects ISO timestamp without timezone', async () => {
94
- const result = await reminderTool.execute({
95
- action: 'create',
73
+ const result = executeReminderCreate({
96
74
  fire_at: '2027-03-15T09:00:00',
97
75
  label: 'No timezone',
98
76
  message: 'Missing timezone offset',
99
- }, dummyContext);
77
+ });
100
78
 
101
79
  expect(result.isError).toBe(true);
102
80
  expect(result.content).toContain('Invalid timestamp');
@@ -106,12 +84,11 @@ describe('reminder tool', () => {
106
84
  const future = new Date(Date.now() + 120_000);
107
85
  const offset = '-05:00';
108
86
  const isoWithOffset = future.toISOString().replace('Z', offset);
109
- const result = await reminderTool.execute({
110
- action: 'create',
87
+ const result = executeReminderCreate({
111
88
  fire_at: isoWithOffset,
112
89
  label: 'With offset',
113
90
  message: 'Has explicit timezone',
114
- }, dummyContext);
91
+ });
115
92
 
116
93
  expect(result.isError).toBe(false);
117
94
  expect(result.content).toContain('Reminder created');
@@ -119,12 +96,11 @@ describe('reminder tool', () => {
119
96
 
120
97
  test('create defaults mode to notify', async () => {
121
98
  const future = new Date(Date.now() + 60_000).toISOString();
122
- const result = await reminderTool.execute({
123
- action: 'create',
99
+ const result = executeReminderCreate({
124
100
  fire_at: future,
125
101
  label: 'Default mode',
126
102
  message: 'Should be notify',
127
- }, dummyContext);
103
+ });
128
104
 
129
105
  expect(result.isError).toBe(false);
130
106
  expect(result.content).toContain('Mode: notify');
@@ -132,24 +108,22 @@ describe('reminder tool', () => {
132
108
 
133
109
  test('create with mode execute sets mode correctly', async () => {
134
110
  const future = new Date(Date.now() + 60_000).toISOString();
135
- const result = await reminderTool.execute({
136
- action: 'create',
111
+ const result = executeReminderCreate({
137
112
  fire_at: future,
138
113
  label: 'Execute mode',
139
114
  message: 'Should be execute',
140
115
  mode: 'execute',
141
- }, dummyContext);
116
+ });
142
117
 
143
118
  expect(result.isError).toBe(false);
144
119
  expect(result.content).toContain('Mode: execute');
145
120
  });
146
121
 
147
122
  test('create requires fire_at', async () => {
148
- const result = await reminderTool.execute({
149
- action: 'create',
123
+ const result = executeReminderCreate({
150
124
  label: 'No fire_at',
151
125
  message: 'Missing fire_at',
152
- }, dummyContext);
126
+ });
153
127
 
154
128
  expect(result.isError).toBe(true);
155
129
  expect(result.content).toContain('fire_at is required');
@@ -157,11 +131,10 @@ describe('reminder tool', () => {
157
131
 
158
132
  test('create requires label', async () => {
159
133
  const future = new Date(Date.now() + 60_000).toISOString();
160
- const result = await reminderTool.execute({
161
- action: 'create',
134
+ const result = executeReminderCreate({
162
135
  fire_at: future,
163
136
  message: 'Missing label',
164
- }, dummyContext);
137
+ });
165
138
 
166
139
  expect(result.isError).toBe(true);
167
140
  expect(result.content).toContain('label is required');
@@ -169,11 +142,10 @@ describe('reminder tool', () => {
169
142
 
170
143
  test('create requires message', async () => {
171
144
  const future = new Date(Date.now() + 60_000).toISOString();
172
- const result = await reminderTool.execute({
173
- action: 'create',
145
+ const result = executeReminderCreate({
174
146
  fire_at: future,
175
147
  label: 'No message',
176
- }, dummyContext);
148
+ });
177
149
 
178
150
  expect(result.isError).toBe(true);
179
151
  expect(result.content).toContain('message is required');
@@ -182,21 +154,20 @@ describe('reminder tool', () => {
182
154
  // ── list ────────────────────────────────────────────────────────────
183
155
 
184
156
  test('list returns "No reminders found" when empty', async () => {
185
- const result = await reminderTool.execute({ action: 'list' }, dummyContext);
157
+ const result = executeReminderList();
186
158
  expect(result.isError).toBe(false);
187
159
  expect(result.content).toContain('No reminders found');
188
160
  });
189
161
 
190
162
  test('list returns formatted reminders', async () => {
191
163
  const future = new Date(Date.now() + 60_000).toISOString();
192
- await reminderTool.execute({
193
- action: 'create',
164
+ executeReminderCreate({
194
165
  fire_at: future,
195
166
  label: 'Test reminder',
196
167
  message: 'Test message',
197
- }, dummyContext);
168
+ });
198
169
 
199
- const result = await reminderTool.execute({ action: 'list' }, dummyContext);
170
+ const result = executeReminderList();
200
171
  expect(result.isError).toBe(false);
201
172
  expect(result.content).toContain('Test reminder');
202
173
  expect(result.content).toContain('pending');
@@ -206,32 +177,29 @@ describe('reminder tool', () => {
206
177
 
207
178
  test('cancel with valid pending reminder succeeds', async () => {
208
179
  const future = new Date(Date.now() + 60_000).toISOString();
209
- const createResult = await reminderTool.execute({
210
- action: 'create',
180
+ const createResult = executeReminderCreate({
211
181
  fire_at: future,
212
182
  label: 'Cancel me',
213
183
  message: 'To be cancelled',
214
- }, dummyContext);
184
+ });
215
185
 
216
186
  // Extract ID from the create result
217
187
  const idMatch = createResult.content.match(/ID: (.+)/);
218
188
  expect(idMatch).not.toBeNull();
219
189
  const id = idMatch![1].trim();
220
190
 
221
- const result = await reminderTool.execute({
222
- action: 'cancel',
191
+ const result = executeReminderCancel({
223
192
  reminder_id: id,
224
- }, dummyContext);
193
+ });
225
194
 
226
195
  expect(result.isError).toBe(false);
227
196
  expect(result.content).toContain('cancelled');
228
197
  });
229
198
 
230
199
  test('cancel with nonexistent ID returns error', async () => {
231
- const result = await reminderTool.execute({
232
- action: 'cancel',
200
+ const result = executeReminderCancel({
233
201
  reminder_id: 'nonexistent',
234
- }, dummyContext);
202
+ });
235
203
 
236
204
  expect(result.isError).toBe(true);
237
205
  expect(result.content).toContain('not found');
@@ -239,12 +207,11 @@ describe('reminder tool', () => {
239
207
 
240
208
  test('cancel with already-fired reminder returns error', async () => {
241
209
  const future = new Date(Date.now() + 60_000).toISOString();
242
- const createResult = await reminderTool.execute({
243
- action: 'create',
210
+ const createResult = executeReminderCreate({
244
211
  fire_at: future,
245
212
  label: 'Fire then cancel',
246
213
  message: 'x',
247
- }, dummyContext);
214
+ });
248
215
 
249
216
  const idMatch = createResult.content.match(/ID: (.+)/);
250
217
  const id = idMatch![1].trim();
@@ -252,10 +219,9 @@ describe('reminder tool', () => {
252
219
  // Force-fire by claiming with a future timestamp
253
220
  claimDueReminders(Date.now() + 120_000);
254
221
 
255
- const result = await reminderTool.execute({
256
- action: 'cancel',
222
+ const result = executeReminderCancel({
257
223
  reminder_id: id,
258
- }, dummyContext);
224
+ });
259
225
 
260
226
  expect(result.isError).toBe(true);
261
227
  expect(result.content).toContain('not found or already fired');
@@ -0,0 +1,222 @@
1
+ /**
2
+ * Tests that HTTP-triggered run/session flows mirror messages into the
3
+ * assistant-events hub with payload parity to IPC outbound messages.
4
+ *
5
+ * The Session class has two distinct outbound paths:
6
+ * 1. updateClient handler — used by the prompter for confirmation_request,
7
+ * trace emitter, secret prompter.
8
+ * 2. runAgentLoop onEvent callback — used for the primary streaming events:
9
+ * assistant_text_delta, message_complete, tool_use_start, tool_result, etc.
10
+ *
11
+ * Both paths must publish to the hub.
12
+ *
13
+ * Tests:
14
+ * - confirmation_request (updateClient path) → hub emits one AssistantEvent
15
+ * - assistant_text_delta + message_complete (onEvent path) → hub emits in order
16
+ * - sessionId falls back to conversationId when the message lacks it
17
+ */
18
+ import { afterAll, describe, test, expect, beforeEach, mock } from 'bun:test';
19
+ import { mkdtempSync } from 'node:fs';
20
+ import { tmpdir } from 'node:os';
21
+ import { join } from 'node:path';
22
+ import type { ServerMessage } from '../daemon/ipc-protocol.js';
23
+ import type { Session } from '../daemon/session.js';
24
+ import type { AssistantEvent } from '../runtime/assistant-event.js';
25
+
26
+ const testDir = mkdtempSync(join(tmpdir(), 'run-orch-hub-test-'));
27
+
28
+ mock.module('../util/platform.js', () => ({
29
+ getRootDir: () => testDir,
30
+ getDataDir: () => testDir,
31
+ isMacOS: () => process.platform === 'darwin',
32
+ isLinux: () => process.platform === 'linux',
33
+ isWindows: () => process.platform === 'win32',
34
+ getSocketPath: () => join(testDir, 'test.sock'),
35
+ getPidPath: () => join(testDir, 'test.pid'),
36
+ getDbPath: () => join(testDir, 'test.db'),
37
+ getLogPath: () => join(testDir, 'test.log'),
38
+ ensureDataDir: () => {},
39
+ }));
40
+
41
+ mock.module('../util/logger.js', () => ({
42
+ getLogger: () => new Proxy({} as Record<string, unknown>, {
43
+ get: () => () => {},
44
+ }),
45
+ }));
46
+
47
+ import { initializeDb, getDb, resetDb } from '../memory/db.js';
48
+ import { createConversation } from '../memory/conversation-store.js';
49
+ import { RunOrchestrator } from '../runtime/run-orchestrator.js';
50
+ import { assistantEventHub } from '../runtime/assistant-event-hub.js';
51
+
52
+ initializeDb();
53
+
54
+ afterAll(() => {
55
+ resetDb();
56
+ });
57
+
58
+ // ── Helpers ──────────────────────────────────────────────────────────────────
59
+
60
+ /**
61
+ * Build a session that calls the updateClient handler with the given messages
62
+ * (simulates prompter / confirmation path).
63
+ */
64
+ function makeSessionEmittingViaClient(...messages: ServerMessage[]): Session {
65
+ let clientHandler: (msg: ServerMessage) => void = () => {};
66
+ return {
67
+ isProcessing: () => false,
68
+ persistUserMessage: () => undefined as unknown as string,
69
+ setChannelCapabilities: () => {},
70
+ updateClient: (handler: (msg: ServerMessage) => void) => {
71
+ clientHandler = handler;
72
+ },
73
+ runAgentLoop: async () => {
74
+ for (const msg of messages) {
75
+ clientHandler(msg);
76
+ }
77
+ },
78
+ handleConfirmationResponse: () => {},
79
+ } as unknown as Session;
80
+ }
81
+
82
+ /**
83
+ * Build a session that calls the onEvent callback with the given messages
84
+ * (simulates the primary agent-loop streaming path).
85
+ */
86
+ function makeSessionEmittingViaAgentLoop(...messages: ServerMessage[]): Session {
87
+ return {
88
+ isProcessing: () => false,
89
+ persistUserMessage: () => undefined as unknown as string,
90
+ setChannelCapabilities: () => {},
91
+ updateClient: () => {},
92
+ runAgentLoop: async (_content: string, _messageId: string, onEvent: (msg: ServerMessage) => void) => {
93
+ for (const msg of messages) {
94
+ onEvent(msg);
95
+ }
96
+ },
97
+ handleConfirmationResponse: () => {},
98
+ } as unknown as Session;
99
+ }
100
+
101
+ // ── Tests ─────────────────────────────────────────────────────────────────────
102
+
103
+ describe('HTTP run → confirmation_request mirrors to assistant-events hub', () => {
104
+ beforeEach(() => {
105
+ const db = getDb();
106
+ db.run('DELETE FROM message_runs');
107
+ db.run('DELETE FROM messages');
108
+ db.run('DELETE FROM conversations');
109
+ });
110
+
111
+ test('confirmation_request (updateClient path) emits one AssistantEvent', async () => {
112
+ const conversation = createConversation('http-confirmation-test');
113
+ const confirmationMsg: ServerMessage = {
114
+ type: 'confirmation_request',
115
+ requestId: 'req-http-1',
116
+ toolName: 'bash',
117
+ input: { command: 'ls' },
118
+ riskLevel: 'medium',
119
+ allowlistOptions: [{ label: 'ls', description: 'List files', pattern: 'ls' }],
120
+ scopeOptions: [{ label: 'everywhere', scope: 'everywhere' }],
121
+ };
122
+ const session = makeSessionEmittingViaClient(confirmationMsg);
123
+
124
+ const received: AssistantEvent[] = [];
125
+ const sub = assistantEventHub.subscribe(
126
+ { assistantId: 'self' },
127
+ (e) => { received.push(e); },
128
+ );
129
+
130
+ const orchestrator = new RunOrchestrator({
131
+ getOrCreateSession: async () => session,
132
+ resolveAttachments: () => [],
133
+ });
134
+
135
+ await orchestrator.startRun(conversation.id, 'Do something');
136
+ // Wait for the async hub chain to flush.
137
+ await new Promise((r) => setTimeout(r, 20));
138
+
139
+ sub.dispose();
140
+
141
+ expect(received).toHaveLength(1);
142
+ expect(received[0].assistantId).toBe('self');
143
+ expect(received[0].sessionId).toBe(conversation.id);
144
+ expect(received[0].message.type).toBe('confirmation_request');
145
+ expect(received[0].message).toBe(confirmationMsg);
146
+ });
147
+ });
148
+
149
+ describe('HTTP run → message flow mirrors to assistant-events hub', () => {
150
+ beforeEach(() => {
151
+ const db = getDb();
152
+ db.run('DELETE FROM message_runs');
153
+ db.run('DELETE FROM messages');
154
+ db.run('DELETE FROM conversations');
155
+ });
156
+
157
+ test('assistant_text_delta and message_complete (onEvent path) emit in order', async () => {
158
+ const conversation = createConversation('http-message-flow-test');
159
+ const deltaMsg: ServerMessage = {
160
+ type: 'assistant_text_delta',
161
+ sessionId: conversation.id,
162
+ text: 'Working on it...',
163
+ };
164
+ const completeMsg: ServerMessage = {
165
+ type: 'message_complete',
166
+ sessionId: conversation.id,
167
+ };
168
+ const session = makeSessionEmittingViaAgentLoop(deltaMsg, completeMsg);
169
+
170
+ const received: AssistantEvent[] = [];
171
+ const sub = assistantEventHub.subscribe(
172
+ { assistantId: 'self' },
173
+ (e) => { received.push(e); },
174
+ );
175
+
176
+ const orchestrator = new RunOrchestrator({
177
+ getOrCreateSession: async () => session,
178
+ resolveAttachments: () => [],
179
+ });
180
+
181
+ await orchestrator.startRun(conversation.id, 'Hello');
182
+ await new Promise((r) => setTimeout(r, 20));
183
+
184
+ sub.dispose();
185
+
186
+ expect(received).toHaveLength(2);
187
+ expect(received[0].message.type).toBe('assistant_text_delta');
188
+ expect(received[1].message.type).toBe('message_complete');
189
+ // Both should carry the session id
190
+ expect(received[0].sessionId).toBe(conversation.id);
191
+ expect(received[1].sessionId).toBe(conversation.id);
192
+ // Messages are the unmodified originals
193
+ expect(received[0].message).toBe(deltaMsg);
194
+ expect(received[1].message).toBe(completeMsg);
195
+ });
196
+
197
+ test('sessionId falls back to conversationId when message lacks it (onEvent path)', async () => {
198
+ const conversation = createConversation('http-session-fallback-test');
199
+ // pong has no sessionId field
200
+ const msg: ServerMessage = { type: 'pong' };
201
+ const session = makeSessionEmittingViaAgentLoop(msg);
202
+
203
+ const received: AssistantEvent[] = [];
204
+ const sub = assistantEventHub.subscribe(
205
+ { assistantId: 'self' },
206
+ (e) => { received.push(e); },
207
+ );
208
+
209
+ const orchestrator = new RunOrchestrator({
210
+ getOrCreateSession: async () => session,
211
+ resolveAttachments: () => [],
212
+ });
213
+
214
+ await orchestrator.startRun(conversation.id, 'ping');
215
+ await new Promise((r) => setTimeout(r, 20));
216
+
217
+ sub.dispose();
218
+
219
+ expect(received).toHaveLength(1);
220
+ expect(received[0].sessionId).toBe(conversation.id);
221
+ });
222
+ });
@@ -40,7 +40,7 @@ function makeSessionWithConfirmation(message: ServerMessage): Session {
40
40
  // Return undefined so createRun stores messageId as null and avoids
41
41
  // a foreign-key dependency on the conversation-store message table.
42
42
  persistUserMessage: () => undefined as unknown as string,
43
- setAssistantId: () => {},
43
+ setChannelCapabilities: () => {},
44
44
  updateClient: (handler: (msg: ServerMessage) => void) => {
45
45
  clientHandler = handler;
46
46
  },
@@ -60,7 +60,7 @@ function makeSessionWithEvent(message: ServerMessage): Session {
60
60
  return {
61
61
  isProcessing: () => false,
62
62
  persistUserMessage: () => undefined as unknown as string,
63
- setAssistantId: () => {},
63
+ setChannelCapabilities: () => {},
64
64
  updateClient: () => {},
65
65
  runAgentLoop: async (_content: string, _messageId: string, onEvent: (msg: ServerMessage) => void) => {
66
66
  onEvent(message);
@@ -92,7 +92,7 @@ describe('run failure detection', () => {
92
92
  resolveAttachments: () => [],
93
93
  });
94
94
 
95
- const run = await orchestrator.startRun('assistant-1', conversation.id, 'Hello');
95
+ const run = await orchestrator.startRun(conversation.id, 'Hello');
96
96
 
97
97
  // The agent loop fires asynchronously; give it a tick to settle.
98
98
  await new Promise((r) => setTimeout(r, 50));
@@ -114,7 +114,7 @@ describe('run failure detection', () => {
114
114
  resolveAttachments: () => [],
115
115
  });
116
116
 
117
- const run = await orchestrator.startRun('assistant-1', conversation.id, 'Hello');
117
+ const run = await orchestrator.startRun(conversation.id, 'Hello');
118
118
 
119
119
  await new Promise((r) => setTimeout(r, 50));
120
120
 
@@ -139,7 +139,7 @@ describe('run approval state executionTarget', () => {
139
139
 
140
140
  test('stores pending confirmation executionTarget when provided', () => {
141
141
  const conversation = createConversation('run test');
142
- const run = createRun('assistant-1', conversation.id);
142
+ const run = createRun(conversation.id);
143
143
 
144
144
  setRunConfirmation(run.id, {
145
145
  toolName: 'host_file_read',
@@ -158,7 +158,7 @@ describe('run approval state executionTarget', () => {
158
158
 
159
159
  test('parses pending confirmations without executionTarget for legacy rows', () => {
160
160
  const conversation = createConversation('legacy run test');
161
- const run = createRun('assistant-1', conversation.id);
161
+ const run = createRun(conversation.id);
162
162
 
163
163
  setRunConfirmation(run.id, {
164
164
  toolName: 'bash',
@@ -192,7 +192,7 @@ describe('run approval state executionTarget', () => {
192
192
  resolveAttachments: () => [],
193
193
  });
194
194
 
195
- const run = await orchestrator.startRun('assistant-1', conversation.id, 'Run host command');
195
+ const run = await orchestrator.startRun(conversation.id, 'Run host command');
196
196
  const stored = orchestrator.getRun(run.id);
197
197
  expect(stored?.status).toBe('needs_confirmation');
198
198
  expect(stored?.pendingConfirmation?.executionTarget).toBe('host');