@vellumai/assistant 0.8.1 → 0.8.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (506) hide show
  1. package/ARCHITECTURE.md +2 -7
  2. package/Dockerfile +75 -1
  3. package/bun.lock +11 -1
  4. package/docker-entrypoint.sh +5 -0
  5. package/docker-init-apt-root.sh +94 -0
  6. package/docker-kata-apt-env.sh +39 -0
  7. package/docs/plugins.md +88 -47
  8. package/docs/skills.md +9 -7
  9. package/examples/plugins/echo/README.md +27 -27
  10. package/examples/plugins/echo/package.json +3 -0
  11. package/examples/plugins/echo/register.ts +31 -31
  12. package/node_modules/@vellumai/slack-text/src/index.test.ts +114 -14
  13. package/node_modules/@vellumai/slack-text/src/index.ts +82 -18
  14. package/openapi.yaml +325 -3
  15. package/package.json +3 -1
  16. package/scripts/generate-openapi.ts +83 -10
  17. package/scripts/sync-llm-catalog.ts +2 -2
  18. package/scripts/sync-web-search-catalog.ts +47 -25
  19. package/src/__tests__/agent-image-optimize.test.ts +11 -3
  20. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +131 -0
  21. package/src/__tests__/anthropic-provider.test.ts +45 -0
  22. package/src/__tests__/app-builder-tool-scripts.test.ts +9 -3
  23. package/src/__tests__/app-executors.test.ts +220 -4
  24. package/src/__tests__/auto-analysis-end-to-end.test.ts +35 -0
  25. package/src/__tests__/bundled-asset.test.ts +6 -6
  26. package/src/__tests__/channel-availability-routes.test.ts +206 -0
  27. package/src/__tests__/channel-delivery-store.test.ts +289 -1
  28. package/src/__tests__/circuit-breaker-pipeline.test.ts +0 -1
  29. package/src/__tests__/clawhub.test.ts +75 -16
  30. package/src/__tests__/compactor-tail-resolution.test.ts +41 -0
  31. package/src/__tests__/config-schema.test.ts +21 -0
  32. package/src/__tests__/config-set-route.test.ts +80 -0
  33. package/src/__tests__/config-sounds-sync.test.ts +97 -0
  34. package/src/__tests__/config-watcher-skill-reseed.test.ts +453 -0
  35. package/src/__tests__/context-search-conversations-source.test.ts +117 -2
  36. package/src/__tests__/context-search-memory-v2-source.test.ts +0 -1
  37. package/src/__tests__/context-search-workspace-source.test.ts +7 -0
  38. package/src/__tests__/context-token-estimator.test.ts +1 -0
  39. package/src/__tests__/conversation-abort-tool-results.test.ts +4 -1
  40. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +1 -0
  41. package/src/__tests__/conversation-agent-loop-overflow.test.ts +92 -92
  42. package/src/__tests__/conversation-agent-loop.test.ts +2 -0
  43. package/src/__tests__/conversation-error.test.ts +42 -3
  44. package/src/__tests__/conversation-fork-crud.test.ts +82 -0
  45. package/src/__tests__/conversation-inference-profile-route.test.ts +40 -4
  46. package/src/__tests__/conversation-lifecycle.test.ts +173 -0
  47. package/src/__tests__/conversation-message-sync-tags.test.ts +97 -0
  48. package/src/__tests__/conversation-pairing.test.ts +54 -0
  49. package/src/__tests__/conversation-process-callsite.test.ts +4 -1
  50. package/src/__tests__/conversation-provider-retry-repair.test.ts +5 -1
  51. package/src/__tests__/conversation-queue.test.ts +4 -1
  52. package/src/__tests__/conversation-runtime-assembly.test.ts +76 -9
  53. package/src/__tests__/conversation-slash-queue.test.ts +59 -1
  54. package/src/__tests__/conversation-slash-unknown.test.ts +4 -1
  55. package/src/__tests__/conversation-surfaces-table-action.test.ts +360 -0
  56. package/src/__tests__/conversation-sync-tags.test.ts +235 -0
  57. package/src/__tests__/conversation-workspace-injection.test.ts +5 -1
  58. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +5 -1
  59. package/src/__tests__/credential-security-invariants.test.ts +3 -2
  60. package/src/__tests__/db-slack-external-content-normalization.test.ts +301 -0
  61. package/src/__tests__/delete-managed-skill-tool.test.ts +55 -13
  62. package/src/__tests__/disk-pressure-tools.test.ts +1 -0
  63. package/src/__tests__/dm-backfill.test.ts +121 -10
  64. package/src/__tests__/document-tool-security.test.ts +258 -0
  65. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +0 -1
  66. package/src/__tests__/edit-propagation.test.ts +33 -0
  67. package/src/__tests__/empty-response-pipeline.test.ts +0 -4
  68. package/src/__tests__/external-plugin-loader.test.ts +60 -36
  69. package/src/__tests__/filing-service.test.ts +140 -0
  70. package/src/__tests__/get-skill-detail-audit.test.ts +0 -4
  71. package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +43 -62
  72. package/src/__tests__/helpers/tar-fixtures.ts +39 -0
  73. package/src/__tests__/helpers/wait-for.ts +21 -0
  74. package/src/__tests__/history-repair-pipeline.test.ts +0 -3
  75. package/src/__tests__/history-repair.test.ts +73 -0
  76. package/src/__tests__/host-app-control-proxy.test.ts +266 -10
  77. package/src/__tests__/image-credentials.test.ts +1 -1
  78. package/src/__tests__/inbound-slack-persistence.test.ts +2 -0
  79. package/src/__tests__/inference-no-mode-boot-e2e.test.ts +1 -1
  80. package/src/__tests__/inference-profile-reaper.test.ts +4 -2
  81. package/src/__tests__/inference-profile-session-handler.test.ts +18 -6
  82. package/src/__tests__/inference-profile-session-ipc.test.ts +17 -5
  83. package/src/__tests__/injector-chain.test.ts +10 -8
  84. package/src/__tests__/install-skill-routing.test.ts +155 -37
  85. package/src/__tests__/lifecycle-memory-v2-seed.test.ts +92 -3
  86. package/src/__tests__/list-messages-page-latest.test.ts +55 -0
  87. package/src/__tests__/llm-call-pipeline.test.ts +0 -3
  88. package/src/__tests__/llm-catalog-parity.test.ts +55 -13
  89. package/src/__tests__/llm-request-log-source-clickhouse.test.ts +34 -0
  90. package/src/__tests__/llm-request-log-source-factory.test.ts +29 -53
  91. package/src/__tests__/llm-usage-store.test.ts +114 -0
  92. package/src/__tests__/managed-profile-guard.test.ts +31 -29
  93. package/src/__tests__/managed-skill-lifecycle.test.ts +109 -18
  94. package/src/__tests__/managed-store.test.ts +84 -192
  95. package/src/__tests__/media-generate-image.test.ts +1 -1
  96. package/src/__tests__/memory-retrieval-pipeline.test.ts +0 -2
  97. package/src/__tests__/messages-after-tiebreaker.test.ts +122 -0
  98. package/src/__tests__/oauth-commands-routes.test.ts +168 -16
  99. package/src/__tests__/oauth-provider-profiles.test.ts +9 -0
  100. package/src/__tests__/openai-provider.test.ts +24 -0
  101. package/src/__tests__/openai-responses-cutover-guard.test.ts +17 -9
  102. package/src/__tests__/overflow-reduce-pipeline.test.ts +0 -2
  103. package/src/__tests__/persistence-pipeline.test.ts +0 -2
  104. package/src/__tests__/{managed-proxy-context.test.ts → platform-proxy-context.test.ts} +1 -1
  105. package/src/__tests__/platform.test.ts +2 -0
  106. package/src/__tests__/plugin-api-shim.test.ts +125 -0
  107. package/src/__tests__/plugin-bootstrap.test.ts +10 -36
  108. package/src/__tests__/plugin-external-api.test.ts +68 -0
  109. package/src/__tests__/plugin-registry.test.ts +0 -77
  110. package/src/__tests__/plugin-route-contribution.test.ts +0 -1
  111. package/src/__tests__/plugin-skill-contribution.test.ts +0 -2
  112. package/src/__tests__/plugin-tool-contribution.test.ts +16 -15
  113. package/src/__tests__/plugin-types.test.ts +3 -13
  114. package/src/__tests__/process-message-background-slack.test.ts +8 -1
  115. package/src/__tests__/process-message-display-content.test.ts +421 -0
  116. package/src/__tests__/provider-catalog-visibility.test.ts +142 -0
  117. package/src/__tests__/provider-error-scenarios.test.ts +111 -0
  118. package/src/__tests__/{provider-managed-proxy-integration.test.ts → provider-platform-proxy-integration.test.ts} +8 -8
  119. package/src/__tests__/scaffold-managed-skill-tool.test.ts +65 -13
  120. package/src/__tests__/schedule-routes.test.ts +50 -3
  121. package/src/__tests__/schedule-store.test.ts +94 -0
  122. package/src/__tests__/scheduler-reuse-conversation.test.ts +54 -7
  123. package/src/__tests__/schema-transforms.test.ts +20 -0
  124. package/src/__tests__/search-skills-unified.test.ts +0 -5
  125. package/src/__tests__/server-history-render.test.ts +43 -0
  126. package/src/__tests__/skill-load-feature-flag.test.ts +0 -12
  127. package/src/__tests__/skill-load-tool.test.ts +27 -89
  128. package/src/__tests__/skill-memory.test.ts +23 -3
  129. package/src/__tests__/skills-file-content-endpoint.test.ts +9 -38
  130. package/src/__tests__/skills-files-catalog-fallback.test.ts +0 -3
  131. package/src/__tests__/skills-install-extract.test.ts +49 -38
  132. package/src/__tests__/skills-install-staging.test.ts +159 -0
  133. package/src/__tests__/skills-uninstall.test.ts +9 -41
  134. package/src/__tests__/skills.test.ts +51 -58
  135. package/src/__tests__/slack-channel-config.test.ts +9 -0
  136. package/src/__tests__/subagent-tool-filtering.test.ts +50 -0
  137. package/src/__tests__/system-prompt.test.ts +737 -63
  138. package/src/__tests__/terminal-tools.test.ts +28 -1
  139. package/src/__tests__/thread-backfill.test.ts +557 -27
  140. package/src/__tests__/title-generate-pipeline.test.ts +0 -13
  141. package/src/__tests__/token-estimate-pipeline.test.ts +0 -3
  142. package/src/__tests__/tool-error-pipeline.test.ts +0 -3
  143. package/src/__tests__/tool-execute-pipeline.test.ts +0 -5
  144. package/src/__tests__/tool-executor-lifecycle-events.test.ts +1 -1
  145. package/src/__tests__/tool-executor.test.ts +16 -4
  146. package/src/__tests__/tool-result-truncate-pipeline.test.ts +0 -12
  147. package/src/__tests__/turn-events-store.test.ts +256 -0
  148. package/src/__tests__/twilio-routes.test.ts +4 -0
  149. package/src/__tests__/user-plugin-loader.test.ts +0 -7
  150. package/src/__tests__/voice-session-bridge.test.ts +198 -0
  151. package/src/__tests__/web-search-catalog-parity.test.ts +32 -10
  152. package/src/__tests__/workspace-migration-057-repair-stale-gemini-model-ids.test.ts +115 -3
  153. package/src/__tests__/workspace-migration-072-seed-reply-suggestion-callsite.test.ts +50 -0
  154. package/src/__tests__/workspace-migration-073-repair-recall-callsite-empty-profile.test.ts +153 -0
  155. package/src/__tests__/workspace-migration-085-memory-v2-bm25-b-reembed-disabled-v2-pages.test.ts +220 -0
  156. package/src/__tests__/workspace-migration-086-revert-stale-gemini-mis-rewrites.test.ts +269 -0
  157. package/src/__tests__/workspace-migration-remove-legacy-skills-index.test.ts +309 -0
  158. package/src/__tests__/workspace-migrations-runner.test.ts +111 -3
  159. package/src/acp/resolve-agent.ts +1 -1
  160. package/src/agent/image-optimize.ts +13 -5
  161. package/src/calls/voice-session-bridge.ts +61 -42
  162. package/src/channels/types.ts +108 -0
  163. package/src/cli/__tests__/unknown-command.test.ts +24 -0
  164. package/src/cli/commands/__tests__/changelog.test.ts +304 -319
  165. package/src/cli/commands/__tests__/schedules.test.ts +491 -0
  166. package/src/cli/commands/changelog.ts +106 -42
  167. package/src/cli/commands/conversations.ts +102 -17
  168. package/src/cli/commands/default-action.ts +10 -53
  169. package/src/cli/commands/notifications.ts +329 -317
  170. package/src/cli/commands/plugins.ts +185 -0
  171. package/src/cli/commands/schedules.ts +391 -0
  172. package/src/cli/commands/telemetry.ts +40 -0
  173. package/src/cli/lib/__tests__/cli-colors.test.ts +48 -0
  174. package/src/cli/lib/__tests__/confirm-prompt.test.ts +159 -0
  175. package/src/cli/lib/__tests__/install-from-github.test.ts +355 -0
  176. package/src/cli/lib/__tests__/list-installed-plugins.test.ts +154 -0
  177. package/src/cli/lib/__tests__/uninstall-plugin.test.ts +124 -0
  178. package/src/cli/lib/__tests__/unknown-command.test.ts +106 -0
  179. package/src/cli/lib/cli-colors.ts +12 -0
  180. package/src/cli/lib/confirm-prompt.ts +79 -0
  181. package/src/cli/lib/install-from-github.ts +304 -0
  182. package/src/cli/lib/list-installed-plugins.ts +137 -0
  183. package/src/cli/lib/uninstall-plugin.ts +82 -0
  184. package/src/cli/lib/unknown-command.ts +111 -0
  185. package/src/cli/program.ts +38 -2
  186. package/src/config/bundled-skills/app-builder/SKILL.md +23 -21
  187. package/src/config/bundled-skills/app-builder/TOOLS.json +7 -0
  188. package/src/config/bundled-skills/computer-use/TOOLS.json +15 -52
  189. package/src/config/bundled-skills/document/SKILL.md +23 -3
  190. package/src/config/bundled-skills/document/TOOLS.json +53 -0
  191. package/src/config/bundled-skills/document/tools/document-delete.ts +12 -0
  192. package/src/config/bundled-skills/document/tools/document-list.ts +12 -0
  193. package/src/config/bundled-skills/document/tools/document-read.ts +12 -0
  194. package/src/config/bundled-skills/skill-management/SKILL.md +2 -2
  195. package/src/config/bundled-skills/skill-management/TOOLS.json +7 -7
  196. package/src/config/bundled-tool-registry.ts +6 -0
  197. package/src/config/feature-flag-registry.json +41 -1
  198. package/src/config/loader.ts +64 -38
  199. package/src/config/schema.ts +7 -10
  200. package/src/config/schemas/__tests__/llm-request-logs.test.ts +36 -0
  201. package/src/config/schemas/channels.ts +8 -0
  202. package/src/config/schemas/compaction.ts +28 -0
  203. package/src/config/schemas/heartbeat.ts +9 -0
  204. package/src/config/schemas/llm-request-logs.ts +31 -7
  205. package/src/config/schemas/llm.ts +3 -0
  206. package/src/config/schemas/memory-retrieval.ts +18 -0
  207. package/src/config/schemas/tools.ts +14 -0
  208. package/src/config/skills.ts +3 -96
  209. package/src/context/compactor.ts +1047 -0
  210. package/src/context/token-estimator.ts +2 -2
  211. package/src/context/window-manager.ts +197 -1520
  212. package/src/credential-execution/managed-catalog.ts +37 -0
  213. package/src/credential-health/credential-health-service.ts +280 -19
  214. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +34 -0
  215. package/src/daemon/__tests__/conversation-tool-setup-exclude.test.ts +138 -0
  216. package/src/daemon/__tests__/conversation-tool-setup.test.ts +74 -0
  217. package/src/daemon/approval-generators.ts +8 -6
  218. package/src/daemon/config-watcher.ts +94 -31
  219. package/src/daemon/conversation-agent-loop.ts +169 -9
  220. package/src/daemon/conversation-error.ts +171 -37
  221. package/src/daemon/conversation-lifecycle.ts +53 -40
  222. package/src/daemon/conversation-messaging.ts +25 -6
  223. package/src/daemon/conversation-process.ts +49 -12
  224. package/src/daemon/conversation-runtime-assembly.ts +16 -1
  225. package/src/daemon/conversation-slash.ts +12 -5
  226. package/src/daemon/conversation-store.ts +11 -4
  227. package/src/daemon/conversation-tool-setup.ts +39 -7
  228. package/src/daemon/conversation.ts +33 -1
  229. package/src/daemon/external-plugins-bootstrap.ts +217 -181
  230. package/src/daemon/first-greeting.ts +22 -2
  231. package/src/daemon/handlers/config-model.ts +6 -5
  232. package/src/daemon/handlers/config-slack-channel.ts +15 -3
  233. package/src/daemon/handlers/shared.ts +14 -5
  234. package/src/daemon/handlers/skills.ts +111 -108
  235. package/src/daemon/history-repair.ts +28 -1
  236. package/src/daemon/host-app-control-proxy.ts +98 -23
  237. package/src/daemon/lifecycle.ts +45 -35
  238. package/src/daemon/meet-host-supervisor.ts +5 -4
  239. package/src/daemon/memory-v2-startup.ts +49 -0
  240. package/src/daemon/message-protocol.ts +1 -0
  241. package/src/daemon/message-types/conversations.ts +25 -0
  242. package/src/daemon/message-types/messages.ts +61 -0
  243. package/src/daemon/message-types/subagents.ts +1 -0
  244. package/src/daemon/message-types/sync.ts +1 -0
  245. package/src/daemon/pkb-reminder-builder.test.ts +1 -1
  246. package/src/daemon/pkb-reminder-builder.ts +1 -1
  247. package/src/daemon/plugin-source-watcher.ts +146 -0
  248. package/src/daemon/process-message.ts +21 -3
  249. package/src/daemon/server.ts +11 -2
  250. package/src/daemon/skill-memory-refresh.ts +29 -0
  251. package/src/documents/document-store.ts +221 -3
  252. package/src/embedded/plugin-api.ts +40 -0
  253. package/src/filing/filing-service.ts +39 -0
  254. package/src/heartbeat/__tests__/heartbeat-service.test.ts +91 -6
  255. package/src/heartbeat/heartbeat-run-store.ts +2 -1
  256. package/src/heartbeat/heartbeat-service.ts +41 -0
  257. package/src/home/__tests__/feed-types.test.ts +40 -0
  258. package/src/home/feed-types.ts +22 -0
  259. package/src/home/post-connect-feed.ts +1 -0
  260. package/src/index.ts +18 -1
  261. package/src/live-voice/__tests__/live-voice-stt.test.ts +57 -0
  262. package/src/mcp/client.ts +20 -4
  263. package/src/media/image-credentials.ts +3 -3
  264. package/src/memory/__tests__/bookmark-crud.test.ts +33 -27
  265. package/src/memory/__tests__/conversation-queries.test.ts +263 -0
  266. package/src/memory/__tests__/jobs-worker-v2-graph-trigger-embed.test.ts +113 -0
  267. package/src/memory/__tests__/memory-retrospective-startup-cleanup.test.ts +119 -14
  268. package/src/memory/__tests__/message-content.test.ts +35 -0
  269. package/src/memory/bookmark-crud.ts +42 -10
  270. package/src/memory/context-search/sources/conversations.ts +62 -2
  271. package/src/memory/context-search/sources/workspace.ts +4 -0
  272. package/src/memory/conversation-crud.ts +63 -19
  273. package/src/memory/conversation-queries.ts +110 -10
  274. package/src/memory/db-init.ts +6 -0
  275. package/src/memory/delivery-crud.ts +152 -5
  276. package/src/memory/embedding-backend.ts +4 -4
  277. package/src/memory/external-conversation-store.ts +66 -5
  278. package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +66 -9
  279. package/src/memory/graph/conversation-graph-memory.ts +31 -15
  280. package/src/memory/graph/tools.ts +3 -3
  281. package/src/memory/indexer.ts +34 -29
  282. package/src/memory/jobs/__tests__/embed-concept-page.test.ts +73 -0
  283. package/src/memory/jobs/embed-concept-page.ts +20 -11
  284. package/src/memory/jobs-worker.ts +6 -1
  285. package/src/memory/llm-request-log-source-clickhouse.ts +17 -10
  286. package/src/memory/llm-request-log-source.ts +19 -52
  287. package/src/memory/llm-usage-store.ts +125 -5
  288. package/src/memory/memory-retrospective-startup-cleanup.ts +72 -5
  289. package/src/memory/message-content.ts +1 -1
  290. package/src/memory/migrations/109-external-conversation-bindings.ts +15 -4
  291. package/src/memory/migrations/229-delete-private-conversations.test.ts +38 -1
  292. package/src/memory/migrations/229-delete-private-conversations.ts +7 -0
  293. package/src/memory/migrations/247-external-conversation-binding-thread-id.ts +78 -0
  294. package/src/memory/migrations/248-create-onboarding-events.ts +21 -0
  295. package/src/memory/migrations/249-normalize-slack-external-content.ts +240 -0
  296. package/src/memory/migrations/index.ts +6 -0
  297. package/src/memory/migrations/registry.ts +8 -0
  298. package/src/memory/onboarding-events-store.ts +106 -0
  299. package/src/memory/schema/bookmarks.ts +0 -2
  300. package/src/memory/schema/calls.ts +1 -0
  301. package/src/memory/schema/inference.ts +1 -3
  302. package/src/memory/schema/infrastructure.ts +12 -0
  303. package/src/memory/turn-events-store.ts +127 -2
  304. package/src/memory/v2/__tests__/activation.test.ts +0 -8
  305. package/src/memory/v2/__tests__/injection.test.ts +98 -8
  306. package/src/memory/v2/__tests__/migration.test.ts +87 -0
  307. package/src/memory/v2/__tests__/page-index.test.ts +83 -0
  308. package/src/memory/v2/__tests__/prompts-router.test.ts +58 -6
  309. package/src/memory/v2/__tests__/qdrant.test.ts +66 -3
  310. package/src/memory/v2/__tests__/router.test.ts +15 -0
  311. package/src/memory/v2/__tests__/skill-store.test.ts +387 -8
  312. package/src/memory/v2/injection.ts +32 -6
  313. package/src/memory/v2/migration.ts +49 -19
  314. package/src/memory/v2/page-index.ts +35 -5
  315. package/src/memory/v2/prompts/router.ts +11 -8
  316. package/src/memory/v2/prompts/sweep.ts +2 -2
  317. package/src/memory/v2/qdrant.ts +135 -7
  318. package/src/memory/v2/router.ts +9 -8
  319. package/src/memory/v2/skill-store.ts +120 -35
  320. package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +45 -5
  321. package/src/messaging/providers/slack/__tests__/download.test.ts +231 -0
  322. package/src/messaging/providers/slack/adapter.ts +43 -5
  323. package/src/messaging/providers/slack/client.ts +27 -0
  324. package/src/messaging/providers/slack/deep-link.ts +65 -0
  325. package/src/messaging/providers/slack/download.ts +104 -0
  326. package/src/messaging/providers/slack/message-metadata.test.ts +32 -0
  327. package/src/messaging/providers/slack/message-metadata.ts +27 -0
  328. package/src/messaging/providers/slack/render-transcript.test.ts +134 -0
  329. package/src/messaging/providers/slack/render-transcript.ts +69 -5
  330. package/src/messaging/providers/slack/types.ts +20 -1
  331. package/src/notifications/conversation-pairing.ts +2 -1
  332. package/src/notifications/decision-engine.ts +2 -1
  333. package/src/notifications/emit-signal.ts +20 -1
  334. package/src/notifications/home-feed-side-effect.ts +54 -0
  335. package/src/notifications/signal.ts +3 -1
  336. package/src/oauth/connection-resolver.ts +8 -4
  337. package/src/oauth/platform-connection.ts +6 -2
  338. package/src/oauth/seed-providers.ts +10 -1
  339. package/src/permissions/checker.ts +2 -0
  340. package/src/permissions/ipc-risk-types.ts +1 -0
  341. package/src/permissions/question-prompter.test.ts +416 -0
  342. package/src/permissions/question-prompter.ts +294 -0
  343. package/src/platform/client.test.ts +1 -1
  344. package/src/platform/client.ts +1 -1
  345. package/src/plugin-api/constants.ts +26 -0
  346. package/src/plugin-api/index.ts +34 -1
  347. package/src/plugin-api/types.ts +104 -22
  348. package/src/plugins/defaults/circuit-breaker.ts +0 -5
  349. package/src/plugins/defaults/compaction.ts +0 -4
  350. package/src/plugins/defaults/empty-response.ts +0 -2
  351. package/src/plugins/defaults/history-repair.ts +0 -2
  352. package/src/plugins/defaults/injectors.ts +36 -3
  353. package/src/plugins/defaults/llm-call.ts +0 -2
  354. package/src/plugins/defaults/memory-retrieval.ts +0 -1
  355. package/src/plugins/defaults/overflow-reduce.ts +0 -1
  356. package/src/plugins/defaults/persistence.ts +0 -2
  357. package/src/plugins/defaults/title-generate.ts +0 -5
  358. package/src/plugins/defaults/token-estimate.ts +0 -2
  359. package/src/plugins/defaults/tool-error.ts +0 -7
  360. package/src/plugins/defaults/tool-execute.ts +0 -2
  361. package/src/plugins/defaults/tool-result-truncate.ts +0 -4
  362. package/src/plugins/ensure-plugin-api-shim.ts +96 -0
  363. package/src/plugins/external-api.ts +104 -0
  364. package/src/plugins/external-plugin-loader.ts +105 -32
  365. package/src/plugins/feature-gate.ts +22 -0
  366. package/src/plugins/pipeline.ts +37 -0
  367. package/src/plugins/registry.ts +48 -80
  368. package/src/plugins/types.ts +31 -26
  369. package/src/plugins/user-loader.ts +21 -2
  370. package/src/proactive-artifact/aux-message-injector.ts +11 -0
  371. package/src/proactive-artifact/job.test.ts +37 -5
  372. package/src/prompts/__tests__/system-prompt.test.ts +12 -0
  373. package/src/prompts/__tests__/task-progress-hint-section.test.ts +99 -0
  374. package/src/prompts/normalize-onboarding.ts +27 -0
  375. package/src/prompts/sections.ts +302 -0
  376. package/src/prompts/system-prompt.ts +63 -166
  377. package/src/prompts/templates/BOOTSTRAP.md +17 -1
  378. package/src/prompts/templates/system-sections.ts +173 -0
  379. package/src/providers/__tests__/inference.test.ts +22 -7
  380. package/src/providers/anthropic/client.ts +28 -28
  381. package/src/providers/connection-resolution.ts +7 -0
  382. package/src/providers/inference/adapter-factory.ts +41 -4
  383. package/src/providers/inference/connections.ts +74 -29
  384. package/src/providers/inference/resolve-auth.ts +12 -4
  385. package/src/providers/model-catalog.ts +294 -12
  386. package/src/providers/openai/chat-completions-provider.ts +10 -2
  387. package/src/providers/openrouter/client.ts +7 -0
  388. package/src/providers/{managed-proxy → platform-proxy}/constants.ts +4 -1
  389. package/src/providers/{managed-proxy → platform-proxy}/context.ts +3 -3
  390. package/src/providers/provider-availability.ts +17 -2
  391. package/src/providers/provider-catalog-visibility.ts +36 -0
  392. package/src/providers/registry.ts +22 -14
  393. package/src/providers/retry.ts +47 -1
  394. package/src/runtime/__tests__/agent-wake.test.ts +152 -0
  395. package/src/runtime/agent-wake.ts +42 -14
  396. package/src/runtime/auth/route-policy.ts +8 -1
  397. package/src/runtime/btw-sidechain.ts +2 -0
  398. package/src/runtime/http-types.ts +19 -0
  399. package/src/runtime/migrations/origin-mode.ts +1 -1
  400. package/src/runtime/pending-interactions.ts +1 -0
  401. package/src/runtime/routes/__tests__/bookmark-routes.test.ts +17 -0
  402. package/src/runtime/routes/__tests__/conversation-management-routes.test.ts +5 -1
  403. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +107 -20
  404. package/src/runtime/routes/__tests__/question-routes.test.ts +395 -0
  405. package/src/runtime/routes/__tests__/tts-routes.test.ts +64 -1
  406. package/src/runtime/routes/acp-routes-list.test.ts +143 -0
  407. package/src/runtime/routes/acp-routes.ts +5 -3
  408. package/src/runtime/routes/auth-routes.ts +1 -1
  409. package/src/runtime/routes/bookmark-routes.ts +5 -3
  410. package/src/runtime/routes/btw-routes.ts +5 -1
  411. package/src/runtime/routes/channel-availability-routes.ts +121 -0
  412. package/src/runtime/routes/conversation-cli-routes.ts +44 -3
  413. package/src/runtime/routes/conversation-list-routes.ts +3 -20
  414. package/src/runtime/routes/conversation-management-routes.ts +17 -42
  415. package/src/runtime/routes/conversation-query-routes.ts +40 -35
  416. package/src/runtime/routes/conversation-routes.ts +90 -11
  417. package/src/runtime/routes/documents-routes.ts +25 -86
  418. package/src/runtime/routes/group-routes.ts +5 -0
  419. package/src/runtime/routes/inbound-conversation.ts +28 -8
  420. package/src/runtime/routes/inbound-message-handler.ts +236 -41
  421. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +111 -0
  422. package/src/runtime/routes/inbound-stages/background-dispatch.ts +32 -1
  423. package/src/runtime/routes/inbound-stages/edit-intercept.ts +17 -4
  424. package/src/runtime/routes/index.ts +6 -0
  425. package/src/runtime/routes/inference-profile-session-handler.ts +17 -44
  426. package/src/runtime/routes/inference-profile-session-reaper.ts +7 -21
  427. package/src/runtime/routes/inference-provider-connection-routes.ts +65 -21
  428. package/src/runtime/routes/integrations/slack/share.ts +4 -52
  429. package/src/runtime/routes/integrations/slack/token.ts +43 -0
  430. package/src/runtime/routes/integrations/twilio.ts +6 -13
  431. package/src/runtime/routes/notification-routes.ts +1 -1
  432. package/src/runtime/routes/oauth-commands-routes.ts +105 -15
  433. package/src/runtime/routes/oauth-lifecycle-routes.ts +43 -0
  434. package/src/runtime/routes/question-routes.ts +259 -0
  435. package/src/runtime/routes/rename-conversation-routes.ts +2 -33
  436. package/src/runtime/routes/schedule-routes.ts +4 -7
  437. package/src/runtime/routes/subagents-routes.ts +57 -18
  438. package/src/runtime/routes/telemetry-routes.ts +27 -0
  439. package/src/runtime/routes/tts-routes.ts +27 -2
  440. package/src/runtime/routes/workspace-routes.test.ts +43 -0
  441. package/src/runtime/routes/workspace-routes.ts +28 -0
  442. package/src/runtime/services/conversation-serializer.ts +39 -7
  443. package/src/runtime/sync/resource-sync-events.ts +93 -1
  444. package/src/schedule/schedule-store.ts +27 -2
  445. package/src/schedule/scheduler.ts +9 -1
  446. package/src/security/__tests__/untrusted-content.test.ts +86 -0
  447. package/src/security/untrusted-content.ts +93 -8
  448. package/src/skills/catalog-files.ts +1 -1
  449. package/src/skills/catalog-install.ts +233 -116
  450. package/src/skills/clawhub.ts +70 -13
  451. package/src/skills/managed-store.ts +4 -119
  452. package/src/skills/skillssh-registry.ts +27 -48
  453. package/src/subagent/manager.ts +15 -7
  454. package/src/telemetry/types.ts +113 -1
  455. package/src/telemetry/usage-telemetry-reporter.test.ts +312 -5
  456. package/src/telemetry/usage-telemetry-reporter.ts +113 -7
  457. package/src/tools/apps/executors.ts +58 -7
  458. package/src/tools/ask-question/ask-question-tool.test.ts +509 -0
  459. package/src/tools/ask-question/ask-question-tool.ts +304 -0
  460. package/src/tools/browser/browser-execution.ts +15 -11
  461. package/src/tools/computer-use/definitions.ts +3 -3
  462. package/src/tools/credentials/vault.ts +1 -1
  463. package/src/tools/document/document-tool.ts +124 -1
  464. package/src/tools/filesystem/edit.ts +1 -1
  465. package/src/tools/filesystem/list.ts +1 -1
  466. package/src/tools/filesystem/read.ts +1 -1
  467. package/src/tools/filesystem/write.ts +5 -2
  468. package/src/tools/host-filesystem/transfer.ts +1 -1
  469. package/src/tools/host-terminal/host-shell.ts +1 -1
  470. package/src/tools/permission-checker.ts +1 -1
  471. package/src/tools/registry.ts +17 -7
  472. package/src/tools/schedule/create.ts +2 -2
  473. package/src/tools/schema-transforms.ts +7 -2
  474. package/src/tools/side-effects.ts +1 -0
  475. package/src/tools/skills/delete-managed.ts +4 -4
  476. package/src/tools/skills/execute.ts +1 -1
  477. package/src/tools/skills/scaffold-managed.ts +3 -2
  478. package/src/tools/subagent/notify-parent.ts +1 -1
  479. package/src/tools/system/request-permission.ts +2 -2
  480. package/src/tools/terminal/safe-env.ts +60 -1
  481. package/src/tools/tool-manifest.ts +2 -0
  482. package/src/tools/types.ts +72 -21
  483. package/src/tools/ui-surface/definitions.ts +6 -5
  484. package/src/tts/__tests__/provider-adapters.test.ts +76 -2
  485. package/src/tts/providers/elevenlabs-provider.ts +75 -1
  486. package/src/types/onboarding-context.ts +2 -0
  487. package/src/util/errors.ts +17 -0
  488. package/src/util/platform.ts +10 -0
  489. package/src/watcher/__tests__/engine.test.ts +22 -0
  490. package/src/watcher/engine.ts +6 -2
  491. package/src/workspace/migrations/057-repair-stale-gemini-model-ids.ts +80 -15
  492. package/src/workspace/migrations/072-seed-reply-suggestion-callsite.ts +35 -22
  493. package/src/workspace/migrations/073-repair-recall-callsite-empty-profile.ts +3 -1
  494. package/src/workspace/migrations/083-system-prompt-prefix-to-file.ts +191 -0
  495. package/src/workspace/migrations/084-remove-legacy-skills-index.ts +276 -0
  496. package/src/workspace/migrations/085-memory-v2-bm25-b-reembed-disabled-v2-pages.ts +137 -0
  497. package/src/workspace/migrations/086-revert-stale-gemini-mis-rewrites.ts +198 -0
  498. package/src/workspace/migrations/registry.ts +8 -0
  499. package/src/workspace/migrations/runner.ts +39 -9
  500. package/src/workspace/migrations/types.ts +4 -0
  501. package/examples/plugins/echo/bun.lock +0 -25
  502. package/src/__tests__/context-window-manager.test.ts +0 -2481
  503. package/src/context/__tests__/compact-prompt.test.ts +0 -63
  504. package/src/context/prompts/compact.md +0 -26
  505. package/src/prompts/__tests__/build-cli-reference-section.test.ts +0 -37
  506. /package/src/__tests__/{secret-routes-managed-proxy.test.ts → secret-routes-platform-proxy.test.ts} +0 -0
@@ -61,7 +61,11 @@ mock.module("../config/loader.js", () => ({
61
61
  pricingOverrides: [],
62
62
  },
63
63
  rateLimit: { maxRequestsPerMinute: 0 },
64
- memory: { enabled: false, v2: { enabled: false } },
64
+ memory: {
65
+ enabled: false,
66
+ v2: { enabled: false },
67
+ retrieval: { scratchpadInjection: { enabled: true } },
68
+ },
65
69
  daemon: {
66
70
  startupSocketWaitMs: 5000,
67
71
  stopTimeoutMs: 5000,
@@ -189,10 +189,10 @@ describe("Invariant 2: no generic plaintext secret read API", () => {
189
189
  "messaging/providers/slack/adapter.ts", // Slack bot token lookup for Socket Mode connectivity check
190
190
  "credential-health/credential-health-service.ts", // proactive credential health monitoring
191
191
  "daemon/handlers/config-slack-channel.ts", // Slack channel config credential management
192
- "providers/managed-proxy/context.ts", // managed proxy API key lookup for provider initialization
192
+ "providers/platform-proxy/context.ts", // managed proxy API key lookup for provider initialization
193
193
  "platform/client.ts", // platform client credential store fallback for standalone CLI auth
194
194
  "mcp/mcp-oauth-provider.ts", // MCP OAuth token/client/discovery persistence
195
- "runtime/routes/integrations/slack/share.ts", // Slack share routes credential lookup
195
+ "runtime/routes/integrations/slack/token.ts", // shared Slack token resolver (bot/user token lookup for CLI use routes)
196
196
  "mcp/client.ts", // MCP client cached-token lookup
197
197
  "oauth/token-persistence.ts", // OAuth token persistence (set/delete tokens)
198
198
  "oauth/credential-token-resolver.ts", // centralized access-token key resolution for OAuth and manual-token providers
@@ -226,6 +226,7 @@ describe("Invariant 2: no generic plaintext secret read API", () => {
226
226
  "runtime/routes/platform-routes.ts", // CLI platform connect/disconnect/status routes (CLI-migrated to IPC)
227
227
  "ipc/skill-routes/providers.ts", // host.providers.secureKeys.getProviderKey IPC route (out-of-process SkillHost companion)
228
228
  "daemon/external-plugins-bootstrap.ts", // reads credentials at plugin init (manifest.requiresCredential) via the CES-mediated getSecureKeyAsync path
229
+ "plugins/external-api.ts", // globalThis runtime bridge that exposes getSecureKeyAsync to dynamically-imported workspace plugins (compiled-binary plugin loading)
229
230
  "inbound/platform-callback-registration.ts", // managed credential lookup for platform base URL, assistant ID, and API key
230
231
  "tts/providers/elevenlabs-provider.ts", // ElevenLabs TTS API key lookup
231
232
  "tts/providers/deepgram-provider.ts", // Deepgram TTS API key lookup
@@ -0,0 +1,301 @@
1
+ import { Database } from "bun:sqlite";
2
+ import { describe, expect, test } from "bun:test";
3
+
4
+ import { drizzle } from "drizzle-orm/bun-sqlite";
5
+
6
+ import { getSqliteFrom } from "../memory/db-connection.js";
7
+ import { createMessagesFts } from "../memory/migrations/116-messages-fts.js";
8
+ import { migrateNormalizeSlackExternalContent } from "../memory/migrations/249-normalize-slack-external-content.js";
9
+ import * as schema from "../memory/schema.js";
10
+ import { writeSlackMetadata } from "../messaging/providers/slack/message-metadata.js";
11
+ import { wrapUntrustedContent } from "../security/untrusted-content.js";
12
+
13
+ interface MessageRow {
14
+ id: string;
15
+ content: string;
16
+ metadata: string | null;
17
+ }
18
+
19
+ interface FtsRow {
20
+ content: string;
21
+ }
22
+
23
+ function createTestDb() {
24
+ const sqlite = new Database(":memory:");
25
+ sqlite.exec("PRAGMA journal_mode=WAL");
26
+ sqlite.exec("PRAGMA foreign_keys = ON");
27
+ return drizzle(sqlite, { schema });
28
+ }
29
+
30
+ function bootstrapConversationTables(raw: Database): void {
31
+ raw.exec(/*sql*/ `
32
+ CREATE TABLE memory_checkpoints (
33
+ key TEXT PRIMARY KEY,
34
+ value TEXT NOT NULL,
35
+ updated_at INTEGER NOT NULL
36
+ );
37
+
38
+ CREATE TABLE conversations (
39
+ id TEXT PRIMARY KEY,
40
+ title TEXT,
41
+ created_at INTEGER NOT NULL,
42
+ updated_at INTEGER NOT NULL
43
+ );
44
+
45
+ CREATE TABLE messages (
46
+ id TEXT PRIMARY KEY,
47
+ conversation_id TEXT NOT NULL REFERENCES conversations(id) ON DELETE CASCADE,
48
+ role TEXT NOT NULL,
49
+ content TEXT NOT NULL,
50
+ created_at INTEGER NOT NULL,
51
+ metadata TEXT
52
+ );
53
+
54
+ INSERT INTO conversations (id, title, created_at, updated_at)
55
+ VALUES ('conv-slack', 'Slack conversation', 1000, 1000);
56
+ `);
57
+ }
58
+
59
+ function slackEnvelope(
60
+ channelTs: string,
61
+ extra: Record<string, unknown> = {},
62
+ ): string {
63
+ return JSON.stringify({
64
+ userMessageChannel: "slack",
65
+ assistantMessageChannel: "slack",
66
+ slackMeta: writeSlackMetadata({
67
+ source: "slack",
68
+ channelId: "C0123",
69
+ channelTs,
70
+ eventKind: "message",
71
+ displayName: "@alice",
72
+ }),
73
+ ...extra,
74
+ });
75
+ }
76
+
77
+ function slackBackfillEnvelope(
78
+ channelTs: string,
79
+ extra: Record<string, unknown> = {},
80
+ ): string {
81
+ const { slackFiles, ...outerExtra } = extra;
82
+ return JSON.stringify({
83
+ slackMeta: writeSlackMetadata({
84
+ source: "slack",
85
+ channelId: "C0123",
86
+ channelTs,
87
+ eventKind: "message",
88
+ displayName: "@alice",
89
+ ...(Array.isArray(slackFiles) ? { slackFiles } : {}),
90
+ }),
91
+ ...outerExtra,
92
+ });
93
+ }
94
+
95
+ function insertMessage(
96
+ raw: Database,
97
+ id: string,
98
+ content: string,
99
+ metadata: string,
100
+ role = "user",
101
+ ): void {
102
+ raw
103
+ .query(
104
+ /*sql*/ `
105
+ INSERT INTO messages (id, conversation_id, role, content, created_at, metadata)
106
+ VALUES (?, 'conv-slack', ?, ?, 1000, ?)
107
+ `,
108
+ )
109
+ .run(id, role, content, metadata);
110
+ }
111
+
112
+ function getRows(raw: Database): Record<string, MessageRow> {
113
+ const rows = raw
114
+ .query(`SELECT id, content, metadata FROM messages ORDER BY id`)
115
+ .all() as MessageRow[];
116
+ return Object.fromEntries(rows.map((row) => [row.id, row]));
117
+ }
118
+
119
+ function getFtsContent(raw: Database, messageId: string): string {
120
+ const row = raw
121
+ .query(`SELECT content FROM messages_fts WHERE message_id = ?`)
122
+ .get(messageId) as FtsRow | null;
123
+ return row?.content ?? "";
124
+ }
125
+
126
+ describe("migrateNormalizeSlackExternalContent", () => {
127
+ test("normalizes complete Slack envelopes and leaves unrelated rows unchanged", () => {
128
+ const db = createTestDb();
129
+ const raw = getSqliteFrom(db);
130
+ bootstrapConversationTables(raw);
131
+ createMessagesFts(db);
132
+
133
+ const wrappedPlain = wrapUntrustedContent("plain Slack text", {
134
+ source: "slack",
135
+ sourceDetail: "@alice",
136
+ });
137
+ const wrappedBlock = wrapUntrustedContent("block Slack text", {
138
+ source: "slack",
139
+ sourceDetail: "@alice",
140
+ });
141
+ const jsonContent = JSON.stringify([
142
+ { type: "text", text: wrappedBlock },
143
+ {
144
+ type: "image",
145
+ source: { type: "base64", media_type: "image/png", data: "aGVsbG8=" },
146
+ },
147
+ { type: "file", source: { type: "file_id", file_id: "file_1" } },
148
+ ]);
149
+ const nonSlackWrapped = wrapUntrustedContent("web text", {
150
+ source: "web",
151
+ sourceDetail: "https://example.com",
152
+ });
153
+ const guardianMetadata = slackEnvelope("1700000003.000000", {
154
+ provenanceTrustClass: "guardian",
155
+ });
156
+ const liveRawMissingProvenanceMetadata = slackEnvelope("1700000007.000000");
157
+ const legacyGuardianMetadata = slackBackfillEnvelope("1700000004.000000");
158
+ const legacyGuardianJsonContent = JSON.stringify([
159
+ { type: "text", text: "guardian image caption" },
160
+ {
161
+ type: "image",
162
+ source: { type: "base64", media_type: "image/png", data: "aGVsbG8=" },
163
+ },
164
+ ]);
165
+ const ambiguousAttachmentOnlyMetadata = slackBackfillEnvelope(
166
+ "1700000006.000000",
167
+ {
168
+ slackFiles: [
169
+ {
170
+ id: "F0123",
171
+ name: "example.png",
172
+ mimetype: "image/png",
173
+ },
174
+ ],
175
+ },
176
+ );
177
+ const ambiguousAttachmentOnlyContent = JSON.stringify([
178
+ {
179
+ type: "image",
180
+ source: { type: "base64", media_type: "image/png", data: "aGVsbG8=" },
181
+ },
182
+ ]);
183
+
184
+ insertMessage(
185
+ raw,
186
+ "slack-plain",
187
+ wrappedPlain,
188
+ slackEnvelope("1700000000.000000"),
189
+ );
190
+ insertMessage(
191
+ raw,
192
+ "slack-json",
193
+ jsonContent,
194
+ slackEnvelope("1700000001.000000"),
195
+ );
196
+ insertMessage(
197
+ raw,
198
+ "non-slack",
199
+ nonSlackWrapped,
200
+ JSON.stringify({ userMessageChannel: "web" }),
201
+ );
202
+ insertMessage(raw, "guardian-raw", "guardian Slack text", guardianMetadata);
203
+ insertMessage(
204
+ raw,
205
+ "live-raw-missing-provenance",
206
+ "live raw Slack text",
207
+ liveRawMissingProvenanceMetadata,
208
+ );
209
+ insertMessage(
210
+ raw,
211
+ "legacy-guardian-raw",
212
+ "legacy guardian Slack text",
213
+ legacyGuardianMetadata,
214
+ );
215
+ insertMessage(
216
+ raw,
217
+ "legacy-guardian-json",
218
+ legacyGuardianJsonContent,
219
+ slackBackfillEnvelope("1700000005.000000"),
220
+ );
221
+ insertMessage(
222
+ raw,
223
+ "ambiguous-attachment-only",
224
+ ambiguousAttachmentOnlyContent,
225
+ ambiguousAttachmentOnlyMetadata,
226
+ );
227
+
228
+ migrateNormalizeSlackExternalContent(db);
229
+ const afterFirstRun = getRows(raw);
230
+ migrateNormalizeSlackExternalContent(db);
231
+ const afterSecondRun = getRows(raw);
232
+
233
+ expect(afterSecondRun).toEqual(afterFirstRun);
234
+
235
+ expect(afterFirstRun["slack-plain"]?.content).toBe("plain Slack text");
236
+ const plainMetadata = JSON.parse(
237
+ afterFirstRun["slack-plain"]!.metadata!,
238
+ ) as Record<string, unknown>;
239
+ expect(plainMetadata.provenanceTrustClass).toBe("unknown");
240
+ expect(typeof plainMetadata.slackMeta).toBe("string");
241
+
242
+ const normalizedBlocks = JSON.parse(afterFirstRun["slack-json"]!.content);
243
+ expect(normalizedBlocks).toEqual([
244
+ { type: "text", text: "block Slack text" },
245
+ {
246
+ type: "image",
247
+ source: { type: "base64", media_type: "image/png", data: "aGVsbG8=" },
248
+ },
249
+ { type: "file", source: { type: "file_id", file_id: "file_1" } },
250
+ ]);
251
+ const jsonMetadata = JSON.parse(
252
+ afterFirstRun["slack-json"]!.metadata!,
253
+ ) as Record<string, unknown>;
254
+ expect(jsonMetadata.provenanceTrustClass).toBe("unknown");
255
+
256
+ expect(afterFirstRun["non-slack"]?.content).toBe(nonSlackWrapped);
257
+ expect(afterFirstRun["non-slack"]?.metadata).toBe(
258
+ JSON.stringify({ userMessageChannel: "web" }),
259
+ );
260
+
261
+ expect(afterFirstRun["guardian-raw"]?.content).toBe("guardian Slack text");
262
+ expect(afterFirstRun["guardian-raw"]?.metadata).toBe(guardianMetadata);
263
+
264
+ expect(afterFirstRun["live-raw-missing-provenance"]?.content).toBe(
265
+ "live raw Slack text",
266
+ );
267
+ expect(afterFirstRun["live-raw-missing-provenance"]?.metadata).toBe(
268
+ liveRawMissingProvenanceMetadata,
269
+ );
270
+
271
+ expect(afterFirstRun["legacy-guardian-raw"]?.content).toBe(
272
+ "legacy guardian Slack text",
273
+ );
274
+ const legacyGuardianMetadataAfter = JSON.parse(
275
+ afterFirstRun["legacy-guardian-raw"]!.metadata!,
276
+ ) as Record<string, unknown>;
277
+ expect(legacyGuardianMetadataAfter.provenanceTrustClass).toBe("guardian");
278
+ expect(legacyGuardianMetadataAfter.provenanceSourceChannel).toBe("slack");
279
+
280
+ expect(afterFirstRun["legacy-guardian-json"]?.content).toBe(
281
+ legacyGuardianJsonContent,
282
+ );
283
+ const legacyGuardianJsonMetadataAfter = JSON.parse(
284
+ afterFirstRun["legacy-guardian-json"]!.metadata!,
285
+ ) as Record<string, unknown>;
286
+ expect(legacyGuardianJsonMetadataAfter.provenanceTrustClass).toBe(
287
+ "guardian",
288
+ );
289
+
290
+ expect(afterFirstRun["ambiguous-attachment-only"]?.content).toBe(
291
+ ambiguousAttachmentOnlyContent,
292
+ );
293
+ expect(afterFirstRun["ambiguous-attachment-only"]?.metadata).toBe(
294
+ ambiguousAttachmentOnlyMetadata,
295
+ );
296
+
297
+ expect(getFtsContent(raw, "slack-plain")).toBe("plain Slack text");
298
+ expect(getFtsContent(raw, "slack-json")).toContain("block Slack text");
299
+ expect(getFtsContent(raw, "slack-json")).not.toContain("<external_content");
300
+ });
301
+ });
@@ -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 () => {