vellum 0.2.1 → 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 (349) 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 +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 +299 -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-twitter-config.test.ts +718 -0
  36. package/src/__tests__/intent-routing.test.ts +64 -57
  37. package/src/__tests__/ipc-roundtrip.benchmark.test.ts +237 -0
  38. package/src/__tests__/ipc-snapshot.test.ts +62 -28
  39. package/src/__tests__/llm-usage-store.test.ts +3 -8
  40. package/src/__tests__/media-generate-image.test.ts +1 -1
  41. package/src/__tests__/media-reuse-story.e2e.test.ts +7 -7
  42. package/src/__tests__/memory-retrieval.benchmark.test.ts +430 -0
  43. package/src/__tests__/parallel-tool.benchmark.test.ts +294 -0
  44. package/src/__tests__/playbook-tools.test.ts +342 -0
  45. package/src/__tests__/profile-compiler.test.ts +2 -1
  46. package/src/__tests__/provider-streaming.benchmark.test.ts +773 -0
  47. package/src/__tests__/recurrence-engine-rruleset.test.ts +78 -0
  48. package/src/__tests__/recurrence-engine.test.ts +69 -0
  49. package/src/__tests__/recurrence-types.test.ts +71 -0
  50. package/src/__tests__/registry.test.ts +5 -3
  51. package/src/__tests__/relay-server.test.ts +633 -0
  52. package/src/__tests__/reminder-store.test.ts +6 -3
  53. package/src/__tests__/reminder.test.ts +43 -77
  54. package/src/__tests__/run-orchestrator-assistant-events.test.ts +8 -4
  55. package/src/__tests__/run-orchestrator.test.ts +4 -4
  56. package/src/__tests__/runtime-attachment-metadata.test.ts +7 -6
  57. package/src/__tests__/runtime-runs-http.test.ts +4 -4
  58. package/src/__tests__/runtime-runs.test.ts +4 -4
  59. package/src/__tests__/schedule-store.test.ts +482 -0
  60. package/src/__tests__/schedule-tools.test.ts +700 -0
  61. package/src/__tests__/scheduler-recurrence.test.ts +329 -0
  62. package/src/__tests__/server-history-render.test.ts +14 -13
  63. package/src/__tests__/session-error.test.ts +28 -0
  64. package/src/__tests__/session-init.benchmark.test.ts +462 -0
  65. package/src/__tests__/session-queue.test.ts +71 -48
  66. package/src/__tests__/session-runtime-assembly.test.ts +161 -0
  67. package/src/__tests__/session-surfaces-task-progress.test.ts +104 -0
  68. package/src/__tests__/signup-e2e.test.ts +2 -1
  69. package/src/__tests__/skill-projection.benchmark.test.ts +328 -0
  70. package/src/__tests__/skill-script-runner.test.ts +159 -0
  71. package/src/__tests__/speaker-identification.test.ts +52 -0
  72. package/src/__tests__/subagent-manager-notify.test.ts +42 -10
  73. package/src/__tests__/subagent-tools.test.ts +141 -41
  74. package/src/__tests__/task-compiler.test.ts +2 -1
  75. package/src/__tests__/task-runner.test.ts +2 -1
  76. package/src/__tests__/task-scheduler.test.ts +2 -1
  77. package/src/__tests__/task-tools.test.ts +49 -56
  78. package/src/__tests__/tool-audit-listener.test.ts +1 -0
  79. package/src/__tests__/tool-domain-event-publisher.test.ts +2 -0
  80. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +500 -0
  81. package/src/__tests__/tool-executor.test.ts +13 -17
  82. package/src/__tests__/turn-commit.test.ts +218 -3
  83. package/src/__tests__/twilio-provider.test.ts +143 -0
  84. package/src/__tests__/twilio-routes.test.ts +789 -0
  85. package/src/__tests__/twitter-auth-handler.test.ts +581 -0
  86. package/src/__tests__/view-image-tool.test.ts +217 -0
  87. package/src/__tests__/workspace-git-service.test.ts +186 -0
  88. package/src/__tests__/workspace-heartbeat-service.test.ts +13 -3
  89. package/src/agent-heartbeat/agent-heartbeat-service.ts +155 -0
  90. package/src/bundler/app-bundler.ts +12 -8
  91. package/src/calls/call-bridge.ts +95 -0
  92. package/src/calls/call-constants.ts +43 -5
  93. package/src/calls/call-domain.ts +276 -0
  94. package/src/calls/call-orchestrator.ts +43 -17
  95. package/src/calls/call-recovery.ts +207 -0
  96. package/src/calls/call-state-machine.ts +68 -0
  97. package/src/calls/call-store.ts +192 -5
  98. package/src/calls/relay-server.ts +41 -4
  99. package/src/calls/speaker-identification.ts +213 -0
  100. package/src/calls/twilio-provider.ts +10 -6
  101. package/src/calls/twilio-routes.ts +90 -76
  102. package/src/calls/types.ts +1 -1
  103. package/src/cli/config-commands.ts +334 -0
  104. package/src/cli/core-commands.ts +776 -0
  105. package/src/cli/doordash.ts +251 -1
  106. package/src/cli/ipc-client.ts +82 -0
  107. package/src/cli/map.ts +246 -0
  108. package/src/cli/twitter.ts +575 -0
  109. package/src/cli.ts +7 -5
  110. package/src/commands/__tests__/cc-command-registry.test.ts +319 -0
  111. package/src/commands/cc-command-registry.ts +209 -0
  112. package/src/config/bundled-skills/contacts/SKILL.md +39 -0
  113. package/src/config/bundled-skills/contacts/TOOLS.json +122 -0
  114. package/src/config/bundled-skills/contacts/tools/contact-merge.ts +9 -0
  115. package/src/config/bundled-skills/contacts/tools/contact-search.ts +9 -0
  116. package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +9 -0
  117. package/src/config/bundled-skills/document/SKILL.md +18 -0
  118. package/src/config/bundled-skills/document/TOOLS.json +53 -0
  119. package/src/config/bundled-skills/document/tools/document-create.ts +9 -0
  120. package/src/config/bundled-skills/document/tools/document-update.ts +9 -0
  121. package/src/config/bundled-skills/doordash/SKILL.md +82 -23
  122. package/src/config/bundled-skills/followups/SKILL.md +32 -0
  123. package/src/config/bundled-skills/followups/TOOLS.json +100 -0
  124. package/src/config/bundled-skills/followups/tools/followup-create.ts +9 -0
  125. package/src/config/bundled-skills/followups/tools/followup-list.ts +9 -0
  126. package/src/config/bundled-skills/followups/tools/followup-resolve.ts +9 -0
  127. package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +1 -23
  128. package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +2 -1
  129. package/src/config/bundled-skills/playbooks/SKILL.md +31 -0
  130. package/src/config/bundled-skills/playbooks/TOOLS.json +126 -0
  131. package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +9 -0
  132. package/src/config/bundled-skills/playbooks/tools/playbook-delete.ts +9 -0
  133. package/src/config/bundled-skills/playbooks/tools/playbook-list.ts +9 -0
  134. package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +9 -0
  135. package/src/config/bundled-skills/reminder/SKILL.md +20 -0
  136. package/src/config/bundled-skills/reminder/TOOLS.json +67 -0
  137. package/src/config/bundled-skills/reminder/tools/reminder-cancel.ts +9 -0
  138. package/src/config/bundled-skills/reminder/tools/reminder-create.ts +9 -0
  139. package/src/config/bundled-skills/reminder/tools/reminder-list.ts +9 -0
  140. package/src/config/bundled-skills/schedule/SKILL.md +74 -0
  141. package/src/config/bundled-skills/schedule/TOOLS.json +135 -0
  142. package/src/config/bundled-skills/schedule/tools/schedule-create.ts +9 -0
  143. package/src/config/bundled-skills/schedule/tools/schedule-delete.ts +9 -0
  144. package/src/config/bundled-skills/schedule/tools/schedule-list.ts +9 -0
  145. package/src/config/bundled-skills/schedule/tools/schedule-update.ts +9 -0
  146. package/src/config/bundled-skills/subagent/SKILL.md +25 -0
  147. package/src/config/bundled-skills/subagent/TOOLS.json +107 -0
  148. package/src/config/bundled-skills/subagent/tools/subagent-abort.ts +9 -0
  149. package/src/config/bundled-skills/subagent/tools/subagent-message.ts +9 -0
  150. package/src/config/bundled-skills/subagent/tools/subagent-read.ts +9 -0
  151. package/src/config/bundled-skills/subagent/tools/subagent-spawn.ts +9 -0
  152. package/src/config/bundled-skills/subagent/tools/subagent-status.ts +9 -0
  153. package/src/config/bundled-skills/tasks/SKILL.md +28 -0
  154. package/src/config/bundled-skills/tasks/TOOLS.json +256 -0
  155. package/src/config/bundled-skills/tasks/tools/task-delete.ts +9 -0
  156. package/src/config/bundled-skills/tasks/tools/task-list-add.ts +9 -0
  157. package/src/config/bundled-skills/tasks/tools/task-list-remove.ts +9 -0
  158. package/src/config/bundled-skills/tasks/tools/task-list-show.ts +9 -0
  159. package/src/config/bundled-skills/tasks/tools/task-list-update.ts +9 -0
  160. package/src/config/bundled-skills/tasks/tools/task-list.ts +9 -0
  161. package/src/config/bundled-skills/tasks/tools/task-run.ts +9 -0
  162. package/src/config/bundled-skills/tasks/tools/task-save.ts +9 -0
  163. package/src/config/bundled-skills/twitter/SKILL.md +134 -0
  164. package/src/config/bundled-skills/watcher/SKILL.md +27 -0
  165. package/src/config/bundled-skills/watcher/TOOLS.json +147 -0
  166. package/src/config/bundled-skills/watcher/tools/watcher-create.ts +9 -0
  167. package/src/config/bundled-skills/watcher/tools/watcher-delete.ts +9 -0
  168. package/src/config/bundled-skills/watcher/tools/watcher-digest.ts +9 -0
  169. package/src/config/bundled-skills/watcher/tools/watcher-list.ts +9 -0
  170. package/src/config/bundled-skills/watcher/tools/watcher-update.ts +9 -0
  171. package/src/config/defaults.ts +33 -0
  172. package/src/config/loader.ts +4 -1
  173. package/src/config/schema.ts +161 -1
  174. package/src/config/system-prompt.ts +61 -16
  175. package/src/config/templates/IDENTITY.md +7 -0
  176. package/src/config/types.ts +4 -0
  177. package/src/contacts/contact-store.ts +4 -4
  178. package/src/daemon/assistant-attachments.ts +10 -0
  179. package/src/daemon/classifier.ts +3 -1
  180. package/src/daemon/computer-use-session.ts +3 -1
  181. package/src/daemon/date-context.ts +136 -0
  182. package/src/daemon/handlers/apps.ts +16 -1
  183. package/src/daemon/handlers/browser.ts +54 -0
  184. package/src/daemon/handlers/computer-use.ts +7 -1
  185. package/src/daemon/handlers/config.ts +163 -5
  186. package/src/daemon/handlers/diagnostics.ts +5 -1
  187. package/src/daemon/handlers/documents.ts +18 -29
  188. package/src/daemon/handlers/home-base.ts +5 -1
  189. package/src/daemon/handlers/index.ts +40 -277
  190. package/src/daemon/handlers/misc.ts +9 -1
  191. package/src/daemon/handlers/publish.ts +6 -1
  192. package/src/daemon/handlers/sessions.ts +65 -12
  193. package/src/daemon/handlers/shared.ts +36 -1
  194. package/src/daemon/handlers/signing.ts +37 -0
  195. package/src/daemon/handlers/skills.ts +20 -6
  196. package/src/daemon/handlers/subagents.ts +8 -3
  197. package/src/daemon/handlers/twitter-auth.ts +169 -0
  198. package/src/daemon/handlers/work-items.ts +384 -68
  199. package/src/daemon/ipc-contract-inventory.json +28 -4
  200. package/src/daemon/ipc-contract.ts +133 -37
  201. package/src/daemon/ipc-protocol.ts +7 -2
  202. package/src/daemon/lifecycle.ts +21 -0
  203. package/src/daemon/main.ts +10 -4
  204. package/src/daemon/ride-shotgun-handler.ts +74 -10
  205. package/src/daemon/server.ts +143 -26
  206. package/src/daemon/session-agent-loop.ts +887 -0
  207. package/src/daemon/session-attachments.ts +28 -5
  208. package/src/daemon/session-error.ts +24 -3
  209. package/src/daemon/session-lifecycle.ts +147 -0
  210. package/src/daemon/session-media-retry.ts +147 -0
  211. package/src/daemon/session-messaging.ts +145 -0
  212. package/src/daemon/session-notifiers.ts +164 -0
  213. package/src/daemon/session-process.ts +2 -2
  214. package/src/daemon/session-queue-manager.ts +1 -0
  215. package/src/daemon/session-runtime-assembly.ts +52 -0
  216. package/src/daemon/session-skill-tools.ts +124 -5
  217. package/src/daemon/session-slash.ts +3 -0
  218. package/src/daemon/session-surfaces.ts +77 -2
  219. package/src/daemon/session-tool-setup.ts +216 -2
  220. package/src/daemon/session-usage.ts +0 -2
  221. package/src/daemon/session.ts +114 -1404
  222. package/src/daemon/video-thumbnail.ts +60 -0
  223. package/src/doordash/client.ts +121 -27
  224. package/src/doordash/queries.ts +1 -2
  225. package/src/export/formatter.ts +3 -1
  226. package/src/followups/followup-store.ts +4 -2
  227. package/src/followups/types.ts +6 -0
  228. package/src/hooks/templates.ts +1 -1
  229. package/src/index.ts +32 -1153
  230. package/src/memory/attachments-store.ts +28 -83
  231. package/src/memory/channel-delivery-store.ts +7 -21
  232. package/src/memory/clarification-resolver.ts +6 -5
  233. package/src/memory/contradiction-checker.ts +3 -2
  234. package/src/memory/conversation-key-store.ts +10 -29
  235. package/src/memory/conversation-store.ts +2 -1
  236. package/src/memory/db.ts +96 -2
  237. package/src/memory/entity-extractor.ts +6 -3
  238. package/src/memory/items-extractor.ts +5 -4
  239. package/src/memory/jobs-store.ts +3 -2
  240. package/src/memory/llm-usage-store.ts +1 -2
  241. package/src/memory/runs-store.ts +1 -2
  242. package/src/memory/schema.ts +23 -2
  243. package/src/messaging/style-analyzer.ts +3 -2
  244. package/src/messaging/thread-summarizer.ts +8 -12
  245. package/src/messaging/triage-engine.ts +4 -2
  246. package/src/providers/openrouter/client.ts +20 -0
  247. package/src/providers/registry.ts +8 -0
  248. package/src/runtime/http-server.ts +108 -20
  249. package/src/runtime/routes/attachment-routes.ts +2 -3
  250. package/src/runtime/routes/call-routes.ts +140 -0
  251. package/src/runtime/routes/channel-routes.ts +5 -10
  252. package/src/runtime/routes/conversation-routes.ts +5 -5
  253. package/src/runtime/routes/run-routes.ts +2 -2
  254. package/src/runtime/run-orchestrator.ts +9 -3
  255. package/src/schedule/recurrence-engine.ts +138 -0
  256. package/src/schedule/recurrence-types.ts +67 -0
  257. package/src/schedule/schedule-store.ts +102 -57
  258. package/src/schedule/scheduler.ts +9 -6
  259. package/src/security/oauth2.ts +29 -4
  260. package/src/security/secret-allowlist.ts +46 -0
  261. package/src/skills/clawhub.ts +1 -1
  262. package/src/subagent/manager.ts +40 -8
  263. package/src/swarm/backend-claude-code.ts +64 -9
  264. package/src/swarm/worker-prompts.ts +2 -1
  265. package/src/tasks/SPEC.md +34 -28
  266. package/src/tasks/ephemeral-permissions.ts +16 -7
  267. package/src/tasks/task-compiler.ts +5 -4
  268. package/src/tasks/task-runner.ts +10 -5
  269. package/src/tasks/task-scheduler.ts +1 -1
  270. package/src/tasks/tool-sanitizer.ts +36 -0
  271. package/src/tools/assets/search.ts +4 -4
  272. package/src/tools/browser/api-map.ts +220 -0
  273. package/src/tools/browser/auto-navigate.ts +270 -0
  274. package/src/tools/browser/browser-execution.ts +2 -1
  275. package/src/tools/browser/browser-manager.ts +2 -2
  276. package/src/tools/browser/network-recorder.ts +5 -4
  277. package/src/tools/browser/x-auto-navigate.ts +207 -0
  278. package/src/tools/calls/call-end.ts +17 -67
  279. package/src/tools/calls/call-start.ts +24 -85
  280. package/src/tools/calls/call-status.ts +35 -51
  281. package/src/tools/claude-code/claude-code.ts +77 -11
  282. package/src/tools/contacts/contact-merge.ts +46 -78
  283. package/src/tools/contacts/contact-search.ts +35 -79
  284. package/src/tools/contacts/contact-upsert.ts +35 -108
  285. package/src/tools/credentials/vault.ts +20 -4
  286. package/src/tools/document/document-tool.ts +71 -144
  287. package/src/tools/executor.ts +129 -10
  288. package/src/tools/followups/followup_create.ts +46 -88
  289. package/src/tools/followups/followup_list.ts +34 -74
  290. package/src/tools/followups/followup_resolve.ts +31 -66
  291. package/src/tools/host-terminal/cli-discover.ts +2 -1
  292. package/src/tools/host-terminal/host-shell.ts +10 -0
  293. package/src/tools/memory/handlers.ts +5 -4
  294. package/src/tools/network/__tests__/web-search.test.ts +427 -0
  295. package/src/tools/network/script-proxy/__tests__/logging.test.ts +248 -0
  296. package/src/tools/network/script-proxy/__tests__/policy.test.ts +234 -0
  297. package/src/tools/network/script-proxy/__tests__/router.test.ts +76 -0
  298. package/src/tools/network/web-fetch.ts +18 -6
  299. package/src/tools/playbooks/index.ts +4 -5
  300. package/src/tools/playbooks/playbook-create.ts +3 -47
  301. package/src/tools/playbooks/playbook-delete.ts +1 -25
  302. package/src/tools/playbooks/playbook-list.ts +1 -28
  303. package/src/tools/playbooks/playbook-update.ts +3 -51
  304. package/src/tools/reminder/reminder.ts +5 -78
  305. package/src/tools/schedule/create.ts +69 -74
  306. package/src/tools/schedule/delete.ts +21 -47
  307. package/src/tools/schedule/list.ts +55 -74
  308. package/src/tools/schedule/update.ts +77 -84
  309. package/src/tools/subagent/abort.ts +29 -58
  310. package/src/tools/subagent/message.ts +30 -63
  311. package/src/tools/subagent/read.ts +53 -84
  312. package/src/tools/subagent/spawn.ts +43 -82
  313. package/src/tools/subagent/status.ts +42 -71
  314. package/src/tools/swarm/delegate.ts +2 -1
  315. package/src/tools/tasks/index.ts +8 -8
  316. package/src/tools/tasks/task-delete.ts +60 -88
  317. package/src/tools/tasks/task-list.ts +31 -52
  318. package/src/tools/tasks/task-run.ts +72 -108
  319. package/src/tools/tasks/task-save.ts +33 -65
  320. package/src/tools/tasks/work-item-enqueue.ts +183 -215
  321. package/src/tools/tasks/work-item-list.ts +33 -63
  322. package/src/tools/tasks/work-item-remove.ts +45 -97
  323. package/src/tools/tasks/work-item-update.ts +91 -163
  324. package/src/tools/terminal/backends/native.ts +3 -1
  325. package/src/tools/tool-manifest.ts +0 -62
  326. package/src/tools/types.ts +6 -0
  327. package/src/tools/ui-surface/definitions.ts +3 -1
  328. package/src/tools/watch/screen-watch.ts +3 -1
  329. package/src/tools/watcher/create.ts +52 -98
  330. package/src/tools/watcher/delete.ts +20 -46
  331. package/src/tools/watcher/digest.ts +36 -70
  332. package/src/tools/watcher/list.ts +49 -79
  333. package/src/tools/watcher/update.ts +45 -91
  334. package/src/twitter/client.ts +690 -0
  335. package/src/twitter/session.ts +91 -0
  336. package/src/usage/types.ts +0 -1
  337. package/src/util/truncate.ts +6 -0
  338. package/src/watcher/providers/slack.ts +2 -1
  339. package/src/watcher/watcher-store.ts +3 -2
  340. package/src/work-items/work-item-store.ts +27 -2
  341. package/src/workspace/commit-message-enrichment-service.ts +31 -7
  342. package/src/workspace/git-service.ts +87 -22
  343. package/src/workspace/provider-commit-message-generator.ts +242 -0
  344. package/src/workspace/turn-commit.ts +62 -3
  345. package/src/tools/contacts/index.ts +0 -4
  346. package/src/tools/document/index.ts +0 -5
  347. package/src/tools/followups/index.ts +0 -3
  348. package/src/tools/subagent/index.ts +0 -5
  349. /package/src/__tests__/{memory-context-benchmark.test.ts → memory-context-benchmark.benchmark.test.ts} +0 -0
@@ -35,7 +35,7 @@ mock.module('../config/loader.js', () => ({
35
35
  }),
36
36
  }));
37
37
 
38
- import { initializeDb, getDb } from '../memory/db.js';
38
+ import { initializeDb, getDb, resetDb } from '../memory/db.js';
39
39
  import { uploadAttachment, linkAttachmentToMessage } from '../memory/attachments-store.js';
40
40
  import { createConversation, addMessage } from '../memory/conversation-store.js';
41
41
  import { searchAttachments } from '../tools/assets/search.js';
@@ -45,6 +45,7 @@ import type { ToolContext } from '../tools/types.js';
45
45
  initializeDb();
46
46
 
47
47
  afterAll(() => {
48
+ resetDb();
48
49
  try { rmSync(testDir, { recursive: true }); } catch { /* best effort */ }
49
50
  });
50
51
 
@@ -66,10 +67,10 @@ function seedAttachments() {
66
67
  // Force createdAt by uploading then manipulating the DB
67
68
  const db = getDb();
68
69
 
69
- const png1 = uploadAttachment('ast-1', 'selfie.png', 'image/png', 'AAAA');
70
- const jpg1 = uploadAttachment('ast-1', 'photo.jpg', 'image/jpeg', 'BBBB');
71
- const pdf1 = uploadAttachment('ast-1', 'report.pdf', 'application/pdf', 'CCCC');
72
- const png2 = uploadAttachment('ast-1', 'screenshot.png', 'image/png', 'DDDD');
70
+ const png1 = uploadAttachment('selfie.png', 'image/png', 'AAAA');
71
+ const jpg1 = uploadAttachment('photo.jpg', 'image/jpeg', 'BBBB');
72
+ const pdf1 = uploadAttachment('report.pdf', 'application/pdf', 'CCCC');
73
+ const png2 = uploadAttachment('screenshot.png', 'image/png', 'DDDD');
73
74
 
74
75
  // Backdate some attachments for recency testing
75
76
  db.run(`UPDATE attachments SET created_at = ${oneDayAgo} WHERE id = '${jpg1.id}'`);
@@ -217,8 +218,8 @@ describe('searchAttachments with conversation_id', () => {
217
218
  beforeEach(resetTables);
218
219
 
219
220
  test('returns only attachments linked to the specified conversation', () => {
220
- const png1 = uploadAttachment('ast-1', 'in-conv.png', 'image/png', 'AAAA');
221
- const png2 = uploadAttachment('ast-1', 'other-conv.png', 'image/png', 'BBBB');
221
+ const png1 = uploadAttachment('in-conv.png', 'image/png', 'AAAA');
222
+ const png2 = uploadAttachment('other-conv.png', 'image/png', 'BBBB');
222
223
 
223
224
  const conv1 = createConversation();
224
225
  const conv2 = createConversation();
@@ -234,7 +235,7 @@ describe('searchAttachments with conversation_id', () => {
234
235
  });
235
236
 
236
237
  test('returns empty when conversation has no attachments', () => {
237
- uploadAttachment('ast-1', 'orphan.png', 'image/png', 'AAAA');
238
+ uploadAttachment('orphan.png', 'image/png', 'AAAA');
238
239
  const conv = createConversation();
239
240
  addMessage(conv.id, 'user', 'No attachments here');
240
241
 
@@ -243,14 +244,14 @@ describe('searchAttachments with conversation_id', () => {
243
244
  });
244
245
 
245
246
  test('returns empty for nonexistent conversation_id', () => {
246
- uploadAttachment('ast-1', 'file.png', 'image/png', 'AAAA');
247
+ uploadAttachment('file.png', 'image/png', 'AAAA');
247
248
  const results = searchAttachments({ conversation_id: 'conv-nonexistent' });
248
249
  expect(results.length).toBe(0);
249
250
  });
250
251
 
251
252
  test('combines conversation_id with mime_type filter', () => {
252
- const png = uploadAttachment('ast-1', 'image.png', 'image/png', 'AAAA');
253
- const pdf = uploadAttachment('ast-1', 'doc.pdf', 'application/pdf', 'BBBB');
253
+ const png = uploadAttachment('image.png', 'image/png', 'AAAA');
254
+ const pdf = uploadAttachment('doc.pdf', 'application/pdf', 'BBBB');
254
255
 
255
256
  const conv = createConversation();
256
257
  const msg = addMessage(conv.id, 'user', 'Both types');
@@ -264,8 +265,8 @@ describe('searchAttachments with conversation_id', () => {
264
265
  });
265
266
 
266
267
  test('combines conversation_id with filename filter', () => {
267
- const a = uploadAttachment('ast-1', 'target.png', 'image/png', 'AAAA');
268
- const b = uploadAttachment('ast-1', 'other.png', 'image/png', 'BBBB');
268
+ const a = uploadAttachment('target.png', 'image/png', 'AAAA');
269
+ const b = uploadAttachment('other.png', 'image/png', 'BBBB');
269
270
 
270
271
  const conv = createConversation();
271
272
  const msg = addMessage(conv.id, 'user', 'Both');
@@ -294,7 +295,7 @@ describe('AssetSearchTool.execute', () => {
294
295
  });
295
296
 
296
297
  test('returns formatted results for matching assets', async () => {
297
- uploadAttachment('ast-1', 'selfie.png', 'image/png', 'AAAA');
298
+ uploadAttachment('selfie.png', 'image/png', 'AAAA');
298
299
  const result = await tool.execute({}, dummyContext);
299
300
  expect(result.isError).toBe(false);
300
301
  expect(result.content).toContain('selfie.png');
@@ -320,14 +321,14 @@ describe('AssetSearchTool.execute', () => {
320
321
  });
321
322
 
322
323
  test('includes attachment ID in output', async () => {
323
- const stored = uploadAttachment('ast-1', 'chart.png', 'image/png', 'AAAA');
324
+ const stored = uploadAttachment('chart.png', 'image/png', 'AAAA');
324
325
  const result = await tool.execute({}, dummyContext);
325
326
  expect(result.isError).toBe(false);
326
327
  expect(result.content).toContain(stored.id);
327
328
  });
328
329
 
329
330
  test('includes MIME type and kind in output', async () => {
330
- uploadAttachment('ast-1', 'chart.png', 'image/png', 'AAAA');
331
+ uploadAttachment('chart.png', 'image/png', 'AAAA');
331
332
  const result = await tool.execute({}, dummyContext);
332
333
  expect(result.isError).toBe(false);
333
334
  expect(result.content).toContain('image/png');
@@ -363,7 +364,7 @@ describe('AssetSearchTool visibility policy', () => {
363
364
 
364
365
  test('attachments from standard threads are visible from any context', async () => {
365
366
  const standardConv = createConversation({ title: 'standard-conv' });
366
- const attachment = uploadAttachment('ast-1', 'public.png', 'image/png', 'AAAA');
367
+ const attachment = uploadAttachment('public.png', 'image/png', 'AAAA');
367
368
  const msg = addMessage(standardConv.id, 'user', 'standard message');
368
369
  linkAttachmentToMessage(msg.id, attachment.id, 0);
369
370
 
@@ -382,7 +383,7 @@ describe('AssetSearchTool visibility policy', () => {
382
383
 
383
384
  test('attachments from private threads are visible within the same private thread', async () => {
384
385
  const privateConv = createConversation({ title: 'private-conv', threadType: 'private' });
385
- const attachment = uploadAttachment('ast-1', 'secret.png', 'image/png', 'AAAA');
386
+ const attachment = uploadAttachment('secret.png', 'image/png', 'AAAA');
386
387
  const msg = addMessage(privateConv.id, 'user', 'private message');
387
388
  linkAttachmentToMessage(msg.id, attachment.id, 0);
388
389
 
@@ -400,7 +401,7 @@ describe('AssetSearchTool visibility policy', () => {
400
401
 
401
402
  test('attachments from private threads are NOT visible from a different conversation', async () => {
402
403
  const privateConv = createConversation({ title: 'private-conv', threadType: 'private' });
403
- const attachment = uploadAttachment('ast-1', 'secret.png', 'image/png', 'AAAA');
404
+ const attachment = uploadAttachment('secret.png', 'image/png', 'AAAA');
404
405
  const msg = addMessage(privateConv.id, 'user', 'private message');
405
406
  linkAttachmentToMessage(msg.id, attachment.id, 0);
406
407
 
@@ -419,7 +420,7 @@ describe('AssetSearchTool visibility policy', () => {
419
420
 
420
421
  test('attachments from private threads are NOT visible from standard threads', async () => {
421
422
  const privateConv = createConversation({ title: 'private-conv', threadType: 'private' });
422
- const attachment = uploadAttachment('ast-1', 'secret.png', 'image/png', 'AAAA');
423
+ const attachment = uploadAttachment('secret.png', 'image/png', 'AAAA');
423
424
  const msg = addMessage(privateConv.id, 'user', 'private message');
424
425
  linkAttachmentToMessage(msg.id, attachment.id, 0);
425
426
 
@@ -439,7 +440,7 @@ describe('AssetSearchTool visibility policy', () => {
439
440
  test('attachment linked to both private and standard threads is visible everywhere', async () => {
440
441
  const privateConv = createConversation({ title: 'private-conv', threadType: 'private' });
441
442
  const standardConv = createConversation({ title: 'standard-conv' });
442
- const attachment = uploadAttachment('ast-1', 'shared.png', 'image/png', 'AAAA');
443
+ const attachment = uploadAttachment('shared.png', 'image/png', 'AAAA');
443
444
 
444
445
  const msg1 = addMessage(privateConv.id, 'user', 'private message');
445
446
  const msg2 = addMessage(standardConv.id, 'user', 'standard message');
@@ -460,7 +461,7 @@ describe('AssetSearchTool visibility policy', () => {
460
461
  });
461
462
 
462
463
  test('orphan attachments (no message linkage) remain visible', async () => {
463
- uploadAttachment('ast-1', 'orphan.png', 'image/png', 'AAAA');
464
+ uploadAttachment('orphan.png', 'image/png', 'AAAA');
464
465
 
465
466
  const conv = createConversation({ title: 'any-conv' });
466
467
  const context: ToolContext = {
@@ -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', () => {
@@ -71,10 +71,11 @@ describe('browser skill migration end-state', () => {
71
71
  expect(defNames).not.toContain(name);
72
72
  }
73
73
 
74
- // Payload ceiling: browser tools contribute ~4 640 chars. If they leak
75
- // 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.
76
77
  const payloadSize = JSON.stringify(definitions).length;
77
- expect(payloadSize).toBeLessThan(42_000);
78
+ expect(payloadSize).toBeLessThan(47_000);
78
79
  });
79
80
 
80
81
  // ── 2. Browser skill exists and is active ──────────────────────────