@vellumai/assistant 0.8.8 → 0.8.9-dev.202606091926.ebb2d62

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 (490) hide show
  1. package/ARCHITECTURE.md +6 -6
  2. package/bun.lock +2 -2
  3. package/docs/activation-funnel-telemetry.md +310 -0
  4. package/examples/plugins/echo/README.md +61 -60
  5. package/examples/plugins/echo/hooks/post-tool-use.ts +18 -0
  6. package/examples/plugins/echo/hooks/stop.ts +16 -0
  7. package/examples/plugins/echo/hooks/user-prompt-submit.ts +18 -0
  8. package/examples/plugins/echo/package.json +1 -2
  9. package/examples/plugins/echo/src/emit.ts +19 -0
  10. package/node_modules/@vellumai/skill-host-contracts/src/skill-host.ts +7 -6
  11. package/openapi.yaml +273 -121
  12. package/package.json +2 -2
  13. package/scripts/sync-llm-catalog.ts +2 -0
  14. package/src/__tests__/activation-early-marking.test.ts +120 -0
  15. package/src/__tests__/agent-loop-callsite-precedence.test.ts +69 -14
  16. package/src/__tests__/agent-loop-exit-reason.test.ts +204 -144
  17. package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +50 -35
  18. package/src/__tests__/agent-loop-output-hooks.test.ts +357 -0
  19. package/src/__tests__/agent-loop-override-profile.test.ts +25 -6
  20. package/src/__tests__/agent-loop-provider-error-recording.test.ts +41 -21
  21. package/src/__tests__/agent-loop-thinking.test.ts +36 -20
  22. package/src/__tests__/agent-loop.test.ts +441 -96
  23. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +14 -14
  24. package/src/__tests__/agent-wake-override-profile.test.ts +17 -21
  25. package/src/__tests__/anthropic-provider.test.ts +23 -8
  26. package/src/__tests__/app-builder-tool-scripts.test.ts +21 -0
  27. package/src/__tests__/app-control-flow.test.ts +1 -1
  28. package/src/__tests__/app-dir-path-guard.test.ts +1 -0
  29. package/src/__tests__/app-executors.test.ts +132 -0
  30. package/src/__tests__/approval-cascade.test.ts +6 -5
  31. package/src/__tests__/approval-routes-http.test.ts +4 -1
  32. package/src/__tests__/background-workers-disk-pressure.test.ts +1 -1
  33. package/src/__tests__/build-persisted-content.test.ts +75 -1
  34. package/src/__tests__/channel-approval-routes.test.ts +1 -1
  35. package/src/__tests__/channel-approvals.test.ts +1 -1
  36. package/src/__tests__/compaction-circuit.test.ts +258 -0
  37. package/src/__tests__/compaction-direct.test.ts +146 -0
  38. package/src/__tests__/compaction-events.test.ts +7 -7
  39. package/src/__tests__/compaction.benchmark.test.ts +1 -1
  40. package/src/__tests__/compactor-web-search-strip.test.ts +213 -0
  41. package/src/__tests__/config-managed-gemini-defaults.test.ts +2 -46
  42. package/src/__tests__/context-overflow-reducer.test.ts +265 -125
  43. package/src/__tests__/context-window-manager-compact-retry.test.ts +121 -15
  44. package/src/__tests__/conversation-abort-tool-results.test.ts +7 -5
  45. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +7 -10
  46. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +78 -119
  47. package/src/__tests__/conversation-agent-loop-overflow.test.ts +158 -234
  48. package/src/__tests__/conversation-agent-loop.test.ts +342 -623
  49. package/src/__tests__/conversation-clean-command.test.ts +5 -2
  50. package/src/__tests__/conversation-confirmation-signals.test.ts +6 -5
  51. package/src/__tests__/conversation-crud-inference-profile.test.ts +7 -9
  52. package/src/__tests__/conversation-error.test.ts +15 -1
  53. package/src/__tests__/conversation-history-web-search.test.ts +6 -1
  54. package/src/__tests__/conversation-media-retry.test.ts +1 -1
  55. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +44 -4
  56. package/src/__tests__/conversation-process-callsite.test.ts +15 -15
  57. package/src/__tests__/conversation-provider-retry-repair.test.ts +66 -65
  58. package/src/__tests__/conversation-queue.test.ts +10 -10
  59. package/src/__tests__/conversation-runtime-assembly.test.ts +1059 -231
  60. package/src/__tests__/conversation-runtime-workspace.test.ts +115 -20
  61. package/src/__tests__/conversation-slash-queue.test.ts +7 -5
  62. package/src/__tests__/conversation-slash-unknown.test.ts +7 -5
  63. package/src/__tests__/conversation-speed-override.test.ts +11 -10
  64. package/src/__tests__/conversation-starter-routes.test.ts +14 -6
  65. package/src/__tests__/conversation-surfaces-activation-emit.test.ts +395 -0
  66. package/src/__tests__/conversation-surfaces-app-control.test.ts +48 -1
  67. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +73 -4
  68. package/src/__tests__/conversation-undo.test.ts +2 -2
  69. package/src/__tests__/conversation-workspace-cache-state.test.ts +24 -21
  70. package/src/__tests__/conversation-workspace-injection.test.ts +69 -7
  71. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +15 -12
  72. package/src/__tests__/conversations-import-system-filter.test.ts +101 -0
  73. package/src/__tests__/corrected-target.test.ts +93 -0
  74. package/src/__tests__/credential-execution-feature-gates.test.ts +5 -4
  75. package/src/__tests__/credential-security-invariants.test.ts +1 -0
  76. package/src/__tests__/cu-unified-flow.test.ts +36 -0
  77. package/src/__tests__/db-acp-history.test.ts +101 -0
  78. package/src/__tests__/dynamic-page-surface.test.ts +31 -0
  79. package/src/__tests__/embedding-managed-proxy-selection.test.ts +6 -40
  80. package/src/__tests__/empty-response-hook.test.ts +1 -1
  81. package/src/__tests__/file-write-tool.test.ts +63 -0
  82. package/src/__tests__/gateway-only-guard.test.ts +17 -2
  83. package/src/__tests__/guardian-grant-minting.test.ts +1 -1
  84. package/src/__tests__/guardian-routing-invariants.test.ts +93 -4
  85. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +1 -1
  86. package/src/__tests__/heartbeat-disk-pressure.test.ts +1 -0
  87. package/src/__tests__/heartbeat-service.test.ts +1 -0
  88. package/src/__tests__/history-repair-hook.test.ts +5 -1
  89. package/src/__tests__/host-app-control-proxy.test.ts +45 -0
  90. package/src/__tests__/host-app-control-routes.test.ts +1 -1
  91. package/src/__tests__/host-cu-routes-targeted.test.ts +3 -3
  92. package/src/__tests__/inference-profile-reaper.test.ts +62 -0
  93. package/src/__tests__/inference-profile-session-handler.test.ts +86 -0
  94. package/src/__tests__/injector-background-turn.test.ts +13 -23
  95. package/src/__tests__/injector-chain.test.ts +268 -44
  96. package/src/__tests__/injector-disk-pressure.test.ts +210 -52
  97. package/src/__tests__/injector-document-comments.test.ts +97 -114
  98. package/src/__tests__/injector-pkb-v2-silenced.test.ts +2 -2
  99. package/src/__tests__/injector-v3-suppression.test.ts +4 -4
  100. package/src/__tests__/list-messages-client-message-id.test.ts +91 -0
  101. package/src/__tests__/list-messages-hidden-metadata.test.ts +38 -0
  102. package/src/__tests__/llm-catalog-parity.test.ts +4 -0
  103. package/src/__tests__/llm-resolver.test.ts +73 -0
  104. package/src/__tests__/memory-retrieval-hook.test.ts +156 -38
  105. package/src/__tests__/memory-v2-static-injector.test.ts +86 -8
  106. package/src/__tests__/notification-decision-strategy.test.ts +3 -3
  107. package/src/__tests__/parallel-tool.benchmark.test.ts +35 -8
  108. package/src/__tests__/persist-unsendable-image-downscale.test.ts +145 -0
  109. package/src/__tests__/persist-unsendable-image.test.ts +97 -1
  110. package/src/__tests__/persistence-secret-redaction.test.ts +78 -0
  111. package/src/__tests__/plugin-api-shim.test.ts +6 -9
  112. package/src/__tests__/plugin-bootstrap.test.ts +91 -81
  113. package/src/__tests__/plugin-registry.test.ts +3 -49
  114. package/src/__tests__/plugin-skill-contribution.test.ts +15 -14
  115. package/src/__tests__/plugin-tool-contribution.test.ts +7 -4
  116. package/src/__tests__/plugin-types.test.ts +0 -70
  117. package/src/__tests__/post-turn-tool-result-truncation.test.ts +69 -0
  118. package/src/__tests__/pre-model-call-sanitize.test.ts +109 -0
  119. package/src/__tests__/published-app-updater.test.ts +138 -0
  120. package/src/__tests__/reaction-persistence.test.ts +1 -1
  121. package/src/__tests__/retry-thinking-adaptive-only.test.ts +245 -0
  122. package/src/__tests__/send-endpoint-busy.test.ts +4 -1
  123. package/src/__tests__/server-history-render.test.ts +39 -0
  124. package/src/__tests__/skill-feature-flags-integration.test.ts +31 -0
  125. package/src/__tests__/steer-tool-repair.test.ts +1 -1
  126. package/src/__tests__/subagent-call-site-routing.test.ts +1 -1
  127. package/src/__tests__/subagent-detail.test.ts +25 -7
  128. package/src/__tests__/subagent-fork-notifications.test.ts +1 -3
  129. package/src/__tests__/subagent-fork-spawn.test.ts +1 -1
  130. package/src/__tests__/subagent-manager-notify.test.ts +1 -3
  131. package/src/__tests__/subagent-notify-parent.test.ts +1 -3
  132. package/src/__tests__/subagent-spawn-tool-fork.test.ts +1 -1
  133. package/src/__tests__/title-generate-hook.test.ts +5 -1
  134. package/src/__tests__/tool-error-hook.test.ts +1 -1
  135. package/src/__tests__/tool-result-truncate-hook.test.ts +1 -1
  136. package/src/__tests__/tool-start-timestamp.test.ts +218 -0
  137. package/src/__tests__/user-plugin-loader.test.ts +54 -286
  138. package/src/__tests__/web-fetch.test.ts +45 -0
  139. package/src/acp/__tests__/agent-process.test.ts +161 -0
  140. package/src/acp/__tests__/client-handler.test.ts +40 -0
  141. package/src/acp/__tests__/helpers/acp-config-stub.ts +0 -2
  142. package/src/acp/__tests__/helpers/acp-history-db.ts +82 -0
  143. package/src/acp/__tests__/helpers/exec-file-stub.ts +106 -0
  144. package/src/acp/__tests__/prepare-agent-env.test.ts +97 -0
  145. package/src/acp/__tests__/session-manager-persistence.test.ts +95 -28
  146. package/src/acp/__tests__/session-manager-resume.test.ts +888 -0
  147. package/src/acp/agent-process.ts +61 -1
  148. package/src/acp/auto-install.test.ts +280 -0
  149. package/src/acp/auto-install.ts +232 -0
  150. package/src/acp/client-handler.ts +31 -0
  151. package/src/acp/prepare-agent-env.ts +80 -27
  152. package/src/acp/resolve-agent.test.ts +188 -28
  153. package/src/acp/resolve-agent.ts +119 -42
  154. package/src/acp/resume-hint.ts +23 -0
  155. package/src/acp/session-manager.ts +507 -73
  156. package/src/agent/compaction-circuit.ts +60 -102
  157. package/src/agent/loop.ts +403 -251
  158. package/src/api/events/tool-result.ts +9 -0
  159. package/src/api/events/tool-use-start.ts +7 -0
  160. package/src/api/responses/conversation-message.ts +72 -13
  161. package/src/api/responses/memory-v3-selection-log.ts +19 -10
  162. package/src/approvals/guardian-request-resolvers.ts +27 -1
  163. package/src/background-wake/next-wake.ts +1 -0
  164. package/src/cli/commands/__tests__/memory-v3.test.ts +191 -210
  165. package/src/cli/commands/channel-verification-sessions.ts +6 -6
  166. package/src/cli/commands/db/__tests__/repair.test.ts +3 -1
  167. package/src/cli/commands/memory-v3.ts +57 -199
  168. package/src/cli/commands/plugins.ts +43 -37
  169. package/src/cli/lib/__tests__/install-from-github.test.ts +790 -111
  170. package/src/cli/lib/__tests__/plugin-catalog-cache.test.ts +196 -0
  171. package/src/cli/lib/__tests__/plugin-details.test.ts +381 -0
  172. package/src/cli/lib/__tests__/plugin-marketplace.test.ts +270 -0
  173. package/src/cli/lib/__tests__/search-plugins.test.ts +233 -32
  174. package/src/cli/lib/install-from-github.ts +797 -60
  175. package/src/cli/lib/plugin-catalog-cache.ts +84 -0
  176. package/src/cli/lib/plugin-details.ts +416 -0
  177. package/src/cli/lib/plugin-marketplace.ts +215 -0
  178. package/src/cli/lib/search-plugins.ts +201 -29
  179. package/src/config/__tests__/feature-flag-registry-guard.test.ts +2 -2
  180. package/src/config/acp-defaults.test.ts +10 -0
  181. package/src/config/acp-defaults.ts +9 -3
  182. package/src/config/acp-schema.ts +1 -7
  183. package/src/config/bundled-skills/acp/SKILL.md +80 -41
  184. package/src/config/bundled-skills/acp/TOOLS.json +4 -4
  185. package/src/config/bundled-skills/app-builder/SKILL.md +224 -381
  186. package/src/config/bundled-skills/app-builder/TOOLS.json +72 -2
  187. package/src/config/bundled-skills/app-builder/references/DESIGN_SYSTEM.md +48 -0
  188. package/src/config/bundled-skills/app-builder/references/RESPONSIVE.md +57 -0
  189. package/src/config/bundled-skills/app-builder/references/SLIDES.md +38 -0
  190. package/src/config/bundled-skills/app-builder/tools/app-list.ts +62 -0
  191. package/src/config/bundled-skills/app-builder/tools/app-update.ts +18 -0
  192. package/src/config/bundled-skills/document-editor/SKILL.md +28 -23
  193. package/src/config/bundled-skills/document-editor/TOOLS.json +1 -1
  194. package/src/config/bundled-tool-registry.ts +4 -0
  195. package/src/config/call-site-defaults.ts +0 -3
  196. package/src/config/feature-flag-registry.json +10 -56
  197. package/src/config/llm-resolver.ts +39 -7
  198. package/src/config/loader.ts +5 -22
  199. package/src/config/schemas/__tests__/memory-v3.test.ts +25 -9
  200. package/src/config/schemas/call-site-catalog.ts +0 -21
  201. package/src/config/schemas/heartbeat.ts +9 -0
  202. package/src/config/schemas/llm.ts +17 -3
  203. package/src/config/schemas/memory-v3.ts +58 -8
  204. package/src/config/seed-inference-profiles.ts +18 -0
  205. package/src/context/compactor.ts +22 -4
  206. package/src/context/post-turn-tool-result-truncation.ts +39 -1
  207. package/src/context/strip-injections.ts +8 -2
  208. package/src/daemon/conversation-agent-loop-handlers.ts +120 -42
  209. package/src/daemon/conversation-agent-loop.ts +308 -1297
  210. package/src/daemon/conversation-error.ts +31 -4
  211. package/src/daemon/conversation-history.ts +1 -1
  212. package/src/daemon/conversation-lifecycle.ts +11 -255
  213. package/src/daemon/conversation-media-retry.ts +19 -6
  214. package/src/daemon/conversation-messaging.ts +17 -0
  215. package/src/daemon/conversation-process.ts +22 -141
  216. package/src/daemon/conversation-queue-manager.ts +8 -0
  217. package/src/daemon/conversation-registry.ts +159 -0
  218. package/src/daemon/conversation-runtime-assembly.ts +361 -393
  219. package/src/daemon/conversation-store.ts +9 -90
  220. package/src/daemon/conversation-surfaces.ts +165 -11
  221. package/src/daemon/conversation-workspace.ts +17 -0
  222. package/src/daemon/conversation.ts +444 -62
  223. package/src/daemon/disk-pressure-policy.ts +0 -1
  224. package/src/daemon/external-plugins-bootstrap.ts +36 -46
  225. package/src/daemon/handlers/config-channels.ts +11 -2
  226. package/src/daemon/handlers/conversations.ts +3 -1
  227. package/src/daemon/handlers/shared.ts +9 -1
  228. package/src/daemon/handlers/skills.ts +4 -1
  229. package/src/daemon/host-app-control-proxy.ts +72 -57
  230. package/src/daemon/host-proxy-preactivation.ts +1 -3
  231. package/src/daemon/lifecycle.ts +21 -7
  232. package/src/daemon/persist-unsendable-image.ts +62 -25
  233. package/src/daemon/process-message.ts +1 -1
  234. package/src/daemon/server.ts +2 -0
  235. package/src/daemon/tool-side-effects.ts +15 -0
  236. package/src/daemon/wake-conversation-ops.ts +269 -0
  237. package/src/embedded/plugin-api.ts +2 -2
  238. package/src/export/__tests__/transcript-formatter.test.ts +5 -0
  239. package/src/heartbeat/__tests__/heartbeat-service.test.ts +3 -0
  240. package/src/heartbeat/heartbeat-run-store.ts +23 -1
  241. package/src/heartbeat/heartbeat-service.ts +26 -0
  242. package/src/ipc/__tests__/browser-ipc.test.ts +1 -1
  243. package/src/ipc/__tests__/ui-request-route.test.ts +3 -3
  244. package/src/ipc/skill-routes/__tests__/memory.test.ts +15 -0
  245. package/src/ipc/skill-routes/memory.ts +4 -2
  246. package/src/memory/__tests__/activation-session-store.test.ts +41 -0
  247. package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +87 -0
  248. package/src/memory/__tests__/onboarding-events-store.test.ts +80 -0
  249. package/src/memory/activation-session-store.ts +43 -0
  250. package/src/memory/conversation-crud.ts +29 -19
  251. package/src/memory/conversation-starter-checkpoints.ts +1 -0
  252. package/src/memory/db-init.ts +6 -0
  253. package/src/memory/embedding-backend.ts +5 -22
  254. package/src/memory/job-handlers/conversation-starters.ts +13 -2
  255. package/src/memory/jobs/__tests__/embed-concept-page.test.ts +5 -4
  256. package/src/memory/jobs-worker.ts +25 -1
  257. package/src/memory/migrations/272-acp-session-history-cwd.ts +36 -0
  258. package/src/memory/migrations/273-onboarding-events-funnel-columns.ts +46 -0
  259. package/src/memory/migrations/274-create-activation-sessions.ts +15 -0
  260. package/src/memory/migrations/index.ts +3 -0
  261. package/src/memory/onboarding-events-store.ts +66 -18
  262. package/src/memory/schema/acp.ts +4 -0
  263. package/src/memory/schema/infrastructure.ts +13 -0
  264. package/src/memory/v2/__tests__/consolidation-job.test.ts +4 -97
  265. package/src/memory/v2/__tests__/page-store.test.ts +22 -0
  266. package/src/memory/v2/consolidation-job.ts +2 -63
  267. package/src/memory/v2/types.ts +5 -0
  268. package/src/messaging/providers/telegram-bot/api.ts +14 -5
  269. package/src/notifications/__tests__/copy-composer.test.ts +244 -0
  270. package/src/notifications/access-request-copy.ts +298 -0
  271. package/src/notifications/adapters/slack.ts +3 -3
  272. package/src/notifications/adapters/telegram.ts +9 -2
  273. package/src/notifications/copy-composer.ts +49 -267
  274. package/src/notifications/decision-engine.ts +16 -35
  275. package/src/notifications/home-feed-side-effect.ts +1 -6
  276. package/src/plugin-api/constants.ts +4 -0
  277. package/src/plugin-api/index.ts +40 -11
  278. package/src/plugin-api/types.ts +108 -0
  279. package/src/plugins/defaults/compaction/compact.ts +106 -0
  280. package/src/{daemon → plugins/defaults/compaction}/context-overflow-reducer.ts +250 -42
  281. package/src/plugins/defaults/compaction/corrected-target.ts +53 -0
  282. package/src/plugins/defaults/compaction/manager-store.ts +57 -0
  283. package/src/plugins/defaults/compaction/package.json +1 -2
  284. package/src/{context → plugins/defaults/compaction}/window-manager.ts +95 -25
  285. package/src/plugins/defaults/empty-response/package.json +0 -1
  286. package/src/plugins/defaults/history-repair/package.json +0 -1
  287. package/src/plugins/defaults/index.ts +156 -26
  288. package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +100 -52
  289. package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit.ts +285 -0
  290. package/src/plugins/defaults/memory-retrieval/injector-chain.ts +2 -2
  291. package/src/plugins/defaults/{injectors/register.ts → memory-retrieval/injectors.ts} +148 -73
  292. package/src/plugins/defaults/memory-retrieval/package.json +14 -0
  293. package/src/plugins/defaults/memory-retrieval/unified-turn-context.ts +223 -0
  294. package/src/plugins/defaults/memory-v3-shadow/__tests__/capabilities.test.ts +71 -0
  295. package/src/plugins/defaults/memory-v3-shadow/__tests__/dense.test.ts +181 -0
  296. package/src/plugins/defaults/memory-v3-shadow/__tests__/edge.test.ts +247 -0
  297. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/live-integration.test.ts +147 -134
  298. package/src/plugins/defaults/memory-v3-shadow/__tests__/maintain-job.test.ts +456 -0
  299. package/src/plugins/defaults/memory-v3-shadow/__tests__/orchestrate.test.ts +662 -0
  300. package/src/plugins/defaults/memory-v3-shadow/__tests__/pool-select.test.ts +306 -0
  301. package/src/plugins/defaults/memory-v3-shadow/__tests__/render-injection.test.ts +91 -0
  302. package/src/plugins/defaults/memory-v3-shadow/__tests__/section-dense-store.test.ts +402 -0
  303. package/src/plugins/defaults/memory-v3-shadow/__tests__/section-needle.test.ts +135 -0
  304. package/src/plugins/defaults/memory-v3-shadow/__tests__/sections.test.ts +125 -0
  305. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/selection-log-store.test.ts +74 -19
  306. package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-integration.test.ts +446 -0
  307. package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +592 -0
  308. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/types.test.ts +2 -24
  309. package/src/plugins/defaults/memory-v3-shadow/capabilities.ts +91 -0
  310. package/src/plugins/defaults/memory-v3-shadow/dense.ts +97 -0
  311. package/src/plugins/defaults/memory-v3-shadow/edge.ts +252 -0
  312. package/src/plugins/defaults/memory-v3-shadow/hooks/post-compact.ts +14 -0
  313. package/src/plugins/defaults/memory-v3-shadow/hooks/user-prompt-submit.ts +19 -0
  314. package/src/plugins/defaults/memory-v3-shadow/injector.ts +76 -0
  315. package/src/plugins/defaults/memory-v3-shadow/maintain-job.ts +555 -0
  316. package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +266 -0
  317. package/src/plugins/defaults/memory-v3-shadow/package.json +14 -0
  318. package/src/plugins/defaults/memory-v3-shadow/page-content.ts +71 -0
  319. package/src/plugins/defaults/memory-v3-shadow/pool-select.ts +204 -0
  320. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/render-injection.ts +16 -8
  321. package/src/plugins/defaults/memory-v3-shadow/section-dense-store.ts +236 -0
  322. package/src/plugins/defaults/memory-v3-shadow/section-needle.ts +200 -0
  323. package/src/plugins/defaults/memory-v3-shadow/sections.ts +115 -0
  324. package/src/plugins/defaults/memory-v3-shadow/selection-log-store.ts +156 -0
  325. package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +382 -0
  326. package/src/plugins/defaults/memory-v3-shadow/types.ts +92 -0
  327. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/working-set.ts +4 -1
  328. package/src/plugins/defaults/title-generate/package.json +0 -1
  329. package/src/plugins/defaults/tool-error/package.json +0 -1
  330. package/src/plugins/defaults/tool-result-truncate/package.json +0 -1
  331. package/src/plugins/external-api.ts +12 -2
  332. package/src/plugins/pipeline.ts +6 -293
  333. package/src/plugins/registry.ts +9 -37
  334. package/src/plugins/types.ts +76 -381
  335. package/src/plugins/user-loader.ts +30 -127
  336. package/src/prompts/__tests__/system-prompt.test.ts +6 -0
  337. package/src/prompts/system-prompt.ts +61 -10
  338. package/src/prompts/templates/BOOTSTRAP-ACTIVATION-RAIL.md +71 -4
  339. package/src/providers/anthropic/client.ts +9 -10
  340. package/src/providers/inference/kimi-cjk-token-ids.ts +493 -0
  341. package/src/providers/inference/logit-bias.ts +55 -0
  342. package/src/providers/model-catalog.ts +57 -0
  343. package/src/providers/openai/__tests__/vision-not-supported.test.ts +75 -0
  344. package/src/providers/openai/chat-completions-provider.ts +44 -1
  345. package/src/providers/retry.ts +57 -7
  346. package/src/providers/types.ts +6 -0
  347. package/src/runtime/__tests__/agent-wake.test.ts +555 -691
  348. package/src/runtime/__tests__/interactive-ui.test.ts +1 -1
  349. package/src/runtime/agent-wake.ts +108 -209
  350. package/src/runtime/assistant-event-hub.ts +1 -1
  351. package/src/runtime/channel-approvals.ts +1 -1
  352. package/src/runtime/interactive-ui.ts +1 -1
  353. package/src/runtime/routes/__tests__/acp-routes.test.ts +466 -55
  354. package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +1 -1
  355. package/src/runtime/routes/__tests__/plugins-routes.test.ts +423 -73
  356. package/src/runtime/routes/__tests__/stt-routes.test.ts +112 -0
  357. package/src/runtime/routes/__tests__/surface-action-routes.test.ts +5 -4
  358. package/src/runtime/routes/__tests__/surface-content-routes.test.ts +4 -1
  359. package/src/runtime/routes/acp-routes.test.ts +185 -32
  360. package/src/runtime/routes/acp-routes.ts +328 -30
  361. package/src/runtime/routes/app-management-routes.ts +3 -0
  362. package/src/runtime/routes/approval-routes.ts +1 -1
  363. package/src/runtime/routes/browser-routes.ts +1 -1
  364. package/src/runtime/routes/browser-tabs-routes.ts +6 -10
  365. package/src/runtime/routes/channel-verification-routes.ts +14 -5
  366. package/src/runtime/routes/conversation-cli-routes.ts +1 -1
  367. package/src/runtime/routes/conversation-list-routes.ts +1 -1
  368. package/src/runtime/routes/conversation-query-routes.ts +1 -1
  369. package/src/runtime/routes/conversation-routes.ts +30 -2
  370. package/src/runtime/routes/conversation-starter-routes.ts +13 -7
  371. package/src/runtime/routes/conversations-import-routes.ts +24 -7
  372. package/src/runtime/routes/host-app-control-routes.ts +1 -1
  373. package/src/runtime/routes/host-cu-routes.ts +1 -1
  374. package/src/runtime/routes/identity-routes.ts +18 -3
  375. package/src/runtime/routes/inbound-message-handler.ts +1 -1
  376. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +33 -34
  377. package/src/runtime/routes/inference-profile-session-handler.ts +11 -0
  378. package/src/runtime/routes/inference-profile-session-reaper.ts +6 -0
  379. package/src/runtime/routes/memory-v3-routes.ts +65 -305
  380. package/src/runtime/routes/playground/__tests__/force-compact.test.ts +1 -1
  381. package/src/runtime/routes/playground/helpers.ts +1 -1
  382. package/src/runtime/routes/plugins-routes.ts +337 -35
  383. package/src/runtime/routes/stt-routes.ts +45 -12
  384. package/src/runtime/routes/surface-conversation-resolver.ts +4 -3
  385. package/src/runtime/routes/work-items-routes.ts +2 -4
  386. package/src/runtime/routes/workspace-routes.ts +50 -15
  387. package/src/runtime/services/conversation-serializer.ts +1 -1
  388. package/src/runtime/verification-outbound-actions.ts +147 -2
  389. package/src/runtime/verification-templates.ts +29 -3
  390. package/src/services/published-app-updater.ts +30 -8
  391. package/src/signals/cancel.ts +2 -4
  392. package/src/subagent/manager.ts +21 -5
  393. package/src/telemetry/__tests__/activation-funnel.test.ts +95 -0
  394. package/src/telemetry/activation-funnel.ts +167 -0
  395. package/src/telemetry/types.ts +13 -0
  396. package/src/telemetry/usage-telemetry-reporter.test.ts +154 -0
  397. package/src/telemetry/usage-telemetry-reporter.ts +26 -1
  398. package/src/tools/acp/context.ts +20 -0
  399. package/src/tools/acp/list-agents.test.ts +9 -19
  400. package/src/tools/acp/list-agents.ts +3 -15
  401. package/src/tools/acp/spawn.test.ts +173 -202
  402. package/src/tools/acp/spawn.ts +37 -172
  403. package/src/tools/acp/steer.test.ts +105 -8
  404. package/src/tools/acp/steer.ts +48 -17
  405. package/src/tools/apps/executors.ts +166 -50
  406. package/src/tools/browser/browser-execution.ts +12 -2
  407. package/src/tools/filesystem/write.ts +34 -0
  408. package/src/tools/network/web-fetch.ts +65 -24
  409. package/src/tools/skills/load.ts +1 -1
  410. package/src/tools/subagent/spawn.ts +2 -4
  411. package/src/tools/ui-surface/definitions.ts +32 -5
  412. package/src/workspace/migrations/051-seed-conversation-summarization-callsite.ts +4 -5
  413. package/src/workspace/migrations/097-enable-adaptive-thinking-managed-profiles.ts +69 -45
  414. package/docs/plugins.md +0 -832
  415. package/examples/plugins/echo/register.ts +0 -143
  416. package/src/__tests__/circuit-breaker-pipeline.test.ts +0 -405
  417. package/src/__tests__/compaction-pipeline.test.ts +0 -210
  418. package/src/__tests__/compaction-timeout-recovery.test.ts +0 -251
  419. package/src/__tests__/overflow-reduce-pipeline.test.ts +0 -667
  420. package/src/__tests__/pipeline-runner.test.ts +0 -554
  421. package/src/daemon/wake-target-adapter.ts +0 -253
  422. package/src/memory/v3/__tests__/assign.test.ts +0 -242
  423. package/src/memory/v3/__tests__/capabilities.test.ts +0 -118
  424. package/src/memory/v3/__tests__/core.test.ts +0 -39
  425. package/src/memory/v3/__tests__/health.test.ts +0 -219
  426. package/src/memory/v3/__tests__/maintain-job.test.ts +0 -288
  427. package/src/memory/v3/__tests__/needle.test.ts +0 -107
  428. package/src/memory/v3/__tests__/orchestrate.test.ts +0 -436
  429. package/src/memory/v3/__tests__/provider-blocks.test.ts +0 -13
  430. package/src/memory/v3/__tests__/reconcile.test.ts +0 -274
  431. package/src/memory/v3/__tests__/render-injection.test.ts +0 -61
  432. package/src/memory/v3/__tests__/router.test.ts +0 -332
  433. package/src/memory/v3/__tests__/selector.test.ts +0 -470
  434. package/src/memory/v3/__tests__/shadow-plugin.test.ts +0 -432
  435. package/src/memory/v3/__tests__/snapshot.test.ts +0 -168
  436. package/src/memory/v3/__tests__/tree.test.ts +0 -192
  437. package/src/memory/v3/assign.ts +0 -268
  438. package/src/memory/v3/capabilities.ts +0 -124
  439. package/src/memory/v3/core.ts +0 -26
  440. package/src/memory/v3/health.ts +0 -0
  441. package/src/memory/v3/maintain-job.ts +0 -314
  442. package/src/memory/v3/needle.ts +0 -115
  443. package/src/memory/v3/orchestrate.ts +0 -126
  444. package/src/memory/v3/page-content.ts +0 -34
  445. package/src/memory/v3/provider-blocks.ts +0 -26
  446. package/src/memory/v3/reconcile.ts +0 -523
  447. package/src/memory/v3/router.ts +0 -190
  448. package/src/memory/v3/selection-log-store.ts +0 -84
  449. package/src/memory/v3/selector.ts +0 -226
  450. package/src/memory/v3/shadow-plugin.ts +0 -411
  451. package/src/memory/v3/snapshot.ts +0 -209
  452. package/src/memory/v3/tree.ts +0 -174
  453. package/src/memory/v3/types.ts +0 -59
  454. package/src/notifications/__tests__/emit-signal-home-feed.test.ts +0 -187
  455. package/src/plugins/defaults/circuit-breaker/middlewares/circuitBreaker.ts +0 -93
  456. package/src/plugins/defaults/circuit-breaker/package.json +0 -15
  457. package/src/plugins/defaults/circuit-breaker/register.ts +0 -39
  458. package/src/plugins/defaults/compaction/middlewares/compaction.ts +0 -25
  459. package/src/plugins/defaults/compaction/register.ts +0 -35
  460. package/src/plugins/defaults/compaction/terminal.ts +0 -73
  461. package/src/plugins/defaults/empty-response/register.ts +0 -23
  462. package/src/plugins/defaults/history-repair/register.ts +0 -24
  463. package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit-temp.ts +0 -216
  464. package/src/plugins/defaults/overflow-reduce/middlewares/overflowReduce.ts +0 -126
  465. package/src/plugins/defaults/overflow-reduce/package.json +0 -15
  466. package/src/plugins/defaults/overflow-reduce/register.ts +0 -42
  467. package/src/plugins/defaults/title-generate/register.ts +0 -35
  468. package/src/plugins/defaults/tool-error/register.ts +0 -23
  469. package/src/plugins/defaults/tool-result-truncate/register.ts +0 -24
  470. package/src/proactive-artifact/aux-message-injector.ts +0 -97
  471. package/src/proactive-artifact/decision.test.ts +0 -226
  472. package/src/proactive-artifact/decision.ts +0 -165
  473. package/src/proactive-artifact/index.ts +0 -7
  474. package/src/proactive-artifact/job.test.ts +0 -962
  475. package/src/proactive-artifact/job.ts +0 -372
  476. package/src/proactive-artifact/message-copy.ts +0 -58
  477. package/src/proactive-artifact/trigger-state.test.ts +0 -286
  478. package/src/proactive-artifact/trigger-state.ts +0 -123
  479. package/src/util/map-limit.ts +0 -27
  480. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/fixtures/eval-turns.json +0 -0
  481. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/fixtures/live-turns.json +0 -0
  482. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/working-set-eviction.test.ts +0 -0
  483. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/working-set-skeleton.test.ts +0 -0
  484. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/README.md +0 -0
  485. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/assignments.json +0 -0
  486. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/core.json +0 -0
  487. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-a/topic-x.md +0 -0
  488. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-a/topic-y.md +0 -0
  489. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-b/topic-z.md +0 -0
  490. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/llm-retry.ts +0 -0
@@ -0,0 +1,84 @@
1
+ /**
2
+ * In-memory cache for the installable plugin catalog.
3
+ *
4
+ * The catalog (`loadPluginCatalog`) is built from two unauthenticated GitHub
5
+ * Contents API calls — the first-party `experimental/plugins/` directory
6
+ * listing and the `marketplace.json` manifest. GitHub's unauthenticated rate
7
+ * limit is 60 requests/hour per IP, so a long-lived daemon that hit GitHub on
8
+ * every catalog search (on web mount, on every keystroke, across reloads and
9
+ * multiple clients) would exhaust that budget and start getting HTTP 403s.
10
+ *
11
+ * Because the GitHub responses are **query-independent** — `searchPlugins`
12
+ * applies the regex filter in memory — one catalog load serves any number of
13
+ * searches. This cache holds that load per git ref for a short TTL and, on an
14
+ * upstream failure, serves the last good catalog so a transient rate-limit or
15
+ * outage is invisible to callers. Mirrors the skills catalog cache
16
+ * (`src/skills/catalog-cache.ts`).
17
+ */
18
+
19
+ import { getLogger } from "../../util/logger.js";
20
+ import {
21
+ loadPluginCatalog,
22
+ type PluginCatalog,
23
+ PluginCatalogUnavailableError,
24
+ type SearchPluginsDeps,
25
+ } from "./search-plugins.js";
26
+
27
+ const log = getLogger("plugin-catalog-cache");
28
+
29
+ /** How long a fetched catalog is served before a refresh is attempted. */
30
+ export const PLUGIN_CATALOG_CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutes
31
+
32
+ interface CacheEntry {
33
+ catalog: PluginCatalog;
34
+ timestamp: number;
35
+ }
36
+
37
+ const cache = new Map<string, CacheEntry>();
38
+
39
+ /**
40
+ * Resolve the full plugin catalog at {@link ref} with in-memory caching.
41
+ *
42
+ * Within the TTL window the cached catalog is returned without touching the
43
+ * network. After the TTL elapses a refresh is attempted; if it fails with a
44
+ * transient upstream error ({@link PluginCatalogUnavailableError} — rate
45
+ * limiting or a 5xx) and a cached catalog exists, the stale catalog is served
46
+ * (and its TTL window is reset so a sustained outage doesn't re-enter the
47
+ * failing fetch on every call). A hard error (e.g. the canonical plugins
48
+ * prefix returning 404, or a deleted ref) always propagates — serving stale
49
+ * there would silently mask a real misconfiguration. With no cache to fall
50
+ * back on, the underlying error propagates regardless so the caller can
51
+ * surface it (e.g. the route maps a rate-limit failure to 503).
52
+ */
53
+ export async function getPluginCatalog(
54
+ ref: string,
55
+ deps: SearchPluginsDeps,
56
+ ): Promise<PluginCatalog> {
57
+ const cached = cache.get(ref);
58
+ if (cached && Date.now() - cached.timestamp < PLUGIN_CATALOG_CACHE_TTL_MS) {
59
+ return cached.catalog;
60
+ }
61
+
62
+ try {
63
+ const catalog = await loadPluginCatalog({ ref }, deps);
64
+ cache.set(ref, { catalog, timestamp: Date.now() });
65
+ return catalog;
66
+ } catch (err) {
67
+ if (cached && err instanceof PluginCatalogUnavailableError) {
68
+ log.warn(
69
+ { err, ref },
70
+ "Failed to refresh plugin catalog, serving stale cache",
71
+ );
72
+ // Reset the TTL window so subsequent calls during the outage are served
73
+ // from cache instead of re-entering loadPluginCatalog() every time.
74
+ cached.timestamp = Date.now();
75
+ return cached.catalog;
76
+ }
77
+ throw err;
78
+ }
79
+ }
80
+
81
+ /** Invalidate the cache (for testing or forced refresh). */
82
+ export function invalidatePluginCatalogCache(): void {
83
+ cache.clear();
84
+ }
@@ -0,0 +1,416 @@
1
+ /**
2
+ * Resolve a single plugin's detail view: tracked metadata plus its README.
3
+ *
4
+ * Powers the daemon's `GET /v1/plugins/:name` route and, through it, the web
5
+ * plugin detail page. The detail view unions three sources, preferring the
6
+ * most authoritative for each field:
7
+ * 1. The locally installed copy under `<workspacePluginsDir>/<name>/`, when
8
+ * present — its `package.json` and `README.md` are read straight off disk.
9
+ * 2. The curated `experimental/plugins/marketplace.json` entry, for external
10
+ * ecosystem plugins (description / homepage / license / pinned source).
11
+ * 3. The plugin's own repository at the pinned ref (first-party
12
+ * `experimental/plugins/<name>/` or the external `owner/repo[/path]`),
13
+ * fetched via the GitHub Contents API for the README and any
14
+ * `package.json` fields the manifest doesn't carry.
15
+ *
16
+ * Name-collision precedence matches {@link ./search-plugins} and
17
+ * {@link ./install-from-github}: a marketplace entry owns its name, so the
18
+ * detail page advertises the external source the catalog and installer use. A
19
+ * same-named `experimental/plugins/<name>/` directory is that plugin's adapter
20
+ * stub, not a standalone first-party plugin, so it does not override the claim.
21
+ *
22
+ * Designed for direct programmatic use with an injected `fetch`, mirroring the
23
+ * sibling plugin libraries.
24
+ */
25
+
26
+ import { existsSync, readdirSync, readFileSync } from "node:fs";
27
+ import { join } from "node:path";
28
+
29
+ import { getWorkspacePluginsDir } from "../../util/platform.js";
30
+ import {
31
+ DEFAULT_PLUGIN_REF,
32
+ type FetchLike,
33
+ sanitizePluginName,
34
+ } from "./install-from-github.js";
35
+ import {
36
+ fetchMarketplaceEntries,
37
+ type MarketplaceEntry,
38
+ } from "./plugin-marketplace.js";
39
+ import type { PluginMatchSource } from "./search-plugins.js";
40
+
41
+ const PLUGIN_SOURCE_OWNER = "vellum-ai";
42
+ const PLUGIN_SOURCE_REPO = "vellum-assistant";
43
+ const PLUGIN_SOURCE_PATH_PREFIX = "experimental/plugins";
44
+
45
+ /** Recognised README filenames, matched case-insensitively against a listing. */
46
+ const README_RE = /^readme(\.md|\.markdown)?$/i;
47
+
48
+ /** Entry shape returned by the GitHub Contents API for a directory listing. */
49
+ interface GitHubContentEntry {
50
+ readonly name: string;
51
+ readonly path: string;
52
+ readonly type: "file" | "dir" | "symlink" | "submodule";
53
+ readonly download_url: string | null;
54
+ }
55
+
56
+ /** The subset of `package.json` fields the detail view surfaces. */
57
+ interface PluginManifestFields {
58
+ readonly version: string | null;
59
+ readonly description: string | null;
60
+ readonly homepage: string | null;
61
+ readonly license: string | null;
62
+ }
63
+
64
+ /** Options that control which plugin to resolve and at what ref. */
65
+ export interface PluginDetailsOptions {
66
+ /** Install name (kebab-case directory name). */
67
+ readonly name: string;
68
+ /** Git ref to read catalog metadata / README from. Defaults to {@link DEFAULT_PLUGIN_REF}. */
69
+ readonly ref?: string;
70
+ }
71
+
72
+ /** Dependencies injected by the caller. */
73
+ export interface PluginDetailsDeps {
74
+ /** HTTP client. Production callers pass `globalThis.fetch.bind(globalThis)`. */
75
+ readonly fetch: FetchLike;
76
+ /** Override the workspace plugins directory. Falls back to {@link getWorkspacePluginsDir}. */
77
+ readonly workspacePluginsDir?: string;
78
+ }
79
+
80
+ /** Resolved detail view for a single plugin. */
81
+ export interface PluginDetails {
82
+ /** Install name. Matches `assistant plugins install <name>`. */
83
+ readonly name: string;
84
+ /** Whether a copy is materialized under the workspace plugins directory. */
85
+ readonly installed: boolean;
86
+ /** Short description, best-effort across the three sources; `null` when unknown. */
87
+ readonly description: string | null;
88
+ /** Project homepage URL, when known; `null` otherwise. */
89
+ readonly homepage: string | null;
90
+ /** SPDX license expression, when known; `null` otherwise. */
91
+ readonly license: string | null;
92
+ /** Resolved version (installed copy first, then repo `package.json`); `null` when unknown. */
93
+ readonly version: string | null;
94
+ /** Discriminated origin, mirroring the catalog's {@link PluginMatchSource}. */
95
+ readonly source: PluginMatchSource;
96
+ /** README markdown, or `null` when the plugin ships none. */
97
+ readonly readme: string | null;
98
+ /** Git ref the catalog metadata / README were resolved at. */
99
+ readonly ref: string;
100
+ }
101
+
102
+ /** No installed copy and no catalog/source entry claims the name. */
103
+ export class PluginDetailsNotFoundError extends Error {
104
+ constructor(
105
+ readonly pluginName: string,
106
+ readonly ref: string,
107
+ ) {
108
+ super(`Plugin "${pluginName}" not found in the catalog (ref ${ref}).`);
109
+ this.name = "PluginDetailsNotFoundError";
110
+ }
111
+ }
112
+
113
+ /**
114
+ * Resolve the detail view for {@link opts.name}.
115
+ *
116
+ * Throws {@link PluginDetailsNotFoundError} when the name is neither installed
117
+ * locally nor present in the catalog (first-party directory or marketplace
118
+ * entry). Network failures while enriching from GitHub degrade to the fields
119
+ * already known from disk / the manifest rather than failing the whole view —
120
+ * a detail page that renders metadata without a README beats a hard error.
121
+ */
122
+ export async function getPluginDetails(
123
+ opts: PluginDetailsOptions,
124
+ deps: PluginDetailsDeps,
125
+ ): Promise<PluginDetails> {
126
+ const name = sanitizePluginName(opts.name);
127
+ const ref = opts.ref ?? DEFAULT_PLUGIN_REF;
128
+ const { fetch: fetchFn } = deps;
129
+
130
+ const pluginsDir = deps.workspacePluginsDir ?? getWorkspacePluginsDir();
131
+ const local = readLocalPlugin(pluginsDir, name);
132
+
133
+ const marketplaceEntry = await findMarketplaceEntry(name, ref, fetchFn);
134
+
135
+ // A marketplace entry owns its name (the same-named in-repo directory, if
136
+ // any, is its adapter stub) — so only probe the first-party directory when
137
+ // the name is unclaimed, which also spares a GitHub request in the common
138
+ // external case.
139
+ const firstPartyEntries =
140
+ marketplaceEntry === null
141
+ ? await listDirSafe(
142
+ PLUGIN_SOURCE_OWNER,
143
+ PLUGIN_SOURCE_REPO,
144
+ `${PLUGIN_SOURCE_PATH_PREFIX}/${name}`,
145
+ ref,
146
+ fetchFn,
147
+ )
148
+ : null;
149
+ const firstPartyExists = firstPartyEntries !== null;
150
+
151
+ if (!local.installed && !firstPartyExists && !marketplaceEntry) {
152
+ throw new PluginDetailsNotFoundError(name, ref);
153
+ }
154
+
155
+ const useExternal = marketplaceEntry !== null;
156
+
157
+ const source: PluginMatchSource = useExternal
158
+ ? {
159
+ kind: "github",
160
+ repo: marketplaceEntry!.source.repo,
161
+ ref: marketplaceEntry!.source.ref,
162
+ ...(marketplaceEntry!.source.path
163
+ ? { path: marketplaceEntry!.source.path }
164
+ : {}),
165
+ }
166
+ : { kind: "first-party" };
167
+
168
+ const remote = await readRemotePlugin(source, firstPartyEntries, fetchFn);
169
+
170
+ const readme = local.readme ?? remote.readme;
171
+
172
+ return {
173
+ name,
174
+ installed: local.installed,
175
+ description:
176
+ local.manifest.description ??
177
+ marketplaceEntry?.description ??
178
+ remote.manifest.description ??
179
+ null,
180
+ homepage:
181
+ local.manifest.homepage ??
182
+ marketplaceEntry?.homepage ??
183
+ remote.manifest.homepage ??
184
+ null,
185
+ license:
186
+ local.manifest.license ??
187
+ marketplaceEntry?.license ??
188
+ remote.manifest.license ??
189
+ null,
190
+ version: local.manifest.version ?? remote.manifest.version ?? null,
191
+ source,
192
+ readme,
193
+ ref,
194
+ };
195
+ }
196
+
197
+ interface LocalPlugin {
198
+ readonly installed: boolean;
199
+ readonly manifest: PluginManifestFields;
200
+ readonly readme: string | null;
201
+ }
202
+
203
+ /** Read an installed copy's `package.json` + README off disk, if present. */
204
+ function readLocalPlugin(pluginsDir: string, name: string): LocalPlugin {
205
+ const target = join(pluginsDir, name);
206
+ if (!existsSync(target)) {
207
+ return { installed: false, manifest: emptyManifest(), readme: null };
208
+ }
209
+
210
+ const pkgPath = join(target, "package.json");
211
+ let manifest = emptyManifest();
212
+ if (existsSync(pkgPath)) {
213
+ try {
214
+ manifest = parseManifest(readFileSync(pkgPath, "utf8"));
215
+ } catch {
216
+ // A malformed local manifest degrades to empty fields — the entry is
217
+ // still "installed", we just have nothing extra to surface from it.
218
+ }
219
+ }
220
+
221
+ return { installed: true, manifest, readme: readLocalReadme(target) };
222
+ }
223
+
224
+ /** Find a README file in a directory listing and read it, if any. */
225
+ function readLocalReadme(dir: string): string | null {
226
+ let names: string[];
227
+ try {
228
+ names = readdirSync(dir);
229
+ } catch {
230
+ return null;
231
+ }
232
+ const readme = names.find((n) => README_RE.test(n));
233
+ if (!readme) return null;
234
+ try {
235
+ return readFileSync(join(dir, readme), "utf8");
236
+ } catch {
237
+ return null;
238
+ }
239
+ }
240
+
241
+ interface RemotePlugin {
242
+ readonly manifest: PluginManifestFields;
243
+ readonly readme: string | null;
244
+ }
245
+
246
+ /**
247
+ * Fetch README + `package.json` from the plugin's repository.
248
+ *
249
+ * For a first-party plugin the listing is the in-repo directory the caller
250
+ * already fetched at the catalog ref (passed through to avoid a duplicate
251
+ * request). For an external plugin we list its `owner/repo[/path]` directory
252
+ * fresh at the plugin's own pinned `source.ref`.
253
+ */
254
+ async function readRemotePlugin(
255
+ source: PluginMatchSource,
256
+ firstPartyEntries: readonly GitHubContentEntry[] | null,
257
+ fetchFn: FetchLike,
258
+ ): Promise<RemotePlugin> {
259
+ let owner: string;
260
+ let repo: string;
261
+ let entries: readonly GitHubContentEntry[] | null;
262
+
263
+ if (source.kind === "github") {
264
+ [owner, repo] = source.repo.split("/", 2) as [string, string];
265
+ // Read the external repo at its pinned ref, not the assistant catalog ref —
266
+ // the detail page must describe the same artifact the installer resolves
267
+ // from `source.ref`, even when that differs from the catalog ref (`main`).
268
+ entries = await listDirSafe(
269
+ owner,
270
+ repo,
271
+ source.path ?? "",
272
+ source.ref,
273
+ fetchFn,
274
+ );
275
+ } else {
276
+ owner = PLUGIN_SOURCE_OWNER;
277
+ repo = PLUGIN_SOURCE_REPO;
278
+ entries = firstPartyEntries;
279
+ }
280
+
281
+ if (!entries) {
282
+ return { manifest: emptyManifest(), readme: null };
283
+ }
284
+
285
+ const readmeEntry = entries.find(
286
+ (e) => e.type === "file" && README_RE.test(e.name),
287
+ );
288
+ const pkgEntry = entries.find(
289
+ (e) => e.type === "file" && e.name === "package.json",
290
+ );
291
+
292
+ const [readme, manifest] = await Promise.all([
293
+ readmeEntry ? fetchRawFile(readmeEntry, fetchFn) : Promise.resolve(null),
294
+ pkgEntry
295
+ ? fetchRawFile(pkgEntry, fetchFn).then((raw) =>
296
+ raw ? safeParseManifest(raw) : emptyManifest(),
297
+ )
298
+ : Promise.resolve(emptyManifest()),
299
+ ]);
300
+
301
+ return { manifest, readme };
302
+ }
303
+
304
+ /** List a repo directory, returning `null` on 404 or any transient failure. */
305
+ async function listDirSafe(
306
+ owner: string,
307
+ repo: string,
308
+ apiPath: string,
309
+ ref: string,
310
+ fetchFn: FetchLike,
311
+ ): Promise<readonly GitHubContentEntry[] | null> {
312
+ const suffix = apiPath
313
+ ? `/${encodeURIComponent(apiPath).replaceAll("%2F", "/")}`
314
+ : "";
315
+ const url =
316
+ `https://api.github.com/repos/${owner}/${repo}/contents${suffix}` +
317
+ `?ref=${encodeURIComponent(ref)}`;
318
+
319
+ try {
320
+ const res = await githubFetch(url, "application/vnd.github+json", fetchFn);
321
+ if (!res.ok) return null;
322
+ const body = (await res.json()) as unknown;
323
+ if (!Array.isArray(body)) return null;
324
+ return body as readonly GitHubContentEntry[];
325
+ } catch {
326
+ return null;
327
+ }
328
+ }
329
+
330
+ /** Download a file entry's raw body, returning `null` on any failure. */
331
+ async function fetchRawFile(
332
+ entry: GitHubContentEntry,
333
+ fetchFn: FetchLike,
334
+ ): Promise<string | null> {
335
+ if (!entry.download_url) return null;
336
+ try {
337
+ const res = await githubFetch(
338
+ entry.download_url,
339
+ "application/vnd.github.raw",
340
+ fetchFn,
341
+ );
342
+ if (!res.ok) return null;
343
+ return await res.text();
344
+ } catch {
345
+ return null;
346
+ }
347
+ }
348
+
349
+ async function findMarketplaceEntry(
350
+ name: string,
351
+ ref: string,
352
+ fetchFn: FetchLike,
353
+ ): Promise<MarketplaceEntry | null> {
354
+ try {
355
+ const entries = await fetchMarketplaceEntries({ fetch: fetchFn }, { ref });
356
+ return entries.find((e) => e.name === name) ?? null;
357
+ } catch {
358
+ // A missing or malformed manifest degrades to "no external entry" — the
359
+ // marketplace is supplementary, never required to render a detail view.
360
+ return null;
361
+ }
362
+ }
363
+
364
+ function githubFetch(
365
+ url: string,
366
+ accept: string,
367
+ fetchFn: FetchLike,
368
+ ): Promise<Response> {
369
+ return fetchFn(url, {
370
+ headers: { Accept: accept, "User-Agent": "vellum-assistant-cli" },
371
+ });
372
+ }
373
+
374
+ function emptyManifest(): PluginManifestFields {
375
+ return { version: null, description: null, homepage: null, license: null };
376
+ }
377
+
378
+ function safeParseManifest(raw: string): PluginManifestFields {
379
+ try {
380
+ return parseManifest(raw);
381
+ } catch {
382
+ return emptyManifest();
383
+ }
384
+ }
385
+
386
+ /** Extract the surfaced fields from a `package.json` body. Throws on bad JSON. */
387
+ function parseManifest(raw: string): PluginManifestFields {
388
+ const parsed: unknown = JSON.parse(raw);
389
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
390
+ return emptyManifest();
391
+ }
392
+ const meta = parsed as Record<string, unknown>;
393
+ return {
394
+ version: typeof meta.version === "string" ? meta.version : null,
395
+ description: typeof meta.description === "string" ? meta.description : null,
396
+ homepage: typeof meta.homepage === "string" ? meta.homepage : null,
397
+ license: normalizeLicense(meta.license),
398
+ };
399
+ }
400
+
401
+ /**
402
+ * `package.json#license` is usually an SPDX string but the legacy object form
403
+ * `{ "type": "MIT" }` still appears in the wild — surface its `type`.
404
+ */
405
+ function normalizeLicense(value: unknown): string | null {
406
+ if (typeof value === "string") return value;
407
+ if (
408
+ typeof value === "object" &&
409
+ value !== null &&
410
+ "type" in value &&
411
+ typeof (value as { type: unknown }).type === "string"
412
+ ) {
413
+ return (value as { type: string }).type;
414
+ }
415
+ return null;
416
+ }