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
@@ -5,18 +5,18 @@
5
5
  * data. Provides upload, delete, and message-linkage operations.
6
6
  */
7
7
 
8
- import { eq, and } from 'drizzle-orm';
8
+ import { eq } from 'drizzle-orm';
9
9
  import { v4 as uuid } from 'uuid';
10
10
  import { getDb } from './db.js';
11
11
  import { attachments, messageAttachments } from './schema.js';
12
12
 
13
13
  export interface StoredAttachment {
14
14
  id: string;
15
- assistantId: string;
16
15
  originalFilename: string;
17
16
  mimeType: string;
18
17
  sizeBytes: number;
19
18
  kind: string;
19
+ thumbnailBase64: string | null;
20
20
  createdAt: number;
21
21
  }
22
22
 
@@ -152,7 +152,6 @@ function computeContentHash(dataBase64: string): string {
152
152
  }
153
153
 
154
154
  export function uploadAttachment(
155
- assistantId: string,
156
155
  filename: string,
157
156
  mimeType: string,
158
157
  dataBase64: string,
@@ -173,25 +172,20 @@ export function uploadAttachment(
173
172
  const db = getDb();
174
173
  const contentHash = computeContentHash(dataBase64);
175
174
 
176
- // Dedup: if an attachment with the same content already exists for this
177
- // assistant, return it instead of storing a duplicate.
175
+ // Dedup: if an attachment with the same content already exists, return it
176
+ // instead of storing a duplicate.
178
177
  const existing = db
179
178
  .select({
180
179
  id: attachments.id,
181
- assistantId: attachments.assistantId,
182
180
  originalFilename: attachments.originalFilename,
183
181
  mimeType: attachments.mimeType,
184
182
  sizeBytes: attachments.sizeBytes,
185
183
  kind: attachments.kind,
184
+ thumbnailBase64: attachments.thumbnailBase64,
186
185
  createdAt: attachments.createdAt,
187
186
  })
188
187
  .from(attachments)
189
- .where(
190
- and(
191
- eq(attachments.assistantId, assistantId),
192
- eq(attachments.contentHash, contentHash),
193
- ),
194
- )
188
+ .where(eq(attachments.contentHash, contentHash))
195
189
  .get();
196
190
 
197
191
  if (existing) {
@@ -203,7 +197,7 @@ export function uploadAttachment(
203
197
 
204
198
  const record = {
205
199
  id: uuid(),
206
- assistantId,
200
+ assistantId: 'self',
207
201
  originalFilename: filename,
208
202
  mimeType,
209
203
  sizeBytes,
@@ -217,28 +211,34 @@ export function uploadAttachment(
217
211
 
218
212
  return {
219
213
  id: record.id,
220
- assistantId,
221
214
  originalFilename: filename,
222
215
  mimeType,
223
216
  sizeBytes,
224
217
  kind,
218
+ thumbnailBase64: null,
225
219
  createdAt: now,
226
220
  };
227
221
  }
228
222
 
223
+ /**
224
+ * Update the thumbnail for an existing attachment.
225
+ */
226
+ export function setAttachmentThumbnail(attachmentId: string, thumbnailBase64: string): void {
227
+ const db = getDb();
228
+ db.update(attachments)
229
+ .set({ thumbnailBase64 })
230
+ .where(eq(attachments.id, attachmentId))
231
+ .run();
232
+ }
233
+
229
234
  export type DeleteAttachmentResult = 'deleted' | 'not_found' | 'still_referenced';
230
235
 
231
- export function deleteAttachment(assistantId: string, attachmentId: string): DeleteAttachmentResult {
236
+ export function deleteAttachment(attachmentId: string): DeleteAttachmentResult {
232
237
  const db = getDb();
233
238
  const existing = db
234
239
  .select({ id: attachments.id })
235
240
  .from(attachments)
236
- .where(
237
- and(
238
- eq(attachments.id, attachmentId),
239
- eq(attachments.assistantId, assistantId),
240
- ),
241
- )
241
+ .where(eq(attachments.id, attachmentId))
242
242
  .get();
243
243
 
244
244
  if (!existing) return 'not_found';
@@ -263,7 +263,6 @@ export function deleteAttachment(assistantId: string, attachmentId: string): Del
263
263
  }
264
264
 
265
265
  export function getAttachmentsByIds(
266
- assistantId: string,
267
266
  ids: string[],
268
267
  ): Array<StoredAttachment & { dataBase64: string }> {
269
268
  if (ids.length === 0) return [];
@@ -273,21 +272,16 @@ export function getAttachmentsByIds(
273
272
  const row = db
274
273
  .select()
275
274
  .from(attachments)
276
- .where(
277
- and(
278
- eq(attachments.id, id),
279
- eq(attachments.assistantId, assistantId),
280
- ),
281
- )
275
+ .where(eq(attachments.id, id))
282
276
  .get();
283
277
  if (row) {
284
278
  results.push({
285
279
  id: row.id,
286
- assistantId: row.assistantId,
287
280
  originalFilename: row.originalFilename,
288
281
  mimeType: row.mimeType,
289
282
  sizeBytes: row.sizeBytes,
290
283
  kind: row.kind,
284
+ thumbnailBase64: row.thumbnailBase64,
291
285
  dataBase64: row.dataBase64,
292
286
  createdAt: row.createdAt,
293
287
  });
@@ -318,7 +312,6 @@ export function linkAttachmentToMessage(
318
312
  */
319
313
  export function getAttachmentsForMessage(
320
314
  messageId: string,
321
- assistantId: string,
322
315
  ): Array<StoredAttachment & { dataBase64: string }> {
323
316
  const db = getDb();
324
317
  const links = db
@@ -331,7 +324,7 @@ export function getAttachmentsForMessage(
331
324
  if (links.length === 0) return [];
332
325
 
333
326
  const ids = links.map((l) => l.attachmentId).filter((id): id is string => id !== null);
334
- return getAttachmentsByIds(assistantId, ids);
327
+ return getAttachmentsByIds(ids);
335
328
  }
336
329
 
337
330
  /**
@@ -342,7 +335,6 @@ export function getAttachmentsForMessage(
342
335
  */
343
336
  export function getAttachmentMetadataForMessage(
344
337
  messageId: string,
345
- assistantId: string,
346
338
  ): StoredAttachment[] {
347
339
  const db = getDb();
348
340
  const links = db
@@ -360,75 +352,28 @@ export function getAttachmentMetadataForMessage(
360
352
  const row = db
361
353
  .select({
362
354
  id: attachments.id,
363
- assistantId: attachments.assistantId,
364
355
  originalFilename: attachments.originalFilename,
365
356
  mimeType: attachments.mimeType,
366
357
  sizeBytes: attachments.sizeBytes,
367
358
  kind: attachments.kind,
359
+ thumbnailBase64: attachments.thumbnailBase64,
368
360
  createdAt: attachments.createdAt,
369
361
  })
370
362
  .from(attachments)
371
- .where(
372
- and(
373
- eq(attachments.id, link.attachmentId),
374
- eq(attachments.assistantId, assistantId),
375
- ),
376
- )
377
- .get();
378
- if (row) results.push(row);
379
- }
380
- return results;
381
- }
382
-
383
- /**
384
- * Return all attachments linked to a message without assistant scoping.
385
- * Used by the desktop IPC history handler where tenant isolation is not needed.
386
- */
387
- export function getAttachmentsForMessageUnscoped(
388
- messageId: string,
389
- ): Array<StoredAttachment & { dataBase64: string }> {
390
- const db = getDb();
391
- const links = db
392
- .select({ attachmentId: messageAttachments.attachmentId, position: messageAttachments.position })
393
- .from(messageAttachments)
394
- .where(eq(messageAttachments.messageId, messageId))
395
- .orderBy(messageAttachments.position)
396
- .all();
397
-
398
- if (links.length === 0) return [];
399
-
400
- const results: Array<StoredAttachment & { dataBase64: string }> = [];
401
- for (const link of links) {
402
- if (!link.attachmentId) continue;
403
- const row = db
404
- .select()
405
- .from(attachments)
406
363
  .where(eq(attachments.id, link.attachmentId))
407
364
  .get();
408
- if (row) {
409
- results.push({
410
- id: row.id,
411
- assistantId: row.assistantId,
412
- originalFilename: row.originalFilename,
413
- mimeType: row.mimeType,
414
- sizeBytes: row.sizeBytes,
415
- kind: row.kind,
416
- dataBase64: row.dataBase64,
417
- createdAt: row.createdAt,
418
- });
419
- }
365
+ if (row) results.push(row);
420
366
  }
421
367
  return results;
422
368
  }
423
369
 
424
370
  /**
425
- * Retrieve a single attachment by ID, scoped to an assistant.
371
+ * Retrieve a single attachment by ID.
426
372
  */
427
373
  export function getAttachmentById(
428
- assistantId: string,
429
374
  attachmentId: string,
430
375
  ): (StoredAttachment & { dataBase64: string }) | null {
431
- const results = getAttachmentsByIds(assistantId, [attachmentId]);
376
+ const results = getAttachmentsByIds([attachmentId]);
432
377
  return results[0] ?? null;
433
378
  }
434
379
 
@@ -35,10 +35,9 @@ export interface RecordInboundOptions {
35
35
 
36
36
  /**
37
37
  * Record an inbound channel event. Returns `duplicate: true` if this
38
- * exact (assistant, channel, chat, message) combination was already seen.
38
+ * exact (channel, chat, message) combination was already seen.
39
39
  */
40
40
  export function recordInbound(
41
- assistantId: string,
42
41
  sourceChannel: string,
43
42
  externalChatId: string,
44
43
  externalMessageId: string,
@@ -54,7 +53,6 @@ export function recordInbound(
54
53
  .from(channelInboundEvents)
55
54
  .where(
56
55
  and(
57
- eq(channelInboundEvents.assistantId, assistantId),
58
56
  eq(channelInboundEvents.sourceChannel, sourceChannel),
59
57
  eq(channelInboundEvents.externalChatId, externalChatId),
60
58
  eq(channelInboundEvents.externalMessageId, externalMessageId),
@@ -72,7 +70,7 @@ export function recordInbound(
72
70
  }
73
71
 
74
72
  const conversationKey = `${sourceChannel}:${externalChatId}`;
75
- const mapping = getOrCreateConversation(assistantId, conversationKey);
73
+ const mapping = getOrCreateConversation(conversationKey);
76
74
  const now = Date.now();
77
75
  const eventId = uuid();
78
76
 
@@ -84,7 +82,7 @@ export function recordInbound(
84
82
  tx.insert(channelInboundEvents)
85
83
  .values({
86
84
  id: eventId,
87
- assistantId,
85
+ assistantId: 'self',
88
86
  sourceChannel,
89
87
  externalChatId,
90
88
  externalMessageId,
@@ -122,7 +120,6 @@ export function linkMessage(eventId: string, messageId: string): void {
122
120
  * platform-level message identifier (e.g. Telegram message_id).
123
121
  */
124
122
  export function findMessageBySourceId(
125
- assistantId: string,
126
123
  sourceChannel: string,
127
124
  externalChatId: string,
128
125
  sourceMessageId: string,
@@ -136,7 +133,6 @@ export function findMessageBySourceId(
136
133
  .from(channelInboundEvents)
137
134
  .where(
138
135
  and(
139
- eq(channelInboundEvents.assistantId, assistantId),
140
136
  eq(channelInboundEvents.sourceChannel, sourceChannel),
141
137
  eq(channelInboundEvents.externalChatId, externalChatId),
142
138
  eq(channelInboundEvents.sourceMessageId, sourceMessageId),
@@ -153,7 +149,6 @@ export function findMessageBySourceId(
153
149
  * Acknowledge delivery of an outbound message for a channel event.
154
150
  */
155
151
  export function acknowledgeDelivery(
156
- assistantId: string,
157
152
  sourceChannel: string,
158
153
  externalChatId: string,
159
154
  externalMessageId: string,
@@ -166,7 +161,6 @@ export function acknowledgeDelivery(
166
161
  .from(channelInboundEvents)
167
162
  .where(
168
163
  and(
169
- eq(channelInboundEvents.assistantId, assistantId),
170
164
  eq(channelInboundEvents.sourceChannel, sourceChannel),
171
165
  eq(channelInboundEvents.externalChatId, externalChatId),
172
166
  eq(channelInboundEvents.externalMessageId, externalMessageId),
@@ -271,7 +265,6 @@ export function recordProcessingFailure(eventId: string, err: unknown): void {
271
265
  /** Fetch events eligible for automatic retry (failed + past their backoff). */
272
266
  export function getRetryableEvents(limit = 20): Array<{
273
267
  id: string;
274
- assistantId: string;
275
268
  conversationId: string;
276
269
  processingAttempts: number;
277
270
  rawPayload: string | null;
@@ -281,7 +274,6 @@ export function getRetryableEvents(limit = 20): Array<{
281
274
  return db
282
275
  .select({
283
276
  id: channelInboundEvents.id,
284
- assistantId: channelInboundEvents.assistantId,
285
277
  conversationId: channelInboundEvents.conversationId,
286
278
  processingAttempts: channelInboundEvents.processingAttempts,
287
279
  rawPayload: channelInboundEvents.rawPayload,
@@ -297,8 +289,8 @@ export function getRetryableEvents(limit = 20): Array<{
297
289
  .all();
298
290
  }
299
291
 
300
- /** Fetch dead-lettered events for an assistant. */
301
- export function getDeadLetterEvents(assistantId: string): Array<{
292
+ /** Fetch dead-lettered events. */
293
+ export function getDeadLetterEvents(): Array<{
302
294
  id: string;
303
295
  sourceChannel: string;
304
296
  externalChatId: string;
@@ -321,12 +313,7 @@ export function getDeadLetterEvents(assistantId: string): Array<{
321
313
  createdAt: channelInboundEvents.createdAt,
322
314
  })
323
315
  .from(channelInboundEvents)
324
- .where(
325
- and(
326
- eq(channelInboundEvents.assistantId, assistantId),
327
- eq(channelInboundEvents.processingStatus, 'dead_letter'),
328
- ),
329
- )
316
+ .where(eq(channelInboundEvents.processingStatus, 'dead_letter'))
330
317
  .all();
331
318
  }
332
319
 
@@ -334,7 +321,7 @@ export function getDeadLetterEvents(assistantId: string): Array<{
334
321
  * Reset dead-lettered events back to 'failed' so the sweep can retry
335
322
  * them. Resets attempt counter and sets an immediate retry_after.
336
323
  */
337
- export function replayDeadLetters(assistantId: string, eventIds: string[]): number {
324
+ export function replayDeadLetters(eventIds: string[]): number {
338
325
  const db = getDb();
339
326
  const now = Date.now();
340
327
  let count = 0;
@@ -345,7 +332,6 @@ export function replayDeadLetters(assistantId: string, eventIds: string[]): numb
345
332
  .where(
346
333
  and(
347
334
  eq(channelInboundEvents.id, id),
348
- eq(channelInboundEvents.assistantId, assistantId),
349
335
  eq(channelInboundEvents.processingStatus, 'dead_letter'),
350
336
  ),
351
337
  )
@@ -1,5 +1,6 @@
1
1
  import Anthropic from '@anthropic-ai/sdk';
2
2
  import { getConfig } from '../config/loader.js';
3
+ import { truncate } from '../util/truncate.js';
3
4
 
4
5
  const DEFAULT_RESOLVER_MODEL = 'claude-haiku-4-5-20251001';
5
6
  const DEFAULT_RESOLVER_TIMEOUT_MS = 12_000;
@@ -85,7 +86,7 @@ export async function resolveConflictClarification(
85
86
  resolution: 'still_unclear',
86
87
  strategy: 'llm_error',
87
88
  resolvedStatement: null,
88
- explanation: `Clarification resolver failed: ${message.slice(0, 300)}`,
89
+ explanation: `Clarification resolver failed: ${truncate(message, 300, '')}`,
89
90
  };
90
91
  }
91
92
  }
@@ -243,7 +244,7 @@ async function resolveWithLlm(
243
244
  resolution: parsed.resolution,
244
245
  strategy: 'llm',
245
246
  resolvedStatement,
246
- explanation: normalize(parsed.explanation ?? 'Resolved via LLM fallback.').slice(0, 500),
247
+ explanation: truncate(normalize(parsed.explanation ?? 'Resolved via LLM fallback.'), 500, ''),
247
248
  };
248
249
  } catch (err) {
249
250
  clearTimeout(timer);
@@ -291,7 +292,7 @@ function buildMergedStatement(input: ClarificationResolverInput): string {
291
292
  if (normalizedUserMessage.length >= 8 && normalizedUserMessage.length <= 320) {
292
293
  return normalizedUserMessage;
293
294
  }
294
- const existing = normalize(input.existingStatement).slice(0, 140);
295
- const candidate = normalize(input.candidateStatement).slice(0, 140);
296
- return `Merged clarification: ${existing}; ${candidate}`.slice(0, 320);
295
+ const existing = truncate(normalize(input.existingStatement), 140, '');
296
+ const candidate = truncate(normalize(input.candidateStatement), 140, '');
297
+ return truncate(`Merged clarification: ${existing}; ${candidate}`, 320, '');
297
298
  }
@@ -2,6 +2,7 @@ import Anthropic from '@anthropic-ai/sdk';
2
2
  import { eq } from 'drizzle-orm';
3
3
  import { getConfig } from '../config/loader.js';
4
4
  import { getLogger } from '../util/logger.js';
5
+ import { truncate } from '../util/truncate.js';
5
6
  import { createOrUpdatePendingConflict } from './conflict-store.js';
6
7
  import { getDb } from './db.js';
7
8
  import { enqueueMemoryJob } from './jobs-store.js';
@@ -234,7 +235,7 @@ async function classifyRelationship(
234
235
 
235
236
  return {
236
237
  relationship,
237
- explanation: String(input.explanation ?? '').slice(0, 500),
238
+ explanation: truncate(String(input.explanation ?? ''), 500, ''),
238
239
  };
239
240
  }
240
241
 
@@ -324,6 +325,6 @@ function escapeSqlLike(s: string): string {
324
325
 
325
326
  function buildClarificationQuestion(existingStatement: string, candidateStatement: string): string {
326
327
  const normalize = (input: string): string =>
327
- input.replace(/\s+/g, ' ').trim().slice(0, 180);
328
+ truncate(input.replace(/\s+/g, ' ').trim(), 180, '');
328
329
  return `I have conflicting notes: "${normalize(existingStatement)}" vs "${normalize(candidateStatement)}". Which one is correct?`;
329
330
  }
@@ -1,49 +1,42 @@
1
1
  /**
2
- * Maps (assistant_id, conversation_key) pairs to internal conversation IDs.
2
+ * Maps conversation keys to internal conversation IDs.
3
3
  *
4
4
  * The web UI identifies conversations by an opaque `conversationKey` (e.g.
5
5
  * a user ID, a channel thread ID). This store resolves those keys to the
6
- * assistant's internal conversation IDs, creating new conversations on
6
+ * daemon's internal conversation IDs, creating new conversations on
7
7
  * first contact.
8
8
  */
9
9
 
10
- import { eq, and } from 'drizzle-orm';
10
+ import { eq } from 'drizzle-orm';
11
11
  import { v4 as uuid } from 'uuid';
12
12
  import { getDb } from './db.js';
13
13
  import { conversations, conversationKeys } from './schema.js';
14
14
 
15
15
  export interface ConversationKeyMapping {
16
16
  id: string;
17
- assistantId: string;
18
17
  conversationKey: string;
19
18
  conversationId: string;
20
19
  createdAt: number;
21
20
  }
22
21
 
23
22
  /**
24
- * Look up the conversation ID for a given (assistantId, conversationKey).
23
+ * Look up the conversation ID for a given conversationKey.
25
24
  * Returns `null` if no mapping exists yet.
26
25
  */
27
26
  export function getConversationByKey(
28
- assistantId: string,
29
27
  conversationKey: string,
30
28
  ): ConversationKeyMapping | null {
31
29
  const db = getDb();
32
30
  const result = db
33
31
  .select()
34
32
  .from(conversationKeys)
35
- .where(
36
- and(
37
- eq(conversationKeys.assistantId, assistantId),
38
- eq(conversationKeys.conversationKey, conversationKey),
39
- ),
40
- )
33
+ .where(eq(conversationKeys.conversationKey, conversationKey))
41
34
  .get();
42
35
  return result ?? null;
43
36
  }
44
37
 
45
38
  /**
46
- * Delete the conversation-key mapping for a given (assistantId, conversationKey).
39
+ * Delete the conversation-key mapping for a given conversationKey.
47
40
  *
48
41
  * This is a soft reset: the old conversation data remains in the database,
49
42
  * but it is no longer reachable via this key. The next message with the
@@ -51,29 +44,22 @@ export function getConversationByKey(
51
44
  *
52
45
  */
53
46
  export function deleteConversationKey(
54
- assistantId: string,
55
47
  conversationKey: string,
56
48
  ): void {
57
49
  const db = getDb();
58
50
  db.delete(conversationKeys)
59
- .where(
60
- and(
61
- eq(conversationKeys.assistantId, assistantId),
62
- eq(conversationKeys.conversationKey, conversationKey),
63
- ),
64
- )
51
+ .where(eq(conversationKeys.conversationKey, conversationKey))
65
52
  .run();
66
53
  }
67
54
 
68
55
  /**
69
- * Get or create a conversation for the given (assistantId, conversationKey).
56
+ * Get or create a conversation for the given conversationKey.
70
57
  *
71
58
  * If a mapping already exists, returns the existing conversation ID.
72
59
  * Otherwise, creates a new conversation and mapping atomically within a
73
60
  * single transaction to prevent race conditions and orphaned rows.
74
61
  */
75
62
  export function getOrCreateConversation(
76
- assistantId: string,
77
63
  conversationKey: string,
78
64
  ): { conversationId: string; created: boolean } {
79
65
  const db = getDb();
@@ -82,12 +68,7 @@ export function getOrCreateConversation(
82
68
  const existing = tx
83
69
  .select()
84
70
  .from(conversationKeys)
85
- .where(
86
- and(
87
- eq(conversationKeys.assistantId, assistantId),
88
- eq(conversationKeys.conversationKey, conversationKey),
89
- ),
90
- )
71
+ .where(eq(conversationKeys.conversationKey, conversationKey))
91
72
  .get();
92
73
 
93
74
  if (existing) {
@@ -115,7 +96,7 @@ export function getOrCreateConversation(
115
96
  tx.insert(conversationKeys)
116
97
  .values({
117
98
  id: uuid(),
118
- assistantId,
99
+ assistantId: 'self',
119
100
  conversationKey,
120
101
  conversationId,
121
102
  createdAt: now,
@@ -108,7 +108,7 @@ export function getLatestConversation() {
108
108
  return result ?? null;
109
109
  }
110
110
 
111
- export function addMessage(conversationId: string, role: string, content: string) {
111
+ export function addMessage(conversationId: string, role: string, content: string, metadata?: Record<string, unknown>) {
112
112
  const db = getDb();
113
113
  const now = monotonicNow();
114
114
  const message = {
@@ -117,6 +117,7 @@ export function addMessage(conversationId: string, role: string, content: string
117
117
  role,
118
118
  content,
119
119
  createdAt: now,
120
+ ...(metadata ? { metadata: JSON.stringify(metadata) } : {}),
120
121
  };
121
122
  // Wrap insert + updatedAt bump in a transaction so they're atomic.
122
123
  db.transaction((tx) => {