@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
@@ -2,26 +2,46 @@
2
2
  * Tests for the `memory-v2-static` runtime injector.
3
3
  *
4
4
  * Covers:
5
- * - Returns null when `memoryV2Static` is missing/empty.
5
+ * - Returns null when the v2 static memory files are absent/empty.
6
6
  * - Returns null when `mode === "minimal"`.
7
7
  * - Wraps content in `<info>...</info>` and uses
8
8
  * `after-memory-prefix` placement.
9
9
  * - Escapes any `</info>` substring inside the authored content so the
10
10
  * wrapper cannot be broken out of.
11
+ * - Skips (re)injection when the `<info>` block is already present in the
12
+ * turn's working messages (presence detection — the block persists in
13
+ * history between compactions).
11
14
  *
12
- * Hermetic: drives the injector's `produce()` directly with a synthesized
13
- * `TurnContext` no daemon, no filesystem.
15
+ * The injector sources its content itself via `readMemoryV2StaticContent()`
16
+ * behind the personal-memory trust gate, so each test seeds the workspace
17
+ * memory files rather than passing the content in as an option. Mocks
18
+ * `config/loader` so the v2 gates are on without standing up a full config.
14
19
  */
15
20
 
16
- import { describe, expect, test } from "bun:test";
21
+ import { mkdirSync, rmSync, writeFileSync } from "node:fs";
22
+ import { dirname } from "node:path";
23
+ import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
17
24
 
18
- import { defaultInjectorsPlugin } from "../plugins/defaults/injectors/register.js";
25
+ const realLoader = await import("../config/loader.js");
26
+
27
+ mock.module("../config/loader.js", () => ({
28
+ ...realLoader,
29
+ loadConfig: () => ({
30
+ memory: { enabled: true, v2: { enabled: true } },
31
+ }),
32
+ getConfig: () => ({
33
+ memory: { enabled: true, v2: { enabled: true } },
34
+ }),
35
+ }));
36
+
37
+ const { defaultInjectors } =
38
+ await import("../plugins/defaults/memory-retrieval/injectors.js");
19
39
  import type { Injector, TurnContext } from "../plugins/types.js";
40
+ import type { Message } from "../providers/types.js";
41
+ import { getWorkspacePromptPath } from "../util/platform.js";
20
42
 
21
43
  function findInjector(name: string): Injector {
22
- const injector = defaultInjectorsPlugin.injectors?.find(
23
- (i) => i.name === name,
24
- );
44
+ const injector = defaultInjectors.find((i) => i.name === name);
25
45
  if (!injector) {
26
46
  throw new Error(`injector '${name}' not registered`);
27
47
  }
@@ -34,57 +54,69 @@ function makeContext(overrides: Partial<TurnContext> = {}): TurnContext {
34
54
  conversationId: "conv-test",
35
55
  turnIndex: 0,
36
56
  trust: { sourceChannel: "vellum", trustClass: "guardian" },
57
+ injectionInputs: {},
37
58
  ...overrides,
38
59
  };
39
60
  }
40
61
 
62
+ /** Seed a single v2 static memory section under `## Essentials`. */
63
+ function seedEssentials(body: string): void {
64
+ const path = getWorkspacePromptPath("memory/essentials.md");
65
+ mkdirSync(dirname(path), { recursive: true });
66
+ writeFileSync(path, body, "utf-8");
67
+ }
68
+
69
+ function seedThreads(body: string): void {
70
+ const path = getWorkspacePromptPath("memory/threads.md");
71
+ mkdirSync(dirname(path), { recursive: true });
72
+ writeFileSync(path, body, "utf-8");
73
+ }
74
+
75
+ function clearV2StaticFiles(): void {
76
+ for (const file of [
77
+ "memory/essentials.md",
78
+ "memory/threads.md",
79
+ "memory/recent.md",
80
+ "memory/buffer.md",
81
+ ]) {
82
+ rmSync(getWorkspacePromptPath(file), { force: true });
83
+ }
84
+ }
85
+
41
86
  const memoryV2StaticInjector = findInjector("memory-v2-static");
42
87
 
43
88
  describe("memory-v2-static injector", () => {
44
- test("returns null when memoryV2Static is undefined", async () => {
45
- const ctx = makeContext({ injectionInputs: {} });
46
- expect(await memoryV2StaticInjector.produce(ctx)).toBeNull();
47
- });
89
+ beforeEach(() => clearV2StaticFiles());
90
+ afterEach(() => clearV2StaticFiles());
48
91
 
49
- test("returns null when memoryV2Static is null", async () => {
50
- const ctx = makeContext({ injectionInputs: { memoryV2Static: null } });
51
- expect(await memoryV2StaticInjector.produce(ctx)).toBeNull();
52
- });
53
-
54
- test("returns null when memoryV2Static is an empty string", async () => {
55
- const ctx = makeContext({ injectionInputs: { memoryV2Static: "" } });
92
+ test("returns null when the v2 static memory files are absent", async () => {
93
+ const ctx = makeContext();
56
94
  expect(await memoryV2StaticInjector.produce(ctx)).toBeNull();
57
95
  });
58
96
 
59
97
  test("returns null in minimal mode even with content", async () => {
60
- const ctx = makeContext({
61
- injectionInputs: {
62
- mode: "minimal",
63
- memoryV2Static: "## Essentials\n\nAlice prefers VS Code.",
64
- },
65
- });
98
+ seedEssentials("Alice prefers VS Code.");
99
+ const ctx = makeContext({ injectionInputs: { mode: "minimal" } });
66
100
  expect(await memoryV2StaticInjector.produce(ctx)).toBeNull();
67
101
  });
68
102
 
69
103
  test("wraps content in <info>...</info> with after-memory-prefix placement", async () => {
70
- const content =
71
- "## Essentials\n\nAlice prefers VS Code.\n\n## Threads\n\nOpen: ship PR.";
72
- const ctx = makeContext({
73
- injectionInputs: { memoryV2Static: content },
74
- });
104
+ seedEssentials("Alice prefers VS Code.");
105
+ seedThreads("Open: ship PR.");
106
+ const ctx = makeContext();
75
107
 
76
108
  const block = await memoryV2StaticInjector.produce(ctx);
77
109
  expect(block).not.toBeNull();
78
110
  expect(block!.id).toBe("memory-v2-static");
79
111
  expect(block!.placement).toBe("after-memory-prefix");
80
- expect(block!.text).toBe(`<info>\n${content}\n</info>`);
112
+ expect(block!.text).toBe(
113
+ "<info>\n## Essentials\n\nAlice prefers VS Code.\n\n## Threads\n\nOpen: ship PR.\n</info>",
114
+ );
81
115
  });
82
116
 
83
117
  test("escapes inner </info> closing tags so the wrapper cannot be broken out of", async () => {
84
- const content = "## Essentials\n\nText with </info> embedded.";
85
- const ctx = makeContext({
86
- injectionInputs: { memoryV2Static: content },
87
- });
118
+ seedEssentials("Text with </info> embedded.");
119
+ const ctx = makeContext();
88
120
 
89
121
  const block = await memoryV2StaticInjector.produce(ctx);
90
122
  expect(block).not.toBeNull();
@@ -92,4 +124,40 @@ describe("memory-v2-static injector", () => {
92
124
  "<info>\n## Essentials\n\nText with &lt;/info&gt; embedded.\n</info>",
93
125
  );
94
126
  });
127
+
128
+ test("skips (re)injection when the <info> block is already present", async () => {
129
+ seedEssentials("Alice prefers VS Code.");
130
+ const ctx = makeContext();
131
+ const runMessages: Message[] = [
132
+ {
133
+ role: "user",
134
+ content: [
135
+ { type: "text", text: "<info>\nstale memory\n</info>" },
136
+ { type: "text", text: "What next?" },
137
+ ],
138
+ },
139
+ ];
140
+ expect(await memoryV2StaticInjector.produce(ctx, runMessages)).toBeNull();
141
+ });
142
+
143
+ test("skips (re)injection when a legacy <memory>-wrapped static block is present", async () => {
144
+ // Rows persisted before the `<info>` switch rehydrate the static block as
145
+ // `<memory>…</memory>`. Re-injecting a fresh `<info>` copy alongside it
146
+ // would duplicate the content until the next compaction.
147
+ seedEssentials("Alice prefers VS Code.");
148
+ const ctx = makeContext();
149
+ const runMessages: Message[] = [
150
+ {
151
+ role: "user",
152
+ content: [
153
+ {
154
+ type: "text",
155
+ text: "<memory>\n## Essentials\n\nAlice prefers VS Code.\n</memory>",
156
+ },
157
+ { type: "text", text: "What next?" },
158
+ ],
159
+ },
160
+ ];
161
+ expect(await memoryV2StaticInjector.produce(ctx, runMessages)).toBeNull();
162
+ });
95
163
  });
@@ -71,9 +71,56 @@ mock.module("@anthropic-ai/sdk", () => ({
71
71
  },
72
72
  }));
73
73
 
74
+ // Mock daemon collaborators the handler module imports at load time so the
75
+ // handler-level tests below can drive `server_tool_complete` in isolation.
76
+ mock.module("../config/loader.js", () => ({
77
+ getConfig: () => ({
78
+ skills: {
79
+ entries: {},
80
+ load: { extraDirs: [], watch: false, watchDebounceMs: 0 },
81
+ install: { nodeManager: "npm" },
82
+ allowBundled: null,
83
+ remoteProviders: {
84
+ skillssh: { enabled: true },
85
+ clawhub: { enabled: true },
86
+ },
87
+ remotePolicy: {
88
+ blockSuspicious: true,
89
+ blockMalware: true,
90
+ maxSkillsShRisk: "medium",
91
+ },
92
+ },
93
+ }),
94
+ loadConfig: () => ({}),
95
+ }));
96
+
97
+ mock.module("../memory/conversation-crud.js", () => ({
98
+ addMessage: () => ({ id: "mock-msg-id" }),
99
+ getMessageById: () => null,
100
+ updateMessageContent: () => {},
101
+ provenanceFromTrustContext: () => ({}),
102
+ reserveMessage: mock(async () => ({ id: "msg-reserve" })),
103
+ }));
104
+
105
+ mock.module("../memory/llm-request-log-store.js", () => ({
106
+ recordRequestLog: () => {},
107
+ backfillMessageIdOnLogs: () => {},
108
+ }));
109
+
74
110
  // Import after mocking
111
+ import {
112
+ createEventHandlerState,
113
+ type EventHandlerState,
114
+ } from "../daemon/conversation-agent-loop-handlers.js";
75
115
  import { AnthropicProvider } from "../providers/anthropic/client.js";
76
116
  import { isNativeWebSearchCapableProvider } from "../providers/registry.js";
117
+ import { WEB_SEARCH_BACKEND_FAILURE_MESSAGE } from "../tools/network/web-search-error.js";
118
+ import {
119
+ completeNativeWebSearch,
120
+ createHandlerDeps,
121
+ lastToolResult,
122
+ toolResults,
123
+ } from "./helpers/native-web-search-harness.js";
77
124
 
78
125
  // ---------------------------------------------------------------------------
79
126
  // Helpers
@@ -499,3 +546,147 @@ describe("Native Web Search — Streaming Events", () => {
499
546
  expect(toolUseEvents).toHaveLength(1);
500
547
  });
501
548
  });
549
+
550
+ // ---------------------------------------------------------------------------
551
+ // Tests — Native server_tool_complete backend-failure handling (ATL-727)
552
+ // ---------------------------------------------------------------------------
553
+
554
+ describe("Native Web Search — Backend Failure Handling", () => {
555
+ let state: EventHandlerState;
556
+
557
+ beforeEach(() => {
558
+ state = createEventHandlerState();
559
+ });
560
+
561
+ test("backend failure surfaces friendly copy with isError true and empty results", async () => {
562
+ const { deps, events } = createHandlerDeps();
563
+ await completeNativeWebSearch(state, deps, "tu_backend", {
564
+ isError: true,
565
+ errorCode: "unavailable",
566
+ });
567
+
568
+ const result = lastToolResult(events);
569
+ expect(result?.activityMetadata?.webSearch?.errorMessage).toBe(
570
+ WEB_SEARCH_BACKEND_FAILURE_MESSAGE,
571
+ );
572
+ expect(result?.isError).toBe(true);
573
+ expect(result?.activityMetadata?.webSearch?.resultCount).toBe(0);
574
+ expect(result?.activityMetadata?.webSearch?.results).toEqual([]);
575
+ });
576
+
577
+ test("raw error_code is logged under web_search_backend_failure but absent from user copy", async () => {
578
+ const { deps, events, warnings } = createHandlerDeps();
579
+ await completeNativeWebSearch(state, deps, "tu_log", {
580
+ isError: true,
581
+ errorCode: "unavailable",
582
+ });
583
+
584
+ const failureLog = warnings.find(
585
+ (w) => w.obj.event === "web_search_backend_failure",
586
+ );
587
+ expect(failureLog).toBeDefined();
588
+ expect(failureLog?.obj.provider).toBe("anthropic-native");
589
+ expect(String(failureLog?.obj.rawDetail)).toContain("unavailable");
590
+ expect(failureLog?.obj.fallbackShown).toBe(true);
591
+
592
+ const errorMessage = lastToolResult(events)?.activityMetadata?.webSearch
593
+ ?.errorMessage;
594
+ expect(errorMessage).not.toContain("unavailable");
595
+ });
596
+
597
+ test("dedups repeat backend failures within one turn to a single friendly notice", async () => {
598
+ const { deps, events, warnings } = createHandlerDeps();
599
+
600
+ await completeNativeWebSearch(state, deps, "tu_dup_1", {
601
+ isError: true,
602
+ errorCode: "unavailable",
603
+ });
604
+ await completeNativeWebSearch(state, deps, "tu_dup_2", {
605
+ isError: true,
606
+ errorCode: "overloaded_error",
607
+ });
608
+
609
+ const results = toolResults(events);
610
+ expect(results).toHaveLength(2);
611
+ expect(results[0]?.activityMetadata?.webSearch?.errorMessage).toBe(
612
+ WEB_SEARCH_BACKEND_FAILURE_MESSAGE,
613
+ );
614
+ // The second backend failure in the same turn is terse, not the full notice.
615
+ expect(results[1]?.activityMetadata?.webSearch?.errorMessage).not.toBe(
616
+ WEB_SEARCH_BACKEND_FAILURE_MESSAGE,
617
+ );
618
+
619
+ const failureLogs = warnings.filter(
620
+ (w) => w.obj.event === "web_search_backend_failure",
621
+ );
622
+ // Both failures are logged, but only the first reports fallbackShown.
623
+ expect(failureLogs).toHaveLength(2);
624
+ expect(failureLogs.filter((w) => w.obj.fallbackShown === true)).toHaveLength(
625
+ 1,
626
+ );
627
+ });
628
+
629
+ test("successful search leaves errorMessage undefined and populates results", async () => {
630
+ const { deps, events, warnings } = createHandlerDeps();
631
+ await completeNativeWebSearch(state, deps, "tu_ok", {
632
+ isError: false,
633
+ content: [
634
+ {
635
+ type: "web_search_result",
636
+ title: "Weather",
637
+ url: "https://example.com/weather",
638
+ },
639
+ ],
640
+ });
641
+
642
+ const meta = lastToolResult(events)?.activityMetadata?.webSearch;
643
+ expect(meta?.errorMessage).toBeUndefined();
644
+ expect(meta?.resultCount).toBe(1);
645
+ expect(meta?.results[0]?.title).toBe("Weather");
646
+ expect(lastToolResult(events)?.isError).toBe(false);
647
+ expect(
648
+ warnings.filter((w) => w.obj.event === "web_search_backend_failure"),
649
+ ).toHaveLength(0);
650
+ });
651
+
652
+ test("query_too_long yields a distinct non-backend message", async () => {
653
+ const { deps, events, warnings } = createHandlerDeps();
654
+ await completeNativeWebSearch(state, deps, "tu_long", {
655
+ isError: true,
656
+ errorCode: "query_too_long",
657
+ });
658
+
659
+ const errorMessage = lastToolResult(events)?.activityMetadata?.webSearch
660
+ ?.errorMessage;
661
+ expect(errorMessage).toBeDefined();
662
+ expect(errorMessage).not.toBe(WEB_SEARCH_BACKEND_FAILURE_MESSAGE);
663
+ // Recoverable non-backend errors must NOT emit backend-failure telemetry.
664
+ expect(
665
+ warnings.filter((w) => w.obj.event === "web_search_backend_failure"),
666
+ ).toHaveLength(0);
667
+ });
668
+
669
+ test("message-less native failure (no error_code) surfaces friendly copy, not the terse 'Search failed' placeholder, and emits no backend telemetry", async () => {
670
+ const { deps, events, warnings } = createHandlerDeps("req-unknown");
671
+ // `isError:true` with no error_code/message classifies as `unknown`
672
+ // (isBackendFailure:false, empty userMessage). It must still get the
673
+ // friendly copy rather than the bare "Search failed".
674
+ await completeNativeWebSearch(state, deps, "tu_unknown", {
675
+ isError: true,
676
+ });
677
+
678
+ const result = lastToolResult(events);
679
+ const meta = result?.activityMetadata?.webSearch;
680
+ expect(meta?.errorMessage).toBe(WEB_SEARCH_BACKEND_FAILURE_MESSAGE);
681
+ expect(meta?.errorMessage).not.toBe("Search failed");
682
+ expect(result?.isError).toBe(true);
683
+ expect(meta?.resultCount).toBe(0);
684
+ expect(meta?.results).toEqual([]);
685
+
686
+ // An unclassifiable failure borrows the friendly copy but must NOT be
687
+ // logged as a backend outage.
688
+ expect(
689
+ warnings.filter((w) => w.obj.event === "web_search_backend_failure"),
690
+ ).toHaveLength(0);
691
+ });
692
+ });
@@ -71,6 +71,8 @@ describe("onboarding template contracts", () => {
71
71
 
72
72
  test("offers assistant migration during low-signal first openings", () => {
73
73
  expect(bootstrap).toContain("## Assistant migration");
74
+ expect(bootstrap).toContain("onboarding self-introduction");
75
+ expect(bootstrap).toContain("treat it as the real first user turn");
74
76
  expect(bootstrap).toContain(
75
77
  "If the first real user turn is only a greeting",
76
78
  );
@@ -351,6 +351,23 @@ describe("mapOpenAIError", () => {
351
351
  expect(msg).toContain("Rate limit");
352
352
  });
353
353
 
354
+ test("maps 402 status to a non-retryable out-of-credits message", () => {
355
+ const msg = mapOpenAIError(new FakeAPIError(402, "payment required"));
356
+ expect(msg).toContain("out of credits");
357
+ expect(msg).not.toContain("Please try again");
358
+ });
359
+
360
+ test("maps insufficient_quota (reported as 429) to out-of-credits message", () => {
361
+ const msg = mapOpenAIError(
362
+ new FakeAPIError(
363
+ 429,
364
+ "You exceeded your current quota, please check your plan and billing details. (insufficient_quota)",
365
+ ),
366
+ );
367
+ expect(msg).toContain("out of credits");
368
+ expect(msg).not.toContain("Rate limit");
369
+ });
370
+
354
371
  test("maps 500 status to server error message", () => {
355
372
  const msg = mapOpenAIError(new FakeAPIError(500, "internal"));
356
373
  expect(msg).toContain("temporarily unavailable");
@@ -105,7 +105,10 @@ mock.module("openai", () => ({
105
105
  import { FireworksProvider } from "../providers/fireworks/client.js";
106
106
  import { MinimaxProvider } from "../providers/minimax/client.js";
107
107
  import { OllamaProvider } from "../providers/ollama/client.js";
108
- import { OpenAIChatCompletionsProvider } from "../providers/openai/chat-completions-provider.js";
108
+ import {
109
+ EMPTY_ASSISTANT_TURN_PLACEHOLDER,
110
+ OpenAIChatCompletionsProvider,
111
+ } from "../providers/openai/chat-completions-provider.js";
109
112
  import { OpenAIProvider } from "../providers/openai/client.js";
110
113
  import { OpenRouterProvider } from "../providers/openrouter/client.js";
111
114
 
@@ -1584,6 +1587,33 @@ describe("OpenRouterProvider reasoning", () => {
1584
1587
  expect(lastCreateParams).not.toHaveProperty("usageAttributionHeaders");
1585
1588
  });
1586
1589
 
1590
+ test("backfills placeholder content for a reasoning-only assistant turn", async () => {
1591
+ const provider = new OpenRouterProvider("or-key", "deepseek/deepseek-chat");
1592
+ await provider.sendMessage([
1593
+ userMsg("question"),
1594
+ {
1595
+ role: "assistant",
1596
+ content: [
1597
+ { type: "thinking", thinking: "truncated reasoning", signature: "" },
1598
+ ],
1599
+ },
1600
+ ]);
1601
+
1602
+ const sent = lastCreateParams!.messages as Array<{
1603
+ role: string;
1604
+ content: string | null;
1605
+ reasoning?: string;
1606
+ tool_calls?: unknown;
1607
+ }>;
1608
+ const assistantMsg = sent.find((m) => m.role === "assistant")!;
1609
+ // DeepSeek via OpenRouter rejects an assistant message with neither content
1610
+ // nor tool_calls, so the reasoning-only turn is backfilled with the sentinel
1611
+ // while the reasoning itself travels in the separate `reasoning` field.
1612
+ expect(assistantMsg.content).toBe(EMPTY_ASSISTANT_TURN_PLACEHOLDER);
1613
+ expect(assistantMsg.tool_calls).toBeUndefined();
1614
+ expect(assistantMsg.reasoning).toBe("truncated reasoning");
1615
+ });
1616
+
1587
1617
  test("RetryProvider + OpenRouterProvider enables thinking end-to-end", async () => {
1588
1618
  const provider = new OpenRouterProvider("or-key", "x-ai/grok-4");
1589
1619
  const retry = new RetryProvider(provider);