@vellumai/assistant 0.8.8 → 0.8.9-staging.1

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 (340) hide show
  1. package/bun.lock +2 -2
  2. package/examples/plugins/echo/README.md +61 -60
  3. package/examples/plugins/echo/hooks/post-tool-use.ts +18 -0
  4. package/examples/plugins/echo/hooks/stop.ts +16 -0
  5. package/examples/plugins/echo/hooks/user-prompt-submit.ts +18 -0
  6. package/examples/plugins/echo/package.json +1 -2
  7. package/examples/plugins/echo/src/emit.ts +19 -0
  8. package/node_modules/@vellumai/skill-host-contracts/src/skill-host.ts +7 -6
  9. package/openapi.yaml +236 -6
  10. package/package.json +2 -2
  11. package/src/__tests__/agent-loop-callsite-precedence.test.ts +69 -14
  12. package/src/__tests__/agent-loop-exit-reason.test.ts +185 -140
  13. package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +50 -35
  14. package/src/__tests__/agent-loop-output-hooks.test.ts +357 -0
  15. package/src/__tests__/agent-loop-override-profile.test.ts +25 -6
  16. package/src/__tests__/agent-loop-provider-error-recording.test.ts +41 -21
  17. package/src/__tests__/agent-loop-thinking.test.ts +36 -20
  18. package/src/__tests__/agent-loop.test.ts +441 -96
  19. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +14 -14
  20. package/src/__tests__/agent-wake-override-profile.test.ts +17 -21
  21. package/src/__tests__/anthropic-provider.test.ts +1 -1
  22. package/src/__tests__/app-control-flow.test.ts +1 -1
  23. package/src/__tests__/app-dir-path-guard.test.ts +1 -0
  24. package/src/__tests__/approval-cascade.test.ts +5 -4
  25. package/src/__tests__/approval-routes-http.test.ts +4 -1
  26. package/src/__tests__/background-workers-disk-pressure.test.ts +1 -1
  27. package/src/__tests__/channel-approval-routes.test.ts +1 -1
  28. package/src/__tests__/channel-approvals.test.ts +1 -1
  29. package/src/__tests__/compaction-circuit.test.ts +258 -0
  30. package/src/__tests__/compaction-direct.test.ts +132 -0
  31. package/src/__tests__/compaction-events.test.ts +5 -5
  32. package/src/__tests__/conversation-abort-tool-results.test.ts +6 -4
  33. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +7 -10
  34. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +59 -103
  35. package/src/__tests__/conversation-agent-loop-overflow.test.ts +94 -189
  36. package/src/__tests__/conversation-agent-loop.test.ts +249 -490
  37. package/src/__tests__/conversation-clean-command.test.ts +5 -2
  38. package/src/__tests__/conversation-confirmation-signals.test.ts +5 -4
  39. package/src/__tests__/conversation-crud-inference-profile.test.ts +7 -9
  40. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +4 -4
  41. package/src/__tests__/conversation-process-callsite.test.ts +14 -14
  42. package/src/__tests__/conversation-provider-retry-repair.test.ts +18 -16
  43. package/src/__tests__/conversation-queue.test.ts +9 -9
  44. package/src/__tests__/conversation-runtime-assembly.test.ts +923 -231
  45. package/src/__tests__/conversation-runtime-workspace.test.ts +115 -20
  46. package/src/__tests__/conversation-slash-queue.test.ts +6 -4
  47. package/src/__tests__/conversation-slash-unknown.test.ts +6 -4
  48. package/src/__tests__/conversation-speed-override.test.ts +10 -9
  49. package/src/__tests__/conversation-starter-routes.test.ts +14 -6
  50. package/src/__tests__/conversation-workspace-cache-state.test.ts +23 -20
  51. package/src/__tests__/conversation-workspace-injection.test.ts +68 -6
  52. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +14 -11
  53. package/src/__tests__/conversations-import-system-filter.test.ts +101 -0
  54. package/src/__tests__/credential-security-invariants.test.ts +0 -1
  55. package/src/__tests__/db-acp-history.test.ts +101 -0
  56. package/src/__tests__/dynamic-page-surface.test.ts +31 -0
  57. package/src/__tests__/empty-response-hook.test.ts +1 -1
  58. package/src/__tests__/file-write-tool.test.ts +63 -0
  59. package/src/__tests__/gateway-only-guard.test.ts +12 -2
  60. package/src/__tests__/guardian-grant-minting.test.ts +1 -1
  61. package/src/__tests__/guardian-routing-invariants.test.ts +2 -4
  62. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +1 -1
  63. package/src/__tests__/heartbeat-disk-pressure.test.ts +1 -0
  64. package/src/__tests__/heartbeat-service.test.ts +1 -0
  65. package/src/__tests__/history-repair-hook.test.ts +1 -1
  66. package/src/__tests__/host-app-control-routes.test.ts +1 -1
  67. package/src/__tests__/host-cu-routes-targeted.test.ts +3 -3
  68. package/src/__tests__/inference-profile-reaper.test.ts +62 -0
  69. package/src/__tests__/inference-profile-session-handler.test.ts +86 -0
  70. package/src/__tests__/injector-background-turn.test.ts +13 -23
  71. package/src/__tests__/injector-chain.test.ts +268 -44
  72. package/src/__tests__/injector-disk-pressure.test.ts +210 -52
  73. package/src/__tests__/injector-document-comments.test.ts +97 -114
  74. package/src/__tests__/injector-pkb-v2-silenced.test.ts +2 -2
  75. package/src/__tests__/injector-v3-suppression.test.ts +4 -4
  76. package/src/__tests__/list-messages-client-message-id.test.ts +91 -0
  77. package/src/__tests__/list-messages-hidden-metadata.test.ts +38 -0
  78. package/src/__tests__/memory-retrieval-hook.test.ts +73 -6
  79. package/src/__tests__/memory-v2-static-injector.test.ts +86 -8
  80. package/src/__tests__/parallel-tool.benchmark.test.ts +35 -8
  81. package/src/__tests__/plugin-api-shim.test.ts +6 -9
  82. package/src/__tests__/plugin-bootstrap.test.ts +12 -23
  83. package/src/__tests__/plugin-registry.test.ts +3 -49
  84. package/src/__tests__/plugin-types.test.ts +0 -70
  85. package/src/__tests__/reaction-persistence.test.ts +1 -1
  86. package/src/__tests__/send-endpoint-busy.test.ts +4 -1
  87. package/src/__tests__/skill-feature-flags-integration.test.ts +33 -0
  88. package/src/__tests__/steer-tool-repair.test.ts +1 -1
  89. package/src/__tests__/subagent-call-site-routing.test.ts +1 -1
  90. package/src/__tests__/subagent-detail.test.ts +25 -7
  91. package/src/__tests__/subagent-fork-notifications.test.ts +1 -3
  92. package/src/__tests__/subagent-fork-spawn.test.ts +1 -1
  93. package/src/__tests__/subagent-manager-notify.test.ts +1 -3
  94. package/src/__tests__/subagent-notify-parent.test.ts +1 -3
  95. package/src/__tests__/subagent-spawn-tool-fork.test.ts +1 -1
  96. package/src/__tests__/title-generate-hook.test.ts +1 -1
  97. package/src/__tests__/tool-error-hook.test.ts +1 -1
  98. package/src/__tests__/tool-result-truncate-hook.test.ts +1 -1
  99. package/src/__tests__/user-plugin-loader.test.ts +54 -286
  100. package/src/acp/__tests__/agent-process.test.ts +161 -0
  101. package/src/acp/__tests__/client-handler.test.ts +40 -0
  102. package/src/acp/__tests__/helpers/acp-history-db.ts +82 -0
  103. package/src/acp/__tests__/helpers/exec-file-stub.ts +101 -0
  104. package/src/acp/__tests__/prepare-agent-env.test.ts +137 -0
  105. package/src/acp/__tests__/session-manager-persistence.test.ts +95 -28
  106. package/src/acp/__tests__/session-manager-resume.test.ts +736 -0
  107. package/src/acp/agent-process.ts +61 -1
  108. package/src/acp/auto-install.test.ts +196 -0
  109. package/src/acp/auto-install.ts +177 -0
  110. package/src/acp/client-handler.ts +31 -0
  111. package/src/acp/feature-gate.test.ts +48 -0
  112. package/src/acp/feature-gate.ts +34 -0
  113. package/src/acp/prepare-agent-env.ts +83 -29
  114. package/src/acp/resolve-agent.test.ts +320 -7
  115. package/src/acp/resolve-agent.ts +182 -18
  116. package/src/acp/resume-hint.ts +25 -0
  117. package/src/acp/session-manager.ts +495 -73
  118. package/src/acp/types.ts +8 -0
  119. package/src/agent/compaction-circuit.ts +60 -102
  120. package/src/agent/loop.ts +390 -240
  121. package/src/api/responses/conversation-message.ts +14 -1
  122. package/src/approvals/guardian-request-resolvers.ts +1 -1
  123. package/src/background-wake/next-wake.ts +1 -0
  124. package/src/cli/commands/db/__tests__/repair.test.ts +3 -1
  125. package/src/cli/commands/plugins.ts +43 -37
  126. package/src/cli/lib/__tests__/install-from-github.test.ts +429 -111
  127. package/src/cli/lib/__tests__/plugin-catalog-cache.test.ts +196 -0
  128. package/src/cli/lib/__tests__/plugin-details.test.ts +372 -0
  129. package/src/cli/lib/__tests__/plugin-marketplace.test.ts +220 -0
  130. package/src/cli/lib/__tests__/search-plugins.test.ts +226 -32
  131. package/src/cli/lib/install-from-github.ts +464 -55
  132. package/src/cli/lib/plugin-catalog-cache.ts +84 -0
  133. package/src/cli/lib/plugin-details.ts +409 -0
  134. package/src/cli/lib/plugin-marketplace.ts +197 -0
  135. package/src/cli/lib/search-plugins.ts +195 -29
  136. package/src/config/__tests__/feature-flag-registry-guard.test.ts +2 -2
  137. package/src/config/acp-defaults.test.ts +10 -0
  138. package/src/config/acp-defaults.ts +6 -0
  139. package/src/config/bundled-skills/acp/SKILL.md +83 -31
  140. package/src/config/bundled-skills/acp/TOOLS.json +4 -4
  141. package/src/config/bundled-skills/app-builder/SKILL.md +224 -381
  142. package/src/config/bundled-skills/app-builder/TOOLS.json +29 -0
  143. package/src/config/bundled-skills/app-builder/references/DESIGN_SYSTEM.md +48 -0
  144. package/src/config/bundled-skills/app-builder/references/RESPONSIVE.md +57 -0
  145. package/src/config/bundled-skills/app-builder/references/SLIDES.md +38 -0
  146. package/src/config/bundled-skills/app-builder/tools/app-list.ts +62 -0
  147. package/src/config/bundled-skills/document-editor/SKILL.md +28 -23
  148. package/src/config/bundled-skills/document-editor/TOOLS.json +1 -1
  149. package/src/config/bundled-tool-registry.ts +2 -0
  150. package/src/config/feature-flag-registry.json +14 -5
  151. package/src/config/schemas/heartbeat.ts +9 -0
  152. package/src/context/strip-injections.ts +8 -2
  153. package/src/context/window-manager.ts +27 -13
  154. package/src/daemon/conversation-agent-loop-handlers.ts +10 -35
  155. package/src/daemon/conversation-agent-loop.ts +167 -1005
  156. package/src/daemon/conversation-lifecycle.ts +11 -255
  157. package/src/daemon/conversation-process.ts +8 -136
  158. package/src/daemon/conversation-registry.ts +159 -0
  159. package/src/daemon/conversation-runtime-assembly.ts +293 -392
  160. package/src/daemon/conversation-store.ts +9 -90
  161. package/src/daemon/conversation-surfaces.ts +24 -8
  162. package/src/daemon/conversation-workspace.ts +17 -0
  163. package/src/daemon/conversation.ts +383 -56
  164. package/src/daemon/external-plugins-bootstrap.ts +14 -19
  165. package/src/daemon/handlers/conversations.ts +3 -1
  166. package/src/daemon/handlers/skills.ts +4 -1
  167. package/src/daemon/host-proxy-preactivation.ts +1 -3
  168. package/src/daemon/lifecycle.ts +21 -0
  169. package/src/daemon/server.ts +2 -0
  170. package/src/daemon/wake-conversation-ops.ts +269 -0
  171. package/src/embedded/plugin-api.ts +2 -2
  172. package/src/export/__tests__/transcript-formatter.test.ts +5 -0
  173. package/src/heartbeat/__tests__/heartbeat-service.test.ts +3 -0
  174. package/src/heartbeat/heartbeat-run-store.ts +23 -1
  175. package/src/heartbeat/heartbeat-service.ts +26 -0
  176. package/src/ipc/__tests__/browser-ipc.test.ts +1 -1
  177. package/src/ipc/__tests__/ui-request-route.test.ts +3 -3
  178. package/src/ipc/skill-routes/__tests__/memory.test.ts +15 -0
  179. package/src/ipc/skill-routes/memory.ts +4 -2
  180. package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +87 -0
  181. package/src/memory/conversation-crud.ts +29 -19
  182. package/src/memory/conversation-starter-checkpoints.ts +1 -0
  183. package/src/memory/db-init.ts +2 -0
  184. package/src/memory/job-handlers/conversation-starters.ts +13 -2
  185. package/src/memory/jobs/__tests__/embed-concept-page.test.ts +5 -4
  186. package/src/memory/jobs-worker.ts +25 -1
  187. package/src/memory/migrations/272-acp-session-history-cwd.ts +36 -0
  188. package/src/memory/migrations/index.ts +1 -0
  189. package/src/memory/schema/acp.ts +4 -0
  190. package/src/memory/v2/__tests__/consolidation-job.test.ts +3 -3
  191. package/src/memory/v2/consolidation-job.ts +13 -4
  192. package/src/plugin-api/constants.ts +4 -0
  193. package/src/plugin-api/index.ts +6 -5
  194. package/src/plugin-api/types.ts +75 -0
  195. package/src/plugins/defaults/compaction/compact.ts +59 -0
  196. package/src/plugins/defaults/compaction/manager-store.ts +44 -0
  197. package/src/plugins/defaults/compaction/package.json +1 -2
  198. package/src/plugins/defaults/empty-response/package.json +0 -1
  199. package/src/plugins/defaults/history-repair/package.json +0 -1
  200. package/src/plugins/defaults/index.ts +135 -26
  201. package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +103 -38
  202. package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit-temp.ts +167 -56
  203. package/src/plugins/defaults/memory-retrieval/injector-chain.ts +2 -2
  204. package/src/plugins/defaults/{injectors/register.ts → memory-retrieval/injectors.ts} +148 -73
  205. package/src/plugins/defaults/memory-retrieval/unified-turn-context.ts +223 -0
  206. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/assign.test.ts +4 -4
  207. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/live-integration.test.ts +9 -6
  208. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/maintain-job.test.ts +5 -5
  209. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/orchestrate.test.ts +8 -5
  210. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/reconcile.test.ts +2 -2
  211. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/render-injection.test.ts +1 -1
  212. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/router.test.ts +10 -5
  213. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/selection-log-store.test.ts +8 -8
  214. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/selector.test.ts +5 -5
  215. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/shadow-plugin.test.ts +16 -17
  216. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/types.test.ts +2 -2
  217. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/assign.ts +9 -5
  218. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/capabilities.ts +5 -2
  219. package/src/plugins/defaults/memory-v3-shadow/hooks/post-compact.ts +14 -0
  220. package/src/plugins/defaults/memory-v3-shadow/hooks/user-prompt-submit.ts +19 -0
  221. package/src/plugins/defaults/memory-v3-shadow/injector.ts +75 -0
  222. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/maintain-job.ts +15 -8
  223. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/orchestrate.ts +2 -2
  224. package/src/plugins/defaults/memory-v3-shadow/package.json +14 -0
  225. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/page-content.ts +2 -2
  226. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/provider-blocks.ts +1 -1
  227. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/reconcile.ts +7 -3
  228. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/render-injection.ts +1 -1
  229. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/router.ts +5 -5
  230. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/selection-log-store.ts +4 -4
  231. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/selector.ts +7 -7
  232. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/shadow-plugin.ts +32 -94
  233. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/tree.ts +1 -1
  234. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/types.ts +1 -1
  235. package/src/plugins/defaults/title-generate/package.json +0 -1
  236. package/src/plugins/defaults/tool-error/package.json +0 -1
  237. package/src/plugins/defaults/tool-result-truncate/package.json +0 -1
  238. package/src/plugins/pipeline.ts +6 -293
  239. package/src/plugins/registry.ts +9 -37
  240. package/src/plugins/types.ts +76 -381
  241. package/src/plugins/user-loader.ts +30 -127
  242. package/src/proactive-artifact/aux-message-injector.ts +1 -1
  243. package/src/proactive-artifact/job.test.ts +1 -1
  244. package/src/prompts/__tests__/system-prompt.test.ts +6 -0
  245. package/src/prompts/templates/BOOTSTRAP-ACTIVATION-RAIL.md +35 -3
  246. package/src/runtime/__tests__/agent-wake.test.ts +555 -691
  247. package/src/runtime/__tests__/interactive-ui.test.ts +1 -1
  248. package/src/runtime/agent-wake.ts +108 -209
  249. package/src/runtime/assistant-event-hub.ts +1 -1
  250. package/src/runtime/channel-approvals.ts +1 -1
  251. package/src/runtime/interactive-ui.ts +1 -1
  252. package/src/runtime/routes/__tests__/acp-routes.test.ts +283 -55
  253. package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +1 -1
  254. package/src/runtime/routes/__tests__/plugins-routes.test.ts +398 -73
  255. package/src/runtime/routes/__tests__/surface-action-routes.test.ts +5 -4
  256. package/src/runtime/routes/__tests__/surface-content-routes.test.ts +4 -1
  257. package/src/runtime/routes/acp-routes.test.ts +89 -25
  258. package/src/runtime/routes/acp-routes.ts +81 -29
  259. package/src/runtime/routes/approval-routes.ts +1 -1
  260. package/src/runtime/routes/browser-routes.ts +1 -1
  261. package/src/runtime/routes/browser-tabs-routes.ts +6 -10
  262. package/src/runtime/routes/conversation-cli-routes.ts +1 -1
  263. package/src/runtime/routes/conversation-list-routes.ts +1 -1
  264. package/src/runtime/routes/conversation-query-routes.ts +1 -1
  265. package/src/runtime/routes/conversation-routes.ts +28 -2
  266. package/src/runtime/routes/conversation-starter-routes.ts +13 -7
  267. package/src/runtime/routes/conversations-import-routes.ts +24 -7
  268. package/src/runtime/routes/host-app-control-routes.ts +1 -1
  269. package/src/runtime/routes/host-cu-routes.ts +1 -1
  270. package/src/runtime/routes/identity-routes.ts +18 -3
  271. package/src/runtime/routes/inbound-message-handler.ts +1 -1
  272. package/src/runtime/routes/inference-profile-session-handler.ts +11 -0
  273. package/src/runtime/routes/inference-profile-session-reaper.ts +6 -0
  274. package/src/runtime/routes/memory-v3-routes.ts +16 -6
  275. package/src/runtime/routes/playground/helpers.ts +1 -1
  276. package/src/runtime/routes/plugins-routes.ts +336 -35
  277. package/src/runtime/routes/surface-conversation-resolver.ts +4 -3
  278. package/src/runtime/routes/work-items-routes.ts +2 -4
  279. package/src/runtime/services/conversation-serializer.ts +1 -1
  280. package/src/signals/cancel.ts +2 -4
  281. package/src/subagent/manager.ts +21 -5
  282. package/src/tools/acp/context.ts +20 -0
  283. package/src/tools/acp/list-agents.test.ts +7 -1
  284. package/src/tools/acp/spawn.test.ts +158 -55
  285. package/src/tools/acp/spawn.ts +47 -72
  286. package/src/tools/acp/steer.test.ts +105 -8
  287. package/src/tools/acp/steer.ts +48 -17
  288. package/src/tools/apps/executors.ts +13 -8
  289. package/src/tools/filesystem/write.ts +34 -0
  290. package/src/tools/subagent/spawn.ts +2 -4
  291. package/src/tools/ui-surface/definitions.ts +25 -5
  292. package/src/workspace/migrations/051-seed-conversation-summarization-callsite.ts +4 -5
  293. package/src/workspace/migrations/097-enable-adaptive-thinking-managed-profiles.ts +69 -45
  294. package/docs/plugins.md +0 -832
  295. package/examples/plugins/echo/register.ts +0 -143
  296. package/src/__tests__/circuit-breaker-pipeline.test.ts +0 -405
  297. package/src/__tests__/compaction-pipeline.test.ts +0 -210
  298. package/src/__tests__/compaction-timeout-recovery.test.ts +0 -251
  299. package/src/__tests__/overflow-reduce-pipeline.test.ts +0 -667
  300. package/src/__tests__/pipeline-runner.test.ts +0 -554
  301. package/src/__tests__/plugin-external-api.test.ts +0 -68
  302. package/src/daemon/wake-target-adapter.ts +0 -253
  303. package/src/plugins/defaults/circuit-breaker/middlewares/circuitBreaker.ts +0 -93
  304. package/src/plugins/defaults/circuit-breaker/package.json +0 -15
  305. package/src/plugins/defaults/circuit-breaker/register.ts +0 -39
  306. package/src/plugins/defaults/compaction/middlewares/compaction.ts +0 -25
  307. package/src/plugins/defaults/compaction/register.ts +0 -35
  308. package/src/plugins/defaults/compaction/terminal.ts +0 -73
  309. package/src/plugins/defaults/empty-response/register.ts +0 -23
  310. package/src/plugins/defaults/history-repair/register.ts +0 -24
  311. package/src/plugins/defaults/overflow-reduce/middlewares/overflowReduce.ts +0 -126
  312. package/src/plugins/defaults/overflow-reduce/package.json +0 -15
  313. package/src/plugins/defaults/overflow-reduce/register.ts +0 -42
  314. package/src/plugins/defaults/title-generate/register.ts +0 -35
  315. package/src/plugins/defaults/tool-error/register.ts +0 -23
  316. package/src/plugins/defaults/tool-result-truncate/register.ts +0 -24
  317. package/src/plugins/external-api.ts +0 -104
  318. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/capabilities.test.ts +0 -0
  319. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/core.test.ts +0 -0
  320. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/fixtures/eval-turns.json +0 -0
  321. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/fixtures/live-turns.json +0 -0
  322. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/health.test.ts +0 -0
  323. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/needle.test.ts +0 -0
  324. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/provider-blocks.test.ts +0 -0
  325. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/snapshot.test.ts +0 -0
  326. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/tree.test.ts +0 -0
  327. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/working-set-eviction.test.ts +0 -0
  328. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/working-set-skeleton.test.ts +0 -0
  329. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/core.ts +0 -0
  330. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/README.md +0 -0
  331. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/assignments.json +0 -0
  332. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/core.json +0 -0
  333. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-a/topic-x.md +0 -0
  334. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-a/topic-y.md +0 -0
  335. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-b/topic-z.md +0 -0
  336. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/health.ts +0 -0
  337. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/llm-retry.ts +0 -0
  338. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/needle.ts +0 -0
  339. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/snapshot.ts +0 -0
  340. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/working-set.ts +0 -0
@@ -33,6 +33,7 @@ import { NotFoundError } from "./errors.js";
33
33
  import {
34
34
  getCachedIntro,
35
35
  readWorkspaceGreetings,
36
+ readWorkspaceIdentityIntro,
36
37
  setCachedIntro,
37
38
  } from "./identity-intro-cache.js";
38
39
  import type { RouteDefinition } from "./types.js";
@@ -455,16 +456,30 @@ function getIdentityIntro(): IdentityIntroResponse {
455
456
  return identityIntroResponse(FALLBACK_GREETINGS, "fallback");
456
457
  }
457
458
 
458
- // 2. Cached LLM-generated greetings
459
+ // 2. Cached LLM-generated greetings (populated by background refresh)
459
460
  const cached = getCachedIntro();
460
461
  if (cached) {
461
462
  return identityIntroResponse(cached.greetings, "cache");
462
463
  }
463
464
 
464
- // 3. Trigger fresh generation without blocking the empty-state UI.
465
+ // 3. Identity intro tagline from `## Identity Intro` in IDENTITY.md
466
+ // (written during onboarding by BOOTSTRAP.md instructions)
467
+ const identityIntro = readWorkspaceIdentityIntro();
468
+ if (identityIntro) {
469
+ // Still trigger background generation so the next request gets
470
+ // LLM-generated greetings instead of the static tagline.
471
+ const refreshing = triggerEmptyStateGreetingGeneration();
472
+ return identityIntroResponse(
473
+ [identityIntro, ...FALLBACK_GREETINGS],
474
+ "workspace",
475
+ refreshing,
476
+ );
477
+ }
478
+
479
+ // 4. Trigger fresh generation without blocking the empty-state UI.
465
480
  const refreshing = triggerEmptyStateGreetingGeneration();
466
481
 
467
- // 4. Generic fallback only when generation is unavailable.
482
+ // 5. Generic fallback only when generation is unavailable.
468
483
  return identityIntroResponse(FALLBACK_GREETINGS, "fallback", refreshing);
469
484
  }
470
485
 
@@ -20,7 +20,7 @@ import {
20
20
  createApprovalConversationGenerator,
21
21
  createApprovalCopyGenerator,
22
22
  } from "../../daemon/approval-generators.js";
23
- import { findConversation } from "../../daemon/conversation-store.js";
23
+ import { findConversation } from "../../daemon/conversation-registry.js";
24
24
  import {
25
25
  canonicalizeTimeZone,
26
26
  resolveTurnTimezoneContext,
@@ -15,6 +15,7 @@
15
15
  import { randomUUID } from "node:crypto";
16
16
 
17
17
  import { loadConfig } from "../../config/loader.js";
18
+ import { findConversation } from "../../daemon/conversation-registry.js";
18
19
  import {
19
20
  getConversation,
20
21
  listActiveInferenceProfileSessions,
@@ -118,6 +119,11 @@ export async function setInferenceProfileSession({
118
119
  };
119
120
  }
120
121
  setConversationInferenceProfileSession(resolvedId, null, null, null);
122
+ findConversation(resolvedId)?.applyInferenceProfileState({
123
+ profile: null,
124
+ sessionId: null,
125
+ expiresAt: null,
126
+ });
121
127
  publishConversationInferenceProfileChanged(
122
128
  {
123
129
  conversationId: resolvedId,
@@ -188,6 +194,11 @@ export async function setInferenceProfileSession({
188
194
  newSessionId ?? null,
189
195
  newExpiresAt ?? null,
190
196
  );
197
+ findConversation(resolvedId)?.applyInferenceProfileState({
198
+ profile,
199
+ sessionId: newSessionId ?? null,
200
+ expiresAt: newExpiresAt ?? null,
201
+ });
191
202
 
192
203
  publishConversationInferenceProfileChanged(
193
204
  {
@@ -11,6 +11,7 @@
11
11
  * background sweeps in `http-server.ts`.
12
12
  */
13
13
 
14
+ import { findConversation } from "../../daemon/conversation-registry.js";
14
15
  import { clearExpiredInferenceProfiles } from "../../memory/conversation-crud.js";
15
16
  import { getLogger } from "../../util/logger.js";
16
17
  import { publishConversationInferenceProfileChanged } from "../sync/resource-sync-events.js";
@@ -37,6 +38,11 @@ export function tickInferenceProfileReaper(): void {
37
38
  const now = Date.now();
38
39
  const cleared = clearExpiredInferenceProfiles(now);
39
40
  for (const { conversationId } of cleared) {
41
+ findConversation(conversationId)?.applyInferenceProfileState({
42
+ profile: null,
43
+ sessionId: null,
44
+ expiresAt: null,
45
+ });
40
46
  publishConversationInferenceProfileChanged({
41
47
  conversationId,
42
48
  profile: null,
@@ -21,16 +21,26 @@ import { join } from "node:path";
21
21
  import { z } from "zod";
22
22
 
23
23
  import { getPageIndex } from "../../memory/v2/page-index.js";
24
- import { loadCore } from "../../memory/v3/core.js";
25
- import { computeV3Health, renderV3Health } from "../../memory/v3/health.js";
26
- import { type LeafRef, reconcileTree } from "../../memory/v3/reconcile.js";
27
- import { invalidateLanes } from "../../memory/v3/shadow-plugin.js";
24
+ import { loadCore } from "../../plugins/defaults/memory-v3-shadow/core.js";
25
+ import {
26
+ computeV3Health,
27
+ renderV3Health,
28
+ } from "../../plugins/defaults/memory-v3-shadow/health.js";
29
+ import {
30
+ type LeafRef,
31
+ reconcileTree,
32
+ } from "../../plugins/defaults/memory-v3-shadow/reconcile.js";
33
+ import { invalidateLanes } from "../../plugins/defaults/memory-v3-shadow/shadow-plugin.js";
28
34
  import {
29
35
  coreSlugs,
30
36
  loadLeafTree,
31
37
  resolveDataDir,
32
- } from "../../memory/v3/tree.js";
33
- import type { LeafPath, LeafTree, Slug } from "../../memory/v3/types.js";
38
+ } from "../../plugins/defaults/memory-v3-shadow/tree.js";
39
+ import type {
40
+ LeafPath,
41
+ LeafTree,
42
+ Slug,
43
+ } from "../../plugins/defaults/memory-v3-shadow/types.js";
34
44
  import { getLogger } from "../../util/logger.js";
35
45
  import { getWorkspaceDir } from "../../util/platform.js";
36
46
  import { ACTOR_PRINCIPALS, type RoutePolicy } from "../auth/route-policy.js";
@@ -7,9 +7,9 @@
7
7
  */
8
8
 
9
9
  import type { Conversation } from "../../../daemon/conversation.js";
10
+ import { findConversation } from "../../../daemon/conversation-registry.js";
10
11
  import {
11
12
  destroyActiveConversation,
12
- findConversation,
13
13
  getOrCreateConversation,
14
14
  } from "../../../daemon/conversation-store.js";
15
15
  import {
@@ -1,51 +1,66 @@
1
1
  /**
2
2
  * Route handlers for the assistant plugins surface.
3
3
  *
4
- * GET /v1/plugins — list installed plugins under `<workspaceDir>/plugins/`.
5
- * GET /v1/plugins/search — search the canonical GitHub catalog of installable plugins.
6
- * DELETE /v1/plugins/:name — uninstall a plugin from `<workspaceDir>/plugins/<name>/`.
4
+ * GET /v1/plugins — list installed plugins under `<workspaceDir>/plugins/`.
5
+ * GET /v1/plugins/search — search the canonical GitHub catalog of installable plugins.
6
+ * GET /v1/plugins/:name — resolve a single plugin's detail view (metadata + README).
7
+ * POST /v1/plugins/install — install a plugin by name from the canonical source.
8
+ * DELETE /v1/plugins/:name — uninstall a plugin from `<workspaceDir>/plugins/<name>/`.
7
9
  *
8
10
  * The read-only routes are projections over the same library functions
9
11
  * the CLI uses (`assistant plugins list`, `assistant plugins search`).
10
- * The DELETE route is symmetric to `assistant plugins uninstall` and
11
- * delegates to the same `uninstallPlugin` lib function. CLI / daemon /
12
- * web stay aligned on what an installed or available plugin looks like.
13
- *
14
- * Install is intentionally not exposed here. The CLI remains the
15
- * install surface because installation fetches from GitHub, applies
16
- * sanitization, and writes to disk — a heavier flow than a single
17
- * JSON request justifies right now.
12
+ * The install / uninstall routes are symmetric to `assistant plugins
13
+ * install` / `uninstall` and delegate to the same `installPlugin` /
14
+ * `uninstallPlugin` lib functions. CLI / daemon / web stay aligned on
15
+ * what an installed or available plugin looks like — mirroring the
16
+ * skills surface, which already exposes detail + install over HTTP.
18
17
  *
19
18
  * # Policy gating
20
19
  *
21
- * Every route declares `policyKey: "plugins"` + `requirePolicyEnforcement:
22
- * true`. The HTTP router enforces via `enforcePolicy()` against the
23
- * `plugins:GET` / `plugins/search:GET` / `plugins:DELETE` registry
24
- * entries in `runtime/auth/route-policy.ts`. The same registry is the
25
- * source of truth for the IPC path too: the IPC route adapter resolves
26
- * each route's policy and ships it in `get_route_schema`, which the
27
- * gateway's IPC proxy reads from its in-memory cache. Reads require
28
- * `settings.read`; uninstall requires `settings.write`.
20
+ * Reads require `settings.read`; install and uninstall require
21
+ * `settings.write`. The HTTP router enforces the per-route `policy`
22
+ * block below, and the IPC route adapter ships the same policy in
23
+ * `get_route_schema` so the gateway's IPC proxy stays in sync.
29
24
  */
30
25
 
31
26
  import { z } from "zod";
32
27
 
33
- import { InvalidPluginNameError } from "../../cli/lib/install-from-github.js";
28
+ import {
29
+ DEFAULT_PLUGIN_REF,
30
+ installPlugin,
31
+ InvalidPluginNameError,
32
+ PluginAlreadyInstalledError,
33
+ PluginNotFoundError,
34
+ PluginSourceUnavailableError,
35
+ } from "../../cli/lib/install-from-github.js";
34
36
  import {
35
37
  type InstalledPluginInfo,
36
38
  listInstalledPlugins,
37
39
  } from "../../cli/lib/list-installed-plugins.js";
40
+ import { getPluginCatalog } from "../../cli/lib/plugin-catalog-cache.js";
41
+ import {
42
+ getPluginDetails,
43
+ PluginDetailsNotFoundError,
44
+ } from "../../cli/lib/plugin-details.js";
38
45
  import {
46
+ assertValidSearchPattern,
47
+ filterPluginCatalog,
39
48
  InvalidSearchPatternError,
49
+ PluginCatalogUnavailableError,
40
50
  type PluginSearchMatch,
41
- searchPlugins,
42
51
  } from "../../cli/lib/search-plugins.js";
43
52
  import {
44
53
  PluginNotInstalledError,
45
54
  uninstallPlugin,
46
55
  } from "../../cli/lib/uninstall-plugin.js";
47
56
  import { ACTOR_PRINCIPALS } from "../auth/route-policy.js";
48
- import { BadRequestError, InternalError, NotFoundError } from "./errors.js";
57
+ import {
58
+ BadRequestError,
59
+ ConflictError,
60
+ InternalError,
61
+ NotFoundError,
62
+ ServiceUnavailableError,
63
+ } from "./errors.js";
49
64
  import type { RouteDefinition, RouteHandlerArgs } from "./types.js";
50
65
 
51
66
  // ---------------------------------------------------------------------------
@@ -83,17 +98,43 @@ const pluginsListResponseSchema = z.object({
83
98
  plugins: z.array(pluginInfoSchema),
84
99
  });
85
100
 
101
+ const pluginMatchSourceSchema = z
102
+ .discriminatedUnion("kind", [
103
+ z.object({
104
+ kind: z.literal("first-party"),
105
+ }),
106
+ z.object({
107
+ kind: z.literal("github"),
108
+ repo: z
109
+ .string()
110
+ .describe("`owner/repo` of the external plugin repository."),
111
+ path: z
112
+ .string()
113
+ .optional()
114
+ .describe(
115
+ "Directory within the repo, when the plugin is not at the root.",
116
+ ),
117
+ ref: z.string().describe("Pinned git ref the plugin is fetched from."),
118
+ }),
119
+ ])
120
+ .describe(
121
+ "Origin of the match: a first-party in-repo plugin or a whitelisted external one.",
122
+ );
123
+
86
124
  const pluginSearchMatchSchema = z.object({
87
125
  name: z
88
126
  .string()
89
- .describe(
90
- "Directory name under `experimental/plugins/`. Matches `assistant plugins install <name>`.",
91
- ),
127
+ .describe("Install name. Matches `assistant plugins install <name>`."),
92
128
  path: z
93
129
  .string()
94
130
  .describe(
95
- "Repo-relative path of the match (e.g. `experimental/plugins/<name>`).",
131
+ "Human-readable origin: a repo-relative path (`experimental/plugins/<name>`) for first-party plugins or a `github:owner/repo@ref` locator for external ones.",
96
132
  ),
133
+ description: z
134
+ .string()
135
+ .optional()
136
+ .describe("Short description, when known (external entries only today)."),
137
+ source: pluginMatchSourceSchema,
97
138
  });
98
139
 
99
140
  const pluginsSearchResponseSchema = z.object({
@@ -119,6 +160,72 @@ const pluginUninstallResponseSchema = z.object({
119
160
  ),
120
161
  });
121
162
 
163
+ const pluginDetailsResponseSchema = z.object({
164
+ name: z
165
+ .string()
166
+ .describe("Install name. Matches `assistant plugins install <name>`."),
167
+ installed: z
168
+ .boolean()
169
+ .describe(
170
+ "Whether a copy is materialized under `<workspaceDir>/plugins/<name>/`.",
171
+ ),
172
+ description: z
173
+ .string()
174
+ .nullable()
175
+ .describe(
176
+ "Short description, best-effort across disk, manifest, and repo.",
177
+ ),
178
+ homepage: z.string().nullable().describe("Project homepage URL, when known."),
179
+ license: z
180
+ .string()
181
+ .nullable()
182
+ .describe("SPDX license expression, when known."),
183
+ version: z
184
+ .string()
185
+ .nullable()
186
+ .describe(
187
+ "Resolved version (installed copy first, then repo `package.json`).",
188
+ ),
189
+ source: pluginMatchSourceSchema,
190
+ readme: z
191
+ .string()
192
+ .nullable()
193
+ .describe("README markdown, or null when the plugin ships none."),
194
+ ref: z
195
+ .string()
196
+ .describe("Git ref the catalog metadata / README were resolved at."),
197
+ });
198
+
199
+ const pluginInstallRequestSchema = z.object({
200
+ name: z
201
+ .string()
202
+ .describe(
203
+ "Install name to resolve against the catalog (first-party directory or marketplace entry).",
204
+ ),
205
+ ref: z
206
+ .string()
207
+ .optional()
208
+ .describe(
209
+ "Optional git ref to install from. Defaults to the CLI's `DEFAULT_PLUGIN_REF`.",
210
+ ),
211
+ force: z
212
+ .boolean()
213
+ .optional()
214
+ .describe("Overwrite an existing install in place. Defaults to false."),
215
+ });
216
+
217
+ const pluginInstallResponseSchema = z.object({
218
+ ok: z.literal(true),
219
+ name: z.string().describe("Install name that was materialized."),
220
+ target: z
221
+ .string()
222
+ .describe("Absolute path the plugin was materialized into on the host."),
223
+ fileCount: z
224
+ .number()
225
+ .describe("Number of files written for the installed plugin."),
226
+ ref: z.string().describe("Git ref the plugin was fetched from."),
227
+ });
228
+
122
229
  // ---------------------------------------------------------------------------
123
230
  // Helpers
124
231
  // ---------------------------------------------------------------------------
@@ -149,6 +256,41 @@ function projectPlugin(entry: InstalledPluginInfo): PluginView {
149
256
  return view;
150
257
  }
151
258
 
259
+ /** Wire shape for a catalog match. Mirrors {@link pluginSearchMatchSchema}. */
260
+ interface PluginMatchView {
261
+ name: string;
262
+ path: string;
263
+ description?: string;
264
+ source:
265
+ | { kind: "first-party" }
266
+ | { kind: "github"; repo: string; path?: string; ref: string };
267
+ }
268
+
269
+ /**
270
+ * Re-pack a `readonly` lib match into a mutable wire object so the route
271
+ * serializer's `Record<string, unknown>` contract holds. The wire shape is
272
+ * identical to {@link PluginSearchMatch}.
273
+ */
274
+ function projectMatch(m: PluginSearchMatch): PluginMatchView {
275
+ const view: PluginMatchView = {
276
+ name: m.name,
277
+ path: m.path,
278
+ source:
279
+ m.source.kind === "github"
280
+ ? {
281
+ kind: "github",
282
+ repo: m.source.repo,
283
+ ref: m.source.ref,
284
+ ...(m.source.path !== undefined ? { path: m.source.path } : {}),
285
+ }
286
+ : { kind: "first-party" },
287
+ };
288
+ if (m.description !== undefined) {
289
+ view.description = m.description;
290
+ }
291
+ return view;
292
+ }
293
+
152
294
  function matchesQuery(plugin: PluginView, needle: string): boolean {
153
295
  const q = needle.toLowerCase();
154
296
  if (plugin.id.toLowerCase().includes(q)) return true;
@@ -180,7 +322,7 @@ function handleListPlugins({ queryParams = {} }: RouteHandlerArgs): {
180
322
  interface PluginsSearchResponse {
181
323
  query: string;
182
324
  ref: string;
183
- matches: PluginSearchMatch[];
325
+ matches: PluginMatchView[];
184
326
  }
185
327
 
186
328
  async function handleSearchPlugins({
@@ -189,25 +331,39 @@ async function handleSearchPlugins({
189
331
  // Empty string is a legitimate "match everything" query per the lib's
190
332
  // contract — accept it without forcing the caller to pick a sentinel.
191
333
  const query = queryParams.q ?? "";
192
- const ref = queryParams.ref?.trim() || undefined;
334
+ const ref = queryParams.ref?.trim() || DEFAULT_PLUGIN_REF;
193
335
 
194
336
  try {
195
- const result = await searchPlugins(
196
- { query, ref },
197
- { fetch: globalThis.fetch.bind(globalThis) },
198
- );
337
+ // Reject a malformed regex before any network I/O so a user typo is a
338
+ // cheap deterministic 400 — never a wasted GitHub request that could
339
+ // surface as 503 on a cold cache when upstream is rate-limited.
340
+ assertValidSearchPattern(query);
341
+ // The catalog is cached per ref (and served stale on upstream failure),
342
+ // so repeated searches don't re-hit GitHub's unauthenticated rate limit.
343
+ // Filtering by the query is an in-memory operation over that catalog.
344
+ const catalog = await getPluginCatalog(ref, {
345
+ fetch: globalThis.fetch.bind(globalThis),
346
+ });
347
+ const matches = filterPluginCatalog(catalog, query);
199
348
  // Re-pack `readonly` lib types into mutable copies so the route
200
349
  // serializer's `Record<string, unknown>` contract holds. The wire
201
350
  // shape is identical.
202
351
  return {
203
- query: result.query,
204
- ref: result.ref,
205
- matches: result.matches.map((m) => ({ name: m.name, path: m.path })),
352
+ query,
353
+ ref: catalog.ref,
354
+ matches: matches.map(projectMatch),
206
355
  };
207
356
  } catch (err) {
208
357
  if (err instanceof InvalidSearchPatternError) {
209
358
  throw new BadRequestError(err.message);
210
359
  }
360
+ // A rate-limited or unavailable upstream (with no cache to fall back on)
361
+ // is transient and retryable — surface it as 503 rather than a
362
+ // misleading 500 so the client can show a "temporarily unavailable"
363
+ // state and retry later.
364
+ if (err instanceof PluginCatalogUnavailableError) {
365
+ throw new ServiceUnavailableError(err.message);
366
+ }
211
367
  throw new InternalError(
212
368
  err instanceof Error ? err.message : "plugin catalog search failed",
213
369
  );
@@ -248,6 +404,80 @@ function handleUninstallPlugin({
248
404
  }
249
405
  }
250
406
 
407
+ // ---------------------------------------------------------------------------
408
+ // Handler — detail view
409
+ // ---------------------------------------------------------------------------
410
+
411
+ async function handleGetPluginDetails({
412
+ pathParams = {},
413
+ queryParams = {},
414
+ }: RouteHandlerArgs) {
415
+ const rawName = pathParams.name ?? "";
416
+ const ref = queryParams.ref?.trim() || undefined;
417
+
418
+ try {
419
+ return await getPluginDetails(
420
+ { name: rawName, ref },
421
+ { fetch: globalThis.fetch.bind(globalThis) },
422
+ );
423
+ } catch (err) {
424
+ if (err instanceof InvalidPluginNameError) {
425
+ throw new BadRequestError(err.message);
426
+ }
427
+ if (err instanceof PluginDetailsNotFoundError) {
428
+ throw new NotFoundError(err.message);
429
+ }
430
+ throw new InternalError(
431
+ err instanceof Error ? err.message : "plugin detail lookup failed",
432
+ );
433
+ }
434
+ }
435
+
436
+ // ---------------------------------------------------------------------------
437
+ // Handler — install
438
+ // ---------------------------------------------------------------------------
439
+
440
+ async function handleInstallPlugin({ body = {} }: RouteHandlerArgs) {
441
+ const name = typeof body.name === "string" ? body.name : "";
442
+ if (!name) {
443
+ throw new BadRequestError("`name` is required");
444
+ }
445
+ const ref = typeof body.ref === "string" ? body.ref : undefined;
446
+ const force = typeof body.force === "boolean" ? body.force : undefined;
447
+
448
+ try {
449
+ const result = await installPlugin(
450
+ { name, ref, force },
451
+ { fetch: globalThis.fetch.bind(globalThis) },
452
+ );
453
+ return {
454
+ ok: true as const,
455
+ name: result.name,
456
+ target: result.target,
457
+ fileCount: result.fileCount,
458
+ ref: result.ref,
459
+ };
460
+ } catch (err) {
461
+ if (err instanceof InvalidPluginNameError) {
462
+ throw new BadRequestError(err.message);
463
+ }
464
+ if (err instanceof PluginAlreadyInstalledError) {
465
+ throw new ConflictError(err.message);
466
+ }
467
+ if (err instanceof PluginNotFoundError) {
468
+ throw new NotFoundError(err.message);
469
+ }
470
+ // A rate-limited or otherwise temporarily-down GitHub source is
471
+ // retryable, so surface 503 rather than a misleading 500.
472
+ if (err instanceof PluginSourceUnavailableError) {
473
+ throw new ServiceUnavailableError(err.message);
474
+ }
475
+ throw new InternalError(
476
+ err instanceof Error ? err.message : "plugin install failed",
477
+ );
478
+ }
479
+ }
480
+
251
481
  // ---------------------------------------------------------------------------
252
482
  // Route definitions
253
483
  // ---------------------------------------------------------------------------
@@ -305,6 +535,77 @@ export const ROUTES: RouteDefinition[] = [
305
535
  responseBody: pluginsSearchResponseSchema,
306
536
  handler: handleSearchPlugins,
307
537
  },
538
+ {
539
+ operationId: "plugins_install",
540
+ endpoint: "plugins/install",
541
+ method: "POST",
542
+ policy: {
543
+ requiredScopes: ["settings.write"],
544
+ allowedPrincipalTypes: ACTOR_PRINCIPALS,
545
+ },
546
+ summary: "Install a plugin",
547
+ description:
548
+ "Install a plugin by name from the canonical source — a whitelisted `experimental/plugins/marketplace.json` entry, else the first-party `experimental/plugins/<name>/` convention. Materializes the plugin under `<workspaceDir>/plugins/<name>/`; the assistant must be restarted to load it. Mirrors the CLI's `assistant plugins install <name>`. An already-installed name without `force` returns 409; a name that resolves to nothing at the ref returns 404. Sibling to `POST /v1/skills/install`.",
549
+ tags: ["plugins"],
550
+ requestBody: pluginInstallRequestSchema,
551
+ responseBody: pluginInstallResponseSchema,
552
+ additionalResponses: {
553
+ "400": {
554
+ description:
555
+ "The request body was missing `name` or the name failed sanitization.",
556
+ },
557
+ "404": {
558
+ description:
559
+ "No plugin resolves to the given name at the requested ref.",
560
+ },
561
+ "409": {
562
+ description:
563
+ "A plugin with the same name is already installed and `force` was not set.",
564
+ },
565
+ },
566
+ handler: handleInstallPlugin,
567
+ },
568
+ {
569
+ operationId: "plugins_get",
570
+ endpoint: "plugins/:name",
571
+ method: "GET",
572
+ policy: {
573
+ requiredScopes: ["settings.read"],
574
+ allowedPrincipalTypes: ACTOR_PRINCIPALS,
575
+ },
576
+ summary: "Get a plugin's detail view",
577
+ description:
578
+ "Resolve a single plugin's tracked metadata (description, homepage, license, version, source) plus its README markdown. Unions the locally installed copy, the marketplace manifest, and the plugin's repository at the pinned ref — preferring the installed copy. Names that are neither installed nor present in the catalog return 404. Powers the web plugin detail page; mirrors `GET /v1/skills/:id`.",
579
+ tags: ["plugins"],
580
+ pathParams: [
581
+ {
582
+ name: "name",
583
+ type: "string",
584
+ description:
585
+ "Install name. Must match the kebab-case name accepted by `assistant plugins install`.",
586
+ },
587
+ ],
588
+ queryParams: [
589
+ {
590
+ name: "ref",
591
+ schema: { type: "string" },
592
+ description:
593
+ "Optional git ref to read catalog metadata / README at. Defaults to the CLI's `DEFAULT_PLUGIN_REF`.",
594
+ },
595
+ ],
596
+ responseBody: pluginDetailsResponseSchema,
597
+ additionalResponses: {
598
+ "400": {
599
+ description:
600
+ "The plugin name failed sanitization (e.g. contained slashes, dots, or uppercase letters).",
601
+ },
602
+ "404": {
603
+ description:
604
+ "No installed copy and no catalog entry claims the given name.",
605
+ },
606
+ },
607
+ handler: handleGetPluginDetails,
608
+ },
308
609
  {
309
610
  operationId: "plugins_uninstall",
310
611
  endpoint: "plugins/:name",
@@ -18,8 +18,8 @@ import type { Conversation } from "../../daemon/conversation.js";
18
18
  import {
19
19
  findConversation,
20
20
  findConversationBySurfaceId,
21
- getOrCreateConversation,
22
- } from "../../daemon/conversation-store.js";
21
+ } from "../../daemon/conversation-registry.js";
22
+ import { getOrCreateConversation } from "../../daemon/conversation-store.js";
23
23
  import { rawGet } from "../../memory/raw-query.js";
24
24
 
25
25
  /**
@@ -60,6 +60,7 @@ export async function resolveSurfaceConversation(
60
60
  `%"surfaceId":"${escaped}"%`,
61
61
  );
62
62
  if (!row) return undefined;
63
- if (conversationId && conversationId !== row.conversation_id) return undefined;
63
+ if (conversationId && conversationId !== row.conversation_id)
64
+ return undefined;
64
65
  return await getOrCreateConversation(row.conversation_id);
65
66
  }
@@ -8,10 +8,8 @@
8
8
  import { z } from "zod";
9
9
 
10
10
  import type { Conversation } from "../../daemon/conversation.js";
11
- import {
12
- findConversation,
13
- getOrCreateConversation,
14
- } from "../../daemon/conversation-store.js";
11
+ import { findConversation } from "../../daemon/conversation-registry.js";
12
+ import { getOrCreateConversation } from "../../daemon/conversation-store.js";
15
13
  import type { ServerMessage } from "../../daemon/message-protocol.js";
16
14
  import { getMessages } from "../../memory/conversation-crud.js";
17
15
  import { check, classifyRisk } from "../../permissions/checker.js";
@@ -9,7 +9,7 @@
9
9
 
10
10
  import { parseChannelId } from "../../channels/types.js";
11
11
  import { getConfig } from "../../config/loader.js";
12
- import { findConversation } from "../../daemon/conversation-store.js";
12
+ import { findConversation } from "../../daemon/conversation-registry.js";
13
13
  import { normalizeConversationType } from "../../daemon/message-types/shared.js";
14
14
  import {
15
15
  type AttentionState,
@@ -11,10 +11,8 @@ import { readFileSync } from "node:fs";
11
11
  import { join } from "node:path";
12
12
 
13
13
  import { getIsContainerized } from "../config/env-registry.js";
14
- import {
15
- findConversation,
16
- touchConversation,
17
- } from "../daemon/conversation-store.js";
14
+ import { findConversation } from "../daemon/conversation-registry.js";
15
+ import { touchConversation } from "../daemon/conversation-store.js";
18
16
  import { getSubagentManager } from "../subagent/index.js";
19
17
  import { createAbortReason } from "../util/abort-reasons.js";
20
18
  import { getLogger } from "../util/logger.js";