@vellumai/assistant 0.6.0 → 0.6.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 (358) hide show
  1. package/AGENTS.md +4 -0
  2. package/ARCHITECTURE.md +68 -15
  3. package/Dockerfile +2 -2
  4. package/bun.lock +6 -2
  5. package/docker-entrypoint.sh +42 -1
  6. package/docs/architecture/integrations.md +1 -1
  7. package/docs/architecture/memory.md +21 -24
  8. package/node_modules/@vellumai/ces-contracts/src/handles.ts +7 -9
  9. package/openapi.yaml +539 -4
  10. package/package.json +5 -1
  11. package/src/__tests__/anthropic-provider.test.ts +160 -95
  12. package/src/__tests__/app-dir-path-guard.test.ts +1 -0
  13. package/src/__tests__/app-executors.test.ts +47 -1
  14. package/src/__tests__/app-source-watcher.test.ts +159 -0
  15. package/src/__tests__/assistant-event-hub.test.ts +30 -0
  16. package/src/__tests__/checker.test.ts +138 -172
  17. package/src/__tests__/cli-command-risk-guard.test.ts +1 -1
  18. package/src/__tests__/config-schema.test.ts +5 -0
  19. package/src/__tests__/context-overflow-approval.test.ts +5 -5
  20. package/src/__tests__/conversation-agent-loop-overflow.test.ts +4 -6
  21. package/src/__tests__/conversation-agent-loop.test.ts +4 -51
  22. package/src/__tests__/conversation-analysis-routes.test.ts +169 -0
  23. package/src/__tests__/conversation-directories-parse.test.ts +105 -0
  24. package/src/__tests__/conversation-history-web-search.test.ts +1 -1
  25. package/src/__tests__/conversation-runtime-assembly.test.ts +653 -832
  26. package/src/__tests__/conversation-runtime-workspace.test.ts +1 -93
  27. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +17 -4
  28. package/src/__tests__/conversation-wipe.test.ts +2 -6
  29. package/src/__tests__/conversation-workspace-cache-state.test.ts +6 -12
  30. package/src/__tests__/conversation-workspace-injection.test.ts +25 -26
  31. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +1 -1
  32. package/src/__tests__/copy-composer-tc-templates.test.ts +335 -0
  33. package/src/__tests__/credential-execution-approval-bridge.test.ts +0 -2
  34. package/src/__tests__/date-context.test.ts +76 -210
  35. package/src/__tests__/db-schedule-syntax-migration.test.ts +16 -1
  36. package/src/__tests__/file-list-tool.test.ts +219 -0
  37. package/src/__tests__/first-greeting.test.ts +1 -1
  38. package/src/__tests__/heartbeat-service.test.ts +180 -3
  39. package/src/__tests__/identity-routes.test.ts +328 -0
  40. package/src/__tests__/init-feature-flag-overrides.test.ts +167 -0
  41. package/src/__tests__/injection-block.test.ts +24 -0
  42. package/src/__tests__/inline-command-runner.test.ts +7 -5
  43. package/src/__tests__/install-skill-routing.test.ts +7 -6
  44. package/src/__tests__/jobs-store-qdrant-breaker.test.ts +15 -14
  45. package/src/__tests__/list-messages-tool-merge.test.ts +300 -0
  46. package/src/__tests__/llm-context-normalization.test.ts +18 -18
  47. package/src/__tests__/llm-context-route-provider.test.ts +101 -0
  48. package/src/__tests__/llm-request-log-turn-query.test.ts +162 -0
  49. package/src/__tests__/log-export-workspace.test.ts +257 -100
  50. package/src/__tests__/managed-credential-catalog-cli.test.ts +12 -14
  51. package/src/__tests__/mcp-abort-signal.test.ts +5 -0
  52. package/src/__tests__/mcp-client-auth.test.ts +5 -0
  53. package/src/__tests__/memory-recall-log-store.test.ts +132 -0
  54. package/src/__tests__/migration-export-streaming.test.ts +304 -0
  55. package/src/__tests__/migration-import-commit-http.test.ts +11 -10
  56. package/src/__tests__/mock-fetch.ts +87 -0
  57. package/src/__tests__/navigate-settings-tab.test.ts +14 -1
  58. package/src/__tests__/notification-broadcaster.test.ts +65 -0
  59. package/src/__tests__/notification-decision-recipient-context.test.ts +282 -0
  60. package/src/__tests__/onboarding-template-contract.test.ts +63 -14
  61. package/src/__tests__/parser.test.ts +32 -0
  62. package/src/__tests__/permission-checker-host-gate.test.ts +452 -0
  63. package/src/__tests__/permission-controls-v2-flag.test.ts +55 -0
  64. package/src/__tests__/permission-mode-sse.test.ts +418 -0
  65. package/src/__tests__/permission-mode-store.test.ts +277 -0
  66. package/src/__tests__/permission-mode.test.ts +101 -0
  67. package/src/__tests__/pkb-autoinject.test.ts +96 -0
  68. package/src/__tests__/platform-bash-auto-approve.test.ts +359 -0
  69. package/src/__tests__/profiler-routes.test.ts +502 -0
  70. package/src/__tests__/profiler-run-store.test.ts +441 -0
  71. package/src/__tests__/proxy-approval-callback.test.ts +4 -75
  72. package/src/__tests__/registry.test.ts +1 -1
  73. package/src/__tests__/require-fresh-approval.test.ts +0 -2
  74. package/src/__tests__/sandbox-diagnostics.test.ts +1 -32
  75. package/src/__tests__/sandbox-host-parity.test.ts +5 -4
  76. package/src/__tests__/scheduler-reuse-conversation.test.ts +368 -0
  77. package/src/__tests__/scrub-corrupted-image-attachments.test.ts +278 -0
  78. package/src/__tests__/search-skills-unified.test.ts +4 -3
  79. package/src/__tests__/send-endpoint-busy.test.ts +42 -3
  80. package/src/__tests__/set-permission-mode.test.ts +274 -0
  81. package/src/__tests__/skill-load-feature-flag.test.ts +12 -0
  82. package/src/__tests__/skill-memory.test.ts +2 -783
  83. package/src/__tests__/strip-memory-injections.test.ts +187 -0
  84. package/src/__tests__/subagent-detail.test.ts +84 -0
  85. package/src/__tests__/subagent-disposal.test.ts +308 -0
  86. package/src/__tests__/subagent-manager-notify.test.ts +19 -10
  87. package/src/__tests__/subagent-notify-parent.test.ts +390 -0
  88. package/src/__tests__/subagent-role-registry.test.ts +108 -0
  89. package/src/__tests__/subagent-tool-filtering.test.ts +71 -0
  90. package/src/__tests__/subagent-tools.test.ts +464 -4
  91. package/src/__tests__/system-prompt-ask-mode.test.ts +139 -0
  92. package/src/__tests__/task-memory-cleanup.test.ts +12 -12
  93. package/src/__tests__/terminal-sandbox.test.ts +1 -1
  94. package/src/__tests__/terminal-tools.test.ts +16 -29
  95. package/src/__tests__/test-preload.ts +18 -0
  96. package/src/__tests__/tool-domain-event-publisher.test.ts +0 -1
  97. package/src/__tests__/tool-executor-lifecycle-events.test.ts +1 -8
  98. package/src/__tests__/tool-executor.test.ts +4 -27
  99. package/src/__tests__/tool-side-effects-slack-dm.test.ts +1 -0
  100. package/src/__tests__/top-level-renderer.test.ts +10 -13
  101. package/src/__tests__/transport-hints-queue.test.ts +77 -0
  102. package/src/__tests__/trust-store.test.ts +4 -4
  103. package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +116 -2
  104. package/src/__tests__/workspace-migration-028-recover-conversations-from-disk-view.test.ts +387 -0
  105. package/src/__tests__/workspace-migration-030-seed-pkb-autoinject.test.ts +168 -0
  106. package/src/__tests__/workspace-policy.test.ts +2 -7
  107. package/src/agent/loop.ts +6 -29
  108. package/src/approvals/guardian-request-resolvers.ts +24 -0
  109. package/src/avatar/traits-png-sync.ts +3 -3
  110. package/src/channels/types.ts +5 -0
  111. package/src/cli/__tests__/run-assistant-command.ts +56 -0
  112. package/src/cli/__tests__/unknown-command.test.ts +33 -0
  113. package/src/cli/commands/__tests__/email-download.test.ts +245 -0
  114. package/src/cli/commands/__tests__/email-list.test.ts +192 -0
  115. package/src/cli/commands/__tests__/email-register.test.ts +186 -0
  116. package/src/cli/commands/__tests__/email-send.test.ts +291 -0
  117. package/src/cli/commands/__tests__/email-status.test.ts +181 -0
  118. package/src/cli/commands/__tests__/email-unregister.test.ts +139 -0
  119. package/src/cli/commands/__tests__/routes.test.ts +562 -0
  120. package/src/cli/commands/conversations.ts +1 -8
  121. package/src/cli/commands/default-action.ts +68 -1
  122. package/src/cli/commands/email.ts +584 -835
  123. package/src/cli/commands/memory.ts +1 -34
  124. package/src/cli/commands/notifications.ts +7 -2
  125. package/src/cli/commands/oauth/__tests__/connect.test.ts +27 -0
  126. package/src/cli/commands/oauth/connect.ts +25 -5
  127. package/src/cli/commands/platform/__tests__/connect.test.ts +1 -1
  128. package/src/cli/commands/platform/__tests__/disconnect.test.ts +1 -1
  129. package/src/cli/commands/platform/__tests__/status.test.ts +1 -1
  130. package/src/cli/commands/routes.ts +396 -0
  131. package/src/cli/commands/skills.ts +130 -20
  132. package/src/cli/program.ts +11 -2
  133. package/src/cli.ts +1 -120
  134. package/src/config/assistant-feature-flags.ts +59 -55
  135. package/src/config/bundled-skills/app-builder/SKILL.md +91 -5
  136. package/src/config/bundled-skills/gmail/SKILL.md +13 -8
  137. package/src/config/bundled-skills/gmail/TOOLS.json +1 -1
  138. package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +2 -1
  139. package/src/config/bundled-skills/messaging/SKILL.md +7 -0
  140. package/src/config/bundled-skills/schedule/SKILL.md +22 -2
  141. package/src/config/bundled-skills/schedule/TOOLS.json +8 -0
  142. package/src/config/bundled-skills/settings/TOOLS.json +1 -1
  143. package/src/config/bundled-skills/settings/tools/avatar-get.ts +3 -13
  144. package/src/config/bundled-skills/settings/tools/avatar-remove.ts +2 -4
  145. package/src/config/bundled-skills/settings/tools/avatar-update.ts +5 -2
  146. package/src/config/bundled-skills/settings/tools/navigate-settings-tab.ts +8 -3
  147. package/src/config/bundled-skills/slack/SKILL.md +2 -0
  148. package/src/config/bundled-skills/subagent/SKILL.md +43 -3
  149. package/src/config/bundled-skills/subagent/TOOLS.json +29 -4
  150. package/src/config/env-registry.ts +63 -0
  151. package/src/config/feature-flag-registry.json +17 -1
  152. package/src/config/schema.ts +8 -0
  153. package/src/config/schemas/filing.ts +51 -0
  154. package/src/config/schemas/heartbeat.ts +15 -12
  155. package/src/config/schemas/memory-lifecycle.ts +12 -0
  156. package/src/config/schemas/security.ts +14 -0
  157. package/src/config/schemas/services.ts +8 -0
  158. package/src/credential-execution/approval-bridge.ts +0 -1
  159. package/src/credential-execution/managed-catalog.ts +3 -7
  160. package/src/daemon/app-source-watcher.ts +93 -0
  161. package/src/daemon/config-watcher.ts +85 -3
  162. package/src/daemon/context-overflow-approval.ts +0 -1
  163. package/src/daemon/conversation-agent-loop-handlers.ts +20 -0
  164. package/src/daemon/conversation-agent-loop.ts +179 -65
  165. package/src/daemon/conversation-attachments.ts +0 -1
  166. package/src/daemon/conversation-history.ts +4 -19
  167. package/src/daemon/conversation-lifecycle.ts +8 -14
  168. package/src/daemon/conversation-messaging.ts +3 -0
  169. package/src/daemon/conversation-process.ts +30 -8
  170. package/src/daemon/conversation-queue-manager.ts +8 -0
  171. package/src/daemon/conversation-runtime-assembly.ts +359 -308
  172. package/src/daemon/conversation-surfaces.ts +65 -0
  173. package/src/daemon/conversation-tool-setup.ts +44 -17
  174. package/src/daemon/conversation-workspace.ts +1 -2
  175. package/src/daemon/conversation.ts +19 -3
  176. package/src/daemon/date-context.ts +26 -53
  177. package/src/daemon/first-greeting.ts +1 -1
  178. package/src/daemon/handlers/conversations.ts +5 -7
  179. package/src/daemon/handlers/shared.test.ts +143 -0
  180. package/src/daemon/handlers/shared.ts +70 -5
  181. package/src/daemon/handlers/skills.ts +11 -18
  182. package/src/daemon/lifecycle.ts +220 -158
  183. package/src/daemon/message-types/conversations.ts +29 -6
  184. package/src/daemon/message-types/messages.ts +9 -2
  185. package/src/daemon/message-types/notifications.ts +12 -0
  186. package/src/daemon/message-types/schedules.ts +1 -0
  187. package/src/daemon/message-types/settings.ts +18 -0
  188. package/src/daemon/profiler-run-store.ts +557 -0
  189. package/src/daemon/server.ts +87 -10
  190. package/src/daemon/shutdown-handlers.ts +5 -0
  191. package/src/daemon/tool-side-effects.ts +23 -3
  192. package/src/daemon/transport-hints.ts +33 -0
  193. package/src/export/transcript-formatter.ts +148 -0
  194. package/src/filing/filing-service.ts +228 -0
  195. package/src/heartbeat/heartbeat-service.ts +96 -7
  196. package/src/index.ts +1 -1
  197. package/src/mcp/client.ts +6 -0
  198. package/src/mcp/mcp-oauth-provider.ts +149 -27
  199. package/src/memory/admin.ts +33 -32
  200. package/src/memory/app-store.ts +69 -0
  201. package/src/memory/conversation-bootstrap.ts +1 -1
  202. package/src/memory/conversation-crud.ts +151 -117
  203. package/src/memory/conversation-directories.ts +39 -0
  204. package/src/memory/conversation-group-migration.ts +66 -6
  205. package/src/memory/conversation-queries.ts +58 -12
  206. package/src/memory/conversation-title-service.ts +1 -0
  207. package/src/memory/db-init.ts +182 -376
  208. package/src/memory/embedding-local.ts +1 -1
  209. package/src/memory/graph/bootstrap.ts +75 -66
  210. package/src/memory/graph/capability-seed.ts +167 -17
  211. package/src/memory/graph/consolidation.ts +38 -4
  212. package/src/memory/graph/conversation-graph-memory.ts +133 -104
  213. package/src/memory/graph/extraction-job.ts +9 -4
  214. package/src/memory/graph/extraction.ts +66 -23
  215. package/src/memory/graph/graph-memory-state-store.ts +37 -0
  216. package/src/memory/graph/graph-search.ts +29 -15
  217. package/src/memory/graph/injection.ts +38 -8
  218. package/src/memory/graph/inspect.ts +12 -3
  219. package/src/memory/graph/retriever.ts +365 -262
  220. package/src/memory/graph/store.test.ts +48 -0
  221. package/src/memory/graph/store.ts +150 -11
  222. package/src/memory/graph/tool-handlers.ts +84 -209
  223. package/src/memory/graph/tools.ts +8 -52
  224. package/src/memory/graph/types.ts +24 -0
  225. package/src/memory/group-crud.ts +25 -9
  226. package/src/memory/job-handlers/cleanup.ts +44 -1
  227. package/src/memory/jobs-store.ts +70 -60
  228. package/src/memory/jobs-worker.ts +44 -28
  229. package/src/memory/llm-request-log-store.ts +96 -12
  230. package/src/memory/memory-recall-log-store.ts +49 -5
  231. package/src/memory/migrations/203-drop-memory-items-tables.ts +33 -1
  232. package/src/memory/migrations/206-memory-graph-node-edits.ts +19 -0
  233. package/src/memory/migrations/206-scrub-corrupted-image-attachments.ts +131 -0
  234. package/src/memory/migrations/207-conversation-graph-memory-state.ts +20 -0
  235. package/src/memory/migrations/208-conversations-last-message-at.ts +35 -0
  236. package/src/memory/migrations/209-strip-thinking-from-consolidated.ts +85 -0
  237. package/src/memory/migrations/210-schedule-reuse-conversation.ts +13 -0
  238. package/src/memory/migrations/211-memory-recall-logs-query-context.ts +21 -0
  239. package/src/memory/migrations/212-llm-request-logs-created-at-index.ts +19 -0
  240. package/src/memory/migrations/index.ts +8 -0
  241. package/src/memory/migrations/registry.ts +8 -0
  242. package/src/memory/schema/conversations.ts +14 -0
  243. package/src/memory/schema/infrastructure.ts +8 -1
  244. package/src/memory/schema/memory-core.ts +0 -51
  245. package/src/memory/schema/memory-graph.ts +15 -0
  246. package/src/memory/task-memory-cleanup.ts +30 -11
  247. package/src/messaging/provider.ts +1 -1
  248. package/src/notifications/broadcaster.ts +6 -0
  249. package/src/notifications/conversation-pairing.ts +12 -4
  250. package/src/notifications/copy-composer.ts +86 -0
  251. package/src/notifications/decision-engine.ts +35 -0
  252. package/src/notifications/emit-signal.ts +14 -0
  253. package/src/notifications/signal.ts +11 -0
  254. package/src/oauth/platform-connection.test.ts +2 -2
  255. package/src/oauth/seed-providers.ts +1 -0
  256. package/src/permissions/checker.ts +15 -4
  257. package/src/permissions/defaults.ts +7 -8
  258. package/src/permissions/permission-mode-store.ts +180 -0
  259. package/src/permissions/permission-mode.ts +31 -0
  260. package/src/permissions/prompter.ts +0 -2
  261. package/src/permissions/workspace-policy.ts +9 -0
  262. package/src/platform/client.ts +1 -1
  263. package/src/prompts/system-prompt.ts +59 -7
  264. package/src/prompts/templates/BOOTSTRAP-REFERENCE.md +100 -0
  265. package/src/prompts/templates/BOOTSTRAP.md +76 -162
  266. package/src/prompts/templates/HEARTBEAT.md +3 -1
  267. package/src/prompts/templates/SOUL.md +30 -9
  268. package/src/prompts/templates/UPDATES.md +8 -0
  269. package/src/providers/anthropic/client.ts +107 -219
  270. package/src/runtime/assistant-event-hub.ts +22 -0
  271. package/src/runtime/auth/route-policy.ts +23 -0
  272. package/src/runtime/auth/token-service.ts +8 -0
  273. package/src/runtime/http-server.ts +32 -2
  274. package/src/runtime/http-types.ts +12 -1
  275. package/src/runtime/migrations/vbundle-builder.ts +389 -3
  276. package/src/runtime/migrations/vbundle-importer.ts +8 -6
  277. package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +378 -0
  278. package/src/runtime/routes/app-management-routes.ts +1 -11
  279. package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +26 -0
  280. package/src/runtime/routes/archive-utils.ts +29 -0
  281. package/src/runtime/routes/avatar-routes.ts +2 -9
  282. package/src/runtime/routes/btw-routes.ts +14 -1
  283. package/src/runtime/routes/conversation-analysis-routes.ts +185 -0
  284. package/src/runtime/routes/conversation-management-routes.ts +1 -14
  285. package/src/runtime/routes/conversation-query-routes.ts +49 -3
  286. package/src/runtime/routes/conversation-routes.ts +270 -44
  287. package/src/runtime/routes/group-routes.ts +22 -8
  288. package/src/runtime/routes/heartbeat-routes.ts +4 -10
  289. package/src/runtime/routes/identity-routes.ts +53 -18
  290. package/src/runtime/routes/llm-context-normalization.ts +14 -10
  291. package/src/runtime/routes/log-export/AGENTS.md +104 -0
  292. package/src/runtime/routes/log-export/__tests__/workspace-allowlist-error-contract.test.ts +103 -0
  293. package/src/runtime/routes/log-export/__tests__/workspace-allowlist.test.ts +716 -0
  294. package/src/runtime/routes/log-export/workspace-allowlist.ts +458 -0
  295. package/src/runtime/routes/log-export-routes.ts +41 -278
  296. package/src/runtime/routes/memory-item-routes.test.ts +168 -233
  297. package/src/runtime/routes/migration-routes.ts +18 -7
  298. package/src/runtime/routes/profiler-routes.ts +350 -0
  299. package/src/runtime/routes/schedule-routes.ts +27 -12
  300. package/src/runtime/routes/settings-routes.ts +95 -8
  301. package/src/runtime/routes/subagents-routes.ts +28 -7
  302. package/src/runtime/routes/user-route-dispatcher.ts +223 -0
  303. package/src/runtime/routes/user-routes.ts +41 -0
  304. package/src/runtime/routes/workspace-routes.ts +0 -1
  305. package/src/schedule/schedule-store.ts +30 -0
  306. package/src/schedule/scheduler.ts +45 -18
  307. package/src/skills/catalog-install.ts +10 -2
  308. package/src/skills/inline-command-runner.ts +12 -14
  309. package/src/skills/managed-store.ts +2 -2
  310. package/src/skills/skill-memory.ts +1 -293
  311. package/src/subagent/index.ts +13 -3
  312. package/src/subagent/manager.ts +308 -29
  313. package/src/subagent/types.ts +68 -0
  314. package/src/tasks/task-runner.ts +4 -4
  315. package/src/tools/apps/executors.ts +29 -4
  316. package/src/tools/filesystem/list.ts +93 -0
  317. package/src/tools/permission-checker.ts +78 -18
  318. package/src/tools/registry.ts +4 -0
  319. package/src/tools/schedule/create.ts +3 -0
  320. package/src/tools/schedule/list.ts +1 -0
  321. package/src/tools/schedule/update.ts +6 -0
  322. package/src/tools/secret-detection-handler.ts +0 -1
  323. package/src/tools/shared/filesystem/errors.ts +5 -0
  324. package/src/tools/shared/filesystem/file-ops-service.ts +90 -2
  325. package/src/tools/shared/filesystem/types.ts +17 -0
  326. package/src/tools/shared/shell-output.ts +31 -2
  327. package/src/tools/skills/sandbox-runner.ts +3 -6
  328. package/src/tools/subagent/abort.ts +12 -2
  329. package/src/tools/subagent/message.ts +9 -2
  330. package/src/tools/subagent/notify-parent.ts +79 -0
  331. package/src/tools/subagent/read.ts +29 -8
  332. package/src/tools/subagent/resolve.ts +21 -0
  333. package/src/tools/subagent/spawn.ts +2 -0
  334. package/src/tools/subagent/status.ts +11 -1
  335. package/src/tools/system/avatar-generator.ts +3 -3
  336. package/src/tools/system/register.ts +23 -0
  337. package/src/tools/system/set-permission-mode.ts +103 -0
  338. package/src/tools/terminal/parser.ts +30 -5
  339. package/src/tools/terminal/safe-env.ts +16 -1
  340. package/src/tools/terminal/sandbox-diagnostics.ts +4 -4
  341. package/src/tools/terminal/sandbox.ts +4 -1
  342. package/src/tools/terminal/shell.ts +3 -5
  343. package/src/tools/tool-manifest.ts +6 -0
  344. package/src/tools/types.ts +2 -3
  345. package/src/util/logger.ts +1 -1
  346. package/src/util/platform.ts +50 -17
  347. package/src/watcher/provider-types.ts +1 -1
  348. package/src/workspace/migrations/023-move-config-files-to-workspace.ts +2 -2
  349. package/src/workspace/migrations/024-move-runtime-files-to-workspace.ts +2 -2
  350. package/src/workspace/migrations/028-recover-conversations-from-disk-view.ts +270 -0
  351. package/src/workspace/migrations/029-seed-pkb.ts +85 -0
  352. package/src/workspace/migrations/030-seed-pkb-autoinject.ts +73 -0
  353. package/src/workspace/migrations/registry.ts +6 -0
  354. package/src/workspace/top-level-renderer.ts +5 -9
  355. package/src/__tests__/cli-memory.test.ts +0 -377
  356. package/src/__tests__/clipboard.test.ts +0 -88
  357. package/src/cli/cli-memory.ts +0 -179
  358. package/src/util/clipboard.ts +0 -34
@@ -91,10 +91,7 @@ export function backfillMessageIdOnLogs(
91
91
  * for a conversation), this targets only the given log rows — safe for
92
92
  * concurrent watch/assistant turns.
93
93
  */
94
- export function setMessageIdOnLogs(
95
- logIds: string[],
96
- messageId: string,
97
- ): void {
94
+ export function setMessageIdOnLogs(logIds: string[], messageId: string): void {
98
95
  if (logIds.length === 0) return;
99
96
  const db = getDb();
100
97
  db.update(llmRequestLogs)
@@ -185,18 +182,75 @@ function selectOrphanedLogsInRange(
185
182
  .all();
186
183
  }
187
184
 
185
+ /**
186
+ * Find unlinked logs — logs with `message_id IS NULL` that haven't been
187
+ * backfilled yet. This covers the race where the client queries the inspector
188
+ * before `backfillMessageIdOnLogs` runs in `handleMessageComplete`, or when
189
+ * the backfill fails silently (try-catch in the agent loop).
190
+ *
191
+ * Scoped to a single conversation and a time range to avoid cross-turn bleed.
192
+ */
193
+ function selectUnlinkedLogsInRange(
194
+ conversationId: string,
195
+ startTime: number,
196
+ endTime: number,
197
+ ): LogRow[] {
198
+ if (endTime <= startTime) return [];
199
+ const db = getDb();
200
+ return db
201
+ .select({
202
+ id: llmRequestLogs.id,
203
+ conversationId: llmRequestLogs.conversationId,
204
+ messageId: llmRequestLogs.messageId,
205
+ provider: llmRequestLogs.provider,
206
+ requestPayload: llmRequestLogs.requestPayload,
207
+ responsePayload: llmRequestLogs.responsePayload,
208
+ createdAt: llmRequestLogs.createdAt,
209
+ })
210
+ .from(llmRequestLogs)
211
+ .where(
212
+ and(
213
+ eq(llmRequestLogs.conversationId, conversationId),
214
+ gte(llmRequestLogs.createdAt, startTime),
215
+ lte(llmRequestLogs.createdAt, endTime),
216
+ isNull(llmRequestLogs.messageId),
217
+ ),
218
+ )
219
+ .orderBy(llmRequestLogs.createdAt)
220
+ .all();
221
+ }
222
+
223
+ export function getRequestLogById(logId: string): LogRow | null {
224
+ const db = getDb();
225
+ return (
226
+ db
227
+ .select({
228
+ id: llmRequestLogs.id,
229
+ conversationId: llmRequestLogs.conversationId,
230
+ messageId: llmRequestLogs.messageId,
231
+ provider: llmRequestLogs.provider,
232
+ requestPayload: llmRequestLogs.requestPayload,
233
+ responsePayload: llmRequestLogs.responsePayload,
234
+ createdAt: llmRequestLogs.createdAt,
235
+ })
236
+ .from(llmRequestLogs)
237
+ .where(eq(llmRequestLogs.id, logId))
238
+ .get() ?? null
239
+ );
240
+ }
241
+
188
242
  export function getRequestLogsByMessageId(messageId: string): LogRow[] {
189
243
  // Resolve all assistant message IDs in the same turn so the inspector
190
244
  // shows every LLM call from the entire agent turn, not just the queried message.
191
245
  const turnMessageIds = getAssistantMessageIdsInTurn(messageId);
192
246
  const turnLogs = selectLogsByMessageIds(turnMessageIds);
193
247
 
194
- // Orphaned-log recovery: intermediate assistant messages within a turn can
195
- // be deleted (e.g. by retry / deleteLastExchange) while their
196
- // llm_request_logs rows survive. When that happens the message-ID-based
197
- // query misses those logs. We detect orphaned logs (logs whose message_id
198
- // references a deleted message) within the turn's time window and merge
199
- // them with the message-ID-based results.
248
+ // Recovery: find logs in the turn's time window that the message-ID-based
249
+ // query missed. Two categories:
250
+ // 1. Orphaned messageId references a deleted message (retry/deleteLastExchange).
251
+ // 2. Unlinked messageId is still NULL because the backfill hasn't run yet
252
+ // or failed silently. This covers the race where the client queries the
253
+ // inspector before handleMessageComplete persists and backfills.
200
254
  const message = getMessageById(messageId);
201
255
  if (message) {
202
256
  const bounds = getTurnTimeBounds(message.conversationId, message.createdAt);
@@ -206,10 +260,16 @@ export function getRequestLogsByMessageId(messageId: string): LogRow[] {
206
260
  bounds.startTime,
207
261
  bounds.endTime,
208
262
  );
209
- if (orphanedLogs.length > 0) {
263
+ const unlinkedLogs = selectUnlinkedLogsInRange(
264
+ message.conversationId,
265
+ bounds.startTime,
266
+ bounds.endTime,
267
+ );
268
+
269
+ if (orphanedLogs.length > 0 || unlinkedLogs.length > 0) {
210
270
  const seen = new Set(turnLogs.map((l) => l.id));
211
271
  const merged = [...turnLogs];
212
- for (const log of orphanedLogs) {
272
+ for (const log of [...orphanedLogs, ...unlinkedLogs]) {
213
273
  if (!seen.has(log.id)) {
214
274
  merged.push(log);
215
275
  seen.add(log.id);
@@ -218,6 +278,30 @@ export function getRequestLogsByMessageId(messageId: string): LogRow[] {
218
278
  merged.sort(
219
279
  (a, b) => a.createdAt - b.createdAt || a.id.localeCompare(b.id),
220
280
  );
281
+
282
+ // Opportunistically backfill recovered unlinked logs so future queries
283
+ // hit the fast indexed-by-messageId path. Guard with isNull so this
284
+ // recovery path never overwrites a messageId already set by an
285
+ // authoritative caller (e.g. watch-notifier).
286
+ if (unlinkedLogs.length > 0 && turnMessageIds.length > 0) {
287
+ try {
288
+ const db = getDb();
289
+ const ids = unlinkedLogs.map((l) => l.id);
290
+ const targetMessageId = turnMessageIds[turnMessageIds.length - 1]!;
291
+ db.update(llmRequestLogs)
292
+ .set({ messageId: targetMessageId })
293
+ .where(
294
+ and(
295
+ inArray(llmRequestLogs.id, ids),
296
+ isNull(llmRequestLogs.messageId),
297
+ ),
298
+ )
299
+ .run();
300
+ } catch {
301
+ // non-fatal — the recovery already returned the right data
302
+ }
303
+ }
304
+
221
305
  return merged;
222
306
  }
223
307
  }
@@ -23,9 +23,12 @@ export interface RecordMemoryRecallLogParams {
23
23
  topCandidatesJson: unknown;
24
24
  injectedText?: string;
25
25
  reason?: string;
26
+ queryContext?: string;
26
27
  }
27
28
 
28
- export function recordMemoryRecallLog(params: RecordMemoryRecallLogParams): void {
29
+ export function recordMemoryRecallLog(
30
+ params: RecordMemoryRecallLogParams,
31
+ ): void {
29
32
  const db = getDb();
30
33
  db.insert(memoryRecallLogs)
31
34
  .values({
@@ -51,6 +54,7 @@ export function recordMemoryRecallLog(params: RecordMemoryRecallLogParams): void
51
54
  topCandidatesJson: JSON.stringify(params.topCandidatesJson),
52
55
  injectedText: params.injectedText ?? null,
53
56
  reason: params.reason ?? null,
57
+ queryContext: params.queryContext ?? null,
54
58
  createdAt: Date.now(),
55
59
  })
56
60
  .run();
@@ -90,6 +94,47 @@ export interface MemoryRecallLog {
90
94
  topCandidates: unknown;
91
95
  injectedText: string | null;
92
96
  reason: string | null;
97
+ queryContext: string | null;
98
+ }
99
+
100
+ /**
101
+ * Normalizes top-candidate entries from the stored SSE-event format
102
+ * (key/finalScore/semantic/recency/kind) to the inspector format expected
103
+ * by the Swift MemoryRecallCandidate struct (nodeId/score/semanticSimilarity/recencyBoost).
104
+ * Entries already in inspector format pass through unchanged.
105
+ */
106
+ export function normalizeTopCandidates(raw: unknown): unknown {
107
+ if (!Array.isArray(raw)) return raw;
108
+ return raw.flatMap((entry: Record<string, unknown>) => {
109
+ if (!entry || typeof entry !== "object") return [];
110
+
111
+ // Start with a shallow copy, then apply field renames
112
+ const { key, finalScore, semantic, recency, kind: _kind, ...rest } = entry;
113
+
114
+ // nodeId: prefer existing nodeId, fall back to key
115
+ if (rest.nodeId === undefined && key !== undefined) {
116
+ rest.nodeId = key;
117
+ }
118
+
119
+ // score: prefer existing score, fall back to finalScore
120
+ if (rest.score === undefined && finalScore !== undefined) {
121
+ rest.score = finalScore;
122
+ }
123
+
124
+ // semanticSimilarity: prefer existing, fall back to semantic
125
+ if (rest.semanticSimilarity === undefined && semantic !== undefined) {
126
+ rest.semanticSimilarity = semantic;
127
+ }
128
+
129
+ // recencyBoost: prefer existing, fall back to recency
130
+ if (rest.recencyBoost === undefined && recency !== undefined) {
131
+ rest.recencyBoost = recency;
132
+ }
133
+
134
+ // kind is stripped (not in the Swift model) — already excluded via destructuring
135
+
136
+ return rest;
137
+ });
93
138
  }
94
139
 
95
140
  export function getMemoryRecallLogByMessageIds(
@@ -109,9 +154,7 @@ export function getMemoryRecallLogByMessageIds(
109
154
  degraded: !!row.degraded,
110
155
  provider: row.provider,
111
156
  model: row.model,
112
- degradation: row.degradationJson
113
- ? JSON.parse(row.degradationJson)
114
- : null,
157
+ degradation: row.degradationJson ? JSON.parse(row.degradationJson) : null,
115
158
  semanticHits: row.semanticHits,
116
159
  mergedCount: row.mergedCount,
117
160
  selectedCount: row.selectedCount,
@@ -121,8 +164,9 @@ export function getMemoryRecallLogByMessageIds(
121
164
  sparseVectorUsed: !!row.sparseVectorUsed,
122
165
  injectedTokens: row.injectedTokens,
123
166
  latencyMs: row.latencyMs,
124
- topCandidates: JSON.parse(row.topCandidatesJson),
167
+ topCandidates: normalizeTopCandidates(JSON.parse(row.topCandidatesJson)),
125
168
  injectedText: row.injectedText,
126
169
  reason: row.reason,
170
+ queryContext: row.queryContext,
127
171
  };
128
172
  }
@@ -6,13 +6,45 @@ import { getSqliteFrom } from "../db-connection.js";
6
6
  *
7
7
  * All consumers have been migrated to memory_graph_nodes (#22698).
8
8
  * These tables are now dead weight.
9
+ *
10
+ * Safety: only drops tables when they are empty or the tool-created-items
11
+ * migration has already copied relevant rows into memory_graph_nodes.
12
+ * Workspaces that haven't run migrateToolCreatedItems() yet keep the tables
13
+ * so data isn't silently lost.
9
14
  */
10
15
  export function migrateDropMemoryItemsTables(database: DrizzleDb): void {
11
16
  const raw = getSqliteFrom(database);
12
17
 
18
+ // Guard: verify tables are safe to drop (empty or already migrated).
19
+ try {
20
+ const row = raw
21
+ .prepare(
22
+ /*sql*/ `SELECT COUNT(*) as cnt FROM memory_items WHERE status = 'active'`,
23
+ )
24
+ .get() as { cnt: number } | undefined;
25
+
26
+ if (row && row.cnt > 0) {
27
+ // Tables have active rows — only drop if the migration checkpoint exists.
28
+ const checkpoint = raw
29
+ .prepare(
30
+ /*sql*/ `SELECT value FROM memory_checkpoints WHERE key = ?`,
31
+ )
32
+ .get("graph_bootstrap:migrated_tool_items") as
33
+ | { value: string }
34
+ | undefined;
35
+
36
+ if (!checkpoint?.value) {
37
+ // Data exists but hasn't been migrated — skip the drop to prevent data loss.
38
+ return;
39
+ }
40
+ }
41
+ } catch {
42
+ // Table doesn't exist (fresh install) — proceed with drop (IF EXISTS is a no-op).
43
+ }
44
+
13
45
  // Drop indexes first (idempotent — IF EXISTS).
14
46
  raw.exec(
15
- /*sql*/ `DROP INDEX IF EXISTS idx_memory_item_sources_memory_item_id`
47
+ /*sql*/ `DROP INDEX IF EXISTS idx_memory_item_sources_memory_item_id`,
16
48
  );
17
49
  raw.exec(/*sql*/ `DROP INDEX IF EXISTS idx_memory_items_scope_id`);
18
50
  raw.exec(/*sql*/ `DROP INDEX IF EXISTS idx_memory_items_fingerprint`);
@@ -0,0 +1,19 @@
1
+ import type { DrizzleDb } from "../db-connection.js";
2
+ import { getSqliteFrom } from "../db-connection.js";
3
+
4
+ export function migrateCreateMemoryGraphNodeEdits(database: DrizzleDb): void {
5
+ const raw = getSqliteFrom(database);
6
+ raw.exec(`
7
+ CREATE TABLE IF NOT EXISTS memory_graph_node_edits (
8
+ id TEXT PRIMARY KEY,
9
+ node_id TEXT NOT NULL REFERENCES memory_graph_nodes(id) ON DELETE CASCADE,
10
+ previous_content TEXT NOT NULL,
11
+ new_content TEXT NOT NULL,
12
+ source TEXT NOT NULL,
13
+ conversation_id TEXT,
14
+ created INTEGER NOT NULL
15
+ );
16
+ CREATE INDEX IF NOT EXISTS idx_graph_node_edits_node_id ON memory_graph_node_edits(node_id);
17
+ CREATE INDEX IF NOT EXISTS idx_graph_node_edits_created ON memory_graph_node_edits(created);
18
+ `);
19
+ }
@@ -0,0 +1,131 @@
1
+ import { readFileSync, unlinkSync } from "node:fs";
2
+
3
+ import type { DrizzleDb } from "../db-connection.js";
4
+ import { getSqliteFrom } from "../db-connection.js";
5
+ import { withCrashRecovery } from "./validate-migration-state.js";
6
+
7
+ /**
8
+ * Remove image attachments that contain HTML error pages instead of actual
9
+ * image data. This can happen when a CDN (e.g. Slack) returns an HTML sign-in
10
+ * page due to a missing OAuth scope, and the gateway stores the response body
11
+ * as an image attachment.
12
+ *
13
+ * Handles both inline (data_base64) and on-disk (file_path) storage.
14
+ */
15
+
16
+ const HTML_MARKERS = ["<!doctype", "<html"];
17
+
18
+ function looksLikeHtml(bytes: Buffer): boolean {
19
+ // Strip leading BOM / whitespace
20
+ const text = bytes.toString("utf-8").trimStart().toLowerCase();
21
+ return HTML_MARKERS.some((marker) => text.startsWith(marker));
22
+ }
23
+
24
+ export function migrateScrubCorruptedImageAttachments(
25
+ database: DrizzleDb,
26
+ ): void {
27
+ withCrashRecovery(
28
+ database,
29
+ "migration_scrub_corrupted_image_attachments_v1",
30
+ () => {
31
+ const raw = getSqliteFrom(database);
32
+
33
+ function deleteCorruptedAttachment(
34
+ id: string,
35
+ filePath: string | null,
36
+ ): void {
37
+ raw
38
+ .query(`DELETE FROM message_attachments WHERE attachment_id = ?`)
39
+ .run(id);
40
+ raw.query(`DELETE FROM attachments WHERE id = ?`).run(id);
41
+
42
+ if (filePath) {
43
+ try {
44
+ unlinkSync(filePath);
45
+ } catch {
46
+ // File already missing — ignore
47
+ }
48
+ }
49
+
50
+ console.log(
51
+ `[scrub-corrupted-attachments] Removed corrupted attachment ${id}`,
52
+ );
53
+ }
54
+
55
+ // Step A — Find and remove corrupted attachments stored inline (data_base64)
56
+ // Process in batches using rowid cursor to ensure all rows are scanned
57
+ // even when corrupted rows are non-contiguous.
58
+ const BATCH_SIZE = 100;
59
+ let lastRowid = 0;
60
+ for (;;) {
61
+ const rows = raw
62
+ .query(
63
+ `SELECT rowid, id, data_base64, file_path FROM attachments
64
+ WHERE mime_type LIKE 'image/%'
65
+ AND data_base64 IS NOT NULL
66
+ AND data_base64 != ''
67
+ AND rowid > ?
68
+ ORDER BY rowid
69
+ LIMIT ?`,
70
+ )
71
+ .all(lastRowid, BATCH_SIZE) as Array<{
72
+ rowid: number;
73
+ id: string;
74
+ data_base64: string;
75
+ file_path: string | null;
76
+ }>;
77
+
78
+ if (rows.length === 0) break;
79
+
80
+ for (const row of rows) {
81
+ lastRowid = row.rowid;
82
+ try {
83
+ const decoded = Buffer.from(
84
+ row.data_base64.slice(0, 200),
85
+ "base64",
86
+ );
87
+ if (looksLikeHtml(decoded)) {
88
+ deleteCorruptedAttachment(row.id, row.file_path);
89
+ }
90
+ } catch {
91
+ // Skip rows with invalid base64
92
+ }
93
+ }
94
+ }
95
+
96
+ // Step B — Find and remove corrupted attachments stored on disk (file_path)
97
+ // Disk-backed attachments are typically fewer; query all at once.
98
+ const diskRows = raw
99
+ .query(
100
+ `SELECT id, file_path FROM attachments
101
+ WHERE mime_type LIKE 'image/%'
102
+ AND file_path IS NOT NULL
103
+ AND (data_base64 IS NULL OR data_base64 = '')`,
104
+ )
105
+ .all() as Array<{ id: string; file_path: string }>;
106
+
107
+ for (const row of diskRows) {
108
+ try {
109
+ const bytes = readFileSync(row.file_path);
110
+ const head = bytes.subarray(0, 100);
111
+ if (looksLikeHtml(head)) {
112
+ deleteCorruptedAttachment(row.id, row.file_path);
113
+ }
114
+ } catch {
115
+ // File doesn't exist or can't be read — skip
116
+ }
117
+ }
118
+ },
119
+ );
120
+ }
121
+
122
+ /**
123
+ * Reverse: no-op.
124
+ *
125
+ * Corrupted data (HTML stored as image) has no value to restore.
126
+ */
127
+ export function migrateScrubCorruptedImageAttachmentsDown(
128
+ _database: DrizzleDb,
129
+ ): void {
130
+ // No-op — corrupted data (HTML stored as image) has no value to restore.
131
+ }
@@ -0,0 +1,20 @@
1
+ import type { DrizzleDb } from "../db-connection.js";
2
+ import { getSqliteFrom } from "../db-connection.js";
3
+
4
+ /**
5
+ * Persist ConversationGraphMemory + InContextTracker state across eviction.
6
+ * Idempotent — uses CREATE TABLE IF NOT EXISTS.
7
+ */
8
+ export function migrateCreateConversationGraphMemoryState(
9
+ database: DrizzleDb,
10
+ ): void {
11
+ const raw = getSqliteFrom(database);
12
+ raw.exec(`
13
+ CREATE TABLE IF NOT EXISTS conversation_graph_memory_state (
14
+ conversation_id TEXT PRIMARY KEY REFERENCES conversations(id) ON DELETE CASCADE,
15
+ state_json TEXT NOT NULL,
16
+ created_at INTEGER NOT NULL,
17
+ updated_at INTEGER NOT NULL
18
+ )
19
+ `);
20
+ }
@@ -0,0 +1,35 @@
1
+ import type { DrizzleDb } from "../db-connection.js";
2
+ import { getSqliteFrom } from "../db-connection.js";
3
+
4
+ /**
5
+ * Add last_message_at denormalized column to conversations for sorting by
6
+ * latest message timestamp instead of updatedAt (which is bumped by non-message
7
+ * events like title changes and context compaction).
8
+ *
9
+ * Idempotent — uses ALTER TABLE try/catch and IF NOT EXISTS guards.
10
+ */
11
+ export function migrateConversationsLastMessageAt(database: DrizzleDb): void {
12
+ const raw = getSqliteFrom(database);
13
+
14
+ try {
15
+ raw.exec(`ALTER TABLE conversations ADD COLUMN last_message_at INTEGER`);
16
+ } catch {
17
+ // Column already exists
18
+ }
19
+
20
+ // Backfill from the latest message in each conversation.
21
+ // Idempotent: re-running produces the same result.
22
+ raw.exec(`
23
+ UPDATE conversations
24
+ SET last_message_at = (
25
+ SELECT MAX(created_at) FROM messages
26
+ WHERE messages.conversation_id = conversations.id
27
+ )
28
+ WHERE last_message_at IS NULL
29
+ `);
30
+
31
+ raw.exec(`
32
+ CREATE INDEX IF NOT EXISTS idx_conversations_last_message_at
33
+ ON conversations(last_message_at)
34
+ `);
35
+ }
@@ -0,0 +1,85 @@
1
+ import type { DrizzleDb } from "../db-connection.js";
2
+ import { getSqliteFrom } from "../db-connection.js";
3
+ import { withCrashRecovery } from "./validate-migration-state.js";
4
+
5
+ /**
6
+ * Strip thinking and redacted_thinking blocks from all assistant messages.
7
+ *
8
+ * Consolidated messages merge thinking blocks from different API responses,
9
+ * making their cryptographic signatures invalid. Previously the Anthropic
10
+ * provider stripped these on every request, mutating the conversation prefix
11
+ * and defeating prompt caching. This migration cleans them at rest so the
12
+ * provider no longer needs to strip, enabling append-only conversation
13
+ * history and stable prefix caching.
14
+ *
15
+ * Idempotent — safe to re-run.
16
+ */
17
+ export function migrateStripThinkingFromConsolidated(
18
+ database: DrizzleDb,
19
+ ): void {
20
+ withCrashRecovery(
21
+ database,
22
+ "migration_strip_thinking_from_consolidated_v1",
23
+ () => {
24
+ const raw = getSqliteFrom(database);
25
+
26
+ const BATCH_SIZE = 100;
27
+ let lastRowid = 0;
28
+
29
+ for (;;) {
30
+ const rows = raw
31
+ .query(
32
+ `SELECT rowid, id, content FROM messages
33
+ WHERE role = 'assistant'
34
+ AND rowid > ?
35
+ ORDER BY rowid
36
+ LIMIT ?`,
37
+ )
38
+ .all(lastRowid, BATCH_SIZE) as Array<{
39
+ rowid: number;
40
+ id: string;
41
+ content: string;
42
+ }>;
43
+
44
+ if (rows.length === 0) break;
45
+
46
+ for (const row of rows) {
47
+ lastRowid = row.rowid;
48
+
49
+ let blocks: Array<{ type: string }>;
50
+ try {
51
+ const parsed = JSON.parse(row.content);
52
+ if (!Array.isArray(parsed)) continue;
53
+ blocks = parsed;
54
+ } catch {
55
+ continue;
56
+ }
57
+
58
+ const hasThinking = blocks.some(
59
+ (b) => b.type === "thinking" || b.type === "redacted_thinking",
60
+ );
61
+ if (!hasThinking) continue;
62
+
63
+ const stripped = blocks.filter(
64
+ (b) => b.type !== "thinking" && b.type !== "redacted_thinking",
65
+ );
66
+
67
+ // Preserve at least one block so the message isn't empty.
68
+ const finalContent =
69
+ stripped.length > 0
70
+ ? stripped
71
+ : [
72
+ {
73
+ type: "text" as const,
74
+ text: "\x00__PLACEHOLDER__[internal blocks omitted]",
75
+ },
76
+ ];
77
+
78
+ raw
79
+ .query(`UPDATE messages SET content = ? WHERE id = ?`)
80
+ .run(JSON.stringify(finalContent), row.id);
81
+ }
82
+ }
83
+ },
84
+ );
85
+ }
@@ -0,0 +1,13 @@
1
+ import type { DrizzleDb } from "../db-connection.js";
2
+ import { getSqliteFrom } from "../db-connection.js";
3
+
4
+ export function migrateScheduleReuseConversation(database: DrizzleDb): void {
5
+ const raw = getSqliteFrom(database);
6
+ try {
7
+ raw.exec(
8
+ `ALTER TABLE cron_jobs ADD COLUMN reuse_conversation INTEGER NOT NULL DEFAULT 0`,
9
+ );
10
+ } catch {
11
+ // Column already exists — nothing to do.
12
+ }
13
+ }
@@ -0,0 +1,21 @@
1
+ import type { DrizzleDb } from "../db-connection.js";
2
+ import { getSqliteFrom } from "../db-connection.js";
3
+ import { tableHasColumn } from "./schema-introspection.js";
4
+ import { withCrashRecovery } from "./validate-migration-state.js";
5
+
6
+ const CHECKPOINT_KEY = "migration_memory_recall_logs_query_context_v1";
7
+
8
+ /**
9
+ * Add query_context column to memory_recall_logs to persist the query text
10
+ * that drove semantic search, enabling the inspector to show what was searched.
11
+ */
12
+ export function migrateMemoryRecallLogsQueryContext(database: DrizzleDb): void {
13
+ withCrashRecovery(database, CHECKPOINT_KEY, () => {
14
+ if (!tableHasColumn(database, "memory_recall_logs", "query_context")) {
15
+ const raw = getSqliteFrom(database);
16
+ raw.exec(
17
+ `ALTER TABLE memory_recall_logs ADD COLUMN query_context TEXT`,
18
+ );
19
+ }
20
+ });
21
+ }
@@ -0,0 +1,19 @@
1
+ import type { DrizzleDb } from "../db-connection.js";
2
+ import { getSqliteFrom } from "../db-connection.js";
3
+ import { withCrashRecovery } from "./validate-migration-state.js";
4
+
5
+ const CHECKPOINT_KEY = "migration_llm_request_logs_created_at_index_v1";
6
+
7
+ /**
8
+ * Add an index on `llm_request_logs.created_at` so time-range deletes
9
+ * (used by the log-pruning GC job) can scan efficiently without a full
10
+ * table scan.
11
+ */
12
+ export function migrateLlmRequestLogsCreatedAtIndex(database: DrizzleDb): void {
13
+ withCrashRecovery(database, CHECKPOINT_KEY, () => {
14
+ const raw = getSqliteFrom(database);
15
+ raw.exec(
16
+ `CREATE INDEX IF NOT EXISTS idx_llm_request_logs_created_at ON llm_request_logs(created_at)`,
17
+ );
18
+ });
19
+ }
@@ -146,6 +146,14 @@ export { migrateCreateMemoryGraphTables } from "./202-memory-graph-tables.js";
146
146
  export { migrateDropMemoryItemsTables } from "./203-drop-memory-items-tables.js";
147
147
  export { migrateRenameMemoryGraphTypeValues } from "./204-rename-memory-graph-type-values.js";
148
148
  export { migrateMemoryGraphImageRefs } from "./205-memory-graph-image-refs.js";
149
+ export { migrateCreateMemoryGraphNodeEdits } from "./206-memory-graph-node-edits.js";
150
+ export { migrateScrubCorruptedImageAttachments } from "./206-scrub-corrupted-image-attachments.js";
151
+ export { migrateCreateConversationGraphMemoryState } from "./207-conversation-graph-memory-state.js";
152
+ export { migrateConversationsLastMessageAt } from "./208-conversations-last-message-at.js";
153
+ export { migrateStripThinkingFromConsolidated } from "./209-strip-thinking-from-consolidated.js";
154
+ export { migrateScheduleReuseConversation } from "./210-schedule-reuse-conversation.js";
155
+ export { migrateMemoryRecallLogsQueryContext } from "./211-memory-recall-logs-query-context.js";
156
+ export { migrateLlmRequestLogsCreatedAtIndex } from "./212-llm-request-logs-created-at-index.js";
149
157
  export {
150
158
  MIGRATION_REGISTRY,
151
159
  type MigrationRegistryEntry,
@@ -40,6 +40,7 @@ import { migrateBackfillAudioAttachmentMimeTypesDown } from "./191-backfill-audi
40
40
  import { migrateAddSourceTypeColumnsDown } from "./193-add-source-type-columns.js";
41
41
  import { migrateStripIntegrationPrefixFromProviderKeysDown } from "./196-strip-integration-prefix-from-provider-keys.js";
42
42
  import { migrateRenameMemoryGraphTypeValuesDown } from "./204-rename-memory-graph-type-values.js";
43
+ import { migrateScrubCorruptedImageAttachmentsDown } from "./206-scrub-corrupted-image-attachments.js";
43
44
 
44
45
  export interface MigrationRegistryEntry {
45
46
  /** The checkpoint key written to memory_checkpoints on completion. */
@@ -349,6 +350,13 @@ export const MIGRATION_REGISTRY: MigrationRegistryEntry[] = [
349
350
  "Rename legacy memory graph node type values: style → behavioral, relationship → semantic",
350
351
  down: migrateRenameMemoryGraphTypeValuesDown,
351
352
  },
353
+ {
354
+ key: "migration_scrub_corrupted_image_attachments_v1",
355
+ version: 40,
356
+ description:
357
+ "Remove image attachments containing HTML error pages instead of image data",
358
+ down: migrateScrubCorruptedImageAttachmentsDown,
359
+ },
352
360
  ];
353
361
 
354
362
  export function getMaxMigrationVersion(): number {