@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
@@ -10,7 +10,9 @@
10
10
  import { getConfiguredProvider } from "../providers/provider-send-message.js";
11
11
  import type { Provider } from "../providers/types.js";
12
12
  import { runBtwSidechain } from "../runtime/btw-sidechain.js";
13
+ import { publishConversationTitleChanged } from "../runtime/sync/resource-sync-events.js";
13
14
  import { getLogger } from "../util/logger.js";
15
+ import { Mutex } from "../util/mutex.js";
14
16
  import {
15
17
  getConversation,
16
18
  getMessages,
@@ -93,8 +95,6 @@ export interface GenerateTitleParams {
93
95
  userMessage?: string;
94
96
  /** Assistant response text (first turn). */
95
97
  assistantResponse?: string;
96
- /** Callback to emit title update events. */
97
- onTitleUpdated?: (title: string) => void;
98
98
  /** Abort signal. */
99
99
  signal?: AbortSignal;
100
100
  }
@@ -106,14 +106,8 @@ export interface GenerateTitleParams {
106
106
  export async function generateAndPersistConversationTitle(
107
107
  params: GenerateTitleParams,
108
108
  ): Promise<{ title: string; updated: boolean }> {
109
- const {
110
- conversationId,
111
- context,
112
- userMessage,
113
- assistantResponse,
114
- onTitleUpdated,
115
- signal,
116
- } = params;
109
+ const { conversationId, context, userMessage, assistantResponse, signal } =
110
+ params;
117
111
 
118
112
  // Check current title is replaceable
119
113
  const conversation = getConversation(conversationId);
@@ -127,7 +121,7 @@ export async function generateAndPersistConversationTitle(
127
121
  // No provider available — fall back to context-derived title or untitled
128
122
  const fallback = deriveFallbackTitle(context) ?? UNTITLED_FALLBACK;
129
123
  updateConversationTitle(conversationId, fallback, 1);
130
- onTitleUpdated?.(fallback);
124
+ publishConversationTitleChanged(conversationId, fallback);
131
125
  return { title: fallback, updated: true };
132
126
  }
133
127
 
@@ -139,7 +133,7 @@ export async function generateAndPersistConversationTitle(
139
133
  tools: [],
140
134
  callSite: "conversationTitle",
141
135
  signal,
142
- timeoutMs: 10_000,
136
+ timeoutMs: 15_000,
143
137
  });
144
138
  const title = normalizeTitle(result.text);
145
139
  if (title) {
@@ -150,7 +144,7 @@ export async function generateAndPersistConversationTitle(
150
144
  }
151
145
 
152
146
  updateConversationTitle(conversationId, title, 1);
153
- onTitleUpdated?.(title);
147
+ publishConversationTitleChanged(conversationId, title);
154
148
  log.info({ conversationId, title }, "Auto-generated conversation title");
155
149
  return { title, updated: true };
156
150
  }
@@ -167,36 +161,61 @@ export async function generateAndPersistConversationTitle(
167
161
 
168
162
  const fallback = deriveFallbackTitle(context) ?? UNTITLED_FALLBACK;
169
163
  updateConversationTitle(conversationId, fallback, 1);
170
- onTitleUpdated?.(fallback);
164
+ publishConversationTitleChanged(conversationId, fallback);
171
165
  return { title: fallback, updated: true };
172
166
  }
173
167
 
168
+ // ── Serial title-generation queue ────────────────────────────────────
169
+
170
+ /**
171
+ * Each title generation makes an LLM call. Without serialization, burst
172
+ * conversation creation (e.g. 5 new chats in quick succession) fires N
173
+ * concurrent requests that can hit provider rate limits or contend for
174
+ * API capacity, causing later calls to time out and fall back to
175
+ * "Untitled Conversation".
176
+ *
177
+ * A serial queue ensures at most one title-generation LLM call is
178
+ * in-flight at a time. Each call is lightweight (~1–3 s for a ≤5-word
179
+ * title), so the added serial latency is modest and invisible to the
180
+ * user (the UI shows "Generating title…" as a placeholder during the
181
+ * wait). Both initial generation and second-pass regeneration share
182
+ * this queue since they hit the same provider.
183
+ */
184
+ export const titleMutex = new Mutex();
185
+
174
186
  /**
175
187
  * Fire-and-forget wrapper for title generation. Failures are logged
176
188
  * but do not propagate. On failure, replaces loading placeholder with
177
189
  * a stable fallback title so loading state is never permanent.
190
+ *
191
+ * Calls are serialized via {@link titleMutex} so burst conversation
192
+ * creation does not overwhelm the LLM provider.
178
193
  */
179
194
  export function queueGenerateConversationTitle(
180
195
  params: GenerateTitleParams,
181
196
  ): void {
182
- generateAndPersistConversationTitle(params).catch((err) => {
183
- log.warn(
184
- { err, conversationId: params.conversationId },
185
- "Failed to generate conversation title (non-fatal)",
186
- );
187
- // Replace loading placeholder with stable fallback
188
- try {
189
- const conversation = getConversation(params.conversationId);
190
- if (conversation && conversation.title === GENERATING_TITLE) {
191
- const fallback =
192
- deriveFallbackTitle(params.context) ?? UNTITLED_FALLBACK;
193
- updateConversationTitle(params.conversationId, fallback);
194
- params.onTitleUpdated?.(fallback);
197
+ void titleMutex
198
+ .withLock(async () => {
199
+ await generateAndPersistConversationTitle(params);
200
+ })
201
+ .catch((err) => {
202
+ log.warn(
203
+ { err, conversationId: params.conversationId },
204
+ "Failed to generate conversation title (non-fatal)",
205
+ );
206
+ // Replace loading placeholder with stable fallback
207
+ try {
208
+ const conversation = getConversation(params.conversationId);
209
+ if (conversation && conversation.title === GENERATING_TITLE) {
210
+ const fallback =
211
+ deriveFallbackTitle(params.context) ?? UNTITLED_FALLBACK;
212
+ updateConversationTitle(params.conversationId, fallback);
213
+ publishConversationTitleChanged(params.conversationId, fallback);
214
+ }
215
+ } catch {
216
+ // Best-effort
195
217
  }
196
- } catch {
197
- // Best-effort
198
- }
199
- });
218
+ });
200
219
  }
201
220
 
202
221
  // ── Title regeneration (second pass) ─────────────────────────────────
@@ -204,7 +223,6 @@ export function queueGenerateConversationTitle(
204
223
  export interface RegenerateTitleParams {
205
224
  conversationId: string;
206
225
  provider?: Provider;
207
- onTitleUpdated?: (title: string) => void;
208
226
  signal?: AbortSignal;
209
227
  }
210
228
 
@@ -216,7 +234,7 @@ export interface RegenerateTitleParams {
216
234
  export async function regenerateConversationTitle(
217
235
  params: RegenerateTitleParams,
218
236
  ): Promise<{ title: string; updated: boolean }> {
219
- const { conversationId, onTitleUpdated, signal } = params;
237
+ const { conversationId, signal } = params;
220
238
 
221
239
  const conversation = getConversation(conversationId);
222
240
  if (!conversation || !conversation.isAutoTitle) {
@@ -249,7 +267,7 @@ export async function regenerateConversationTitle(
249
267
  tools: [],
250
268
  callSite: "conversationTitle",
251
269
  signal,
252
- timeoutMs: 10_000,
270
+ timeoutMs: 15_000,
253
271
  });
254
272
  const title = normalizeTitle(result.text);
255
273
  if (title) {
@@ -260,7 +278,7 @@ export async function regenerateConversationTitle(
260
278
  }
261
279
 
262
280
  updateConversationTitle(conversationId, title, 1);
263
- onTitleUpdated?.(title);
281
+ publishConversationTitleChanged(conversationId, title);
264
282
  log.info(
265
283
  { conversationId, title },
266
284
  "Re-generated conversation title (second pass)",
@@ -273,16 +291,22 @@ export async function regenerateConversationTitle(
273
291
 
274
292
  /**
275
293
  * Fire-and-forget wrapper for title regeneration.
294
+ *
295
+ * Serialized via the same {@link titleMutex} as initial generation.
276
296
  */
277
297
  export function queueRegenerateConversationTitle(
278
298
  params: RegenerateTitleParams,
279
299
  ): void {
280
- regenerateConversationTitle(params).catch((err) => {
281
- log.warn(
282
- { err, conversationId: params.conversationId },
283
- "Failed to regenerate conversation title (non-fatal)",
284
- );
285
- });
300
+ void titleMutex
301
+ .withLock(async () => {
302
+ await regenerateConversationTitle(params);
303
+ })
304
+ .catch((err) => {
305
+ log.warn(
306
+ { err, conversationId: params.conversationId },
307
+ "Failed to regenerate conversation title (non-fatal)",
308
+ );
309
+ });
286
310
  }
287
311
 
288
312
  // ── Internal helpers ─────────────────────────────────────────────────
@@ -18,6 +18,7 @@ import {
18
18
  addCoreColumns,
19
19
  createApprovalPromptTsTrackerTable,
20
20
  createAssistantInboxTables,
21
+ createAuthFallbackEventsTable,
21
22
  createCallSessionsTables,
22
23
  createCanonicalGuardianTables,
23
24
  createChannelGuardianTables,
@@ -40,6 +41,7 @@ import {
40
41
  migrate230AcpSessionHistory,
41
42
  migrate231RepairMemoryGraphEventDates,
42
43
  migrateA2ATasks,
44
+ migrateAcpSessionHistoryCwd,
43
45
  migrateActivationState,
44
46
  migrateActivationStateFkCascade,
45
47
  migrateAddConversationInferenceProfile,
@@ -188,6 +190,7 @@ import {
188
190
  migrateScheduleReuseConversation,
189
191
  migrateScheduleScriptColumn,
190
192
  migrateScheduleScriptTimeout,
193
+ migrateScheduleSourceConversation,
191
194
  migrateScheduleWakeConversationId,
192
195
  migrateSchemaIndexesAndColumns,
193
196
  migrateScrubCorruptedImageAttachments,
@@ -473,7 +476,10 @@ export function initializeDb(): void {
473
476
  migrateLlmUsageEventsAddAssistantVersion,
474
477
  migrateAddMemoryV3Selections,
475
478
  migrateScheduleScriptTimeout,
479
+ migrateScheduleSourceConversation,
476
480
  migrateMessagesRoleCreatedAtIndex,
481
+ createAuthFallbackEventsTable,
482
+ migrateAcpSessionHistoryCwd,
477
483
  ];
478
484
 
479
485
  // Run each migration step, catching and logging individual failures so one
@@ -0,0 +1,119 @@
1
+ /**
2
+ * Tests for the live, per-conversation {@link ConversationGraphMemory} registry
3
+ * (`getLiveGraphMemory`). The registry lets memory-domain code that only knows
4
+ * a conversation id — notably the post-compaction re-injection hook — reach the
5
+ * same in-memory handle the turn's retrieval mutated, without the agent loop
6
+ * threading the handle through its generic context.
7
+ */
8
+ import { describe, expect, mock, test } from "bun:test";
9
+
10
+ import { createMockLoggerModule } from "../../../__tests__/helpers/mock-logger.js";
11
+
12
+ mock.module("../../../util/logger.js", () => createMockLoggerModule());
13
+
14
+ const { ConversationGraphMemory, getLiveGraphMemory } =
15
+ await import("../conversation-graph-memory.js");
16
+
17
+ describe("ConversationGraphMemory live registry", () => {
18
+ test("a constructed handle is discoverable by its conversation id", () => {
19
+ /**
20
+ * Tests that constructing a handle registers it so it can be looked up.
21
+ */
22
+
23
+ // GIVEN a conversation id
24
+ const conversationId = `conv-registry-${crypto.randomUUID()}`;
25
+
26
+ // WHEN a graph handle is constructed for it
27
+ const handle = new ConversationGraphMemory(conversationId);
28
+
29
+ // THEN the registry returns that exact instance
30
+ expect(getLiveGraphMemory(conversationId)).toBe(handle);
31
+ });
32
+
33
+ test("dispose removes the handle from the registry", () => {
34
+ /**
35
+ * Tests that disposing a handle unregisters it.
36
+ */
37
+
38
+ // GIVEN a registered graph handle
39
+ const conversationId = `conv-registry-${crypto.randomUUID()}`;
40
+ const handle = new ConversationGraphMemory(conversationId);
41
+
42
+ // WHEN it is disposed
43
+ handle.dispose();
44
+
45
+ // THEN the registry no longer resolves the conversation id
46
+ expect(getLiveGraphMemory(conversationId)).toBeUndefined();
47
+ });
48
+
49
+ test("missing id and undefined id both resolve to undefined", () => {
50
+ /**
51
+ * Tests the lookup's absence handling for unknown and undefined keys.
52
+ */
53
+
54
+ // GIVEN no handle registered for these keys
55
+ // WHEN the registry is queried with an unknown id and with undefined
56
+ // THEN both resolve to undefined (the hook treats absence as "no graph")
57
+ expect(
58
+ getLiveGraphMemory(`conv-never-${crypto.randomUUID()}`),
59
+ ).toBeUndefined();
60
+ expect(getLiveGraphMemory(undefined)).toBeUndefined();
61
+ });
62
+
63
+ test("recreating for the same id replaces the registered handle", () => {
64
+ /**
65
+ * Tests that the latest constructed handle wins for a conversation id.
66
+ */
67
+
68
+ // GIVEN a handle already registered for a conversation id
69
+ const conversationId = `conv-registry-${crypto.randomUUID()}`;
70
+ new ConversationGraphMemory(conversationId);
71
+
72
+ // WHEN a second handle is constructed for the same id
73
+ const recreated = new ConversationGraphMemory(conversationId);
74
+
75
+ // THEN the registry resolves to the most recent instance
76
+ expect(getLiveGraphMemory(conversationId)).toBe(recreated);
77
+ });
78
+
79
+ test("disposing a stale handle does not evict the current one", () => {
80
+ /**
81
+ * Tests the dispose guard during an eviction + recreation race: a stale
82
+ * handle must not delete the live entry that now points at a newer handle.
83
+ */
84
+
85
+ // GIVEN a superseded (stale) handle and the current handle for one id
86
+ const conversationId = `conv-registry-${crypto.randomUUID()}`;
87
+ const stale = new ConversationGraphMemory(conversationId);
88
+ const current = new ConversationGraphMemory(conversationId);
89
+
90
+ // WHEN the stale handle is disposed
91
+ stale.dispose();
92
+
93
+ // THEN the registry still resolves to the current handle
94
+ expect(getLiveGraphMemory(conversationId)).toBe(current);
95
+ });
96
+
97
+ test("records and exposes the PKB query vector pair from the registry", () => {
98
+ /**
99
+ * Tests that the dense/sparse PKB query pair recorded during retrieval is
100
+ * readable off the same live handle the PKB-reminder injector looks up.
101
+ */
102
+
103
+ // GIVEN a registered graph handle with no recorded vectors yet
104
+ const conversationId = `conv-registry-${crypto.randomUUID()}`;
105
+ const handle = new ConversationGraphMemory(conversationId);
106
+ expect(handle.pkbQueryVector).toBeUndefined();
107
+ expect(handle.pkbSparseVector).toBeUndefined();
108
+
109
+ // WHEN a retrieval records the turn's dense/sparse pair
110
+ const dense = [0.1, 0.2, 0.3];
111
+ const sparse = { indices: [0, 2], values: [0.5, 0.9] };
112
+ handle.recordPkbQueryVectors(dense, sparse);
113
+
114
+ // THEN a registry consumer reads back the same pair by conversation id
115
+ const looked = getLiveGraphMemory(conversationId);
116
+ expect(looked?.pkbQueryVector).toBe(dense);
117
+ expect(looked?.pkbSparseVector).toBe(sparse);
118
+ });
119
+ });
@@ -62,6 +62,31 @@ const ESTIMATED_IMAGE_TOKENS = 1000;
62
62
  // Per-conversation state
63
63
  // ---------------------------------------------------------------------------
64
64
 
65
+ /**
66
+ * Registry of the live, per-conversation graph handles keyed by conversation
67
+ * id. A handle registers itself on construction and removes itself on
68
+ * {@link ConversationGraphMemory.dispose}, so memory-domain code that only
69
+ * knows a conversation id (e.g. the post-compaction re-injection hook) can
70
+ * reach the same in-memory handle the turn's retrieval used — its live
71
+ * `tracker` / cached-node state, which a DB-reconstructed handle would not
72
+ * carry. Not a general service locator: it holds only the graph handle, and
73
+ * the daemon's `Conversation` remains the owner of the instance's lifecycle.
74
+ */
75
+ const liveByConversation = new Map<string, ConversationGraphMemory>();
76
+
77
+ /**
78
+ * Look up the live {@link ConversationGraphMemory} for a conversation, or
79
+ * `undefined` when none is registered (no active conversation, or a context
80
+ * with no conversation id). Returns the same instance the turn's retrieval
81
+ * mutated, so cached-node re-tracking operates on real state.
82
+ */
83
+ export function getLiveGraphMemory(
84
+ conversationId: string | undefined,
85
+ ): ConversationGraphMemory | undefined {
86
+ if (!conversationId) return undefined;
87
+ return liveByConversation.get(conversationId);
88
+ }
89
+
65
90
  /**
66
91
  * Manages memory graph state for a single conversation.
67
92
  * Create one per Conversation instance. Persists across turns.
@@ -75,9 +100,24 @@ export class ConversationGraphMemory {
75
100
  private lastInjectedBlock: string | null = null;
76
101
  private lastInjectedNodeIds: string[] = [];
77
102
  private lastInjectedImages: Map<string, ResolvedImage> = new Map();
103
+ private lastPkbQueryVector: number[] | undefined;
104
+ private lastPkbSparseVector: QdrantSparseVector | undefined;
78
105
 
79
106
  constructor(conversationId: string) {
80
107
  this.conversationId = conversationId;
108
+ liveByConversation.set(conversationId, this);
109
+ }
110
+
111
+ /**
112
+ * Remove this handle from the live registry. Called from
113
+ * `Conversation.dispose`. Guards against clobbering a newer handle for the
114
+ * same conversation (eviction + recreation) by only deleting the entry when
115
+ * it still points at this instance.
116
+ */
117
+ dispose(): void {
118
+ if (liveByConversation.get(this.conversationId) === this) {
119
+ liveByConversation.delete(this.conversationId);
120
+ }
81
121
  }
82
122
 
83
123
  /**
@@ -305,6 +345,31 @@ export class ConversationGraphMemory {
305
345
  this.tracker.add(this.lastInjectedNodeIds);
306
346
  }
307
347
 
348
+ /**
349
+ * Record the dense/sparse query-vector pair this turn's retrieval produced
350
+ * for PKB hybrid search. The PKB-reminder injector reuses the same
351
+ * embedding (looked up by conversation id via {@link getLiveGraphMemory})
352
+ * rather than receiving it threaded through the agent loop, so the vectors
353
+ * stay owned by the memory-retrieval domain that computes them.
354
+ */
355
+ recordPkbQueryVectors(
356
+ dense: number[] | undefined,
357
+ sparse: QdrantSparseVector | undefined,
358
+ ): void {
359
+ this.lastPkbQueryVector = dense;
360
+ this.lastPkbSparseVector = sparse;
361
+ }
362
+
363
+ /** Dense PKB query vector from this turn's retrieval, or `undefined`. */
364
+ get pkbQueryVector(): number[] | undefined {
365
+ return this.lastPkbQueryVector;
366
+ }
367
+
368
+ /** Sparse PKB query vector paired with {@link pkbQueryVector}. */
369
+ get pkbSparseVector(): QdrantSparseVector | undefined {
370
+ return this.lastPkbSparseVector;
371
+ }
372
+
308
373
  /**
309
374
  * Main entry point — called on every turn before the LLM sees the messages.
310
375
  *
@@ -22,6 +22,7 @@ import {
22
22
  checkpointKey,
23
23
  CK_BATCH,
24
24
  CK_ITEM_COUNT,
25
+ CK_LAST_ATTEMPT_AT,
25
26
  CK_LAST_GEN_AT,
26
27
  countActiveMemoryNodes,
27
28
  getCheckpointValue,
@@ -336,6 +337,7 @@ Bad → Good (incomplete phrase → complete):
336
337
  config: {
337
338
  callSite: "conversationStarters" as const,
338
339
  max_tokens: 2048,
340
+ temperature: 0.7,
339
341
  tool_choice: {
340
342
  type: "tool" as const,
341
343
  name: "store_conversation_starters",
@@ -395,18 +397,27 @@ export async function generateConversationStartersJob(
395
397
  const db = getDb();
396
398
  const now = Date.now();
397
399
 
400
+ // Record attempt time so the route handler's cooldown prevents rapid retries
401
+ // even when generation produces 0 valid starters.
402
+ upsertCheckpoint(
403
+ checkpointKey(CK_LAST_ATTEMPT_AT, scopeId),
404
+ String(now),
405
+ now,
406
+ );
407
+
398
408
  const starters = await generateStarters(scopeId);
399
409
  if (starters.length === 0) {
400
410
  log.info({ scopeId }, "No conversation starters generated");
401
411
 
402
- // Sync checkpoints so both `staleByAge` and `checkpointAhead` clear.
412
+ // Sync the item count checkpoint so `checkpointAhead` clears, but do NOT
413
+ // update `CK_LAST_GEN_AT` — the stale TTL should trigger a retry on the
414
+ // next GET rather than blocking retries for the full TTL window.
403
415
  const totalActive = countActiveMemoryNodes(scopeId);
404
416
  upsertCheckpoint(
405
417
  checkpointKey(CK_ITEM_COUNT, scopeId),
406
418
  String(totalActive),
407
419
  now,
408
420
  );
409
- upsertCheckpoint(checkpointKey(CK_LAST_GEN_AT, scopeId), String(now), now);
410
421
  return;
411
422
  }
412
423
 
@@ -79,6 +79,11 @@ export const SLOW_LLM_JOB_TYPES: MemoryJobType[] = [
79
79
  "graph_bootstrap",
80
80
  ];
81
81
 
82
+ export const MEMORY_V2_CONSOLIDATION_JOB_TRIGGERS = {
83
+ automatic: "automatic",
84
+ manual: "manual",
85
+ } as const;
86
+
82
87
  /** Returns `false` only when `config.memory.enabled` is explicitly `false`; defaults to `true` on missing config or load errors. */
83
88
  export function isMemoryEnabled(): boolean {
84
89
  try {
@@ -337,6 +342,34 @@ export function hasActiveJobOfType(type: MemoryJobType): boolean {
337
342
  );
338
343
  }
339
344
 
345
+ export function isAutomaticConsolidationJob(job: MemoryJob): boolean {
346
+ return job.payload.trigger !== MEMORY_V2_CONSOLIDATION_JOB_TRIGGERS.manual;
347
+ }
348
+
349
+ export function cancelPendingAutomaticConsolidationJobs(): number {
350
+ const db = getDb();
351
+ db.update(memoryJobs)
352
+ .set({
353
+ status: "failed",
354
+ lastError: "automatic_consolidation_disabled",
355
+ updatedAt: Date.now(),
356
+ })
357
+ .where(
358
+ and(
359
+ eq(memoryJobs.type, "memory_v2_consolidate"),
360
+ eq(memoryJobs.status, "pending"),
361
+ or(
362
+ sql`json_extract(${memoryJobs.payload}, '$.trigger') = ${MEMORY_V2_CONSOLIDATION_JOB_TRIGGERS.automatic}`,
363
+ // Legacy rows predate trigger markers and are indistinguishable from
364
+ // automatic enqueues, so disabling the schedule treats them as auto.
365
+ sql`json_extract(${memoryJobs.payload}, '$.trigger') IS NULL`,
366
+ ),
367
+ ),
368
+ )
369
+ .run();
370
+ return rawChanges();
371
+ }
372
+
340
373
  export function enqueuePruneOldLlmRequestLogsJob(retentionMs?: number): string {
341
374
  const db = getDb();
342
375
  const existing = db
@@ -8,6 +8,7 @@ import {
8
8
  diskPressureBackgroundSkipLogFields,
9
9
  shouldLogDiskPressureBackgroundSkip,
10
10
  } from "../daemon/disk-pressure-background-gate.js";
11
+ import { maintainJob as memoryV3MaintainJob } from "../plugins/defaults/memory-v3-shadow/maintain-job.js";
11
12
  import { getLogger } from "../util/logger.js";
12
13
  import { getWorkspaceDir } from "../util/platform.js";
13
14
  import { getMemoryCheckpoint, setMemoryCheckpoint } from "./checkpoints.js";
@@ -67,6 +68,8 @@ import {
67
68
  failMemoryJob,
68
69
  failStalledJobs,
69
70
  hasActiveJobOfType,
71
+ isAutomaticConsolidationJob,
72
+ MEMORY_V2_CONSOLIDATION_JOB_TRIGGERS,
70
73
  type MemoryJob,
71
74
  type MemoryJobType,
72
75
  resetRunningJobsToPending,
@@ -85,10 +88,13 @@ import {
85
88
  memoryV2ConsolidateJob,
86
89
  } from "./v2/consolidation-job.js";
87
90
  import { memoryV2SweepJob } from "./v2/sweep-job.js";
88
- import { maintainJob as memoryV3MaintainJob } from "./v3/maintain-job.js";
89
91
 
90
92
  const log = getLogger("memory-jobs-worker");
91
93
 
94
+ const AUTOMATIC_CONSOLIDATION_JOB_PAYLOAD = {
95
+ trigger: MEMORY_V2_CONSOLIDATION_JOB_TRIGGERS.automatic,
96
+ } as const;
97
+
92
98
  /**
93
99
  * V1 job types that read or write the v1 Qdrant collection via
94
100
  * `getQdrantClient()`. When `memory.v2.enabled` is true, the v1 client is
@@ -609,6 +615,16 @@ async function processJob(
609
615
  await memoryV2SweepJob(job, config);
610
616
  return;
611
617
  case "memory_v2_consolidate":
618
+ if (
619
+ isAutomaticConsolidationJob(job) &&
620
+ !config.memory.v2.consolidation_enabled
621
+ ) {
622
+ log.info(
623
+ { jobId: job.id },
624
+ "Skipping automatic memory v2 consolidation because scheduled consolidation is disabled",
625
+ );
626
+ return;
627
+ }
612
628
  await memoryV2ConsolidateJob(job, config);
613
629
  return;
614
630
  case "memory_v2_migrate":
@@ -734,6 +750,8 @@ export function maybeEnqueueGraphMaintenanceJobs(
734
750
  nowMs = Date.now(),
735
751
  ): void {
736
752
  const v2Active = config.memory.v2.enabled;
753
+ const v2ConsolidationAutomaticEnabled =
754
+ v2Active && config.memory.v2.consolidation_enabled;
737
755
 
738
756
  // The single buffer-drainer entry for the v2-active branch. Referenced again
739
757
  // below by the size-based trigger.
@@ -748,7 +766,9 @@ export function maybeEnqueueGraphMaintenanceJobs(
748
766
  intervalMs: number;
749
767
  jobType: MemoryJobType;
750
768
  }> = v2Active
751
- ? [consolidateEntry]
769
+ ? v2ConsolidationAutomaticEnabled
770
+ ? [consolidateEntry]
771
+ : []
752
772
  : [
753
773
  {
754
774
  key: GRAPH_MAINTENANCE_CHECKPOINTS.decay,
@@ -795,7 +815,11 @@ export function maybeEnqueueGraphMaintenanceJobs(
795
815
  for (const { key, intervalMs, jobType } of schedule) {
796
816
  const lastRun = parseInt(getMemoryCheckpoint(key) ?? "0", 10);
797
817
  if (nowMs - lastRun >= intervalMs) {
798
- enqueueMemoryJob(jobType, {});
818
+ const payload =
819
+ jobType === consolidateEntry.jobType
820
+ ? AUTOMATIC_CONSOLIDATION_JOB_PAYLOAD
821
+ : {};
822
+ enqueueMemoryJob(jobType, payload);
799
823
  setMemoryCheckpoint(key, String(nowMs));
800
824
  if (jobType === consolidateEntry.jobType) enqueuedConsolidate = true;
801
825
  }
@@ -811,14 +835,17 @@ export function maybeEnqueueGraphMaintenanceJobs(
811
835
  // buffer stays over threshold, flooding the queue with redundant LLM work.
812
836
  const maxLines = config.memory.v2.consolidation_max_buffer_lines;
813
837
  if (
814
- v2Active &&
838
+ v2ConsolidationAutomaticEnabled &&
815
839
  !enqueuedConsolidate &&
816
840
  maxLines !== null &&
817
841
  !hasActiveJobOfType(consolidateEntry.jobType)
818
842
  ) {
819
843
  const bufferPath = join(getWorkspaceDir(), "memory", "buffer.md");
820
844
  if (countBufferLines(bufferPath) >= maxLines) {
821
- enqueueMemoryJob(consolidateEntry.jobType, {});
845
+ enqueueMemoryJob(
846
+ consolidateEntry.jobType,
847
+ AUTOMATIC_CONSOLIDATION_JOB_PAYLOAD,
848
+ );
822
849
  setMemoryCheckpoint(consolidateEntry.key, String(nowMs));
823
850
  }
824
851
  }