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
@@ -34,7 +34,7 @@ mock.module('../config/loader.js', () => ({
34
34
  }),
35
35
  }));
36
36
 
37
- import { initializeDb, getDb } from '../memory/db.js';
37
+ import { initializeDb, getDb, resetDb } from '../memory/db.js';
38
38
  import {
39
39
  uploadAttachment,
40
40
  deleteAttachment,
@@ -42,7 +42,6 @@ import {
42
42
  getAttachmentById,
43
43
  linkAttachmentToMessage,
44
44
  getAttachmentsForMessage,
45
- getAttachmentsForMessageUnscoped,
46
45
  deleteOrphanAttachments,
47
46
  validateAttachmentUpload,
48
47
  isValidBase64,
@@ -54,6 +53,7 @@ import { createConversation, addMessage } from '../memory/conversation-store.js'
54
53
  initializeDb();
55
54
 
56
55
  afterAll(() => {
56
+ resetDb();
57
57
  try { rmSync(testDir, { recursive: true }); } catch { /* best effort */ }
58
58
  });
59
59
 
@@ -73,10 +73,9 @@ describe('uploadAttachment', () => {
73
73
  beforeEach(resetTables);
74
74
 
75
75
  test('stores attachment and returns metadata', () => {
76
- const stored = uploadAttachment('ast-1', 'chart.png', 'image/png', 'iVBORw0K');
76
+ const stored = uploadAttachment('chart.png', 'image/png', 'iVBORw0K');
77
77
 
78
78
  expect(stored.id).toBeDefined();
79
- expect(stored.assistantId).toBe('ast-1');
80
79
  expect(stored.originalFilename).toBe('chart.png');
81
80
  expect(stored.mimeType).toBe('image/png');
82
81
  expect(stored.kind).toBe('image');
@@ -85,48 +84,42 @@ describe('uploadAttachment', () => {
85
84
  });
86
85
 
87
86
  test('classifies image MIME as image kind', () => {
88
- const stored = uploadAttachment('ast-1', 'pic.jpg', 'image/jpeg', 'AAAA');
87
+ const stored = uploadAttachment('pic.jpg', 'image/jpeg', 'AAAA');
89
88
  expect(stored.kind).toBe('image');
90
89
  });
91
90
 
92
91
  test('classifies non-image MIME as document kind', () => {
93
- const stored = uploadAttachment('ast-1', 'doc.pdf', 'application/pdf', 'JVBER');
92
+ const stored = uploadAttachment('doc.pdf', 'application/pdf', 'JVBER');
94
93
  expect(stored.kind).toBe('document');
95
94
  });
96
95
 
97
96
  test('generates unique IDs for each upload', () => {
98
- const a = uploadAttachment('ast-1', 'a.txt', 'text/plain', 'AA==');
99
- const b = uploadAttachment('ast-1', 'b.txt', 'text/plain', 'QQ==');
97
+ const a = uploadAttachment('a.txt', 'text/plain', 'AA==');
98
+ const b = uploadAttachment('b.txt', 'text/plain', 'QQ==');
100
99
  expect(a.id).not.toBe(b.id);
101
100
  });
102
101
 
103
102
  test('computes sizeBytes from base64 correctly', () => {
104
103
  // "hello" = "aGVsbG8=" (8 chars, 1 pad → 5 bytes)
105
- const stored = uploadAttachment('ast-1', 'hello.txt', 'text/plain', 'aGVsbG8=');
104
+ const stored = uploadAttachment('hello.txt', 'text/plain', 'aGVsbG8=');
106
105
  expect(stored.sizeBytes).toBe(5);
107
106
  });
108
107
 
109
- test('deduplicates by content hash within the same assistant', () => {
110
- const first = uploadAttachment('ast-1', 'photo.png', 'image/png', 'iVBORw0KGgoAAAANSUh');
111
- const second = uploadAttachment('ast-1', 'photo.png', 'image/png', 'iVBORw0KGgoAAAANSUh');
108
+ test('deduplicates by content hash', () => {
109
+ const first = uploadAttachment('photo.png', 'image/png', 'iVBORw0KGgoAAAANSUh');
110
+ const second = uploadAttachment('photo.png', 'image/png', 'iVBORw0KGgoAAAANSUh');
112
111
  expect(second.id).toBe(first.id);
113
112
  });
114
113
 
115
114
  test('deduplicates even when filenames differ', () => {
116
- const first = uploadAttachment('ast-1', 'original.png', 'image/png', 'DUPECONTENT123');
117
- const second = uploadAttachment('ast-1', 'renamed.png', 'image/png', 'DUPECONTENT123');
115
+ const first = uploadAttachment('original.png', 'image/png', 'DUPECONTENT123');
116
+ const second = uploadAttachment('renamed.png', 'image/png', 'DUPECONTENT123');
118
117
  expect(second.id).toBe(first.id);
119
118
  });
120
119
 
121
- test('does not deduplicate across different assistants', () => {
122
- const first = uploadAttachment('ast-1', 'file.txt', 'text/plain', 'CROSSASSISTANT');
123
- const second = uploadAttachment('ast-2', 'file.txt', 'text/plain', 'CROSSASSISTANT');
124
- expect(second.id).not.toBe(first.id);
125
- });
126
-
127
120
  test('does not deduplicate different content', () => {
128
- const first = uploadAttachment('ast-1', 'a.txt', 'text/plain', 'CONTENTA');
129
- const second = uploadAttachment('ast-1', 'b.txt', 'text/plain', 'CONTENTB');
121
+ const first = uploadAttachment('a.txt', 'text/plain', 'CONTENTA');
122
+ const second = uploadAttachment('b.txt', 'text/plain', 'CONTENTB');
130
123
  expect(second.id).not.toBe(first.id);
131
124
  });
132
125
 
@@ -137,20 +130,20 @@ describe('uploadAttachment', () => {
137
130
  const oversizedData = 'A'.repeat(oversizedLength);
138
131
 
139
132
  expect(() =>
140
- uploadAttachment('ast-1', 'huge.bin', 'application/octet-stream', oversizedData),
133
+ uploadAttachment('huge.bin', 'application/octet-stream', oversizedData),
141
134
  ).toThrow(AttachmentUploadError);
142
135
  });
143
136
 
144
137
  test('rejects invalid base64 data', () => {
145
138
  expect(() =>
146
- uploadAttachment('ast-1', 'bad.txt', 'text/plain', '!!!not-base64!!!'),
139
+ uploadAttachment('bad.txt', 'text/plain', '!!!not-base64!!!'),
147
140
  ).toThrow(AttachmentUploadError);
148
141
  });
149
142
 
150
143
  test('accepts base64 with non-standard padding/length', () => {
151
144
  // Lenient on length — only character set is validated
152
145
  expect(() =>
153
- uploadAttachment('ast-1', 'ok.txt', 'text/plain', 'AAA'),
146
+ uploadAttachment('ok.txt', 'text/plain', 'AAA'),
154
147
  ).not.toThrow();
155
148
  });
156
149
 
@@ -161,7 +154,7 @@ describe('uploadAttachment', () => {
161
154
  const exactData = 'A'.repeat(exactLength);
162
155
 
163
156
  expect(() =>
164
- uploadAttachment('ast-1', 'exact.bin', 'application/octet-stream', exactData),
157
+ uploadAttachment('exact.bin', 'application/octet-stream', exactData),
165
158
  ).not.toThrow();
166
159
  });
167
160
  });
@@ -199,27 +192,17 @@ describe('deleteAttachment', () => {
199
192
  beforeEach(resetTables);
200
193
 
201
194
  test('deletes existing attachment and returns deleted', () => {
202
- const stored = uploadAttachment('ast-1', 'file.txt', 'text/plain', 'dGVzdA==');
203
- const result = deleteAttachment('ast-1', stored.id);
195
+ const stored = uploadAttachment('file.txt', 'text/plain', 'dGVzdA==');
196
+ const result = deleteAttachment(stored.id);
204
197
  expect(result).toBe('deleted');
205
198
 
206
- const fetched = getAttachmentById('ast-1', stored.id);
199
+ const fetched = getAttachmentById(stored.id);
207
200
  expect(fetched).toBeNull();
208
201
  });
209
202
 
210
203
  test('returns not_found for nonexistent attachment', () => {
211
- const result = deleteAttachment('ast-1', 'nonexistent-id');
212
- expect(result).toBe('not_found');
213
- });
214
-
215
- test('returns not_found when assistantId does not match', () => {
216
- const stored = uploadAttachment('ast-owner', 'file.txt', 'text/plain', 'dGVzdA==');
217
- const result = deleteAttachment('ast-other', stored.id);
204
+ const result = deleteAttachment('nonexistent-id');
218
205
  expect(result).toBe('not_found');
219
-
220
- // Original still exists
221
- const fetched = getAttachmentById('ast-owner', stored.id);
222
- expect(fetched).not.toBeNull();
223
206
  });
224
207
 
225
208
  test('returns still_referenced when messages reference the attachment', () => {
@@ -228,35 +211,35 @@ describe('deleteAttachment', () => {
228
211
  const msg2 = addMessage(conv.id, 'user', 'Duplicate upload');
229
212
 
230
213
  // Dedup: both uploads return the same attachment row
231
- const first = uploadAttachment('ast-1', 'photo.png', 'image/png', 'SHAREDCONTENT1');
232
- const second = uploadAttachment('ast-1', 'photo.png', 'image/png', 'SHAREDCONTENT1');
214
+ const first = uploadAttachment('photo.png', 'image/png', 'SHAREDCONTENT1');
215
+ const second = uploadAttachment('photo.png', 'image/png', 'SHAREDCONTENT1');
233
216
  expect(second.id).toBe(first.id);
234
217
 
235
218
  linkAttachmentToMessage(msg1.id, first.id, 0);
236
219
  linkAttachmentToMessage(msg2.id, second.id, 0);
237
220
 
238
221
  // Delete should return still_referenced and NOT remove the attachment row
239
- const result = deleteAttachment('ast-1', first.id);
222
+ const result = deleteAttachment(first.id);
240
223
  expect(result).toBe('still_referenced');
241
224
 
242
225
  // Attachment row still exists because messages reference it
243
- const fetched = getAttachmentById('ast-1', first.id);
226
+ const fetched = getAttachmentById(first.id);
244
227
  expect(fetched).not.toBeNull();
245
228
 
246
229
  // Both messages still see the attachment
247
- const linked1 = getAttachmentsForMessage(msg1.id, 'ast-1');
230
+ const linked1 = getAttachmentsForMessage(msg1.id);
248
231
  expect(linked1).toHaveLength(1);
249
- const linked2 = getAttachmentsForMessage(msg2.id, 'ast-1');
232
+ const linked2 = getAttachmentsForMessage(msg2.id);
250
233
  expect(linked2).toHaveLength(1);
251
234
  });
252
235
 
253
236
  test('deletes attachment when no messages reference it', () => {
254
- const stored = uploadAttachment('ast-1', 'lonely.txt', 'text/plain', 'UNREFERENCED');
237
+ const stored = uploadAttachment('lonely.txt', 'text/plain', 'UNREFERENCED');
255
238
  // No linkAttachmentToMessage call — zero references
256
- const result = deleteAttachment('ast-1', stored.id);
239
+ const result = deleteAttachment(stored.id);
257
240
  expect(result).toBe('deleted');
258
241
 
259
- const fetched = getAttachmentById('ast-1', stored.id);
242
+ const fetched = getAttachmentById(stored.id);
260
243
  expect(fetched).toBeNull();
261
244
  });
262
245
  });
@@ -269,31 +252,25 @@ describe('getAttachmentsByIds', () => {
269
252
  beforeEach(resetTables);
270
253
 
271
254
  test('returns matching attachments with data', () => {
272
- const a = uploadAttachment('ast-1', 'a.txt', 'text/plain', 'AAAA');
273
- const b = uploadAttachment('ast-1', 'b.txt', 'text/plain', 'BBBB');
255
+ const a = uploadAttachment('a.txt', 'text/plain', 'AAAA');
256
+ const b = uploadAttachment('b.txt', 'text/plain', 'BBBB');
274
257
 
275
- const results = getAttachmentsByIds('ast-1', [a.id, b.id]);
258
+ const results = getAttachmentsByIds([a.id, b.id]);
276
259
  expect(results).toHaveLength(2);
277
260
  expect(results[0].dataBase64).toBe('AAAA');
278
261
  expect(results[1].dataBase64).toBe('BBBB');
279
262
  });
280
263
 
281
264
  test('returns empty array for empty IDs list', () => {
282
- const results = getAttachmentsByIds('ast-1', []);
265
+ const results = getAttachmentsByIds([]);
283
266
  expect(results).toHaveLength(0);
284
267
  });
285
268
 
286
269
  test('skips IDs that do not exist', () => {
287
- const a = uploadAttachment('ast-1', 'a.txt', 'text/plain', 'AAAA');
288
- const results = getAttachmentsByIds('ast-1', [a.id, 'nonexistent']);
270
+ const a = uploadAttachment('a.txt', 'text/plain', 'AAAA');
271
+ const results = getAttachmentsByIds([a.id, 'nonexistent']);
289
272
  expect(results).toHaveLength(1);
290
273
  });
291
-
292
- test('enforces assistantId scoping', () => {
293
- const a = uploadAttachment('ast-owner', 'a.txt', 'text/plain', 'AAAA');
294
- const results = getAttachmentsByIds('ast-other', [a.id]);
295
- expect(results).toHaveLength(0);
296
- });
297
274
  });
298
275
 
299
276
  // ---------------------------------------------------------------------------
@@ -304,8 +281,8 @@ describe('getAttachmentById', () => {
304
281
  beforeEach(resetTables);
305
282
 
306
283
  test('returns attachment with data when found', () => {
307
- const stored = uploadAttachment('ast-1', 'report.pdf', 'application/pdf', 'JVBER');
308
- const result = getAttachmentById('ast-1', stored.id);
284
+ const stored = uploadAttachment('report.pdf', 'application/pdf', 'JVBER');
285
+ const result = getAttachmentById(stored.id);
309
286
 
310
287
  expect(result).not.toBeNull();
311
288
  expect(result!.id).toBe(stored.id);
@@ -313,14 +290,8 @@ describe('getAttachmentById', () => {
313
290
  expect(result!.dataBase64).toBe('JVBER');
314
291
  });
315
292
 
316
- test('returns null for wrong assistantId', () => {
317
- const stored = uploadAttachment('ast-1', 'file.txt', 'text/plain', 'dGVzdA==');
318
- const result = getAttachmentById('ast-other', stored.id);
319
- expect(result).toBeNull();
320
- });
321
-
322
293
  test('returns null for nonexistent ID', () => {
323
- const result = getAttachmentById('ast-1', 'no-such-id');
294
+ const result = getAttachmentById('no-such-id');
324
295
  expect(result).toBeNull();
325
296
  });
326
297
  });
@@ -335,11 +306,11 @@ describe('linkAttachmentToMessage + getAttachmentsForMessage', () => {
335
306
  test('links attachment and retrieves it by message', () => {
336
307
  const conv = createConversation();
337
308
  const msg = addMessage(conv.id, 'assistant', 'Here is a chart');
338
- const stored = uploadAttachment('ast-1', 'chart.png', 'image/png', 'iVBORw0K');
309
+ const stored = uploadAttachment('chart.png', 'image/png', 'iVBORw0K');
339
310
 
340
311
  linkAttachmentToMessage(msg.id, stored.id, 0);
341
312
 
342
- const linked = getAttachmentsForMessage(msg.id, 'ast-1');
313
+ const linked = getAttachmentsForMessage(msg.id);
343
314
  expect(linked).toHaveLength(1);
344
315
  expect(linked[0].id).toBe(stored.id);
345
316
  expect(linked[0].originalFilename).toBe('chart.png');
@@ -349,14 +320,14 @@ describe('linkAttachmentToMessage + getAttachmentsForMessage', () => {
349
320
  test('returns attachments in position order', () => {
350
321
  const conv = createConversation();
351
322
  const msg = addMessage(conv.id, 'assistant', 'Multiple files');
352
- const a = uploadAttachment('ast-1', 'first.txt', 'text/plain', 'AAAA');
353
- const b = uploadAttachment('ast-1', 'second.txt', 'text/plain', 'BBBB');
323
+ const a = uploadAttachment('first.txt', 'text/plain', 'AAAA');
324
+ const b = uploadAttachment('second.txt', 'text/plain', 'BBBB');
354
325
 
355
326
  // Link in reverse order
356
327
  linkAttachmentToMessage(msg.id, b.id, 1);
357
328
  linkAttachmentToMessage(msg.id, a.id, 0);
358
329
 
359
- const linked = getAttachmentsForMessage(msg.id, 'ast-1');
330
+ const linked = getAttachmentsForMessage(msg.id);
360
331
  expect(linked).toHaveLength(2);
361
332
  expect(linked[0].originalFilename).toBe('first.txt');
362
333
  expect(linked[1].originalFilename).toBe('second.txt');
@@ -366,49 +337,7 @@ describe('linkAttachmentToMessage + getAttachmentsForMessage', () => {
366
337
  const conv = createConversation();
367
338
  const msg = addMessage(conv.id, 'assistant', 'No attachments');
368
339
 
369
- const linked = getAttachmentsForMessage(msg.id, 'ast-1');
370
- expect(linked).toHaveLength(0);
371
- });
372
-
373
- test('enforces assistantId scoping on retrieval', () => {
374
- const conv = createConversation();
375
- const msg = addMessage(conv.id, 'assistant', 'Scoped');
376
- const stored = uploadAttachment('ast-owner', 'secret.txt', 'text/plain', 'c2VjcmV0');
377
-
378
- linkAttachmentToMessage(msg.id, stored.id, 0);
379
-
380
- const wrongScope = getAttachmentsForMessage(msg.id, 'ast-other');
381
- expect(wrongScope).toHaveLength(0);
382
-
383
- const rightScope = getAttachmentsForMessage(msg.id, 'ast-owner');
384
- expect(rightScope).toHaveLength(1);
385
- });
386
- });
387
-
388
- // ---------------------------------------------------------------------------
389
- // getAttachmentsForMessageUnscoped
390
- // ---------------------------------------------------------------------------
391
-
392
- describe('getAttachmentsForMessageUnscoped', () => {
393
- beforeEach(resetTables);
394
-
395
- test('returns attachments without assistant scoping', () => {
396
- const conv = createConversation();
397
- const msg = addMessage(conv.id, 'assistant', 'Desktop history');
398
- const stored = uploadAttachment('ast-1', 'result.png', 'image/png', 'iVBORw0K');
399
-
400
- linkAttachmentToMessage(msg.id, stored.id, 0);
401
-
402
- const linked = getAttachmentsForMessageUnscoped(msg.id);
403
- expect(linked).toHaveLength(1);
404
- expect(linked[0].id).toBe(stored.id);
405
- });
406
-
407
- test('returns empty for message with no links', () => {
408
- const conv = createConversation();
409
- const msg = addMessage(conv.id, 'assistant', 'Nothing here');
410
-
411
- const linked = getAttachmentsForMessageUnscoped(msg.id);
340
+ const linked = getAttachmentsForMessage(msg.id);
412
341
  expect(linked).toHaveLength(0);
413
342
  });
414
343
  });
@@ -421,7 +350,7 @@ describe('deleteOrphanAttachments', () => {
421
350
  beforeEach(resetTables);
422
351
 
423
352
  test('removes candidate attachments with no message links', () => {
424
- const stored = uploadAttachment('ast-1', 'orphan.txt', 'text/plain', 'ZGF0YQ==');
353
+ const stored = uploadAttachment('orphan.txt', 'text/plain', 'ZGF0YQ==');
425
354
 
426
355
  const removed = deleteOrphanAttachments([stored.id]);
427
356
  expect(removed).toBe(1);
@@ -430,27 +359,27 @@ describe('deleteOrphanAttachments', () => {
430
359
  test('preserves attachments that are still linked', () => {
431
360
  const conv = createConversation();
432
361
  const msg = addMessage(conv.id, 'assistant', 'With attachment');
433
- const stored = uploadAttachment('ast-1', 'linked.txt', 'text/plain', 'ZGF0YQ==');
362
+ const stored = uploadAttachment('linked.txt', 'text/plain', 'ZGF0YQ==');
434
363
  linkAttachmentToMessage(msg.id, stored.id, 0);
435
364
 
436
365
  const removed = deleteOrphanAttachments([stored.id]);
437
366
  expect(removed).toBe(0);
438
367
 
439
- const fetched = getAttachmentById('ast-1', stored.id);
368
+ const fetched = getAttachmentById(stored.id);
440
369
  expect(fetched).not.toBeNull();
441
370
  });
442
371
 
443
372
  test('removes only orphans when mixed candidates provided', () => {
444
373
  const conv = createConversation();
445
374
  const msg = addMessage(conv.id, 'assistant', 'Mixed');
446
- const linked = uploadAttachment('ast-1', 'linked.txt', 'text/plain', 'AAAA');
447
- const orphan = uploadAttachment('ast-1', 'orphan.txt', 'text/plain', 'BBBB');
375
+ const linked = uploadAttachment('linked.txt', 'text/plain', 'AAAA');
376
+ const orphan = uploadAttachment('orphan.txt', 'text/plain', 'BBBB');
448
377
  linkAttachmentToMessage(msg.id, linked.id, 0);
449
378
 
450
379
  const removed = deleteOrphanAttachments([linked.id, orphan.id]);
451
380
  expect(removed).toBe(1);
452
381
 
453
- const remaining = getAttachmentById('ast-1', linked.id);
382
+ const remaining = getAttachmentById(linked.id);
454
383
  expect(remaining).not.toBeNull();
455
384
  });
456
385
 
@@ -460,14 +389,14 @@ describe('deleteOrphanAttachments', () => {
460
389
  });
461
390
 
462
391
  test('does not delete attachments outside the candidate set', () => {
463
- const unrelated = uploadAttachment('ast-1', 'unrelated.txt', 'text/plain', 'AAAA');
464
- const candidate = uploadAttachment('ast-1', 'candidate.txt', 'text/plain', 'BBBB');
392
+ const unrelated = uploadAttachment('unrelated.txt', 'text/plain', 'AAAA');
393
+ const candidate = uploadAttachment('candidate.txt', 'text/plain', 'BBBB');
465
394
 
466
395
  const removed = deleteOrphanAttachments([candidate.id]);
467
396
  expect(removed).toBe(1);
468
397
 
469
398
  // The unrelated attachment should still exist
470
- const fetched = getAttachmentById('ast-1', unrelated.id);
399
+ const fetched = getAttachmentById(unrelated.id);
471
400
  expect(fetched).not.toBeNull();
472
401
  });
473
402
  });
@@ -50,11 +50,12 @@ describe('browser skill cutover — startup tool payload', () => {
50
50
  test('serialized tool definitions payload still exceeds a reasonable floor', () => {
51
51
  const definitions = getAllToolDefinitions();
52
52
  const serialized = JSON.stringify(definitions);
53
- // Startup payload is ~32 771 chars without browser tools.
54
- // Floor at 30 000 catches accidental wholesale removal; ceiling ensures
55
- // browser tools (~4 640 chars) haven't leaked back in.
53
+ // Startup payload is ~45 034 chars without browser tools.
54
+ // Floor at 30 000 catches accidental wholesale removal; ceiling at 47 000
55
+ // gives ~2 000 char headroom while still catching browser tool leakage
56
+ // (~4 640 chars would push it past the ceiling).
56
57
  expect(serialized.length).toBeGreaterThan(30_000);
57
- expect(serialized.length).toBeLessThan(42_000);
58
+ expect(serialized.length).toBeLessThan(47_000);
58
59
  });
59
60
 
60
61
  test('no browser-categorised tools remain in startup registry', () => {
@@ -6,7 +6,7 @@
6
6
  */
7
7
  import { describe, test, expect, beforeAll, afterAll } from 'bun:test';
8
8
 
9
- import { eagerModules, eagerModuleToolNames } from '../tools/tool-manifest.js';
9
+ import { eagerModuleToolNames } from '../tools/tool-manifest.js';
10
10
  import {
11
11
  initializeTools,
12
12
  getAllTools,
@@ -52,10 +52,6 @@ describe('browser skill migration end-state', () => {
52
52
  }
53
53
  });
54
54
 
55
- test('browser module is NOT in eagerModules', () => {
56
- expect(eagerModules).not.toContain('./browser/headless-browser.js');
57
- });
58
-
59
55
  test('browser tool names are NOT in eagerModuleToolNames', () => {
60
56
  for (const name of BROWSER_TOOLS) {
61
57
  expect(eagerModuleToolNames).not.toContain(name);
@@ -75,10 +71,11 @@ describe('browser skill migration end-state', () => {
75
71
  expect(defNames).not.toContain(name);
76
72
  }
77
73
 
78
- // Payload ceiling: browser tools contribute ~4 640 chars. If they leak
79
- // back into startup definitions the payload would exceed 38 000.
74
+ // Payload ceiling: startup payload is ~45 034 chars. Browser tools
75
+ // contribute ~4 640 chars — if they leak back in, the total would exceed
76
+ // 47 000. The 2 000-char margin absorbs minor tool additions.
80
77
  const payloadSize = JSON.stringify(definitions).length;
81
- expect(payloadSize).toBeLessThan(42_000);
78
+ expect(payloadSize).toBeLessThan(47_000);
82
79
  });
83
80
 
84
81
  // ── 2. Browser skill exists and is active ──────────────────────────