@vellumai/assistant 0.8.8 → 0.8.9-staging.1

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 (340) hide show
  1. package/bun.lock +2 -2
  2. package/examples/plugins/echo/README.md +61 -60
  3. package/examples/plugins/echo/hooks/post-tool-use.ts +18 -0
  4. package/examples/plugins/echo/hooks/stop.ts +16 -0
  5. package/examples/plugins/echo/hooks/user-prompt-submit.ts +18 -0
  6. package/examples/plugins/echo/package.json +1 -2
  7. package/examples/plugins/echo/src/emit.ts +19 -0
  8. package/node_modules/@vellumai/skill-host-contracts/src/skill-host.ts +7 -6
  9. package/openapi.yaml +236 -6
  10. package/package.json +2 -2
  11. package/src/__tests__/agent-loop-callsite-precedence.test.ts +69 -14
  12. package/src/__tests__/agent-loop-exit-reason.test.ts +185 -140
  13. package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +50 -35
  14. package/src/__tests__/agent-loop-output-hooks.test.ts +357 -0
  15. package/src/__tests__/agent-loop-override-profile.test.ts +25 -6
  16. package/src/__tests__/agent-loop-provider-error-recording.test.ts +41 -21
  17. package/src/__tests__/agent-loop-thinking.test.ts +36 -20
  18. package/src/__tests__/agent-loop.test.ts +441 -96
  19. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +14 -14
  20. package/src/__tests__/agent-wake-override-profile.test.ts +17 -21
  21. package/src/__tests__/anthropic-provider.test.ts +1 -1
  22. package/src/__tests__/app-control-flow.test.ts +1 -1
  23. package/src/__tests__/app-dir-path-guard.test.ts +1 -0
  24. package/src/__tests__/approval-cascade.test.ts +5 -4
  25. package/src/__tests__/approval-routes-http.test.ts +4 -1
  26. package/src/__tests__/background-workers-disk-pressure.test.ts +1 -1
  27. package/src/__tests__/channel-approval-routes.test.ts +1 -1
  28. package/src/__tests__/channel-approvals.test.ts +1 -1
  29. package/src/__tests__/compaction-circuit.test.ts +258 -0
  30. package/src/__tests__/compaction-direct.test.ts +132 -0
  31. package/src/__tests__/compaction-events.test.ts +5 -5
  32. package/src/__tests__/conversation-abort-tool-results.test.ts +6 -4
  33. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +7 -10
  34. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +59 -103
  35. package/src/__tests__/conversation-agent-loop-overflow.test.ts +94 -189
  36. package/src/__tests__/conversation-agent-loop.test.ts +249 -490
  37. package/src/__tests__/conversation-clean-command.test.ts +5 -2
  38. package/src/__tests__/conversation-confirmation-signals.test.ts +5 -4
  39. package/src/__tests__/conversation-crud-inference-profile.test.ts +7 -9
  40. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +4 -4
  41. package/src/__tests__/conversation-process-callsite.test.ts +14 -14
  42. package/src/__tests__/conversation-provider-retry-repair.test.ts +18 -16
  43. package/src/__tests__/conversation-queue.test.ts +9 -9
  44. package/src/__tests__/conversation-runtime-assembly.test.ts +923 -231
  45. package/src/__tests__/conversation-runtime-workspace.test.ts +115 -20
  46. package/src/__tests__/conversation-slash-queue.test.ts +6 -4
  47. package/src/__tests__/conversation-slash-unknown.test.ts +6 -4
  48. package/src/__tests__/conversation-speed-override.test.ts +10 -9
  49. package/src/__tests__/conversation-starter-routes.test.ts +14 -6
  50. package/src/__tests__/conversation-workspace-cache-state.test.ts +23 -20
  51. package/src/__tests__/conversation-workspace-injection.test.ts +68 -6
  52. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +14 -11
  53. package/src/__tests__/conversations-import-system-filter.test.ts +101 -0
  54. package/src/__tests__/credential-security-invariants.test.ts +0 -1
  55. package/src/__tests__/db-acp-history.test.ts +101 -0
  56. package/src/__tests__/dynamic-page-surface.test.ts +31 -0
  57. package/src/__tests__/empty-response-hook.test.ts +1 -1
  58. package/src/__tests__/file-write-tool.test.ts +63 -0
  59. package/src/__tests__/gateway-only-guard.test.ts +12 -2
  60. package/src/__tests__/guardian-grant-minting.test.ts +1 -1
  61. package/src/__tests__/guardian-routing-invariants.test.ts +2 -4
  62. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +1 -1
  63. package/src/__tests__/heartbeat-disk-pressure.test.ts +1 -0
  64. package/src/__tests__/heartbeat-service.test.ts +1 -0
  65. package/src/__tests__/history-repair-hook.test.ts +1 -1
  66. package/src/__tests__/host-app-control-routes.test.ts +1 -1
  67. package/src/__tests__/host-cu-routes-targeted.test.ts +3 -3
  68. package/src/__tests__/inference-profile-reaper.test.ts +62 -0
  69. package/src/__tests__/inference-profile-session-handler.test.ts +86 -0
  70. package/src/__tests__/injector-background-turn.test.ts +13 -23
  71. package/src/__tests__/injector-chain.test.ts +268 -44
  72. package/src/__tests__/injector-disk-pressure.test.ts +210 -52
  73. package/src/__tests__/injector-document-comments.test.ts +97 -114
  74. package/src/__tests__/injector-pkb-v2-silenced.test.ts +2 -2
  75. package/src/__tests__/injector-v3-suppression.test.ts +4 -4
  76. package/src/__tests__/list-messages-client-message-id.test.ts +91 -0
  77. package/src/__tests__/list-messages-hidden-metadata.test.ts +38 -0
  78. package/src/__tests__/memory-retrieval-hook.test.ts +73 -6
  79. package/src/__tests__/memory-v2-static-injector.test.ts +86 -8
  80. package/src/__tests__/parallel-tool.benchmark.test.ts +35 -8
  81. package/src/__tests__/plugin-api-shim.test.ts +6 -9
  82. package/src/__tests__/plugin-bootstrap.test.ts +12 -23
  83. package/src/__tests__/plugin-registry.test.ts +3 -49
  84. package/src/__tests__/plugin-types.test.ts +0 -70
  85. package/src/__tests__/reaction-persistence.test.ts +1 -1
  86. package/src/__tests__/send-endpoint-busy.test.ts +4 -1
  87. package/src/__tests__/skill-feature-flags-integration.test.ts +33 -0
  88. package/src/__tests__/steer-tool-repair.test.ts +1 -1
  89. package/src/__tests__/subagent-call-site-routing.test.ts +1 -1
  90. package/src/__tests__/subagent-detail.test.ts +25 -7
  91. package/src/__tests__/subagent-fork-notifications.test.ts +1 -3
  92. package/src/__tests__/subagent-fork-spawn.test.ts +1 -1
  93. package/src/__tests__/subagent-manager-notify.test.ts +1 -3
  94. package/src/__tests__/subagent-notify-parent.test.ts +1 -3
  95. package/src/__tests__/subagent-spawn-tool-fork.test.ts +1 -1
  96. package/src/__tests__/title-generate-hook.test.ts +1 -1
  97. package/src/__tests__/tool-error-hook.test.ts +1 -1
  98. package/src/__tests__/tool-result-truncate-hook.test.ts +1 -1
  99. package/src/__tests__/user-plugin-loader.test.ts +54 -286
  100. package/src/acp/__tests__/agent-process.test.ts +161 -0
  101. package/src/acp/__tests__/client-handler.test.ts +40 -0
  102. package/src/acp/__tests__/helpers/acp-history-db.ts +82 -0
  103. package/src/acp/__tests__/helpers/exec-file-stub.ts +101 -0
  104. package/src/acp/__tests__/prepare-agent-env.test.ts +137 -0
  105. package/src/acp/__tests__/session-manager-persistence.test.ts +95 -28
  106. package/src/acp/__tests__/session-manager-resume.test.ts +736 -0
  107. package/src/acp/agent-process.ts +61 -1
  108. package/src/acp/auto-install.test.ts +196 -0
  109. package/src/acp/auto-install.ts +177 -0
  110. package/src/acp/client-handler.ts +31 -0
  111. package/src/acp/feature-gate.test.ts +48 -0
  112. package/src/acp/feature-gate.ts +34 -0
  113. package/src/acp/prepare-agent-env.ts +83 -29
  114. package/src/acp/resolve-agent.test.ts +320 -7
  115. package/src/acp/resolve-agent.ts +182 -18
  116. package/src/acp/resume-hint.ts +25 -0
  117. package/src/acp/session-manager.ts +495 -73
  118. package/src/acp/types.ts +8 -0
  119. package/src/agent/compaction-circuit.ts +60 -102
  120. package/src/agent/loop.ts +390 -240
  121. package/src/api/responses/conversation-message.ts +14 -1
  122. package/src/approvals/guardian-request-resolvers.ts +1 -1
  123. package/src/background-wake/next-wake.ts +1 -0
  124. package/src/cli/commands/db/__tests__/repair.test.ts +3 -1
  125. package/src/cli/commands/plugins.ts +43 -37
  126. package/src/cli/lib/__tests__/install-from-github.test.ts +429 -111
  127. package/src/cli/lib/__tests__/plugin-catalog-cache.test.ts +196 -0
  128. package/src/cli/lib/__tests__/plugin-details.test.ts +372 -0
  129. package/src/cli/lib/__tests__/plugin-marketplace.test.ts +220 -0
  130. package/src/cli/lib/__tests__/search-plugins.test.ts +226 -32
  131. package/src/cli/lib/install-from-github.ts +464 -55
  132. package/src/cli/lib/plugin-catalog-cache.ts +84 -0
  133. package/src/cli/lib/plugin-details.ts +409 -0
  134. package/src/cli/lib/plugin-marketplace.ts +197 -0
  135. package/src/cli/lib/search-plugins.ts +195 -29
  136. package/src/config/__tests__/feature-flag-registry-guard.test.ts +2 -2
  137. package/src/config/acp-defaults.test.ts +10 -0
  138. package/src/config/acp-defaults.ts +6 -0
  139. package/src/config/bundled-skills/acp/SKILL.md +83 -31
  140. package/src/config/bundled-skills/acp/TOOLS.json +4 -4
  141. package/src/config/bundled-skills/app-builder/SKILL.md +224 -381
  142. package/src/config/bundled-skills/app-builder/TOOLS.json +29 -0
  143. package/src/config/bundled-skills/app-builder/references/DESIGN_SYSTEM.md +48 -0
  144. package/src/config/bundled-skills/app-builder/references/RESPONSIVE.md +57 -0
  145. package/src/config/bundled-skills/app-builder/references/SLIDES.md +38 -0
  146. package/src/config/bundled-skills/app-builder/tools/app-list.ts +62 -0
  147. package/src/config/bundled-skills/document-editor/SKILL.md +28 -23
  148. package/src/config/bundled-skills/document-editor/TOOLS.json +1 -1
  149. package/src/config/bundled-tool-registry.ts +2 -0
  150. package/src/config/feature-flag-registry.json +14 -5
  151. package/src/config/schemas/heartbeat.ts +9 -0
  152. package/src/context/strip-injections.ts +8 -2
  153. package/src/context/window-manager.ts +27 -13
  154. package/src/daemon/conversation-agent-loop-handlers.ts +10 -35
  155. package/src/daemon/conversation-agent-loop.ts +167 -1005
  156. package/src/daemon/conversation-lifecycle.ts +11 -255
  157. package/src/daemon/conversation-process.ts +8 -136
  158. package/src/daemon/conversation-registry.ts +159 -0
  159. package/src/daemon/conversation-runtime-assembly.ts +293 -392
  160. package/src/daemon/conversation-store.ts +9 -90
  161. package/src/daemon/conversation-surfaces.ts +24 -8
  162. package/src/daemon/conversation-workspace.ts +17 -0
  163. package/src/daemon/conversation.ts +383 -56
  164. package/src/daemon/external-plugins-bootstrap.ts +14 -19
  165. package/src/daemon/handlers/conversations.ts +3 -1
  166. package/src/daemon/handlers/skills.ts +4 -1
  167. package/src/daemon/host-proxy-preactivation.ts +1 -3
  168. package/src/daemon/lifecycle.ts +21 -0
  169. package/src/daemon/server.ts +2 -0
  170. package/src/daemon/wake-conversation-ops.ts +269 -0
  171. package/src/embedded/plugin-api.ts +2 -2
  172. package/src/export/__tests__/transcript-formatter.test.ts +5 -0
  173. package/src/heartbeat/__tests__/heartbeat-service.test.ts +3 -0
  174. package/src/heartbeat/heartbeat-run-store.ts +23 -1
  175. package/src/heartbeat/heartbeat-service.ts +26 -0
  176. package/src/ipc/__tests__/browser-ipc.test.ts +1 -1
  177. package/src/ipc/__tests__/ui-request-route.test.ts +3 -3
  178. package/src/ipc/skill-routes/__tests__/memory.test.ts +15 -0
  179. package/src/ipc/skill-routes/memory.ts +4 -2
  180. package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +87 -0
  181. package/src/memory/conversation-crud.ts +29 -19
  182. package/src/memory/conversation-starter-checkpoints.ts +1 -0
  183. package/src/memory/db-init.ts +2 -0
  184. package/src/memory/job-handlers/conversation-starters.ts +13 -2
  185. package/src/memory/jobs/__tests__/embed-concept-page.test.ts +5 -4
  186. package/src/memory/jobs-worker.ts +25 -1
  187. package/src/memory/migrations/272-acp-session-history-cwd.ts +36 -0
  188. package/src/memory/migrations/index.ts +1 -0
  189. package/src/memory/schema/acp.ts +4 -0
  190. package/src/memory/v2/__tests__/consolidation-job.test.ts +3 -3
  191. package/src/memory/v2/consolidation-job.ts +13 -4
  192. package/src/plugin-api/constants.ts +4 -0
  193. package/src/plugin-api/index.ts +6 -5
  194. package/src/plugin-api/types.ts +75 -0
  195. package/src/plugins/defaults/compaction/compact.ts +59 -0
  196. package/src/plugins/defaults/compaction/manager-store.ts +44 -0
  197. package/src/plugins/defaults/compaction/package.json +1 -2
  198. package/src/plugins/defaults/empty-response/package.json +0 -1
  199. package/src/plugins/defaults/history-repair/package.json +0 -1
  200. package/src/plugins/defaults/index.ts +135 -26
  201. package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +103 -38
  202. package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit-temp.ts +167 -56
  203. package/src/plugins/defaults/memory-retrieval/injector-chain.ts +2 -2
  204. package/src/plugins/defaults/{injectors/register.ts → memory-retrieval/injectors.ts} +148 -73
  205. package/src/plugins/defaults/memory-retrieval/unified-turn-context.ts +223 -0
  206. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/assign.test.ts +4 -4
  207. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/live-integration.test.ts +9 -6
  208. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/maintain-job.test.ts +5 -5
  209. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/orchestrate.test.ts +8 -5
  210. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/reconcile.test.ts +2 -2
  211. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/render-injection.test.ts +1 -1
  212. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/router.test.ts +10 -5
  213. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/selection-log-store.test.ts +8 -8
  214. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/selector.test.ts +5 -5
  215. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/shadow-plugin.test.ts +16 -17
  216. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/types.test.ts +2 -2
  217. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/assign.ts +9 -5
  218. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/capabilities.ts +5 -2
  219. package/src/plugins/defaults/memory-v3-shadow/hooks/post-compact.ts +14 -0
  220. package/src/plugins/defaults/memory-v3-shadow/hooks/user-prompt-submit.ts +19 -0
  221. package/src/plugins/defaults/memory-v3-shadow/injector.ts +75 -0
  222. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/maintain-job.ts +15 -8
  223. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/orchestrate.ts +2 -2
  224. package/src/plugins/defaults/memory-v3-shadow/package.json +14 -0
  225. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/page-content.ts +2 -2
  226. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/provider-blocks.ts +1 -1
  227. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/reconcile.ts +7 -3
  228. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/render-injection.ts +1 -1
  229. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/router.ts +5 -5
  230. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/selection-log-store.ts +4 -4
  231. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/selector.ts +7 -7
  232. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/shadow-plugin.ts +32 -94
  233. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/tree.ts +1 -1
  234. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/types.ts +1 -1
  235. package/src/plugins/defaults/title-generate/package.json +0 -1
  236. package/src/plugins/defaults/tool-error/package.json +0 -1
  237. package/src/plugins/defaults/tool-result-truncate/package.json +0 -1
  238. package/src/plugins/pipeline.ts +6 -293
  239. package/src/plugins/registry.ts +9 -37
  240. package/src/plugins/types.ts +76 -381
  241. package/src/plugins/user-loader.ts +30 -127
  242. package/src/proactive-artifact/aux-message-injector.ts +1 -1
  243. package/src/proactive-artifact/job.test.ts +1 -1
  244. package/src/prompts/__tests__/system-prompt.test.ts +6 -0
  245. package/src/prompts/templates/BOOTSTRAP-ACTIVATION-RAIL.md +35 -3
  246. package/src/runtime/__tests__/agent-wake.test.ts +555 -691
  247. package/src/runtime/__tests__/interactive-ui.test.ts +1 -1
  248. package/src/runtime/agent-wake.ts +108 -209
  249. package/src/runtime/assistant-event-hub.ts +1 -1
  250. package/src/runtime/channel-approvals.ts +1 -1
  251. package/src/runtime/interactive-ui.ts +1 -1
  252. package/src/runtime/routes/__tests__/acp-routes.test.ts +283 -55
  253. package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +1 -1
  254. package/src/runtime/routes/__tests__/plugins-routes.test.ts +398 -73
  255. package/src/runtime/routes/__tests__/surface-action-routes.test.ts +5 -4
  256. package/src/runtime/routes/__tests__/surface-content-routes.test.ts +4 -1
  257. package/src/runtime/routes/acp-routes.test.ts +89 -25
  258. package/src/runtime/routes/acp-routes.ts +81 -29
  259. package/src/runtime/routes/approval-routes.ts +1 -1
  260. package/src/runtime/routes/browser-routes.ts +1 -1
  261. package/src/runtime/routes/browser-tabs-routes.ts +6 -10
  262. package/src/runtime/routes/conversation-cli-routes.ts +1 -1
  263. package/src/runtime/routes/conversation-list-routes.ts +1 -1
  264. package/src/runtime/routes/conversation-query-routes.ts +1 -1
  265. package/src/runtime/routes/conversation-routes.ts +28 -2
  266. package/src/runtime/routes/conversation-starter-routes.ts +13 -7
  267. package/src/runtime/routes/conversations-import-routes.ts +24 -7
  268. package/src/runtime/routes/host-app-control-routes.ts +1 -1
  269. package/src/runtime/routes/host-cu-routes.ts +1 -1
  270. package/src/runtime/routes/identity-routes.ts +18 -3
  271. package/src/runtime/routes/inbound-message-handler.ts +1 -1
  272. package/src/runtime/routes/inference-profile-session-handler.ts +11 -0
  273. package/src/runtime/routes/inference-profile-session-reaper.ts +6 -0
  274. package/src/runtime/routes/memory-v3-routes.ts +16 -6
  275. package/src/runtime/routes/playground/helpers.ts +1 -1
  276. package/src/runtime/routes/plugins-routes.ts +336 -35
  277. package/src/runtime/routes/surface-conversation-resolver.ts +4 -3
  278. package/src/runtime/routes/work-items-routes.ts +2 -4
  279. package/src/runtime/services/conversation-serializer.ts +1 -1
  280. package/src/signals/cancel.ts +2 -4
  281. package/src/subagent/manager.ts +21 -5
  282. package/src/tools/acp/context.ts +20 -0
  283. package/src/tools/acp/list-agents.test.ts +7 -1
  284. package/src/tools/acp/spawn.test.ts +158 -55
  285. package/src/tools/acp/spawn.ts +47 -72
  286. package/src/tools/acp/steer.test.ts +105 -8
  287. package/src/tools/acp/steer.ts +48 -17
  288. package/src/tools/apps/executors.ts +13 -8
  289. package/src/tools/filesystem/write.ts +34 -0
  290. package/src/tools/subagent/spawn.ts +2 -4
  291. package/src/tools/ui-surface/definitions.ts +25 -5
  292. package/src/workspace/migrations/051-seed-conversation-summarization-callsite.ts +4 -5
  293. package/src/workspace/migrations/097-enable-adaptive-thinking-managed-profiles.ts +69 -45
  294. package/docs/plugins.md +0 -832
  295. package/examples/plugins/echo/register.ts +0 -143
  296. package/src/__tests__/circuit-breaker-pipeline.test.ts +0 -405
  297. package/src/__tests__/compaction-pipeline.test.ts +0 -210
  298. package/src/__tests__/compaction-timeout-recovery.test.ts +0 -251
  299. package/src/__tests__/overflow-reduce-pipeline.test.ts +0 -667
  300. package/src/__tests__/pipeline-runner.test.ts +0 -554
  301. package/src/__tests__/plugin-external-api.test.ts +0 -68
  302. package/src/daemon/wake-target-adapter.ts +0 -253
  303. package/src/plugins/defaults/circuit-breaker/middlewares/circuitBreaker.ts +0 -93
  304. package/src/plugins/defaults/circuit-breaker/package.json +0 -15
  305. package/src/plugins/defaults/circuit-breaker/register.ts +0 -39
  306. package/src/plugins/defaults/compaction/middlewares/compaction.ts +0 -25
  307. package/src/plugins/defaults/compaction/register.ts +0 -35
  308. package/src/plugins/defaults/compaction/terminal.ts +0 -73
  309. package/src/plugins/defaults/empty-response/register.ts +0 -23
  310. package/src/plugins/defaults/history-repair/register.ts +0 -24
  311. package/src/plugins/defaults/overflow-reduce/middlewares/overflowReduce.ts +0 -126
  312. package/src/plugins/defaults/overflow-reduce/package.json +0 -15
  313. package/src/plugins/defaults/overflow-reduce/register.ts +0 -42
  314. package/src/plugins/defaults/title-generate/register.ts +0 -35
  315. package/src/plugins/defaults/tool-error/register.ts +0 -23
  316. package/src/plugins/defaults/tool-result-truncate/register.ts +0 -24
  317. package/src/plugins/external-api.ts +0 -104
  318. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/capabilities.test.ts +0 -0
  319. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/core.test.ts +0 -0
  320. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/fixtures/eval-turns.json +0 -0
  321. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/fixtures/live-turns.json +0 -0
  322. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/health.test.ts +0 -0
  323. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/needle.test.ts +0 -0
  324. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/provider-blocks.test.ts +0 -0
  325. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/snapshot.test.ts +0 -0
  326. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/tree.test.ts +0 -0
  327. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/working-set-eviction.test.ts +0 -0
  328. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/working-set-skeleton.test.ts +0 -0
  329. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/core.ts +0 -0
  330. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/README.md +0 -0
  331. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/assignments.json +0 -0
  332. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/core.json +0 -0
  333. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-a/topic-x.md +0 -0
  334. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-a/topic-y.md +0 -0
  335. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-b/topic-z.md +0 -0
  336. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/health.ts +0 -0
  337. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/llm-retry.ts +0 -0
  338. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/needle.ts +0 -0
  339. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/snapshot.ts +0 -0
  340. /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,16 @@ 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 { createContextWindowManager } from "../plugins/defaults/compaction/manager-store.js";
67
+ import { repairHistory } from "../plugins/defaults/history-repair/terminal.js";
63
68
  import { buildSystemPrompt } from "../prompts/system-prompt.js";
64
- import type { Message } from "../providers/types.js";
69
+ import type { ContentBlock, Message } from "../providers/types.js";
65
70
  import type { Provider } from "../providers/types.js";
66
- import type { TrustClass } from "../runtime/actor-trust-resolver.js";
71
+ import {
72
+ isUntrustedTrustClass,
73
+ type TrustClass,
74
+ } from "../runtime/actor-trust-resolver.js";
67
75
  import { broadcastMessage } from "../runtime/assistant-event-hub.js";
68
76
  import type { AuthContext } from "../runtime/auth/types.js";
69
77
  import type { InteractiveUiResult } from "../runtime/interactive-ui.js";
@@ -74,6 +82,8 @@ import type { ToolLifecycleEvent } from "../tools/types.js";
74
82
  import type { OnboardingContext } from "../types/onboarding-context.js";
75
83
  import type { AbortReason } from "../util/abort-reasons.js";
76
84
  import { getLogger } from "../util/logger.js";
85
+ import type { WorkspaceGitService } from "../workspace/git-service.js";
86
+ import type { commitTurnChanges } from "../workspace/turn-commit.js";
77
87
  import type { AssistantAttachmentDraft } from "./assistant-attachments.js";
78
88
  import type { AssistantSurface } from "./conversation-agent-loop.js";
79
89
  import {
@@ -85,7 +95,7 @@ import { undo as undoImpl } from "./conversation-history.js";
85
95
  import {
86
96
  abortConversation,
87
97
  disposeConversation,
88
- loadFromDb as loadFromDbImpl,
98
+ reinjectImageSourcePaths,
89
99
  } from "./conversation-lifecycle.js";
90
100
  import type {
91
101
  EnqueueMessageOptions,
@@ -99,10 +109,7 @@ import {
99
109
  } from "./conversation-messaging.js";
100
110
  // Extracted modules
101
111
  import { registerConversationNotifiers } from "./conversation-notifiers.js";
102
- import type {
103
- ProcessConversationContext,
104
- ProcessMessageOptions,
105
- } from "./conversation-process.js";
112
+ import type { ProcessMessageOptions } from "./conversation-process.js";
106
113
  import {
107
114
  drainQueue as drainQueueImpl,
108
115
  processMessage as processMessageImpl,
@@ -128,7 +135,6 @@ import {
128
135
  createResolveToolsCallback,
129
136
  createToolExecutor,
130
137
  } from "./conversation-tool-setup.js";
131
- import { refreshWorkspaceTopLevelContextIfNeeded as refreshWorkspaceImpl } from "./conversation-workspace.js";
132
138
  import { canonicalizeTimeZone } from "./date-context.js";
133
139
  import { HostAppControlProxy } from "./host-app-control-proxy.js";
134
140
  import { HostCuProxy } from "./host-cu-proxy.js";
@@ -139,6 +145,7 @@ import type {
139
145
  SurfaceType,
140
146
  UsageStats,
141
147
  } from "./message-protocol.js";
148
+ import { filterMessagesForUntrustedActor } from "./message-provenance.js";
142
149
  import type { ConversationTransportMetadata } from "./message-types/conversations.js";
143
150
  import { isHostProxyTransport } from "./message-types/conversations.js";
144
151
  import type { ConfirmationStateChanged } from "./message-types/messages.js";
@@ -201,18 +208,31 @@ export class Conversation {
201
208
  /** @internal */ contextWindowManager: ContextWindowManager;
202
209
  /** @internal */ contextCompactedMessageCount = 0;
203
210
  /** @internal */ contextCompactedAt: number | null = null;
211
+ /** @internal */ contextSummary: string | null = null;
212
+ /** @internal */ slackContextCompactionWatermarkTs: string | null = null;
213
+ /** @internal */ lastNotifiedInferenceProfile: string | null = null;
204
214
  /**
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.
215
+ * Per-conversation inference-profile override mirrored from the DB row.
216
+ * `inferenceProfileSessionId`/`inferenceProfileExpiresAt` are set when the
217
+ * override is session-backed (expiring); both are null for a sticky
218
+ * override or when no override is active. Hydrated on load and kept in sync
219
+ * by the HTTP setters and the background expiry reaper so the live instance
220
+ * is the single source of truth for the per-turn override derivation.
221
+ * @internal
213
222
  */
214
- /** @internal */ pendingPostCompactReinject = false;
223
+ inferenceProfile: string | null = null;
224
+ /** @internal */ inferenceProfileSessionId: string | null = null;
225
+ /** @internal */ inferenceProfileExpiresAt: number | null = null;
215
226
  /** @internal */ currentRequestId?: string;
227
+ /**
228
+ * The {@link LLMCallSite} of the in-flight turn, set at turn start from
229
+ * `options?.callSite ?? "mainAgent"`. Lets the per-turn plugin context tell
230
+ * the main reply apart from background agent-loop work (compaction,
231
+ * subagents, …) on this same conversation. Per-turn mutable, mirroring
232
+ * {@link currentRequestId}.
233
+ * @internal
234
+ */
235
+ currentCallSite?: LLMCallSite;
216
236
  /** @internal */ hasNoClient = false;
217
237
  /** @internal */ isSubagent = false;
218
238
  /** @internal */ headlessLock = false;
@@ -242,11 +262,37 @@ export class Conversation {
242
262
  /** @internal */ currentTurnTrustContext?: TrustContext;
243
263
  /** @internal */ currentTurnChannelCapabilities?: ChannelCapabilities;
244
264
  /** @internal */ currentTurnOverrideProfile?: string;
265
+ /** @internal */ toolRoutedProfile?: string;
245
266
  /** @internal */ authContext?: AuthContext;
246
267
  /** @internal */ loadedHistoryTrustClass?: TrustClass;
247
268
  /** @internal */ loadedHistoryPersonalMemoryAllowed?: boolean;
248
269
  /** @internal */ voiceCallControlPrompt?: string;
249
270
  /** @internal */ transportHints?: string[];
271
+ /**
272
+ * Optional workspace-git seams, overridable in tests to stub the git
273
+ * initializer and turn-commit behavior. Default to the real
274
+ * implementations in the agent loop when unset.
275
+ * @internal
276
+ */
277
+ getWorkspaceGitService?: (
278
+ workspaceDir: string,
279
+ ) => Pick<WorkspaceGitService, "ensureInitialized">;
280
+ /** @internal */ commitTurnChanges?: typeof commitTurnChanges;
281
+ /**
282
+ * The conversation's immutable creation type (`interactive`, `background`,
283
+ * `scheduled`, …) as stored on the DB row. Cached on load (and set directly
284
+ * for subagent conversations) so the runtime-assembly path can derive the
285
+ * background-turn flag from live state without a per-injection DB read.
286
+ * @internal
287
+ */
288
+ conversationType?: string;
289
+ /**
290
+ * The conversation's creation source (`user`, …) as stored on the DB row,
291
+ * cached on load so the runtime-assembly and disk-pressure paths can read it
292
+ * from live state without a per-turn DB row read.
293
+ * @internal
294
+ */
295
+ source?: string;
250
296
  /** @internal */ assistantId?: string;
251
297
  /** @internal */ commandIntent?: {
252
298
  type: string;
@@ -340,6 +386,26 @@ export class Conversation {
340
386
  */
341
387
  hostUsername?: string;
342
388
  /** @internal */ clientTimezone?: string;
389
+ /**
390
+ * Per-turn temporal snapshot frozen by the agent loop and read by
391
+ * `applyRuntimeInjections` to build the `<turn_context>` timezone-mismatch
392
+ * affordance and `time_since_last_message` line. Holds the client-reported
393
+ * timezone captured at turn start and the human-readable gap since the
394
+ * previous user message (null unless it exceeds the long-absence threshold).
395
+ *
396
+ * Frozen here rather than read live in assembly so the client timezone is not
397
+ * clobbered when a newer message for the same conversation overwrites the
398
+ * live {@link clientTimezone} mid-turn (every inbound message re-applies
399
+ * transport metadata before it is enqueued). Its presence also gates the
400
+ * `<turn_context>` block: assembly emits the block only for turns the loop has
401
+ * frozen a snapshot for. The `current_time` value is computed fresh at each
402
+ * injection so post-compaction re-injections reflect the current wall clock.
403
+ * @internal
404
+ */
405
+ currentTurnTemporalSnapshot?: {
406
+ clientTimezone: string | null;
407
+ timeSinceLastMessage: string | null;
408
+ };
343
409
  public readonly traceEmitter: TraceEmitter;
344
410
  /** @internal */ hasSystemPromptOverride: boolean;
345
411
  /** @internal */ readonly graphMemory: ConversationGraphMemory;
@@ -359,6 +425,22 @@ export class Conversation {
359
425
  /** @internal */ currentTurnChannelContext: TurnChannelContext | null = null;
360
426
  /** @internal */ currentTurnInterfaceContext: TurnInterfaceContext | null =
361
427
  null;
428
+ /**
429
+ * The conversation's recorded origin interface, cached from the DB row at
430
+ * load time. It is immutable once recorded, so it backs the `<turn_context>`
431
+ * interface fallback for turns that don't set a per-turn interface context
432
+ * (regenerate, wake, subagent) without a per-injection DB lookup.
433
+ * @internal
434
+ */
435
+ originInterface: InterfaceId | undefined = undefined;
436
+ /**
437
+ * The conversation's recorded origin channel, cached from the DB row at load
438
+ * time. It is immutable once recorded, so it backs the `<turn_context>`
439
+ * channel fallback for turns that don't set a per-turn channel context
440
+ * (regenerate, wake, subagent) without a per-injection DB lookup.
441
+ * @internal
442
+ */
443
+ originChannel: ChannelId | undefined = undefined;
362
444
  /** @internal */ activityVersion = 0;
363
445
  /** Last emitted activity state message, retained for replay on SSE reconnection. */
364
446
  /** @internal */ lastActivityStateMsg: ServerMessage | null = null;
@@ -518,7 +600,9 @@ export class Conversation {
518
600
  agentLoopConfig.maxTokens = configuredMaxTokens;
519
601
  }
520
602
 
521
- this.agentLoop = new AgentLoop(provider, systemPrompt, {
603
+ this.agentLoop = new AgentLoop({
604
+ provider,
605
+ systemPrompt,
522
606
  conversationId: this.conversationId,
523
607
  config: agentLoopConfig,
524
608
  tools: toolDefs.length > 0 ? toolDefs : undefined,
@@ -526,7 +610,7 @@ export class Conversation {
526
610
  resolveTools,
527
611
  resolveSystemPrompt: resolveSystemPromptCallback,
528
612
  });
529
- this.contextWindowManager = new ContextWindowManager({
613
+ this.contextWindowManager = createContextWindowManager({
530
614
  provider,
531
615
  systemPrompt: () => resolveSystemPromptCallback([]).systemPrompt,
532
616
  config: initialContextWindowConfig,
@@ -548,6 +632,22 @@ export class Conversation {
548
632
  return this.onboardingContext;
549
633
  }
550
634
 
635
+ /**
636
+ * Mirror an inference-profile override write onto the live instance so the
637
+ * per-turn override derivation reads current state without re-fetching the
638
+ * DB row. Called alongside the corresponding DB write by the HTTP setters
639
+ * and the background expiry reaper.
640
+ */
641
+ applyInferenceProfileState(state: {
642
+ profile: string | null;
643
+ sessionId: string | null;
644
+ expiresAt: number | null;
645
+ }): void {
646
+ this.inferenceProfile = state.profile;
647
+ this.inferenceProfileSessionId = state.sessionId;
648
+ this.inferenceProfileExpiresAt = state.expiresAt;
649
+ }
650
+
551
651
  // ── Prompt Cache Warming ─────────────────────────────────────────
552
652
 
553
653
  /**
@@ -605,7 +705,250 @@ export class Conversation {
605
705
  // ── Lifecycle ────────────────────────────────────────────────────
606
706
 
607
707
  async loadFromDb(): Promise<void> {
608
- await loadFromDbImpl(this);
708
+ const trustClass = this.trustContext?.trustClass;
709
+ const allDbMessages = getMessages(this.conversationId);
710
+ const dbMessages = isUntrustedTrustClass(trustClass)
711
+ ? filterMessagesForUntrustedActor(allDbMessages)
712
+ : allDbMessages;
713
+
714
+ const conv = getConversation(this.conversationId);
715
+ this.conversationType = conv?.conversationType ?? undefined;
716
+ this.originInterface = parseInterfaceId(conv?.originInterface) ?? undefined;
717
+ this.originChannel = parseChannelId(conv?.originChannel) ?? undefined;
718
+ this.source = conv?.source ?? undefined;
719
+ this.contextSummary = conv?.contextSummary ?? null;
720
+ this.slackContextCompactionWatermarkTs =
721
+ conv?.slackContextCompactionWatermarkTs ?? null;
722
+ this.lastNotifiedInferenceProfile =
723
+ conv?.lastNotifiedInferenceProfile ?? null;
724
+ this.inferenceProfile = conv?.inferenceProfile ?? null;
725
+ this.inferenceProfileSessionId = conv?.inferenceProfileSessionId ?? null;
726
+ this.inferenceProfileExpiresAt = conv?.inferenceProfileExpiresAt ?? null;
727
+ this.contextCompactedMessageCount = Math.max(
728
+ 0,
729
+ conv?.contextCompactedMessageCount ?? 0,
730
+ );
731
+ this.contextCompactedAt = conv?.contextCompactedAt ?? null;
732
+
733
+ // Untrusted actor views never receive summary-based compaction: a
734
+ // compacted summary can embed trusted/guardian-only detail, so the
735
+ // summary message is suppressed and the persisted history is rendered
736
+ // unsliced. The slice boundary is clamped so it can never drop more rows
737
+ // than exist. Slack chronological context is a separate consumer that
738
+ // applies its own trust filtering downstream, so it reads the raw
739
+ // mirrored count rather than this in-context boundary.
740
+ const inContextCompactedCount = isUntrustedTrustClass(trustClass)
741
+ ? 0
742
+ : Math.min(this.contextCompactedMessageCount, dbMessages.length);
743
+ const contextSummaryForHistory = isUntrustedTrustClass(trustClass)
744
+ ? null
745
+ : this.contextSummary?.trim() || null;
746
+
747
+ // Every injection-strip event (`/clean` or compaction) updates
748
+ // `historyStrippedAt`. Messages older than this should skip metadata
749
+ // rehydration and have any injection prefixes still embedded in their
750
+ // content stripped, so the post-strip view survives reload and forks.
751
+ const historyStrippedAt = conv?.historyStrippedAt ?? null;
752
+ const slicedDbMessages = dbMessages.slice(inContextCompactedCount);
753
+ let preStrippedCount = 0;
754
+ if (historyStrippedAt != null) {
755
+ const boundary = slicedDbMessages.findIndex(
756
+ (m) => m.createdAt >= historyStrippedAt,
757
+ );
758
+ preStrippedCount = boundary === -1 ? slicedDbMessages.length : boundary;
759
+ }
760
+
761
+ // Mirror the injection-time gate (`shouldExposePersonalMemory` in
762
+ // `conversation-agent-loop.ts`) so background/local conversations
763
+ // (sourceChannel `undefined` or `"vellum"`) can rehydrate the persisted
764
+ // v2 static memory block. Use `resolveTrustClass` for parity with the
765
+ // agent loop — it folds in the HTTP-auth-disabled dev bypass so
766
+ // rehydration and injection agree on the effective trust class.
767
+ const personalMemoryAllowed = shouldExposePersonalMemory({
768
+ sourceChannel: this.trustContext?.sourceChannel,
769
+ isTrustedActor: resolveTrustClass(this.trustContext) === "guardian",
770
+ });
771
+ const parsedMessages: Message[] = slicedDbMessages.map((m, index, arr) => {
772
+ const isPreStripped = index < preStrippedCount;
773
+ const role = m.role as "user" | "assistant";
774
+ let content: ContentBlock[];
775
+ try {
776
+ const parsed = JSON.parse(m.content);
777
+ content = Array.isArray(parsed)
778
+ ? parsed
779
+ : [{ type: "text", text: m.content }];
780
+ } catch {
781
+ log.warn(
782
+ { conversationId: this.conversationId, messageId: m.id },
783
+ "Invalid JSON in persisted message content, replacing with safe text block",
784
+ );
785
+ content = [{ type: "text", text: m.content }];
786
+ }
787
+
788
+ content = reinjectImageSourcePaths(content, role, m.metadata);
789
+
790
+ // Re-inject persisted injection blocks from metadata so it survives
791
+ // conversation reloads (eviction, restart, fork).
792
+ if (role === "user" && m.metadata && !isPreStripped) {
793
+ try {
794
+ const meta = JSON.parse(m.metadata);
795
+ const isTail = index === arr.length - 1;
796
+
797
+ // Rehydrate in reverse injection order (innermost block first)
798
+ // so the resulting layout matches `applyRuntimeInjections`'s
799
+ // after-memory-prefix splices in ascending injector order
800
+ // (pkb-context 30, pkb-reminder 35, memory-v2-static 38,
801
+ // now-md 40 — the v2 static block lands inside the memory
802
+ // prefix, so now-md splices *after* it):
803
+ // [<workspace>, <turn_context>, <memory>dynamic</memory>,
804
+ // <info>v2static</info>, <NOW.md>, <system_reminder>,
805
+ // <knowledge_base>, ...original]
806
+ // The v2 static block is replayed verbatim from stored metadata,
807
+ // so rows may carry either `<info>…</info>` or `<memory>…</memory>`
808
+ // depending on when they were persisted.
809
+ // Required so Anthropic's prefix cache keeps matching msg[0]
810
+ // across daemon restart and conversation eviction. The tail
811
+ // row only rehydrates `memoryInjectedBlock` — the next turn
812
+ // re-injects the rest fresh.
813
+ if (!isTail && typeof meta.pkbContextBlock === "string") {
814
+ content = [
815
+ { type: "text" as const, text: meta.pkbContextBlock },
816
+ ...content,
817
+ ];
818
+ }
819
+
820
+ if (!isTail && typeof meta.pkbSystemReminderBlock === "string") {
821
+ content = [
822
+ { type: "text" as const, text: meta.pkbSystemReminderBlock },
823
+ ...content,
824
+ ];
825
+ }
826
+
827
+ if (!isTail && typeof meta.nowScratchpadBlock === "string") {
828
+ content = [
829
+ { type: "text" as const, text: meta.nowScratchpadBlock },
830
+ ...content,
831
+ ];
832
+ }
833
+
834
+ // The v2 static memory block (essentials/threads/recent/buffer
835
+ // wrapped in either `<info>…</info>` or `<memory>…</memory>`)
836
+ // carries personal user memory. Trust-gated to mirror
837
+ // `shouldExposePersonalMemory` at injection time — untrusted-actor
838
+ // views must not read persisted personal memory back through
839
+ // metadata. Skipped on the tail row because the next turn
840
+ // re-injects fresh content on full-mode turns.
841
+ if (
842
+ !isTail &&
843
+ personalMemoryAllowed &&
844
+ typeof meta.memoryV2StaticBlock === "string"
845
+ ) {
846
+ content = [
847
+ { type: "text" as const, text: meta.memoryV2StaticBlock },
848
+ ...content,
849
+ ];
850
+ }
851
+
852
+ // Memory remains rehydrated on all rows (existing behavior).
853
+ // Strip any pre-existing wrapper before re-wrapping so historical
854
+ // rows persisted with the wrapper (v2 path before the
855
+ // injectedBlockText contract was unified with v1's unwrapped form)
856
+ // don't render double-wrapped after rehydrate. Only unwrap when
857
+ // the full <memory>...</memory> pair is present so we don't mutate
858
+ // legitimate unwrapped payloads that happen to start with
859
+ // "<memory>\n" or end with "\n</memory>".
860
+ if (typeof meta.memoryInjectedBlock === "string") {
861
+ const block = meta.memoryInjectedBlock;
862
+ const inner =
863
+ block.startsWith("<memory>\n") && block.endsWith("\n</memory>")
864
+ ? block.slice("<memory>\n".length, -"\n</memory>".length)
865
+ : block;
866
+ content = [
867
+ {
868
+ type: "text" as const,
869
+ text: `<memory>\n${inner}\n</memory>`,
870
+ },
871
+ ...content,
872
+ ];
873
+ }
874
+
875
+ if (!isTail && typeof meta.turnContextBlock === "string") {
876
+ content = [
877
+ { type: "text" as const, text: meta.turnContextBlock },
878
+ ...content,
879
+ ];
880
+ }
881
+
882
+ if (!isTail && typeof meta.workspaceBlock === "string") {
883
+ content = [
884
+ { type: "text" as const, text: meta.workspaceBlock },
885
+ ...content,
886
+ ];
887
+ }
888
+ } catch {
889
+ /* ignore parse errors — metadata may be malformed */
890
+ }
891
+ }
892
+
893
+ return { role, content };
894
+ });
895
+
896
+ // Strip pre-clean messages only; post-clean messages keep the fresh
897
+ // injections they were generated with.
898
+ const messagesBeforeRepair =
899
+ preStrippedCount === 0
900
+ ? parsedMessages
901
+ : [
902
+ ...stripInjectionsForCompaction(
903
+ parsedMessages.slice(0, preStrippedCount),
904
+ ),
905
+ ...parsedMessages.slice(preStrippedCount),
906
+ ];
907
+
908
+ // Normalize the canonical persisted history once at load. Every consumer
909
+ // of `this.messages` outside the agent loop (history edit/undo, PKB context
910
+ // tracking, surfaces) reads this list directly, so it must satisfy the
911
+ // provider pairing/alternation rules before any of them run. The agent
912
+ // loop's pre-run repair only repairs the transient per-turn message list it
913
+ // sends to the provider and never writes back here, so this pass is not
914
+ // redundant with it.
915
+ const { messages: repairedMessages, stats } =
916
+ repairHistory(messagesBeforeRepair);
917
+ if (
918
+ stats.assistantToolResultsMigrated > 0 ||
919
+ stats.missingToolResultsInserted > 0 ||
920
+ stats.orphanToolResultsDowngraded > 0 ||
921
+ stats.consecutiveSameRoleMerged > 0
922
+ ) {
923
+ log.warn(
924
+ { conversationId: this.conversationId, phase: "load", ...stats },
925
+ "Repaired persisted history",
926
+ );
927
+ }
928
+ this.messages = repairedMessages;
929
+
930
+ if (contextSummaryForHistory) {
931
+ this.messages.unshift(
932
+ createContextSummaryMessage(contextSummaryForHistory),
933
+ );
934
+ }
935
+
936
+ if (conv) {
937
+ this.usageStats = {
938
+ inputTokens: conv.totalInputTokens,
939
+ outputTokens: conv.totalOutputTokens,
940
+ estimatedCost: conv.totalEstimatedCost,
941
+ };
942
+ }
943
+
944
+ this.loadedHistoryTrustClass = trustClass;
945
+ this.loadedHistoryPersonalMemoryAllowed = personalMemoryAllowed;
946
+
947
+ log.info(
948
+ { conversationId: this.conversationId, count: this.messages.length },
949
+ "Loaded messages from DB",
950
+ );
951
+
609
952
  this.restoreSurfaceStateFromHistory();
610
953
  this.graphMemory.restoreState();
611
954
  }
@@ -719,9 +1062,7 @@ export class Conversation {
719
1062
  );
720
1063
  }
721
1064
  this.messages = [...messages];
722
- this.contextWindowManager.nonPersistedPrefixCount = messages.length;
723
- this.contextWindowManager.summaryIsInjected =
724
- getSummaryFromContextMessage(messages[0]) != null;
1065
+ this.contextWindowManager.seedNonPersistedPrefix(messages.length);
725
1066
  }
726
1067
 
727
1068
  /**
@@ -1045,20 +1386,14 @@ export class Conversation {
1045
1386
  }
1046
1387
 
1047
1388
  async forceCompact(): Promise<ContextWindowResult> {
1048
- const conversationRow = getConversation(this.conversationId);
1049
- const overrideProfile =
1050
- getConversationOverrideProfileFromRow(conversationRow) ?? null;
1389
+ const overrideProfile = resolveOverrideProfile(this) ?? null;
1051
1390
  const config = getConfig();
1052
1391
  const effectiveContextWindow = resolveEffectiveContextWindow({
1053
1392
  llm: config.llm,
1054
1393
  callSite: "mainAgent",
1055
1394
  overrideProfile: overrideProfile ?? undefined,
1056
1395
  });
1057
- (
1058
- this.contextWindowManager as ContextWindowManager & {
1059
- updateConfig?: (config: ContextWindowConfig) => void;
1060
- }
1061
- ).updateConfig?.(
1396
+ this.contextWindowManager.updateConfig(
1062
1397
  contextWindowConfigFromEffective(
1063
1398
  resolveCallSiteConfig("mainAgent", config.llm, {
1064
1399
  overrideProfile: overrideProfile ?? undefined,
@@ -1073,25 +1408,23 @@ export class Conversation {
1073
1408
  this.channelCapabilities,
1074
1409
  {
1075
1410
  trustClass: this.trustContext?.trustClass,
1076
- contextSummary: conversationRow?.contextSummary,
1077
- contextCompactedMessageCount:
1078
- conversationRow?.contextCompactedMessageCount,
1411
+ contextSummary: this.contextSummary,
1412
+ contextCompactedMessageCount: this.contextCompactedMessageCount,
1079
1413
  slackContextCompactionWatermarkTs:
1080
- conversationRow?.slackContextCompactionWatermarkTs,
1414
+ this.slackContextCompactionWatermarkTs,
1081
1415
  },
1082
1416
  )
1083
1417
  : null;
1084
1418
  const messagesToCompact =
1085
1419
  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
- );
1420
+ const result = await defaultCompact({
1421
+ manager: this.contextWindowManager,
1422
+ messages: messagesToCompact,
1423
+ signal: this.abortController?.signal ?? undefined,
1424
+ force: true,
1425
+ overrideProfile,
1426
+ actorTrustClass: this.trustContext?.trustClass,
1427
+ });
1095
1428
  // Track circuit-breaker state for user-initiated `/compact` and other
1096
1429
  // forced paths so a successful forced compaction clears a stuck counter
1097
1430
  // and a run of forced failures still trips the breaker. `summaryFailed`
@@ -1099,7 +1432,6 @@ export class Conversation {
1099
1432
  // etc.) — skip those so they don't silently reset the counter.
1100
1433
  if (result.summaryFailed !== undefined) {
1101
1434
  await this.agentLoop.compactionCircuit.recordOutcome(
1102
- this,
1103
1435
  result.summaryFailed,
1104
1436
  this.sendToClient,
1105
1437
  );
@@ -1128,7 +1460,6 @@ export class Conversation {
1128
1460
  const stripped = stripInjectionsForCompaction(this.messages);
1129
1461
  this.messages = stripped;
1130
1462
  await this.graphMemory.onCompacted(0);
1131
- this.pendingPostCompactReinject = true;
1132
1463
  setConversationHistoryStrippedAt(this.conversationId, Date.now());
1133
1464
  const estimatedInputTokens = this.contextWindowManager.estimateInputTokens(
1134
1465
  this.messages,
@@ -1308,13 +1639,13 @@ export class Conversation {
1308
1639
  }
1309
1640
 
1310
1641
  drainQueue(reason: QueueDrainReason = "loop_complete"): Promise<void> {
1311
- return drainQueueImpl(this as ProcessConversationContext, reason);
1642
+ return drainQueueImpl(this, reason);
1312
1643
  }
1313
1644
 
1314
1645
  async processMessage(options: ProcessMessageOptions): Promise<string> {
1315
1646
  this.cacheWarmAbort?.abort();
1316
1647
  this.cacheWarmAbort = undefined;
1317
- return processMessageImpl(this as ProcessConversationContext, {
1648
+ return processMessageImpl(this, {
1318
1649
  ...options,
1319
1650
  onEvent: options.onEvent ?? this.sendToClient,
1320
1651
  });
@@ -1346,10 +1677,6 @@ export class Conversation {
1346
1677
 
1347
1678
  // ── Workspace ────────────────────────────────────────────────────
1348
1679
 
1349
- refreshWorkspaceTopLevelContextIfNeeded(): void {
1350
- refreshWorkspaceImpl(this);
1351
- }
1352
-
1353
1680
  markWorkspaceTopLevelDirty(): void {
1354
1681
  this.workspaceTopLevelDirty = true;
1355
1682
  }