@vellumai/assistant 0.8.8 → 0.8.9-staging.2

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 (360) hide show
  1. package/ARCHITECTURE.md +6 -6
  2. package/bun.lock +2 -2
  3. package/examples/plugins/echo/README.md +61 -60
  4. package/examples/plugins/echo/hooks/post-tool-use.ts +18 -0
  5. package/examples/plugins/echo/hooks/stop.ts +16 -0
  6. package/examples/plugins/echo/hooks/user-prompt-submit.ts +18 -0
  7. package/examples/plugins/echo/package.json +1 -2
  8. package/examples/plugins/echo/src/emit.ts +19 -0
  9. package/node_modules/@vellumai/skill-host-contracts/src/skill-host.ts +7 -6
  10. package/openapi.yaml +235 -6
  11. package/package.json +2 -2
  12. package/src/__tests__/agent-loop-callsite-precedence.test.ts +69 -14
  13. package/src/__tests__/agent-loop-exit-reason.test.ts +204 -144
  14. package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +50 -35
  15. package/src/__tests__/agent-loop-output-hooks.test.ts +357 -0
  16. package/src/__tests__/agent-loop-override-profile.test.ts +25 -6
  17. package/src/__tests__/agent-loop-provider-error-recording.test.ts +41 -21
  18. package/src/__tests__/agent-loop-thinking.test.ts +36 -20
  19. package/src/__tests__/agent-loop.test.ts +441 -96
  20. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +14 -14
  21. package/src/__tests__/agent-wake-override-profile.test.ts +17 -21
  22. package/src/__tests__/anthropic-provider.test.ts +1 -1
  23. package/src/__tests__/app-builder-tool-scripts.test.ts +21 -0
  24. package/src/__tests__/app-control-flow.test.ts +1 -1
  25. package/src/__tests__/app-dir-path-guard.test.ts +1 -0
  26. package/src/__tests__/app-executors.test.ts +132 -0
  27. package/src/__tests__/approval-cascade.test.ts +5 -4
  28. package/src/__tests__/approval-routes-http.test.ts +4 -1
  29. package/src/__tests__/background-workers-disk-pressure.test.ts +1 -1
  30. package/src/__tests__/channel-approval-routes.test.ts +1 -1
  31. package/src/__tests__/channel-approvals.test.ts +1 -1
  32. package/src/__tests__/compaction-circuit.test.ts +258 -0
  33. package/src/__tests__/compaction-direct.test.ts +132 -0
  34. package/src/__tests__/compaction-events.test.ts +5 -5
  35. package/src/__tests__/compactor-web-search-strip.test.ts +213 -0
  36. package/src/__tests__/context-overflow-reducer.test.ts +1 -1
  37. package/src/__tests__/conversation-abort-tool-results.test.ts +6 -4
  38. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +7 -10
  39. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +78 -119
  40. package/src/__tests__/conversation-agent-loop-overflow.test.ts +142 -218
  41. package/src/__tests__/conversation-agent-loop.test.ts +297 -586
  42. package/src/__tests__/conversation-clean-command.test.ts +5 -2
  43. package/src/__tests__/conversation-confirmation-signals.test.ts +5 -4
  44. package/src/__tests__/conversation-crud-inference-profile.test.ts +7 -9
  45. package/src/__tests__/conversation-history-web-search.test.ts +1 -1
  46. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +4 -4
  47. package/src/__tests__/conversation-process-callsite.test.ts +14 -14
  48. package/src/__tests__/conversation-provider-retry-repair.test.ts +70 -65
  49. package/src/__tests__/conversation-queue.test.ts +9 -9
  50. package/src/__tests__/conversation-runtime-assembly.test.ts +923 -231
  51. package/src/__tests__/conversation-runtime-workspace.test.ts +115 -20
  52. package/src/__tests__/conversation-slash-queue.test.ts +6 -4
  53. package/src/__tests__/conversation-slash-unknown.test.ts +6 -4
  54. package/src/__tests__/conversation-speed-override.test.ts +10 -9
  55. package/src/__tests__/conversation-starter-routes.test.ts +14 -6
  56. package/src/__tests__/conversation-workspace-cache-state.test.ts +23 -20
  57. package/src/__tests__/conversation-workspace-injection.test.ts +68 -6
  58. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +14 -11
  59. package/src/__tests__/conversations-import-system-filter.test.ts +101 -0
  60. package/src/__tests__/credential-security-invariants.test.ts +0 -1
  61. package/src/__tests__/db-acp-history.test.ts +101 -0
  62. package/src/__tests__/dynamic-page-surface.test.ts +31 -0
  63. package/src/__tests__/empty-response-hook.test.ts +1 -1
  64. package/src/__tests__/file-write-tool.test.ts +63 -0
  65. package/src/__tests__/gateway-only-guard.test.ts +12 -2
  66. package/src/__tests__/guardian-grant-minting.test.ts +1 -1
  67. package/src/__tests__/guardian-routing-invariants.test.ts +2 -4
  68. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +1 -1
  69. package/src/__tests__/heartbeat-disk-pressure.test.ts +1 -0
  70. package/src/__tests__/heartbeat-service.test.ts +1 -0
  71. package/src/__tests__/history-repair-hook.test.ts +1 -1
  72. package/src/__tests__/host-app-control-routes.test.ts +1 -1
  73. package/src/__tests__/host-cu-routes-targeted.test.ts +3 -3
  74. package/src/__tests__/inference-profile-reaper.test.ts +62 -0
  75. package/src/__tests__/inference-profile-session-handler.test.ts +86 -0
  76. package/src/__tests__/injector-background-turn.test.ts +13 -23
  77. package/src/__tests__/injector-chain.test.ts +268 -44
  78. package/src/__tests__/injector-disk-pressure.test.ts +210 -52
  79. package/src/__tests__/injector-document-comments.test.ts +97 -114
  80. package/src/__tests__/injector-pkb-v2-silenced.test.ts +2 -2
  81. package/src/__tests__/injector-v3-suppression.test.ts +4 -4
  82. package/src/__tests__/list-messages-client-message-id.test.ts +91 -0
  83. package/src/__tests__/list-messages-hidden-metadata.test.ts +38 -0
  84. package/src/__tests__/memory-retrieval-hook.test.ts +131 -26
  85. package/src/__tests__/memory-v2-static-injector.test.ts +86 -8
  86. package/src/__tests__/parallel-tool.benchmark.test.ts +35 -8
  87. package/src/__tests__/plugin-api-shim.test.ts +6 -9
  88. package/src/__tests__/plugin-bootstrap.test.ts +12 -23
  89. package/src/__tests__/plugin-registry.test.ts +3 -49
  90. package/src/__tests__/plugin-types.test.ts +0 -70
  91. package/src/__tests__/pre-model-call-sanitize.test.ts +109 -0
  92. package/src/__tests__/reaction-persistence.test.ts +1 -1
  93. package/src/__tests__/send-endpoint-busy.test.ts +4 -1
  94. package/src/__tests__/skill-feature-flags-integration.test.ts +33 -0
  95. package/src/__tests__/steer-tool-repair.test.ts +1 -1
  96. package/src/__tests__/subagent-call-site-routing.test.ts +1 -1
  97. package/src/__tests__/subagent-detail.test.ts +25 -7
  98. package/src/__tests__/subagent-fork-notifications.test.ts +1 -3
  99. package/src/__tests__/subagent-fork-spawn.test.ts +1 -1
  100. package/src/__tests__/subagent-manager-notify.test.ts +1 -3
  101. package/src/__tests__/subagent-notify-parent.test.ts +1 -3
  102. package/src/__tests__/subagent-spawn-tool-fork.test.ts +1 -1
  103. package/src/__tests__/title-generate-hook.test.ts +1 -1
  104. package/src/__tests__/tool-error-hook.test.ts +1 -1
  105. package/src/__tests__/tool-result-truncate-hook.test.ts +1 -1
  106. package/src/__tests__/user-plugin-loader.test.ts +54 -286
  107. package/src/acp/__tests__/agent-process.test.ts +161 -0
  108. package/src/acp/__tests__/client-handler.test.ts +40 -0
  109. package/src/acp/__tests__/helpers/acp-history-db.ts +82 -0
  110. package/src/acp/__tests__/helpers/exec-file-stub.ts +106 -0
  111. package/src/acp/__tests__/prepare-agent-env.test.ts +97 -0
  112. package/src/acp/__tests__/session-manager-persistence.test.ts +95 -28
  113. package/src/acp/__tests__/session-manager-resume.test.ts +888 -0
  114. package/src/acp/agent-process.ts +61 -1
  115. package/src/acp/auto-install.test.ts +280 -0
  116. package/src/acp/auto-install.ts +232 -0
  117. package/src/acp/client-handler.ts +31 -0
  118. package/src/acp/feature-gate.test.ts +48 -0
  119. package/src/acp/feature-gate.ts +34 -0
  120. package/src/acp/prepare-agent-env.ts +80 -27
  121. package/src/acp/resolve-agent.test.ts +225 -9
  122. package/src/acp/resolve-agent.ts +122 -17
  123. package/src/acp/resume-hint.ts +23 -0
  124. package/src/acp/session-manager.ts +507 -73
  125. package/src/agent/compaction-circuit.ts +60 -102
  126. package/src/agent/loop.ts +414 -248
  127. package/src/api/responses/conversation-message.ts +14 -1
  128. package/src/approvals/guardian-request-resolvers.ts +1 -1
  129. package/src/background-wake/next-wake.ts +1 -0
  130. package/src/cli/commands/db/__tests__/repair.test.ts +3 -1
  131. package/src/cli/commands/plugins.ts +43 -37
  132. package/src/cli/lib/__tests__/install-from-github.test.ts +429 -111
  133. package/src/cli/lib/__tests__/plugin-catalog-cache.test.ts +196 -0
  134. package/src/cli/lib/__tests__/plugin-details.test.ts +372 -0
  135. package/src/cli/lib/__tests__/plugin-marketplace.test.ts +220 -0
  136. package/src/cli/lib/__tests__/search-plugins.test.ts +226 -32
  137. package/src/cli/lib/install-from-github.ts +464 -55
  138. package/src/cli/lib/plugin-catalog-cache.ts +84 -0
  139. package/src/cli/lib/plugin-details.ts +409 -0
  140. package/src/cli/lib/plugin-marketplace.ts +197 -0
  141. package/src/cli/lib/search-plugins.ts +195 -29
  142. package/src/config/__tests__/feature-flag-registry-guard.test.ts +2 -2
  143. package/src/config/acp-defaults.test.ts +10 -0
  144. package/src/config/acp-defaults.ts +6 -0
  145. package/src/config/bundled-skills/acp/SKILL.md +85 -33
  146. package/src/config/bundled-skills/acp/TOOLS.json +4 -4
  147. package/src/config/bundled-skills/app-builder/SKILL.md +224 -381
  148. package/src/config/bundled-skills/app-builder/TOOLS.json +72 -2
  149. package/src/config/bundled-skills/app-builder/references/DESIGN_SYSTEM.md +48 -0
  150. package/src/config/bundled-skills/app-builder/references/RESPONSIVE.md +57 -0
  151. package/src/config/bundled-skills/app-builder/references/SLIDES.md +38 -0
  152. package/src/config/bundled-skills/app-builder/tools/app-list.ts +62 -0
  153. package/src/config/bundled-skills/app-builder/tools/app-update.ts +18 -0
  154. package/src/config/bundled-skills/document-editor/SKILL.md +28 -23
  155. package/src/config/bundled-skills/document-editor/TOOLS.json +1 -1
  156. package/src/config/bundled-tool-registry.ts +4 -0
  157. package/src/config/call-site-defaults.ts +0 -2
  158. package/src/config/feature-flag-registry.json +15 -6
  159. package/src/config/schemas/call-site-catalog.ts +0 -14
  160. package/src/config/schemas/heartbeat.ts +9 -0
  161. package/src/config/schemas/llm.ts +0 -2
  162. package/src/context/compactor.ts +22 -4
  163. package/src/context/strip-injections.ts +8 -2
  164. package/src/context/window-manager.ts +27 -13
  165. package/src/daemon/conversation-agent-loop-handlers.ts +10 -35
  166. package/src/daemon/conversation-agent-loop.ts +175 -1055
  167. package/src/daemon/conversation-lifecycle.ts +11 -255
  168. package/src/daemon/conversation-process.ts +8 -136
  169. package/src/daemon/conversation-registry.ts +159 -0
  170. package/src/daemon/conversation-runtime-assembly.ts +293 -392
  171. package/src/daemon/conversation-store.ts +9 -90
  172. package/src/daemon/conversation-surfaces.ts +24 -8
  173. package/src/daemon/conversation-workspace.ts +17 -0
  174. package/src/daemon/conversation.ts +404 -57
  175. package/src/daemon/disk-pressure-policy.ts +0 -1
  176. package/src/daemon/external-plugins-bootstrap.ts +14 -19
  177. package/src/daemon/handlers/conversations.ts +3 -1
  178. package/src/daemon/handlers/skills.ts +4 -1
  179. package/src/daemon/host-proxy-preactivation.ts +1 -3
  180. package/src/daemon/lifecycle.ts +21 -7
  181. package/src/daemon/server.ts +2 -0
  182. package/src/daemon/wake-conversation-ops.ts +269 -0
  183. package/src/embedded/plugin-api.ts +2 -2
  184. package/src/export/__tests__/transcript-formatter.test.ts +5 -0
  185. package/src/heartbeat/__tests__/heartbeat-service.test.ts +3 -0
  186. package/src/heartbeat/heartbeat-run-store.ts +23 -1
  187. package/src/heartbeat/heartbeat-service.ts +26 -0
  188. package/src/ipc/__tests__/browser-ipc.test.ts +1 -1
  189. package/src/ipc/__tests__/ui-request-route.test.ts +3 -3
  190. package/src/ipc/skill-routes/__tests__/memory.test.ts +15 -0
  191. package/src/ipc/skill-routes/memory.ts +4 -2
  192. package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +87 -0
  193. package/src/memory/conversation-crud.ts +29 -19
  194. package/src/memory/conversation-starter-checkpoints.ts +1 -0
  195. package/src/memory/db-init.ts +2 -0
  196. package/src/memory/job-handlers/conversation-starters.ts +13 -2
  197. package/src/memory/jobs/__tests__/embed-concept-page.test.ts +5 -4
  198. package/src/memory/jobs-worker.ts +25 -1
  199. package/src/memory/migrations/272-acp-session-history-cwd.ts +36 -0
  200. package/src/memory/migrations/index.ts +1 -0
  201. package/src/memory/schema/acp.ts +4 -0
  202. package/src/memory/v2/__tests__/consolidation-job.test.ts +3 -3
  203. package/src/memory/v2/consolidation-job.ts +13 -4
  204. package/src/plugin-api/constants.ts +4 -0
  205. package/src/plugin-api/index.ts +6 -5
  206. package/src/plugin-api/types.ts +75 -0
  207. package/src/plugins/defaults/compaction/compact.ts +59 -0
  208. package/src/{daemon → plugins/defaults/compaction}/context-overflow-reducer.ts +7 -7
  209. package/src/plugins/defaults/compaction/manager-store.ts +57 -0
  210. package/src/plugins/defaults/compaction/package.json +1 -2
  211. package/src/plugins/defaults/empty-response/package.json +0 -1
  212. package/src/plugins/defaults/history-repair/package.json +0 -1
  213. package/src/plugins/defaults/index.ts +135 -26
  214. package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +100 -52
  215. package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit-temp.ts +184 -74
  216. package/src/plugins/defaults/memory-retrieval/injector-chain.ts +2 -2
  217. package/src/plugins/defaults/{injectors/register.ts → memory-retrieval/injectors.ts} +148 -73
  218. package/src/plugins/defaults/memory-retrieval/unified-turn-context.ts +223 -0
  219. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/assign.test.ts +4 -4
  220. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/live-integration.test.ts +9 -6
  221. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/maintain-job.test.ts +5 -5
  222. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/orchestrate.test.ts +8 -5
  223. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/reconcile.test.ts +2 -2
  224. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/render-injection.test.ts +1 -1
  225. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/router.test.ts +10 -5
  226. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/selection-log-store.test.ts +8 -8
  227. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/selector.test.ts +5 -5
  228. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/shadow-plugin.test.ts +16 -17
  229. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/types.test.ts +2 -2
  230. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/assign.ts +9 -5
  231. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/capabilities.ts +5 -2
  232. package/src/plugins/defaults/memory-v3-shadow/hooks/post-compact.ts +14 -0
  233. package/src/plugins/defaults/memory-v3-shadow/hooks/user-prompt-submit.ts +19 -0
  234. package/src/plugins/defaults/memory-v3-shadow/injector.ts +75 -0
  235. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/maintain-job.ts +15 -8
  236. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/orchestrate.ts +2 -2
  237. package/src/plugins/defaults/memory-v3-shadow/package.json +14 -0
  238. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/page-content.ts +2 -2
  239. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/provider-blocks.ts +1 -1
  240. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/reconcile.ts +7 -3
  241. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/render-injection.ts +1 -1
  242. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/router.ts +5 -5
  243. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/selection-log-store.ts +4 -4
  244. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/selector.ts +7 -7
  245. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/shadow-plugin.ts +32 -94
  246. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/tree.ts +1 -1
  247. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/types.ts +1 -1
  248. package/src/plugins/defaults/title-generate/package.json +0 -1
  249. package/src/plugins/defaults/tool-error/package.json +0 -1
  250. package/src/plugins/defaults/tool-result-truncate/package.json +0 -1
  251. package/src/plugins/pipeline.ts +6 -293
  252. package/src/plugins/registry.ts +9 -37
  253. package/src/plugins/types.ts +76 -381
  254. package/src/plugins/user-loader.ts +30 -127
  255. package/src/prompts/__tests__/system-prompt.test.ts +6 -0
  256. package/src/prompts/templates/BOOTSTRAP-ACTIVATION-RAIL.md +35 -3
  257. package/src/runtime/__tests__/agent-wake.test.ts +555 -691
  258. package/src/runtime/__tests__/interactive-ui.test.ts +1 -1
  259. package/src/runtime/agent-wake.ts +108 -209
  260. package/src/runtime/assistant-event-hub.ts +1 -1
  261. package/src/runtime/channel-approvals.ts +1 -1
  262. package/src/runtime/interactive-ui.ts +1 -1
  263. package/src/runtime/routes/__tests__/acp-routes.test.ts +315 -55
  264. package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +1 -1
  265. package/src/runtime/routes/__tests__/plugins-routes.test.ts +423 -73
  266. package/src/runtime/routes/__tests__/surface-action-routes.test.ts +5 -4
  267. package/src/runtime/routes/__tests__/surface-content-routes.test.ts +4 -1
  268. package/src/runtime/routes/acp-routes.test.ts +89 -25
  269. package/src/runtime/routes/acp-routes.ts +81 -29
  270. package/src/runtime/routes/approval-routes.ts +1 -1
  271. package/src/runtime/routes/browser-routes.ts +1 -1
  272. package/src/runtime/routes/browser-tabs-routes.ts +6 -10
  273. package/src/runtime/routes/conversation-cli-routes.ts +1 -1
  274. package/src/runtime/routes/conversation-list-routes.ts +1 -1
  275. package/src/runtime/routes/conversation-query-routes.ts +1 -1
  276. package/src/runtime/routes/conversation-routes.ts +28 -2
  277. package/src/runtime/routes/conversation-starter-routes.ts +13 -7
  278. package/src/runtime/routes/conversations-import-routes.ts +24 -7
  279. package/src/runtime/routes/host-app-control-routes.ts +1 -1
  280. package/src/runtime/routes/host-cu-routes.ts +1 -1
  281. package/src/runtime/routes/identity-routes.ts +18 -3
  282. package/src/runtime/routes/inbound-message-handler.ts +1 -1
  283. package/src/runtime/routes/inference-profile-session-handler.ts +11 -0
  284. package/src/runtime/routes/inference-profile-session-reaper.ts +6 -0
  285. package/src/runtime/routes/memory-v3-routes.ts +16 -6
  286. package/src/runtime/routes/playground/helpers.ts +1 -1
  287. package/src/runtime/routes/plugins-routes.ts +337 -35
  288. package/src/runtime/routes/surface-conversation-resolver.ts +4 -3
  289. package/src/runtime/routes/work-items-routes.ts +2 -4
  290. package/src/runtime/services/conversation-serializer.ts +1 -1
  291. package/src/signals/cancel.ts +2 -4
  292. package/src/subagent/manager.ts +21 -5
  293. package/src/tools/acp/context.ts +20 -0
  294. package/src/tools/acp/list-agents.test.ts +8 -2
  295. package/src/tools/acp/spawn.test.ts +176 -195
  296. package/src/tools/acp/spawn.ts +37 -172
  297. package/src/tools/acp/steer.test.ts +105 -8
  298. package/src/tools/acp/steer.ts +48 -17
  299. package/src/tools/apps/executors.ts +166 -50
  300. package/src/tools/filesystem/write.ts +34 -0
  301. package/src/tools/subagent/spawn.ts +2 -4
  302. package/src/tools/ui-surface/definitions.ts +25 -5
  303. package/src/workspace/migrations/051-seed-conversation-summarization-callsite.ts +4 -5
  304. package/src/workspace/migrations/097-enable-adaptive-thinking-managed-profiles.ts +69 -45
  305. package/docs/plugins.md +0 -832
  306. package/examples/plugins/echo/register.ts +0 -143
  307. package/src/__tests__/circuit-breaker-pipeline.test.ts +0 -405
  308. package/src/__tests__/compaction-pipeline.test.ts +0 -210
  309. package/src/__tests__/compaction-timeout-recovery.test.ts +0 -251
  310. package/src/__tests__/overflow-reduce-pipeline.test.ts +0 -667
  311. package/src/__tests__/pipeline-runner.test.ts +0 -554
  312. package/src/__tests__/plugin-external-api.test.ts +0 -68
  313. package/src/daemon/wake-target-adapter.ts +0 -253
  314. package/src/plugins/defaults/circuit-breaker/middlewares/circuitBreaker.ts +0 -93
  315. package/src/plugins/defaults/circuit-breaker/package.json +0 -15
  316. package/src/plugins/defaults/circuit-breaker/register.ts +0 -39
  317. package/src/plugins/defaults/compaction/middlewares/compaction.ts +0 -25
  318. package/src/plugins/defaults/compaction/register.ts +0 -35
  319. package/src/plugins/defaults/compaction/terminal.ts +0 -73
  320. package/src/plugins/defaults/empty-response/register.ts +0 -23
  321. package/src/plugins/defaults/history-repair/register.ts +0 -24
  322. package/src/plugins/defaults/overflow-reduce/middlewares/overflowReduce.ts +0 -126
  323. package/src/plugins/defaults/overflow-reduce/package.json +0 -15
  324. package/src/plugins/defaults/overflow-reduce/register.ts +0 -42
  325. package/src/plugins/defaults/title-generate/register.ts +0 -35
  326. package/src/plugins/defaults/tool-error/register.ts +0 -23
  327. package/src/plugins/defaults/tool-result-truncate/register.ts +0 -24
  328. package/src/plugins/external-api.ts +0 -104
  329. package/src/proactive-artifact/aux-message-injector.ts +0 -97
  330. package/src/proactive-artifact/decision.test.ts +0 -226
  331. package/src/proactive-artifact/decision.ts +0 -165
  332. package/src/proactive-artifact/index.ts +0 -7
  333. package/src/proactive-artifact/job.test.ts +0 -962
  334. package/src/proactive-artifact/job.ts +0 -372
  335. package/src/proactive-artifact/message-copy.ts +0 -58
  336. package/src/proactive-artifact/trigger-state.test.ts +0 -286
  337. package/src/proactive-artifact/trigger-state.ts +0 -123
  338. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/capabilities.test.ts +0 -0
  339. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/core.test.ts +0 -0
  340. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/fixtures/eval-turns.json +0 -0
  341. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/fixtures/live-turns.json +0 -0
  342. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/health.test.ts +0 -0
  343. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/needle.test.ts +0 -0
  344. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/provider-blocks.test.ts +0 -0
  345. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/snapshot.test.ts +0 -0
  346. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/tree.test.ts +0 -0
  347. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/working-set-eviction.test.ts +0 -0
  348. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/working-set-skeleton.test.ts +0 -0
  349. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/core.ts +0 -0
  350. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/README.md +0 -0
  351. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/assignments.json +0 -0
  352. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/core.json +0 -0
  353. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-a/topic-x.md +0 -0
  354. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-a/topic-y.md +0 -0
  355. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-b/topic-z.md +0 -0
  356. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/health.ts +0 -0
  357. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/llm-retry.ts +0 -0
  358. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/needle.ts +0 -0
  359. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/snapshot.ts +0 -0
  360. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/working-set.ts +0 -0
@@ -2,25 +2,28 @@
2
2
  * Tests for the ACP route handlers.
3
3
  *
4
4
  * Suites:
5
- * - POST /v1/acp/spawn the three failure paths produced by
6
- * `resolveAcpAgent` (acp_disabled, unknown_agent, binary_not_found).
5
+ * - POST /v1/acp/spawn: resolver failure paths (acp_disabled,
6
+ * unknown_agent) and the body-shape guard. The binary_not_found /
7
+ * auto-install surface is covered in `__tests__/acp-routes.test.ts`.
7
8
  * - POST /v1/acp/spawn (env injection) — CLAUDE_CODE_OAUTH_TOKEN is read
8
9
  * from the credential broker (policy-gated + audited) and merged into
9
10
  * `agentConfig.env` ONLY for the `claude` agent.
10
11
  * - DELETE /v1/acp/sessions?status=completed — the bulk-clear route that
11
12
  * wipes terminal-state rows (completed/failed/cancelled) from
12
- * `acp_session_history` while leaving running/initializing rows intact.
13
+ * `acp_session_history` while leaving running/initializing rows and
14
+ * rows with an in-flight resume intact.
13
15
  * - DELETE /v1/acp/sessions/:id — single-row delete: completed → 200,
14
- * running → 409, unknown id → idempotent { deleted: false }.
16
+ * running or mid-resume → 409, unknown id → idempotent
17
+ * { deleted: false }.
15
18
  *
16
19
  * The spawn tests mirror the resolver's test setup using the shared
17
20
  * `installAcpConfigStub` and `installWhichStub` helpers so the host
18
21
  * environment doesn't influence the resolver's PATH preflight.
19
22
  *
20
- * The single-id delete tests stub `getAcpSessionManager` so we can drive
21
- * the in-memory-status check without spawning real ACP child processes,
22
- * and use the real DB (initialized via the test preload's per-file
23
- * workspace) to verify the row is actually removed.
23
+ * The delete tests stub `getAcpSessionManager` so we can drive the
24
+ * in-memory-status and pending-resume checks without spawning real ACP
25
+ * child processes, and use the real DB (initialized via the test preload's
26
+ * per-file workspace) to verify the row is actually removed.
24
27
  */
25
28
 
26
29
  import {
@@ -48,8 +51,10 @@ afterAll(() => {
48
51
  // in-memory-status check without spawning real ACP processes, and so the
49
52
  // env-injection spawn tests can capture the `agentConfig` arg without
50
53
  // launching a real subprocess. Stored in mutable state so individual tests
51
- // can plant arbitrary states / inspect capture.
54
+ // can plant arbitrary states / inspect capture. `pendingResumeIds` models
55
+ // ids reserved by an in-flight resumeFromHistory (no SessionEntry yet).
52
56
  const inMemoryStates = new Map<string, AcpSessionState>();
57
+ const pendingResumeIds = new Set<string>();
53
58
 
54
59
  interface CapturedSpawn {
55
60
  agent: string;
@@ -70,6 +75,9 @@ mock.module("../../acp/index.js", () => ({
70
75
  if (!state) throw new Error(`ACP session "${id}" not found`);
71
76
  return state;
72
77
  },
78
+ getActiveAndPendingIds: () => [
79
+ ...new Set([...inMemoryStates.keys(), ...pendingResumeIds]),
80
+ ],
73
81
  spawn: async (
74
82
  agent: string,
75
83
  agentConfig: { env?: Record<string, string> },
@@ -226,22 +234,6 @@ describe("POST /v1/acp/spawn", () => {
226
234
  ).rejects.toThrow('Unknown agent "nonexistent"');
227
235
  });
228
236
 
229
- test("throws FailedDependencyError when the agent binary is missing", async () => {
230
- config.setConfig({ agents: {} });
231
- which.setWhich({});
232
-
233
- const handler = getSpawnHandler();
234
- await expect(
235
- handler({
236
- body: {
237
- agent: "codex",
238
- task: "do a thing",
239
- conversationId: "conv-1",
240
- },
241
- }),
242
- ).rejects.toThrow("codex-acp is not on PATH");
243
- });
244
-
245
237
  test("body-shape guard short-circuits before the resolver runs", async () => {
246
238
  config.setConfig({ enabled: false });
247
239
 
@@ -457,6 +449,8 @@ describe("DELETE /v1/acp/sessions?status=completed", () => {
457
449
  });
458
450
 
459
451
  beforeEach(() => {
452
+ inMemoryStates.clear();
453
+ pendingResumeIds.clear();
460
454
  getSqlite().run("DELETE FROM acp_session_history");
461
455
  });
462
456
 
@@ -482,6 +476,57 @@ describe("DELETE /v1/acp/sessions?status=completed", () => {
482
476
  ]);
483
477
  });
484
478
 
479
+ test("excludes terminal rows whose session is active in memory (resumed sessions)", async () => {
480
+ // A resumed session reuses its original id: the in-memory state is
481
+ // `running` while its history row still carries the old terminal
482
+ // status until the next terminal upsert. The bulk delete must not
483
+ // remove that row out from under the live session.
484
+ seedHistoryRow("row-resumed", "completed", 1000);
485
+ seedHistoryRow("row-completed", "completed", 2000);
486
+ inMemoryStates.set("row-resumed", {
487
+ id: "row-resumed",
488
+ agentId: "claude",
489
+ acpSessionId: "proto-resumed",
490
+ parentConversationId: "conv-test",
491
+ status: "running",
492
+ startedAt: 1000,
493
+ });
494
+
495
+ const handler = getBulkDeleteHandler();
496
+ const result = (await handler({
497
+ queryParams: { status: "completed" },
498
+ })) as {
499
+ deleted: number;
500
+ };
501
+ expect(result.deleted).toBe(1);
502
+
503
+ const remaining = listRows();
504
+ expect(remaining).toEqual([{ id: "row-resumed", status: "completed" }]);
505
+ });
506
+
507
+ test("excludes terminal rows whose session has a resume in flight", async () => {
508
+ // A resume that is still awaiting env preparation has no in-memory
509
+ // SessionEntry yet, but its history row (still terminal) must survive:
510
+ // deleting it mid-resume would let the later terminal upsert resurrect
511
+ // it as an orphan row.
512
+ seedHistoryRow("row-pending-resume", "completed", 1000);
513
+ seedHistoryRow("row-completed", "completed", 2000);
514
+ pendingResumeIds.add("row-pending-resume");
515
+
516
+ const handler = getBulkDeleteHandler();
517
+ const result = (await handler({
518
+ queryParams: { status: "completed" },
519
+ })) as {
520
+ deleted: number;
521
+ };
522
+ expect(result.deleted).toBe(1);
523
+
524
+ const remaining = listRows();
525
+ expect(remaining).toEqual([
526
+ { id: "row-pending-resume", status: "completed" },
527
+ ]);
528
+ });
529
+
485
530
  test("returns deleted=0 when no terminal rows are present", async () => {
486
531
  seedHistoryRow("row-running", "running", 1000);
487
532
 
@@ -559,6 +604,7 @@ describe("DELETE /v1/acp/sessions/:id", () => {
559
604
 
560
605
  beforeEach(() => {
561
606
  inMemoryStates.clear();
607
+ pendingResumeIds.clear();
562
608
  getDb().delete(acpSessionHistory).run();
563
609
  });
564
610
 
@@ -610,6 +656,24 @@ describe("DELETE /v1/acp/sessions/:id", () => {
610
656
  },
611
657
  );
612
658
 
659
+ test("returns 409 when the session has a resume in flight (not yet in memory)", async () => {
660
+ pendingResumeIds.add("sess-resuming");
661
+ insertHistoryRow({ id: "sess-resuming", status: "completed" });
662
+
663
+ const handler = getDeleteSessionHandler();
664
+ expect(() => handler({ pathParams: { id: "sess-resuming" } })).toThrow(
665
+ "resume in flight",
666
+ );
667
+
668
+ // Row untouched.
669
+ const remaining = getDb()
670
+ .select()
671
+ .from(acpSessionHistory)
672
+ .where(eq(acpSessionHistory.id, "sess-resuming"))
673
+ .all();
674
+ expect(remaining).toHaveLength(1);
675
+ });
676
+
613
677
  test("idempotent for unknown id — returns { deleted: false }", async () => {
614
678
  const handler = getDeleteSessionHandler();
615
679
  const result = (await handler({
@@ -4,12 +4,14 @@
4
4
  * Exposes spawn, steer, cancel, close, sessions, and permission operations
5
5
  * over HTTP and IPC.
6
6
  */
7
- import { desc, eq, inArray } from "drizzle-orm";
7
+ import { and, desc, eq, inArray, notInArray } from "drizzle-orm";
8
8
  import { z } from "zod";
9
9
 
10
+ import { resolveAgentWithAutoInstall } from "../../acp/auto-install.js";
10
11
  import { getAcpSessionManager } from "../../acp/index.js";
11
12
  import { prepareAgentEnv } from "../../acp/prepare-agent-env.js";
12
- import { resolveAcpAgent } from "../../acp/resolve-agent.js";
13
+ import { formatResolveFailure } from "../../acp/resolve-agent.js";
14
+ import { AcpResumeError } from "../../acp/session-manager.js";
13
15
  import type { AcpSessionState } from "../../acp/types.js";
14
16
  import { getDb } from "../../memory/db-connection.js";
15
17
  import { rawChanges } from "../../memory/raw-query.js";
@@ -61,26 +63,19 @@ async function spawnSession({ body }: RouteHandlerArgs) {
61
63
  throw new BadRequestError("agent, task, and conversationId are required");
62
64
  }
63
65
 
64
- const resolved = resolveAcpAgent(agent);
66
+ // Resolve the agent, silently auto-installing a missing allowlisted
67
+ // adapter binary (see acp/auto-install.ts). Shared with the skill-tool
68
+ // path in tools/acp/spawn.ts; only the transport mapping differs.
69
+ const { resolved, failureMessage } = await resolveAgentWithAutoInstall(agent);
70
+ if (failureMessage) {
71
+ throw new FailedDependencyError(failureMessage);
72
+ }
65
73
  if (!resolved.ok) {
66
- switch (resolved.reason) {
67
- case "acp_disabled":
68
- throw new BadRequestError(resolved.hint);
69
- case "unknown_agent":
70
- throw new BadRequestError(
71
- `Unknown agent "${agent}". Available: ${resolved.available.join(", ")}.`,
72
- );
73
- case "binary_not_found":
74
- throw new FailedDependencyError(
75
- `${resolved.command} is not on PATH. ${resolved.hint}`,
76
- );
77
- default: {
78
- const _exhaustive: never = resolved;
79
- throw new Error(
80
- `Unexpected acp resolver reason: ${(_exhaustive as { reason: string }).reason}`,
81
- );
82
- }
74
+ const message = formatResolveFailure(agent, resolved);
75
+ if (resolved.reason === "binary_not_found") {
76
+ throw new FailedDependencyError(message);
83
77
  }
78
+ throw new BadRequestError(message);
84
79
  }
85
80
 
86
81
  // Inject required env vars and preflight via the shared helper. See
@@ -116,13 +111,31 @@ async function steerSession({ pathParams, body }: RouteHandlerArgs) {
116
111
  throw new BadRequestError("instruction is required");
117
112
  }
118
113
 
114
+ // Sessions no longer in memory (completed, or lost to a daemon restart)
115
+ // are transparently resumed from persisted history with the instruction
116
+ // fired in the same call, mirroring the acp_steer skill tool.
117
+ // broadcastMessage plays the sender role spawnSession gives it, so
118
+ // connected clients render the session.
119
119
  const manager = getAcpSessionManager();
120
120
  try {
121
- await manager.steer(id, instruction);
122
- } catch {
121
+ const { resumed } = await manager.steerOrResume(
122
+ id,
123
+ instruction,
124
+ broadcastMessage,
125
+ );
126
+ return resumed
127
+ ? { acpSessionId: id, steered: true, resumed: true }
128
+ : { acpSessionId: id, steered: true };
129
+ } catch (err) {
130
+ // Resume errors carry the actionable hint (legacy row without cwd,
131
+ // agent capability missing, resolver failures, ...).
132
+ if (err instanceof AcpResumeError) {
133
+ throw new FailedDependencyError(err.message);
134
+ }
135
+ // Unknown ids (no in-memory session, no history row) and plain steer
136
+ // failures both map to 404, as before.
123
137
  throw new NotFoundError("ACP session not found");
124
138
  }
125
- return { acpSessionId: id, steered: true };
126
139
  }
127
140
 
128
141
  async function cancelSession({ pathParams }: RouteHandlerArgs) {
@@ -161,9 +174,25 @@ function bulkDeleteSessions({ queryParams }: RouteHandlerArgs) {
161
174
  "status query param is required and must be 'completed'",
162
175
  );
163
176
  }
177
+ // Exclude sessions currently active in memory AND ids with a resume in
178
+ // flight (reserved but not yet registered): a resumed session reuses its
179
+ // original id, and its history row keeps the old terminal status until
180
+ // the next terminal upsert - a status-only delete would remove the row
181
+ // out from under the live (or resuming) session, and the later terminal
182
+ // upsert would resurrect it. Mirrors the 409 guard on the single-id
183
+ // delete route.
184
+ const activeIds = getAcpSessionManager().getActiveAndPendingIds();
185
+ const terminalFilter = inArray(
186
+ acpSessionHistory.status,
187
+ TERMINAL_SESSION_STATUSES,
188
+ );
164
189
  getDb()
165
190
  .delete(acpSessionHistory)
166
- .where(inArray(acpSessionHistory.status, TERMINAL_SESSION_STATUSES))
191
+ .where(
192
+ activeIds.length > 0
193
+ ? and(terminalFilter, notInArray(acpSessionHistory.id, activeIds))
194
+ : terminalFilter,
195
+ )
167
196
  .run();
168
197
  const deleted = rawChanges();
169
198
  log.info({ deleted }, "Bulk-cleared terminal ACP session history");
@@ -172,9 +201,10 @@ function bulkDeleteSessions({ queryParams }: RouteHandlerArgs) {
172
201
 
173
202
  function deleteSession({ pathParams }: RouteHandlerArgs) {
174
203
  const id = pathParams?.id as string;
204
+ const manager = getAcpSessionManager();
175
205
 
176
206
  try {
177
- const state = getAcpSessionManager().getStatus(id);
207
+ const state = manager.getStatus(id);
178
208
  if (
179
209
  !Array.isArray(state) &&
180
210
  (state.status === "running" || state.status === "initializing")
@@ -185,7 +215,16 @@ function deleteSession({ pathParams }: RouteHandlerArgs) {
185
215
  }
186
216
  } catch (err) {
187
217
  if (err instanceof ConflictError) throw err;
188
- // Not in memory fall through to the (idempotent) DB delete.
218
+ // Not registered in memory, but a resume may be in flight (id reserved
219
+ // while awaiting env preparation). Its history row must survive until
220
+ // the resume lands - the later terminal upsert would resurrect a
221
+ // deleted row.
222
+ if (manager.getActiveAndPendingIds().includes(id)) {
223
+ throw new ConflictError(
224
+ `ACP session "${id}" has a resume in flight. Wait for it to finish before deleting.`,
225
+ );
226
+ }
227
+ // Otherwise fall through to the (idempotent) DB delete.
189
228
  }
190
229
 
191
230
  getDb().delete(acpSessionHistory).where(eq(acpSessionHistory.id, id)).run();
@@ -233,7 +272,11 @@ export const ROUTES: RouteDefinition[] = [
233
272
  },
234
273
  handler: steerSession,
235
274
  summary: "Steer ACP session",
236
- description: "Send a steering instruction to an active ACP session.",
275
+ description:
276
+ "Send a steering instruction to an ACP session. Sessions no longer " +
277
+ "in memory (completed, or lost to a daemon restart) are " +
278
+ "transparently resumed from persisted history first, when the agent " +
279
+ "supports ACP session loading.",
237
280
  tags: ["acp"],
238
281
  requestBody: z.object({
239
282
  instruction: z.string(),
@@ -241,6 +284,12 @@ export const ROUTES: RouteDefinition[] = [
241
284
  responseBody: z.object({
242
285
  acpSessionId: z.string(),
243
286
  steered: z.boolean(),
287
+ resumed: z
288
+ .boolean()
289
+ .optional()
290
+ .describe(
291
+ "True when the session was resumed from persisted history before steering.",
292
+ ),
244
293
  }),
245
294
  },
246
295
  {
@@ -324,7 +373,9 @@ export const ROUTES: RouteDefinition[] = [
324
373
  summary: "Bulk-clear terminal ACP sessions",
325
374
  description:
326
375
  "Remove every terminal-state row (completed/failed/cancelled) from " +
327
- "the persisted acp_session_history table.",
376
+ "the persisted acp_session_history table. Rows whose session is " +
377
+ "currently active in memory (e.g. resumed) or has a resume in " +
378
+ "flight are excluded.",
328
379
  tags: ["acp"],
329
380
  queryParams: [
330
381
  {
@@ -350,7 +401,8 @@ export const ROUTES: RouteDefinition[] = [
350
401
  summary: "Delete ACP session from history",
351
402
  description:
352
403
  "Remove a persisted ACP session row. Rejects with 409 when the " +
353
- "session is still active in memory; idempotent for unknown ids.",
404
+ "session is still active in memory or has a resume in flight; " +
405
+ "idempotent for unknown ids.",
354
406
  tags: ["acp"],
355
407
  responseBody: z.object({
356
408
  deleted: z.boolean(),
@@ -9,7 +9,7 @@
9
9
  */
10
10
  import { z } from "zod";
11
11
 
12
- import { findConversation } from "../../daemon/conversation-store.js";
12
+ import { findConversation } from "../../daemon/conversation-registry.js";
13
13
  import { getConversationByKey } from "../../memory/conversation-key-store.js";
14
14
  import type { UserDecision } from "../../permissions/types.js";
15
15
  import { getLogger } from "../../util/logger.js";
@@ -16,7 +16,7 @@ import {
16
16
  BROWSER_OPERATIONS,
17
17
  type BrowserOperation,
18
18
  } from "../../browser/types.js";
19
- import { findConversation } from "../../daemon/conversation-store.js";
19
+ import { findConversation } from "../../daemon/conversation-registry.js";
20
20
  import type { ContentBlock } from "../../providers/types.js";
21
21
  import { LOCAL_PRINCIPALS } from "../auth/route-policy.js";
22
22
  import type { RouteDefinition, RouteHandlerArgs } from "./types.js";
@@ -7,7 +7,7 @@
7
7
 
8
8
  import { z } from "zod";
9
9
 
10
- import { findConversation } from "../../daemon/conversation-store.js";
10
+ import { findConversation } from "../../daemon/conversation-registry.js";
11
11
  import { getCdpClient } from "../../tools/browser/cdp-client/factory.js";
12
12
  import {
13
13
  clearPinnedTab,
@@ -73,11 +73,7 @@ async function handleBrowserTabs({ body = {} }: RouteHandlerArgs) {
73
73
  ? result.clientId
74
74
  : undefined;
75
75
  if (result?.tabId !== undefined) {
76
- setPinnedTab(
77
- resolvedConversationId,
78
- String(result.tabId),
79
- clientId,
80
- );
76
+ setPinnedTab(resolvedConversationId, String(result.tabId), clientId);
81
77
  }
82
78
  return { ok: true, tab: result };
83
79
  } finally {
@@ -88,10 +84,10 @@ async function handleBrowserTabs({ body = {} }: RouteHandlerArgs) {
88
84
  if (command === "new") {
89
85
  const cdp = getCdpClient(context, cdpOptions);
90
86
  try {
91
- const result = await cdp.send<{ tabId?: number | string; clientId?: string }>(
92
- "Vellum.createTab",
93
- {},
94
- );
87
+ const result = await cdp.send<{
88
+ tabId?: number | string;
89
+ clientId?: string;
90
+ }>("Vellum.createTab", {});
95
91
  // Normalise to string for internal use (setCdpSessionId / setPinnedTab)
96
92
  // and keep the numeric form for the API response.
97
93
  const newTabIdStr: string | undefined =
@@ -9,7 +9,7 @@
9
9
  import { v4 as uuid } from "uuid";
10
10
  import { z } from "zod";
11
11
 
12
- import { findConversation } from "../../daemon/conversation-store.js";
12
+ import { findConversation } from "../../daemon/conversation-registry.js";
13
13
  import { clearAllConversations as clearAllActive } from "../../daemon/handlers/conversations.js";
14
14
  import { formatJson, formatMarkdown } from "../../export/formatter.js";
15
15
  import { ipcCall as ipcCallGateway } from "../../ipc/gateway-client.js";
@@ -9,7 +9,7 @@
9
9
 
10
10
  import { z } from "zod";
11
11
 
12
- import { findConversation } from "../../daemon/conversation-store.js";
12
+ import { findConversation } from "../../daemon/conversation-registry.js";
13
13
  import {
14
14
  type Confidence,
15
15
  getAttentionStateByConversationIds,
@@ -72,7 +72,7 @@ import { type LogRow } from "../../memory/llm-request-log-store.js";
72
72
  import { getMemoryRecallLogByMessageIds } from "../../memory/memory-recall-log-store.js";
73
73
  import { getMemoryV2ActivationLogByMessageIds } from "../../memory/memory-v2-activation-log-store.js";
74
74
  import { MEMORY_V2_CONSOLIDATION_SOURCE } from "../../memory/v2/constants.js";
75
- import { getMemoryV3SelectionForInspector } from "../../memory/v3/selection-log-store.js";
75
+ import { getMemoryV3SelectionForInspector } from "../../plugins/defaults/memory-v3-shadow/selection-log-store.js";
76
76
  import {
77
77
  createConnection,
78
78
  listConnections,
@@ -680,7 +680,14 @@ export function handleListMessages({
680
680
  // assistant message has tool_use blocks but its matching user tool_result
681
681
  // is left visible, the result will render as a standalone orphan because
682
682
  // `mergeToolResultsIntoAssistantMessages` has nothing to merge it into.
683
- const visibleFilter = (m: MessageRow) => !isHiddenMessage(m.metadata);
683
+ //
684
+ // Only renderable roles reach this UI-facing transcript. `system` rows (a
685
+ // permitted `MessageRole`, e.g. skill-authored context) are agent-context
686
+ // scaffolding, never a displayed turn, so they are dropped here at the
687
+ // source rather than narrowed away per-client.
688
+ const visibleFilter = (m: MessageRow) =>
689
+ !isHiddenMessage(m.metadata) &&
690
+ (m.role === "user" || m.role === "assistant");
684
691
 
685
692
  if (isPaginated) {
686
693
  const result = getMessagesPaginated(
@@ -772,14 +779,21 @@ export function handleListMessages({
772
779
  },
773
780
  );
774
781
 
782
+ // `visibleFilter` has already dropped every non-renderable role, so the
783
+ // only values reaching here are `user` and `assistant`; narrow the raw DB
784
+ // `string` to the wire union.
785
+ const role: "user" | "assistant" =
786
+ msg.role === "assistant" ? "assistant" : "user";
787
+
775
788
  return {
776
789
  id: msg.id,
777
- role: msg.role,
790
+ role,
778
791
  content,
779
792
  createdAt: msg.createdAt,
780
793
  sentAt,
781
794
  subagentNotification,
782
795
  slackMessage,
796
+ clientMessageId: msg.clientMessageId ?? undefined,
783
797
  };
784
798
  });
785
799
 
@@ -916,6 +930,7 @@ export function handleListMessages({
916
930
  return {
917
931
  id: m.id ?? "",
918
932
  ...(mergedMessageIds.length > 0 ? { mergedMessageIds } : {}),
933
+ ...(m.clientMessageId ? { clientMessageId: m.clientMessageId } : {}),
919
934
  role: m.role,
920
935
  // Flat plain-text body the legacy Swift client reads directly; see the
921
936
  // `content` field on ConversationMessageSchema for why this must stay.
@@ -1354,6 +1369,11 @@ export async function handleSendMessage(
1354
1369
  mapping.conversationId,
1355
1370
  requestedInferenceProfile,
1356
1371
  );
1372
+ conversation.applyInferenceProfileState({
1373
+ profile: requestedInferenceProfile,
1374
+ sessionId: null,
1375
+ expiresAt: null,
1376
+ });
1357
1377
  }
1358
1378
 
1359
1379
  // Store pre-chat onboarding context on the conversation when this is the
@@ -2657,6 +2677,12 @@ export const ROUTES: RouteDefinition[] = [
2657
2677
  conversationType: z.string().optional(),
2658
2678
  slashCommand: z.string().optional(),
2659
2679
  clientTimezone: z.string().optional(),
2680
+ clientMessageId: z
2681
+ .string()
2682
+ .describe(
2683
+ "Client-generated idempotency nonce. Persisted on the row and echoed back on the message_echo event and the messages snapshot so the client can correlate its optimistic row by identity. Duplicate sends for the same (conversation, clientMessageId) are deduplicated server-side.",
2684
+ )
2685
+ .optional(),
2660
2686
  inferenceProfile: z.string().nullable().optional(),
2661
2687
  riskThreshold: z.enum(VALID_RISK_THRESHOLDS).optional(),
2662
2688
  onboarding: z
@@ -11,6 +11,7 @@ import { z } from "zod";
11
11
  import {
12
12
  checkpointKey,
13
13
  CK_ITEM_COUNT,
14
+ CK_LAST_ATTEMPT_AT,
14
15
  CK_LAST_GEN_AT,
15
16
  countActiveMemoryNodes,
16
17
  getCheckpointValue,
@@ -42,10 +43,11 @@ const starterItemSchema = z.object({
42
43
 
43
44
  type StarterItem = z.infer<typeof starterItemSchema>;
44
45
 
45
- export const CONVERSATION_STARTERS_STALE_TTL_MS = 24 * 60 * 60 * 1000;
46
+ export const CONVERSATION_STARTERS_STALE_TTL_MS = 4 * 60 * 60 * 1000;
46
47
 
47
- /** Minimum interval between re-enqueue attempts triggered by invalid items. */
48
- const REFRESH_COOLDOWN_MS = 60_000;
48
+ /** Minimum interval between re-enqueue attempts (prevents tight retry loops
49
+ * when generation repeatedly fails or produces 0 valid starters). */
50
+ const REFRESH_COOLDOWN_MS = 5 * 60 * 1000;
49
51
 
50
52
  function hasActiveConversationStarterJob(
51
53
  db: ReturnType<typeof getDb>,
@@ -199,12 +201,16 @@ function handleListConversationStarters({
199
201
  Date.now() - lastGenAt >= CONVERSATION_STARTERS_STALE_TTL_MS;
200
202
  const checkpointAhead = lastCount != null && totalActive < lastCount;
201
203
  let hasActiveJob = hasActiveConversationStarterJob(db, scopeId);
204
+ const lastAttemptAt = parseCheckpointInt(
205
+ getCheckpointValue(checkpointKey(CK_LAST_ATTEMPT_AT, scopeId)),
206
+ );
202
207
  const withinCooldown =
203
- lastGenAt != null && Date.now() - lastGenAt < REFRESH_COOLDOWN_MS;
208
+ lastAttemptAt != null && Date.now() - lastAttemptAt < REFRESH_COOLDOWN_MS;
204
209
  const shouldRefresh =
205
- staleByAge ||
206
- checkpointAhead ||
207
- (invalidItemCount > 0 && totalActive > 0 && !withinCooldown);
210
+ !withinCooldown &&
211
+ (staleByAge ||
212
+ checkpointAhead ||
213
+ (invalidItemCount > 0 && totalActive > 0));
208
214
 
209
215
  if (shouldRefresh && !hasActiveJob && isMemoryEnabled()) {
210
216
  enqueueMemoryJob("generate_conversation_starters", { scopeId });
@@ -46,7 +46,10 @@ interface ImportPayload {
46
46
 
47
47
  // -- Helpers (ported from CLI) --
48
48
 
49
- function resolveTimestamps(conv: ImportConversation): {
49
+ function resolveTimestamps(
50
+ conv: ImportConversation,
51
+ messages: ImportMessage[],
52
+ ): {
50
53
  convCreatedAt: number;
51
54
  convUpdatedAt: number;
52
55
  messageTimestamps: number[];
@@ -54,13 +57,23 @@ function resolveTimestamps(conv: ImportConversation): {
54
57
  const now = Date.now();
55
58
  const convCreatedAt = conv.createdAt ?? now;
56
59
  const convUpdatedAt = conv.updatedAt ?? conv.createdAt ?? now;
57
- const messageTimestamps = conv.messages.map((msg, i) => {
60
+ const messageTimestamps = messages.map((msg, i) => {
58
61
  if (msg.createdAt != null) return msg.createdAt;
59
62
  return convCreatedAt + i;
60
63
  });
61
64
  return { convCreatedAt, convUpdatedAt, messageTimestamps };
62
65
  }
63
66
 
67
+ /**
68
+ * The messages store is UI-facing (`ConversationMessage`), so only renderable
69
+ * turns are persisted. Non-renderable roles (e.g. agent-context `system`
70
+ * rows an export might carry) are dropped here rather than imported — the
71
+ * import still succeeds with the displayable turns.
72
+ */
73
+ function isRenderableRole(role: string): role is "user" | "assistant" {
74
+ return role === "user" || role === "assistant";
75
+ }
76
+
64
77
  // -- Handler --
65
78
 
66
79
  async function handleConversationsImport({ body }: RouteHandlerArgs) {
@@ -99,12 +112,16 @@ async function handleConversationsImport({ body }: RouteHandlerArgs) {
99
112
  }
100
113
  }
101
114
 
115
+ const messages = conv.messages.filter((msg) =>
116
+ isRenderableRole(msg.role),
117
+ );
118
+
102
119
  const { convCreatedAt, convUpdatedAt, messageTimestamps } =
103
- resolveTimestamps(conv);
120
+ resolveTimestamps(conv, messages);
104
121
 
105
122
  const conversation = createConversation(conv.title);
106
123
 
107
- for (const msg of conv.messages) {
124
+ for (const msg of messages) {
108
125
  const contentStr =
109
126
  typeof msg.content === "string"
110
127
  ? msg.content
@@ -144,8 +161,8 @@ async function handleConversationsImport({ body }: RouteHandlerArgs) {
144
161
  }
145
162
 
146
163
  // Index messages
147
- for (let i = 0; i < dbMessages.length && i < conv.messages.length; i++) {
148
- const msg = conv.messages[i];
164
+ for (let i = 0; i < dbMessages.length && i < messages.length; i++) {
165
+ const msg = messages[i];
149
166
  const contentStr =
150
167
  typeof msg.content === "string"
151
168
  ? msg.content
@@ -176,7 +193,7 @@ async function handleConversationsImport({ body }: RouteHandlerArgs) {
176
193
  }
177
194
 
178
195
  imported++;
179
- totalMessages += conv.messages.length;
196
+ totalMessages += messages.length;
180
197
  } catch (err) {
181
198
  errors.push({
182
199
  index: idx,
@@ -14,7 +14,7 @@
14
14
  */
15
15
  import { z } from "zod";
16
16
 
17
- import { findConversation } from "../../daemon/conversation-store.js";
17
+ import { findConversation } from "../../daemon/conversation-registry.js";
18
18
  import type {
19
19
  HostAppControlResultPayload,
20
20
  HostAppControlState,
@@ -6,7 +6,7 @@
6
6
  */
7
7
  import { z } from "zod";
8
8
 
9
- import { findConversation } from "../../daemon/conversation-store.js";
9
+ import { findConversation } from "../../daemon/conversation-registry.js";
10
10
  import { ACTOR_PRINCIPALS } from "../auth/route-policy.js";
11
11
  import {
12
12
  enforceSameActorOrThrow,