@vellumai/assistant 0.8.7 → 0.8.8-dev.202606052332.17fc8ea

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 (570) hide show
  1. package/Dockerfile +20 -4
  2. package/bun.lock +2 -2
  3. package/docker-entrypoint.sh +4 -2
  4. package/docker-init-apt-root.sh +3 -1
  5. package/docker-kata-apt-env.sh +3 -1
  6. package/docker-kata-runtime-family.sh +12 -0
  7. package/docs/architecture/memory.md +1 -1
  8. package/examples/plugins/echo/README.md +61 -66
  9. package/examples/plugins/echo/hooks/post-tool-use.ts +18 -0
  10. package/examples/plugins/echo/hooks/stop.ts +16 -0
  11. package/examples/plugins/echo/hooks/user-prompt-submit.ts +18 -0
  12. package/examples/plugins/echo/package.json +1 -2
  13. package/examples/plugins/echo/src/emit.ts +19 -0
  14. package/node_modules/@vellumai/skill-host-contracts/src/server-message.ts +3 -3
  15. package/node_modules/@vellumai/skill-host-contracts/src/skill-host.ts +7 -6
  16. package/openapi.yaml +3378 -335
  17. package/package.json +2 -2
  18. package/scripts/generate-openapi.ts +68 -41
  19. package/src/__tests__/agent-loop-exit-reason.test.ts +35 -93
  20. package/src/__tests__/agent-loop-provider-error-recording.test.ts +1 -1
  21. package/src/__tests__/agent-loop.test.ts +37 -87
  22. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +2 -0
  23. package/src/__tests__/annotate-activity-metadata.test.ts +262 -0
  24. package/src/__tests__/annotate-risk-options.test.ts +2 -3
  25. package/src/__tests__/anthropic-provider.test.ts +95 -2
  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__/approval-routes-http.test.ts +4 -1
  29. package/src/__tests__/assistant-event-hub.test.ts +25 -0
  30. package/src/__tests__/assistant-events-sse-shed.test.ts +8 -0
  31. package/src/__tests__/{conversation-stream-state.test.ts → assistant-stream-state.test.ts} +252 -91
  32. package/src/__tests__/auth-fallback-events-store.test.ts +116 -0
  33. package/src/__tests__/background-workers-disk-pressure.test.ts +6 -0
  34. package/src/__tests__/btw-routes.test.ts +62 -3
  35. package/src/__tests__/build-persisted-content.test.ts +184 -0
  36. package/src/__tests__/catalog-files.test.ts +1 -1
  37. package/src/__tests__/channel-approval-routes.test.ts +1 -1
  38. package/src/__tests__/channel-approvals.test.ts +1 -1
  39. package/src/__tests__/clawhub-files.test.ts +1 -1
  40. package/src/__tests__/compaction-circuit.test.ts +258 -0
  41. package/src/__tests__/compaction-direct.test.ts +132 -0
  42. package/src/__tests__/compaction.benchmark.test.ts +0 -30
  43. package/src/__tests__/config-watcher.test.ts +1 -1
  44. package/src/__tests__/conversation-abort-tool-results.test.ts +57 -19
  45. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +6 -5
  46. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +10 -7
  47. package/src/__tests__/conversation-agent-loop-overflow.test.ts +316 -1143
  48. package/src/__tests__/conversation-agent-loop.test.ts +638 -1655
  49. package/src/__tests__/conversation-analysis-routes.test.ts +6 -0
  50. package/src/__tests__/conversation-clean-command.test.ts +5 -2
  51. package/src/__tests__/conversation-history-web-search.test.ts +11 -1
  52. package/src/__tests__/conversation-pairing.test.ts +4 -31
  53. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +6 -0
  54. package/src/__tests__/conversation-provider-retry-repair.test.ts +30 -10
  55. package/src/__tests__/conversation-queue.test.ts +2 -0
  56. package/src/__tests__/conversation-routes-disk-view.test.ts +3 -0
  57. package/src/__tests__/conversation-routes-slash-commands.test.ts +6 -5
  58. package/src/__tests__/conversation-runtime-assembly.test.ts +310 -300
  59. package/src/__tests__/conversation-runtime-workspace.test.ts +105 -45
  60. package/src/__tests__/conversation-slash-commands.test.ts +8 -42
  61. package/src/__tests__/conversation-slash-queue.test.ts +6 -1
  62. package/src/__tests__/conversation-starter-routes.test.ts +14 -6
  63. package/src/__tests__/conversation-surfaces-action-delivery.test.ts +84 -0
  64. package/src/__tests__/conversation-sync-tags.test.ts +27 -15
  65. package/src/__tests__/conversation-title-service.test.ts +135 -2
  66. package/src/__tests__/conversation-workspace-cache-state.test.ts +17 -16
  67. package/src/__tests__/conversation-workspace-injection.test.ts +67 -2
  68. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +7 -6
  69. package/src/__tests__/conversations-import-system-filter.test.ts +101 -0
  70. package/src/__tests__/cross-provider-web-search.test.ts +214 -1
  71. package/src/__tests__/db-acp-history.test.ts +101 -0
  72. package/src/__tests__/db-schedule-syntax-migration.test.ts +5 -0
  73. package/src/__tests__/dm-persistence.test.ts +5 -1
  74. package/src/__tests__/dynamic-page-surface.test.ts +31 -0
  75. package/src/__tests__/empty-response-hook.test.ts +304 -0
  76. package/src/__tests__/feature-flag-test-helpers.ts +2 -2
  77. package/src/__tests__/file-write-tool.test.ts +63 -0
  78. package/src/__tests__/gateway-only-guard.test.ts +12 -2
  79. package/src/__tests__/gemini-image-service.test.ts +13 -0
  80. package/src/__tests__/guardian-grant-minting.test.ts +1 -1
  81. package/src/__tests__/guardian-routing-invariants.test.ts +2 -4
  82. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +1 -1
  83. package/src/__tests__/heartbeat-disk-pressure.test.ts +1 -0
  84. package/src/__tests__/heartbeat-service.test.ts +1 -0
  85. package/src/__tests__/helpers/mock-provider.ts +110 -0
  86. package/src/__tests__/helpers/native-web-search-harness.ts +129 -0
  87. package/src/__tests__/history-repair-hook.test.ts +1 -0
  88. package/src/__tests__/host-app-control-routes.test.ts +1 -1
  89. package/src/__tests__/host-cu-routes-targeted.test.ts +3 -3
  90. package/src/__tests__/identity-intro-cache.test.ts +12 -100
  91. package/src/__tests__/identity-routes.test.ts +248 -7
  92. package/src/__tests__/inbound-slack-persistence.test.ts +5 -1
  93. package/src/__tests__/injector-background-turn.test.ts +3 -9
  94. package/src/__tests__/injector-chain.test.ts +139 -275
  95. package/src/__tests__/injector-disk-pressure.test.ts +75 -41
  96. package/src/__tests__/injector-document-comments.test.ts +3 -3
  97. package/src/__tests__/injector-pkb-v2-silenced.test.ts +30 -22
  98. package/src/__tests__/injector-v3-suppression.test.ts +31 -37
  99. package/src/__tests__/internal-telemetry-routes.test.ts +109 -0
  100. package/src/__tests__/list-messages-hidden-metadata.test.ts +38 -0
  101. package/src/__tests__/list-messages-page-latest.test.ts +60 -0
  102. package/src/__tests__/list-messages-tool-merge.test.ts +20 -0
  103. package/src/__tests__/llm-usage-store.test.ts +223 -1
  104. package/src/__tests__/memory-retrieval-hook.test.ts +297 -0
  105. package/src/__tests__/memory-v2-static-injector.test.ts +103 -35
  106. package/src/__tests__/native-web-search.test.ts +191 -0
  107. package/src/__tests__/onboarding-template-contract.test.ts +2 -0
  108. package/src/__tests__/openai-image-service.test.ts +17 -0
  109. package/src/__tests__/openai-provider.test.ts +31 -1
  110. package/src/__tests__/{overflow-reduce-pipeline.test.ts → overflow-reduction-loop.test.ts} +64 -284
  111. package/src/__tests__/persist-unsendable-image.test.ts +215 -0
  112. package/src/__tests__/persistence-secret-redaction.test.ts +1 -0
  113. package/src/__tests__/pkb-autoinject.test.ts +2 -5
  114. package/src/__tests__/plugin-api-shim.test.ts +3 -6
  115. package/src/__tests__/plugin-bootstrap.test.ts +14 -40
  116. package/src/__tests__/plugin-registry.test.ts +3 -76
  117. package/src/__tests__/plugin-types.test.ts +0 -193
  118. package/src/__tests__/process-message-display-content.test.ts +6 -2
  119. package/src/__tests__/reaction-persistence.test.ts +1 -1
  120. package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +5 -1
  121. package/src/__tests__/resolve-trust-class.test.ts +4 -4
  122. package/src/__tests__/runtime-events-sse-reconnect.test.ts +60 -23
  123. package/src/__tests__/schedule-routes.test.ts +603 -2
  124. package/src/__tests__/schedule-store.test.ts +41 -0
  125. package/src/__tests__/schedule-tools.test.ts +35 -0
  126. package/src/__tests__/send-endpoint-busy.test.ts +4 -1
  127. package/src/__tests__/server-history-render.test.ts +314 -1
  128. package/src/__tests__/skill-feature-flags-integration.test.ts +33 -0
  129. package/src/__tests__/skillssh-files.test.ts +1 -1
  130. package/src/__tests__/subagent-call-site-routing.test.ts +1 -1
  131. package/src/__tests__/subagent-fork-notifications.test.ts +1 -3
  132. package/src/__tests__/subagent-fork-spawn.test.ts +1 -1
  133. package/src/__tests__/subagent-manager-notify.test.ts +1 -3
  134. package/src/__tests__/subagent-notify-parent.test.ts +1 -3
  135. package/src/__tests__/subagent-spawn-tool-fork.test.ts +1 -1
  136. package/src/__tests__/system-prompt.test.ts +20 -0
  137. package/src/__tests__/task-scheduler.test.ts +162 -1
  138. package/src/__tests__/terminal-tools.test.ts +6 -1
  139. package/src/__tests__/title-generate-hook.test.ts +319 -0
  140. package/src/__tests__/tool-error-hook.test.ts +278 -0
  141. package/src/__tests__/tool-preview-lifecycle.test.ts +468 -5
  142. package/src/__tests__/tool-result-metadata-plumbing.test.ts +1 -0
  143. package/src/__tests__/tool-result-truncate-hook.test.ts +127 -0
  144. package/src/__tests__/tool-result-truncation.test.ts +0 -2
  145. package/src/__tests__/ui-choice-copy-surfaces.test.ts +254 -0
  146. package/src/__tests__/ui-work-result-surface.test.ts +159 -0
  147. package/src/__tests__/usage-routes.test.ts +285 -1
  148. package/src/__tests__/user-plugin-loader.test.ts +54 -286
  149. package/src/__tests__/voice-session-bridge.test.ts +6 -3
  150. package/src/__tests__/web-search-backend-failure.test.ts +166 -0
  151. package/src/acp/__tests__/agent-process.test.ts +161 -0
  152. package/src/acp/__tests__/client-handler.test.ts +40 -0
  153. package/src/acp/__tests__/helpers/acp-history-db.ts +82 -0
  154. package/src/acp/__tests__/helpers/exec-file-stub.ts +101 -0
  155. package/src/acp/__tests__/prepare-agent-env.test.ts +137 -0
  156. package/src/acp/__tests__/session-manager-persistence.test.ts +95 -28
  157. package/src/acp/__tests__/session-manager-resume.test.ts +736 -0
  158. package/src/acp/agent-process.ts +61 -1
  159. package/src/acp/auto-install.test.ts +196 -0
  160. package/src/acp/auto-install.ts +177 -0
  161. package/src/acp/client-handler.ts +31 -0
  162. package/src/acp/feature-gate.test.ts +48 -0
  163. package/src/acp/feature-gate.ts +34 -0
  164. package/src/acp/prepare-agent-env.ts +83 -29
  165. package/src/acp/resolve-agent.test.ts +320 -7
  166. package/src/acp/resolve-agent.ts +182 -18
  167. package/src/acp/resume-hint.ts +25 -0
  168. package/src/acp/session-manager.ts +495 -73
  169. package/src/acp/types.ts +8 -0
  170. package/src/agent/compaction-circuit.ts +60 -102
  171. package/src/agent/loop.ts +362 -485
  172. package/src/api/events/assistant-thinking-delta.ts +33 -0
  173. package/src/api/events/tool-output-chunk.ts +45 -0
  174. package/src/api/events/tool-use-preview-start.ts +32 -0
  175. package/src/api/events/trace-event.ts +69 -0
  176. package/src/api/index.ts +48 -13
  177. package/src/api/responses/conversation-message.ts +374 -0
  178. package/src/approvals/guardian-request-resolvers.ts +1 -1
  179. package/src/avatar/__tests__/avatar-store.test.ts +34 -29
  180. package/src/background-wake/next-wake.ts +1 -0
  181. package/src/cli/commands/__tests__/notifications.test.ts +58 -14
  182. package/src/cli/commands/notifications.ts +112 -60
  183. package/src/config/__tests__/feature-flag-registry-guard.test.ts +2 -2
  184. package/src/config/acp-defaults.test.ts +10 -0
  185. package/src/config/acp-defaults.ts +6 -0
  186. package/src/config/assistant-feature-flags.ts +22 -11
  187. package/src/config/bundled-skills/acp/SKILL.md +83 -31
  188. package/src/config/bundled-skills/acp/TOOLS.json +4 -4
  189. package/src/config/bundled-skills/app-builder/SKILL.md +224 -398
  190. package/src/config/bundled-skills/app-builder/TOOLS.json +29 -0
  191. package/src/config/bundled-skills/app-builder/references/DESIGN_SYSTEM.md +48 -0
  192. package/src/config/bundled-skills/app-builder/references/RESPONSIVE.md +57 -0
  193. package/src/config/bundled-skills/app-builder/references/SLIDES.md +38 -0
  194. package/src/config/bundled-skills/app-builder/references/examples/README.md +17 -0
  195. package/src/config/bundled-skills/app-builder/references/examples/expense-tracker.md +515 -0
  196. package/src/config/bundled-skills/app-builder/references/examples/focus-timer.md +342 -0
  197. package/src/config/bundled-skills/app-builder/references/examples/habit-tracker.md +490 -0
  198. package/src/config/bundled-skills/app-builder/tools/app-list.ts +62 -0
  199. package/src/config/bundled-skills/document-editor/SKILL.md +28 -23
  200. package/src/config/bundled-skills/document-editor/TOOLS.json +1 -1
  201. package/src/config/bundled-skills/messaging/SKILL.md +0 -7
  202. package/src/config/bundled-tool-registry.ts +2 -0
  203. package/src/config/feature-flag-cache.ts +3 -3
  204. package/src/config/feature-flag-registry.json +48 -7
  205. package/src/config/schemas/__tests__/memory-v2.test.ts +1 -0
  206. package/src/config/schemas/__tests__/memory-v3.test.ts +25 -0
  207. package/src/config/schemas/heartbeat.ts +9 -0
  208. package/src/config/schemas/llm.ts +1 -0
  209. package/src/config/schemas/memory-v2.ts +8 -0
  210. package/src/config/schemas/memory-v3.ts +8 -0
  211. package/src/config/schemas/platform.ts +8 -0
  212. package/src/config/seed-inference-profiles.ts +2 -2
  213. package/src/config/skills.ts +13 -0
  214. package/src/context/compactor.ts +1 -1
  215. package/src/context/strip-injections.ts +128 -0
  216. package/src/context/token-estimator.ts +23 -0
  217. package/src/context/tool-result-truncation.ts +0 -23
  218. package/src/context/window-manager.ts +5 -7
  219. package/src/credential-execution/executable-discovery.ts +16 -0
  220. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +6 -0
  221. package/src/daemon/__tests__/inference-profile-notification.test.ts +153 -0
  222. package/src/daemon/__tests__/native-web-search-metadata.test.ts +10 -8
  223. package/src/daemon/assistant-attachments.ts +1 -1
  224. package/src/daemon/config-watcher.ts +2 -2
  225. package/src/daemon/context-overflow-reducer.ts +0 -1
  226. package/src/daemon/conversation-agent-loop-handlers.ts +594 -153
  227. package/src/daemon/conversation-agent-loop.ts +301 -997
  228. package/src/daemon/conversation-history.ts +5 -4
  229. package/src/daemon/conversation-lifecycle.ts +3 -4
  230. package/src/daemon/conversation-messaging.ts +7 -6
  231. package/src/daemon/conversation-process.ts +11 -16
  232. package/src/daemon/conversation-registry.ts +159 -0
  233. package/src/daemon/conversation-runtime-assembly.ts +218 -398
  234. package/src/daemon/conversation-slash.ts +6 -25
  235. package/src/daemon/conversation-store.ts +9 -90
  236. package/src/daemon/conversation-surfaces.ts +222 -4
  237. package/src/daemon/conversation-tool-setup.ts +2 -29
  238. package/src/daemon/conversation-workspace.ts +17 -0
  239. package/src/daemon/conversation.ts +32 -20
  240. package/src/daemon/external-plugins-bootstrap.ts +17 -18
  241. package/src/daemon/handlers/config-a2a.ts +51 -36
  242. package/src/daemon/handlers/config-slack-channel.ts +20 -14
  243. package/src/daemon/handlers/config-telegram.ts +16 -2
  244. package/src/daemon/handlers/conversations.ts +3 -1
  245. package/src/daemon/handlers/shared.ts +156 -84
  246. package/src/daemon/handlers/skills.ts +42 -10
  247. package/src/daemon/lifecycle.ts +25 -0
  248. package/src/daemon/message-types/apps.ts +1 -29
  249. package/src/daemon/message-types/messages.ts +9 -57
  250. package/src/daemon/message-types/skills.ts +2 -0
  251. package/src/daemon/message-types/surfaces.ts +136 -3
  252. package/src/daemon/now-scratchpad.ts +21 -0
  253. package/src/daemon/orphan-reaper.test.ts +210 -0
  254. package/src/daemon/orphan-reaper.ts +240 -0
  255. package/src/daemon/overflow-reduction-loop.ts +230 -0
  256. package/src/daemon/persist-unsendable-image.ts +117 -0
  257. package/src/daemon/process-message.ts +1 -3
  258. package/src/daemon/server.ts +2 -0
  259. package/src/daemon/trace-emitter.ts +6 -4
  260. package/src/daemon/trust-context.ts +19 -0
  261. package/src/daemon/wake-target-adapter.ts +3 -1
  262. package/src/heartbeat/__tests__/heartbeat-service.test.ts +3 -0
  263. package/src/heartbeat/heartbeat-run-store.ts +23 -1
  264. package/src/heartbeat/heartbeat-service.ts +26 -0
  265. package/src/home/home-greeting-cache.ts +24 -1
  266. package/src/ipc/__tests__/browser-ipc.test.ts +1 -1
  267. package/src/ipc/__tests__/ui-request-route.test.ts +3 -3
  268. package/src/ipc/gateway-client.test.ts +2 -2
  269. package/src/ipc/gateway-client.ts +3 -3
  270. package/src/ipc/skill-routes/__tests__/memory.test.ts +15 -0
  271. package/src/ipc/skill-routes/memory.ts +4 -2
  272. package/src/media/gemini-image-service.ts +15 -0
  273. package/src/media/openai-image-service.ts +14 -0
  274. package/src/media/types.ts +34 -0
  275. package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +56 -0
  276. package/src/memory/auth-fallback-events-store.ts +94 -0
  277. package/src/memory/conversation-starter-checkpoints.ts +1 -0
  278. package/src/memory/conversation-title-service.ts +65 -41
  279. package/src/memory/db-init.ts +6 -0
  280. package/src/memory/graph/__tests__/conversation-graph-memory-registry.test.ts +119 -0
  281. package/src/memory/graph/conversation-graph-memory.ts +65 -0
  282. package/src/memory/job-handlers/conversation-starters.ts +13 -2
  283. package/src/memory/jobs-store.ts +33 -0
  284. package/src/memory/jobs-worker.ts +32 -5
  285. package/src/memory/llm-usage-store.ts +224 -50
  286. package/src/memory/migrations/222-strip-placeholder-sentinels-from-messages.ts +6 -5
  287. package/src/memory/migrations/270-schedule-source-conversation.ts +13 -0
  288. package/src/memory/migrations/271-create-auth-fallback-events.ts +21 -0
  289. package/src/memory/migrations/272-acp-session-history-cwd.ts +36 -0
  290. package/src/memory/migrations/index.ts +3 -0
  291. package/src/memory/pkb/autoinject.ts +61 -0
  292. package/src/memory/pkb/context.ts +50 -0
  293. package/src/memory/pkb/types.ts +14 -0
  294. package/src/memory/schedule-attribution-sql.ts +104 -0
  295. package/src/memory/schema/acp.ts +4 -0
  296. package/src/memory/schema/infrastructure.ts +16 -0
  297. package/src/memory/usage-grouped-buckets.ts +6 -1
  298. package/src/memory/v2/__tests__/consolidation-job.test.ts +4 -4
  299. package/src/memory/v2/consolidation-job.ts +14 -5
  300. package/src/notifications/conversation-pairing.ts +8 -15
  301. package/src/notifications/decision-engine.ts +6 -3
  302. package/src/notifications/home-feed-side-effect.ts +12 -1
  303. package/src/permissions/prompter.ts +4 -0
  304. package/src/plugin-api/constants.ts +4 -0
  305. package/src/plugin-api/index.ts +7 -5
  306. package/src/plugin-api/types.ts +151 -1
  307. package/src/plugins/defaults/compaction/compact.ts +59 -0
  308. package/src/plugins/defaults/compaction/package.json +1 -1
  309. package/src/plugins/defaults/compaction/register.ts +8 -19
  310. package/src/plugins/defaults/empty-response/hooks/stop.ts +126 -0
  311. package/src/plugins/defaults/empty-response/register.ts +8 -13
  312. package/src/plugins/defaults/index.ts +2 -18
  313. package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +95 -0
  314. package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit-temp.ts +216 -0
  315. package/src/plugins/defaults/memory-retrieval/injector-chain.ts +35 -0
  316. package/src/plugins/defaults/{injectors/register.ts → memory-retrieval/injectors.ts} +288 -81
  317. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/assign.test.ts +4 -4
  318. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/health.test.ts +16 -0
  319. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/live-integration.test.ts +4 -4
  320. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/maintain-job.test.ts +5 -5
  321. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/orchestrate.test.ts +48 -12
  322. package/src/plugins/defaults/memory-v3-shadow/__tests__/provider-blocks.test.ts +13 -0
  323. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/reconcile.test.ts +2 -2
  324. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/render-injection.test.ts +1 -1
  325. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/router.test.ts +104 -32
  326. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/selection-log-store.test.ts +8 -8
  327. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/selector.test.ts +96 -30
  328. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/shadow-plugin.test.ts +34 -16
  329. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/assign.ts +5 -5
  330. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/capabilities.ts +2 -2
  331. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/health.ts +0 -0
  332. package/src/plugins/defaults/memory-v3-shadow/hooks/post-compact.ts +14 -0
  333. package/src/plugins/defaults/memory-v3-shadow/hooks/user-prompt-submit.ts +19 -0
  334. package/src/plugins/defaults/memory-v3-shadow/injector.ts +75 -0
  335. package/src/plugins/defaults/memory-v3-shadow/llm-retry.ts +32 -0
  336. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/maintain-job.ts +8 -8
  337. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/orchestrate.ts +26 -14
  338. package/src/plugins/defaults/{llm-call → memory-v3-shadow}/package.json +2 -2
  339. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/page-content.ts +2 -2
  340. package/src/plugins/defaults/memory-v3-shadow/provider-blocks.ts +26 -0
  341. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/reconcile.ts +3 -3
  342. package/src/plugins/defaults/memory-v3-shadow/register.ts +26 -0
  343. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/render-injection.ts +1 -1
  344. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/router.ts +51 -45
  345. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/selection-log-store.ts +4 -4
  346. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/selector.ts +61 -46
  347. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/shadow-plugin.ts +69 -99
  348. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/tree.ts +1 -1
  349. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/types.ts +8 -0
  350. package/src/plugins/defaults/title-generate/hooks/stop.ts +75 -0
  351. package/src/plugins/defaults/title-generate/hooks/user-prompt-submit.ts +35 -0
  352. package/src/plugins/defaults/title-generate/package.json +1 -1
  353. package/src/plugins/defaults/title-generate/register.ts +18 -18
  354. package/src/plugins/defaults/tool-error/hooks/post-tool-use.ts +118 -0
  355. package/src/plugins/defaults/tool-error/package.json +1 -1
  356. package/src/plugins/defaults/tool-error/register.ts +9 -21
  357. package/src/plugins/defaults/tool-result-truncate/hooks/post-tool-use.ts +32 -0
  358. package/src/plugins/defaults/tool-result-truncate/register.ts +10 -21
  359. package/src/plugins/defaults/tool-result-truncate/terminal.ts +37 -18
  360. package/src/plugins/external-api.ts +2 -2
  361. package/src/plugins/pipeline.ts +6 -305
  362. package/src/plugins/registry.ts +10 -55
  363. package/src/plugins/types.ts +62 -797
  364. package/src/plugins/user-loader.ts +30 -127
  365. package/src/proactive-artifact/aux-message-injector.ts +4 -4
  366. package/src/proactive-artifact/job.test.ts +8 -13
  367. package/src/prompts/__tests__/system-prompt.test.ts +42 -0
  368. package/src/prompts/templates/BOOTSTRAP-ACTIVATION-RAIL.md +64 -0
  369. package/src/prompts/templates/BOOTSTRAP.md +2 -2
  370. package/src/prompts/templates/system-sections.ts +15 -0
  371. package/src/providers/anthropic/client.ts +37 -29
  372. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +112 -0
  373. package/src/providers/openai/chat-completions-provider.ts +44 -0
  374. package/src/providers/openrouter/client.ts +1 -0
  375. package/src/providers/placeholder-sentinels.ts +35 -0
  376. package/src/runtime/__tests__/agent-wake.test.ts +10 -6
  377. package/src/runtime/__tests__/interactive-ui.test.ts +1 -1
  378. package/src/runtime/agent-wake.ts +2 -5
  379. package/src/runtime/assistant-event-hub.ts +37 -7
  380. package/src/runtime/{conversation-stream-state.ts → assistant-stream-state.ts} +132 -58
  381. package/src/runtime/channel-approvals.ts +1 -1
  382. package/src/runtime/http-router.ts +16 -21
  383. package/src/runtime/http-types.ts +16 -70
  384. package/src/runtime/interactive-ui.ts +1 -1
  385. package/src/runtime/pending-interactions.ts +1 -0
  386. package/src/runtime/routes/__tests__/acp-routes.test.ts +283 -55
  387. package/src/runtime/routes/__tests__/consolidation-routes.test.ts +265 -2
  388. package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +1 -1
  389. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +31 -1
  390. package/src/runtime/routes/__tests__/memory-v2-routes.test.ts +6 -2
  391. package/src/runtime/routes/__tests__/surface-action-routes.test.ts +5 -4
  392. package/src/runtime/routes/__tests__/surface-content-routes.test.ts +4 -1
  393. package/src/runtime/routes/__tests__/tts-routes.test.ts +6 -2
  394. package/src/runtime/routes/acp-routes.test.ts +89 -25
  395. package/src/runtime/routes/acp-routes.ts +81 -29
  396. package/src/runtime/routes/app-management-routes.ts +6 -117
  397. package/src/runtime/routes/app-routes.ts +13 -15
  398. package/src/runtime/routes/approval-routes.ts +1 -1
  399. package/src/runtime/routes/attachment-routes.ts +26 -15
  400. package/src/runtime/routes/avatar-routes.ts +26 -0
  401. package/src/runtime/routes/browser-routes.ts +1 -1
  402. package/src/runtime/routes/browser-tabs-routes.ts +6 -10
  403. package/src/runtime/routes/btw-routes.ts +29 -23
  404. package/src/runtime/routes/consolidation-routes.ts +120 -20
  405. package/src/runtime/routes/conversation-cli-routes.ts +1 -1
  406. package/src/runtime/routes/conversation-list-routes.ts +1 -1
  407. package/src/runtime/routes/conversation-query-routes.ts +3 -1
  408. package/src/runtime/routes/conversation-routes.ts +372 -185
  409. package/src/runtime/routes/conversation-starter-routes.ts +13 -7
  410. package/src/runtime/routes/conversations-import-routes.ts +24 -7
  411. package/src/runtime/routes/documents-routes.ts +4 -0
  412. package/src/runtime/routes/domain-routes.ts +51 -37
  413. package/src/runtime/routes/epoch-millis-range.ts +34 -0
  414. package/src/runtime/routes/events-routes.ts +28 -34
  415. package/src/runtime/routes/gateway-log-routes.ts +26 -4
  416. package/src/runtime/routes/heartbeat-routes.ts +32 -12
  417. package/src/runtime/routes/host-app-control-routes.ts +1 -1
  418. package/src/runtime/routes/host-cu-routes.ts +1 -1
  419. package/src/runtime/routes/identity-intro-cache.ts +11 -34
  420. package/src/runtime/routes/identity-routes.ts +224 -18
  421. package/src/runtime/routes/image-generation-routes.ts +40 -2
  422. package/src/runtime/routes/inbound-message-handler.ts +1 -1
  423. package/src/runtime/routes/index.ts +2 -0
  424. package/src/runtime/routes/integrations/a2a.ts +12 -10
  425. package/src/runtime/routes/integrations/slack/__tests__/channel.test.ts +16 -0
  426. package/src/runtime/routes/integrations/slack/channel.ts +4 -0
  427. package/src/runtime/routes/integrations/slack/share.ts +27 -6
  428. package/src/runtime/routes/integrations/telegram.ts +6 -0
  429. package/src/runtime/routes/integrations/twilio.ts +42 -0
  430. package/src/runtime/routes/internal-telemetry-routes.ts +88 -0
  431. package/src/runtime/routes/log-export-routes.ts +8 -0
  432. package/src/runtime/routes/memory-v2-routes.ts +15 -8
  433. package/src/runtime/routes/memory-v3-routes.ts +66 -34
  434. package/src/runtime/routes/oauth-apps.ts +66 -12
  435. package/src/runtime/routes/oauth-providers.ts +44 -5
  436. package/src/runtime/routes/platform-routes.ts +81 -5
  437. package/src/runtime/routes/playground/__tests__/force-compact.test.ts +6 -4
  438. package/src/runtime/routes/playground/force-compact.ts +1 -1
  439. package/src/runtime/routes/playground/helpers.ts +1 -1
  440. package/src/runtime/routes/rename-conversation-routes.ts +5 -0
  441. package/src/runtime/routes/schedule-routes.ts +152 -42
  442. package/src/runtime/routes/secret-routes.ts +14 -2
  443. package/src/runtime/routes/skills-routes.ts +43 -14
  444. package/src/runtime/routes/surface-conversation-resolver.ts +4 -3
  445. package/src/runtime/routes/tool-call-confirmation-enrichment.test.ts +161 -0
  446. package/src/runtime/routes/tool-call-confirmation-enrichment.ts +107 -0
  447. package/src/runtime/routes/trust-rules-routes.ts +26 -2
  448. package/src/runtime/routes/tts-routes.ts +35 -0
  449. package/src/runtime/routes/types.ts +66 -8
  450. package/src/runtime/routes/usage-routes.ts +47 -39
  451. package/src/runtime/routes/webhook-routes.ts +41 -2
  452. package/src/runtime/routes/work-items-routes.ts +2 -4
  453. package/src/runtime/routes/workspace-routes.ts +4 -0
  454. package/src/runtime/services/__tests__/analyze-conversation.test.ts +6 -0
  455. package/src/runtime/services/analyze-conversation.ts +2 -2
  456. package/src/runtime/services/conversation-serializer.ts +1 -1
  457. package/src/schedule/schedule-store.ts +20 -1
  458. package/src/schedule/schedule-usage-store.ts +83 -0
  459. package/src/schedule/scheduler.ts +12 -5
  460. package/src/signals/cancel.ts +2 -4
  461. package/src/skills/catalog-files.ts +2 -2
  462. package/src/skills/catalog-install.ts +3 -0
  463. package/src/skills/categories-cache.ts +118 -0
  464. package/src/skills/clawhub-files.ts +1 -2
  465. package/src/skills/skillssh-files.ts +1 -2
  466. package/src/subagent/manager.ts +17 -5
  467. package/src/telemetry/types.ts +29 -1
  468. package/src/telemetry/usage-telemetry-reporter.test.ts +112 -3
  469. package/src/telemetry/usage-telemetry-reporter.ts +57 -2
  470. package/src/tools/acp/context.ts +20 -0
  471. package/src/tools/acp/list-agents.test.ts +7 -1
  472. package/src/tools/acp/spawn.test.ts +158 -55
  473. package/src/tools/acp/spawn.ts +47 -72
  474. package/src/tools/acp/steer.test.ts +105 -8
  475. package/src/tools/acp/steer.ts +48 -17
  476. package/src/tools/apps/executors.ts +13 -8
  477. package/src/tools/executor.ts +1 -53
  478. package/src/tools/filesystem/write.ts +34 -0
  479. package/src/tools/network/__tests__/web-search-metadata.test.ts +7 -1
  480. package/src/tools/network/__tests__/web-search.test.ts +11 -3
  481. package/src/tools/network/web-search-error.test.ts +248 -0
  482. package/src/tools/network/web-search-error.ts +267 -0
  483. package/src/tools/network/web-search.ts +207 -48
  484. package/src/tools/schedule/create.ts +2 -0
  485. package/src/tools/subagent/spawn.ts +2 -4
  486. package/src/tools/terminal/safe-env.ts +10 -1
  487. package/src/tools/ui-surface/definitions.ts +34 -5
  488. package/src/tts/__tests__/provider-catalog-consistency.test.ts +85 -1
  489. package/src/tts/provider-catalog.ts +76 -1
  490. package/src/util/mutex.ts +47 -0
  491. package/src/workspace/git-service.ts +1 -42
  492. package/src/workspace/migrations/051-seed-conversation-summarization-callsite.ts +4 -5
  493. package/src/workspace/migrations/095-bump-heartbeat-interval-30m-to-60m.ts +51 -0
  494. package/src/workspace/migrations/096-reduce-quality-profile-effort.ts +72 -0
  495. package/src/workspace/migrations/097-enable-adaptive-thinking-managed-profiles.ts +117 -0
  496. package/src/workspace/migrations/registry.ts +6 -0
  497. package/docs/plugins.md +0 -836
  498. package/examples/plugins/echo/register.ts +0 -184
  499. package/src/__tests__/bootstrap-turn-cleanup.test.ts +0 -44
  500. package/src/__tests__/circuit-breaker-pipeline.test.ts +0 -405
  501. package/src/__tests__/compaction-pipeline.test.ts +0 -210
  502. package/src/__tests__/compaction-timeout-recovery.test.ts +0 -251
  503. package/src/__tests__/empty-response-pipeline.test.ts +0 -423
  504. package/src/__tests__/llm-call-pipeline.test.ts +0 -287
  505. package/src/__tests__/memory-retrieval-pipeline.test.ts +0 -418
  506. package/src/__tests__/persistence-pipeline.test.ts +0 -503
  507. package/src/__tests__/pipeline-runner.test.ts +0 -564
  508. package/src/__tests__/title-generate-pipeline.test.ts +0 -211
  509. package/src/__tests__/token-estimate-pipeline.test.ts +0 -479
  510. package/src/__tests__/tool-error-pipeline.test.ts +0 -241
  511. package/src/__tests__/tool-execute-pipeline.test.ts +0 -417
  512. package/src/__tests__/tool-result-truncate-pipeline.test.ts +0 -341
  513. package/src/daemon/bootstrap-turn-cleanup.ts +0 -45
  514. package/src/gallery/default-gallery.ts +0 -1359
  515. package/src/gallery/gallery-manifest.ts +0 -28
  516. package/src/home/feature-gate.ts +0 -22
  517. package/src/memory/v3/provider-blocks.ts +0 -16
  518. package/src/plugins/defaults/circuit-breaker/middlewares/circuitBreaker.ts +0 -93
  519. package/src/plugins/defaults/circuit-breaker/package.json +0 -15
  520. package/src/plugins/defaults/circuit-breaker/register.ts +0 -39
  521. package/src/plugins/defaults/compaction/middlewares/compaction.ts +0 -25
  522. package/src/plugins/defaults/compaction/terminal.ts +0 -73
  523. package/src/plugins/defaults/empty-response/middlewares/emptyResponse.ts +0 -22
  524. package/src/plugins/defaults/empty-response/terminal.ts +0 -106
  525. package/src/plugins/defaults/injectors/package.json +0 -15
  526. package/src/plugins/defaults/llm-call/middlewares/llmCall.ts +0 -17
  527. package/src/plugins/defaults/llm-call/register.ts +0 -45
  528. package/src/plugins/defaults/memory-retrieval/middlewares/memoryRetrieval.ts +0 -17
  529. package/src/plugins/defaults/memory-retrieval/package.json +0 -15
  530. package/src/plugins/defaults/memory-retrieval/register.ts +0 -181
  531. package/src/plugins/defaults/overflow-reduce/middlewares/overflowReduce.ts +0 -126
  532. package/src/plugins/defaults/overflow-reduce/package.json +0 -15
  533. package/src/plugins/defaults/overflow-reduce/register.ts +0 -42
  534. package/src/plugins/defaults/persistence/middlewares/persistence.ts +0 -19
  535. package/src/plugins/defaults/persistence/package.json +0 -15
  536. package/src/plugins/defaults/persistence/register.ts +0 -38
  537. package/src/plugins/defaults/persistence/terminal.ts +0 -83
  538. package/src/plugins/defaults/title-generate/terminal.ts +0 -31
  539. package/src/plugins/defaults/token-estimate/middlewares/tokenEstimate.ts +0 -23
  540. package/src/plugins/defaults/token-estimate/package.json +0 -15
  541. package/src/plugins/defaults/token-estimate/register.ts +0 -34
  542. package/src/plugins/defaults/token-estimate/terminal.ts +0 -40
  543. package/src/plugins/defaults/tool-error/middlewares/toolError.ts +0 -21
  544. package/src/plugins/defaults/tool-error/terminal.ts +0 -47
  545. package/src/plugins/defaults/tool-execute/middlewares/toolExecute.ts +0 -23
  546. package/src/plugins/defaults/tool-execute/package.json +0 -15
  547. package/src/plugins/defaults/tool-execute/register.ts +0 -49
  548. package/src/plugins/defaults/tool-result-truncate/middlewares/toolResultTruncate.ts +0 -23
  549. package/src/plugins/defaults/tool-result-truncate/types.ts +0 -22
  550. package/src/skills/category-inference.ts +0 -111
  551. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/capabilities.test.ts +0 -0
  552. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/core.test.ts +0 -0
  553. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/fixtures/eval-turns.json +0 -0
  554. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/fixtures/live-turns.json +0 -0
  555. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/needle.test.ts +0 -0
  556. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/snapshot.test.ts +0 -0
  557. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/tree.test.ts +0 -0
  558. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/types.test.ts +0 -0
  559. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/working-set-eviction.test.ts +0 -0
  560. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/working-set-skeleton.test.ts +0 -0
  561. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/core.ts +0 -0
  562. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/README.md +0 -0
  563. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/assignments.json +0 -0
  564. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/core.json +0 -0
  565. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-a/topic-x.md +0 -0
  566. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-a/topic-y.md +0 -0
  567. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-b/topic-z.md +0 -0
  568. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/needle.ts +0 -0
  569. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/snapshot.ts +0 -0
  570. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/working-set.ts +0 -0
@@ -1,17 +1,22 @@
1
1
  /**
2
- * Tests for the user plugin loader (PR 29).
2
+ * Tests for the user plugin loader.
3
3
  *
4
4
  * Redirects `getWorkspaceDir()` into a per-test temp directory via
5
5
  * `VELLUM_WORKSPACE_DIR` so `loadUserPlugins()` walks an isolated tree
6
6
  * that we populate on demand.
7
7
  *
8
- * Covers:
9
- * - A plugin whose `register.ts` calls `registerPlugin()` at import time
10
- * ends up in the registry after `loadUserPlugins()` resolves.
11
- * - A plugin whose `register.ts` throws during import is logged + skipped;
12
- * other plugins in the same directory still load.
13
- * - A missing `getWorkspaceDir()/plugins/` directory is a no-op (zero installed
14
- * user plugins is the default shape of a fresh daemon).
8
+ * The loader's own responsibility is directory discovery and dispatch: it
9
+ * scans `<workspaceDir>/plugins/*` and hands every subdirectory carrying a
10
+ * `package.json` to `loadExternalPlugin`. The plugin-build mechanics
11
+ * (manifest parsing, hook/tool wiring, the per-plugin timeout, error
12
+ * isolation) are covered directly in `external-plugin-loader.test.ts`; here
13
+ * we assert the discovery contract:
14
+ *
15
+ * - A directory with a `package.json` is loaded and registered.
16
+ * - A directory without a `package.json` is skipped silently.
17
+ * - A missing `getWorkspaceDir()/plugins/` directory is a no-op (zero
18
+ * installed user plugins is the default shape of a fresh daemon).
19
+ * - One failing plugin does not prevent a sibling from registering.
15
20
  */
16
21
  import { mkdirSync, rmSync, writeFileSync } from "node:fs";
17
22
  import { tmpdir } from "node:os";
@@ -26,7 +31,6 @@ import { loadUserPlugins } from "../plugins/user-loader.js";
26
31
 
27
32
  // Isolate every run under its own tempdir so parallel test files (and
28
33
  // repeated runs of this file) cannot collide on `<workspaceDir>/plugins/`.
29
- // Each describe-scope gets a fresh subdirectory.
30
34
  const TEST_WORKSPACE_DIR = join(
31
35
  tmpdir(),
32
36
  `vellum-user-plugin-loader-test-${process.pid}-${Date.now()}`,
@@ -37,30 +41,26 @@ process.env.VELLUM_WORKSPACE_DIR = TEST_WORKSPACE_DIR;
37
41
  const PLUGINS_DIR = join(TEST_WORKSPACE_DIR, "plugins");
38
42
 
39
43
  /**
40
- * Write a plugin directory with a `register.ts` (TypeScript source, so bun
41
- * can import it at test time without a build step) that executes the given
42
- * body. The body has access to `registerPlugin` via a relative import back
43
- * into the repo's registry module.
44
- *
45
- * `relativeRegistryImport` points from the synthetic plugin file at
46
- * `<TEST_WORKSPACE_DIR>/plugins/<name>/register.ts` to the real
47
- * registry source at `<repo>/assistant/src/plugins/registry.ts`. Using a
48
- * relative path (rather than a project-root alias) keeps the test hermetic
49
- * and matches how an on-disk user plugin would actually import the
50
- * registry's public API in a real install.
44
+ * Write a directory-convention plugin: a `package.json` manifest plus any
45
+ * surface files (`hooks/<name>.ts`, `tools/<name>.ts`) whose default export
46
+ * the loader wires up.
51
47
  */
52
- function writePlugin(name: string, body: string): void {
48
+ function writePlugin(
49
+ name: string,
50
+ pkg: Record<string, unknown>,
51
+ files: Record<string, string> = {},
52
+ ): void {
53
53
  const pluginDir = join(PLUGINS_DIR, name);
54
54
  mkdirSync(pluginDir, { recursive: true });
55
- // Resolve the absolute path to the registry module so the synthetic
56
- // register.ts can import it. bun happily resolves `.ts` files at runtime
57
- // when the test suite itself is running in source mode.
58
- const registryPath = join(import.meta.dir, "..", "plugins", "registry.ts");
59
- const registerSource = `
60
- import { registerPlugin } from ${JSON.stringify(registryPath)};
61
- ${body}
62
- `;
63
- writeFileSync(join(pluginDir, "register.ts"), registerSource);
55
+ writeFileSync(join(pluginDir, "package.json"), JSON.stringify(pkg, null, 2));
56
+ for (const [rel, body] of Object.entries(files)) {
57
+ const parts = rel.split("/");
58
+ parts.pop();
59
+ if (parts.length > 0) {
60
+ mkdirSync(join(pluginDir, ...parts), { recursive: true });
61
+ }
62
+ writeFileSync(join(pluginDir, rel), body);
63
+ }
64
64
  }
65
65
 
66
66
  function clearPluginsDir(): void {
@@ -73,17 +73,14 @@ describe("user plugin loader", () => {
73
73
  clearPluginsDir();
74
74
  });
75
75
 
76
- test("loads a valid plugin whose register.ts calls registerPlugin()", async () => {
76
+ test("loads a plugin via its package.json manifest and registers it", async () => {
77
77
  writePlugin(
78
78
  "my-plugin",
79
- `
80
- registerPlugin({
81
- manifest: {
82
- name: "my-plugin",
83
- version: "0.0.1",
84
- },
85
- });
86
- `,
79
+ { name: "my-plugin", version: "0.1.0" },
80
+ {
81
+ "hooks/init.ts":
82
+ "export default async function init(_ctx: unknown): Promise<void> {}\n",
83
+ },
87
84
  );
88
85
 
89
86
  await loadUserPlugins();
@@ -91,34 +88,22 @@ registerPlugin({
91
88
  const registered = getRegisteredPlugins();
92
89
  expect(registered).toHaveLength(1);
93
90
  expect(registered[0]?.manifest.name).toBe("my-plugin");
91
+ expect(typeof registered[0]?.hooks?.init).toBe("function");
94
92
  });
95
93
 
96
94
  test("per-plugin failure is isolated: other plugins still load", async () => {
97
- // Plugin A throws at import time. The loader must log and move on so
98
- // Plugin B still ends up registered — one bad user plugin cannot brick
99
- // the entire user-plugin surface or crash the daemon.
100
- writePlugin(
101
- "broken-plugin",
102
- `
103
- throw new Error("boom at import time");
104
- `,
105
- );
106
- writePlugin(
107
- "good-plugin",
108
- `
109
- registerPlugin({
110
- manifest: {
111
- name: "good-plugin",
112
- version: "0.0.1",
113
- },
114
- });
115
- `,
116
- );
95
+ // Plugin A has a malformed package.json; the loader must isolate the
96
+ // failure and still register the healthy sibling — one bad user plugin
97
+ // cannot brick the entire user-plugin surface or crash the daemon.
98
+ const brokenDir = join(PLUGINS_DIR, "broken-plugin");
99
+ mkdirSync(brokenDir, { recursive: true });
100
+ writeFileSync(join(brokenDir, "package.json"), "{ not valid json");
101
+
102
+ writePlugin("good-plugin", { name: "good-plugin", version: "0.1.0" });
117
103
 
118
104
  await loadUserPlugins();
119
105
 
120
- const registered = getRegisteredPlugins();
121
- const names = registered.map((p) => p.manifest.name);
106
+ const names = getRegisteredPlugins().map((p) => p.manifest.name);
122
107
  // Order is not guaranteed (filesystem-dependent) — assert membership.
123
108
  expect(names).toContain("good-plugin");
124
109
  expect(names).not.toContain("broken-plugin");
@@ -132,92 +117,9 @@ registerPlugin({
132
117
  expect(getRegisteredPlugins()).toHaveLength(0);
133
118
  });
134
119
 
135
- test("plugin with hanging top-level await is timed out and skipped", async () => {
136
- // A plugin whose module evaluation never resolves (hanging top-level
137
- // await) would otherwise block daemon startup indefinitely. The loader
138
- // must bound the import with a timeout so the hang is isolated the same
139
- // way a thrown error would be. A neighboring well-behaved plugin must
140
- // still load.
141
- writePlugin(
142
- "hanging-plugin",
143
- `
144
- await new Promise(() => {
145
- // Intentionally never resolves — simulates a plugin whose top-level
146
- // await blocks forever.
147
- });
148
- registerPlugin({
149
- manifest: {
150
- name: "hanging-plugin",
151
- version: "0.0.1",
152
- },
153
- });
154
- `,
155
- );
156
- writePlugin(
157
- "healthy-plugin",
158
- `
159
- registerPlugin({
160
- manifest: {
161
- name: "healthy-plugin",
162
- version: "0.0.1",
163
- },
164
- });
165
- `,
166
- );
167
-
168
- // Use a short test-only timeout so the suite does not wait the full
169
- // production 10s for the hung-plugin path.
170
- await loadUserPlugins({ importTimeoutMs: 250 });
171
-
172
- const names = getRegisteredPlugins().map((p) => p.manifest.name);
173
- expect(names).toContain("healthy-plugin");
174
- expect(names).not.toContain("hanging-plugin");
175
- });
176
-
177
- test("plugin whose top-level await resolves AFTER the timeout cannot register late", async () => {
178
- // Codex/Devin P1 regression: racing `import(moduleUrl)` against a timeout
179
- // only stops the loader from awaiting the module — it does NOT cancel
180
- // module evaluation. A plugin whose top-level await eventually resolves
181
- // continues running in the background and would otherwise call
182
- // `registerPlugin()` after `loadUserPlugins()` has returned (and after
183
- // `bootstrapPlugins()` has potentially already walked the registry),
184
- // leaving the plugin visible to `getMiddlewaresFor()` / `getInjectors()`
185
- // with its `init()` hook never invoked.
186
- //
187
- // The `closeRegistration()` latch must reject that late arrival so the
188
- // registry stays consistent with the bootstrap invariant.
189
- writePlugin(
190
- "slow-late-plugin",
191
- `
192
- await new Promise((resolve) => setTimeout(resolve, 200));
193
- registerPlugin({
194
- manifest: {
195
- name: "slow-late-plugin",
196
- version: "0.0.1",
197
- },
198
- });
199
- `,
200
- );
201
-
202
- // Time out well before the plugin's top-level await resolves. The loader
203
- // returns immediately; the abandoned import keeps evaluating in the
204
- // background.
205
- await loadUserPlugins({ importTimeoutMs: 25 });
206
-
207
- // Wait long enough for the abandoned import's top-level await to resolve
208
- // and try to `registerPlugin()`. The closed-registration latch must
209
- // reject the call; the `.catch(() => {})` on the abandoned import must
210
- // swallow the resulting rejection so the test does not see an
211
- // unhandled-rejection crash.
212
- await new Promise((resolve) => setTimeout(resolve, 400));
213
-
214
- const names = getRegisteredPlugins().map((p) => p.manifest.name);
215
- expect(names).not.toContain("slow-late-plugin");
216
- });
217
-
218
- test("subdirectory without register.{ts,js} is silently skipped", async () => {
219
- // Populate a directory that looks like a plugin but lacks a register
220
- // file. The loader must skip it without throwing.
120
+ test("subdirectory without package.json is silently skipped", async () => {
121
+ // Populate a directory that looks like a plugin but lacks a manifest.
122
+ // The loader must skip it without throwing.
221
123
  const stubDir = join(PLUGINS_DIR, "not-a-plugin");
222
124
  mkdirSync(stubDir, { recursive: true });
223
125
  writeFileSync(join(stubDir, "README.md"), "# not actually a plugin\n");
@@ -226,146 +128,12 @@ registerPlugin({
226
128
  expect(getRegisteredPlugins()).toHaveLength(0);
227
129
  });
228
130
 
229
- describe("experimental plugin framework branch", () => {
230
- /**
231
- * Write a directory-convention plugin (package.json + optional
232
- * hooks/tools default exports). Mirrors `writePlugin()` above but
233
- * targets the new experimental loader path.
234
- */
235
- function writeExperimentalPlugin(
236
- name: string,
237
- pkg: Record<string, unknown>,
238
- files: Record<string, string> = {},
239
- ): void {
240
- const pluginDir = join(PLUGINS_DIR, name);
241
- mkdirSync(pluginDir, { recursive: true });
242
- writeFileSync(
243
- join(pluginDir, "package.json"),
244
- JSON.stringify(pkg, null, 2),
245
- );
246
- for (const [rel, body] of Object.entries(files)) {
247
- const parts = rel.split("/");
248
- parts.pop();
249
- if (parts.length > 0) {
250
- mkdirSync(join(pluginDir, ...parts), { recursive: true });
251
- }
252
- writeFileSync(join(pluginDir, rel), body);
253
- }
254
- }
255
-
256
- test("loads a plugin via the package.json branch and registers it", async () => {
257
- writeExperimentalPlugin(
258
- "experimental-one",
259
- { name: "experimental-one", version: "0.1.0" },
260
- {
261
- "hooks/init.ts":
262
- "export default async function init(_ctx: unknown): Promise<void> {}\n",
263
- },
264
- );
265
-
266
- await loadUserPlugins();
267
-
268
- const names = getRegisteredPlugins().map((p) => p.manifest.name);
269
- expect(names).toContain("experimental-one");
270
- const registered = getRegisteredPlugins().find(
271
- (p) => p.manifest.name === "experimental-one",
272
- );
273
- expect(typeof registered?.hooks?.init).toBe("function");
274
- });
275
-
276
- test("strips npm scope from package.json name", async () => {
277
- writeExperimentalPlugin("scoped", {
278
- name: "@vellumai/cool-plugin",
279
- version: "0.1.0",
280
- });
281
-
282
- await loadUserPlugins();
131
+ test("strips npm scope from package.json name", async () => {
132
+ writePlugin("scoped", { name: "@vellumai/cool-plugin", version: "0.1.0" });
283
133
 
284
- const names = getRegisteredPlugins().map((p) => p.manifest.name);
285
- expect(names).toContain("cool-plugin");
286
- });
287
-
288
- test("a broken experimental plugin is logged and skipped without affecting others", async () => {
289
- // Plugin A has a malformed package.json; Plugin B is a healthy
290
- // legacy register.ts. The loader must isolate A's failure and still
291
- // register B — same per-plugin contract as the legacy path.
292
- const brokenDir = join(PLUGINS_DIR, "broken-experimental");
293
- mkdirSync(brokenDir, { recursive: true });
294
- writeFileSync(join(brokenDir, "package.json"), "{ not valid json");
295
-
296
- writePlugin(
297
- "healthy-legacy",
298
- `
299
- registerPlugin({
300
- manifest: {
301
- name: "healthy-legacy",
302
- version: "0.0.1",
303
- },
304
- });
305
- `,
306
- );
307
-
308
- await loadUserPlugins();
309
-
310
- const names = getRegisteredPlugins().map((p) => p.manifest.name);
311
- expect(names).toContain("healthy-legacy");
312
- expect(names).not.toContain("broken-experimental");
313
- });
314
-
315
- test("experimental and legacy plugins coexist in one workspace", async () => {
316
- writeExperimentalPlugin("new-style", {
317
- name: "new-style",
318
- version: "0.1.0",
319
- });
320
- writePlugin(
321
- "old-style",
322
- `
323
- registerPlugin({
324
- manifest: {
325
- name: "old-style",
326
- version: "0.0.1",
327
- },
328
- });
329
- `,
330
- );
331
-
332
- await loadUserPlugins();
333
-
334
- const names = getRegisteredPlugins()
335
- .map((p) => p.manifest.name)
336
- .sort();
337
- expect(names).toEqual(["new-style", "old-style"]);
338
- });
339
-
340
- test("package.json branch wins over a stale register.ts in the same dir", async () => {
341
- // A migrated plugin may keep its old register.ts on disk while it
342
- // adopts the new convention. The package.json gate takes the
343
- // experimental path and the legacy register.ts is never imported,
344
- // so the plugin must register exactly once.
345
- const dir = join(PLUGINS_DIR, "migrated");
346
- mkdirSync(dir, { recursive: true });
347
- writeFileSync(
348
- join(dir, "package.json"),
349
- JSON.stringify({ name: "migrated", version: "0.1.0" }),
350
- );
351
- // A register.ts that would double-register if both paths fired.
352
- const registryPath = join(
353
- import.meta.dir,
354
- "..",
355
- "plugins",
356
- "registry.ts",
357
- );
358
- writeFileSync(
359
- join(dir, "register.ts"),
360
- `import { registerPlugin } from ${JSON.stringify(registryPath)};
361
- registerPlugin({ manifest: { name: "migrated", version: "0.0.1", requires: { pluginRuntime: "v1" } } });
362
- `,
363
- );
364
-
365
- await loadUserPlugins();
134
+ await loadUserPlugins();
366
135
 
367
- const names = getRegisteredPlugins().map((p) => p.manifest.name);
368
- expect(names.filter((n) => n === "migrated")).toHaveLength(1);
369
- });
136
+ const names = getRegisteredPlugins().map((p) => p.manifest.name);
137
+ expect(names).toContain("cool-plugin");
370
138
  });
371
139
  });
@@ -96,10 +96,10 @@ function makePersistingStreamingSession(
96
96
 
97
97
  let turnChannelContext: TurnChannelContext | null = null;
98
98
  let turnInterfaceContext: TurnInterfaceContext | null = null;
99
+ let processing = false;
99
100
  const session = {
100
101
  conversationId,
101
102
  messages: [],
102
- processing: false,
103
103
  abortController: null,
104
104
  currentRequestId: undefined,
105
105
  queue: {} as never,
@@ -108,7 +108,10 @@ function makePersistingStreamingSession(
108
108
  scopeId: "default",
109
109
  includeDefaultFallback: false,
110
110
  },
111
- isProcessing: () => session.processing,
111
+ isProcessing: () => processing,
112
+ setProcessing: (value: boolean) => {
113
+ processing = value;
114
+ },
112
115
  persistUserMessage: async (
113
116
  ...args: Parameters<Conversation["persistUserMessage"]>
114
117
  ) => persistUserMessageImpl(session, ...args),
@@ -138,7 +141,7 @@ function makePersistingStreamingSession(
138
141
  for (const event of events) {
139
142
  onEvent(event);
140
143
  }
141
- session.processing = false;
144
+ processing = false;
142
145
  session.abortController = null;
143
146
  session.currentRequestId = undefined;
144
147
  },
@@ -0,0 +1,166 @@
1
+ // End-to-end integration coverage for the centralized web_search
2
+ // backend-failure normalization layer (ATL-727).
3
+ //
4
+ // PRs 1-4 built the layer in three places:
5
+ // - `tools/network/web-search-error.ts` — `classifyWebSearchFailure` +
6
+ // `WEB_SEARCH_BACKEND_FAILURE_MESSAGE` + the structured
7
+ // `web_search_backend_failure` log helper.
8
+ // - `daemon/conversation-agent-loop-handlers.ts` — the native
9
+ // `server_tool_complete` handler maps Anthropic backend failures to the
10
+ // friendly copy, dedups per turn (`webSearchBackendFailureNotified`), and
11
+ // gates telemetry on `classification.isBackendFailure`.
12
+ // - `tools/network/web-search.ts` — the app-side `backendFailureResult`
13
+ // helper routes 5xx/network/429 to the same copy.
14
+ //
15
+ // The single-layer native-handler invariants (friendly copy + isError + empty
16
+ // results, raw-detail-logged-not-shown, per-turn dedup, successful-empty
17
+ // search) are owned by `native-web-search.test.ts`. This file locks the
18
+ // cross-cutting acceptance criteria that file does not cover:
19
+ // - honest continuation (the failure is a recoverable tool_result, not a
20
+ // thrown provider error; the search is never marked successful),
21
+ // - web_fetch DNS failures are NOT conflated with the search backend copy.
22
+ //
23
+ // It genuinely reuses the shared handler harness in
24
+ // `helpers/native-web-search-harness.ts` (the same harness driven by
25
+ // `native-web-search.test.ts`); the web_fetch invariant is exercised through
26
+ // the same `handleToolResult` path an app-executed fetch tool drives.
27
+
28
+ import { beforeEach, describe, expect, mock, test } from "bun:test";
29
+
30
+ import type { ToolActivityMetadata } from "../daemon/message-types/web-activity.js";
31
+
32
+ // ---------------------------------------------------------------------------
33
+ // Mock the daemon collaborators the handler module imports at load time so the
34
+ // handler can be driven in isolation (mirrors native-web-search.test.ts).
35
+ // `mock.module()` is file-scoped, so the shared harness cannot install these.
36
+ // ---------------------------------------------------------------------------
37
+
38
+ mock.module("../config/loader.js", () => ({
39
+ getConfig: () => ({
40
+ skills: {
41
+ entries: {},
42
+ load: { extraDirs: [], watch: false, watchDebounceMs: 0 },
43
+ install: { nodeManager: "npm" },
44
+ allowBundled: null,
45
+ remoteProviders: {
46
+ skillssh: { enabled: true },
47
+ clawhub: { enabled: true },
48
+ },
49
+ remotePolicy: {
50
+ blockSuspicious: true,
51
+ blockMalware: true,
52
+ maxSkillsShRisk: "medium",
53
+ },
54
+ },
55
+ }),
56
+ loadConfig: () => ({}),
57
+ }));
58
+
59
+ mock.module("../memory/conversation-crud.js", () => ({
60
+ addMessage: () => ({ id: "mock-msg-id" }),
61
+ getMessageById: () => null,
62
+ updateMessageContent: () => {},
63
+ provenanceFromTrustContext: () => ({}),
64
+ reserveMessage: mock(async () => ({ id: "msg-reserve" })),
65
+ }));
66
+
67
+ mock.module("../memory/llm-request-log-store.js", () => ({
68
+ recordRequestLog: () => {},
69
+ backfillMessageIdOnLogs: () => {},
70
+ }));
71
+
72
+ // Import after mocking.
73
+ import {
74
+ createEventHandlerState,
75
+ dispatchAgentEvent,
76
+ type EventHandlerState,
77
+ } from "../daemon/conversation-agent-loop-handlers.js";
78
+ import { WEB_SEARCH_BACKEND_FAILURE_MESSAGE } from "../tools/network/web-search-error.js";
79
+ import {
80
+ backendFailureLogs,
81
+ completeNativeWebSearch,
82
+ createHandlerDeps,
83
+ lastToolResult,
84
+ } from "./helpers/native-web-search-harness.js";
85
+
86
+ describe("web_search backend-failure end-to-end (ATL-727)", () => {
87
+ let state: EventHandlerState;
88
+
89
+ beforeEach(() => {
90
+ state = createEventHandlerState();
91
+ });
92
+
93
+ test("backend failure stays an honest, recoverable tool result (not a thrown provider error, never marked successful)", async () => {
94
+ const { deps, events } = createHandlerDeps("req-honesty");
95
+
96
+ // A recoverable backend failure flows as a tool_result, so dispatch must
97
+ // resolve without throwing.
98
+ await expect(
99
+ completeNativeWebSearch(state, deps, "tu_honesty", {
100
+ isError: true,
101
+ errorCode: "unavailable",
102
+ }),
103
+ ).resolves.toBeUndefined();
104
+
105
+ const result = lastToolResult(events);
106
+ // The search is never silently upgraded to a success.
107
+ expect(result?.isError).toBe(true);
108
+ expect(result?.activityMetadata?.webSearch?.resultCount).toBe(0);
109
+ expect(result?.activityMetadata?.webSearch?.results).toEqual([]);
110
+ });
111
+
112
+ test("web_fetch DNS failure is NOT conflated with the web_search backend copy", async () => {
113
+ // The normalization layer keys exclusively on web_search (the native
114
+ // `server_tool_complete` handler and `web-search.ts`). It never inspects
115
+ // `webFetch` metadata. handleToolResult forwards an app-executed tool's
116
+ // `activityMetadata` verbatim, so a web_fetch DNS failure must reach the
117
+ // client with its own copy and acquire NO `webSearch` metadata. Drive that
118
+ // path directly with a DNS-failure fetch result for grimgoods.io.
119
+ const { deps, events, warnings } = createHandlerDeps("req-web-fetch");
120
+
121
+ const dnsError =
122
+ 'Error: Unable to resolve host "grimgoods.io" (DNS lookup failed)';
123
+ const fetchMetadata: ToolActivityMetadata = {
124
+ webFetch: {
125
+ url: "https://grimgoods.io",
126
+ finalUrl: "https://grimgoods.io",
127
+ status: 0,
128
+ byteCount: 0,
129
+ charCount: 0,
130
+ truncated: false,
131
+ domain: "grimgoods.io",
132
+ redirectCount: 0,
133
+ durationMs: 12,
134
+ errorMessage: dnsError,
135
+ },
136
+ };
137
+
138
+ await dispatchAgentEvent(state, deps, {
139
+ type: "tool_use",
140
+ id: "tu_fetch",
141
+ name: "web_fetch",
142
+ input: { url: "https://grimgoods.io" },
143
+ });
144
+ await dispatchAgentEvent(state, deps, {
145
+ type: "tool_result",
146
+ toolUseId: "tu_fetch",
147
+ content: dnsError,
148
+ isError: true,
149
+ activityMetadata: fetchMetadata,
150
+ });
151
+
152
+ const result = lastToolResult(events);
153
+ expect(result?.isError).toBe(true);
154
+
155
+ // The DNS error keeps its own webFetch copy untouched...
156
+ expect(result?.activityMetadata?.webFetch?.errorMessage).toBe(dnsError);
157
+ // ...and is never rewritten to the search backend copy.
158
+ expect(result?.activityMetadata?.webFetch?.errorMessage).not.toBe(
159
+ WEB_SEARCH_BACKEND_FAILURE_MESSAGE,
160
+ );
161
+ // No webSearch metadata is fabricated for a fetch failure.
162
+ expect(result?.activityMetadata?.webSearch).toBeUndefined();
163
+ // And the web_search backend-failure telemetry never fires for a fetch.
164
+ expect(backendFailureLogs(warnings)).toHaveLength(0);
165
+ });
166
+ });