@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
@@ -0,0 +1,159 @@
1
+ import {
2
+ existsSync,
3
+ mkdirSync,
4
+ mkdtempSync,
5
+ readFileSync,
6
+ rmSync,
7
+ writeFileSync,
8
+ } from "node:fs";
9
+ import { tmpdir } from "node:os";
10
+ import { join } from "node:path";
11
+ import { gzipSync } from "node:zlib";
12
+ import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
13
+
14
+ const mockExecSync = mock(() => {});
15
+
16
+ mock.module("node:child_process", () => ({
17
+ execSync: mockExecSync,
18
+ }));
19
+
20
+ import { loadSkillCatalog } from "../config/skills.js";
21
+ import { installSkillLocally } from "../skills/catalog-install.js";
22
+ import { installExternalSkill } from "../skills/skillssh-registry.js";
23
+ import { makeTar } from "./helpers/tar-fixtures.js";
24
+
25
+ const originalWorkspaceDir = process.env.VELLUM_WORKSPACE_DIR;
26
+ const originalFetch = globalThis.fetch;
27
+
28
+ let workspaceDir: string;
29
+
30
+ function skillMarkdown(name: string, body: string): string {
31
+ return `---
32
+ name: "${name}"
33
+ description: "A test skill."
34
+ ---
35
+
36
+ ${body}
37
+ `;
38
+ }
39
+
40
+ function writeInstalledSkill(skillId: string, name: string): void {
41
+ const skillDir = join(workspaceDir, "skills", skillId);
42
+ mkdirSync(skillDir, { recursive: true });
43
+ writeFileSync(join(skillDir, "SKILL.md"), skillMarkdown(name, "Old body."));
44
+ writeFileSync(join(skillDir, "old.txt"), "keep me\n");
45
+ }
46
+
47
+ beforeEach(() => {
48
+ workspaceDir = mkdtempSync(join(tmpdir(), "skills-install-staging-"));
49
+ process.env.VELLUM_WORKSPACE_DIR = workspaceDir;
50
+ mkdirSync(join(workspaceDir, "skills"), { recursive: true });
51
+ mockExecSync.mockReset();
52
+ mockExecSync.mockImplementation(() => {
53
+ throw new Error("dependency install failed");
54
+ });
55
+ });
56
+
57
+ afterEach(() => {
58
+ globalThis.fetch = originalFetch;
59
+ if (originalWorkspaceDir === undefined) {
60
+ delete process.env.VELLUM_WORKSPACE_DIR;
61
+ } else {
62
+ process.env.VELLUM_WORKSPACE_DIR = originalWorkspaceDir;
63
+ }
64
+ rmSync(workspaceDir, { recursive: true, force: true });
65
+ });
66
+
67
+ describe("staged skill installs", () => {
68
+ test("catalog dependency failure does not leave a discoverable fresh install", async () => {
69
+ const archive = gzipSync(
70
+ makeTar([
71
+ {
72
+ name: "SKILL.md",
73
+ content: skillMarkdown("Demo Skill", "New body."),
74
+ },
75
+ {
76
+ name: "package.json",
77
+ content: JSON.stringify({ dependencies: { example: "1.0.0" } }),
78
+ },
79
+ ]),
80
+ );
81
+ globalThis.fetch = mock(
82
+ async () => new Response(archive),
83
+ ) as unknown as typeof fetch;
84
+
85
+ await expect(
86
+ installSkillLocally(
87
+ "demo-skill",
88
+ {
89
+ id: "demo-skill",
90
+ name: "Demo Skill",
91
+ description: "A test skill.",
92
+ },
93
+ false,
94
+ ),
95
+ ).rejects.toThrow("dependency install failed");
96
+
97
+ expect(
98
+ existsSync(join(workspaceDir, "skills", "demo-skill", "SKILL.md")),
99
+ ).toBe(false);
100
+ expect(loadSkillCatalog().some((skill) => skill.id === "demo-skill")).toBe(
101
+ false,
102
+ );
103
+ });
104
+
105
+ test("skills.sh overwrite dependency failure preserves the previous skill", async () => {
106
+ writeInstalledSkill("demo-skill", "Old Demo Skill");
107
+
108
+ globalThis.fetch = mock(async (input: string | URL | Request) => {
109
+ const url = String(input);
110
+ if (url.includes("/contents/skills/demo-skill")) {
111
+ return new Response(
112
+ JSON.stringify([
113
+ {
114
+ name: "SKILL.md",
115
+ type: "file",
116
+ download_url: "https://example.com/SKILL.md",
117
+ },
118
+ {
119
+ name: "package.json",
120
+ type: "file",
121
+ download_url: "https://example.com/package.json",
122
+ },
123
+ {
124
+ name: "new.txt",
125
+ type: "file",
126
+ download_url: "https://example.com/new.txt",
127
+ },
128
+ ]),
129
+ );
130
+ }
131
+ if (url.endsWith("/SKILL.md")) {
132
+ return new Response(skillMarkdown("New Demo Skill", "New body."));
133
+ }
134
+ if (url.endsWith("/package.json")) {
135
+ return new Response(
136
+ JSON.stringify({ dependencies: { example: "1.0.0" } }),
137
+ );
138
+ }
139
+ if (url.endsWith("/new.txt")) {
140
+ return new Response("new file\n");
141
+ }
142
+ return new Response("not found", { status: 404 });
143
+ }) as unknown as typeof fetch;
144
+
145
+ await expect(
146
+ installExternalSkill("owner", "repo", "demo-skill", true),
147
+ ).rejects.toThrow("dependency install failed");
148
+
149
+ const skillDir = join(workspaceDir, "skills", "demo-skill");
150
+ expect(readFileSync(join(skillDir, "SKILL.md"), "utf-8")).toContain(
151
+ "Old Demo Skill",
152
+ );
153
+ expect(readFileSync(join(skillDir, "old.txt"), "utf-8")).toBe("keep me\n");
154
+ expect(existsSync(join(skillDir, "new.txt"))).toBe(false);
155
+ expect(
156
+ loadSkillCatalog().find((skill) => skill.id === "demo-skill")?.name,
157
+ ).toBe("Old Demo Skill");
158
+ });
159
+ });
@@ -53,81 +53,49 @@ afterEach(() => {
53
53
  });
54
54
 
55
55
  describe("assistant skills uninstall", () => {
56
- test("removes skill directory and SKILLS.md entry", () => {
57
- /**
58
- * Tests the happy path for uninstalling a skill.
59
- */
60
-
61
- // GIVEN a skill is installed locally
56
+ test("removes skill directory while leaving a stale SKILLS.md index unchanged", () => {
62
57
  installFakeSkill("weather");
63
- writeSkillsIndex("- weather\n- vellum-self-knowledge\n");
58
+ const originalIndex = "- weather\n- vellum-self-knowledge\n";
59
+ writeSkillsIndex(originalIndex);
64
60
 
65
- // WHEN we uninstall the skill
66
61
  uninstallSkillLocally("weather");
67
62
 
68
- // THEN the skill directory should be removed
69
63
  expect(existsSync(join(getSkillsDir(), "weather"))).toBe(false);
70
64
 
71
- // AND the SKILLS.md entry should be removed
72
65
  const index = readFileSync(getSkillsIndexPath(), "utf-8");
73
- expect(index).not.toContain("weather");
74
-
75
- // AND other skills should remain in the index
76
- expect(index).toContain("vellum-self-knowledge");
66
+ expect(index).toBe(originalIndex);
77
67
  });
78
68
 
79
69
  test("errors when skill is not installed", () => {
80
- /**
81
- * Tests that uninstalling a non-existent skill throws an error.
82
- */
83
-
84
- // GIVEN no skills are installed
85
- // WHEN we try to uninstall a nonexistent skill
86
- // THEN it should throw an error
87
70
  expect(() => uninstallSkillLocally("nonexistent")).toThrow(
88
71
  'Skill "nonexistent" is not installed.',
89
72
  );
90
73
  });
91
74
 
92
- test("works when SKILLS.md does not exist", () => {
93
- /**
94
- * Tests that uninstall works even if the SKILLS.md index file is missing.
95
- */
96
-
97
- // GIVEN a skill directory exists but no SKILLS.md
75
+ test("works when no stale SKILLS.md index exists", () => {
98
76
  installFakeSkill("weather");
99
77
 
100
- // WHEN we uninstall the skill
101
78
  uninstallSkillLocally("weather");
102
79
 
103
- // THEN the skill directory should be removed
104
80
  expect(existsSync(join(getSkillsDir(), "weather"))).toBe(false);
105
81
 
106
- // AND no SKILLS.md should have been created
107
82
  expect(existsSync(getSkillsIndexPath())).toBe(false);
108
83
  });
109
84
 
110
- test("removes skill with nested files", () => {
111
- /**
112
- * Tests that uninstall recursively removes skills with nested directories.
113
- */
114
-
115
- // GIVEN a skill with nested files is installed
85
+ test("removes skill with nested files while leaving a stale SKILLS.md index unchanged", () => {
116
86
  const skillDir = join(getSkillsDir(), "weather");
117
87
  mkdirSync(join(skillDir, "scripts", "lib"), { recursive: true });
118
88
  writeFileSync(join(skillDir, "SKILL.md"), "# weather\n");
119
89
  writeFileSync(join(skillDir, "scripts", "fetch.sh"), "#!/bin/bash\n");
120
90
  writeFileSync(join(skillDir, "scripts", "lib", "utils.sh"), "# utils\n");
121
- writeSkillsIndex("- weather\n");
91
+ const originalIndex = "- weather\n";
92
+ writeSkillsIndex(originalIndex);
122
93
 
123
- // WHEN we uninstall the skill
124
94
  uninstallSkillLocally("weather");
125
95
 
126
- // THEN the entire skill directory tree should be removed
127
96
  expect(existsSync(skillDir)).toBe(false);
128
97
 
129
- // AND the SKILLS.md entry should be removed
130
98
  const index = readFileSync(getSkillsIndexPath(), "utf-8");
131
- expect(index).not.toContain("weather");
99
+ expect(index).toBe(originalIndex);
132
100
  });
133
101
  });
@@ -67,42 +67,62 @@ describe("skills catalog loading", () => {
67
67
  rmSync(skillsDir, { recursive: true, force: true });
68
68
  });
69
69
 
70
- test("parses markdown list path entries from SKILLS.md", () => {
71
- writeSkill("alpha", "Alpha Skill", "First skill");
72
- writeSkill("beta", "Beta Skill", "Second skill");
73
- writeFileSync(
74
- join(TEST_DIR, "skills", "SKILLS.md"),
75
- "- alpha\n- beta/SKILL.md\n",
76
- );
70
+ test("discovers valid skill directories alphabetically", () => {
71
+ writeSkill("zeta", "Zeta Skill", "Zeta");
72
+ writeSkill("alpha", "Alpha Skill", "Alpha");
77
73
 
78
74
  const catalog = loadUserSkillCatalog();
79
- expect(catalog.map((skill) => skill.id)).toEqual(["alpha", "beta"]);
75
+ expect(catalog.map((skill) => skill.id)).toEqual(["alpha", "zeta"]);
80
76
  });
81
77
 
82
- test("resolves markdown links from SKILLS.md", () => {
83
- writeSkill("lint", "Lint Skill", "Runs lint checks");
84
- writeSkill("test", "Test Skill", "Runs test checks");
78
+ test("ignores stale SKILLS.md while discovering valid skill directories", () => {
79
+ writeSkill("first", "First Skill", "First");
80
+ writeSkill("second", "Second Skill", "Second");
85
81
  writeFileSync(
86
82
  join(TEST_DIR, "skills", "SKILLS.md"),
87
- "- [Lint](lint)\n- [Tests](test)\n",
83
+ "- second\n- missing\n",
88
84
  );
89
85
 
90
86
  const catalog = loadUserSkillCatalog();
91
- expect(catalog.map((skill) => skill.id)).toEqual(["lint", "test"]);
87
+ expect(catalog.map((skill) => skill.id)).toEqual(["first", "second"]);
88
+ });
89
+
90
+ test("managed skill overrides bundled skill with the same id", () => {
91
+ writeSkill(
92
+ "skill-management",
93
+ "Custom Skill Management",
94
+ "Managed override",
95
+ );
96
+
97
+ const skill = loadSkillCatalog().find((s) => s.id === "skill-management");
98
+ expect(skill).toBeDefined();
99
+ expect(skill!.source).toBe("managed");
100
+ expect(skill!.name).toBe("Custom Skill Management");
101
+ expect(skill!.bundled).toBeUndefined();
92
102
  });
93
103
 
94
- test("rejects SKILLS.md entries that resolve outside ~/.vellum/workspace/skills", () => {
95
- writeSkill("safe", "Safe Skill", "Safe skill");
104
+ test("discovers symlinked skill directories that point inside ~/.vellum/workspace/skills", () => {
105
+ const internalSkillDir = join(
106
+ TEST_DIR,
107
+ "skills",
108
+ ".linked-targets",
109
+ "internal-skill",
110
+ );
111
+ mkdirSync(internalSkillDir, { recursive: true });
96
112
  writeFileSync(
97
- join(TEST_DIR, "skills", "SKILLS.md"),
98
- "- ../escape\n- /tmp/absolute\n- safe\n",
113
+ join(internalSkillDir, "SKILL.md"),
114
+ '---\nname: "Internal Linked Skill"\ndescription: "Inside skills root."\n---\n\nLoad me.\n',
99
115
  );
100
116
 
117
+ symlinkSync(internalSkillDir, join(TEST_DIR, "skills", "linked-skill"));
118
+
101
119
  const catalog = loadUserSkillCatalog();
102
- expect(catalog.map((skill) => skill.id)).toEqual(["safe"]);
120
+ expect(catalog).toHaveLength(1);
121
+ expect(catalog[0].id).toBe("linked-skill");
122
+ expect(catalog[0].name).toBe("Internal Linked Skill");
103
123
  });
104
124
 
105
- test("rejects symlinked SKILLS.md entries that point outside ~/.vellum/workspace/skills", () => {
125
+ test("does not discover symlinked skill directories that point outside ~/.vellum/workspace/skills", () => {
106
126
  const externalSkillDir = join(TEST_DIR, "outside", "external-skill");
107
127
  mkdirSync(externalSkillDir, { recursive: true });
108
128
  writeFileSync(
@@ -111,7 +131,6 @@ describe("skills catalog loading", () => {
111
131
  );
112
132
 
113
133
  symlinkSync(externalSkillDir, join(TEST_DIR, "skills", "linked-skill"));
114
- writeFileSync(join(TEST_DIR, "skills", "SKILLS.md"), "- linked-skill\n");
115
134
 
116
135
  const catalog = loadUserSkillCatalog();
117
136
  expect(catalog).toHaveLength(0);
@@ -130,35 +149,6 @@ describe("skills catalog loading", () => {
130
149
  );
131
150
 
132
151
  symlinkSync(externalSkillFile, join(linkedSkillDir, "SKILL.md"));
133
- writeFileSync(
134
- join(TEST_DIR, "skills", "SKILLS.md"),
135
- "- linked-file-skill\n",
136
- );
137
-
138
- const catalog = loadUserSkillCatalog();
139
- expect(catalog).toHaveLength(0);
140
- });
141
-
142
- test("uses SKILLS.md ordering when index exists", () => {
143
- writeSkill("first", "First Skill", "First");
144
- writeSkill("second", "Second Skill", "Second");
145
- writeFileSync(join(TEST_DIR, "skills", "SKILLS.md"), "- second\n- first\n");
146
-
147
- const catalog = loadUserSkillCatalog();
148
- expect(catalog.map((skill) => skill.id)).toEqual(["second", "first"]);
149
- });
150
-
151
- test("falls back to auto-discovery when SKILLS.md is missing", () => {
152
- writeSkill("zeta", "Zeta Skill", "Zeta");
153
- writeSkill("alpha", "Alpha Skill", "Alpha");
154
-
155
- const catalog = loadUserSkillCatalog();
156
- expect(catalog.map((skill) => skill.id)).toEqual(["alpha", "zeta"]);
157
- });
158
-
159
- test("treats SKILLS.md as authoritative when present", () => {
160
- writeSkill("available", "Available Skill", "Present on disk");
161
- writeFileSync(join(TEST_DIR, "skills", "SKILLS.md"), "- ../invalid-only\n");
162
152
 
163
153
  const catalog = loadUserSkillCatalog();
164
154
  expect(catalog).toHaveLength(0);
@@ -248,6 +238,18 @@ describe("workspace skills", () => {
248
238
  expect(result.skill!.body).toBe("Full workspace body here");
249
239
  expect(result.skill!.source).toBe("workspace");
250
240
  });
241
+
242
+ test("workspace skill overrides managed skill with the same id", () => {
243
+ writeSkill("shared-id", "Managed Shared", "Managed version");
244
+ writeWorkspaceSkill("shared-id", "Workspace Shared", "Workspace version");
245
+
246
+ const skill = loadSkillCatalog(workspaceSkillsDir).find(
247
+ (s) => s.id === "shared-id",
248
+ );
249
+ expect(skill).toBeDefined();
250
+ expect(skill!.source).toBe("workspace");
251
+ expect(skill!.name).toBe("Workspace Shared");
252
+ });
251
253
  });
252
254
 
253
255
  describe("tool manifest detection", () => {
@@ -449,7 +451,6 @@ describe("includes frontmatter parsing", () => {
449
451
 
450
452
  test("parses valid includes array", () => {
451
453
  writeSkillWithIncludes("parent", '["child-a", "child-b"]');
452
- writeFileSync(join(TEST_DIR, "skills", "SKILLS.md"), "- parent\n");
453
454
  const catalog = loadUserSkillCatalog();
454
455
  const skill = catalog.find((s) => s.id === "parent");
455
456
  expect(skill).toBeDefined();
@@ -458,7 +459,6 @@ describe("includes frontmatter parsing", () => {
458
459
 
459
460
  test("trims whitespace in includes entries", () => {
460
461
  writeSkillWithIncludes("parent", '[" child-a ", " child-b "]');
461
- writeFileSync(join(TEST_DIR, "skills", "SKILLS.md"), "- parent\n");
462
462
  const catalog = loadUserSkillCatalog();
463
463
  const skill = catalog.find((s) => s.id === "parent");
464
464
  expect(skill!.includes).toEqual(["child-a", "child-b"]);
@@ -466,7 +466,6 @@ describe("includes frontmatter parsing", () => {
466
466
 
467
467
  test("removes empty strings from includes", () => {
468
468
  writeSkillWithIncludes("parent", '["child-a", "", " ", "child-b"]');
469
- writeFileSync(join(TEST_DIR, "skills", "SKILLS.md"), "- parent\n");
470
469
  const catalog = loadUserSkillCatalog();
471
470
  const skill = catalog.find((s) => s.id === "parent");
472
471
  expect(skill!.includes).toEqual(["child-a", "child-b"]);
@@ -474,7 +473,6 @@ describe("includes frontmatter parsing", () => {
474
473
 
475
474
  test("deduplicates includes preserving first-seen order", () => {
476
475
  writeSkillWithIncludes("parent", '["child-a", "child-b", "child-a"]');
477
- writeFileSync(join(TEST_DIR, "skills", "SKILLS.md"), "- parent\n");
478
476
  const catalog = loadUserSkillCatalog();
479
477
  const skill = catalog.find((s) => s.id === "parent");
480
478
  expect(skill!.includes).toEqual(["child-a", "child-b"]);
@@ -482,7 +480,6 @@ describe("includes frontmatter parsing", () => {
482
480
 
483
481
  test("returns undefined for invalid JSON", () => {
484
482
  writeSkillWithIncludes("parent", "not-json");
485
- writeFileSync(join(TEST_DIR, "skills", "SKILLS.md"), "- parent\n");
486
483
  const catalog = loadUserSkillCatalog();
487
484
  const skill = catalog.find((s) => s.id === "parent");
488
485
  expect(skill!.includes).toBeUndefined();
@@ -490,7 +487,6 @@ describe("includes frontmatter parsing", () => {
490
487
 
491
488
  test("returns undefined for non-array JSON", () => {
492
489
  writeSkillWithIncludes("parent", '"just-a-string"');
493
- writeFileSync(join(TEST_DIR, "skills", "SKILLS.md"), "- parent\n");
494
490
  const catalog = loadUserSkillCatalog();
495
491
  const skill = catalog.find((s) => s.id === "parent");
496
492
  expect(skill!.includes).toBeUndefined();
@@ -498,7 +494,6 @@ describe("includes frontmatter parsing", () => {
498
494
 
499
495
  test("returns undefined for array with non-string elements", () => {
500
496
  writeSkillWithIncludes("parent", "[123, true]");
501
- writeFileSync(join(TEST_DIR, "skills", "SKILLS.md"), "- parent\n");
502
497
  const catalog = loadUserSkillCatalog();
503
498
  const skill = catalog.find((s) => s.id === "parent");
504
499
  expect(skill!.includes).toBeUndefined();
@@ -506,7 +501,6 @@ describe("includes frontmatter parsing", () => {
506
501
 
507
502
  test("returns undefined for empty array", () => {
508
503
  writeSkillWithIncludes("parent", "[]");
509
- writeFileSync(join(TEST_DIR, "skills", "SKILLS.md"), "- parent\n");
510
504
  const catalog = loadUserSkillCatalog();
511
505
  const skill = catalog.find((s) => s.id === "parent");
512
506
  expect(skill!.includes).toBeUndefined();
@@ -514,7 +508,6 @@ describe("includes frontmatter parsing", () => {
514
508
 
515
509
  test("skill without includes has undefined includes", () => {
516
510
  writeSkill("no-includes", "No Includes", "Test");
517
- writeFileSync(join(TEST_DIR, "skills", "SKILLS.md"), "- no-includes\n");
518
511
  const catalog = loadUserSkillCatalog();
519
512
  const skill = catalog.find((s) => s.id === "no-includes");
520
513
  expect(skill!.includes).toBeUndefined();
@@ -48,6 +48,9 @@ mock.module("../config/loader.js", () => ({
48
48
  teamName:
49
49
  ((configStore.slack as Record<string, unknown>)?.teamName as string) ??
50
50
  "",
51
+ teamUrl:
52
+ ((configStore.slack as Record<string, unknown>)?.teamUrl as string) ??
53
+ "",
51
54
  botUserId:
52
55
  ((configStore.slack as Record<string, unknown>)?.botUserId as string) ??
53
56
  "",
@@ -277,6 +280,7 @@ describe("Slack channel config handler", () => {
277
280
  slack: {
278
281
  teamId: "T123",
279
282
  teamName: "TestTeam",
283
+ teamUrl: "https://example.slack.com/",
280
284
  botUserId: "U_BOT",
281
285
  botUsername: "testbot",
282
286
  },
@@ -285,6 +289,7 @@ describe("Slack channel config handler", () => {
285
289
  const result = await getSlackChannelConfig();
286
290
  expect(result.teamId).toBe("T123");
287
291
  expect(result.teamName).toBe("TestTeam");
292
+ expect(result.teamUrl).toBe("https://example.slack.com/");
288
293
  expect(result.botUserId).toBe("U_BOT");
289
294
  expect(result.botUsername).toBe("testbot");
290
295
  });
@@ -314,6 +319,7 @@ describe("Slack channel config handler", () => {
314
319
  ok: true,
315
320
  team_id: "T_TEAM",
316
321
  team: "MyTeam",
322
+ url: "https://myteam.slack.com/",
317
323
  user_id: "U_BOT",
318
324
  user: "mybot",
319
325
  }),
@@ -329,11 +335,13 @@ describe("Slack channel config handler", () => {
329
335
  expect(result.hasBotToken).toBe(true);
330
336
  expect(result.teamId).toBe("T_TEAM");
331
337
  expect(result.teamName).toBe("MyTeam");
338
+ expect(result.teamUrl).toBe("https://myteam.slack.com/");
332
339
 
333
340
  // Assert metadata was written to config (not credential metadata)
334
341
  const slack = configStore.slack as Record<string, unknown>;
335
342
  expect(slack.teamId).toBe("T_TEAM");
336
343
  expect(slack.teamName).toBe("MyTeam");
344
+ expect(slack.teamUrl).toBe("https://myteam.slack.com/");
337
345
  expect(slack.botUserId).toBe("U_BOT");
338
346
  expect(slack.botUsername).toBe("mybot");
339
347
  });
@@ -397,6 +405,7 @@ describe("Slack channel config handler", () => {
397
405
  const slack = configStore.slack as Record<string, unknown>;
398
406
  expect(slack.teamId).toBe("");
399
407
  expect(slack.teamName).toBe("");
408
+ expect(slack.teamUrl).toBe("");
400
409
  expect(slack.botUserId).toBe("");
401
410
  expect(slack.botUsername).toBe("");
402
411
  });
@@ -68,4 +68,54 @@ describe("subagent-only tool filtering", () => {
68
68
  // A regular tool not in SUBAGENT_ONLY_TOOL_NAMES should still be active
69
69
  expect(isToolActiveForContext("bash", ctx)).toBe(true);
70
70
  });
71
+
72
+ test("respects subagentAllowedTools — tools outside the allowlist are inactive", () => {
73
+ // Mirrors `createResolveToolsCallback`'s post-filter so callers see the
74
+ // same final tool set the LLM receives.
75
+ const ctx: SkillProjectionContext = {
76
+ skillProjectionState: new Map(),
77
+ skillProjectionCache: {},
78
+ coreToolNames: new Set(),
79
+ toolsDisabledDepth: 0,
80
+ hasNoClient: false,
81
+ isSubagent: true,
82
+ subagentAllowedTools: new Set(["bash"]),
83
+ };
84
+
85
+ expect(isToolActiveForContext("bash", ctx)).toBe(true);
86
+ expect(isToolActiveForContext("ask_question", ctx)).toBe(false);
87
+ expect(isToolActiveForContext(TEST_TOOL_NAME, ctx)).toBe(false);
88
+ });
89
+
90
+ test("returns false for every tool when toolsDisabledDepth > 0", () => {
91
+ // `createResolveToolsCallback` returns an empty tool list when tools are
92
+ // disabled; mirror it here so this helper reports the same final tool set.
93
+ const ctx: SkillProjectionContext = {
94
+ skillProjectionState: new Map(),
95
+ skillProjectionCache: {},
96
+ coreToolNames: new Set(),
97
+ toolsDisabledDepth: 1,
98
+ hasNoClient: false,
99
+ };
100
+
101
+ expect(isToolActiveForContext("bash", ctx)).toBe(false);
102
+ expect(isToolActiveForContext("ask_question", ctx)).toBe(false);
103
+ });
104
+
105
+ test("under disk-pressure cleanup mode, only cleanup-safe tools are active", () => {
106
+ // `createResolveToolsCallback` restricts the turn to cleanup-safe tools
107
+ // (`file_remove`, `bash`, etc.); ensure the helper agrees.
108
+ const ctx: SkillProjectionContext = {
109
+ skillProjectionState: new Map(),
110
+ skillProjectionCache: {},
111
+ coreToolNames: new Set(),
112
+ toolsDisabledDepth: 0,
113
+ hasNoClient: false,
114
+ diskPressureCleanupModeActive: true,
115
+ };
116
+
117
+ // `bash` is in DISK_PRESSURE_CLEANUP_TOOL_NAMES; `ask_question` is not.
118
+ expect(isToolActiveForContext("bash", ctx)).toBe(true);
119
+ expect(isToolActiveForContext("ask_question", ctx)).toBe(false);
120
+ });
71
121
  });