@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
@@ -19,6 +19,7 @@ mock.module("../util/logger.js", () => ({
19
19
  const mockAutoInstall = mock((_skillId: string) => Promise.resolve(false));
20
20
  mock.module("../skills/catalog-install.js", () => ({
21
21
  autoInstallFromCatalog: (skillId: string) => mockAutoInstall(skillId),
22
+ getRepoSkillsDir: () => undefined,
22
23
  resolveCatalog: (_skillId?: string) => Promise.resolve([]),
23
24
  }));
24
25
 
@@ -115,10 +116,6 @@ describe("skill_load tool", () => {
115
116
  "Runs release checks",
116
117
  "1. Run tests",
117
118
  );
118
- writeFileSync(
119
- join(TEST_DIR, "skills", "SKILLS.md"),
120
- "- release-checklist\n",
121
- );
122
119
 
123
120
  const result = await executeSkillLoad({ skill: "release-checklist" });
124
121
  expect(result.isError).toBe(false);
@@ -140,7 +137,6 @@ describe("skill_load tool", () => {
140
137
  "Handles incidents",
141
138
  "Page primary responder",
142
139
  );
143
- writeFileSync(join(TEST_DIR, "skills", "SKILLS.md"), "- oncall\n");
144
140
 
145
141
  const result = await executeSkillLoad({ skill: "oncall runbook" });
146
142
  expect(result.isError).toBe(false);
@@ -165,10 +161,6 @@ describe("skill_load tool", () => {
165
161
  "Release flow",
166
162
  "Run release checklist",
167
163
  );
168
- writeFileSync(
169
- join(TEST_DIR, "skills", "SKILLS.md"),
170
- "- incident-response\n- release-checklist\n",
171
- );
172
164
 
173
165
  const result = await executeSkillLoad({ skill: "incident" });
174
166
  expect(result.isError).toBe(false);
@@ -182,10 +174,6 @@ describe("skill_load tool", () => {
182
174
  test("returns an error when name resolution is ambiguous", async () => {
183
175
  writeSkill("skill-a", "Shared Name", "First", "Body A");
184
176
  writeSkill("skill-b", "Shared Name", "Second", "Body B");
185
- writeFileSync(
186
- join(TEST_DIR, "skills", "SKILLS.md"),
187
- "- skill-a\n- skill-b\n",
188
- );
189
177
 
190
178
  const result = await executeSkillLoad({ skill: "Shared Name" });
191
179
  expect(result.isError).toBe(true);
@@ -200,7 +188,6 @@ describe("skill_load tool", () => {
200
188
  "Test versioning",
201
189
  "Original body",
202
190
  );
203
- writeFileSync(join(TEST_DIR, "skills", "SKILLS.md"), "- versioned\n");
204
191
 
205
192
  const result1 = await executeSkillLoad({ skill: "versioned" });
206
193
  const match1 = result1.content.match(
@@ -229,7 +216,6 @@ describe("skill_load tool", () => {
229
216
 
230
217
  test("returns an error when skill is missing", async () => {
231
218
  writeSkill("existing", "Existing Skill", "Exists", "Body");
232
- writeFileSync(join(TEST_DIR, "skills", "SKILLS.md"), "- existing\n");
233
219
 
234
220
  const result = await executeSkillLoad({ skill: "does-not-exist" });
235
221
  expect(result.isError).toBe(true);
@@ -237,6 +223,32 @@ describe("skill_load tool", () => {
237
223
  expect(result.content).not.toContain("<loaded_skill");
238
224
  });
239
225
 
226
+ test("loads a valid disk-discovered skill omitted from stale SKILLS.md", async () => {
227
+ writeSkill("existing", "Existing Skill", "Exists", "Existing body");
228
+ writeSkill(
229
+ "geo-article-writer",
230
+ "Geo Article Writer",
231
+ "Writes local geo articles",
232
+ "Draft the article.",
233
+ );
234
+ writeFileSync(join(TEST_DIR, "skills", "SKILLS.md"), "- existing\n");
235
+
236
+ const result = await executeSkillLoad({ skill: "geo-article-writer" });
237
+
238
+ expect(result.isError).toBe(false);
239
+ expect(result.content).toContain("ID: geo-article-writer");
240
+ const markers = result.content.match(/<loaded_skill/g) || [];
241
+ expect(markers.length).toBe(1);
242
+ expect(result.content).toMatch(
243
+ /<loaded_skill id="geo-article-writer" version="v1:[a-f0-9]{64}" \/>/,
244
+ );
245
+
246
+ const missing = await executeSkillLoad({ skill: "does-not-exist" });
247
+ expect(missing.isError).toBe(true);
248
+ expect(missing.content).toContain("No skill matched");
249
+ expect(missing.content).not.toContain("<loaded_skill");
250
+ });
251
+
240
252
  test('successful skill_load output shows "none" for skills without includes', async () => {
241
253
  writeSkill(
242
254
  "standalone",
@@ -244,7 +256,6 @@ describe("skill_load tool", () => {
244
256
  "A skill with no children",
245
257
  "Do the thing",
246
258
  );
247
- writeFileSync(join(TEST_DIR, "skills", "SKILLS.md"), "- standalone\n");
248
259
 
249
260
  const result = await executeSkillLoad({ skill: "standalone" });
250
261
  expect(result.isError).toBe(false);
@@ -258,7 +269,6 @@ describe("skill_load tool", () => {
258
269
  "Should have one marker",
259
270
  "Step 1",
260
271
  );
261
- writeFileSync(join(TEST_DIR, "skills", "SKILLS.md"), "- single-marker\n");
262
272
 
263
273
  const result = await executeSkillLoad({ skill: "single-marker" });
264
274
  expect(result.isError).toBe(false);
@@ -270,7 +280,6 @@ describe("skill_load tool", () => {
270
280
  writeSkillWithIncludes("parent", "Parent", "Has missing child", "Body", [
271
281
  "missing-child",
272
282
  ]);
273
- writeFileSync(join(TEST_DIR, "skills", "SKILLS.md"), "- parent\n");
274
283
 
275
284
  const result = await executeSkillLoad({ skill: "parent" });
276
285
  expect(result.isError).toBe(false);
@@ -288,10 +297,6 @@ describe("skill_load tool", () => {
288
297
  writeSkillWithIncludes("skill-b", "Skill B", "Cycles", "Body B", [
289
298
  "skill-a",
290
299
  ]);
291
- writeFileSync(
292
- join(TEST_DIR, "skills", "SKILLS.md"),
293
- "- skill-a\n- skill-b\n",
294
- );
295
300
 
296
301
  const result = await executeSkillLoad({ skill: "skill-a" });
297
302
  expect(result.isError).toBe(true);
@@ -308,10 +313,6 @@ describe("skill_load tool", () => {
308
313
  ["valid-child"],
309
314
  );
310
315
  writeSkill("valid-child", "Valid Child", "A child", "Child body");
311
- writeFileSync(
312
- join(TEST_DIR, "skills", "SKILLS.md"),
313
- "- valid-parent\n- valid-child\n",
314
- );
315
316
 
316
317
  const result = await executeSkillLoad({ skill: "valid-parent" });
317
318
  expect(result.isError).toBe(false);
@@ -326,7 +327,6 @@ describe("skill_load tool", () => {
326
327
  join(skillDir, "SKILL.md"),
327
328
  '---\nname: "Marker Missing"\ndescription: "test"\nmetadata: {"vellum":{"includes":["nonexistent"]}}\n---\n\nBody.\n',
328
329
  );
329
- writeFileSync(join(TEST_DIR, "skills", "SKILLS.md"), "- marker-missing\n");
330
330
 
331
331
  const result = await executeSkillLoad({ skill: "marker-missing" });
332
332
  expect(result.isError).toBe(false);
@@ -351,10 +351,6 @@ describe("skill_load tool", () => {
351
351
  join(dirB, "SKILL.md"),
352
352
  '---\nname: "Cycle B"\ndescription: "test"\nmetadata: {"vellum":{"includes":["cycle-a"]}}\n---\n\nBody B.\n',
353
353
  );
354
- writeFileSync(
355
- join(TEST_DIR, "skills", "SKILLS.md"),
356
- "- cycle-a\n- cycle-b\n",
357
- );
358
354
 
359
355
  const result = await executeSkillLoad({ skill: "cycle-a" });
360
356
  expect(result.isError).toBe(true);
@@ -364,7 +360,6 @@ describe("skill_load tool", () => {
364
360
 
365
361
  test("succeeds when skill has no includes", async () => {
366
362
  writeSkill("no-includes", "No Includes", "Plain skill", "Body");
367
- writeFileSync(join(TEST_DIR, "skills", "SKILLS.md"), "- no-includes\n");
368
363
 
369
364
  const result = await executeSkillLoad({ skill: "no-includes" });
370
365
  expect(result.isError).toBe(false);
@@ -399,10 +394,6 @@ describe("skill_load tool", () => {
399
394
  join(parentDir, "SKILL.md"),
400
395
  '---\nname: "Parent"\ndescription: "Has children"\nmetadata: {"vellum":{"includes":["child-skill"]}}\n---\n\nParent body.\n',
401
396
  );
402
- writeFileSync(
403
- join(TEST_DIR, "skills", "SKILLS.md"),
404
- "- parent-with-children\n- child-skill\n",
405
- );
406
397
 
407
398
  const result = await executeSkillLoad({ skill: "parent-with-children" });
408
399
  expect(result.isError).toBe(false);
@@ -413,7 +404,6 @@ describe("skill_load tool", () => {
413
404
 
414
405
  test('skill_load output shows "none" when no includes', async () => {
415
406
  writeSkill("solo-skill", "Solo", "No children", "Body");
416
- writeFileSync(join(TEST_DIR, "skills", "SKILLS.md"), "- solo-skill\n");
417
407
 
418
408
  const result = await executeSkillLoad({ skill: "solo-skill" });
419
409
  expect(result.isError).toBe(false);
@@ -435,10 +425,6 @@ describe("skill_load tool", () => {
435
425
  "Parent instructions.",
436
426
  ["e2e-child"],
437
427
  );
438
- writeFileSync(
439
- join(TEST_DIR, "skills", "SKILLS.md"),
440
- "- e2e-parent\n- e2e-child\n",
441
- );
442
428
 
443
429
  // Load the parent
444
430
  const result = await executeSkillLoad({ skill: "e2e-parent" });
@@ -479,10 +465,6 @@ describe("skill_load tool", () => {
479
465
  "Grandparent body",
480
466
  ["child"],
481
467
  );
482
- writeFileSync(
483
- join(TEST_DIR, "skills", "SKILLS.md"),
484
- "- grandparent\n- child\n- grandchild\n",
485
- );
486
468
 
487
469
  const result = await executeSkillLoad({ skill: "grandparent" });
488
470
  expect(result.isError).toBe(false);
@@ -534,10 +516,6 @@ describe("skill_load tool", () => {
534
516
  "Root body",
535
517
  ["branch-a", "branch-b"],
536
518
  );
537
- writeFileSync(
538
- join(TEST_DIR, "skills", "SKILLS.md"),
539
- "- diamond-root\n- branch-a\n- branch-b\n- shared-leaf\n",
540
- );
541
519
 
542
520
  const result = await executeSkillLoad({ skill: "diamond-root" });
543
521
  expect(result.isError).toBe(false);
@@ -576,7 +554,6 @@ describe("skill_load tool", () => {
576
554
  "Body",
577
555
  ["self-ref"],
578
556
  );
579
- writeFileSync(join(TEST_DIR, "skills", "SKILLS.md"), "- self-ref\n");
580
557
 
581
558
  const result = await executeSkillLoad({ skill: "self-ref" });
582
559
  expect(result.isError).toBe(true);
@@ -601,7 +578,6 @@ describe("skill_load tool", () => {
601
578
  join(skillDir, "references", "TROUBLESHOOTING.md"),
602
579
  "# Troubleshooting\n\nFix things here.",
603
580
  );
604
- writeFileSync(join(TEST_DIR, "skills", "SKILLS.md"), "- with-refs\n");
605
581
 
606
582
  const result = await executeSkillLoad({ skill: "with-refs" });
607
583
  expect(result.isError).toBe(false);
@@ -624,7 +600,6 @@ describe("skill_load tool", () => {
624
600
 
625
601
  test("skill without references/ directory loads normally", async () => {
626
602
  writeSkill("no-refs", "No Refs", "No references dir", "Just body.");
627
- writeFileSync(join(TEST_DIR, "skills", "SKILLS.md"), "- no-refs\n");
628
603
 
629
604
  const result = await executeSkillLoad({ skill: "no-refs" });
630
605
  expect(result.isError).toBe(false);
@@ -650,8 +625,6 @@ describe("skill_load tool", () => {
650
625
  writeFileSync(outsideSecretPath, "TOP_SECRET_DO_NOT_LOAD");
651
626
  symlinkSync(outsideSecretPath, join(skillDir, "references", "secret.md"));
652
627
 
653
- writeFileSync(join(TEST_DIR, "skills", "SKILLS.md"), "- refs-symlink\n");
654
-
655
628
  const result = await executeSkillLoad({ skill: "refs-symlink" });
656
629
  expect(result.isError).toBe(false);
657
630
  expect(result.content).toContain("Body.");
@@ -675,7 +648,6 @@ describe("skill_load tool", () => {
675
648
  join(skillDir, "references", "data.json"),
676
649
  '{"key": "value"}',
677
650
  );
678
- writeFileSync(join(TEST_DIR, "skills", "SKILLS.md"), "- refs-filter\n");
679
651
 
680
652
  const result = await executeSkillLoad({ skill: "refs-filter" });
681
653
  expect(result.isError).toBe(false);
@@ -695,7 +667,6 @@ describe("skill_load tool", () => {
695
667
  join(skillDir, "SKILL.md"),
696
668
  '---\nname: "Empty Includes"\ndescription: "Has empty array"\nmetadata: {"vellum":{"includes":[]}}\n---\n\nBody.\n',
697
669
  );
698
- writeFileSync(join(TEST_DIR, "skills", "SKILLS.md"), "- empty-includes\n");
699
670
 
700
671
  const result = await executeSkillLoad({ skill: "empty-includes" });
701
672
  expect(result.isError).toBe(false);
@@ -746,11 +717,6 @@ describe("skill_load tool", () => {
746
717
  },
747
718
  },
748
719
  ]);
749
- writeFileSync(
750
- join(TEST_DIR, "skills", "SKILLS.md"),
751
- "- skill-with-tools\n",
752
- );
753
-
754
720
  const result = await executeSkillLoad({ skill: "skill-with-tools" });
755
721
  expect(result.isError).toBe(false);
756
722
 
@@ -784,7 +750,6 @@ describe("skill_load tool", () => {
784
750
 
785
751
  test("skill without TOOLS.json does not include tool schemas section", async () => {
786
752
  writeSkill("no-tools", "No Tools", "No tools manifest", "Body.");
787
- writeFileSync(join(TEST_DIR, "skills", "SKILLS.md"), "- no-tools\n");
788
753
 
789
754
  const result = await executeSkillLoad({ skill: "no-tools" });
790
755
  expect(result.isError).toBe(false);
@@ -817,10 +782,6 @@ describe("skill_load tool", () => {
817
782
  },
818
783
  },
819
784
  ]);
820
- writeFileSync(
821
- join(TEST_DIR, "skills", "SKILLS.md"),
822
- "- parent-tools\n- child-tools\n",
823
- );
824
785
 
825
786
  const result = await executeSkillLoad({ skill: "parent-tools" });
826
787
  expect(result.isError).toBe(false);
@@ -845,17 +806,11 @@ describe("skill_load tool", () => {
845
806
  "Parent body",
846
807
  ["dep-a"],
847
808
  );
848
- writeFileSync(join(TEST_DIR, "skills", "SKILLS.md"), "- auto-parent\n");
849
809
 
850
810
  // Mock autoInstallFromCatalog to succeed and write the skill to disk
851
811
  mockAutoInstall.mockImplementation((skillId: string) => {
852
812
  if (skillId === "dep-a") {
853
813
  writeSkill("dep-a", "Dep A", "A dependency", "Dep A body");
854
- // Add to SKILLS.md so catalog reload finds it
855
- writeFileSync(
856
- join(TEST_DIR, "skills", "SKILLS.md"),
857
- "- auto-parent\n- dep-a\n",
858
- );
859
814
  return Promise.resolve(true);
860
815
  }
861
816
  return Promise.resolve(false);
@@ -873,7 +828,6 @@ describe("skill_load tool", () => {
873
828
  writeSkillWithIncludes("trans-a", "Trans A", "Top level", "Body A", [
874
829
  "trans-b",
875
830
  ]);
876
- writeFileSync(join(TEST_DIR, "skills", "SKILLS.md"), "- trans-a\n");
877
831
 
878
832
  let round = 0;
879
833
  mockAutoInstall.mockImplementation((skillId: string) => {
@@ -882,20 +836,12 @@ describe("skill_load tool", () => {
882
836
  writeSkillWithIncludes("trans-b", "Trans B", "Mid level", "Body B", [
883
837
  "trans-c",
884
838
  ]);
885
- writeFileSync(
886
- join(TEST_DIR, "skills", "SKILLS.md"),
887
- "- trans-a\n- trans-b\n",
888
- );
889
839
  round++;
890
840
  return Promise.resolve(true);
891
841
  }
892
842
  if (skillId === "trans-c") {
893
843
  // Second round: install C
894
844
  writeSkill("trans-c", "Trans C", "Leaf", "Body C");
895
- writeFileSync(
896
- join(TEST_DIR, "skills", "SKILLS.md"),
897
- "- trans-a\n- trans-b\n- trans-c\n",
898
- );
899
845
  return Promise.resolve(true);
900
846
  }
901
847
  return Promise.resolve(false);
@@ -917,7 +863,6 @@ describe("skill_load tool", () => {
917
863
  "Body",
918
864
  ["dep-x"],
919
865
  );
920
- writeFileSync(join(TEST_DIR, "skills", "SKILLS.md"), "- fail-parent\n");
921
866
 
922
867
  // autoInstallFromCatalog throws an error
923
868
  mockAutoInstall.mockImplementation((skillId: string) => {
@@ -941,7 +886,6 @@ describe("skill_load tool", () => {
941
886
  writeSkillWithIncludes("loop-root", "Loop Root", "Infinite deps", "Body", [
942
887
  "loop-dep-0",
943
888
  ]);
944
- writeFileSync(join(TEST_DIR, "skills", "SKILLS.md"), "- loop-root\n");
945
889
 
946
890
  let installCount = 0;
947
891
  mockAutoInstall.mockImplementation((skillId: string) => {
@@ -957,12 +901,6 @@ describe("skill_load tool", () => {
957
901
  "Body",
958
902
  [nextDepId],
959
903
  );
960
- // Update SKILLS.md to include all installed deps so far
961
- const entries = ["- loop-root\n"];
962
- for (let i = 0; i < installCount; i++) {
963
- entries.push(`- loop-dep-${i}\n`);
964
- }
965
- writeFileSync(join(TEST_DIR, "skills", "SKILLS.md"), entries.join(""));
966
904
  return Promise.resolve(true);
967
905
  }
968
906
  return Promise.resolve(false);
@@ -3,9 +3,7 @@ import { describe, expect, test } from "bun:test";
3
3
  import type { SkillSummary } from "../config/skills.js";
4
4
  import { fromSkillSummary } from "../skills/skill-memory.js";
5
5
 
6
- function makeSkillSummary(
7
- overrides: Partial<SkillSummary> = {},
8
- ): SkillSummary {
6
+ function makeSkillSummary(overrides: Partial<SkillSummary> = {}): SkillSummary {
9
7
  return {
10
8
  id: "test-skill",
11
9
  name: "test-skill",
@@ -62,4 +60,26 @@ describe("fromSkillSummary", () => {
62
60
  expect(input.id).toBe("my-id");
63
61
  expect(input.description).toBe("Does amazing things");
64
62
  });
63
+
64
+ test("maps custom managed skill metadata required by Memory V2 rendering", () => {
65
+ const entry = makeSkillSummary({
66
+ id: "geo-article-writer",
67
+ name: "geo-article-writer",
68
+ displayName: "Geo Article Writer",
69
+ description: "Writes local geo articles",
70
+ source: "managed",
71
+ activationHints: ["user asks for local article drafts"],
72
+ avoidWhen: ["user only wants citation extraction"],
73
+ });
74
+
75
+ const input = fromSkillSummary(entry);
76
+
77
+ expect(input).toEqual({
78
+ id: "geo-article-writer",
79
+ displayName: "Geo Article Writer",
80
+ description: "Writes local geo articles",
81
+ activationHints: ["user asks for local article drafts"],
82
+ avoidWhen: ["user only wants citation extraction"],
83
+ });
84
+ });
65
85
  });
@@ -199,7 +199,6 @@ mock.module("../skills/skill-file-provider.js", () => ({}));
199
199
 
200
200
  mock.module("../skills/catalog-install.js", () => ({
201
201
  installSkillLocally: async () => {},
202
- upsertSkillsIndex: () => {},
203
202
  getRepoSkillsDir: () => undefined,
204
203
  }));
205
204
 
@@ -230,7 +229,6 @@ mock.module("../skills/install-meta.js", () => ({
230
229
  mock.module("../skills/managed-store.js", () => ({
231
230
  createManagedSkill: () => ({ created: true }),
232
231
  deleteManagedSkill: () => ({ deleted: true }),
233
- removeSkillsIndexEntry: () => {},
234
232
  validateManagedSkillId: () => null,
235
233
  }));
236
234
 
@@ -412,10 +410,7 @@ describe("getSkillFileContent — installed skill", () => {
412
410
  mockResolvedSkills = [installedSkill("my-skill", skillDir)];
413
411
  installFetchForbidden();
414
412
 
415
- const result = await getSkillFileContent(
416
- "my-skill",
417
- "SKILL.md\0.png",
418
- );
413
+ const result = await getSkillFileContent("my-skill", "SKILL.md\0.png");
419
414
  expect("error" in result).toBe(true);
420
415
  if (!("error" in result)) return;
421
416
  expect(result.status).toBe(400);
@@ -458,10 +453,7 @@ describe("getSkillFileContent — uninstalled skill (provider chain)", () => {
458
453
  toSlimSkill: async () => null,
459
454
  };
460
455
 
461
- const result = await getSkillFileContent(
462
- "remote-skill",
463
- "SKILL.md",
464
- );
456
+ const result = await getSkillFileContent("remote-skill", "SKILL.md");
465
457
  expect("error" in result).toBe(false);
466
458
  if ("error" in result) return;
467
459
  expect(result.path).toBe("SKILL.md");
@@ -478,10 +470,7 @@ describe("getSkillFileContent — uninstalled skill (provider chain)", () => {
478
470
 
479
471
  // All providers return canHandle=false (default noop)
480
472
 
481
- const result = await getSkillFileContent(
482
- "ghost-skill",
483
- "SKILL.md",
484
- );
473
+ const result = await getSkillFileContent("ghost-skill", "SKILL.md");
485
474
  expect("error" in result).toBe(true);
486
475
  if (!("error" in result)) return;
487
476
  expect(result.status).toBe(404);
@@ -510,10 +499,7 @@ describe("getSkillFileContent — uninstalled skill (provider chain)", () => {
510
499
  toSlimSkill: async () => null,
511
500
  };
512
501
 
513
- const result = await getSkillFileContent(
514
- "owner/repo/my-skill",
515
- "SKILL.md",
516
- );
502
+ const result = await getSkillFileContent("owner/repo/my-skill", "SKILL.md");
517
503
  expect("error" in result).toBe(false);
518
504
  if ("error" in result) return;
519
505
  expect(result.content).toBe("# skillssh content\n");
@@ -562,10 +548,7 @@ describe("getSkillFileContent — uninstalled skill (provider chain)", () => {
562
548
  toSlimSkill: async () => null,
563
549
  };
564
550
 
565
- const result = await getSkillFileContent(
566
- "known-skill",
567
- "nonexistent.txt",
568
- );
551
+ const result = await getSkillFileContent("known-skill", "nonexistent.txt");
569
552
  expect("error" in result).toBe(true);
570
553
  if (!("error" in result)) return;
571
554
  expect(result.status).toBe(404);
@@ -604,10 +587,7 @@ describe("getSkillFileContent — uninstalled skill (provider chain)", () => {
604
587
  toSlimSkill: async () => null,
605
588
  };
606
589
 
607
- const result = await getSkillFileContent(
608
- "simple-slug",
609
- "SKILL.md",
610
- );
590
+ const result = await getSkillFileContent("simple-slug", "SKILL.md");
611
591
  // Should be "File not found" (vellum handled but returned null)
612
592
  expect("error" in result).toBe(true);
613
593
  if (!("error" in result)) return;
@@ -626,10 +606,7 @@ describe("getSkillFileContent — skill not found", () => {
626
606
  mockResolvedSkills = [];
627
607
  installFetchForbidden();
628
608
 
629
- const result = await getSkillFileContent(
630
- "ghost-skill",
631
- "SKILL.md",
632
- );
609
+ const result = await getSkillFileContent("ghost-skill", "SKILL.md");
633
610
  expect("error" in result).toBe(true);
634
611
  if (!("error" in result)) return;
635
612
  expect(result.status).toBe(404);
@@ -666,10 +643,7 @@ describe("getSkillFileContent — installed skill with missing directory", () =>
666
643
  };
667
644
  installFetchForbidden();
668
645
 
669
- const result = await getSkillFileContent(
670
- "ghost-installed",
671
- "SKILL.md",
672
- );
646
+ const result = await getSkillFileContent("ghost-installed", "SKILL.md");
673
647
 
674
648
  expect("error" in result).toBe(true);
675
649
  if (!("error" in result)) return;
@@ -766,10 +740,7 @@ describe("getSkillFileContent — hidden / SKIP_DIRS rejection", () => {
766
740
  mockResolvedSkills = [installedSkill("healthy-skill", skillDir)];
767
741
  installFetchForbidden();
768
742
 
769
- const result = await getSkillFileContent(
770
- "healthy-skill",
771
- "SKILL.md",
772
- );
743
+ const result = await getSkillFileContent("healthy-skill", "SKILL.md");
773
744
  expect("error" in result).toBe(false);
774
745
  if ("error" in result) return;
775
746
  expect(result.content).toBe("# hello\n");
@@ -164,7 +164,6 @@ mock.module("../skills/skill-file-provider.js", () => ({}));
164
164
 
165
165
  mock.module("../skills/catalog-install.js", () => ({
166
166
  installSkillLocally: async () => {},
167
- upsertSkillsIndex: () => {},
168
167
  }));
169
168
 
170
169
  mock.module("../skills/catalog-search.js", () => ({
@@ -188,7 +187,6 @@ mock.module("../skills/skillssh-registry.js", () => ({
188
187
  mock.module("../skills/managed-store.js", () => ({
189
188
  createManagedSkill: () => ({ created: true }),
190
189
  deleteManagedSkill: () => ({ deleted: true }),
191
- removeSkillsIndexEntry: () => {},
192
190
  validateManagedSkillId: () => null,
193
191
  }));
194
192
 
@@ -238,7 +236,6 @@ import {
238
236
  // Helpers
239
237
  // ---------------------------------------------------------------------------
240
238
 
241
-
242
239
  function makeSummary(overrides: Partial<SkillSummary>): SkillSummary {
243
240
  return {
244
241
  id: overrides.id ?? "summary-id",
@@ -3,47 +3,14 @@ import { tmpdir } from "node:os";
3
3
  import { join } from "node:path";
4
4
  import { afterEach, beforeEach, describe, expect, test } from "bun:test";
5
5
 
6
- import { extractTarToDir } from "../skills/catalog-install.js";
6
+ import {
7
+ extractTarToDir,
8
+ writeSkillFilesToDir,
9
+ } from "../skills/catalog-install.js";
10
+ import { makeTar } from "./helpers/tar-fixtures.js";
7
11
 
8
12
  let tempDir: string;
9
13
 
10
- function makeTarEntry(name: string, content: string): Buffer {
11
- const header = Buffer.alloc(512, 0);
12
- const nameBuffer = Buffer.from(name, "utf-8");
13
- nameBuffer.copy(header, 0, 0, Math.min(nameBuffer.length, 100));
14
-
15
- const mode = Buffer.from("0000644\0", "ascii");
16
- mode.copy(header, 100);
17
- Buffer.from("0000000\0", "ascii").copy(header, 108); // uid
18
- Buffer.from("0000000\0", "ascii").copy(header, 116); // gid
19
-
20
- const sizeOct = content.length.toString(8).padStart(11, "0") + "\0";
21
- Buffer.from(sizeOct, "ascii").copy(header, 124);
22
-
23
- Buffer.from("00000000000\0", "ascii").copy(header, 136); // mtime
24
- Buffer.from(" ", "ascii").copy(header, 148); // checksum placeholder
25
- header[156] = "0".charCodeAt(0);
26
- Buffer.from("ustar\0", "ascii").copy(header, 257);
27
- Buffer.from("00", "ascii").copy(header, 263);
28
-
29
- let sum = 0;
30
- for (let i = 0; i < 512; i += 1) sum += header[i] ?? 0;
31
- const checksum = sum.toString(8).padStart(6, "0");
32
- Buffer.from(`${checksum}\0 `, "ascii").copy(header, 148);
33
-
34
- const data = Buffer.from(content, "utf-8");
35
- const paddedSize = Math.ceil(data.length / 512) * 512;
36
- const padded = Buffer.alloc(paddedSize, 0);
37
- data.copy(padded);
38
-
39
- return Buffer.concat([header, padded]);
40
- }
41
-
42
- function makeTar(entries: Array<{ name: string; content: string }>): Buffer {
43
- const body = entries.map((entry) => makeTarEntry(entry.name, entry.content));
44
- return Buffer.concat([...body, Buffer.alloc(1024, 0)]);
45
- }
46
-
47
14
  beforeEach(() => {
48
15
  tempDir = join(
49
16
  tmpdir(),
@@ -90,4 +57,48 @@ describe("extractTarToDir", () => {
90
57
  expect(existsSync(join(tempDir, "windows.txt"))).toBe(false);
91
58
  expect(readFileSync(join(tempDir, "SKILL.md"), "utf-8")).toBe("# demo\n");
92
59
  });
60
+
61
+ test("does not count nested SKILL.md as a valid skill root", () => {
62
+ const tar = makeTar([
63
+ { name: "nested/SKILL.md", content: "# nested\n" },
64
+ { name: "README.md", content: "# wrapper\n" },
65
+ ]);
66
+
67
+ const foundSkillMd = extractTarToDir(tar, tempDir);
68
+
69
+ expect(foundSkillMd).toBe(false);
70
+ expect(existsSync(join(tempDir, "SKILL.md"))).toBe(false);
71
+ expect(readFileSync(join(tempDir, "nested", "SKILL.md"), "utf-8")).toBe(
72
+ "# nested\n",
73
+ );
74
+ });
75
+
76
+ test("normalizes safe relative segments before top-level SKILL.md detection", () => {
77
+ const tar = makeTar([{ name: "wrapper/../SKILL.md", content: "# demo\n" }]);
78
+
79
+ const foundSkillMd = extractTarToDir(tar, tempDir);
80
+
81
+ expect(foundSkillMd).toBe(true);
82
+ expect(readFileSync(join(tempDir, "SKILL.md"), "utf-8")).toBe("# demo\n");
83
+ });
84
+ });
85
+
86
+ describe("writeSkillFilesToDir", () => {
87
+ test("uses the same traversal rules as tar extraction", () => {
88
+ const foundSkillMd = writeSkillFilesToDir(
89
+ {
90
+ "./SKILL.md": "# demo\n",
91
+ "scripts/../notes.md": "ok\n",
92
+ "../../escape.txt": "nope\n",
93
+ "/absolute.txt": "nope\n",
94
+ },
95
+ tempDir,
96
+ );
97
+
98
+ expect(foundSkillMd).toBe(true);
99
+ expect(readFileSync(join(tempDir, "SKILL.md"), "utf-8")).toBe("# demo\n");
100
+ expect(readFileSync(join(tempDir, "notes.md"), "utf-8")).toBe("ok\n");
101
+ expect(existsSync(join(tempDir, "escape.txt"))).toBe(false);
102
+ expect(existsSync(join(tempDir, "absolute.txt"))).toBe(false);
103
+ });
93
104
  });