@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
@@ -1223,7 +1223,14 @@ describe("getUsageGroupedSeries", () => {
1223
1223
  describe("queryUnreportedUsageEvents", () => {
1224
1224
  beforeEach(() => {
1225
1225
  const db = getDb();
1226
+ // Order matters: clear `llm_usage_events` (no FK), then `messages`
1227
+ // (FK to conversations cascades, but be explicit), then
1228
+ // `conversations`. The conversation-level metadata tests below
1229
+ // depend on a clean conversations + messages slate so JOINs are
1230
+ // deterministic.
1226
1231
  db.run(`DELETE FROM llm_usage_events`);
1232
+ db.run(`DELETE FROM messages`);
1233
+ db.run(`DELETE FROM conversations`);
1227
1234
  });
1228
1235
 
1229
1236
  test("returns events with createdAt strictly greater than afterCreatedAt in ascending order", () => {
@@ -1277,4 +1284,111 @@ describe("queryUnreportedUsageEvents", () => {
1277
1284
  const events = queryUnreportedUsageEvents(0, undefined, 100);
1278
1285
  expect(events).toHaveLength(0);
1279
1286
  });
1287
+
1288
+ // -------------------------------------------------------------------------
1289
+ // Conversation-level metadata (conversationType + turnIndex). These are
1290
+ // JOIN-computed at telemetry-query time so the reporter can emit them on
1291
+ // the wire without persisting extra columns on `llm_usage_events`.
1292
+ // -------------------------------------------------------------------------
1293
+
1294
+ test("conversationType is JOINed from the conversations table", () => {
1295
+ const db = getDb();
1296
+ const now = Date.now();
1297
+ db.run(
1298
+ `INSERT INTO conversations (id, conversation_type, created_at, updated_at) VALUES ('conv-std', 'standard', ${now}, ${now})`,
1299
+ );
1300
+ db.run(
1301
+ `INSERT INTO conversations (id, conversation_type, created_at, updated_at) VALUES ('conv-bg', 'background', ${now}, ${now})`,
1302
+ );
1303
+
1304
+ insertEventAt(1000, { conversationId: "conv-std" });
1305
+ insertEventAt(2000, { conversationId: "conv-bg" });
1306
+ insertEventAt(3000, { conversationId: null });
1307
+
1308
+ const events = queryUnreportedUsageEvents(0, undefined, 100);
1309
+ expect(events).toHaveLength(3);
1310
+ expect(events[0].conversationType).toBe("standard");
1311
+ expect(events[1].conversationType).toBe("background");
1312
+ // LLM calls without a parent conversation get null — LEFT JOIN, not INNER.
1313
+ expect(events[2].conversationType).toBeNull();
1314
+ });
1315
+
1316
+ test("turnIndex counts real user turns up to the LLM call's createdAt", () => {
1317
+ const db = getDb();
1318
+ const now = Date.now();
1319
+ db.run(
1320
+ `INSERT INTO conversations (id, conversation_type, created_at, updated_at) VALUES ('conv-1', 'standard', ${now}, ${now})`,
1321
+ );
1322
+ // Two user messages in the conversation.
1323
+ db.run(
1324
+ `INSERT INTO messages (id, conversation_id, role, content, created_at) VALUES ('m1', 'conv-1', 'user', 'hello', 1000)`,
1325
+ );
1326
+ db.run(
1327
+ `INSERT INTO messages (id, conversation_id, role, content, created_at) VALUES ('m2', 'conv-1', 'user', 'follow up', 3000)`,
1328
+ );
1329
+
1330
+ // Three LLM calls across the timeline:
1331
+ // - mid-turn-1 (between m1 and m2)
1332
+ // - exactly at m2's createdAt (still counts m2: `created_at <= e.created_at`)
1333
+ // - after m2
1334
+ insertEventAt(2000, { conversationId: "conv-1" });
1335
+ insertEventAt(3000, { conversationId: "conv-1" });
1336
+ insertEventAt(4000, { conversationId: "conv-1" });
1337
+
1338
+ const events = queryUnreportedUsageEvents(0, undefined, 100);
1339
+ expect(events).toHaveLength(3);
1340
+ expect(events[0].turnIndex).toBe(1);
1341
+ expect(events[1].turnIndex).toBe(2);
1342
+ expect(events[2].turnIndex).toBe(2);
1343
+ });
1344
+
1345
+ test("turnIndex is null when the LLM call has no conversationId", () => {
1346
+ insertEventAt(1000, { conversationId: null });
1347
+
1348
+ const events = queryUnreportedUsageEvents(0, undefined, 100);
1349
+ expect(events).toHaveLength(1);
1350
+ expect(events[0].conversationId).toBeNull();
1351
+ expect(events[0].turnIndex).toBeNull();
1352
+ });
1353
+
1354
+ test("turnIndex skips tool_result rows when counting", () => {
1355
+ const db = getDb();
1356
+ const now = Date.now();
1357
+ db.run(
1358
+ `INSERT INTO conversations (id, conversation_type, created_at, updated_at) VALUES ('conv-tr', 'standard', ${now}, ${now})`,
1359
+ );
1360
+ // One real user turn, then a tool_result row (which should be
1361
+ // ignored), then the LLM call. Expected turn_index = 1.
1362
+ db.run(
1363
+ `INSERT INTO messages (id, conversation_id, role, content, created_at) VALUES ('real-1', 'conv-tr', 'user', 'real text', 1000)`,
1364
+ );
1365
+ db.run(
1366
+ `INSERT INTO messages (id, conversation_id, role, content, created_at) VALUES ('tool-1', 'conv-tr', 'user', '[{"type":"tool_result","tool_use_id":"x","content":""}]', 1500)`,
1367
+ );
1368
+ insertEventAt(2000, { conversationId: "conv-tr" });
1369
+
1370
+ const events = queryUnreportedUsageEvents(0, undefined, 100);
1371
+ expect(events).toHaveLength(1);
1372
+ expect(events[0].turnIndex).toBe(1);
1373
+ });
1374
+
1375
+ test("turnIndex is 0 when the LLM call fires before any user message", () => {
1376
+ const db = getDb();
1377
+ const now = Date.now();
1378
+ db.run(
1379
+ `INSERT INTO conversations (id, conversation_type, created_at, updated_at) VALUES ('conv-early', 'standard', ${now}, ${now})`,
1380
+ );
1381
+ // LLM call fires at t=1000; first user message is at t=2000.
1382
+ insertEventAt(1000, { conversationId: "conv-early" });
1383
+ db.run(
1384
+ `INSERT INTO messages (id, conversation_id, role, content, created_at) VALUES ('later', 'conv-early', 'user', 'hi', 2000)`,
1385
+ );
1386
+
1387
+ const events = queryUnreportedUsageEvents(0, undefined, 100);
1388
+ expect(events).toHaveLength(1);
1389
+ // Conversation exists but no user turn has fired yet. The CASE
1390
+ // short-circuits only on null conversationId, so we get a real 0.
1391
+ // Analytics can treat 0 as "pre-first-turn" if needed.
1392
+ expect(events[0].turnIndex).toBe(0);
1393
+ });
1280
1394
  });
@@ -67,6 +67,8 @@ mock.module("../config/loader.js", () => ({
67
67
  },
68
68
  getConfig: () => rawConfig,
69
69
  invalidateConfigCache: () => {},
70
+ withSuppressedConfigDiskWrites: async (fn: () => unknown) => fn(),
71
+ withSuppressedConfigDiskWritesSync: (fn: () => unknown) => fn(),
70
72
  }));
71
73
 
72
74
  mock.module("../providers/registry.js", () => ({
@@ -96,39 +98,39 @@ beforeEach(() => {
96
98
  // ---------------------------------------------------------------------------
97
99
 
98
100
  describe("PUT /v1/config/llm/profiles/:name — managed profile guard", () => {
99
- test("rejects edits to quality-optimized that touch non-label/status fields", () => {
100
- expect(() =>
101
+ test("rejects edits to quality-optimized that touch non-label/status fields", async () => {
102
+ await expect(
101
103
  replaceRoute.handler({
102
104
  pathParams: { name: "quality-optimized" },
103
105
  body: { provider: "openai", model: "gpt-4o" },
104
106
  }),
105
- ).toThrow(
107
+ ).rejects.toThrow(
106
108
  'Cannot edit managed profile "quality-optimized" fields [provider, model]. ' +
107
109
  "Only label and status may be edited; duplicate to a custom profile to change other fields.",
108
110
  );
109
111
  });
110
112
 
111
- test("rejects edits to balanced", () => {
112
- expect(() =>
113
+ test("rejects edits to balanced", async () => {
114
+ await expect(
113
115
  replaceRoute.handler({
114
116
  pathParams: { name: "balanced" },
115
117
  body: { provider: "openai", model: "gpt-4o" },
116
118
  }),
117
- ).toThrow(BadRequestError);
119
+ ).rejects.toThrow(BadRequestError);
118
120
  });
119
121
 
120
- test("rejects edits to cost-optimized", () => {
121
- expect(() =>
122
+ test("rejects edits to cost-optimized", async () => {
123
+ await expect(
122
124
  replaceRoute.handler({
123
125
  pathParams: { name: "cost-optimized" },
124
126
  body: { provider: "openai", model: "gpt-4o" },
125
127
  }),
126
- ).toThrow(BadRequestError);
128
+ ).rejects.toThrow(BadRequestError);
127
129
  });
128
130
 
129
- test("allows edits to custom-balanced (user-owned)", () => {
131
+ test("allows edits to custom-balanced (user-owned)", async () => {
130
132
  savedRaw = null;
131
- const result = replaceRoute.handler({
133
+ const result = await replaceRoute.handler({
132
134
  pathParams: { name: "custom-balanced" },
133
135
  body: { provider: "openai", model: "gpt-4o" },
134
136
  });
@@ -136,9 +138,9 @@ describe("PUT /v1/config/llm/profiles/:name — managed profile guard", () => {
136
138
  expect(savedRaw).not.toBeNull();
137
139
  });
138
140
 
139
- test("allows edits to a user-defined profile", () => {
141
+ test("allows edits to a user-defined profile", async () => {
140
142
  savedRaw = null;
141
- const result = replaceRoute.handler({
143
+ const result = await replaceRoute.handler({
142
144
  pathParams: { name: "my-custom" },
143
145
  body: { provider: "openai", model: "gpt-4o" },
144
146
  });
@@ -147,14 +149,14 @@ describe("PUT /v1/config/llm/profiles/:name — managed profile guard", () => {
147
149
  });
148
150
 
149
151
  // -------------------------------------------------------------------------
150
- // Null-as-clear sentinel: `patchManagedProfileFields` has handled `null`
151
- // for label and status since #30362, but the Zod `ProfileEntry` schema
152
- // had them as `.optional()` (not `.nullable()`), which rejected null
153
- // payloads at parse time before the route handler ever saw them.
154
- // These tests lock the round-trip now that the schema accepts null.
152
+ // Null-as-clear sentinel: clients send `{ label: null }` or
153
+ // `{ status: null }` to clear a managed profile's overrides back to the
154
+ // seed defaults. The Zod `ProfileEntry` schema accepts null for both
155
+ // fields, and the managed-profile guard / `patchManagedProfileFields`
156
+ // propagate the clear through to disk. These tests lock the round-trip.
155
157
  // -------------------------------------------------------------------------
156
158
 
157
- test("PUT { label: null } on managed profile clears the label on disk", () => {
159
+ test("PUT { label: null } on managed profile clears the label on disk", async () => {
158
160
  savedRaw = null;
159
161
  rawConfig = {
160
162
  llm: {
@@ -168,7 +170,7 @@ describe("PUT /v1/config/llm/profiles/:name — managed profile guard", () => {
168
170
  },
169
171
  },
170
172
  };
171
- const result = replaceRoute.handler({
173
+ const result = await replaceRoute.handler({
172
174
  pathParams: { name: "balanced" },
173
175
  body: { label: null },
174
176
  });
@@ -182,7 +184,7 @@ describe("PUT /v1/config/llm/profiles/:name — managed profile guard", () => {
182
184
  expect(profile.source).toBe("managed");
183
185
  });
184
186
 
185
- test("PUT { status: null } on managed profile clears status (back to active-by-absence)", () => {
187
+ test("PUT { status: null } on managed profile clears status (back to active-by-absence)", async () => {
186
188
  savedRaw = null;
187
189
  rawConfig = {
188
190
  llm: {
@@ -196,7 +198,7 @@ describe("PUT /v1/config/llm/profiles/:name — managed profile guard", () => {
196
198
  },
197
199
  },
198
200
  };
199
- const result = replaceRoute.handler({
201
+ const result = await replaceRoute.handler({
200
202
  pathParams: { name: "quality-optimized" },
201
203
  body: { status: null },
202
204
  });
@@ -208,7 +210,7 @@ describe("PUT /v1/config/llm/profiles/:name — managed profile guard", () => {
208
210
  expect(profile.model).toBe("claude-opus");
209
211
  });
210
212
 
211
- test("PUT { label: null, status: null } clears both in a single request", () => {
213
+ test("PUT { label: null, status: null } clears both in a single request", async () => {
212
214
  savedRaw = null;
213
215
  rawConfig = {
214
216
  llm: {
@@ -223,7 +225,7 @@ describe("PUT /v1/config/llm/profiles/:name — managed profile guard", () => {
223
225
  },
224
226
  },
225
227
  };
226
- const result = replaceRoute.handler({
228
+ const result = await replaceRoute.handler({
227
229
  pathParams: { name: "cost-optimized" },
228
230
  body: { label: null, status: null },
229
231
  });
@@ -236,7 +238,7 @@ describe("PUT /v1/config/llm/profiles/:name — managed profile guard", () => {
236
238
  expect(profile.model).toBe("claude-haiku");
237
239
  });
238
240
 
239
- test("PUT { label: null, status: 'disabled' } mixes clear + set in one call", () => {
241
+ test("PUT { label: null, status: 'disabled' } mixes clear + set in one call", async () => {
240
242
  savedRaw = null;
241
243
  rawConfig = {
242
244
  llm: {
@@ -250,7 +252,7 @@ describe("PUT /v1/config/llm/profiles/:name — managed profile guard", () => {
250
252
  },
251
253
  },
252
254
  };
253
- const result = replaceRoute.handler({
255
+ const result = await replaceRoute.handler({
254
256
  pathParams: { name: "balanced" },
255
257
  body: { label: null, status: "disabled" },
256
258
  });
@@ -261,17 +263,17 @@ describe("PUT /v1/config/llm/profiles/:name — managed profile guard", () => {
261
263
  expect(profile.status).toBe("disabled");
262
264
  });
263
265
 
264
- test("PUT { label: '' } on managed profile still rejected by `.min(1)`", () => {
266
+ test("PUT { label: '' } on managed profile still rejected by `.min(1)`", async () => {
265
267
  // `.nullable()` only widens the type to accept null — empty strings
266
268
  // still fail the min-length check, which is correct: an empty string
267
269
  // would persist as a literal "" override, not the clear-to-seed
268
270
  // intent. Clients must send `null` to clear.
269
- expect(() =>
271
+ await expect(
270
272
  replaceRoute.handler({
271
273
  pathParams: { name: "balanced" },
272
274
  body: { label: "" },
273
275
  }),
274
- ).toThrow(BadRequestError);
276
+ ).rejects.toThrow(BadRequestError);
275
277
  });
276
278
  });
277
279
 
@@ -1,8 +1,9 @@
1
- import { existsSync, mkdirSync, readFileSync } from "node:fs";
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
2
  import { join } from "node:path";
3
3
  import { beforeEach, describe, expect, mock, test } from "bun:test";
4
4
 
5
5
  let TEST_DIR = "";
6
+ const seedUpsertSlugs: string[] = [];
6
7
 
7
8
  const mockConfig = {
8
9
  provider: "anthropic",
@@ -32,6 +33,10 @@ const mockConfig = {
32
33
  },
33
34
  "web-search": { mode: "your-own", provider: "inference-provider-native" },
34
35
  },
36
+ skills: {
37
+ entries: {},
38
+ allowBundled: [],
39
+ },
35
40
  };
36
41
 
37
42
  mock.module("../util/logger.js", () => ({
@@ -51,7 +56,36 @@ mock.module("../config/loader.js", () => ({
51
56
  setNestedValue: () => {},
52
57
  }));
53
58
 
59
+ mock.module("../skills/catalog-cache.js", () => ({
60
+ getCatalog: async () => [],
61
+ }));
62
+
63
+ mock.module("../memory/embedding-backend.js", () => ({
64
+ embedWithBackend: async (_config: unknown, inputs: unknown[]) => ({
65
+ provider: "local",
66
+ model: "test-model",
67
+ vectors: inputs.map(() => [0.1, 0.2, 0.3]),
68
+ }),
69
+ generateSparseEmbedding: () => ({ indices: [1], values: [1] }),
70
+ }));
71
+
72
+ mock.module("../memory/v2/qdrant.js", () => ({
73
+ upsertConceptPageEmbedding: async (params: { slug: string }) => {
74
+ seedUpsertSlugs.push(params.slug);
75
+ },
76
+ pruneSlugsWithPrefixExcept: async () => {},
77
+ }));
78
+
79
+ mock.module("../daemon/skill-memory-refresh.js", () => ({
80
+ refreshSkillCapabilityMemories: mock(() => {}),
81
+ }));
82
+
54
83
  import { loadSkillCatalog } from "../config/skills.js";
84
+ import {
85
+ _resetSkillStoreForTests,
86
+ getSkillCapability,
87
+ seedV2SkillEntries,
88
+ } from "../memory/v2/skill-store.js";
55
89
  import { executeDeleteManagedSkill } from "../tools/skills/delete-managed.js";
56
90
  import { SkillLoadTool } from "../tools/skills/load.js";
57
91
  import { executeScaffoldManagedSkill } from "../tools/skills/scaffold-managed.js";
@@ -68,9 +102,69 @@ function makeContext(): ToolContext {
68
102
  beforeEach(() => {
69
103
  TEST_DIR = process.env.VELLUM_WORKSPACE_DIR!;
70
104
  mkdirSync(join(TEST_DIR, "skills"), { recursive: true });
105
+ seedUpsertSlugs.length = 0;
106
+ _resetSkillStoreForTests();
71
107
  });
72
108
 
73
109
  describe("managed skill lifecycle: scaffold → catalog → prompt → delete", () => {
110
+ test("valid managed skill without SKILLS.md works across catalog, skill_load, and Memory V2 seeding", async () => {
111
+ const skillId = "e2e-custom-skill";
112
+ const skillSlug = `skills/${skillId}`;
113
+ const skillDir = join(TEST_DIR, "skills", skillId);
114
+ mkdirSync(skillDir, { recursive: true });
115
+ writeFileSync(
116
+ join(skillDir, "SKILL.md"),
117
+ `---
118
+ name: "E2E Custom Skill"
119
+ description: "Exercises custom managed skill loading."
120
+ metadata:
121
+ vellum:
122
+ activation-hints:
123
+ - user asks for custom lifecycle verification
124
+ ---
125
+
126
+ Run the custom lifecycle verification procedure.
127
+ `,
128
+ "utf-8",
129
+ );
130
+
131
+ expect(existsSync(join(TEST_DIR, "skills", "SKILLS.md"))).toBe(false);
132
+
133
+ const catalog = loadSkillCatalog();
134
+ const catalogSkill = catalog.find((s) => s.id === skillId);
135
+ expect(catalogSkill).toBeDefined();
136
+ expect(catalogSkill!.source).toBe("managed");
137
+ expect(catalogSkill!.displayName).toBe("E2E Custom Skill");
138
+
139
+ const skillLoadTool = new (SkillLoadTool as any)() as InstanceType<
140
+ typeof SkillLoadTool
141
+ >;
142
+ const loadResult = await skillLoadTool.execute(
143
+ { skill: skillId },
144
+ makeContext(),
145
+ );
146
+ expect(loadResult.isError).not.toBe(true);
147
+ expect(loadResult.content as string).toContain("Skill: E2E Custom Skill");
148
+ expect(loadResult.content as string).toContain("ID: e2e-custom-skill");
149
+ expect(loadResult.content as string).toContain(
150
+ "Run the custom lifecycle verification procedure.",
151
+ );
152
+
153
+ await seedV2SkillEntries();
154
+
155
+ expect(seedUpsertSlugs).toContain(skillSlug);
156
+ const capability = getSkillCapability(skillSlug);
157
+ expect(capability).not.toBeNull();
158
+ expect(capability!.id).toBe("e2e-custom-skill");
159
+ expect(capability!.content).toContain('The "E2E Custom Skill" skill');
160
+ expect(capability!.content).toContain(
161
+ "Exercises custom managed skill loading.",
162
+ );
163
+ expect(capability!.content).toContain(
164
+ "Use when: user asks for custom lifecycle verification.",
165
+ );
166
+ }, 15_000);
167
+
74
168
  test("full lifecycle: create skill, verify in catalog and prompt, then delete", async () => {
75
169
  // Step 1: Scaffold a managed skill
76
170
  const scaffoldResult = await executeScaffoldManagedSkill(
@@ -87,9 +181,11 @@ describe("managed skill lifecycle: scaffold → catalog → prompt → delete",
87
181
  expect(scaffoldResult.isError).not.toBe(true);
88
182
  const scaffoldData = JSON.parse(scaffoldResult.content as string);
89
183
  expect(scaffoldData.created).toBe(true);
184
+ expect(scaffoldData).not.toHaveProperty("index_updated");
90
185
 
91
186
  // Step 2: Verify SKILL.md was written
92
- const skillMdPath = join(TEST_DIR, "skills", "lifecycle-test", "SKILL.md");
187
+ const skillDir = join(TEST_DIR, "skills", "lifecycle-test");
188
+ const skillMdPath = join(skillDir, "SKILL.md");
93
189
  expect(existsSync(skillMdPath)).toBe(true);
94
190
  const skillContent = readFileSync(skillMdPath, "utf-8");
95
191
  expect(skillContent).toContain('name: "Lifecycle Test"');
@@ -102,6 +198,7 @@ describe("managed skill lifecycle: scaffold → catalog → prompt → delete",
102
198
  expect(found).toBeDefined();
103
199
  expect(found!.name).toBe("Lifecycle Test");
104
200
  expect(found!.description).toBe("Integration test skill.");
201
+ expect(existsSync(join(TEST_DIR, "skills", "SKILLS.md"))).toBe(false);
105
202
 
106
203
  // Step 4: Delete the skill
107
204
  const deleteResult = await executeDeleteManagedSkill(
@@ -114,20 +211,15 @@ describe("managed skill lifecycle: scaffold → catalog → prompt → delete",
114
211
  expect(deleteResult.isError).not.toBe(true);
115
212
  const deleteData = JSON.parse(deleteResult.content as string);
116
213
  expect(deleteData.deleted).toBe(true);
214
+ expect(deleteData).not.toHaveProperty("index_updated");
117
215
 
118
- // Step 5: Verify skill is gone from filesystem
119
- expect(existsSync(skillMdPath)).toBe(false);
216
+ // Step 5: Verify skill directory is gone from filesystem
217
+ expect(existsSync(skillDir)).toBe(false);
120
218
 
121
219
  // Step 6: Verify skill no longer in catalog
122
220
  const catalogAfter = loadSkillCatalog();
123
221
  expect(catalogAfter.find((s) => s.id === "lifecycle-test")).toBeUndefined();
124
-
125
- // Step 7: Verify SKILLS.md index no longer has the entry
126
- const indexPath = join(TEST_DIR, "skills", "SKILLS.md");
127
- if (existsSync(indexPath)) {
128
- const indexContent = readFileSync(indexPath, "utf-8");
129
- expect(indexContent).not.toContain("lifecycle-test");
130
- }
222
+ expect(existsSync(join(TEST_DIR, "skills", "SKILLS.md"))).toBe(false);
131
223
  });
132
224
 
133
225
  test("scaffold with overwrite replaces existing skill", async () => {
@@ -166,13 +258,12 @@ describe("managed skill lifecycle: scaffold → catalog → prompt → delete",
166
258
  expect(skillContent).toContain("Updated body.");
167
259
  expect(skillContent).not.toContain("Original body.");
168
260
 
169
- // Index should still have exactly one entry
170
- const indexContent = readFileSync(
171
- join(TEST_DIR, "skills", "SKILLS.md"),
172
- "utf-8",
173
- );
174
- const matches = indexContent.match(/overwrite-test/g);
175
- expect(matches?.length).toBe(1);
261
+ expect(existsSync(join(TEST_DIR, "skills", "SKILLS.md"))).toBe(false);
262
+
263
+ const catalog = loadSkillCatalog();
264
+ const skill = catalog.find((s) => s.id === "overwrite-test");
265
+ expect(skill).toBeDefined();
266
+ expect(skill!.name).toBe("V2");
176
267
  });
177
268
 
178
269
  test("delete non-existent skill returns error", async () => {