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
package/README.md CHANGED
@@ -104,7 +104,7 @@ assistant/
104
104
  │ ├── home-base/ # Home Base app-link bootstrap
105
105
  │ ├── hooks/ # Git-style lifecycle hooks
106
106
  │ ├── media/ # Media processing and attachments
107
- │ ├── schedule/ # Reminders and scheduling
107
+ │ ├── schedule/ # Reminders and recurrence scheduling (cron + RRULE)
108
108
  │ ├── tasks/ # Task management
109
109
  │ ├── workspace/ # Workspace file operations
110
110
  │ ├── events/ # Domain event bus
@@ -122,7 +122,9 @@ assistant/
122
122
 
123
123
  ## Database
124
124
 
125
- SQLite via Drizzle ORM, stored at `~/.vellum/workspace/data/db/assistant.db`. Key tables include conversations, messages, tool invocations, attachments, memory segments (with FTS5), memory items, entities, and reminders.
125
+ SQLite via Drizzle ORM, stored at `~/.vellum/workspace/data/db/assistant.db`. Key tables include conversations, messages, tool invocations, attachments, memory segments (with FTS5), memory items, entities, reminders, and recurrence schedules (cron + RRULE).
126
+
127
+ > **Compatibility note:** The recurrence schedule system supports both cron expressions and iCalendar RRULE syntax. The legacy field names `cron_expression` and `cronExpression` remain supported in API inputs. New code should use the `expression` field with an explicit `syntax` discriminator. See [`ARCHITECTURE.md`](../ARCHITECTURE.md) for details.
126
128
 
127
129
  Run migrations:
128
130
 
@@ -145,6 +147,17 @@ docker run --rm -p 3001:3001 \
145
147
 
146
148
  The image runs as non-root user `assistant` (uid 1001) and exposes port `3001`.
147
149
 
150
+ ## Troubleshooting
151
+
152
+ ### Invalid RRULE set expressions
153
+
154
+ If `schedule_create` rejects an RRULE expression, check the following:
155
+
156
+ - **Missing DTSTART** — Every RRULE expression must include a `DTSTART` line (e.g., `DTSTART:20250101T090000Z`).
157
+ - **No inclusion rule** — At least one `RRULE:` or `RDATE` line is required. An expression with only `EXDATE` or `EXRULE` lines and no inclusion has no occurrences to schedule.
158
+ - **Unsupported lines** — Only `DTSTART`, `RRULE:`, `RDATE`, `EXDATE`, and `EXRULE` prefixes are recognized. Any other line (e.g., `VTIMEZONE`, `VEVENT`) will be rejected.
159
+ - **Newline encoding** — When passing multi-line RRULE expressions through JSON, use literal `\n` between lines. The engine normalizes escaped newlines automatically.
160
+
148
161
  ## Development
149
162
 
150
163
  ```bash
package/bun.lock CHANGED
@@ -11,7 +11,7 @@
11
11
  "@huggingface/transformers": "^3.8.1",
12
12
  "@qdrant/js-client-rest": "^1.16.2",
13
13
  "@sentry/node": "^10.38.0",
14
- "@vellumai/cli": "0.1.1",
14
+ "@vellumai/cli": "0.1.5",
15
15
  "@vellumai/vellum-gateway": "^0.1.7",
16
16
  "agentmail": "^0.1.0",
17
17
  "archiver": "^7.0.1",
@@ -28,6 +28,7 @@
28
28
  "playwright": "^1.58.2",
29
29
  "postgres": "^3.4.8",
30
30
  "react": "^19.2.4",
31
+ "rrule": "^2.8.1",
31
32
  "tldts": "^7.0.23",
32
33
  "tree-sitter-bash": "0.25.1",
33
34
  "uuid": "^11.1.0",
@@ -539,7 +540,7 @@
539
540
 
540
541
  "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.55.0", "", { "dependencies": { "@typescript-eslint/types": "8.55.0", "eslint-visitor-keys": "^4.2.1" } }, "sha512-AxNRwEie8Nn4eFS1FzDMJWIISMGoXMb037sgCBJ3UR6o0fQTzr2tqN9WT+DkWJPhIdQCfV7T6D387566VtnCJA=="],
541
542
 
542
- "@vellumai/cli": ["@vellumai/cli@0.1.1", "", { "dependencies": { "ink": "^6.7.0", "react": "^19.2.4" }, "bin": { "vellum-cli": "src/index.ts" } }, "sha512-4VxPX7qqM/qZXukWbYxMEn9J9b0eSr8EA8NCjmvpLBzmQl/L9oMyhf5TmkVsZv+jE70z5/bvp+gtKGb+xQDxRg=="],
543
+ "@vellumai/cli": ["@vellumai/cli@0.1.5", "", { "dependencies": { "ink": "^6.7.0", "react": "^19.2.4" }, "bin": { "vellum-cli": "src/index.ts" } }, "sha512-DTFm7wHjzaa5fhpGD/ZoZwz8NXFoaCla61YNdncmsnHgpWgxesjFl7mhOqhkWt8IVQkr5SAMouiGvm0+kSjSUg=="],
543
544
 
544
545
  "@vellumai/vellum-gateway": ["@vellumai/vellum-gateway@0.1.7", "", { "dependencies": { "file-type": "^21.3.0", "pino": "^9.6.0", "pino-pretty": "^13.1.3", "zod": "^4.3.6" } }, "sha512-JJhCqhsnZ99+dazCUWtOac24p92WyCr3/bxYkfmTgk6yYz9zqH7O1DpjxTLOtjAohQnki4WXNFUCzu48LCtFsg=="],
545
546
 
@@ -1105,6 +1106,8 @@
1105
1106
 
1106
1107
  "roarr": ["roarr@2.15.4", "", { "dependencies": { "boolean": "^3.0.1", "detect-node": "^2.0.4", "globalthis": "^1.0.1", "json-stringify-safe": "^5.0.1", "semver-compare": "^1.0.0", "sprintf-js": "^1.1.2" } }, "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A=="],
1107
1108
 
1109
+ "rrule": ["rrule@2.8.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-hM3dHSBMeaJ0Ktp7W38BJZ7O1zOgaFEsn41PDk+yHoEtfLV+PoJt9E9xAlZiWgf/iqEqionN0ebHFZIDAp+iGw=="],
1110
+
1108
1111
  "run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="],
1109
1112
 
1110
1113
  "safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vellum",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "vellum": "./src/index.ts"
@@ -18,6 +18,7 @@
18
18
  "typecheck": "bunx tsc --noEmit",
19
19
  "test": "bash scripts/test.sh",
20
20
  "test:stable": "EXCLUDE_EXPERIMENTAL=true bash scripts/test.sh",
21
+ "test:bench": "find src/__tests__ -maxdepth 1 -type f -name '*.benchmark.test.ts' -print0 | xargs -0 -P 1 -I {} bun test {}",
21
22
  "test:filesystem-tools": "bash scripts/test-filesystem-tools.sh"
22
23
  },
23
24
  "dependencies": {
@@ -27,7 +28,7 @@
27
28
  "@huggingface/transformers": "^3.8.1",
28
29
  "@qdrant/js-client-rest": "^1.16.2",
29
30
  "@sentry/node": "^10.38.0",
30
- "@vellumai/cli": "0.1.1",
31
+ "@vellumai/cli": "0.1.5",
31
32
  "@vellumai/vellum-gateway": "^0.1.7",
32
33
  "agentmail": "^0.1.0",
33
34
  "archiver": "^7.0.1",
@@ -44,6 +45,7 @@
44
45
  "playwright": "^1.58.2",
45
46
  "postgres": "^3.4.8",
46
47
  "react": "^19.2.4",
48
+ "rrule": "^2.8.1",
47
49
  "tldts": "^7.0.23",
48
50
  "tree-sitter-bash": "0.25.1",
49
51
  "uuid": "^11.1.0",
@@ -0,0 +1,562 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * Capture X.com GraphQL API calls via Chrome CDP.
4
+ *
5
+ * Usage:
6
+ * 1. Make sure Chrome is running with CDP (vellum x refresh will do this)
7
+ * 2. Run: bun run scripts/capture-x-graphql.ts [--auto] [--all]
8
+ * 3. In --auto mode, Chrome is navigated automatically. Otherwise browse X manually.
9
+ * 4. Press Ctrl+C to stop (or wait for --auto to finish).
10
+ *
11
+ * Flags:
12
+ * --auto Automatically navigate Chrome through X.com pages via CDP
13
+ * --all Capture ALL GraphQL queries (skip relevance filter)
14
+ */
15
+
16
+ import { mkdirSync, existsSync } from 'node:fs';
17
+
18
+ const CDP_BASE = 'http://localhost:9222';
19
+ const CAPTURE_DIR = '/tmp/x-graphql-capture';
20
+
21
+ // ─── Relevance filter ────────────────────────────────────────────────────────
22
+
23
+ const RELEVANT_QUERIES = new Set([
24
+ // Reads
25
+ 'UserByScreenName', 'UserTweets', 'TweetDetail', 'TweetResultByRestId',
26
+ 'SearchTimeline', 'Bookmarks', 'Likes', 'Favoriters',
27
+ 'Followers', 'Following', 'HomeTimeline', 'HomeLatestTimeline',
28
+ 'NotificationsTimeline', 'ListTimeline',
29
+ 'UserMedia',
30
+ // Writes
31
+ 'CreateTweet', 'DeleteTweet', 'FavoriteTweet',
32
+ 'UnfavoriteTweet', 'CreateRetweet', 'DeleteRetweet', 'CreateBookmark',
33
+ 'DeleteBookmark',
34
+ ]);
35
+
36
+ // ─── Types ───────────────────────────────────────────────────────────────────
37
+
38
+ interface CapturedQuery {
39
+ queryName: string;
40
+ queryId: string;
41
+ method: string;
42
+ variables: unknown;
43
+ features?: unknown;
44
+ response?: unknown;
45
+ timestamp: number;
46
+ }
47
+
48
+ // ─── Minimal CDP WebSocket client ────────────────────────────────────────────
49
+
50
+ class CDPClient {
51
+ private ws: WebSocket | null = null;
52
+ private nextId = 1;
53
+ private callbacks = new Map<number, { resolve: (v: unknown) => void; reject: (e: Error) => void }>();
54
+ private eventHandlers = new Map<string, Array<(params: Record<string, unknown>) => void>>();
55
+
56
+ async connect(wsUrl: string): Promise<void> {
57
+ return new Promise((resolve, reject) => {
58
+ const ws = new WebSocket(wsUrl);
59
+ ws.onopen = () => { this.ws = ws; resolve(); };
60
+ ws.onerror = (e) => reject(new Error(`CDP error: ${e}`));
61
+ ws.onclose = () => { this.ws = null; };
62
+ ws.onmessage = (event) => {
63
+ const msg = JSON.parse(String(event.data));
64
+ if (msg.id != null) {
65
+ const cb = this.callbacks.get(msg.id);
66
+ if (cb) { this.callbacks.delete(msg.id); msg.error ? cb.reject(new Error(msg.error.message)) : cb.resolve(msg.result); }
67
+ } else if (msg.method) {
68
+ for (const h of this.eventHandlers.get(msg.method) ?? []) h(msg.params ?? {});
69
+ }
70
+ };
71
+ });
72
+ }
73
+
74
+ async send(method: string, params?: Record<string, unknown>): Promise<unknown> {
75
+ const id = this.nextId++;
76
+ return new Promise((resolve, reject) => {
77
+ this.callbacks.set(id, { resolve, reject });
78
+ this.ws!.send(JSON.stringify({ id, method, params }));
79
+ });
80
+ }
81
+
82
+ on(event: string, handler: (params: Record<string, unknown>) => void) {
83
+ const list = this.eventHandlers.get(event) ?? [];
84
+ list.push(handler);
85
+ this.eventHandlers.set(event, list);
86
+ }
87
+
88
+ close() { this.ws?.close(); }
89
+ }
90
+
91
+ // ─── State ───────────────────────────────────────────────────────────────────
92
+
93
+ const args = process.argv.slice(2);
94
+ const autoMode = args.includes('--auto');
95
+ const captureAll = args.includes('--all');
96
+
97
+ const captured: CapturedQuery[] = [];
98
+ const seenQueries = new Set<string>();
99
+
100
+ // Ensure capture directory exists
101
+ if (!existsSync(CAPTURE_DIR)) mkdirSync(CAPTURE_DIR, { recursive: true });
102
+
103
+ // ─── Auto-navigation steps ───────────────────────────────────────────────────
104
+
105
+ interface GuideStep {
106
+ label: string;
107
+ url?: string;
108
+ clickSelector?: string;
109
+ expectedQueries: string[];
110
+ }
111
+
112
+ // Resolve the logged-in user's screen name for profile-based URLs
113
+ async function getScreenName(): Promise<string | null> {
114
+ if (!navigationClient) return null;
115
+ try {
116
+ const result = await navigationClient.send('Runtime.evaluate', {
117
+ expression: `
118
+ (function() {
119
+ const link = document.querySelector('a[data-testid="AppTabBar_Profile_Link"]');
120
+ if (link) return link.getAttribute('href')?.replace('/', '') ?? null;
121
+ return null;
122
+ })()
123
+ `,
124
+ awaitPromise: false,
125
+ returnByValue: true,
126
+ }) as { result?: { value?: string | null } };
127
+ return result?.result?.value ?? null;
128
+ } catch {
129
+ return null;
130
+ }
131
+ }
132
+
133
+ const GUIDE_STEPS: GuideStep[] = [
134
+ {
135
+ label: 'Home timeline',
136
+ url: 'https://x.com/home',
137
+ expectedQueries: ['HomeTimeline', 'HomeLatestTimeline'],
138
+ },
139
+ {
140
+ label: 'Profile',
141
+ // URL set dynamically in runAutoMode after resolving screen name
142
+ clickSelector: 'a[data-testid="AppTabBar_Profile_Link"]',
143
+ expectedQueries: ['UserByScreenName', 'UserTweets'],
144
+ },
145
+ {
146
+ label: 'Tweet detail',
147
+ clickSelector: 'article[data-testid="tweet"] a[href*="/status/"]',
148
+ expectedQueries: ['TweetDetail'],
149
+ },
150
+ {
151
+ label: 'Search',
152
+ url: 'https://x.com/search?q=hello&src=typed_query',
153
+ expectedQueries: ['SearchTimeline'],
154
+ },
155
+ {
156
+ label: 'Bookmarks',
157
+ url: 'https://x.com/i/bookmarks',
158
+ expectedQueries: ['Bookmarks'],
159
+ },
160
+ {
161
+ label: 'Notifications',
162
+ url: 'https://x.com/notifications',
163
+ expectedQueries: ['NotificationsTimeline'],
164
+ },
165
+ {
166
+ label: 'Likes',
167
+ // URL set dynamically
168
+ expectedQueries: ['Likes'],
169
+ },
170
+ {
171
+ label: 'Followers',
172
+ // URL set dynamically
173
+ expectedQueries: ['Followers'],
174
+ },
175
+ {
176
+ label: 'Following',
177
+ // URL set dynamically
178
+ expectedQueries: ['Following'],
179
+ },
180
+ {
181
+ label: 'Media',
182
+ // URL set dynamically
183
+ expectedQueries: ['UserMedia'],
184
+ },
185
+ ];
186
+
187
+ // ─── Discover Chrome tabs ────────────────────────────────────────────────────
188
+
189
+ const res = await fetch(`${CDP_BASE}/json/list`);
190
+ if (!res.ok) {
191
+ console.error('Chrome CDP not available. Run `vellum x refresh` first.');
192
+ process.exit(1);
193
+ }
194
+ const targets = (await res.json()) as Array<{ type: string; url: string; webSocketDebuggerUrl: string }>;
195
+ const pages = targets.filter(t => t.type === 'page');
196
+
197
+ if (pages.length === 0) {
198
+ console.error('No pages found in Chrome.');
199
+ process.exit(1);
200
+ }
201
+
202
+ console.log(`Found ${pages.length} tab(s). Attaching to all...`);
203
+
204
+ // ─── Pending request tracking ────────────────────────────────────────────────
205
+
206
+ const pendingRequests = new Map<string, { url: string; queryName: string }>();
207
+ // Resolve callbacks for --auto mode: queryName → resolve function
208
+ const queryWaiters = new Map<string, () => void>();
209
+
210
+ function notifyQuerySeen(queryName: string) {
211
+ seenQueries.add(queryName);
212
+ const waiter = queryWaiters.get(queryName);
213
+ if (waiter) {
214
+ queryWaiters.delete(queryName);
215
+ waiter();
216
+ }
217
+ }
218
+
219
+ function waitForQuery(queryName: string, timeoutMs = 15000): Promise<boolean> {
220
+ if (seenQueries.has(queryName)) return Promise.resolve(true);
221
+ return new Promise(resolve => {
222
+ const timer = setTimeout(() => {
223
+ queryWaiters.delete(queryName);
224
+ resolve(false);
225
+ }, timeoutMs);
226
+ queryWaiters.set(queryName, () => {
227
+ clearTimeout(timer);
228
+ resolve(true);
229
+ });
230
+ });
231
+ }
232
+
233
+ function waitForAnyQuery(queryNames: string[], timeoutMs = 15000): Promise<boolean> {
234
+ if (queryNames.some(q => seenQueries.has(q))) return Promise.resolve(true);
235
+ return new Promise(resolve => {
236
+ const timer = setTimeout(() => {
237
+ for (const q of queryNames) queryWaiters.delete(q);
238
+ resolve(false);
239
+ }, timeoutMs);
240
+ for (const q of queryNames) {
241
+ queryWaiters.set(q, () => {
242
+ clearTimeout(timer);
243
+ for (const q2 of queryNames) queryWaiters.delete(q2);
244
+ resolve(true);
245
+ });
246
+ }
247
+ });
248
+ }
249
+
250
+ // ─── Attach to all tabs ──────────────────────────────────────────────────────
251
+
252
+ // We'll keep a reference to one client that's on an x.com tab for navigation
253
+ let navigationClient: CDPClient | null = null;
254
+ let navigationWsUrl: string | null = null;
255
+
256
+ for (const page of pages) {
257
+ const client = new CDPClient();
258
+ await client.connect(page.webSocketDebuggerUrl);
259
+ await client.send('Network.enable');
260
+
261
+ // Track which client is on an x.com tab for navigation
262
+ if (page.url.includes('x.com') || page.url.includes('twitter.com')) {
263
+ navigationClient = client;
264
+ navigationWsUrl = page.webSocketDebuggerUrl;
265
+ }
266
+
267
+ client.on('Network.requestWillBeSent', (params) => {
268
+ const req = params.request as Record<string, unknown> | undefined;
269
+ const url = (req?.url ?? params.url) as string | undefined;
270
+ if (!url?.includes('/i/api/graphql/')) return;
271
+
272
+ const match = url.match(/\/graphql\/([^/]+)\/([^?]+)/);
273
+ const queryId = match?.[1] ?? 'unknown';
274
+ const queryName = match?.[2] ?? 'unknown';
275
+ const method = (req?.method as string) ?? 'GET';
276
+
277
+ // Apply relevance filter
278
+ if (!captureAll && !RELEVANT_QUERIES.has(queryName)) return;
279
+
280
+ let variables: unknown = undefined;
281
+ let features: unknown = undefined;
282
+
283
+ if (method === 'POST' && req?.postData) {
284
+ try {
285
+ const body = JSON.parse(req.postData as string);
286
+ variables = body.variables;
287
+ features = body.features;
288
+ } catch { /* ignore */ }
289
+ } else if (method === 'GET') {
290
+ try {
291
+ const u = new URL(url);
292
+ const v = u.searchParams.get('variables');
293
+ if (v) variables = JSON.parse(v);
294
+ const f = u.searchParams.get('features');
295
+ if (f) features = JSON.parse(f);
296
+ } catch { /* ignore */ }
297
+ }
298
+
299
+ console.log(`\n>>> ${method} ${queryName} (${queryId})`);
300
+ if (variables) console.log(` variables: ${JSON.stringify(variables).slice(0, 200)}`);
301
+
302
+ pendingRequests.set(params.requestId as string, { url, queryName });
303
+ captured.push({ queryName, queryId, method, variables, features, timestamp: Date.now() });
304
+ });
305
+
306
+ client.on('Network.responseReceived', (params) => {
307
+ const requestId = params.requestId as string;
308
+ const pending = pendingRequests.get(requestId);
309
+ if (!pending) return;
310
+
311
+ const response = params.response as Record<string, unknown>;
312
+ const status = response.status as number;
313
+ console.log(` <<< ${status}`);
314
+
315
+ // Get full response body
316
+ client.send('Network.getResponseBody', { requestId }).then((result) => {
317
+ const body = (result as Record<string, unknown>).body as string;
318
+ try {
319
+ const json = JSON.parse(body);
320
+ // Attach full response to the captured entry
321
+ const entry = [...captured].reverse().find(e => e.queryName === pending.queryName && !e.response);
322
+ if (entry) {
323
+ entry.response = json;
324
+
325
+ // Write individual file
326
+ const filename = `${pending.queryName}-${entry.timestamp}.json`;
327
+ Bun.write(`${CAPTURE_DIR}/${filename}`, JSON.stringify({
328
+ queryName: entry.queryName,
329
+ queryId: entry.queryId,
330
+ method: entry.method,
331
+ variables: entry.variables,
332
+ features: entry.features,
333
+ response: json,
334
+ }, null, 2));
335
+ }
336
+
337
+ // Notify waiters
338
+ notifyQuerySeen(pending.queryName);
339
+ } catch { /* ignore */ }
340
+ }).catch(() => { /* body not available */ });
341
+
342
+ pendingRequests.delete(requestId);
343
+ });
344
+ }
345
+
346
+ // If no x.com tab found, use the first page for navigation
347
+ if (!navigationClient && pages.length > 0) {
348
+ navigationClient = new CDPClient();
349
+ await navigationClient.connect(pages[0].webSocketDebuggerUrl);
350
+ navigationWsUrl = pages[0].webSocketDebuggerUrl;
351
+ }
352
+
353
+ // ─── CDP navigation helpers ──────────────────────────────────────────────────
354
+
355
+ async function navigateTo(url: string): Promise<void> {
356
+ if (!navigationClient) return;
357
+ await navigationClient.send('Page.navigate', { url });
358
+ // Wait for page to load
359
+ await new Promise(r => setTimeout(r, 3000));
360
+ }
361
+
362
+ async function clickElement(selector: string): Promise<boolean> {
363
+ if (!navigationClient) return false;
364
+ try {
365
+ const result = await navigationClient.send('Runtime.evaluate', {
366
+ expression: `
367
+ (function() {
368
+ const el = document.querySelector(${JSON.stringify(selector)});
369
+ if (!el) return false;
370
+ el.scrollIntoView({ block: 'center' });
371
+ el.click();
372
+ return true;
373
+ })()
374
+ `,
375
+ awaitPromise: false,
376
+ returnByValue: true,
377
+ }) as { result?: { value?: boolean } };
378
+ return result?.result?.value === true;
379
+ } catch {
380
+ return false;
381
+ }
382
+ }
383
+
384
+ async function scrollDown(): Promise<void> {
385
+ if (!navigationClient) return;
386
+ try {
387
+ await navigationClient.send('Runtime.evaluate', {
388
+ expression: 'window.scrollBy(0, 800)',
389
+ awaitPromise: false,
390
+ });
391
+ } catch { /* ignore */ }
392
+ }
393
+
394
+ // ─── Auto mode ───────────────────────────────────────────────────────────────
395
+
396
+ async function runAutoMode() {
397
+ console.log('\n🚗 Auto mode: navigating Chrome through X.com...\n');
398
+
399
+ // Enable Page domain for navigation
400
+ if (navigationClient) {
401
+ await navigationClient.send('Page.enable').catch(() => {});
402
+ }
403
+
404
+ // Navigate to home first to discover the screen name
405
+ await navigateTo('https://x.com/home');
406
+ const screenName = await getScreenName();
407
+ if (screenName) {
408
+ console.log(` Detected user: @${screenName}\n`);
409
+ // Fill in profile-based URLs
410
+ for (const step of GUIDE_STEPS) {
411
+ if (step.label === 'Likes') step.url = `https://x.com/${screenName}/likes`;
412
+ if (step.label === 'Followers') step.url = `https://x.com/${screenName}/followers`;
413
+ if (step.label === 'Following') step.url = `https://x.com/${screenName}/following`;
414
+ if (step.label === 'Media') step.url = `https://x.com/${screenName}/media`;
415
+ }
416
+ } else {
417
+ console.log(' Could not detect screen name — some steps will use click navigation\n');
418
+ }
419
+
420
+ const completedSteps: string[] = [];
421
+ const failedSteps: string[] = [];
422
+
423
+ for (const step of GUIDE_STEPS) {
424
+ const alreadySeen = step.expectedQueries.some(q => seenQueries.has(q));
425
+ if (alreadySeen) {
426
+ console.log(` ✓ ${step.label} (already captured)`);
427
+ completedSteps.push(step.label);
428
+ continue;
429
+ }
430
+
431
+ process.stdout.write(` ⏳ ${step.label}...`);
432
+
433
+ // Navigate if URL provided
434
+ if (step.url) {
435
+ await navigateTo(step.url);
436
+ }
437
+
438
+ // Click if selector provided
439
+ if (step.clickSelector) {
440
+ await new Promise(r => setTimeout(r, 1500)); // wait for page to settle
441
+ const clicked = await clickElement(step.clickSelector);
442
+ if (!clicked) {
443
+ // Try scrolling and clicking again
444
+ await scrollDown();
445
+ await new Promise(r => setTimeout(r, 1000));
446
+ await clickElement(step.clickSelector);
447
+ }
448
+ await new Promise(r => setTimeout(r, 2000)); // wait for navigation
449
+ }
450
+
451
+ // Scroll to trigger lazy-loaded content
452
+ await scrollDown();
453
+
454
+ // Wait for any expected query
455
+ const seen = await waitForAnyQuery(step.expectedQueries, 10000);
456
+
457
+ if (seen) {
458
+ const captured = step.expectedQueries.filter(q => seenQueries.has(q));
459
+ console.log(`\r ✅ ${step.label} → ${captured.join(', ')}`);
460
+ completedSteps.push(step.label);
461
+ } else {
462
+ console.log(`\r ⚠️ ${step.label} → no queries captured (page may need manual interaction)`);
463
+ failedSteps.push(step.label);
464
+ }
465
+ }
466
+
467
+ console.log(`\n🏁 Auto navigation complete: ${completedSteps.length}/${GUIDE_STEPS.length} steps succeeded`);
468
+ if (failedSteps.length > 0) {
469
+ console.log(` Missed: ${failedSteps.join(', ')}`);
470
+ }
471
+
472
+ // Finish
473
+ printSummary();
474
+ process.exit(0);
475
+ }
476
+
477
+ // ─── Summary ─────────────────────────────────────────────────────────────────
478
+
479
+ function printSummary() {
480
+ console.log(`\n\n${'='.repeat(60)}`);
481
+ console.log(` Captured ${captured.length} GraphQL requests`);
482
+ console.log(`${'='.repeat(60)}\n`);
483
+
484
+ // Dedupe by queryName
485
+ const seen = new Set<string>();
486
+ const unique = captured.filter(q => {
487
+ if (seen.has(q.queryName)) return false;
488
+ seen.add(q.queryName);
489
+ return true;
490
+ });
491
+
492
+ // Print table
493
+ const nameWidth = Math.max(25, ...unique.map(q => q.queryName.length));
494
+ const idWidth = 22;
495
+ const methodWidth = 6;
496
+
497
+ console.log(
498
+ ` ${'Query'.padEnd(nameWidth)} ${'QueryID'.padEnd(idWidth)} ${'Method'.padEnd(methodWidth)} Variables`,
499
+ );
500
+ console.log(` ${'─'.repeat(nameWidth)} ${'─'.repeat(idWidth)} ${'─'.repeat(methodWidth)} ${'─'.repeat(30)}`);
501
+
502
+ for (const q of unique) {
503
+ const varKeys = q.variables && typeof q.variables === 'object'
504
+ ? Object.keys(q.variables as Record<string, unknown>).join(', ')
505
+ : '—';
506
+ console.log(
507
+ ` ${q.queryName.padEnd(nameWidth)} ${q.queryId.padEnd(idWidth)} ${q.method.padEnd(methodWidth)} ${varKeys}`,
508
+ );
509
+ }
510
+
511
+ // Gap analysis
512
+ if (!captureAll) {
513
+ const notSeen = [...RELEVANT_QUERIES].filter(q => !seenQueries.has(q));
514
+ if (notSeen.length > 0) {
515
+ console.log(`\n ⚠️ Not captured (${notSeen.length}):`);
516
+ for (const q of notSeen) {
517
+ console.log(` • ${q}`);
518
+ }
519
+ } else {
520
+ console.log('\n ✅ All relevant queries captured!');
521
+ }
522
+ }
523
+
524
+ // Save summary
525
+ const summaryPath = `${CAPTURE_DIR}/summary.json`;
526
+ const summary = {
527
+ capturedAt: new Date().toISOString(),
528
+ totalRequests: captured.length,
529
+ uniqueQueries: unique.map(q => ({
530
+ queryName: q.queryName,
531
+ queryId: q.queryId,
532
+ method: q.method,
533
+ variableKeys: q.variables && typeof q.variables === 'object'
534
+ ? Object.keys(q.variables as Record<string, unknown>)
535
+ : [],
536
+ })),
537
+ notCaptured: captureAll ? [] : [...RELEVANT_QUERIES].filter(q => !seenQueries.has(q)),
538
+ };
539
+ Bun.write(summaryPath, JSON.stringify(summary, null, 2));
540
+ console.log(`\n Summary saved to ${summaryPath}`);
541
+ console.log(` Individual captures in ${CAPTURE_DIR}/`);
542
+ }
543
+
544
+ // ─── Main ────────────────────────────────────────────────────────────────────
545
+
546
+ if (autoMode) {
547
+ console.log('\nRecording X.com GraphQL requests (auto mode)...');
548
+ console.log(`Filter: ${captureAll ? 'ALL queries' : `${RELEVANT_QUERIES.size} relevant queries`}`);
549
+ // Give network listeners a moment to settle, then start auto-navigation
550
+ setTimeout(() => runAutoMode(), 1000);
551
+ } else {
552
+ console.log('\nRecording X.com GraphQL requests...');
553
+ console.log(`Filter: ${captureAll ? 'ALL queries' : `${RELEVANT_QUERIES.size} relevant queries`}`);
554
+ console.log('Browse X in Chrome — visit a profile, scroll tweets, search.');
555
+ console.log('Press Ctrl+C to stop and dump results.\n');
556
+ }
557
+
558
+ // Ctrl+C handler
559
+ process.on('SIGINT', () => {
560
+ printSummary();
561
+ process.exit(0);
562
+ });
@@ -47,10 +47,11 @@ const SWIFT_OMIT_ALLOWLIST = new Set<string>([
47
47
  // Watcher messages — not yet consumed by the macOS client
48
48
  'watcher_escalation',
49
49
  'watcher_notification',
50
+ // Agent heartbeat alerts — not yet consumed by the macOS client
51
+ 'agent_heartbeat_alert',
50
52
  // Browser handoff — not yet consumed by the macOS client
51
53
  'browser_handoff_request',
52
54
  // Work item messages — not yet consumed by the macOS client
53
- 'work_item_create_response',
54
55
  'work_item_get_response',
55
56
  'work_item_run_task_response',
56
57
  'work_item_status_changed',