@vellumai/assistant 0.8.1 → 0.8.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (630) hide show
  1. package/ARCHITECTURE.md +13 -19
  2. package/Dockerfile +75 -1
  3. package/bun.lock +11 -1
  4. package/docker-entrypoint.sh +17 -0
  5. package/docker-init-apt-root.sh +167 -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 +642 -5
  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-loop-exit-reason.test.ts +272 -0
  21. package/src/__tests__/agent-loop-provider-error-recording.test.ts +195 -0
  22. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +131 -0
  23. package/src/__tests__/anthropic-provider.test.ts +45 -0
  24. package/src/__tests__/app-builder-tool-scripts.test.ts +9 -3
  25. package/src/__tests__/app-executors.test.ts +220 -4
  26. package/src/__tests__/auto-analysis-end-to-end.test.ts +35 -0
  27. package/src/__tests__/bundled-asset.test.ts +6 -6
  28. package/src/__tests__/channel-availability-routes.test.ts +206 -0
  29. package/src/__tests__/channel-delivery-store.test.ts +289 -1
  30. package/src/__tests__/circuit-breaker-pipeline.test.ts +0 -1
  31. package/src/__tests__/clawhub.test.ts +75 -16
  32. package/src/__tests__/compactor-tail-resolution.test.ts +147 -0
  33. package/src/__tests__/config-get-vision-flag.test.ts +136 -0
  34. package/src/__tests__/config-loader-backfill.test.ts +115 -18
  35. package/src/__tests__/config-schema.test.ts +21 -0
  36. package/src/__tests__/config-set-route.test.ts +80 -0
  37. package/src/__tests__/config-sounds-sync.test.ts +97 -0
  38. package/src/__tests__/config-watcher-skill-reseed.test.ts +453 -0
  39. package/src/__tests__/context-search-conversations-source.test.ts +117 -2
  40. package/src/__tests__/context-search-memory-v2-source.test.ts +0 -1
  41. package/src/__tests__/context-search-workspace-source.test.ts +7 -0
  42. package/src/__tests__/context-token-estimator.test.ts +31 -65
  43. package/src/__tests__/conversation-abort-tool-results.test.ts +4 -1
  44. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +1 -0
  45. package/src/__tests__/conversation-agent-loop-overflow.test.ts +92 -92
  46. package/src/__tests__/conversation-agent-loop.test.ts +59 -1
  47. package/src/__tests__/conversation-error.test.ts +42 -3
  48. package/src/__tests__/conversation-fork-crud.test.ts +82 -0
  49. package/src/__tests__/conversation-inference-profile-route.test.ts +40 -4
  50. package/src/__tests__/conversation-lifecycle.test.ts +173 -0
  51. package/src/__tests__/conversation-media-retry.test.ts +19 -8
  52. package/src/__tests__/conversation-message-sync-tags.test.ts +97 -0
  53. package/src/__tests__/conversation-pairing.test.ts +54 -0
  54. package/src/__tests__/conversation-process-callsite.test.ts +4 -1
  55. package/src/__tests__/conversation-provider-retry-repair.test.ts +5 -1
  56. package/src/__tests__/conversation-queue.test.ts +4 -1
  57. package/src/__tests__/conversation-runtime-assembly.test.ts +102 -13
  58. package/src/__tests__/conversation-slash-queue.test.ts +59 -1
  59. package/src/__tests__/conversation-slash-unknown.test.ts +4 -1
  60. package/src/__tests__/conversation-surfaces-table-action.test.ts +360 -0
  61. package/src/__tests__/conversation-sync-tags.test.ts +235 -0
  62. package/src/__tests__/conversation-workspace-injection.test.ts +5 -1
  63. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +5 -1
  64. package/src/__tests__/credential-security-invariants.test.ts +3 -2
  65. package/src/__tests__/date-context.test.ts +45 -0
  66. package/src/__tests__/db-slack-external-content-normalization.test.ts +301 -0
  67. package/src/__tests__/delete-managed-skill-tool.test.ts +55 -13
  68. package/src/__tests__/disk-pressure-tools.test.ts +1 -0
  69. package/src/__tests__/dm-backfill.test.ts +121 -10
  70. package/src/__tests__/document-tool-security.test.ts +258 -0
  71. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +0 -1
  72. package/src/__tests__/edit-propagation.test.ts +33 -0
  73. package/src/__tests__/empty-response-pipeline.test.ts +0 -4
  74. package/src/__tests__/external-plugin-loader.test.ts +151 -55
  75. package/src/__tests__/filing-service.test.ts +140 -0
  76. package/src/__tests__/get-skill-detail-audit.test.ts +0 -4
  77. package/src/__tests__/guardian-action-no-hardcoded-copy.test.ts +0 -1
  78. package/src/__tests__/guardian-dispatch.test.ts +1 -0
  79. package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +43 -62
  80. package/src/__tests__/heartbeat-service.test.ts +24 -164
  81. package/src/__tests__/helpers/channel-test-adapter.ts +0 -2
  82. package/src/__tests__/helpers/tar-fixtures.ts +39 -0
  83. package/src/__tests__/helpers/wait-for.ts +21 -0
  84. package/src/__tests__/history-repair-pipeline.test.ts +0 -3
  85. package/src/__tests__/history-repair.test.ts +73 -0
  86. package/src/__tests__/host-app-control-proxy.test.ts +507 -10
  87. package/src/__tests__/host-proxy-preactivation.test.ts +200 -13
  88. package/src/__tests__/image-credentials.test.ts +1 -1
  89. package/src/__tests__/inbound-slack-persistence.test.ts +2 -0
  90. package/src/__tests__/inference-no-mode-boot-e2e.test.ts +1 -1
  91. package/src/__tests__/inference-profile-reaper.test.ts +4 -2
  92. package/src/__tests__/inference-profile-session-handler.test.ts +18 -6
  93. package/src/__tests__/inference-profile-session-ipc.test.ts +17 -5
  94. package/src/__tests__/injector-background-turn.test.ts +153 -0
  95. package/src/__tests__/injector-chain.test.ts +15 -8
  96. package/src/__tests__/install-skill-routing.test.ts +155 -37
  97. package/src/__tests__/lifecycle-memory-v2-seed.test.ts +99 -3
  98. package/src/__tests__/list-messages-page-latest.test.ts +55 -0
  99. package/src/__tests__/llm-call-pipeline.test.ts +0 -3
  100. package/src/__tests__/llm-callsite-catalog.test.ts +25 -0
  101. package/src/__tests__/llm-catalog-parity.test.ts +58 -13
  102. package/src/__tests__/llm-request-log-agent-loop-exit-reason.test.ts +116 -0
  103. package/src/__tests__/llm-request-log-error-payload.test.ts +138 -0
  104. package/src/__tests__/llm-request-log-source-clickhouse.test.ts +36 -0
  105. package/src/__tests__/llm-request-log-source-factory.test.ts +29 -53
  106. package/src/__tests__/llm-resolver.test.ts +255 -2
  107. package/src/__tests__/llm-usage-store.test.ts +114 -0
  108. package/src/__tests__/managed-profile-guard.test.ts +41 -29
  109. package/src/__tests__/managed-skill-lifecycle.test.ts +109 -18
  110. package/src/__tests__/managed-store.test.ts +84 -192
  111. package/src/__tests__/media-generate-image.test.ts +1 -1
  112. package/src/__tests__/memory-retrieval-pipeline.test.ts +0 -2
  113. package/src/__tests__/messages-after-tiebreaker.test.ts +122 -0
  114. package/src/__tests__/notification-decision-fallback.test.ts +0 -91
  115. package/src/__tests__/notification-decision-strategy.test.ts +14 -31
  116. package/src/__tests__/notification-deep-link.test.ts +15 -0
  117. package/src/__tests__/notification-guardian-path.test.ts +1 -2
  118. package/src/__tests__/notification-platform-adapter.test.ts +5 -4
  119. package/src/__tests__/notification-telegram-adapter.test.ts +1 -0
  120. package/src/__tests__/notification-vellum-adapter.test.ts +113 -0
  121. package/src/__tests__/oauth-commands-routes.test.ts +168 -16
  122. package/src/__tests__/oauth-provider-profiles.test.ts +9 -0
  123. package/src/__tests__/openai-provider.test.ts +242 -3
  124. package/src/__tests__/openai-responses-cutover-guard.test.ts +17 -9
  125. package/src/__tests__/openrouter-provider-only.test.ts +51 -3
  126. package/src/__tests__/openrouter-token-estimation.test.ts +34 -25
  127. package/src/__tests__/overflow-reduce-pipeline.test.ts +0 -2
  128. package/src/__tests__/persistence-pipeline.test.ts +0 -2
  129. package/src/__tests__/{managed-proxy-context.test.ts → platform-proxy-context.test.ts} +7 -2
  130. package/src/__tests__/platform.test.ts +2 -0
  131. package/src/__tests__/plugin-api-shim.test.ts +125 -0
  132. package/src/__tests__/plugin-bootstrap.test.ts +10 -36
  133. package/src/__tests__/plugin-external-api.test.ts +68 -0
  134. package/src/__tests__/plugin-registry.test.ts +0 -77
  135. package/src/__tests__/plugin-route-contribution.test.ts +0 -1
  136. package/src/__tests__/plugin-skill-contribution.test.ts +0 -2
  137. package/src/__tests__/plugin-tool-contribution.test.ts +16 -15
  138. package/src/__tests__/plugin-types.test.ts +3 -13
  139. package/src/__tests__/process-message-background-slack.test.ts +8 -1
  140. package/src/__tests__/process-message-display-content.test.ts +421 -0
  141. package/src/__tests__/provider-catalog-visibility.test.ts +158 -0
  142. package/src/__tests__/provider-error-scenarios.test.ts +111 -0
  143. package/src/__tests__/{provider-managed-proxy-integration.test.ts → provider-platform-proxy-integration.test.ts} +33 -31
  144. package/src/__tests__/scaffold-managed-skill-tool.test.ts +65 -13
  145. package/src/__tests__/schedule-routes.test.ts +50 -3
  146. package/src/__tests__/schedule-store.test.ts +94 -0
  147. package/src/__tests__/scheduler-reuse-conversation.test.ts +54 -7
  148. package/src/__tests__/schema-transforms.test.ts +20 -0
  149. package/src/__tests__/search-skills-unified.test.ts +0 -5
  150. package/src/__tests__/{secret-routes-managed-proxy.test.ts → secret-routes-platform-proxy.test.ts} +1 -1
  151. package/src/__tests__/server-history-render.test.ts +43 -0
  152. package/src/__tests__/skill-load-feature-flag.test.ts +0 -12
  153. package/src/__tests__/skill-load-tool.test.ts +27 -89
  154. package/src/__tests__/skill-memory.test.ts +23 -3
  155. package/src/__tests__/skills-file-content-endpoint.test.ts +9 -38
  156. package/src/__tests__/skills-files-catalog-fallback.test.ts +0 -3
  157. package/src/__tests__/skills-install-extract.test.ts +49 -38
  158. package/src/__tests__/skills-install-staging.test.ts +159 -0
  159. package/src/__tests__/skills-uninstall.test.ts +9 -41
  160. package/src/__tests__/skills.test.ts +51 -58
  161. package/src/__tests__/slack-channel-config.test.ts +9 -0
  162. package/src/__tests__/subagent-tool-filtering.test.ts +50 -0
  163. package/src/__tests__/system-prompt.test.ts +670 -63
  164. package/src/__tests__/terminal-tools.test.ts +28 -1
  165. package/src/__tests__/thread-backfill.test.ts +557 -27
  166. package/src/__tests__/title-generate-pipeline.test.ts +0 -13
  167. package/src/__tests__/token-estimate-pipeline.test.ts +0 -3
  168. package/src/__tests__/tool-error-pipeline.test.ts +0 -3
  169. package/src/__tests__/tool-execute-pipeline.test.ts +0 -5
  170. package/src/__tests__/tool-executor-lifecycle-events.test.ts +1 -1
  171. package/src/__tests__/tool-executor.test.ts +16 -4
  172. package/src/__tests__/tool-result-truncate-pipeline.test.ts +0 -12
  173. package/src/__tests__/turn-events-store.test.ts +256 -0
  174. package/src/__tests__/twilio-routes.test.ts +4 -0
  175. package/src/__tests__/user-plugin-loader.test.ts +0 -7
  176. package/src/__tests__/voice-session-bridge.test.ts +198 -0
  177. package/src/__tests__/web-search-catalog-parity.test.ts +32 -10
  178. package/src/__tests__/workspace-migration-057-repair-stale-gemini-model-ids.test.ts +115 -3
  179. package/src/__tests__/workspace-migration-072-seed-reply-suggestion-callsite.test.ts +50 -0
  180. package/src/__tests__/workspace-migration-073-repair-recall-callsite-empty-profile.test.ts +153 -0
  181. package/src/__tests__/workspace-migration-085-memory-v2-bm25-b-reembed-disabled-v2-pages.test.ts +220 -0
  182. package/src/__tests__/workspace-migration-086-revert-stale-gemini-mis-rewrites.test.ts +269 -0
  183. package/src/__tests__/workspace-migration-087-memory-router-balanced-profile.test.ts +228 -0
  184. package/src/__tests__/workspace-migration-remove-legacy-skills-index.test.ts +309 -0
  185. package/src/__tests__/workspace-migrations-runner.test.ts +111 -3
  186. package/src/a2a/__tests__/agent-card.test.ts +98 -0
  187. package/src/a2a/__tests__/e2e-a2a-channel.test.ts +597 -0
  188. package/src/a2a/__tests__/protocol-helpers.test.ts +113 -0
  189. package/src/a2a/__tests__/task-store.test.ts +246 -0
  190. package/src/a2a/agent-card.ts +58 -0
  191. package/src/a2a/feature-gate.ts +8 -0
  192. package/src/a2a/protocol-constants.ts +21 -0
  193. package/src/a2a/protocol-errors.ts +50 -0
  194. package/src/a2a/protocol-types.ts +162 -0
  195. package/src/a2a/task-store.ts +168 -0
  196. package/src/acp/resolve-agent.ts +1 -1
  197. package/src/agent/image-optimize.ts +13 -5
  198. package/src/agent/loop.ts +167 -18
  199. package/src/calls/voice-session-bridge.ts +61 -42
  200. package/src/channels/config.ts +9 -0
  201. package/src/channels/types.ts +122 -0
  202. package/src/cli/__tests__/unknown-command.test.ts +24 -0
  203. package/src/cli/commands/__tests__/changelog.test.ts +304 -319
  204. package/src/cli/{__tests__ → commands/__tests__}/notifications.test.ts +201 -28
  205. package/src/cli/commands/__tests__/schedules.test.ts +960 -0
  206. package/src/cli/commands/changelog.ts +106 -42
  207. package/src/cli/commands/conversations.ts +102 -17
  208. package/src/cli/commands/default-action.ts +10 -53
  209. package/src/cli/commands/notifications.ts +388 -346
  210. package/src/cli/commands/plugins.ts +252 -0
  211. package/src/cli/commands/schedules.ts +683 -0
  212. package/src/cli/commands/telemetry.ts +40 -0
  213. package/src/cli/lib/__tests__/cli-colors.test.ts +48 -0
  214. package/src/cli/lib/__tests__/confirm-prompt.test.ts +159 -0
  215. package/src/cli/lib/__tests__/install-from-github.test.ts +355 -0
  216. package/src/cli/lib/__tests__/list-installed-plugins.test.ts +154 -0
  217. package/src/cli/lib/__tests__/search-plugins.test.ts +261 -0
  218. package/src/cli/lib/__tests__/uninstall-plugin.test.ts +124 -0
  219. package/src/cli/lib/__tests__/unknown-command.test.ts +106 -0
  220. package/src/cli/lib/cli-colors.ts +12 -0
  221. package/src/cli/lib/confirm-prompt.ts +79 -0
  222. package/src/cli/lib/install-from-github.ts +303 -0
  223. package/src/cli/lib/list-installed-plugins.ts +137 -0
  224. package/src/cli/lib/search-plugins.ts +163 -0
  225. package/src/cli/lib/uninstall-plugin.ts +82 -0
  226. package/src/cli/lib/unknown-command.ts +111 -0
  227. package/src/cli/program.ts +52 -2
  228. package/src/config/assistant-feature-flags.ts +24 -54
  229. package/src/config/bundled-skills/app-builder/SKILL.md +140 -22
  230. package/src/config/bundled-skills/app-builder/TOOLS.json +7 -0
  231. package/src/config/bundled-skills/computer-use/TOOLS.json +15 -52
  232. package/src/config/bundled-skills/document/SKILL.md +23 -3
  233. package/src/config/bundled-skills/document/TOOLS.json +53 -0
  234. package/src/config/bundled-skills/document/tools/document-delete.ts +12 -0
  235. package/src/config/bundled-skills/document/tools/document-list.ts +12 -0
  236. package/src/config/bundled-skills/document/tools/document-read.ts +12 -0
  237. package/src/config/bundled-skills/phone-calls/SKILL.md +1 -1
  238. package/src/config/bundled-skills/skill-management/SKILL.md +2 -2
  239. package/src/config/bundled-skills/skill-management/TOOLS.json +7 -7
  240. package/src/config/bundled-tool-registry.ts +6 -0
  241. package/src/config/call-site-defaults.ts +105 -0
  242. package/src/config/feature-flag-registry.json +41 -9
  243. package/src/config/llm-resolver.ts +52 -1
  244. package/src/config/loader.ts +64 -38
  245. package/src/config/schema.ts +9 -10
  246. package/src/config/schemas/__tests__/llm-request-logs.test.ts +36 -0
  247. package/src/config/schemas/__tests__/memory-v2.test.ts +3 -3
  248. package/src/config/schemas/channels.ts +17 -0
  249. package/src/config/schemas/compaction.ts +28 -0
  250. package/src/config/schemas/conversations.ts +10 -0
  251. package/src/config/schemas/heartbeat.ts +23 -0
  252. package/src/config/schemas/llm-request-logs.ts +31 -7
  253. package/src/config/schemas/llm.ts +1 -0
  254. package/src/config/schemas/memory-retrieval.ts +18 -0
  255. package/src/config/schemas/memory-retrospective.ts +1 -1
  256. package/src/config/schemas/memory-v2.ts +4 -4
  257. package/src/config/schemas/memory.ts +3 -1
  258. package/src/config/schemas/tools.ts +14 -0
  259. package/src/config/seed-inference-profiles.ts +99 -29
  260. package/src/config/skills.ts +3 -96
  261. package/src/context/compactor.ts +1107 -0
  262. package/src/context/token-estimator.ts +34 -36
  263. package/src/context/window-manager.ts +197 -1520
  264. package/src/credential-execution/managed-catalog.ts +37 -0
  265. package/src/credential-health/credential-health-service.ts +280 -19
  266. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +33 -18
  267. package/src/daemon/__tests__/conversation-tool-setup-exclude.test.ts +138 -0
  268. package/src/daemon/__tests__/conversation-tool-setup.test.ts +74 -0
  269. package/src/daemon/approval-generators.ts +8 -6
  270. package/src/daemon/config-watcher.ts +94 -31
  271. package/src/daemon/conversation-agent-loop-handlers.ts +78 -0
  272. package/src/daemon/conversation-agent-loop.ts +198 -11
  273. package/src/daemon/conversation-error.ts +171 -37
  274. package/src/daemon/conversation-lifecycle.ts +53 -40
  275. package/src/daemon/conversation-messaging.ts +25 -6
  276. package/src/daemon/conversation-process.ts +49 -12
  277. package/src/daemon/conversation-runtime-assembly.ts +25 -1
  278. package/src/daemon/conversation-slash.ts +12 -5
  279. package/src/daemon/conversation-store.ts +11 -4
  280. package/src/daemon/conversation-tool-setup.ts +39 -7
  281. package/src/daemon/conversation.ts +33 -8
  282. package/src/daemon/date-context.ts +40 -0
  283. package/src/daemon/external-plugins-bootstrap.ts +217 -181
  284. package/src/daemon/first-greeting.ts +22 -2
  285. package/src/daemon/guardian-action-generators.ts +1 -125
  286. package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +248 -0
  287. package/src/daemon/handlers/__tests__/config-a2a-invite.test.ts +154 -0
  288. package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +133 -0
  289. package/src/daemon/handlers/__tests__/config-a2a.test.ts +95 -0
  290. package/src/daemon/handlers/config-a2a.ts +289 -0
  291. package/src/daemon/handlers/config-model.ts +6 -5
  292. package/src/daemon/handlers/config-slack-channel.ts +15 -3
  293. package/src/daemon/handlers/conversations.ts +1 -0
  294. package/src/daemon/handlers/shared.ts +14 -5
  295. package/src/daemon/handlers/skills.ts +111 -108
  296. package/src/daemon/history-repair.ts +28 -1
  297. package/src/daemon/host-app-control-proxy.ts +153 -27
  298. package/src/daemon/host-proxy-preactivation.ts +85 -18
  299. package/src/daemon/lifecycle.ts +89 -91
  300. package/src/daemon/meet-host-supervisor.ts +5 -4
  301. package/src/daemon/memory-v2-startup.ts +85 -0
  302. package/src/daemon/message-protocol.ts +1 -0
  303. package/src/daemon/message-types/conversations.ts +25 -0
  304. package/src/daemon/message-types/messages.ts +61 -0
  305. package/src/daemon/message-types/notifications.ts +21 -0
  306. package/src/daemon/message-types/subagents.ts +1 -0
  307. package/src/daemon/message-types/sync.ts +1 -0
  308. package/src/daemon/pkb-reminder-builder.test.ts +11 -54
  309. package/src/daemon/pkb-reminder-builder.ts +5 -20
  310. package/src/daemon/plugin-source-watcher.ts +146 -0
  311. package/src/daemon/process-message.ts +24 -3
  312. package/src/daemon/server.ts +11 -2
  313. package/src/daemon/skill-memory-refresh.ts +33 -0
  314. package/src/daemon/wake-target-adapter.ts +2 -0
  315. package/src/documents/document-store.ts +221 -3
  316. package/src/embedded/plugin-api.ts +40 -0
  317. package/src/export/__tests__/transcript-formatter.test.ts +121 -0
  318. package/src/export/transcript-formatter.ts +54 -20
  319. package/src/filing/filing-service.ts +39 -0
  320. package/src/heartbeat/__tests__/heartbeat-service.test.ts +135 -6
  321. package/src/heartbeat/heartbeat-run-store.ts +2 -1
  322. package/src/heartbeat/heartbeat-service.ts +73 -189
  323. package/src/home/__tests__/feed-types.test.ts +80 -0
  324. package/src/home/feed-types.ts +36 -2
  325. package/src/home/post-connect-feed.ts +1 -0
  326. package/src/index.ts +18 -1
  327. package/src/ipc/cli-client.ts +147 -45
  328. package/src/live-voice/__tests__/live-voice-stt.test.ts +57 -0
  329. package/src/mcp/client.ts +20 -4
  330. package/src/media/image-credentials.ts +3 -3
  331. package/src/memory/__tests__/bookmark-crud.test.ts +33 -27
  332. package/src/memory/__tests__/conversation-queries.test.ts +483 -0
  333. package/src/memory/__tests__/jobs-worker-v2-graph-trigger-embed.test.ts +113 -0
  334. package/src/memory/__tests__/memory-retrospective-enqueue.test.ts +2 -50
  335. package/src/memory/__tests__/memory-retrospective-job.test.ts +87 -4
  336. package/src/memory/__tests__/memory-retrospective-startup-cleanup.test.ts +119 -14
  337. package/src/memory/__tests__/message-content.test.ts +35 -0
  338. package/src/memory/bookmark-crud.ts +42 -10
  339. package/src/memory/context-search/sources/conversations.ts +62 -2
  340. package/src/memory/context-search/sources/workspace.ts +4 -0
  341. package/src/memory/conversation-crud.ts +63 -19
  342. package/src/memory/conversation-queries.ts +197 -11
  343. package/src/memory/conversation-title-service.ts +26 -4
  344. package/src/memory/db-init.ts +12 -0
  345. package/src/memory/delivery-crud.ts +152 -5
  346. package/src/memory/embedding-backend.ts +4 -4
  347. package/src/memory/external-conversation-store.ts +66 -5
  348. package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +150 -12
  349. package/src/memory/graph/conversation-graph-memory.ts +49 -21
  350. package/src/memory/graph/tools.ts +9 -40
  351. package/src/memory/indexer.ts +34 -29
  352. package/src/memory/invite-store.ts +53 -0
  353. package/src/memory/jobs/__tests__/embed-concept-page.test.ts +73 -0
  354. package/src/memory/jobs/embed-concept-page.ts +20 -11
  355. package/src/memory/jobs-worker.ts +6 -1
  356. package/src/memory/llm-request-log-source-clickhouse.ts +24 -12
  357. package/src/memory/llm-request-log-source.ts +19 -52
  358. package/src/memory/llm-request-log-store.ts +92 -1
  359. package/src/memory/llm-usage-store.ts +125 -5
  360. package/src/memory/memory-retrospective-enqueue.ts +1 -20
  361. package/src/memory/memory-retrospective-job.ts +33 -6
  362. package/src/memory/memory-retrospective-startup-cleanup.ts +72 -5
  363. package/src/memory/message-content.ts +1 -1
  364. package/src/memory/migrations/109-external-conversation-bindings.ts +15 -4
  365. package/src/memory/migrations/229-delete-private-conversations.test.ts +38 -1
  366. package/src/memory/migrations/229-delete-private-conversations.ts +7 -0
  367. package/src/memory/migrations/247-external-conversation-binding-thread-id.ts +78 -0
  368. package/src/memory/migrations/248-create-onboarding-events.ts +21 -0
  369. package/src/memory/migrations/249-normalize-slack-external-content.ts +240 -0
  370. package/src/memory/migrations/250-provider-connection-base-url-and-models.ts +28 -0
  371. package/src/memory/migrations/251-a2a-tasks.ts +49 -0
  372. package/src/memory/migrations/252-llm-request-log-agent-loop-exit-reason.ts +32 -0
  373. package/src/memory/migrations/index.ts +9 -0
  374. package/src/memory/migrations/registry.ts +16 -0
  375. package/src/memory/onboarding-events-store.ts +106 -0
  376. package/src/memory/schema/a2a.ts +15 -0
  377. package/src/memory/schema/bookmarks.ts +0 -2
  378. package/src/memory/schema/calls.ts +1 -0
  379. package/src/memory/schema/index.ts +1 -0
  380. package/src/memory/schema/inference.ts +3 -3
  381. package/src/memory/schema/infrastructure.ts +13 -0
  382. package/src/memory/turn-events-store.ts +127 -2
  383. package/src/memory/v2/__tests__/activation-store.test.ts +25 -23
  384. package/src/memory/v2/__tests__/activation.test.ts +0 -8
  385. package/src/memory/v2/__tests__/cli-command-store.test.ts +404 -0
  386. package/src/memory/v2/__tests__/frontmatter-sweep.test.ts +25 -4
  387. package/src/memory/v2/__tests__/injection.test.ts +288 -11
  388. package/src/memory/v2/__tests__/migration.test.ts +87 -0
  389. package/src/memory/v2/__tests__/page-index.test.ts +83 -0
  390. package/src/memory/v2/__tests__/prompts-router.test.ts +58 -6
  391. package/src/memory/v2/__tests__/qdrant.test.ts +66 -3
  392. package/src/memory/v2/__tests__/router.test.ts +15 -0
  393. package/src/memory/v2/__tests__/skill-store.test.ts +387 -8
  394. package/src/memory/v2/__tests__/static-context.test.ts +12 -1
  395. package/src/memory/v2/activation-store.ts +14 -16
  396. package/src/memory/v2/cli-command-content.ts +19 -0
  397. package/src/memory/v2/cli-command-store.ts +304 -0
  398. package/src/memory/v2/frontmatter-sweep.ts +7 -1
  399. package/src/memory/v2/injection.ts +81 -26
  400. package/src/memory/v2/migration.ts +49 -19
  401. package/src/memory/v2/page-index.ts +63 -8
  402. package/src/memory/v2/prompts/router.ts +11 -8
  403. package/src/memory/v2/prompts/sweep.ts +2 -2
  404. package/src/memory/v2/qdrant.ts +135 -7
  405. package/src/memory/v2/router.ts +9 -8
  406. package/src/memory/v2/skill-store.ts +120 -35
  407. package/src/memory/v2/static-context.ts +4 -4
  408. package/src/memory/v2/types.ts +23 -0
  409. package/src/messaging/providers/a2a/__tests__/deliver.test.ts +274 -0
  410. package/src/messaging/providers/a2a/deliver.ts +156 -0
  411. package/src/messaging/providers/gmail/client.ts +9 -2
  412. package/src/messaging/providers/index.ts +11 -2
  413. package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +45 -5
  414. package/src/messaging/providers/slack/__tests__/download.test.ts +231 -0
  415. package/src/messaging/providers/slack/adapter.ts +43 -5
  416. package/src/messaging/providers/slack/client.ts +27 -0
  417. package/src/messaging/providers/slack/deep-link.ts +65 -0
  418. package/src/messaging/providers/slack/download.ts +104 -0
  419. package/src/messaging/providers/slack/message-metadata.test.ts +32 -0
  420. package/src/messaging/providers/slack/message-metadata.ts +27 -0
  421. package/src/messaging/providers/slack/render-transcript.test.ts +134 -0
  422. package/src/messaging/providers/slack/render-transcript.ts +69 -5
  423. package/src/messaging/providers/slack/types.ts +20 -1
  424. package/src/notifications/__tests__/broadcaster.test.ts +203 -0
  425. package/src/notifications/__tests__/decision-engine.test.ts +283 -0
  426. package/src/notifications/__tests__/deterministic-checks.test.ts +286 -0
  427. package/src/notifications/__tests__/emit-signal-home-feed.test.ts +1 -0
  428. package/src/notifications/__tests__/home-feed-side-effect.test.ts +430 -7
  429. package/src/notifications/adapters/macos.ts +12 -2
  430. package/src/notifications/broadcaster.ts +29 -4
  431. package/src/notifications/conversation-pairing.ts +2 -1
  432. package/src/notifications/copy-composer.ts +17 -64
  433. package/src/notifications/decision-engine.ts +113 -45
  434. package/src/notifications/deterministic-checks.ts +96 -0
  435. package/src/notifications/emit-signal.ts +21 -1
  436. package/src/notifications/home-feed-side-effect.ts +138 -5
  437. package/src/notifications/signal.ts +3 -5
  438. package/src/notifications/types.ts +8 -0
  439. package/src/oauth/connection-resolver.ts +8 -4
  440. package/src/oauth/platform-connection.test.ts +43 -3
  441. package/src/oauth/platform-connection.ts +19 -6
  442. package/src/oauth/seed-providers.ts +10 -1
  443. package/src/permissions/checker.ts +2 -0
  444. package/src/permissions/ipc-risk-types.ts +1 -0
  445. package/src/permissions/question-prompter.test.ts +416 -0
  446. package/src/permissions/question-prompter.ts +294 -0
  447. package/src/platform/client.test.ts +1 -1
  448. package/src/platform/client.ts +1 -1
  449. package/src/plugin-api/constants.ts +26 -0
  450. package/src/plugin-api/index.ts +34 -1
  451. package/src/plugin-api/types.ts +104 -22
  452. package/src/plugins/defaults/circuit-breaker.ts +0 -5
  453. package/src/plugins/defaults/compaction.ts +0 -4
  454. package/src/plugins/defaults/empty-response.ts +0 -2
  455. package/src/plugins/defaults/history-repair.ts +0 -2
  456. package/src/plugins/defaults/injectors.ts +74 -22
  457. package/src/plugins/defaults/llm-call.ts +0 -2
  458. package/src/plugins/defaults/memory-retrieval.ts +0 -1
  459. package/src/plugins/defaults/overflow-reduce.ts +0 -1
  460. package/src/plugins/defaults/persistence.ts +0 -2
  461. package/src/plugins/defaults/title-generate.ts +0 -5
  462. package/src/plugins/defaults/token-estimate.ts +0 -2
  463. package/src/plugins/defaults/tool-error.ts +0 -7
  464. package/src/plugins/defaults/tool-execute.ts +0 -2
  465. package/src/plugins/defaults/tool-result-truncate.ts +0 -4
  466. package/src/plugins/ensure-plugin-api-shim.ts +96 -0
  467. package/src/plugins/external-api.ts +104 -0
  468. package/src/plugins/external-plugin-loader.ts +187 -42
  469. package/src/plugins/feature-gate.ts +22 -0
  470. package/src/plugins/pipeline.ts +37 -0
  471. package/src/plugins/registry.ts +48 -80
  472. package/src/plugins/types.ts +40 -26
  473. package/src/plugins/user-loader.ts +21 -2
  474. package/src/proactive-artifact/aux-message-injector.ts +11 -0
  475. package/src/proactive-artifact/job.test.ts +37 -5
  476. package/src/prompts/__tests__/system-prompt.test.ts +10 -43
  477. package/src/prompts/__tests__/task-progress-hint-section.test.ts +95 -0
  478. package/src/prompts/normalize-onboarding.ts +27 -0
  479. package/src/prompts/sections.ts +302 -0
  480. package/src/prompts/system-prompt.ts +63 -174
  481. package/src/prompts/templates/BOOTSTRAP.md +17 -1
  482. package/src/prompts/templates/system-sections.ts +164 -0
  483. package/src/providers/__tests__/inference.test.ts +24 -7
  484. package/src/providers/anthropic/client.ts +28 -28
  485. package/src/providers/call-site-routing.ts +24 -6
  486. package/src/providers/connection-resolution.ts +68 -11
  487. package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +74 -0
  488. package/src/providers/inference/__tests__/connections-openai-compatible.test.ts +175 -0
  489. package/src/providers/inference/__tests__/connections-status-label.test.ts +15 -0
  490. package/src/providers/inference/adapter-factory.ts +32 -6
  491. package/src/providers/inference/auth.ts +12 -0
  492. package/src/providers/inference/backfill.ts +14 -1
  493. package/src/providers/inference/connections.ts +159 -34
  494. package/src/providers/inference/resolve-auth.ts +14 -4
  495. package/src/providers/model-catalog.ts +249 -12
  496. package/src/providers/model-intents.ts +3 -3
  497. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +235 -0
  498. package/src/providers/openai/chat-completions-provider.ts +169 -8
  499. package/src/providers/openrouter/client.ts +49 -4
  500. package/src/providers/{managed-proxy → platform-proxy}/constants.ts +4 -2
  501. package/src/providers/{managed-proxy → platform-proxy}/context.ts +3 -3
  502. package/src/providers/provider-availability.ts +17 -2
  503. package/src/providers/provider-catalog-visibility.ts +38 -0
  504. package/src/providers/provider-send-message.ts +27 -12
  505. package/src/providers/registry.ts +52 -15
  506. package/src/providers/retry.ts +47 -1
  507. package/src/runtime/__tests__/agent-wake.test.ts +152 -0
  508. package/src/runtime/agent-wake.ts +103 -15
  509. package/src/runtime/auth/route-policy.ts +21 -1
  510. package/src/runtime/btw-sidechain.ts +2 -0
  511. package/src/runtime/http-server.ts +7 -16
  512. package/src/runtime/http-types.ts +19 -47
  513. package/src/runtime/migrations/origin-mode.ts +1 -1
  514. package/src/runtime/pending-interactions.ts +1 -0
  515. package/src/runtime/routes/__tests__/bookmark-routes.test.ts +17 -0
  516. package/src/runtime/routes/__tests__/consolidation-routes.test.ts +258 -0
  517. package/src/runtime/routes/__tests__/conversation-management-routes.test.ts +5 -1
  518. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +172 -23
  519. package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +275 -44
  520. package/src/runtime/routes/__tests__/llm-call-sites-routes.test.ts +12 -0
  521. package/src/runtime/routes/__tests__/question-routes.test.ts +395 -0
  522. package/src/runtime/routes/__tests__/tts-routes.test.ts +64 -1
  523. package/src/runtime/routes/acp-routes-list.test.ts +143 -0
  524. package/src/runtime/routes/acp-routes.ts +5 -3
  525. package/src/runtime/routes/auth-routes.ts +1 -1
  526. package/src/runtime/routes/bookmark-routes.ts +5 -3
  527. package/src/runtime/routes/btw-routes.ts +5 -1
  528. package/src/runtime/routes/channel-availability-routes.ts +126 -0
  529. package/src/runtime/routes/consolidation-routes.ts +100 -0
  530. package/src/runtime/routes/conversation-cli-routes.ts +44 -3
  531. package/src/runtime/routes/conversation-list-routes.ts +3 -20
  532. package/src/runtime/routes/conversation-management-routes.ts +17 -42
  533. package/src/runtime/routes/conversation-query-routes.ts +99 -35
  534. package/src/runtime/routes/conversation-routes.ts +97 -11
  535. package/src/runtime/routes/documents-routes.ts +25 -86
  536. package/src/runtime/routes/group-routes.ts +5 -0
  537. package/src/runtime/routes/inbound-conversation.ts +28 -8
  538. package/src/runtime/routes/inbound-message-handler.ts +236 -41
  539. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +111 -0
  540. package/src/runtime/routes/inbound-stages/background-dispatch.ts +32 -1
  541. package/src/runtime/routes/inbound-stages/edit-intercept.ts +17 -4
  542. package/src/runtime/routes/index.ts +8 -0
  543. package/src/runtime/routes/inference-profile-session-handler.ts +17 -44
  544. package/src/runtime/routes/inference-profile-session-reaper.ts +7 -21
  545. package/src/runtime/routes/inference-provider-connection-routes.ts +199 -22
  546. package/src/runtime/routes/integrations/a2a.ts +235 -0
  547. package/src/runtime/routes/integrations/slack/share.ts +4 -52
  548. package/src/runtime/routes/integrations/slack/token.ts +43 -0
  549. package/src/runtime/routes/integrations/twilio.ts +6 -13
  550. package/src/runtime/routes/llm-call-sites-routes.ts +11 -1
  551. package/src/runtime/routes/notification-routes.ts +1 -1
  552. package/src/runtime/routes/oauth-commands-routes.ts +105 -15
  553. package/src/runtime/routes/oauth-lifecycle-routes.ts +43 -0
  554. package/src/runtime/routes/question-routes.ts +259 -0
  555. package/src/runtime/routes/rename-conversation-routes.ts +2 -33
  556. package/src/runtime/routes/schedule-routes.ts +4 -7
  557. package/src/runtime/routes/subagents-routes.ts +98 -18
  558. package/src/runtime/routes/telemetry-routes.ts +27 -0
  559. package/src/runtime/routes/tts-routes.ts +27 -2
  560. package/src/runtime/routes/workspace-routes.test.ts +43 -0
  561. package/src/runtime/routes/workspace-routes.ts +28 -0
  562. package/src/runtime/services/conversation-serializer.ts +39 -7
  563. package/src/runtime/sync/resource-sync-events.ts +93 -1
  564. package/src/schedule/schedule-store.ts +27 -2
  565. package/src/schedule/scheduler.ts +9 -1
  566. package/src/security/__tests__/untrusted-content.test.ts +86 -0
  567. package/src/security/untrusted-content.ts +93 -8
  568. package/src/skills/catalog-files.ts +1 -1
  569. package/src/skills/catalog-install.ts +233 -116
  570. package/src/skills/clawhub.ts +70 -13
  571. package/src/skills/managed-store.ts +4 -119
  572. package/src/skills/skillssh-registry.ts +27 -48
  573. package/src/subagent/manager.ts +17 -7
  574. package/src/telemetry/types.ts +113 -1
  575. package/src/telemetry/usage-telemetry-reporter.test.ts +312 -5
  576. package/src/telemetry/usage-telemetry-reporter.ts +113 -7
  577. package/src/tools/apps/executors.ts +58 -7
  578. package/src/tools/ask-question/ask-question-tool.test.ts +509 -0
  579. package/src/tools/ask-question/ask-question-tool.ts +304 -0
  580. package/src/tools/browser/browser-execution.ts +15 -11
  581. package/src/tools/computer-use/definitions.ts +3 -3
  582. package/src/tools/credentials/vault.ts +1 -1
  583. package/src/tools/document/document-tool.ts +124 -1
  584. package/src/tools/filesystem/edit.ts +1 -1
  585. package/src/tools/filesystem/list.ts +1 -1
  586. package/src/tools/filesystem/read.ts +1 -1
  587. package/src/tools/filesystem/write.ts +5 -2
  588. package/src/tools/host-filesystem/transfer.ts +1 -1
  589. package/src/tools/host-terminal/host-shell.ts +1 -1
  590. package/src/tools/memory/register.ts +1 -9
  591. package/src/tools/permission-checker.ts +1 -1
  592. package/src/tools/registry.ts +17 -7
  593. package/src/tools/schedule/create.ts +2 -2
  594. package/src/tools/schema-transforms.ts +7 -2
  595. package/src/tools/side-effects.ts +1 -0
  596. package/src/tools/skills/delete-managed.ts +4 -4
  597. package/src/tools/skills/execute.ts +1 -1
  598. package/src/tools/skills/scaffold-managed.ts +3 -2
  599. package/src/tools/subagent/notify-parent.ts +1 -1
  600. package/src/tools/system/request-permission.ts +2 -2
  601. package/src/tools/terminal/safe-env.ts +60 -1
  602. package/src/tools/tool-manifest.ts +2 -0
  603. package/src/tools/types.ts +107 -21
  604. package/src/tools/ui-surface/definitions.ts +6 -5
  605. package/src/tts/__tests__/provider-adapters.test.ts +76 -2
  606. package/src/tts/providers/elevenlabs-provider.ts +75 -1
  607. package/src/types/onboarding-context.ts +2 -0
  608. package/src/util/errors.ts +17 -0
  609. package/src/util/platform.ts +10 -0
  610. package/src/watcher/__tests__/engine.test.ts +22 -0
  611. package/src/watcher/engine.ts +6 -2
  612. package/src/workspace/migrations/057-repair-stale-gemini-model-ids.ts +80 -15
  613. package/src/workspace/migrations/072-seed-reply-suggestion-callsite.ts +35 -22
  614. package/src/workspace/migrations/073-repair-recall-callsite-empty-profile.ts +3 -1
  615. package/src/workspace/migrations/083-system-prompt-prefix-to-file.ts +191 -0
  616. package/src/workspace/migrations/084-remove-legacy-skills-index.ts +276 -0
  617. package/src/workspace/migrations/085-memory-v2-bm25-b-reembed-disabled-v2-pages.ts +137 -0
  618. package/src/workspace/migrations/086-revert-stale-gemini-mis-rewrites.ts +198 -0
  619. package/src/workspace/migrations/087-memory-router-balanced-profile.ts +91 -0
  620. package/src/workspace/migrations/registry.ts +10 -0
  621. package/src/workspace/migrations/runner.ts +39 -9
  622. package/src/workspace/migrations/types.ts +4 -0
  623. package/examples/plugins/echo/bun.lock +0 -25
  624. package/src/__tests__/context-window-manager.test.ts +0 -2481
  625. package/src/__tests__/guardian-action-conversation-turn.test.ts +0 -441
  626. package/src/context/__tests__/compact-prompt.test.ts +0 -63
  627. package/src/context/prompts/compact.md +0 -26
  628. package/src/memory/graph/__tests__/remember-description.test.ts +0 -55
  629. package/src/prompts/__tests__/build-cli-reference-section.test.ts +0 -37
  630. package/src/runtime/guardian-action-conversation-turn.ts +0 -99
@@ -5,11 +5,17 @@
5
5
  * finding messages by source identifiers, and managing raw payload storage.
6
6
  */
7
7
 
8
- import { and, eq, isNotNull } from "drizzle-orm";
8
+ import { and, eq, isNotNull, or } from "drizzle-orm";
9
9
  import { v4 as uuid } from "uuid";
10
10
 
11
+ import { readSlackMetadataFromMessageMetadata } from "../messaging/providers/slack/message-metadata.js";
11
12
  import { DAEMON_INTERNAL_ASSISTANT_ID } from "../runtime/assistant-scope.js";
12
- import { getOrCreateConversation } from "./conversation-key-store.js";
13
+ import { selectSlackMetaCandidateMetadata } from "./conversation-crud.js";
14
+ import {
15
+ getConversationByKey,
16
+ getOrCreateConversation,
17
+ setConversationKeyIfAbsent,
18
+ } from "./conversation-key-store.js";
13
19
  import { getDb } from "./db-connection.js";
14
20
  import { channelInboundEvents, conversations } from "./schema.js";
15
21
 
@@ -23,6 +29,144 @@ export interface InboundResult {
23
29
  export interface RecordInboundOptions {
24
30
  sourceMessageId?: string;
25
31
  assistantId?: string;
32
+ sourceThreadId?: string;
33
+ }
34
+
35
+ const SLACK_LEGACY_THREAD_EVIDENCE_BATCH_SIZE = 50;
36
+ const SLACK_LEGACY_THREAD_EVIDENCE_MAX_SCAN = 500;
37
+
38
+ function buildScopedConversationKeyForAssistant(
39
+ assistantId: string,
40
+ sourceChannel: string,
41
+ externalChatId: string,
42
+ sourceThreadId?: string | null,
43
+ ): string {
44
+ const threadId = sourceThreadId?.trim();
45
+ if (sourceChannel === "slack" && threadId) {
46
+ return `asst:${assistantId}:${sourceChannel}:${externalChatId}:thread:${threadId}`;
47
+ }
48
+ return `asst:${assistantId}:${sourceChannel}:${externalChatId}`;
49
+ }
50
+
51
+ export function buildScopedConversationKey(
52
+ sourceChannel: string,
53
+ externalChatId: string,
54
+ sourceThreadId?: string | null,
55
+ ): string {
56
+ return buildScopedConversationKeyForAssistant(
57
+ DAEMON_INTERNAL_ASSISTANT_ID,
58
+ sourceChannel,
59
+ externalChatId,
60
+ sourceThreadId,
61
+ );
62
+ }
63
+
64
+ function legacySlackConversationHasThreadEvidence(
65
+ conversationId: string,
66
+ externalChatId: string,
67
+ sourceThreadId: string,
68
+ ): boolean {
69
+ const db = getDb();
70
+ const inboundEvidence = db
71
+ .select({ id: channelInboundEvents.id })
72
+ .from(channelInboundEvents)
73
+ .where(
74
+ and(
75
+ eq(channelInboundEvents.conversationId, conversationId),
76
+ eq(channelInboundEvents.sourceChannel, "slack"),
77
+ eq(channelInboundEvents.externalChatId, externalChatId),
78
+ or(
79
+ eq(channelInboundEvents.sourceMessageId, sourceThreadId),
80
+ eq(channelInboundEvents.externalMessageId, sourceThreadId),
81
+ ),
82
+ ),
83
+ )
84
+ .get();
85
+
86
+ if (inboundEvidence) {
87
+ return true;
88
+ }
89
+
90
+ let offset = 0;
91
+ while (offset < SLACK_LEGACY_THREAD_EVIDENCE_MAX_SCAN) {
92
+ const remaining = SLACK_LEGACY_THREAD_EVIDENCE_MAX_SCAN - offset;
93
+ const batchLimit = Math.min(
94
+ SLACK_LEGACY_THREAD_EVIDENCE_BATCH_SIZE,
95
+ remaining,
96
+ );
97
+ const metadataRows = selectSlackMetaCandidateMetadata(
98
+ conversationId,
99
+ batchLimit,
100
+ offset,
101
+ { includeFlatLegacy: true },
102
+ );
103
+
104
+ if (metadataRows.length === 0) return false;
105
+ for (const metadata of metadataRows) {
106
+ const slackMeta = readSlackMetadataFromMessageMetadata(metadata, {
107
+ allowFlatLegacy: true,
108
+ });
109
+ if (
110
+ slackMeta?.channelId === externalChatId &&
111
+ slackMeta.threadTs === sourceThreadId
112
+ ) {
113
+ return true;
114
+ }
115
+ }
116
+
117
+ if (metadataRows.length < batchLimit) return false;
118
+ offset += metadataRows.length;
119
+ }
120
+
121
+ return false;
122
+ }
123
+
124
+ function resolveInboundConversation(
125
+ assistantId: string,
126
+ sourceChannel: string,
127
+ externalChatId: string,
128
+ sourceThreadId?: string | null,
129
+ ): { conversationId: string } {
130
+ const threadedKey = buildScopedConversationKeyForAssistant(
131
+ assistantId,
132
+ sourceChannel,
133
+ externalChatId,
134
+ sourceThreadId,
135
+ );
136
+
137
+ const threadId = sourceThreadId?.trim();
138
+ if (sourceChannel !== "slack" || !threadId) {
139
+ return getOrCreateConversation(threadedKey);
140
+ }
141
+
142
+ const threadedMapping = getConversationByKey(threadedKey);
143
+ if (threadedMapping) {
144
+ return { conversationId: threadedMapping.conversationId };
145
+ }
146
+
147
+ const legacyKey = buildScopedConversationKeyForAssistant(
148
+ assistantId,
149
+ sourceChannel,
150
+ externalChatId,
151
+ null,
152
+ );
153
+ const legacyMapping = getConversationByKey(legacyKey);
154
+ if (
155
+ legacyMapping &&
156
+ legacySlackConversationHasThreadEvidence(
157
+ legacyMapping.conversationId,
158
+ externalChatId,
159
+ threadId,
160
+ )
161
+ ) {
162
+ setConversationKeyIfAbsent(threadedKey, legacyMapping.conversationId);
163
+ const aliasedMapping = getConversationByKey(threadedKey);
164
+ if (aliasedMapping) {
165
+ return { conversationId: aliasedMapping.conversationId };
166
+ }
167
+ }
168
+
169
+ return getOrCreateConversation(threadedKey);
26
170
  }
27
171
 
28
172
  /**
@@ -62,8 +206,12 @@ export function recordInbound(
62
206
  }
63
207
 
64
208
  const assistantId = options?.assistantId ?? DAEMON_INTERNAL_ASSISTANT_ID;
65
- const scopedKey = `asst:${assistantId}:${sourceChannel}:${externalChatId}`;
66
- const mapping = getOrCreateConversation(scopedKey);
209
+ const mapping = resolveInboundConversation(
210
+ assistantId,
211
+ sourceChannel,
212
+ externalChatId,
213
+ options?.sourceThreadId,
214
+ );
67
215
  const now = Date.now();
68
216
  const eventId = uuid();
69
217
 
@@ -176,4 +324,3 @@ export function clearPayload(eventId: string): void {
176
324
  .where(eq(channelInboundEvents.id, eventId))
177
325
  .run();
178
326
  }
179
-
@@ -4,8 +4,8 @@ import { isAssistantFeatureFlagEnabled } from "../config/assistant-feature-flags
4
4
  import { getOllamaBaseUrlEnv } from "../config/env.js";
5
5
  import { resolveCallSiteConfig } from "../config/llm-resolver.js";
6
6
  import type { AssistantConfig } from "../config/types.js";
7
- import { MANAGED_PROVIDER_META } from "../providers/managed-proxy/constants.js";
8
- import { resolveManagedProxyContext } from "../providers/managed-proxy/context.js";
7
+ import { PLATFORM_PROVIDER_META } from "../providers/platform-proxy/constants.js";
8
+ import { resolveManagedProxyContext } from "../providers/platform-proxy/context.js";
9
9
  import { getProviderKeyAsync } from "../security/secure-keys.js";
10
10
  import { getLogger } from "../util/logger.js";
11
11
  import { GeminiEmbeddingBackend } from "./embedding-gemini.js";
@@ -311,7 +311,7 @@ export async function selectEmbeddingBackend(
311
311
  ) {
312
312
  const proxyCtx = await resolveManagedProxyContext();
313
313
  if (proxyCtx.enabled) {
314
- const meta = MANAGED_PROVIDER_META["gemini"];
314
+ const meta = PLATFORM_PROVIDER_META["gemini"];
315
315
  if (meta?.managed && meta.proxyPath) {
316
316
  const managedBaseUrl = `${proxyCtx.platformBaseUrl}${meta.proxyPath}`;
317
317
  const managedModel = config.memory.embeddings.geminiModel;
@@ -685,7 +685,7 @@ async function selectFallbackBackends(
685
685
  ) {
686
686
  // Try managed proxy Gemini as fallback when no direct key exists.
687
687
  const proxyCtx = await resolveManagedProxyContext();
688
- const meta = MANAGED_PROVIDER_META["gemini"];
688
+ const meta = PLATFORM_PROVIDER_META["gemini"];
689
689
  if (proxyCtx.enabled && meta?.managed && meta.proxyPath) {
690
690
  const managedBaseUrl = `${proxyCtx.platformBaseUrl}${meta.proxyPath}`;
691
691
  const managedModel = config.memory.embeddings.geminiModel;
@@ -7,7 +7,7 @@
7
7
  * list APIs.
8
8
  */
9
9
 
10
- import { and, eq, inArray } from "drizzle-orm";
10
+ import { and, eq, inArray, isNull } from "drizzle-orm";
11
11
 
12
12
  import { getDb } from "./db-connection.js";
13
13
  import { externalConversationBindings } from "./schema.js";
@@ -16,6 +16,7 @@ export interface ExternalConversationBinding {
16
16
  conversationId: string;
17
17
  sourceChannel: string;
18
18
  externalChatId: string;
19
+ externalThreadId?: string | null;
19
20
  externalUserId?: string | null;
20
21
  displayName?: string | null;
21
22
  username?: string | null;
@@ -29,11 +30,19 @@ export interface UpsertBindingInput {
29
30
  conversationId: string;
30
31
  sourceChannel: string;
31
32
  externalChatId: string;
33
+ externalThreadId?: string | null;
32
34
  externalUserId?: string | null;
33
35
  displayName?: string | null;
34
36
  username?: string | null;
35
37
  }
36
38
 
39
+ function normalizeExternalThreadId(
40
+ externalThreadId?: string | null,
41
+ ): string | null {
42
+ const trimmed = externalThreadId?.trim();
43
+ return trimmed ? trimmed : null;
44
+ }
45
+
37
46
  /**
38
47
  * Insert or update an external conversation binding on conflict (conversationId).
39
48
  * On conflict, updates channel metadata and timestamps.
@@ -41,12 +50,14 @@ export interface UpsertBindingInput {
41
50
  export function upsertBinding(input: UpsertBindingInput): void {
42
51
  const db = getDb();
43
52
  const now = Date.now();
53
+ const externalThreadId = normalizeExternalThreadId(input.externalThreadId);
44
54
 
45
- // If a stale binding exists for this (sourceChannel, externalChatId) under a
55
+ // If a stale binding exists for this channel/chat/thread tuple under a
46
56
  // different conversationId, remove it first so the unique index is not violated.
47
- const existing = getBindingByChannelChat(
57
+ const existing = getBindingByChannelChatThread(
48
58
  input.sourceChannel,
49
59
  input.externalChatId,
60
+ externalThreadId,
50
61
  );
51
62
  if (existing && existing.conversationId !== input.conversationId) {
52
63
  db.delete(externalConversationBindings)
@@ -64,6 +75,7 @@ export function upsertBinding(input: UpsertBindingInput): void {
64
75
  conversationId: input.conversationId,
65
76
  sourceChannel: input.sourceChannel,
66
77
  externalChatId: input.externalChatId,
78
+ externalThreadId,
67
79
  externalUserId: input.externalUserId ?? null,
68
80
  displayName: input.displayName ?? null,
69
81
  username: input.username ?? null,
@@ -76,6 +88,7 @@ export function upsertBinding(input: UpsertBindingInput): void {
76
88
  set: {
77
89
  sourceChannel: input.sourceChannel,
78
90
  externalChatId: input.externalChatId,
91
+ externalThreadId,
79
92
  externalUserId: input.externalUserId ?? null,
80
93
  displayName: input.displayName ?? null,
81
94
  username: input.username ?? null,
@@ -95,15 +108,18 @@ export function upsertOutboundBinding(input: {
95
108
  conversationId: string;
96
109
  sourceChannel: string;
97
110
  externalChatId: string;
111
+ externalThreadId?: string | null;
98
112
  }): void {
99
113
  const db = getDb();
100
114
  const now = Date.now();
115
+ const externalThreadId = normalizeExternalThreadId(input.externalThreadId);
101
116
 
102
- // If a stale binding exists for this (sourceChannel, externalChatId) under a
117
+ // If a stale binding exists for this channel/chat/thread tuple under a
103
118
  // different conversationId, remove it first so the unique index is not violated.
104
- const existing = getBindingByChannelChat(
119
+ const existing = getBindingByChannelChatThread(
105
120
  input.sourceChannel,
106
121
  input.externalChatId,
122
+ externalThreadId,
107
123
  );
108
124
  if (existing && existing.conversationId !== input.conversationId) {
109
125
  db.delete(externalConversationBindings)
@@ -121,6 +137,7 @@ export function upsertOutboundBinding(input: {
121
137
  conversationId: input.conversationId,
122
138
  sourceChannel: input.sourceChannel,
123
139
  externalChatId: input.externalChatId,
140
+ externalThreadId,
124
141
  externalUserId: null,
125
142
  displayName: null,
126
143
  username: null,
@@ -133,6 +150,7 @@ export function upsertOutboundBinding(input: {
133
150
  set: {
134
151
  sourceChannel: input.sourceChannel,
135
152
  externalChatId: input.externalChatId,
153
+ externalThreadId,
136
154
  updatedAt: now,
137
155
  lastOutboundAt: now,
138
156
  },
@@ -161,8 +179,20 @@ export function getBindingByConversation(
161
179
  export function getBindingByChannelChat(
162
180
  sourceChannel: string,
163
181
  externalChatId: string,
182
+ ): ExternalConversationBinding | null {
183
+ return getBindingByChannelChatThread(sourceChannel, externalChatId, null);
184
+ }
185
+
186
+ /**
187
+ * Look up an external binding by channel + external chat ID + optional thread ID.
188
+ */
189
+ export function getBindingByChannelChatThread(
190
+ sourceChannel: string,
191
+ externalChatId: string,
192
+ externalThreadId?: string | null,
164
193
  ): ExternalConversationBinding | null {
165
194
  const db = getDb();
195
+ const normalizedThreadId = normalizeExternalThreadId(externalThreadId);
166
196
  const row = db
167
197
  .select()
168
198
  .from(externalConversationBindings)
@@ -170,6 +200,12 @@ export function getBindingByChannelChat(
170
200
  and(
171
201
  eq(externalConversationBindings.sourceChannel, sourceChannel),
172
202
  eq(externalConversationBindings.externalChatId, externalChatId),
203
+ normalizedThreadId
204
+ ? eq(
205
+ externalConversationBindings.externalThreadId,
206
+ normalizedThreadId,
207
+ )
208
+ : isNull(externalConversationBindings.externalThreadId),
173
209
  ),
174
210
  )
175
211
  .get();
@@ -195,6 +231,31 @@ export function deleteBindingByChannelChat(
195
231
  .run();
196
232
  }
197
233
 
234
+ /**
235
+ * Remove an external binding by channel + external chat ID + thread ID.
236
+ */
237
+ export function deleteBindingByChannelChatThread(
238
+ sourceChannel: string,
239
+ externalChatId: string,
240
+ externalThreadId: string,
241
+ ): void {
242
+ const db = getDb();
243
+ const normalizedThreadId = normalizeExternalThreadId(externalThreadId);
244
+ if (!normalizedThreadId) {
245
+ deleteBindingByChannelChat(sourceChannel, externalChatId);
246
+ return;
247
+ }
248
+ db.delete(externalConversationBindings)
249
+ .where(
250
+ and(
251
+ eq(externalConversationBindings.sourceChannel, sourceChannel),
252
+ eq(externalConversationBindings.externalChatId, externalChatId),
253
+ eq(externalConversationBindings.externalThreadId, normalizedThreadId),
254
+ ),
255
+ )
256
+ .run();
257
+ }
258
+
198
259
  /**
199
260
  * Get bindings for multiple conversation IDs at once.
200
261
  * Returns a map of conversationId -> binding for efficient lookup.
@@ -107,18 +107,20 @@ mock.module("@qdrant/js-client-rest", () => ({
107
107
  QdrantClient: MockQdrantClient,
108
108
  }));
109
109
 
110
+ const embedWithBackendMock = mock(async (_config, texts: string[]) => ({
111
+ provider: "local",
112
+ model: "test-model",
113
+ vectors: texts.map(() => [0.1, 0.2, 0.3]) as number[][],
114
+ }));
115
+ const generateSparseEmbeddingMock = mock((_text: string) => ({
116
+ indices: [1, 2, 3],
117
+ values: [0.5, 0.5, 0.5] as number[],
118
+ }));
110
119
  const realEmbeddingBackend = await import("../../embedding-backend.js");
111
120
  mock.module("../../embedding-backend.js", () => ({
112
121
  ...realEmbeddingBackend,
113
- embedWithBackend: async () => ({
114
- provider: "local",
115
- model: "test-model",
116
- vectors: [[0.1, 0.2, 0.3]] as number[][],
117
- }),
118
- generateSparseEmbedding: () => ({
119
- indices: [1, 2, 3],
120
- values: [0.5, 0.5, 0.5] as number[],
121
- }),
122
+ embedWithBackend: embedWithBackendMock,
123
+ generateSparseEmbedding: generateSparseEmbeddingMock,
122
124
  }));
123
125
 
124
126
  const realQdrantClient = await import("../../qdrant-client.js");
@@ -175,7 +177,7 @@ const { migrateActivationState } =
175
177
  await import("../../migrations/232-activation-state.js");
176
178
  const schema = await import("../../schema.js");
177
179
  const { _resetMemoryV2QdrantForTests } = await import("../../v2/qdrant.js");
178
- const { hydrate: hydrateActivationState } =
180
+ const { hydrate: hydrateActivationState, save: saveActivationState } =
179
181
  await import("../../v2/activation-store.js");
180
182
 
181
183
  // The wiring layer calls `getDb()` to fetch the SQLite handle. We mock
@@ -213,9 +215,14 @@ function createTestDb(): DrizzleDb {
213
215
  return db;
214
216
  }
215
217
 
216
- function makeConfig(v2Enabled: boolean): AssistantConfig {
218
+ function makeConfig(v2Enabled: boolean, memoryEnabled = true): AssistantConfig {
219
+ // Pin `router.enabled: false` so these tests exercise the activation
220
+ // pipeline. Router-mode coverage lives in `memory/v2/__tests__/injection.test.ts`.
217
221
  return applyNestedDefaults({
218
- memory: { v2: { enabled: v2Enabled } },
222
+ memory: {
223
+ enabled: memoryEnabled,
224
+ v2: { enabled: v2Enabled, router: { enabled: false } },
225
+ },
219
226
  }) as AssistantConfig;
220
227
  }
221
228
 
@@ -293,6 +300,8 @@ beforeEach(() => {
293
300
  qdrantState.queryResponses.sparse.length = 0;
294
301
  loadContextMemoryMock.mockClear();
295
302
  retrieveForTurnMock.mockClear();
303
+ embedWithBackendMock.mockClear();
304
+ generateSparseEmbeddingMock.mockClear();
296
305
  _resetMemoryV2QdrantForTests();
297
306
  });
298
307
 
@@ -390,6 +399,59 @@ describe("ConversationGraphMemory.prepareMemory — v2 routing (per-turn path)",
390
399
  expect(firstBlock.text).toContain("# memory/concepts/alice-vscode.md");
391
400
  });
392
401
 
402
+ test("per-turn dense embedding is computed from combined assistant+user text", async () => {
403
+ // Short referential follow-ups ("do that one") carry no semantic signal
404
+ // on their own — the dense PKB query embedding must mirror v1's
405
+ // `retrieveForTurn` and combine the prior assistant turn so hint search
406
+ // still resolves what "that one" refers to. The sparse vector matches
407
+ // v1 by using the user message alone so lexical signal isn't diluted.
408
+ stageTurn([{ slug: "alice-vscode", denseScore: 0.9 }]);
409
+
410
+ const memory = makeMemory();
411
+ const config = makeConfig(true);
412
+ const assistantText =
413
+ "Alice prefers VS Code as her editor — she finds the extension ecosystem unmatched.";
414
+ const userText = "do that one";
415
+ const messages: Message[] = [
416
+ {
417
+ role: "user",
418
+ content: [
419
+ { type: "text" as const, text: "what editors did we cover?" },
420
+ ],
421
+ },
422
+ {
423
+ role: "assistant",
424
+ content: [{ type: "text" as const, text: assistantText }],
425
+ },
426
+ { role: "user", content: [{ type: "text" as const, text: userText }] },
427
+ ];
428
+
429
+ await memory.prepareMemory(
430
+ messages,
431
+ config,
432
+ new AbortController().signal,
433
+ noopEvent,
434
+ );
435
+
436
+ // v1's `retrieveForTurn` joins assistantLast + userLast with "\n\n" and
437
+ // embeds the combined string as the dense query vector. Assert the v2
438
+ // path makes the exact same embed call somewhere during this turn.
439
+ const expectedCombined = `${assistantText}\n\n${userText}`;
440
+ const matchingCall = embedWithBackendMock.mock.calls.find((call) => {
441
+ const texts = call[1] as string[];
442
+ return texts.length === 1 && texts[0] === expectedCombined;
443
+ });
444
+ expect(matchingCall).toBeDefined();
445
+
446
+ // Sparse embedding for the per-turn query uses userLast only.
447
+ expect(generateSparseEmbeddingMock.mock.calls).toContainEqual([userText]);
448
+ expect(
449
+ generateSparseEmbeddingMock.mock.calls.some((call) =>
450
+ (call[0] as string).includes(assistantText),
451
+ ),
452
+ ).toBe(false);
453
+ });
454
+
393
455
  test("config on with empty Qdrant hits → no v2 block, v1 fallback skipped", async () => {
394
456
  // No `stageTurn` call — every channel returns `{ points: [] }` so the
395
457
  // candidate set is empty and `injectMemoryV2Block` returns block=null.
@@ -461,6 +523,50 @@ describe("ConversationGraphMemory.prepareMemory — v2 routing (context-load pat
461
523
  });
462
524
  });
463
525
 
526
+ describe("ConversationGraphMemory.prepareMemory — memory.enabled gate", () => {
527
+ test("memory.enabled=false short-circuits per-turn path: mode=none, no injection, v2/v1 not called", async () => {
528
+ stageTurn([{ slug: "alice-vscode", denseScore: 0.9 }]);
529
+
530
+ const memory = makeMemory();
531
+ const config = makeConfig(true, false);
532
+ const messages = makeMessages();
533
+
534
+ const result = await memory.prepareMemory(
535
+ messages,
536
+ config,
537
+ new AbortController().signal,
538
+ noopEvent,
539
+ );
540
+
541
+ expect(result.mode).toBe("none");
542
+ expect(result.injectedBlockText).toBeNull();
543
+ expect(result.runMessages).toEqual(messages);
544
+ expect(retrieveForTurnMock).not.toHaveBeenCalled();
545
+ expect(loadContextMemoryMock).not.toHaveBeenCalled();
546
+ });
547
+
548
+ test("memory.enabled=false short-circuits context-load path: mode=none, no injection, v2/v1 not called", async () => {
549
+ stageTurn([{ slug: "alice-vscode", denseScore: 0.9 }]);
550
+
551
+ const memory = new ConversationGraphMemory("conv-test-master-off");
552
+ const config = makeConfig(true, false);
553
+ const messages = makeMessages("first message of the conversation here");
554
+
555
+ const result = await memory.prepareMemory(
556
+ messages,
557
+ config,
558
+ new AbortController().signal,
559
+ noopEvent,
560
+ );
561
+
562
+ expect(result.mode).toBe("none");
563
+ expect(result.injectedBlockText).toBeNull();
564
+ expect(result.runMessages).toEqual(messages);
565
+ expect(loadContextMemoryMock).not.toHaveBeenCalled();
566
+ expect(retrieveForTurnMock).not.toHaveBeenCalled();
567
+ });
568
+ });
569
+
464
570
  describe("ConversationGraphMemory.onCompacted — v2 activation eviction", () => {
465
571
  test("clears everInjected so a previously-injected slug can re-attach", async () => {
466
572
  // Without this wiring, `selectInjections` keeps subtracting the slug from
@@ -504,4 +610,36 @@ describe("ConversationGraphMemory.onCompacted — v2 activation eviction", () =>
504
610
  "# memory/concepts/alice-vscode.md",
505
611
  );
506
612
  });
613
+
614
+ test("clears everInjected entries whose turn exceeds the tracker's currentTurn (zombie drift)", async () => {
615
+ // Regression: under the prior turn-bounded eviction, entries with `turn >
616
+ // tracker.currentTurn` survived `onCompacted` forever. This can happen
617
+ // after a non-graceful shutdown: `everInjected` is persisted every turn
618
+ // while the tracker snapshot is only persisted on graceful dispose, so a
619
+ // SIGKILL'd session followed by a reload restores the tracker from an
620
+ // older snapshot with a lower currentTurn while keeping the high-turn
621
+ // entries on disk.
622
+ const conversationId = "conv-test-zombie-drift";
623
+ const memory = new ConversationGraphMemory(conversationId);
624
+
625
+ // Seed the simulated post-crash state directly: tracker stays at
626
+ // currentTurn=0 (default for a fresh ConversationGraphMemory), while the
627
+ // persisted row carries everInjected entries from turns 10 and 20 (left
628
+ // over from a prior session that never disposed cleanly).
629
+ await saveActivationState(testDbHandle!, conversationId, {
630
+ messageId: "msg-zombie",
631
+ state: {},
632
+ everInjected: [
633
+ { slug: "alice-vscode", turn: 10 },
634
+ { slug: "bob-pkg-mgr", turn: 20 },
635
+ ],
636
+ currentTurn: 0,
637
+ updatedAt: 1,
638
+ });
639
+
640
+ await memory.onCompacted(0);
641
+
642
+ const after = await hydrateActivationState(testDbHandle!, conversationId);
643
+ expect(after?.everInjected).toEqual([]);
644
+ });
507
645
  });