@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
@@ -0,0 +1,248 @@
1
+ import { describe, expect, test } from "bun:test";
2
+
3
+ import { createAbortReason } from "../../util/abort-reasons.js";
4
+ import {
5
+ classifyWebSearchFailure,
6
+ logWebSearchBackendFailure,
7
+ WEB_SEARCH_BACKEND_FAILURE_MESSAGE,
8
+ } from "./web-search-error.js";
9
+
10
+ describe("classifyWebSearchFailure", () => {
11
+ test("Anthropic 'unavailable' code is a backend failure with friendly copy", () => {
12
+ const result = classifyWebSearchFailure({
13
+ isError: true,
14
+ errorCode: "unavailable",
15
+ });
16
+ expect(result.category).toBe("backend_unavailable");
17
+ expect(result.isBackendFailure).toBe(true);
18
+ expect(result.userMessage).toBe(WEB_SEARCH_BACKEND_FAILURE_MESSAGE);
19
+ expect(result.rawDetail).toContain("unavailable");
20
+ });
21
+
22
+ test.each(["internal_error", "overloaded_error"])(
23
+ "Anthropic '%s' code is a backend failure",
24
+ (errorCode) => {
25
+ const result = classifyWebSearchFailure({ isError: true, errorCode });
26
+ expect(result.category).toBe("backend_unavailable");
27
+ expect(result.isBackendFailure).toBe(true);
28
+ },
29
+ );
30
+
31
+ test("HTTP 503 is a backend failure", () => {
32
+ const result = classifyWebSearchFailure({ isError: true, statusCode: 503 });
33
+ expect(result.category).toBe("backend_unavailable");
34
+ expect(result.userMessage).toBe(WEB_SEARCH_BACKEND_FAILURE_MESSAGE);
35
+ });
36
+
37
+ test("thrown TypeError('fetch failed') is a backend failure", () => {
38
+ const result = classifyWebSearchFailure({
39
+ isError: true,
40
+ error: new TypeError("fetch failed"),
41
+ });
42
+ expect(result.category).toBe("backend_unavailable");
43
+ expect(result.isBackendFailure).toBe(true);
44
+ });
45
+
46
+ test("AbortError-shaped timeout is a backend failure", () => {
47
+ const err = new Error("The operation was aborted due to timeout");
48
+ err.name = "AbortError";
49
+ const result = classifyWebSearchFailure({ isError: true, error: err });
50
+ expect(result.category).toBe("backend_unavailable");
51
+ expect(result.isBackendFailure).toBe(true);
52
+ });
53
+
54
+ test("user-initiated abort is not a backend failure", () => {
55
+ const err = new Error("The operation was aborted");
56
+ err.name = "AbortError";
57
+ Object.assign(err, {
58
+ reason: createAbortReason("user_cancel", "cancelGeneration"),
59
+ });
60
+ const result = classifyWebSearchFailure({ isError: true, error: err });
61
+ expect(result.isBackendFailure).toBe(false);
62
+ expect(result.userMessage).not.toBe(WEB_SEARCH_BACKEND_FAILURE_MESSAGE);
63
+ });
64
+
65
+ test("wrapped ProviderError carrying abortReason is not a backend failure", () => {
66
+ // A provider wrapper erases the AbortError name and re-words the message,
67
+ // but carries the tagged reason on `abortReason` (ProviderError shape).
68
+ const err = Object.assign(new Error("Request was aborted"), {
69
+ name: "ProviderError",
70
+ abortReason: createAbortReason("user_cancel", "cancelGeneration"),
71
+ });
72
+ const result = classifyWebSearchFailure({ isError: true, error: err });
73
+ expect(result.category).not.toBe("backend_unavailable");
74
+ expect(result.isBackendFailure).toBe(false);
75
+ expect(result.userMessage).not.toBe(WEB_SEARCH_BACKEND_FAILURE_MESSAGE);
76
+ });
77
+
78
+ test("tagged abort with a transport-shaped cause is not a backend failure", () => {
79
+ // A user cancellation wrapped as a ProviderError that ALSO carries a
80
+ // transport-shaped `cause` (ECONNRESET). The tagged abort guard must win
81
+ // over transport-retryability so this is not mislabeled a backend outage.
82
+ const err = Object.assign(new Error("Request was aborted"), {
83
+ name: "ProviderError",
84
+ cause: { code: "ECONNRESET" },
85
+ abortReason: createAbortReason("user_cancel", "cancelGeneration"),
86
+ });
87
+ const result = classifyWebSearchFailure({ isError: true, error: err });
88
+ expect(result.category).not.toBe("backend_unavailable");
89
+ expect(result.isBackendFailure).toBe(false);
90
+ expect(result.userMessage).not.toBe(WEB_SEARCH_BACKEND_FAILURE_MESSAGE);
91
+ });
92
+
93
+ test("explicit statusCode wins over a misleading error-body keyword", () => {
94
+ // The provider response body contains "aborted" (a keyword the error-body
95
+ // heuristic would sniff as a non-failure), but the authoritative HTTP 503
96
+ // must classify this as a backend failure.
97
+ const result = classifyWebSearchFailure({
98
+ isError: true,
99
+ statusCode: 503,
100
+ error: new Error("the upstream request was aborted unexpectedly"),
101
+ });
102
+ expect(result.category).toBe("backend_unavailable");
103
+ expect(result.isBackendFailure).toBe(true);
104
+ expect(result.userMessage).toBe(WEB_SEARCH_BACKEND_FAILURE_MESSAGE);
105
+ });
106
+
107
+ test("ECONNRESET network error is a backend failure", () => {
108
+ const err = Object.assign(new Error("socket hang up"), {
109
+ code: "ECONNRESET",
110
+ });
111
+ const result = classifyWebSearchFailure({ isError: true, error: err });
112
+ expect(result.category).toBe("backend_unavailable");
113
+ });
114
+
115
+ test("Anthropic 'too_many_requests' code is rate-limited with backend copy", () => {
116
+ const result = classifyWebSearchFailure({
117
+ isError: true,
118
+ errorCode: "too_many_requests",
119
+ });
120
+ expect(result.category).toBe("rate_limited");
121
+ expect(result.isBackendFailure).toBe(true);
122
+ expect(result.userMessage).toBe(WEB_SEARCH_BACKEND_FAILURE_MESSAGE);
123
+ });
124
+
125
+ test("HTTP 429 is rate-limited with backend copy", () => {
126
+ const result = classifyWebSearchFailure({ isError: true, statusCode: 429 });
127
+ expect(result.category).toBe("rate_limited");
128
+ expect(result.userMessage).toBe(WEB_SEARCH_BACKEND_FAILURE_MESSAGE);
129
+ });
130
+
131
+ test("'query_too_long' has a distinct, non-backend message", () => {
132
+ const result = classifyWebSearchFailure({
133
+ isError: true,
134
+ errorCode: "query_too_long",
135
+ });
136
+ expect(result.category).toBe("query_too_long");
137
+ expect(result.isBackendFailure).toBe(false);
138
+ expect(result.userMessage).not.toBe(WEB_SEARCH_BACKEND_FAILURE_MESSAGE);
139
+ expect(result.userMessage.length).toBeGreaterThan(0);
140
+ });
141
+
142
+ test("'max_uses_exceeded' has a distinct, non-backend message", () => {
143
+ const result = classifyWebSearchFailure({
144
+ isError: true,
145
+ errorCode: "max_uses_exceeded",
146
+ });
147
+ expect(result.category).toBe("max_uses_exceeded");
148
+ expect(result.isBackendFailure).toBe(false);
149
+ expect(result.userMessage).not.toBe(WEB_SEARCH_BACKEND_FAILURE_MESSAGE);
150
+ expect(result.userMessage.length).toBeGreaterThan(0);
151
+ });
152
+
153
+ test("'invalid_input' is unknown and not a backend failure", () => {
154
+ const result = classifyWebSearchFailure({
155
+ isError: true,
156
+ errorCode: "invalid_input",
157
+ });
158
+ expect(result.category).toBe("unknown");
159
+ expect(result.isBackendFailure).toBe(false);
160
+ });
161
+
162
+ test("HTTP 401 is a config failure, not the backend copy", () => {
163
+ const result = classifyWebSearchFailure({ isError: true, statusCode: 401 });
164
+ expect(result.category).toBe("config");
165
+ expect(result.isBackendFailure).toBe(false);
166
+ expect(result.userMessage).not.toBe(WEB_SEARCH_BACKEND_FAILURE_MESSAGE);
167
+ });
168
+
169
+ test("HTTP 403 is a config failure", () => {
170
+ const result = classifyWebSearchFailure({ isError: true, statusCode: 403 });
171
+ expect(result.category).toBe("config");
172
+ expect(result.isBackendFailure).toBe(false);
173
+ });
174
+
175
+ test("successful-but-empty result is no_results, not a failure", () => {
176
+ const result = classifyWebSearchFailure({
177
+ isError: false,
178
+ hasResults: false,
179
+ });
180
+ expect(result.category).toBe("no_results");
181
+ expect(result.isBackendFailure).toBe(false);
182
+ expect(result.userMessage).not.toBe(WEB_SEARCH_BACKEND_FAILURE_MESSAGE);
183
+ });
184
+
185
+ test("rawDetail is truncated to 500 chars", () => {
186
+ const result = classifyWebSearchFailure({
187
+ isError: true,
188
+ error: new Error("x".repeat(1000)),
189
+ });
190
+ expect(result.rawDetail.length).toBeLessThanOrEqual(500 + 40);
191
+ expect(result.rawDetail).toContain("truncated");
192
+ });
193
+ });
194
+
195
+ describe("WEB_SEARCH_BACKEND_FAILURE_MESSAGE copy safety", () => {
196
+ test("offers retry / continue-without / paste", () => {
197
+ expect(WEB_SEARCH_BACKEND_FAILURE_MESSAGE).toContain("try again");
198
+ expect(WEB_SEARCH_BACKEND_FAILURE_MESSAGE).toContain("continue without");
199
+ expect(WEB_SEARCH_BACKEND_FAILURE_MESSAGE).toContain("paste");
200
+ });
201
+
202
+ test("contains no raw provider details, JSON, or exception names", () => {
203
+ for (const banned of [
204
+ "{",
205
+ "error_code",
206
+ "Anthropic",
207
+ "web_search_tool_result_error",
208
+ "TypeError",
209
+ "stack",
210
+ ]) {
211
+ expect(WEB_SEARCH_BACKEND_FAILURE_MESSAGE).not.toContain(banned);
212
+ }
213
+ });
214
+ });
215
+
216
+ describe("logWebSearchBackendFailure", () => {
217
+ test("captures the event and rawDetail without raw query text", () => {
218
+ const calls: unknown[][] = [];
219
+ const fakeLogger = {
220
+ warn: (...args: unknown[]) => {
221
+ calls.push(args);
222
+ },
223
+ } as unknown as Parameters<typeof logWebSearchBackendFailure>[0];
224
+
225
+ const secretQuery = "super secret user query text";
226
+ logWebSearchBackendFailure(fakeLogger, {
227
+ provider: "anthropic",
228
+ requestId: "req-1",
229
+ errorCategory: "backend_unavailable",
230
+ rawDetail: "errorCode=unavailable",
231
+ fallbackShown: true,
232
+ queryLength: secretQuery.length,
233
+ });
234
+
235
+ expect(calls).toHaveLength(1);
236
+ const [payload, msg] = calls[0] as [Record<string, unknown>, string];
237
+ expect(payload.event).toBe("web_search_backend_failure");
238
+ expect(payload.tool).toBe("web_search");
239
+ expect(payload.provider).toBe("anthropic");
240
+ expect(payload.rawDetail).toBe("errorCode=unavailable");
241
+ expect(payload.queryLength).toBe(secretQuery.length);
242
+ expect(msg).toBe("web_search backend failure");
243
+
244
+ // The raw query text must never appear anywhere in the logged payload.
245
+ const serialized = JSON.stringify(calls);
246
+ expect(serialized).not.toContain(secretQuery);
247
+ });
248
+ });
@@ -0,0 +1,267 @@
1
+ // Single source of truth for classifying `web_search` provider/backend
2
+ // failures and the user-facing copy we surface for them (ATL-727).
3
+ //
4
+ // This is a pure leaf module: it has NO imports from `daemon/`, `agent/`,
5
+ // `apps/`, or any client/UI package. It may only import the logger and pino
6
+ // types (for the telemetry helper). Every web_search code path — native
7
+ // Anthropic handler, app-side providers, and the web client default — funnels
8
+ // failures through `classifyWebSearchFailure` so the same friendly message
9
+ // propagates to every client via `WebSearchMetadata.errorMessage`.
10
+
11
+ import type { Logger } from "pino";
12
+
13
+ import { isAbortReason } from "../../util/abort-reasons.js";
14
+ import { truncateForLog } from "../../util/logger.js";
15
+ import { isRetryableNetworkError } from "../../util/retry.js";
16
+
17
+ /**
18
+ * Canonical user-facing copy for a recoverable web_search backend failure.
19
+ * This is what propagates to every client via `WebSearchMetadata.errorMessage`.
20
+ *
21
+ * It names the search tool as the thing struggling, offers retry /
22
+ * continue-without-search / paste-details, does not blame the user, does not
23
+ * imply we can fix the provider, does not claim the whole internet or all
24
+ * tools are down, and contains no raw provider details, JSON, stack traces,
25
+ * or exception names.
26
+ */
27
+ export const WEB_SEARCH_BACKEND_FAILURE_MESSAGE =
28
+ "Search is having trouble right now. You can try again in a moment, continue without web search, or paste the relevant details here and I'll use those.";
29
+
30
+ const QUERY_TOO_LONG_MESSAGE =
31
+ "That search query was too long. Try a shorter query.";
32
+
33
+ const MAX_USES_EXCEEDED_MESSAGE =
34
+ "I've hit the web-search limit for this turn. I can continue without more searches, or you can paste the details and I'll use those.";
35
+
36
+ const CONFIG_MESSAGE = "Web search isn't configured.";
37
+
38
+ export type WebSearchFailureCategory =
39
+ | "backend_unavailable"
40
+ | "rate_limited"
41
+ | "query_too_long"
42
+ | "max_uses_exceeded"
43
+ | "config"
44
+ | "no_results"
45
+ | "unknown";
46
+
47
+ export interface WebSearchFailureClassification {
48
+ category: WebSearchFailureCategory;
49
+ isBackendFailure: boolean;
50
+ userMessage: string;
51
+ rawDetail: string;
52
+ }
53
+
54
+ export interface WebSearchFailureInput {
55
+ /** Anthropic `web_search_tool_result_error` code, when present. */
56
+ errorCode?: string;
57
+ /** Thrown error or rejected value from a fetch/provider call. */
58
+ error?: unknown;
59
+ /** HTTP status code from a provider response, when present. */
60
+ statusCode?: number;
61
+ /** Whether the tool result was flagged as an error. */
62
+ isError?: boolean;
63
+ /** Whether a successful call returned any results. */
64
+ hasResults?: boolean;
65
+ }
66
+
67
+ /** Categories we consider a transient backend failure worth the friendly copy. */
68
+ function isBackendFailureCategory(category: WebSearchFailureCategory): boolean {
69
+ return category === "backend_unavailable" || category === "rate_limited";
70
+ }
71
+
72
+ function userMessageFor(category: WebSearchFailureCategory): string {
73
+ switch (category) {
74
+ case "backend_unavailable":
75
+ case "rate_limited":
76
+ return WEB_SEARCH_BACKEND_FAILURE_MESSAGE;
77
+ case "query_too_long":
78
+ return QUERY_TOO_LONG_MESSAGE;
79
+ case "max_uses_exceeded":
80
+ return MAX_USES_EXCEEDED_MESSAGE;
81
+ case "config":
82
+ return CONFIG_MESSAGE;
83
+ case "no_results":
84
+ case "unknown":
85
+ // Neutral passthrough: callers keep their existing behavior.
86
+ return "";
87
+ }
88
+ }
89
+
90
+ /** Map an Anthropic `web_search_tool_result_error` code to a category. */
91
+ function categoryFromErrorCode(
92
+ errorCode: string,
93
+ ): WebSearchFailureCategory | undefined {
94
+ switch (errorCode) {
95
+ case "unavailable":
96
+ case "internal_error":
97
+ case "overloaded_error":
98
+ return "backend_unavailable";
99
+ case "too_many_requests":
100
+ return "rate_limited";
101
+ case "query_too_long":
102
+ return "query_too_long";
103
+ case "max_uses_exceeded":
104
+ return "max_uses_exceeded";
105
+ case "invalid_input":
106
+ // Recoverable, but not a backend failure — let callers handle it.
107
+ return "unknown";
108
+ default:
109
+ return undefined;
110
+ }
111
+ }
112
+
113
+ /** Map an HTTP status code to a category. */
114
+ function categoryFromStatusCode(
115
+ statusCode: number,
116
+ ): WebSearchFailureCategory | undefined {
117
+ if (statusCode === 429) return "rate_limited";
118
+ if (statusCode === 401 || statusCode === 403) return "config";
119
+ if (statusCode >= 500) return "backend_unavailable";
120
+ return undefined;
121
+ }
122
+
123
+ /**
124
+ * Classify a thrown error / rejected value. This module is web_search-only, so
125
+ * network-layer failures (fetch failed, connection reset/refused, DNS, and
126
+ * timeouts without an explicit user-abort reason) are treated as backend
127
+ * failures.
128
+ */
129
+ function categoryFromError(error: unknown): WebSearchFailureCategory | undefined {
130
+ if (error == null) return undefined;
131
+
132
+ // A user-initiated abort (Stop/Esc, preemption, dispose) is not a failure.
133
+ // The tagged `AbortReason` may surface directly (`AbortSignal.throwIfAborted`
134
+ // throws `signal.reason` verbatim), via `error.reason`, or — when a provider
135
+ // wrapper erases the `AbortError` name — on `ProviderError.abortReason`. Check
136
+ // all three FIRST, before the transport-retryability and abort/timeout
137
+ // substring heuristics, so a tagged cancellation that ALSO carries a
138
+ // transport-shaped `cause` (e.g. ECONNRESET) short-circuits to "not a
139
+ // failure" instead of being mislabeled a backend outage. A bare
140
+ // AbortError/timeout with no tagged reason still falls through below.
141
+ if (
142
+ isAbortReason(error) ||
143
+ isAbortReason((error as { reason?: unknown }).reason) ||
144
+ isAbortReason((error as { abortReason?: unknown }).abortReason)
145
+ ) {
146
+ return undefined;
147
+ }
148
+
149
+ // Retryable transport failures (ECONNRESET/ECONNREFUSED/ETIMEDOUT, socket
150
+ // hang-ups, including one level of `cause` chain) are backend failures.
151
+ if (isRetryableNetworkError(error)) return "backend_unavailable";
152
+
153
+ const name = typeof (error as { name?: unknown }).name === "string"
154
+ ? (error as { name: string }).name
155
+ : "";
156
+ const haystack = `${name} ${(error as { message?: unknown }).message ?? ""}`
157
+ .toLowerCase();
158
+
159
+ // web_search-only: treat aborts/timeouts/DNS/fetch failures (the cases
160
+ // `isRetryableNetworkError` doesn't cover) as backend failures.
161
+ if (
162
+ name === "AbortError" ||
163
+ haystack.includes("abort") ||
164
+ haystack.includes("timeout") ||
165
+ haystack.includes("timed out") ||
166
+ haystack.includes("fetch failed") ||
167
+ haystack.includes("failed to fetch") ||
168
+ haystack.includes("enotfound")
169
+ ) {
170
+ return "backend_unavailable";
171
+ }
172
+ return undefined;
173
+ }
174
+
175
+ /** Build the internal-only raw detail string (never embedded in userMessage). */
176
+ function buildRawDetail(input: WebSearchFailureInput): string {
177
+ const parts: string[] = [];
178
+ if (input.errorCode) parts.push(`errorCode=${input.errorCode}`);
179
+ if (typeof input.statusCode === "number") {
180
+ parts.push(`statusCode=${input.statusCode}`);
181
+ }
182
+ if (input.error != null) {
183
+ const err = input.error as { message?: unknown };
184
+ const msg =
185
+ typeof err.message === "string" ? err.message : String(input.error);
186
+ if (msg) parts.push(msg);
187
+ }
188
+ return truncateForLog(parts.join(" "), 500);
189
+ }
190
+
191
+ /**
192
+ * Classify a web_search failure into a stable category, a user-facing message,
193
+ * and an internal-only raw detail. Never treats an empty-but-successful result
194
+ * as a failure.
195
+ */
196
+ export function classifyWebSearchFailure(
197
+ input: WebSearchFailureInput,
198
+ ): WebSearchFailureClassification {
199
+ const rawDetail = buildRawDetail(input);
200
+
201
+ // Success-passthrough: nothing went wrong, there were just no results.
202
+ if (!input.isError && input.error == null) {
203
+ return {
204
+ category: "no_results",
205
+ isBackendFailure: false,
206
+ userMessage: userMessageFor("no_results"),
207
+ rawDetail,
208
+ };
209
+ }
210
+
211
+ // Resolution order: Anthropic `errorCode` → explicit HTTP `statusCode` →
212
+ // error-body/network heuristics. An explicit status code is authoritative
213
+ // over substring-sniffing the provider's response body (which can contain
214
+ // misleading keywords like "timeout"/"abort"). Tagged user-aborts carry no
215
+ // status code, so they still flow through `categoryFromError` and
216
+ // short-circuit to a non-failure.
217
+ const category =
218
+ (input.errorCode != null
219
+ ? categoryFromErrorCode(input.errorCode)
220
+ : undefined) ??
221
+ (typeof input.statusCode === "number"
222
+ ? categoryFromStatusCode(input.statusCode)
223
+ : undefined) ??
224
+ categoryFromError(input.error) ??
225
+ "unknown";
226
+
227
+ return {
228
+ category,
229
+ isBackendFailure: isBackendFailureCategory(category),
230
+ userMessage: userMessageFor(category),
231
+ rawDetail,
232
+ };
233
+ }
234
+
235
+ export interface WebSearchBackendFailureMeta {
236
+ provider: string;
237
+ requestId?: string;
238
+ errorCategory: WebSearchFailureCategory;
239
+ rawDetail: string;
240
+ fallbackShown: boolean;
241
+ queryLength?: number;
242
+ }
243
+
244
+ /**
245
+ * Emit a structured warning for a web_search backend failure (ATL-727).
246
+ *
247
+ * Do NOT log raw query text — only `queryLength`. `rawDetail` is internal-only
248
+ * provider/HTTP context and must never be surfaced to users.
249
+ */
250
+ export function logWebSearchBackendFailure(
251
+ log: Logger,
252
+ meta: WebSearchBackendFailureMeta,
253
+ ): void {
254
+ log.warn(
255
+ {
256
+ event: "web_search_backend_failure",
257
+ tool: "web_search",
258
+ provider: meta.provider,
259
+ requestId: meta.requestId,
260
+ errorCategory: meta.errorCategory,
261
+ rawDetail: meta.rawDetail,
262
+ fallbackShown: meta.fallbackShown,
263
+ queryLength: meta.queryLength,
264
+ },
265
+ "web_search backend failure",
266
+ );
267
+ }