@vellumai/assistant 0.8.1 → 0.8.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (630) hide show
  1. package/ARCHITECTURE.md +13 -19
  2. package/Dockerfile +75 -1
  3. package/bun.lock +11 -1
  4. package/docker-entrypoint.sh +17 -0
  5. package/docker-init-apt-root.sh +167 -0
  6. package/docker-kata-apt-env.sh +39 -0
  7. package/docs/plugins.md +88 -47
  8. package/docs/skills.md +9 -7
  9. package/examples/plugins/echo/README.md +27 -27
  10. package/examples/plugins/echo/package.json +3 -0
  11. package/examples/plugins/echo/register.ts +31 -31
  12. package/node_modules/@vellumai/slack-text/src/index.test.ts +114 -14
  13. package/node_modules/@vellumai/slack-text/src/index.ts +82 -18
  14. package/openapi.yaml +642 -5
  15. package/package.json +3 -1
  16. package/scripts/generate-openapi.ts +83 -10
  17. package/scripts/sync-llm-catalog.ts +2 -2
  18. package/scripts/sync-web-search-catalog.ts +47 -25
  19. package/src/__tests__/agent-image-optimize.test.ts +11 -3
  20. package/src/__tests__/agent-loop-exit-reason.test.ts +272 -0
  21. package/src/__tests__/agent-loop-provider-error-recording.test.ts +195 -0
  22. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +131 -0
  23. package/src/__tests__/anthropic-provider.test.ts +45 -0
  24. package/src/__tests__/app-builder-tool-scripts.test.ts +9 -3
  25. package/src/__tests__/app-executors.test.ts +220 -4
  26. package/src/__tests__/auto-analysis-end-to-end.test.ts +35 -0
  27. package/src/__tests__/bundled-asset.test.ts +6 -6
  28. package/src/__tests__/channel-availability-routes.test.ts +206 -0
  29. package/src/__tests__/channel-delivery-store.test.ts +289 -1
  30. package/src/__tests__/circuit-breaker-pipeline.test.ts +0 -1
  31. package/src/__tests__/clawhub.test.ts +75 -16
  32. package/src/__tests__/compactor-tail-resolution.test.ts +147 -0
  33. package/src/__tests__/config-get-vision-flag.test.ts +136 -0
  34. package/src/__tests__/config-loader-backfill.test.ts +115 -18
  35. package/src/__tests__/config-schema.test.ts +21 -0
  36. package/src/__tests__/config-set-route.test.ts +80 -0
  37. package/src/__tests__/config-sounds-sync.test.ts +97 -0
  38. package/src/__tests__/config-watcher-skill-reseed.test.ts +453 -0
  39. package/src/__tests__/context-search-conversations-source.test.ts +117 -2
  40. package/src/__tests__/context-search-memory-v2-source.test.ts +0 -1
  41. package/src/__tests__/context-search-workspace-source.test.ts +7 -0
  42. package/src/__tests__/context-token-estimator.test.ts +31 -65
  43. package/src/__tests__/conversation-abort-tool-results.test.ts +4 -1
  44. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +1 -0
  45. package/src/__tests__/conversation-agent-loop-overflow.test.ts +92 -92
  46. package/src/__tests__/conversation-agent-loop.test.ts +59 -1
  47. package/src/__tests__/conversation-error.test.ts +42 -3
  48. package/src/__tests__/conversation-fork-crud.test.ts +82 -0
  49. package/src/__tests__/conversation-inference-profile-route.test.ts +40 -4
  50. package/src/__tests__/conversation-lifecycle.test.ts +173 -0
  51. package/src/__tests__/conversation-media-retry.test.ts +19 -8
  52. package/src/__tests__/conversation-message-sync-tags.test.ts +97 -0
  53. package/src/__tests__/conversation-pairing.test.ts +54 -0
  54. package/src/__tests__/conversation-process-callsite.test.ts +4 -1
  55. package/src/__tests__/conversation-provider-retry-repair.test.ts +5 -1
  56. package/src/__tests__/conversation-queue.test.ts +4 -1
  57. package/src/__tests__/conversation-runtime-assembly.test.ts +102 -13
  58. package/src/__tests__/conversation-slash-queue.test.ts +59 -1
  59. package/src/__tests__/conversation-slash-unknown.test.ts +4 -1
  60. package/src/__tests__/conversation-surfaces-table-action.test.ts +360 -0
  61. package/src/__tests__/conversation-sync-tags.test.ts +235 -0
  62. package/src/__tests__/conversation-workspace-injection.test.ts +5 -1
  63. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +5 -1
  64. package/src/__tests__/credential-security-invariants.test.ts +3 -2
  65. package/src/__tests__/date-context.test.ts +45 -0
  66. package/src/__tests__/db-slack-external-content-normalization.test.ts +301 -0
  67. package/src/__tests__/delete-managed-skill-tool.test.ts +55 -13
  68. package/src/__tests__/disk-pressure-tools.test.ts +1 -0
  69. package/src/__tests__/dm-backfill.test.ts +121 -10
  70. package/src/__tests__/document-tool-security.test.ts +258 -0
  71. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +0 -1
  72. package/src/__tests__/edit-propagation.test.ts +33 -0
  73. package/src/__tests__/empty-response-pipeline.test.ts +0 -4
  74. package/src/__tests__/external-plugin-loader.test.ts +151 -55
  75. package/src/__tests__/filing-service.test.ts +140 -0
  76. package/src/__tests__/get-skill-detail-audit.test.ts +0 -4
  77. package/src/__tests__/guardian-action-no-hardcoded-copy.test.ts +0 -1
  78. package/src/__tests__/guardian-dispatch.test.ts +1 -0
  79. package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +43 -62
  80. package/src/__tests__/heartbeat-service.test.ts +24 -164
  81. package/src/__tests__/helpers/channel-test-adapter.ts +0 -2
  82. package/src/__tests__/helpers/tar-fixtures.ts +39 -0
  83. package/src/__tests__/helpers/wait-for.ts +21 -0
  84. package/src/__tests__/history-repair-pipeline.test.ts +0 -3
  85. package/src/__tests__/history-repair.test.ts +73 -0
  86. package/src/__tests__/host-app-control-proxy.test.ts +507 -10
  87. package/src/__tests__/host-proxy-preactivation.test.ts +200 -13
  88. package/src/__tests__/image-credentials.test.ts +1 -1
  89. package/src/__tests__/inbound-slack-persistence.test.ts +2 -0
  90. package/src/__tests__/inference-no-mode-boot-e2e.test.ts +1 -1
  91. package/src/__tests__/inference-profile-reaper.test.ts +4 -2
  92. package/src/__tests__/inference-profile-session-handler.test.ts +18 -6
  93. package/src/__tests__/inference-profile-session-ipc.test.ts +17 -5
  94. package/src/__tests__/injector-background-turn.test.ts +153 -0
  95. package/src/__tests__/injector-chain.test.ts +15 -8
  96. package/src/__tests__/install-skill-routing.test.ts +155 -37
  97. package/src/__tests__/lifecycle-memory-v2-seed.test.ts +99 -3
  98. package/src/__tests__/list-messages-page-latest.test.ts +55 -0
  99. package/src/__tests__/llm-call-pipeline.test.ts +0 -3
  100. package/src/__tests__/llm-callsite-catalog.test.ts +25 -0
  101. package/src/__tests__/llm-catalog-parity.test.ts +58 -13
  102. package/src/__tests__/llm-request-log-agent-loop-exit-reason.test.ts +116 -0
  103. package/src/__tests__/llm-request-log-error-payload.test.ts +138 -0
  104. package/src/__tests__/llm-request-log-source-clickhouse.test.ts +36 -0
  105. package/src/__tests__/llm-request-log-source-factory.test.ts +29 -53
  106. package/src/__tests__/llm-resolver.test.ts +255 -2
  107. package/src/__tests__/llm-usage-store.test.ts +114 -0
  108. package/src/__tests__/managed-profile-guard.test.ts +41 -29
  109. package/src/__tests__/managed-skill-lifecycle.test.ts +109 -18
  110. package/src/__tests__/managed-store.test.ts +84 -192
  111. package/src/__tests__/media-generate-image.test.ts +1 -1
  112. package/src/__tests__/memory-retrieval-pipeline.test.ts +0 -2
  113. package/src/__tests__/messages-after-tiebreaker.test.ts +122 -0
  114. package/src/__tests__/notification-decision-fallback.test.ts +0 -91
  115. package/src/__tests__/notification-decision-strategy.test.ts +14 -31
  116. package/src/__tests__/notification-deep-link.test.ts +15 -0
  117. package/src/__tests__/notification-guardian-path.test.ts +1 -2
  118. package/src/__tests__/notification-platform-adapter.test.ts +5 -4
  119. package/src/__tests__/notification-telegram-adapter.test.ts +1 -0
  120. package/src/__tests__/notification-vellum-adapter.test.ts +113 -0
  121. package/src/__tests__/oauth-commands-routes.test.ts +168 -16
  122. package/src/__tests__/oauth-provider-profiles.test.ts +9 -0
  123. package/src/__tests__/openai-provider.test.ts +242 -3
  124. package/src/__tests__/openai-responses-cutover-guard.test.ts +17 -9
  125. package/src/__tests__/openrouter-provider-only.test.ts +51 -3
  126. package/src/__tests__/openrouter-token-estimation.test.ts +34 -25
  127. package/src/__tests__/overflow-reduce-pipeline.test.ts +0 -2
  128. package/src/__tests__/persistence-pipeline.test.ts +0 -2
  129. package/src/__tests__/{managed-proxy-context.test.ts → platform-proxy-context.test.ts} +7 -2
  130. package/src/__tests__/platform.test.ts +2 -0
  131. package/src/__tests__/plugin-api-shim.test.ts +125 -0
  132. package/src/__tests__/plugin-bootstrap.test.ts +10 -36
  133. package/src/__tests__/plugin-external-api.test.ts +68 -0
  134. package/src/__tests__/plugin-registry.test.ts +0 -77
  135. package/src/__tests__/plugin-route-contribution.test.ts +0 -1
  136. package/src/__tests__/plugin-skill-contribution.test.ts +0 -2
  137. package/src/__tests__/plugin-tool-contribution.test.ts +16 -15
  138. package/src/__tests__/plugin-types.test.ts +3 -13
  139. package/src/__tests__/process-message-background-slack.test.ts +8 -1
  140. package/src/__tests__/process-message-display-content.test.ts +421 -0
  141. package/src/__tests__/provider-catalog-visibility.test.ts +158 -0
  142. package/src/__tests__/provider-error-scenarios.test.ts +111 -0
  143. package/src/__tests__/{provider-managed-proxy-integration.test.ts → provider-platform-proxy-integration.test.ts} +33 -31
  144. package/src/__tests__/scaffold-managed-skill-tool.test.ts +65 -13
  145. package/src/__tests__/schedule-routes.test.ts +50 -3
  146. package/src/__tests__/schedule-store.test.ts +94 -0
  147. package/src/__tests__/scheduler-reuse-conversation.test.ts +54 -7
  148. package/src/__tests__/schema-transforms.test.ts +20 -0
  149. package/src/__tests__/search-skills-unified.test.ts +0 -5
  150. package/src/__tests__/{secret-routes-managed-proxy.test.ts → secret-routes-platform-proxy.test.ts} +1 -1
  151. package/src/__tests__/server-history-render.test.ts +43 -0
  152. package/src/__tests__/skill-load-feature-flag.test.ts +0 -12
  153. package/src/__tests__/skill-load-tool.test.ts +27 -89
  154. package/src/__tests__/skill-memory.test.ts +23 -3
  155. package/src/__tests__/skills-file-content-endpoint.test.ts +9 -38
  156. package/src/__tests__/skills-files-catalog-fallback.test.ts +0 -3
  157. package/src/__tests__/skills-install-extract.test.ts +49 -38
  158. package/src/__tests__/skills-install-staging.test.ts +159 -0
  159. package/src/__tests__/skills-uninstall.test.ts +9 -41
  160. package/src/__tests__/skills.test.ts +51 -58
  161. package/src/__tests__/slack-channel-config.test.ts +9 -0
  162. package/src/__tests__/subagent-tool-filtering.test.ts +50 -0
  163. package/src/__tests__/system-prompt.test.ts +670 -63
  164. package/src/__tests__/terminal-tools.test.ts +28 -1
  165. package/src/__tests__/thread-backfill.test.ts +557 -27
  166. package/src/__tests__/title-generate-pipeline.test.ts +0 -13
  167. package/src/__tests__/token-estimate-pipeline.test.ts +0 -3
  168. package/src/__tests__/tool-error-pipeline.test.ts +0 -3
  169. package/src/__tests__/tool-execute-pipeline.test.ts +0 -5
  170. package/src/__tests__/tool-executor-lifecycle-events.test.ts +1 -1
  171. package/src/__tests__/tool-executor.test.ts +16 -4
  172. package/src/__tests__/tool-result-truncate-pipeline.test.ts +0 -12
  173. package/src/__tests__/turn-events-store.test.ts +256 -0
  174. package/src/__tests__/twilio-routes.test.ts +4 -0
  175. package/src/__tests__/user-plugin-loader.test.ts +0 -7
  176. package/src/__tests__/voice-session-bridge.test.ts +198 -0
  177. package/src/__tests__/web-search-catalog-parity.test.ts +32 -10
  178. package/src/__tests__/workspace-migration-057-repair-stale-gemini-model-ids.test.ts +115 -3
  179. package/src/__tests__/workspace-migration-072-seed-reply-suggestion-callsite.test.ts +50 -0
  180. package/src/__tests__/workspace-migration-073-repair-recall-callsite-empty-profile.test.ts +153 -0
  181. package/src/__tests__/workspace-migration-085-memory-v2-bm25-b-reembed-disabled-v2-pages.test.ts +220 -0
  182. package/src/__tests__/workspace-migration-086-revert-stale-gemini-mis-rewrites.test.ts +269 -0
  183. package/src/__tests__/workspace-migration-087-memory-router-balanced-profile.test.ts +228 -0
  184. package/src/__tests__/workspace-migration-remove-legacy-skills-index.test.ts +309 -0
  185. package/src/__tests__/workspace-migrations-runner.test.ts +111 -3
  186. package/src/a2a/__tests__/agent-card.test.ts +98 -0
  187. package/src/a2a/__tests__/e2e-a2a-channel.test.ts +597 -0
  188. package/src/a2a/__tests__/protocol-helpers.test.ts +113 -0
  189. package/src/a2a/__tests__/task-store.test.ts +246 -0
  190. package/src/a2a/agent-card.ts +58 -0
  191. package/src/a2a/feature-gate.ts +8 -0
  192. package/src/a2a/protocol-constants.ts +21 -0
  193. package/src/a2a/protocol-errors.ts +50 -0
  194. package/src/a2a/protocol-types.ts +162 -0
  195. package/src/a2a/task-store.ts +168 -0
  196. package/src/acp/resolve-agent.ts +1 -1
  197. package/src/agent/image-optimize.ts +13 -5
  198. package/src/agent/loop.ts +167 -18
  199. package/src/calls/voice-session-bridge.ts +61 -42
  200. package/src/channels/config.ts +9 -0
  201. package/src/channels/types.ts +122 -0
  202. package/src/cli/__tests__/unknown-command.test.ts +24 -0
  203. package/src/cli/commands/__tests__/changelog.test.ts +304 -319
  204. package/src/cli/{__tests__ → commands/__tests__}/notifications.test.ts +201 -28
  205. package/src/cli/commands/__tests__/schedules.test.ts +960 -0
  206. package/src/cli/commands/changelog.ts +106 -42
  207. package/src/cli/commands/conversations.ts +102 -17
  208. package/src/cli/commands/default-action.ts +10 -53
  209. package/src/cli/commands/notifications.ts +388 -346
  210. package/src/cli/commands/plugins.ts +252 -0
  211. package/src/cli/commands/schedules.ts +683 -0
  212. package/src/cli/commands/telemetry.ts +40 -0
  213. package/src/cli/lib/__tests__/cli-colors.test.ts +48 -0
  214. package/src/cli/lib/__tests__/confirm-prompt.test.ts +159 -0
  215. package/src/cli/lib/__tests__/install-from-github.test.ts +355 -0
  216. package/src/cli/lib/__tests__/list-installed-plugins.test.ts +154 -0
  217. package/src/cli/lib/__tests__/search-plugins.test.ts +261 -0
  218. package/src/cli/lib/__tests__/uninstall-plugin.test.ts +124 -0
  219. package/src/cli/lib/__tests__/unknown-command.test.ts +106 -0
  220. package/src/cli/lib/cli-colors.ts +12 -0
  221. package/src/cli/lib/confirm-prompt.ts +79 -0
  222. package/src/cli/lib/install-from-github.ts +303 -0
  223. package/src/cli/lib/list-installed-plugins.ts +137 -0
  224. package/src/cli/lib/search-plugins.ts +163 -0
  225. package/src/cli/lib/uninstall-plugin.ts +82 -0
  226. package/src/cli/lib/unknown-command.ts +111 -0
  227. package/src/cli/program.ts +52 -2
  228. package/src/config/assistant-feature-flags.ts +24 -54
  229. package/src/config/bundled-skills/app-builder/SKILL.md +140 -22
  230. package/src/config/bundled-skills/app-builder/TOOLS.json +7 -0
  231. package/src/config/bundled-skills/computer-use/TOOLS.json +15 -52
  232. package/src/config/bundled-skills/document/SKILL.md +23 -3
  233. package/src/config/bundled-skills/document/TOOLS.json +53 -0
  234. package/src/config/bundled-skills/document/tools/document-delete.ts +12 -0
  235. package/src/config/bundled-skills/document/tools/document-list.ts +12 -0
  236. package/src/config/bundled-skills/document/tools/document-read.ts +12 -0
  237. package/src/config/bundled-skills/phone-calls/SKILL.md +1 -1
  238. package/src/config/bundled-skills/skill-management/SKILL.md +2 -2
  239. package/src/config/bundled-skills/skill-management/TOOLS.json +7 -7
  240. package/src/config/bundled-tool-registry.ts +6 -0
  241. package/src/config/call-site-defaults.ts +105 -0
  242. package/src/config/feature-flag-registry.json +41 -9
  243. package/src/config/llm-resolver.ts +52 -1
  244. package/src/config/loader.ts +64 -38
  245. package/src/config/schema.ts +9 -10
  246. package/src/config/schemas/__tests__/llm-request-logs.test.ts +36 -0
  247. package/src/config/schemas/__tests__/memory-v2.test.ts +3 -3
  248. package/src/config/schemas/channels.ts +17 -0
  249. package/src/config/schemas/compaction.ts +28 -0
  250. package/src/config/schemas/conversations.ts +10 -0
  251. package/src/config/schemas/heartbeat.ts +23 -0
  252. package/src/config/schemas/llm-request-logs.ts +31 -7
  253. package/src/config/schemas/llm.ts +1 -0
  254. package/src/config/schemas/memory-retrieval.ts +18 -0
  255. package/src/config/schemas/memory-retrospective.ts +1 -1
  256. package/src/config/schemas/memory-v2.ts +4 -4
  257. package/src/config/schemas/memory.ts +3 -1
  258. package/src/config/schemas/tools.ts +14 -0
  259. package/src/config/seed-inference-profiles.ts +99 -29
  260. package/src/config/skills.ts +3 -96
  261. package/src/context/compactor.ts +1107 -0
  262. package/src/context/token-estimator.ts +34 -36
  263. package/src/context/window-manager.ts +197 -1520
  264. package/src/credential-execution/managed-catalog.ts +37 -0
  265. package/src/credential-health/credential-health-service.ts +280 -19
  266. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +33 -18
  267. package/src/daemon/__tests__/conversation-tool-setup-exclude.test.ts +138 -0
  268. package/src/daemon/__tests__/conversation-tool-setup.test.ts +74 -0
  269. package/src/daemon/approval-generators.ts +8 -6
  270. package/src/daemon/config-watcher.ts +94 -31
  271. package/src/daemon/conversation-agent-loop-handlers.ts +78 -0
  272. package/src/daemon/conversation-agent-loop.ts +198 -11
  273. package/src/daemon/conversation-error.ts +171 -37
  274. package/src/daemon/conversation-lifecycle.ts +53 -40
  275. package/src/daemon/conversation-messaging.ts +25 -6
  276. package/src/daemon/conversation-process.ts +49 -12
  277. package/src/daemon/conversation-runtime-assembly.ts +25 -1
  278. package/src/daemon/conversation-slash.ts +12 -5
  279. package/src/daemon/conversation-store.ts +11 -4
  280. package/src/daemon/conversation-tool-setup.ts +39 -7
  281. package/src/daemon/conversation.ts +33 -8
  282. package/src/daemon/date-context.ts +40 -0
  283. package/src/daemon/external-plugins-bootstrap.ts +217 -181
  284. package/src/daemon/first-greeting.ts +22 -2
  285. package/src/daemon/guardian-action-generators.ts +1 -125
  286. package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +248 -0
  287. package/src/daemon/handlers/__tests__/config-a2a-invite.test.ts +154 -0
  288. package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +133 -0
  289. package/src/daemon/handlers/__tests__/config-a2a.test.ts +95 -0
  290. package/src/daemon/handlers/config-a2a.ts +289 -0
  291. package/src/daemon/handlers/config-model.ts +6 -5
  292. package/src/daemon/handlers/config-slack-channel.ts +15 -3
  293. package/src/daemon/handlers/conversations.ts +1 -0
  294. package/src/daemon/handlers/shared.ts +14 -5
  295. package/src/daemon/handlers/skills.ts +111 -108
  296. package/src/daemon/history-repair.ts +28 -1
  297. package/src/daemon/host-app-control-proxy.ts +153 -27
  298. package/src/daemon/host-proxy-preactivation.ts +85 -18
  299. package/src/daemon/lifecycle.ts +89 -91
  300. package/src/daemon/meet-host-supervisor.ts +5 -4
  301. package/src/daemon/memory-v2-startup.ts +85 -0
  302. package/src/daemon/message-protocol.ts +1 -0
  303. package/src/daemon/message-types/conversations.ts +25 -0
  304. package/src/daemon/message-types/messages.ts +61 -0
  305. package/src/daemon/message-types/notifications.ts +21 -0
  306. package/src/daemon/message-types/subagents.ts +1 -0
  307. package/src/daemon/message-types/sync.ts +1 -0
  308. package/src/daemon/pkb-reminder-builder.test.ts +11 -54
  309. package/src/daemon/pkb-reminder-builder.ts +5 -20
  310. package/src/daemon/plugin-source-watcher.ts +146 -0
  311. package/src/daemon/process-message.ts +24 -3
  312. package/src/daemon/server.ts +11 -2
  313. package/src/daemon/skill-memory-refresh.ts +33 -0
  314. package/src/daemon/wake-target-adapter.ts +2 -0
  315. package/src/documents/document-store.ts +221 -3
  316. package/src/embedded/plugin-api.ts +40 -0
  317. package/src/export/__tests__/transcript-formatter.test.ts +121 -0
  318. package/src/export/transcript-formatter.ts +54 -20
  319. package/src/filing/filing-service.ts +39 -0
  320. package/src/heartbeat/__tests__/heartbeat-service.test.ts +135 -6
  321. package/src/heartbeat/heartbeat-run-store.ts +2 -1
  322. package/src/heartbeat/heartbeat-service.ts +73 -189
  323. package/src/home/__tests__/feed-types.test.ts +80 -0
  324. package/src/home/feed-types.ts +36 -2
  325. package/src/home/post-connect-feed.ts +1 -0
  326. package/src/index.ts +18 -1
  327. package/src/ipc/cli-client.ts +147 -45
  328. package/src/live-voice/__tests__/live-voice-stt.test.ts +57 -0
  329. package/src/mcp/client.ts +20 -4
  330. package/src/media/image-credentials.ts +3 -3
  331. package/src/memory/__tests__/bookmark-crud.test.ts +33 -27
  332. package/src/memory/__tests__/conversation-queries.test.ts +483 -0
  333. package/src/memory/__tests__/jobs-worker-v2-graph-trigger-embed.test.ts +113 -0
  334. package/src/memory/__tests__/memory-retrospective-enqueue.test.ts +2 -50
  335. package/src/memory/__tests__/memory-retrospective-job.test.ts +87 -4
  336. package/src/memory/__tests__/memory-retrospective-startup-cleanup.test.ts +119 -14
  337. package/src/memory/__tests__/message-content.test.ts +35 -0
  338. package/src/memory/bookmark-crud.ts +42 -10
  339. package/src/memory/context-search/sources/conversations.ts +62 -2
  340. package/src/memory/context-search/sources/workspace.ts +4 -0
  341. package/src/memory/conversation-crud.ts +63 -19
  342. package/src/memory/conversation-queries.ts +197 -11
  343. package/src/memory/conversation-title-service.ts +26 -4
  344. package/src/memory/db-init.ts +12 -0
  345. package/src/memory/delivery-crud.ts +152 -5
  346. package/src/memory/embedding-backend.ts +4 -4
  347. package/src/memory/external-conversation-store.ts +66 -5
  348. package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +150 -12
  349. package/src/memory/graph/conversation-graph-memory.ts +49 -21
  350. package/src/memory/graph/tools.ts +9 -40
  351. package/src/memory/indexer.ts +34 -29
  352. package/src/memory/invite-store.ts +53 -0
  353. package/src/memory/jobs/__tests__/embed-concept-page.test.ts +73 -0
  354. package/src/memory/jobs/embed-concept-page.ts +20 -11
  355. package/src/memory/jobs-worker.ts +6 -1
  356. package/src/memory/llm-request-log-source-clickhouse.ts +24 -12
  357. package/src/memory/llm-request-log-source.ts +19 -52
  358. package/src/memory/llm-request-log-store.ts +92 -1
  359. package/src/memory/llm-usage-store.ts +125 -5
  360. package/src/memory/memory-retrospective-enqueue.ts +1 -20
  361. package/src/memory/memory-retrospective-job.ts +33 -6
  362. package/src/memory/memory-retrospective-startup-cleanup.ts +72 -5
  363. package/src/memory/message-content.ts +1 -1
  364. package/src/memory/migrations/109-external-conversation-bindings.ts +15 -4
  365. package/src/memory/migrations/229-delete-private-conversations.test.ts +38 -1
  366. package/src/memory/migrations/229-delete-private-conversations.ts +7 -0
  367. package/src/memory/migrations/247-external-conversation-binding-thread-id.ts +78 -0
  368. package/src/memory/migrations/248-create-onboarding-events.ts +21 -0
  369. package/src/memory/migrations/249-normalize-slack-external-content.ts +240 -0
  370. package/src/memory/migrations/250-provider-connection-base-url-and-models.ts +28 -0
  371. package/src/memory/migrations/251-a2a-tasks.ts +49 -0
  372. package/src/memory/migrations/252-llm-request-log-agent-loop-exit-reason.ts +32 -0
  373. package/src/memory/migrations/index.ts +9 -0
  374. package/src/memory/migrations/registry.ts +16 -0
  375. package/src/memory/onboarding-events-store.ts +106 -0
  376. package/src/memory/schema/a2a.ts +15 -0
  377. package/src/memory/schema/bookmarks.ts +0 -2
  378. package/src/memory/schema/calls.ts +1 -0
  379. package/src/memory/schema/index.ts +1 -0
  380. package/src/memory/schema/inference.ts +3 -3
  381. package/src/memory/schema/infrastructure.ts +13 -0
  382. package/src/memory/turn-events-store.ts +127 -2
  383. package/src/memory/v2/__tests__/activation-store.test.ts +25 -23
  384. package/src/memory/v2/__tests__/activation.test.ts +0 -8
  385. package/src/memory/v2/__tests__/cli-command-store.test.ts +404 -0
  386. package/src/memory/v2/__tests__/frontmatter-sweep.test.ts +25 -4
  387. package/src/memory/v2/__tests__/injection.test.ts +288 -11
  388. package/src/memory/v2/__tests__/migration.test.ts +87 -0
  389. package/src/memory/v2/__tests__/page-index.test.ts +83 -0
  390. package/src/memory/v2/__tests__/prompts-router.test.ts +58 -6
  391. package/src/memory/v2/__tests__/qdrant.test.ts +66 -3
  392. package/src/memory/v2/__tests__/router.test.ts +15 -0
  393. package/src/memory/v2/__tests__/skill-store.test.ts +387 -8
  394. package/src/memory/v2/__tests__/static-context.test.ts +12 -1
  395. package/src/memory/v2/activation-store.ts +14 -16
  396. package/src/memory/v2/cli-command-content.ts +19 -0
  397. package/src/memory/v2/cli-command-store.ts +304 -0
  398. package/src/memory/v2/frontmatter-sweep.ts +7 -1
  399. package/src/memory/v2/injection.ts +81 -26
  400. package/src/memory/v2/migration.ts +49 -19
  401. package/src/memory/v2/page-index.ts +63 -8
  402. package/src/memory/v2/prompts/router.ts +11 -8
  403. package/src/memory/v2/prompts/sweep.ts +2 -2
  404. package/src/memory/v2/qdrant.ts +135 -7
  405. package/src/memory/v2/router.ts +9 -8
  406. package/src/memory/v2/skill-store.ts +120 -35
  407. package/src/memory/v2/static-context.ts +4 -4
  408. package/src/memory/v2/types.ts +23 -0
  409. package/src/messaging/providers/a2a/__tests__/deliver.test.ts +274 -0
  410. package/src/messaging/providers/a2a/deliver.ts +156 -0
  411. package/src/messaging/providers/gmail/client.ts +9 -2
  412. package/src/messaging/providers/index.ts +11 -2
  413. package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +45 -5
  414. package/src/messaging/providers/slack/__tests__/download.test.ts +231 -0
  415. package/src/messaging/providers/slack/adapter.ts +43 -5
  416. package/src/messaging/providers/slack/client.ts +27 -0
  417. package/src/messaging/providers/slack/deep-link.ts +65 -0
  418. package/src/messaging/providers/slack/download.ts +104 -0
  419. package/src/messaging/providers/slack/message-metadata.test.ts +32 -0
  420. package/src/messaging/providers/slack/message-metadata.ts +27 -0
  421. package/src/messaging/providers/slack/render-transcript.test.ts +134 -0
  422. package/src/messaging/providers/slack/render-transcript.ts +69 -5
  423. package/src/messaging/providers/slack/types.ts +20 -1
  424. package/src/notifications/__tests__/broadcaster.test.ts +203 -0
  425. package/src/notifications/__tests__/decision-engine.test.ts +283 -0
  426. package/src/notifications/__tests__/deterministic-checks.test.ts +286 -0
  427. package/src/notifications/__tests__/emit-signal-home-feed.test.ts +1 -0
  428. package/src/notifications/__tests__/home-feed-side-effect.test.ts +430 -7
  429. package/src/notifications/adapters/macos.ts +12 -2
  430. package/src/notifications/broadcaster.ts +29 -4
  431. package/src/notifications/conversation-pairing.ts +2 -1
  432. package/src/notifications/copy-composer.ts +17 -64
  433. package/src/notifications/decision-engine.ts +113 -45
  434. package/src/notifications/deterministic-checks.ts +96 -0
  435. package/src/notifications/emit-signal.ts +21 -1
  436. package/src/notifications/home-feed-side-effect.ts +138 -5
  437. package/src/notifications/signal.ts +3 -5
  438. package/src/notifications/types.ts +8 -0
  439. package/src/oauth/connection-resolver.ts +8 -4
  440. package/src/oauth/platform-connection.test.ts +43 -3
  441. package/src/oauth/platform-connection.ts +19 -6
  442. package/src/oauth/seed-providers.ts +10 -1
  443. package/src/permissions/checker.ts +2 -0
  444. package/src/permissions/ipc-risk-types.ts +1 -0
  445. package/src/permissions/question-prompter.test.ts +416 -0
  446. package/src/permissions/question-prompter.ts +294 -0
  447. package/src/platform/client.test.ts +1 -1
  448. package/src/platform/client.ts +1 -1
  449. package/src/plugin-api/constants.ts +26 -0
  450. package/src/plugin-api/index.ts +34 -1
  451. package/src/plugin-api/types.ts +104 -22
  452. package/src/plugins/defaults/circuit-breaker.ts +0 -5
  453. package/src/plugins/defaults/compaction.ts +0 -4
  454. package/src/plugins/defaults/empty-response.ts +0 -2
  455. package/src/plugins/defaults/history-repair.ts +0 -2
  456. package/src/plugins/defaults/injectors.ts +74 -22
  457. package/src/plugins/defaults/llm-call.ts +0 -2
  458. package/src/plugins/defaults/memory-retrieval.ts +0 -1
  459. package/src/plugins/defaults/overflow-reduce.ts +0 -1
  460. package/src/plugins/defaults/persistence.ts +0 -2
  461. package/src/plugins/defaults/title-generate.ts +0 -5
  462. package/src/plugins/defaults/token-estimate.ts +0 -2
  463. package/src/plugins/defaults/tool-error.ts +0 -7
  464. package/src/plugins/defaults/tool-execute.ts +0 -2
  465. package/src/plugins/defaults/tool-result-truncate.ts +0 -4
  466. package/src/plugins/ensure-plugin-api-shim.ts +96 -0
  467. package/src/plugins/external-api.ts +104 -0
  468. package/src/plugins/external-plugin-loader.ts +187 -42
  469. package/src/plugins/feature-gate.ts +22 -0
  470. package/src/plugins/pipeline.ts +37 -0
  471. package/src/plugins/registry.ts +48 -80
  472. package/src/plugins/types.ts +40 -26
  473. package/src/plugins/user-loader.ts +21 -2
  474. package/src/proactive-artifact/aux-message-injector.ts +11 -0
  475. package/src/proactive-artifact/job.test.ts +37 -5
  476. package/src/prompts/__tests__/system-prompt.test.ts +10 -43
  477. package/src/prompts/__tests__/task-progress-hint-section.test.ts +95 -0
  478. package/src/prompts/normalize-onboarding.ts +27 -0
  479. package/src/prompts/sections.ts +302 -0
  480. package/src/prompts/system-prompt.ts +63 -174
  481. package/src/prompts/templates/BOOTSTRAP.md +17 -1
  482. package/src/prompts/templates/system-sections.ts +164 -0
  483. package/src/providers/__tests__/inference.test.ts +24 -7
  484. package/src/providers/anthropic/client.ts +28 -28
  485. package/src/providers/call-site-routing.ts +24 -6
  486. package/src/providers/connection-resolution.ts +68 -11
  487. package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +74 -0
  488. package/src/providers/inference/__tests__/connections-openai-compatible.test.ts +175 -0
  489. package/src/providers/inference/__tests__/connections-status-label.test.ts +15 -0
  490. package/src/providers/inference/adapter-factory.ts +32 -6
  491. package/src/providers/inference/auth.ts +12 -0
  492. package/src/providers/inference/backfill.ts +14 -1
  493. package/src/providers/inference/connections.ts +159 -34
  494. package/src/providers/inference/resolve-auth.ts +14 -4
  495. package/src/providers/model-catalog.ts +249 -12
  496. package/src/providers/model-intents.ts +3 -3
  497. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +235 -0
  498. package/src/providers/openai/chat-completions-provider.ts +169 -8
  499. package/src/providers/openrouter/client.ts +49 -4
  500. package/src/providers/{managed-proxy → platform-proxy}/constants.ts +4 -2
  501. package/src/providers/{managed-proxy → platform-proxy}/context.ts +3 -3
  502. package/src/providers/provider-availability.ts +17 -2
  503. package/src/providers/provider-catalog-visibility.ts +38 -0
  504. package/src/providers/provider-send-message.ts +27 -12
  505. package/src/providers/registry.ts +52 -15
  506. package/src/providers/retry.ts +47 -1
  507. package/src/runtime/__tests__/agent-wake.test.ts +152 -0
  508. package/src/runtime/agent-wake.ts +103 -15
  509. package/src/runtime/auth/route-policy.ts +21 -1
  510. package/src/runtime/btw-sidechain.ts +2 -0
  511. package/src/runtime/http-server.ts +7 -16
  512. package/src/runtime/http-types.ts +19 -47
  513. package/src/runtime/migrations/origin-mode.ts +1 -1
  514. package/src/runtime/pending-interactions.ts +1 -0
  515. package/src/runtime/routes/__tests__/bookmark-routes.test.ts +17 -0
  516. package/src/runtime/routes/__tests__/consolidation-routes.test.ts +258 -0
  517. package/src/runtime/routes/__tests__/conversation-management-routes.test.ts +5 -1
  518. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +172 -23
  519. package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +275 -44
  520. package/src/runtime/routes/__tests__/llm-call-sites-routes.test.ts +12 -0
  521. package/src/runtime/routes/__tests__/question-routes.test.ts +395 -0
  522. package/src/runtime/routes/__tests__/tts-routes.test.ts +64 -1
  523. package/src/runtime/routes/acp-routes-list.test.ts +143 -0
  524. package/src/runtime/routes/acp-routes.ts +5 -3
  525. package/src/runtime/routes/auth-routes.ts +1 -1
  526. package/src/runtime/routes/bookmark-routes.ts +5 -3
  527. package/src/runtime/routes/btw-routes.ts +5 -1
  528. package/src/runtime/routes/channel-availability-routes.ts +126 -0
  529. package/src/runtime/routes/consolidation-routes.ts +100 -0
  530. package/src/runtime/routes/conversation-cli-routes.ts +44 -3
  531. package/src/runtime/routes/conversation-list-routes.ts +3 -20
  532. package/src/runtime/routes/conversation-management-routes.ts +17 -42
  533. package/src/runtime/routes/conversation-query-routes.ts +99 -35
  534. package/src/runtime/routes/conversation-routes.ts +97 -11
  535. package/src/runtime/routes/documents-routes.ts +25 -86
  536. package/src/runtime/routes/group-routes.ts +5 -0
  537. package/src/runtime/routes/inbound-conversation.ts +28 -8
  538. package/src/runtime/routes/inbound-message-handler.ts +236 -41
  539. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +111 -0
  540. package/src/runtime/routes/inbound-stages/background-dispatch.ts +32 -1
  541. package/src/runtime/routes/inbound-stages/edit-intercept.ts +17 -4
  542. package/src/runtime/routes/index.ts +8 -0
  543. package/src/runtime/routes/inference-profile-session-handler.ts +17 -44
  544. package/src/runtime/routes/inference-profile-session-reaper.ts +7 -21
  545. package/src/runtime/routes/inference-provider-connection-routes.ts +199 -22
  546. package/src/runtime/routes/integrations/a2a.ts +235 -0
  547. package/src/runtime/routes/integrations/slack/share.ts +4 -52
  548. package/src/runtime/routes/integrations/slack/token.ts +43 -0
  549. package/src/runtime/routes/integrations/twilio.ts +6 -13
  550. package/src/runtime/routes/llm-call-sites-routes.ts +11 -1
  551. package/src/runtime/routes/notification-routes.ts +1 -1
  552. package/src/runtime/routes/oauth-commands-routes.ts +105 -15
  553. package/src/runtime/routes/oauth-lifecycle-routes.ts +43 -0
  554. package/src/runtime/routes/question-routes.ts +259 -0
  555. package/src/runtime/routes/rename-conversation-routes.ts +2 -33
  556. package/src/runtime/routes/schedule-routes.ts +4 -7
  557. package/src/runtime/routes/subagents-routes.ts +98 -18
  558. package/src/runtime/routes/telemetry-routes.ts +27 -0
  559. package/src/runtime/routes/tts-routes.ts +27 -2
  560. package/src/runtime/routes/workspace-routes.test.ts +43 -0
  561. package/src/runtime/routes/workspace-routes.ts +28 -0
  562. package/src/runtime/services/conversation-serializer.ts +39 -7
  563. package/src/runtime/sync/resource-sync-events.ts +93 -1
  564. package/src/schedule/schedule-store.ts +27 -2
  565. package/src/schedule/scheduler.ts +9 -1
  566. package/src/security/__tests__/untrusted-content.test.ts +86 -0
  567. package/src/security/untrusted-content.ts +93 -8
  568. package/src/skills/catalog-files.ts +1 -1
  569. package/src/skills/catalog-install.ts +233 -116
  570. package/src/skills/clawhub.ts +70 -13
  571. package/src/skills/managed-store.ts +4 -119
  572. package/src/skills/skillssh-registry.ts +27 -48
  573. package/src/subagent/manager.ts +17 -7
  574. package/src/telemetry/types.ts +113 -1
  575. package/src/telemetry/usage-telemetry-reporter.test.ts +312 -5
  576. package/src/telemetry/usage-telemetry-reporter.ts +113 -7
  577. package/src/tools/apps/executors.ts +58 -7
  578. package/src/tools/ask-question/ask-question-tool.test.ts +509 -0
  579. package/src/tools/ask-question/ask-question-tool.ts +304 -0
  580. package/src/tools/browser/browser-execution.ts +15 -11
  581. package/src/tools/computer-use/definitions.ts +3 -3
  582. package/src/tools/credentials/vault.ts +1 -1
  583. package/src/tools/document/document-tool.ts +124 -1
  584. package/src/tools/filesystem/edit.ts +1 -1
  585. package/src/tools/filesystem/list.ts +1 -1
  586. package/src/tools/filesystem/read.ts +1 -1
  587. package/src/tools/filesystem/write.ts +5 -2
  588. package/src/tools/host-filesystem/transfer.ts +1 -1
  589. package/src/tools/host-terminal/host-shell.ts +1 -1
  590. package/src/tools/memory/register.ts +1 -9
  591. package/src/tools/permission-checker.ts +1 -1
  592. package/src/tools/registry.ts +17 -7
  593. package/src/tools/schedule/create.ts +2 -2
  594. package/src/tools/schema-transforms.ts +7 -2
  595. package/src/tools/side-effects.ts +1 -0
  596. package/src/tools/skills/delete-managed.ts +4 -4
  597. package/src/tools/skills/execute.ts +1 -1
  598. package/src/tools/skills/scaffold-managed.ts +3 -2
  599. package/src/tools/subagent/notify-parent.ts +1 -1
  600. package/src/tools/system/request-permission.ts +2 -2
  601. package/src/tools/terminal/safe-env.ts +60 -1
  602. package/src/tools/tool-manifest.ts +2 -0
  603. package/src/tools/types.ts +107 -21
  604. package/src/tools/ui-surface/definitions.ts +6 -5
  605. package/src/tts/__tests__/provider-adapters.test.ts +76 -2
  606. package/src/tts/providers/elevenlabs-provider.ts +75 -1
  607. package/src/types/onboarding-context.ts +2 -0
  608. package/src/util/errors.ts +17 -0
  609. package/src/util/platform.ts +10 -0
  610. package/src/watcher/__tests__/engine.test.ts +22 -0
  611. package/src/watcher/engine.ts +6 -2
  612. package/src/workspace/migrations/057-repair-stale-gemini-model-ids.ts +80 -15
  613. package/src/workspace/migrations/072-seed-reply-suggestion-callsite.ts +35 -22
  614. package/src/workspace/migrations/073-repair-recall-callsite-empty-profile.ts +3 -1
  615. package/src/workspace/migrations/083-system-prompt-prefix-to-file.ts +191 -0
  616. package/src/workspace/migrations/084-remove-legacy-skills-index.ts +276 -0
  617. package/src/workspace/migrations/085-memory-v2-bm25-b-reembed-disabled-v2-pages.ts +137 -0
  618. package/src/workspace/migrations/086-revert-stale-gemini-mis-rewrites.ts +198 -0
  619. package/src/workspace/migrations/087-memory-router-balanced-profile.ts +91 -0
  620. package/src/workspace/migrations/registry.ts +10 -0
  621. package/src/workspace/migrations/runner.ts +39 -9
  622. package/src/workspace/migrations/types.ts +4 -0
  623. package/examples/plugins/echo/bun.lock +0 -25
  624. package/src/__tests__/context-window-manager.test.ts +0 -2481
  625. package/src/__tests__/guardian-action-conversation-turn.test.ts +0 -441
  626. package/src/context/__tests__/compact-prompt.test.ts +0 -63
  627. package/src/context/prompts/compact.md +0 -26
  628. package/src/memory/graph/__tests__/remember-description.test.ts +0 -55
  629. package/src/prompts/__tests__/build-cli-reference-section.test.ts +0 -37
  630. package/src/runtime/guardian-action-conversation-turn.ts +0 -99
@@ -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
  }
@@ -2,6 +2,8 @@ import { describe, expect, test } from "bun:test";
2
2
 
3
3
  import {
4
4
  escapeContentBoundaries,
5
+ parseExternalContentEnvelope,
6
+ unwrapExternalContentForDisplay,
5
7
  wrapUntrustedContent,
6
8
  } from "../untrusted-content.js";
7
9
 
@@ -107,3 +109,87 @@ describe("escapeContentBoundaries", () => {
107
109
  expect(escapeContentBoundaries(safe)).toBe(safe);
108
110
  });
109
111
  });
112
+
113
+ describe("parseExternalContentEnvelope", () => {
114
+ test("parses a complete envelope with source and content", () => {
115
+ expect(
116
+ parseExternalContentEnvelope(
117
+ '<external_content source="slack">\nhello world\n</external_content>',
118
+ ),
119
+ ).toEqual({
120
+ source: "slack",
121
+ content: "hello world",
122
+ });
123
+ });
124
+
125
+ test("parses optional origin and multiline content from wrapped output", () => {
126
+ const wrapped = wrapUntrustedContent("line one\nline two", {
127
+ source: "slack",
128
+ sourceDetail: "channel-123",
129
+ });
130
+
131
+ expect(parseExternalContentEnvelope(wrapped)).toEqual({
132
+ source: "slack",
133
+ origin: "channel-123",
134
+ content: "line one\nline two",
135
+ });
136
+ });
137
+
138
+ test("returns null for malformed wrappers", () => {
139
+ expect(
140
+ parseExternalContentEnvelope(
141
+ '<external_content source="slack">body\n</external_content>',
142
+ ),
143
+ ).toBeNull();
144
+ expect(
145
+ parseExternalContentEnvelope(
146
+ '<external_content source="slack">\nbody</external_content>',
147
+ ),
148
+ ).toBeNull();
149
+ expect(
150
+ parseExternalContentEnvelope(
151
+ '<external_content source="slack" extra="ignored">\nbody\n</external_content>',
152
+ ),
153
+ ).toBeNull();
154
+ expect(
155
+ parseExternalContentEnvelope(
156
+ '<external_content source="slack">\nbody\n</external_content>\n</external_content>',
157
+ ),
158
+ ).toBeNull();
159
+ });
160
+
161
+ test("returns null for mixed prefix or suffix content", () => {
162
+ const envelope =
163
+ '<external_content source="email">\nbody\n</external_content>';
164
+
165
+ expect(parseExternalContentEnvelope(`prefix ${envelope}`)).toBeNull();
166
+ expect(parseExternalContentEnvelope(`${envelope} suffix`)).toBeNull();
167
+ });
168
+
169
+ test("returns null for unknown sources", () => {
170
+ expect(
171
+ parseExternalContentEnvelope(
172
+ '<external_content source="chat">\nbody\n</external_content>',
173
+ ),
174
+ ).toBeNull();
175
+ });
176
+ });
177
+
178
+ describe("unwrapExternalContentForDisplay", () => {
179
+ test("returns only the inner body for a complete envelope", () => {
180
+ expect(
181
+ unwrapExternalContentForDisplay(
182
+ '<external_content source="slack">\nvisible text\n</external_content>',
183
+ ),
184
+ ).toBe("visible text");
185
+ });
186
+
187
+ test("leaves partial or malformed external content unchanged", () => {
188
+ const partial = '<external_content source="slack">\nvisible text';
189
+ const malformed =
190
+ '<external_content source="slack">visible text</external_content>';
191
+
192
+ expect(unwrapExternalContentForDisplay(partial)).toBe(partial);
193
+ expect(unwrapExternalContentForDisplay(malformed)).toBe(malformed);
194
+ });
195
+ });
@@ -15,14 +15,23 @@
15
15
  // Types
16
16
  // ---------------------------------------------------------------------------
17
17
 
18
- export type UntrustedContentSource =
19
- | "email"
20
- | "slack"
21
- | "web"
22
- | "calendar"
23
- | "webhook"
24
- | "search"
25
- | "tool_result";
18
+ const UNTRUSTED_CONTENT_SOURCES = [
19
+ "email",
20
+ "slack",
21
+ "web",
22
+ "calendar",
23
+ "webhook",
24
+ "search",
25
+ "tool_result",
26
+ ] as const;
27
+
28
+ export type UntrustedContentSource = (typeof UNTRUSTED_CONTENT_SOURCES)[number];
29
+
30
+ export interface ExternalContentEnvelope {
31
+ source: UntrustedContentSource;
32
+ origin?: string;
33
+ content: string;
34
+ }
26
35
 
27
36
  export interface WrapOptions {
28
37
  /** Which external source produced this content. */
@@ -47,6 +56,14 @@ const DEFAULT_BUDGETS: Record<UntrustedContentSource, number> = {
47
56
  tool_result: 20_000,
48
57
  };
49
58
 
59
+ const UNTRUSTED_CONTENT_SOURCE_SET = new Set<string>(UNTRUSTED_CONTENT_SOURCES);
60
+
61
+ const EXTERNAL_CONTENT_ENVELOPE_PATTERN =
62
+ /^<external_content\s+([^\r\n<>]*)>\n([\s\S]*)\n<\/external_content>$/;
63
+
64
+ const EXTERNAL_CONTENT_ATTRIBUTE_PATTERN =
65
+ /(?:^|\s+)(source|origin)="([^"\r\n]*)"/g;
66
+
50
67
  // ---------------------------------------------------------------------------
51
68
  // Core API
52
69
  // ---------------------------------------------------------------------------
@@ -70,6 +87,31 @@ export function wrapUntrustedContent(
70
87
  return `<external_content source="${options.source}"${detail}>\n${truncated}\n</external_content>`;
71
88
  }
72
89
 
90
+ export function parseExternalContentEnvelope(
91
+ value: string,
92
+ ): ExternalContentEnvelope | null {
93
+ const match = EXTERNAL_CONTENT_ENVELOPE_PATTERN.exec(value);
94
+ if (!match || match[0] !== value) {
95
+ return null;
96
+ }
97
+
98
+ const attributes = parseExternalContentAttributes(match[1]);
99
+ if (!attributes) {
100
+ return null;
101
+ }
102
+
103
+ const content = match[2];
104
+ if (/<\/external_content/gi.test(content)) {
105
+ return null;
106
+ }
107
+
108
+ return { ...attributes, content };
109
+ }
110
+
111
+ export function unwrapExternalContentForDisplay(value: string): string {
112
+ return parseExternalContentEnvelope(value)?.content ?? value;
113
+ }
114
+
73
115
  /**
74
116
  * Escape sequences that could break out of the `<external_content>` wrapper.
75
117
  * Case-insensitive to cover mixed-case evasion attempts.
@@ -90,6 +132,49 @@ function sanitizeAttr(value: string): string {
90
132
  return value.replace(/[<>"&\r\n]/g, "").slice(0, 200);
91
133
  }
92
134
 
135
+ function parseExternalContentAttributes(
136
+ attributes: string,
137
+ ): Pick<ExternalContentEnvelope, "source" | "origin"> | null {
138
+ let source: UntrustedContentSource | undefined;
139
+ let origin: string | undefined;
140
+ let originSeen = false;
141
+ let cursor = 0;
142
+
143
+ EXTERNAL_CONTENT_ATTRIBUTE_PATTERN.lastIndex = 0;
144
+ for (const match of attributes.matchAll(EXTERNAL_CONTENT_ATTRIBUTE_PATTERN)) {
145
+ if (match.index !== cursor) {
146
+ return null;
147
+ }
148
+
149
+ const [, name, value] = match;
150
+ if (name === "source") {
151
+ if (source || !isUntrustedContentSource(value)) {
152
+ return null;
153
+ }
154
+ source = value;
155
+ } else {
156
+ if (originSeen) {
157
+ return null;
158
+ }
159
+ originSeen = true;
160
+ origin = value;
161
+ }
162
+ cursor = match.index + match[0].length;
163
+ }
164
+
165
+ if (cursor !== attributes.length || !source) {
166
+ return null;
167
+ }
168
+
169
+ return originSeen ? { source, origin } : { source };
170
+ }
171
+
172
+ function isUntrustedContentSource(
173
+ value: string,
174
+ ): value is UntrustedContentSource {
175
+ return UNTRUSTED_CONTENT_SOURCE_SET.has(value);
176
+ }
177
+
93
178
  /** Truncate content to a character budget, appending a notice if truncated. */
94
179
  function truncateWithNotice(content: string, maxChars: number): string {
95
180
  if (content.length <= maxChars) {
@@ -501,7 +501,7 @@ export function catalogSkillToSlim(cs: CatalogSkill): SlimSkillResponse {
501
501
  id: cs.id,
502
502
  name: cs.metadata?.vellum?.["display-name"] ?? cs.name,
503
503
  description: cs.description,
504
- emoji: cs.emoji,
504
+ emoji: cs.emoji ?? cs.metadata?.emoji,
505
505
  kind: "catalog",
506
506
  origin: "vellum",
507
507
  status: "available",