@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,5 +1,11 @@
1
1
  import { and, count, desc, eq, sql } from "drizzle-orm";
2
2
 
3
+ import {
4
+ parseExternalContentEnvelope,
5
+ type UntrustedContentSource,
6
+ unwrapExternalContentForDisplay,
7
+ wrapUntrustedContent,
8
+ } from "../security/untrusted-content.js";
3
9
  import { getLogger } from "../util/logger.js";
4
10
  import type { ConversationRow } from "./conversation-crud.js";
5
11
  import { parseConversation } from "./conversation-crud.js";
@@ -55,9 +61,14 @@ export function listConversations(
55
61
  // SQLite file without running migrations in-process, so legacy private rows
56
62
  // can briefly exist before migration cleanup. Hide them from foreground
57
63
  // lists until the next migration pass deletes them.
64
+ //
65
+ // group_id is checked alongside conversationType so that conversations
66
+ // routed to system:background (e.g. heartbeat) via conversationMetadata
67
+ // but created with conversationType "standard" (vellum channel strategy)
68
+ // appear in the correct bucket.
58
69
  const typeCond = backgroundOnly
59
- ? sql`${conversations.conversationType} IN ('background', 'scheduled') AND (${conversations.source} IS NULL OR ${conversations.source} != 'subagent')`
60
- : sql`${conversations.conversationType} NOT IN ('background', 'scheduled', 'private')`;
70
+ ? sql`(${conversations.conversationType} IN ('background', 'scheduled') OR group_id IN ('system:background', 'system:scheduled')) AND (${conversations.source} IS NULL OR ${conversations.source} != 'subagent')`
71
+ : sql`${conversations.conversationType} NOT IN ('background', 'scheduled', 'private') AND COALESCE(group_id, 'system:all') NOT IN ('system:background', 'system:scheduled')`;
61
72
  const where = includeArchived
62
73
  ? typeCond
63
74
  : sql`${typeCond} AND ${conversations.archivedAt} IS NULL`;
@@ -151,10 +162,11 @@ export function listConversationsByTitlePrefix(
151
162
  }
152
163
 
153
164
  export function countConversations(backgroundOnly = false): number {
165
+ ensureGroupMigration();
154
166
  const db = getDb();
155
167
  const where = backgroundOnly
156
- ? sql`${conversations.conversationType} IN ('background', 'scheduled') AND (${conversations.source} IS NULL OR ${conversations.source} != 'subagent')`
157
- : sql`${conversations.conversationType} NOT IN ('background', 'scheduled')`;
168
+ ? sql`(${conversations.conversationType} IN ('background', 'scheduled') OR group_id IN ('system:background', 'system:scheduled')) AND (${conversations.source} IS NULL OR ${conversations.source} != 'subagent')`
169
+ : sql`${conversations.conversationType} NOT IN ('background', 'scheduled', 'private') AND COALESCE(group_id, 'system:all') NOT IN ('system:background', 'system:scheduled')`;
158
170
  const [{ total }] = db
159
171
  .select({ total: count() })
160
172
  .from(conversations)
@@ -233,6 +245,7 @@ export function searchConversations(
233
245
  ): ConversationSearchResult[] {
234
246
  if (!query.trim()) return [];
235
247
 
248
+ ensureGroupMigration();
236
249
  const db = getDb();
237
250
  const limit = opts?.limit ?? 20;
238
251
  const maxMsgsPerConv = opts?.maxMessagesPerConversation ?? 3;
@@ -262,7 +275,7 @@ export function searchConversations(
262
275
  FROM messages_fts f
263
276
  JOIN messages m ON m.id = f.message_id
264
277
  JOIN conversations c ON c.id = m.conversation_id
265
- WHERE messages_fts MATCH ? AND c.conversation_type NOT IN ('background', 'scheduled', 'private') AND c.archived_at IS NULL
278
+ WHERE messages_fts MATCH ? AND c.conversation_type NOT IN ('background', 'scheduled', 'private') AND COALESCE(c.group_id, 'system:all') NOT IN ('system:background', 'system:scheduled') AND c.archived_at IS NULL
266
279
  LIMIT 1000
267
280
  `,
268
281
  ftsMatch,
@@ -287,7 +300,7 @@ export function searchConversations(
287
300
  SELECT DISTINCT m.conversation_id
288
301
  FROM messages m
289
302
  JOIN conversations c ON c.id = m.conversation_id
290
- WHERE m.content LIKE ? ESCAPE '\\' AND c.conversation_type NOT IN ('background', 'scheduled', 'private') AND c.archived_at IS NULL
303
+ WHERE m.content LIKE ? ESCAPE '\\' AND c.conversation_type NOT IN ('background', 'scheduled', 'private') AND COALESCE(c.group_id, 'system:all') NOT IN ('system:background', 'system:scheduled') AND c.archived_at IS NULL
291
304
  LIMIT 1000
292
305
  `,
293
306
  likePattern,
@@ -302,6 +315,7 @@ export function searchConversations(
302
315
  .where(
303
316
  and(
304
317
  sql`${conversations.conversationType} NOT IN ('background', 'scheduled', 'private')`,
318
+ sql`COALESCE(group_id, 'system:all') NOT IN ('system:background', 'system:scheduled')`,
305
319
  sql`${conversations.title} LIKE ${titlePattern} ESCAPE '\\'`,
306
320
  sql`${conversations.archivedAt} IS NULL`,
307
321
  ),
@@ -403,29 +417,68 @@ export function searchConversations(
403
417
  * text; we extract a readable snippet in either case.
404
418
  */
405
419
  export function buildExcerpt(rawContent: string, query: string): string {
420
+ return buildExcerptWithExternalContentMode(rawContent, query, "display");
421
+ }
422
+
423
+ /**
424
+ * Build an excerpt for model-facing recall evidence. Unlike display excerpts,
425
+ * this keeps complete external_content envelopes around untrusted snippets so
426
+ * the model still sees clear third-party content boundaries.
427
+ */
428
+ export function buildRecallEvidenceExcerpt(
429
+ rawContent: string,
430
+ query: string,
431
+ ): string {
432
+ return buildExcerptWithExternalContentMode(rawContent, query, "preserve");
433
+ }
434
+
435
+ function buildExcerptWithExternalContentMode(
436
+ rawContent: string,
437
+ query: string,
438
+ externalContentMode: "display" | "preserve",
439
+ ): string {
406
440
  // Try to extract plain text from JSON content blocks first.
407
441
  let text = rawContent;
408
442
  try {
409
443
  const parsed = JSON.parse(rawContent);
410
444
  if (Array.isArray(parsed)) {
411
445
  const parts: string[] = [];
446
+ let preservedExternalContent = false;
412
447
  for (const block of parsed) {
413
448
  if (typeof block === "object" && block != null) {
414
449
  if (block.type === "text" && typeof block.text === "string") {
415
- parts.push(block.text);
450
+ if (externalContentMode === "display") {
451
+ parts.push(unwrapExternalContentForDisplay(block.text));
452
+ } else {
453
+ const excerpt = buildRecallEvidenceText(block.text, query);
454
+ parts.push(excerpt.text);
455
+ preservedExternalContent ||= excerpt.preservedExternalContent;
456
+ }
416
457
  } else if (
417
458
  block.type === "tool_result" ||
418
459
  block.type === "web_search_tool_result"
419
460
  ) {
420
461
  const inner = Array.isArray(block.content) ? block.content : [];
421
462
  for (const ib of inner) {
422
- if (ib?.type === "text" && typeof ib.text === "string")
423
- parts.push(ib.text);
463
+ if (ib?.type === "text" && typeof ib.text === "string") {
464
+ if (externalContentMode === "display") {
465
+ parts.push(unwrapExternalContentForDisplay(ib.text));
466
+ } else {
467
+ const excerpt = buildRecallEvidenceText(ib.text, query);
468
+ parts.push(excerpt.text);
469
+ preservedExternalContent ||= excerpt.preservedExternalContent;
470
+ }
471
+ }
424
472
  }
425
473
  }
426
474
  }
427
475
  }
428
- if (parts.length > 0) text = parts.join(" ");
476
+ if (parts.length > 0) {
477
+ text = parts.join(" ");
478
+ if (externalContentMode === "preserve" && preservedExternalContent) {
479
+ return text;
480
+ }
481
+ }
429
482
  } else if (typeof parsed === "string") {
430
483
  text = parsed;
431
484
  }
@@ -433,6 +486,53 @@ export function buildExcerpt(rawContent: string, query: string): string {
433
486
  // Not JSON — use as-is
434
487
  }
435
488
 
489
+ if (externalContentMode === "display") {
490
+ text = unwrapExternalContentForDisplay(text);
491
+ } else {
492
+ const envelope = parseExternalContentEnvelope(text);
493
+ if (envelope) {
494
+ const innerExcerpt = buildExcerptFromText(envelope.content, query);
495
+ return wrapRecallEvidenceExcerpt(
496
+ innerExcerpt,
497
+ envelope.source,
498
+ envelope.origin,
499
+ );
500
+ }
501
+ }
502
+
503
+ return buildExcerptFromText(text, query);
504
+ }
505
+
506
+ function buildRecallEvidenceText(
507
+ text: string,
508
+ query: string,
509
+ ): { text: string; preservedExternalContent: boolean } {
510
+ const envelope = parseExternalContentEnvelope(text);
511
+ if (!envelope) {
512
+ return { text, preservedExternalContent: false };
513
+ }
514
+ const innerExcerpt = buildExcerptFromText(envelope.content, query);
515
+ return {
516
+ text: wrapRecallEvidenceExcerpt(
517
+ innerExcerpt,
518
+ envelope.source,
519
+ envelope.origin,
520
+ ),
521
+ preservedExternalContent: true,
522
+ };
523
+ }
524
+
525
+ function wrapRecallEvidenceExcerpt(
526
+ excerpt: string,
527
+ source: UntrustedContentSource,
528
+ origin?: string,
529
+ ): string {
530
+ return origin
531
+ ? wrapUntrustedContent(excerpt, { source, sourceDetail: origin })
532
+ : wrapUntrustedContent(excerpt, { source });
533
+ }
534
+
535
+ function buildExcerptFromText(text: string, query: string): string {
436
536
  const WINDOW = 100;
437
537
  const lowerText = text.toLowerCase();
438
538
  const lowerQuery = query.toLowerCase();
@@ -32,6 +32,7 @@ import {
32
32
  createMessagesFts,
33
33
  createNotificationTables,
34
34
  createOAuthTables,
35
+ createOnboardingEventsTable,
35
36
  createScopedApprovalGrantsTable,
36
37
  createSequenceTables,
37
38
  createTasksAndWorkItemsTables,
@@ -94,6 +95,7 @@ import {
94
95
  migrateDropSetupSkillIdColumn,
95
96
  migrateDropSimplifiedMemory,
96
97
  migrateDropUsageCompositeIndexes,
98
+ migrateExternalConversationBindingThreadId,
97
99
  migrateFkCascadeRebuilds,
98
100
  migrateGuardianActionFollowup,
99
101
  migrateGuardianActionSupersession,
@@ -122,6 +124,7 @@ import {
122
124
  migrateMessagesConversationCreatedAtIndex,
123
125
  migrateMessagesFtsBackfill,
124
126
  migrateNormalizePhoneIdentities,
127
+ migrateNormalizeSlackExternalContent,
125
128
  migrateNormalizeUserFileByPrincipal,
126
129
  migrateNotificationDeliveryThreadDecision,
127
130
  migrateOAuthAppsClientSecretPath,
@@ -424,6 +427,9 @@ export function initializeDb(): void {
424
427
  migrateProviderConnectionStatusLabel,
425
428
  migrateMemoryRetrospectiveState,
426
429
  migrateBackfillProviderConnectionLabel,
430
+ migrateExternalConversationBindingThreadId,
431
+ createOnboardingEventsTable,
432
+ migrateNormalizeSlackExternalContent,
427
433
  ];
428
434
 
429
435
  // Run each migration step, catching and logging individual failures so one
@@ -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.