@vellumai/assistant 0.8.1 → 0.8.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 (506) hide show
  1. package/ARCHITECTURE.md +2 -7
  2. package/Dockerfile +75 -1
  3. package/bun.lock +11 -1
  4. package/docker-entrypoint.sh +5 -0
  5. package/docker-init-apt-root.sh +94 -0
  6. package/docker-kata-apt-env.sh +39 -0
  7. package/docs/plugins.md +88 -47
  8. package/docs/skills.md +9 -7
  9. package/examples/plugins/echo/README.md +27 -27
  10. package/examples/plugins/echo/package.json +3 -0
  11. package/examples/plugins/echo/register.ts +31 -31
  12. package/node_modules/@vellumai/slack-text/src/index.test.ts +114 -14
  13. package/node_modules/@vellumai/slack-text/src/index.ts +82 -18
  14. package/openapi.yaml +325 -3
  15. package/package.json +3 -1
  16. package/scripts/generate-openapi.ts +83 -10
  17. package/scripts/sync-llm-catalog.ts +2 -2
  18. package/scripts/sync-web-search-catalog.ts +47 -25
  19. package/src/__tests__/agent-image-optimize.test.ts +11 -3
  20. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +131 -0
  21. package/src/__tests__/anthropic-provider.test.ts +45 -0
  22. package/src/__tests__/app-builder-tool-scripts.test.ts +9 -3
  23. package/src/__tests__/app-executors.test.ts +220 -4
  24. package/src/__tests__/auto-analysis-end-to-end.test.ts +35 -0
  25. package/src/__tests__/bundled-asset.test.ts +6 -6
  26. package/src/__tests__/channel-availability-routes.test.ts +206 -0
  27. package/src/__tests__/channel-delivery-store.test.ts +289 -1
  28. package/src/__tests__/circuit-breaker-pipeline.test.ts +0 -1
  29. package/src/__tests__/clawhub.test.ts +75 -16
  30. package/src/__tests__/compactor-tail-resolution.test.ts +41 -0
  31. package/src/__tests__/config-schema.test.ts +21 -0
  32. package/src/__tests__/config-set-route.test.ts +80 -0
  33. package/src/__tests__/config-sounds-sync.test.ts +97 -0
  34. package/src/__tests__/config-watcher-skill-reseed.test.ts +453 -0
  35. package/src/__tests__/context-search-conversations-source.test.ts +117 -2
  36. package/src/__tests__/context-search-memory-v2-source.test.ts +0 -1
  37. package/src/__tests__/context-search-workspace-source.test.ts +7 -0
  38. package/src/__tests__/context-token-estimator.test.ts +1 -0
  39. package/src/__tests__/conversation-abort-tool-results.test.ts +4 -1
  40. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +1 -0
  41. package/src/__tests__/conversation-agent-loop-overflow.test.ts +92 -92
  42. package/src/__tests__/conversation-agent-loop.test.ts +2 -0
  43. package/src/__tests__/conversation-error.test.ts +42 -3
  44. package/src/__tests__/conversation-fork-crud.test.ts +82 -0
  45. package/src/__tests__/conversation-inference-profile-route.test.ts +40 -4
  46. package/src/__tests__/conversation-lifecycle.test.ts +173 -0
  47. package/src/__tests__/conversation-message-sync-tags.test.ts +97 -0
  48. package/src/__tests__/conversation-pairing.test.ts +54 -0
  49. package/src/__tests__/conversation-process-callsite.test.ts +4 -1
  50. package/src/__tests__/conversation-provider-retry-repair.test.ts +5 -1
  51. package/src/__tests__/conversation-queue.test.ts +4 -1
  52. package/src/__tests__/conversation-runtime-assembly.test.ts +76 -9
  53. package/src/__tests__/conversation-slash-queue.test.ts +59 -1
  54. package/src/__tests__/conversation-slash-unknown.test.ts +4 -1
  55. package/src/__tests__/conversation-surfaces-table-action.test.ts +360 -0
  56. package/src/__tests__/conversation-sync-tags.test.ts +235 -0
  57. package/src/__tests__/conversation-workspace-injection.test.ts +5 -1
  58. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +5 -1
  59. package/src/__tests__/credential-security-invariants.test.ts +3 -2
  60. package/src/__tests__/db-slack-external-content-normalization.test.ts +301 -0
  61. package/src/__tests__/delete-managed-skill-tool.test.ts +55 -13
  62. package/src/__tests__/disk-pressure-tools.test.ts +1 -0
  63. package/src/__tests__/dm-backfill.test.ts +121 -10
  64. package/src/__tests__/document-tool-security.test.ts +258 -0
  65. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +0 -1
  66. package/src/__tests__/edit-propagation.test.ts +33 -0
  67. package/src/__tests__/empty-response-pipeline.test.ts +0 -4
  68. package/src/__tests__/external-plugin-loader.test.ts +60 -36
  69. package/src/__tests__/filing-service.test.ts +140 -0
  70. package/src/__tests__/get-skill-detail-audit.test.ts +0 -4
  71. package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +43 -62
  72. package/src/__tests__/helpers/tar-fixtures.ts +39 -0
  73. package/src/__tests__/helpers/wait-for.ts +21 -0
  74. package/src/__tests__/history-repair-pipeline.test.ts +0 -3
  75. package/src/__tests__/history-repair.test.ts +73 -0
  76. package/src/__tests__/host-app-control-proxy.test.ts +266 -10
  77. package/src/__tests__/image-credentials.test.ts +1 -1
  78. package/src/__tests__/inbound-slack-persistence.test.ts +2 -0
  79. package/src/__tests__/inference-no-mode-boot-e2e.test.ts +1 -1
  80. package/src/__tests__/inference-profile-reaper.test.ts +4 -2
  81. package/src/__tests__/inference-profile-session-handler.test.ts +18 -6
  82. package/src/__tests__/inference-profile-session-ipc.test.ts +17 -5
  83. package/src/__tests__/injector-chain.test.ts +10 -8
  84. package/src/__tests__/install-skill-routing.test.ts +155 -37
  85. package/src/__tests__/lifecycle-memory-v2-seed.test.ts +92 -3
  86. package/src/__tests__/list-messages-page-latest.test.ts +55 -0
  87. package/src/__tests__/llm-call-pipeline.test.ts +0 -3
  88. package/src/__tests__/llm-catalog-parity.test.ts +55 -13
  89. package/src/__tests__/llm-request-log-source-clickhouse.test.ts +34 -0
  90. package/src/__tests__/llm-request-log-source-factory.test.ts +29 -53
  91. package/src/__tests__/llm-usage-store.test.ts +114 -0
  92. package/src/__tests__/managed-profile-guard.test.ts +31 -29
  93. package/src/__tests__/managed-skill-lifecycle.test.ts +109 -18
  94. package/src/__tests__/managed-store.test.ts +84 -192
  95. package/src/__tests__/media-generate-image.test.ts +1 -1
  96. package/src/__tests__/memory-retrieval-pipeline.test.ts +0 -2
  97. package/src/__tests__/messages-after-tiebreaker.test.ts +122 -0
  98. package/src/__tests__/oauth-commands-routes.test.ts +168 -16
  99. package/src/__tests__/oauth-provider-profiles.test.ts +9 -0
  100. package/src/__tests__/openai-provider.test.ts +24 -0
  101. package/src/__tests__/openai-responses-cutover-guard.test.ts +17 -9
  102. package/src/__tests__/overflow-reduce-pipeline.test.ts +0 -2
  103. package/src/__tests__/persistence-pipeline.test.ts +0 -2
  104. package/src/__tests__/{managed-proxy-context.test.ts → platform-proxy-context.test.ts} +1 -1
  105. package/src/__tests__/platform.test.ts +2 -0
  106. package/src/__tests__/plugin-api-shim.test.ts +125 -0
  107. package/src/__tests__/plugin-bootstrap.test.ts +10 -36
  108. package/src/__tests__/plugin-external-api.test.ts +68 -0
  109. package/src/__tests__/plugin-registry.test.ts +0 -77
  110. package/src/__tests__/plugin-route-contribution.test.ts +0 -1
  111. package/src/__tests__/plugin-skill-contribution.test.ts +0 -2
  112. package/src/__tests__/plugin-tool-contribution.test.ts +16 -15
  113. package/src/__tests__/plugin-types.test.ts +3 -13
  114. package/src/__tests__/process-message-background-slack.test.ts +8 -1
  115. package/src/__tests__/process-message-display-content.test.ts +421 -0
  116. package/src/__tests__/provider-catalog-visibility.test.ts +142 -0
  117. package/src/__tests__/provider-error-scenarios.test.ts +111 -0
  118. package/src/__tests__/{provider-managed-proxy-integration.test.ts → provider-platform-proxy-integration.test.ts} +8 -8
  119. package/src/__tests__/scaffold-managed-skill-tool.test.ts +65 -13
  120. package/src/__tests__/schedule-routes.test.ts +50 -3
  121. package/src/__tests__/schedule-store.test.ts +94 -0
  122. package/src/__tests__/scheduler-reuse-conversation.test.ts +54 -7
  123. package/src/__tests__/schema-transforms.test.ts +20 -0
  124. package/src/__tests__/search-skills-unified.test.ts +0 -5
  125. package/src/__tests__/server-history-render.test.ts +43 -0
  126. package/src/__tests__/skill-load-feature-flag.test.ts +0 -12
  127. package/src/__tests__/skill-load-tool.test.ts +27 -89
  128. package/src/__tests__/skill-memory.test.ts +23 -3
  129. package/src/__tests__/skills-file-content-endpoint.test.ts +9 -38
  130. package/src/__tests__/skills-files-catalog-fallback.test.ts +0 -3
  131. package/src/__tests__/skills-install-extract.test.ts +49 -38
  132. package/src/__tests__/skills-install-staging.test.ts +159 -0
  133. package/src/__tests__/skills-uninstall.test.ts +9 -41
  134. package/src/__tests__/skills.test.ts +51 -58
  135. package/src/__tests__/slack-channel-config.test.ts +9 -0
  136. package/src/__tests__/subagent-tool-filtering.test.ts +50 -0
  137. package/src/__tests__/system-prompt.test.ts +737 -63
  138. package/src/__tests__/terminal-tools.test.ts +28 -1
  139. package/src/__tests__/thread-backfill.test.ts +557 -27
  140. package/src/__tests__/title-generate-pipeline.test.ts +0 -13
  141. package/src/__tests__/token-estimate-pipeline.test.ts +0 -3
  142. package/src/__tests__/tool-error-pipeline.test.ts +0 -3
  143. package/src/__tests__/tool-execute-pipeline.test.ts +0 -5
  144. package/src/__tests__/tool-executor-lifecycle-events.test.ts +1 -1
  145. package/src/__tests__/tool-executor.test.ts +16 -4
  146. package/src/__tests__/tool-result-truncate-pipeline.test.ts +0 -12
  147. package/src/__tests__/turn-events-store.test.ts +256 -0
  148. package/src/__tests__/twilio-routes.test.ts +4 -0
  149. package/src/__tests__/user-plugin-loader.test.ts +0 -7
  150. package/src/__tests__/voice-session-bridge.test.ts +198 -0
  151. package/src/__tests__/web-search-catalog-parity.test.ts +32 -10
  152. package/src/__tests__/workspace-migration-057-repair-stale-gemini-model-ids.test.ts +115 -3
  153. package/src/__tests__/workspace-migration-072-seed-reply-suggestion-callsite.test.ts +50 -0
  154. package/src/__tests__/workspace-migration-073-repair-recall-callsite-empty-profile.test.ts +153 -0
  155. package/src/__tests__/workspace-migration-085-memory-v2-bm25-b-reembed-disabled-v2-pages.test.ts +220 -0
  156. package/src/__tests__/workspace-migration-086-revert-stale-gemini-mis-rewrites.test.ts +269 -0
  157. package/src/__tests__/workspace-migration-remove-legacy-skills-index.test.ts +309 -0
  158. package/src/__tests__/workspace-migrations-runner.test.ts +111 -3
  159. package/src/acp/resolve-agent.ts +1 -1
  160. package/src/agent/image-optimize.ts +13 -5
  161. package/src/calls/voice-session-bridge.ts +61 -42
  162. package/src/channels/types.ts +108 -0
  163. package/src/cli/__tests__/unknown-command.test.ts +24 -0
  164. package/src/cli/commands/__tests__/changelog.test.ts +304 -319
  165. package/src/cli/commands/__tests__/schedules.test.ts +491 -0
  166. package/src/cli/commands/changelog.ts +106 -42
  167. package/src/cli/commands/conversations.ts +102 -17
  168. package/src/cli/commands/default-action.ts +10 -53
  169. package/src/cli/commands/notifications.ts +329 -317
  170. package/src/cli/commands/plugins.ts +185 -0
  171. package/src/cli/commands/schedules.ts +391 -0
  172. package/src/cli/commands/telemetry.ts +40 -0
  173. package/src/cli/lib/__tests__/cli-colors.test.ts +48 -0
  174. package/src/cli/lib/__tests__/confirm-prompt.test.ts +159 -0
  175. package/src/cli/lib/__tests__/install-from-github.test.ts +355 -0
  176. package/src/cli/lib/__tests__/list-installed-plugins.test.ts +154 -0
  177. package/src/cli/lib/__tests__/uninstall-plugin.test.ts +124 -0
  178. package/src/cli/lib/__tests__/unknown-command.test.ts +106 -0
  179. package/src/cli/lib/cli-colors.ts +12 -0
  180. package/src/cli/lib/confirm-prompt.ts +79 -0
  181. package/src/cli/lib/install-from-github.ts +304 -0
  182. package/src/cli/lib/list-installed-plugins.ts +137 -0
  183. package/src/cli/lib/uninstall-plugin.ts +82 -0
  184. package/src/cli/lib/unknown-command.ts +111 -0
  185. package/src/cli/program.ts +38 -2
  186. package/src/config/bundled-skills/app-builder/SKILL.md +23 -21
  187. package/src/config/bundled-skills/app-builder/TOOLS.json +7 -0
  188. package/src/config/bundled-skills/computer-use/TOOLS.json +15 -52
  189. package/src/config/bundled-skills/document/SKILL.md +23 -3
  190. package/src/config/bundled-skills/document/TOOLS.json +53 -0
  191. package/src/config/bundled-skills/document/tools/document-delete.ts +12 -0
  192. package/src/config/bundled-skills/document/tools/document-list.ts +12 -0
  193. package/src/config/bundled-skills/document/tools/document-read.ts +12 -0
  194. package/src/config/bundled-skills/skill-management/SKILL.md +2 -2
  195. package/src/config/bundled-skills/skill-management/TOOLS.json +7 -7
  196. package/src/config/bundled-tool-registry.ts +6 -0
  197. package/src/config/feature-flag-registry.json +41 -1
  198. package/src/config/loader.ts +64 -38
  199. package/src/config/schema.ts +7 -10
  200. package/src/config/schemas/__tests__/llm-request-logs.test.ts +36 -0
  201. package/src/config/schemas/channels.ts +8 -0
  202. package/src/config/schemas/compaction.ts +28 -0
  203. package/src/config/schemas/heartbeat.ts +9 -0
  204. package/src/config/schemas/llm-request-logs.ts +31 -7
  205. package/src/config/schemas/llm.ts +3 -0
  206. package/src/config/schemas/memory-retrieval.ts +18 -0
  207. package/src/config/schemas/tools.ts +14 -0
  208. package/src/config/skills.ts +3 -96
  209. package/src/context/compactor.ts +1047 -0
  210. package/src/context/token-estimator.ts +2 -2
  211. package/src/context/window-manager.ts +197 -1520
  212. package/src/credential-execution/managed-catalog.ts +37 -0
  213. package/src/credential-health/credential-health-service.ts +280 -19
  214. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +34 -0
  215. package/src/daemon/__tests__/conversation-tool-setup-exclude.test.ts +138 -0
  216. package/src/daemon/__tests__/conversation-tool-setup.test.ts +74 -0
  217. package/src/daemon/approval-generators.ts +8 -6
  218. package/src/daemon/config-watcher.ts +94 -31
  219. package/src/daemon/conversation-agent-loop.ts +169 -9
  220. package/src/daemon/conversation-error.ts +171 -37
  221. package/src/daemon/conversation-lifecycle.ts +53 -40
  222. package/src/daemon/conversation-messaging.ts +25 -6
  223. package/src/daemon/conversation-process.ts +49 -12
  224. package/src/daemon/conversation-runtime-assembly.ts +16 -1
  225. package/src/daemon/conversation-slash.ts +12 -5
  226. package/src/daemon/conversation-store.ts +11 -4
  227. package/src/daemon/conversation-tool-setup.ts +39 -7
  228. package/src/daemon/conversation.ts +33 -1
  229. package/src/daemon/external-plugins-bootstrap.ts +217 -181
  230. package/src/daemon/first-greeting.ts +22 -2
  231. package/src/daemon/handlers/config-model.ts +6 -5
  232. package/src/daemon/handlers/config-slack-channel.ts +15 -3
  233. package/src/daemon/handlers/shared.ts +14 -5
  234. package/src/daemon/handlers/skills.ts +111 -108
  235. package/src/daemon/history-repair.ts +28 -1
  236. package/src/daemon/host-app-control-proxy.ts +98 -23
  237. package/src/daemon/lifecycle.ts +45 -35
  238. package/src/daemon/meet-host-supervisor.ts +5 -4
  239. package/src/daemon/memory-v2-startup.ts +49 -0
  240. package/src/daemon/message-protocol.ts +1 -0
  241. package/src/daemon/message-types/conversations.ts +25 -0
  242. package/src/daemon/message-types/messages.ts +61 -0
  243. package/src/daemon/message-types/subagents.ts +1 -0
  244. package/src/daemon/message-types/sync.ts +1 -0
  245. package/src/daemon/pkb-reminder-builder.test.ts +1 -1
  246. package/src/daemon/pkb-reminder-builder.ts +1 -1
  247. package/src/daemon/plugin-source-watcher.ts +146 -0
  248. package/src/daemon/process-message.ts +21 -3
  249. package/src/daemon/server.ts +11 -2
  250. package/src/daemon/skill-memory-refresh.ts +29 -0
  251. package/src/documents/document-store.ts +221 -3
  252. package/src/embedded/plugin-api.ts +40 -0
  253. package/src/filing/filing-service.ts +39 -0
  254. package/src/heartbeat/__tests__/heartbeat-service.test.ts +91 -6
  255. package/src/heartbeat/heartbeat-run-store.ts +2 -1
  256. package/src/heartbeat/heartbeat-service.ts +41 -0
  257. package/src/home/__tests__/feed-types.test.ts +40 -0
  258. package/src/home/feed-types.ts +22 -0
  259. package/src/home/post-connect-feed.ts +1 -0
  260. package/src/index.ts +18 -1
  261. package/src/live-voice/__tests__/live-voice-stt.test.ts +57 -0
  262. package/src/mcp/client.ts +20 -4
  263. package/src/media/image-credentials.ts +3 -3
  264. package/src/memory/__tests__/bookmark-crud.test.ts +33 -27
  265. package/src/memory/__tests__/conversation-queries.test.ts +263 -0
  266. package/src/memory/__tests__/jobs-worker-v2-graph-trigger-embed.test.ts +113 -0
  267. package/src/memory/__tests__/memory-retrospective-startup-cleanup.test.ts +119 -14
  268. package/src/memory/__tests__/message-content.test.ts +35 -0
  269. package/src/memory/bookmark-crud.ts +42 -10
  270. package/src/memory/context-search/sources/conversations.ts +62 -2
  271. package/src/memory/context-search/sources/workspace.ts +4 -0
  272. package/src/memory/conversation-crud.ts +63 -19
  273. package/src/memory/conversation-queries.ts +110 -10
  274. package/src/memory/db-init.ts +6 -0
  275. package/src/memory/delivery-crud.ts +152 -5
  276. package/src/memory/embedding-backend.ts +4 -4
  277. package/src/memory/external-conversation-store.ts +66 -5
  278. package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +66 -9
  279. package/src/memory/graph/conversation-graph-memory.ts +31 -15
  280. package/src/memory/graph/tools.ts +3 -3
  281. package/src/memory/indexer.ts +34 -29
  282. package/src/memory/jobs/__tests__/embed-concept-page.test.ts +73 -0
  283. package/src/memory/jobs/embed-concept-page.ts +20 -11
  284. package/src/memory/jobs-worker.ts +6 -1
  285. package/src/memory/llm-request-log-source-clickhouse.ts +17 -10
  286. package/src/memory/llm-request-log-source.ts +19 -52
  287. package/src/memory/llm-usage-store.ts +125 -5
  288. package/src/memory/memory-retrospective-startup-cleanup.ts +72 -5
  289. package/src/memory/message-content.ts +1 -1
  290. package/src/memory/migrations/109-external-conversation-bindings.ts +15 -4
  291. package/src/memory/migrations/229-delete-private-conversations.test.ts +38 -1
  292. package/src/memory/migrations/229-delete-private-conversations.ts +7 -0
  293. package/src/memory/migrations/247-external-conversation-binding-thread-id.ts +78 -0
  294. package/src/memory/migrations/248-create-onboarding-events.ts +21 -0
  295. package/src/memory/migrations/249-normalize-slack-external-content.ts +240 -0
  296. package/src/memory/migrations/index.ts +6 -0
  297. package/src/memory/migrations/registry.ts +8 -0
  298. package/src/memory/onboarding-events-store.ts +106 -0
  299. package/src/memory/schema/bookmarks.ts +0 -2
  300. package/src/memory/schema/calls.ts +1 -0
  301. package/src/memory/schema/inference.ts +1 -3
  302. package/src/memory/schema/infrastructure.ts +12 -0
  303. package/src/memory/turn-events-store.ts +127 -2
  304. package/src/memory/v2/__tests__/activation.test.ts +0 -8
  305. package/src/memory/v2/__tests__/injection.test.ts +98 -8
  306. package/src/memory/v2/__tests__/migration.test.ts +87 -0
  307. package/src/memory/v2/__tests__/page-index.test.ts +83 -0
  308. package/src/memory/v2/__tests__/prompts-router.test.ts +58 -6
  309. package/src/memory/v2/__tests__/qdrant.test.ts +66 -3
  310. package/src/memory/v2/__tests__/router.test.ts +15 -0
  311. package/src/memory/v2/__tests__/skill-store.test.ts +387 -8
  312. package/src/memory/v2/injection.ts +32 -6
  313. package/src/memory/v2/migration.ts +49 -19
  314. package/src/memory/v2/page-index.ts +35 -5
  315. package/src/memory/v2/prompts/router.ts +11 -8
  316. package/src/memory/v2/prompts/sweep.ts +2 -2
  317. package/src/memory/v2/qdrant.ts +135 -7
  318. package/src/memory/v2/router.ts +9 -8
  319. package/src/memory/v2/skill-store.ts +120 -35
  320. package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +45 -5
  321. package/src/messaging/providers/slack/__tests__/download.test.ts +231 -0
  322. package/src/messaging/providers/slack/adapter.ts +43 -5
  323. package/src/messaging/providers/slack/client.ts +27 -0
  324. package/src/messaging/providers/slack/deep-link.ts +65 -0
  325. package/src/messaging/providers/slack/download.ts +104 -0
  326. package/src/messaging/providers/slack/message-metadata.test.ts +32 -0
  327. package/src/messaging/providers/slack/message-metadata.ts +27 -0
  328. package/src/messaging/providers/slack/render-transcript.test.ts +134 -0
  329. package/src/messaging/providers/slack/render-transcript.ts +69 -5
  330. package/src/messaging/providers/slack/types.ts +20 -1
  331. package/src/notifications/conversation-pairing.ts +2 -1
  332. package/src/notifications/decision-engine.ts +2 -1
  333. package/src/notifications/emit-signal.ts +20 -1
  334. package/src/notifications/home-feed-side-effect.ts +54 -0
  335. package/src/notifications/signal.ts +3 -1
  336. package/src/oauth/connection-resolver.ts +8 -4
  337. package/src/oauth/platform-connection.ts +6 -2
  338. package/src/oauth/seed-providers.ts +10 -1
  339. package/src/permissions/checker.ts +2 -0
  340. package/src/permissions/ipc-risk-types.ts +1 -0
  341. package/src/permissions/question-prompter.test.ts +416 -0
  342. package/src/permissions/question-prompter.ts +294 -0
  343. package/src/platform/client.test.ts +1 -1
  344. package/src/platform/client.ts +1 -1
  345. package/src/plugin-api/constants.ts +26 -0
  346. package/src/plugin-api/index.ts +34 -1
  347. package/src/plugin-api/types.ts +104 -22
  348. package/src/plugins/defaults/circuit-breaker.ts +0 -5
  349. package/src/plugins/defaults/compaction.ts +0 -4
  350. package/src/plugins/defaults/empty-response.ts +0 -2
  351. package/src/plugins/defaults/history-repair.ts +0 -2
  352. package/src/plugins/defaults/injectors.ts +36 -3
  353. package/src/plugins/defaults/llm-call.ts +0 -2
  354. package/src/plugins/defaults/memory-retrieval.ts +0 -1
  355. package/src/plugins/defaults/overflow-reduce.ts +0 -1
  356. package/src/plugins/defaults/persistence.ts +0 -2
  357. package/src/plugins/defaults/title-generate.ts +0 -5
  358. package/src/plugins/defaults/token-estimate.ts +0 -2
  359. package/src/plugins/defaults/tool-error.ts +0 -7
  360. package/src/plugins/defaults/tool-execute.ts +0 -2
  361. package/src/plugins/defaults/tool-result-truncate.ts +0 -4
  362. package/src/plugins/ensure-plugin-api-shim.ts +96 -0
  363. package/src/plugins/external-api.ts +104 -0
  364. package/src/plugins/external-plugin-loader.ts +105 -32
  365. package/src/plugins/feature-gate.ts +22 -0
  366. package/src/plugins/pipeline.ts +37 -0
  367. package/src/plugins/registry.ts +48 -80
  368. package/src/plugins/types.ts +31 -26
  369. package/src/plugins/user-loader.ts +21 -2
  370. package/src/proactive-artifact/aux-message-injector.ts +11 -0
  371. package/src/proactive-artifact/job.test.ts +37 -5
  372. package/src/prompts/__tests__/system-prompt.test.ts +12 -0
  373. package/src/prompts/__tests__/task-progress-hint-section.test.ts +99 -0
  374. package/src/prompts/normalize-onboarding.ts +27 -0
  375. package/src/prompts/sections.ts +302 -0
  376. package/src/prompts/system-prompt.ts +63 -166
  377. package/src/prompts/templates/BOOTSTRAP.md +17 -1
  378. package/src/prompts/templates/system-sections.ts +173 -0
  379. package/src/providers/__tests__/inference.test.ts +22 -7
  380. package/src/providers/anthropic/client.ts +28 -28
  381. package/src/providers/connection-resolution.ts +7 -0
  382. package/src/providers/inference/adapter-factory.ts +41 -4
  383. package/src/providers/inference/connections.ts +74 -29
  384. package/src/providers/inference/resolve-auth.ts +12 -4
  385. package/src/providers/model-catalog.ts +294 -12
  386. package/src/providers/openai/chat-completions-provider.ts +10 -2
  387. package/src/providers/openrouter/client.ts +7 -0
  388. package/src/providers/{managed-proxy → platform-proxy}/constants.ts +4 -1
  389. package/src/providers/{managed-proxy → platform-proxy}/context.ts +3 -3
  390. package/src/providers/provider-availability.ts +17 -2
  391. package/src/providers/provider-catalog-visibility.ts +36 -0
  392. package/src/providers/registry.ts +22 -14
  393. package/src/providers/retry.ts +47 -1
  394. package/src/runtime/__tests__/agent-wake.test.ts +152 -0
  395. package/src/runtime/agent-wake.ts +42 -14
  396. package/src/runtime/auth/route-policy.ts +8 -1
  397. package/src/runtime/btw-sidechain.ts +2 -0
  398. package/src/runtime/http-types.ts +19 -0
  399. package/src/runtime/migrations/origin-mode.ts +1 -1
  400. package/src/runtime/pending-interactions.ts +1 -0
  401. package/src/runtime/routes/__tests__/bookmark-routes.test.ts +17 -0
  402. package/src/runtime/routes/__tests__/conversation-management-routes.test.ts +5 -1
  403. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +107 -20
  404. package/src/runtime/routes/__tests__/question-routes.test.ts +395 -0
  405. package/src/runtime/routes/__tests__/tts-routes.test.ts +64 -1
  406. package/src/runtime/routes/acp-routes-list.test.ts +143 -0
  407. package/src/runtime/routes/acp-routes.ts +5 -3
  408. package/src/runtime/routes/auth-routes.ts +1 -1
  409. package/src/runtime/routes/bookmark-routes.ts +5 -3
  410. package/src/runtime/routes/btw-routes.ts +5 -1
  411. package/src/runtime/routes/channel-availability-routes.ts +121 -0
  412. package/src/runtime/routes/conversation-cli-routes.ts +44 -3
  413. package/src/runtime/routes/conversation-list-routes.ts +3 -20
  414. package/src/runtime/routes/conversation-management-routes.ts +17 -42
  415. package/src/runtime/routes/conversation-query-routes.ts +40 -35
  416. package/src/runtime/routes/conversation-routes.ts +90 -11
  417. package/src/runtime/routes/documents-routes.ts +25 -86
  418. package/src/runtime/routes/group-routes.ts +5 -0
  419. package/src/runtime/routes/inbound-conversation.ts +28 -8
  420. package/src/runtime/routes/inbound-message-handler.ts +236 -41
  421. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +111 -0
  422. package/src/runtime/routes/inbound-stages/background-dispatch.ts +32 -1
  423. package/src/runtime/routes/inbound-stages/edit-intercept.ts +17 -4
  424. package/src/runtime/routes/index.ts +6 -0
  425. package/src/runtime/routes/inference-profile-session-handler.ts +17 -44
  426. package/src/runtime/routes/inference-profile-session-reaper.ts +7 -21
  427. package/src/runtime/routes/inference-provider-connection-routes.ts +65 -21
  428. package/src/runtime/routes/integrations/slack/share.ts +4 -52
  429. package/src/runtime/routes/integrations/slack/token.ts +43 -0
  430. package/src/runtime/routes/integrations/twilio.ts +6 -13
  431. package/src/runtime/routes/notification-routes.ts +1 -1
  432. package/src/runtime/routes/oauth-commands-routes.ts +105 -15
  433. package/src/runtime/routes/oauth-lifecycle-routes.ts +43 -0
  434. package/src/runtime/routes/question-routes.ts +259 -0
  435. package/src/runtime/routes/rename-conversation-routes.ts +2 -33
  436. package/src/runtime/routes/schedule-routes.ts +4 -7
  437. package/src/runtime/routes/subagents-routes.ts +57 -18
  438. package/src/runtime/routes/telemetry-routes.ts +27 -0
  439. package/src/runtime/routes/tts-routes.ts +27 -2
  440. package/src/runtime/routes/workspace-routes.test.ts +43 -0
  441. package/src/runtime/routes/workspace-routes.ts +28 -0
  442. package/src/runtime/services/conversation-serializer.ts +39 -7
  443. package/src/runtime/sync/resource-sync-events.ts +93 -1
  444. package/src/schedule/schedule-store.ts +27 -2
  445. package/src/schedule/scheduler.ts +9 -1
  446. package/src/security/__tests__/untrusted-content.test.ts +86 -0
  447. package/src/security/untrusted-content.ts +93 -8
  448. package/src/skills/catalog-files.ts +1 -1
  449. package/src/skills/catalog-install.ts +233 -116
  450. package/src/skills/clawhub.ts +70 -13
  451. package/src/skills/managed-store.ts +4 -119
  452. package/src/skills/skillssh-registry.ts +27 -48
  453. package/src/subagent/manager.ts +15 -7
  454. package/src/telemetry/types.ts +113 -1
  455. package/src/telemetry/usage-telemetry-reporter.test.ts +312 -5
  456. package/src/telemetry/usage-telemetry-reporter.ts +113 -7
  457. package/src/tools/apps/executors.ts +58 -7
  458. package/src/tools/ask-question/ask-question-tool.test.ts +509 -0
  459. package/src/tools/ask-question/ask-question-tool.ts +304 -0
  460. package/src/tools/browser/browser-execution.ts +15 -11
  461. package/src/tools/computer-use/definitions.ts +3 -3
  462. package/src/tools/credentials/vault.ts +1 -1
  463. package/src/tools/document/document-tool.ts +124 -1
  464. package/src/tools/filesystem/edit.ts +1 -1
  465. package/src/tools/filesystem/list.ts +1 -1
  466. package/src/tools/filesystem/read.ts +1 -1
  467. package/src/tools/filesystem/write.ts +5 -2
  468. package/src/tools/host-filesystem/transfer.ts +1 -1
  469. package/src/tools/host-terminal/host-shell.ts +1 -1
  470. package/src/tools/permission-checker.ts +1 -1
  471. package/src/tools/registry.ts +17 -7
  472. package/src/tools/schedule/create.ts +2 -2
  473. package/src/tools/schema-transforms.ts +7 -2
  474. package/src/tools/side-effects.ts +1 -0
  475. package/src/tools/skills/delete-managed.ts +4 -4
  476. package/src/tools/skills/execute.ts +1 -1
  477. package/src/tools/skills/scaffold-managed.ts +3 -2
  478. package/src/tools/subagent/notify-parent.ts +1 -1
  479. package/src/tools/system/request-permission.ts +2 -2
  480. package/src/tools/terminal/safe-env.ts +60 -1
  481. package/src/tools/tool-manifest.ts +2 -0
  482. package/src/tools/types.ts +72 -21
  483. package/src/tools/ui-surface/definitions.ts +6 -5
  484. package/src/tts/__tests__/provider-adapters.test.ts +76 -2
  485. package/src/tts/providers/elevenlabs-provider.ts +75 -1
  486. package/src/types/onboarding-context.ts +2 -0
  487. package/src/util/errors.ts +17 -0
  488. package/src/util/platform.ts +10 -0
  489. package/src/watcher/__tests__/engine.test.ts +22 -0
  490. package/src/watcher/engine.ts +6 -2
  491. package/src/workspace/migrations/057-repair-stale-gemini-model-ids.ts +80 -15
  492. package/src/workspace/migrations/072-seed-reply-suggestion-callsite.ts +35 -22
  493. package/src/workspace/migrations/073-repair-recall-callsite-empty-profile.ts +3 -1
  494. package/src/workspace/migrations/083-system-prompt-prefix-to-file.ts +191 -0
  495. package/src/workspace/migrations/084-remove-legacy-skills-index.ts +276 -0
  496. package/src/workspace/migrations/085-memory-v2-bm25-b-reembed-disabled-v2-pages.ts +137 -0
  497. package/src/workspace/migrations/086-revert-stale-gemini-mis-rewrites.ts +198 -0
  498. package/src/workspace/migrations/registry.ts +8 -0
  499. package/src/workspace/migrations/runner.ts +39 -9
  500. package/src/workspace/migrations/types.ts +4 -0
  501. package/examples/plugins/echo/bun.lock +0 -25
  502. package/src/__tests__/context-window-manager.test.ts +0 -2481
  503. package/src/context/__tests__/compact-prompt.test.ts +0 -63
  504. package/src/context/prompts/compact.md +0 -26
  505. package/src/prompts/__tests__/build-cli-reference-section.test.ts +0 -37
  506. /package/src/__tests__/{secret-routes-managed-proxy.test.ts → secret-routes-platform-proxy.test.ts} +0 -0
@@ -1,4 +1,4 @@
1
- import { and, asc, desc, eq, gt, or } from "drizzle-orm";
1
+ import { and, asc, desc, eq, gt, or, sql } from "drizzle-orm";
2
2
  import { v4 as uuid } from "uuid";
3
3
 
4
4
  import type {
@@ -8,7 +8,7 @@ import type {
8
8
  } from "../usage/types.js";
9
9
  import { getDb } from "./db-connection.js";
10
10
  import { rawAll } from "./raw-query.js";
11
- import { llmUsageEvents } from "./schema.js";
11
+ import { conversations, llmUsageEvents } from "./schema.js";
12
12
  import {
13
13
  bucketEventsByDay,
14
14
  bucketEventsByHour,
@@ -123,15 +123,96 @@ export function listUsageEvents(options?: { limit?: number }): UsageEvent[] {
123
123
  return rows.map(rowToUsageEvent);
124
124
  }
125
125
 
126
+ /**
127
+ * Telemetry-flavoured `UsageEvent`: the persisted columns plus the two
128
+ * JOIN-computed conversation-level fields the reporter needs to emit
129
+ * for analytics (`avg turns per conversation`, `tokens on first turn`,
130
+ * foreground/background split on llm_usage rows themselves).
131
+ *
132
+ * Lives next to the query that produces it so the shape stays in lockstep
133
+ * with the SELECT; broader `UsageEvent` consumers stay untouched.
134
+ */
135
+ export interface UnreportedUsageEvent extends UsageEvent {
136
+ /**
137
+ * Type of the parent conversation (`"standard"` / `"background"` /
138
+ * `"scheduled"`). Null when the LLM call has no `conversationId`
139
+ * (memory consolidation, background embedding work, etc.) and so no
140
+ * `conversations` row to join against.
141
+ */
142
+ conversationType: string | null;
143
+ /**
144
+ * 1-indexed position of the user turn this LLM call belongs to within
145
+ * the parent conversation, counting only real user turns (tool-result
146
+ * rows persisted with role="user" are excluded — same filter as the
147
+ * turn-event eligibility predicate). Computed as the count of user
148
+ * messages with `created_at <= this LLM call's created_at` in the
149
+ * parent conversation. Null when there's no parent conversation, or
150
+ * when the LLM call fired before any user turn (rare — covers seed
151
+ * agent starts).
152
+ */
153
+ turnIndex: number | null;
154
+ }
155
+
126
156
  export function queryUnreportedUsageEvents(
127
157
  afterCreatedAt: number,
128
158
  afterId: string | undefined,
129
159
  limit: number,
130
- ): UsageEvent[] {
160
+ ): UnreportedUsageEvent[] {
131
161
  const db = getDb();
162
+ // JOIN to `conversations` to attach `conversationType`. LEFT JOIN
163
+ // because `llm_usage_events.conversationId` is nullable — calls that
164
+ // aren't tied to a conversation (memory consolidation, etc.) still
165
+ // need to flush through telemetry.
166
+ //
167
+ // `turnIndex` is a correlated subquery counting real user turns in
168
+ // the same conversation up to and including this LLM call's
169
+ // `created_at`. The filter mirrors `queryUnreportedTurnEvents` so the
170
+ // two indexes stay aligned: an LLM call fired during processing of
171
+ // turn N reports `turn_index = N`, matching what the turn event
172
+ // stream emitted for the triggering user message.
132
173
  const rows = db
133
- .select()
174
+ .select({
175
+ id: llmUsageEvents.id,
176
+ createdAt: llmUsageEvents.createdAt,
177
+ conversationId: llmUsageEvents.conversationId,
178
+ runId: llmUsageEvents.runId,
179
+ requestId: llmUsageEvents.requestId,
180
+ actor: llmUsageEvents.actor,
181
+ callSite: llmUsageEvents.callSite,
182
+ inferenceProfile: llmUsageEvents.inferenceProfile,
183
+ inferenceProfileSource: llmUsageEvents.inferenceProfileSource,
184
+ provider: llmUsageEvents.provider,
185
+ model: llmUsageEvents.model,
186
+ inputTokens: llmUsageEvents.inputTokens,
187
+ outputTokens: llmUsageEvents.outputTokens,
188
+ cacheCreationInputTokens: llmUsageEvents.cacheCreationInputTokens,
189
+ cacheReadInputTokens: llmUsageEvents.cacheReadInputTokens,
190
+ estimatedCostUsd: llmUsageEvents.estimatedCostUsd,
191
+ pricingStatus: llmUsageEvents.pricingStatus,
192
+ conversationType: conversations.conversationType,
193
+ // Null when conversationId is null (no parent conversation).
194
+ // Otherwise the count of eligible user turns up to and including
195
+ // this LLM call's createdAt. The COALESCE guard returns null
196
+ // (rather than 0) for the "no user turn yet" edge case so the
197
+ // analytics layer can distinguish "before-first-turn" LLM calls.
198
+ turnIndex: sql<number | null>`(
199
+ CASE WHEN ${llmUsageEvents.conversationId} IS NULL THEN NULL
200
+ ELSE (
201
+ SELECT COUNT(*) FROM messages AS m2
202
+ WHERE m2.conversation_id = ${llmUsageEvents.conversationId}
203
+ AND m2.role = 'user'
204
+ AND m2.content NOT LIKE '%"type":"tool\\_result"%' ESCAPE '\\'
205
+ AND m2.content NOT LIKE '%"type":"web\\_search\\_tool\\_result"%' ESCAPE '\\'
206
+ AND m2.created_at <= ${llmUsageEvents.createdAt}
207
+ )
208
+ END
209
+ )`.as("turn_index"),
210
+ })
134
211
  .from(llmUsageEvents)
212
+ .leftJoin(
213
+ conversations,
214
+ eq(llmUsageEvents.conversationId, conversations.id),
215
+ )
135
216
  .where(
136
217
  afterId
137
218
  ? or(
@@ -146,7 +227,15 @@ export function queryUnreportedUsageEvents(
146
227
  .orderBy(asc(llmUsageEvents.createdAt), asc(llmUsageEvents.id))
147
228
  .limit(limit)
148
229
  .all();
149
- return rows.map(rowToUsageEvent);
230
+ return rows.map((row) => ({
231
+ ...rowToUsageEvent(row),
232
+ conversationType: row.conversationType,
233
+ // SQLite returns COUNT(*) as 0 when no rows match; the CASE in the
234
+ // subquery already collapses the no-conversation case to NULL.
235
+ // Convert the integer column to `number | null` for the typed
236
+ // return value.
237
+ turnIndex: row.turnIndex === null ? null : Number(row.turnIndex),
238
+ }));
150
239
  }
151
240
 
152
241
  // ---------------------------------------------------------------------------
@@ -252,6 +341,37 @@ interface GroupRow {
252
341
  event_count: number;
253
342
  }
254
343
 
344
+ /**
345
+ * Return aggregate usage for a single conversation (e.g. a subagent).
346
+ */
347
+ export function getConversationUsageTotals(conversationId: string): {
348
+ inputTokens: number;
349
+ outputTokens: number;
350
+ estimatedCost: number;
351
+ } {
352
+ const rows = rawAll<{
353
+ total_input: number;
354
+ total_output: number;
355
+ total_cost: number | null;
356
+ }>(
357
+ /*sql*/ `
358
+ SELECT
359
+ COALESCE(SUM(input_tokens + COALESCE(cache_creation_input_tokens, 0) + COALESCE(cache_read_input_tokens, 0)), 0) AS total_input,
360
+ COALESCE(SUM(output_tokens), 0) AS total_output,
361
+ COALESCE(SUM(estimated_cost_usd), 0) AS total_cost
362
+ FROM llm_usage_events
363
+ WHERE conversation_id = ?1
364
+ `,
365
+ conversationId,
366
+ );
367
+ const row = rows[0];
368
+ return {
369
+ inputTokens: row.total_input,
370
+ outputTokens: row.total_output,
371
+ estimatedCost: row.total_cost ?? 0,
372
+ };
373
+ }
374
+
255
375
  /**
256
376
  * Return aggregate totals for all usage events within the given time range.
257
377
  */
@@ -24,8 +24,23 @@
24
24
  // conversation might be the active one. We're conservative and only
25
25
  // sweep when no job exists at all, since the worst-case false-positive
26
26
  // is leaving a few extra orphans for the next sweep to catch.)
27
+ // - AND the row is NOT the most-recent retrospective for its source
28
+ // conversation. The next retrospective run reads the most-recent prior
29
+ // retro via `findMostRecentRetrospectiveFor` to seed its
30
+ // `<already_remembered>` dedup block; sweeping it would force the
31
+ // next run to re-save facts the prior pass already captured.
27
32
 
28
- import { and, eq, inArray, isNotNull, lt, notInArray, sql } from "drizzle-orm";
33
+ import {
34
+ and,
35
+ eq,
36
+ inArray,
37
+ isNotNull,
38
+ isNull,
39
+ lt,
40
+ notInArray,
41
+ or,
42
+ sql,
43
+ } from "drizzle-orm";
29
44
 
30
45
  import { getLogger } from "../util/logger.js";
31
46
  import { deleteConversation } from "./conversation-crud.js";
@@ -53,7 +68,14 @@ export function sweepOrphanMemoryRetrospectiveConversations(
53
68
  const cutoff = now - ORPHAN_AGE_MS;
54
69
  const db = getDb();
55
70
 
56
- const activeJobConversationIds = db
71
+ // Job payloads encode the SOURCE conversation id (the conversation being
72
+ // analyzed), not the background-conversation id of the retrospective itself.
73
+ // The background conversation links back to its source via
74
+ // `forkParentConversationId` (set when bootstrapped — see
75
+ // memory-retrospective-job.ts). To protect in-flight jobs we therefore
76
+ // compare source-id to source-id by filtering on
77
+ // `conversations.forkParentConversationId`, not `conversations.id`.
78
+ const activeJobSourceConversationIds = db
57
79
  .select({
58
80
  conversationId: sql<string>`json_extract(${memoryJobs.payload}, '$.conversationId')`,
59
81
  })
@@ -68,6 +90,40 @@ export function sweepOrphanMemoryRetrospectiveConversations(
68
90
  .map((row) => row.conversationId)
69
91
  .filter((id): id is string => typeof id === "string" && id.length > 0);
70
92
 
93
+ // Compute the most-recent retro per source so we can preserve it.
94
+ // `findMostRecentRetrospectiveFor` (called by the next retrospective run)
95
+ // pulls dedup context from this row; sweeping it would re-introduce the
96
+ // unbounded-growth bug PR #30331 was created to fix.
97
+ const allRetros = db
98
+ .select({
99
+ id: conversations.id,
100
+ forkParentConversationId: conversations.forkParentConversationId,
101
+ createdAt: conversations.createdAt,
102
+ })
103
+ .from(conversations)
104
+ .where(
105
+ and(
106
+ eq(conversations.source, MEMORY_RETROSPECTIVE_SOURCE),
107
+ isNotNull(conversations.forkParentConversationId),
108
+ ),
109
+ )
110
+ .all();
111
+ const mostRecentPerSource = new Map<
112
+ string,
113
+ { id: string; createdAt: number }
114
+ >();
115
+ for (const row of allRetros) {
116
+ const parent = row.forkParentConversationId;
117
+ if (parent === null) continue;
118
+ const cur = mostRecentPerSource.get(parent);
119
+ if (!cur || row.createdAt > cur.createdAt) {
120
+ mostRecentPerSource.set(parent, { id: row.id, createdAt: row.createdAt });
121
+ }
122
+ }
123
+ const preservedIds = new Set(
124
+ Array.from(mostRecentPerSource.values(), (v) => v.id),
125
+ );
126
+
71
127
  const orphans = db
72
128
  .select({ id: conversations.id })
73
129
  .from(conversations)
@@ -79,12 +135,23 @@ export function sweepOrphanMemoryRetrospectiveConversations(
79
135
  // last_message_at value are too fresh to assess.
80
136
  isNotNull(conversations.lastMessageAt),
81
137
  lt(conversations.lastMessageAt, cutoff),
82
- activeJobConversationIds.length > 0
83
- ? notInArray(conversations.id, activeJobConversationIds)
138
+ activeJobSourceConversationIds.length > 0
139
+ ? // `forkParentConversationId` is nullable, and SQLite's
140
+ // `NULL NOT IN (...)` evaluates to unknown (falsy), so legacy
141
+ // rows with a null parent would never match. Include them
142
+ // explicitly so the sweep covers them.
143
+ or(
144
+ isNull(conversations.forkParentConversationId),
145
+ notInArray(
146
+ conversations.forkParentConversationId,
147
+ activeJobSourceConversationIds,
148
+ ),
149
+ )
84
150
  : sql`1=1`,
85
151
  ),
86
152
  )
87
- .all();
153
+ .all()
154
+ .filter((row) => !preservedIds.has(row.id));
88
155
 
89
156
  let swept = 0;
90
157
  for (const row of orphans) {
@@ -152,7 +152,7 @@ export function stringifyMessageContent(stored: string): string {
152
152
  return stored.trim();
153
153
  }
154
154
  if (typeof parsed === "string") return parsed.trim();
155
- if (!Array.isArray(parsed)) return "";
155
+ if (!Array.isArray(parsed)) return stored.trim();
156
156
  const parts: string[] = [];
157
157
  for (const block of parsed) {
158
158
  if (
@@ -1,8 +1,7 @@
1
1
  import type { DrizzleDb } from "../db-connection.js";
2
- import { migrateExtConvBindingsChannelChatUnique } from "./010-ext-conv-bindings-channel-chat-unique.js";
3
2
 
4
3
  /**
5
- * External conversation bindings table with indexes and unique constraint migration.
4
+ * External conversation bindings table with indexes.
6
5
  */
7
6
  export function createExternalConversationBindingsTables(
8
7
  database: DrizzleDb,
@@ -12,6 +11,7 @@ export function createExternalConversationBindingsTables(
12
11
  conversation_id TEXT PRIMARY KEY REFERENCES conversations(id) ON DELETE CASCADE,
13
12
  source_channel TEXT NOT NULL,
14
13
  external_chat_id TEXT NOT NULL,
14
+ external_thread_id TEXT,
15
15
  external_user_id TEXT,
16
16
  display_name TEXT,
17
17
  username TEXT,
@@ -25,9 +25,20 @@ export function createExternalConversationBindingsTables(
25
25
  database.run(
26
26
  /*sql*/ `CREATE INDEX IF NOT EXISTS idx_ext_conv_bindings_channel_chat ON external_conversation_bindings(source_channel, external_chat_id)`,
27
27
  );
28
+ database.run(
29
+ /*sql*/ `CREATE INDEX IF NOT EXISTS idx_ext_conv_bindings_channel_chat_thread ON external_conversation_bindings(source_channel, external_chat_id, external_thread_id)`,
30
+ );
28
31
  database.run(
29
32
  /*sql*/ `CREATE INDEX IF NOT EXISTS idx_ext_conv_bindings_channel ON external_conversation_bindings(source_channel)`,
30
33
  );
31
-
32
- migrateExtConvBindingsChannelChatUnique(database);
34
+ database.run(/*sql*/ `
35
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_ext_conv_bindings_channel_chat_no_thread_unique
36
+ ON external_conversation_bindings(source_channel, external_chat_id)
37
+ WHERE external_thread_id IS NULL
38
+ `);
39
+ database.run(/*sql*/ `
40
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_ext_conv_bindings_channel_chat_thread_unique
41
+ ON external_conversation_bindings(source_channel, external_chat_id, external_thread_id)
42
+ WHERE external_thread_id IS NOT NULL
43
+ `);
33
44
  }
@@ -1133,7 +1133,7 @@ describe("migrateDeletePrivateConversations", () => {
1133
1133
  1,
1134
1134
  'text',
1135
1135
  'eA==',
1136
- ${now}
1136
+ ${now - 60_000}
1137
1137
  );
1138
1138
  `);
1139
1139
 
@@ -1153,4 +1153,41 @@ describe("migrateDeletePrivateConversations", () => {
1153
1153
  countWhere(raw, "attachments", `id = 'conv-standard-attachment'`),
1154
1154
  ).toBe(1);
1155
1155
  });
1156
+
1157
+ test("preserves pre-staged uploads (unlinked attachments) created after migration starts", () => {
1158
+ const db = createTestDb();
1159
+ const raw = getSqliteFrom(db);
1160
+ const now = Date.now();
1161
+
1162
+ bootstrapTables(raw);
1163
+ seedConversation(raw, "conv-standard", "standard");
1164
+ // created_at in the future ensures it lands after the migration's start
1165
+ // snapshot regardless of clock resolution / test-runner scheduling.
1166
+ raw.exec(/*sql*/ `
1167
+ INSERT INTO attachments (
1168
+ id,
1169
+ original_filename,
1170
+ mime_type,
1171
+ size_bytes,
1172
+ kind,
1173
+ data_base64,
1174
+ created_at
1175
+ ) VALUES (
1176
+ 'pre-staged-upload',
1177
+ 'pending.txt',
1178
+ 'text/plain',
1179
+ 1,
1180
+ 'text',
1181
+ 'eA==',
1182
+ ${now + 60_000}
1183
+ );
1184
+ `);
1185
+
1186
+ migrateDeletePrivateConversations(db);
1187
+
1188
+ expect(countWhere(raw, "attachments", `id = 'pre-staged-upload'`)).toBe(1);
1189
+ expect(
1190
+ countWhere(raw, "attachments", `id = 'conv-standard-attachment'`),
1191
+ ).toBe(1);
1192
+ });
1156
1193
  });
@@ -12,6 +12,12 @@ const PRIVATE_GRAPH_NODE_IDS = /*sql*/ `
12
12
  `;
13
13
 
14
14
  export function migrateDeletePrivateConversations(database: DrizzleDb): void {
15
+ // Snapshot the migration's start time. The trailing orphan-attachment sweep
16
+ // uses this as an upper bound so it cleans up leaks from prior runs of this
17
+ // migration (those rows were created before this run started) without
18
+ // touching pre-staged uploads created during or after the migration.
19
+ const migrationStartTs = Date.now();
20
+
15
21
  database.run(/*sql*/ `
16
22
  DELETE FROM tool_invocations
17
23
  WHERE conversation_id IN (${PRIVATE_CONVERSATION_IDS})
@@ -204,6 +210,7 @@ export function migrateDeletePrivateConversations(database: DrizzleDb): void {
204
210
  FROM message_attachments ma
205
211
  WHERE ma.attachment_id = attachments.id
206
212
  )
213
+ AND created_at <= ${migrationStartTs}
207
214
  `);
208
215
  database.run(/*sql*/ `
209
216
  DELETE FROM memory_summaries
@@ -0,0 +1,78 @@
1
+ import { type DrizzleDb, getSqliteFrom } from "../db-connection.js";
2
+
3
+ /**
4
+ * Add a provider thread anchor to external conversation bindings.
5
+ *
6
+ * Slack conversations are keyed by `(channel_id, thread_ts)`, while legacy
7
+ * channels still use only `(source_channel, external_chat_id)`. SQLite treats
8
+ * NULLs as distinct in unique indexes, so use two partial unique indexes:
9
+ * one for legacy/no-thread bindings and one for threaded bindings.
10
+ */
11
+ export function migrateExternalConversationBindingThreadId(
12
+ database: DrizzleDb,
13
+ ): void {
14
+ const raw = getSqliteFrom(database);
15
+
16
+ const tableExists = raw
17
+ .query(
18
+ `SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'external_conversation_bindings'`,
19
+ )
20
+ .get();
21
+ if (!tableExists) return;
22
+
23
+ const columns = raw
24
+ .query(`PRAGMA table_info(external_conversation_bindings)`)
25
+ .all() as Array<{ name: string }>;
26
+ if (!columns.some((column) => column.name === "external_thread_id")) {
27
+ raw.exec(
28
+ `ALTER TABLE external_conversation_bindings ADD COLUMN external_thread_id TEXT`,
29
+ );
30
+ }
31
+
32
+ try {
33
+ raw.exec("BEGIN");
34
+
35
+ raw.exec(`DROP INDEX IF EXISTS idx_ext_conv_bindings_channel_chat_unique`);
36
+
37
+ raw.exec(/*sql*/ `
38
+ DELETE FROM external_conversation_bindings
39
+ WHERE rowid NOT IN (
40
+ SELECT rowid FROM (
41
+ SELECT rowid,
42
+ ROW_NUMBER() OVER (
43
+ PARTITION BY source_channel, external_chat_id, COALESCE(external_thread_id, '')
44
+ ORDER BY updated_at DESC, created_at DESC, rowid DESC
45
+ ) AS rn
46
+ FROM external_conversation_bindings
47
+ )
48
+ WHERE rn = 1
49
+ )
50
+ `);
51
+
52
+ raw.exec(/*sql*/ `
53
+ CREATE INDEX IF NOT EXISTS idx_ext_conv_bindings_channel_chat_thread
54
+ ON external_conversation_bindings(source_channel, external_chat_id, external_thread_id)
55
+ `);
56
+
57
+ raw.exec(/*sql*/ `
58
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_ext_conv_bindings_channel_chat_no_thread_unique
59
+ ON external_conversation_bindings(source_channel, external_chat_id)
60
+ WHERE external_thread_id IS NULL
61
+ `);
62
+
63
+ raw.exec(/*sql*/ `
64
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_ext_conv_bindings_channel_chat_thread_unique
65
+ ON external_conversation_bindings(source_channel, external_chat_id, external_thread_id)
66
+ WHERE external_thread_id IS NOT NULL
67
+ `);
68
+
69
+ raw.exec("COMMIT");
70
+ } catch (err) {
71
+ try {
72
+ raw.exec("ROLLBACK");
73
+ } catch {
74
+ /* no active transaction */
75
+ }
76
+ throw err;
77
+ }
78
+ }
@@ -0,0 +1,21 @@
1
+ import type { DrizzleDb } from "../db-connection.js";
2
+
3
+ /**
4
+ * Create the onboarding_events table for tracking pre-chat onboarding
5
+ * selections (tools, tasks, tone, Google connect status).
6
+ */
7
+ export function createOnboardingEventsTable(database: DrizzleDb): void {
8
+ database.run(/*sql*/ `
9
+ CREATE TABLE IF NOT EXISTS onboarding_events (
10
+ id TEXT PRIMARY KEY,
11
+ created_at INTEGER NOT NULL,
12
+ screen TEXT NOT NULL,
13
+ tools_json TEXT,
14
+ tasks_json TEXT,
15
+ tone TEXT,
16
+ google_connected INTEGER,
17
+ google_scopes_json TEXT,
18
+ ab_variant TEXT
19
+ )
20
+ `);
21
+ }