@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
@@ -141,7 +141,7 @@ mock.module("../memory/conversation-crud.js", () => ({
141
141
  getMessageById: () => null,
142
142
  getLastUserTimestampBefore: () => 0,
143
143
  setLastNotifiedInferenceProfile: () => {},
144
- getConversationOverrideProfileFromRow: () => undefined,
144
+ resolveOverrideProfile: () => undefined,
145
145
  updateMessageMetadata: () => {},
146
146
  reserveMessage: mock(async () => ({ id: "msg-reserve" })),
147
147
  updateMessageContent: mock(() => {}),
@@ -180,6 +180,7 @@ mock.module("../memory/retrieval-budget.js", () => ({
180
180
  }));
181
181
  mock.module("../context/window-manager.js", () => ({
182
182
  ContextWindowManager: class {
183
+ updateConfig() {}
183
184
  shouldCompact() {
184
185
  return { needed: false, estimatedTokens: 0 };
185
186
  }
@@ -228,10 +229,11 @@ mock.module("../agent/loop.js", () => ({
228
229
  getActiveModel() {
229
230
  return undefined;
230
231
  }
231
- async run(
232
- messages: Message[],
233
- onEvent: (event: AgentEvent) => void,
234
- ): Promise<Message[]> {
232
+ async run(options: {
233
+ messages: Message[];
234
+ onEvent: (event: AgentEvent) => void;
235
+ }): Promise<Message[]> {
236
+ const { messages, onEvent } = options;
235
237
  // Prime the assistant row anchor — production code emits this from
236
238
  // `AgentLoop.run` just before `provider.sendMessage`.
237
239
  await onEvent({ type: "llm_call_started" });
@@ -273,6 +275,7 @@ mock.module("../memory/canonical-guardian-store.js", () => ({
273
275
  }));
274
276
 
275
277
  import { Conversation } from "../daemon/conversation.js";
278
+ import { refreshWorkspaceTopLevelContextIfNeeded } from "../daemon/conversation-workspace.js";
276
279
 
277
280
  function makeConversation(): Conversation {
278
281
  const provider = {
@@ -310,7 +313,7 @@ describe("Conversation workspace dirty on file mutations", () => {
310
313
  await conversation.loadFromDb();
311
314
 
312
315
  // Prime the cache so dirty=false
313
- conversation.refreshWorkspaceTopLevelContextIfNeeded();
316
+ refreshWorkspaceTopLevelContextIfNeeded(conversation);
314
317
  expect(conversation.isWorkspaceTopLevelDirty()).toBe(false);
315
318
 
316
319
  agentLoopScript = (onEvent) => {
@@ -339,7 +342,7 @@ describe("Conversation workspace dirty on file mutations", () => {
339
342
  const conversation = makeConversation();
340
343
  await conversation.loadFromDb();
341
344
 
342
- conversation.refreshWorkspaceTopLevelContextIfNeeded();
345
+ refreshWorkspaceTopLevelContextIfNeeded(conversation);
343
346
  expect(conversation.isWorkspaceTopLevelDirty()).toBe(false);
344
347
 
345
348
  agentLoopScript = (onEvent) => {
@@ -370,7 +373,7 @@ describe("Conversation workspace dirty on file mutations", () => {
370
373
  const conversation = makeConversation();
371
374
  await conversation.loadFromDb();
372
375
 
373
- conversation.refreshWorkspaceTopLevelContextIfNeeded();
376
+ refreshWorkspaceTopLevelContextIfNeeded(conversation);
374
377
  expect(conversation.isWorkspaceTopLevelDirty()).toBe(false);
375
378
 
376
379
  agentLoopScript = (onEvent) => {
@@ -399,7 +402,7 @@ describe("Conversation workspace dirty on file mutations", () => {
399
402
  const conversation = makeConversation();
400
403
  await conversation.loadFromDb();
401
404
 
402
- conversation.refreshWorkspaceTopLevelContextIfNeeded();
405
+ refreshWorkspaceTopLevelContextIfNeeded(conversation);
403
406
  expect(conversation.isWorkspaceTopLevelDirty()).toBe(false);
404
407
 
405
408
  agentLoopScript = (onEvent) => {
@@ -428,7 +431,7 @@ describe("Conversation workspace dirty on file mutations", () => {
428
431
  const conversation = makeConversation();
429
432
  await conversation.loadFromDb();
430
433
 
431
- conversation.refreshWorkspaceTopLevelContextIfNeeded();
434
+ refreshWorkspaceTopLevelContextIfNeeded(conversation);
432
435
  expect(conversation.isWorkspaceTopLevelDirty()).toBe(false);
433
436
 
434
437
  agentLoopScript = (onEvent) => {
@@ -457,7 +460,7 @@ describe("Conversation workspace dirty on file mutations", () => {
457
460
  const conversation = makeConversation();
458
461
  await conversation.loadFromDb();
459
462
 
460
- conversation.refreshWorkspaceTopLevelContextIfNeeded();
463
+ refreshWorkspaceTopLevelContextIfNeeded(conversation);
461
464
  expect(conversation.isWorkspaceTopLevelDirty()).toBe(false);
462
465
 
463
466
  agentLoopScript = (onEvent) => {
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Tests that the conversations import route never persists non-renderable
3
+ * roles. The messages store is UI-facing (`ConversationMessage`), so an
4
+ * imported export carrying agent-context `system` rows must land only its
5
+ * `user`/`assistant` turns — the `system` rows are dropped, not persisted.
6
+ */
7
+
8
+ import { beforeEach, describe, expect, mock, test } from "bun:test";
9
+
10
+ mock.module("../util/logger.js", () => ({
11
+ getLogger: () =>
12
+ new Proxy({} as Record<string, unknown>, {
13
+ get: () => () => {},
14
+ }),
15
+ }));
16
+
17
+ mock.module("../config/loader.js", () => ({
18
+ getConfig: () => ({
19
+ ui: {},
20
+ model: "test",
21
+ provider: "test",
22
+ memory: { enabled: false },
23
+ rateLimit: { maxRequestsPerMinute: 0 },
24
+ }),
25
+ }));
26
+
27
+ mock.module("../memory/indexer.js", () => ({
28
+ indexMessageNow: async () => {},
29
+ }));
30
+
31
+ import { getMessages } from "../memory/conversation-crud.js";
32
+ import { getDb } from "../memory/db-connection.js";
33
+ import { initializeDb } from "../memory/db-init.js";
34
+ import { conversations, messages } from "../memory/schema.js";
35
+ import { ROUTES } from "../runtime/routes/conversations-import-routes.js";
36
+ import type { RouteHandlerArgs } from "../runtime/routes/types.js";
37
+
38
+ initializeDb();
39
+
40
+ function resetTables() {
41
+ const db = getDb();
42
+ db.run("DELETE FROM message_attachments");
43
+ db.run("DELETE FROM attachments");
44
+ db.run("DELETE FROM messages");
45
+ db.run("DELETE FROM conversation_keys");
46
+ db.run("DELETE FROM conversations");
47
+ }
48
+
49
+ const importHandler = ROUTES.find(
50
+ (r) => r.operationId === "conversations_import",
51
+ )!.handler;
52
+
53
+ describe("conversations import system-row filtering", () => {
54
+ beforeEach(resetTables);
55
+
56
+ test("imports renderable turns but drops system rows", async () => {
57
+ // GIVEN an export whose conversation sandwiches a system row between two
58
+ // renderable turns (e.g. agent-context scaffolding an export carried)
59
+ const body = {
60
+ conversations: [
61
+ {
62
+ sourceKey: "src-1",
63
+ title: "Imported chat",
64
+ messages: [
65
+ { role: "user", content: "first visible" },
66
+ { role: "system", content: "agent-context scaffolding" },
67
+ { role: "assistant", content: "second visible" },
68
+ ],
69
+ },
70
+ ],
71
+ };
72
+
73
+ // WHEN the conversation is imported
74
+ const result = (await importHandler({
75
+ body,
76
+ } as unknown as RouteHandlerArgs)) as {
77
+ ok: boolean;
78
+ imported: number;
79
+ messages: number;
80
+ };
81
+
82
+ // THEN the import succeeds and only counts the renderable turns
83
+ expect(result.ok).toBe(true);
84
+ expect(result.imported).toBe(1);
85
+ expect(result.messages).toBe(2);
86
+
87
+ // AND the persisted rows are exactly the user/assistant turns, never the
88
+ // system scaffolding
89
+ const db = getDb();
90
+ const conv = db.select().from(conversations).all()[0];
91
+ const rows = getMessages(conv.id);
92
+ expect(rows.map((m) => m.role)).toEqual(["user", "assistant"]);
93
+ expect(
94
+ db
95
+ .select()
96
+ .from(messages)
97
+ .all()
98
+ .some((m) => m.role === "system"),
99
+ ).toBe(false);
100
+ });
101
+ });
@@ -230,7 +230,6 @@ describe("Invariant 2: no generic plaintext secret read API", () => {
230
230
  "runtime/routes/platform-routes.ts", // CLI platform connect/disconnect/status routes (CLI-migrated to IPC)
231
231
  "ipc/skill-routes/providers.ts", // host.providers.secureKeys.getProviderKey IPC route (out-of-process SkillHost companion)
232
232
  "daemon/external-plugins-bootstrap.ts", // reads credentials at plugin init (manifest.requiresCredential) via the CES-mediated getSecureKeyAsync path
233
- "plugins/external-api.ts", // globalThis runtime bridge that exposes getSecureKeyAsync to dynamically-imported workspace plugins (compiled-binary plugin loading)
234
233
  "inbound/platform-callback-registration.ts", // managed credential lookup for platform base URL, assistant ID, and API key
235
234
  "tts/providers/elevenlabs-provider.ts", // ElevenLabs TTS API key lookup
236
235
  "tts/providers/deepgram-provider.ts", // Deepgram TTS API key lookup
@@ -5,6 +5,7 @@ import { drizzle } from "drizzle-orm/bun-sqlite";
5
5
 
6
6
  import { getSqliteFrom } from "../memory/db-connection.js";
7
7
  import { migrate230AcpSessionHistory } from "../memory/migrations/230-acp-session-history.js";
8
+ import { migrateAcpSessionHistoryCwd } from "../memory/migrations/272-acp-session-history-cwd.js";
8
9
  import * as schema from "../memory/schema.js";
9
10
 
10
11
  function createTestDb() {
@@ -282,3 +283,103 @@ describe("acp_session_history migration", () => {
282
283
  expect(row?.id).toBe("history-rerun");
283
284
  });
284
285
  });
286
+
287
+ describe("acp_session_history cwd migration (272)", () => {
288
+ function readColumns(raw: Database): Map<string, ColumnRow> {
289
+ const columns = raw
290
+ .query(`PRAGMA table_info(acp_session_history)`)
291
+ .all() as ColumnRow[];
292
+ return new Map(columns.map((c) => [c.name, c]));
293
+ }
294
+
295
+ test("adds a nullable cwd TEXT column to an upgraded table", () => {
296
+ const db = createTestDb();
297
+ const raw = getSqliteFrom(db);
298
+ bootstrapCheckpointsTable(raw);
299
+
300
+ migrate230AcpSessionHistory(db);
301
+ expect(readColumns(raw).has("cwd")).toBe(false);
302
+
303
+ migrateAcpSessionHistoryCwd(db);
304
+
305
+ const cwd = readColumns(raw).get("cwd");
306
+ expect(cwd).toBeDefined();
307
+ expect(cwd?.type).toBe("TEXT");
308
+ expect(cwd?.notnull).toBe(0);
309
+ });
310
+
311
+ test("re-running is a no-op, including with a cleared checkpoint", () => {
312
+ const db = createTestDb();
313
+ const raw = getSqliteFrom(db);
314
+ bootstrapCheckpointsTable(raw);
315
+
316
+ migrate230AcpSessionHistory(db);
317
+ migrateAcpSessionHistoryCwd(db);
318
+
319
+ // Second run short-circuits on the completed checkpoint.
320
+ expect(() => migrateAcpSessionHistoryCwd(db)).not.toThrow();
321
+
322
+ // Even with the checkpoint cleared (simulating crash recovery), the
323
+ // column-existence guard makes the ALTER a no-op.
324
+ raw
325
+ .query(`DELETE FROM memory_checkpoints WHERE key = ?`)
326
+ .run("migration_acp_session_history_cwd_v1");
327
+ expect(() => migrateAcpSessionHistoryCwd(db)).not.toThrow();
328
+
329
+ const columns = raw
330
+ .query(`PRAGMA table_info(acp_session_history)`)
331
+ .all() as ColumnRow[];
332
+ expect(columns.filter((c) => c.name === "cwd")).toHaveLength(1);
333
+ });
334
+
335
+ test("legacy rows read back with cwd null; new rows round-trip cwd", () => {
336
+ const db = createTestDb();
337
+ const raw = getSqliteFrom(db);
338
+ bootstrapCheckpointsTable(raw);
339
+
340
+ migrate230AcpSessionHistory(db);
341
+
342
+ // Legacy row inserted before the cwd column existed.
343
+ raw
344
+ .query(
345
+ /*sql*/ `
346
+ INSERT INTO acp_session_history (
347
+ id, agent_id, acp_session_id, parent_conversation_id,
348
+ started_at, status
349
+ ) VALUES (?, ?, ?, ?, ?, ?)
350
+ `,
351
+ )
352
+ .run("legacy-1", "agent-old", "acp-old", "conv-old", 100, "completed");
353
+
354
+ migrateAcpSessionHistoryCwd(db);
355
+
356
+ const legacy = raw
357
+ .query(`SELECT cwd FROM acp_session_history WHERE id = 'legacy-1'`)
358
+ .get() as { cwd: string | null } | null;
359
+ expect(legacy?.cwd).toBeNull();
360
+
361
+ raw
362
+ .query(
363
+ /*sql*/ `
364
+ INSERT INTO acp_session_history (
365
+ id, agent_id, acp_session_id, parent_conversation_id,
366
+ started_at, status, cwd
367
+ ) VALUES (?, ?, ?, ?, ?, ?, ?)
368
+ `,
369
+ )
370
+ .run(
371
+ "new-1",
372
+ "agent-new",
373
+ "acp-new",
374
+ "conv-new",
375
+ 200,
376
+ "completed",
377
+ "/Users/me/project",
378
+ );
379
+
380
+ const fresh = raw
381
+ .query(`SELECT cwd FROM acp_session_history WHERE id = 'new-1'`)
382
+ .get() as { cwd: string | null } | null;
383
+ expect(fresh?.cwd).toBe("/Users/me/project");
384
+ });
385
+ });
@@ -94,6 +94,37 @@ describe("ui_show dynamic_page app substitute guard", () => {
94
94
  expect(proxied).toBe(false);
95
95
  });
96
96
 
97
+ test("rejects dynamic_page with a clean title but substantial interactive html", async () => {
98
+ let proxied = false;
99
+
100
+ const result = await uiShowTool.execute(
101
+ {
102
+ surface_type: "dynamic_page",
103
+ title: "Labor Market Stats",
104
+ data: {
105
+ html:
106
+ "<div id='root'></div><script>" +
107
+ "const data=[/*...*/];".padEnd(2100, "/") +
108
+ "new Chart(document.getElementById('root'), {});</script>",
109
+ preview: { title: "Labor Market Stats" },
110
+ },
111
+ },
112
+ {
113
+ conversationId: "conversation-123",
114
+ workingDir: "/tmp",
115
+ trustClass: "guardian",
116
+ proxyToolResolver: async () => {
117
+ proxied = true;
118
+ return { content: "proxied", isError: false };
119
+ },
120
+ },
121
+ );
122
+
123
+ expect(result.isError).toBe(true);
124
+ expect(result.content).toContain('skill: "app-builder"');
125
+ expect(proxied).toBe(false);
126
+ });
127
+
97
128
  test("allows transient non-app dynamic_page surfaces", async () => {
98
129
  let proxied = false;
99
130
 
@@ -31,7 +31,7 @@ import {
31
31
  REFUSAL_NUDGE_TEXT,
32
32
  } from "../plugins/defaults/empty-response/hooks/stop.js";
33
33
  import stop from "../plugins/defaults/empty-response/hooks/stop.js";
34
- import { defaultEmptyResponsePlugin } from "../plugins/defaults/empty-response/register.js";
34
+ import { defaultEmptyResponsePlugin } from "../plugins/defaults/index.js";
35
35
  import { runHook } from "../plugins/pipeline.js";
36
36
  import {
37
37
  registerPlugin,
@@ -260,3 +260,66 @@ describe("file_write tool PKB re-index hook", () => {
260
260
  expect(existsSync(join(workingDir, "pkb", "oops.md"))).toBe(true);
261
261
  });
262
262
  });
263
+
264
+ describe("file_write artifact-HTML guard", () => {
265
+ test("rejects a self-contained interactive HTML visualization", async () => {
266
+ const dir = makeTempDir();
267
+ const html =
268
+ "<!doctype html><html><head><title>Food Market</title></head>" +
269
+ "<body><canvas id='c'></canvas><script>" +
270
+ ("const data=[{x:1,y:2}];").padEnd(4000, "/") +
271
+ "new Chart(document.getElementById('c'), {data});</script></body></html>";
272
+
273
+ const result = await fileWriteTool.execute(
274
+ { path: "food-market-stats.html", content: html },
275
+ makeContext(dir),
276
+ );
277
+
278
+ expect(result.isError).toBe(true);
279
+ expect(result.content).toContain('skill: "app-builder"');
280
+ expect(existsSync(join(dir, "food-market-stats.html"))).toBe(false);
281
+ });
282
+
283
+ test("allows app-builder's thin shell index.html (external module script)", async () => {
284
+ const dir = makeTempDir();
285
+ const shell =
286
+ "<!doctype html><html><head>" +
287
+ "<link rel='stylesheet' href='/src/styles.css'>".padEnd(3200, " ") +
288
+ "</head><body><div id='app'></div>" +
289
+ "<script type='module' src='/src/main.tsx'></script></body></html>";
290
+
291
+ const result = await fileWriteTool.execute(
292
+ { path: "src/index.html", content: shell },
293
+ makeContext(dir),
294
+ );
295
+
296
+ expect(result.isError).toBe(false);
297
+ expect(existsSync(join(dir, "src", "index.html"))).toBe(true);
298
+ });
299
+
300
+ test("allows a small/static HTML snippet", async () => {
301
+ const dir = makeTempDir();
302
+ const result = await fileWriteTool.execute(
303
+ { path: "note.html", content: "<html><body><h1>Hi</h1></body></html>" },
304
+ makeContext(dir),
305
+ );
306
+
307
+ expect(result.isError).toBe(false);
308
+ expect(existsSync(join(dir, "note.html"))).toBe(true);
309
+ });
310
+
311
+ test("allows a non-HTML file even with inline script-like content", async () => {
312
+ const dir = makeTempDir();
313
+ const result = await fileWriteTool.execute(
314
+ {
315
+ path: "notes.md",
316
+ content:
317
+ "<script>" + "x".padEnd(2000, "x") + "</script>" + "\n# notes\n",
318
+ },
319
+ makeContext(dir),
320
+ );
321
+
322
+ expect(result.isError).toBe(false);
323
+ expect(existsSync(join(dir, "notes.md"))).toBe(true);
324
+ });
325
+ });
@@ -27,6 +27,7 @@ const ALLOWLIST = new Set([
27
27
  "clients/shared/App/Auth/PlatformOAuthService.swift", // comment explaining runtimeUrl vs platformUrl
28
28
  "clients/macos/vellum-assistant/App/AppDelegate.swift",
29
29
  "clients/macos/vellum-assistant/Features/Settings/SettingsConnectTab.swift",
30
+ "apps/macos/src/main/bundle-flow.ts", // Electron main calls the local gateway (gatewayPort) with a Guardian token to scan bundles
30
31
  ".claude/skills/update/SKILL.md", // daemon health check script
31
32
 
32
33
  // --- Test fixtures that poll the daemon directly (gateway may require auth) ---
@@ -84,12 +85,21 @@ function isGatewayInternal(filePath: string): boolean {
84
85
  return filePath.startsWith("gateway/");
85
86
  }
86
87
 
88
+ /** Additional files allowed for the interpolated-port check only (use gateway port, not runtime). */
89
+ const INTERPOLATED_PORT_ALLOWLIST = new Set([
90
+ "apps/macos/src/main/bundle-flow.ts",
91
+ ]);
92
+
87
93
  /** Shared violation filter: exempt test files, gateway internals, and allowlisted paths. */
88
- function filterViolations(files: string[]): string[] {
94
+ function filterViolations(
95
+ files: string[],
96
+ extraAllowlist?: Set<string>,
97
+ ): string[] {
89
98
  return files.filter((f) => {
90
99
  if (isTestFile(f)) return false;
91
100
  if (isGatewayInternal(f)) return false;
92
101
  if (ALLOWLIST.has(f)) return false;
102
+ if (extraAllowlist?.has(f)) return false;
93
103
  return true;
94
104
  });
95
105
  }
@@ -181,7 +191,7 @@ describe("gateway-only API consumption guard", () => {
181
191
  }
182
192
 
183
193
  const files = grepOutput.split("\n").filter((f) => f.length > 0);
184
- const violations = filterViolations(files);
194
+ const violations = filterViolations(files, INTERPOLATED_PORT_ALLOWLIST);
185
195
 
186
196
  if (violations.length > 0) {
187
197
  const message = [
@@ -20,7 +20,7 @@ mock.module("../util/logger.js", () => ({
20
20
  }));
21
21
 
22
22
  const _conversationMocks = new Map<string, unknown>();
23
- mock.module("../daemon/conversation-store.js", () => ({
23
+ mock.module("../daemon/conversation-registry.js", () => ({
24
24
  findConversation: (id: string) => _conversationMocks.get(id),
25
25
  }));
26
26
 
@@ -21,7 +21,7 @@ import { beforeEach, describe, expect, mock, test } from "bun:test";
21
21
  mock.module("../config/env.js", () => ({ isHttpAuthDisabled: () => true }));
22
22
 
23
23
  const _conversationMocks = new Map<string, unknown>();
24
- mock.module("../daemon/conversation-store.js", () => ({
24
+ mock.module("../daemon/conversation-registry.js", () => ({
25
25
  findConversation: (id: string) => _conversationMocks.get(id),
26
26
  }));
27
27
 
@@ -51,9 +51,7 @@ import {
51
51
  routeGuardianReply,
52
52
  } from "../runtime/guardian-reply-router.js";
53
53
  import * as pendingInteractions from "../runtime/pending-interactions.js";
54
- import {
55
- listGuardianDecisionPrompts,
56
- } from "../runtime/routes/guardian-action-routes.js";
54
+ import { listGuardianDecisionPrompts } from "../runtime/routes/guardian-action-routes.js";
57
55
 
58
56
  initializeDb();
59
57
 
@@ -143,7 +143,7 @@ mock.module("../util/logger.js", () => ({
143
143
  import {
144
144
  clearConversations,
145
145
  setConversation,
146
- } from "../daemon/conversation-store.js";
146
+ } from "../daemon/conversation-registry.js";
147
147
  import { handleConfirmationResponse } from "../daemon/handlers/conversations.js";
148
148
 
149
149
  describe("handleConfirmationResponse canonical status sync", () => {
@@ -56,6 +56,7 @@ mock.module("../heartbeat/heartbeat-run-store.js", () => ({
56
56
  markStaleRunsAsMissed: mockMarkStaleRunsAsMissed,
57
57
  markStaleRunningAsError: mockMarkStaleRunningAsError,
58
58
  countCompletedHeartbeatRuns: mock(() => 10),
59
+ countCompletedRunsToday: mock(() => 0),
59
60
  countRecentConsecutiveRuns: mock(() => 0),
60
61
  }));
61
62
 
@@ -32,6 +32,7 @@ mock.module("../heartbeat/heartbeat-run-store.js", () => ({
32
32
  markStaleRunningAsError: mockMarkStaleRunningAsError,
33
33
  listHeartbeatRuns: mockListHeartbeatRuns,
34
34
  countCompletedHeartbeatRuns: mockCountCompletedHeartbeatRuns,
35
+ countCompletedRunsToday: mock(() => 0),
35
36
  countRecentConsecutiveRuns: mock(() => 0),
36
37
  }));
37
38
 
@@ -21,8 +21,8 @@ import type {
21
21
  UserPromptSubmitContext,
22
22
  } from "../plugin-api/types.js";
23
23
  import userPromptSubmit from "../plugins/defaults/history-repair/hooks/user-prompt-submit.js";
24
- import { defaultHistoryRepairPlugin } from "../plugins/defaults/history-repair/register.js";
25
24
  import { repairHistory } from "../plugins/defaults/history-repair/terminal.js";
25
+ import { defaultHistoryRepairPlugin } from "../plugins/defaults/index.js";
26
26
  import { runHook } from "../plugins/pipeline.js";
27
27
  import {
28
28
  registerPlugin,
@@ -47,7 +47,7 @@ interface FakeConversation {
47
47
 
48
48
  const conversations = new Map<string, FakeConversation>();
49
49
 
50
- mock.module("../daemon/conversation-store.js", () => ({
50
+ mock.module("../daemon/conversation-registry.js", () => ({
51
51
  findConversation: (id: string) => conversations.get(id),
52
52
  }));
53
53
 
@@ -8,9 +8,9 @@
8
8
  * 4. Untargeted (no targetClientId, no header) → 200 accepted (regression)
9
9
  *
10
10
  * Resolution goes through conversation.hostCuProxy?.resolve(...). The
11
- * conversation store is mocked to return a controlled conversation object.
11
+ * conversation registry is mocked to return a controlled conversation object.
12
12
  *
13
- * Note: host-cu-routes.ts has a deep import chain (conversation-store →
13
+ * Note: host-cu-routes.ts has a deep import chain (conversation-registry →
14
14
  * conversation.ts → ces-client → service-contracts) that requires mocking
15
15
  * before the module loads. We use dynamic imports to ensure all mocks are
16
16
  * registered before the route module is evaluated.
@@ -75,7 +75,7 @@ const conversationStore = new Map<
75
75
  { hostCuProxy?: { processObservation: (...args: unknown[]) => void } }
76
76
  >();
77
77
 
78
- mock.module("../daemon/conversation-store.js", () => ({
78
+ mock.module("../daemon/conversation-registry.js", () => ({
79
79
  findConversation: (conversationId: string) =>
80
80
  conversationStore.get(conversationId),
81
81
  }));
@@ -7,6 +7,11 @@ mock.module("../util/logger.js", () => ({
7
7
  getLogger: () => makeMockLogger(),
8
8
  }));
9
9
 
10
+ import type { Conversation } from "../daemon/conversation.js";
11
+ import {
12
+ deleteConversation,
13
+ setConversation,
14
+ } from "../daemon/conversation-registry.js";
10
15
  import {
11
16
  createConversation,
12
17
  getConversation,
@@ -113,6 +118,63 @@ describe("inference-profile-session-reaper", () => {
113
118
  subscription.dispose();
114
119
  });
115
120
 
121
+ test("syncs the live in-memory Conversation instance for each cleared session", async () => {
122
+ // GIVEN an expired session-backed override persisted on a conversation row
123
+ const conv = createConversation("reaper-live-sync");
124
+ setConversationInferenceProfileSession(
125
+ conv.id,
126
+ "balanced",
127
+ "session-live",
128
+ Date.now() - 1,
129
+ );
130
+
131
+ // AND a live Conversation instance registered for that conversation,
132
+ // seeded with the same override state the reaper is about to expire
133
+ const appliedStates: Array<{
134
+ profile: string | null;
135
+ sessionId: string | null;
136
+ expiresAt: number | null;
137
+ }> = [];
138
+ const liveConversation = {
139
+ conversationId: conv.id,
140
+ inferenceProfile: "balanced",
141
+ inferenceProfileSessionId: "session-live",
142
+ inferenceProfileExpiresAt: Date.now() - 1,
143
+ applyInferenceProfileState(
144
+ this: {
145
+ inferenceProfile: string | null;
146
+ inferenceProfileSessionId: string | null;
147
+ inferenceProfileExpiresAt: number | null;
148
+ },
149
+ state: {
150
+ profile: string | null;
151
+ sessionId: string | null;
152
+ expiresAt: number | null;
153
+ },
154
+ ) {
155
+ appliedStates.push(state);
156
+ this.inferenceProfile = state.profile;
157
+ this.inferenceProfileSessionId = state.sessionId;
158
+ this.inferenceProfileExpiresAt = state.expiresAt;
159
+ },
160
+ } as unknown as Conversation;
161
+ setConversation(conv.id, liveConversation);
162
+
163
+ // WHEN the reaper sweeps the expired session
164
+ tickInferenceProfileReaper();
165
+
166
+ // THEN the live instance is cleared in lock-step with the DB row, so the
167
+ // per-turn override derivation reads the cleared state without re-fetching
168
+ expect(appliedStates).toEqual([
169
+ { profile: null, sessionId: null, expiresAt: null },
170
+ ]);
171
+ expect(liveConversation.inferenceProfile).toBeNull();
172
+ expect(liveConversation.inferenceProfileSessionId).toBeNull();
173
+ expect(liveConversation.inferenceProfileExpiresAt).toBeNull();
174
+
175
+ deleteConversation(conv.id);
176
+ });
177
+
116
178
  test("CAS protection: row with NULL expiresAt (sticky override) is not touched", async () => {
117
179
  const conv = createConversation("reaper-cas-conv");
118
180