vellum 0.2.1 → 0.2.7

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 +71 -100
  3. package/package.json +5 -3
  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 +133 -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__/asset-materialize-tool.test.ts +16 -15
  11. package/src/__tests__/asset-search-tool.test.ts +23 -22
  12. package/src/__tests__/attachments-store.test.ts +56 -127
  13. package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +5 -4
  14. package/src/__tests__/browser-skill-endstate.test.ts +4 -3
  15. package/src/__tests__/call-bridge.test.ts +385 -0
  16. package/src/__tests__/call-constants.test.ts +40 -0
  17. package/src/__tests__/call-orchestrator.test.ts +130 -4
  18. package/src/__tests__/call-recovery.test.ts +518 -0
  19. package/src/__tests__/call-routes-http.test.ts +459 -0
  20. package/src/__tests__/call-state-machine.test.ts +143 -0
  21. package/src/__tests__/call-store.test.ts +216 -1
  22. package/src/__tests__/cli-discover.test.ts +1 -1
  23. package/src/__tests__/commit-message-enrichment-service.test.ts +148 -7
  24. package/src/__tests__/compaction.benchmark.test.ts +176 -0
  25. package/src/__tests__/computer-use-tools.test.ts +250 -0
  26. package/src/__tests__/config-schema.test.ts +305 -3
  27. package/src/__tests__/conflict-store.test.ts +2 -1
  28. package/src/__tests__/contacts-tools.test.ts +331 -0
  29. package/src/__tests__/conversation-store.test.ts +30 -32
  30. package/src/__tests__/credential-security-invariants.test.ts +4 -0
  31. package/src/__tests__/date-context.test.ts +373 -0
  32. package/src/__tests__/db-schedule-syntax-migration.test.ts +129 -0
  33. package/src/__tests__/fixtures/media-reuse-fixtures.ts +3 -3
  34. package/src/__tests__/followup-tools.test.ts +303 -0
  35. package/src/__tests__/handlers-twilio-config.test.ts +221 -0
  36. package/src/__tests__/handlers-twitter-config.test.ts +718 -0
  37. package/src/__tests__/intent-routing.test.ts +64 -57
  38. package/src/__tests__/ipc-roundtrip.benchmark.test.ts +237 -0
  39. package/src/__tests__/ipc-snapshot.test.ts +71 -28
  40. package/src/__tests__/llm-usage-store.test.ts +3 -8
  41. package/src/__tests__/media-generate-image.test.ts +1 -1
  42. package/src/__tests__/media-reuse-story.e2e.test.ts +7 -7
  43. package/src/__tests__/memory-regressions.test.ts +100 -2
  44. package/src/__tests__/memory-retrieval.benchmark.test.ts +430 -0
  45. package/src/__tests__/parallel-tool.benchmark.test.ts +294 -0
  46. package/src/__tests__/playbook-tools.test.ts +342 -0
  47. package/src/__tests__/profile-compiler.test.ts +2 -1
  48. package/src/__tests__/provider-commit-message-generator.test.ts +303 -0
  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 +5 -3
  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 +8 -4
  58. package/src/__tests__/run-orchestrator.test.ts +4 -4
  59. package/src/__tests__/runtime-attachment-metadata.test.ts +7 -6
  60. package/src/__tests__/runtime-runs-http.test.ts +4 -4
  61. package/src/__tests__/runtime-runs.test.ts +4 -4
  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-conflict-gate.test.ts +28 -25
  67. package/src/__tests__/session-error.test.ts +28 -0
  68. package/src/__tests__/session-init.benchmark.test.ts +462 -0
  69. package/src/__tests__/session-queue.test.ts +71 -48
  70. package/src/__tests__/session-runtime-assembly.test.ts +161 -0
  71. package/src/__tests__/session-surfaces-task-progress.test.ts +104 -0
  72. package/src/__tests__/signup-e2e.test.ts +2 -1
  73. package/src/__tests__/skill-projection.benchmark.test.ts +328 -0
  74. package/src/__tests__/skill-script-runner.test.ts +159 -0
  75. package/src/__tests__/speaker-identification.test.ts +52 -0
  76. package/src/__tests__/subagent-manager-notify.test.ts +42 -10
  77. package/src/__tests__/subagent-tools.test.ts +141 -41
  78. package/src/__tests__/task-compiler.test.ts +2 -1
  79. package/src/__tests__/task-runner.test.ts +2 -1
  80. package/src/__tests__/task-scheduler.test.ts +2 -1
  81. package/src/__tests__/task-tools.test.ts +49 -56
  82. package/src/__tests__/tool-audit-listener.test.ts +1 -0
  83. package/src/__tests__/tool-domain-event-publisher.test.ts +2 -0
  84. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +500 -0
  85. package/src/__tests__/tool-executor.test.ts +13 -17
  86. package/src/__tests__/turn-commit.test.ts +218 -3
  87. package/src/__tests__/twilio-provider.test.ts +143 -0
  88. package/src/__tests__/twilio-routes.test.ts +789 -0
  89. package/src/__tests__/twitter-auth-handler.test.ts +581 -0
  90. package/src/__tests__/view-image-tool.test.ts +217 -0
  91. package/src/__tests__/workspace-git-service.test.ts +186 -0
  92. package/src/__tests__/workspace-heartbeat-service.test.ts +13 -3
  93. package/src/agent-heartbeat/agent-heartbeat-service.ts +155 -0
  94. package/src/bundler/app-bundler.ts +12 -8
  95. package/src/calls/__tests__/twilio-webhook-urls.test.ts +162 -0
  96. package/src/calls/call-bridge.ts +95 -0
  97. package/src/calls/call-constants.ts +43 -5
  98. package/src/calls/call-domain.ts +276 -0
  99. package/src/calls/call-orchestrator.ts +43 -17
  100. package/src/calls/call-recovery.ts +207 -0
  101. package/src/calls/call-state-machine.ts +68 -0
  102. package/src/calls/call-store.ts +192 -5
  103. package/src/calls/relay-server.ts +41 -4
  104. package/src/calls/speaker-identification.ts +213 -0
  105. package/src/calls/twilio-config.ts +8 -8
  106. package/src/calls/twilio-provider.ts +13 -9
  107. package/src/calls/twilio-routes.ts +90 -76
  108. package/src/calls/twilio-webhook-urls.ts +50 -0
  109. package/src/calls/types.ts +1 -1
  110. package/src/cli/config-commands.ts +334 -0
  111. package/src/cli/core-commands.ts +776 -0
  112. package/src/cli/doordash.ts +251 -1
  113. package/src/cli/ipc-client.ts +82 -0
  114. package/src/cli/map.ts +270 -0
  115. package/src/cli/twitter.ts +575 -0
  116. package/src/cli.ts +7 -5
  117. package/src/commands/__tests__/cc-command-registry.test.ts +319 -0
  118. package/src/commands/cc-command-registry.ts +209 -0
  119. package/src/config/bundled-skills/contacts/SKILL.md +39 -0
  120. package/src/config/bundled-skills/contacts/TOOLS.json +122 -0
  121. package/src/config/bundled-skills/contacts/tools/contact-merge.ts +9 -0
  122. package/src/config/bundled-skills/contacts/tools/contact-search.ts +9 -0
  123. package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +9 -0
  124. package/src/config/bundled-skills/document/SKILL.md +18 -0
  125. package/src/config/bundled-skills/document/TOOLS.json +53 -0
  126. package/src/config/bundled-skills/document/tools/document-create.ts +9 -0
  127. package/src/config/bundled-skills/document/tools/document-update.ts +9 -0
  128. package/src/config/bundled-skills/doordash/SKILL.md +82 -23
  129. package/src/config/bundled-skills/followups/SKILL.md +32 -0
  130. package/src/config/bundled-skills/followups/TOOLS.json +100 -0
  131. package/src/config/bundled-skills/followups/tools/followup-create.ts +9 -0
  132. package/src/config/bundled-skills/followups/tools/followup-list.ts +9 -0
  133. package/src/config/bundled-skills/followups/tools/followup-resolve.ts +9 -0
  134. package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +1 -23
  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 +34 -0
  179. package/src/config/loader.ts +4 -1
  180. package/src/config/schema.ts +165 -1
  181. package/src/config/system-prompt.ts +61 -16
  182. package/src/config/templates/IDENTITY.md +7 -0
  183. package/src/config/types.ts +4 -0
  184. package/src/config/vellum-skills/telegram-setup/SKILL.md +1 -5
  185. package/src/contacts/contact-store.ts +4 -4
  186. package/src/daemon/assistant-attachments.ts +10 -0
  187. package/src/daemon/classifier.ts +3 -1
  188. package/src/daemon/computer-use-session.ts +3 -1
  189. package/src/daemon/date-context.ts +136 -0
  190. package/src/daemon/handlers/apps.ts +16 -1
  191. package/src/daemon/handlers/browser.ts +54 -0
  192. package/src/daemon/handlers/computer-use.ts +7 -1
  193. package/src/daemon/handlers/config.ts +205 -5
  194. package/src/daemon/handlers/diagnostics.ts +5 -1
  195. package/src/daemon/handlers/documents.ts +18 -29
  196. package/src/daemon/handlers/home-base.ts +5 -1
  197. package/src/daemon/handlers/index.ts +40 -277
  198. package/src/daemon/handlers/misc.ts +9 -1
  199. package/src/daemon/handlers/publish.ts +6 -1
  200. package/src/daemon/handlers/sessions.ts +65 -12
  201. package/src/daemon/handlers/shared.ts +36 -1
  202. package/src/daemon/handlers/signing.ts +37 -0
  203. package/src/daemon/handlers/skills.ts +20 -6
  204. package/src/daemon/handlers/subagents.ts +8 -3
  205. package/src/daemon/handlers/twitter-auth.ts +169 -0
  206. package/src/daemon/handlers/work-items.ts +384 -68
  207. package/src/daemon/ipc-contract-inventory.json +32 -4
  208. package/src/daemon/ipc-contract.ts +156 -37
  209. package/src/daemon/ipc-protocol.ts +7 -2
  210. package/src/daemon/lifecycle.ts +21 -0
  211. package/src/daemon/main.ts +10 -4
  212. package/src/daemon/ride-shotgun-handler.ts +75 -10
  213. package/src/daemon/server.ts +143 -26
  214. package/src/daemon/session-agent-loop.ts +922 -0
  215. package/src/daemon/session-attachments.ts +28 -5
  216. package/src/daemon/session-conflict-gate.ts +18 -109
  217. package/src/daemon/session-error.ts +24 -3
  218. package/src/daemon/session-lifecycle.ts +147 -0
  219. package/src/daemon/session-media-retry.ts +147 -0
  220. package/src/daemon/session-messaging.ts +145 -0
  221. package/src/daemon/session-notifiers.ts +164 -0
  222. package/src/daemon/session-process.ts +2 -2
  223. package/src/daemon/session-queue-manager.ts +1 -0
  224. package/src/daemon/session-runtime-assembly.ts +52 -0
  225. package/src/daemon/session-skill-tools.ts +124 -5
  226. package/src/daemon/session-slash.ts +3 -0
  227. package/src/daemon/session-surfaces.ts +77 -2
  228. package/src/daemon/session-tool-setup.ts +216 -2
  229. package/src/daemon/session-usage.ts +0 -2
  230. package/src/daemon/session.ts +114 -1404
  231. package/src/daemon/video-thumbnail.ts +60 -0
  232. package/src/doordash/client.ts +121 -27
  233. package/src/doordash/queries.ts +1 -2
  234. package/src/export/formatter.ts +3 -1
  235. package/src/followups/followup-store.ts +4 -2
  236. package/src/followups/types.ts +6 -0
  237. package/src/hooks/templates.ts +1 -1
  238. package/src/index.ts +32 -1153
  239. package/src/memory/attachments-store.ts +28 -83
  240. package/src/memory/channel-delivery-store.ts +7 -21
  241. package/src/memory/clarification-resolver.ts +6 -5
  242. package/src/memory/conflict-intent.ts +114 -0
  243. package/src/memory/contradiction-checker.ts +3 -2
  244. package/src/memory/conversation-key-store.ts +10 -29
  245. package/src/memory/conversation-store.ts +2 -1
  246. package/src/memory/db.ts +96 -2
  247. package/src/memory/entity-extractor.ts +6 -3
  248. package/src/memory/items-extractor.ts +5 -4
  249. package/src/memory/job-handlers/conflict.ts +23 -1
  250. package/src/memory/jobs-store.ts +3 -2
  251. package/src/memory/llm-usage-store.ts +1 -2
  252. package/src/memory/runs-store.ts +1 -2
  253. package/src/memory/schema.ts +23 -2
  254. package/src/messaging/style-analyzer.ts +3 -2
  255. package/src/messaging/thread-summarizer.ts +8 -12
  256. package/src/messaging/triage-engine.ts +4 -2
  257. package/src/providers/openrouter/client.ts +20 -0
  258. package/src/providers/registry.ts +8 -0
  259. package/src/runtime/gateway-client.ts +36 -0
  260. package/src/runtime/http-server.ts +166 -22
  261. package/src/runtime/routes/attachment-routes.ts +2 -3
  262. package/src/runtime/routes/call-routes.ts +140 -0
  263. package/src/runtime/routes/channel-routes.ts +125 -88
  264. package/src/runtime/routes/conversation-routes.ts +5 -5
  265. package/src/runtime/routes/run-routes.ts +2 -2
  266. package/src/runtime/run-orchestrator.ts +9 -3
  267. package/src/schedule/recurrence-engine.ts +138 -0
  268. package/src/schedule/recurrence-types.ts +67 -0
  269. package/src/schedule/schedule-store.ts +102 -57
  270. package/src/schedule/scheduler.ts +9 -6
  271. package/src/security/oauth2.ts +29 -4
  272. package/src/security/secret-allowlist.ts +46 -0
  273. package/src/skills/clawhub.ts +1 -1
  274. package/src/subagent/manager.ts +40 -8
  275. package/src/swarm/backend-claude-code.ts +64 -9
  276. package/src/swarm/worker-prompts.ts +2 -1
  277. package/src/tasks/SPEC.md +34 -28
  278. package/src/tasks/ephemeral-permissions.ts +16 -7
  279. package/src/tasks/task-compiler.ts +5 -4
  280. package/src/tasks/task-runner.ts +10 -5
  281. package/src/tasks/task-scheduler.ts +1 -1
  282. package/src/tasks/tool-sanitizer.ts +36 -0
  283. package/src/tools/assets/search.ts +4 -4
  284. package/src/tools/browser/api-map.ts +293 -0
  285. package/src/tools/browser/auto-navigate.ts +270 -0
  286. package/src/tools/browser/browser-execution.ts +2 -1
  287. package/src/tools/browser/browser-manager.ts +2 -2
  288. package/src/tools/browser/network-recorder.ts +5 -4
  289. package/src/tools/browser/x-auto-navigate.ts +207 -0
  290. package/src/tools/calls/call-end.ts +17 -67
  291. package/src/tools/calls/call-start.ts +24 -85
  292. package/src/tools/calls/call-status.ts +35 -51
  293. package/src/tools/claude-code/claude-code.ts +207 -11
  294. package/src/tools/contacts/contact-merge.ts +46 -78
  295. package/src/tools/contacts/contact-search.ts +35 -79
  296. package/src/tools/contacts/contact-upsert.ts +35 -108
  297. package/src/tools/credentials/vault.ts +20 -4
  298. package/src/tools/document/document-tool.ts +71 -144
  299. package/src/tools/executor.ts +129 -10
  300. package/src/tools/followups/followup_create.ts +46 -88
  301. package/src/tools/followups/followup_list.ts +34 -74
  302. package/src/tools/followups/followup_resolve.ts +31 -66
  303. package/src/tools/host-terminal/cli-discover.ts +2 -1
  304. package/src/tools/host-terminal/host-shell.ts +10 -0
  305. package/src/tools/memory/handlers.ts +5 -4
  306. package/src/tools/network/__tests__/web-search.test.ts +427 -0
  307. package/src/tools/network/script-proxy/__tests__/logging.test.ts +248 -0
  308. package/src/tools/network/script-proxy/__tests__/policy.test.ts +234 -0
  309. package/src/tools/network/script-proxy/__tests__/router.test.ts +76 -0
  310. package/src/tools/network/web-fetch.ts +18 -6
  311. package/src/tools/playbooks/index.ts +4 -5
  312. package/src/tools/playbooks/playbook-create.ts +3 -47
  313. package/src/tools/playbooks/playbook-delete.ts +1 -25
  314. package/src/tools/playbooks/playbook-list.ts +1 -28
  315. package/src/tools/playbooks/playbook-update.ts +3 -51
  316. package/src/tools/reminder/reminder.ts +5 -78
  317. package/src/tools/schedule/create.ts +69 -74
  318. package/src/tools/schedule/delete.ts +21 -47
  319. package/src/tools/schedule/list.ts +55 -74
  320. package/src/tools/schedule/update.ts +77 -84
  321. package/src/tools/subagent/abort.ts +29 -58
  322. package/src/tools/subagent/message.ts +30 -63
  323. package/src/tools/subagent/read.ts +53 -84
  324. package/src/tools/subagent/spawn.ts +43 -82
  325. package/src/tools/subagent/status.ts +42 -71
  326. package/src/tools/swarm/delegate.ts +2 -1
  327. package/src/tools/tasks/index.ts +8 -8
  328. package/src/tools/tasks/task-delete.ts +60 -88
  329. package/src/tools/tasks/task-list.ts +31 -52
  330. package/src/tools/tasks/task-run.ts +72 -108
  331. package/src/tools/tasks/task-save.ts +33 -65
  332. package/src/tools/tasks/work-item-enqueue.ts +183 -215
  333. package/src/tools/tasks/work-item-list.ts +33 -63
  334. package/src/tools/tasks/work-item-remove.ts +45 -97
  335. package/src/tools/tasks/work-item-update.ts +91 -163
  336. package/src/tools/terminal/backends/native.ts +3 -1
  337. package/src/tools/tool-manifest.ts +0 -62
  338. package/src/tools/types.ts +6 -0
  339. package/src/tools/ui-surface/definitions.ts +3 -1
  340. package/src/tools/watch/screen-watch.ts +3 -1
  341. package/src/tools/watcher/create.ts +52 -98
  342. package/src/tools/watcher/delete.ts +20 -46
  343. package/src/tools/watcher/digest.ts +36 -70
  344. package/src/tools/watcher/list.ts +49 -79
  345. package/src/tools/watcher/update.ts +45 -91
  346. package/src/twitter/client.ts +690 -0
  347. package/src/twitter/session.ts +91 -0
  348. package/src/usage/types.ts +0 -1
  349. package/src/util/truncate.ts +6 -0
  350. package/src/watcher/providers/slack.ts +2 -1
  351. package/src/watcher/watcher-store.ts +3 -2
  352. package/src/work-items/work-item-store.ts +27 -2
  353. package/src/workspace/commit-message-enrichment-service.ts +31 -7
  354. package/src/workspace/git-service.ts +87 -22
  355. package/src/workspace/provider-commit-message-generator.ts +269 -0
  356. package/src/workspace/turn-commit.ts +62 -3
  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,293 @@
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
+ const DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
42
+
43
+ /** URL path patterns that indicate non-API noise. */
44
+ const NOISE_PATH_PATTERNS = [
45
+ /\/web-translations\//,
46
+ /\/cdn-cgi\//,
47
+ /\.properties$/,
48
+ /\.js$/,
49
+ /\.css$/,
50
+ /\.woff2?$/,
51
+ /\.png$/,
52
+ /\.jpg$/,
53
+ /\.svg$/,
54
+ /\.ico$/,
55
+ /\.map$/,
56
+ /\/preference\//,
57
+ /\/userpreference-service\//,
58
+ ];
59
+
60
+ /** Returns true when a path segment looks like a dynamic ID. */
61
+ function isIdSegment(segment: string): boolean {
62
+ if (NUMERIC_RE.test(segment)) return true;
63
+ if (UUID_RE.test(segment)) return true;
64
+ if (HEX_HASH_RE.test(segment)) return true;
65
+ if (DATE_RE.test(segment)) return true;
66
+ return false;
67
+ }
68
+
69
+ /** Replace ID-like path segments with `{id}`. */
70
+ function normalizePathSegments(pathname: string): string {
71
+ return pathname
72
+ .split('/')
73
+ .map((seg) => (isIdSegment(seg) ? '{id}' : seg))
74
+ .join('/');
75
+ }
76
+
77
+ /** Safely parse JSON, returning undefined on failure. */
78
+ function tryParseJson(text: string | undefined): Record<string, unknown> | undefined {
79
+ if (!text) return undefined;
80
+ try {
81
+ const parsed = JSON.parse(text);
82
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
83
+ return parsed as Record<string, unknown>;
84
+ }
85
+ } catch {
86
+ // not JSON
87
+ }
88
+ return undefined;
89
+ }
90
+
91
+ /** Extract GraphQL operation name from request body. */
92
+ function extractGraphQLOperationName(postData: string | undefined): string | null {
93
+ if (!postData) return null;
94
+ const body = tryParseJson(postData);
95
+ if (!body) return null;
96
+ if (typeof body.operationName === 'string' && body.operationName) return body.operationName;
97
+ // Try extracting from query string: "query FooBar { ..." or "mutation FooBar { ..."
98
+ if (typeof body.query === 'string') {
99
+ const named = body.query.match(/(?:query|mutation|subscription)\s+(\w+)/);
100
+ if (named) return named[1];
101
+ // Unnamed query — extract the first field name: "query{fooBar(" or "query { fooBar {"
102
+ const firstField = body.query.match(/(?:query|mutation|subscription)\s*\{?\s*(\w+)/);
103
+ if (firstField) return firstField[1];
104
+ }
105
+ return null;
106
+ }
107
+
108
+ // ---------------------------------------------------------------------------
109
+ // Core analysis
110
+ // ---------------------------------------------------------------------------
111
+
112
+ interface GroupData {
113
+ method: string;
114
+ urlPattern: string;
115
+ exampleUrl: string;
116
+ queryParams: Set<string>;
117
+ requestBodyKeys: Set<string>;
118
+ responseStatus: Set<number>;
119
+ responseBodyKeys: Set<string>;
120
+ count: number;
121
+ }
122
+
123
+ export function analyzeApiMap(
124
+ entries: NetworkRecordedEntry[],
125
+ domain: string,
126
+ ): ApiMapResult {
127
+ const groups = new Map<string, GroupData>();
128
+
129
+ for (const entry of entries) {
130
+ const { request, response } = entry;
131
+ let parsed: URL;
132
+ try {
133
+ parsed = new URL(request.url);
134
+ } catch {
135
+ continue;
136
+ }
137
+
138
+ // Skip non-API noise
139
+ if (NOISE_PATH_PATTERNS.some(p => p.test(parsed.pathname))) continue;
140
+
141
+ // Skip non-JSON responses
142
+ const mimeType = response?.mimeType ?? '';
143
+ if (response && !mimeType.includes('json') && !mimeType.includes('graphql')) continue;
144
+
145
+ const method = request.method.toUpperCase();
146
+ const normalizedPath = normalizePathSegments(parsed.pathname);
147
+ const basePattern = `${parsed.hostname}${normalizedPath}`;
148
+
149
+ // For GraphQL endpoints, split by operation name
150
+ let urlPattern = basePattern;
151
+ const isGraphQL = normalizedPath.includes('graphql');
152
+ if (isGraphQL && method === 'POST') {
153
+ const opName = extractGraphQLOperationName(request.postData);
154
+ if (opName) {
155
+ urlPattern = `${basePattern} → ${opName}`;
156
+ }
157
+ }
158
+
159
+ const key = `${method} ${urlPattern}`;
160
+
161
+ let group = groups.get(key);
162
+ if (!group) {
163
+ group = {
164
+ method,
165
+ urlPattern,
166
+ exampleUrl: request.url,
167
+ queryParams: new Set(),
168
+ requestBodyKeys: new Set(),
169
+ responseStatus: new Set(),
170
+ responseBodyKeys: new Set(),
171
+ count: 0,
172
+ };
173
+ groups.set(key, group);
174
+ }
175
+
176
+ group.count++;
177
+
178
+ for (const paramKey of parsed.searchParams.keys()) {
179
+ group.queryParams.add(paramKey);
180
+ }
181
+
182
+ if (['POST', 'PUT', 'PATCH'].includes(method)) {
183
+ const body = tryParseJson(request.postData);
184
+ if (body) {
185
+ for (const k of Object.keys(body)) {
186
+ if (k !== 'query' && k !== 'operationName' && k !== 'extensions') {
187
+ group.requestBodyKeys.add(k);
188
+ }
189
+ }
190
+ }
191
+ }
192
+
193
+ if (response) {
194
+ group.responseStatus.add(response.status);
195
+ const resBody = tryParseJson(response.body);
196
+ if (resBody) {
197
+ for (const k of Object.keys(resBody)) {
198
+ group.responseBodyKeys.add(k);
199
+ }
200
+ }
201
+ }
202
+ }
203
+
204
+ const endpoints: ApiEndpoint[] = Array.from(groups.values()).map((g) => ({
205
+ method: g.method,
206
+ urlPattern: g.urlPattern,
207
+ exampleUrl: g.exampleUrl,
208
+ queryParams: Array.from(g.queryParams).sort(),
209
+ requestBodyKeys: Array.from(g.requestBodyKeys).sort(),
210
+ responseStatus: Array.from(g.responseStatus).sort((a, b) => a - b),
211
+ responseBodyKeys: Array.from(g.responseBodyKeys).sort(),
212
+ count: g.count,
213
+ }));
214
+
215
+ // Sort: data endpoints first (low count = unique pages), then boilerplate
216
+ // Within each tier, sort alphabetically by pattern for readability
217
+ endpoints.sort((a, b) => {
218
+ const aIsBoilerplate = a.count > 15;
219
+ const bIsBoilerplate = b.count > 15;
220
+ if (aIsBoilerplate !== bIsBoilerplate) return aIsBoilerplate ? 1 : -1;
221
+ return a.urlPattern.localeCompare(b.urlPattern);
222
+ });
223
+
224
+ const totalApiRequests = endpoints.reduce((sum, ep) => sum + ep.count, 0);
225
+
226
+ return {
227
+ domain,
228
+ analyzedAt: Date.now(),
229
+ totalRequests: totalApiRequests,
230
+ endpoints,
231
+ };
232
+ }
233
+
234
+ // ---------------------------------------------------------------------------
235
+ // Persistence
236
+ // ---------------------------------------------------------------------------
237
+
238
+ export function saveApiMap(domain: string, result: ApiMapResult): string {
239
+ const dir = join(getDataDir(), 'api-maps');
240
+ mkdirSync(dir, { recursive: true });
241
+
242
+ const timestamp = Date.now();
243
+ const filePath = join(dir, `${domain}-${timestamp}.json`);
244
+ writeFileSync(filePath, JSON.stringify(result, null, 2));
245
+ return filePath;
246
+ }
247
+
248
+ // ---------------------------------------------------------------------------
249
+ // Pretty-print
250
+ // ---------------------------------------------------------------------------
251
+
252
+ export function printApiMapTable(result: ApiMapResult): void {
253
+ const dataEndpoints = result.endpoints.filter(ep => ep.count <= 15);
254
+ const boilerplate = result.endpoints.filter(ep => ep.count > 15);
255
+
256
+ console.log(`\nAPI Map for ${result.domain} — ${result.endpoints.length} endpoints discovered\n`);
257
+
258
+ const stripDomain = (pattern: string) => {
259
+ const idx = pattern.indexOf('/');
260
+ return idx >= 0 ? pattern.slice(idx) : pattern;
261
+ };
262
+
263
+ const printSection = (title: string, eps: ApiEndpoint[]) => {
264
+ if (eps.length === 0) return;
265
+ console.log(` ${title} (${eps.length})\n`);
266
+
267
+ const header = ['Method', 'Endpoint', 'Hits', 'Response Keys'];
268
+ const rows = eps.map((ep) => [
269
+ ep.method,
270
+ stripDomain(ep.urlPattern),
271
+ String(ep.count),
272
+ ep.responseBodyKeys.slice(0, 5).join(', ') || '-',
273
+ ]);
274
+
275
+ const widths = header.map((h, i) =>
276
+ Math.min(i === 1 ? 72 : i === 3 ? 50 : 200, Math.max(h.length, ...rows.map((r) => r[i].length))),
277
+ );
278
+
279
+ const sep = widths.map((w) => '-'.repeat(w)).join(' | ');
280
+ const fmt = (row: string[]) =>
281
+ row.map((cell, i) => cell.slice(0, widths[i]).padEnd(widths[i])).join(' | ');
282
+
283
+ console.log(` ${fmt(header)}`);
284
+ console.log(` ${sep}`);
285
+ for (const row of rows) {
286
+ console.log(` ${fmt(row)}`);
287
+ }
288
+ console.log();
289
+ };
290
+
291
+ printSection('DATA ENDPOINTS', dataEndpoints);
292
+ printSection('PAGE-LOAD BOILERPLATE', boilerplate);
293
+ }
@@ -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
  }