@vellumai/assistant 0.5.1 → 0.5.3

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 (405) hide show
  1. package/ARCHITECTURE.md +163 -54
  2. package/docs/architecture/integrations.md +62 -67
  3. package/docs/credential-execution-service.md +3 -3
  4. package/docs/skills.md +100 -0
  5. package/package.json +1 -1
  6. package/src/__tests__/agent-loop.test.ts +111 -0
  7. package/src/__tests__/always-loaded-tools-guard.test.ts +3 -4
  8. package/src/__tests__/app-builder-tool-scripts.test.ts +13 -151
  9. package/src/__tests__/app-dir-path-guard.test.ts +78 -0
  10. package/src/__tests__/app-executors.test.ts +1 -291
  11. package/src/__tests__/app-git-history.test.ts +4 -4
  12. package/src/__tests__/app-routes-csp.test.ts +1 -0
  13. package/src/__tests__/app-store-dir-names.test.ts +426 -0
  14. package/src/__tests__/attachments-store.test.ts +169 -21
  15. package/src/__tests__/attachments.test.ts +115 -1
  16. package/src/__tests__/btw-routes.test.ts +1 -0
  17. package/src/__tests__/canonical-guardian-store.test.ts +38 -0
  18. package/src/__tests__/channel-reply-delivery.test.ts +55 -0
  19. package/src/__tests__/checker.test.ts +54 -0
  20. package/src/__tests__/claude-code-skill-regression.test.ts +2 -0
  21. package/src/__tests__/claude-code-tool-profiles.test.ts +2 -0
  22. package/src/__tests__/compaction.benchmark.test.ts +2 -1
  23. package/src/__tests__/config-schema-cmd.test.ts +68 -21
  24. package/src/__tests__/config-schema.test.ts +1 -1
  25. package/src/__tests__/conversation-agent-loop-overflow.test.ts +156 -5
  26. package/src/__tests__/conversation-agent-loop.test.ts +297 -2
  27. package/src/__tests__/conversation-attachments.test.ts +17 -19
  28. package/src/__tests__/conversation-disk-view-integration.test.ts +277 -0
  29. package/src/__tests__/conversation-disk-view.test.ts +810 -0
  30. package/src/__tests__/conversation-error.test.ts +1 -1
  31. package/src/__tests__/conversation-fork-crud.test.ts +551 -0
  32. package/src/__tests__/conversation-fork-route.test.ts +386 -0
  33. package/src/__tests__/conversation-history-web-search.test.ts +1 -1
  34. package/src/__tests__/conversation-key-store-disk-view.test.ts +130 -0
  35. package/src/__tests__/conversation-media-retry.test.ts +8 -2
  36. package/src/__tests__/conversation-memory-dirty-tail.test.ts +150 -0
  37. package/src/__tests__/conversation-provider-retry-repair.test.ts +7 -0
  38. package/src/__tests__/conversation-queue.test.ts +36 -1
  39. package/src/__tests__/conversation-routes-disk-view.test.ts +439 -0
  40. package/src/__tests__/conversation-routes-guardian-reply.test.ts +2 -2
  41. package/src/__tests__/conversation-routes-slash-commands.test.ts +2 -7
  42. package/src/__tests__/conversation-runtime-assembly.test.ts +17 -2
  43. package/src/__tests__/conversation-skill-tools.test.ts +4 -9
  44. package/src/__tests__/conversation-slash-commands.test.ts +149 -0
  45. package/src/__tests__/conversation-store.test.ts +24 -21
  46. package/src/__tests__/conversation-surfaces-state-update.test.ts +246 -0
  47. package/src/__tests__/conversation-surfaces-task-progress.test.ts +1 -0
  48. package/src/__tests__/conversation-title-service.test.ts +137 -0
  49. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +25 -315
  50. package/src/__tests__/conversation-tool-setup-memory-scope.test.ts +1 -0
  51. package/src/__tests__/conversation-tool-setup-side-effect-flag.test.ts +1 -0
  52. package/src/__tests__/conversation-wipe.test.ts +226 -0
  53. package/src/__tests__/conversation-workspace-cache-state.test.ts +44 -2
  54. package/src/__tests__/conversation-workspace-injection.test.ts +11 -0
  55. package/src/__tests__/credential-security-invariants.test.ts +3 -0
  56. package/src/__tests__/credential-vault-unit.test.ts +5 -10
  57. package/src/__tests__/cu-unified-flow.test.ts +1 -0
  58. package/src/__tests__/db-conversation-fork-lineage-migration.test.ts +241 -0
  59. package/src/__tests__/db-llm-request-log-provider-migration.test.ts +214 -0
  60. package/src/__tests__/db-memory-archive-migration.test.ts +372 -0
  61. package/src/__tests__/db-memory-brief-state-migration.test.ts +213 -0
  62. package/src/__tests__/db-memory-reducer-checkpoints.test.ts +273 -0
  63. package/src/__tests__/diagnostics-export.test.ts +70 -1
  64. package/src/__tests__/first-greeting.test.ts +80 -0
  65. package/src/__tests__/gateway-only-guard.test.ts +1 -0
  66. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +3 -7
  67. package/src/__tests__/history-repair.test.ts +32 -10
  68. package/src/__tests__/http-conversation-lineage.test.ts +251 -0
  69. package/src/__tests__/image-source-path-reinject.test.ts +136 -0
  70. package/src/__tests__/inline-command-runner.test.ts +311 -0
  71. package/src/__tests__/inline-skill-authoring-guard.test.ts +220 -0
  72. package/src/__tests__/inline-skill-load-permissions.test.ts +435 -0
  73. package/src/__tests__/list-messages-attachments.test.ts +96 -0
  74. package/src/__tests__/llm-context-normalization.test.ts +1116 -0
  75. package/src/__tests__/llm-context-route-provider.test.ts +217 -0
  76. package/src/__tests__/llm-request-log-turn-query.test.ts +270 -0
  77. package/src/__tests__/media-generate-image.test.ts +47 -94
  78. package/src/__tests__/memory-brief-open-loops.test.ts +530 -0
  79. package/src/__tests__/memory-brief-time.test.ts +285 -0
  80. package/src/__tests__/memory-brief-wrapper.test.ts +311 -0
  81. package/src/__tests__/memory-chunk-archive.test.ts +400 -0
  82. package/src/__tests__/memory-chunk-dual-write.test.ts +453 -0
  83. package/src/__tests__/memory-episode-archive.test.ts +370 -0
  84. package/src/__tests__/memory-episode-dual-write.test.ts +626 -0
  85. package/src/__tests__/memory-lifecycle-e2e.test.ts +3 -1
  86. package/src/__tests__/memory-observation-archive.test.ts +375 -0
  87. package/src/__tests__/memory-observation-dual-write.test.ts +318 -0
  88. package/src/__tests__/memory-recall-quality.test.ts +7 -7
  89. package/src/__tests__/memory-reducer-store.test.ts +728 -0
  90. package/src/__tests__/memory-reducer-types.test.ts +699 -0
  91. package/src/__tests__/memory-reducer.test.ts +698 -0
  92. package/src/__tests__/memory-regressions.test.ts +6 -4
  93. package/src/__tests__/memory-simplified-config.test.ts +281 -0
  94. package/src/__tests__/migration-cross-version-compatibility.test.ts +4 -1
  95. package/src/__tests__/migration-export-http.test.ts +3 -1
  96. package/src/__tests__/migration-import-commit-http.test.ts +18 -4
  97. package/src/__tests__/migration-import-preflight-http.test.ts +1 -3
  98. package/src/__tests__/mime-builder.test.ts +3 -2
  99. package/src/__tests__/non-member-access-request.test.ts +12 -1
  100. package/src/__tests__/notification-decision-identity.test.ts +52 -0
  101. package/src/__tests__/oauth-apps-routes.test.ts +103 -0
  102. package/src/__tests__/oauth-store.test.ts +115 -0
  103. package/src/__tests__/parse-identity-fields.test.ts +129 -0
  104. package/src/__tests__/provider-error-scenarios.test.ts +1 -3
  105. package/src/__tests__/provider-failover-actual-provider.test.ts +66 -0
  106. package/src/__tests__/recording-handler.test.ts +17 -0
  107. package/src/__tests__/registry.test.ts +3 -8
  108. package/src/__tests__/relay-server.test.ts +1 -1
  109. package/src/__tests__/runtime-attachment-metadata.test.ts +7 -3
  110. package/src/__tests__/schema-transforms.test.ts +165 -5
  111. package/src/__tests__/server-history-render.test.ts +2 -2
  112. package/src/__tests__/skill-load-inline-command.test.ts +598 -0
  113. package/src/__tests__/skill-load-inline-includes.test.ts +644 -0
  114. package/src/__tests__/skills-inline-command-expansions.test.ts +301 -0
  115. package/src/__tests__/skills-transitive-hash.test.ts +333 -0
  116. package/src/__tests__/slack-app-setup-skill-regression.test.ts +3 -1
  117. package/src/__tests__/slack-inbound-verification.test.ts +2 -2
  118. package/src/__tests__/starter-task-flow.test.ts +1 -0
  119. package/src/__tests__/suggestion-routes.test.ts +443 -0
  120. package/src/__tests__/swarm-conversation-integration.test.ts +1 -0
  121. package/src/__tests__/swarm-recursion.test.ts +1 -0
  122. package/src/__tests__/swarm-tool.test.ts +1 -0
  123. package/src/__tests__/tool-execution-abort-cleanup.test.ts +1 -0
  124. package/src/__tests__/tool-preview-lifecycle.test.ts +32 -5
  125. package/src/__tests__/top-level-renderer.test.ts +22 -0
  126. package/src/__tests__/turn-boundary-resolution.test.ts +243 -0
  127. package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +320 -0
  128. package/src/__tests__/web-fetch.test.ts +6 -2
  129. package/src/__tests__/workspace-migration-006-services-config.test.ts +335 -0
  130. package/src/__tests__/workspace-migration-007-web-search-provider-rename.test.ts +312 -0
  131. package/src/__tests__/workspace-migration-009-backfill-conversation-disk-view.test.ts +278 -0
  132. package/src/__tests__/workspace-migration-010-app-dir-rename.test.ts +275 -0
  133. package/src/__tests__/workspace-migration-012-rename-conversation-disk-view-dirs.test.ts +77 -0
  134. package/src/__tests__/workspace-migration-013-repair-conversation-disk-view.test.ts +401 -0
  135. package/src/__tests__/workspace-migration-backfill-installation-id.test.ts +328 -0
  136. package/src/__tests__/workspace-migration-seed-device-id.test.ts +6 -10
  137. package/src/agent/attachments.ts +27 -1
  138. package/src/agent/loop.ts +29 -1
  139. package/src/avatar/traits-png-sync.ts +80 -25
  140. package/src/bundler/app-bundler.ts +4 -4
  141. package/src/calls/call-domain.ts +1 -0
  142. package/src/calls/voice-session-bridge.ts +1 -0
  143. package/src/cli/commands/auth.ts +92 -0
  144. package/src/cli/commands/avatar.ts +7 -6
  145. package/src/cli/commands/config.ts +2 -0
  146. package/src/cli/commands/oauth/providers.ts +29 -0
  147. package/src/cli/program.ts +12 -0
  148. package/src/cli.ts +15 -48
  149. package/src/config/bundled-skills/app-builder/SKILL.md +103 -28
  150. package/src/config/bundled-skills/app-builder/TOOLS.json +5 -199
  151. package/src/config/bundled-skills/app-builder/tools/{app-query.ts → app-refresh.ts} +2 -2
  152. package/src/config/bundled-skills/contacts/tools/google-contacts.ts +2 -3
  153. package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +6 -9
  154. package/src/config/bundled-skills/gmail/tools/gmail-attachments.ts +4 -6
  155. package/src/config/bundled-skills/gmail/tools/gmail-draft.ts +2 -3
  156. package/src/config/bundled-skills/gmail/tools/gmail-filters.ts +2 -3
  157. package/src/config/bundled-skills/gmail/tools/gmail-follow-up.ts +2 -3
  158. package/src/config/bundled-skills/gmail/tools/gmail-forward.ts +2 -3
  159. package/src/config/bundled-skills/gmail/tools/gmail-label.ts +4 -6
  160. package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +2 -3
  161. package/src/config/bundled-skills/gmail/tools/gmail-send-draft.ts +2 -3
  162. package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +2 -3
  163. package/src/config/bundled-skills/gmail/tools/gmail-trash.ts +2 -3
  164. package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +2 -3
  165. package/src/config/bundled-skills/gmail/tools/gmail-vacation.ts +2 -3
  166. package/src/config/bundled-skills/google-calendar/tools/shared.ts +1 -1
  167. package/src/config/bundled-skills/image-studio/SKILL.md +2 -2
  168. package/src/config/bundled-skills/image-studio/TOOLS.json +2 -2
  169. package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +45 -72
  170. package/src/config/bundled-skills/media-processing/tools/extract-keyframes.ts +2 -2
  171. package/src/config/bundled-skills/messaging/tools/shared.ts +1 -1
  172. package/src/config/bundled-skills/settings/tools/voice-config-update.ts +19 -3
  173. package/src/config/bundled-skills/skill-management/SKILL.md +1 -1
  174. package/src/config/bundled-skills/skill-management/TOOLS.json +2 -2
  175. package/src/config/bundled-skills/slack/tools/shared.ts +19 -4
  176. package/src/config/bundled-skills/slack/tools/slack-scan-digest.ts +2 -3
  177. package/src/config/bundled-skills/transcribe/SKILL.md +1 -1
  178. package/src/config/bundled-skills/transcribe/TOOLS.json +2 -6
  179. package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +19 -83
  180. package/src/config/bundled-tool-registry.ts +2 -14
  181. package/src/config/feature-flag-registry.json +24 -0
  182. package/src/config/loader.ts +65 -0
  183. package/src/config/raw-config-utils.ts +58 -0
  184. package/src/config/schema-utils.ts +28 -7
  185. package/src/config/schema.ts +20 -0
  186. package/src/config/schemas/elevenlabs.ts +18 -0
  187. package/src/config/schemas/memory-lifecycle.ts +4 -2
  188. package/src/config/schemas/memory-simplified.ts +101 -0
  189. package/src/config/schemas/memory-storage.ts +1 -1
  190. package/src/config/schemas/memory.ts +4 -0
  191. package/src/config/schemas/services.ts +8 -6
  192. package/src/config/skills.ts +50 -4
  193. package/src/contacts/contact-store.ts +13 -6
  194. package/src/contacts/contacts-write.ts +0 -1
  195. package/src/context/window-manager.ts +13 -2
  196. package/src/daemon/conversation-agent-loop-handlers.ts +54 -8
  197. package/src/daemon/conversation-agent-loop.ts +127 -20
  198. package/src/daemon/conversation-attachments.ts +18 -36
  199. package/src/daemon/conversation-error.ts +2 -1
  200. package/src/daemon/conversation-history.ts +18 -4
  201. package/src/daemon/conversation-lifecycle.ts +50 -16
  202. package/src/daemon/conversation-messaging.ts +70 -26
  203. package/src/daemon/conversation-process.ts +58 -34
  204. package/src/daemon/conversation-runtime-assembly.ts +22 -38
  205. package/src/daemon/conversation-slash.ts +121 -256
  206. package/src/daemon/conversation-surfaces.ts +170 -24
  207. package/src/daemon/conversation-tool-setup.ts +0 -6
  208. package/src/daemon/conversation-workspace.ts +21 -1
  209. package/src/daemon/conversation.ts +69 -30
  210. package/src/daemon/first-greeting.ts +35 -0
  211. package/src/daemon/handlers/config-embeddings.ts +156 -0
  212. package/src/daemon/handlers/config-model.ts +62 -26
  213. package/src/daemon/handlers/conversations.ts +0 -23
  214. package/src/daemon/handlers/identity.ts +12 -1
  215. package/src/daemon/handlers/recording.ts +26 -21
  216. package/src/daemon/host-cu-proxy.ts +2 -2
  217. package/src/daemon/lifecycle.ts +115 -65
  218. package/src/daemon/message-protocol.ts +3 -0
  219. package/src/daemon/message-types/conversations.ts +18 -0
  220. package/src/daemon/message-types/messages.ts +1 -0
  221. package/src/daemon/message-types/shared.ts +2 -0
  222. package/src/daemon/message-types/surfaces.ts +2 -0
  223. package/src/daemon/message-types/upgrades.ts +23 -0
  224. package/src/daemon/server.ts +83 -12
  225. package/src/daemon/shutdown-handlers.ts +8 -5
  226. package/src/daemon/startup-error.ts +9 -0
  227. package/src/daemon/tool-side-effects.ts +11 -28
  228. package/src/events/tool-permission-telemetry-listener.ts +1 -3
  229. package/src/followups/followup-store.ts +47 -1
  230. package/src/instrument.ts +0 -4
  231. package/src/media/app-icon-generator.ts +2 -2
  232. package/src/memory/app-git-service.ts +28 -16
  233. package/src/memory/app-store.ts +230 -41
  234. package/src/memory/archive-store.ts +400 -0
  235. package/src/memory/attachments-store.ts +558 -130
  236. package/src/memory/brief-formatting.ts +33 -0
  237. package/src/memory/brief-open-loops.ts +266 -0
  238. package/src/memory/brief-time.ts +161 -0
  239. package/src/memory/brief.ts +75 -0
  240. package/src/memory/conversation-attention-store.ts +70 -0
  241. package/src/memory/conversation-crud.ts +591 -8
  242. package/src/memory/conversation-directories.ts +125 -0
  243. package/src/memory/conversation-disk-view.ts +390 -0
  244. package/src/memory/conversation-key-store.ts +17 -5
  245. package/src/memory/conversation-queries.ts +5 -1
  246. package/src/memory/conversation-title-service.ts +21 -49
  247. package/src/memory/db-init.ts +40 -0
  248. package/src/memory/embedding-backend.ts +42 -53
  249. package/src/memory/embedding-gemini.test.ts +4 -4
  250. package/src/memory/embedding-local.ts +1 -3
  251. package/src/memory/embedding-ollama.ts +1 -3
  252. package/src/memory/embedding-openai.ts +1 -3
  253. package/src/memory/indexer.ts +114 -21
  254. package/src/memory/items-extractor.ts +42 -13
  255. package/src/memory/job-handlers/conversation-starters.ts +6 -1
  256. package/src/memory/job-handlers/embedding.test.ts +2 -4
  257. package/src/memory/job-handlers/embedding.ts +83 -0
  258. package/src/memory/job-utils.ts +1 -1
  259. package/src/memory/jobs-store.ts +6 -0
  260. package/src/memory/jobs-worker.ts +12 -0
  261. package/src/memory/llm-request-log-store.ts +100 -1
  262. package/src/memory/migrations/102-alter-table-columns.ts +5 -0
  263. package/src/memory/migrations/146-schedule-oneshot-routing.ts +3 -3
  264. package/src/memory/migrations/147-migrate-reminders-to-schedules.ts +66 -70
  265. package/src/memory/migrations/148-drop-reminders-table.ts +5 -9
  266. package/src/memory/migrations/160-drop-loopback-port-column.ts +1 -3
  267. package/src/memory/migrations/174-rename-thread-starters-table.ts +0 -7
  268. package/src/memory/migrations/178-oauth-providers-managed-service-config-key.ts +15 -0
  269. package/src/memory/migrations/179-llm-request-log-message-id.ts +16 -0
  270. package/src/memory/migrations/180-backfill-inline-attachments-to-disk.ts +66 -0
  271. package/src/memory/migrations/181-rename-thread-starters-checkpoints.ts +46 -0
  272. package/src/memory/migrations/182-oauth-providers-display-metadata.ts +20 -0
  273. package/src/memory/migrations/183-add-conversation-fork-lineage.ts +22 -0
  274. package/src/memory/migrations/184-llm-request-log-provider.ts +12 -0
  275. package/src/memory/migrations/185-memory-brief-state.ts +52 -0
  276. package/src/memory/migrations/186-memory-archive.ts +109 -0
  277. package/src/memory/migrations/187-memory-reducer-checkpoints.ts +19 -0
  278. package/src/memory/migrations/index.ts +10 -0
  279. package/src/memory/migrations/registry.ts +13 -0
  280. package/src/memory/qdrant-client.ts +23 -4
  281. package/src/memory/reducer-store.ts +271 -0
  282. package/src/memory/reducer-types.ts +99 -0
  283. package/src/memory/reducer.ts +453 -0
  284. package/src/memory/retriever.test.ts +601 -2
  285. package/src/memory/retriever.ts +85 -9
  286. package/src/memory/schema/conversations.ts +9 -0
  287. package/src/memory/schema/index.ts +2 -0
  288. package/src/memory/schema/infrastructure.ts +13 -7
  289. package/src/memory/schema/memory-archive.ts +121 -0
  290. package/src/memory/schema/memory-brief.ts +55 -0
  291. package/src/memory/schema/oauth.ts +6 -0
  292. package/src/memory/search/semantic.ts +17 -4
  293. package/src/messaging/providers/gmail/mime-builder.ts +3 -1
  294. package/src/notifications/copy-composer.ts +26 -0
  295. package/src/notifications/decision-engine.ts +14 -1
  296. package/src/notifications/emit-signal.ts +1 -1
  297. package/src/notifications/signal.ts +36 -0
  298. package/src/oauth/byo-connection.test.ts +1 -45
  299. package/src/oauth/byo-connection.ts +2 -8
  300. package/src/oauth/connect-orchestrator.ts +15 -11
  301. package/src/oauth/connection-resolver.test.ts +191 -0
  302. package/src/oauth/connection-resolver.ts +66 -38
  303. package/src/oauth/connection.ts +0 -1
  304. package/src/oauth/oauth-store.ts +99 -47
  305. package/src/oauth/platform-connection.test.ts +0 -1
  306. package/src/oauth/platform-connection.ts +11 -3
  307. package/src/oauth/seed-providers.ts +78 -3
  308. package/src/oauth/token-persistence.ts +16 -10
  309. package/src/permissions/checker.ts +160 -14
  310. package/src/permissions/defaults.ts +14 -0
  311. package/src/prompts/templates/BOOTSTRAP.md +2 -0
  312. package/src/providers/anthropic/client.ts +8 -1
  313. package/src/providers/failover.ts +4 -1
  314. package/src/providers/gemini/client.ts +50 -0
  315. package/src/providers/model-catalog.ts +92 -0
  316. package/src/providers/model-intents.ts +29 -20
  317. package/src/providers/openai/client.ts +49 -0
  318. package/src/providers/types.ts +2 -0
  319. package/src/runtime/access-request-helper.ts +16 -7
  320. package/src/runtime/auth/credential-service.ts +3 -1
  321. package/src/runtime/auth/route-policy.ts +14 -1
  322. package/src/runtime/btw-sidechain.ts +101 -0
  323. package/src/runtime/channel-reply-delivery.ts +17 -1
  324. package/src/runtime/http-router.ts +3 -1
  325. package/src/runtime/http-server.ts +196 -141
  326. package/src/runtime/http-types.ts +1 -0
  327. package/src/runtime/migrations/vbundle-builder.ts +5 -1
  328. package/src/runtime/routes/access-request-decision.ts +41 -0
  329. package/src/runtime/routes/app-management-routes.ts +6 -3
  330. package/src/runtime/routes/app-routes.ts +7 -3
  331. package/src/runtime/routes/approval-routes.ts +1 -0
  332. package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +34 -2
  333. package/src/runtime/routes/attachment-routes.ts +45 -15
  334. package/src/runtime/routes/btw-routes.ts +21 -61
  335. package/src/runtime/routes/conversation-management-routes.ts +74 -0
  336. package/src/runtime/routes/conversation-query-routes.ts +187 -10
  337. package/src/runtime/routes/conversation-routes.ts +269 -28
  338. package/src/runtime/routes/conversation-starter-routes.ts +9 -11
  339. package/src/runtime/routes/diagnostics-routes.ts +1 -0
  340. package/src/runtime/routes/identity-routes.ts +2 -35
  341. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +2 -2
  342. package/src/runtime/routes/llm-context-normalization.ts +1212 -0
  343. package/src/runtime/routes/log-export-routes.ts +3 -0
  344. package/src/runtime/routes/memory-item-routes.test.ts +34 -0
  345. package/src/runtime/routes/memory-item-routes.ts +94 -5
  346. package/src/runtime/routes/migration-routes.ts +4 -1
  347. package/src/runtime/routes/oauth-apps.ts +291 -0
  348. package/src/runtime/routes/secret-routes.ts +30 -1
  349. package/src/runtime/routes/settings-routes.ts +14 -0
  350. package/src/runtime/routes/surface-action-routes.ts +68 -1
  351. package/src/runtime/routes/trace-event-routes.ts +4 -1
  352. package/src/schedule/schedule-store.ts +30 -21
  353. package/src/security/secure-keys.ts +21 -0
  354. package/src/signals/bash.ts +1 -1
  355. package/src/skills/inline-command-expansions.ts +204 -0
  356. package/src/skills/inline-command-render.ts +127 -0
  357. package/src/skills/inline-command-runner.ts +242 -0
  358. package/src/skills/transitive-version-hash.ts +88 -0
  359. package/src/swarm/backend-claude-code.ts +3 -6
  360. package/src/tasks/task-store.ts +43 -1
  361. package/src/telemetry/usage-telemetry-reporter.test.ts +3 -2
  362. package/src/telemetry/usage-telemetry-reporter.ts +3 -1
  363. package/src/tools/AGENTS.md +6 -10
  364. package/src/tools/apps/executors.ts +17 -232
  365. package/src/tools/claude-code/claude-code.ts +2 -3
  366. package/src/tools/credentials/vault.ts +7 -12
  367. package/src/tools/host-filesystem/read.ts +13 -10
  368. package/src/tools/network/__tests__/web-search.test.ts +4 -2
  369. package/src/tools/permission-checker.ts +8 -1
  370. package/src/tools/schedule/list.ts +2 -7
  371. package/src/tools/schema-transforms.ts +5 -0
  372. package/src/tools/shared/filesystem/format-diff.ts +2 -7
  373. package/src/tools/skills/execute.ts +1 -1
  374. package/src/tools/skills/load.ts +140 -6
  375. package/src/tools/tool-manifest.ts +0 -6
  376. package/src/tools/ui-surface/definitions.ts +2 -2
  377. package/src/util/device-id.ts +28 -5
  378. package/src/util/platform.ts +24 -0
  379. package/src/util/pricing.ts +1 -0
  380. package/src/util/retry.ts +1 -3
  381. package/src/workspace/migrations/003-seed-device-id.ts +3 -4
  382. package/src/workspace/migrations/006-services-config.ts +5 -0
  383. package/src/workspace/migrations/008-voice-timeout-and-max-steps.ts +12 -0
  384. package/src/workspace/migrations/009-backfill-conversation-disk-view.ts +10 -0
  385. package/src/workspace/migrations/010-app-dir-rename.ts +223 -0
  386. package/src/workspace/migrations/{002-backfill-installation-id.ts → 011-backfill-installation-id.ts} +24 -13
  387. package/src/workspace/migrations/012-rename-conversation-disk-view-dirs.ts +64 -0
  388. package/src/workspace/migrations/013-repair-conversation-disk-view.ts +11 -0
  389. package/src/workspace/migrations/rebuild-conversation-disk-view.ts +186 -0
  390. package/src/workspace/migrations/registry.ts +11 -1
  391. package/src/workspace/top-level-renderer.ts +12 -0
  392. package/src/__tests__/asset-materialize-tool.test.ts +0 -523
  393. package/src/__tests__/asset-search-tool.test.ts +0 -536
  394. package/src/__tests__/fixtures/media-reuse-fixtures.ts +0 -56
  395. package/src/__tests__/media-reuse-story.e2e.test.ts +0 -762
  396. package/src/__tests__/media-visibility-policy.test.ts +0 -190
  397. package/src/config/bundled-skills/app-builder/tools/app-file-edit.ts +0 -14
  398. package/src/config/bundled-skills/app-builder/tools/app-file-list.ts +0 -13
  399. package/src/config/bundled-skills/app-builder/tools/app-file-read.ts +0 -21
  400. package/src/config/bundled-skills/app-builder/tools/app-file-write.ts +0 -14
  401. package/src/config/bundled-skills/app-builder/tools/app-list.ts +0 -13
  402. package/src/config/bundled-skills/app-builder/tools/app-update.ts +0 -23
  403. package/src/daemon/media-visibility-policy.ts +0 -59
  404. package/src/tools/assets/materialize.ts +0 -248
  405. package/src/tools/assets/search.ts +0 -400
@@ -2,11 +2,10 @@
2
2
  * Regression tests for app surface refresh and eventing side effects in
3
3
  * createToolExecutor (conversation-tool-setup.ts).
4
4
  *
5
- * After the App Builder tools source swap (PR 8), app tools like app_update,
6
- * app_file_edit, and app_file_write are provided by the app-builder skill
7
- * rather than core registration. The afterExecute hooks in createToolExecutor
8
- * fire based on tool *name* matching, so they must continue to work for
9
- * skill-origin tools. These tests verify that contract.
5
+ * The app_refresh hook is the sole hook for app change refresh. These tests
6
+ * verify that app_refresh, app_create, and app_delete hooks fire correctly,
7
+ * and that removed hooks (app_update, app_file_edit, app_file_write) no
8
+ * longer trigger side effects.
10
9
  */
11
10
 
12
11
  import { beforeEach, describe, expect, mock, test } from "bun:test";
@@ -69,6 +68,7 @@ function makeCtx(overrides: Partial<ToolSetupContext> = {}): ToolSetupContext {
69
68
  { surfaceType: SurfaceType; data: SurfaceData; title?: string }
70
69
  >(),
71
70
  surfaceUndoStacks: new Map(),
71
+ accumulatedSurfaceState: new Map(),
72
72
  surfaceActionRequestIds: new Set<string>(),
73
73
  currentTurnSurfaces: [],
74
74
  isProcessing: () => false,
@@ -109,9 +109,9 @@ describe("session-tool-setup app refresh side effects", () => {
109
109
  updatePublishedSpy.mockClear();
110
110
  });
111
111
 
112
- // ── app_update ──────────────────────────────────────────────────────
112
+ // ── app_refresh ─────────────────────────────────────────────────────
113
113
 
114
- describe("app_update", () => {
114
+ describe("app_refresh", () => {
115
115
  test("triggers refreshSurfacesForApp when result is not an error", async () => {
116
116
  const ctx = makeCtx();
117
117
  const executor = makeFakeExecutor({
@@ -129,7 +129,7 @@ describe("session-tool-setup app refresh side effects", () => {
129
129
  broadcastSpy,
130
130
  );
131
131
 
132
- await toolFn("app_update", { app_id: "app-1", name: "New Name" });
132
+ await toolFn("app_refresh", { app_id: "app-1" });
133
133
 
134
134
  expect(refreshSpy).toHaveBeenCalledTimes(1);
135
135
  expect((refreshSpy.mock.calls as unknown[][])[0][0]).toBe(ctx);
@@ -150,7 +150,7 @@ describe("session-tool-setup app refresh side effects", () => {
150
150
  broadcastSpy,
151
151
  );
152
152
 
153
- await toolFn("app_update", { app_id: "app-42" });
153
+ await toolFn("app_refresh", { app_id: "app-42" });
154
154
 
155
155
  expect(broadcastSpy).toHaveBeenCalledTimes(1);
156
156
  expect((broadcastSpy.mock.calls as unknown[][])[0][0]).toEqual({
@@ -172,7 +172,7 @@ describe("session-tool-setup app refresh side effects", () => {
172
172
  mock(() => {}),
173
173
  );
174
174
 
175
- await toolFn("app_update", { app_id: "app-publish" });
175
+ await toolFn("app_refresh", { app_id: "app-publish" });
176
176
 
177
177
  // updatePublishedAppDeployment is called with void (fire-and-forget),
178
178
  // so just verify it was invoked.
@@ -199,7 +199,7 @@ describe("session-tool-setup app refresh side effects", () => {
199
199
  broadcastSpy,
200
200
  );
201
201
 
202
- await toolFn("app_update", { app_id: "app-err" });
202
+ await toolFn("app_refresh", { app_id: "app-err" });
203
203
 
204
204
  expect(refreshSpy).not.toHaveBeenCalled();
205
205
  expect(broadcastSpy).not.toHaveBeenCalled();
@@ -220,303 +220,13 @@ describe("session-tool-setup app refresh side effects", () => {
220
220
  broadcastSpy,
221
221
  );
222
222
 
223
- await toolFn("app_update", {});
223
+ await toolFn("app_refresh", {});
224
224
 
225
225
  expect(refreshSpy).not.toHaveBeenCalled();
226
226
  expect(broadcastSpy).not.toHaveBeenCalled();
227
227
  });
228
228
  });
229
229
 
230
- // ── app_file_edit ───────────────────────────────────────────────────
231
-
232
- describe("app_file_edit", () => {
233
- test("triggers refreshSurfacesForApp with fileChange flag", async () => {
234
- const ctx = makeCtx();
235
- const executor = makeFakeExecutor({
236
- content: '{"ok":true}',
237
- isError: false,
238
- });
239
- const broadcastSpy = mock(() => {});
240
-
241
- const toolFn = createToolExecutor(
242
- executor as unknown as ToolExecutor,
243
- noopPrompter,
244
- noopSecretPrompter,
245
- ctx,
246
- noopLifecycleHandler,
247
- broadcastSpy,
248
- );
249
-
250
- await toolFn("app_file_edit", {
251
- app_id: "app-edit",
252
- path: "index.html",
253
- old_string: "old",
254
- new_string: "new",
255
- });
256
-
257
- expect(refreshSpy).toHaveBeenCalledTimes(1);
258
- expect((refreshSpy.mock.calls as unknown[][])[0][1]).toBe("app-edit");
259
- // Verify opts include fileChange: true
260
- expect((refreshSpy.mock.calls as unknown[][])[0][2]).toEqual({
261
- fileChange: true,
262
- status: undefined,
263
- });
264
- });
265
-
266
- test("propagates status field through refresh opts", async () => {
267
- const ctx = makeCtx();
268
- const executor = makeFakeExecutor({
269
- content: '{"ok":true}',
270
- isError: false,
271
- });
272
-
273
- const toolFn = createToolExecutor(
274
- executor as unknown as ToolExecutor,
275
- noopPrompter,
276
- noopSecretPrompter,
277
- ctx,
278
- noopLifecycleHandler,
279
- mock(() => {}),
280
- );
281
-
282
- await toolFn("app_file_edit", {
283
- app_id: "app-status",
284
- path: "styles.css",
285
- old_string: "x",
286
- new_string: "y",
287
- status: "updating styles",
288
- });
289
-
290
- expect(refreshSpy).toHaveBeenCalledTimes(1);
291
- expect((refreshSpy.mock.calls as unknown[][])[0][2]).toEqual({
292
- fileChange: true,
293
- status: "updating styles",
294
- });
295
- });
296
-
297
- test("broadcasts app_files_changed for file edit", async () => {
298
- const ctx = makeCtx();
299
- const executor = makeFakeExecutor({ content: "{}", isError: false });
300
- const broadcastSpy = mock(() => {});
301
-
302
- const toolFn = createToolExecutor(
303
- executor as unknown as ToolExecutor,
304
- noopPrompter,
305
- noopSecretPrompter,
306
- ctx,
307
- noopLifecycleHandler,
308
- broadcastSpy,
309
- );
310
-
311
- await toolFn("app_file_edit", {
312
- app_id: "app-edit-bc",
313
- path: "f",
314
- old_string: "a",
315
- new_string: "b",
316
- });
317
-
318
- expect(broadcastSpy).toHaveBeenCalledTimes(1);
319
- expect((broadcastSpy.mock.calls as unknown[][])[0][0]).toEqual({
320
- type: "app_files_changed",
321
- appId: "app-edit-bc",
322
- });
323
- });
324
-
325
- test("calls updatePublishedAppDeployment for file edit", async () => {
326
- const ctx = makeCtx();
327
- const executor = makeFakeExecutor({ content: "{}", isError: false });
328
-
329
- const toolFn = createToolExecutor(
330
- executor as unknown as ToolExecutor,
331
- noopPrompter,
332
- noopSecretPrompter,
333
- ctx,
334
- noopLifecycleHandler,
335
- mock(() => {}),
336
- );
337
-
338
- await toolFn("app_file_edit", {
339
- app_id: "app-pub-edit",
340
- path: "f",
341
- old_string: "a",
342
- new_string: "b",
343
- });
344
-
345
- expect(updatePublishedSpy).toHaveBeenCalledTimes(1);
346
- expect((updatePublishedSpy.mock.calls as unknown[][])[0][0]).toBe(
347
- "app-pub-edit",
348
- );
349
- });
350
-
351
- test("skips side effects when result is an error", async () => {
352
- const ctx = makeCtx();
353
- const executor = makeFakeExecutor({ content: "Error", isError: true });
354
- const broadcastSpy = mock(() => {});
355
-
356
- const toolFn = createToolExecutor(
357
- executor as unknown as ToolExecutor,
358
- noopPrompter,
359
- noopSecretPrompter,
360
- ctx,
361
- noopLifecycleHandler,
362
- broadcastSpy,
363
- );
364
-
365
- await toolFn("app_file_edit", {
366
- app_id: "app-err",
367
- path: "f",
368
- old_string: "a",
369
- new_string: "b",
370
- });
371
-
372
- expect(refreshSpy).not.toHaveBeenCalled();
373
- expect(broadcastSpy).not.toHaveBeenCalled();
374
- expect(updatePublishedSpy).not.toHaveBeenCalled();
375
- });
376
- });
377
-
378
- // ── app_file_write ──────────────────────────────────────────────────
379
-
380
- describe("app_file_write", () => {
381
- test("triggers refreshSurfacesForApp with fileChange flag", async () => {
382
- const ctx = makeCtx();
383
- const executor = makeFakeExecutor({
384
- content: '{"written":true}',
385
- isError: false,
386
- });
387
- const broadcastSpy = mock(() => {});
388
-
389
- const toolFn = createToolExecutor(
390
- executor as unknown as ToolExecutor,
391
- noopPrompter,
392
- noopSecretPrompter,
393
- ctx,
394
- noopLifecycleHandler,
395
- broadcastSpy,
396
- );
397
-
398
- await toolFn("app_file_write", {
399
- app_id: "app-write",
400
- path: "new.html",
401
- content: "<div/>",
402
- });
403
-
404
- expect(refreshSpy).toHaveBeenCalledTimes(1);
405
- expect((refreshSpy.mock.calls as unknown[][])[0][1]).toBe("app-write");
406
- expect((refreshSpy.mock.calls as unknown[][])[0][2]).toEqual({
407
- fileChange: true,
408
- status: undefined,
409
- });
410
- });
411
-
412
- test("propagates status field through refresh opts", async () => {
413
- const ctx = makeCtx();
414
- const executor = makeFakeExecutor({
415
- content: '{"written":true}',
416
- isError: false,
417
- });
418
-
419
- const toolFn = createToolExecutor(
420
- executor as unknown as ToolExecutor,
421
- noopPrompter,
422
- noopSecretPrompter,
423
- ctx,
424
- noopLifecycleHandler,
425
- mock(() => {}),
426
- );
427
-
428
- await toolFn("app_file_write", {
429
- app_id: "app-ws",
430
- path: "f.txt",
431
- content: "hi",
432
- status: "adding dark mode",
433
- });
434
-
435
- expect(refreshSpy).toHaveBeenCalledTimes(1);
436
- expect((refreshSpy.mock.calls as unknown[][])[0][2]).toEqual({
437
- fileChange: true,
438
- status: "adding dark mode",
439
- });
440
- });
441
-
442
- test("broadcasts app_files_changed for file write", async () => {
443
- const ctx = makeCtx();
444
- const executor = makeFakeExecutor({ content: "{}", isError: false });
445
- const broadcastSpy = mock(() => {});
446
-
447
- const toolFn = createToolExecutor(
448
- executor as unknown as ToolExecutor,
449
- noopPrompter,
450
- noopSecretPrompter,
451
- ctx,
452
- noopLifecycleHandler,
453
- broadcastSpy,
454
- );
455
-
456
- await toolFn("app_file_write", {
457
- app_id: "app-write-bc",
458
- path: "f",
459
- content: "x",
460
- });
461
-
462
- expect(broadcastSpy).toHaveBeenCalledTimes(1);
463
- expect((broadcastSpy.mock.calls as unknown[][])[0][0]).toEqual({
464
- type: "app_files_changed",
465
- appId: "app-write-bc",
466
- });
467
- });
468
-
469
- test("calls updatePublishedAppDeployment for file write", async () => {
470
- const ctx = makeCtx();
471
- const executor = makeFakeExecutor({ content: "{}", isError: false });
472
-
473
- const toolFn = createToolExecutor(
474
- executor as unknown as ToolExecutor,
475
- noopPrompter,
476
- noopSecretPrompter,
477
- ctx,
478
- noopLifecycleHandler,
479
- mock(() => {}),
480
- );
481
-
482
- await toolFn("app_file_write", {
483
- app_id: "app-pub-write",
484
- path: "f",
485
- content: "x",
486
- });
487
-
488
- expect(updatePublishedSpy).toHaveBeenCalledTimes(1);
489
- expect((updatePublishedSpy.mock.calls as unknown[][])[0][0]).toBe(
490
- "app-pub-write",
491
- );
492
- });
493
-
494
- test("skips side effects when result is an error", async () => {
495
- const ctx = makeCtx();
496
- const executor = makeFakeExecutor({ content: "Error", isError: true });
497
- const broadcastSpy = mock(() => {});
498
-
499
- const toolFn = createToolExecutor(
500
- executor as unknown as ToolExecutor,
501
- noopPrompter,
502
- noopSecretPrompter,
503
- ctx,
504
- noopLifecycleHandler,
505
- broadcastSpy,
506
- );
507
-
508
- await toolFn("app_file_write", {
509
- app_id: "app-err",
510
- path: "f",
511
- content: "x",
512
- });
513
-
514
- expect(refreshSpy).not.toHaveBeenCalled();
515
- expect(broadcastSpy).not.toHaveBeenCalled();
516
- expect(updatePublishedSpy).not.toHaveBeenCalled();
517
- });
518
- });
519
-
520
230
  // ── app_create side effects ─────────────────────────────────────────
521
231
 
522
232
  describe("app_create side effects", () => {
@@ -616,7 +326,7 @@ describe("session-tool-setup app refresh side effects", () => {
616
326
 
617
327
  describe("name-based hooks fire for skill-origin tools", () => {
618
328
  test("hooks fire purely on tool name, regardless of tool origin", async () => {
619
- // The key invariant: createToolExecutor uses `name === 'app_update'`
329
+ // The key invariant: createToolExecutor uses `name === 'app_refresh'`
620
330
  // string comparison, not tool metadata or origin. This means skill-
621
331
  // projected tools with the same name trigger the same afterExecute
622
332
  // hooks as core tools.
@@ -633,22 +343,14 @@ describe("session-tool-setup app refresh side effects", () => {
633
343
  broadcastSpy,
634
344
  );
635
345
 
636
- // Simulate calling each app tool by name (as the agent loop does)
637
- for (const toolName of [
638
- "app_update",
639
- "app_file_edit",
640
- "app_file_write",
641
- ]) {
346
+ // Simulate calling app_refresh by name (as the agent loop does)
347
+ for (const toolName of ["app_refresh"]) {
642
348
  refreshSpy.mockClear();
643
349
  broadcastSpy.mockClear();
644
350
  updatePublishedSpy.mockClear();
645
351
 
646
352
  await toolFn(toolName, {
647
353
  app_id: "skill-app",
648
- path: "f",
649
- old_string: "a",
650
- new_string: "b",
651
- content: "x",
652
354
  });
653
355
 
654
356
  expect(refreshSpy).toHaveBeenCalledTimes(1);
@@ -675,7 +377,15 @@ describe("session-tool-setup app refresh side effects", () => {
675
377
  broadcastSpy,
676
378
  );
677
379
 
678
- for (const toolName of ["read_file", "write_file", "shell", "app_list"]) {
380
+ for (const toolName of [
381
+ "read_file",
382
+ "write_file",
383
+ "shell",
384
+ "app_list",
385
+ "app_update",
386
+ "app_file_edit",
387
+ "app_file_write",
388
+ ]) {
679
389
  refreshSpy.mockClear();
680
390
  broadcastSpy.mockClear();
681
391
  updatePublishedSpy.mockClear();
@@ -706,7 +416,7 @@ describe("session-tool-setup app refresh side effects", () => {
706
416
  );
707
417
 
708
418
  // Should not throw even though broadcastToAllClients is undefined
709
- const result = await toolFn("app_update", { app_id: "app-no-bc" });
419
+ const result = await toolFn("app_refresh", { app_id: "app-no-bc" });
710
420
 
711
421
  expect(result.isError).toBe(false);
712
422
  expect(refreshSpy).toHaveBeenCalledTimes(1);
@@ -56,6 +56,7 @@ function makeCtx(overrides: Partial<ToolSetupContext> = {}): ToolSetupContext {
56
56
  { surfaceType: SurfaceType; data: SurfaceData; title?: string }
57
57
  >(),
58
58
  surfaceUndoStacks: new Map(),
59
+ accumulatedSurfaceState: new Map(),
59
60
  surfaceActionRequestIds: new Set<string>(),
60
61
  currentTurnSurfaces: [],
61
62
  isProcessing: () => false,
@@ -56,6 +56,7 @@ function makeCtx(overrides: Partial<ToolSetupContext> = {}): ToolSetupContext {
56
56
  { surfaceType: SurfaceType; data: SurfaceData; title?: string }
57
57
  >(),
58
58
  surfaceUndoStacks: new Map(),
59
+ accumulatedSurfaceState: new Map(),
59
60
  surfaceActionRequestIds: new Set<string>(),
60
61
  currentTurnSurfaces: [],
61
62
  isProcessing: () => false,
@@ -26,6 +26,7 @@ mock.module("../util/logger.js", () => ({
26
26
  import {
27
27
  addMessage,
28
28
  createConversation,
29
+ deleteConversation,
29
30
  getConversation,
30
31
  getMessages,
31
32
  wipeConversation,
@@ -436,3 +437,228 @@ describe("wipeConversation", () => {
436
437
  expect(itemBRow).not.toBeNull();
437
438
  });
438
439
  });
440
+
441
+ describe("deleteConversation — private scope cleanup", () => {
442
+ beforeEach(() => {
443
+ const db = getDb();
444
+ db.run(`DELETE FROM conversation_starters`);
445
+ db.run(`DELETE FROM memory_item_sources`);
446
+ db.run(`DELETE FROM memory_segments`);
447
+ db.run(`DELETE FROM memory_items`);
448
+ db.run(`DELETE FROM memory_summaries`);
449
+ db.run(`DELETE FROM memory_embeddings`);
450
+ db.run(`DELETE FROM memory_jobs`);
451
+ db.run(`DELETE FROM tool_invocations`);
452
+ db.run(`DELETE FROM llm_request_logs`);
453
+ db.run(`DELETE FROM messages`);
454
+ db.run(`DELETE FROM conversations`);
455
+ });
456
+
457
+ test("sourceless items cleaned up", () => {
458
+ const conv = createConversation({ conversationType: "private" });
459
+ const scopeId = conv.memoryScopeId;
460
+ const now = Date.now();
461
+
462
+ const raw = (
463
+ getDb() as unknown as {
464
+ $client: import("bun:sqlite").Database;
465
+ }
466
+ ).$client;
467
+
468
+ // Insert a memory item with matching scopeId but no memory_item_sources
469
+ raw
470
+ .query(
471
+ `INSERT INTO memory_items (id, status, kind, subject, statement, confidence, fingerprint, scope_id, first_seen_at, last_seen_at)
472
+ VALUES ('priv-item-1', 'active', 'fact', 'test', 'test fact', 0.8, 'fp-priv-1', ?, ?, ?)`,
473
+ )
474
+ .run(scopeId, now, now);
475
+
476
+ const result = deleteConversation(conv.id);
477
+
478
+ // Item should be gone
479
+ const itemRow = raw
480
+ .query("SELECT * FROM memory_items WHERE id = 'priv-item-1'")
481
+ .get();
482
+ expect(itemRow).toBeNull();
483
+
484
+ // Its ID should be in orphanedItemIds
485
+ expect(result.orphanedItemIds).toContain("priv-item-1");
486
+ });
487
+
488
+ test("summaries cleaned up", () => {
489
+ const conv = createConversation({ conversationType: "private" });
490
+ const scopeId = conv.memoryScopeId;
491
+ const now = Date.now();
492
+
493
+ const raw = (
494
+ getDb() as unknown as {
495
+ $client: import("bun:sqlite").Database;
496
+ }
497
+ ).$client;
498
+
499
+ // Insert a memory summary with matching scopeId
500
+ raw
501
+ .query(
502
+ `INSERT INTO memory_summaries (id, scope, scope_key, summary, token_estimate, version, scope_id, start_at, end_at, created_at, updated_at)
503
+ VALUES ('priv-sum-1', 'global', 'all', 'private summary', 100, 1, ?, ?, ?, ?, ?)`,
504
+ )
505
+ .run(scopeId, now, now, now, now);
506
+
507
+ const result = deleteConversation(conv.id);
508
+
509
+ // Summary should be gone
510
+ const summaryRow = raw
511
+ .query("SELECT * FROM memory_summaries WHERE id = 'priv-sum-1'")
512
+ .get();
513
+ expect(summaryRow).toBeNull();
514
+
515
+ // Its ID should be in deletedSummaryIds
516
+ expect(result.deletedSummaryIds).toContain("priv-sum-1");
517
+ });
518
+
519
+ test("standard conversations unaffected", async () => {
520
+ const conv = createConversation("standard test");
521
+ const now = Date.now();
522
+
523
+ const raw = (
524
+ getDb() as unknown as {
525
+ $client: import("bun:sqlite").Database;
526
+ }
527
+ ).$client;
528
+
529
+ // Insert items with scopeId = "default"
530
+ raw
531
+ .query(
532
+ `INSERT INTO memory_items (id, status, kind, subject, statement, confidence, fingerprint, scope_id, first_seen_at, last_seen_at)
533
+ VALUES ('default-item-1', 'active', 'fact', 'test', 'test fact', 0.8, 'fp-default', 'default', ?, ?)`,
534
+ )
535
+ .run(now, now);
536
+
537
+ deleteConversation(conv.id);
538
+
539
+ // Default-scope items should still exist
540
+ const itemRow = raw
541
+ .query("SELECT * FROM memory_items WHERE id = 'default-item-1'")
542
+ .get();
543
+ expect(itemRow).not.toBeNull();
544
+ });
545
+
546
+ test("embeddings cleaned up", () => {
547
+ const conv = createConversation({ conversationType: "private" });
548
+ const scopeId = conv.memoryScopeId;
549
+ const now = Date.now();
550
+
551
+ const raw = (
552
+ getDb() as unknown as {
553
+ $client: import("bun:sqlite").Database;
554
+ }
555
+ ).$client;
556
+
557
+ // Insert a memory item with matching scopeId
558
+ raw
559
+ .query(
560
+ `INSERT INTO memory_items (id, status, kind, subject, statement, confidence, fingerprint, scope_id, first_seen_at, last_seen_at)
561
+ VALUES ('priv-item-emb', 'active', 'fact', 'test', 'test fact', 0.8, 'fp-priv-emb', ?, ?, ?)`,
562
+ )
563
+ .run(scopeId, now, now);
564
+
565
+ // Insert a corresponding embedding
566
+ raw
567
+ .query(
568
+ `INSERT INTO memory_embeddings (id, target_type, target_id, provider, model, dimensions, created_at, updated_at)
569
+ VALUES ('emb-priv-item', 'item', 'priv-item-emb', 'test', 'test', 384, ?, ?)`,
570
+ )
571
+ .run(now, now);
572
+
573
+ deleteConversation(conv.id);
574
+
575
+ // Both item and embedding should be deleted
576
+ const itemRow = raw
577
+ .query("SELECT * FROM memory_items WHERE id = 'priv-item-emb'")
578
+ .get();
579
+ expect(itemRow).toBeNull();
580
+
581
+ const embeddingRow = raw
582
+ .query("SELECT * FROM memory_embeddings WHERE id = 'emb-priv-item'")
583
+ .get();
584
+ expect(embeddingRow).toBeNull();
585
+ });
586
+
587
+ test("conversationStarters cleaned up", () => {
588
+ const conv = createConversation({ conversationType: "private" });
589
+ const scopeId = conv.memoryScopeId;
590
+ const now = Date.now();
591
+
592
+ const raw = (
593
+ getDb() as unknown as {
594
+ $client: import("bun:sqlite").Database;
595
+ }
596
+ ).$client;
597
+
598
+ // Insert a conversation_starters row with the private scopeId
599
+ raw
600
+ .query(
601
+ `INSERT INTO conversation_starters (id, label, prompt, generation_batch, scope_id, card_type, created_at)
602
+ VALUES ('starter-1', 'Test starter', 'Tell me about tests', 1, ?, 'chip', ?)`,
603
+ )
604
+ .run(scopeId, now);
605
+
606
+ // Also insert a default-scope starter that should NOT be deleted
607
+ raw
608
+ .query(
609
+ `INSERT INTO conversation_starters (id, label, prompt, generation_batch, scope_id, card_type, created_at)
610
+ VALUES ('starter-default', 'Default starter', 'Hello', 1, 'default', 'chip', ?)`,
611
+ )
612
+ .run(now);
613
+
614
+ deleteConversation(conv.id);
615
+
616
+ // Private-scope starter should be gone
617
+ const starterRow = raw
618
+ .query("SELECT * FROM conversation_starters WHERE id = 'starter-1'")
619
+ .get();
620
+ expect(starterRow).toBeNull();
621
+
622
+ // Default-scope starter should still exist
623
+ const defaultStarterRow = raw
624
+ .query("SELECT * FROM conversation_starters WHERE id = 'starter-default'")
625
+ .get();
626
+ expect(defaultStarterRow).not.toBeNull();
627
+ });
628
+
629
+ test("no duplicate IDs", async () => {
630
+ const conv = createConversation({ conversationType: "private" });
631
+ const scopeId = conv.memoryScopeId;
632
+ const msg = await addMessage(conv.id, "user", "hello");
633
+ const now = Date.now();
634
+
635
+ const raw = (
636
+ getDb() as unknown as {
637
+ $client: import("bun:sqlite").Database;
638
+ }
639
+ ).$client;
640
+
641
+ // Insert a memory item with the private scopeId AND a source linking to the message
642
+ raw
643
+ .query(
644
+ `INSERT INTO memory_items (id, status, kind, subject, statement, confidence, fingerprint, scope_id, first_seen_at, last_seen_at)
645
+ VALUES ('priv-item-dup', 'active', 'fact', 'test', 'test fact', 0.8, 'fp-priv-dup', ?, ?, ?)`,
646
+ )
647
+ .run(scopeId, now, now);
648
+
649
+ raw
650
+ .query(
651
+ `INSERT INTO memory_item_sources (memory_item_id, message_id, created_at) VALUES ('priv-item-dup', ?, ?)`,
652
+ )
653
+ .run(msg.id, now);
654
+
655
+ const result = deleteConversation(conv.id);
656
+
657
+ // The item ID should appear exactly once in orphanedItemIds (caught by
658
+ // source-based cleanup, not double-counted by scope sweep).
659
+ const count = result.orphanedItemIds.filter(
660
+ (id) => id === "priv-item-dup",
661
+ ).length;
662
+ expect(count).toBe(1);
663
+ });
664
+ });