@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
@@ -51,9 +51,18 @@ mock.module("../util/logger.js", () => ({
51
51
  }),
52
52
  }));
53
53
 
54
+ const mockPublishConversationTitleChanged = mock(
55
+ (_conversationId: string, _title: string) => {},
56
+ );
57
+ mock.module("../runtime/sync/resource-sync-events.js", () => ({
58
+ publishConversationTitleChanged: mockPublishConversationTitleChanged,
59
+ }));
60
+
54
61
  import {
55
62
  generateAndPersistConversationTitle,
63
+ queueGenerateConversationTitle,
56
64
  regenerateConversationTitle,
65
+ titleMutex,
57
66
  } from "../memory/conversation-title-service.js";
58
67
 
59
68
  describe("conversation-title-service", () => {
@@ -63,6 +72,7 @@ describe("conversation-title-service", () => {
63
72
  mockGetMessages.mockClear();
64
73
  mockUpdateConversationTitle.mockClear();
65
74
  mockGetConfiguredProvider.mockClear();
75
+ mockPublishConversationTitleChanged.mockClear();
66
76
  });
67
77
 
68
78
  test("uses the BTW side-chain helper for initial title generation", async () => {
@@ -87,7 +97,7 @@ describe("conversation-title-service", () => {
87
97
  systemPrompt: expect.stringContaining("conversation titles"),
88
98
  tools: [],
89
99
  callSite: "conversationTitle",
90
- timeoutMs: 10_000,
100
+ timeoutMs: 15_000,
91
101
  }),
92
102
  );
93
103
  expect(mockUpdateConversationTitle).toHaveBeenCalledWith(
@@ -95,6 +105,12 @@ describe("conversation-title-service", () => {
95
105
  "Project kickoff",
96
106
  1,
97
107
  );
108
+ // Emit is service-native: persisting a title broadcasts the update so
109
+ // every title origin (agent loop, bootstrap, voice) updates clients live.
110
+ expect(mockPublishConversationTitleChanged).toHaveBeenCalledWith(
111
+ "conv-1",
112
+ "Project kickoff",
113
+ );
98
114
  });
99
115
 
100
116
  test("regeneration extracts text from JSON content blocks", async () => {
@@ -205,7 +221,7 @@ describe("conversation-title-service", () => {
205
221
  systemPrompt: expect.stringContaining("conversation titles"),
206
222
  tools: [],
207
223
  callSite: "conversationTitle",
208
- timeoutMs: 10_000,
224
+ timeoutMs: 15_000,
209
225
  }),
210
226
  );
211
227
  expect(mockUpdateConversationTitle).toHaveBeenCalledWith(
@@ -354,4 +370,121 @@ describe("conversation-title-service", () => {
354
370
  expect(call.content).not.toContain("do NOT respond");
355
371
  expect(call.systemPrompt).toContain("Do NOT respond");
356
372
  });
373
+
374
+ test("queueGenerateConversationTitle serializes concurrent calls", async () => {
375
+ const callOrder: string[] = [];
376
+ let resolveFirst!: () => void;
377
+ const firstBlocked = new Promise<void>((r) => {
378
+ resolveFirst = r;
379
+ });
380
+
381
+ // First call: blocks until we release it
382
+ mockRunBtwSidechain.mockImplementationOnce(async () => {
383
+ callOrder.push("first:start");
384
+ await firstBlocked;
385
+ callOrder.push("first:end");
386
+ return {
387
+ text: "Title One",
388
+ hadTextDeltas: true,
389
+ response: {
390
+ content: [{ type: "text", text: "Title One" }],
391
+ model: "test-model",
392
+ usage: { inputTokens: 10, outputTokens: 5 },
393
+ stopReason: "end_turn",
394
+ },
395
+ };
396
+ });
397
+
398
+ // Second call: resolves immediately
399
+ mockRunBtwSidechain.mockImplementationOnce(async () => {
400
+ callOrder.push("second:start");
401
+ return {
402
+ text: "Title Two",
403
+ hadTextDeltas: true,
404
+ response: {
405
+ content: [{ type: "text", text: "Title Two" }],
406
+ model: "test-model",
407
+ usage: { inputTokens: 10, outputTokens: 5 },
408
+ stopReason: "end_turn",
409
+ },
410
+ };
411
+ });
412
+
413
+ const provider = {
414
+ name: "test-provider",
415
+ sendMessage: mock(async () => {
416
+ throw new Error("should not call directly");
417
+ }),
418
+ };
419
+
420
+ // Fire both calls — without serialization both would start immediately
421
+ queueGenerateConversationTitle({
422
+ conversationId: "conv-1",
423
+ provider,
424
+ userMessage: "first message",
425
+ });
426
+ queueGenerateConversationTitle({
427
+ conversationId: "conv-2",
428
+ provider,
429
+ userMessage: "second message",
430
+ });
431
+
432
+ // Let microtasks settle — only the first call should have started
433
+ await new Promise((r) => setTimeout(r, 10));
434
+ expect(callOrder).toEqual(["first:start"]);
435
+
436
+ // Release the first call
437
+ resolveFirst();
438
+ await titleMutex.withLock(async () => {});
439
+
440
+ // Second should have started only after first finished
441
+ expect(callOrder).toEqual(["first:start", "first:end", "second:start"]);
442
+ });
443
+
444
+ test("queue continues processing after a failed call", async () => {
445
+ // First call: throws
446
+ mockRunBtwSidechain.mockImplementationOnce(async () => {
447
+ throw new Error("provider timeout");
448
+ });
449
+
450
+ // Second call: succeeds
451
+ mockRunBtwSidechain.mockImplementationOnce(async () => ({
452
+ text: "Recovery Title",
453
+ hadTextDeltas: true,
454
+ response: {
455
+ content: [{ type: "text", text: "Recovery Title" }],
456
+ model: "test-model",
457
+ usage: { inputTokens: 10, outputTokens: 5 },
458
+ stopReason: "end_turn",
459
+ },
460
+ }));
461
+
462
+ const provider = {
463
+ name: "test-provider",
464
+ sendMessage: mock(async () => {
465
+ throw new Error("should not call directly");
466
+ }),
467
+ };
468
+
469
+ queueGenerateConversationTitle({
470
+ conversationId: "conv-1",
471
+ provider,
472
+ userMessage: "will fail",
473
+ });
474
+ queueGenerateConversationTitle({
475
+ conversationId: "conv-2",
476
+ provider,
477
+ userMessage: "will succeed",
478
+ });
479
+
480
+ await titleMutex.withLock(async () => {});
481
+
482
+ // Both calls went through — failure didn't break the chain
483
+ expect(mockRunBtwSidechain).toHaveBeenCalledTimes(2);
484
+ // Second conversation got a proper title
485
+ const secondUpdate = (
486
+ mockUpdateConversationTitle.mock.calls as unknown as string[][]
487
+ ).find((c) => c[0] === "conv-2" && c[1] === "Recovery Title");
488
+ expect(secondUpdate).toBeTruthy();
489
+ });
357
490
  });
@@ -198,6 +198,7 @@ mock.module("../agent/loop.js", () => ({
198
198
  }));
199
199
 
200
200
  import { Conversation } from "../daemon/conversation.js";
201
+ import { refreshWorkspaceTopLevelContextIfNeeded } from "../daemon/conversation-workspace.js";
201
202
 
202
203
  // ---------------------------------------------------------------------------
203
204
  // Helpers
@@ -249,7 +250,7 @@ describe("Conversation workspace cache state", () => {
249
250
  });
250
251
 
251
252
  test("refreshWorkspaceTopLevelContextIfNeeded populates context and clears dirty", () => {
252
- conversation.refreshWorkspaceTopLevelContextIfNeeded();
253
+ refreshWorkspaceTopLevelContextIfNeeded(conversation);
253
254
 
254
255
  expect(conversation.isWorkspaceTopLevelDirty()).toBe(false);
255
256
  expect(conversation.getWorkspaceTopLevelContext()).not.toBeNull();
@@ -265,10 +266,10 @@ describe("Conversation workspace cache state", () => {
265
266
  });
266
267
 
267
268
  test("refreshWorkspaceTopLevelContextIfNeeded no-ops when not dirty and cache exists", () => {
268
- conversation.refreshWorkspaceTopLevelContextIfNeeded();
269
+ refreshWorkspaceTopLevelContextIfNeeded(conversation);
269
270
  const first = conversation.getWorkspaceTopLevelContext();
270
271
 
271
- conversation.refreshWorkspaceTopLevelContextIfNeeded();
272
+ refreshWorkspaceTopLevelContextIfNeeded(conversation);
272
273
  const second = conversation.getWorkspaceTopLevelContext();
273
274
 
274
275
  // Same reference — no recomputation
@@ -276,7 +277,7 @@ describe("Conversation workspace cache state", () => {
276
277
  });
277
278
 
278
279
  test("markWorkspaceTopLevelDirty sets dirty flag", () => {
279
- conversation.refreshWorkspaceTopLevelContextIfNeeded();
280
+ refreshWorkspaceTopLevelContextIfNeeded(conversation);
280
281
  expect(conversation.isWorkspaceTopLevelDirty()).toBe(false);
281
282
 
282
283
  conversation.markWorkspaceTopLevelDirty();
@@ -284,10 +285,10 @@ describe("Conversation workspace cache state", () => {
284
285
  });
285
286
 
286
287
  test("refresh after marking dirty produces fresh context", () => {
287
- conversation.refreshWorkspaceTopLevelContextIfNeeded();
288
+ refreshWorkspaceTopLevelContextIfNeeded(conversation);
288
289
 
289
290
  conversation.markWorkspaceTopLevelDirty();
290
- conversation.refreshWorkspaceTopLevelContextIfNeeded();
291
+ refreshWorkspaceTopLevelContextIfNeeded(conversation);
291
292
 
292
293
  expect(conversation.getWorkspaceTopLevelContext()).not.toBeNull();
293
294
  expect(conversation.getWorkspaceTopLevelContext()!).toContain(
@@ -299,7 +300,7 @@ describe("Conversation workspace cache state", () => {
299
300
  test("renders client-reported host env when set on the conversation", () => {
300
301
  conversation.hostHomeDir = "/Users/alice";
301
302
  conversation.hostUsername = "alice";
302
- conversation.refreshWorkspaceTopLevelContextIfNeeded();
303
+ refreshWorkspaceTopLevelContextIfNeeded(conversation);
303
304
 
304
305
  const block = conversation.getWorkspaceTopLevelContext();
305
306
  expect(block).not.toBeNull();
@@ -309,7 +310,7 @@ describe("Conversation workspace cache state", () => {
309
310
 
310
311
  test("falls back to daemon os info when client host env is absent", async () => {
311
312
  const { homedir, userInfo } = await import("node:os");
312
- conversation.refreshWorkspaceTopLevelContextIfNeeded();
313
+ refreshWorkspaceTopLevelContextIfNeeded(conversation);
313
314
 
314
315
  const block = conversation.getWorkspaceTopLevelContext();
315
316
  expect(block).not.toBeNull();
@@ -320,7 +321,7 @@ describe("Conversation workspace cache state", () => {
320
321
  test("re-renders with updated host env after marking dirty", () => {
321
322
  conversation.hostHomeDir = "/Users/alice";
322
323
  conversation.hostUsername = "alice";
323
- conversation.refreshWorkspaceTopLevelContextIfNeeded();
324
+ refreshWorkspaceTopLevelContextIfNeeded(conversation);
324
325
  expect(conversation.getWorkspaceTopLevelContext()!).toContain(
325
326
  "Host home directory: /Users/alice",
326
327
  );
@@ -328,7 +329,7 @@ describe("Conversation workspace cache state", () => {
328
329
  conversation.hostHomeDir = "/Users/bob";
329
330
  conversation.hostUsername = "bob";
330
331
  conversation.markWorkspaceTopLevelDirty();
331
- conversation.refreshWorkspaceTopLevelContextIfNeeded();
332
+ refreshWorkspaceTopLevelContextIfNeeded(conversation);
332
333
 
333
334
  const block = conversation.getWorkspaceTopLevelContext();
334
335
  expect(block).not.toBeNull();
@@ -344,7 +345,7 @@ describe("Conversation workspace cache state", () => {
344
345
  // Simulate a macOS turn populating host env.
345
346
  conversation.hostHomeDir = "/Users/alice";
346
347
  conversation.hostUsername = "alice";
347
- conversation.refreshWorkspaceTopLevelContextIfNeeded();
348
+ refreshWorkspaceTopLevelContextIfNeeded(conversation);
348
349
  expect(conversation.getWorkspaceTopLevelContext()!).toContain(
349
350
  "Host home directory: /Users/alice",
350
351
  );
@@ -355,7 +356,7 @@ describe("Conversation workspace cache state", () => {
355
356
  conversation.hostHomeDir = undefined;
356
357
  conversation.hostUsername = undefined;
357
358
  conversation.markWorkspaceTopLevelDirty();
358
- conversation.refreshWorkspaceTopLevelContextIfNeeded();
359
+ refreshWorkspaceTopLevelContextIfNeeded(conversation);
359
360
 
360
361
  const block = conversation.getWorkspaceTopLevelContext();
361
362
  expect(block).not.toBeNull();
@@ -381,7 +382,7 @@ describe("Conversation workspace cache state", () => {
381
382
  expect(conversation.hostUsername).toBe("alice");
382
383
  expect(conversation.isWorkspaceTopLevelDirty()).toBe(true);
383
384
 
384
- conversation.refreshWorkspaceTopLevelContextIfNeeded();
385
+ refreshWorkspaceTopLevelContextIfNeeded(conversation);
385
386
  const block = conversation.getWorkspaceTopLevelContext();
386
387
  expect(block!).toContain("Host home directory: /Users/alice");
387
388
  expect(block!).toContain("Host username: alice");
@@ -455,7 +456,7 @@ describe("Conversation workspace cache state", () => {
455
456
  hostUsername: "alice",
456
457
  });
457
458
  // Render once so the dirty flag clears.
458
- conversation.refreshWorkspaceTopLevelContextIfNeeded();
459
+ refreshWorkspaceTopLevelContextIfNeeded(conversation);
459
460
  expect(conversation.isWorkspaceTopLevelDirty()).toBe(false);
460
461
 
461
462
  // Re-apply the same values — dirty flag should remain false so we don't
@@ -476,7 +477,7 @@ describe("Conversation workspace cache state", () => {
476
477
  hostHomeDir: "/Users/alice",
477
478
  hostUsername: "alice",
478
479
  });
479
- conversation.refreshWorkspaceTopLevelContextIfNeeded();
480
+ refreshWorkspaceTopLevelContextIfNeeded(conversation);
480
481
  expect(conversation.isWorkspaceTopLevelDirty()).toBe(false);
481
482
 
482
483
  // New values — should mark dirty so the next render picks them up.
@@ -500,7 +501,7 @@ describe("Conversation workspace cache state", () => {
500
501
 
501
502
  try {
502
503
  const tempConversation = makeConversation(workspaceRoot);
503
- tempConversation.refreshWorkspaceTopLevelContextIfNeeded();
504
+ refreshWorkspaceTopLevelContextIfNeeded(tempConversation);
504
505
 
505
506
  expect(tempConversation.getWorkspaceTopLevelContext()!).toContain(
506
507
  `Current conversation attachments: conversations/${legacyDirName}/attachments/`,
@@ -265,7 +265,12 @@ mock.module("../agent/loop.js", () => ({
265
265
  content: [{ type: "text", text: "ok" }],
266
266
  };
267
267
  onEvent({ type: "message_complete", message: assistantMessage });
268
- return { history: [...messages, assistantMessage], exitReason: null };
268
+ return {
269
+ history: [...messages, assistantMessage],
270
+ exitReason: null,
271
+ appendedNewMessages: true,
272
+ newMessages: [assistantMessage],
273
+ };
269
274
  }
270
275
  },
271
276
  }));
@@ -290,6 +295,14 @@ mock.module("../memory/canonical-guardian-store.js", () => ({
290
295
  }));
291
296
 
292
297
  import { Conversation } from "../daemon/conversation.js";
298
+ import {
299
+ clearConversations,
300
+ findConversation,
301
+ removeSubagentConversation,
302
+ setConversation,
303
+ setSubagentConversation,
304
+ } from "../daemon/conversation-registry.js";
305
+ import { resolveWorkspaceTopLevelContext } from "../daemon/conversation-workspace.js";
293
306
  import { resetPluginRegistryAndRegisterDefaults } from "../plugins/defaults/index.js";
294
307
 
295
308
  function makeConversation(): Conversation {
@@ -304,7 +317,7 @@ function makeConversation(): Conversation {
304
317
  };
305
318
  },
306
319
  };
307
- return new Conversation(
320
+ const conversation = new Conversation(
308
321
  "conv-1",
309
322
  provider,
310
323
  "system prompt",
@@ -312,6 +325,10 @@ function makeConversation(): Conversation {
312
325
  "/tmp",
313
326
  { maxTokens: 4096 },
314
327
  );
328
+ // Mirror production: top-level conversations are registered in the store, so
329
+ // the workspace injector can resolve them by id via the conversation registry.
330
+ setConversation(conversation.conversationId, conversation);
331
+ return conversation;
315
332
  }
316
333
 
317
334
  function messageText(message: Message): string {
@@ -330,6 +347,7 @@ describe("Conversation workspace injection", () => {
330
347
  runCalls = [];
331
348
  agentLoopScript = () => {};
332
349
  scanCallCount = 0;
350
+ clearConversations();
333
351
  resetPluginRegistryAndRegisterDefaults();
334
352
  });
335
353
 
@@ -412,11 +430,58 @@ describe("Conversation workspace injection", () => {
412
430
  });
413
431
  });
414
432
 
433
+ describe("Conversation workspace injection — subagents", () => {
434
+ beforeEach(() => {
435
+ scanCallCount = 0;
436
+ clearConversations();
437
+ resetPluginRegistryAndRegisterDefaults();
438
+ });
439
+
440
+ test("workspace context resolves for subagent conversations not in the store", () => {
441
+ const provider = {
442
+ name: "mock",
443
+ async sendMessage(): Promise<ProviderResponse> {
444
+ return {
445
+ content: [],
446
+ model: "mock",
447
+ usage: { inputTokens: 0, outputTokens: 0 },
448
+ stopReason: "end_turn",
449
+ };
450
+ },
451
+ };
452
+ const subagentId = "subagent-conv-1";
453
+ const conversation = new Conversation(
454
+ subagentId,
455
+ provider,
456
+ "system prompt",
457
+ () => {},
458
+ "/tmp",
459
+ { maxTokens: 4096 },
460
+ );
461
+ setSubagentConversation(subagentId, conversation);
462
+
463
+ try {
464
+ // Subagents live only in the manager's index, never in the
465
+ // eviction-managed store, so the store lookup must not see them.
466
+ expect(findConversation(subagentId)).toBeUndefined();
467
+
468
+ // The per-conversation workspace lookup still resolves them, so subagent
469
+ // turns retain workspace grounding.
470
+ const context = resolveWorkspaceTopLevelContext(subagentId);
471
+ expect(context).not.toBeNull();
472
+ expect(context).toContain("Root: /tmp");
473
+ } finally {
474
+ removeSubagentConversation(subagentId, conversation);
475
+ }
476
+ });
477
+ });
478
+
415
479
  describe("Conversation workspace dirty-refresh E2E", () => {
416
480
  beforeEach(() => {
417
481
  runCalls = [];
418
482
  agentLoopScript = () => {};
419
483
  scanCallCount = 0;
484
+ clearConversations();
420
485
  resetPluginRegistryAndRegisterDefaults();
421
486
  });
422
487
 
@@ -273,6 +273,7 @@ mock.module("../memory/canonical-guardian-store.js", () => ({
273
273
  }));
274
274
 
275
275
  import { Conversation } from "../daemon/conversation.js";
276
+ import { refreshWorkspaceTopLevelContextIfNeeded } from "../daemon/conversation-workspace.js";
276
277
 
277
278
  function makeConversation(): Conversation {
278
279
  const provider = {
@@ -310,7 +311,7 @@ describe("Conversation workspace dirty on file mutations", () => {
310
311
  await conversation.loadFromDb();
311
312
 
312
313
  // Prime the cache so dirty=false
313
- conversation.refreshWorkspaceTopLevelContextIfNeeded();
314
+ refreshWorkspaceTopLevelContextIfNeeded(conversation);
314
315
  expect(conversation.isWorkspaceTopLevelDirty()).toBe(false);
315
316
 
316
317
  agentLoopScript = (onEvent) => {
@@ -339,7 +340,7 @@ describe("Conversation workspace dirty on file mutations", () => {
339
340
  const conversation = makeConversation();
340
341
  await conversation.loadFromDb();
341
342
 
342
- conversation.refreshWorkspaceTopLevelContextIfNeeded();
343
+ refreshWorkspaceTopLevelContextIfNeeded(conversation);
343
344
  expect(conversation.isWorkspaceTopLevelDirty()).toBe(false);
344
345
 
345
346
  agentLoopScript = (onEvent) => {
@@ -370,7 +371,7 @@ describe("Conversation workspace dirty on file mutations", () => {
370
371
  const conversation = makeConversation();
371
372
  await conversation.loadFromDb();
372
373
 
373
- conversation.refreshWorkspaceTopLevelContextIfNeeded();
374
+ refreshWorkspaceTopLevelContextIfNeeded(conversation);
374
375
  expect(conversation.isWorkspaceTopLevelDirty()).toBe(false);
375
376
 
376
377
  agentLoopScript = (onEvent) => {
@@ -399,7 +400,7 @@ describe("Conversation workspace dirty on file mutations", () => {
399
400
  const conversation = makeConversation();
400
401
  await conversation.loadFromDb();
401
402
 
402
- conversation.refreshWorkspaceTopLevelContextIfNeeded();
403
+ refreshWorkspaceTopLevelContextIfNeeded(conversation);
403
404
  expect(conversation.isWorkspaceTopLevelDirty()).toBe(false);
404
405
 
405
406
  agentLoopScript = (onEvent) => {
@@ -428,7 +429,7 @@ describe("Conversation workspace dirty on file mutations", () => {
428
429
  const conversation = makeConversation();
429
430
  await conversation.loadFromDb();
430
431
 
431
- conversation.refreshWorkspaceTopLevelContextIfNeeded();
432
+ refreshWorkspaceTopLevelContextIfNeeded(conversation);
432
433
  expect(conversation.isWorkspaceTopLevelDirty()).toBe(false);
433
434
 
434
435
  agentLoopScript = (onEvent) => {
@@ -457,7 +458,7 @@ describe("Conversation workspace dirty on file mutations", () => {
457
458
  const conversation = makeConversation();
458
459
  await conversation.loadFromDb();
459
460
 
460
- conversation.refreshWorkspaceTopLevelContextIfNeeded();
461
+ refreshWorkspaceTopLevelContextIfNeeded(conversation);
461
462
  expect(conversation.isWorkspaceTopLevelDirty()).toBe(false);
462
463
 
463
464
  agentLoopScript = (onEvent) => {
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Tests that the conversations import route never persists non-renderable
3
+ * roles. The messages store is UI-facing (`ConversationMessage`), so an
4
+ * imported export carrying agent-context `system` rows must land only its
5
+ * `user`/`assistant` turns — the `system` rows are dropped, not persisted.
6
+ */
7
+
8
+ import { beforeEach, describe, expect, mock, test } from "bun:test";
9
+
10
+ mock.module("../util/logger.js", () => ({
11
+ getLogger: () =>
12
+ new Proxy({} as Record<string, unknown>, {
13
+ get: () => () => {},
14
+ }),
15
+ }));
16
+
17
+ mock.module("../config/loader.js", () => ({
18
+ getConfig: () => ({
19
+ ui: {},
20
+ model: "test",
21
+ provider: "test",
22
+ memory: { enabled: false },
23
+ rateLimit: { maxRequestsPerMinute: 0 },
24
+ }),
25
+ }));
26
+
27
+ mock.module("../memory/indexer.js", () => ({
28
+ indexMessageNow: async () => {},
29
+ }));
30
+
31
+ import { getMessages } from "../memory/conversation-crud.js";
32
+ import { getDb } from "../memory/db-connection.js";
33
+ import { initializeDb } from "../memory/db-init.js";
34
+ import { conversations, messages } from "../memory/schema.js";
35
+ import { ROUTES } from "../runtime/routes/conversations-import-routes.js";
36
+ import type { RouteHandlerArgs } from "../runtime/routes/types.js";
37
+
38
+ initializeDb();
39
+
40
+ function resetTables() {
41
+ const db = getDb();
42
+ db.run("DELETE FROM message_attachments");
43
+ db.run("DELETE FROM attachments");
44
+ db.run("DELETE FROM messages");
45
+ db.run("DELETE FROM conversation_keys");
46
+ db.run("DELETE FROM conversations");
47
+ }
48
+
49
+ const importHandler = ROUTES.find(
50
+ (r) => r.operationId === "conversations_import",
51
+ )!.handler;
52
+
53
+ describe("conversations import system-row filtering", () => {
54
+ beforeEach(resetTables);
55
+
56
+ test("imports renderable turns but drops system rows", async () => {
57
+ // GIVEN an export whose conversation sandwiches a system row between two
58
+ // renderable turns (e.g. agent-context scaffolding an export carried)
59
+ const body = {
60
+ conversations: [
61
+ {
62
+ sourceKey: "src-1",
63
+ title: "Imported chat",
64
+ messages: [
65
+ { role: "user", content: "first visible" },
66
+ { role: "system", content: "agent-context scaffolding" },
67
+ { role: "assistant", content: "second visible" },
68
+ ],
69
+ },
70
+ ],
71
+ };
72
+
73
+ // WHEN the conversation is imported
74
+ const result = (await importHandler({
75
+ body,
76
+ } as unknown as RouteHandlerArgs)) as {
77
+ ok: boolean;
78
+ imported: number;
79
+ messages: number;
80
+ };
81
+
82
+ // THEN the import succeeds and only counts the renderable turns
83
+ expect(result.ok).toBe(true);
84
+ expect(result.imported).toBe(1);
85
+ expect(result.messages).toBe(2);
86
+
87
+ // AND the persisted rows are exactly the user/assistant turns, never the
88
+ // system scaffolding
89
+ const db = getDb();
90
+ const conv = db.select().from(conversations).all()[0];
91
+ const rows = getMessages(conv.id);
92
+ expect(rows.map((m) => m.role)).toEqual(["user", "assistant"]);
93
+ expect(
94
+ db
95
+ .select()
96
+ .from(messages)
97
+ .all()
98
+ .some((m) => m.role === "system"),
99
+ ).toBe(false);
100
+ });
101
+ });