@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,129 @@
1
+ /**
2
+ * Shared test harness for driving the daemon's native `server_tool_complete`
3
+ * web_search handler in isolation (ATL-727).
4
+ *
5
+ * Both `native-web-search.test.ts` and `web-search-backend-failure.test.ts`
6
+ * exercise the same handler the same way: build a set of mocked
7
+ * `EventHandlerDeps` (capturing emitted `ServerMessage`s and `rlog.warn`
8
+ * records), then drive a `server_tool_start` → `server_tool_complete` pair.
9
+ * This module is the single source of truth for that harness so the two suites
10
+ * cannot drift apart.
11
+ *
12
+ * Note: each consuming test file must still install its own
13
+ * `mock.module(...)` stubs for the daemon collaborators the handler imports at
14
+ * load time (config loader, conversation-crud, llm-request-log-store), because
15
+ * Bun's `mock.module()` is scoped to the file that registers it.
16
+ */
17
+ import type {
18
+ EventHandlerDeps,
19
+ EventHandlerState,
20
+ } from "../../daemon/conversation-agent-loop-handlers.js";
21
+ import { dispatchAgentEvent } from "../../daemon/conversation-agent-loop-handlers.js";
22
+ import type { ServerMessage } from "../../daemon/message-protocol.js";
23
+
24
+ /** A `tool_result` `ServerMessage` emitted by the handler. */
25
+ export type ToolResultEvent = Extract<ServerMessage, { type: "tool_result" }>;
26
+
27
+ /** A captured `rlog.warn(obj, msg)` call. */
28
+ export interface LogRecord {
29
+ obj: Record<string, unknown>;
30
+ msg?: string;
31
+ }
32
+
33
+ export interface HandlerHarness {
34
+ deps: EventHandlerDeps;
35
+ /** Every `ServerMessage` the handler emitted via `onEvent`. */
36
+ events: ServerMessage[];
37
+ /** Every `rlog.warn(obj, msg)` call the handler made. */
38
+ warnings: LogRecord[];
39
+ }
40
+
41
+ /** Build mocked handler deps that capture emitted events and warn logs. */
42
+ export function createHandlerDeps(reqId = "req-web-search"): HandlerHarness {
43
+ const events: ServerMessage[] = [];
44
+ const warnings: LogRecord[] = [];
45
+ const rlog = {
46
+ warn: (obj: Record<string, unknown>, msg?: string) =>
47
+ warnings.push({ obj, msg }),
48
+ info: () => {},
49
+ error: () => {},
50
+ debug: () => {},
51
+ trace: () => {},
52
+ fatal: () => {},
53
+ };
54
+ const deps = {
55
+ ctx: {
56
+ conversationId: "conv-web-search",
57
+ provider: { name: "anthropic" },
58
+ traceEmitter: { emit: () => {} },
59
+ streamThinking: false,
60
+ emitActivityState: () => {},
61
+ markWorkspaceTopLevelDirty: () => {},
62
+ currentTurnSurfaces: [],
63
+ } as unknown as EventHandlerDeps["ctx"],
64
+ onEvent: (msg: ServerMessage) => events.push(msg),
65
+ reqId,
66
+ isFirstMessage: false,
67
+ shouldGenerateTitle: false,
68
+ rlog: rlog as unknown as EventHandlerDeps["rlog"],
69
+ turnChannelContext: {
70
+ userMessageChannel: "vellum",
71
+ assistantMessageChannel: "vellum",
72
+ } as EventHandlerDeps["turnChannelContext"],
73
+ turnInterfaceContext: {
74
+ userMessageInterface: "macos",
75
+ assistantMessageInterface: "macos",
76
+ } as EventHandlerDeps["turnInterfaceContext"],
77
+ applyCompaction: async () => {},
78
+ } as EventHandlerDeps;
79
+ return { deps, events, warnings };
80
+ }
81
+
82
+ /** The `server_tool_complete` payload shape a test supplies. */
83
+ export interface WebSearchCompleteEvent {
84
+ isError: boolean;
85
+ errorCode?: string;
86
+ errorMessage?: string;
87
+ content?: unknown[];
88
+ }
89
+
90
+ /** Drive one native (Anthropic) web_search start → complete pair. */
91
+ export async function completeNativeWebSearch(
92
+ state: EventHandlerState,
93
+ deps: EventHandlerDeps,
94
+ toolUseId: string,
95
+ event: WebSearchCompleteEvent,
96
+ ): Promise<void> {
97
+ await dispatchAgentEvent(state, deps, {
98
+ type: "server_tool_start",
99
+ name: "web_search",
100
+ toolUseId,
101
+ input: { query: "what is the weather" },
102
+ });
103
+ await dispatchAgentEvent(state, deps, {
104
+ type: "server_tool_complete",
105
+ toolUseId,
106
+ isError: event.isError,
107
+ ...(event.errorCode ? { errorCode: event.errorCode } : {}),
108
+ ...(event.errorMessage ? { errorMessage: event.errorMessage } : {}),
109
+ content: event.content ?? [],
110
+ });
111
+ }
112
+
113
+ /** All `tool_result` events emitted so far, in order. */
114
+ export function toolResults(events: ServerMessage[]): ToolResultEvent[] {
115
+ return events.filter((e): e is ToolResultEvent => e.type === "tool_result");
116
+ }
117
+
118
+ /** The most recent `tool_result` event, if any. */
119
+ export function lastToolResult(
120
+ events: ServerMessage[],
121
+ ): ToolResultEvent | undefined {
122
+ const results = toolResults(events);
123
+ return results[results.length - 1];
124
+ }
125
+
126
+ /** The captured `web_search_backend_failure` telemetry warn records. */
127
+ export function backendFailureLogs(warnings: LogRecord[]): LogRecord[] {
128
+ return warnings.filter((w) => w.obj.event === "web_search_backend_failure");
129
+ }
@@ -40,6 +40,7 @@ const noopLogger: PluginLogger = {
40
40
  function makeCtx(messages: Message[]): UserPromptSubmitContext {
41
41
  return {
42
42
  conversationId: "conv-test",
43
+ prompt: "",
43
44
  originalMessages: messages,
44
45
  latestMessages: messages,
45
46
  logger: noopLogger,
@@ -47,7 +47,7 @@ interface FakeConversation {
47
47
 
48
48
  const conversations = new Map<string, FakeConversation>();
49
49
 
50
- mock.module("../daemon/conversation-store.js", () => ({
50
+ mock.module("../daemon/conversation-registry.js", () => ({
51
51
  findConversation: (id: string) => conversations.get(id),
52
52
  }));
53
53
 
@@ -8,9 +8,9 @@
8
8
  * 4. Untargeted (no targetClientId, no header) → 200 accepted (regression)
9
9
  *
10
10
  * Resolution goes through conversation.hostCuProxy?.resolve(...). The
11
- * conversation store is mocked to return a controlled conversation object.
11
+ * conversation registry is mocked to return a controlled conversation object.
12
12
  *
13
- * Note: host-cu-routes.ts has a deep import chain (conversation-store
13
+ * Note: host-cu-routes.ts has a deep import chain (conversation-registry
14
14
  * conversation.ts → ces-client → service-contracts) that requires mocking
15
15
  * before the module loads. We use dynamic imports to ensure all mocks are
16
16
  * registered before the route module is evaluated.
@@ -75,7 +75,7 @@ const conversationStore = new Map<
75
75
  { hostCuProxy?: { processObservation: (...args: unknown[]) => void } }
76
76
  >();
77
77
 
78
- mock.module("../daemon/conversation-store.js", () => ({
78
+ mock.module("../daemon/conversation-registry.js", () => ({
79
79
  findConversation: (conversationId: string) =>
80
80
  conversationStore.get(conversationId),
81
81
  }));
@@ -1,9 +1,8 @@
1
1
  /**
2
2
  * Unit tests for the identity intro cache (identity-intro-cache.ts).
3
3
  *
4
- * Validates TTL-based expiration, content-hash-based invalidation when
5
- * workspace identity files or the guardian persona content change, and
6
- * round-trip get/set behavior.
4
+ * Validates TTL-based expiration, round-trip get/set behavior, and parsing
5
+ * workspace-authored greeting sections.
7
6
  */
8
7
 
9
8
  import { afterEach, describe, expect, mock, test } from "bun:test";
@@ -45,20 +44,11 @@ mock.module("node:fs", () => ({
45
44
  },
46
45
  }));
47
46
 
48
- // Mocked guardian persona — mutable so tests can change it and verify cache
49
- // invalidation based on the per-user persona file content.
50
- let guardianPersonaContent: string | null = null;
51
-
52
- mock.module("../prompts/persona-resolver.js", () => ({
53
- resolveGuardianPersona: () => guardianPersonaContent,
54
- }));
55
-
56
47
  // ---------------------------------------------------------------------------
57
48
  // Imports (after mocks)
58
49
  // ---------------------------------------------------------------------------
59
50
 
60
51
  import {
61
- computeIdentityContentHash,
62
52
  getCachedIntro,
63
53
  parseGreetingsSection,
64
54
  readWorkspaceGreetings,
@@ -75,7 +65,6 @@ afterEach(() => {
75
65
  for (const key of Object.keys(workspaceFiles)) {
76
66
  delete workspaceFiles[key];
77
67
  }
78
- guardianPersonaContent = null;
79
68
  });
80
69
 
81
70
  // ---------------------------------------------------------------------------
@@ -118,7 +107,6 @@ describe("identity intro cache", () => {
118
107
  test("round-trip: set then get returns cached greetings array", () => {
119
108
  workspaceFiles["IDENTITY.md"] = "- **Name:** Atlas";
120
109
  workspaceFiles["SOUL.md"] = "Be playful.";
121
- guardianPersonaContent = "The user likes coffee.";
122
110
 
123
111
  setCachedIntro(["Hey, I'm Atlas.", "What's up?"]);
124
112
  const cached = getCachedIntro();
@@ -131,111 +119,45 @@ describe("identity intro cache", () => {
131
119
 
132
120
  setCachedIntro(["Hello!"]);
133
121
 
134
- // Manually set the timestamp to 5 hours ago
135
- const fiveHoursAgo = String(Date.now() - 5 * 60 * 60 * 1000);
136
- checkpointStore.set("identity:intro:cached_at", fiveHoursAgo);
122
+ const fortyNineHoursAgo = String(Date.now() - 49 * 60 * 60 * 1000);
123
+ checkpointStore.set("identity:intro:cached_at", fortyNineHoursAgo);
137
124
 
138
125
  expect(getCachedIntro()).toBeNull();
139
126
  });
140
127
 
141
- test("returns cached greetings when within TTL (3 hours ago)", () => {
128
+ test("returns cached greetings when within TTL (47 hours ago)", () => {
142
129
  workspaceFiles["IDENTITY.md"] = "- **Name:** Atlas";
143
130
 
144
131
  setCachedIntro(["Hello!"]);
145
132
 
146
- // Set timestamp to 3 hours ago (within 4-hour TTL)
147
- const threeHoursAgo = String(Date.now() - 3 * 60 * 60 * 1000);
148
- checkpointStore.set("identity:intro:cached_at", threeHoursAgo);
133
+ const fortySevenHoursAgo = String(Date.now() - 47 * 60 * 60 * 1000);
134
+ checkpointStore.set("identity:intro:cached_at", fortySevenHoursAgo);
149
135
 
150
136
  const cached = getCachedIntro();
151
137
  expect(cached).not.toBeNull();
152
138
  expect(cached!.greetings).toEqual(["Hello!"]);
153
139
  });
154
140
 
155
- test("busts cache when IDENTITY.md changes", () => {
141
+ test("keeps cached greetings when IDENTITY.md changes", () => {
156
142
  workspaceFiles["IDENTITY.md"] = "- **Name:** Atlas";
157
143
  setCachedIntro(["I'm Atlas!"]);
158
144
 
159
- // Change IDENTITY.md
160
145
  workspaceFiles["IDENTITY.md"] = "- **Name:** Nova";
161
146
 
162
- expect(getCachedIntro()).toBeNull();
147
+ expect(getCachedIntro()?.greetings).toEqual(["I'm Atlas!"]);
163
148
  });
164
149
 
165
- test("busts cache when SOUL.md changes", () => {
150
+ test("keeps cached greetings when SOUL.md changes", () => {
166
151
  workspaceFiles["SOUL.md"] = "Be playful.";
167
152
  setCachedIntro(["Hey there!"]);
168
153
 
169
- // Change SOUL.md
170
154
  workspaceFiles["SOUL.md"] = "Be serious and formal.";
171
155
 
172
- expect(getCachedIntro()).toBeNull();
173
- });
174
-
175
- test("busts cache when guardian persona content changes", () => {
176
- guardianPersonaContent = "Likes coffee.";
177
- setCachedIntro(["Good morning!"]);
178
-
179
- // Change guardian persona (e.g. user edited users/<slug>.md)
180
- guardianPersonaContent = "Likes tea.";
181
-
182
- expect(getCachedIntro()).toBeNull();
183
- });
184
-
185
- test("cache remains valid when guardian persona is unchanged", () => {
186
- workspaceFiles["IDENTITY.md"] = "- **Name:** Atlas";
187
- workspaceFiles["SOUL.md"] = "Be chill.";
188
- guardianPersonaContent = "Likes sunsets.";
189
-
190
- setCachedIntro(["Atlas here.", "Hey friend."]);
191
-
192
- expect(getCachedIntro()?.greetings).toEqual(["Atlas here.", "Hey friend."]);
193
- expect(getCachedIntro()?.greetings).toEqual(["Atlas here.", "Hey friend."]);
194
- });
195
-
196
- test("computeIdentityContentHash is deterministic", () => {
197
- workspaceFiles["IDENTITY.md"] = "test";
198
- workspaceFiles["SOUL.md"] = "test2";
199
- guardianPersonaContent = "test3";
200
-
201
- const hash1 = computeIdentityContentHash();
202
- const hash2 = computeIdentityContentHash();
203
- expect(hash1).toBe(hash2);
204
- expect(hash1).toMatch(/^[a-f0-9]{64}$/); // SHA-256 hex
205
- });
206
-
207
- test("computeIdentityContentHash changes when guardian persona changes", () => {
208
- workspaceFiles["IDENTITY.md"] = "- **Name:** Atlas";
209
- workspaceFiles["SOUL.md"] = "Be playful.";
210
- guardianPersonaContent = "Likes coffee.";
211
- const hash1 = computeIdentityContentHash();
212
-
213
- guardianPersonaContent = "Likes tea.";
214
- const hash2 = computeIdentityContentHash();
215
-
216
- expect(hash1).not.toBe(hash2);
217
- });
218
-
219
- test("computeIdentityContentHash handles null guardian persona", () => {
220
- workspaceFiles["IDENTITY.md"] = "- **Name:** Atlas";
221
- guardianPersonaContent = null;
222
-
223
- const hash = computeIdentityContentHash();
224
- expect(hash).toMatch(/^[a-f0-9]{64}$/);
225
- });
226
-
227
- test("computeIdentityContentHash changes when file content changes", () => {
228
- workspaceFiles["IDENTITY.md"] = "v1";
229
- const hash1 = computeIdentityContentHash();
230
-
231
- workspaceFiles["IDENTITY.md"] = "v2";
232
- const hash2 = computeIdentityContentHash();
233
-
234
- expect(hash1).not.toBe(hash2);
156
+ expect(getCachedIntro()?.greetings).toEqual(["Hey there!"]);
235
157
  });
236
158
 
237
159
  test("handles missing workspace files gracefully", () => {
238
- // No files exist — should still work (empty content hashed)
160
+ // No files exist — should still work.
239
161
  setCachedIntro(["Hello!"]);
240
162
  const cached = getCachedIntro();
241
163
  expect(cached).not.toBeNull();
@@ -244,9 +166,7 @@ describe("identity intro cache", () => {
244
166
 
245
167
  test("handles legacy single-string cache value", () => {
246
168
  // Simulate a cache entry written by an older daemon version
247
- const hash = computeIdentityContentHash();
248
169
  checkpointStore.set("identity:intro:greetings", "Legacy greeting");
249
- checkpointStore.set("identity:intro:content_hash", hash);
250
170
  checkpointStore.set("identity:intro:cached_at", String(Date.now()));
251
171
 
252
172
  const cached = getCachedIntro();
@@ -255,20 +175,12 @@ describe("identity intro cache", () => {
255
175
  });
256
176
 
257
177
  test("returns null when greetings checkpoint is missing", () => {
258
- checkpointStore.set("identity:intro:content_hash", "abc");
259
- checkpointStore.set("identity:intro:cached_at", String(Date.now()));
260
- expect(getCachedIntro()).toBeNull();
261
- });
262
-
263
- test("returns null when hash checkpoint is missing", () => {
264
- checkpointStore.set("identity:intro:greetings", '["Hello"]');
265
178
  checkpointStore.set("identity:intro:cached_at", String(Date.now()));
266
179
  expect(getCachedIntro()).toBeNull();
267
180
  });
268
181
 
269
182
  test("returns null when timestamp checkpoint is missing", () => {
270
183
  checkpointStore.set("identity:intro:greetings", '["Hello"]');
271
- checkpointStore.set("identity:intro:content_hash", "abc");
272
184
  expect(getCachedIntro()).toBeNull();
273
185
  });
274
186
  });
@@ -22,14 +22,70 @@ mock.module("../util/logger.js", () => ({
22
22
  }),
23
23
  }));
24
24
 
25
+ const checkpointStore = new Map<string, string>();
26
+
27
+ mock.module("../memory/checkpoints.js", () => ({
28
+ getMemoryCheckpoint: (key: string) => checkpointStore.get(key) ?? null,
29
+ setMemoryCheckpoint: (key: string, value: string) => {
30
+ checkpointStore.set(key, value);
31
+ },
32
+ }));
33
+
34
+ const getConfiguredProviderCalls: string[] = [];
35
+ const mockProvider = { name: "mock-provider" };
36
+
37
+ mock.module("../providers/provider-send-message.js", () => ({
38
+ getConfiguredProvider: mock(async (callSite: string) => {
39
+ getConfiguredProviderCalls.push(callSite);
40
+ return mockProvider;
41
+ }),
42
+ }));
43
+
44
+ type SidechainCall = {
45
+ callSite?: string;
46
+ content: string;
47
+ maxTokens?: number;
48
+ systemPrompt?: string;
49
+ tools: unknown[];
50
+ };
51
+
52
+ type SidechainResult = {
53
+ text: string;
54
+ hadTextDeltas: false;
55
+ response: { content: [] };
56
+ };
57
+
58
+ const sidechainCalls: SidechainCall[] = [];
59
+ let sidechainText = "";
60
+ let sidechainResultPromise: Promise<SidechainResult> | null = null;
61
+
62
+ mock.module("../runtime/btw-sidechain.js", () => ({
63
+ runBtwSidechain: mock(async (params: SidechainCall) => {
64
+ sidechainCalls.push(params);
65
+ if (sidechainResultPromise) {
66
+ return sidechainResultPromise;
67
+ }
68
+ return {
69
+ text: sidechainText,
70
+ hadTextDeltas: false,
71
+ response: { content: [] },
72
+ };
73
+ }),
74
+ }));
75
+
76
+ const assistantFeatureFlags: Record<string, boolean> = {};
77
+
78
+ mock.module("../config/assistant-feature-flags.js", () => ({
79
+ isAssistantFeatureFlagEnabled: (key: string) =>
80
+ assistantFeatureFlags[key] ?? false,
81
+ }));
82
+
25
83
  import {
26
84
  handleDetailedHealth,
27
85
  handleReadyz,
28
86
  ROUTES,
29
87
  } from "../runtime/routes/identity-routes.js";
30
- import {
31
- setCesClient,
32
- } from "../security/secure-keys.js";
88
+ import { setCesClient } from "../security/secure-keys.js";
33
89
  import { getWorkspaceDir } from "../util/platform.js";
34
90
  import {
35
91
  getHatchedSidecarPath,
@@ -37,6 +93,17 @@ import {
37
93
  selectHatchedAtFromStats,
38
94
  } from "../workspace/hatched-date.js";
39
95
 
96
+ function createDeferred<T>(): {
97
+ promise: Promise<T>;
98
+ resolve: (value: T) => void;
99
+ } {
100
+ let resolve!: (value: T) => void;
101
+ const promise = new Promise<T>((res) => {
102
+ resolve = res;
103
+ });
104
+ return { promise, resolve };
105
+ }
106
+
40
107
  // ── Env helpers ─────────────────────────────────────────────────────────
41
108
 
42
109
  let savedEnv: Record<string, string | undefined>;
@@ -133,6 +200,15 @@ beforeEach(() => {
133
200
 
134
201
  rmSync(getHatchedSidecarPath(), { force: true });
135
202
  rmSync(join(getWorkspaceDir(), "IDENTITY.md"), { force: true });
203
+ rmSync(join(getWorkspaceDir(), "SOUL.md"), { force: true });
204
+ checkpointStore.clear();
205
+ getConfiguredProviderCalls.length = 0;
206
+ sidechainCalls.length = 0;
207
+ sidechainText = "";
208
+ sidechainResultPromise = null;
209
+ for (const key of Object.keys(assistantFeatureFlags)) {
210
+ delete assistantFeatureFlags[key];
211
+ }
136
212
  });
137
213
 
138
214
  afterEach(() => {
@@ -202,21 +278,30 @@ describe("identity routes — health endpoint", () => {
202
278
  });
203
279
 
204
280
  test("readyz returns 200 when CES is connected and ready", () => {
205
- const mockClient = { isReady: () => true, close: () => {} } as unknown as import("../credential-execution/client.js").CesClient;
281
+ const mockClient = {
282
+ isReady: () => true,
283
+ close: () => {},
284
+ } as unknown as import("../credential-execution/client.js").CesClient;
206
285
  setCesClient(mockClient);
207
286
  const res = handleReadyz();
208
287
  expect(res.status).toBe(200);
209
288
  });
210
289
 
211
290
  test("readyz returns 200 when CES client exists but is not ready", () => {
212
- const mockClient = { isReady: () => false, close: () => {} } as unknown as import("../credential-execution/client.js").CesClient;
291
+ const mockClient = {
292
+ isReady: () => false,
293
+ close: () => {},
294
+ } as unknown as import("../credential-execution/client.js").CesClient;
213
295
  setCesClient(mockClient);
214
296
  const res = handleReadyz();
215
297
  expect(res.status).toBe(200);
216
298
  });
217
299
 
218
300
  test("/v1/health reports ces.connected=true when CES is ready", async () => {
219
- const mockClient = { isReady: () => true, close: () => {} } as unknown as import("../credential-execution/client.js").CesClient;
301
+ const mockClient = {
302
+ isReady: () => true,
303
+ close: () => {},
304
+ } as unknown as import("../credential-execution/client.js").CesClient;
220
305
  setCesClient(mockClient);
221
306
  const res = handleDetailedHealth();
222
307
  const body = (await res.json()) as Record<string, unknown>;
@@ -226,7 +311,10 @@ describe("identity routes — health endpoint", () => {
226
311
  });
227
312
 
228
313
  test("/v1/health reports ces.connected=false when CES is not ready", async () => {
229
- const mockClient = { isReady: () => false, close: () => {} } as unknown as import("../credential-execution/client.js").CesClient;
314
+ const mockClient = {
315
+ isReady: () => false,
316
+ close: () => {},
317
+ } as unknown as import("../credential-execution/client.js").CesClient;
230
318
  setCesClient(mockClient);
231
319
  const res = handleDetailedHealth();
232
320
  const body = (await res.json()) as Record<string, unknown>;
@@ -485,3 +573,156 @@ describe("identity routes — createdAt selection", () => {
485
573
  expect(existsSync(getHatchedSidecarPath())).toBe(false);
486
574
  });
487
575
  });
576
+
577
+ describe("identity routes — intro greetings", () => {
578
+ test("returns static fallback and does not generate when the dynamic greetings flag is off", async () => {
579
+ const workspaceDir = getWorkspaceDir();
580
+ writeFileSync(
581
+ join(workspaceDir, "IDENTITY.md"),
582
+ "# Identity\n\n- **Name:** Example Assistant\n",
583
+ "utf-8",
584
+ );
585
+ writeFileSync(
586
+ join(workspaceDir, "SOUL.md"),
587
+ "# Soul\n\nNo explicit greetings section here.\n",
588
+ "utf-8",
589
+ );
590
+
591
+ const route = ROUTES.find(
592
+ (candidate) => candidate.operationId === "identity_intro",
593
+ );
594
+ expect(route).toBeDefined();
595
+
596
+ const body = route!.handler({}) as {
597
+ greetings: string[];
598
+ text: string;
599
+ source: string;
600
+ refreshing: boolean;
601
+ };
602
+
603
+ expect(body).toEqual({
604
+ greetings: [
605
+ "What are we working on?",
606
+ "I'm here whenever you need me.",
607
+ "What's on your mind?",
608
+ "Ready when you are.",
609
+ ],
610
+ text: "What are we working on?",
611
+ source: "fallback",
612
+ refreshing: false,
613
+ });
614
+
615
+ await Promise.resolve();
616
+ await Promise.resolve();
617
+
618
+ expect(getConfiguredProviderCalls).toEqual([]);
619
+ expect(sidechainCalls).toEqual([]);
620
+ });
621
+
622
+ test("returns fallback immediately, generates personalized greetings in the background, then reuses the cache", async () => {
623
+ assistantFeatureFlags["empty-state-dynamic-greetings"] = true;
624
+
625
+ const workspaceDir = getWorkspaceDir();
626
+ writeFileSync(
627
+ join(workspaceDir, "IDENTITY.md"),
628
+ [
629
+ "# Identity",
630
+ "",
631
+ "- **Name:** Example Assistant",
632
+ "- **Personality:** enjoys crisp, useful hellos",
633
+ "",
634
+ "Identity sentinel: chartreuse compass.",
635
+ ].join("\n"),
636
+ "utf-8",
637
+ );
638
+ writeFileSync(
639
+ join(workspaceDir, "SOUL.md"),
640
+ [
641
+ "# Soul",
642
+ "",
643
+ "Soul sentinel: copper lighthouse.",
644
+ "",
645
+ "Keep greetings warm and specific.",
646
+ ].join("\n"),
647
+ "utf-8",
648
+ );
649
+ const deferredSidechain = createDeferred<SidechainResult>();
650
+ sidechainResultPromise = deferredSidechain.promise;
651
+
652
+ const route = ROUTES.find(
653
+ (candidate) => candidate.operationId === "identity_intro",
654
+ );
655
+ expect(route).toBeDefined();
656
+
657
+ const body = route!.handler({}) as {
658
+ greetings: string[];
659
+ text: string;
660
+ source: string;
661
+ refreshing: boolean;
662
+ };
663
+
664
+ expect(body).toEqual({
665
+ greetings: [
666
+ "What are we working on?",
667
+ "I'm here whenever you need me.",
668
+ "What's on your mind?",
669
+ "Ready when you are.",
670
+ ],
671
+ text: "What are we working on?",
672
+ source: "fallback",
673
+ refreshing: true,
674
+ });
675
+ expect(getConfiguredProviderCalls).toEqual([]);
676
+ expect(sidechainCalls).toEqual([]);
677
+
678
+ await Promise.resolve();
679
+ await Promise.resolve();
680
+
681
+ expect(getConfiguredProviderCalls).toEqual(["emptyStateGreeting"]);
682
+ expect(sidechainCalls).toHaveLength(1);
683
+ expect(sidechainCalls[0]?.callSite).toBe("emptyStateGreeting");
684
+ expect(sidechainCalls[0]?.tools).toEqual([]);
685
+ expect(sidechainCalls[0]?.content).toContain("JSON array");
686
+ expect(sidechainCalls[0]?.systemPrompt).toContain(
687
+ "Identity sentinel: chartreuse compass.",
688
+ );
689
+ expect(sidechainCalls[0]?.systemPrompt).toContain(
690
+ "Soul sentinel: copper lighthouse.",
691
+ );
692
+ deferredSidechain.resolve({
693
+ text: JSON.stringify([
694
+ "Charting the next useful thing?",
695
+ "I brought the compass. Where to?",
696
+ "Ready to make this lighter.",
697
+ ]),
698
+ hadTextDeltas: false,
699
+ response: { content: [] },
700
+ });
701
+
702
+ await sidechainResultPromise;
703
+ await new Promise((resolve) => setTimeout(resolve, 0));
704
+
705
+ sidechainCalls.length = 0;
706
+ getConfiguredProviderCalls.length = 0;
707
+
708
+ const cachedBody = (await route!.handler({})) as {
709
+ greetings: string[];
710
+ text: string;
711
+ source: string;
712
+ refreshing: boolean;
713
+ };
714
+
715
+ expect(cachedBody).toEqual({
716
+ greetings: [
717
+ "Charting the next useful thing?",
718
+ "I brought the compass. Where to?",
719
+ "Ready to make this lighter.",
720
+ ],
721
+ text: "Charting the next useful thing?",
722
+ source: "cache",
723
+ refreshing: false,
724
+ });
725
+ expect(getConfiguredProviderCalls).toEqual([]);
726
+ expect(sidechainCalls).toEqual([]);
727
+ });
728
+ });
@@ -105,10 +105,14 @@ function createTestContext(
105
105
  }
106
106
  : null;
107
107
 
108
+ let processing = false;
108
109
  return {
109
110
  conversationId: "conv-test",
110
111
  messages: [],
111
- processing: false,
112
+ isProcessing: () => processing,
113
+ setProcessing: (value: boolean) => {
114
+ processing = value;
115
+ },
112
116
  abortController: null,
113
117
  queue: queueStub,
114
118
  getTurnChannelContext: () => turnChannel,