@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
@@ -9,6 +9,7 @@ import { join } from "node:path";
9
9
  import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
10
10
 
11
11
  const TEST_DIR = process.env.VELLUM_WORKSPACE_DIR!;
12
+ const mockRefreshSkillCapabilityMemories = mock(() => {});
12
13
 
13
14
  mock.module("../util/logger.js", () => ({
14
15
  getLogger: () =>
@@ -17,6 +18,10 @@ mock.module("../util/logger.js", () => ({
17
18
  }),
18
19
  }));
19
20
 
21
+ mock.module("../daemon/skill-memory-refresh.js", () => ({
22
+ refreshSkillCapabilityMemories: mockRefreshSkillCapabilityMemories,
23
+ }));
24
+
20
25
  import { executeDeleteManagedSkill } from "../tools/skills/delete-managed.js";
21
26
  import type { ToolContext } from "../tools/types.js";
22
27
 
@@ -35,16 +40,11 @@ function createSkill(id: string): void {
35
40
  join(skillDir, "SKILL.md"),
36
41
  '---\nname: "Test"\ndescription: "Test"\n---\n\nBody.\n',
37
42
  );
38
- // Update SKILLS.md
39
- const indexPath = join(TEST_DIR, "skills", "SKILLS.md");
40
- const existing = existsSync(indexPath)
41
- ? readFileSync(indexPath, "utf-8")
42
- : "";
43
- writeFileSync(indexPath, existing + `- ${id}\n`);
44
43
  }
45
44
 
46
45
  beforeEach(() => {
47
46
  mkdirSync(join(TEST_DIR, "skills"), { recursive: true });
47
+ mockRefreshSkillCapabilityMemories.mockClear();
48
48
  });
49
49
 
50
50
  afterEach(() => {
@@ -52,9 +52,34 @@ afterEach(() => {
52
52
  });
53
53
 
54
54
  describe("delete_managed_skill tool", () => {
55
- test("deletes existing skill and updates index", async () => {
55
+ test("keeps legacy index control as a deprecated no-op schema field", () => {
56
+ const tools = JSON.parse(
57
+ readFileSync(
58
+ join(
59
+ import.meta.dirname,
60
+ "../config/bundled-skills/skill-management/TOOLS.json",
61
+ ),
62
+ "utf-8",
63
+ ),
64
+ );
65
+ const deleteTool = tools.tools.find(
66
+ (tool: { name: string }) => tool.name === "delete_managed_skill",
67
+ );
68
+
69
+ expect(deleteTool).toBeDefined();
70
+ expect(deleteTool.input_schema.properties.remove_from_index).toEqual({
71
+ type: "boolean",
72
+ description:
73
+ "Deprecated no-op compatibility field. Skill deletion does not edit SKILLS.md.",
74
+ });
75
+ });
76
+
77
+ test("deletes existing skill without modifying the legacy index", async () => {
56
78
  createSkill("doomed");
57
79
  createSkill("survivor");
80
+ const indexPath = join(TEST_DIR, "skills", "SKILLS.md");
81
+ const staleIndex = "- doomed\n- survivor\n";
82
+ writeFileSync(indexPath, staleIndex);
58
83
 
59
84
  const result = await executeDeleteManagedSkill(
60
85
  {
@@ -67,16 +92,32 @@ describe("delete_managed_skill tool", () => {
67
92
  const parsed = JSON.parse(result.content);
68
93
  expect(parsed.deleted).toBe(true);
69
94
  expect(parsed.skill_id).toBe("doomed");
70
- expect(parsed.index_updated).toBe(true);
95
+ expect(parsed).not.toHaveProperty("index_updated");
71
96
 
72
97
  expect(existsSync(join(TEST_DIR, "skills", "doomed"))).toBe(false);
73
98
 
74
- const indexContent = readFileSync(
75
- join(TEST_DIR, "skills", "SKILLS.md"),
76
- "utf-8",
99
+ expect(existsSync(indexPath)).toBe(true);
100
+ expect(readFileSync(indexPath, "utf-8")).toBe(staleIndex);
101
+ expect(mockRefreshSkillCapabilityMemories).toHaveBeenCalledTimes(1);
102
+ });
103
+
104
+ test("accepts legacy remove_from_index input without returning index metadata", async () => {
105
+ createSkill("legacy-delete");
106
+
107
+ const result = await executeDeleteManagedSkill(
108
+ {
109
+ skill_id: "legacy-delete",
110
+ remove_from_index: true,
111
+ },
112
+ makeContext(),
77
113
  );
78
- expect(indexContent).not.toContain("doomed");
79
- expect(indexContent).toContain("survivor");
114
+
115
+ expect(result.isError).toBe(false);
116
+ const parsed = JSON.parse(result.content);
117
+ expect(parsed.deleted).toBe(true);
118
+ expect(parsed).not.toHaveProperty("index_updated");
119
+ expect(existsSync(join(TEST_DIR, "skills", "legacy-delete"))).toBe(false);
120
+ expect(mockRefreshSkillCapabilityMemories).toHaveBeenCalledTimes(1);
80
121
  });
81
122
 
82
123
  test("returns error for non-existent skill", async () => {
@@ -89,6 +130,7 @@ describe("delete_managed_skill tool", () => {
89
130
 
90
131
  expect(result.isError).toBe(true);
91
132
  expect(result.content).toContain("not found");
133
+ expect(mockRefreshSkillCapabilityMemories).not.toHaveBeenCalled();
92
134
  });
93
135
 
94
136
  test("rejects missing skill_id", async () => {
@@ -25,6 +25,7 @@ const mockConfig = {
25
25
  },
26
26
  },
27
27
  permissions: { mode: "workspace" as const },
28
+ tools: { exclude: [] },
28
29
  };
29
30
 
30
31
  mock.module("../config/loader.js", () => ({
@@ -63,7 +63,10 @@ import { upsertContactChannel } from "../contacts/contacts-write.js";
63
63
  import { getDb } from "../memory/db-connection.js";
64
64
  import { initializeDb } from "../memory/db-init.js";
65
65
  import { messages } from "../memory/schema/conversations.js";
66
- import { readSlackMetadata } from "../messaging/providers/slack/message-metadata.js";
66
+ import {
67
+ readSlackMetadata,
68
+ type SlackMessageMetadata,
69
+ } from "../messaging/providers/slack/message-metadata.js";
67
70
  import { handleChannelInbound } from "./helpers/channel-test-adapter.js";
68
71
 
69
72
  initializeDb();
@@ -98,6 +101,18 @@ function seedActiveMember(): void {
98
101
  });
99
102
  }
100
103
 
104
+ function seedSlackGuardian(): void {
105
+ upsertContactChannel({
106
+ sourceChannel: "slack",
107
+ externalUserId: SLACK_DM_USER_ID,
108
+ externalChatId: SLACK_DM_CHANNEL_ID,
109
+ status: "active",
110
+ policy: "allow",
111
+ displayName: SLACK_DM_DISPLAY_NAME,
112
+ role: "guardian",
113
+ });
114
+ }
115
+
101
116
  let msgCounter = 0;
102
117
 
103
118
  function buildDmRequest(
@@ -136,7 +151,12 @@ function buildDmRequest(
136
151
  function readPersistedSlackRows(): Array<{
137
152
  role: string;
138
153
  content: string;
139
- metadata: string | null;
154
+ rawContent: string;
155
+ slackMeta: SlackMessageMetadata | null;
156
+ provenanceTrustClass: string | undefined;
157
+ provenanceSourceChannel: string | undefined;
158
+ provenanceGuardianExternalUserId: string | undefined;
159
+ provenanceRequesterIdentifier: string | undefined;
140
160
  }> {
141
161
  const db = getDb();
142
162
  return db
@@ -146,7 +166,58 @@ function readPersistedSlackRows(): Array<{
146
166
  metadata: messages.metadata,
147
167
  })
148
168
  .from(messages)
149
- .all();
169
+ .all()
170
+ .map((row) => {
171
+ let envelope: Record<string, unknown> = {};
172
+ if (row.metadata) {
173
+ try {
174
+ const parsed = JSON.parse(row.metadata) as unknown;
175
+ if (
176
+ parsed !== null &&
177
+ typeof parsed === "object" &&
178
+ !Array.isArray(parsed)
179
+ ) {
180
+ envelope = parsed as Record<string, unknown>;
181
+ }
182
+ } catch {
183
+ envelope = {};
184
+ }
185
+ }
186
+ const slackMeta =
187
+ typeof envelope.slackMeta === "string"
188
+ ? readSlackMetadata(envelope.slackMeta)
189
+ : null;
190
+ return {
191
+ role: row.role,
192
+ content: unwrapExternalContent(row.content),
193
+ rawContent: row.content,
194
+ slackMeta,
195
+ provenanceTrustClass:
196
+ typeof envelope.provenanceTrustClass === "string"
197
+ ? envelope.provenanceTrustClass
198
+ : undefined,
199
+ provenanceSourceChannel:
200
+ typeof envelope.provenanceSourceChannel === "string"
201
+ ? envelope.provenanceSourceChannel
202
+ : undefined,
203
+ provenanceGuardianExternalUserId:
204
+ typeof envelope.provenanceGuardianExternalUserId === "string"
205
+ ? envelope.provenanceGuardianExternalUserId
206
+ : undefined,
207
+ provenanceRequesterIdentifier:
208
+ typeof envelope.provenanceRequesterIdentifier === "string"
209
+ ? envelope.provenanceRequesterIdentifier
210
+ : undefined,
211
+ };
212
+ });
213
+ }
214
+
215
+ const EXTERNAL_CONTENT_WRAPPER =
216
+ /^<external_content[^>]*>\n([\s\S]*?)\n<\/external_content>$/;
217
+
218
+ function unwrapExternalContent(content: string): string {
219
+ const match = content.match(EXTERNAL_CONTENT_WRAPPER);
220
+ return match ? match[1] : content;
150
221
  }
151
222
 
152
223
  function makeBackfilledMessage(overrides: Partial<Message> = {}): Message {
@@ -228,12 +299,15 @@ describe("PR 23 — Slack DM cold-start backfill", () => {
228
299
  expect(rows.length).toBe(3);
229
300
 
230
301
  const persistedTs = rows.map((r) => {
231
- const envelope = JSON.parse(r.metadata!) as Record<string, unknown>;
232
- const meta = readSlackMetadata(envelope.slackMeta as string);
302
+ const meta = r.slackMeta;
233
303
  expect(meta).not.toBeNull();
234
304
  expect(meta!.source).toBe("slack");
235
305
  expect(meta!.eventKind).toBe("message");
236
306
  expect(meta!.channelId).toBe(SLACK_DM_CHANNEL_ID);
307
+ expect(meta!.actorExternalUserId).toBe(SLACK_DM_USER_ID);
308
+ expect(r.provenanceTrustClass).toBe("unknown");
309
+ expect(r.provenanceSourceChannel).toBe("slack");
310
+ expect(r.provenanceRequesterIdentifier).toBe(SLACK_DM_USER_ID);
237
311
  return meta!.channelTs;
238
312
  });
239
313
  expect(new Set(persistedTs)).toEqual(
@@ -246,6 +320,36 @@ describe("PR 23 — Slack DM cold-start backfill", () => {
246
320
  expect(texts).toEqual(["older A", "older B", "older C"]);
247
321
  });
248
322
 
323
+ test("guardian DM backfill persists guardian text without external_content wrapping", async () => {
324
+ resetState();
325
+ seedSlackGuardian();
326
+
327
+ backfillDmMock.mockImplementation(async () => [
328
+ makeBackfilledMessage({
329
+ id: "1700000000.000001",
330
+ text: "trusted older context",
331
+ sender: { id: SLACK_DM_USER_ID, name: "Guardian Sender" },
332
+ }),
333
+ ]);
334
+
335
+ await handleChannelInbound(
336
+ buildDmRequest("live guardian DM"),
337
+ noopProcessMessage,
338
+ TEST_BEARER_TOKEN,
339
+ );
340
+
341
+ const [row] = readPersistedSlackRows();
342
+ expect(row).toBeDefined();
343
+ expect(row.role).toBe("user");
344
+ expect(row.rawContent).toBe("trusted older context");
345
+ expect(row.rawContent).not.toContain("<external_content");
346
+ expect(row.slackMeta?.actorExternalUserId).toBe(SLACK_DM_USER_ID);
347
+ expect(row.provenanceTrustClass).toBe("guardian");
348
+ expect(row.provenanceSourceChannel).toBe("slack");
349
+ expect(row.provenanceGuardianExternalUserId).toBe(SLACK_DM_USER_ID);
350
+ expect(row.provenanceRequesterIdentifier).toBe(SLACK_DM_USER_ID);
351
+ });
352
+
249
353
  test("warm storage prevents re-trigger on subsequent DMs", async () => {
250
354
  backfillDmMock.mockImplementation(async () => [
251
355
  makeBackfilledMessage({ id: "1700000000.000001", text: "older A" }),
@@ -352,10 +456,10 @@ describe("PR 23 — Slack DM cold-start backfill", () => {
352
456
  expect(texts).toEqual(["older A", "older B"]);
353
457
  });
354
458
 
355
- test("bot-authored backfilled messages are persisted with role=assistant", async () => {
356
- // Slack DM history includes our own prior bot replies. If those rows
357
- // were rehydrated as `user` turns, the assistant would later treat its
358
- // own output as new user input and speaker attribution would break.
459
+ test("bot-authored backfilled messages are persisted raw as user history", async () => {
460
+ // Backfilled Slack history is third-party channel replay. Even bot rows
461
+ // must not become `assistant` messages; that role is reserved for outputs
462
+ // produced by the local assistant loop.
359
463
  backfillDmMock.mockImplementation(async () => [
360
464
  makeBackfilledMessage({
361
465
  id: "1700000000.000001",
@@ -380,7 +484,14 @@ describe("PR 23 — Slack DM cold-start backfill", () => {
380
484
  expect(rows.length).toBe(2);
381
485
  const byText = new Map(rows.map((r) => [r.content, r.role]));
382
486
  expect(byText.get("user reply")).toBe("user");
383
- expect(byText.get("assistant reply")).toBe("assistant");
487
+ expect(byText.get("assistant reply")).toBe("user");
488
+ const botRow = rows.find((r) => r.content === "assistant reply");
489
+ expect(botRow?.rawContent).toBe("assistant reply");
490
+ expect(botRow?.rawContent).not.toContain("<external_content");
491
+ expect(botRow?.slackMeta?.actorExternalUserId).toBe("B_BOT");
492
+ expect(botRow?.provenanceTrustClass).toBe("unknown");
493
+ expect(botRow?.provenanceSourceChannel).toBe("slack");
494
+ expect(botRow?.provenanceRequesterIdentifier).toBe("B_BOT");
384
495
  });
385
496
 
386
497
  test("backfill skips channelTs values already stored", async () => {
@@ -0,0 +1,258 @@
1
+ import { beforeEach, describe, expect, test } from "bun:test";
2
+
3
+ import { getDocumentById } from "../documents/document-store.js";
4
+ import { getSqlite, resetDb } from "../memory/db-connection.js";
5
+ import {
6
+ executeDocumentDelete,
7
+ executeDocumentList,
8
+ executeDocumentRead,
9
+ executeDocumentUpdate,
10
+ } from "../tools/document/document-tool.js";
11
+ import type { ToolContext, ToolExecutionResult } from "../tools/types.js";
12
+
13
+ function makeContext(overrides: Partial<ToolContext> = {}): ToolContext {
14
+ return {
15
+ workingDir: "/tmp/project",
16
+ conversationId: "conv-current",
17
+ trustClass: "trusted_contact",
18
+ executionChannel: "slack",
19
+ ...overrides,
20
+ };
21
+ }
22
+
23
+ function parseResult<T>(result: ToolExecutionResult): T {
24
+ return JSON.parse(result.content) as T;
25
+ }
26
+
27
+ function bootstrapDocumentTables(): void {
28
+ resetDb();
29
+ const raw = getSqlite();
30
+ raw.exec(/*sql*/ `
31
+ DROP TABLE IF EXISTS document_conversations;
32
+ DROP TABLE IF EXISTS documents;
33
+ DROP TABLE IF EXISTS conversations;
34
+
35
+ CREATE TABLE conversations (
36
+ id TEXT PRIMARY KEY,
37
+ created_at INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)
38
+ );
39
+
40
+ CREATE TABLE documents (
41
+ surface_id TEXT PRIMARY KEY,
42
+ conversation_id TEXT NOT NULL REFERENCES conversations(id) ON DELETE CASCADE,
43
+ title TEXT NOT NULL,
44
+ content TEXT NOT NULL,
45
+ word_count INTEGER NOT NULL DEFAULT 0,
46
+ created_at INTEGER NOT NULL,
47
+ updated_at INTEGER NOT NULL
48
+ );
49
+
50
+ CREATE TABLE document_conversations (
51
+ surface_id TEXT NOT NULL,
52
+ conversation_id TEXT NOT NULL,
53
+ created_at INTEGER NOT NULL,
54
+ PRIMARY KEY (surface_id, conversation_id),
55
+ FOREIGN KEY (surface_id) REFERENCES documents(surface_id) ON DELETE CASCADE
56
+ );
57
+ `);
58
+ }
59
+
60
+ function seedDocument(params: {
61
+ surfaceId: string;
62
+ conversationId: string;
63
+ title: string;
64
+ content: string;
65
+ updatedAt: number;
66
+ }): void {
67
+ const raw = getSqlite();
68
+ raw
69
+ .query(`INSERT OR IGNORE INTO conversations (id, created_at) VALUES (?, ?)`)
70
+ .run(params.conversationId, params.updatedAt);
71
+ raw
72
+ .query(
73
+ `INSERT INTO documents (surface_id, conversation_id, title, content, word_count, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?)`,
74
+ )
75
+ .run(
76
+ params.surfaceId,
77
+ params.conversationId,
78
+ params.title,
79
+ params.content,
80
+ params.content.split(/\s+/).filter(Boolean).length,
81
+ params.updatedAt,
82
+ params.updatedAt,
83
+ );
84
+ raw
85
+ .query(
86
+ `INSERT OR IGNORE INTO document_conversations (surface_id, conversation_id, created_at) VALUES (?, ?, ?)`,
87
+ )
88
+ .run(params.surfaceId, params.conversationId, params.updatedAt);
89
+ }
90
+
91
+ function seedFixtureDocuments(): void {
92
+ seedDocument({
93
+ surfaceId: "doc-current",
94
+ conversationId: "conv-current",
95
+ title: "Current Business Plan",
96
+ content: "current plan",
97
+ updatedAt: 1000,
98
+ });
99
+ seedDocument({
100
+ surfaceId: "doc-other",
101
+ conversationId: "conv-other",
102
+ title: "Other Business Plan",
103
+ content: "other plan",
104
+ updatedAt: 2000,
105
+ });
106
+ seedDocument({
107
+ surfaceId: "doc-percent",
108
+ conversationId: "conv-other",
109
+ title: "100% Plan",
110
+ content: "literal percent",
111
+ updatedAt: 3000,
112
+ });
113
+ }
114
+
115
+ describe("document tool security", () => {
116
+ beforeEach(() => {
117
+ bootstrapDocumentTables();
118
+ seedFixtureDocuments();
119
+ });
120
+
121
+ test("scopes title search to the current conversation for non-guardian remote actors", () => {
122
+ const result = executeDocumentList(
123
+ { query: "Business Plan" },
124
+ makeContext({ trustClass: "trusted_contact", executionChannel: "slack" }),
125
+ );
126
+
127
+ const body = parseResult<{ documents: Array<{ surface_id: string }> }>(
128
+ result,
129
+ );
130
+ expect(body.documents.map((doc) => doc.surface_id)).toEqual([
131
+ "doc-current",
132
+ ]);
133
+ });
134
+
135
+ test("does not treat SQL LIKE wildcards as title-search wildcards", () => {
136
+ const result = executeDocumentList(
137
+ { query: "%" },
138
+ makeContext({ trustClass: "guardian", executionChannel: "telegram" }),
139
+ );
140
+
141
+ const body = parseResult<{ documents: Array<{ surface_id: string }> }>(
142
+ result,
143
+ );
144
+ expect(body.documents.map((doc) => doc.surface_id)).toEqual([
145
+ "doc-percent",
146
+ ]);
147
+ });
148
+
149
+ test("allows guardian and local actors to use documents from previous conversations", () => {
150
+ const guardianContext = makeContext({
151
+ trustClass: "guardian",
152
+ executionChannel: "telegram",
153
+ sendToClient: () => {},
154
+ });
155
+ const guardianList = executeDocumentList(
156
+ { query: "Other Business" },
157
+ guardianContext,
158
+ );
159
+ const guardianBody = parseResult<{
160
+ documents: Array<{ surface_id: string }>;
161
+ }>(guardianList);
162
+ expect(guardianBody.documents.map((doc) => doc.surface_id)).toEqual([
163
+ "doc-other",
164
+ ]);
165
+
166
+ const localRead = executeDocumentRead(
167
+ { surface_id: "doc-other" },
168
+ makeContext({ trustClass: "unknown", executionChannel: "vellum" }),
169
+ );
170
+ const localBody = parseResult<{
171
+ success: boolean;
172
+ surface_id: string;
173
+ content: string;
174
+ }>(localRead);
175
+ expect(localBody).toMatchObject({
176
+ success: true,
177
+ surface_id: "doc-other",
178
+ content: "other plan",
179
+ });
180
+
181
+ const guardianUpdate = executeDocumentUpdate(
182
+ { surface_id: "doc-other", content: "guardian edit", mode: "replace" },
183
+ guardianContext,
184
+ );
185
+ expect(guardianUpdate.isError).toBe(false);
186
+ expect(getDocumentById("doc-other")?.content).toBe("guardian edit");
187
+
188
+ const guardianDelete = executeDocumentDelete(
189
+ { surface_id: "doc-other" },
190
+ guardianContext,
191
+ );
192
+ expect(guardianDelete.isError).toBe(false);
193
+ expect(getDocumentById("doc-other")).toBeNull();
194
+ });
195
+
196
+ test("blocks cross-conversation read, update, and delete for non-guardian remote actors", () => {
197
+ const remoteContext = makeContext({
198
+ trustClass: "trusted_contact",
199
+ executionChannel: "slack",
200
+ sendToClient: () => {},
201
+ });
202
+
203
+ const read = executeDocumentRead(
204
+ { surface_id: "doc-other" },
205
+ remoteContext,
206
+ );
207
+ expect(read.isError).toBe(true);
208
+ expect(parseResult<{ error: string }>(read).error).toBe(
209
+ "Document not found",
210
+ );
211
+
212
+ const update = executeDocumentUpdate(
213
+ {
214
+ surface_id: "doc-other",
215
+ content: "updated by another conversation",
216
+ mode: "replace",
217
+ },
218
+ remoteContext,
219
+ );
220
+ expect(update.isError).toBe(true);
221
+ expect(getDocumentById("doc-other")?.content).toBe("other plan");
222
+
223
+ const deleted = executeDocumentDelete(
224
+ { surface_id: "doc-other" },
225
+ remoteContext,
226
+ );
227
+ expect(deleted.isError).toBe(true);
228
+ expect(getDocumentById("doc-other")).not.toBeNull();
229
+ });
230
+
231
+ test("keeps current-conversation documents editable and deletable", () => {
232
+ const remoteContext = makeContext({
233
+ trustClass: "trusted_contact",
234
+ executionChannel: "slack",
235
+ sendToClient: () => {},
236
+ });
237
+
238
+ const read = executeDocumentRead(
239
+ { surface_id: "doc-current" },
240
+ remoteContext,
241
+ );
242
+ expect(read.isError).toBe(false);
243
+
244
+ const update = executeDocumentUpdate(
245
+ { surface_id: "doc-current", content: "revised plan", mode: "replace" },
246
+ remoteContext,
247
+ );
248
+ expect(update.isError).toBe(false);
249
+ expect(getDocumentById("doc-current")?.content).toBe("revised plan");
250
+
251
+ const deleted = executeDocumentDelete(
252
+ { surface_id: "doc-current" },
253
+ remoteContext,
254
+ );
255
+ expect(deleted.isError).toBe(false);
256
+ expect(getDocumentById("doc-current")).toBeNull();
257
+ });
258
+ });
@@ -74,7 +74,6 @@ describe("Dynamic Skill Authoring Workflow moved to tool descriptions", () => {
74
74
  join(skillsDir, "test-skill", "SKILL.md"),
75
75
  '---\nname: "Test Skill"\ndescription: "For testing."\n---\n\nDo testing.\n',
76
76
  );
77
- writeFileSync(join(skillsDir, "SKILLS.md"), "- test-skill\n");
78
77
  writeFileSync(join(TEST_DIR, "IDENTITY.md"), "I am Vellum.");
79
78
 
80
79
  const result = buildSystemPrompt();
@@ -19,6 +19,7 @@ mock.module("../util/logger.js", () => ({
19
19
  }));
20
20
 
21
21
  import { addMessage } from "../memory/conversation-crud.js";
22
+ import { getConversationByKey } from "../memory/conversation-key-store.js";
22
23
  import { getDb } from "../memory/db-connection.js";
23
24
  import { initializeDb } from "../memory/db-init.js";
24
25
  import { linkMessage, recordInbound } from "../memory/delivery-crud.js";
@@ -160,6 +161,38 @@ describe("Slack edit propagation", () => {
160
161
  expect(slackMeta!.editedAt!).toBeGreaterThanOrEqual(t0);
161
162
  });
162
163
 
164
+ test("threaded Slack edits use the threaded conversation key and preserve thread metadata", async () => {
165
+ const conversationExternalId = "C0123CHANNEL";
166
+ const threadTs = "1234.0000";
167
+ const seeded = await seedSlackMessage({
168
+ conversationExternalId,
169
+ channelTs: "1234.5678",
170
+ initialContent: "original text",
171
+ });
172
+
173
+ const resp = await handleEditIntercept({
174
+ sourceChannel: "slack",
175
+ conversationExternalId,
176
+ externalMessageId: nextEditEventId(),
177
+ sourceMessageId: seeded.channelTs,
178
+ sourceThreadId: threadTs,
179
+ canonicalAssistantId: "self",
180
+ assistantId: "self",
181
+ content: "new text",
182
+ });
183
+
184
+ expect((resp as Record<string, unknown>).accepted).toBe(true);
185
+ const threadedKey = `asst:self:slack:${conversationExternalId}:thread:${threadTs}`;
186
+ const editConversation = getConversationByKey(threadedKey);
187
+ expect(editConversation).not.toBeNull();
188
+ expect(editConversation!.conversationId).not.toBe(seeded.conversationId);
189
+
190
+ const after = readMessageRow(seeded.messageId);
191
+ const outer = JSON.parse(after.metadata!) as Record<string, unknown>;
192
+ const slackMeta = readSlackMetadata(outer.slackMeta as string);
193
+ expect(slackMeta?.threadTs).toBe(threadTs);
194
+ });
195
+
163
196
  test("is idempotent across successive edits", async () => {
164
197
  const seeded = await seedSlackMessage({
165
198
  conversationExternalId: "C0123CHANNEL",
@@ -192,7 +192,6 @@ describe("emptyResponse pipeline — custom middleware overrides", () => {
192
192
  manifest: {
193
193
  name: "force-accept",
194
194
  version: "1.0.0",
195
- requires: { pluginRuntime: "v1" },
196
195
  },
197
196
  middleware: {
198
197
  emptyResponse: async () => ({ action: "accept" }),
@@ -219,7 +218,6 @@ describe("emptyResponse pipeline — custom middleware overrides", () => {
219
218
  manifest: {
220
219
  name: "force-error",
221
220
  version: "1.0.0",
222
- requires: { pluginRuntime: "v1" },
223
221
  },
224
222
  middleware: {
225
223
  emptyResponse: async () => ({ action: "error" }),
@@ -239,7 +237,6 @@ describe("emptyResponse pipeline — custom middleware overrides", () => {
239
237
  manifest: {
240
238
  name: "rewrite-nudge",
241
239
  version: "1.0.0",
242
- requires: { pluginRuntime: "v1" },
243
240
  },
244
241
  middleware: {
245
242
  emptyResponse: async (args, next, ctx) => {
@@ -287,7 +284,6 @@ describe("emptyResponse pipeline — custom middleware overrides", () => {
287
284
  manifest: {
288
285
  name: "late-user-empty-response",
289
286
  version: "0.0.1",
290
- requires: { pluginRuntime: "v1", emptyResponseApi: "v1" },
291
287
  },
292
288
  middleware: { emptyResponse: userMiddleware },
293
289
  });