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
@@ -15,12 +15,30 @@ import { getLogger } from '../util/logger.js';
15
15
  import { NetworkRecorder } from '../tools/browser/network-recorder.js';
16
16
  import { saveRecording } from '../tools/browser/recording-store.js';
17
17
  import type { SessionRecording } from '../tools/browser/network-recording-types.js';
18
+ import { navigateXPages } from '../tools/browser/x-auto-navigate.js';
19
+ import { autoNavigate } from '../tools/browser/auto-navigate.js';
18
20
 
19
21
  const log = getLogger('ride-shotgun-handler');
20
22
 
21
23
  /** Active network recorders keyed by watchId. */
22
24
  const activeRecorders = new Map<string, NetworkRecorder>();
23
25
 
26
+ /** Return domain-specific URL patterns that indicate a successful login. */
27
+ function getLoginSignals(targetDomain?: string): string[] {
28
+ if (targetDomain === 'x.com' || targetDomain === 'twitter.com') {
29
+ return [
30
+ '/i/api/graphql/', // any authenticated GraphQL call
31
+ '/1.1/account/settings', // legacy API session check
32
+ ];
33
+ }
34
+ // DoorDash and general fallback
35
+ return [
36
+ '/graphql/postLoginQuery',
37
+ '/graphql/homePageFacetFeed',
38
+ '/graphql/getConsumerOrdersWithDetails',
39
+ ];
40
+ }
41
+
24
42
  /**
25
43
  * Complete a session — finalize recording (if learn mode), generate summary, fire notifier.
26
44
  * Shared by both the duration timeout and the early-stop handler.
@@ -106,11 +124,7 @@ export async function handleRideShotgunStart(
106
124
  }
107
125
  try {
108
126
  const recorder = new NetworkRecorder(targetDomain);
109
- recorder.loginSignals = [
110
- '/graphql/postLoginQuery',
111
- '/graphql/homePageFacetFeed',
112
- '/graphql/getConsumerOrdersWithDetails',
113
- ];
127
+ recorder.loginSignals = getLoginSignals(targetDomain);
114
128
  await recorder.startDirect();
115
129
  // If session completed while we were connecting, stop immediately to avoid leak
116
130
  if (session.status !== 'active') {
@@ -118,13 +132,63 @@ export async function handleRideShotgunStart(
118
132
  await recorder.stop();
119
133
  return;
120
134
  }
121
- // Auto-stop when login is detected
122
- recorder.onLoginDetected = () => {
123
- log.info({ watchId }, 'Login detected — auto-stopping learn session');
124
- completeSession(session);
125
- };
126
135
  activeRecorders.set(watchId, recorder);
127
136
  log.info({ watchId, targetDomain, attempt }, 'Network recording started for learn session');
137
+
138
+ // For x.com, auto-navigate Chrome through key pages to capture the full API surface.
139
+ // Skip login detection — auto-navigation will complete the session when done.
140
+ if (targetDomain === 'x.com' || targetDomain === 'twitter.com') {
141
+ // Don't set onLoginDetected — it would kill the session after the first
142
+ // GraphQL call (5s grace), before auto-navigation finishes.
143
+ const abortSignal = { aborted: false };
144
+ const checkInterval = setInterval(() => {
145
+ if (session.status !== 'active') {
146
+ abortSignal.aborted = true;
147
+ clearInterval(checkInterval);
148
+ }
149
+ }, 1000);
150
+ navigateXPages(abortSignal).then(completed => {
151
+ clearInterval(checkInterval);
152
+ log.info({ watchId, completedSteps: completed.length }, 'X auto-navigation finished');
153
+ if (session.status === 'active') {
154
+ completeSession(session);
155
+ }
156
+ }).catch(err => {
157
+ clearInterval(checkInterval);
158
+ log.warn({ err, watchId }, 'X auto-navigation failed');
159
+ if (session.status === 'active') {
160
+ completeSession(session);
161
+ }
162
+ });
163
+ } else if (msg.autoNavigate && targetDomain) {
164
+ const abortSignal = { aborted: false };
165
+ const checkInterval = setInterval(() => {
166
+ if (session.status !== 'active') {
167
+ abortSignal.aborted = true;
168
+ clearInterval(checkInterval);
169
+ }
170
+ }, 1000);
171
+ autoNavigate(targetDomain, abortSignal).then(visited => {
172
+ clearInterval(checkInterval);
173
+ log.info({ watchId, visitedPages: visited.length }, 'Generic auto-navigation finished');
174
+ if (session.status === 'active') {
175
+ completeSession(session);
176
+ }
177
+ }).catch(err => {
178
+ clearInterval(checkInterval);
179
+ log.warn({ err, watchId }, 'Generic auto-navigation failed');
180
+ if (session.status === 'active') {
181
+ completeSession(session);
182
+ }
183
+ });
184
+ } else {
185
+ // No targetDomain: use login detection as before
186
+ recorder.onLoginDetected = () => {
187
+ log.info({ watchId }, 'Login detected — auto-stopping learn session');
188
+ completeSession(session);
189
+ };
190
+ }
191
+
128
192
  return;
129
193
  } catch (err) {
130
194
  if (attempt < 9) {
@@ -1,6 +1,6 @@
1
1
  import * as net from 'node:net';
2
2
  import { randomBytes } from 'node:crypto';
3
- import { existsSync, chmodSync, writeFileSync, unlinkSync, readdirSync, watch, type FSWatcher } from 'node:fs';
3
+ import { existsSync, chmodSync, readFileSync, writeFileSync, unlinkSync, readdirSync, watch, type FSWatcher } from 'node:fs';
4
4
  import { join } from 'node:path';
5
5
  import { getSocketPath, getSessionTokenPath, getRootDir, getWorkspaceDir, getWorkspaceSkillsDir, getSandboxWorkingDir, removeSocketFile, getTCPPort, getTCPHost, isTCPEnabled } from '../util/platform.js';
6
6
  import { hasNoAuthOverride } from './connection-policy.js';
@@ -10,7 +10,7 @@ import { RateLimitProvider } from '../providers/ratelimit.js';
10
10
  import { getConfig, invalidateConfigCache } from '../config/loader.js';
11
11
  import { buildSystemPrompt } from '../config/system-prompt.js';
12
12
  import { clearCache as clearTrustCache } from '../permissions/trust-store.js';
13
- import { resetAllowlist } from '../security/secret-allowlist.js';
13
+ import { resetAllowlist, validateAllowlistFile } from '../security/secret-allowlist.js';
14
14
  import { checkIngressForSecrets } from '../security/secret-ingress.js';
15
15
  import { IngressBlockedError } from '../util/errors.js';
16
16
  import { clearEmbeddingBackendCache } from '../memory/embedding-backend.js';
@@ -36,9 +36,24 @@ import { assistantEventHub } from '../runtime/assistant-event-hub.js';
36
36
  import { buildAssistantEvent } from '../runtime/assistant-event.js';
37
37
  import { SessionEvictor } from './session-evictor.js';
38
38
  import { getSubagentManager } from '../subagent/index.js';
39
+ import { tryHandlePendingCallAnswer } from '../calls/call-bridge.js';
40
+ import { resolveSlash } from './session-slash.js';
41
+ import { createUserMessage, createAssistantMessage } from '../agent/message-types.js';
39
42
 
40
43
  const log = getLogger('server');
41
44
 
45
+ function readPackageVersion(): string | undefined {
46
+ try {
47
+ const pkgPath = join(import.meta.dir, '../../package.json');
48
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8')) as { version?: string };
49
+ return pkg.version;
50
+ } catch {
51
+ return undefined;
52
+ }
53
+ }
54
+
55
+ const daemonVersion = readPackageVersion();
56
+
42
57
  export class DaemonServer {
43
58
  private server: net.Server | null = null;
44
59
  private tcpServer: net.Server | null = null;
@@ -125,21 +140,23 @@ export class DaemonServer {
125
140
  };
126
141
  // When a subagent finishes, inject the result into the parent session
127
142
  // so the LLM automatically informs the user.
128
- getSubagentManager().onSubagentFinished = (parentSessionId, message, sendToClient) => {
143
+ getSubagentManager().onSubagentFinished = (parentSessionId, message, sendToClient, notification) => {
129
144
  const parentSession = this.sessions.get(parentSessionId);
130
145
  if (!parentSession) {
131
146
  log.warn({ parentSessionId }, 'Subagent finished but parent session not found');
132
147
  return;
133
148
  }
134
149
  const requestId = `subagent-notify-${Date.now()}`;
135
- const enqueueResult = parentSession.enqueueMessage(message, [], sendToClient, requestId);
150
+ // Store structured notification data in the DB for history reconstruction
151
+ const metadata = { subagentNotification: notification };
152
+ const enqueueResult = parentSession.enqueueMessage(message, [], sendToClient, requestId, undefined, undefined, metadata);
136
153
  if (enqueueResult.rejected) {
137
154
  log.warn({ parentSessionId }, 'Parent session queue full, dropping subagent notification');
138
155
  return;
139
156
  }
140
157
  if (!enqueueResult.queued) {
141
158
  // Parent is idle — send directly.
142
- const messageId = parentSession.persistUserMessage(message, []);
159
+ const messageId = parentSession.persistUserMessage(message, [], undefined, metadata);
143
160
  parentSession.runAgentLoop(message, messageId, sendToClient).catch((err) => {
144
161
  log.error({ parentSessionId, err }, 'Failed to process subagent notification in parent');
145
162
  });
@@ -330,6 +347,16 @@ export class DaemonServer {
330
347
  },
331
348
  'secret-allowlist.json': () => {
332
349
  resetAllowlist();
350
+ try {
351
+ const errors = validateAllowlistFile();
352
+ if (errors && errors.length > 0) {
353
+ for (const e of errors) {
354
+ log.warn({ index: e.index, pattern: e.pattern }, `Invalid regex in secret-allowlist.json: ${e.message}`);
355
+ }
356
+ }
357
+ } catch (err) {
358
+ log.warn({ err }, 'Failed to validate secret-allowlist.json');
359
+ }
333
360
  },
334
361
  };
335
362
 
@@ -381,6 +408,7 @@ export class DaemonServer {
381
408
  this.broadcast({
382
409
  type: 'daemon_status',
383
410
  httpPort: port,
411
+ version: daemonVersion,
384
412
  });
385
413
  }
386
414
 
@@ -744,10 +772,7 @@ export class DaemonServer {
744
772
  ('sessionId' in msg && typeof msgRecord.sessionId === 'string'
745
773
  ? msgRecord.sessionId as string
746
774
  : undefined) ?? this.socketToSession.get(socket);
747
- // Resolve assistantId from the session if available; fall back to daemon default.
748
- const socketSession = sessionId ? this.sessions.get(sessionId) : undefined;
749
- const assistantId = socketSession?.assistantId ?? this.assistantId;
750
- this.publishAssistantEvent(msg, sessionId, assistantId);
775
+ this.publishAssistantEvent(msg, sessionId, this.assistantId);
751
776
  }
752
777
 
753
778
  broadcast(msg: ServerMessage, excludeSocket?: net.Socket): void {
@@ -763,10 +788,7 @@ export class DaemonServer {
763
788
  ('sessionId' in msg && typeof msgRecord.sessionId === 'string'
764
789
  ? msgRecord.sessionId as string
765
790
  : undefined) ?? (excludeSocket ? this.socketToSession.get(excludeSocket) : undefined);
766
- // Resolve assistantId from the origin session if available; fall back to daemon default.
767
- const originSession = sessionId ? this.sessions.get(sessionId) : undefined;
768
- const assistantId = originSession?.assistantId ?? this.assistantId;
769
- this.publishAssistantEvent(msg, sessionId, assistantId);
791
+ this.publishAssistantEvent(msg, sessionId, this.assistantId);
770
792
  }
771
793
 
772
794
  /**
@@ -797,6 +819,7 @@ export class DaemonServer {
797
819
  this.send(socket, {
798
820
  type: 'daemon_status',
799
821
  httpPort: this.httpPort,
822
+ version: daemonVersion,
800
823
  });
801
824
  return;
802
825
  }
@@ -815,6 +838,7 @@ export class DaemonServer {
815
838
  this.send(socket, {
816
839
  type: 'daemon_status',
817
840
  httpPort: this.httpPort,
841
+ version: daemonVersion,
818
842
  });
819
843
  }
820
844
 
@@ -967,7 +991,6 @@ export class DaemonServer {
967
991
  * is not blocked for the duration of the agent loop.
968
992
  */
969
993
  async persistAndProcessMessage(
970
- assistantId: string,
971
994
  conversationId: string,
972
995
  content: string,
973
996
  attachmentIds?: string[],
@@ -988,14 +1011,11 @@ export class DaemonServer {
988
1011
  throw new Error('Session is already processing a message');
989
1012
  }
990
1013
 
991
- // Set assistantId AFTER the isProcessing check so a rejected request
992
- // doesn't mutate the session state visible to an in-flight request.
993
- session.setAssistantId(assistantId);
994
1014
  session.setChannelCapabilities(resolveChannelCapabilities(sourceChannel));
995
1015
 
996
1016
  // Resolve attachment IDs to full attachment data for the session
997
1017
  const attachments = attachmentIds
998
- ? attachmentsStore.getAttachmentsByIds(assistantId, attachmentIds).map((a) => ({
1018
+ ? attachmentsStore.getAttachmentsByIds(attachmentIds).map((a) => ({
999
1019
  id: a.id,
1000
1020
  filename: a.originalFilename,
1001
1021
  mimeType: a.mimeType,
@@ -1003,10 +1023,32 @@ export class DaemonServer {
1003
1023
  }))
1004
1024
  : [];
1005
1025
 
1006
- // persistUserMessage throws if the session is busy or persistence fails
1026
+ // Persist the user message immediately after the isProcessing() guard.
1027
+ // This synchronously sets session.processing = true, closing the race
1028
+ // window that previously existed between the guard and the async bridge
1029
+ // check (two concurrent requests could both pass isProcessing() and
1030
+ // race into message handling).
1007
1031
  const requestId = crypto.randomUUID();
1008
1032
  const messageId = session.persistUserMessage(content, attachments, requestId);
1009
1033
 
1034
+ // Now that the processing lock is held, check the call-answer bridge.
1035
+ let bridgeHandled = false;
1036
+ try {
1037
+ const bridgeResult = await tryHandlePendingCallAnswer(conversationId, content, messageId);
1038
+ bridgeHandled = bridgeResult.handled;
1039
+ } catch (err) {
1040
+ log.warn({ err, conversationId }, 'Call-answer bridge check failed (non-fatal), proceeding with agent loop');
1041
+ }
1042
+
1043
+ if (bridgeHandled) {
1044
+ // The message was consumed by the call system. Release the session.
1045
+ resetSessionProcessingState(session);
1046
+ // Drain any queued messages that arrived while processing was true.
1047
+ session.drainQueue('loop_complete');
1048
+ log.info({ conversationId, messageId }, 'User message consumed by call-answer bridge, skipping agent loop');
1049
+ return { messageId };
1050
+ }
1051
+
1010
1052
  // Fire-and-forget the agent loop. Errors are logged but do not
1011
1053
  // affect the HTTP response (the client polls GET /messages).
1012
1054
  session.runAgentLoop(content, messageId, () => {}).catch((err) => {
@@ -1022,7 +1064,6 @@ export class DaemonServer {
1022
1064
  * Used by the channel inbound endpoint which needs the assistant reply.
1023
1065
  */
1024
1066
  async processMessage(
1025
- assistantId: string,
1026
1067
  conversationId: string,
1027
1068
  content: string,
1028
1069
  attachmentIds?: string[],
@@ -1041,14 +1082,11 @@ export class DaemonServer {
1041
1082
  throw new Error('Session is already processing a message');
1042
1083
  }
1043
1084
 
1044
- // Set assistantId AFTER the isProcessing check so a rejected request
1045
- // doesn't mutate the session state visible to an in-flight request.
1046
- session.setAssistantId(assistantId);
1047
1085
  session.setChannelCapabilities(resolveChannelCapabilities(sourceChannel));
1048
1086
 
1049
1087
  // Resolve attachment IDs to full attachment data for the session
1050
1088
  const attachments = attachmentIds
1051
- ? attachmentsStore.getAttachmentsByIds(assistantId, attachmentIds).map((a) => ({
1089
+ ? attachmentsStore.getAttachmentsByIds(attachmentIds).map((a) => ({
1052
1090
  id: a.id,
1053
1091
  filename: a.originalFilename,
1054
1092
  mimeType: a.mimeType,
@@ -1056,12 +1094,74 @@ export class DaemonServer {
1056
1094
  }))
1057
1095
  : [];
1058
1096
 
1059
- const messageId = await session.processMessage(content, attachments, () => {}, crypto.randomUUID());
1097
+ // Resolve slash commands before persistence (synchronous no race window).
1098
+ const slashResult = resolveSlash(content);
1099
+
1100
+ // Unknown slash command — persist the exchange (user + assistant) and
1101
+ // return immediately. This path doesn't set processing=true since no
1102
+ // agent loop runs, so there is no race concern.
1103
+ if (slashResult.kind === 'unknown') {
1104
+ const userMsg = createUserMessage(content, attachments);
1105
+ const persisted = conversationStore.addMessage(
1106
+ conversationId,
1107
+ 'user',
1108
+ JSON.stringify(userMsg.content),
1109
+ );
1110
+ session.getMessages().push(userMsg);
1111
+
1112
+ const assistantMsg = createAssistantMessage(slashResult.message);
1113
+ conversationStore.addMessage(
1114
+ conversationId,
1115
+ 'assistant',
1116
+ JSON.stringify(assistantMsg.content),
1117
+ );
1118
+ session.getMessages().push(assistantMsg);
1119
+ return { messageId: persisted.id };
1120
+ }
1060
1121
 
1061
- if (!messageId) {
1062
- throw new Error('Failed to persist user message');
1122
+ const resolvedContent = slashResult.content;
1123
+
1124
+ // Preactivate skill tools when slash resolution identifies a known skill
1125
+ if (slashResult.kind === 'rewritten') {
1126
+ (session as unknown as { preactivatedSkillIds?: string[] }).preactivatedSkillIds = [slashResult.skillId];
1127
+ }
1128
+
1129
+ // Persist the user message immediately after the isProcessing() guard.
1130
+ // This synchronously sets session.processing = true, closing the race
1131
+ // window that previously existed between the guard and the async bridge
1132
+ // check.
1133
+ const requestId = crypto.randomUUID();
1134
+ let messageId: string;
1135
+ try {
1136
+ messageId = session.persistUserMessage(resolvedContent, attachments, requestId);
1137
+ } catch (err) {
1138
+ // runAgentLoop never ran, so its finally block won't clear this
1139
+ (session as unknown as { preactivatedSkillIds?: string[] }).preactivatedSkillIds = undefined;
1140
+ throw err;
1063
1141
  }
1064
1142
 
1143
+ // Now that the processing lock is held, check the call-answer bridge.
1144
+ let bridgeHandled = false;
1145
+ try {
1146
+ const bridgeResult = await tryHandlePendingCallAnswer(conversationId, content, messageId);
1147
+ bridgeHandled = bridgeResult.handled;
1148
+ } catch (err) {
1149
+ log.warn({ err, conversationId }, 'Call-answer bridge check failed (non-fatal), proceeding with agent loop');
1150
+ }
1151
+
1152
+ if (bridgeHandled) {
1153
+ // The message was consumed by the call system. Release the session.
1154
+ (session as unknown as { preactivatedSkillIds?: string[] }).preactivatedSkillIds = undefined;
1155
+ resetSessionProcessingState(session);
1156
+ // Drain any queued messages that arrived while processing was true.
1157
+ session.drainQueue('loop_complete');
1158
+ log.info({ conversationId, messageId }, 'User message consumed by call-answer bridge, skipping agent loop');
1159
+ return { messageId };
1160
+ }
1161
+
1162
+ // Run the agent loop (blocking — the channel inbound endpoint needs the reply).
1163
+ await session.runAgentLoop(resolvedContent, messageId, () => {});
1164
+
1065
1165
  return { messageId };
1066
1166
  }
1067
1167
 
@@ -1072,8 +1172,8 @@ export class DaemonServer {
1072
1172
  return new RunOrchestrator({
1073
1173
  getOrCreateSession: (conversationId) =>
1074
1174
  this.getOrCreateSession(conversationId),
1075
- resolveAttachments: (assistantId, attachmentIds) =>
1076
- attachmentsStore.getAttachmentsByIds(assistantId, attachmentIds).map((a) => ({
1175
+ resolveAttachments: (attachmentIds) =>
1176
+ attachmentsStore.getAttachmentsByIds(attachmentIds).map((a) => ({
1077
1177
  id: a.id,
1078
1178
  filename: a.originalFilename,
1079
1179
  mimeType: a.mimeType,
@@ -1083,3 +1183,18 @@ export class DaemonServer {
1083
1183
  }
1084
1184
 
1085
1185
  }
1186
+
1187
+ /**
1188
+ * Reset the processing state set by `persistUserMessage` when the agent loop
1189
+ * is intentionally skipped (e.g. call-answer bridge consumed the message).
1190
+ */
1191
+ function resetSessionProcessingState(session: Session): void {
1192
+ const s = session as unknown as {
1193
+ processing: boolean;
1194
+ abortController: AbortController | null;
1195
+ currentRequestId: string | undefined;
1196
+ };
1197
+ s.processing = false;
1198
+ s.abortController = null;
1199
+ s.currentRequestId = undefined;
1200
+ }