@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
@@ -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";
@@ -229,7 +231,7 @@ function getModelSetContext(): ModelSetContext {
229
231
  watcher.suppressConfigReload = value;
230
232
  },
231
233
  updateConfigFingerprint() {
232
- watcher.updateFingerprint();
234
+ withSuppressedConfigDiskWritesSync(() => watcher.updateFingerprint());
233
235
  },
234
236
  debounceTimers: watcher.timers,
235
237
  };
@@ -471,15 +473,14 @@ async function commitConfigWrite(
471
473
 
472
474
  clearEmbeddingBackendCache();
473
475
  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.
476
+ // Reinitialize providers so the live registry reflects the new config.
477
+ // Suppress disk writes inside loadConfig() we just wrote the raw config
478
+ // and the first-launch seed path would overwrite it with full defaults.
480
479
  try {
481
- await initializeProviders(getConfig());
482
- configWatcher.updateFingerprint();
480
+ await withSuppressedConfigDiskWrites(async () => {
481
+ await initializeProviders(getConfig());
482
+ configWatcher.updateFingerprint();
483
+ });
483
484
  } catch (err) {
484
485
  const message = err instanceof Error ? err.message : String(err);
485
486
  log.error({ err }, `${opLabel} config: provider reinit failed: ${message}`);
@@ -577,7 +578,7 @@ function handleValidateAllowlist() {
577
578
  }
578
579
  }
579
580
 
580
- function handleReplaceInferenceProfile({
581
+ async function handleReplaceInferenceProfile({
581
582
  pathParams = {},
582
583
  body,
583
584
  }: RouteHandlerArgs) {
@@ -616,32 +617,36 @@ function handleReplaceInferenceProfile({
616
617
  );
617
618
  }
618
619
  }
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
- );
638
- }
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}`);
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
+ );
644
638
  }
639
+ // Route through `commitConfigWrite` so profile edits flow through the
640
+ // post-write side effects shared with `handlePatchConfig` /
641
+ // `handleSetConfig`: file-watcher suppression so the in-process reload
642
+ // doesn't race the explicit reinit, embedding backend cache clear,
643
+ // in-process `getConfig` cache invalidation, and provider registry
644
+ // reinitialization. `status: "disabled"` on a managed profile (and any
645
+ // `provider` / `model` / `provider_connection` change on a custom
646
+ // profile) must take effect immediately rather than waiting for the
647
+ // next watcher tick.
648
+ await commitConfigWrite(raw, "replace inference profile");
649
+ return { ok: true };
645
650
  }
646
651
 
647
652
  /**
@@ -90,6 +90,12 @@ 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";
@@ -101,7 +107,6 @@ import {
101
107
  getWorkspacePromptPath,
102
108
  } from "../../util/platform.js";
103
109
  import { silentlyWithLog } from "../../util/silently.js";
104
- import { buildAssistantEvent } from "../assistant-event.js";
105
110
  import { assistantEventHub, broadcastMessage } from "../assistant-event-hub.js";
106
111
  import { DAEMON_INTERNAL_ASSISTANT_ID } from "../assistant-scope.js";
107
112
  import { routeGuardianReply } from "../guardian-reply-router.js";
@@ -114,6 +119,10 @@ import type {
114
119
  } from "../http-types.js";
115
120
  import { resolveLocalTrustContext } from "../local-actor-identity.js";
116
121
  import * as pendingInteractions from "../pending-interactions.js";
122
+ import {
123
+ publishConversationListAndMetadataChanged,
124
+ publishConversationMessagesChanged,
125
+ } from "../sync/resource-sync-events.js";
117
126
  import {
118
127
  resolveTrustContext,
119
128
  withSourceChannel,
@@ -138,6 +147,36 @@ function isValidRiskThreshold(value: unknown): value is RiskThreshold {
138
147
  );
139
148
  }
140
149
 
150
+ function buildSlackHistoryMessage(
151
+ slackMeta: SlackMessageMetadata | null,
152
+ ): RuntimeMessagePayload["slackMessage"] | undefined {
153
+ if (!slackMeta) return undefined;
154
+
155
+ const slackConfig = getConfig().slack;
156
+ const messageLink = buildSlackMessageDeepLinks({
157
+ teamId: slackConfig?.teamId,
158
+ teamUrl: slackConfig?.teamUrl,
159
+ channelId: slackMeta.channelId,
160
+ messageTs: slackMeta.channelTs,
161
+ });
162
+ const threadLink = slackMeta.threadTs
163
+ ? buildSlackMessageDeepLinks({
164
+ teamId: slackConfig?.teamId,
165
+ teamUrl: slackConfig?.teamUrl,
166
+ channelId: slackMeta.channelId,
167
+ messageTs: slackMeta.threadTs,
168
+ })
169
+ : undefined;
170
+
171
+ return {
172
+ channelId: slackMeta.channelId,
173
+ channelTs: slackMeta.channelTs,
174
+ ...(slackMeta.threadTs ? { threadTs: slackMeta.threadTs } : {}),
175
+ ...(messageLink ? { messageLink } : {}),
176
+ ...(threadLink ? { threadLink } : {}),
177
+ };
178
+ }
179
+
141
180
  function collectCanonicalGuardianRequestHintIds(
142
181
  conversationId: string,
143
182
  sourceChannel: string,
@@ -339,6 +378,7 @@ async function tryConsumeCanonicalGuardianReply(params: {
339
378
  });
340
379
  onEvent({ type: "message_complete", conversationId: conversationId });
341
380
  }
381
+ publishConversationMessagesChanged(conversationId);
342
382
  } catch (err) {
343
383
  log.warn(
344
384
  { err, conversationId },
@@ -506,6 +546,9 @@ export function handleListMessages(
506
546
  ...(typeof n.conversationId === "string"
507
547
  ? { conversationId: n.conversationId }
508
548
  : {}),
549
+ ...(typeof n.objective === "string"
550
+ ? { objective: n.objective }
551
+ : {}),
509
552
  };
510
553
  }
511
554
  }
@@ -513,6 +556,9 @@ export function handleListMessages(
513
556
  // Ignore malformed metadata
514
557
  }
515
558
  }
559
+ const slackMessage = buildSlackHistoryMessage(
560
+ readSlackMetadataFromMessageMetadata(msg.metadata),
561
+ );
516
562
 
517
563
  // Strip <no_response/> markers from assistant messages so web/API
518
564
  // clients never see the raw sentinel. Only assistant messages produce
@@ -552,6 +598,7 @@ export function handleListMessages(
552
598
  textSegments: filteredSegments,
553
599
  contentOrder: filteredContentOrder,
554
600
  surfaces: rendered.surfaces,
601
+ slackMessage,
555
602
  ...(rendered.thinkingSegments.length > 0
556
603
  ? { thinkingSegments: rendered.thinkingSegments }
557
604
  : {}),
@@ -570,6 +617,7 @@ export function handleListMessages(
570
617
  textSegments: rendered.textSegments,
571
618
  contentOrder: rendered.contentOrder,
572
619
  surfaces: rendered.surfaces,
620
+ slackMessage,
573
621
  ...(rendered.thinkingSegments.length > 0
574
622
  ? { thinkingSegments: rendered.thinkingSegments }
575
623
  : {}),
@@ -678,6 +726,7 @@ export function handleListMessages(
678
726
  ...(m.subagentNotification
679
727
  ? { subagentNotification: m.subagentNotification }
680
728
  : {}),
729
+ ...(m.slackMessage ? { slackMessage: m.slackMessage } : {}),
681
730
  };
682
731
  });
683
732
 
@@ -1117,6 +1166,8 @@ export async function handleSendMessage(
1117
1166
  tone: string;
1118
1167
  userName?: string;
1119
1168
  assistantName?: string;
1169
+ googleConnected?: boolean;
1170
+ googleScopes?: string[];
1120
1171
  };
1121
1172
  };
1122
1173
 
@@ -1293,16 +1344,10 @@ export async function handleSendMessage(
1293
1344
  // that other clients don't yet know about.
1294
1345
  if (mapping.conversationType === "standard") {
1295
1346
  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
- });
1347
+ publishConversationListAndMetadataChanged(
1348
+ "created",
1349
+ mapping.conversationId,
1350
+ );
1306
1351
  }
1307
1352
  }
1308
1353
 
@@ -1509,6 +1554,18 @@ export async function handleSendMessage(
1509
1554
 
1510
1555
  if (isFirstOnboarding) {
1511
1556
  persistOnboardingArtifacts(body.onboarding!);
1557
+ try {
1558
+ recordOnboardingEvent({
1559
+ screen: "complete",
1560
+ tools: body.onboarding!.tools,
1561
+ tasks: body.onboarding!.tasks,
1562
+ tone: body.onboarding!.tone,
1563
+ googleConnected: body.onboarding!.googleConnected,
1564
+ googleScopes: body.onboarding!.googleScopes,
1565
+ });
1566
+ } catch (err) {
1567
+ log.warn({ err }, "Failed to record onboarding telemetry event");
1568
+ }
1512
1569
  }
1513
1570
 
1514
1571
  setTimeout(() => {
@@ -1525,6 +1582,7 @@ export async function handleSendMessage(
1525
1582
  conversationId,
1526
1583
  });
1527
1584
  broadcastMessage({ type: "message_complete", conversationId });
1585
+ publishConversationMessagesChanged(conversationId);
1528
1586
  conversation.processing = false;
1529
1587
  silentlyWithLog(
1530
1588
  conversation.drainQueue(),
@@ -1550,6 +1608,18 @@ export async function handleSendMessage(
1550
1608
 
1551
1609
  if (isFirstOnboarding) {
1552
1610
  persistOnboardingArtifacts(body.onboarding!);
1611
+ try {
1612
+ recordOnboardingEvent({
1613
+ screen: "complete",
1614
+ tools: body.onboarding!.tools,
1615
+ tasks: body.onboarding!.tasks,
1616
+ tone: body.onboarding!.tone,
1617
+ googleConnected: body.onboarding!.googleConnected,
1618
+ googleScopes: body.onboarding!.googleScopes,
1619
+ });
1620
+ } catch (err) {
1621
+ log.warn({ err }, "Failed to record onboarding telemetry event");
1622
+ }
1553
1623
  }
1554
1624
 
1555
1625
  const attachments = hasAttachments
@@ -1828,6 +1898,7 @@ export async function handleSendMessage(
1828
1898
  type: "message_complete",
1829
1899
  conversationId: conversationId,
1830
1900
  });
1901
+ publishConversationMessagesChanged(conversationId);
1831
1902
  conversation.processing = false;
1832
1903
  silentlyWithLog(conversation.drainQueue(), "slash-command queue drain");
1833
1904
  }, 0);
@@ -1869,6 +1940,7 @@ export async function handleSendMessage(
1869
1940
  // forceCompact() makes an LLM call that can exceed the client's
1870
1941
  // HTTP timeout on large contexts, causing a false "Failed to send".
1871
1942
  (async () => {
1943
+ let assistantMessagePersisted = false;
1872
1944
  try {
1873
1945
  broadcastMessage({
1874
1946
  type: "user_message_echo",
@@ -1877,6 +1949,7 @@ export async function handleSendMessage(
1877
1949
  messageId: persisted.id,
1878
1950
  clientMessageId,
1879
1951
  });
1952
+ publishConversationMessagesChanged(conversationId);
1880
1953
  conversation.emitActivityState(
1881
1954
  "thinking",
1882
1955
  "context_compacting",
@@ -1894,6 +1967,7 @@ export async function handleSendMessage(
1894
1967
  JSON.stringify(assistantMsg.content),
1895
1968
  channelMeta,
1896
1969
  );
1970
+ assistantMessagePersisted = true;
1897
1971
  conversation.getMessages().push(assistantMsg);
1898
1972
 
1899
1973
  broadcastMessage({
@@ -1902,7 +1976,11 @@ export async function handleSendMessage(
1902
1976
  conversationId,
1903
1977
  });
1904
1978
  broadcastMessage({ type: "message_complete", conversationId });
1979
+ publishConversationMessagesChanged(conversationId);
1905
1980
  } catch (err) {
1981
+ if (assistantMessagePersisted) {
1982
+ publishConversationMessagesChanged(conversationId);
1983
+ }
1906
1984
  log.error({ err, conversationId }, "Compact command failed");
1907
1985
  broadcastMessage({
1908
1986
  type: "conversation_error",
@@ -1950,6 +2028,7 @@ export async function handleSendMessage(
1950
2028
  requestId,
1951
2029
  clientMessageId,
1952
2030
  });
2031
+ publishConversationMessagesChanged(mapping.conversationId);
1953
2032
 
1954
2033
  // Fire-and-forget the agent loop; events flow to the hub via broadcastMessage.
1955
2034
  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
 
@@ -1,16 +1,23 @@
1
1
  /**
2
2
  * Channel conversation deletion handler.
3
3
  */
4
- import { deleteConversationKey } from "../../memory/conversation-key-store.js";
5
- import { deleteBindingByChannelChat } from "../../memory/external-conversation-store.js";
6
- import { DAEMON_INTERNAL_ASSISTANT_ID } from "../assistant-scope.js";
4
+ import {
5
+ deleteConversationKey,
6
+ getOrCreateConversation,
7
+ } from "../../memory/conversation-key-store.js";
8
+ import { buildScopedConversationKey } from "../../memory/delivery-crud.js";
9
+ import {
10
+ deleteBindingByChannelChat,
11
+ deleteBindingByChannelChatThread,
12
+ } from "../../memory/external-conversation-store.js";
7
13
  import { BadRequestError } from "./errors.js";
8
14
  import type { RouteHandlerArgs } from "./types.js";
9
15
 
10
16
  export function handleDeleteConversation({ body = {} }: RouteHandlerArgs) {
11
- const { sourceChannel, conversationExternalId } = body as {
17
+ const { sourceChannel, conversationExternalId, sourceThreadId } = body as {
12
18
  sourceChannel?: string;
13
19
  conversationExternalId?: string;
20
+ sourceThreadId?: string;
14
21
  };
15
22
 
16
23
  if (!sourceChannel || typeof sourceChannel !== "string") {
@@ -20,14 +27,27 @@ export function handleDeleteConversation({ body = {} }: RouteHandlerArgs) {
20
27
  throw new BadRequestError("conversationExternalId is required");
21
28
  }
22
29
 
23
- const assistantId = DAEMON_INTERNAL_ASSISTANT_ID;
30
+ const normalizedThreadId = sourceThreadId?.trim() || undefined;
24
31
 
25
- const scopedKey = `asst:${assistantId}:${sourceChannel}:${conversationExternalId}`;
32
+ const scopedKey = buildScopedConversationKey(
33
+ sourceChannel,
34
+ conversationExternalId,
35
+ normalizedThreadId,
36
+ );
26
37
  deleteConversationKey(scopedKey);
27
- if (assistantId === DAEMON_INTERNAL_ASSISTANT_ID) {
28
- const legacyKey = `${sourceChannel}:${conversationExternalId}`;
38
+ const legacyKey = `${sourceChannel}:${conversationExternalId}`;
39
+ if (!normalizedThreadId) {
29
40
  deleteConversationKey(legacyKey);
30
41
  deleteBindingByChannelChat(sourceChannel, conversationExternalId);
42
+ } else {
43
+ if (sourceChannel === "slack") {
44
+ getOrCreateConversation(scopedKey);
45
+ }
46
+ deleteBindingByChannelChatThread(
47
+ sourceChannel,
48
+ conversationExternalId,
49
+ normalizedThreadId,
50
+ );
31
51
  }
32
52
 
33
53
  return { ok: true };