@vellumai/assistant 0.8.8 → 0.8.9-dev.202606091853.fbaa2ae

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 (475) 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 +263 -121
  12. package/package.json +2 -2
  13. package/src/__tests__/activation-early-marking.test.ts +120 -0
  14. package/src/__tests__/agent-loop-callsite-precedence.test.ts +69 -14
  15. package/src/__tests__/agent-loop-exit-reason.test.ts +204 -144
  16. package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +50 -35
  17. package/src/__tests__/agent-loop-output-hooks.test.ts +357 -0
  18. package/src/__tests__/agent-loop-override-profile.test.ts +25 -6
  19. package/src/__tests__/agent-loop-provider-error-recording.test.ts +41 -21
  20. package/src/__tests__/agent-loop-thinking.test.ts +36 -20
  21. package/src/__tests__/agent-loop.test.ts +441 -96
  22. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +14 -14
  23. package/src/__tests__/agent-wake-override-profile.test.ts +17 -21
  24. package/src/__tests__/anthropic-provider.test.ts +23 -8
  25. package/src/__tests__/app-builder-tool-scripts.test.ts +21 -0
  26. package/src/__tests__/app-control-flow.test.ts +1 -1
  27. package/src/__tests__/app-dir-path-guard.test.ts +1 -0
  28. package/src/__tests__/app-executors.test.ts +132 -0
  29. package/src/__tests__/approval-cascade.test.ts +6 -5
  30. package/src/__tests__/approval-routes-http.test.ts +4 -1
  31. package/src/__tests__/background-workers-disk-pressure.test.ts +1 -1
  32. package/src/__tests__/channel-approval-routes.test.ts +1 -1
  33. package/src/__tests__/channel-approvals.test.ts +1 -1
  34. package/src/__tests__/compaction-circuit.test.ts +258 -0
  35. package/src/__tests__/compaction-direct.test.ts +146 -0
  36. package/src/__tests__/compaction-events.test.ts +7 -7
  37. package/src/__tests__/compaction.benchmark.test.ts +1 -1
  38. package/src/__tests__/compactor-web-search-strip.test.ts +213 -0
  39. package/src/__tests__/context-overflow-reducer.test.ts +265 -125
  40. package/src/__tests__/context-window-manager-compact-retry.test.ts +121 -15
  41. package/src/__tests__/conversation-abort-tool-results.test.ts +7 -5
  42. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +7 -10
  43. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +78 -119
  44. package/src/__tests__/conversation-agent-loop-overflow.test.ts +158 -234
  45. package/src/__tests__/conversation-agent-loop.test.ts +342 -623
  46. package/src/__tests__/conversation-clean-command.test.ts +5 -2
  47. package/src/__tests__/conversation-confirmation-signals.test.ts +6 -5
  48. package/src/__tests__/conversation-crud-inference-profile.test.ts +7 -9
  49. package/src/__tests__/conversation-error.test.ts +15 -1
  50. package/src/__tests__/conversation-history-web-search.test.ts +6 -1
  51. package/src/__tests__/conversation-media-retry.test.ts +1 -1
  52. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +44 -4
  53. package/src/__tests__/conversation-process-callsite.test.ts +15 -15
  54. package/src/__tests__/conversation-provider-retry-repair.test.ts +66 -65
  55. package/src/__tests__/conversation-queue.test.ts +10 -10
  56. package/src/__tests__/conversation-runtime-assembly.test.ts +1059 -231
  57. package/src/__tests__/conversation-runtime-workspace.test.ts +115 -20
  58. package/src/__tests__/conversation-slash-queue.test.ts +7 -5
  59. package/src/__tests__/conversation-slash-unknown.test.ts +7 -5
  60. package/src/__tests__/conversation-speed-override.test.ts +11 -10
  61. package/src/__tests__/conversation-starter-routes.test.ts +14 -6
  62. package/src/__tests__/conversation-surfaces-activation-emit.test.ts +395 -0
  63. package/src/__tests__/conversation-surfaces-app-control.test.ts +48 -1
  64. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +73 -4
  65. package/src/__tests__/conversation-undo.test.ts +2 -2
  66. package/src/__tests__/conversation-workspace-cache-state.test.ts +24 -21
  67. package/src/__tests__/conversation-workspace-injection.test.ts +69 -7
  68. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +15 -12
  69. package/src/__tests__/conversations-import-system-filter.test.ts +101 -0
  70. package/src/__tests__/corrected-target.test.ts +93 -0
  71. package/src/__tests__/credential-security-invariants.test.ts +1 -0
  72. package/src/__tests__/cu-unified-flow.test.ts +36 -0
  73. package/src/__tests__/db-acp-history.test.ts +101 -0
  74. package/src/__tests__/dynamic-page-surface.test.ts +31 -0
  75. package/src/__tests__/empty-response-hook.test.ts +1 -1
  76. package/src/__tests__/file-write-tool.test.ts +63 -0
  77. package/src/__tests__/gateway-only-guard.test.ts +17 -2
  78. package/src/__tests__/guardian-grant-minting.test.ts +1 -1
  79. package/src/__tests__/guardian-routing-invariants.test.ts +93 -4
  80. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +1 -1
  81. package/src/__tests__/heartbeat-disk-pressure.test.ts +1 -0
  82. package/src/__tests__/heartbeat-service.test.ts +1 -0
  83. package/src/__tests__/history-repair-hook.test.ts +5 -1
  84. package/src/__tests__/host-app-control-proxy.test.ts +45 -0
  85. package/src/__tests__/host-app-control-routes.test.ts +1 -1
  86. package/src/__tests__/host-cu-routes-targeted.test.ts +3 -3
  87. package/src/__tests__/inference-profile-reaper.test.ts +62 -0
  88. package/src/__tests__/inference-profile-session-handler.test.ts +86 -0
  89. package/src/__tests__/injector-background-turn.test.ts +13 -23
  90. package/src/__tests__/injector-chain.test.ts +268 -44
  91. package/src/__tests__/injector-disk-pressure.test.ts +210 -52
  92. package/src/__tests__/injector-document-comments.test.ts +97 -114
  93. package/src/__tests__/injector-pkb-v2-silenced.test.ts +2 -2
  94. package/src/__tests__/injector-v3-suppression.test.ts +4 -4
  95. package/src/__tests__/list-messages-client-message-id.test.ts +91 -0
  96. package/src/__tests__/list-messages-hidden-metadata.test.ts +38 -0
  97. package/src/__tests__/llm-resolver.test.ts +73 -0
  98. package/src/__tests__/memory-retrieval-hook.test.ts +156 -38
  99. package/src/__tests__/memory-v2-static-injector.test.ts +86 -8
  100. package/src/__tests__/notification-decision-strategy.test.ts +3 -3
  101. package/src/__tests__/parallel-tool.benchmark.test.ts +35 -8
  102. package/src/__tests__/persist-unsendable-image-downscale.test.ts +145 -0
  103. package/src/__tests__/persist-unsendable-image.test.ts +97 -1
  104. package/src/__tests__/plugin-api-shim.test.ts +6 -9
  105. package/src/__tests__/plugin-bootstrap.test.ts +91 -81
  106. package/src/__tests__/plugin-registry.test.ts +3 -49
  107. package/src/__tests__/plugin-skill-contribution.test.ts +15 -14
  108. package/src/__tests__/plugin-tool-contribution.test.ts +7 -4
  109. package/src/__tests__/plugin-types.test.ts +0 -70
  110. package/src/__tests__/post-turn-tool-result-truncation.test.ts +69 -0
  111. package/src/__tests__/pre-model-call-sanitize.test.ts +109 -0
  112. package/src/__tests__/published-app-updater.test.ts +138 -0
  113. package/src/__tests__/reaction-persistence.test.ts +1 -1
  114. package/src/__tests__/send-endpoint-busy.test.ts +4 -1
  115. package/src/__tests__/skill-feature-flags-integration.test.ts +31 -0
  116. package/src/__tests__/steer-tool-repair.test.ts +1 -1
  117. package/src/__tests__/subagent-call-site-routing.test.ts +1 -1
  118. package/src/__tests__/subagent-detail.test.ts +25 -7
  119. package/src/__tests__/subagent-fork-notifications.test.ts +1 -3
  120. package/src/__tests__/subagent-fork-spawn.test.ts +1 -1
  121. package/src/__tests__/subagent-manager-notify.test.ts +1 -3
  122. package/src/__tests__/subagent-notify-parent.test.ts +1 -3
  123. package/src/__tests__/subagent-spawn-tool-fork.test.ts +1 -1
  124. package/src/__tests__/title-generate-hook.test.ts +5 -1
  125. package/src/__tests__/tool-error-hook.test.ts +1 -1
  126. package/src/__tests__/tool-result-truncate-hook.test.ts +1 -1
  127. package/src/__tests__/user-plugin-loader.test.ts +54 -286
  128. package/src/__tests__/web-fetch.test.ts +45 -0
  129. package/src/acp/__tests__/agent-process.test.ts +161 -0
  130. package/src/acp/__tests__/client-handler.test.ts +40 -0
  131. package/src/acp/__tests__/helpers/acp-config-stub.ts +0 -2
  132. package/src/acp/__tests__/helpers/acp-history-db.ts +82 -0
  133. package/src/acp/__tests__/helpers/exec-file-stub.ts +106 -0
  134. package/src/acp/__tests__/prepare-agent-env.test.ts +97 -0
  135. package/src/acp/__tests__/session-manager-persistence.test.ts +95 -28
  136. package/src/acp/__tests__/session-manager-resume.test.ts +888 -0
  137. package/src/acp/agent-process.ts +61 -1
  138. package/src/acp/auto-install.test.ts +280 -0
  139. package/src/acp/auto-install.ts +232 -0
  140. package/src/acp/client-handler.ts +31 -0
  141. package/src/acp/prepare-agent-env.ts +80 -27
  142. package/src/acp/resolve-agent.test.ts +188 -28
  143. package/src/acp/resolve-agent.ts +119 -42
  144. package/src/acp/resume-hint.ts +23 -0
  145. package/src/acp/session-manager.ts +507 -73
  146. package/src/agent/compaction-circuit.ts +60 -102
  147. package/src/agent/loop.ts +403 -251
  148. package/src/api/responses/conversation-message.ts +22 -2
  149. package/src/api/responses/memory-v3-selection-log.ts +19 -10
  150. package/src/approvals/guardian-request-resolvers.ts +27 -1
  151. package/src/background-wake/next-wake.ts +1 -0
  152. package/src/cli/commands/__tests__/memory-v3.test.ts +191 -210
  153. package/src/cli/commands/channel-verification-sessions.ts +6 -6
  154. package/src/cli/commands/db/__tests__/repair.test.ts +3 -1
  155. package/src/cli/commands/memory-v3.ts +57 -199
  156. package/src/cli/commands/plugins.ts +43 -37
  157. package/src/cli/lib/__tests__/install-from-github.test.ts +790 -111
  158. package/src/cli/lib/__tests__/plugin-catalog-cache.test.ts +196 -0
  159. package/src/cli/lib/__tests__/plugin-details.test.ts +381 -0
  160. package/src/cli/lib/__tests__/plugin-marketplace.test.ts +270 -0
  161. package/src/cli/lib/__tests__/search-plugins.test.ts +233 -32
  162. package/src/cli/lib/install-from-github.ts +797 -60
  163. package/src/cli/lib/plugin-catalog-cache.ts +84 -0
  164. package/src/cli/lib/plugin-details.ts +416 -0
  165. package/src/cli/lib/plugin-marketplace.ts +215 -0
  166. package/src/cli/lib/search-plugins.ts +201 -29
  167. package/src/config/__tests__/feature-flag-registry-guard.test.ts +2 -2
  168. package/src/config/acp-defaults.test.ts +10 -0
  169. package/src/config/acp-defaults.ts +9 -3
  170. package/src/config/acp-schema.ts +1 -7
  171. package/src/config/bundled-skills/acp/SKILL.md +80 -41
  172. package/src/config/bundled-skills/acp/TOOLS.json +4 -4
  173. package/src/config/bundled-skills/app-builder/SKILL.md +224 -381
  174. package/src/config/bundled-skills/app-builder/TOOLS.json +72 -2
  175. package/src/config/bundled-skills/app-builder/references/DESIGN_SYSTEM.md +48 -0
  176. package/src/config/bundled-skills/app-builder/references/RESPONSIVE.md +57 -0
  177. package/src/config/bundled-skills/app-builder/references/SLIDES.md +38 -0
  178. package/src/config/bundled-skills/app-builder/tools/app-list.ts +62 -0
  179. package/src/config/bundled-skills/app-builder/tools/app-update.ts +18 -0
  180. package/src/config/bundled-skills/document-editor/SKILL.md +28 -23
  181. package/src/config/bundled-skills/document-editor/TOOLS.json +1 -1
  182. package/src/config/bundled-tool-registry.ts +4 -0
  183. package/src/config/call-site-defaults.ts +0 -3
  184. package/src/config/feature-flag-registry.json +10 -16
  185. package/src/config/llm-resolver.ts +39 -7
  186. package/src/config/schemas/__tests__/memory-v3.test.ts +25 -9
  187. package/src/config/schemas/call-site-catalog.ts +0 -21
  188. package/src/config/schemas/heartbeat.ts +9 -0
  189. package/src/config/schemas/llm.ts +17 -3
  190. package/src/config/schemas/memory-v3.ts +58 -8
  191. package/src/config/seed-inference-profiles.ts +18 -0
  192. package/src/context/compactor.ts +22 -4
  193. package/src/context/post-turn-tool-result-truncation.ts +39 -1
  194. package/src/context/strip-injections.ts +8 -2
  195. package/src/daemon/conversation-agent-loop-handlers.ts +18 -36
  196. package/src/daemon/conversation-agent-loop.ts +308 -1297
  197. package/src/daemon/conversation-error.ts +31 -4
  198. package/src/daemon/conversation-history.ts +1 -1
  199. package/src/daemon/conversation-lifecycle.ts +11 -255
  200. package/src/daemon/conversation-media-retry.ts +19 -6
  201. package/src/daemon/conversation-messaging.ts +17 -0
  202. package/src/daemon/conversation-process.ts +22 -141
  203. package/src/daemon/conversation-queue-manager.ts +8 -0
  204. package/src/daemon/conversation-registry.ts +159 -0
  205. package/src/daemon/conversation-runtime-assembly.ts +361 -393
  206. package/src/daemon/conversation-store.ts +9 -90
  207. package/src/daemon/conversation-surfaces.ts +165 -11
  208. package/src/daemon/conversation-workspace.ts +17 -0
  209. package/src/daemon/conversation.ts +444 -62
  210. package/src/daemon/disk-pressure-policy.ts +0 -1
  211. package/src/daemon/external-plugins-bootstrap.ts +36 -46
  212. package/src/daemon/handlers/config-channels.ts +11 -2
  213. package/src/daemon/handlers/conversations.ts +3 -1
  214. package/src/daemon/handlers/skills.ts +4 -1
  215. package/src/daemon/host-app-control-proxy.ts +72 -57
  216. package/src/daemon/host-proxy-preactivation.ts +1 -3
  217. package/src/daemon/lifecycle.ts +21 -7
  218. package/src/daemon/persist-unsendable-image.ts +62 -25
  219. package/src/daemon/process-message.ts +1 -1
  220. package/src/daemon/server.ts +2 -0
  221. package/src/daemon/tool-side-effects.ts +15 -0
  222. package/src/daemon/wake-conversation-ops.ts +269 -0
  223. package/src/embedded/plugin-api.ts +2 -2
  224. package/src/export/__tests__/transcript-formatter.test.ts +5 -0
  225. package/src/heartbeat/__tests__/heartbeat-service.test.ts +3 -0
  226. package/src/heartbeat/heartbeat-run-store.ts +23 -1
  227. package/src/heartbeat/heartbeat-service.ts +26 -0
  228. package/src/ipc/__tests__/browser-ipc.test.ts +1 -1
  229. package/src/ipc/__tests__/ui-request-route.test.ts +3 -3
  230. package/src/ipc/skill-routes/__tests__/memory.test.ts +15 -0
  231. package/src/ipc/skill-routes/memory.ts +4 -2
  232. package/src/memory/__tests__/activation-session-store.test.ts +41 -0
  233. package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +87 -0
  234. package/src/memory/__tests__/onboarding-events-store.test.ts +80 -0
  235. package/src/memory/activation-session-store.ts +43 -0
  236. package/src/memory/conversation-crud.ts +29 -19
  237. package/src/memory/conversation-starter-checkpoints.ts +1 -0
  238. package/src/memory/db-init.ts +6 -0
  239. package/src/memory/job-handlers/conversation-starters.ts +13 -2
  240. package/src/memory/jobs/__tests__/embed-concept-page.test.ts +5 -4
  241. package/src/memory/jobs-worker.ts +25 -1
  242. package/src/memory/migrations/272-acp-session-history-cwd.ts +36 -0
  243. package/src/memory/migrations/273-onboarding-events-funnel-columns.ts +46 -0
  244. package/src/memory/migrations/274-create-activation-sessions.ts +15 -0
  245. package/src/memory/migrations/index.ts +3 -0
  246. package/src/memory/onboarding-events-store.ts +66 -18
  247. package/src/memory/schema/acp.ts +4 -0
  248. package/src/memory/schema/infrastructure.ts +13 -0
  249. package/src/memory/v2/__tests__/consolidation-job.test.ts +4 -97
  250. package/src/memory/v2/__tests__/page-store.test.ts +22 -0
  251. package/src/memory/v2/consolidation-job.ts +2 -63
  252. package/src/memory/v2/types.ts +5 -0
  253. package/src/messaging/providers/telegram-bot/api.ts +14 -5
  254. package/src/notifications/__tests__/copy-composer.test.ts +244 -0
  255. package/src/notifications/access-request-copy.ts +298 -0
  256. package/src/notifications/adapters/slack.ts +3 -3
  257. package/src/notifications/adapters/telegram.ts +9 -2
  258. package/src/notifications/copy-composer.ts +49 -267
  259. package/src/notifications/decision-engine.ts +16 -35
  260. package/src/notifications/home-feed-side-effect.ts +1 -6
  261. package/src/plugin-api/constants.ts +4 -0
  262. package/src/plugin-api/index.ts +40 -11
  263. package/src/plugin-api/types.ts +108 -0
  264. package/src/plugins/defaults/compaction/compact.ts +106 -0
  265. package/src/{daemon → plugins/defaults/compaction}/context-overflow-reducer.ts +250 -42
  266. package/src/plugins/defaults/compaction/corrected-target.ts +53 -0
  267. package/src/plugins/defaults/compaction/manager-store.ts +57 -0
  268. package/src/plugins/defaults/compaction/package.json +1 -2
  269. package/src/{context → plugins/defaults/compaction}/window-manager.ts +95 -25
  270. package/src/plugins/defaults/empty-response/package.json +0 -1
  271. package/src/plugins/defaults/history-repair/package.json +0 -1
  272. package/src/plugins/defaults/index.ts +156 -26
  273. package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +100 -52
  274. package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit.ts +285 -0
  275. package/src/plugins/defaults/memory-retrieval/injector-chain.ts +2 -2
  276. package/src/plugins/defaults/{injectors/register.ts → memory-retrieval/injectors.ts} +148 -73
  277. package/src/plugins/defaults/memory-retrieval/package.json +14 -0
  278. package/src/plugins/defaults/memory-retrieval/unified-turn-context.ts +223 -0
  279. package/src/plugins/defaults/memory-v3-shadow/__tests__/capabilities.test.ts +71 -0
  280. package/src/plugins/defaults/memory-v3-shadow/__tests__/dense.test.ts +181 -0
  281. package/src/plugins/defaults/memory-v3-shadow/__tests__/edge.test.ts +247 -0
  282. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/live-integration.test.ts +147 -134
  283. package/src/plugins/defaults/memory-v3-shadow/__tests__/maintain-job.test.ts +456 -0
  284. package/src/plugins/defaults/memory-v3-shadow/__tests__/orchestrate.test.ts +662 -0
  285. package/src/plugins/defaults/memory-v3-shadow/__tests__/pool-select.test.ts +306 -0
  286. package/src/plugins/defaults/memory-v3-shadow/__tests__/render-injection.test.ts +91 -0
  287. package/src/plugins/defaults/memory-v3-shadow/__tests__/section-dense-store.test.ts +402 -0
  288. package/src/plugins/defaults/memory-v3-shadow/__tests__/section-needle.test.ts +135 -0
  289. package/src/plugins/defaults/memory-v3-shadow/__tests__/sections.test.ts +125 -0
  290. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/selection-log-store.test.ts +74 -19
  291. package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-integration.test.ts +446 -0
  292. package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +592 -0
  293. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/types.test.ts +2 -24
  294. package/src/plugins/defaults/memory-v3-shadow/capabilities.ts +91 -0
  295. package/src/plugins/defaults/memory-v3-shadow/dense.ts +97 -0
  296. package/src/plugins/defaults/memory-v3-shadow/edge.ts +252 -0
  297. package/src/plugins/defaults/memory-v3-shadow/hooks/post-compact.ts +14 -0
  298. package/src/plugins/defaults/memory-v3-shadow/hooks/user-prompt-submit.ts +19 -0
  299. package/src/plugins/defaults/memory-v3-shadow/injector.ts +76 -0
  300. package/src/plugins/defaults/memory-v3-shadow/maintain-job.ts +555 -0
  301. package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +266 -0
  302. package/src/plugins/defaults/memory-v3-shadow/package.json +14 -0
  303. package/src/plugins/defaults/memory-v3-shadow/page-content.ts +71 -0
  304. package/src/plugins/defaults/memory-v3-shadow/pool-select.ts +204 -0
  305. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/render-injection.ts +16 -8
  306. package/src/plugins/defaults/memory-v3-shadow/section-dense-store.ts +236 -0
  307. package/src/plugins/defaults/memory-v3-shadow/section-needle.ts +200 -0
  308. package/src/plugins/defaults/memory-v3-shadow/sections.ts +115 -0
  309. package/src/plugins/defaults/memory-v3-shadow/selection-log-store.ts +156 -0
  310. package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +382 -0
  311. package/src/plugins/defaults/memory-v3-shadow/types.ts +92 -0
  312. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/working-set.ts +4 -1
  313. package/src/plugins/defaults/title-generate/package.json +0 -1
  314. package/src/plugins/defaults/tool-error/package.json +0 -1
  315. package/src/plugins/defaults/tool-result-truncate/package.json +0 -1
  316. package/src/plugins/external-api.ts +12 -2
  317. package/src/plugins/pipeline.ts +6 -293
  318. package/src/plugins/registry.ts +9 -37
  319. package/src/plugins/types.ts +76 -381
  320. package/src/plugins/user-loader.ts +30 -127
  321. package/src/prompts/__tests__/system-prompt.test.ts +6 -0
  322. package/src/prompts/system-prompt.ts +61 -10
  323. package/src/prompts/templates/BOOTSTRAP-ACTIVATION-RAIL.md +71 -4
  324. package/src/providers/anthropic/client.ts +9 -10
  325. package/src/providers/inference/kimi-cjk-token-ids.ts +493 -0
  326. package/src/providers/inference/logit-bias.ts +55 -0
  327. package/src/providers/model-catalog.ts +34 -0
  328. package/src/providers/openai/__tests__/vision-not-supported.test.ts +75 -0
  329. package/src/providers/openai/chat-completions-provider.ts +44 -1
  330. package/src/providers/retry.ts +22 -0
  331. package/src/providers/types.ts +6 -0
  332. package/src/runtime/__tests__/agent-wake.test.ts +555 -691
  333. package/src/runtime/__tests__/interactive-ui.test.ts +1 -1
  334. package/src/runtime/agent-wake.ts +108 -209
  335. package/src/runtime/assistant-event-hub.ts +1 -1
  336. package/src/runtime/channel-approvals.ts +1 -1
  337. package/src/runtime/interactive-ui.ts +1 -1
  338. package/src/runtime/routes/__tests__/acp-routes.test.ts +466 -55
  339. package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +1 -1
  340. package/src/runtime/routes/__tests__/plugins-routes.test.ts +423 -73
  341. package/src/runtime/routes/__tests__/stt-routes.test.ts +112 -0
  342. package/src/runtime/routes/__tests__/surface-action-routes.test.ts +5 -4
  343. package/src/runtime/routes/__tests__/surface-content-routes.test.ts +4 -1
  344. package/src/runtime/routes/acp-routes.test.ts +185 -32
  345. package/src/runtime/routes/acp-routes.ts +328 -30
  346. package/src/runtime/routes/app-management-routes.ts +3 -0
  347. package/src/runtime/routes/approval-routes.ts +1 -1
  348. package/src/runtime/routes/browser-routes.ts +1 -1
  349. package/src/runtime/routes/browser-tabs-routes.ts +6 -10
  350. package/src/runtime/routes/channel-verification-routes.ts +14 -5
  351. package/src/runtime/routes/conversation-cli-routes.ts +1 -1
  352. package/src/runtime/routes/conversation-list-routes.ts +1 -1
  353. package/src/runtime/routes/conversation-query-routes.ts +1 -1
  354. package/src/runtime/routes/conversation-routes.ts +30 -2
  355. package/src/runtime/routes/conversation-starter-routes.ts +13 -7
  356. package/src/runtime/routes/conversations-import-routes.ts +24 -7
  357. package/src/runtime/routes/host-app-control-routes.ts +1 -1
  358. package/src/runtime/routes/host-cu-routes.ts +1 -1
  359. package/src/runtime/routes/identity-routes.ts +18 -3
  360. package/src/runtime/routes/inbound-message-handler.ts +1 -1
  361. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +33 -34
  362. package/src/runtime/routes/inference-profile-session-handler.ts +11 -0
  363. package/src/runtime/routes/inference-profile-session-reaper.ts +6 -0
  364. package/src/runtime/routes/memory-v3-routes.ts +65 -305
  365. package/src/runtime/routes/playground/__tests__/force-compact.test.ts +1 -1
  366. package/src/runtime/routes/playground/helpers.ts +1 -1
  367. package/src/runtime/routes/plugins-routes.ts +337 -35
  368. package/src/runtime/routes/stt-routes.ts +45 -12
  369. package/src/runtime/routes/surface-conversation-resolver.ts +4 -3
  370. package/src/runtime/routes/work-items-routes.ts +2 -4
  371. package/src/runtime/routes/workspace-routes.ts +50 -15
  372. package/src/runtime/services/conversation-serializer.ts +1 -1
  373. package/src/runtime/verification-outbound-actions.ts +147 -2
  374. package/src/runtime/verification-templates.ts +29 -3
  375. package/src/services/published-app-updater.ts +30 -8
  376. package/src/signals/cancel.ts +2 -4
  377. package/src/subagent/manager.ts +21 -5
  378. package/src/telemetry/__tests__/activation-funnel.test.ts +95 -0
  379. package/src/telemetry/activation-funnel.ts +167 -0
  380. package/src/telemetry/types.ts +13 -0
  381. package/src/telemetry/usage-telemetry-reporter.test.ts +154 -0
  382. package/src/telemetry/usage-telemetry-reporter.ts +26 -1
  383. package/src/tools/acp/context.ts +20 -0
  384. package/src/tools/acp/list-agents.test.ts +9 -19
  385. package/src/tools/acp/list-agents.ts +3 -15
  386. package/src/tools/acp/spawn.test.ts +173 -202
  387. package/src/tools/acp/spawn.ts +37 -172
  388. package/src/tools/acp/steer.test.ts +105 -8
  389. package/src/tools/acp/steer.ts +48 -17
  390. package/src/tools/apps/executors.ts +166 -50
  391. package/src/tools/browser/browser-execution.ts +12 -2
  392. package/src/tools/filesystem/write.ts +34 -0
  393. package/src/tools/network/web-fetch.ts +65 -24
  394. package/src/tools/skills/load.ts +1 -1
  395. package/src/tools/subagent/spawn.ts +2 -4
  396. package/src/tools/ui-surface/definitions.ts +32 -5
  397. package/src/workspace/migrations/051-seed-conversation-summarization-callsite.ts +4 -5
  398. package/src/workspace/migrations/097-enable-adaptive-thinking-managed-profiles.ts +69 -45
  399. package/docs/plugins.md +0 -832
  400. package/examples/plugins/echo/register.ts +0 -143
  401. package/src/__tests__/circuit-breaker-pipeline.test.ts +0 -405
  402. package/src/__tests__/compaction-pipeline.test.ts +0 -210
  403. package/src/__tests__/compaction-timeout-recovery.test.ts +0 -251
  404. package/src/__tests__/overflow-reduce-pipeline.test.ts +0 -667
  405. package/src/__tests__/pipeline-runner.test.ts +0 -554
  406. package/src/daemon/wake-target-adapter.ts +0 -253
  407. package/src/memory/v3/__tests__/assign.test.ts +0 -242
  408. package/src/memory/v3/__tests__/capabilities.test.ts +0 -118
  409. package/src/memory/v3/__tests__/core.test.ts +0 -39
  410. package/src/memory/v3/__tests__/health.test.ts +0 -219
  411. package/src/memory/v3/__tests__/maintain-job.test.ts +0 -288
  412. package/src/memory/v3/__tests__/needle.test.ts +0 -107
  413. package/src/memory/v3/__tests__/orchestrate.test.ts +0 -436
  414. package/src/memory/v3/__tests__/provider-blocks.test.ts +0 -13
  415. package/src/memory/v3/__tests__/reconcile.test.ts +0 -274
  416. package/src/memory/v3/__tests__/render-injection.test.ts +0 -61
  417. package/src/memory/v3/__tests__/router.test.ts +0 -332
  418. package/src/memory/v3/__tests__/selector.test.ts +0 -470
  419. package/src/memory/v3/__tests__/shadow-plugin.test.ts +0 -432
  420. package/src/memory/v3/__tests__/snapshot.test.ts +0 -168
  421. package/src/memory/v3/__tests__/tree.test.ts +0 -192
  422. package/src/memory/v3/assign.ts +0 -268
  423. package/src/memory/v3/capabilities.ts +0 -124
  424. package/src/memory/v3/core.ts +0 -26
  425. package/src/memory/v3/health.ts +0 -0
  426. package/src/memory/v3/maintain-job.ts +0 -314
  427. package/src/memory/v3/needle.ts +0 -115
  428. package/src/memory/v3/orchestrate.ts +0 -126
  429. package/src/memory/v3/page-content.ts +0 -34
  430. package/src/memory/v3/provider-blocks.ts +0 -26
  431. package/src/memory/v3/reconcile.ts +0 -523
  432. package/src/memory/v3/router.ts +0 -190
  433. package/src/memory/v3/selection-log-store.ts +0 -84
  434. package/src/memory/v3/selector.ts +0 -226
  435. package/src/memory/v3/shadow-plugin.ts +0 -411
  436. package/src/memory/v3/snapshot.ts +0 -209
  437. package/src/memory/v3/tree.ts +0 -174
  438. package/src/memory/v3/types.ts +0 -59
  439. package/src/notifications/__tests__/emit-signal-home-feed.test.ts +0 -187
  440. package/src/plugins/defaults/circuit-breaker/middlewares/circuitBreaker.ts +0 -93
  441. package/src/plugins/defaults/circuit-breaker/package.json +0 -15
  442. package/src/plugins/defaults/circuit-breaker/register.ts +0 -39
  443. package/src/plugins/defaults/compaction/middlewares/compaction.ts +0 -25
  444. package/src/plugins/defaults/compaction/register.ts +0 -35
  445. package/src/plugins/defaults/compaction/terminal.ts +0 -73
  446. package/src/plugins/defaults/empty-response/register.ts +0 -23
  447. package/src/plugins/defaults/history-repair/register.ts +0 -24
  448. package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit-temp.ts +0 -216
  449. package/src/plugins/defaults/overflow-reduce/middlewares/overflowReduce.ts +0 -126
  450. package/src/plugins/defaults/overflow-reduce/package.json +0 -15
  451. package/src/plugins/defaults/overflow-reduce/register.ts +0 -42
  452. package/src/plugins/defaults/title-generate/register.ts +0 -35
  453. package/src/plugins/defaults/tool-error/register.ts +0 -23
  454. package/src/plugins/defaults/tool-result-truncate/register.ts +0 -24
  455. package/src/proactive-artifact/aux-message-injector.ts +0 -97
  456. package/src/proactive-artifact/decision.test.ts +0 -226
  457. package/src/proactive-artifact/decision.ts +0 -165
  458. package/src/proactive-artifact/index.ts +0 -7
  459. package/src/proactive-artifact/job.test.ts +0 -962
  460. package/src/proactive-artifact/job.ts +0 -372
  461. package/src/proactive-artifact/message-copy.ts +0 -58
  462. package/src/proactive-artifact/trigger-state.test.ts +0 -286
  463. package/src/proactive-artifact/trigger-state.ts +0 -123
  464. package/src/util/map-limit.ts +0 -27
  465. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/fixtures/eval-turns.json +0 -0
  466. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/fixtures/live-turns.json +0 -0
  467. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/working-set-eviction.test.ts +0 -0
  468. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/working-set-skeleton.test.ts +0 -0
  469. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/README.md +0 -0
  470. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/assignments.json +0 -0
  471. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/core.json +0 -0
  472. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-a/topic-x.md +0 -0
  473. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-a/topic-y.md +0 -0
  474. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-b/topic-z.md +0 -0
  475. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/llm-retry.ts +0 -0
@@ -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,66 @@ 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
+ force: z
206
+ .boolean()
207
+ .optional()
208
+ .describe("Overwrite an existing install in place. Defaults to false."),
209
+ });
210
+
211
+ const pluginInstallResponseSchema = z.object({
212
+ ok: z.literal(true),
213
+ name: z.string().describe("Install name that was materialized."),
214
+ target: z
215
+ .string()
216
+ .describe("Absolute path the plugin was materialized into on the host."),
217
+ fileCount: z
218
+ .number()
219
+ .describe("Number of files written for the installed plugin."),
220
+ ref: z.string().describe("Git ref the plugin was fetched from."),
221
+ });
222
+
122
223
  // ---------------------------------------------------------------------------
123
224
  // Helpers
124
225
  // ---------------------------------------------------------------------------
@@ -149,6 +250,41 @@ function projectPlugin(entry: InstalledPluginInfo): PluginView {
149
250
  return view;
150
251
  }
151
252
 
253
+ /** Wire shape for a catalog match. Mirrors {@link pluginSearchMatchSchema}. */
254
+ interface PluginMatchView {
255
+ name: string;
256
+ path: string;
257
+ description?: string;
258
+ source:
259
+ | { kind: "first-party" }
260
+ | { kind: "github"; repo: string; path?: string; ref: string };
261
+ }
262
+
263
+ /**
264
+ * Re-pack a `readonly` lib match into a mutable wire object so the route
265
+ * serializer's `Record<string, unknown>` contract holds. The wire shape is
266
+ * identical to {@link PluginSearchMatch}.
267
+ */
268
+ function projectMatch(m: PluginSearchMatch): PluginMatchView {
269
+ const view: PluginMatchView = {
270
+ name: m.name,
271
+ path: m.path,
272
+ source:
273
+ m.source.kind === "github"
274
+ ? {
275
+ kind: "github",
276
+ repo: m.source.repo,
277
+ ref: m.source.ref,
278
+ ...(m.source.path !== undefined ? { path: m.source.path } : {}),
279
+ }
280
+ : { kind: "first-party" },
281
+ };
282
+ if (m.description !== undefined) {
283
+ view.description = m.description;
284
+ }
285
+ return view;
286
+ }
287
+
152
288
  function matchesQuery(plugin: PluginView, needle: string): boolean {
153
289
  const q = needle.toLowerCase();
154
290
  if (plugin.id.toLowerCase().includes(q)) return true;
@@ -180,7 +316,7 @@ function handleListPlugins({ queryParams = {} }: RouteHandlerArgs): {
180
316
  interface PluginsSearchResponse {
181
317
  query: string;
182
318
  ref: string;
183
- matches: PluginSearchMatch[];
319
+ matches: PluginMatchView[];
184
320
  }
185
321
 
186
322
  async function handleSearchPlugins({
@@ -189,25 +325,39 @@ async function handleSearchPlugins({
189
325
  // Empty string is a legitimate "match everything" query per the lib's
190
326
  // contract — accept it without forcing the caller to pick a sentinel.
191
327
  const query = queryParams.q ?? "";
192
- const ref = queryParams.ref?.trim() || undefined;
328
+ const ref = queryParams.ref?.trim() || DEFAULT_PLUGIN_REF;
193
329
 
194
330
  try {
195
- const result = await searchPlugins(
196
- { query, ref },
197
- { fetch: globalThis.fetch.bind(globalThis) },
198
- );
331
+ // Reject a malformed regex before any network I/O so a user typo is a
332
+ // cheap deterministic 400 — never a wasted GitHub request that could
333
+ // surface as 503 on a cold cache when upstream is rate-limited.
334
+ assertValidSearchPattern(query);
335
+ // The catalog is cached per ref (and served stale on upstream failure),
336
+ // so repeated searches don't re-hit GitHub's unauthenticated rate limit.
337
+ // Filtering by the query is an in-memory operation over that catalog.
338
+ const catalog = await getPluginCatalog(ref, {
339
+ fetch: globalThis.fetch.bind(globalThis),
340
+ });
341
+ const matches = filterPluginCatalog(catalog, query);
199
342
  // Re-pack `readonly` lib types into mutable copies so the route
200
343
  // serializer's `Record<string, unknown>` contract holds. The wire
201
344
  // shape is identical.
202
345
  return {
203
- query: result.query,
204
- ref: result.ref,
205
- matches: result.matches.map((m) => ({ name: m.name, path: m.path })),
346
+ query,
347
+ ref: catalog.ref,
348
+ matches: matches.map(projectMatch),
206
349
  };
207
350
  } catch (err) {
208
351
  if (err instanceof InvalidSearchPatternError) {
209
352
  throw new BadRequestError(err.message);
210
353
  }
354
+ // A rate-limited or unavailable upstream (with no cache to fall back on)
355
+ // is transient and retryable — surface it as 503 rather than a
356
+ // misleading 500 so the client can show a "temporarily unavailable"
357
+ // state and retry later.
358
+ if (err instanceof PluginCatalogUnavailableError) {
359
+ throw new ServiceUnavailableError(err.message);
360
+ }
211
361
  throw new InternalError(
212
362
  err instanceof Error ? err.message : "plugin catalog search failed",
213
363
  );
@@ -248,6 +398,87 @@ function handleUninstallPlugin({
248
398
  }
249
399
  }
250
400
 
401
+ // ---------------------------------------------------------------------------
402
+ // Handler — detail view
403
+ // ---------------------------------------------------------------------------
404
+
405
+ async function handleGetPluginDetails({
406
+ pathParams = {},
407
+ queryParams = {},
408
+ }: RouteHandlerArgs) {
409
+ const rawName = pathParams.name ?? "";
410
+ const ref = queryParams.ref?.trim() || undefined;
411
+
412
+ try {
413
+ return await getPluginDetails(
414
+ { name: rawName, ref },
415
+ { fetch: globalThis.fetch.bind(globalThis) },
416
+ );
417
+ } catch (err) {
418
+ if (err instanceof InvalidPluginNameError) {
419
+ throw new BadRequestError(err.message);
420
+ }
421
+ if (err instanceof PluginDetailsNotFoundError) {
422
+ throw new NotFoundError(err.message);
423
+ }
424
+ throw new InternalError(
425
+ err instanceof Error ? err.message : "plugin detail lookup failed",
426
+ );
427
+ }
428
+ }
429
+
430
+ // ---------------------------------------------------------------------------
431
+ // Handler — install
432
+ // ---------------------------------------------------------------------------
433
+
434
+ async function handleInstallPlugin({ body = {} }: RouteHandlerArgs) {
435
+ const name = typeof body.name === "string" ? body.name : "";
436
+ if (!name) {
437
+ throw new BadRequestError("`name` is required");
438
+ }
439
+ const force = typeof body.force === "boolean" ? body.force : undefined;
440
+
441
+ // The ref is pinned to the curated `DEFAULT_PLUGIN_REF` rather than taken
442
+ // from the request: a caller-supplied ref would let any `settings.write`
443
+ // principal install from an unreviewed revision (a PR branch, fork ref,
444
+ // ...) whose marketplace manifest or first-party plugin directory could
445
+ // carry attacker code that the loader then dynamically imports. Installs
446
+ // over HTTP therefore only ever resolve against the reviewed catalog on
447
+ // the default ref. Operators who need another ref use the local CLI's
448
+ // `assistant plugins install --ref`.
449
+ try {
450
+ const result = await installPlugin(
451
+ { name, ref: DEFAULT_PLUGIN_REF, force },
452
+ { fetch: globalThis.fetch.bind(globalThis) },
453
+ );
454
+ return {
455
+ ok: true as const,
456
+ name: result.name,
457
+ target: result.target,
458
+ fileCount: result.fileCount,
459
+ ref: result.ref,
460
+ };
461
+ } catch (err) {
462
+ if (err instanceof InvalidPluginNameError) {
463
+ throw new BadRequestError(err.message);
464
+ }
465
+ if (err instanceof PluginAlreadyInstalledError) {
466
+ throw new ConflictError(err.message);
467
+ }
468
+ if (err instanceof PluginNotFoundError) {
469
+ throw new NotFoundError(err.message);
470
+ }
471
+ // A rate-limited or otherwise temporarily-down GitHub source is
472
+ // retryable, so surface 503 rather than a misleading 500.
473
+ if (err instanceof PluginSourceUnavailableError) {
474
+ throw new ServiceUnavailableError(err.message);
475
+ }
476
+ throw new InternalError(
477
+ err instanceof Error ? err.message : "plugin install failed",
478
+ );
479
+ }
480
+ }
481
+
251
482
  // ---------------------------------------------------------------------------
252
483
  // Route definitions
253
484
  // ---------------------------------------------------------------------------
@@ -305,6 +536,77 @@ export const ROUTES: RouteDefinition[] = [
305
536
  responseBody: pluginsSearchResponseSchema,
306
537
  handler: handleSearchPlugins,
307
538
  },
539
+ {
540
+ operationId: "plugins_install",
541
+ endpoint: "plugins/install",
542
+ method: "POST",
543
+ policy: {
544
+ requiredScopes: ["settings.write"],
545
+ allowedPrincipalTypes: ACTOR_PRINCIPALS,
546
+ },
547
+ summary: "Install a plugin",
548
+ description:
549
+ "Install a plugin by name from the canonical source — a whitelisted `experimental/plugins/marketplace.json` entry, else the first-party `experimental/plugins/<name>/` convention. Always resolves against the curated default git ref (no caller-supplied ref): installing from an unreviewed revision would bypass the marketplace/first-party curation boundary and let attacker-controlled code be loaded. 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 returns 404. Sibling to `POST /v1/skills/install`.",
550
+ tags: ["plugins"],
551
+ requestBody: pluginInstallRequestSchema,
552
+ responseBody: pluginInstallResponseSchema,
553
+ additionalResponses: {
554
+ "400": {
555
+ description:
556
+ "The request body was missing `name` or the name failed sanitization.",
557
+ },
558
+ "404": {
559
+ description:
560
+ "No plugin resolves to the given name at the requested ref.",
561
+ },
562
+ "409": {
563
+ description:
564
+ "A plugin with the same name is already installed and `force` was not set.",
565
+ },
566
+ },
567
+ handler: handleInstallPlugin,
568
+ },
569
+ {
570
+ operationId: "plugins_get",
571
+ endpoint: "plugins/:name",
572
+ method: "GET",
573
+ policy: {
574
+ requiredScopes: ["settings.read"],
575
+ allowedPrincipalTypes: ACTOR_PRINCIPALS,
576
+ },
577
+ summary: "Get a plugin's detail view",
578
+ description:
579
+ "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`.",
580
+ tags: ["plugins"],
581
+ pathParams: [
582
+ {
583
+ name: "name",
584
+ type: "string",
585
+ description:
586
+ "Install name. Must match the kebab-case name accepted by `assistant plugins install`.",
587
+ },
588
+ ],
589
+ queryParams: [
590
+ {
591
+ name: "ref",
592
+ schema: { type: "string" },
593
+ description:
594
+ "Optional git ref to read catalog metadata / README at. Defaults to the CLI's `DEFAULT_PLUGIN_REF`.",
595
+ },
596
+ ],
597
+ responseBody: pluginDetailsResponseSchema,
598
+ additionalResponses: {
599
+ "400": {
600
+ description:
601
+ "The plugin name failed sanitization (e.g. contained slashes, dots, or uppercase letters).",
602
+ },
603
+ "404": {
604
+ description:
605
+ "No installed copy and no catalog entry claims the given name.",
606
+ },
607
+ },
608
+ handler: handleGetPluginDetails,
609
+ },
308
610
  {
309
611
  operationId: "plugins_uninstall",
310
612
  endpoint: "plugins/:name",
@@ -17,6 +17,7 @@ import { listProviderEntries } from "../../providers/speech-to-text/provider-cat
17
17
  import { resolveBatchTranscriber } from "../../providers/speech-to-text/resolve.js";
18
18
  import { normalizeSttError } from "../../stt/daemon-batch-transcriber.js";
19
19
  import type { BatchTranscriber, SttErrorCategory } from "../../stt/types.js";
20
+ import { SttError } from "../../stt/types.js";
20
21
  import { getLogger } from "../../util/logger.js";
21
22
  import {
22
23
  FFMPEG_TRANSCODE_TIMEOUT_MS,
@@ -141,6 +142,27 @@ async function toWav(inputPath: string, isVideo: boolean): Promise<string> {
141
142
  return wavPath;
142
143
  }
143
144
 
145
+ /**
146
+ * Call the provider and tag any failure as an {@link SttError} so callers can
147
+ * distinguish provider failures (categorized HTTP statuses) from local
148
+ * preprocessing failures (ffmpeg/ffprobe), which stay on the bad-gateway path.
149
+ */
150
+ async function transcribeChunk(
151
+ transcriber: BatchTranscriber,
152
+ audioBuffer: Buffer,
153
+ ): Promise<string> {
154
+ try {
155
+ const result = await transcriber.transcribe({
156
+ audio: audioBuffer,
157
+ mimeType: "audio/wav",
158
+ signal: AbortSignal.timeout(STT_REQUEST_TIMEOUT_MS),
159
+ });
160
+ return result.text;
161
+ } catch (err) {
162
+ throw normalizeSttError(err);
163
+ }
164
+ }
165
+
144
166
  async function transcribeWithProvider(
145
167
  audioPath: string,
146
168
  transcriber: BatchTranscriber,
@@ -151,12 +173,7 @@ async function transcribeWithProvider(
151
173
  // If small enough, send directly
152
174
  if (fileSize <= STT_CHUNK_MAX_BYTES) {
153
175
  const audioBuffer = await readFile(audioPath);
154
- const result = await transcriber.transcribe({
155
- audio: audioBuffer,
156
- mimeType: "audio/wav",
157
- signal: AbortSignal.timeout(STT_REQUEST_TIMEOUT_MS),
158
- });
159
- return result.text;
176
+ return transcribeChunk(transcriber, audioBuffer);
160
177
  }
161
178
 
162
179
  // Split into chunks for large files
@@ -175,12 +192,8 @@ async function transcribeWithProvider(
175
192
  ` Transcribing chunk ${i + 1}/${chunks.length}...\n`,
176
193
  );
177
194
  const audioBuffer = await readFile(chunks[i]);
178
- const result = await transcriber.transcribe({
179
- audio: audioBuffer,
180
- mimeType: "audio/wav",
181
- signal: AbortSignal.timeout(STT_REQUEST_TIMEOUT_MS),
182
- });
183
- if (result.text) parts.push(result.text);
195
+ const text = await transcribeChunk(transcriber, audioBuffer);
196
+ if (text) parts.push(text);
184
197
  }
185
198
 
186
199
  return parts.join(" ");
@@ -363,6 +376,26 @@ async function handleTranscribeFile({ body }: RouteHandlerArgs) {
363
376
  durationSeconds,
364
377
  };
365
378
  } catch (err) {
379
+ // Only provider failures (tagged as SttError by transcribeChunk) are
380
+ // categorized into HTTP statuses. Local preprocessing failures (ffmpeg/
381
+ // ffprobe) stay on the bad-gateway path so their stderr never decides the
382
+ // category (e.g. a path under a dir named "403" must not become a 401).
383
+ if (err instanceof SttError) {
384
+ // Auth/rate-limit/timeout/invalid-audio are user-config or transient
385
+ // conditions, not daemon defects, so log them at warn to keep them out
386
+ // of error telemetry. Reserve error level (with the raw err for a Sentry
387
+ // stack) for genuine provider/unexpected failures.
388
+ if (err.category === "provider-error") {
389
+ log.error({ err, filePath }, "File transcription failed");
390
+ } else {
391
+ log.warn(
392
+ { category: err.category, message: err.message, filePath },
393
+ "File transcription failed",
394
+ );
395
+ }
396
+ throw STT_ERROR_MAP[err.category]();
397
+ }
398
+
366
399
  log.error({ err, filePath }, "File transcription failed");
367
400
  throw new BadGatewayError(
368
401
  err instanceof Error ? err.message : "Transcription failed",
@@ -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";