vellum 0.2.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (361) hide show
  1. package/README.md +15 -2
  2. package/bun.lock +5 -2
  3. package/package.json +4 -2
  4. package/scripts/capture-x-graphql.ts +562 -0
  5. package/scripts/ipc/check-swift-decoder-drift.ts +2 -1
  6. package/scripts/test.sh +5 -0
  7. package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +161 -34
  8. package/src/__tests__/account-registry.test.ts +2 -1
  9. package/src/__tests__/agent-heartbeat-service.test.ts +250 -0
  10. package/src/__tests__/app-bundler.test.ts +12 -33
  11. package/src/__tests__/asset-materialize-tool.test.ts +16 -15
  12. package/src/__tests__/asset-search-tool.test.ts +23 -22
  13. package/src/__tests__/attachments-store.test.ts +56 -127
  14. package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +5 -4
  15. package/src/__tests__/browser-skill-endstate.test.ts +5 -8
  16. package/src/__tests__/call-bridge.test.ts +385 -0
  17. package/src/__tests__/call-constants.test.ts +40 -0
  18. package/src/__tests__/call-orchestrator.test.ts +454 -0
  19. package/src/__tests__/call-recovery.test.ts +518 -0
  20. package/src/__tests__/call-routes-http.test.ts +459 -0
  21. package/src/__tests__/call-state-machine.test.ts +143 -0
  22. package/src/__tests__/call-state.test.ts +133 -0
  23. package/src/__tests__/call-store.test.ts +691 -0
  24. package/src/__tests__/cli-discover.test.ts +1 -1
  25. package/src/__tests__/commit-message-enrichment-service.test.ts +550 -0
  26. package/src/__tests__/compaction.benchmark.test.ts +176 -0
  27. package/src/__tests__/computer-use-tools.test.ts +250 -0
  28. package/src/__tests__/config-schema.test.ts +348 -3
  29. package/src/__tests__/conflict-store.test.ts +2 -1
  30. package/src/__tests__/contacts-tools.test.ts +331 -0
  31. package/src/__tests__/conversation-store.test.ts +30 -32
  32. package/src/__tests__/credential-security-invariants.test.ts +4 -0
  33. package/src/__tests__/date-context.test.ts +373 -0
  34. package/src/__tests__/db-schedule-syntax-migration.test.ts +129 -0
  35. package/src/__tests__/doordash-session.test.ts +9 -0
  36. package/src/__tests__/fixtures/media-reuse-fixtures.ts +3 -3
  37. package/src/__tests__/followup-tools.test.ts +303 -0
  38. package/src/__tests__/handlers-twitter-config.test.ts +718 -0
  39. package/src/__tests__/intent-routing.test.ts +64 -57
  40. package/src/__tests__/ipc-roundtrip.benchmark.test.ts +237 -0
  41. package/src/__tests__/ipc-snapshot.test.ts +96 -28
  42. package/src/__tests__/llm-usage-store.test.ts +3 -8
  43. package/src/__tests__/media-generate-image.test.ts +1 -1
  44. package/src/__tests__/media-reuse-story.e2e.test.ts +7 -7
  45. package/src/__tests__/memory-retrieval.benchmark.test.ts +430 -0
  46. package/src/__tests__/parallel-tool.benchmark.test.ts +294 -0
  47. package/src/__tests__/playbook-tools.test.ts +342 -0
  48. package/src/__tests__/profile-compiler.test.ts +2 -1
  49. package/src/__tests__/provider-streaming.benchmark.test.ts +773 -0
  50. package/src/__tests__/recurrence-engine-rruleset.test.ts +78 -0
  51. package/src/__tests__/recurrence-engine.test.ts +69 -0
  52. package/src/__tests__/recurrence-types.test.ts +71 -0
  53. package/src/__tests__/registry.test.ts +17 -10
  54. package/src/__tests__/relay-server.test.ts +633 -0
  55. package/src/__tests__/reminder-store.test.ts +6 -3
  56. package/src/__tests__/reminder.test.ts +43 -77
  57. package/src/__tests__/run-orchestrator-assistant-events.test.ts +222 -0
  58. package/src/__tests__/run-orchestrator.test.ts +7 -7
  59. package/src/__tests__/runtime-attachment-metadata.test.ts +19 -20
  60. package/src/__tests__/runtime-runs-http.test.ts +5 -23
  61. package/src/__tests__/runtime-runs.test.ts +11 -11
  62. package/src/__tests__/schedule-store.test.ts +482 -0
  63. package/src/__tests__/schedule-tools.test.ts +700 -0
  64. package/src/__tests__/scheduler-recurrence.test.ts +329 -0
  65. package/src/__tests__/server-history-render.test.ts +14 -13
  66. package/src/__tests__/session-error.test.ts +28 -0
  67. package/src/__tests__/session-init.benchmark.test.ts +462 -0
  68. package/src/__tests__/session-queue.test.ts +89 -16
  69. package/src/__tests__/session-runtime-assembly.test.ts +161 -0
  70. package/src/__tests__/session-surfaces-task-progress.test.ts +104 -0
  71. package/src/__tests__/signup-e2e.test.ts +2 -1
  72. package/src/__tests__/skill-projection.benchmark.test.ts +328 -0
  73. package/src/__tests__/skill-script-runner.test.ts +159 -0
  74. package/src/__tests__/speaker-identification.test.ts +52 -0
  75. package/src/__tests__/subagent-manager-notify.test.ts +42 -10
  76. package/src/__tests__/subagent-tools.test.ts +141 -41
  77. package/src/__tests__/task-compiler.test.ts +2 -1
  78. package/src/__tests__/task-runner.test.ts +2 -1
  79. package/src/__tests__/task-scheduler.test.ts +2 -1
  80. package/src/__tests__/task-tools.test.ts +49 -56
  81. package/src/__tests__/tool-audit-listener.test.ts +1 -0
  82. package/src/__tests__/tool-domain-event-publisher.test.ts +2 -0
  83. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +500 -0
  84. package/src/__tests__/tool-executor.test.ts +13 -17
  85. package/src/__tests__/turn-commit.test.ts +273 -2
  86. package/src/__tests__/twilio-provider.test.ts +143 -0
  87. package/src/__tests__/twilio-routes.test.ts +789 -0
  88. package/src/__tests__/twitter-auth-handler.test.ts +581 -0
  89. package/src/__tests__/view-image-tool.test.ts +217 -0
  90. package/src/__tests__/workspace-git-service.test.ts +403 -0
  91. package/src/__tests__/workspace-heartbeat-service.test.ts +141 -2
  92. package/src/agent-heartbeat/agent-heartbeat-service.ts +155 -0
  93. package/src/bundler/app-bundler.ts +35 -14
  94. package/src/calls/call-bridge.ts +95 -0
  95. package/src/calls/call-constants.ts +48 -0
  96. package/src/calls/call-domain.ts +276 -0
  97. package/src/calls/call-orchestrator.ts +390 -0
  98. package/src/calls/call-recovery.ts +207 -0
  99. package/src/calls/call-state-machine.ts +68 -0
  100. package/src/calls/call-state.ts +64 -0
  101. package/src/calls/call-store.ts +416 -0
  102. package/src/calls/relay-server.ts +335 -0
  103. package/src/calls/speaker-identification.ts +213 -0
  104. package/src/calls/twilio-config.ts +34 -0
  105. package/src/calls/twilio-provider.ts +173 -0
  106. package/src/calls/twilio-routes.ts +250 -0
  107. package/src/calls/types.ts +37 -0
  108. package/src/calls/voice-provider.ts +14 -0
  109. package/src/cli/config-commands.ts +334 -0
  110. package/src/cli/core-commands.ts +776 -0
  111. package/src/cli/doordash.ts +256 -25
  112. package/src/cli/ipc-client.ts +82 -0
  113. package/src/cli/map.ts +246 -0
  114. package/src/cli/twitter.ts +575 -0
  115. package/src/cli.ts +7 -5
  116. package/src/commands/__tests__/cc-command-registry.test.ts +319 -0
  117. package/src/commands/cc-command-registry.ts +209 -0
  118. package/src/config/bundled-skills/contacts/SKILL.md +39 -0
  119. package/src/config/bundled-skills/contacts/TOOLS.json +122 -0
  120. package/src/config/bundled-skills/contacts/tools/contact-merge.ts +9 -0
  121. package/src/config/bundled-skills/contacts/tools/contact-search.ts +9 -0
  122. package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +9 -0
  123. package/src/config/bundled-skills/document/SKILL.md +18 -0
  124. package/src/config/bundled-skills/document/TOOLS.json +53 -0
  125. package/src/config/bundled-skills/document/tools/document-create.ts +9 -0
  126. package/src/config/bundled-skills/document/tools/document-update.ts +9 -0
  127. package/src/config/bundled-skills/doordash/SKILL.md +163 -0
  128. package/src/config/bundled-skills/followups/SKILL.md +32 -0
  129. package/src/config/bundled-skills/followups/TOOLS.json +100 -0
  130. package/src/config/bundled-skills/followups/tools/followup-create.ts +9 -0
  131. package/src/config/bundled-skills/followups/tools/followup-list.ts +9 -0
  132. package/src/config/bundled-skills/followups/tools/followup-resolve.ts +9 -0
  133. package/src/config/bundled-skills/image-studio/TOOLS.json +2 -2
  134. package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +2 -24
  135. package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +2 -1
  136. package/src/config/bundled-skills/playbooks/SKILL.md +31 -0
  137. package/src/config/bundled-skills/playbooks/TOOLS.json +126 -0
  138. package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +9 -0
  139. package/src/config/bundled-skills/playbooks/tools/playbook-delete.ts +9 -0
  140. package/src/config/bundled-skills/playbooks/tools/playbook-list.ts +9 -0
  141. package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +9 -0
  142. package/src/config/bundled-skills/reminder/SKILL.md +20 -0
  143. package/src/config/bundled-skills/reminder/TOOLS.json +67 -0
  144. package/src/config/bundled-skills/reminder/tools/reminder-cancel.ts +9 -0
  145. package/src/config/bundled-skills/reminder/tools/reminder-create.ts +9 -0
  146. package/src/config/bundled-skills/reminder/tools/reminder-list.ts +9 -0
  147. package/src/config/bundled-skills/schedule/SKILL.md +74 -0
  148. package/src/config/bundled-skills/schedule/TOOLS.json +135 -0
  149. package/src/config/bundled-skills/schedule/tools/schedule-create.ts +9 -0
  150. package/src/config/bundled-skills/schedule/tools/schedule-delete.ts +9 -0
  151. package/src/config/bundled-skills/schedule/tools/schedule-list.ts +9 -0
  152. package/src/config/bundled-skills/schedule/tools/schedule-update.ts +9 -0
  153. package/src/config/bundled-skills/subagent/SKILL.md +25 -0
  154. package/src/config/bundled-skills/subagent/TOOLS.json +107 -0
  155. package/src/config/bundled-skills/subagent/tools/subagent-abort.ts +9 -0
  156. package/src/config/bundled-skills/subagent/tools/subagent-message.ts +9 -0
  157. package/src/config/bundled-skills/subagent/tools/subagent-read.ts +9 -0
  158. package/src/config/bundled-skills/subagent/tools/subagent-spawn.ts +9 -0
  159. package/src/config/bundled-skills/subagent/tools/subagent-status.ts +9 -0
  160. package/src/config/bundled-skills/tasks/SKILL.md +28 -0
  161. package/src/config/bundled-skills/tasks/TOOLS.json +256 -0
  162. package/src/config/bundled-skills/tasks/tools/task-delete.ts +9 -0
  163. package/src/config/bundled-skills/tasks/tools/task-list-add.ts +9 -0
  164. package/src/config/bundled-skills/tasks/tools/task-list-remove.ts +9 -0
  165. package/src/config/bundled-skills/tasks/tools/task-list-show.ts +9 -0
  166. package/src/config/bundled-skills/tasks/tools/task-list-update.ts +9 -0
  167. package/src/config/bundled-skills/tasks/tools/task-list.ts +9 -0
  168. package/src/config/bundled-skills/tasks/tools/task-run.ts +9 -0
  169. package/src/config/bundled-skills/tasks/tools/task-save.ts +9 -0
  170. package/src/config/bundled-skills/twitter/SKILL.md +134 -0
  171. package/src/config/bundled-skills/watcher/SKILL.md +27 -0
  172. package/src/config/bundled-skills/watcher/TOOLS.json +147 -0
  173. package/src/config/bundled-skills/watcher/tools/watcher-create.ts +9 -0
  174. package/src/config/bundled-skills/watcher/tools/watcher-delete.ts +9 -0
  175. package/src/config/bundled-skills/watcher/tools/watcher-digest.ts +9 -0
  176. package/src/config/bundled-skills/watcher/tools/watcher-list.ts +9 -0
  177. package/src/config/bundled-skills/watcher/tools/watcher-update.ts +9 -0
  178. package/src/config/defaults.ts +44 -0
  179. package/src/config/loader.ts +4 -1
  180. package/src/config/schema.ts +218 -1
  181. package/src/config/system-prompt.ts +100 -6
  182. package/src/config/templates/IDENTITY.md +7 -0
  183. package/src/config/types.ts +5 -0
  184. package/src/contacts/contact-store.ts +4 -4
  185. package/src/daemon/assistant-attachments.ts +10 -0
  186. package/src/daemon/classifier.ts +3 -1
  187. package/src/daemon/computer-use-session.ts +3 -1
  188. package/src/daemon/date-context.ts +136 -0
  189. package/src/daemon/handlers/apps.ts +16 -1
  190. package/src/daemon/handlers/browser.ts +54 -0
  191. package/src/daemon/handlers/computer-use.ts +7 -1
  192. package/src/daemon/handlers/config.ts +192 -4
  193. package/src/daemon/handlers/diagnostics.ts +5 -1
  194. package/src/daemon/handlers/documents.ts +18 -29
  195. package/src/daemon/handlers/home-base.ts +5 -1
  196. package/src/daemon/handlers/index.ts +40 -271
  197. package/src/daemon/handlers/misc.ts +9 -1
  198. package/src/daemon/handlers/publish.ts +6 -1
  199. package/src/daemon/handlers/sessions.ts +65 -12
  200. package/src/daemon/handlers/shared.ts +36 -1
  201. package/src/daemon/handlers/signing.ts +37 -0
  202. package/src/daemon/handlers/skills.ts +20 -6
  203. package/src/daemon/handlers/subagents.ts +8 -3
  204. package/src/daemon/handlers/twitter-auth.ts +169 -0
  205. package/src/daemon/handlers/work-items.ts +495 -39
  206. package/src/daemon/ipc-contract-inventory.json +40 -4
  207. package/src/daemon/ipc-contract.ts +185 -37
  208. package/src/daemon/ipc-protocol.ts +7 -2
  209. package/src/daemon/lifecycle.ts +48 -5
  210. package/src/daemon/main.ts +10 -4
  211. package/src/daemon/ride-shotgun-handler.ts +74 -10
  212. package/src/daemon/server.ts +144 -29
  213. package/src/daemon/session-agent-loop.ts +887 -0
  214. package/src/daemon/session-attachments.ts +28 -5
  215. package/src/daemon/session-error.ts +24 -3
  216. package/src/daemon/session-lifecycle.ts +147 -0
  217. package/src/daemon/session-media-retry.ts +147 -0
  218. package/src/daemon/session-messaging.ts +145 -0
  219. package/src/daemon/session-notifiers.ts +164 -0
  220. package/src/daemon/session-process.ts +2 -2
  221. package/src/daemon/session-queue-manager.ts +1 -0
  222. package/src/daemon/session-runtime-assembly.ts +52 -0
  223. package/src/daemon/session-skill-tools.ts +124 -5
  224. package/src/daemon/session-slash.ts +3 -0
  225. package/src/daemon/session-surfaces.ts +77 -2
  226. package/src/daemon/session-tool-setup.ts +222 -2
  227. package/src/daemon/session-usage.ts +0 -2
  228. package/src/daemon/session.ts +114 -1365
  229. package/src/daemon/video-thumbnail.ts +60 -0
  230. package/src/doordash/client.ts +121 -27
  231. package/src/doordash/queries.ts +1 -2
  232. package/src/export/formatter.ts +3 -1
  233. package/src/followups/followup-store.ts +4 -2
  234. package/src/followups/types.ts +6 -0
  235. package/src/hooks/templates.ts +1 -1
  236. package/src/index.ts +32 -1151
  237. package/src/media/gemini-image-service.ts +1 -1
  238. package/src/memory/attachments-store.ts +28 -83
  239. package/src/memory/channel-delivery-store.ts +7 -21
  240. package/src/memory/clarification-resolver.ts +6 -5
  241. package/src/memory/contradiction-checker.ts +3 -2
  242. package/src/memory/conversation-key-store.ts +10 -29
  243. package/src/memory/conversation-store.ts +2 -1
  244. package/src/memory/db.ts +362 -2
  245. package/src/memory/entity-extractor.ts +6 -3
  246. package/src/memory/items-extractor.ts +5 -4
  247. package/src/memory/jobs-store.ts +3 -2
  248. package/src/memory/llm-usage-store.ts +1 -2
  249. package/src/memory/runs-store.ts +1 -2
  250. package/src/memory/schema.ts +65 -2
  251. package/src/messaging/style-analyzer.ts +3 -2
  252. package/src/messaging/thread-summarizer.ts +8 -12
  253. package/src/messaging/triage-engine.ts +4 -2
  254. package/src/providers/openrouter/client.ts +20 -0
  255. package/src/providers/registry.ts +8 -0
  256. package/src/runtime/http-server.ts +277 -25
  257. package/src/runtime/http-types.ts +0 -2
  258. package/src/runtime/routes/attachment-routes.ts +5 -6
  259. package/src/runtime/routes/call-routes.ts +140 -0
  260. package/src/runtime/routes/channel-routes.ts +12 -19
  261. package/src/runtime/routes/conversation-routes.ts +5 -9
  262. package/src/runtime/routes/run-routes.ts +4 -8
  263. package/src/runtime/run-orchestrator.ts +39 -6
  264. package/src/schedule/recurrence-engine.ts +138 -0
  265. package/src/schedule/recurrence-types.ts +67 -0
  266. package/src/schedule/schedule-store.ts +102 -57
  267. package/src/schedule/scheduler.ts +9 -6
  268. package/src/security/oauth2.ts +29 -4
  269. package/src/security/secret-allowlist.ts +46 -0
  270. package/src/skills/clawhub.ts +1 -1
  271. package/src/subagent/manager.ts +40 -8
  272. package/src/swarm/backend-claude-code.ts +64 -9
  273. package/src/swarm/worker-prompts.ts +2 -1
  274. package/src/tasks/SPEC.md +34 -28
  275. package/src/tasks/ephemeral-permissions.ts +16 -7
  276. package/src/tasks/task-compiler.ts +5 -4
  277. package/src/tasks/task-runner.ts +10 -5
  278. package/src/tasks/task-scheduler.ts +1 -1
  279. package/src/tasks/tool-sanitizer.ts +36 -0
  280. package/src/tools/assets/search.ts +4 -4
  281. package/src/tools/browser/api-map.ts +220 -0
  282. package/src/tools/browser/auto-navigate.ts +270 -0
  283. package/src/tools/browser/browser-execution.ts +2 -1
  284. package/src/tools/browser/browser-manager.ts +2 -2
  285. package/src/tools/browser/network-recorder.ts +5 -4
  286. package/src/tools/browser/x-auto-navigate.ts +207 -0
  287. package/src/tools/calls/call-end.ts +67 -0
  288. package/src/tools/calls/call-start.ts +73 -0
  289. package/src/tools/calls/call-status.ts +81 -0
  290. package/src/tools/claude-code/claude-code.ts +77 -11
  291. package/src/tools/contacts/contact-merge.ts +46 -78
  292. package/src/tools/contacts/contact-search.ts +35 -79
  293. package/src/tools/contacts/contact-upsert.ts +35 -108
  294. package/src/tools/credentials/vault.ts +21 -5
  295. package/src/tools/document/document-tool.ts +71 -144
  296. package/src/tools/executor.ts +129 -10
  297. package/src/tools/followups/followup_create.ts +46 -88
  298. package/src/tools/followups/followup_list.ts +34 -74
  299. package/src/tools/followups/followup_resolve.ts +31 -66
  300. package/src/tools/host-terminal/cli-discover.ts +2 -1
  301. package/src/tools/host-terminal/host-shell.ts +10 -0
  302. package/src/tools/memory/handlers.ts +5 -4
  303. package/src/tools/network/__tests__/web-search.test.ts +427 -0
  304. package/src/tools/network/script-proxy/__tests__/logging.test.ts +248 -0
  305. package/src/tools/network/script-proxy/__tests__/policy.test.ts +234 -0
  306. package/src/tools/network/script-proxy/__tests__/router.test.ts +76 -0
  307. package/src/tools/network/web-fetch.ts +18 -6
  308. package/src/tools/playbooks/index.ts +4 -5
  309. package/src/tools/playbooks/playbook-create.ts +3 -47
  310. package/src/tools/playbooks/playbook-delete.ts +1 -25
  311. package/src/tools/playbooks/playbook-list.ts +1 -28
  312. package/src/tools/playbooks/playbook-update.ts +3 -51
  313. package/src/tools/registry.ts +2 -4
  314. package/src/tools/reminder/reminder.ts +5 -78
  315. package/src/tools/schedule/create.ts +69 -74
  316. package/src/tools/schedule/delete.ts +21 -47
  317. package/src/tools/schedule/list.ts +55 -74
  318. package/src/tools/schedule/update.ts +77 -84
  319. package/src/tools/subagent/abort.ts +29 -58
  320. package/src/tools/subagent/message.ts +30 -63
  321. package/src/tools/subagent/read.ts +53 -84
  322. package/src/tools/subagent/spawn.ts +43 -82
  323. package/src/tools/subagent/status.ts +42 -71
  324. package/src/tools/swarm/delegate.ts +2 -1
  325. package/src/tools/tasks/index.ts +8 -6
  326. package/src/tools/tasks/task-delete.ts +69 -56
  327. package/src/tools/tasks/task-list.ts +31 -52
  328. package/src/tools/tasks/task-run.ts +74 -102
  329. package/src/tools/tasks/task-save.ts +33 -65
  330. package/src/tools/tasks/work-item-enqueue.ts +192 -134
  331. package/src/tools/tasks/work-item-list.ts +33 -78
  332. package/src/tools/tasks/work-item-remove.ts +60 -0
  333. package/src/tools/tasks/work-item-update.ts +114 -0
  334. package/src/tools/terminal/backends/native.ts +3 -1
  335. package/src/tools/tool-manifest.ts +20 -74
  336. package/src/tools/types.ts +6 -0
  337. package/src/tools/ui-surface/definitions.ts +6 -1
  338. package/src/tools/watch/screen-watch.ts +3 -1
  339. package/src/tools/watcher/create.ts +52 -98
  340. package/src/tools/watcher/delete.ts +20 -46
  341. package/src/tools/watcher/digest.ts +36 -70
  342. package/src/tools/watcher/list.ts +49 -79
  343. package/src/tools/watcher/update.ts +45 -91
  344. package/src/twitter/client.ts +690 -0
  345. package/src/twitter/session.ts +91 -0
  346. package/src/usage/types.ts +0 -1
  347. package/src/util/truncate.ts +6 -0
  348. package/src/watcher/providers/slack.ts +2 -1
  349. package/src/watcher/watcher-store.ts +3 -2
  350. package/src/work-items/work-item-store.ts +236 -2
  351. package/src/workspace/commit-message-enrichment-service.ts +284 -0
  352. package/src/workspace/commit-message-provider.ts +95 -0
  353. package/src/workspace/git-service.ts +272 -52
  354. package/src/workspace/heartbeat-service.ts +70 -13
  355. package/src/workspace/provider-commit-message-generator.ts +242 -0
  356. package/src/workspace/turn-commit.ts +100 -51
  357. package/src/tools/contacts/index.ts +0 -4
  358. package/src/tools/document/index.ts +0 -5
  359. package/src/tools/followups/index.ts +0 -3
  360. package/src/tools/subagent/index.ts +0 -5
  361. /package/src/__tests__/{memory-context-benchmark.test.ts → memory-context-benchmark.benchmark.test.ts} +0 -0
@@ -0,0 +1,220 @@
1
+ /**
2
+ * Recording analyzer that processes NetworkRecordedEntry[] into a deduplicated
3
+ * API map. Collapses ID-like path segments into {id} placeholders so repeated
4
+ * calls to the same endpoint are grouped together.
5
+ */
6
+
7
+ import { mkdirSync, writeFileSync } from 'fs';
8
+ import { join } from 'path';
9
+ import { getDataDir } from '../../util/platform.js';
10
+ import type { NetworkRecordedEntry } from './network-recording-types.js';
11
+
12
+ // ---------------------------------------------------------------------------
13
+ // Types
14
+ // ---------------------------------------------------------------------------
15
+
16
+ export interface ApiEndpoint {
17
+ method: string;
18
+ urlPattern: string;
19
+ exampleUrl: string;
20
+ queryParams: string[];
21
+ requestBodyKeys: string[];
22
+ responseStatus: number[];
23
+ responseBodyKeys: string[];
24
+ count: number;
25
+ }
26
+
27
+ export interface ApiMapResult {
28
+ domain: string;
29
+ analyzedAt: number;
30
+ totalRequests: number;
31
+ endpoints: ApiEndpoint[];
32
+ }
33
+
34
+ // ---------------------------------------------------------------------------
35
+ // Helpers
36
+ // ---------------------------------------------------------------------------
37
+
38
+ const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
39
+ const NUMERIC_RE = /^\d+$/;
40
+ const HEX_HASH_RE = /^[0-9a-f]{8,}$/i;
41
+
42
+ /** Returns true when a path segment looks like a dynamic ID. */
43
+ function isIdSegment(segment: string): boolean {
44
+ if (NUMERIC_RE.test(segment)) return true;
45
+ if (UUID_RE.test(segment)) return true;
46
+ if (HEX_HASH_RE.test(segment)) return true;
47
+ return false;
48
+ }
49
+
50
+ /** Replace ID-like path segments with `{id}`. */
51
+ function normalizePathSegments(pathname: string): string {
52
+ return pathname
53
+ .split('/')
54
+ .map((seg) => (isIdSegment(seg) ? '{id}' : seg))
55
+ .join('/');
56
+ }
57
+
58
+ /** Safely parse JSON, returning undefined on failure. */
59
+ function tryParseJson(text: string | undefined): Record<string, unknown> | undefined {
60
+ if (!text) return undefined;
61
+ try {
62
+ const parsed = JSON.parse(text);
63
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
64
+ return parsed as Record<string, unknown>;
65
+ }
66
+ } catch {
67
+ // not JSON
68
+ }
69
+ return undefined;
70
+ }
71
+
72
+ // ---------------------------------------------------------------------------
73
+ // Core analysis
74
+ // ---------------------------------------------------------------------------
75
+
76
+ export function analyzeApiMap(
77
+ entries: NetworkRecordedEntry[],
78
+ domain: string,
79
+ ): ApiMapResult {
80
+ const groups = new Map<
81
+ string,
82
+ {
83
+ method: string;
84
+ urlPattern: string;
85
+ exampleUrl: string;
86
+ queryParams: Set<string>;
87
+ requestBodyKeys: Set<string>;
88
+ responseStatus: Set<number>;
89
+ responseBodyKeys: Set<string>;
90
+ count: number;
91
+ }
92
+ >();
93
+
94
+ for (const entry of entries) {
95
+ const { request, response } = entry;
96
+ let parsed: URL;
97
+ try {
98
+ parsed = new URL(request.url);
99
+ } catch {
100
+ continue; // skip malformed URLs
101
+ }
102
+
103
+ const method = request.method.toUpperCase();
104
+ const urlPattern = `${parsed.hostname}${normalizePathSegments(parsed.pathname)}`;
105
+ const key = `${method} ${urlPattern}`;
106
+
107
+ let group = groups.get(key);
108
+ if (!group) {
109
+ group = {
110
+ method,
111
+ urlPattern,
112
+ exampleUrl: request.url,
113
+ queryParams: new Set(),
114
+ requestBodyKeys: new Set(),
115
+ responseStatus: new Set(),
116
+ responseBodyKeys: new Set(),
117
+ count: 0,
118
+ };
119
+ groups.set(key, group);
120
+ }
121
+
122
+ group.count++;
123
+
124
+ // Collect query param keys
125
+ for (const paramKey of parsed.searchParams.keys()) {
126
+ group.queryParams.add(paramKey);
127
+ }
128
+
129
+ // Request body keys (POST/PUT/PATCH)
130
+ if (['POST', 'PUT', 'PATCH'].includes(method)) {
131
+ const body = tryParseJson(request.postData);
132
+ if (body) {
133
+ for (const k of Object.keys(body)) {
134
+ group.requestBodyKeys.add(k);
135
+ }
136
+ }
137
+ }
138
+
139
+ // Response status
140
+ if (response) {
141
+ group.responseStatus.add(response.status);
142
+
143
+ // Response body keys
144
+ const resBody = tryParseJson(response.body);
145
+ if (resBody) {
146
+ for (const k of Object.keys(resBody)) {
147
+ group.responseBodyKeys.add(k);
148
+ }
149
+ }
150
+ }
151
+ }
152
+
153
+ const endpoints: ApiEndpoint[] = Array.from(groups.values()).map((g) => ({
154
+ method: g.method,
155
+ urlPattern: g.urlPattern,
156
+ exampleUrl: g.exampleUrl,
157
+ queryParams: Array.from(g.queryParams).sort(),
158
+ requestBodyKeys: Array.from(g.requestBodyKeys).sort(),
159
+ responseStatus: Array.from(g.responseStatus).sort((a, b) => a - b),
160
+ responseBodyKeys: Array.from(g.responseBodyKeys).sort(),
161
+ count: g.count,
162
+ }));
163
+
164
+ // Sort by count descending, then by urlPattern for stability
165
+ endpoints.sort((a, b) => b.count - a.count || a.urlPattern.localeCompare(b.urlPattern));
166
+
167
+ return {
168
+ domain,
169
+ analyzedAt: Date.now(),
170
+ totalRequests: entries.length,
171
+ endpoints,
172
+ };
173
+ }
174
+
175
+ // ---------------------------------------------------------------------------
176
+ // Persistence
177
+ // ---------------------------------------------------------------------------
178
+
179
+ export function saveApiMap(domain: string, result: ApiMapResult): string {
180
+ const dir = join(getDataDir(), 'api-maps');
181
+ mkdirSync(dir, { recursive: true });
182
+
183
+ const timestamp = Date.now();
184
+ const filePath = join(dir, `${domain}-${timestamp}.json`);
185
+ writeFileSync(filePath, JSON.stringify(result, null, 2));
186
+ return filePath;
187
+ }
188
+
189
+ // ---------------------------------------------------------------------------
190
+ // Pretty-print
191
+ // ---------------------------------------------------------------------------
192
+
193
+ export function printApiMapTable(result: ApiMapResult): void {
194
+ console.log(`\nAPI Map for ${result.domain} — ${result.totalRequests} total requests, ${result.endpoints.length} unique endpoints\n`);
195
+
196
+ const header = ['Method', 'URL Pattern', 'Count', 'Status', 'Query Params'];
197
+ const rows = result.endpoints.map((ep) => [
198
+ ep.method,
199
+ ep.urlPattern,
200
+ String(ep.count),
201
+ ep.responseStatus.join(',') || '-',
202
+ ep.queryParams.join(',') || '-',
203
+ ]);
204
+
205
+ // Calculate column widths
206
+ const widths = header.map((h, i) =>
207
+ Math.max(h.length, ...rows.map((r) => r[i].length)),
208
+ );
209
+
210
+ const sep = widths.map((w) => '-'.repeat(w)).join(' | ');
211
+ const fmt = (row: string[]) =>
212
+ row.map((cell, i) => cell.padEnd(widths[i])).join(' | ');
213
+
214
+ console.log(fmt(header));
215
+ console.log(sep);
216
+ for (const row of rows) {
217
+ console.log(fmt(row));
218
+ }
219
+ console.log();
220
+ }
@@ -0,0 +1,270 @@
1
+ /**
2
+ * CDP-based auto-navigation for any domain.
3
+ *
4
+ * Drives Chrome through a domain's pages by discovering internal links,
5
+ * so the NetworkRecorder captures the API surface without manual browsing.
6
+ */
7
+
8
+ import { getLogger } from '../../util/logger.js';
9
+
10
+ const log = getLogger('auto-navigate');
11
+
12
+ const CDP_BASE = 'http://localhost:9222';
13
+ const MAX_PAGES = 15;
14
+ const PAGE_WAIT_MS = 3500;
15
+ const SCROLL_WAIT_MS = 2000;
16
+
17
+ /** Minimal CDP client — connects to one page tab. */
18
+ class MiniCDP {
19
+ private ws: WebSocket | null = null;
20
+ private nextId = 1;
21
+ private callbacks = new Map<number, { resolve: (v: unknown) => void; reject: (e: Error) => void }>();
22
+
23
+ async connect(wsUrl: string): Promise<void> {
24
+ return new Promise((resolve, reject) => {
25
+ const ws = new WebSocket(wsUrl);
26
+ ws.onopen = () => { this.ws = ws; resolve(); };
27
+ ws.onerror = (e) => reject(new Error(`CDP error: ${e}`));
28
+ ws.onclose = () => {
29
+ this.ws = null;
30
+ for (const [, cb] of this.callbacks) {
31
+ cb.reject(new Error('WebSocket closed'));
32
+ }
33
+ this.callbacks.clear();
34
+ };
35
+ ws.onmessage = (event) => {
36
+ const msg = JSON.parse(String(event.data));
37
+ if (msg.id != null) {
38
+ const cb = this.callbacks.get(msg.id);
39
+ if (cb) {
40
+ this.callbacks.delete(msg.id);
41
+ msg.error ? cb.reject(new Error(msg.error.message)) : cb.resolve(msg.result);
42
+ }
43
+ }
44
+ };
45
+ });
46
+ }
47
+
48
+ async send(method: string, params?: Record<string, unknown>): Promise<unknown> {
49
+ if (!this.ws) throw new Error('Not connected');
50
+ const id = this.nextId++;
51
+ return new Promise((resolve, reject) => {
52
+ this.callbacks.set(id, { resolve, reject });
53
+ this.ws!.send(JSON.stringify({ id, method, params }));
54
+ });
55
+ }
56
+
57
+ close() { this.ws?.close(); }
58
+ }
59
+
60
+ /**
61
+ * Navigate Chrome through a domain's pages to trigger API calls.
62
+ * Discovers internal links from the DOM and visits up to ~15 unique paths.
63
+ *
64
+ * @param domain The domain to crawl (e.g. "example.com").
65
+ * @param abortSignal Optional signal to stop navigation early.
66
+ * @returns List of visited page URLs.
67
+ */
68
+ export async function autoNavigate(domain: string, abortSignal?: { aborted: boolean }): Promise<string[]> {
69
+ let wsUrl: string | null = null;
70
+ try {
71
+ const res = await fetch(`${CDP_BASE}/json/list`);
72
+ if (!res.ok) {
73
+ log.warn('CDP not available for auto-navigation');
74
+ return [];
75
+ }
76
+ const targets = (await res.json()) as Array<{ type: string; url: string; webSocketDebuggerUrl: string }>;
77
+ const domainTab = targets.find(t => {
78
+ if (t.type !== 'page') return false;
79
+ try {
80
+ const hostname = new URL(t.url).hostname;
81
+ return hostname === domain || hostname.endsWith('.' + domain);
82
+ } catch { return false; }
83
+ });
84
+ wsUrl = domainTab?.webSocketDebuggerUrl ?? targets.find(t => t.type === 'page')?.webSocketDebuggerUrl ?? null;
85
+ } catch (err) {
86
+ log.warn({ err }, 'Failed to discover Chrome tabs');
87
+ return [];
88
+ }
89
+
90
+ if (!wsUrl) {
91
+ log.warn('No Chrome tab found for auto-navigation');
92
+ return [];
93
+ }
94
+
95
+ const cdp = new MiniCDP();
96
+ try {
97
+ await cdp.connect(wsUrl);
98
+ } catch (err) {
99
+ log.warn({ err }, 'Failed to connect CDP for auto-navigation');
100
+ return [];
101
+ }
102
+
103
+ await cdp.send('Page.enable').catch(() => {});
104
+
105
+ const rootUrl = `https://${domain}/`;
106
+ const visited = new Set<string>();
107
+ const visitedUrls: string[] = [];
108
+
109
+ // Navigate to the domain root first
110
+ try {
111
+ await cdp.send('Page.navigate', { url: rootUrl });
112
+ await sleep(PAGE_WAIT_MS);
113
+ visited.add('/');
114
+ visitedUrls.push(rootUrl);
115
+ log.info({ url: rootUrl }, 'Visited root page');
116
+ } catch (err) {
117
+ log.warn({ err }, 'Failed to navigate to domain root');
118
+ cdp.close();
119
+ return [];
120
+ }
121
+
122
+ if (abortSignal?.aborted) { cdp.close(); return visitedUrls; }
123
+
124
+ // Scroll the root page to trigger lazy content
125
+ await scrollPage(cdp);
126
+ await sleep(SCROLL_WAIT_MS);
127
+
128
+ // Click common interactive elements on the root page
129
+ await clickInteractiveElements(cdp);
130
+ await sleep(SCROLL_WAIT_MS);
131
+
132
+ // Discover internal links from the current page
133
+ let discoveredLinks = await discoverInternalLinks(cdp, domain);
134
+ log.info({ count: discoveredLinks.length }, 'Discovered internal links from root');
135
+
136
+ // Visit discovered pages
137
+ for (const link of discoveredLinks) {
138
+ if (abortSignal?.aborted) break;
139
+ if (visited.size >= MAX_PAGES) break;
140
+ if (visited.has(link.key)) continue;
141
+
142
+ const url = link.url;
143
+ log.info({ url }, 'Auto-navigate visiting page');
144
+
145
+ try {
146
+ await cdp.send('Page.navigate', { url });
147
+ await sleep(PAGE_WAIT_MS);
148
+ visited.add(link.key);
149
+ visitedUrls.push(url);
150
+
151
+ // Scroll to trigger lazy-loaded content
152
+ await scrollPage(cdp);
153
+ await sleep(SCROLL_WAIT_MS);
154
+
155
+ // Click interactive elements to trigger more API calls
156
+ await clickInteractiveElements(cdp);
157
+ await sleep(1500);
158
+
159
+ // Discover more links from this page
160
+ const newLinks = await discoverInternalLinks(cdp, domain);
161
+ for (const nl of newLinks) {
162
+ if (!visited.has(nl.key) && !discoveredLinks.some(l => l.key === nl.key)) {
163
+ discoveredLinks.push(nl);
164
+ }
165
+ }
166
+
167
+ log.info({ url }, 'Auto-navigate page completed');
168
+ } catch (err) {
169
+ log.warn({ err, url }, 'Auto-navigate page failed');
170
+ }
171
+ }
172
+
173
+ cdp.close();
174
+ log.info({ visited: visitedUrls.length, total: discoveredLinks.length + 1 }, 'Auto-navigation finished');
175
+ return visitedUrls;
176
+ }
177
+
178
+ interface DiscoveredLink {
179
+ /** Full URL to navigate to (preserves subdomain). */
180
+ url: string;
181
+ /** Deduplication key: origin + pathname. */
182
+ key: string;
183
+ }
184
+
185
+ /** Extract internal links from the current page DOM, preserving subdomains. */
186
+ async function discoverInternalLinks(cdp: MiniCDP, domain: string): Promise<DiscoveredLink[]> {
187
+ try {
188
+ const result = await cdp.send('Runtime.evaluate', {
189
+ expression: `
190
+ (function() {
191
+ const domain = ${JSON.stringify(domain)};
192
+ const seen = new Set();
193
+ const links = [];
194
+ for (const a of document.querySelectorAll('a[href]')) {
195
+ const href = a.getAttribute('href');
196
+ if (!href) continue;
197
+ try {
198
+ const url = new URL(href, location.origin);
199
+ if (url.hostname !== domain && !url.hostname.endsWith('.' + domain)) continue;
200
+ const path = url.pathname;
201
+ // Skip anchors, query-only links, file downloads, and trivial paths
202
+ if (path === '/' || path === '') continue;
203
+ if (path.match(/\\.(png|jpg|jpeg|gif|svg|css|js|woff|pdf|zip)$/i)) continue;
204
+ const key = url.origin + url.pathname;
205
+ if (!seen.has(key)) {
206
+ seen.add(key);
207
+ links.push({ url: url.origin + url.pathname, key });
208
+ }
209
+ } catch { /* skip malformed URLs */ }
210
+ }
211
+ return links;
212
+ })()
213
+ `,
214
+ awaitPromise: false,
215
+ returnByValue: true,
216
+ }) as { result?: { value?: DiscoveredLink[] } };
217
+ return result?.result?.value ?? [];
218
+ } catch {
219
+ return [];
220
+ }
221
+ }
222
+
223
+ /** Scroll the page to trigger lazy-loaded content. */
224
+ async function scrollPage(cdp: MiniCDP): Promise<void> {
225
+ await cdp.send('Runtime.evaluate', {
226
+ expression: 'window.scrollBy(0, 800)',
227
+ awaitPromise: false,
228
+ }).catch(() => {});
229
+ }
230
+
231
+ /** Click common interactive elements (tabs, nav buttons) to trigger API calls. */
232
+ async function clickInteractiveElements(cdp: MiniCDP): Promise<void> {
233
+ const selectors = [
234
+ 'nav a:not([href="/"])',
235
+ '[role="tab"]',
236
+ '[role="tablist"] button',
237
+ 'button[data-tab]',
238
+ '.tab, .nav-tab, .nav-link',
239
+ ];
240
+
241
+ for (const selector of selectors) {
242
+ await clickInPage(cdp, selector);
243
+ await sleep(800);
244
+ }
245
+ }
246
+
247
+ async function clickInPage(cdp: MiniCDP, selector: string): Promise<boolean> {
248
+ try {
249
+ const result = await cdp.send('Runtime.evaluate', {
250
+ expression: `
251
+ (function() {
252
+ const el = document.querySelector(${JSON.stringify(selector)});
253
+ if (!el) return false;
254
+ el.scrollIntoView({ block: 'center' });
255
+ el.click();
256
+ return true;
257
+ })()
258
+ `,
259
+ awaitPromise: false,
260
+ returnByValue: true,
261
+ }) as { result?: { value?: boolean } };
262
+ return result?.result?.value === true;
263
+ } catch {
264
+ return false;
265
+ }
266
+ }
267
+
268
+ function sleep(ms: number): Promise<void> {
269
+ return new Promise(r => setTimeout(r, ms));
270
+ }
@@ -1,6 +1,7 @@
1
1
  import type { ToolContext, ToolExecutionResult } from '../types.js';
2
2
  import type { ImageContent } from '../../providers/types.js';
3
3
  import { getLogger } from '../../util/logger.js';
4
+ import { truncate } from '../../util/truncate.js';
4
5
  import {
5
6
  parseUrl,
6
7
  isPrivateOrLocalHost,
@@ -826,7 +827,7 @@ export async function executeBrowserWaitFor(
826
827
  `document.body?.innerText?.includes(${escaped})`,
827
828
  { timeout },
828
829
  );
829
- return { content: `Text "${text.slice(0, 80)}" appeared on page.`, isError: false };
830
+ return { content: `Text "${truncate(text, 80)}" appeared on page.`, isError: false };
830
831
  }
831
832
 
832
833
  // duration mode (milliseconds)
@@ -433,7 +433,7 @@ class BrowserManager {
433
433
  if (this.browserCdpSession) {
434
434
  try {
435
435
  await this.browserCdpSession.detach();
436
- } catch {}
436
+ } catch (e) { log.debug({ err: e }, 'CDP session detach failed during shutdown'); }
437
437
  this.browserCdpSession = null;
438
438
  this.browserWindowId = null;
439
439
  }
@@ -489,7 +489,7 @@ class BrowserManager {
489
489
  try {
490
490
  await cdp.send('Page.stopScreencast');
491
491
  await cdp.detach();
492
- } catch {}
492
+ } catch (e) { log.debug({ err: e }, 'Screencast stop / CDP detach failed during cleanup'); }
493
493
  this.cdpSessions.delete(sessionId);
494
494
  this.screencastCallbacks.delete(sessionId);
495
495
  }
@@ -6,6 +6,7 @@
6
6
  */
7
7
 
8
8
  import { getLogger } from '../../util/logger.js';
9
+ import { truncate } from '../../util/truncate.js';
9
10
  import type { NetworkRecordedEntry, NetworkRecordedRequest, ExtractedCredential } from './network-recording-types.js';
10
11
 
11
12
  const log = getLogger('network-recorder');
@@ -54,7 +55,7 @@ class DirectCDPClient {
54
55
  for (const h of handlers) h(msg.params ?? {});
55
56
  }
56
57
  }
57
- } catch {}
58
+ } catch (e) { log.debug({ err: e }, 'Failed to parse CDP WebSocket message'); }
58
59
  };
59
60
  });
60
61
  }
@@ -195,7 +196,7 @@ export class NetworkRecorder {
195
196
  for (const client of this.pageClients) {
196
197
  try {
197
198
  await client.send('Network.disable');
198
- } catch {}
199
+ } catch (e) { log.debug({ err: e }, 'Network.disable failed during cleanup'); }
199
200
  client.close();
200
201
  }
201
202
  this.pageClients = [];
@@ -273,7 +274,7 @@ export class NetworkRecorder {
273
274
  const method = (request.method as string) ?? 'GET';
274
275
  const postData = request.postData as string | undefined;
275
276
 
276
- log.debug({ url: url.slice(0, 120), method, requestId }, 'Request captured');
277
+ log.debug({ url: truncate(url, 120, ''), method, requestId }, 'Request captured');
277
278
 
278
279
  const recordedRequest: NetworkRecordedRequest = { method, url, headers, postData };
279
280
  const entry: NetworkRecordedEntry = {
@@ -307,7 +308,7 @@ export class NetworkRecorder {
307
308
  this.loginSignals.some(sig => entry.request.url.includes(sig))
308
309
  ) {
309
310
  this.loginDetectedFired = true;
310
- log.info({ url: entry.request.url.slice(0, 120) }, 'Login detected — will auto-stop in 5s');
311
+ log.info({ url: truncate(entry.request.url, 120, '') }, 'Login detected — will auto-stop in 5s');
311
312
  // Delay to let remaining network requests (cookies, session data) settle
312
313
  setTimeout(() => this.onLoginDetected?.(), 5000);
313
314
  }