@vellumai/assistant 0.8.1 → 0.8.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (506) hide show
  1. package/ARCHITECTURE.md +2 -7
  2. package/Dockerfile +75 -1
  3. package/bun.lock +11 -1
  4. package/docker-entrypoint.sh +5 -0
  5. package/docker-init-apt-root.sh +94 -0
  6. package/docker-kata-apt-env.sh +39 -0
  7. package/docs/plugins.md +88 -47
  8. package/docs/skills.md +9 -7
  9. package/examples/plugins/echo/README.md +27 -27
  10. package/examples/plugins/echo/package.json +3 -0
  11. package/examples/plugins/echo/register.ts +31 -31
  12. package/node_modules/@vellumai/slack-text/src/index.test.ts +114 -14
  13. package/node_modules/@vellumai/slack-text/src/index.ts +82 -18
  14. package/openapi.yaml +325 -3
  15. package/package.json +3 -1
  16. package/scripts/generate-openapi.ts +83 -10
  17. package/scripts/sync-llm-catalog.ts +2 -2
  18. package/scripts/sync-web-search-catalog.ts +47 -25
  19. package/src/__tests__/agent-image-optimize.test.ts +11 -3
  20. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +131 -0
  21. package/src/__tests__/anthropic-provider.test.ts +45 -0
  22. package/src/__tests__/app-builder-tool-scripts.test.ts +9 -3
  23. package/src/__tests__/app-executors.test.ts +220 -4
  24. package/src/__tests__/auto-analysis-end-to-end.test.ts +35 -0
  25. package/src/__tests__/bundled-asset.test.ts +6 -6
  26. package/src/__tests__/channel-availability-routes.test.ts +206 -0
  27. package/src/__tests__/channel-delivery-store.test.ts +289 -1
  28. package/src/__tests__/circuit-breaker-pipeline.test.ts +0 -1
  29. package/src/__tests__/clawhub.test.ts +75 -16
  30. package/src/__tests__/compactor-tail-resolution.test.ts +41 -0
  31. package/src/__tests__/config-schema.test.ts +21 -0
  32. package/src/__tests__/config-set-route.test.ts +80 -0
  33. package/src/__tests__/config-sounds-sync.test.ts +97 -0
  34. package/src/__tests__/config-watcher-skill-reseed.test.ts +453 -0
  35. package/src/__tests__/context-search-conversations-source.test.ts +117 -2
  36. package/src/__tests__/context-search-memory-v2-source.test.ts +0 -1
  37. package/src/__tests__/context-search-workspace-source.test.ts +7 -0
  38. package/src/__tests__/context-token-estimator.test.ts +1 -0
  39. package/src/__tests__/conversation-abort-tool-results.test.ts +4 -1
  40. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +1 -0
  41. package/src/__tests__/conversation-agent-loop-overflow.test.ts +92 -92
  42. package/src/__tests__/conversation-agent-loop.test.ts +2 -0
  43. package/src/__tests__/conversation-error.test.ts +42 -3
  44. package/src/__tests__/conversation-fork-crud.test.ts +82 -0
  45. package/src/__tests__/conversation-inference-profile-route.test.ts +40 -4
  46. package/src/__tests__/conversation-lifecycle.test.ts +173 -0
  47. package/src/__tests__/conversation-message-sync-tags.test.ts +97 -0
  48. package/src/__tests__/conversation-pairing.test.ts +54 -0
  49. package/src/__tests__/conversation-process-callsite.test.ts +4 -1
  50. package/src/__tests__/conversation-provider-retry-repair.test.ts +5 -1
  51. package/src/__tests__/conversation-queue.test.ts +4 -1
  52. package/src/__tests__/conversation-runtime-assembly.test.ts +76 -9
  53. package/src/__tests__/conversation-slash-queue.test.ts +59 -1
  54. package/src/__tests__/conversation-slash-unknown.test.ts +4 -1
  55. package/src/__tests__/conversation-surfaces-table-action.test.ts +360 -0
  56. package/src/__tests__/conversation-sync-tags.test.ts +235 -0
  57. package/src/__tests__/conversation-workspace-injection.test.ts +5 -1
  58. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +5 -1
  59. package/src/__tests__/credential-security-invariants.test.ts +3 -2
  60. package/src/__tests__/db-slack-external-content-normalization.test.ts +301 -0
  61. package/src/__tests__/delete-managed-skill-tool.test.ts +55 -13
  62. package/src/__tests__/disk-pressure-tools.test.ts +1 -0
  63. package/src/__tests__/dm-backfill.test.ts +121 -10
  64. package/src/__tests__/document-tool-security.test.ts +258 -0
  65. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +0 -1
  66. package/src/__tests__/edit-propagation.test.ts +33 -0
  67. package/src/__tests__/empty-response-pipeline.test.ts +0 -4
  68. package/src/__tests__/external-plugin-loader.test.ts +60 -36
  69. package/src/__tests__/filing-service.test.ts +140 -0
  70. package/src/__tests__/get-skill-detail-audit.test.ts +0 -4
  71. package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +43 -62
  72. package/src/__tests__/helpers/tar-fixtures.ts +39 -0
  73. package/src/__tests__/helpers/wait-for.ts +21 -0
  74. package/src/__tests__/history-repair-pipeline.test.ts +0 -3
  75. package/src/__tests__/history-repair.test.ts +73 -0
  76. package/src/__tests__/host-app-control-proxy.test.ts +266 -10
  77. package/src/__tests__/image-credentials.test.ts +1 -1
  78. package/src/__tests__/inbound-slack-persistence.test.ts +2 -0
  79. package/src/__tests__/inference-no-mode-boot-e2e.test.ts +1 -1
  80. package/src/__tests__/inference-profile-reaper.test.ts +4 -2
  81. package/src/__tests__/inference-profile-session-handler.test.ts +18 -6
  82. package/src/__tests__/inference-profile-session-ipc.test.ts +17 -5
  83. package/src/__tests__/injector-chain.test.ts +10 -8
  84. package/src/__tests__/install-skill-routing.test.ts +155 -37
  85. package/src/__tests__/lifecycle-memory-v2-seed.test.ts +92 -3
  86. package/src/__tests__/list-messages-page-latest.test.ts +55 -0
  87. package/src/__tests__/llm-call-pipeline.test.ts +0 -3
  88. package/src/__tests__/llm-catalog-parity.test.ts +55 -13
  89. package/src/__tests__/llm-request-log-source-clickhouse.test.ts +34 -0
  90. package/src/__tests__/llm-request-log-source-factory.test.ts +29 -53
  91. package/src/__tests__/llm-usage-store.test.ts +114 -0
  92. package/src/__tests__/managed-profile-guard.test.ts +31 -29
  93. package/src/__tests__/managed-skill-lifecycle.test.ts +109 -18
  94. package/src/__tests__/managed-store.test.ts +84 -192
  95. package/src/__tests__/media-generate-image.test.ts +1 -1
  96. package/src/__tests__/memory-retrieval-pipeline.test.ts +0 -2
  97. package/src/__tests__/messages-after-tiebreaker.test.ts +122 -0
  98. package/src/__tests__/oauth-commands-routes.test.ts +168 -16
  99. package/src/__tests__/oauth-provider-profiles.test.ts +9 -0
  100. package/src/__tests__/openai-provider.test.ts +24 -0
  101. package/src/__tests__/openai-responses-cutover-guard.test.ts +17 -9
  102. package/src/__tests__/overflow-reduce-pipeline.test.ts +0 -2
  103. package/src/__tests__/persistence-pipeline.test.ts +0 -2
  104. package/src/__tests__/{managed-proxy-context.test.ts → platform-proxy-context.test.ts} +1 -1
  105. package/src/__tests__/platform.test.ts +2 -0
  106. package/src/__tests__/plugin-api-shim.test.ts +125 -0
  107. package/src/__tests__/plugin-bootstrap.test.ts +10 -36
  108. package/src/__tests__/plugin-external-api.test.ts +68 -0
  109. package/src/__tests__/plugin-registry.test.ts +0 -77
  110. package/src/__tests__/plugin-route-contribution.test.ts +0 -1
  111. package/src/__tests__/plugin-skill-contribution.test.ts +0 -2
  112. package/src/__tests__/plugin-tool-contribution.test.ts +16 -15
  113. package/src/__tests__/plugin-types.test.ts +3 -13
  114. package/src/__tests__/process-message-background-slack.test.ts +8 -1
  115. package/src/__tests__/process-message-display-content.test.ts +421 -0
  116. package/src/__tests__/provider-catalog-visibility.test.ts +142 -0
  117. package/src/__tests__/provider-error-scenarios.test.ts +111 -0
  118. package/src/__tests__/{provider-managed-proxy-integration.test.ts → provider-platform-proxy-integration.test.ts} +8 -8
  119. package/src/__tests__/scaffold-managed-skill-tool.test.ts +65 -13
  120. package/src/__tests__/schedule-routes.test.ts +50 -3
  121. package/src/__tests__/schedule-store.test.ts +94 -0
  122. package/src/__tests__/scheduler-reuse-conversation.test.ts +54 -7
  123. package/src/__tests__/schema-transforms.test.ts +20 -0
  124. package/src/__tests__/search-skills-unified.test.ts +0 -5
  125. package/src/__tests__/server-history-render.test.ts +43 -0
  126. package/src/__tests__/skill-load-feature-flag.test.ts +0 -12
  127. package/src/__tests__/skill-load-tool.test.ts +27 -89
  128. package/src/__tests__/skill-memory.test.ts +23 -3
  129. package/src/__tests__/skills-file-content-endpoint.test.ts +9 -38
  130. package/src/__tests__/skills-files-catalog-fallback.test.ts +0 -3
  131. package/src/__tests__/skills-install-extract.test.ts +49 -38
  132. package/src/__tests__/skills-install-staging.test.ts +159 -0
  133. package/src/__tests__/skills-uninstall.test.ts +9 -41
  134. package/src/__tests__/skills.test.ts +51 -58
  135. package/src/__tests__/slack-channel-config.test.ts +9 -0
  136. package/src/__tests__/subagent-tool-filtering.test.ts +50 -0
  137. package/src/__tests__/system-prompt.test.ts +737 -63
  138. package/src/__tests__/terminal-tools.test.ts +28 -1
  139. package/src/__tests__/thread-backfill.test.ts +557 -27
  140. package/src/__tests__/title-generate-pipeline.test.ts +0 -13
  141. package/src/__tests__/token-estimate-pipeline.test.ts +0 -3
  142. package/src/__tests__/tool-error-pipeline.test.ts +0 -3
  143. package/src/__tests__/tool-execute-pipeline.test.ts +0 -5
  144. package/src/__tests__/tool-executor-lifecycle-events.test.ts +1 -1
  145. package/src/__tests__/tool-executor.test.ts +16 -4
  146. package/src/__tests__/tool-result-truncate-pipeline.test.ts +0 -12
  147. package/src/__tests__/turn-events-store.test.ts +256 -0
  148. package/src/__tests__/twilio-routes.test.ts +4 -0
  149. package/src/__tests__/user-plugin-loader.test.ts +0 -7
  150. package/src/__tests__/voice-session-bridge.test.ts +198 -0
  151. package/src/__tests__/web-search-catalog-parity.test.ts +32 -10
  152. package/src/__tests__/workspace-migration-057-repair-stale-gemini-model-ids.test.ts +115 -3
  153. package/src/__tests__/workspace-migration-072-seed-reply-suggestion-callsite.test.ts +50 -0
  154. package/src/__tests__/workspace-migration-073-repair-recall-callsite-empty-profile.test.ts +153 -0
  155. package/src/__tests__/workspace-migration-085-memory-v2-bm25-b-reembed-disabled-v2-pages.test.ts +220 -0
  156. package/src/__tests__/workspace-migration-086-revert-stale-gemini-mis-rewrites.test.ts +269 -0
  157. package/src/__tests__/workspace-migration-remove-legacy-skills-index.test.ts +309 -0
  158. package/src/__tests__/workspace-migrations-runner.test.ts +111 -3
  159. package/src/acp/resolve-agent.ts +1 -1
  160. package/src/agent/image-optimize.ts +13 -5
  161. package/src/calls/voice-session-bridge.ts +61 -42
  162. package/src/channels/types.ts +108 -0
  163. package/src/cli/__tests__/unknown-command.test.ts +24 -0
  164. package/src/cli/commands/__tests__/changelog.test.ts +304 -319
  165. package/src/cli/commands/__tests__/schedules.test.ts +491 -0
  166. package/src/cli/commands/changelog.ts +106 -42
  167. package/src/cli/commands/conversations.ts +102 -17
  168. package/src/cli/commands/default-action.ts +10 -53
  169. package/src/cli/commands/notifications.ts +329 -317
  170. package/src/cli/commands/plugins.ts +185 -0
  171. package/src/cli/commands/schedules.ts +391 -0
  172. package/src/cli/commands/telemetry.ts +40 -0
  173. package/src/cli/lib/__tests__/cli-colors.test.ts +48 -0
  174. package/src/cli/lib/__tests__/confirm-prompt.test.ts +159 -0
  175. package/src/cli/lib/__tests__/install-from-github.test.ts +355 -0
  176. package/src/cli/lib/__tests__/list-installed-plugins.test.ts +154 -0
  177. package/src/cli/lib/__tests__/uninstall-plugin.test.ts +124 -0
  178. package/src/cli/lib/__tests__/unknown-command.test.ts +106 -0
  179. package/src/cli/lib/cli-colors.ts +12 -0
  180. package/src/cli/lib/confirm-prompt.ts +79 -0
  181. package/src/cli/lib/install-from-github.ts +304 -0
  182. package/src/cli/lib/list-installed-plugins.ts +137 -0
  183. package/src/cli/lib/uninstall-plugin.ts +82 -0
  184. package/src/cli/lib/unknown-command.ts +111 -0
  185. package/src/cli/program.ts +38 -2
  186. package/src/config/bundled-skills/app-builder/SKILL.md +23 -21
  187. package/src/config/bundled-skills/app-builder/TOOLS.json +7 -0
  188. package/src/config/bundled-skills/computer-use/TOOLS.json +15 -52
  189. package/src/config/bundled-skills/document/SKILL.md +23 -3
  190. package/src/config/bundled-skills/document/TOOLS.json +53 -0
  191. package/src/config/bundled-skills/document/tools/document-delete.ts +12 -0
  192. package/src/config/bundled-skills/document/tools/document-list.ts +12 -0
  193. package/src/config/bundled-skills/document/tools/document-read.ts +12 -0
  194. package/src/config/bundled-skills/skill-management/SKILL.md +2 -2
  195. package/src/config/bundled-skills/skill-management/TOOLS.json +7 -7
  196. package/src/config/bundled-tool-registry.ts +6 -0
  197. package/src/config/feature-flag-registry.json +41 -1
  198. package/src/config/loader.ts +64 -38
  199. package/src/config/schema.ts +7 -10
  200. package/src/config/schemas/__tests__/llm-request-logs.test.ts +36 -0
  201. package/src/config/schemas/channels.ts +8 -0
  202. package/src/config/schemas/compaction.ts +28 -0
  203. package/src/config/schemas/heartbeat.ts +9 -0
  204. package/src/config/schemas/llm-request-logs.ts +31 -7
  205. package/src/config/schemas/llm.ts +3 -0
  206. package/src/config/schemas/memory-retrieval.ts +18 -0
  207. package/src/config/schemas/tools.ts +14 -0
  208. package/src/config/skills.ts +3 -96
  209. package/src/context/compactor.ts +1047 -0
  210. package/src/context/token-estimator.ts +2 -2
  211. package/src/context/window-manager.ts +197 -1520
  212. package/src/credential-execution/managed-catalog.ts +37 -0
  213. package/src/credential-health/credential-health-service.ts +280 -19
  214. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +34 -0
  215. package/src/daemon/__tests__/conversation-tool-setup-exclude.test.ts +138 -0
  216. package/src/daemon/__tests__/conversation-tool-setup.test.ts +74 -0
  217. package/src/daemon/approval-generators.ts +8 -6
  218. package/src/daemon/config-watcher.ts +94 -31
  219. package/src/daemon/conversation-agent-loop.ts +169 -9
  220. package/src/daemon/conversation-error.ts +171 -37
  221. package/src/daemon/conversation-lifecycle.ts +53 -40
  222. package/src/daemon/conversation-messaging.ts +25 -6
  223. package/src/daemon/conversation-process.ts +49 -12
  224. package/src/daemon/conversation-runtime-assembly.ts +16 -1
  225. package/src/daemon/conversation-slash.ts +12 -5
  226. package/src/daemon/conversation-store.ts +11 -4
  227. package/src/daemon/conversation-tool-setup.ts +39 -7
  228. package/src/daemon/conversation.ts +33 -1
  229. package/src/daemon/external-plugins-bootstrap.ts +217 -181
  230. package/src/daemon/first-greeting.ts +22 -2
  231. package/src/daemon/handlers/config-model.ts +6 -5
  232. package/src/daemon/handlers/config-slack-channel.ts +15 -3
  233. package/src/daemon/handlers/shared.ts +14 -5
  234. package/src/daemon/handlers/skills.ts +111 -108
  235. package/src/daemon/history-repair.ts +28 -1
  236. package/src/daemon/host-app-control-proxy.ts +98 -23
  237. package/src/daemon/lifecycle.ts +45 -35
  238. package/src/daemon/meet-host-supervisor.ts +5 -4
  239. package/src/daemon/memory-v2-startup.ts +49 -0
  240. package/src/daemon/message-protocol.ts +1 -0
  241. package/src/daemon/message-types/conversations.ts +25 -0
  242. package/src/daemon/message-types/messages.ts +61 -0
  243. package/src/daemon/message-types/subagents.ts +1 -0
  244. package/src/daemon/message-types/sync.ts +1 -0
  245. package/src/daemon/pkb-reminder-builder.test.ts +1 -1
  246. package/src/daemon/pkb-reminder-builder.ts +1 -1
  247. package/src/daemon/plugin-source-watcher.ts +146 -0
  248. package/src/daemon/process-message.ts +21 -3
  249. package/src/daemon/server.ts +11 -2
  250. package/src/daemon/skill-memory-refresh.ts +29 -0
  251. package/src/documents/document-store.ts +221 -3
  252. package/src/embedded/plugin-api.ts +40 -0
  253. package/src/filing/filing-service.ts +39 -0
  254. package/src/heartbeat/__tests__/heartbeat-service.test.ts +91 -6
  255. package/src/heartbeat/heartbeat-run-store.ts +2 -1
  256. package/src/heartbeat/heartbeat-service.ts +41 -0
  257. package/src/home/__tests__/feed-types.test.ts +40 -0
  258. package/src/home/feed-types.ts +22 -0
  259. package/src/home/post-connect-feed.ts +1 -0
  260. package/src/index.ts +18 -1
  261. package/src/live-voice/__tests__/live-voice-stt.test.ts +57 -0
  262. package/src/mcp/client.ts +20 -4
  263. package/src/media/image-credentials.ts +3 -3
  264. package/src/memory/__tests__/bookmark-crud.test.ts +33 -27
  265. package/src/memory/__tests__/conversation-queries.test.ts +263 -0
  266. package/src/memory/__tests__/jobs-worker-v2-graph-trigger-embed.test.ts +113 -0
  267. package/src/memory/__tests__/memory-retrospective-startup-cleanup.test.ts +119 -14
  268. package/src/memory/__tests__/message-content.test.ts +35 -0
  269. package/src/memory/bookmark-crud.ts +42 -10
  270. package/src/memory/context-search/sources/conversations.ts +62 -2
  271. package/src/memory/context-search/sources/workspace.ts +4 -0
  272. package/src/memory/conversation-crud.ts +63 -19
  273. package/src/memory/conversation-queries.ts +110 -10
  274. package/src/memory/db-init.ts +6 -0
  275. package/src/memory/delivery-crud.ts +152 -5
  276. package/src/memory/embedding-backend.ts +4 -4
  277. package/src/memory/external-conversation-store.ts +66 -5
  278. package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +66 -9
  279. package/src/memory/graph/conversation-graph-memory.ts +31 -15
  280. package/src/memory/graph/tools.ts +3 -3
  281. package/src/memory/indexer.ts +34 -29
  282. package/src/memory/jobs/__tests__/embed-concept-page.test.ts +73 -0
  283. package/src/memory/jobs/embed-concept-page.ts +20 -11
  284. package/src/memory/jobs-worker.ts +6 -1
  285. package/src/memory/llm-request-log-source-clickhouse.ts +17 -10
  286. package/src/memory/llm-request-log-source.ts +19 -52
  287. package/src/memory/llm-usage-store.ts +125 -5
  288. package/src/memory/memory-retrospective-startup-cleanup.ts +72 -5
  289. package/src/memory/message-content.ts +1 -1
  290. package/src/memory/migrations/109-external-conversation-bindings.ts +15 -4
  291. package/src/memory/migrations/229-delete-private-conversations.test.ts +38 -1
  292. package/src/memory/migrations/229-delete-private-conversations.ts +7 -0
  293. package/src/memory/migrations/247-external-conversation-binding-thread-id.ts +78 -0
  294. package/src/memory/migrations/248-create-onboarding-events.ts +21 -0
  295. package/src/memory/migrations/249-normalize-slack-external-content.ts +240 -0
  296. package/src/memory/migrations/index.ts +6 -0
  297. package/src/memory/migrations/registry.ts +8 -0
  298. package/src/memory/onboarding-events-store.ts +106 -0
  299. package/src/memory/schema/bookmarks.ts +0 -2
  300. package/src/memory/schema/calls.ts +1 -0
  301. package/src/memory/schema/inference.ts +1 -3
  302. package/src/memory/schema/infrastructure.ts +12 -0
  303. package/src/memory/turn-events-store.ts +127 -2
  304. package/src/memory/v2/__tests__/activation.test.ts +0 -8
  305. package/src/memory/v2/__tests__/injection.test.ts +98 -8
  306. package/src/memory/v2/__tests__/migration.test.ts +87 -0
  307. package/src/memory/v2/__tests__/page-index.test.ts +83 -0
  308. package/src/memory/v2/__tests__/prompts-router.test.ts +58 -6
  309. package/src/memory/v2/__tests__/qdrant.test.ts +66 -3
  310. package/src/memory/v2/__tests__/router.test.ts +15 -0
  311. package/src/memory/v2/__tests__/skill-store.test.ts +387 -8
  312. package/src/memory/v2/injection.ts +32 -6
  313. package/src/memory/v2/migration.ts +49 -19
  314. package/src/memory/v2/page-index.ts +35 -5
  315. package/src/memory/v2/prompts/router.ts +11 -8
  316. package/src/memory/v2/prompts/sweep.ts +2 -2
  317. package/src/memory/v2/qdrant.ts +135 -7
  318. package/src/memory/v2/router.ts +9 -8
  319. package/src/memory/v2/skill-store.ts +120 -35
  320. package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +45 -5
  321. package/src/messaging/providers/slack/__tests__/download.test.ts +231 -0
  322. package/src/messaging/providers/slack/adapter.ts +43 -5
  323. package/src/messaging/providers/slack/client.ts +27 -0
  324. package/src/messaging/providers/slack/deep-link.ts +65 -0
  325. package/src/messaging/providers/slack/download.ts +104 -0
  326. package/src/messaging/providers/slack/message-metadata.test.ts +32 -0
  327. package/src/messaging/providers/slack/message-metadata.ts +27 -0
  328. package/src/messaging/providers/slack/render-transcript.test.ts +134 -0
  329. package/src/messaging/providers/slack/render-transcript.ts +69 -5
  330. package/src/messaging/providers/slack/types.ts +20 -1
  331. package/src/notifications/conversation-pairing.ts +2 -1
  332. package/src/notifications/decision-engine.ts +2 -1
  333. package/src/notifications/emit-signal.ts +20 -1
  334. package/src/notifications/home-feed-side-effect.ts +54 -0
  335. package/src/notifications/signal.ts +3 -1
  336. package/src/oauth/connection-resolver.ts +8 -4
  337. package/src/oauth/platform-connection.ts +6 -2
  338. package/src/oauth/seed-providers.ts +10 -1
  339. package/src/permissions/checker.ts +2 -0
  340. package/src/permissions/ipc-risk-types.ts +1 -0
  341. package/src/permissions/question-prompter.test.ts +416 -0
  342. package/src/permissions/question-prompter.ts +294 -0
  343. package/src/platform/client.test.ts +1 -1
  344. package/src/platform/client.ts +1 -1
  345. package/src/plugin-api/constants.ts +26 -0
  346. package/src/plugin-api/index.ts +34 -1
  347. package/src/plugin-api/types.ts +104 -22
  348. package/src/plugins/defaults/circuit-breaker.ts +0 -5
  349. package/src/plugins/defaults/compaction.ts +0 -4
  350. package/src/plugins/defaults/empty-response.ts +0 -2
  351. package/src/plugins/defaults/history-repair.ts +0 -2
  352. package/src/plugins/defaults/injectors.ts +36 -3
  353. package/src/plugins/defaults/llm-call.ts +0 -2
  354. package/src/plugins/defaults/memory-retrieval.ts +0 -1
  355. package/src/plugins/defaults/overflow-reduce.ts +0 -1
  356. package/src/plugins/defaults/persistence.ts +0 -2
  357. package/src/plugins/defaults/title-generate.ts +0 -5
  358. package/src/plugins/defaults/token-estimate.ts +0 -2
  359. package/src/plugins/defaults/tool-error.ts +0 -7
  360. package/src/plugins/defaults/tool-execute.ts +0 -2
  361. package/src/plugins/defaults/tool-result-truncate.ts +0 -4
  362. package/src/plugins/ensure-plugin-api-shim.ts +96 -0
  363. package/src/plugins/external-api.ts +104 -0
  364. package/src/plugins/external-plugin-loader.ts +105 -32
  365. package/src/plugins/feature-gate.ts +22 -0
  366. package/src/plugins/pipeline.ts +37 -0
  367. package/src/plugins/registry.ts +48 -80
  368. package/src/plugins/types.ts +31 -26
  369. package/src/plugins/user-loader.ts +21 -2
  370. package/src/proactive-artifact/aux-message-injector.ts +11 -0
  371. package/src/proactive-artifact/job.test.ts +37 -5
  372. package/src/prompts/__tests__/system-prompt.test.ts +12 -0
  373. package/src/prompts/__tests__/task-progress-hint-section.test.ts +99 -0
  374. package/src/prompts/normalize-onboarding.ts +27 -0
  375. package/src/prompts/sections.ts +302 -0
  376. package/src/prompts/system-prompt.ts +63 -166
  377. package/src/prompts/templates/BOOTSTRAP.md +17 -1
  378. package/src/prompts/templates/system-sections.ts +173 -0
  379. package/src/providers/__tests__/inference.test.ts +22 -7
  380. package/src/providers/anthropic/client.ts +28 -28
  381. package/src/providers/connection-resolution.ts +7 -0
  382. package/src/providers/inference/adapter-factory.ts +41 -4
  383. package/src/providers/inference/connections.ts +74 -29
  384. package/src/providers/inference/resolve-auth.ts +12 -4
  385. package/src/providers/model-catalog.ts +294 -12
  386. package/src/providers/openai/chat-completions-provider.ts +10 -2
  387. package/src/providers/openrouter/client.ts +7 -0
  388. package/src/providers/{managed-proxy → platform-proxy}/constants.ts +4 -1
  389. package/src/providers/{managed-proxy → platform-proxy}/context.ts +3 -3
  390. package/src/providers/provider-availability.ts +17 -2
  391. package/src/providers/provider-catalog-visibility.ts +36 -0
  392. package/src/providers/registry.ts +22 -14
  393. package/src/providers/retry.ts +47 -1
  394. package/src/runtime/__tests__/agent-wake.test.ts +152 -0
  395. package/src/runtime/agent-wake.ts +42 -14
  396. package/src/runtime/auth/route-policy.ts +8 -1
  397. package/src/runtime/btw-sidechain.ts +2 -0
  398. package/src/runtime/http-types.ts +19 -0
  399. package/src/runtime/migrations/origin-mode.ts +1 -1
  400. package/src/runtime/pending-interactions.ts +1 -0
  401. package/src/runtime/routes/__tests__/bookmark-routes.test.ts +17 -0
  402. package/src/runtime/routes/__tests__/conversation-management-routes.test.ts +5 -1
  403. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +107 -20
  404. package/src/runtime/routes/__tests__/question-routes.test.ts +395 -0
  405. package/src/runtime/routes/__tests__/tts-routes.test.ts +64 -1
  406. package/src/runtime/routes/acp-routes-list.test.ts +143 -0
  407. package/src/runtime/routes/acp-routes.ts +5 -3
  408. package/src/runtime/routes/auth-routes.ts +1 -1
  409. package/src/runtime/routes/bookmark-routes.ts +5 -3
  410. package/src/runtime/routes/btw-routes.ts +5 -1
  411. package/src/runtime/routes/channel-availability-routes.ts +121 -0
  412. package/src/runtime/routes/conversation-cli-routes.ts +44 -3
  413. package/src/runtime/routes/conversation-list-routes.ts +3 -20
  414. package/src/runtime/routes/conversation-management-routes.ts +17 -42
  415. package/src/runtime/routes/conversation-query-routes.ts +40 -35
  416. package/src/runtime/routes/conversation-routes.ts +90 -11
  417. package/src/runtime/routes/documents-routes.ts +25 -86
  418. package/src/runtime/routes/group-routes.ts +5 -0
  419. package/src/runtime/routes/inbound-conversation.ts +28 -8
  420. package/src/runtime/routes/inbound-message-handler.ts +236 -41
  421. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +111 -0
  422. package/src/runtime/routes/inbound-stages/background-dispatch.ts +32 -1
  423. package/src/runtime/routes/inbound-stages/edit-intercept.ts +17 -4
  424. package/src/runtime/routes/index.ts +6 -0
  425. package/src/runtime/routes/inference-profile-session-handler.ts +17 -44
  426. package/src/runtime/routes/inference-profile-session-reaper.ts +7 -21
  427. package/src/runtime/routes/inference-provider-connection-routes.ts +65 -21
  428. package/src/runtime/routes/integrations/slack/share.ts +4 -52
  429. package/src/runtime/routes/integrations/slack/token.ts +43 -0
  430. package/src/runtime/routes/integrations/twilio.ts +6 -13
  431. package/src/runtime/routes/notification-routes.ts +1 -1
  432. package/src/runtime/routes/oauth-commands-routes.ts +105 -15
  433. package/src/runtime/routes/oauth-lifecycle-routes.ts +43 -0
  434. package/src/runtime/routes/question-routes.ts +259 -0
  435. package/src/runtime/routes/rename-conversation-routes.ts +2 -33
  436. package/src/runtime/routes/schedule-routes.ts +4 -7
  437. package/src/runtime/routes/subagents-routes.ts +57 -18
  438. package/src/runtime/routes/telemetry-routes.ts +27 -0
  439. package/src/runtime/routes/tts-routes.ts +27 -2
  440. package/src/runtime/routes/workspace-routes.test.ts +43 -0
  441. package/src/runtime/routes/workspace-routes.ts +28 -0
  442. package/src/runtime/services/conversation-serializer.ts +39 -7
  443. package/src/runtime/sync/resource-sync-events.ts +93 -1
  444. package/src/schedule/schedule-store.ts +27 -2
  445. package/src/schedule/scheduler.ts +9 -1
  446. package/src/security/__tests__/untrusted-content.test.ts +86 -0
  447. package/src/security/untrusted-content.ts +93 -8
  448. package/src/skills/catalog-files.ts +1 -1
  449. package/src/skills/catalog-install.ts +233 -116
  450. package/src/skills/clawhub.ts +70 -13
  451. package/src/skills/managed-store.ts +4 -119
  452. package/src/skills/skillssh-registry.ts +27 -48
  453. package/src/subagent/manager.ts +15 -7
  454. package/src/telemetry/types.ts +113 -1
  455. package/src/telemetry/usage-telemetry-reporter.test.ts +312 -5
  456. package/src/telemetry/usage-telemetry-reporter.ts +113 -7
  457. package/src/tools/apps/executors.ts +58 -7
  458. package/src/tools/ask-question/ask-question-tool.test.ts +509 -0
  459. package/src/tools/ask-question/ask-question-tool.ts +304 -0
  460. package/src/tools/browser/browser-execution.ts +15 -11
  461. package/src/tools/computer-use/definitions.ts +3 -3
  462. package/src/tools/credentials/vault.ts +1 -1
  463. package/src/tools/document/document-tool.ts +124 -1
  464. package/src/tools/filesystem/edit.ts +1 -1
  465. package/src/tools/filesystem/list.ts +1 -1
  466. package/src/tools/filesystem/read.ts +1 -1
  467. package/src/tools/filesystem/write.ts +5 -2
  468. package/src/tools/host-filesystem/transfer.ts +1 -1
  469. package/src/tools/host-terminal/host-shell.ts +1 -1
  470. package/src/tools/permission-checker.ts +1 -1
  471. package/src/tools/registry.ts +17 -7
  472. package/src/tools/schedule/create.ts +2 -2
  473. package/src/tools/schema-transforms.ts +7 -2
  474. package/src/tools/side-effects.ts +1 -0
  475. package/src/tools/skills/delete-managed.ts +4 -4
  476. package/src/tools/skills/execute.ts +1 -1
  477. package/src/tools/skills/scaffold-managed.ts +3 -2
  478. package/src/tools/subagent/notify-parent.ts +1 -1
  479. package/src/tools/system/request-permission.ts +2 -2
  480. package/src/tools/terminal/safe-env.ts +60 -1
  481. package/src/tools/tool-manifest.ts +2 -0
  482. package/src/tools/types.ts +72 -21
  483. package/src/tools/ui-surface/definitions.ts +6 -5
  484. package/src/tts/__tests__/provider-adapters.test.ts +76 -2
  485. package/src/tts/providers/elevenlabs-provider.ts +75 -1
  486. package/src/types/onboarding-context.ts +2 -0
  487. package/src/util/errors.ts +17 -0
  488. package/src/util/platform.ts +10 -0
  489. package/src/watcher/__tests__/engine.test.ts +22 -0
  490. package/src/watcher/engine.ts +6 -2
  491. package/src/workspace/migrations/057-repair-stale-gemini-model-ids.ts +80 -15
  492. package/src/workspace/migrations/072-seed-reply-suggestion-callsite.ts +35 -22
  493. package/src/workspace/migrations/073-repair-recall-callsite-empty-profile.ts +3 -1
  494. package/src/workspace/migrations/083-system-prompt-prefix-to-file.ts +191 -0
  495. package/src/workspace/migrations/084-remove-legacy-skills-index.ts +276 -0
  496. package/src/workspace/migrations/085-memory-v2-bm25-b-reembed-disabled-v2-pages.ts +137 -0
  497. package/src/workspace/migrations/086-revert-stale-gemini-mis-rewrites.ts +198 -0
  498. package/src/workspace/migrations/registry.ts +8 -0
  499. package/src/workspace/migrations/runner.ts +39 -9
  500. package/src/workspace/migrations/types.ts +4 -0
  501. package/examples/plugins/echo/bun.lock +0 -25
  502. package/src/__tests__/context-window-manager.test.ts +0 -2481
  503. package/src/context/__tests__/compact-prompt.test.ts +0 -63
  504. package/src/context/prompts/compact.md +0 -26
  505. package/src/prompts/__tests__/build-cli-reference-section.test.ts +0 -37
  506. /package/src/__tests__/{secret-routes-managed-proxy.test.ts → secret-routes-platform-proxy.test.ts} +0 -0
@@ -0,0 +1,258 @@
1
+ import { beforeEach, describe, expect, test } from "bun:test";
2
+
3
+ import { getDocumentById } from "../documents/document-store.js";
4
+ import { getSqlite, resetDb } from "../memory/db-connection.js";
5
+ import {
6
+ executeDocumentDelete,
7
+ executeDocumentList,
8
+ executeDocumentRead,
9
+ executeDocumentUpdate,
10
+ } from "../tools/document/document-tool.js";
11
+ import type { ToolContext, ToolExecutionResult } from "../tools/types.js";
12
+
13
+ function makeContext(overrides: Partial<ToolContext> = {}): ToolContext {
14
+ return {
15
+ workingDir: "/tmp/project",
16
+ conversationId: "conv-current",
17
+ trustClass: "trusted_contact",
18
+ executionChannel: "slack",
19
+ ...overrides,
20
+ };
21
+ }
22
+
23
+ function parseResult<T>(result: ToolExecutionResult): T {
24
+ return JSON.parse(result.content) as T;
25
+ }
26
+
27
+ function bootstrapDocumentTables(): void {
28
+ resetDb();
29
+ const raw = getSqlite();
30
+ raw.exec(/*sql*/ `
31
+ DROP TABLE IF EXISTS document_conversations;
32
+ DROP TABLE IF EXISTS documents;
33
+ DROP TABLE IF EXISTS conversations;
34
+
35
+ CREATE TABLE conversations (
36
+ id TEXT PRIMARY KEY,
37
+ created_at INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)
38
+ );
39
+
40
+ CREATE TABLE documents (
41
+ surface_id TEXT PRIMARY KEY,
42
+ conversation_id TEXT NOT NULL REFERENCES conversations(id) ON DELETE CASCADE,
43
+ title TEXT NOT NULL,
44
+ content TEXT NOT NULL,
45
+ word_count INTEGER NOT NULL DEFAULT 0,
46
+ created_at INTEGER NOT NULL,
47
+ updated_at INTEGER NOT NULL
48
+ );
49
+
50
+ CREATE TABLE document_conversations (
51
+ surface_id TEXT NOT NULL,
52
+ conversation_id TEXT NOT NULL,
53
+ created_at INTEGER NOT NULL,
54
+ PRIMARY KEY (surface_id, conversation_id),
55
+ FOREIGN KEY (surface_id) REFERENCES documents(surface_id) ON DELETE CASCADE
56
+ );
57
+ `);
58
+ }
59
+
60
+ function seedDocument(params: {
61
+ surfaceId: string;
62
+ conversationId: string;
63
+ title: string;
64
+ content: string;
65
+ updatedAt: number;
66
+ }): void {
67
+ const raw = getSqlite();
68
+ raw
69
+ .query(`INSERT OR IGNORE INTO conversations (id, created_at) VALUES (?, ?)`)
70
+ .run(params.conversationId, params.updatedAt);
71
+ raw
72
+ .query(
73
+ `INSERT INTO documents (surface_id, conversation_id, title, content, word_count, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?)`,
74
+ )
75
+ .run(
76
+ params.surfaceId,
77
+ params.conversationId,
78
+ params.title,
79
+ params.content,
80
+ params.content.split(/\s+/).filter(Boolean).length,
81
+ params.updatedAt,
82
+ params.updatedAt,
83
+ );
84
+ raw
85
+ .query(
86
+ `INSERT OR IGNORE INTO document_conversations (surface_id, conversation_id, created_at) VALUES (?, ?, ?)`,
87
+ )
88
+ .run(params.surfaceId, params.conversationId, params.updatedAt);
89
+ }
90
+
91
+ function seedFixtureDocuments(): void {
92
+ seedDocument({
93
+ surfaceId: "doc-current",
94
+ conversationId: "conv-current",
95
+ title: "Current Business Plan",
96
+ content: "current plan",
97
+ updatedAt: 1000,
98
+ });
99
+ seedDocument({
100
+ surfaceId: "doc-other",
101
+ conversationId: "conv-other",
102
+ title: "Other Business Plan",
103
+ content: "other plan",
104
+ updatedAt: 2000,
105
+ });
106
+ seedDocument({
107
+ surfaceId: "doc-percent",
108
+ conversationId: "conv-other",
109
+ title: "100% Plan",
110
+ content: "literal percent",
111
+ updatedAt: 3000,
112
+ });
113
+ }
114
+
115
+ describe("document tool security", () => {
116
+ beforeEach(() => {
117
+ bootstrapDocumentTables();
118
+ seedFixtureDocuments();
119
+ });
120
+
121
+ test("scopes title search to the current conversation for non-guardian remote actors", () => {
122
+ const result = executeDocumentList(
123
+ { query: "Business Plan" },
124
+ makeContext({ trustClass: "trusted_contact", executionChannel: "slack" }),
125
+ );
126
+
127
+ const body = parseResult<{ documents: Array<{ surface_id: string }> }>(
128
+ result,
129
+ );
130
+ expect(body.documents.map((doc) => doc.surface_id)).toEqual([
131
+ "doc-current",
132
+ ]);
133
+ });
134
+
135
+ test("does not treat SQL LIKE wildcards as title-search wildcards", () => {
136
+ const result = executeDocumentList(
137
+ { query: "%" },
138
+ makeContext({ trustClass: "guardian", executionChannel: "telegram" }),
139
+ );
140
+
141
+ const body = parseResult<{ documents: Array<{ surface_id: string }> }>(
142
+ result,
143
+ );
144
+ expect(body.documents.map((doc) => doc.surface_id)).toEqual([
145
+ "doc-percent",
146
+ ]);
147
+ });
148
+
149
+ test("allows guardian and local actors to use documents from previous conversations", () => {
150
+ const guardianContext = makeContext({
151
+ trustClass: "guardian",
152
+ executionChannel: "telegram",
153
+ sendToClient: () => {},
154
+ });
155
+ const guardianList = executeDocumentList(
156
+ { query: "Other Business" },
157
+ guardianContext,
158
+ );
159
+ const guardianBody = parseResult<{
160
+ documents: Array<{ surface_id: string }>;
161
+ }>(guardianList);
162
+ expect(guardianBody.documents.map((doc) => doc.surface_id)).toEqual([
163
+ "doc-other",
164
+ ]);
165
+
166
+ const localRead = executeDocumentRead(
167
+ { surface_id: "doc-other" },
168
+ makeContext({ trustClass: "unknown", executionChannel: "vellum" }),
169
+ );
170
+ const localBody = parseResult<{
171
+ success: boolean;
172
+ surface_id: string;
173
+ content: string;
174
+ }>(localRead);
175
+ expect(localBody).toMatchObject({
176
+ success: true,
177
+ surface_id: "doc-other",
178
+ content: "other plan",
179
+ });
180
+
181
+ const guardianUpdate = executeDocumentUpdate(
182
+ { surface_id: "doc-other", content: "guardian edit", mode: "replace" },
183
+ guardianContext,
184
+ );
185
+ expect(guardianUpdate.isError).toBe(false);
186
+ expect(getDocumentById("doc-other")?.content).toBe("guardian edit");
187
+
188
+ const guardianDelete = executeDocumentDelete(
189
+ { surface_id: "doc-other" },
190
+ guardianContext,
191
+ );
192
+ expect(guardianDelete.isError).toBe(false);
193
+ expect(getDocumentById("doc-other")).toBeNull();
194
+ });
195
+
196
+ test("blocks cross-conversation read, update, and delete for non-guardian remote actors", () => {
197
+ const remoteContext = makeContext({
198
+ trustClass: "trusted_contact",
199
+ executionChannel: "slack",
200
+ sendToClient: () => {},
201
+ });
202
+
203
+ const read = executeDocumentRead(
204
+ { surface_id: "doc-other" },
205
+ remoteContext,
206
+ );
207
+ expect(read.isError).toBe(true);
208
+ expect(parseResult<{ error: string }>(read).error).toBe(
209
+ "Document not found",
210
+ );
211
+
212
+ const update = executeDocumentUpdate(
213
+ {
214
+ surface_id: "doc-other",
215
+ content: "updated by another conversation",
216
+ mode: "replace",
217
+ },
218
+ remoteContext,
219
+ );
220
+ expect(update.isError).toBe(true);
221
+ expect(getDocumentById("doc-other")?.content).toBe("other plan");
222
+
223
+ const deleted = executeDocumentDelete(
224
+ { surface_id: "doc-other" },
225
+ remoteContext,
226
+ );
227
+ expect(deleted.isError).toBe(true);
228
+ expect(getDocumentById("doc-other")).not.toBeNull();
229
+ });
230
+
231
+ test("keeps current-conversation documents editable and deletable", () => {
232
+ const remoteContext = makeContext({
233
+ trustClass: "trusted_contact",
234
+ executionChannel: "slack",
235
+ sendToClient: () => {},
236
+ });
237
+
238
+ const read = executeDocumentRead(
239
+ { surface_id: "doc-current" },
240
+ remoteContext,
241
+ );
242
+ expect(read.isError).toBe(false);
243
+
244
+ const update = executeDocumentUpdate(
245
+ { surface_id: "doc-current", content: "revised plan", mode: "replace" },
246
+ remoteContext,
247
+ );
248
+ expect(update.isError).toBe(false);
249
+ expect(getDocumentById("doc-current")?.content).toBe("revised plan");
250
+
251
+ const deleted = executeDocumentDelete(
252
+ { surface_id: "doc-current" },
253
+ remoteContext,
254
+ );
255
+ expect(deleted.isError).toBe(false);
256
+ expect(getDocumentById("doc-current")).toBeNull();
257
+ });
258
+ });
@@ -74,7 +74,6 @@ describe("Dynamic Skill Authoring Workflow moved to tool descriptions", () => {
74
74
  join(skillsDir, "test-skill", "SKILL.md"),
75
75
  '---\nname: "Test Skill"\ndescription: "For testing."\n---\n\nDo testing.\n',
76
76
  );
77
- writeFileSync(join(skillsDir, "SKILLS.md"), "- test-skill\n");
78
77
  writeFileSync(join(TEST_DIR, "IDENTITY.md"), "I am Vellum.");
79
78
 
80
79
  const result = buildSystemPrompt();
@@ -19,6 +19,7 @@ mock.module("../util/logger.js", () => ({
19
19
  }));
20
20
 
21
21
  import { addMessage } from "../memory/conversation-crud.js";
22
+ import { getConversationByKey } from "../memory/conversation-key-store.js";
22
23
  import { getDb } from "../memory/db-connection.js";
23
24
  import { initializeDb } from "../memory/db-init.js";
24
25
  import { linkMessage, recordInbound } from "../memory/delivery-crud.js";
@@ -160,6 +161,38 @@ describe("Slack edit propagation", () => {
160
161
  expect(slackMeta!.editedAt!).toBeGreaterThanOrEqual(t0);
161
162
  });
162
163
 
164
+ test("threaded Slack edits use the threaded conversation key and preserve thread metadata", async () => {
165
+ const conversationExternalId = "C0123CHANNEL";
166
+ const threadTs = "1234.0000";
167
+ const seeded = await seedSlackMessage({
168
+ conversationExternalId,
169
+ channelTs: "1234.5678",
170
+ initialContent: "original text",
171
+ });
172
+
173
+ const resp = await handleEditIntercept({
174
+ sourceChannel: "slack",
175
+ conversationExternalId,
176
+ externalMessageId: nextEditEventId(),
177
+ sourceMessageId: seeded.channelTs,
178
+ sourceThreadId: threadTs,
179
+ canonicalAssistantId: "self",
180
+ assistantId: "self",
181
+ content: "new text",
182
+ });
183
+
184
+ expect((resp as Record<string, unknown>).accepted).toBe(true);
185
+ const threadedKey = `asst:self:slack:${conversationExternalId}:thread:${threadTs}`;
186
+ const editConversation = getConversationByKey(threadedKey);
187
+ expect(editConversation).not.toBeNull();
188
+ expect(editConversation!.conversationId).not.toBe(seeded.conversationId);
189
+
190
+ const after = readMessageRow(seeded.messageId);
191
+ const outer = JSON.parse(after.metadata!) as Record<string, unknown>;
192
+ const slackMeta = readSlackMetadata(outer.slackMeta as string);
193
+ expect(slackMeta?.threadTs).toBe(threadTs);
194
+ });
195
+
163
196
  test("is idempotent across successive edits", async () => {
164
197
  const seeded = await seedSlackMessage({
165
198
  conversationExternalId: "C0123CHANNEL",
@@ -192,7 +192,6 @@ describe("emptyResponse pipeline — custom middleware overrides", () => {
192
192
  manifest: {
193
193
  name: "force-accept",
194
194
  version: "1.0.0",
195
- requires: { pluginRuntime: "v1" },
196
195
  },
197
196
  middleware: {
198
197
  emptyResponse: async () => ({ action: "accept" }),
@@ -219,7 +218,6 @@ describe("emptyResponse pipeline — custom middleware overrides", () => {
219
218
  manifest: {
220
219
  name: "force-error",
221
220
  version: "1.0.0",
222
- requires: { pluginRuntime: "v1" },
223
221
  },
224
222
  middleware: {
225
223
  emptyResponse: async () => ({ action: "error" }),
@@ -239,7 +237,6 @@ describe("emptyResponse pipeline — custom middleware overrides", () => {
239
237
  manifest: {
240
238
  name: "rewrite-nudge",
241
239
  version: "1.0.0",
242
- requires: { pluginRuntime: "v1" },
243
240
  },
244
241
  middleware: {
245
242
  emptyResponse: async (args, next, ctx) => {
@@ -287,7 +284,6 @@ describe("emptyResponse pipeline — custom middleware overrides", () => {
287
284
  manifest: {
288
285
  name: "late-user-empty-response",
289
286
  version: "0.0.1",
290
- requires: { pluginRuntime: "v1", emptyResponseApi: "v1" },
291
287
  },
292
288
  middleware: { emptyResponse: userMiddleware },
293
289
  });
@@ -70,8 +70,6 @@ describe("loadExternalPlugin — manifest", () => {
70
70
  );
71
71
  expect(registered).toBeDefined();
72
72
  expect(registered?.manifest.version).toBe("1.2.3");
73
- // Defaults to pluginRuntime v1 when no `vellum.requires` is set.
74
- expect(registered?.manifest.requires).toEqual({ pluginRuntime: "v1" });
75
73
  });
76
74
 
77
75
  test("strips npm scope from name", async () => {
@@ -97,56 +95,84 @@ describe("loadExternalPlugin — manifest", () => {
97
95
  );
98
96
  expect(registered?.manifest.version).toBe("0.0.0");
99
97
  });
98
+ });
100
99
 
101
- test("reads vellum.requires from package.json when present", async () => {
102
- // Future direction (out of scope this PR): `requires` may carry
103
- // `assistantVersion` alongside `pluginRuntime`. For today the registry
104
- // validates against `ASSISTANT_API_VERSIONS` and only `pluginRuntime`
105
- // is a known capability — so the loader's pass-through behavior is
106
- // exercised with the keys the registry already accepts. The loader is
107
- // opaque to the shape; additional keys propagate identically.
108
- const dir = freshPluginDir("custom-requires");
100
+ describe("loadExternalPlugin plugin-api peerDependency", () => {
101
+ // Tests anchor against assistantPkg.version (read from the assistant's
102
+ // own package.json) so the matrix below stays correct across version
103
+ // bumps. Constructing a range from the live version + nudging up/down
104
+ // by one keeps the satisfy/un-satisfy cases honest.
105
+ test("loads when peerDependency range satisfies assistant version", async () => {
106
+ const dir = freshPluginDir("compat-ok");
109
107
  writePackageJson(dir, {
110
- name: "custom-requires",
108
+ name: "compat-ok",
111
109
  version: "0.1.0",
112
- vellum: { requires: { pluginRuntime: "v1" } },
110
+ peerDependencies: { "@vellumai/plugin-api": "*" },
113
111
  });
114
112
 
115
113
  await loadExternalPlugin(dir);
116
114
 
117
- const registered = getRegisteredPlugins().find(
118
- (p) => p.manifest.name === "custom-requires",
119
- );
120
- expect(registered?.manifest.requires).toEqual({ pluginRuntime: "v1" });
115
+ expect(registeredNames()).toContain("compat-ok");
121
116
  });
122
117
 
123
- test("vellum.requires overrides the default empty requires fails registration", async () => {
124
- // Belt-and-suspenders proof that the loader reads `vellum.requires`
125
- // rather than always defaulting: an empty `vellum.requires` propagates
126
- // through, the registry rejects the plugin (missing pluginRuntime),
127
- // and the registry stays empty.
128
- const dir = freshPluginDir("empty-requires");
118
+ test("loads plugin whose peerDependency range excludes assistant version (logs error)", async () => {
119
+ // The host-compat gate is soft while the installation flow is in
120
+ // flux an unsatisfied range produces a `log.error` but the
121
+ // plugin still loads. Once installation settles, this case should
122
+ // harden back into a hard reject.
123
+ const dir = freshPluginDir("compat-bad");
129
124
  writePackageJson(dir, {
130
- name: "empty-requires",
125
+ name: "compat-bad",
131
126
  version: "0.1.0",
132
- vellum: { requires: {} },
127
+ // A range that no real assistant version will satisfy.
128
+ peerDependencies: { "@vellumai/plugin-api": ">=999.0.0" },
133
129
  });
134
130
 
135
131
  await loadExternalPlugin(dir);
136
132
 
137
- expect(registeredNames()).not.toContain("empty-requires");
133
+ expect(registeredNames()).toContain("compat-bad");
138
134
  });
139
135
 
140
- test("does not synthesize a provides field", async () => {
141
- const dir = freshPluginDir("no-provides");
142
- writePackageJson(dir, { name: "no-provides", version: "0.1.0" });
136
+ test("loads plugin whose peerDependency range is unparseable (logs error)", async () => {
137
+ // Same soft-gate rationale as the excluded-range case above.
138
+ const dir = freshPluginDir("compat-bogus");
139
+ writePackageJson(dir, {
140
+ name: "compat-bogus",
141
+ version: "0.1.0",
142
+ peerDependencies: { "@vellumai/plugin-api": "not-a-real-range" },
143
+ });
143
144
 
144
145
  await loadExternalPlugin(dir);
145
146
 
146
- const registered = getRegisteredPlugins().find(
147
- (p) => p.manifest.name === "no-provides",
148
- );
149
- expect(registered?.manifest.provides).toBeUndefined();
147
+ expect(registeredNames()).toContain("compat-bogus");
148
+ });
149
+
150
+ test("loads with warning when no peerDependency on plugin-api is declared", async () => {
151
+ // Absent peerDep is non-fatal — the loader logs a warn and proceeds
152
+ // with no host-compat claim. The convention is opt-in while the
153
+ // plugin-api framework is experimental.
154
+ const dir = freshPluginDir("compat-absent");
155
+ writePackageJson(dir, {
156
+ name: "compat-absent",
157
+ version: "0.1.0",
158
+ });
159
+
160
+ await loadExternalPlugin(dir);
161
+
162
+ expect(registeredNames()).toContain("compat-absent");
163
+ });
164
+
165
+ test("loads with warning when peerDependencies is present but lacks plugin-api key", async () => {
166
+ const dir = freshPluginDir("compat-other-peer");
167
+ writePackageJson(dir, {
168
+ name: "compat-other-peer",
169
+ version: "0.1.0",
170
+ peerDependencies: { react: "^18.0.0" },
171
+ });
172
+
173
+ await loadExternalPlugin(dir);
174
+
175
+ expect(registeredNames()).toContain("compat-other-peer");
150
176
  });
151
177
 
152
178
  test("malformed package.json is logged and skipped (registry untouched)", async () => {
@@ -321,9 +347,8 @@ describe("loadExternalPlugin — tools", () => {
321
347
  `export default {
322
348
  name: "two_tools_alpha",
323
349
  description: "alpha",
324
- category: "plugin",
325
350
  defaultRiskLevel: "low" as const,
326
- getDefinition() { return { name: "two_tools_alpha", description: "alpha", input_schema: { type: "object", properties: {}, required: [] } }; },
351
+ input_schema: { type: "object", properties: {}, required: [] },
327
352
  async execute() { return { content: "a", isError: false }; },
328
353
  };
329
354
  `,
@@ -334,9 +359,8 @@ describe("loadExternalPlugin — tools", () => {
334
359
  `export default {
335
360
  name: "two_tools_beta",
336
361
  description: "beta",
337
- category: "plugin",
338
362
  defaultRiskLevel: "low" as const,
339
- getDefinition() { return { name: "two_tools_beta", description: "beta", input_schema: { type: "object", properties: {}, required: [] } }; },
363
+ input_schema: { type: "object", properties: {}, required: [] },
340
364
  async execute() { return { content: "b", isError: false }; },
341
365
  };
342
366
  `,
@@ -303,6 +303,146 @@ describe("FilingService", () => {
303
303
  expect(processMessageCalls).toHaveLength(1);
304
304
  });
305
305
 
306
+ // Helpers for the compaction-retry tests: hold the filing run open by
307
+ // making processMessage return a manually-resolved promise, so `activeRun`
308
+ // stays set and runCompactionOnce() sees the contention path.
309
+ function holdFilingRun(): {
310
+ release: () => void;
311
+ filingCalls: () => number;
312
+ compactionCalls: () => number;
313
+ waitForFilingStarted: () => Promise<void>;
314
+ } {
315
+ let release: (() => void) | undefined;
316
+ let started = false;
317
+ let filingCalls = 0;
318
+ let compactionCalls = 0;
319
+
320
+ setTestProcessMessage((...args: unknown[]) => {
321
+ const callSite = (args[3] as { callSite?: string } | undefined)
322
+ ?.callSite;
323
+ if (callSite === "filingAgent") {
324
+ filingCalls += 1;
325
+ started = true;
326
+ return new Promise((resolve) => {
327
+ release = () => resolve({ messageId: "filing-done" });
328
+ });
329
+ }
330
+ if (callSite === "compactionAgent") {
331
+ compactionCalls += 1;
332
+ }
333
+ return Promise.resolve({ messageId: "mock" });
334
+ });
335
+
336
+ return {
337
+ release: () => release?.(),
338
+ filingCalls: () => filingCalls,
339
+ compactionCalls: () => compactionCalls,
340
+ waitForFilingStarted: async () => {
341
+ while (!started) await Promise.resolve();
342
+ },
343
+ };
344
+ }
345
+
346
+ test("schedules a near-term retry when filing run is in-flight", async () => {
347
+ const hold = holdFilingRun();
348
+ // 5s retry override paired with a production-realistic 24h compaction
349
+ // interval — the assertion proves retry << interval.
350
+ const retryMs = 5_000;
351
+ mockConfig.filing.compactionIntervalMs = 24 * 60 * 60 * 1000;
352
+ const service = new FilingService({
353
+ compactionContendedRetryMs: retryMs,
354
+ });
355
+ const filingPromise = service.runOnce();
356
+ await hold.waitForFilingStarted();
357
+
358
+ const beforeRetry = Date.now();
359
+ const ran = await service.runCompactionOnce();
360
+
361
+ expect(ran).toBe(false);
362
+ expect(service.nextCompactionAt).not.toBeNull();
363
+ const nextAt = service.nextCompactionAt!;
364
+ expect(nextAt - beforeRetry).toBeLessThan(
365
+ mockConfig.filing.compactionIntervalMs,
366
+ );
367
+ expect(nextAt - beforeRetry).toBeLessThanOrEqual(retryMs + 100);
368
+
369
+ hold.release();
370
+ await filingPromise;
371
+ await service.stop();
372
+ });
373
+
374
+ test("retry fires after filing run completes", async () => {
375
+ const hold = holdFilingRun();
376
+ const service = new FilingService({ compactionContendedRetryMs: 1 });
377
+ const filingPromise = service.runOnce();
378
+ await hold.waitForFilingStarted();
379
+
380
+ const skipped = await service.runCompactionOnce();
381
+ expect(skipped).toBe(false);
382
+ expect(hold.compactionCalls()).toBe(0);
383
+
384
+ hold.release();
385
+ await filingPromise;
386
+
387
+ const start = Date.now();
388
+ while (hold.compactionCalls() === 0 && Date.now() - start < 1000) {
389
+ await new Promise((resolve) => setTimeout(resolve, 5));
390
+ }
391
+
392
+ expect(hold.filingCalls()).toBe(1);
393
+ expect(hold.compactionCalls()).toBe(1);
394
+
395
+ await service.stop();
396
+ });
397
+
398
+ test("stop() clears a scheduled compaction retry", async () => {
399
+ const hold = holdFilingRun();
400
+ const service = new FilingService({ compactionContendedRetryMs: 50 });
401
+ const filingPromise = service.runOnce();
402
+ await hold.waitForFilingStarted();
403
+ await service.runCompactionOnce();
404
+ expect(service.nextCompactionAt).not.toBeNull();
405
+
406
+ hold.release();
407
+ await filingPromise;
408
+ await service.stop();
409
+
410
+ // After stop, the retry timer must be cleared and never fire.
411
+ expect(service.nextCompactionAt).toBeNull();
412
+ await new Promise((resolve) => setTimeout(resolve, 100));
413
+ expect(hold.compactionCalls()).toBe(0);
414
+ });
415
+
416
+ test("stop() prevents retry callback from re-arming a fresh timer", async () => {
417
+ // Race: the retry callback fires while filing is still in-flight and
418
+ // stop() has begun. The callback already cleared compactionRetryTimer,
419
+ // so clearCompactionRetry is a no-op. Without a stopped flag, the
420
+ // callback's runCompactionOnce() hits the activeRun branch and schedules
421
+ // a fresh retry, leaving a live timer after stop() resolves.
422
+ const hold = holdFilingRun();
423
+ const service = new FilingService({ compactionContendedRetryMs: 5 });
424
+ const filingPromise = service.runOnce();
425
+ await hold.waitForFilingStarted();
426
+ await service.runCompactionOnce();
427
+ expect(service.nextCompactionAt).not.toBeNull();
428
+
429
+ // Begin stop without awaiting — it would block on the held filing run.
430
+ // stop() flips `stopped` synchronously before the retry timer fires.
431
+ const stopPromise = service.stop();
432
+
433
+ // Wait past the retry delay. Without the guard, the callback would call
434
+ // runCompactionOnce(), observe activeRun, and re-arm a new retry.
435
+ await new Promise((resolve) => setTimeout(resolve, 50));
436
+ expect(service.nextCompactionAt).toBeNull();
437
+
438
+ hold.release();
439
+ await filingPromise;
440
+ await stopPromise;
441
+
442
+ expect(service.nextCompactionAt).toBeNull();
443
+ expect(hold.compactionCalls()).toBe(0);
444
+ });
445
+
306
446
  test("respects active hours", async () => {
307
447
  mockConfig.filing.activeHoursStart = 9;
308
448
  mockConfig.filing.activeHoursEnd = 17;
@@ -150,13 +150,11 @@ mock.module("../skills/clawhub-files.js", () => ({
150
150
 
151
151
  mock.module("../skills/catalog-install.js", () => ({
152
152
  installSkillLocally: async () => {},
153
- upsertSkillsIndex: () => {},
154
153
  }));
155
154
 
156
155
  mock.module("../skills/managed-store.js", () => ({
157
156
  createManagedSkill: () => ({ created: true }),
158
157
  deleteManagedSkill: () => ({ deleted: true }),
159
- removeSkillsIndexEntry: () => {},
160
158
  validateManagedSkillId: () => null,
161
159
  }));
162
160
 
@@ -188,8 +186,6 @@ import { getSkill } from "../daemon/handlers/skills.js";
188
186
  // Helpers
189
187
  // ---------------------------------------------------------------------------
190
188
 
191
-
192
-
193
189
  // ---------------------------------------------------------------------------
194
190
  // Tests
195
191
  // ---------------------------------------------------------------------------