@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
@@ -19,10 +19,12 @@ import type { AgentLoopConfig, ResolvedSystemPrompt } from "../agent/loop.js";
19
19
  import { AgentLoop } from "../agent/loop.js";
20
20
  import type { AssistantActivityStateEvent } from "../api/events/assistant-activity-state.js";
21
21
  import type {
22
+ ChannelId,
22
23
  InterfaceId,
23
24
  TurnChannelContext,
24
25
  TurnInterfaceContext,
25
26
  } from "../channels/types.js";
27
+ import { parseChannelId, parseInterfaceId } from "../channels/types.js";
26
28
  import { isAssistantFeatureFlagEnabled } from "../config/assistant-feature-flags.js";
27
29
  import {
28
30
  contextWindowConfigFromEffective,
@@ -31,11 +33,10 @@ import {
31
33
  import { resolveCallSiteConfig } from "../config/llm-resolver.js";
32
34
  import { getConfig } from "../config/loader.js";
33
35
  import type { LLMCallSite, Speed } from "../config/schemas/llm.js";
34
- import type { ContextWindowConfig } from "../config/types.js";
35
36
  import {
36
- ContextWindowManager,
37
+ type ContextWindowManager,
37
38
  type ContextWindowResult,
38
- getSummaryFromContextMessage,
39
+ createContextSummaryMessage,
39
40
  } from "../context/window-manager.js";
40
41
  import type { CesClient } from "../credential-execution/client.js";
41
42
  import { EventBus } from "../events/bus.js";
@@ -52,7 +53,8 @@ import { registerToolTraceListener } from "../events/tool-trace-listener.js";
52
53
  import { resolveCanonicalGuardianRequest } from "../memory/canonical-guardian-store.js";
53
54
  import {
54
55
  getConversation,
55
- getConversationOverrideProfileFromRow,
56
+ getMessages,
57
+ resolveOverrideProfile,
56
58
  setConversationHistoryStrippedAt,
57
59
  } from "../memory/conversation-crud.js";
58
60
  import { ConversationGraphMemory } from "../memory/graph/conversation-graph-memory.js";
@@ -60,10 +62,19 @@ import { shouldExposePersonalMemory } from "../memory/v2/static-context.js";
60
62
  import { PermissionPrompter } from "../permissions/prompter.js";
61
63
  import { SecretPrompter } from "../permissions/secret-prompter.js";
62
64
  import type { UserDecision } from "../permissions/types.js";
65
+ import { defaultCompact } from "../plugins/defaults/compaction/compact.js";
66
+ import {
67
+ createContextWindowManager,
68
+ getContextWindowManager,
69
+ } from "../plugins/defaults/compaction/manager-store.js";
70
+ import { repairHistory } from "../plugins/defaults/history-repair/terminal.js";
63
71
  import { buildSystemPrompt } from "../prompts/system-prompt.js";
64
- import type { Message } from "../providers/types.js";
72
+ import type { ContentBlock, Message } from "../providers/types.js";
65
73
  import type { Provider } from "../providers/types.js";
66
- import type { TrustClass } from "../runtime/actor-trust-resolver.js";
74
+ import {
75
+ isUntrustedTrustClass,
76
+ type TrustClass,
77
+ } from "../runtime/actor-trust-resolver.js";
67
78
  import { broadcastMessage } from "../runtime/assistant-event-hub.js";
68
79
  import type { AuthContext } from "../runtime/auth/types.js";
69
80
  import type { InteractiveUiResult } from "../runtime/interactive-ui.js";
@@ -74,6 +85,8 @@ import type { ToolLifecycleEvent } from "../tools/types.js";
74
85
  import type { OnboardingContext } from "../types/onboarding-context.js";
75
86
  import type { AbortReason } from "../util/abort-reasons.js";
76
87
  import { getLogger } from "../util/logger.js";
88
+ import type { WorkspaceGitService } from "../workspace/git-service.js";
89
+ import type { commitTurnChanges } from "../workspace/turn-commit.js";
77
90
  import type { AssistantAttachmentDraft } from "./assistant-attachments.js";
78
91
  import type { AssistantSurface } from "./conversation-agent-loop.js";
79
92
  import {
@@ -85,7 +98,7 @@ import { undo as undoImpl } from "./conversation-history.js";
85
98
  import {
86
99
  abortConversation,
87
100
  disposeConversation,
88
- loadFromDb as loadFromDbImpl,
101
+ reinjectImageSourcePaths,
89
102
  } from "./conversation-lifecycle.js";
90
103
  import type {
91
104
  EnqueueMessageOptions,
@@ -99,10 +112,7 @@ import {
99
112
  } from "./conversation-messaging.js";
100
113
  // Extracted modules
101
114
  import { registerConversationNotifiers } from "./conversation-notifiers.js";
102
- import type {
103
- ProcessConversationContext,
104
- ProcessMessageOptions,
105
- } from "./conversation-process.js";
115
+ import type { ProcessMessageOptions } from "./conversation-process.js";
106
116
  import {
107
117
  drainQueue as drainQueueImpl,
108
118
  processMessage as processMessageImpl,
@@ -128,7 +138,6 @@ import {
128
138
  createResolveToolsCallback,
129
139
  createToolExecutor,
130
140
  } from "./conversation-tool-setup.js";
131
- import { refreshWorkspaceTopLevelContextIfNeeded as refreshWorkspaceImpl } from "./conversation-workspace.js";
132
141
  import { canonicalizeTimeZone } from "./date-context.js";
133
142
  import { HostAppControlProxy } from "./host-app-control-proxy.js";
134
143
  import { HostCuProxy } from "./host-cu-proxy.js";
@@ -139,6 +148,7 @@ import type {
139
148
  SurfaceType,
140
149
  UsageStats,
141
150
  } from "./message-protocol.js";
151
+ import { filterMessagesForUntrustedActor } from "./message-provenance.js";
142
152
  import type { ConversationTransportMetadata } from "./message-types/conversations.js";
143
153
  import { isHostProxyTransport } from "./message-types/conversations.js";
144
154
  import type { ConfirmationStateChanged } from "./message-types/messages.js";
@@ -198,21 +208,33 @@ export class Conversation {
198
208
  estimatedCost: 0,
199
209
  };
200
210
  /** @internal */ readonly systemPrompt: string;
201
- /** @internal */ contextWindowManager: ContextWindowManager;
202
211
  /** @internal */ contextCompactedMessageCount = 0;
203
212
  /** @internal */ contextCompactedAt: number | null = null;
213
+ /** @internal */ contextSummary: string | null = null;
214
+ /** @internal */ slackContextCompactionWatermarkTs: string | null = null;
215
+ /** @internal */ lastNotifiedInferenceProfile: string | null = null;
204
216
  /**
205
- * Set true by `applyCompactionResult` when compaction strips runtime
206
- * injections from the tail. The next agent loop turn reads this flag at
207
- * entry, treats it as a `compactedThisTurn` trigger (re-injecting NOW.md,
208
- * PKB, and the v2 essentials/threads/recent/buffer block), and clears it.
209
- *
210
- * Required because `/compact` runs outside the agent loop without this
211
- * signal, the next turn cannot tell that the static blocks were just
212
- * stripped and never re-emits them.
217
+ * Per-conversation inference-profile override mirrored from the DB row.
218
+ * `inferenceProfileSessionId`/`inferenceProfileExpiresAt` are set when the
219
+ * override is session-backed (expiring); both are null for a sticky
220
+ * override or when no override is active. Hydrated on load and kept in sync
221
+ * by the HTTP setters and the background expiry reaper so the live instance
222
+ * is the single source of truth for the per-turn override derivation.
223
+ * @internal
213
224
  */
214
- /** @internal */ pendingPostCompactReinject = false;
225
+ inferenceProfile: string | null = null;
226
+ /** @internal */ inferenceProfileSessionId: string | null = null;
227
+ /** @internal */ inferenceProfileExpiresAt: number | null = null;
215
228
  /** @internal */ currentRequestId?: string;
229
+ /**
230
+ * The {@link LLMCallSite} of the in-flight turn, set at turn start from
231
+ * `options?.callSite ?? "mainAgent"`. Lets the per-turn plugin context tell
232
+ * the main reply apart from background agent-loop work (compaction,
233
+ * subagents, …) on this same conversation. Per-turn mutable, mirroring
234
+ * {@link currentRequestId}.
235
+ * @internal
236
+ */
237
+ currentCallSite?: LLMCallSite;
216
238
  /** @internal */ hasNoClient = false;
217
239
  /** @internal */ isSubagent = false;
218
240
  /** @internal */ headlessLock = false;
@@ -242,11 +264,37 @@ export class Conversation {
242
264
  /** @internal */ currentTurnTrustContext?: TrustContext;
243
265
  /** @internal */ currentTurnChannelCapabilities?: ChannelCapabilities;
244
266
  /** @internal */ currentTurnOverrideProfile?: string;
267
+ /** @internal */ toolRoutedProfile?: string;
245
268
  /** @internal */ authContext?: AuthContext;
246
269
  /** @internal */ loadedHistoryTrustClass?: TrustClass;
247
270
  /** @internal */ loadedHistoryPersonalMemoryAllowed?: boolean;
248
271
  /** @internal */ voiceCallControlPrompt?: string;
249
272
  /** @internal */ transportHints?: string[];
273
+ /**
274
+ * Optional workspace-git seams, overridable in tests to stub the git
275
+ * initializer and turn-commit behavior. Default to the real
276
+ * implementations in the agent loop when unset.
277
+ * @internal
278
+ */
279
+ getWorkspaceGitService?: (
280
+ workspaceDir: string,
281
+ ) => Pick<WorkspaceGitService, "ensureInitialized">;
282
+ /** @internal */ commitTurnChanges?: typeof commitTurnChanges;
283
+ /**
284
+ * The conversation's immutable creation type (`interactive`, `background`,
285
+ * `scheduled`, …) as stored on the DB row. Cached on load (and set directly
286
+ * for subagent conversations) so the runtime-assembly path can derive the
287
+ * background-turn flag from live state without a per-injection DB read.
288
+ * @internal
289
+ */
290
+ conversationType?: string;
291
+ /**
292
+ * The conversation's creation source (`user`, …) as stored on the DB row,
293
+ * cached on load so the runtime-assembly and disk-pressure paths can read it
294
+ * from live state without a per-turn DB row read.
295
+ * @internal
296
+ */
297
+ source?: string;
250
298
  /** @internal */ assistantId?: string;
251
299
  /** @internal */ commandIntent?: {
252
300
  type: string;
@@ -340,6 +388,26 @@ export class Conversation {
340
388
  */
341
389
  hostUsername?: string;
342
390
  /** @internal */ clientTimezone?: string;
391
+ /**
392
+ * Per-turn temporal snapshot frozen by the agent loop and read by
393
+ * `applyRuntimeInjections` to build the `<turn_context>` timezone-mismatch
394
+ * affordance and `time_since_last_message` line. Holds the client-reported
395
+ * timezone captured at turn start and the human-readable gap since the
396
+ * previous user message (null unless it exceeds the long-absence threshold).
397
+ *
398
+ * Frozen here rather than read live in assembly so the client timezone is not
399
+ * clobbered when a newer message for the same conversation overwrites the
400
+ * live {@link clientTimezone} mid-turn (every inbound message re-applies
401
+ * transport metadata before it is enqueued). Its presence also gates the
402
+ * `<turn_context>` block: assembly emits the block only for turns the loop has
403
+ * frozen a snapshot for. The `current_time` value is computed fresh at each
404
+ * injection so post-compaction re-injections reflect the current wall clock.
405
+ * @internal
406
+ */
407
+ currentTurnTemporalSnapshot?: {
408
+ clientTimezone: string | null;
409
+ timeSinceLastMessage: string | null;
410
+ };
343
411
  public readonly traceEmitter: TraceEmitter;
344
412
  /** @internal */ hasSystemPromptOverride: boolean;
345
413
  /** @internal */ readonly graphMemory: ConversationGraphMemory;
@@ -359,6 +427,22 @@ export class Conversation {
359
427
  /** @internal */ currentTurnChannelContext: TurnChannelContext | null = null;
360
428
  /** @internal */ currentTurnInterfaceContext: TurnInterfaceContext | null =
361
429
  null;
430
+ /**
431
+ * The conversation's recorded origin interface, cached from the DB row at
432
+ * load time. It is immutable once recorded, so it backs the `<turn_context>`
433
+ * interface fallback for turns that don't set a per-turn interface context
434
+ * (regenerate, wake, subagent) without a per-injection DB lookup.
435
+ * @internal
436
+ */
437
+ originInterface: InterfaceId | undefined = undefined;
438
+ /**
439
+ * The conversation's recorded origin channel, cached from the DB row at load
440
+ * time. It is immutable once recorded, so it backs the `<turn_context>`
441
+ * channel fallback for turns that don't set a per-turn channel context
442
+ * (regenerate, wake, subagent) without a per-injection DB lookup.
443
+ * @internal
444
+ */
445
+ originChannel: ChannelId | undefined = undefined;
362
446
  /** @internal */ activityVersion = 0;
363
447
  /** Last emitted activity state message, retained for replay on SSE reconnection. */
364
448
  /** @internal */ lastActivityStateMsg: ServerMessage | null = null;
@@ -518,7 +602,9 @@ export class Conversation {
518
602
  agentLoopConfig.maxTokens = configuredMaxTokens;
519
603
  }
520
604
 
521
- this.agentLoop = new AgentLoop(provider, systemPrompt, {
605
+ this.agentLoop = new AgentLoop({
606
+ provider,
607
+ systemPrompt,
522
608
  conversationId: this.conversationId,
523
609
  config: agentLoopConfig,
524
610
  tools: toolDefs.length > 0 ? toolDefs : undefined,
@@ -526,7 +612,7 @@ export class Conversation {
526
612
  resolveTools,
527
613
  resolveSystemPrompt: resolveSystemPromptCallback,
528
614
  });
529
- this.contextWindowManager = new ContextWindowManager({
615
+ createContextWindowManager({
530
616
  provider,
531
617
  systemPrompt: () => resolveSystemPromptCallback([]).systemPrompt,
532
618
  config: initialContextWindowConfig,
@@ -538,6 +624,24 @@ export class Conversation {
538
624
  });
539
625
  }
540
626
 
627
+ /**
628
+ * The conversation's {@link ContextWindowManager}, owned by the compaction
629
+ * module's per-conversation store. The constructor registers it there; this
630
+ * accessor resolves it on demand so the conversation holds no separate
631
+ * handle. Present for the conversation's whole in-memory lifetime (registered
632
+ * at construction, released on teardown), so a live conversation always
633
+ * resolves an instance.
634
+ */
635
+ /** @internal */ get contextWindowManager(): ContextWindowManager {
636
+ const manager = getContextWindowManager(this.conversationId);
637
+ if (manager == null) {
638
+ throw new Error(
639
+ `ContextWindowManager missing for conversation ${this.conversationId} — the compaction store entry was released while the conversation is still live`,
640
+ );
641
+ }
642
+ return manager;
643
+ }
644
+
541
645
  // ── Onboarding context ───────────────────────────────────────────
542
646
 
543
647
  setOnboardingContext(ctx: OnboardingContext): void {
@@ -548,6 +652,22 @@ export class Conversation {
548
652
  return this.onboardingContext;
549
653
  }
550
654
 
655
+ /**
656
+ * Mirror an inference-profile override write onto the live instance so the
657
+ * per-turn override derivation reads current state without re-fetching the
658
+ * DB row. Called alongside the corresponding DB write by the HTTP setters
659
+ * and the background expiry reaper.
660
+ */
661
+ applyInferenceProfileState(state: {
662
+ profile: string | null;
663
+ sessionId: string | null;
664
+ expiresAt: number | null;
665
+ }): void {
666
+ this.inferenceProfile = state.profile;
667
+ this.inferenceProfileSessionId = state.sessionId;
668
+ this.inferenceProfileExpiresAt = state.expiresAt;
669
+ }
670
+
551
671
  // ── Prompt Cache Warming ─────────────────────────────────────────
552
672
 
553
673
  /**
@@ -605,7 +725,250 @@ export class Conversation {
605
725
  // ── Lifecycle ────────────────────────────────────────────────────
606
726
 
607
727
  async loadFromDb(): Promise<void> {
608
- await loadFromDbImpl(this);
728
+ const trustClass = this.trustContext?.trustClass;
729
+ const allDbMessages = getMessages(this.conversationId);
730
+ const dbMessages = isUntrustedTrustClass(trustClass)
731
+ ? filterMessagesForUntrustedActor(allDbMessages)
732
+ : allDbMessages;
733
+
734
+ const conv = getConversation(this.conversationId);
735
+ this.conversationType = conv?.conversationType ?? undefined;
736
+ this.originInterface = parseInterfaceId(conv?.originInterface) ?? undefined;
737
+ this.originChannel = parseChannelId(conv?.originChannel) ?? undefined;
738
+ this.source = conv?.source ?? undefined;
739
+ this.contextSummary = conv?.contextSummary ?? null;
740
+ this.slackContextCompactionWatermarkTs =
741
+ conv?.slackContextCompactionWatermarkTs ?? null;
742
+ this.lastNotifiedInferenceProfile =
743
+ conv?.lastNotifiedInferenceProfile ?? null;
744
+ this.inferenceProfile = conv?.inferenceProfile ?? null;
745
+ this.inferenceProfileSessionId = conv?.inferenceProfileSessionId ?? null;
746
+ this.inferenceProfileExpiresAt = conv?.inferenceProfileExpiresAt ?? null;
747
+ this.contextCompactedMessageCount = Math.max(
748
+ 0,
749
+ conv?.contextCompactedMessageCount ?? 0,
750
+ );
751
+ this.contextCompactedAt = conv?.contextCompactedAt ?? null;
752
+
753
+ // Untrusted actor views never receive summary-based compaction: a
754
+ // compacted summary can embed trusted/guardian-only detail, so the
755
+ // summary message is suppressed and the persisted history is rendered
756
+ // unsliced. The slice boundary is clamped so it can never drop more rows
757
+ // than exist. Slack chronological context is a separate consumer that
758
+ // applies its own trust filtering downstream, so it reads the raw
759
+ // mirrored count rather than this in-context boundary.
760
+ const inContextCompactedCount = isUntrustedTrustClass(trustClass)
761
+ ? 0
762
+ : Math.min(this.contextCompactedMessageCount, dbMessages.length);
763
+ const contextSummaryForHistory = isUntrustedTrustClass(trustClass)
764
+ ? null
765
+ : this.contextSummary?.trim() || null;
766
+
767
+ // Every injection-strip event (`/clean` or compaction) updates
768
+ // `historyStrippedAt`. Messages older than this should skip metadata
769
+ // rehydration and have any injection prefixes still embedded in their
770
+ // content stripped, so the post-strip view survives reload and forks.
771
+ const historyStrippedAt = conv?.historyStrippedAt ?? null;
772
+ const slicedDbMessages = dbMessages.slice(inContextCompactedCount);
773
+ let preStrippedCount = 0;
774
+ if (historyStrippedAt != null) {
775
+ const boundary = slicedDbMessages.findIndex(
776
+ (m) => m.createdAt >= historyStrippedAt,
777
+ );
778
+ preStrippedCount = boundary === -1 ? slicedDbMessages.length : boundary;
779
+ }
780
+
781
+ // Mirror the injection-time gate (`shouldExposePersonalMemory` in
782
+ // `conversation-agent-loop.ts`) so background/local conversations
783
+ // (sourceChannel `undefined` or `"vellum"`) can rehydrate the persisted
784
+ // v2 static memory block. Use `resolveTrustClass` for parity with the
785
+ // agent loop — it folds in the HTTP-auth-disabled dev bypass so
786
+ // rehydration and injection agree on the effective trust class.
787
+ const personalMemoryAllowed = shouldExposePersonalMemory({
788
+ sourceChannel: this.trustContext?.sourceChannel,
789
+ isTrustedActor: resolveTrustClass(this.trustContext) === "guardian",
790
+ });
791
+ const parsedMessages: Message[] = slicedDbMessages.map((m, index, arr) => {
792
+ const isPreStripped = index < preStrippedCount;
793
+ const role = m.role as "user" | "assistant";
794
+ let content: ContentBlock[];
795
+ try {
796
+ const parsed = JSON.parse(m.content);
797
+ content = Array.isArray(parsed)
798
+ ? parsed
799
+ : [{ type: "text", text: m.content }];
800
+ } catch {
801
+ log.warn(
802
+ { conversationId: this.conversationId, messageId: m.id },
803
+ "Invalid JSON in persisted message content, replacing with safe text block",
804
+ );
805
+ content = [{ type: "text", text: m.content }];
806
+ }
807
+
808
+ content = reinjectImageSourcePaths(content, role, m.metadata);
809
+
810
+ // Re-inject persisted injection blocks from metadata so it survives
811
+ // conversation reloads (eviction, restart, fork).
812
+ if (role === "user" && m.metadata && !isPreStripped) {
813
+ try {
814
+ const meta = JSON.parse(m.metadata);
815
+ const isTail = index === arr.length - 1;
816
+
817
+ // Rehydrate in reverse injection order (innermost block first)
818
+ // so the resulting layout matches `applyRuntimeInjections`'s
819
+ // after-memory-prefix splices in ascending injector order
820
+ // (pkb-context 30, pkb-reminder 35, memory-v2-static 38,
821
+ // now-md 40 — the v2 static block lands inside the memory
822
+ // prefix, so now-md splices *after* it):
823
+ // [<workspace>, <turn_context>, <memory>dynamic</memory>,
824
+ // <info>v2static</info>, <NOW.md>, <system_reminder>,
825
+ // <knowledge_base>, ...original]
826
+ // The v2 static block is replayed verbatim from stored metadata,
827
+ // so rows may carry either `<info>…</info>` or `<memory>…</memory>`
828
+ // depending on when they were persisted.
829
+ // Required so Anthropic's prefix cache keeps matching msg[0]
830
+ // across daemon restart and conversation eviction. The tail
831
+ // row only rehydrates `memoryInjectedBlock` — the next turn
832
+ // re-injects the rest fresh.
833
+ if (!isTail && typeof meta.pkbContextBlock === "string") {
834
+ content = [
835
+ { type: "text" as const, text: meta.pkbContextBlock },
836
+ ...content,
837
+ ];
838
+ }
839
+
840
+ if (!isTail && typeof meta.pkbSystemReminderBlock === "string") {
841
+ content = [
842
+ { type: "text" as const, text: meta.pkbSystemReminderBlock },
843
+ ...content,
844
+ ];
845
+ }
846
+
847
+ if (!isTail && typeof meta.nowScratchpadBlock === "string") {
848
+ content = [
849
+ { type: "text" as const, text: meta.nowScratchpadBlock },
850
+ ...content,
851
+ ];
852
+ }
853
+
854
+ // The v2 static memory block (essentials/threads/recent/buffer
855
+ // wrapped in either `<info>…</info>` or `<memory>…</memory>`)
856
+ // carries personal user memory. Trust-gated to mirror
857
+ // `shouldExposePersonalMemory` at injection time — untrusted-actor
858
+ // views must not read persisted personal memory back through
859
+ // metadata. Skipped on the tail row because the next turn
860
+ // re-injects fresh content on full-mode turns.
861
+ if (
862
+ !isTail &&
863
+ personalMemoryAllowed &&
864
+ typeof meta.memoryV2StaticBlock === "string"
865
+ ) {
866
+ content = [
867
+ { type: "text" as const, text: meta.memoryV2StaticBlock },
868
+ ...content,
869
+ ];
870
+ }
871
+
872
+ // Memory remains rehydrated on all rows (existing behavior).
873
+ // Strip any pre-existing wrapper before re-wrapping so historical
874
+ // rows persisted with the wrapper (v2 path before the
875
+ // injectedBlockText contract was unified with v1's unwrapped form)
876
+ // don't render double-wrapped after rehydrate. Only unwrap when
877
+ // the full <memory>...</memory> pair is present so we don't mutate
878
+ // legitimate unwrapped payloads that happen to start with
879
+ // "<memory>\n" or end with "\n</memory>".
880
+ if (typeof meta.memoryInjectedBlock === "string") {
881
+ const block = meta.memoryInjectedBlock;
882
+ const inner =
883
+ block.startsWith("<memory>\n") && block.endsWith("\n</memory>")
884
+ ? block.slice("<memory>\n".length, -"\n</memory>".length)
885
+ : block;
886
+ content = [
887
+ {
888
+ type: "text" as const,
889
+ text: `<memory>\n${inner}\n</memory>`,
890
+ },
891
+ ...content,
892
+ ];
893
+ }
894
+
895
+ if (!isTail && typeof meta.turnContextBlock === "string") {
896
+ content = [
897
+ { type: "text" as const, text: meta.turnContextBlock },
898
+ ...content,
899
+ ];
900
+ }
901
+
902
+ if (!isTail && typeof meta.workspaceBlock === "string") {
903
+ content = [
904
+ { type: "text" as const, text: meta.workspaceBlock },
905
+ ...content,
906
+ ];
907
+ }
908
+ } catch {
909
+ /* ignore parse errors — metadata may be malformed */
910
+ }
911
+ }
912
+
913
+ return { role, content };
914
+ });
915
+
916
+ // Strip pre-clean messages only; post-clean messages keep the fresh
917
+ // injections they were generated with.
918
+ const messagesBeforeRepair =
919
+ preStrippedCount === 0
920
+ ? parsedMessages
921
+ : [
922
+ ...stripInjectionsForCompaction(
923
+ parsedMessages.slice(0, preStrippedCount),
924
+ ),
925
+ ...parsedMessages.slice(preStrippedCount),
926
+ ];
927
+
928
+ // Normalize the canonical persisted history once at load. Every consumer
929
+ // of `this.messages` outside the agent loop (history edit/undo, PKB context
930
+ // tracking, surfaces) reads this list directly, so it must satisfy the
931
+ // provider pairing/alternation rules before any of them run. The agent
932
+ // loop's pre-run repair only repairs the transient per-turn message list it
933
+ // sends to the provider and never writes back here, so this pass is not
934
+ // redundant with it.
935
+ const { messages: repairedMessages, stats } =
936
+ repairHistory(messagesBeforeRepair);
937
+ if (
938
+ stats.assistantToolResultsMigrated > 0 ||
939
+ stats.missingToolResultsInserted > 0 ||
940
+ stats.orphanToolResultsDowngraded > 0 ||
941
+ stats.consecutiveSameRoleMerged > 0
942
+ ) {
943
+ log.warn(
944
+ { conversationId: this.conversationId, phase: "load", ...stats },
945
+ "Repaired persisted history",
946
+ );
947
+ }
948
+ this.messages = repairedMessages;
949
+
950
+ if (contextSummaryForHistory) {
951
+ this.messages.unshift(
952
+ createContextSummaryMessage(contextSummaryForHistory),
953
+ );
954
+ }
955
+
956
+ if (conv) {
957
+ this.usageStats = {
958
+ inputTokens: conv.totalInputTokens,
959
+ outputTokens: conv.totalOutputTokens,
960
+ estimatedCost: conv.totalEstimatedCost,
961
+ };
962
+ }
963
+
964
+ this.loadedHistoryTrustClass = trustClass;
965
+ this.loadedHistoryPersonalMemoryAllowed = personalMemoryAllowed;
966
+
967
+ log.info(
968
+ { conversationId: this.conversationId, count: this.messages.length },
969
+ "Loaded messages from DB",
970
+ );
971
+
609
972
  this.restoreSurfaceStateFromHistory();
610
973
  this.graphMemory.restoreState();
611
974
  }
@@ -719,9 +1082,7 @@ export class Conversation {
719
1082
  );
720
1083
  }
721
1084
  this.messages = [...messages];
722
- this.contextWindowManager.nonPersistedPrefixCount = messages.length;
723
- this.contextWindowManager.summaryIsInjected =
724
- getSummaryFromContextMessage(messages[0]) != null;
1085
+ this.contextWindowManager.seedNonPersistedPrefix(messages.length);
725
1086
  }
726
1087
 
727
1088
  /**
@@ -1045,20 +1406,14 @@ export class Conversation {
1045
1406
  }
1046
1407
 
1047
1408
  async forceCompact(): Promise<ContextWindowResult> {
1048
- const conversationRow = getConversation(this.conversationId);
1049
- const overrideProfile =
1050
- getConversationOverrideProfileFromRow(conversationRow) ?? null;
1409
+ const overrideProfile = resolveOverrideProfile(this) ?? null;
1051
1410
  const config = getConfig();
1052
1411
  const effectiveContextWindow = resolveEffectiveContextWindow({
1053
1412
  llm: config.llm,
1054
1413
  callSite: "mainAgent",
1055
1414
  overrideProfile: overrideProfile ?? undefined,
1056
1415
  });
1057
- (
1058
- this.contextWindowManager as ContextWindowManager & {
1059
- updateConfig?: (config: ContextWindowConfig) => void;
1060
- }
1061
- ).updateConfig?.(
1416
+ this.contextWindowManager.updateConfig(
1062
1417
  contextWindowConfigFromEffective(
1063
1418
  resolveCallSiteConfig("mainAgent", config.llm, {
1064
1419
  overrideProfile: overrideProfile ?? undefined,
@@ -1073,25 +1428,23 @@ export class Conversation {
1073
1428
  this.channelCapabilities,
1074
1429
  {
1075
1430
  trustClass: this.trustContext?.trustClass,
1076
- contextSummary: conversationRow?.contextSummary,
1077
- contextCompactedMessageCount:
1078
- conversationRow?.contextCompactedMessageCount,
1431
+ contextSummary: this.contextSummary,
1432
+ contextCompactedMessageCount: this.contextCompactedMessageCount,
1079
1433
  slackContextCompactionWatermarkTs:
1080
- conversationRow?.slackContextCompactionWatermarkTs,
1434
+ this.slackContextCompactionWatermarkTs,
1081
1435
  },
1082
1436
  )
1083
1437
  : null;
1084
1438
  const messagesToCompact =
1085
1439
  slackChronologicalContext?.messages ?? this.messages;
1086
- const result = await this.contextWindowManager.maybeCompact(
1087
- messagesToCompact,
1088
- this.abortController?.signal ?? undefined,
1089
- {
1090
- force: true,
1091
- overrideProfile,
1092
- actorTrustClass: this.trustContext?.trustClass,
1093
- },
1094
- );
1440
+ const result = await defaultCompact({
1441
+ manager: this.contextWindowManager,
1442
+ messages: messagesToCompact,
1443
+ signal: this.abortController?.signal ?? undefined,
1444
+ force: true,
1445
+ overrideProfile,
1446
+ actorTrustClass: this.trustContext?.trustClass,
1447
+ });
1095
1448
  // Track circuit-breaker state for user-initiated `/compact` and other
1096
1449
  // forced paths so a successful forced compaction clears a stuck counter
1097
1450
  // and a run of forced failures still trips the breaker. `summaryFailed`
@@ -1099,7 +1452,6 @@ export class Conversation {
1099
1452
  // etc.) — skip those so they don't silently reset the counter.
1100
1453
  if (result.summaryFailed !== undefined) {
1101
1454
  await this.agentLoop.compactionCircuit.recordOutcome(
1102
- this,
1103
1455
  result.summaryFailed,
1104
1456
  this.sendToClient,
1105
1457
  );
@@ -1128,7 +1480,6 @@ export class Conversation {
1128
1480
  const stripped = stripInjectionsForCompaction(this.messages);
1129
1481
  this.messages = stripped;
1130
1482
  await this.graphMemory.onCompacted(0);
1131
- this.pendingPostCompactReinject = true;
1132
1483
  setConversationHistoryStrippedAt(this.conversationId, Date.now());
1133
1484
  const estimatedInputTokens = this.contextWindowManager.estimateInputTokens(
1134
1485
  this.messages,
@@ -1308,13 +1659,13 @@ export class Conversation {
1308
1659
  }
1309
1660
 
1310
1661
  drainQueue(reason: QueueDrainReason = "loop_complete"): Promise<void> {
1311
- return drainQueueImpl(this as ProcessConversationContext, reason);
1662
+ return drainQueueImpl(this, reason);
1312
1663
  }
1313
1664
 
1314
1665
  async processMessage(options: ProcessMessageOptions): Promise<string> {
1315
1666
  this.cacheWarmAbort?.abort();
1316
1667
  this.cacheWarmAbort = undefined;
1317
- return processMessageImpl(this as ProcessConversationContext, {
1668
+ return processMessageImpl(this, {
1318
1669
  ...options,
1319
1670
  onEvent: options.onEvent ?? this.sendToClient,
1320
1671
  });
@@ -1346,10 +1697,6 @@ export class Conversation {
1346
1697
 
1347
1698
  // ── Workspace ────────────────────────────────────────────────────
1348
1699
 
1349
- refreshWorkspaceTopLevelContextIfNeeded(): void {
1350
- refreshWorkspaceImpl(this);
1351
- }
1352
-
1353
1700
  markWorkspaceTopLevelDirty(): void {
1354
1701
  this.workspaceTopLevelDirty = true;
1355
1702
  }
@@ -51,7 +51,6 @@ const BACKGROUND_SOURCES = new Set([
51
51
  "heartbeat",
52
52
  "memory",
53
53
  "notification",
54
- "proactive-artifact",
55
54
  "reminder",
56
55
  "schedule",
57
56
  "task",