@vellumai/assistant 0.10.2 → 0.10.3-dev.202606252046.9075fd5

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 (546) hide show
  1. package/bun.lock +20 -0
  2. package/docs/workspace-tools.md +42 -33
  3. package/eslint-rules/cli-no-daemon-internals.js +6 -0
  4. package/node_modules/@vellumai/gateway-client/src/__tests__/guardian-delivery-contract.test.ts +91 -0
  5. package/node_modules/@vellumai/gateway-client/src/__tests__/trust-verdict-contract.test.ts +31 -0
  6. package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +44 -0
  7. package/node_modules/@vellumai/gateway-client/src/guardian-delivery-contract.ts +48 -0
  8. package/node_modules/@vellumai/gateway-client/src/index.ts +14 -0
  9. package/node_modules/@vellumai/gateway-client/src/trust-verdict-contract.ts +17 -0
  10. package/node_modules/@vellumai/service-contracts/package.json +1 -0
  11. package/node_modules/@vellumai/service-contracts/src/__tests__/channels.test.ts +28 -0
  12. package/node_modules/@vellumai/service-contracts/src/channels.ts +41 -0
  13. package/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
  14. package/openapi.yaml +196 -56
  15. package/package.json +4 -1
  16. package/scripts/test.sh +36 -15
  17. package/src/__tests__/actor-token-service.test.ts +36 -14
  18. package/src/__tests__/actor-trust-resolver-address-fallback.test.ts +83 -31
  19. package/src/__tests__/agent-loop-override-profile.test.ts +1 -0
  20. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +2 -0
  21. package/src/__tests__/agent-wake-override-profile.test.ts +2 -0
  22. package/src/__tests__/annotate-activity-metadata.test.ts +2 -0
  23. package/src/__tests__/annotate-risk-options.test.ts +2 -0
  24. package/src/__tests__/approval-cascade.test.ts +2 -0
  25. package/src/__tests__/assistant-attachments.test.ts +42 -0
  26. package/src/__tests__/background-workers-disk-pressure.test.ts +2 -0
  27. package/src/__tests__/btw-routes.test.ts +2 -0
  28. package/src/__tests__/build-persisted-content.test.ts +2 -0
  29. package/src/__tests__/call-controller.test.ts +19 -0
  30. package/src/__tests__/channel-approval-routes.test.ts +21 -26
  31. package/src/__tests__/channel-delivery-store.test.ts +28 -0
  32. package/src/__tests__/channel-guardian.test.ts +164 -78
  33. package/src/__tests__/channel-inbound-disk-pressure.test.ts +11 -19
  34. package/src/__tests__/channel-reply-delivery.test.ts +2 -0
  35. package/src/__tests__/code-search-tool.test.ts +585 -0
  36. package/src/__tests__/compaction-events.test.ts +2 -0
  37. package/src/__tests__/compaction-ledger-store.test.ts +128 -0
  38. package/src/__tests__/compaction.benchmark.test.ts +2 -0
  39. package/src/__tests__/compactor-call-site-logging.test.ts +2 -0
  40. package/src/__tests__/compactor-low-watermark-cut.test.ts +2 -0
  41. package/src/__tests__/compactor-preserved-tail-count.test.ts +2 -0
  42. package/src/__tests__/compactor-summary-call-truncation.test.ts +2 -0
  43. package/src/__tests__/compactor-web-search-strip.test.ts +2 -0
  44. package/src/__tests__/config-loader-backfill.test.ts +123 -10
  45. package/src/__tests__/config-schema.test.ts +1 -0
  46. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +31 -29
  47. package/src/__tests__/consult-deadline.test.ts +60 -0
  48. package/src/__tests__/contact-store-interaction-info.test.ts +156 -0
  49. package/src/__tests__/contact-store-user-file.test.ts +7 -10
  50. package/src/__tests__/contacts-relay-reads.test.ts +19 -24
  51. package/src/__tests__/contacts-write.test.ts +0 -2
  52. package/src/__tests__/conversation-abort-tool-results.test.ts +2 -0
  53. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +2 -0
  54. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +2 -0
  55. package/src/__tests__/conversation-agent-loop-overflow.test.ts +2 -0
  56. package/src/__tests__/conversation-agent-loop.test.ts +134 -0
  57. package/src/__tests__/conversation-analysis-routes.test.ts +2 -0
  58. package/src/__tests__/conversation-app-control-lifecycle.test.ts +2 -0
  59. package/src/__tests__/conversation-attention-telegram.test.ts +9 -11
  60. package/src/__tests__/conversation-confirmation-signals.test.ts +2 -0
  61. package/src/__tests__/conversation-fork-crud.test.ts +354 -24
  62. package/src/__tests__/conversation-history-web-search.test.ts +2 -0
  63. package/src/__tests__/conversation-load-history-repair.test.ts +2 -0
  64. package/src/__tests__/conversation-load-history-stripped.test.ts +2 -0
  65. package/src/__tests__/conversation-pairing.test.ts +2 -0
  66. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +2 -0
  67. package/src/__tests__/conversation-process-callsite.test.ts +2 -0
  68. package/src/__tests__/conversation-provider-retry-repair.test.ts +2 -0
  69. package/src/__tests__/conversation-queue.test.ts +91 -0
  70. package/src/__tests__/conversation-routes-guardian-reply.test.ts +14 -0
  71. package/src/__tests__/conversation-routes-slash-commands.test.ts +14 -0
  72. package/src/__tests__/conversation-slash-queue.test.ts +2 -0
  73. package/src/__tests__/conversation-slash-unknown.test.ts +2 -0
  74. package/src/__tests__/conversation-speed-override.test.ts +2 -0
  75. package/src/__tests__/conversation-surfaces-task-progress.test.ts +29 -0
  76. package/src/__tests__/conversation-title-service.test.ts +2 -0
  77. package/src/__tests__/conversation-tool-setup-attribution.test.ts +47 -0
  78. package/src/__tests__/conversation-usage.test.ts +2 -0
  79. package/src/__tests__/conversation-workspace-cache-state.test.ts +2 -0
  80. package/src/__tests__/conversation-workspace-injection.test.ts +2 -0
  81. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +2 -0
  82. package/src/__tests__/credential-security-invariants.test.ts +1 -1
  83. package/src/__tests__/db-compaction-events-migration.test.ts +129 -0
  84. package/src/__tests__/db-migration-rollback.test.ts +205 -171
  85. package/src/__tests__/db-test-helpers.ts +5 -4
  86. package/src/__tests__/delete-propagation.test.ts +5 -3
  87. package/src/__tests__/deterministic-verification-control-plane.test.ts +4 -2
  88. package/src/__tests__/disk-pressure-guard.test.ts +41 -0
  89. package/src/__tests__/dm-backfill.test.ts +6 -4
  90. package/src/__tests__/dm-persistence.test.ts +2 -0
  91. package/src/__tests__/emit-signal-routing-intent.test.ts +7 -6
  92. package/src/__tests__/events-dev-bypass-actor.test.ts +7 -1
  93. package/src/__tests__/exploration-drift-hook.test.ts +3 -2
  94. package/src/__tests__/filing-service.test.ts +2 -0
  95. package/src/__tests__/guardian-binding-drift-heal.test.ts +104 -19
  96. package/src/__tests__/guardian-dispatch.test.ts +140 -1
  97. package/src/__tests__/guardian-expiry-notifier.test.ts +282 -0
  98. package/src/__tests__/guardian-outbound-http.test.ts +13 -0
  99. package/src/__tests__/guardian-routing-state.test.ts +6 -10
  100. package/src/__tests__/heartbeat-disk-pressure.test.ts +2 -0
  101. package/src/__tests__/heartbeat-service.test.ts +2 -0
  102. package/src/__tests__/helpers/channel-test-adapter.ts +46 -19
  103. package/src/__tests__/helpers/create-guardian-binding.ts +15 -23
  104. package/src/__tests__/helpers/mock-logger.ts +1 -0
  105. package/src/__tests__/helpers/seed-contact-channel.ts +96 -0
  106. package/src/__tests__/host-app-control-routes.test.ts +24 -30
  107. package/src/__tests__/host-bash-routes.test.ts +31 -41
  108. package/src/__tests__/host-browser-routes.test.ts +26 -32
  109. package/src/__tests__/host-cu-routes-targeted.test.ts +25 -33
  110. package/src/__tests__/host-file-routes-targeted.test.ts +40 -52
  111. package/src/__tests__/host-transfer-routes-targeted.test.ts +31 -43
  112. package/src/__tests__/http-user-message-parity.test.ts +290 -8
  113. package/src/__tests__/inbound-invite-redemption.test.ts +115 -10
  114. package/src/__tests__/inbound-slack-persistence.test.ts +2 -0
  115. package/src/__tests__/invite-redemption-service.test.ts +471 -53
  116. package/src/__tests__/invite-routes-http.test.ts +34 -0
  117. package/src/__tests__/invite-service-ipc.test.ts +65 -2
  118. package/src/__tests__/llm-context-normalization.test.ts +105 -0
  119. package/src/__tests__/llm-request-log-error-payload.test.ts +71 -9
  120. package/src/__tests__/llm-usage-store.test.ts +25 -0
  121. package/src/__tests__/mcp-config-secret-boundary.test.ts +392 -0
  122. package/src/__tests__/mcp-health-check.test.ts +2 -1
  123. package/src/__tests__/media-stream-server-integration.test.ts +127 -0
  124. package/src/__tests__/memory-retrieval-hook.test.ts +2 -0
  125. package/src/__tests__/messaging-send-tool.test.ts +2 -0
  126. package/src/__tests__/migration-import-from-url.test.ts +2 -2
  127. package/src/__tests__/mtime-cache.test.ts +275 -5
  128. package/src/__tests__/native-web-search.test.ts +2 -0
  129. package/src/__tests__/non-member-access-request.test.ts +191 -17
  130. package/src/__tests__/notification-broadcaster.test.ts +4 -0
  131. package/src/__tests__/notification-decision-recipient-context.test.ts +33 -32
  132. package/src/__tests__/notification-deep-link.test.ts +6 -0
  133. package/src/__tests__/notification-guardian-path.test.ts +19 -0
  134. package/src/__tests__/onboarding-persona-write.test.ts +52 -22
  135. package/src/__tests__/openai-provider.test.ts +22 -12
  136. package/src/__tests__/openai-responses-provider.test.ts +12 -2
  137. package/src/__tests__/outbound-slack-persistence.test.ts +2 -0
  138. package/src/__tests__/pending-interactions-resolved-event.test.ts +7 -4
  139. package/src/__tests__/persist-onboarding-artifacts.test.ts +1 -0
  140. package/src/__tests__/persistence-secret-redaction.test.ts +2 -0
  141. package/src/__tests__/persona-resolver.test.ts +75 -45
  142. package/src/__tests__/plugin-bootstrap.test.ts +21 -83
  143. package/src/__tests__/plugin-disabled-state.test.ts +190 -0
  144. package/src/__tests__/plugin-pipeline.test.ts +96 -0
  145. package/src/__tests__/plugin-route-contribution.test.ts +6 -19
  146. package/src/__tests__/plugin-tool-contribution.test.ts +5 -20
  147. package/src/__tests__/plugin-types.test.ts +2 -4
  148. package/src/__tests__/process-message-background-slack.test.ts +2 -0
  149. package/src/__tests__/process-message-display-content.test.ts +2 -0
  150. package/src/__tests__/provider-error-scenarios.test.ts +5 -4
  151. package/src/__tests__/provider-usage-tracking.test.ts +39 -0
  152. package/src/__tests__/reaction-intercept-cold-cache-warm.test.ts +135 -0
  153. package/src/__tests__/reaction-intercept-member-verdict-warm.test.ts +158 -0
  154. package/src/__tests__/reaction-persistence.test.ts +51 -4
  155. package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +2 -0
  156. package/src/__tests__/registry.test.ts +4 -1
  157. package/src/__tests__/relay-server.test.ts +758 -32
  158. package/src/__tests__/runtime-attachment-metadata.test.ts +9 -12
  159. package/src/__tests__/secret-ingress-http.test.ts +14 -0
  160. package/src/__tests__/send-endpoint-busy.test.ts +30 -8
  161. package/src/__tests__/settings-routes.test.ts +32 -0
  162. package/src/__tests__/skills.test.ts +44 -0
  163. package/src/__tests__/slack-inbound-verification.test.ts +47 -2
  164. package/src/__tests__/sse-actor-principal-guardian-source.test.ts +79 -0
  165. package/src/__tests__/steer-on-enqueue-question.test.ts +181 -0
  166. package/src/__tests__/stt-hints.test.ts +49 -15
  167. package/src/__tests__/subagent-detail.test.ts +27 -0
  168. package/src/__tests__/subagent-disposal.test.ts +65 -0
  169. package/src/__tests__/subagent-fork-prompt-role.test.ts +195 -0
  170. package/src/__tests__/subagent-fork-spawn.test.ts +6 -7
  171. package/src/__tests__/subagent-notify-parent.test.ts +2 -0
  172. package/src/__tests__/subagent-role-registry.test.ts +24 -6
  173. package/src/__tests__/subagent-spawn-and-await.test.ts +546 -0
  174. package/src/__tests__/subagent-spawn-tool-fork.test.ts +2 -0
  175. package/src/__tests__/subagent-tools.test.ts +398 -1
  176. package/src/__tests__/suggestion-routes.test.ts +2 -0
  177. package/src/__tests__/thread-backfill.test.ts +3 -3
  178. package/src/__tests__/title-generate-hook.test.ts +2 -0
  179. package/src/__tests__/tool-executor-lifecycle-events.test.ts +2 -0
  180. package/src/__tests__/tool-executor.test.ts +16 -11
  181. package/src/__tests__/tool-preview-lifecycle.test.ts +2 -0
  182. package/src/__tests__/tool-result-metadata-plumbing.test.ts +2 -0
  183. package/src/__tests__/tool-start-timestamp.test.ts +2 -0
  184. package/src/__tests__/trusted-contact-approval-notifier.test.ts +37 -51
  185. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +12 -12
  186. package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +9 -7
  187. package/src/__tests__/trusted-contact-multichannel.test.ts +16 -7
  188. package/src/__tests__/trusted-contact-verification.test.ts +79 -54
  189. package/src/__tests__/twilio-routes.test.ts +96 -0
  190. package/src/__tests__/ui-file-upload-surface.test.ts +86 -0
  191. package/src/__tests__/verification-control-plane-policy.test.ts +2 -0
  192. package/src/__tests__/voice-guardian-cold-cache-warm.test.ts +137 -0
  193. package/src/__tests__/voice-invite-redemption.test.ts +216 -20
  194. package/src/__tests__/web-search-backend-failure.test.ts +2 -0
  195. package/src/__tests__/workspace-migration-102-preserve-heartbeat-enabled-for-existing-workspaces.test.ts +3 -3
  196. package/src/__tests__/workspace-migration-111-prune-seeded-callsite-defaults.test.ts +208 -0
  197. package/src/__tests__/workspace-migration-112-remove-advisor-callsite-override.test.ts +170 -0
  198. package/src/__tests__/workspace-migration-drop-user-md.test.ts +196 -238
  199. package/src/__tests__/workspace-migration-remove-hooks.test.ts +14 -35
  200. package/src/__tests__/workspace-tool-loader.test.ts +195 -2
  201. package/src/a2a/__tests__/e2e-a2a-channel.test.ts +35 -47
  202. package/src/agent/loop-exclusive-tool.test.ts +154 -0
  203. package/src/agent/loop-native-web-search.test.ts +200 -0
  204. package/src/agent/loop.ts +164 -1
  205. package/src/api/constants/sse-replay.ts +41 -0
  206. package/src/api/events/conversation-notice.ts +26 -0
  207. package/src/api/index.ts +19 -1
  208. package/src/api/responses/llm-request-log-entry.ts +29 -0
  209. package/src/api/responses/subagent-detail.ts +17 -0
  210. package/src/api/surfaces.ts +39 -3
  211. package/src/approvals/guardian-channel-delivery.ts +30 -0
  212. package/src/approvals/guardian-expiry-notifier.ts +148 -0
  213. package/src/approvals/guardian-request-resolvers.ts +17 -15
  214. package/src/calls/__tests__/relay-setup-router.test.ts +267 -17
  215. package/src/calls/call-domain.ts +3 -3
  216. package/src/calls/guardian-dispatch.ts +14 -9
  217. package/src/calls/inbound-trust-reader.ts +24 -2
  218. package/src/calls/media-stream-server.ts +21 -0
  219. package/src/calls/relay-access-wait.ts +6 -6
  220. package/src/calls/relay-server.ts +188 -51
  221. package/src/calls/relay-setup-router.ts +47 -17
  222. package/src/calls/relay-verification.ts +4 -4
  223. package/src/calls/stt-hints.ts +9 -12
  224. package/src/calls/twilio-routes.ts +14 -4
  225. package/src/channels/types.ts +10 -20
  226. package/src/cli/commands/__tests__/cache.test.ts +8 -1
  227. package/src/cli/commands/__tests__/conversations-slack.test.ts +1 -0
  228. package/src/cli/commands/cache.ts +194 -181
  229. package/src/cli/commands/contacts.ts +10 -7
  230. package/src/cli/commands/db/__tests__/repair.test.ts +6 -5
  231. package/src/cli/commands/db/status.ts +37 -1
  232. package/src/cli/commands/mcp.ts +252 -218
  233. package/src/cli/commands/memory/__tests__/worker.test.ts +432 -0
  234. package/src/cli/commands/memory/index.ts +2 -0
  235. package/src/cli/commands/memory/worker.ts +242 -0
  236. package/src/cli/commands/plugins.ts +125 -13
  237. package/src/cli/lib/__tests__/install-from-github.test.ts +102 -0
  238. package/src/cli/lib/__tests__/list-installed-plugins.test.ts +160 -1
  239. package/src/cli/lib/__tests__/publish-plugin.test.ts +404 -0
  240. package/src/cli/lib/list-installed-plugins.ts +179 -1
  241. package/src/cli/lib/publish-plugin.ts +633 -0
  242. package/src/config/__tests__/loader-callsite-strip-fallback.test.ts +143 -0
  243. package/src/config/__tests__/sync-gated-profiles.test.ts +16 -10
  244. package/src/config/bundled-skills/contacts/tools/contact-merge.ts +27 -17
  245. package/src/config/bundled-skills/contacts/tools/contact-search.ts +13 -3
  246. package/src/config/bundled-skills/subagent/SKILL.md +17 -2
  247. package/src/config/bundled-skills/subagent/TOOLS.json +5 -4
  248. package/src/config/call-site-defaults.ts +0 -6
  249. package/src/config/feature-flag-registry.json +5 -13
  250. package/src/config/llm-resolver.ts +0 -3
  251. package/src/config/loader.ts +38 -5
  252. package/src/config/prune-seeded-callsite-defaults.ts +110 -0
  253. package/src/config/schemas/__tests__/memory-v3.test.ts +1 -0
  254. package/src/config/schemas/call-site-catalog.ts +0 -7
  255. package/src/config/schemas/heartbeat.ts +2 -5
  256. package/src/config/schemas/llm.ts +3 -12
  257. package/src/config/schemas/memory-lifecycle.ts +12 -0
  258. package/src/config/schemas/memory-v3.ts +7 -0
  259. package/src/config/schemas/memory.ts +4 -0
  260. package/src/config/schemas/timeouts.ts +8 -0
  261. package/src/config/seed-inference-profiles.ts +30 -34
  262. package/src/config/skills.ts +27 -5
  263. package/src/config/sync-gated-profiles.ts +12 -16
  264. package/src/contacts/__tests__/contacts-write-revoke-relay.test.ts +128 -0
  265. package/src/contacts/__tests__/guardian-delivery-reader.test.ts +312 -0
  266. package/src/contacts/__tests__/member-write-relay.test.ts +226 -0
  267. package/src/contacts/contact-store.ts +27 -237
  268. package/src/contacts/contacts-write.ts +18 -55
  269. package/src/contacts/gateway-channel-read.ts +51 -0
  270. package/src/contacts/guardian-delivery-reader.ts +223 -0
  271. package/src/contacts/member-write-relay.ts +183 -0
  272. package/src/contacts/types.ts +3 -15
  273. package/src/daemon/__tests__/conversation-tool-setup-exclude.test.ts +35 -0
  274. package/src/daemon/__tests__/conversation-tool-setup.test.ts +0 -44
  275. package/src/daemon/assistant-attachments.ts +27 -4
  276. package/src/daemon/conversation-agent-loop.ts +28 -0
  277. package/src/daemon/conversation-notices.ts +60 -0
  278. package/src/daemon/conversation-process.ts +35 -16
  279. package/src/daemon/conversation-surfaces.ts +111 -38
  280. package/src/daemon/conversation-tool-setup.ts +71 -30
  281. package/src/daemon/conversation.ts +23 -1
  282. package/src/daemon/disk-pressure-guard.ts +12 -2
  283. package/src/daemon/event-loop-watchdog.ts +28 -1
  284. package/src/daemon/external-plugins-bootstrap.ts +17 -41
  285. package/src/daemon/handlers/__tests__/config-a2a-accept.test.ts +0 -1
  286. package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +0 -2
  287. package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +25 -2
  288. package/src/daemon/handlers/__tests__/config-channels.test.ts +220 -0
  289. package/src/daemon/handlers/config-a2a.ts +6 -14
  290. package/src/daemon/handlers/config-channels.ts +67 -26
  291. package/src/daemon/handlers/conversations.ts +77 -0
  292. package/src/daemon/lifecycle.ts +16 -0
  293. package/src/daemon/mcp-reload-service.ts +10 -0
  294. package/src/daemon/memory-v2-startup.test.ts +72 -0
  295. package/src/daemon/memory-v2-startup.ts +87 -19
  296. package/src/daemon/message-types/conversations.ts +2 -0
  297. package/src/daemon/message-types/surfaces.ts +12 -12
  298. package/src/daemon/server.ts +0 -4
  299. package/src/daemon/shutdown-handlers.ts +20 -0
  300. package/src/daemon/tool-setup-types.ts +9 -0
  301. package/src/heartbeat/heartbeat-service.ts +5 -0
  302. package/src/home/relationship-state-writer.ts +5 -0
  303. package/src/hooks/hook-loader.ts +341 -0
  304. package/src/ipc/__tests__/clients-list-ipc.test.ts +1 -1
  305. package/src/ipc/assistant-server.ts +2 -2
  306. package/src/mcp/__tests__/mcp-auth-orchestrator.test.ts +1 -0
  307. package/src/mcp/client.ts +15 -1
  308. package/src/mcp/mcp-auth-orchestrator.ts +6 -1
  309. package/src/mcp/mcp-header-store.ts +134 -0
  310. package/src/mcp/mcp-oauth-provider.ts +19 -8
  311. package/src/memory/__tests__/301-create-watchdog-events.test.ts +110 -0
  312. package/src/memory/__tests__/memory-retrospective-job.test.ts +8 -0
  313. package/src/memory/__tests__/prompt-override.test.ts +192 -0
  314. package/src/memory/__tests__/watchdog-events-store.test.ts +161 -0
  315. package/src/memory/compaction-ledger-store.ts +107 -0
  316. package/src/memory/conversation-crud.ts +129 -61
  317. package/src/memory/db-connection.ts +22 -3
  318. package/src/memory/db-init.ts +37 -503
  319. package/src/memory/db-singleton.ts +6 -4
  320. package/src/memory/jobs-worker.ts +104 -0
  321. package/src/memory/llm-request-log-store.ts +26 -1
  322. package/src/memory/llm-usage-store.ts +48 -20
  323. package/src/memory/memory-retrospective-job.ts +14 -8
  324. package/src/memory/migrations/209-strip-thinking-from-consolidated.ts +152 -56
  325. package/src/memory/migrations/300-add-processing-started-at.ts +30 -0
  326. package/src/memory/migrations/301-create-watchdog-events.ts +45 -0
  327. package/src/memory/migrations/302-create-compaction-events.ts +107 -0
  328. package/src/memory/migrations/__tests__/209-strip-thinking-from-consolidated.test.ts +297 -0
  329. package/src/memory/migrations/__tests__/run-migrations.test.ts +2 -2
  330. package/src/memory/migrations/run-migrations.ts +106 -10
  331. package/src/memory/migrations/validate-migration-state.ts +105 -67
  332. package/src/memory/prompt-override.ts +129 -0
  333. package/src/memory/schema/contacts.ts +6 -2
  334. package/src/memory/schema/conversations.ts +37 -0
  335. package/src/memory/schema/infrastructure.ts +20 -0
  336. package/src/memory/steps.ts +1294 -0
  337. package/src/memory/v2/__tests__/cli-command-store.test.ts +25 -0
  338. package/src/memory/v2/__tests__/skill-store.test.ts +80 -0
  339. package/src/memory/v2/cli-command-store.ts +75 -38
  340. package/src/memory/v2/prompts/consolidation.ts +13 -82
  341. package/src/memory/v2/prompts/router.ts +21 -93
  342. package/src/memory/v2/skill-store.ts +68 -31
  343. package/src/memory/watchdog-events-store.ts +87 -0
  344. package/src/memory/worker-control.ts +204 -0
  345. package/src/memory/worker-process.ts +89 -0
  346. package/src/messaging/channel-binding-metadata.ts +31 -0
  347. package/src/messaging/provider-types.ts +8 -0
  348. package/src/messaging/providers/slack/binding-metadata.ts +60 -0
  349. package/src/notifications/__tests__/broadcaster.test.ts +8 -8
  350. package/src/notifications/__tests__/connected-channels.test.ts +86 -0
  351. package/src/notifications/__tests__/decision-engine.test.ts +78 -9
  352. package/src/notifications/__tests__/destination-resolver.test.ts +151 -0
  353. package/src/notifications/broadcaster.ts +8 -1
  354. package/src/notifications/decision-engine.ts +15 -7
  355. package/src/notifications/destination-resolver.ts +53 -25
  356. package/src/notifications/emit-signal.ts +34 -15
  357. package/src/onboarding/checkin-event.test.ts +222 -0
  358. package/src/onboarding/checkin-event.ts +321 -0
  359. package/src/onboarding/schedule-checkin.ts +190 -0
  360. package/src/permissions/question-prompter.test.ts +1 -1
  361. package/src/permissions/question-prompter.ts +7 -4
  362. package/src/plugin-api/index.ts +12 -12
  363. package/src/plugin-api/types.ts +12 -14
  364. package/src/plugin-api/vision-support.test.ts +28 -4
  365. package/src/plugin-api/vision-support.ts +66 -31
  366. package/src/plugins/defaults/empty-response/hooks/post-model-call.ts +2 -2
  367. package/src/plugins/defaults/empty-response/hooks/stop.ts +2 -2
  368. package/src/plugins/defaults/exploration-drift/hooks/post-tool-use.ts +4 -3
  369. package/src/plugins/defaults/history-repair/hooks/post-model-call.ts +2 -2
  370. package/src/plugins/defaults/history-repair/hooks/stop.ts +2 -2
  371. package/src/plugins/defaults/history-repair/hooks/user-prompt-submit.ts +2 -2
  372. package/src/plugins/defaults/image-fallback/__tests__/image-fallback.test.ts +47 -7
  373. package/src/plugins/defaults/image-fallback/hooks/post-tool-use.ts +12 -13
  374. package/src/plugins/defaults/image-fallback/hooks/user-prompt-submit.ts +14 -22
  375. package/src/plugins/defaults/image-fallback/src/caption-blocks.ts +42 -11
  376. package/src/plugins/defaults/image-recovery/hooks/post-model-call.ts +2 -2
  377. package/src/plugins/defaults/image-recovery/hooks/stop.ts +2 -2
  378. package/src/plugins/defaults/index.ts +0 -35
  379. package/src/plugins/defaults/max-tokens-continue/hooks/post-model-call.ts +2 -2
  380. package/src/plugins/defaults/max-tokens-continue/hooks/stop.ts +2 -2
  381. package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +2 -2
  382. package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit.ts +2 -2
  383. package/src/plugins/defaults/memory-v3-shadow/__tests__/injection.test.ts +33 -3
  384. package/src/plugins/defaults/memory-v3-shadow/__tests__/pool-select.test.ts +48 -4
  385. package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +4 -8
  386. package/src/plugins/defaults/memory-v3-shadow/hooks/post-compact.ts +2 -2
  387. package/src/plugins/defaults/memory-v3-shadow/hooks/user-prompt-submit.ts +2 -2
  388. package/src/plugins/defaults/memory-v3-shadow/injector.ts +43 -15
  389. package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +11 -2
  390. package/src/plugins/defaults/memory-v3-shadow/pool-select.test.ts +146 -0
  391. package/src/plugins/defaults/memory-v3-shadow/pool-select.ts +77 -13
  392. package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +12 -11
  393. package/src/plugins/defaults/surface-completion-nudge/hooks/post-model-call.ts +2 -2
  394. package/src/plugins/defaults/surface-completion-nudge/hooks/stop.ts +2 -2
  395. package/src/plugins/defaults/task-progress-nudge/hooks/post-tool-use.ts +2 -2
  396. package/src/plugins/defaults/title-generate/hooks/stop.ts +2 -2
  397. package/src/plugins/defaults/title-generate/hooks/user-prompt-submit.ts +2 -2
  398. package/src/plugins/defaults/tool-error/hooks/post-tool-use.ts +2 -2
  399. package/src/plugins/defaults/tool-result-truncate/hooks/post-tool-use.ts +2 -2
  400. package/src/plugins/disabled-state.ts +31 -0
  401. package/src/plugins/external-plugin-loader.ts +2 -2
  402. package/src/plugins/mtime-cache.ts +186 -330
  403. package/src/plugins/pipeline.ts +111 -13
  404. package/src/plugins/registry.ts +59 -16
  405. package/src/plugins/surface-import.ts +121 -0
  406. package/src/plugins/types.ts +7 -9
  407. package/src/prompts/persona-resolver.ts +43 -11
  408. package/src/providers/anthropic/client.ts +5 -0
  409. package/src/providers/call-site-routing.ts +45 -0
  410. package/src/providers/model-catalog.ts +16 -0
  411. package/src/providers/openai/__tests__/api-error-normalization.test.ts +321 -0
  412. package/src/providers/openai/api-error-normalization.ts +270 -0
  413. package/src/providers/openai/chat-completions-provider.ts +37 -83
  414. package/src/providers/openai/responses-provider.ts +50 -46
  415. package/src/providers/openrouter/client.ts +5 -0
  416. package/src/providers/provider-send-message.ts +10 -0
  417. package/src/providers/ratelimit.ts +10 -0
  418. package/src/providers/registry.ts +1 -1
  419. package/src/providers/retry.ts +10 -0
  420. package/src/providers/types.ts +22 -0
  421. package/src/providers/usage-tracking.ts +10 -0
  422. package/src/runtime/__tests__/channel-verification-service.test.ts +133 -0
  423. package/src/runtime/__tests__/guardian-vellum-migration.test.ts +184 -0
  424. package/src/runtime/__tests__/is-guardian-bound-for-channel.test.ts +66 -0
  425. package/src/runtime/__tests__/local-principal-trust.test.ts +162 -0
  426. package/src/runtime/__tests__/member-verdict-cache.test.ts +119 -0
  427. package/src/runtime/__tests__/trust-verdict-consumer.test.ts +402 -123
  428. package/src/runtime/access-request-helper.ts +18 -39
  429. package/src/runtime/actor-trust-resolver.ts +46 -19
  430. package/src/runtime/anchored-guardian.test.ts +109 -0
  431. package/src/runtime/anchored-guardian.ts +86 -0
  432. package/src/runtime/assistant-event-hub.ts +1 -1
  433. package/src/runtime/assistant-stream-state.ts +9 -2
  434. package/src/runtime/auth/__tests__/require-bound-guardian.test.ts +99 -0
  435. package/src/runtime/auth/require-bound-guardian.ts +21 -11
  436. package/src/runtime/channel-verification-service.ts +56 -31
  437. package/src/runtime/confirmation-request-guardian-bridge.ts +3 -3
  438. package/src/runtime/guardian-vellum-migration.ts +69 -8
  439. package/src/runtime/invite-redemption-service.ts +213 -187
  440. package/src/runtime/local-actor-identity.test.ts +108 -0
  441. package/src/runtime/local-actor-identity.ts +85 -13
  442. package/src/runtime/local-principal-trust.ts +52 -0
  443. package/src/runtime/member-verdict-cache.ts +0 -0
  444. package/src/runtime/pending-interactions.ts +11 -1
  445. package/src/runtime/routes/__tests__/channel-verification-revoke.test.ts +56 -5
  446. package/src/runtime/routes/__tests__/channel-verification-routes.test.ts +1 -1
  447. package/src/runtime/routes/__tests__/contact-routes.test.ts +305 -0
  448. package/src/runtime/routes/__tests__/global-search-routes.test.ts +92 -0
  449. package/src/runtime/routes/__tests__/http-adapter-actor-header.test.ts +129 -0
  450. package/src/runtime/routes/__tests__/surface-action-routes.test.ts +188 -0
  451. package/src/runtime/routes/browser-routes.ts +1 -1
  452. package/src/runtime/routes/canonical-guardian-expiry-sweep.ts +13 -5
  453. package/src/runtime/routes/channel-verification-routes.ts +3 -3
  454. package/src/runtime/routes/contact-routes.ts +48 -57
  455. package/src/runtime/routes/conversation-cli-routes.ts +4 -5
  456. package/src/runtime/routes/conversation-list-routes.ts +4 -7
  457. package/src/runtime/routes/conversation-query-routes.ts +72 -0
  458. package/src/runtime/routes/conversation-routes.ts +84 -85
  459. package/src/runtime/routes/events-routes.ts +2 -2
  460. package/src/runtime/routes/global-search-routes.ts +3 -1
  461. package/src/runtime/routes/guardian-action-routes.ts +4 -5
  462. package/src/runtime/routes/host-app-control-routes.ts +5 -4
  463. package/src/runtime/routes/host-bash-routes.ts +5 -4
  464. package/src/runtime/routes/host-browser-routes.ts +9 -11
  465. package/src/runtime/routes/host-cu-routes.ts +5 -4
  466. package/src/runtime/routes/host-file-routes.ts +5 -4
  467. package/src/runtime/routes/host-transfer-routes.ts +6 -6
  468. package/src/runtime/routes/http-adapter.ts +16 -1
  469. package/src/runtime/routes/identity-routes.ts +3 -2
  470. package/src/runtime/routes/inbound-message-handler.ts +5 -5
  471. package/src/runtime/routes/inbound-stages/acl-enforcement.test.ts +97 -5
  472. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +61 -59
  473. package/src/runtime/routes/inbound-stages/background-dispatch.ts +13 -5
  474. package/src/runtime/routes/inbound-stages/escalation-intercept.ts +7 -7
  475. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.test.ts +21 -8
  476. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +14 -3
  477. package/src/runtime/routes/inbound-stages/reaction-intercept.ts +19 -0
  478. package/src/runtime/routes/index.ts +2 -0
  479. package/src/runtime/routes/llm-context-normalization.ts +83 -0
  480. package/src/runtime/routes/mcp-auth-routes.ts +171 -19
  481. package/src/runtime/routes/migration-rollback-routes.ts +4 -3
  482. package/src/runtime/routes/migration-routes.ts +4 -1
  483. package/src/runtime/routes/onboarding-checkin-routes.ts +86 -0
  484. package/src/runtime/routes/settings-routes.ts +8 -3
  485. package/src/runtime/routes/subagents-routes.ts +5 -0
  486. package/src/runtime/routes/surface-action-routes.ts +42 -56
  487. package/src/runtime/services/__tests__/conversation-serializer.test.ts +1 -0
  488. package/src/runtime/services/conversation-serializer.ts +13 -58
  489. package/src/runtime/tool-grant-request-helper.ts +3 -3
  490. package/src/runtime/trust-verdict-consumer.ts +111 -40
  491. package/src/runtime/verification-outbound-actions.ts +18 -18
  492. package/src/signals/user-message.ts +16 -0
  493. package/src/subagent/__tests__/consult-prompt.test.ts +35 -0
  494. package/src/{plugins/defaults/advisor/__tests__/transcript.test.ts → subagent/__tests__/consult-transcript.test.ts} +47 -10
  495. package/src/{plugins/defaults/advisor/steering.ts → subagent/consult-prompt.ts} +22 -32
  496. package/src/{plugins/defaults/advisor/transcript.ts → subagent/consult-transcript.ts} +18 -8
  497. package/src/subagent/index.ts +1 -1
  498. package/src/subagent/manager.ts +254 -33
  499. package/src/subagent/types.ts +11 -4
  500. package/src/telemetry/types.ts +34 -1
  501. package/src/telemetry/usage-telemetry-reporter.test.ts +3 -2
  502. package/src/telemetry/usage-telemetry-reporter.ts +87 -3
  503. package/src/tools/ask-question/ask-question-tool.test.ts +29 -0
  504. package/src/tools/ask-question/ask-question-tool.ts +13 -0
  505. package/src/tools/executor.ts +4 -4
  506. package/src/tools/filesystem/search.ts +543 -0
  507. package/src/tools/registry.ts +38 -0
  508. package/src/tools/shared/filesystem/path-policy.ts +12 -5
  509. package/src/tools/subagent/consult-deadline.ts +49 -0
  510. package/src/tools/subagent/spawn.ts +234 -5
  511. package/src/tools/tool-approval-handler.ts +1 -1
  512. package/src/tools/tool-defaults.ts +9 -2
  513. package/src/tools/tool-manifest.ts +3 -0
  514. package/src/tools/types.ts +131 -25
  515. package/src/tools/workspace-tools/loader.ts +348 -244
  516. package/src/util/errors.ts +26 -1
  517. package/src/util/logger.ts +9 -0
  518. package/src/util/platform.ts +19 -0
  519. package/src/util/telemetry-db-path.ts +24 -0
  520. package/src/workflows/library.test.ts +140 -0
  521. package/src/workflows/library.ts +82 -28
  522. package/src/workspace/migrations/017-seed-persona-dirs.ts +3 -34
  523. package/src/workspace/migrations/019-scope-journal-to-guardian.ts +3 -24
  524. package/src/workspace/migrations/031-drop-user-md.ts +232 -148
  525. package/src/workspace/migrations/048-remove-workspace-hooks.ts +14 -66
  526. package/src/workspace/migrations/111-prune-seeded-callsite-defaults.ts +134 -0
  527. package/src/workspace/migrations/112-remove-advisor-callsite-override.ts +64 -0
  528. package/src/workspace/migrations/registry.ts +4 -0
  529. package/src/__tests__/workspace-tools-watcher-flag.test.ts +0 -70
  530. package/src/daemon/workspace-tools-watcher.ts +0 -328
  531. package/src/memory/migrations/registry.ts +0 -573
  532. package/src/plugins/defaults/advisor/__tests__/advisor-gate.test.ts +0 -56
  533. package/src/plugins/defaults/advisor/__tests__/advisor-state-store.test.ts +0 -43
  534. package/src/plugins/defaults/advisor/__tests__/agent-loop-integration.test.ts +0 -137
  535. package/src/plugins/defaults/advisor/__tests__/consult.test.ts +0 -153
  536. package/src/plugins/defaults/advisor/__tests__/hooks.test.ts +0 -138
  537. package/src/plugins/defaults/advisor/advisor-gate.ts +0 -29
  538. package/src/plugins/defaults/advisor/advisor-state-store.ts +0 -94
  539. package/src/plugins/defaults/advisor/config.ts +0 -21
  540. package/src/plugins/defaults/advisor/consult.ts +0 -93
  541. package/src/plugins/defaults/advisor/hooks/post-model-call.ts +0 -34
  542. package/src/plugins/defaults/advisor/hooks/pre-model-call.ts +0 -30
  543. package/src/plugins/defaults/advisor/hooks/user-prompt-submit.ts +0 -19
  544. package/src/plugins/defaults/advisor/package.json +0 -14
  545. package/src/plugins/defaults/advisor/tools/advisor.ts +0 -65
  546. package/src/providers/openai/__tests__/api-error-detail.test.ts +0 -120
@@ -0,0 +1,404 @@
1
+ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
2
+ import { tmpdir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { describe, expect, it } from "bun:test";
5
+
6
+ import {
7
+ buildPublishPayload,
8
+ findPluginRoot,
9
+ formatPayloadForPrint,
10
+ formatPublishResult,
11
+ formatValidationResult,
12
+ type ParsedPackageJson,
13
+ type PublishValidation,
14
+ runPublish,
15
+ validatePluginForPublish,
16
+ } from "../publish-plugin.js";
17
+
18
+ // ---------------------------------------------------------------------------
19
+ // Test helpers
20
+ // ---------------------------------------------------------------------------
21
+
22
+ function makePluginDir(
23
+ base: string,
24
+ pkg: Partial<ParsedPackageJson> = {},
25
+ surfaces: { hooks?: boolean; tools?: boolean; skills?: boolean } = {},
26
+ ): string {
27
+ const dir = mkdtempSync(join(base, "plugin-"));
28
+ writeFileSync(
29
+ join(dir, "package.json"),
30
+ JSON.stringify({
31
+ name: "test-plugin",
32
+ version: "1.0.0",
33
+ description: "A test plugin",
34
+ license: "MIT",
35
+ homepage: "https://github.com/test/test-plugin",
36
+ peerDependencies: { "@vellumai/plugin-api": "^1.0.0" },
37
+ ...pkg,
38
+ }),
39
+ );
40
+ if (surfaces.hooks) {
41
+ mkdirSync(join(dir, "hooks"));
42
+ writeFileSync(join(dir, "hooks", "post-tool-use.ts"), "// hook");
43
+ }
44
+ if (surfaces.tools) {
45
+ mkdirSync(join(dir, "tools"));
46
+ writeFileSync(join(dir, "tools", "my-tool.ts"), "// tool");
47
+ }
48
+ if (surfaces.skills) {
49
+ mkdirSync(join(dir, "skills"));
50
+ writeFileSync(join(dir, "skills", "my-skill"), "content");
51
+ }
52
+ return dir;
53
+ }
54
+
55
+ const validPkg: Partial<ParsedPackageJson> = {
56
+ name: "test-plugin",
57
+ version: "1.0.0",
58
+ peerDependencies: { "@vellumai/plugin-api": "^1.0.0" },
59
+ description: "A test plugin",
60
+ license: "MIT",
61
+ homepage: "https://github.com/test/test-plugin",
62
+ };
63
+
64
+ // ---------------------------------------------------------------------------
65
+ // findPluginRoot
66
+ // ---------------------------------------------------------------------------
67
+
68
+ describe("findPluginRoot", () => {
69
+ it("finds package.json in the given directory", () => {
70
+ const dir = makePluginDir(tmpdir());
71
+ expect(findPluginRoot(dir)).toBe(dir);
72
+ rmSync(dir, { recursive: true });
73
+ });
74
+
75
+ it("finds package.json in a parent directory", () => {
76
+ const dir = makePluginDir(tmpdir());
77
+ const subDir = join(dir, "subdir");
78
+ mkdirSync(subDir);
79
+ expect(findPluginRoot(subDir)).toBe(dir);
80
+ rmSync(dir, { recursive: true });
81
+ });
82
+
83
+ it("returns null when no package.json exists", () => {
84
+ const dir = mkdtempSync(join(tmpdir(), "empty-"));
85
+ expect(findPluginRoot(dir)).toBeNull();
86
+ rmSync(dir, { recursive: true });
87
+ });
88
+ });
89
+
90
+ // ---------------------------------------------------------------------------
91
+ // validatePluginForPublish
92
+ // ---------------------------------------------------------------------------
93
+
94
+ describe("validatePluginForPublish", () => {
95
+ it("passes for a valid plugin with surfaces", () => {
96
+ const dir = makePluginDir(tmpdir(), validPkg, { hooks: true });
97
+ const result = validatePluginForPublish(dir);
98
+ expect(result.valid).toBe(true);
99
+ expect(result.issues).toHaveLength(0);
100
+ rmSync(dir, { recursive: true });
101
+ });
102
+
103
+ it("fails when package.json is missing", () => {
104
+ const dir = mkdtempSync(join(tmpdir(), "no-pkg-"));
105
+ const result = validatePluginForPublish(dir);
106
+ expect(result.valid).toBe(false);
107
+ expect(result.issues).toContain("package.json not found");
108
+ rmSync(dir, { recursive: true });
109
+ });
110
+
111
+ it("fails when name is missing", () => {
112
+ const dir = makePluginDir(tmpdir(), {
113
+ ...validPkg,
114
+ name: undefined,
115
+ });
116
+ const result = validatePluginForPublish(dir);
117
+ expect(result.valid).toBe(false);
118
+ expect(result.issues.some((i) => i.includes("missing 'name'"))).toBe(true);
119
+ rmSync(dir, { recursive: true });
120
+ });
121
+
122
+ it("fails when name is not kebab-case", () => {
123
+ const dir = makePluginDir(tmpdir(), {
124
+ ...validPkg,
125
+ name: "NotKebabCase",
126
+ });
127
+ const result = validatePluginForPublish(dir);
128
+ expect(result.valid).toBe(false);
129
+ expect(result.issues.some((i) => i.includes("kebab-case"))).toBe(true);
130
+ rmSync(dir, { recursive: true });
131
+ });
132
+
133
+ it("fails when version is missing", () => {
134
+ const dir = makePluginDir(tmpdir(), {
135
+ ...validPkg,
136
+ version: undefined,
137
+ });
138
+ const result = validatePluginForPublish(dir);
139
+ expect(result.valid).toBe(false);
140
+ expect(result.issues.some((i) => i.includes("missing 'version'"))).toBe(
141
+ true,
142
+ );
143
+ rmSync(dir, { recursive: true });
144
+ });
145
+
146
+ it("fails when @vellumai/plugin-api peer dep is missing", () => {
147
+ const dir = makePluginDir(tmpdir(), {
148
+ ...validPkg,
149
+ peerDependencies: {},
150
+ });
151
+ const result = validatePluginForPublish(dir);
152
+ expect(result.valid).toBe(false);
153
+ expect(result.issues.some((i) => i.includes("@vellumai/plugin-api"))).toBe(
154
+ true,
155
+ );
156
+ rmSync(dir, { recursive: true });
157
+ });
158
+
159
+ it("warns when no surface directories have entries", () => {
160
+ const dir = makePluginDir(tmpdir(), validPkg);
161
+ const result = validatePluginForPublish(dir);
162
+ expect(result.valid).toBe(true);
163
+ expect(result.warnings.length).toBeGreaterThan(0);
164
+ expect(result.warnings.some((w) => w.includes("No hooks/"))).toBe(true);
165
+ rmSync(dir, { recursive: true });
166
+ });
167
+
168
+ it("warns about stale .js without matching .ts", () => {
169
+ const dir = makePluginDir(tmpdir(), validPkg, { hooks: true });
170
+ // Add a stale .js file without a matching .ts
171
+ writeFileSync(join(dir, "hooks", "orphan.js"), "// stale");
172
+ const result = validatePluginForPublish(dir);
173
+ expect(result.valid).toBe(true);
174
+ expect(result.warnings.some((w) => w.includes("orphan.js"))).toBe(true);
175
+ rmSync(dir, { recursive: true });
176
+ });
177
+ });
178
+
179
+ // ---------------------------------------------------------------------------
180
+ // buildPublishPayload
181
+ // ---------------------------------------------------------------------------
182
+
183
+ describe("buildPublishPayload", () => {
184
+ it("assembles payload from validation and git context", () => {
185
+ const validation: PublishValidation = {
186
+ valid: true,
187
+ issues: [],
188
+ warnings: [],
189
+ packageJson: {
190
+ name: "my-plugin",
191
+ version: "1.0.0",
192
+ description: "Does cool stuff",
193
+ license: "MIT",
194
+ homepage: "https://github.com/me/my-plugin",
195
+ peerDependencies: { "@vellumai/plugin-api": "^1.0.0" },
196
+ },
197
+ pluginDir: "/tmp/plugin",
198
+ };
199
+ const git = {
200
+ sha: "e83c5163316f89bfbde7d9ab23ca2e25604af290",
201
+ repo: "me/my-plugin",
202
+ dirty: false,
203
+ pushed: true,
204
+ pluginPath: "",
205
+ };
206
+
207
+ const payload = buildPublishPayload(validation, git, "productivity");
208
+
209
+ expect(payload.name).toBe("my-plugin");
210
+ expect(payload.source.source).toBe("github");
211
+ expect(payload.source.repo).toBe("me/my-plugin");
212
+ expect(payload.source.ref).toBe("e83c5163316f89bfbde7d9ab23ca2e25604af290");
213
+ expect(payload.source.path).toBeUndefined();
214
+ expect(payload.category).toBe("productivity");
215
+ expect(payload.description).toBe("Does cool stuff");
216
+ expect(payload.license).toBe("MIT");
217
+ expect(payload.homepage).toBe("https://github.com/me/my-plugin");
218
+ });
219
+
220
+ it("sets source.path for nested plugin roots", () => {
221
+ const validation: PublishValidation = {
222
+ valid: true,
223
+ issues: [],
224
+ warnings: [],
225
+ packageJson: {
226
+ name: "my-plugin",
227
+ version: "1.0.0",
228
+ peerDependencies: { "@vellumai/plugin-api": "^1.0.0" },
229
+ },
230
+ pluginDir: "/tmp/monorepo/packages/plugin",
231
+ };
232
+ const git = {
233
+ sha: "e83c5163316f89bfbde7d9ab23ca2e25604af290",
234
+ repo: "me/monorepo",
235
+ dirty: false,
236
+ pushed: true,
237
+ pluginPath: "packages/plugin",
238
+ };
239
+
240
+ const payload = buildPublishPayload(validation, git, "other");
241
+ expect(payload.source.path).toBe("packages/plugin");
242
+ });
243
+
244
+ it("extracts homepage from repository.url when homepage is absent", () => {
245
+ const validation: PublishValidation = {
246
+ valid: true,
247
+ issues: [],
248
+ warnings: [],
249
+ packageJson: {
250
+ name: "my-plugin",
251
+ version: "1.0.0",
252
+ peerDependencies: { "@vellumai/plugin-api": "^1.0.0" },
253
+ repository: { url: "https://github.com/me/my-plugin.git" },
254
+ },
255
+ pluginDir: "/tmp/plugin",
256
+ };
257
+ const git = {
258
+ sha: "e83c5163316f89bfbde7d9ab23ca2e25604af290",
259
+ repo: "me/my-plugin",
260
+ dirty: false,
261
+ pushed: true,
262
+ pluginPath: "",
263
+ };
264
+
265
+ const payload = buildPublishPayload(validation, git, "other");
266
+ expect(payload.homepage).toBe("https://github.com/me/my-plugin");
267
+ });
268
+ });
269
+
270
+ // ---------------------------------------------------------------------------
271
+ // Formatting helpers
272
+ // ---------------------------------------------------------------------------
273
+
274
+ describe("formatPayloadForPrint", () => {
275
+ it("produces valid JSON", () => {
276
+ const payload = {
277
+ name: "test",
278
+ source: { source: "github" as const, repo: "me/test", ref: "abc" },
279
+ category: "other",
280
+ };
281
+ const printed = formatPayloadForPrint(payload);
282
+ expect(JSON.parse(printed)).toEqual(payload);
283
+ });
284
+ });
285
+
286
+ describe("formatPublishResult", () => {
287
+ it("formats success", () => {
288
+ const result = formatPublishResult({
289
+ ok: true,
290
+ pr_url: "https://github.com/vellum-ai/vellum-assistant/pull/1",
291
+ pr_number: 1,
292
+ status: "pending_review",
293
+ });
294
+ expect(result).toContain("PR opened");
295
+ expect(result).toContain("review");
296
+ });
297
+
298
+ it("formats failure", () => {
299
+ const result = formatPublishResult({
300
+ ok: false,
301
+ error: "rate_limited",
302
+ message: "Too many publishes.",
303
+ });
304
+ expect(result).toContain("failed");
305
+ expect(result).toContain("Too many publishes");
306
+ });
307
+ });
308
+
309
+ describe("formatValidationResult", () => {
310
+ it("formats errors and warnings", () => {
311
+ const result = formatValidationResult({
312
+ valid: false,
313
+ issues: ["Missing name"],
314
+ warnings: ["No surfaces"],
315
+ packageJson: {},
316
+ pluginDir: "/tmp",
317
+ });
318
+ expect(result).toContain("Validation errors");
319
+ expect(result).toContain("Missing name");
320
+ expect(result).toContain("Warnings");
321
+ expect(result).toContain("No surfaces");
322
+ });
323
+
324
+ it("formats clean validation", () => {
325
+ const result = formatValidationResult({
326
+ valid: true,
327
+ issues: [],
328
+ warnings: [],
329
+ packageJson: {},
330
+ pluginDir: "/tmp",
331
+ });
332
+ expect(result).toContain("passed");
333
+ });
334
+ });
335
+
336
+ // ---------------------------------------------------------------------------
337
+ // runPublish (CLI entrypoint)
338
+ // ---------------------------------------------------------------------------
339
+
340
+ describe("runPublish", () => {
341
+ it("returns false on denied confirmation without submitting", async () => {
342
+ const dir = makePluginDir(tmpdir(), validPkg, { hooks: true });
343
+ // Initialize a git repo so resolveGitContext doesn't fail
344
+ const { execSync } = await import("node:child_process");
345
+ const gitEnv = {
346
+ ...process.env,
347
+ GIT_AUTHOR_NAME: "Test",
348
+ GIT_AUTHOR_EMAIL: "test@test.com",
349
+ GIT_COMMITTER_NAME: "Test",
350
+ GIT_COMMITTER_EMAIL: "test@test.com",
351
+ };
352
+ execSync("git init && git add -A && git commit -m init", {
353
+ cwd: dir,
354
+ stdio: "ignore",
355
+ env: gitEnv,
356
+ });
357
+ execSync("git remote add origin https://github.com/test/test-plugin.git", {
358
+ cwd: dir,
359
+ stdio: "ignore",
360
+ env: gitEnv,
361
+ });
362
+
363
+ const ok = await runPublish(
364
+ { path: dir, force: false, json: false },
365
+ {
366
+ confirmPrompt: async () => "denied",
367
+ },
368
+ );
369
+
370
+ expect(ok).toBe(false);
371
+ rmSync(dir, { recursive: true });
372
+ });
373
+
374
+ it("returns true on --print without submitting", async () => {
375
+ const dir = makePluginDir(tmpdir(), validPkg, { hooks: true });
376
+
377
+ const ok = await runPublish(
378
+ { path: dir, print: true, json: true },
379
+ {
380
+ confirmPrompt: async () => {
381
+ throw new Error("should not be called");
382
+ },
383
+ },
384
+ );
385
+
386
+ expect(ok).toBe(true);
387
+ rmSync(dir, { recursive: true });
388
+ });
389
+
390
+ it("returns false when no package.json found", async () => {
391
+ const dir = mkdtempSync(join(tmpdir(), "no-pkg-"));
392
+ const ok = await runPublish(
393
+ { path: dir, force: true, json: true },
394
+ {
395
+ confirmPrompt: async () => {
396
+ throw new Error("should not be called");
397
+ },
398
+ },
399
+ );
400
+
401
+ expect(ok).toBe(false);
402
+ rmSync(dir, { recursive: true });
403
+ });
404
+ });
@@ -13,10 +13,25 @@
13
13
  */
14
14
 
15
15
  import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
16
- import { join } from "node:path";
16
+ import { dirname, join } from "node:path";
17
17
 
18
18
  import { getWorkspacePluginsDir } from "../../util/platform.js";
19
19
 
20
+ /**
21
+ * Directory containing first-party default plugin packages. Each subdirectory
22
+ * has a `package.json` with `name` (prefixed `default-`) and `version`.
23
+ * Read from the filesystem at call time to avoid pulling hook/tool
24
+ * implementations into the CLI process (which would create circular
25
+ * dependencies in test environments).
26
+ */
27
+ const DEFAULT_PLUGINS_DIR = join(
28
+ dirname(new URL(import.meta.url).pathname),
29
+ "..",
30
+ "..",
31
+ "plugins",
32
+ "defaults",
33
+ );
34
+
20
35
  /** Minimal manifest fields surfaced to the CLI. */
21
36
  export interface PluginPackageMetadata {
22
37
  readonly name?: string;
@@ -40,6 +55,20 @@ export interface InstalledPluginInfo {
40
55
  readonly issues: readonly string[];
41
56
  }
42
57
 
58
+ /** Where the plugin comes from. */
59
+ export type PluginSource = "user" | "default";
60
+
61
+ /**
62
+ * Extended plugin entry that includes source (`user` vs `default`) and
63
+ * disabled status. Used by {@link listAllPlugins}.
64
+ */
65
+ export interface AllPluginInfo extends InstalledPluginInfo {
66
+ /** Whether this is a user-installed or first-party default plugin. */
67
+ readonly source: PluginSource;
68
+ /** Whether the plugin is disabled via a `.disabled` sentinel file. */
69
+ readonly disabled: boolean;
70
+ }
71
+
43
72
  /** Options accepted by {@link listInstalledPlugins}. */
44
73
  export interface ListInstalledPluginsOptions {
45
74
  /** Override the workspace plugins directory. Falls back to {@link getWorkspacePluginsDir}. */
@@ -146,3 +175,152 @@ function readPluginEntry(
146
175
 
147
176
  return { name, target, packageJson, issues };
148
177
  }
178
+
179
+ /**
180
+ * List all plugins — both user-installed (from `<workspace>/plugins/`) and
181
+ * first-party defaults (from the source tree). Each entry is annotated with
182
+ * its `source` (`"user"` or `"default"`) and `disabled` status (whether a
183
+ * `.disabled` sentinel file exists in the plugin's workspace directory).
184
+ *
185
+ * For user plugins, the `.disabled` file lives in the plugin's own install
186
+ * directory. For default plugins, it lives in a stub directory at
187
+ * `<workspace>/plugins/<manifest-name>/` (created by `plugins disable`).
188
+ *
189
+ * Stub directories created by `plugins disable <default-name>` are excluded
190
+ * from the user listing so a disabled default plugin appears only once (as a
191
+ * default entry, not a duplicate user entry with "missing package.json").
192
+ *
193
+ * Sort order:
194
+ * 1. Enabled user plugins (by install date, oldest first — matches
195
+ * hook/tool resolution order)
196
+ * 2. Disabled user plugins (by install date)
197
+ * 3. Enabled default plugins (by repo array order — matches registration
198
+ * order which fixes hook-chain order)
199
+ * 4. Disabled default plugins (by repo array order)
200
+ */
201
+ export function listAllPlugins(
202
+ opts: ListInstalledPluginsOptions = {},
203
+ ): AllPluginInfo[] {
204
+ const pluginsDir = opts.workspacePluginsDir ?? getWorkspacePluginsDir();
205
+
206
+ // ── User plugins ───────────────────────────────────────────────────────
207
+ // Filter out default-plugin stub directories (created by `plugins disable
208
+ // default-<name>`) so they don't show up as duplicate user entries.
209
+ const defaultNames = new Set(
210
+ readDefaultPluginManifests().map((m) => m.name),
211
+ );
212
+ const userPlugins: AllPluginInfo[] = listInstalledPlugins(opts)
213
+ .filter((entry) => !defaultNames.has(entry.name))
214
+ .map((entry) => ({
215
+ ...entry,
216
+ source: "user" as const,
217
+ disabled: existsSync(join(entry.target, ".disabled")),
218
+ }));
219
+
220
+ // ── Default plugins ────────────────────────────────────────────────────
221
+ // Default plugins live in the source tree at src/plugins/defaults/<name>/.
222
+ // Read each package.json from the filesystem to get name+version without
223
+ // importing hook/tool implementations (which would create circular
224
+ // dependencies in test environments). The .disabled sentinel lives in a
225
+ // stub directory at <workspace>/plugins/<manifest-name>/.
226
+ // readDefaultPluginManifests returns in repo array (registration) order.
227
+ const defaultPlugins: AllPluginInfo[] = readDefaultPluginManifests().map(
228
+ (manifest) => {
229
+ const target = join(pluginsDir, manifest.name);
230
+ const disabled = existsSync(join(target, ".disabled"));
231
+ return {
232
+ name: manifest.name,
233
+ target,
234
+ packageJson: {
235
+ name: manifest.name,
236
+ version: manifest.version,
237
+ },
238
+ issues: [],
239
+ source: "default" as const,
240
+ disabled,
241
+ };
242
+ },
243
+ );
244
+
245
+ // Sort: enabled user (install date), disabled user (install date),
246
+ // enabled default (repo order), disabled default (repo order).
247
+ const enabledUser = userPlugins.filter((p) => !p.disabled);
248
+ const disabledUser = userPlugins.filter((p) => p.disabled);
249
+ const enabledDefault = defaultPlugins.filter((p) => !p.disabled);
250
+ const disabledDefault = defaultPlugins.filter((p) => p.disabled);
251
+
252
+ enabledUser.sort((a, b) => getPluginInstallDate(a) - getPluginInstallDate(b));
253
+ disabledUser.sort(
254
+ (a, b) => getPluginInstallDate(a) - getPluginInstallDate(b),
255
+ );
256
+ // enabledDefault and disabledDefault keep repo array order (no sort).
257
+
258
+ return [...enabledUser, ...disabledUser, ...enabledDefault, ...disabledDefault];
259
+ }
260
+
261
+ interface DefaultPluginManifest {
262
+ readonly name: string;
263
+ readonly version?: string;
264
+ }
265
+
266
+ /**
267
+ * Read first-party default plugin manifests from the filesystem. Each
268
+ * subdirectory under {@link DEFAULT_PLUGINS_DIR} that has a `package.json`
269
+ * with a `name` field is included. This avoids importing `defaults/index.ts`
270
+ * (which would pull in hook/tool implementations and create circular
271
+ * dependencies in test environments).
272
+ */
273
+ function readDefaultPluginManifests(): readonly DefaultPluginManifest[] {
274
+ if (!existsSync(DEFAULT_PLUGINS_DIR)) return [];
275
+
276
+ const entries = readdirSync(DEFAULT_PLUGINS_DIR, { withFileTypes: true })
277
+ .filter((e) => e.isDirectory())
278
+ .map((e) => e.name)
279
+ .sort();
280
+
281
+ const manifests: DefaultPluginManifest[] = [];
282
+ for (const name of entries) {
283
+ const pkgJsonPath = join(DEFAULT_PLUGINS_DIR, name, "package.json");
284
+ if (!existsSync(pkgJsonPath)) continue;
285
+ try {
286
+ const raw = readFileSync(pkgJsonPath, "utf8");
287
+ const parsed = JSON.parse(raw) as Record<string, unknown>;
288
+ if (typeof parsed.name === "string") {
289
+ manifests.push({
290
+ name: parsed.name,
291
+ version:
292
+ typeof parsed.version === "string" ? parsed.version : undefined,
293
+ });
294
+ }
295
+ } catch {
296
+ // Skip malformed entries — lenient like listInstalledPlugins.
297
+ }
298
+ }
299
+ return manifests;
300
+ }
301
+
302
+ /**
303
+ * Resolve the install date for a user plugin directory, in epoch ms.
304
+ * Reads `install-meta.json`'s `installedAt` field first, falling back to
305
+ * the directory's birthtime. Mirrors the logic in mtime-cache's
306
+ * `getInstallDate` so the sort order matches hook/tool resolution order.
307
+ */
308
+ function getPluginInstallDate(plugin: AllPluginInfo): number {
309
+ const metaPath = join(plugin.target, "install-meta.json");
310
+ try {
311
+ if (existsSync(metaPath)) {
312
+ const raw = JSON.parse(readFileSync(metaPath, "utf8")) as Record<string, unknown>;
313
+ if (typeof raw.installedAt === "string") {
314
+ const ms = Date.parse(raw.installedAt);
315
+ if (Number.isFinite(ms)) return ms;
316
+ }
317
+ }
318
+ } catch {
319
+ // Fall through to birthtime.
320
+ }
321
+ try {
322
+ return statSync(plugin.target).birthtimeMs;
323
+ } catch {
324
+ return 0;
325
+ }
326
+ }