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
@@ -0,0 +1,331 @@
1
+ import { describe, test, expect, beforeEach, afterAll, mock } from 'bun:test';
2
+ import { mkdtempSync, rmSync } from 'node:fs';
3
+ import { tmpdir } from 'node:os';
4
+ import { join } from 'node:path';
5
+
6
+ const testDir = mkdtempSync(join(tmpdir(), 'contacts-tools-test-'));
7
+
8
+ mock.module('../util/platform.js', () => ({
9
+ getDataDir: () => testDir,
10
+ isMacOS: () => process.platform === 'darwin',
11
+ isLinux: () => process.platform === 'linux',
12
+ isWindows: () => process.platform === 'win32',
13
+ getSocketPath: () => join(testDir, 'test.sock'),
14
+ getPidPath: () => join(testDir, 'test.pid'),
15
+ getDbPath: () => join(testDir, 'test.db'),
16
+ getLogPath: () => join(testDir, 'test.log'),
17
+ ensureDataDir: () => {},
18
+ migrateToDataLayout: () => {},
19
+ migrateToWorkspaceLayout: () => {},
20
+ }));
21
+
22
+ mock.module('../util/logger.js', () => ({
23
+ getLogger: () => new Proxy({} as Record<string, unknown>, {
24
+ get: () => () => {},
25
+ }),
26
+ }));
27
+
28
+ mock.module('../config/loader.js', () => ({
29
+ getConfig: () => ({ memory: {} }),
30
+ }));
31
+
32
+ import type { Database } from 'bun:sqlite';
33
+ import { initializeDb, getDb, resetDb } from '../memory/db.js';
34
+ import type { ToolContext } from '../tools/types.js';
35
+ import { executeContactUpsert } from '../tools/contacts/contact-upsert.js';
36
+ import { executeContactSearch } from '../tools/contacts/contact-search.js';
37
+ import { executeContactMerge } from '../tools/contacts/contact-merge.js';
38
+
39
+ initializeDb();
40
+
41
+ afterAll(() => {
42
+ resetDb();
43
+ try { rmSync(testDir, { recursive: true }); } catch { /* best effort */ }
44
+ });
45
+
46
+ function getRawDb(): Database {
47
+ return (getDb() as unknown as { $client: Database }).$client;
48
+ }
49
+
50
+ const ctx: ToolContext = {
51
+ workingDir: '/tmp',
52
+ sessionId: 'test-session',
53
+ conversationId: 'test-conversation',
54
+ };
55
+
56
+ function clearContacts(): void {
57
+ getRawDb().run('DELETE FROM contact_channels');
58
+ getRawDb().run('DELETE FROM contacts');
59
+ }
60
+
61
+ // ── contact_upsert ──────────────────────────────────────────────────
62
+
63
+ describe('contact_upsert tool', () => {
64
+ beforeEach(clearContacts);
65
+
66
+ test('creates a new contact with display name only', async () => {
67
+ const result = await executeContactUpsert({ display_name: 'Alice' }, ctx);
68
+
69
+ expect(result.isError).toBe(false);
70
+ expect(result.content).toContain('Created contact');
71
+ expect(result.content).toContain('Alice');
72
+ expect(result.content).toContain('Importance: 0.50');
73
+ });
74
+
75
+ test('creates a contact with all fields', async () => {
76
+ const result = await executeContactUpsert({
77
+ display_name: 'Bob',
78
+ relationship: 'colleague',
79
+ importance: 0.8,
80
+ response_expectation: 'within_hours',
81
+ preferred_tone: 'professional',
82
+ channels: [
83
+ { type: 'email', address: 'bob@example.com', is_primary: true },
84
+ { type: 'slack', address: '@bob' },
85
+ ],
86
+ }, ctx);
87
+
88
+ expect(result.isError).toBe(false);
89
+ expect(result.content).toContain('Bob');
90
+ expect(result.content).toContain('colleague');
91
+ expect(result.content).toContain('0.80');
92
+ expect(result.content).toContain('within_hours');
93
+ expect(result.content).toContain('professional');
94
+ expect(result.content).toContain('email: bob@example.com');
95
+ expect(result.content).toContain('slack: @bob');
96
+ });
97
+
98
+ test('updates an existing contact by ID', async () => {
99
+ const createResult = await executeContactUpsert({ display_name: 'Charlie' }, ctx);
100
+ expect(createResult.isError).toBe(false);
101
+
102
+ // Extract ID from output
103
+ const idMatch = createResult.content.match(/Contact (\S+)/);
104
+ expect(idMatch).not.toBeNull();
105
+ const contactId = idMatch![1];
106
+
107
+ const updateResult = await executeContactUpsert({
108
+ id: contactId,
109
+ display_name: 'Charlie Updated',
110
+ importance: 0.9,
111
+ }, ctx);
112
+
113
+ expect(updateResult.isError).toBe(false);
114
+ expect(updateResult.content).toContain('Updated contact');
115
+ expect(updateResult.content).toContain('Charlie Updated');
116
+ expect(updateResult.content).toContain('0.90');
117
+ });
118
+
119
+ test('auto-matches by channel address on create', async () => {
120
+ // Create a contact with an email
121
+ await executeContactUpsert({
122
+ display_name: 'Diana',
123
+ channels: [{ type: 'email', address: 'diana@example.com' }],
124
+ }, ctx);
125
+
126
+ // Upsert with same email but different display name
127
+ const result = await executeContactUpsert({
128
+ display_name: 'Diana Updated',
129
+ channels: [{ type: 'email', address: 'diana@example.com' }],
130
+ }, ctx);
131
+
132
+ expect(result.isError).toBe(false);
133
+ expect(result.content).toContain('Updated contact');
134
+ expect(result.content).toContain('Diana Updated');
135
+
136
+ // Should still be just 1 contact
137
+ const count = getRawDb().query('SELECT COUNT(*) as c FROM contacts').get() as { c: number };
138
+ expect(count.c).toBe(1);
139
+ });
140
+
141
+ test('rejects missing display_name', async () => {
142
+ const result = await executeContactUpsert({}, ctx);
143
+
144
+ expect(result.isError).toBe(true);
145
+ expect(result.content).toContain('display_name is required');
146
+ });
147
+
148
+ test('rejects empty display_name', async () => {
149
+ const result = await executeContactUpsert({ display_name: ' ' }, ctx);
150
+
151
+ expect(result.isError).toBe(true);
152
+ expect(result.content).toContain('display_name is required');
153
+ });
154
+
155
+ test('rejects importance out of range', async () => {
156
+ const result = await executeContactUpsert({
157
+ display_name: 'Test',
158
+ importance: 1.5,
159
+ }, ctx);
160
+
161
+ expect(result.isError).toBe(true);
162
+ expect(result.content).toContain('importance must be a number between 0 and 1');
163
+ });
164
+
165
+ test('rejects negative importance', async () => {
166
+ const result = await executeContactUpsert({
167
+ display_name: 'Test',
168
+ importance: -0.1,
169
+ }, ctx);
170
+
171
+ expect(result.isError).toBe(true);
172
+ expect(result.content).toContain('importance must be a number between 0 and 1');
173
+ });
174
+ });
175
+
176
+ // ── contact_search ──────────────────────────────────────────────────
177
+
178
+ describe('contact_search tool', () => {
179
+ beforeEach(clearContacts);
180
+
181
+ test('searches by display name', async () => {
182
+ await executeContactUpsert({ display_name: 'Alice Smith' }, ctx);
183
+ await executeContactUpsert({ display_name: 'Bob Jones' }, ctx);
184
+
185
+ const result = await executeContactSearch({ query: 'Alice' }, ctx);
186
+
187
+ expect(result.isError).toBe(false);
188
+ expect(result.content).toContain('Alice Smith');
189
+ expect(result.content).not.toContain('Bob Jones');
190
+ });
191
+
192
+ test('searches by channel address', async () => {
193
+ await executeContactUpsert({
194
+ display_name: 'Charlie',
195
+ channels: [{ type: 'email', address: 'charlie@example.com' }],
196
+ }, ctx);
197
+
198
+ const result = await executeContactSearch({ channel_address: 'charlie@example' }, ctx);
199
+
200
+ expect(result.isError).toBe(false);
201
+ expect(result.content).toContain('Charlie');
202
+ });
203
+
204
+ test('searches by relationship', async () => {
205
+ await executeContactUpsert({ display_name: 'Diana', relationship: 'friend' }, ctx);
206
+ await executeContactUpsert({ display_name: 'Eve', relationship: 'colleague' }, ctx);
207
+
208
+ const result = await executeContactSearch({ relationship: 'friend' }, ctx);
209
+
210
+ expect(result.isError).toBe(false);
211
+ expect(result.content).toContain('Diana');
212
+ expect(result.content).not.toContain('Eve');
213
+ });
214
+
215
+ test('returns no results message when nothing matches', async () => {
216
+ await executeContactUpsert({ display_name: 'Existing' }, ctx);
217
+
218
+ const result = await executeContactSearch({ query: 'Nonexistent' }, ctx);
219
+
220
+ expect(result.isError).toBe(false);
221
+ expect(result.content).toContain('No contacts found');
222
+ });
223
+
224
+ test('rejects search with no criteria', async () => {
225
+ const result = await executeContactSearch({}, ctx);
226
+
227
+ expect(result.isError).toBe(true);
228
+ expect(result.content).toContain('At least one search criterion is required');
229
+ });
230
+
231
+ test('searches by channel address with type filter', async () => {
232
+ await executeContactUpsert({
233
+ display_name: 'Frank',
234
+ channels: [
235
+ { type: 'email', address: 'frank@example.com' },
236
+ { type: 'slack', address: 'frank@example.com' },
237
+ ],
238
+ }, ctx);
239
+
240
+ const result = await executeContactSearch({
241
+ channel_address: 'frank@example',
242
+ channel_type: 'slack',
243
+ }, ctx);
244
+
245
+ expect(result.isError).toBe(false);
246
+ expect(result.content).toContain('Frank');
247
+ });
248
+ });
249
+
250
+ // ── contact_merge ───────────────────────────────────────────────────
251
+
252
+ describe('contact_merge tool', () => {
253
+ beforeEach(clearContacts);
254
+
255
+ function extractContactId(result: { content: string }): string {
256
+ const match = result.content.match(/Contact (\S+)/);
257
+ expect(match).not.toBeNull();
258
+ return match![1];
259
+ }
260
+
261
+ test('merges two contacts', async () => {
262
+ const r1 = await executeContactUpsert({
263
+ display_name: 'Alice (Email)',
264
+ importance: 0.7,
265
+ channels: [{ type: 'email', address: 'alice@example.com' }],
266
+ }, ctx);
267
+ const r2 = await executeContactUpsert({
268
+ display_name: 'Alice (Slack)',
269
+ importance: 0.9,
270
+ channels: [{ type: 'slack', address: '@alice' }],
271
+ }, ctx);
272
+
273
+ const keepId = extractContactId(r1);
274
+ const mergeId = extractContactId(r2);
275
+
276
+ const result = await executeContactMerge({
277
+ keep_id: keepId,
278
+ merge_id: mergeId,
279
+ }, ctx);
280
+
281
+ expect(result.isError).toBe(false);
282
+ expect(result.content).toContain('Merged');
283
+ expect(result.content).toContain('Importance: 0.90'); // takes higher importance
284
+ expect(result.content).toContain('email: alice@example.com');
285
+ expect(result.content).toContain('slack: @alice');
286
+
287
+ // Verify donor is deleted
288
+ const count = getRawDb().query('SELECT COUNT(*) as c FROM contacts').get() as { c: number };
289
+ expect(count.c).toBe(1);
290
+ });
291
+
292
+ test('rejects missing keep_id', async () => {
293
+ const result = await executeContactMerge({ merge_id: 'some-id' }, ctx);
294
+
295
+ expect(result.isError).toBe(true);
296
+ expect(result.content).toContain('keep_id is required');
297
+ });
298
+
299
+ test('rejects missing merge_id', async () => {
300
+ const result = await executeContactMerge({ keep_id: 'some-id' }, ctx);
301
+
302
+ expect(result.isError).toBe(true);
303
+ expect(result.content).toContain('merge_id is required');
304
+ });
305
+
306
+ test('returns error for nonexistent keep_id', async () => {
307
+ const r = await executeContactUpsert({ display_name: 'Exists' }, ctx);
308
+ const existingId = extractContactId(r);
309
+
310
+ const result = await executeContactMerge({
311
+ keep_id: 'nonexistent',
312
+ merge_id: existingId,
313
+ }, ctx);
314
+
315
+ expect(result.isError).toBe(true);
316
+ expect(result.content).toContain('not found');
317
+ });
318
+
319
+ test('returns error for nonexistent merge_id', async () => {
320
+ const r = await executeContactUpsert({ display_name: 'Exists' }, ctx);
321
+ const existingId = extractContactId(r);
322
+
323
+ const result = await executeContactMerge({
324
+ keep_id: existingId,
325
+ merge_id: 'nonexistent',
326
+ }, ctx);
327
+
328
+ expect(result.isError).toBe(true);
329
+ expect(result.content).toContain('not found');
330
+ });
331
+ });
@@ -23,7 +23,7 @@ mock.module('../util/logger.js', () => ({
23
23
  }),
24
24
  }));
25
25
 
26
- import { initializeDb, getDb } from '../memory/db.js';
26
+ import { initializeDb, getDb, resetDb } from '../memory/db.js';
27
27
  import {
28
28
  createConversation,
29
29
  getConversation,
@@ -40,13 +40,13 @@ import {
40
40
  linkAttachmentToMessage,
41
41
  getAttachmentsForMessage,
42
42
  getAttachmentById,
43
- getAttachmentsForMessageUnscoped,
44
43
  } from '../memory/attachments-store.js';
45
44
 
46
45
  // Initialize db once before all tests
47
46
  initializeDb();
48
47
 
49
48
  afterAll(() => {
49
+ resetDb();
50
50
  try { rmSync(testDir, { recursive: true }); } catch { /* best effort */ }
51
51
  });
52
52
 
@@ -236,11 +236,11 @@ describe('attachment orphan cleanup', () => {
236
236
  addMessage(conv.id, 'user', 'hello');
237
237
  const assistantMsg = addMessage(conv.id, 'assistant', 'Here is a file');
238
238
 
239
- const stored = uploadAttachment('ast-1', 'chart.png', 'image/png', 'iVBOR');
239
+ const stored = uploadAttachment('chart.png', 'image/png', 'iVBOR');
240
240
  linkAttachmentToMessage(assistantMsg.id, stored.id, 0);
241
241
 
242
242
  // Verify attachment is linked
243
- expect(getAttachmentsForMessageUnscoped(assistantMsg.id)).toHaveLength(1);
243
+ expect(getAttachmentsForMessage(assistantMsg.id)).toHaveLength(1);
244
244
 
245
245
  // Delete the exchange — should also clean up orphaned attachments
246
246
  deleteLastExchange(conv.id);
@@ -257,7 +257,7 @@ describe('attachment orphan cleanup', () => {
257
257
  addMessage(conv.id, 'user', 'question');
258
258
  const msg2 = addMessage(conv.id, 'assistant', 'second');
259
259
 
260
- const shared = uploadAttachment('ast-1', 'shared.png', 'image/png', 'AAAA');
260
+ const shared = uploadAttachment('shared.png', 'image/png', 'AAAA');
261
261
  linkAttachmentToMessage(msg1.id, shared.id, 0);
262
262
  linkAttachmentToMessage(msg2.id, shared.id, 0);
263
263
 
@@ -273,7 +273,7 @@ describe('attachment orphan cleanup', () => {
273
273
  test('clearAll removes all attachments', () => {
274
274
  const conv = createConversation('test');
275
275
  const msg = addMessage(conv.id, 'assistant', 'file');
276
- const stored = uploadAttachment('ast-1', 'doc.pdf', 'application/pdf', 'JVBER');
276
+ const stored = uploadAttachment('doc.pdf', 'application/pdf', 'JVBER');
277
277
  linkAttachmentToMessage(msg.id, stored.id, 0);
278
278
 
279
279
  clearAll();
@@ -291,11 +291,11 @@ describe('attachment orphan cleanup', () => {
291
291
  const assistantMsg = addMessage(conv.id, 'assistant', 'Here is a file');
292
292
 
293
293
  // An attachment linked to the assistant message (should be cleaned up)
294
- const linked = uploadAttachment('ast-1', 'chart.png', 'image/png', 'iVBOR');
294
+ const linked = uploadAttachment('chart.png', 'image/png', 'iVBOR');
295
295
  linkAttachmentToMessage(assistantMsg.id, linked.id, 0);
296
296
 
297
297
  // A freshly uploaded attachment not linked to any message (should survive)
298
- uploadAttachment('ast-1', 'pending.png', 'image/png', 'AAAA');
298
+ uploadAttachment('pending.png', 'image/png', 'AAAA');
299
299
 
300
300
  deleteLastExchange(conv.id);
301
301
 
@@ -440,7 +440,7 @@ describe('attachment reuse across thread lifecycles', () => {
440
440
  test('attachment uploaded in conversation A is retrievable by ID without any conversation reference', () => {
441
441
  const convA = createConversation('Thread A');
442
442
  const msgA = addMessage(convA.id, 'assistant', 'Here is a file');
443
- const stored = uploadAttachment('ast-1', 'report.pdf', 'application/pdf', 'JVBER');
443
+ const stored = uploadAttachment('report.pdf', 'application/pdf', 'JVBER');
444
444
  linkAttachmentToMessage(msgA.id, stored.id, 0);
445
445
 
446
446
  // Create a completely separate conversation
@@ -448,8 +448,7 @@ describe('attachment reuse across thread lifecycles', () => {
448
448
  addMessage(convB.id, 'user', 'hello');
449
449
 
450
450
  // The attachment is retrievable by ID regardless of which conversation is active.
451
- // getAttachmentById only scopes by assistantId, not conversationId.
452
- const fetched = getAttachmentById('ast-1', stored.id);
451
+ const fetched = getAttachmentById(stored.id);
453
452
  expect(fetched).not.toBeNull();
454
453
  expect(fetched!.id).toBe(stored.id);
455
454
  expect(fetched!.originalFilename).toBe('report.pdf');
@@ -464,16 +463,16 @@ describe('attachment reuse across thread lifecycles', () => {
464
463
  const msgB = addMessage(convB.id, 'assistant', 'Reused file');
465
464
 
466
465
  // Upload once, link to both conversations
467
- const stored = uploadAttachment('ast-1', 'shared.png', 'image/png', 'iVBORw0K');
466
+ const stored = uploadAttachment('shared.png', 'image/png', 'iVBORw0K');
468
467
  linkAttachmentToMessage(msgA.id, stored.id, 0);
469
468
  linkAttachmentToMessage(msgB.id, stored.id, 0);
470
469
 
471
470
  // Both messages see the attachment
472
- const linkedA = getAttachmentsForMessage(msgA.id, 'ast-1');
471
+ const linkedA = getAttachmentsForMessage(msgA.id);
473
472
  expect(linkedA).toHaveLength(1);
474
473
  expect(linkedA[0].id).toBe(stored.id);
475
474
 
476
- const linkedB = getAttachmentsForMessage(msgB.id, 'ast-1');
475
+ const linkedB = getAttachmentsForMessage(msgB.id);
477
476
  expect(linkedB).toHaveLength(1);
478
477
  expect(linkedB[0].id).toBe(stored.id);
479
478
  });
@@ -489,7 +488,7 @@ describe('attachment reuse across thread lifecycles', () => {
489
488
  addMessage(convB.id, 'user', 'Show me the chart');
490
489
  const msgB = addMessage(convB.id, 'assistant', 'Reused');
491
490
 
492
- const stored = uploadAttachment('ast-1', 'chart.png', 'image/png', 'AAAA');
491
+ const stored = uploadAttachment('chart.png', 'image/png', 'AAAA');
493
492
  linkAttachmentToMessage(msgA.id, stored.id, 0);
494
493
  linkAttachmentToMessage(msgB.id, stored.id, 0);
495
494
 
@@ -497,11 +496,11 @@ describe('attachment reuse across thread lifecycles', () => {
497
496
  deleteLastExchange(convA.id);
498
497
 
499
498
  // Attachment survives because convB still references it
500
- const fetched = getAttachmentById('ast-1', stored.id);
499
+ const fetched = getAttachmentById(stored.id);
501
500
  expect(fetched).not.toBeNull();
502
501
 
503
502
  // convB's message still has the attachment linked
504
- const linkedB = getAttachmentsForMessage(msgB.id, 'ast-1');
503
+ const linkedB = getAttachmentsForMessage(msgB.id);
505
504
  expect(linkedB).toHaveLength(1);
506
505
  expect(linkedB[0].id).toBe(stored.id);
507
506
  });
@@ -514,8 +513,8 @@ describe('attachment reuse across thread lifecycles', () => {
514
513
  addMessage(convB.id, 'user', 'upload in B');
515
514
 
516
515
  // Same content uploaded in two different conversation contexts
517
- const first = uploadAttachment('ast-1', 'photo.png', 'image/png', 'DEDUPCROSS');
518
- const second = uploadAttachment('ast-1', 'photo.png', 'image/png', 'DEDUPCROSS');
516
+ const first = uploadAttachment('photo.png', 'image/png', 'DEDUPCROSS');
517
+ const second = uploadAttachment('photo.png', 'image/png', 'DEDUPCROSS');
519
518
 
520
519
  // Dedup returns the same attachment row
521
520
  expect(second.id).toBe(first.id);
@@ -540,11 +539,11 @@ describe('no private-thread attachment visibility boundary', () => {
540
539
  expect(privateConv.threadType).toBe('private');
541
540
 
542
541
  const msg = addMessage(privateConv.id, 'assistant', 'Private content');
543
- const stored = uploadAttachment('ast-1', 'secret.pdf', 'application/pdf', 'JVBER');
542
+ const stored = uploadAttachment('secret.pdf', 'application/pdf', 'JVBER');
544
543
  linkAttachmentToMessage(msg.id, stored.id, 0);
545
544
 
546
545
  // Attachment is globally visible by ID — no thread-type filter exists
547
- const fetched = getAttachmentById('ast-1', stored.id);
546
+ const fetched = getAttachmentById(stored.id);
548
547
  expect(fetched).not.toBeNull();
549
548
  expect(fetched!.originalFilename).toBe('secret.pdf');
550
549
  });
@@ -556,27 +555,26 @@ describe('no private-thread attachment visibility boundary', () => {
556
555
  const privateMsg = addMessage(privateConv.id, 'assistant', 'Private file');
557
556
  const standardMsg = addMessage(standardConv.id, 'assistant', 'Reusing private file');
558
557
 
559
- const stored = uploadAttachment('ast-1', 'private-doc.png', 'image/png', 'PRIVDATA');
558
+ const stored = uploadAttachment('private-doc.png', 'image/png', 'PRIVDATA');
560
559
  linkAttachmentToMessage(privateMsg.id, stored.id, 0);
561
560
  linkAttachmentToMessage(standardMsg.id, stored.id, 0);
562
561
 
563
562
  // Both threads can see the attachment
564
- const linkedPrivate = getAttachmentsForMessage(privateMsg.id, 'ast-1');
563
+ const linkedPrivate = getAttachmentsForMessage(privateMsg.id);
565
564
  expect(linkedPrivate).toHaveLength(1);
566
565
 
567
- const linkedStandard = getAttachmentsForMessage(standardMsg.id, 'ast-1');
566
+ const linkedStandard = getAttachmentsForMessage(standardMsg.id);
568
567
  expect(linkedStandard).toHaveLength(1);
569
568
  expect(linkedStandard[0].id).toBe(stored.id);
570
569
  });
571
570
 
572
- test('getAttachmentsForMessageUnscoped returns private thread attachments', () => {
571
+ test('getAttachmentsForMessage returns private thread attachments', () => {
573
572
  const privateConv = createConversation({ title: 'Private', threadType: 'private' });
574
573
  const msg = addMessage(privateConv.id, 'assistant', 'Private media');
575
- const stored = uploadAttachment('ast-1', 'photo.jpg', 'image/jpeg', 'AAAA');
574
+ const stored = uploadAttachment('photo.jpg', 'image/jpeg', 'AAAA');
576
575
  linkAttachmentToMessage(msg.id, stored.id, 0);
577
576
 
578
- // Unscoped retrieval has no thread-type filter
579
- const linked = getAttachmentsForMessageUnscoped(msg.id);
577
+ const linked = getAttachmentsForMessage(msg.id);
580
578
  expect(linked).toHaveLength(1);
581
579
  expect(linked[0].id).toBe(stored.id);
582
580
  });
@@ -586,8 +584,8 @@ describe('no private-thread attachment visibility boundary', () => {
586
584
  createConversation({ title: 'Standard', threadType: 'standard' });
587
585
 
588
586
  // Same content uploaded in private and standard contexts
589
- const fromPrivate = uploadAttachment('ast-1', 'file.png', 'image/png', 'CROSSTHREAD');
590
- const fromStandard = uploadAttachment('ast-1', 'file.png', 'image/png', 'CROSSTHREAD');
587
+ const fromPrivate = uploadAttachment('file.png', 'image/png', 'CROSSTHREAD');
588
+ const fromStandard = uploadAttachment('file.png', 'image/png', 'CROSSTHREAD');
591
589
 
592
590
  // Dedup returns the same row — no thread-type isolation
593
591
  expect(fromStandard.id).toBe(fromPrivate.id);
@@ -600,8 +598,8 @@ describe('no private-thread attachment visibility boundary', () => {
600
598
  const privateMsg = addMessage(privateConv.id, 'assistant', 'Private file');
601
599
  const standardMsg = addMessage(standardConv.id, 'assistant', 'Standard file');
602
600
 
603
- const att1 = uploadAttachment('ast-1', 'private.png', 'image/png', 'PRIV');
604
- const att2 = uploadAttachment('ast-1', 'standard.png', 'image/png', 'STD');
601
+ const att1 = uploadAttachment('private.png', 'image/png', 'PRIV');
602
+ const att2 = uploadAttachment('standard.png', 'image/png', 'STD');
605
603
  linkAttachmentToMessage(privateMsg.id, att1.id, 0);
606
604
  linkAttachmentToMessage(standardMsg.id, att2.id, 0);
607
605
 
@@ -185,6 +185,10 @@ describe('Invariant 2: no generic plaintext secret read API', () => {
185
185
  'email/providers/index.ts', // email provider API key lookup
186
186
  'tools/network/script-proxy/session-manager.ts', // proxy credential injection at runtime
187
187
  'messaging/registry.ts', // checks stored credentials for connected providers
188
+ 'calls/twilio-config.ts', // call infrastructure credential lookup
189
+ 'calls/twilio-provider.ts', // call infrastructure credential lookup
190
+ 'runtime/http-server.ts', // HTTP server credential lookup
191
+ 'daemon/handlers/twitter-auth.ts', // Twitter OAuth token storage
188
192
  ]);
189
193
 
190
194
  const thisDir = dirname(fileURLToPath(import.meta.url));