@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
@@ -28,6 +28,8 @@ import {
28
28
  loadRawConfig,
29
29
  saveRawConfig,
30
30
  setNestedValue,
31
+ withSuppressedConfigDiskWrites,
32
+ withSuppressedConfigDiskWritesSync,
31
33
  } from "../../config/loader.js";
32
34
  import { AssistantConfigSchema } from "../../config/schema.js";
33
35
  import { getSchemaAtPath } from "../../config/schema-utils.js";
@@ -57,12 +59,20 @@ import {
57
59
  getConversation,
58
60
  getMessageById,
59
61
  } from "../../memory/conversation-crud.js";
62
+ import { getDb } from "../../memory/db-connection.js";
60
63
  import { clearEmbeddingBackendCache } from "../../memory/embedding-backend.js";
61
64
  import { getLlmRequestLogSource } from "../../memory/llm-request-log-source.js";
62
65
  import { getMemoryRecallLogByMessageIds } from "../../memory/memory-recall-log-store.js";
63
66
  import { getMemoryV2ActivationLogByMessageIds } from "../../memory/memory-v2-activation-log-store.js";
64
67
  import { MEMORY_V2_CONSOLIDATION_SOURCE } from "../../memory/v2/constants.js";
68
+ import {
69
+ createConnection,
70
+ listConnections,
71
+ PROVIDERS_REQUIRING_BASE_URL_AND_MODELS,
72
+ } from "../../providers/inference/connections.js";
73
+ import { PROVIDER_CATALOG } from "../../providers/model-catalog.js";
65
74
  import { initializeProviders } from "../../providers/registry.js";
75
+ import { credentialKey } from "../../security/credential-key.js";
66
76
  import { validateAllowlistFile } from "../../security/secret-allowlist.js";
67
77
  import { resolvePricingForUsage } from "../../util/pricing.js";
68
78
  import { BadRequestError, InternalError, NotFoundError } from "./errors.js";
@@ -229,7 +239,7 @@ function getModelSetContext(): ModelSetContext {
229
239
  watcher.suppressConfigReload = value;
230
240
  },
231
241
  updateConfigFingerprint() {
232
- watcher.updateFingerprint();
242
+ withSuppressedConfigDiskWritesSync(() => watcher.updateFingerprint());
233
243
  },
234
244
  debounceTimers: watcher.timers,
235
245
  };
@@ -377,13 +387,42 @@ function readPlainObject(value: unknown): Record<string, unknown> | undefined {
377
387
 
378
388
  function handleGetConfig() {
379
389
  try {
380
- return applyContextDefaultsToRawConfig(loadRawConfig());
390
+ const config = applyContextDefaultsToRawConfig(loadRawConfig());
391
+ enrichProfilesWithVisionFlag(config);
392
+ return config;
381
393
  } catch (err) {
382
394
  const message = err instanceof Error ? err.message : String(err);
383
395
  throw new InternalError(`Failed to read config: ${message}`);
384
396
  }
385
397
  }
386
398
 
399
+ /**
400
+ * Annotate each profile in `config.llm.profiles` with `supportsVision`
401
+ * resolved from the model catalog. The flag is wire-only — it is never
402
+ * persisted to disk. Unknown (provider, model) pairs default to `true`
403
+ * (fail-open) so image upload remains available for custom / unlisted models.
404
+ */
405
+ function enrichProfilesWithVisionFlag(config: unknown): void {
406
+ const root = readPlainObject(config);
407
+ if (!root) return;
408
+ const llm = readPlainObject(root.llm);
409
+ if (!llm) return;
410
+ const profiles = readPlainObject(llm.profiles);
411
+ if (!profiles) return;
412
+
413
+ for (const profile of Object.values(profiles)) {
414
+ const entry = readPlainObject(profile);
415
+ if (!entry) continue;
416
+ const provider = entry.provider;
417
+ const model = entry.model;
418
+ if (typeof provider !== "string" || typeof model !== "string") continue;
419
+
420
+ const catalogProvider = PROVIDER_CATALOG.find((p) => p.id === provider);
421
+ const catalogModel = catalogProvider?.models.find((m) => m.id === model);
422
+ entry.supportsVision = catalogModel?.supportsVision ?? true;
423
+ }
424
+ }
425
+
387
426
  /**
388
427
  * Return the JSON Schema for the assistant config (full or scoped).
389
428
  *
@@ -471,15 +510,14 @@ async function commitConfigWrite(
471
510
 
472
511
  clearEmbeddingBackendCache();
473
512
  invalidateConfigCache();
474
- // Reinitialize providers so the live registry reflects the new config
475
- // (e.g. a mode flip between managed and your-own). Isolated try/catch so
476
- // a provider reinit failure doesn't mask the successful config save.
477
- // Only advance the config fingerprint on success - if reinit failed, leave
478
- // it stale so the watcher can detect the saved config on the next event
479
- // and retry provider initialization.
513
+ // Reinitialize providers so the live registry reflects the new config.
514
+ // Suppress disk writes inside loadConfig() we just wrote the raw config
515
+ // and the first-launch seed path would overwrite it with full defaults.
480
516
  try {
481
- await initializeProviders(getConfig());
482
- configWatcher.updateFingerprint();
517
+ await withSuppressedConfigDiskWrites(async () => {
518
+ await initializeProviders(getConfig());
519
+ configWatcher.updateFingerprint();
520
+ });
483
521
  } catch (err) {
484
522
  const message = err instanceof Error ? err.message : String(err);
485
523
  log.error({ err }, `${opLabel} config: provider reinit failed: ${message}`);
@@ -577,7 +615,7 @@ function handleValidateAllowlist() {
577
615
  }
578
616
  }
579
617
 
580
- function handleReplaceInferenceProfile({
618
+ async function handleReplaceInferenceProfile({
581
619
  pathParams = {},
582
620
  body,
583
621
  }: RouteHandlerArgs) {
@@ -616,32 +654,58 @@ function handleReplaceInferenceProfile({
616
654
  );
617
655
  }
618
656
  }
619
- try {
620
- const raw = loadRawConfig();
621
- if (isManaged) {
622
- // Partial overlay: keep every existing key intact, only update label
623
- // and/or status from the fragment. Using `replaceInferenceProfileConfig`
624
- // here would wipe the UI-owned seed fields (provider, model, advanced
625
- // params) because that function assumes the body carries the full UI
626
- // surface.
627
- patchManagedProfileFields(
628
- raw,
629
- name,
630
- parsed.data as Record<string, unknown>,
631
- );
632
- } else {
633
- replaceInferenceProfileConfig(
634
- raw,
635
- name,
636
- parsed.data as Record<string, unknown>,
637
- );
657
+ // When the UI sends provider but no provider_connection ("Any active X
658
+ // connection"), derive the connection now so the config deep-merge doesn't
659
+ // inherit a stale connection from the default layer.
660
+ const fragment = parsed.data as Record<string, unknown>;
661
+ if (!isManaged && fragment.provider && !fragment.provider_connection) {
662
+ const provider = fragment.provider as string;
663
+ const db = getDb();
664
+ const candidates = listConnections(db, { provider });
665
+ const active = candidates.find((c) => c.status === "active");
666
+ if (active) {
667
+ fragment.provider_connection = active.name;
668
+ } else if (!PROVIDERS_REQUIRING_BASE_URL_AND_MODELS.has(provider)) {
669
+ const connectionName = `${provider}-personal`;
670
+ const isKeyless = provider === "ollama";
671
+ const result = createConnection(db, {
672
+ name: connectionName,
673
+ provider,
674
+ auth: isKeyless
675
+ ? { type: "none" }
676
+ : {
677
+ type: "api_key",
678
+ credential: credentialKey(provider, "api_key"),
679
+ },
680
+ });
681
+ if (result.ok) {
682
+ fragment.provider_connection = connectionName;
683
+ }
638
684
  }
639
- saveRawConfig(raw);
640
- return { ok: true };
641
- } catch (err) {
642
- const message = err instanceof Error ? err.message : String(err);
643
- throw new InternalError(`Failed to replace inference profile: ${message}`);
644
685
  }
686
+
687
+ const raw = loadRawConfig();
688
+ if (isManaged) {
689
+ // Partial overlay: keep every existing key intact, only update label
690
+ // and/or status from the fragment. Using `replaceInferenceProfileConfig`
691
+ // here would wipe the UI-owned seed fields (provider, model, advanced
692
+ // params) because that function assumes the body carries the full UI
693
+ // surface.
694
+ patchManagedProfileFields(raw, name, fragment);
695
+ } else {
696
+ replaceInferenceProfileConfig(raw, name, fragment);
697
+ }
698
+ // Route through `commitConfigWrite` so profile edits flow through the
699
+ // post-write side effects shared with `handlePatchConfig` /
700
+ // `handleSetConfig`: file-watcher suppression so the in-process reload
701
+ // doesn't race the explicit reinit, embedding backend cache clear,
702
+ // in-process `getConfig` cache invalidation, and provider registry
703
+ // reinitialization. `status: "disabled"` on a managed profile (and any
704
+ // `provider` / `model` / `provider_connection` change on a custom
705
+ // profile) must take effect immediately rather than waiting for the
706
+ // next watcher tick.
707
+ await commitConfigWrite(raw, "replace inference profile");
708
+ return { ok: true };
645
709
  }
646
710
 
647
711
  /**
@@ -90,18 +90,24 @@ import {
90
90
  getOrCreateConversation,
91
91
  } from "../../memory/conversation-key-store.js";
92
92
  import { searchConversations } from "../../memory/conversation-queries.js";
93
+ import { recordOnboardingEvent } from "../../memory/onboarding-events-store.js";
94
+ import { buildSlackMessageDeepLinks } from "../../messaging/providers/slack/deep-link.js";
95
+ import {
96
+ readSlackMetadataFromMessageMetadata,
97
+ type SlackMessageMetadata,
98
+ } from "../../messaging/providers/slack/message-metadata.js";
93
99
  import { normalizeOnboardingContext } from "../../prompts/normalize-onboarding.js";
94
100
  import { writeOnboardingSection } from "../../prompts/persona-resolver.js";
95
101
  import { getConfiguredProvider } from "../../providers/provider-send-message.js";
96
102
  import type { Provider } from "../../providers/types.js";
97
103
  import { checkIngressForSecrets } from "../../security/secret-ingress.js";
104
+ import { getSubagentManager } from "../../subagent/index.js";
98
105
  import { getLogger } from "../../util/logger.js";
99
106
  import {
100
107
  getInterfacesDir,
101
108
  getWorkspacePromptPath,
102
109
  } from "../../util/platform.js";
103
110
  import { silentlyWithLog } from "../../util/silently.js";
104
- import { buildAssistantEvent } from "../assistant-event.js";
105
111
  import { assistantEventHub, broadcastMessage } from "../assistant-event-hub.js";
106
112
  import { DAEMON_INTERNAL_ASSISTANT_ID } from "../assistant-scope.js";
107
113
  import { routeGuardianReply } from "../guardian-reply-router.js";
@@ -114,6 +120,10 @@ import type {
114
120
  } from "../http-types.js";
115
121
  import { resolveLocalTrustContext } from "../local-actor-identity.js";
116
122
  import * as pendingInteractions from "../pending-interactions.js";
123
+ import {
124
+ publishConversationListAndMetadataChanged,
125
+ publishConversationMessagesChanged,
126
+ } from "../sync/resource-sync-events.js";
117
127
  import {
118
128
  resolveTrustContext,
119
129
  withSourceChannel,
@@ -138,6 +148,36 @@ function isValidRiskThreshold(value: unknown): value is RiskThreshold {
138
148
  );
139
149
  }
140
150
 
151
+ function buildSlackHistoryMessage(
152
+ slackMeta: SlackMessageMetadata | null,
153
+ ): RuntimeMessagePayload["slackMessage"] | undefined {
154
+ if (!slackMeta) return undefined;
155
+
156
+ const slackConfig = getConfig().slack;
157
+ const messageLink = buildSlackMessageDeepLinks({
158
+ teamId: slackConfig?.teamId,
159
+ teamUrl: slackConfig?.teamUrl,
160
+ channelId: slackMeta.channelId,
161
+ messageTs: slackMeta.channelTs,
162
+ });
163
+ const threadLink = slackMeta.threadTs
164
+ ? buildSlackMessageDeepLinks({
165
+ teamId: slackConfig?.teamId,
166
+ teamUrl: slackConfig?.teamUrl,
167
+ channelId: slackMeta.channelId,
168
+ messageTs: slackMeta.threadTs,
169
+ })
170
+ : undefined;
171
+
172
+ return {
173
+ channelId: slackMeta.channelId,
174
+ channelTs: slackMeta.channelTs,
175
+ ...(slackMeta.threadTs ? { threadTs: slackMeta.threadTs } : {}),
176
+ ...(messageLink ? { messageLink } : {}),
177
+ ...(threadLink ? { threadLink } : {}),
178
+ };
179
+ }
180
+
141
181
  function collectCanonicalGuardianRequestHintIds(
142
182
  conversationId: string,
143
183
  sourceChannel: string,
@@ -339,6 +379,7 @@ async function tryConsumeCanonicalGuardianReply(params: {
339
379
  });
340
380
  onEvent({ type: "message_complete", conversationId: conversationId });
341
381
  }
382
+ publishConversationMessagesChanged(conversationId);
342
383
  } catch (err) {
343
384
  log.warn(
344
385
  { err, conversationId },
@@ -506,6 +547,9 @@ export function handleListMessages(
506
547
  ...(typeof n.conversationId === "string"
507
548
  ? { conversationId: n.conversationId }
508
549
  : {}),
550
+ ...(typeof n.objective === "string"
551
+ ? { objective: n.objective }
552
+ : {}),
509
553
  };
510
554
  }
511
555
  }
@@ -513,6 +557,9 @@ export function handleListMessages(
513
557
  // Ignore malformed metadata
514
558
  }
515
559
  }
560
+ const slackMessage = buildSlackHistoryMessage(
561
+ readSlackMetadataFromMessageMetadata(msg.metadata),
562
+ );
516
563
 
517
564
  // Strip <no_response/> markers from assistant messages so web/API
518
565
  // clients never see the raw sentinel. Only assistant messages produce
@@ -552,6 +599,7 @@ export function handleListMessages(
552
599
  textSegments: filteredSegments,
553
600
  contentOrder: filteredContentOrder,
554
601
  surfaces: rendered.surfaces,
602
+ slackMessage,
555
603
  ...(rendered.thinkingSegments.length > 0
556
604
  ? { thinkingSegments: rendered.thinkingSegments }
557
605
  : {}),
@@ -570,6 +618,7 @@ export function handleListMessages(
570
618
  textSegments: rendered.textSegments,
571
619
  contentOrder: rendered.contentOrder,
572
620
  surfaces: rendered.surfaces,
621
+ slackMessage,
573
622
  ...(rendered.thinkingSegments.length > 0
574
623
  ? { thinkingSegments: rendered.thinkingSegments }
575
624
  : {}),
@@ -678,6 +727,7 @@ export function handleListMessages(
678
727
  ...(m.subagentNotification
679
728
  ? { subagentNotification: m.subagentNotification }
680
729
  : {}),
730
+ ...(m.slackMessage ? { slackMessage: m.slackMessage } : {}),
681
731
  };
682
732
  });
683
733
 
@@ -1117,6 +1167,8 @@ export async function handleSendMessage(
1117
1167
  tone: string;
1118
1168
  userName?: string;
1119
1169
  assistantName?: string;
1170
+ googleConnected?: boolean;
1171
+ googleScopes?: string[];
1120
1172
  };
1121
1173
  };
1122
1174
 
@@ -1293,16 +1345,10 @@ export async function handleSendMessage(
1293
1345
  // that other clients don't yet know about.
1294
1346
  if (mapping.conversationType === "standard") {
1295
1347
  if (!hasMessages(mapping.conversationId)) {
1296
- smDeps.assistantEventHub
1297
- .publish(
1298
- buildAssistantEvent({
1299
- type: "conversation_list_invalidated",
1300
- reason: "created",
1301
- }),
1302
- )
1303
- .catch((err) => {
1304
- log.warn({ err }, "Failed to publish conversation_list_invalidated");
1305
- });
1348
+ publishConversationListAndMetadataChanged(
1349
+ "created",
1350
+ mapping.conversationId,
1351
+ );
1306
1352
  }
1307
1353
  }
1308
1354
 
@@ -1447,6 +1493,12 @@ export async function handleSendMessage(
1447
1493
  // that can service host_browser_request events; we restore that single
1448
1494
  // proxy explicitly below without relaxing `hasNoClient`.
1449
1495
  conversation.updateClient(broadcastMessage, !isInteractive);
1496
+ if (isInteractive) {
1497
+ getSubagentManager().updateParentSender(
1498
+ mapping.conversationId,
1499
+ broadcastMessage,
1500
+ );
1501
+ }
1450
1502
 
1451
1503
  // ── Canned first-greeting fast path ──
1452
1504
  // On a completely fresh workspace, skip LLM inference for the macOS
@@ -1509,6 +1561,18 @@ export async function handleSendMessage(
1509
1561
 
1510
1562
  if (isFirstOnboarding) {
1511
1563
  persistOnboardingArtifacts(body.onboarding!);
1564
+ try {
1565
+ recordOnboardingEvent({
1566
+ screen: "complete",
1567
+ tools: body.onboarding!.tools,
1568
+ tasks: body.onboarding!.tasks,
1569
+ tone: body.onboarding!.tone,
1570
+ googleConnected: body.onboarding!.googleConnected,
1571
+ googleScopes: body.onboarding!.googleScopes,
1572
+ });
1573
+ } catch (err) {
1574
+ log.warn({ err }, "Failed to record onboarding telemetry event");
1575
+ }
1512
1576
  }
1513
1577
 
1514
1578
  setTimeout(() => {
@@ -1525,6 +1589,7 @@ export async function handleSendMessage(
1525
1589
  conversationId,
1526
1590
  });
1527
1591
  broadcastMessage({ type: "message_complete", conversationId });
1592
+ publishConversationMessagesChanged(conversationId);
1528
1593
  conversation.processing = false;
1529
1594
  silentlyWithLog(
1530
1595
  conversation.drainQueue(),
@@ -1550,6 +1615,18 @@ export async function handleSendMessage(
1550
1615
 
1551
1616
  if (isFirstOnboarding) {
1552
1617
  persistOnboardingArtifacts(body.onboarding!);
1618
+ try {
1619
+ recordOnboardingEvent({
1620
+ screen: "complete",
1621
+ tools: body.onboarding!.tools,
1622
+ tasks: body.onboarding!.tasks,
1623
+ tone: body.onboarding!.tone,
1624
+ googleConnected: body.onboarding!.googleConnected,
1625
+ googleScopes: body.onboarding!.googleScopes,
1626
+ });
1627
+ } catch (err) {
1628
+ log.warn({ err }, "Failed to record onboarding telemetry event");
1629
+ }
1553
1630
  }
1554
1631
 
1555
1632
  const attachments = hasAttachments
@@ -1828,6 +1905,7 @@ export async function handleSendMessage(
1828
1905
  type: "message_complete",
1829
1906
  conversationId: conversationId,
1830
1907
  });
1908
+ publishConversationMessagesChanged(conversationId);
1831
1909
  conversation.processing = false;
1832
1910
  silentlyWithLog(conversation.drainQueue(), "slash-command queue drain");
1833
1911
  }, 0);
@@ -1869,6 +1947,7 @@ export async function handleSendMessage(
1869
1947
  // forceCompact() makes an LLM call that can exceed the client's
1870
1948
  // HTTP timeout on large contexts, causing a false "Failed to send".
1871
1949
  (async () => {
1950
+ let assistantMessagePersisted = false;
1872
1951
  try {
1873
1952
  broadcastMessage({
1874
1953
  type: "user_message_echo",
@@ -1877,6 +1956,7 @@ export async function handleSendMessage(
1877
1956
  messageId: persisted.id,
1878
1957
  clientMessageId,
1879
1958
  });
1959
+ publishConversationMessagesChanged(conversationId);
1880
1960
  conversation.emitActivityState(
1881
1961
  "thinking",
1882
1962
  "context_compacting",
@@ -1894,6 +1974,7 @@ export async function handleSendMessage(
1894
1974
  JSON.stringify(assistantMsg.content),
1895
1975
  channelMeta,
1896
1976
  );
1977
+ assistantMessagePersisted = true;
1897
1978
  conversation.getMessages().push(assistantMsg);
1898
1979
 
1899
1980
  broadcastMessage({
@@ -1902,7 +1983,11 @@ export async function handleSendMessage(
1902
1983
  conversationId,
1903
1984
  });
1904
1985
  broadcastMessage({ type: "message_complete", conversationId });
1986
+ publishConversationMessagesChanged(conversationId);
1905
1987
  } catch (err) {
1988
+ if (assistantMessagePersisted) {
1989
+ publishConversationMessagesChanged(conversationId);
1990
+ }
1906
1991
  log.error({ err, conversationId }, "Compact command failed");
1907
1992
  broadcastMessage({
1908
1993
  type: "conversation_error",
@@ -1950,6 +2035,7 @@ export async function handleSendMessage(
1950
2035
  requestId,
1951
2036
  clientMessageId,
1952
2037
  });
2038
+ publishConversationMessagesChanged(mapping.conversationId);
1953
2039
 
1954
2040
  // Fire-and-forget the agent loop; events flow to the hub via broadcastMessage.
1955
2041
  conversation
@@ -6,8 +6,12 @@
6
6
  */
7
7
  import { z } from "zod";
8
8
 
9
- import { saveDocument } from "../../documents/document-store.js";
10
- import { rawAll, rawGet } from "../../memory/raw-query.js";
9
+ import {
10
+ getDocumentById,
11
+ getDocumentsForConversation,
12
+ saveDocument,
13
+ } from "../../documents/document-store.js";
14
+ import { rawAll } from "../../memory/raw-query.js";
11
15
  import { getLogger } from "../../util/logger.js";
12
16
  import { renderMarkdownToPDF } from "./document-pdf-renderer.js";
13
17
  import { BadRequestError, InternalError, NotFoundError } from "./errors.js";
@@ -16,65 +20,16 @@ import { RouteResponse } from "./types.js";
16
20
 
17
21
  const log = getLogger("documents-routes");
18
22
 
19
- interface DocumentRow {
23
+ interface DocumentListRow {
20
24
  surface_id: string;
21
25
  conversation_id: string;
22
26
  title: string;
23
- content: string;
24
27
  word_count: number;
25
28
  created_at: number;
26
29
  updated_at: number;
27
30
  }
28
31
 
29
- type DocumentListRow = Omit<DocumentRow, "content">;
30
-
31
- function loadDocument(surfaceId: string):
32
- | {
33
- success: true;
34
- surfaceId: string;
35
- conversationId: string;
36
- title: string;
37
- content: string;
38
- wordCount: number;
39
- createdAt: number;
40
- updatedAt: number;
41
- }
42
- | { success: false; error: string } {
43
- try {
44
- const result = rawGet<DocumentRow>(
45
- /*sql*/ `
46
- SELECT surface_id, conversation_id, title, content, word_count, created_at, updated_at
47
- FROM documents
48
- WHERE surface_id = ?
49
- `,
50
- surfaceId,
51
- );
52
-
53
- if (result) {
54
- log.info({ surfaceId }, "Loaded document");
55
- return {
56
- success: true,
57
- surfaceId: result.surface_id,
58
- conversationId: result.conversation_id,
59
- title: result.title,
60
- content: result.content,
61
- wordCount: result.word_count,
62
- createdAt: result.created_at,
63
- updatedAt: result.updated_at,
64
- };
65
- }
66
- log.info({ surfaceId }, "Document not found");
67
- return { success: false, error: "Document not found" };
68
- } catch (error) {
69
- log.error({ err: error, surfaceId }, "Load error");
70
- return {
71
- success: false,
72
- error: error instanceof Error ? error.message : "Unknown error",
73
- };
74
- }
75
- }
76
-
77
- function listDocuments(conversationId?: string): Array<{
32
+ function listAllDocuments(): Array<{
78
33
  surfaceId: string;
79
34
  conversationId: string;
80
35
  title: string;
@@ -83,29 +38,11 @@ function listDocuments(conversationId?: string): Array<{
83
38
  updatedAt: number;
84
39
  }> {
85
40
  try {
86
- let results: DocumentListRow[];
87
-
88
- if (conversationId) {
89
- // Query via junction table so we return the *matched* conversation_id
90
- // (not the origin conversation_id from the documents table).
91
- results = rawAll<DocumentListRow>(
92
- /*sql*/ `
93
- SELECT d.surface_id, dc.conversation_id AS conversation_id,
94
- d.title, d.word_count, d.created_at, d.updated_at
95
- FROM documents d
96
- INNER JOIN document_conversations dc ON d.surface_id = dc.surface_id
97
- WHERE dc.conversation_id = ?
98
- ORDER BY d.updated_at DESC
99
- `,
100
- conversationId,
101
- );
102
- } else {
103
- results = rawAll<DocumentListRow>(/*sql*/ `
104
- SELECT surface_id, conversation_id, title, word_count, created_at, updated_at
105
- FROM documents
106
- ORDER BY updated_at DESC
107
- `);
108
- }
41
+ const results = rawAll<DocumentListRow>(/*sql*/ `
42
+ SELECT surface_id, conversation_id, title, word_count, created_at, updated_at
43
+ FROM documents
44
+ ORDER BY updated_at DESC
45
+ `);
109
46
 
110
47
  log.info({ count: results.length }, "Listed documents");
111
48
  return results.map((row) => ({
@@ -148,7 +85,9 @@ export const ROUTES: RouteDefinition[] = [
148
85
  }),
149
86
  handler: ({ queryParams }) => {
150
87
  const conversationId = queryParams?.conversationId ?? undefined;
151
- const documents = listDocuments(conversationId);
88
+ const documents = conversationId
89
+ ? getDocumentsForConversation(conversationId)
90
+ : listAllDocuments();
152
91
  return { documents };
153
92
  },
154
93
  },
@@ -173,11 +112,11 @@ export const ROUTES: RouteDefinition[] = [
173
112
  updatedAt: z.number(),
174
113
  }),
175
114
  handler: ({ pathParams }) => {
176
- const result = loadDocument(pathParams!.id);
177
- if (!result.success) {
178
- throw new NotFoundError(result.error);
115
+ const doc = getDocumentById(pathParams!.id);
116
+ if (!doc) {
117
+ throw new NotFoundError("Document not found");
179
118
  }
180
- return result;
119
+ return { success: true, ...doc };
181
120
  },
182
121
  },
183
122
 
@@ -252,13 +191,13 @@ export const ROUTES: RouteDefinition[] = [
252
191
  description: "Render a document to PDF and return the binary content.",
253
192
  tags: ["documents"],
254
193
  handler: async ({ pathParams }) => {
255
- const result = loadDocument(pathParams!.id);
256
- if (!result.success) {
257
- throw new NotFoundError(result.error);
194
+ const doc = getDocumentById(pathParams!.id);
195
+ if (!doc) {
196
+ throw new NotFoundError("Document not found");
258
197
  }
259
- const pdfBuffer = await renderMarkdownToPDF(result.title, result.content);
198
+ const pdfBuffer = await renderMarkdownToPDF(doc.title, doc.content);
260
199
  const filename =
261
- result.title
200
+ doc.title
262
201
  .replace(/[^a-zA-Z0-9_-]/g, "-")
263
202
  .replace(/-+/g, "-")
264
203
  .replace(/^-|-$/g, "") || "document";
@@ -18,6 +18,7 @@ import {
18
18
  reorderGroups,
19
19
  updateGroup,
20
20
  } from "../../memory/group-crud.js";
21
+ import { publishConversationListChanged } from "../sync/resource-sync-events.js";
21
22
  import { BadRequestError, ForbiddenError, NotFoundError } from "./errors.js";
22
23
  import type { RouteDefinition, RouteHandlerArgs } from "./types.js";
23
24
 
@@ -47,6 +48,7 @@ function handleCreateGroup({ body = {} }: RouteHandlerArgs) {
47
48
  }
48
49
  try {
49
50
  const group = createGroup(name);
51
+ publishConversationListChanged("created");
50
52
  return serializeGroup(group);
51
53
  } catch (err) {
52
54
  if (
@@ -90,6 +92,7 @@ function handleUpdateGroup({ pathParams = {}, body = {} }: RouteHandlerArgs) {
90
92
  if (!updated) {
91
93
  throw new NotFoundError("Group not found");
92
94
  }
95
+ publishConversationListChanged("reordered");
93
96
  return serializeGroup(updated);
94
97
  }
95
98
 
@@ -103,6 +106,7 @@ function handleDeleteGroup({ pathParams = {} }: RouteHandlerArgs) {
103
106
  throw new ForbiddenError("System groups cannot be deleted");
104
107
  }
105
108
  deleteGroup(groupId);
109
+ publishConversationListChanged("reordered");
106
110
  }
107
111
 
108
112
  function handleReorderGroups({ body = {} }: RouteHandlerArgs) {
@@ -131,6 +135,7 @@ function handleReorderGroups({ body = {} }: RouteHandlerArgs) {
131
135
  }
132
136
  }
133
137
  reorderGroups(updates);
138
+ publishConversationListChanged("reordered");
134
139
  return { ok: true };
135
140
  }
136
141