@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
@@ -11,6 +11,7 @@ import {
11
11
  getMessages,
12
12
  type MessageRow,
13
13
  } from "../../memory/conversation-crud.js";
14
+ import { getConversationUsageTotals } from "../../memory/llm-usage-store.js";
14
15
  import { getSubagentManager } from "../../subagent/index.js";
15
16
  import { getLogger } from "../../util/logger.js";
16
17
  import { BadRequestError, NotFoundError } from "./errors.js";
@@ -29,6 +30,7 @@ function isRecord(value: unknown): value is Record<string, unknown> {
29
30
  export interface SubagentDetailResult {
30
31
  subagentId: string;
31
32
  objective?: string;
33
+ usage?: { inputTokens: number; outputTokens: number; estimatedCost: number };
32
34
  events: Array<{
33
35
  type: string;
34
36
  content: string;
@@ -94,7 +96,11 @@ export function parseSubagentMessages(
94
96
  typeof block.text === "string"
95
97
  ) {
96
98
  events.push({ type: "text", content: block.text, messageId: m.id });
97
- } else if (block.type === "tool_use") {
99
+ } else if (
100
+ block.type === "tool_use" ||
101
+ block.type === "server_tool_use" ||
102
+ block.type === "mcp_tool_use"
103
+ ) {
98
104
  const name = typeof block.name === "string" ? block.name : "unknown";
99
105
  const input = isRecord(block.input)
100
106
  ? (block.input as Record<string, unknown>)
@@ -106,7 +112,11 @@ export function parseSubagentMessages(
106
112
  toolName: name,
107
113
  });
108
114
  if (id) pendingTools.set(id, name);
109
- } else if (block.type === "tool_result") {
115
+ } else if (
116
+ block.type === "tool_result" ||
117
+ block.type === "web_search_tool_result" ||
118
+ block.type === "mcp_tool_result"
119
+ ) {
110
120
  const toolUseId =
111
121
  typeof block.tool_use_id === "string" ? block.tool_use_id : "";
112
122
  const resultContent =
@@ -114,13 +124,18 @@ export function parseSubagentMessages(
114
124
  ? block.content
115
125
  : Array.isArray(block.content)
116
126
  ? (block.content as unknown[])
117
- .filter(
118
- (b): b is Record<string, unknown> =>
119
- isRecord(b) &&
120
- (b as Record<string, unknown>).type === "text" &&
121
- typeof (b as Record<string, unknown>).text === "string",
122
- )
123
- .map((b) => b.text as string)
127
+ .filter((b): b is Record<string, unknown> => isRecord(b))
128
+ .map((b) => {
129
+ if (b.type === "text" && typeof b.text === "string")
130
+ return b.text;
131
+ if (
132
+ b.type === "web_search_result" &&
133
+ typeof b.title === "string"
134
+ )
135
+ return `${b.title}\n${typeof b.url === "string" ? b.url : ""}`;
136
+ return null;
137
+ })
138
+ .filter((s): s is string => s != null)
124
139
  .join("\n")
125
140
  : "";
126
141
  const isError = block.is_error === true;
@@ -142,7 +157,30 @@ function getSubagentDetail(
142
157
  subagentId: string,
143
158
  conversationId: string,
144
159
  ): SubagentDetailResult {
145
- return parseSubagentMessages(subagentId, getMessages(conversationId));
160
+ const messages = getMessages(conversationId);
161
+ log.info(
162
+ {
163
+ subagentId,
164
+ conversationId,
165
+ messageCount: messages.length,
166
+ roles: messages.map((m) => m.role),
167
+ },
168
+ "getSubagentDetail: raw messages from DB",
169
+ );
170
+ const result = parseSubagentMessages(subagentId, messages);
171
+ log.info(
172
+ {
173
+ subagentId,
174
+ eventCount: result.events.length,
175
+ eventTypes: result.events.map((e) => `${e.type}:${e.toolName ?? ""}`),
176
+ },
177
+ "getSubagentDetail: parsed events",
178
+ );
179
+ const usage = getConversationUsageTotals(conversationId);
180
+ if (usage.inputTokens > 0 || usage.outputTokens > 0) {
181
+ result.usage = usage;
182
+ }
183
+ return result;
146
184
  }
147
185
 
148
186
  // ---------------------------------------------------------------------------
@@ -168,14 +206,19 @@ export const ROUTES: RouteDefinition[] = [
168
206
  responseBody: z.object({
169
207
  subagentId: z.string(),
170
208
  objective: z.string(),
209
+ usage: z
210
+ .object({
211
+ inputTokens: z.number(),
212
+ outputTokens: z.number(),
213
+ estimatedCost: z.number(),
214
+ })
215
+ .optional(),
171
216
  events: z.array(z.unknown()).describe("Subagent event objects"),
172
217
  }),
173
218
  handler: ({ pathParams, queryParams }) => {
174
219
  const conversationId = queryParams?.conversationId;
175
220
  if (!conversationId) {
176
- throw new BadRequestError(
177
- "conversationId query parameter is required",
178
- );
221
+ throw new BadRequestError("conversationId query parameter is required");
179
222
  }
180
223
 
181
224
  const manager = getSubagentManager();
@@ -209,11 +252,7 @@ export const ROUTES: RouteDefinition[] = [
209
252
  }
210
253
 
211
254
  const manager = getSubagentManager();
212
- const aborted = manager.abort(
213
- pathParams!.id,
214
- () => {},
215
- conversationId,
216
- );
255
+ const aborted = manager.abort(pathParams!.id, () => {}, conversationId);
217
256
 
218
257
  if (!aborted) {
219
258
  log.warn(
@@ -7,6 +7,7 @@
7
7
  import { z } from "zod";
8
8
 
9
9
  import { recordLifecycleEvent } from "../../memory/lifecycle-events-store.js";
10
+ import { getUsageTelemetryReporter } from "../../telemetry/usage-telemetry-reporter.js";
10
11
  import { getLogger } from "../../util/logger.js";
11
12
  import { BadRequestError } from "./errors.js";
12
13
  import type { RouteDefinition, RouteHandlerArgs } from "./types.js";
@@ -32,6 +33,15 @@ function handleRecordLifecycleEvent({ body }: RouteHandlerArgs) {
32
33
  return { id: event.id, event_name: event.eventName };
33
34
  }
34
35
 
36
+ async function handleTelemetryFlush() {
37
+ const reporter = getUsageTelemetryReporter();
38
+ if (!reporter) {
39
+ return { flushed: false, reason: "disabled" };
40
+ }
41
+ await reporter.flush();
42
+ return { flushed: true };
43
+ }
44
+
35
45
  export const ROUTES: RouteDefinition[] = [
36
46
  {
37
47
  operationId: "telemetry_lifecycle",
@@ -58,4 +68,21 @@ export const ROUTES: RouteDefinition[] = [
58
68
  ]),
59
69
  handler: handleRecordLifecycleEvent,
60
70
  },
71
+ {
72
+ operationId: "telemetry_flush",
73
+ endpoint: "telemetry/flush",
74
+ method: "POST",
75
+ summary: "Flush pending telemetry events",
76
+ description:
77
+ "Force-flush all pending usage, turn, and lifecycle telemetry events to the platform.",
78
+ tags: ["telemetry"],
79
+ responseBody: z.union([
80
+ z.object({ flushed: z.literal(true) }),
81
+ z.object({
82
+ flushed: z.literal(false),
83
+ reason: z.string(),
84
+ }),
85
+ ]),
86
+ handler: handleTelemetryFlush,
87
+ },
61
88
  ];
@@ -94,10 +94,35 @@ async function doSynthesize(
94
94
  throw new ServiceUnavailableError("TTS provider is not configured");
95
95
  }
96
96
 
97
- throw new BadGatewayError("TTS synthesis failed");
97
+ throw new BadGatewayError(formatTtsFailureMessage(err));
98
98
  }
99
99
  }
100
100
 
101
+ /**
102
+ * Build a user-facing error message for a failed TTS synthesis, embedding the
103
+ * upstream provider's message when available.
104
+ *
105
+ * The provider adapters surface a clean upstream message (e.g. "Free users
106
+ * cannot use library voices via the API…") and `synthesize-text` already
107
+ * prefixes those with `"TTS synthesis failed (provider: <id>): "`. We pass
108
+ * pre-prefixed messages through verbatim and only add the base prefix for
109
+ * raw provider errors, so users never see double- or triple-prefixed
110
+ * messages on the desktop / channels.
111
+ *
112
+ * Exported for unit testing.
113
+ */
114
+ export function formatTtsFailureMessage(err: unknown): string {
115
+ const base = "TTS synthesis failed";
116
+ if (err instanceof Error && err.message && err.message.trim()) {
117
+ const trimmed = err.message.trim();
118
+ if (/^TTS synthesis failed\b/i.test(trimmed)) {
119
+ return trimmed;
120
+ }
121
+ return `${base}: ${trimmed}`;
122
+ }
123
+ return base;
124
+ }
125
+
101
126
  // ---------------------------------------------------------------------------
102
127
  // Response headers — shared by both routes
103
128
  // ---------------------------------------------------------------------------
@@ -191,7 +216,7 @@ async function handleSynthesizeCliTts({ body }: RouteHandlerArgs) {
191
216
  throw new ServiceUnavailableError("TTS provider is not configured");
192
217
  }
193
218
 
194
- throw new BadGatewayError("TTS synthesis failed");
219
+ throw new BadGatewayError(formatTtsFailureMessage(err));
195
220
  }
196
221
  }
197
222
 
@@ -9,6 +9,10 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
9
9
  import { join } from "node:path";
10
10
  import { beforeAll, describe, expect, test } from "bun:test";
11
11
 
12
+ import { SYNC_TAGS } from "../../daemon/message-types/sync.js";
13
+ import type { AssistantEvent } from "../assistant-event.js";
14
+ import { assistantEventHub } from "../assistant-event-hub.js";
15
+
12
16
  // ---------------------------------------------------------------------------
13
17
  // Create a temp workspace directory for isolation
14
18
  // ---------------------------------------------------------------------------
@@ -59,6 +63,15 @@ function getRoute(operationId: string): RouteDefinition {
59
63
  return route;
60
64
  }
61
65
 
66
+ async function waitFor(predicate: () => boolean): Promise<void> {
67
+ const deadline = Date.now() + 500;
68
+ while (Date.now() < deadline) {
69
+ if (predicate()) return;
70
+ await new Promise((resolve) => setTimeout(resolve, 5));
71
+ }
72
+ throw new Error("Timed out waiting for workspace route event");
73
+ }
74
+
62
75
  // ===========================================================================
63
76
  // resolveWorkspacePath
64
77
  // ===========================================================================
@@ -492,6 +505,36 @@ describe("POST /v1/workspace/write", () => {
492
505
  handler({ body: { path: "subdir", content: "should fail" } }),
493
506
  ).toThrow(ConflictError);
494
507
  });
508
+
509
+ test("publishes sounds sync events when writing sounds config", async () => {
510
+ const received: AssistantEvent[] = [];
511
+ const subscription = assistantEventHub.subscribe({
512
+ type: "process",
513
+ callback: (event) => {
514
+ received.push(event);
515
+ },
516
+ });
517
+
518
+ try {
519
+ handler({
520
+ body: {
521
+ path: "data/sounds/config.json",
522
+ content: "{}",
523
+ },
524
+ });
525
+ await waitFor(() => received.length === 2);
526
+ expect(received.map((event) => event.message.type)).toEqual([
527
+ "sounds_config_updated",
528
+ "sync_changed",
529
+ ]);
530
+ expect(received[1]!.message).toEqual({
531
+ type: "sync_changed",
532
+ tags: [SYNC_TAGS.assistantSounds],
533
+ });
534
+ } finally {
535
+ subscription.dispose();
536
+ }
537
+ });
495
538
  });
496
539
 
497
540
  // ===========================================================================
@@ -18,6 +18,7 @@ import { basename, dirname, join } from "node:path";
18
18
  import { z } from "zod";
19
19
 
20
20
  import { getWorkspaceDir } from "../../util/platform.js";
21
+ import { publishSoundsConfigUpdated } from "../sync/resource-sync-events.js";
21
22
  import {
22
23
  BadRequestError,
23
24
  ConflictError,
@@ -41,6 +42,29 @@ interface TreeEntry {
41
42
  modifiedAt: string;
42
43
  }
43
44
 
45
+ const SOUNDS_WORKSPACE_PATH = "data/sounds";
46
+
47
+ function normaliseWorkspacePathForSync(path: string): string {
48
+ return path
49
+ .split(/[\\/]+/)
50
+ .filter((part) => part.length > 0)
51
+ .join("/");
52
+ }
53
+
54
+ function isSoundsWorkspacePath(path: string): boolean {
55
+ const normalized = normaliseWorkspacePathForSync(path);
56
+ return (
57
+ normalized === SOUNDS_WORKSPACE_PATH ||
58
+ normalized.startsWith(`${SOUNDS_WORKSPACE_PATH}/`)
59
+ );
60
+ }
61
+
62
+ function publishSoundsConfigUpdatedForPaths(paths: string[]): void {
63
+ if (paths.some(isSoundsWorkspacePath)) {
64
+ publishSoundsConfigUpdated();
65
+ }
66
+ }
67
+
44
68
  // ---------------------------------------------------------------------------
45
69
  // GET /v1/workspace/tree — list directory contents
46
70
  // ---------------------------------------------------------------------------
@@ -268,6 +292,7 @@ function handleWorkspaceWrite({ body }: RouteHandlerArgs) {
268
292
 
269
293
  mkdirSync(dirname(resolved), { recursive: true });
270
294
  writeFileSync(resolved, buffer);
295
+ publishSoundsConfigUpdatedForPaths([path]);
271
296
 
272
297
  return { path, size: buffer.byteLength };
273
298
  }
@@ -295,6 +320,7 @@ function handleWorkspaceMkdir({ body }: RouteHandlerArgs) {
295
320
  }
296
321
 
297
322
  mkdirSync(resolved, { recursive: true });
323
+ publishSoundsConfigUpdatedForPaths([path]);
298
324
  return { path };
299
325
  }
300
326
 
@@ -334,6 +360,7 @@ function handleWorkspaceRename({ body }: RouteHandlerArgs) {
334
360
 
335
361
  mkdirSync(dirname(resolvedNew), { recursive: true });
336
362
  renameSync(resolvedOld, resolvedNew);
363
+ publishSoundsConfigUpdatedForPaths([oldPath, newPath]);
337
364
  return { oldPath, newPath };
338
365
  }
339
366
 
@@ -361,6 +388,7 @@ function handleWorkspaceDelete({ body }: RouteHandlerArgs) {
361
388
  }
362
389
 
363
390
  rmSync(resolved, { recursive: true, force: true });
391
+ publishSoundsConfigUpdatedForPaths([path]);
364
392
  return { success: true };
365
393
  }
366
394
 
@@ -8,6 +8,7 @@
8
8
  */
9
9
 
10
10
  import { parseChannelId } from "../../channels/types.js";
11
+ import { getConfig } from "../../config/loader.js";
11
12
  import { normalizeConversationType } from "../../daemon/message-types/shared.js";
12
13
  import {
13
14
  type AttentionState,
@@ -22,6 +23,7 @@ import {
22
23
  } from "../../memory/conversation-crud.js";
23
24
  import type { ExternalConversationBinding } from "../../memory/external-conversation-store.js";
24
25
  import { getBindingsForConversations } from "../../memory/external-conversation-store.js";
26
+ import { buildSlackMessageDeepLinks } from "../../messaging/providers/slack/deep-link.js";
25
27
 
26
28
  // ---------------------------------------------------------------------------
27
29
  // Helpers
@@ -88,6 +90,42 @@ function buildForkParent(
88
90
  };
89
91
  }
90
92
 
93
+ function buildChannelBinding(binding: ExternalConversationBinding) {
94
+ const slackConfig =
95
+ binding.sourceChannel === "slack" && binding.externalThreadId
96
+ ? getConfig().slack
97
+ : undefined;
98
+ const slackThreadLink =
99
+ slackConfig && binding.externalThreadId
100
+ ? buildSlackMessageDeepLinks({
101
+ teamId: slackConfig.teamId,
102
+ teamUrl: slackConfig.teamUrl,
103
+ channelId: binding.externalChatId,
104
+ messageTs: binding.externalThreadId,
105
+ })
106
+ : undefined;
107
+ const slackThread =
108
+ binding.sourceChannel === "slack" && binding.externalThreadId
109
+ ? {
110
+ channelId: binding.externalChatId,
111
+ threadTs: binding.externalThreadId,
112
+ ...(slackThreadLink ? { link: slackThreadLink } : {}),
113
+ }
114
+ : undefined;
115
+
116
+ return {
117
+ sourceChannel: binding.sourceChannel,
118
+ externalChatId: binding.externalChatId,
119
+ ...(binding.externalThreadId
120
+ ? { externalThreadId: binding.externalThreadId }
121
+ : {}),
122
+ externalUserId: binding.externalUserId,
123
+ displayName: binding.displayName,
124
+ username: binding.username,
125
+ ...(slackThread ? { slackThread } : {}),
126
+ };
127
+ }
128
+
91
129
  export function serializeConversationSummary(params: {
92
130
  conversation: ConversationRow;
93
131
  binding?: ExternalConversationBinding | null;
@@ -118,13 +156,7 @@ export function serializeConversationSummary(params: {
118
156
  : {}),
119
157
  ...(binding
120
158
  ? {
121
- channelBinding: {
122
- sourceChannel: binding.sourceChannel,
123
- externalChatId: binding.externalChatId,
124
- externalUserId: binding.externalUserId,
125
- displayName: binding.displayName,
126
- username: binding.username,
127
- },
159
+ channelBinding: buildChannelBinding(binding),
128
160
  }
129
161
  : {}),
130
162
  ...(originChannel ? { conversationOriginChannel: originChannel } : {}),
@@ -1,5 +1,10 @@
1
1
  import type { IdentityFields } from "../../daemon/handlers/identity.js";
2
- import { SYNC_TAGS } from "../../daemon/message-types/sync.js";
2
+ import type { ConversationListInvalidatedReason } from "../../daemon/message-types/conversations.js";
3
+ import {
4
+ conversationMessagesSyncTag,
5
+ conversationMetadataSyncTag,
6
+ SYNC_TAGS,
7
+ } from "../../daemon/message-types/sync.js";
3
8
  import { getAvatarImagePath } from "../../util/platform.js";
4
9
  import { broadcastMessage } from "../assistant-event-hub.js";
5
10
  import { publishSyncInvalidation } from "./sync-publisher.js";
@@ -23,3 +28,90 @@ export function publishIdentityChanged(fields: IdentityFields): void {
23
28
  });
24
29
  void publishSyncInvalidation([SYNC_TAGS.assistantIdentity]);
25
30
  }
31
+
32
+ export function publishConfigChanged(): void {
33
+ broadcastMessage({ type: "config_changed" });
34
+ void publishSyncInvalidation([SYNC_TAGS.assistantConfig]);
35
+ }
36
+
37
+ export function publishSoundsConfigUpdated(): void {
38
+ broadcastMessage({ type: "sounds_config_updated" });
39
+ void publishSyncInvalidation([SYNC_TAGS.assistantSounds]);
40
+ }
41
+
42
+ export function publishSchedulesChanged(): void {
43
+ void publishSyncInvalidation([SYNC_TAGS.assistantSchedules]);
44
+ }
45
+
46
+ export function publishConversationListChanged(
47
+ reason: ConversationListInvalidatedReason,
48
+ ): void {
49
+ broadcastMessage({
50
+ type: "conversation_list_invalidated",
51
+ reason,
52
+ });
53
+ void publishSyncInvalidation([SYNC_TAGS.conversationsList]);
54
+ }
55
+
56
+ export function publishConversationMessagesChanged(
57
+ conversationId: string,
58
+ ): void {
59
+ void publishSyncInvalidation([conversationMessagesSyncTag(conversationId)]);
60
+ }
61
+
62
+ export function publishConversationListAndMetadataChanged(
63
+ reason: ConversationListInvalidatedReason,
64
+ conversationIds: string | string[],
65
+ ): void {
66
+ const ids = Array.isArray(conversationIds)
67
+ ? conversationIds
68
+ : [conversationIds];
69
+ broadcastMessage({
70
+ type: "conversation_list_invalidated",
71
+ reason,
72
+ });
73
+ void publishSyncInvalidation([
74
+ SYNC_TAGS.conversationsList,
75
+ ...ids.map((conversationId) => conversationMetadataSyncTag(conversationId)),
76
+ ]);
77
+ }
78
+
79
+ export function publishConversationTitleChanged(
80
+ conversationId: string,
81
+ title: string,
82
+ ): void {
83
+ broadcastMessage(
84
+ {
85
+ type: "conversation_title_updated",
86
+ conversationId,
87
+ title,
88
+ },
89
+ conversationId,
90
+ );
91
+ void publishSyncInvalidation([
92
+ SYNC_TAGS.conversationsList,
93
+ conversationMetadataSyncTag(conversationId),
94
+ ]);
95
+ }
96
+
97
+ export function publishConversationInferenceProfileChanged(params: {
98
+ conversationId: string;
99
+ profile: string | null;
100
+ sessionId?: string | null;
101
+ expiresAt?: number | null;
102
+ }): void {
103
+ broadcastMessage(
104
+ {
105
+ type: "conversation_inference_profile_updated",
106
+ conversationId: params.conversationId,
107
+ profile: params.profile,
108
+ sessionId: params.sessionId,
109
+ expiresAt: params.expiresAt,
110
+ },
111
+ params.conversationId,
112
+ );
113
+ void publishSyncInvalidation([
114
+ SYNC_TAGS.conversationsList,
115
+ conversationMetadataSyncTag(params.conversationId),
116
+ ]);
117
+ }
@@ -5,6 +5,7 @@ import { v4 as uuid } from "uuid";
5
5
  import { getDb } from "../memory/db-connection.js";
6
6
  import { rawChanges } from "../memory/raw-query.js";
7
7
  import { scheduleJobs, scheduleRuns } from "../memory/schema.js";
8
+ import { publishSchedulesChanged } from "../runtime/sync/resource-sync-events.js";
8
9
  import { getLogger } from "../util/logger.js";
9
10
  import {
10
11
  computeNextRunAt as computeNextRunAtEngine,
@@ -14,6 +15,10 @@ import type { ScheduleSyntax } from "./recurrence-types.js";
14
15
 
15
16
  const logger = getLogger("schedule-store");
16
17
 
18
+ function notifySchedulesChanged(): void {
19
+ publishSchedulesChanged();
20
+ }
21
+
17
22
  export type ScheduleMode = "notify" | "execute" | "script" | "wake";
18
23
  export type RoutingIntent = "single_channel" | "multi_channel" | "all_channels";
19
24
  export type ScheduleStatus = "active" | "firing" | "fired" | "cancelled";
@@ -160,6 +165,7 @@ export function createSchedule(params: {
160
165
  };
161
166
 
162
167
  db.insert(scheduleJobs).values(row).run();
168
+ notifySchedulesChanged();
163
169
  return parseJobRow(row);
164
170
  }
165
171
 
@@ -328,6 +334,7 @@ export function updateSchedule(
328
334
  }
329
335
 
330
336
  db.update(scheduleJobs).set(set).where(eq(scheduleJobs.id, id)).run();
337
+ notifySchedulesChanged();
331
338
 
332
339
  return getSchedule(id);
333
340
  }
@@ -335,7 +342,9 @@ export function updateSchedule(
335
342
  export function deleteSchedule(id: string): boolean {
336
343
  const db = getDb();
337
344
  db.delete(scheduleJobs).where(eq(scheduleJobs.id, id)).run();
338
- return rawChanges() > 0;
345
+ const deleted = rawChanges() > 0;
346
+ if (deleted) notifySchedulesChanged();
347
+ return deleted;
339
348
  }
340
349
 
341
350
  /**
@@ -466,6 +475,7 @@ export function claimDueSchedules(now: number): ScheduleJob[] {
466
475
  );
467
476
  }
468
477
 
478
+ if (claimed.length > 0) notifySchedulesChanged();
469
479
  return claimed;
470
480
  }
471
481
 
@@ -484,6 +494,7 @@ export function completeOneShot(id: string): void {
484
494
  })
485
495
  .where(and(eq(scheduleJobs.id, id), eq(scheduleJobs.status, "firing")))
486
496
  .run();
497
+ if (rawChanges() > 0) notifySchedulesChanged();
487
498
  }
488
499
 
489
500
  /**
@@ -500,6 +511,7 @@ export function failOneShot(id: string): void {
500
511
  })
501
512
  .where(and(eq(scheduleJobs.id, id), eq(scheduleJobs.status, "firing")))
502
513
  .run();
514
+ if (rawChanges() > 0) notifySchedulesChanged();
503
515
  }
504
516
 
505
517
  /**
@@ -510,6 +522,7 @@ export function failOneShot(id: string): void {
510
522
  export function retryOneShot(id: string): void {
511
523
  const db = getDb();
512
524
  const now = Date.now();
525
+ let changed = false;
513
526
  db.update(scheduleJobs)
514
527
  .set({
515
528
  status: "active",
@@ -518,6 +531,8 @@ export function retryOneShot(id: string): void {
518
531
  })
519
532
  .where(and(eq(scheduleJobs.id, id), eq(scheduleJobs.status, "firing")))
520
533
  .run();
534
+ changed = rawChanges() > 0;
535
+ if (changed) notifySchedulesChanged();
521
536
  }
522
537
 
523
538
  /**
@@ -537,6 +552,7 @@ export function failOneShotPermanently(id: string): void {
537
552
  })
538
553
  .where(and(eq(scheduleJobs.id, id), eq(scheduleJobs.status, "firing")))
539
554
  .run();
555
+ if (rawChanges() > 0) notifySchedulesChanged();
540
556
  }
541
557
 
542
558
  /**
@@ -554,7 +570,9 @@ export function cancelSchedule(id: string): boolean {
554
570
  })
555
571
  .where(and(eq(scheduleJobs.id, id), eq(scheduleJobs.status, "active")))
556
572
  .run();
557
- return rawChanges() > 0;
573
+ const cancelled = rawChanges() > 0;
574
+ if (cancelled) notifySchedulesChanged();
575
+ return cancelled;
558
576
  }
559
577
 
560
578
  export function createScheduleRun(
@@ -625,12 +643,14 @@ export function completeScheduleRun(
625
643
  })
626
644
  .where(eq(scheduleJobs.id, run.jobId))
627
645
  .run();
646
+ if (rawChanges() > 0) notifySchedulesChanged();
628
647
  }
629
648
  } else {
630
649
  db.update(scheduleJobs)
631
650
  .set({ lastStatus: "ok", retryCount: 0, updatedAt: now })
632
651
  .where(eq(scheduleJobs.id, run.jobId))
633
652
  .run();
653
+ if (rawChanges() > 0) notifySchedulesChanged();
634
654
  }
635
655
  }
636
656
 
@@ -839,16 +859,20 @@ export function describeCronExpression(expr: string | null): string {
839
859
  export function scheduleRetry(id: string, nextRetryAt: number): void {
840
860
  const db = getDb();
841
861
  const now = Date.now();
862
+ let changed = false;
842
863
  db.update(scheduleJobs)
843
864
  .set({ nextRunAt: nextRetryAt, updatedAt: now })
844
865
  .where(eq(scheduleJobs.id, id))
845
866
  .run();
867
+ changed = rawChanges() > 0;
846
868
  // Revert one-shot status from "firing" to "active" so the scheduler
847
869
  // will claim it again when nextRetryAt arrives. No-op for recurring.
848
870
  db.update(scheduleJobs)
849
871
  .set({ status: "active", updatedAt: now })
850
872
  .where(and(eq(scheduleJobs.id, id), eq(scheduleJobs.status, "firing")))
851
873
  .run();
874
+ changed = rawChanges() > 0 || changed;
875
+ if (changed) notifySchedulesChanged();
852
876
  }
853
877
 
854
878
  /**
@@ -860,6 +884,7 @@ export function resetRetryCount(id: string): void {
860
884
  .set({ retryCount: 0, updatedAt: Date.now() })
861
885
  .where(eq(scheduleJobs.id, id))
862
886
  .run();
887
+ if (rawChanges() > 0) notifySchedulesChanged();
863
888
  }
864
889
 
865
890
  /**
@@ -549,7 +549,15 @@ export async function runScheduleOnce(
549
549
  });
550
550
  },
551
551
  });
552
- conversationId = result.conversationId;
552
+ // Bootstrap-failure path returns `{ ok: false, conversationId: "" }`.
553
+ // Substitute a sentinel only for failures so the schedule-run DB row
554
+ // carries a recognizable marker. Successful skips (e.g.
555
+ // `pre_first_user_message`) also return `conversationId: ""` but with
556
+ // `ok: true` — keep the empty ID to preserve their skip contract.
557
+ conversationId =
558
+ !result.ok && result.conversationId === ""
559
+ ? `bootstrap-error:${job.id}`
560
+ : result.conversationId;
553
561
  ok = result.ok;
554
562
  errorMsg = result.error?.message;
555
563
  }