@vellumai/assistant 0.8.8 → 0.8.9-dev.202606091853.fbaa2ae

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 (475) hide show
  1. package/ARCHITECTURE.md +6 -6
  2. package/bun.lock +2 -2
  3. package/docs/activation-funnel-telemetry.md +310 -0
  4. package/examples/plugins/echo/README.md +61 -60
  5. package/examples/plugins/echo/hooks/post-tool-use.ts +18 -0
  6. package/examples/plugins/echo/hooks/stop.ts +16 -0
  7. package/examples/plugins/echo/hooks/user-prompt-submit.ts +18 -0
  8. package/examples/plugins/echo/package.json +1 -2
  9. package/examples/plugins/echo/src/emit.ts +19 -0
  10. package/node_modules/@vellumai/skill-host-contracts/src/skill-host.ts +7 -6
  11. package/openapi.yaml +263 -121
  12. package/package.json +2 -2
  13. package/src/__tests__/activation-early-marking.test.ts +120 -0
  14. package/src/__tests__/agent-loop-callsite-precedence.test.ts +69 -14
  15. package/src/__tests__/agent-loop-exit-reason.test.ts +204 -144
  16. package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +50 -35
  17. package/src/__tests__/agent-loop-output-hooks.test.ts +357 -0
  18. package/src/__tests__/agent-loop-override-profile.test.ts +25 -6
  19. package/src/__tests__/agent-loop-provider-error-recording.test.ts +41 -21
  20. package/src/__tests__/agent-loop-thinking.test.ts +36 -20
  21. package/src/__tests__/agent-loop.test.ts +441 -96
  22. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +14 -14
  23. package/src/__tests__/agent-wake-override-profile.test.ts +17 -21
  24. package/src/__tests__/anthropic-provider.test.ts +23 -8
  25. package/src/__tests__/app-builder-tool-scripts.test.ts +21 -0
  26. package/src/__tests__/app-control-flow.test.ts +1 -1
  27. package/src/__tests__/app-dir-path-guard.test.ts +1 -0
  28. package/src/__tests__/app-executors.test.ts +132 -0
  29. package/src/__tests__/approval-cascade.test.ts +6 -5
  30. package/src/__tests__/approval-routes-http.test.ts +4 -1
  31. package/src/__tests__/background-workers-disk-pressure.test.ts +1 -1
  32. package/src/__tests__/channel-approval-routes.test.ts +1 -1
  33. package/src/__tests__/channel-approvals.test.ts +1 -1
  34. package/src/__tests__/compaction-circuit.test.ts +258 -0
  35. package/src/__tests__/compaction-direct.test.ts +146 -0
  36. package/src/__tests__/compaction-events.test.ts +7 -7
  37. package/src/__tests__/compaction.benchmark.test.ts +1 -1
  38. package/src/__tests__/compactor-web-search-strip.test.ts +213 -0
  39. package/src/__tests__/context-overflow-reducer.test.ts +265 -125
  40. package/src/__tests__/context-window-manager-compact-retry.test.ts +121 -15
  41. package/src/__tests__/conversation-abort-tool-results.test.ts +7 -5
  42. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +7 -10
  43. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +78 -119
  44. package/src/__tests__/conversation-agent-loop-overflow.test.ts +158 -234
  45. package/src/__tests__/conversation-agent-loop.test.ts +342 -623
  46. package/src/__tests__/conversation-clean-command.test.ts +5 -2
  47. package/src/__tests__/conversation-confirmation-signals.test.ts +6 -5
  48. package/src/__tests__/conversation-crud-inference-profile.test.ts +7 -9
  49. package/src/__tests__/conversation-error.test.ts +15 -1
  50. package/src/__tests__/conversation-history-web-search.test.ts +6 -1
  51. package/src/__tests__/conversation-media-retry.test.ts +1 -1
  52. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +44 -4
  53. package/src/__tests__/conversation-process-callsite.test.ts +15 -15
  54. package/src/__tests__/conversation-provider-retry-repair.test.ts +66 -65
  55. package/src/__tests__/conversation-queue.test.ts +10 -10
  56. package/src/__tests__/conversation-runtime-assembly.test.ts +1059 -231
  57. package/src/__tests__/conversation-runtime-workspace.test.ts +115 -20
  58. package/src/__tests__/conversation-slash-queue.test.ts +7 -5
  59. package/src/__tests__/conversation-slash-unknown.test.ts +7 -5
  60. package/src/__tests__/conversation-speed-override.test.ts +11 -10
  61. package/src/__tests__/conversation-starter-routes.test.ts +14 -6
  62. package/src/__tests__/conversation-surfaces-activation-emit.test.ts +395 -0
  63. package/src/__tests__/conversation-surfaces-app-control.test.ts +48 -1
  64. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +73 -4
  65. package/src/__tests__/conversation-undo.test.ts +2 -2
  66. package/src/__tests__/conversation-workspace-cache-state.test.ts +24 -21
  67. package/src/__tests__/conversation-workspace-injection.test.ts +69 -7
  68. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +15 -12
  69. package/src/__tests__/conversations-import-system-filter.test.ts +101 -0
  70. package/src/__tests__/corrected-target.test.ts +93 -0
  71. package/src/__tests__/credential-security-invariants.test.ts +1 -0
  72. package/src/__tests__/cu-unified-flow.test.ts +36 -0
  73. package/src/__tests__/db-acp-history.test.ts +101 -0
  74. package/src/__tests__/dynamic-page-surface.test.ts +31 -0
  75. package/src/__tests__/empty-response-hook.test.ts +1 -1
  76. package/src/__tests__/file-write-tool.test.ts +63 -0
  77. package/src/__tests__/gateway-only-guard.test.ts +17 -2
  78. package/src/__tests__/guardian-grant-minting.test.ts +1 -1
  79. package/src/__tests__/guardian-routing-invariants.test.ts +93 -4
  80. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +1 -1
  81. package/src/__tests__/heartbeat-disk-pressure.test.ts +1 -0
  82. package/src/__tests__/heartbeat-service.test.ts +1 -0
  83. package/src/__tests__/history-repair-hook.test.ts +5 -1
  84. package/src/__tests__/host-app-control-proxy.test.ts +45 -0
  85. package/src/__tests__/host-app-control-routes.test.ts +1 -1
  86. package/src/__tests__/host-cu-routes-targeted.test.ts +3 -3
  87. package/src/__tests__/inference-profile-reaper.test.ts +62 -0
  88. package/src/__tests__/inference-profile-session-handler.test.ts +86 -0
  89. package/src/__tests__/injector-background-turn.test.ts +13 -23
  90. package/src/__tests__/injector-chain.test.ts +268 -44
  91. package/src/__tests__/injector-disk-pressure.test.ts +210 -52
  92. package/src/__tests__/injector-document-comments.test.ts +97 -114
  93. package/src/__tests__/injector-pkb-v2-silenced.test.ts +2 -2
  94. package/src/__tests__/injector-v3-suppression.test.ts +4 -4
  95. package/src/__tests__/list-messages-client-message-id.test.ts +91 -0
  96. package/src/__tests__/list-messages-hidden-metadata.test.ts +38 -0
  97. package/src/__tests__/llm-resolver.test.ts +73 -0
  98. package/src/__tests__/memory-retrieval-hook.test.ts +156 -38
  99. package/src/__tests__/memory-v2-static-injector.test.ts +86 -8
  100. package/src/__tests__/notification-decision-strategy.test.ts +3 -3
  101. package/src/__tests__/parallel-tool.benchmark.test.ts +35 -8
  102. package/src/__tests__/persist-unsendable-image-downscale.test.ts +145 -0
  103. package/src/__tests__/persist-unsendable-image.test.ts +97 -1
  104. package/src/__tests__/plugin-api-shim.test.ts +6 -9
  105. package/src/__tests__/plugin-bootstrap.test.ts +91 -81
  106. package/src/__tests__/plugin-registry.test.ts +3 -49
  107. package/src/__tests__/plugin-skill-contribution.test.ts +15 -14
  108. package/src/__tests__/plugin-tool-contribution.test.ts +7 -4
  109. package/src/__tests__/plugin-types.test.ts +0 -70
  110. package/src/__tests__/post-turn-tool-result-truncation.test.ts +69 -0
  111. package/src/__tests__/pre-model-call-sanitize.test.ts +109 -0
  112. package/src/__tests__/published-app-updater.test.ts +138 -0
  113. package/src/__tests__/reaction-persistence.test.ts +1 -1
  114. package/src/__tests__/send-endpoint-busy.test.ts +4 -1
  115. package/src/__tests__/skill-feature-flags-integration.test.ts +31 -0
  116. package/src/__tests__/steer-tool-repair.test.ts +1 -1
  117. package/src/__tests__/subagent-call-site-routing.test.ts +1 -1
  118. package/src/__tests__/subagent-detail.test.ts +25 -7
  119. package/src/__tests__/subagent-fork-notifications.test.ts +1 -3
  120. package/src/__tests__/subagent-fork-spawn.test.ts +1 -1
  121. package/src/__tests__/subagent-manager-notify.test.ts +1 -3
  122. package/src/__tests__/subagent-notify-parent.test.ts +1 -3
  123. package/src/__tests__/subagent-spawn-tool-fork.test.ts +1 -1
  124. package/src/__tests__/title-generate-hook.test.ts +5 -1
  125. package/src/__tests__/tool-error-hook.test.ts +1 -1
  126. package/src/__tests__/tool-result-truncate-hook.test.ts +1 -1
  127. package/src/__tests__/user-plugin-loader.test.ts +54 -286
  128. package/src/__tests__/web-fetch.test.ts +45 -0
  129. package/src/acp/__tests__/agent-process.test.ts +161 -0
  130. package/src/acp/__tests__/client-handler.test.ts +40 -0
  131. package/src/acp/__tests__/helpers/acp-config-stub.ts +0 -2
  132. package/src/acp/__tests__/helpers/acp-history-db.ts +82 -0
  133. package/src/acp/__tests__/helpers/exec-file-stub.ts +106 -0
  134. package/src/acp/__tests__/prepare-agent-env.test.ts +97 -0
  135. package/src/acp/__tests__/session-manager-persistence.test.ts +95 -28
  136. package/src/acp/__tests__/session-manager-resume.test.ts +888 -0
  137. package/src/acp/agent-process.ts +61 -1
  138. package/src/acp/auto-install.test.ts +280 -0
  139. package/src/acp/auto-install.ts +232 -0
  140. package/src/acp/client-handler.ts +31 -0
  141. package/src/acp/prepare-agent-env.ts +80 -27
  142. package/src/acp/resolve-agent.test.ts +188 -28
  143. package/src/acp/resolve-agent.ts +119 -42
  144. package/src/acp/resume-hint.ts +23 -0
  145. package/src/acp/session-manager.ts +507 -73
  146. package/src/agent/compaction-circuit.ts +60 -102
  147. package/src/agent/loop.ts +403 -251
  148. package/src/api/responses/conversation-message.ts +22 -2
  149. package/src/api/responses/memory-v3-selection-log.ts +19 -10
  150. package/src/approvals/guardian-request-resolvers.ts +27 -1
  151. package/src/background-wake/next-wake.ts +1 -0
  152. package/src/cli/commands/__tests__/memory-v3.test.ts +191 -210
  153. package/src/cli/commands/channel-verification-sessions.ts +6 -6
  154. package/src/cli/commands/db/__tests__/repair.test.ts +3 -1
  155. package/src/cli/commands/memory-v3.ts +57 -199
  156. package/src/cli/commands/plugins.ts +43 -37
  157. package/src/cli/lib/__tests__/install-from-github.test.ts +790 -111
  158. package/src/cli/lib/__tests__/plugin-catalog-cache.test.ts +196 -0
  159. package/src/cli/lib/__tests__/plugin-details.test.ts +381 -0
  160. package/src/cli/lib/__tests__/plugin-marketplace.test.ts +270 -0
  161. package/src/cli/lib/__tests__/search-plugins.test.ts +233 -32
  162. package/src/cli/lib/install-from-github.ts +797 -60
  163. package/src/cli/lib/plugin-catalog-cache.ts +84 -0
  164. package/src/cli/lib/plugin-details.ts +416 -0
  165. package/src/cli/lib/plugin-marketplace.ts +215 -0
  166. package/src/cli/lib/search-plugins.ts +201 -29
  167. package/src/config/__tests__/feature-flag-registry-guard.test.ts +2 -2
  168. package/src/config/acp-defaults.test.ts +10 -0
  169. package/src/config/acp-defaults.ts +9 -3
  170. package/src/config/acp-schema.ts +1 -7
  171. package/src/config/bundled-skills/acp/SKILL.md +80 -41
  172. package/src/config/bundled-skills/acp/TOOLS.json +4 -4
  173. package/src/config/bundled-skills/app-builder/SKILL.md +224 -381
  174. package/src/config/bundled-skills/app-builder/TOOLS.json +72 -2
  175. package/src/config/bundled-skills/app-builder/references/DESIGN_SYSTEM.md +48 -0
  176. package/src/config/bundled-skills/app-builder/references/RESPONSIVE.md +57 -0
  177. package/src/config/bundled-skills/app-builder/references/SLIDES.md +38 -0
  178. package/src/config/bundled-skills/app-builder/tools/app-list.ts +62 -0
  179. package/src/config/bundled-skills/app-builder/tools/app-update.ts +18 -0
  180. package/src/config/bundled-skills/document-editor/SKILL.md +28 -23
  181. package/src/config/bundled-skills/document-editor/TOOLS.json +1 -1
  182. package/src/config/bundled-tool-registry.ts +4 -0
  183. package/src/config/call-site-defaults.ts +0 -3
  184. package/src/config/feature-flag-registry.json +10 -16
  185. package/src/config/llm-resolver.ts +39 -7
  186. package/src/config/schemas/__tests__/memory-v3.test.ts +25 -9
  187. package/src/config/schemas/call-site-catalog.ts +0 -21
  188. package/src/config/schemas/heartbeat.ts +9 -0
  189. package/src/config/schemas/llm.ts +17 -3
  190. package/src/config/schemas/memory-v3.ts +58 -8
  191. package/src/config/seed-inference-profiles.ts +18 -0
  192. package/src/context/compactor.ts +22 -4
  193. package/src/context/post-turn-tool-result-truncation.ts +39 -1
  194. package/src/context/strip-injections.ts +8 -2
  195. package/src/daemon/conversation-agent-loop-handlers.ts +18 -36
  196. package/src/daemon/conversation-agent-loop.ts +308 -1297
  197. package/src/daemon/conversation-error.ts +31 -4
  198. package/src/daemon/conversation-history.ts +1 -1
  199. package/src/daemon/conversation-lifecycle.ts +11 -255
  200. package/src/daemon/conversation-media-retry.ts +19 -6
  201. package/src/daemon/conversation-messaging.ts +17 -0
  202. package/src/daemon/conversation-process.ts +22 -141
  203. package/src/daemon/conversation-queue-manager.ts +8 -0
  204. package/src/daemon/conversation-registry.ts +159 -0
  205. package/src/daemon/conversation-runtime-assembly.ts +361 -393
  206. package/src/daemon/conversation-store.ts +9 -90
  207. package/src/daemon/conversation-surfaces.ts +165 -11
  208. package/src/daemon/conversation-workspace.ts +17 -0
  209. package/src/daemon/conversation.ts +444 -62
  210. package/src/daemon/disk-pressure-policy.ts +0 -1
  211. package/src/daemon/external-plugins-bootstrap.ts +36 -46
  212. package/src/daemon/handlers/config-channels.ts +11 -2
  213. package/src/daemon/handlers/conversations.ts +3 -1
  214. package/src/daemon/handlers/skills.ts +4 -1
  215. package/src/daemon/host-app-control-proxy.ts +72 -57
  216. package/src/daemon/host-proxy-preactivation.ts +1 -3
  217. package/src/daemon/lifecycle.ts +21 -7
  218. package/src/daemon/persist-unsendable-image.ts +62 -25
  219. package/src/daemon/process-message.ts +1 -1
  220. package/src/daemon/server.ts +2 -0
  221. package/src/daemon/tool-side-effects.ts +15 -0
  222. package/src/daemon/wake-conversation-ops.ts +269 -0
  223. package/src/embedded/plugin-api.ts +2 -2
  224. package/src/export/__tests__/transcript-formatter.test.ts +5 -0
  225. package/src/heartbeat/__tests__/heartbeat-service.test.ts +3 -0
  226. package/src/heartbeat/heartbeat-run-store.ts +23 -1
  227. package/src/heartbeat/heartbeat-service.ts +26 -0
  228. package/src/ipc/__tests__/browser-ipc.test.ts +1 -1
  229. package/src/ipc/__tests__/ui-request-route.test.ts +3 -3
  230. package/src/ipc/skill-routes/__tests__/memory.test.ts +15 -0
  231. package/src/ipc/skill-routes/memory.ts +4 -2
  232. package/src/memory/__tests__/activation-session-store.test.ts +41 -0
  233. package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +87 -0
  234. package/src/memory/__tests__/onboarding-events-store.test.ts +80 -0
  235. package/src/memory/activation-session-store.ts +43 -0
  236. package/src/memory/conversation-crud.ts +29 -19
  237. package/src/memory/conversation-starter-checkpoints.ts +1 -0
  238. package/src/memory/db-init.ts +6 -0
  239. package/src/memory/job-handlers/conversation-starters.ts +13 -2
  240. package/src/memory/jobs/__tests__/embed-concept-page.test.ts +5 -4
  241. package/src/memory/jobs-worker.ts +25 -1
  242. package/src/memory/migrations/272-acp-session-history-cwd.ts +36 -0
  243. package/src/memory/migrations/273-onboarding-events-funnel-columns.ts +46 -0
  244. package/src/memory/migrations/274-create-activation-sessions.ts +15 -0
  245. package/src/memory/migrations/index.ts +3 -0
  246. package/src/memory/onboarding-events-store.ts +66 -18
  247. package/src/memory/schema/acp.ts +4 -0
  248. package/src/memory/schema/infrastructure.ts +13 -0
  249. package/src/memory/v2/__tests__/consolidation-job.test.ts +4 -97
  250. package/src/memory/v2/__tests__/page-store.test.ts +22 -0
  251. package/src/memory/v2/consolidation-job.ts +2 -63
  252. package/src/memory/v2/types.ts +5 -0
  253. package/src/messaging/providers/telegram-bot/api.ts +14 -5
  254. package/src/notifications/__tests__/copy-composer.test.ts +244 -0
  255. package/src/notifications/access-request-copy.ts +298 -0
  256. package/src/notifications/adapters/slack.ts +3 -3
  257. package/src/notifications/adapters/telegram.ts +9 -2
  258. package/src/notifications/copy-composer.ts +49 -267
  259. package/src/notifications/decision-engine.ts +16 -35
  260. package/src/notifications/home-feed-side-effect.ts +1 -6
  261. package/src/plugin-api/constants.ts +4 -0
  262. package/src/plugin-api/index.ts +40 -11
  263. package/src/plugin-api/types.ts +108 -0
  264. package/src/plugins/defaults/compaction/compact.ts +106 -0
  265. package/src/{daemon → plugins/defaults/compaction}/context-overflow-reducer.ts +250 -42
  266. package/src/plugins/defaults/compaction/corrected-target.ts +53 -0
  267. package/src/plugins/defaults/compaction/manager-store.ts +57 -0
  268. package/src/plugins/defaults/compaction/package.json +1 -2
  269. package/src/{context → plugins/defaults/compaction}/window-manager.ts +95 -25
  270. package/src/plugins/defaults/empty-response/package.json +0 -1
  271. package/src/plugins/defaults/history-repair/package.json +0 -1
  272. package/src/plugins/defaults/index.ts +156 -26
  273. package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +100 -52
  274. package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit.ts +285 -0
  275. package/src/plugins/defaults/memory-retrieval/injector-chain.ts +2 -2
  276. package/src/plugins/defaults/{injectors/register.ts → memory-retrieval/injectors.ts} +148 -73
  277. package/src/plugins/defaults/memory-retrieval/package.json +14 -0
  278. package/src/plugins/defaults/memory-retrieval/unified-turn-context.ts +223 -0
  279. package/src/plugins/defaults/memory-v3-shadow/__tests__/capabilities.test.ts +71 -0
  280. package/src/plugins/defaults/memory-v3-shadow/__tests__/dense.test.ts +181 -0
  281. package/src/plugins/defaults/memory-v3-shadow/__tests__/edge.test.ts +247 -0
  282. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/live-integration.test.ts +147 -134
  283. package/src/plugins/defaults/memory-v3-shadow/__tests__/maintain-job.test.ts +456 -0
  284. package/src/plugins/defaults/memory-v3-shadow/__tests__/orchestrate.test.ts +662 -0
  285. package/src/plugins/defaults/memory-v3-shadow/__tests__/pool-select.test.ts +306 -0
  286. package/src/plugins/defaults/memory-v3-shadow/__tests__/render-injection.test.ts +91 -0
  287. package/src/plugins/defaults/memory-v3-shadow/__tests__/section-dense-store.test.ts +402 -0
  288. package/src/plugins/defaults/memory-v3-shadow/__tests__/section-needle.test.ts +135 -0
  289. package/src/plugins/defaults/memory-v3-shadow/__tests__/sections.test.ts +125 -0
  290. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/selection-log-store.test.ts +74 -19
  291. package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-integration.test.ts +446 -0
  292. package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +592 -0
  293. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/types.test.ts +2 -24
  294. package/src/plugins/defaults/memory-v3-shadow/capabilities.ts +91 -0
  295. package/src/plugins/defaults/memory-v3-shadow/dense.ts +97 -0
  296. package/src/plugins/defaults/memory-v3-shadow/edge.ts +252 -0
  297. package/src/plugins/defaults/memory-v3-shadow/hooks/post-compact.ts +14 -0
  298. package/src/plugins/defaults/memory-v3-shadow/hooks/user-prompt-submit.ts +19 -0
  299. package/src/plugins/defaults/memory-v3-shadow/injector.ts +76 -0
  300. package/src/plugins/defaults/memory-v3-shadow/maintain-job.ts +555 -0
  301. package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +266 -0
  302. package/src/plugins/defaults/memory-v3-shadow/package.json +14 -0
  303. package/src/plugins/defaults/memory-v3-shadow/page-content.ts +71 -0
  304. package/src/plugins/defaults/memory-v3-shadow/pool-select.ts +204 -0
  305. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/render-injection.ts +16 -8
  306. package/src/plugins/defaults/memory-v3-shadow/section-dense-store.ts +236 -0
  307. package/src/plugins/defaults/memory-v3-shadow/section-needle.ts +200 -0
  308. package/src/plugins/defaults/memory-v3-shadow/sections.ts +115 -0
  309. package/src/plugins/defaults/memory-v3-shadow/selection-log-store.ts +156 -0
  310. package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +382 -0
  311. package/src/plugins/defaults/memory-v3-shadow/types.ts +92 -0
  312. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/working-set.ts +4 -1
  313. package/src/plugins/defaults/title-generate/package.json +0 -1
  314. package/src/plugins/defaults/tool-error/package.json +0 -1
  315. package/src/plugins/defaults/tool-result-truncate/package.json +0 -1
  316. package/src/plugins/external-api.ts +12 -2
  317. package/src/plugins/pipeline.ts +6 -293
  318. package/src/plugins/registry.ts +9 -37
  319. package/src/plugins/types.ts +76 -381
  320. package/src/plugins/user-loader.ts +30 -127
  321. package/src/prompts/__tests__/system-prompt.test.ts +6 -0
  322. package/src/prompts/system-prompt.ts +61 -10
  323. package/src/prompts/templates/BOOTSTRAP-ACTIVATION-RAIL.md +71 -4
  324. package/src/providers/anthropic/client.ts +9 -10
  325. package/src/providers/inference/kimi-cjk-token-ids.ts +493 -0
  326. package/src/providers/inference/logit-bias.ts +55 -0
  327. package/src/providers/model-catalog.ts +34 -0
  328. package/src/providers/openai/__tests__/vision-not-supported.test.ts +75 -0
  329. package/src/providers/openai/chat-completions-provider.ts +44 -1
  330. package/src/providers/retry.ts +22 -0
  331. package/src/providers/types.ts +6 -0
  332. package/src/runtime/__tests__/agent-wake.test.ts +555 -691
  333. package/src/runtime/__tests__/interactive-ui.test.ts +1 -1
  334. package/src/runtime/agent-wake.ts +108 -209
  335. package/src/runtime/assistant-event-hub.ts +1 -1
  336. package/src/runtime/channel-approvals.ts +1 -1
  337. package/src/runtime/interactive-ui.ts +1 -1
  338. package/src/runtime/routes/__tests__/acp-routes.test.ts +466 -55
  339. package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +1 -1
  340. package/src/runtime/routes/__tests__/plugins-routes.test.ts +423 -73
  341. package/src/runtime/routes/__tests__/stt-routes.test.ts +112 -0
  342. package/src/runtime/routes/__tests__/surface-action-routes.test.ts +5 -4
  343. package/src/runtime/routes/__tests__/surface-content-routes.test.ts +4 -1
  344. package/src/runtime/routes/acp-routes.test.ts +185 -32
  345. package/src/runtime/routes/acp-routes.ts +328 -30
  346. package/src/runtime/routes/app-management-routes.ts +3 -0
  347. package/src/runtime/routes/approval-routes.ts +1 -1
  348. package/src/runtime/routes/browser-routes.ts +1 -1
  349. package/src/runtime/routes/browser-tabs-routes.ts +6 -10
  350. package/src/runtime/routes/channel-verification-routes.ts +14 -5
  351. package/src/runtime/routes/conversation-cli-routes.ts +1 -1
  352. package/src/runtime/routes/conversation-list-routes.ts +1 -1
  353. package/src/runtime/routes/conversation-query-routes.ts +1 -1
  354. package/src/runtime/routes/conversation-routes.ts +30 -2
  355. package/src/runtime/routes/conversation-starter-routes.ts +13 -7
  356. package/src/runtime/routes/conversations-import-routes.ts +24 -7
  357. package/src/runtime/routes/host-app-control-routes.ts +1 -1
  358. package/src/runtime/routes/host-cu-routes.ts +1 -1
  359. package/src/runtime/routes/identity-routes.ts +18 -3
  360. package/src/runtime/routes/inbound-message-handler.ts +1 -1
  361. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +33 -34
  362. package/src/runtime/routes/inference-profile-session-handler.ts +11 -0
  363. package/src/runtime/routes/inference-profile-session-reaper.ts +6 -0
  364. package/src/runtime/routes/memory-v3-routes.ts +65 -305
  365. package/src/runtime/routes/playground/__tests__/force-compact.test.ts +1 -1
  366. package/src/runtime/routes/playground/helpers.ts +1 -1
  367. package/src/runtime/routes/plugins-routes.ts +337 -35
  368. package/src/runtime/routes/stt-routes.ts +45 -12
  369. package/src/runtime/routes/surface-conversation-resolver.ts +4 -3
  370. package/src/runtime/routes/work-items-routes.ts +2 -4
  371. package/src/runtime/routes/workspace-routes.ts +50 -15
  372. package/src/runtime/services/conversation-serializer.ts +1 -1
  373. package/src/runtime/verification-outbound-actions.ts +147 -2
  374. package/src/runtime/verification-templates.ts +29 -3
  375. package/src/services/published-app-updater.ts +30 -8
  376. package/src/signals/cancel.ts +2 -4
  377. package/src/subagent/manager.ts +21 -5
  378. package/src/telemetry/__tests__/activation-funnel.test.ts +95 -0
  379. package/src/telemetry/activation-funnel.ts +167 -0
  380. package/src/telemetry/types.ts +13 -0
  381. package/src/telemetry/usage-telemetry-reporter.test.ts +154 -0
  382. package/src/telemetry/usage-telemetry-reporter.ts +26 -1
  383. package/src/tools/acp/context.ts +20 -0
  384. package/src/tools/acp/list-agents.test.ts +9 -19
  385. package/src/tools/acp/list-agents.ts +3 -15
  386. package/src/tools/acp/spawn.test.ts +173 -202
  387. package/src/tools/acp/spawn.ts +37 -172
  388. package/src/tools/acp/steer.test.ts +105 -8
  389. package/src/tools/acp/steer.ts +48 -17
  390. package/src/tools/apps/executors.ts +166 -50
  391. package/src/tools/browser/browser-execution.ts +12 -2
  392. package/src/tools/filesystem/write.ts +34 -0
  393. package/src/tools/network/web-fetch.ts +65 -24
  394. package/src/tools/skills/load.ts +1 -1
  395. package/src/tools/subagent/spawn.ts +2 -4
  396. package/src/tools/ui-surface/definitions.ts +32 -5
  397. package/src/workspace/migrations/051-seed-conversation-summarization-callsite.ts +4 -5
  398. package/src/workspace/migrations/097-enable-adaptive-thinking-managed-profiles.ts +69 -45
  399. package/docs/plugins.md +0 -832
  400. package/examples/plugins/echo/register.ts +0 -143
  401. package/src/__tests__/circuit-breaker-pipeline.test.ts +0 -405
  402. package/src/__tests__/compaction-pipeline.test.ts +0 -210
  403. package/src/__tests__/compaction-timeout-recovery.test.ts +0 -251
  404. package/src/__tests__/overflow-reduce-pipeline.test.ts +0 -667
  405. package/src/__tests__/pipeline-runner.test.ts +0 -554
  406. package/src/daemon/wake-target-adapter.ts +0 -253
  407. package/src/memory/v3/__tests__/assign.test.ts +0 -242
  408. package/src/memory/v3/__tests__/capabilities.test.ts +0 -118
  409. package/src/memory/v3/__tests__/core.test.ts +0 -39
  410. package/src/memory/v3/__tests__/health.test.ts +0 -219
  411. package/src/memory/v3/__tests__/maintain-job.test.ts +0 -288
  412. package/src/memory/v3/__tests__/needle.test.ts +0 -107
  413. package/src/memory/v3/__tests__/orchestrate.test.ts +0 -436
  414. package/src/memory/v3/__tests__/provider-blocks.test.ts +0 -13
  415. package/src/memory/v3/__tests__/reconcile.test.ts +0 -274
  416. package/src/memory/v3/__tests__/render-injection.test.ts +0 -61
  417. package/src/memory/v3/__tests__/router.test.ts +0 -332
  418. package/src/memory/v3/__tests__/selector.test.ts +0 -470
  419. package/src/memory/v3/__tests__/shadow-plugin.test.ts +0 -432
  420. package/src/memory/v3/__tests__/snapshot.test.ts +0 -168
  421. package/src/memory/v3/__tests__/tree.test.ts +0 -192
  422. package/src/memory/v3/assign.ts +0 -268
  423. package/src/memory/v3/capabilities.ts +0 -124
  424. package/src/memory/v3/core.ts +0 -26
  425. package/src/memory/v3/health.ts +0 -0
  426. package/src/memory/v3/maintain-job.ts +0 -314
  427. package/src/memory/v3/needle.ts +0 -115
  428. package/src/memory/v3/orchestrate.ts +0 -126
  429. package/src/memory/v3/page-content.ts +0 -34
  430. package/src/memory/v3/provider-blocks.ts +0 -26
  431. package/src/memory/v3/reconcile.ts +0 -523
  432. package/src/memory/v3/router.ts +0 -190
  433. package/src/memory/v3/selection-log-store.ts +0 -84
  434. package/src/memory/v3/selector.ts +0 -226
  435. package/src/memory/v3/shadow-plugin.ts +0 -411
  436. package/src/memory/v3/snapshot.ts +0 -209
  437. package/src/memory/v3/tree.ts +0 -174
  438. package/src/memory/v3/types.ts +0 -59
  439. package/src/notifications/__tests__/emit-signal-home-feed.test.ts +0 -187
  440. package/src/plugins/defaults/circuit-breaker/middlewares/circuitBreaker.ts +0 -93
  441. package/src/plugins/defaults/circuit-breaker/package.json +0 -15
  442. package/src/plugins/defaults/circuit-breaker/register.ts +0 -39
  443. package/src/plugins/defaults/compaction/middlewares/compaction.ts +0 -25
  444. package/src/plugins/defaults/compaction/register.ts +0 -35
  445. package/src/plugins/defaults/compaction/terminal.ts +0 -73
  446. package/src/plugins/defaults/empty-response/register.ts +0 -23
  447. package/src/plugins/defaults/history-repair/register.ts +0 -24
  448. package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit-temp.ts +0 -216
  449. package/src/plugins/defaults/overflow-reduce/middlewares/overflowReduce.ts +0 -126
  450. package/src/plugins/defaults/overflow-reduce/package.json +0 -15
  451. package/src/plugins/defaults/overflow-reduce/register.ts +0 -42
  452. package/src/plugins/defaults/title-generate/register.ts +0 -35
  453. package/src/plugins/defaults/tool-error/register.ts +0 -23
  454. package/src/plugins/defaults/tool-result-truncate/register.ts +0 -24
  455. package/src/proactive-artifact/aux-message-injector.ts +0 -97
  456. package/src/proactive-artifact/decision.test.ts +0 -226
  457. package/src/proactive-artifact/decision.ts +0 -165
  458. package/src/proactive-artifact/index.ts +0 -7
  459. package/src/proactive-artifact/job.test.ts +0 -962
  460. package/src/proactive-artifact/job.ts +0 -372
  461. package/src/proactive-artifact/message-copy.ts +0 -58
  462. package/src/proactive-artifact/trigger-state.test.ts +0 -286
  463. package/src/proactive-artifact/trigger-state.ts +0 -123
  464. package/src/util/map-limit.ts +0 -27
  465. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/fixtures/eval-turns.json +0 -0
  466. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/fixtures/live-turns.json +0 -0
  467. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/working-set-eviction.test.ts +0 -0
  468. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/working-set-skeleton.test.ts +0 -0
  469. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/README.md +0 -0
  470. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/assignments.json +0 -0
  471. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/core.json +0 -0
  472. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-a/topic-x.md +0 -0
  473. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-a/topic-y.md +0 -0
  474. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-b/topic-z.md +0 -0
  475. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/llm-retry.ts +0 -0
@@ -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,26 @@ 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
+ // Electron main bridges the host proxy to a local assistant's gateway
92
+ // (gatewayPort) over loopback with a Guardian-minted gateway token — same
93
+ // class as bundle-flow.ts. Added with #34049 (host proxy for cloud/managed
94
+ // assistants); the allowlist entry was missed there.
95
+ "apps/macos/src/main/host-proxy-router.ts",
96
+ ]);
97
+
87
98
  /** Shared violation filter: exempt test files, gateway internals, and allowlisted paths. */
88
- function filterViolations(files: string[]): string[] {
99
+ function filterViolations(
100
+ files: string[],
101
+ extraAllowlist?: Set<string>,
102
+ ): string[] {
89
103
  return files.filter((f) => {
90
104
  if (isTestFile(f)) return false;
91
105
  if (isGatewayInternal(f)) return false;
92
106
  if (ALLOWLIST.has(f)) return false;
107
+ if (extraAllowlist?.has(f)) return false;
93
108
  return true;
94
109
  });
95
110
  }
@@ -181,7 +196,7 @@ describe("gateway-only API consumption guard", () => {
181
196
  }
182
197
 
183
198
  const files = grepOutput.split("\n").filter((f) => f.length > 0);
184
- const violations = filterViolations(files);
199
+ const violations = filterViolations(files, INTERPOLATED_PORT_ALLOWLIST);
185
200
 
186
201
  if (violations.length > 0) {
187
202
  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
 
@@ -1334,6 +1332,97 @@ describe("routing invariant: callback buttons route through canonical primitive"
1334
1332
  });
1335
1333
  });
1336
1334
 
1335
+ // ===========================================================================
1336
+ // SECTION 8b: route-owned (directResolve) confirmations resolve via the
1337
+ // channel/guardian-action path too.
1338
+ //
1339
+ // The ACP spawn/steer approval gate (acp-routes.ts, ATL-822) registers
1340
+ // pending confirmations with a `directResolve` callback rather than a
1341
+ // Conversation.prompter. The tool_approval resolver must fire that callback
1342
+ // directly; otherwise a guardian approval submitted over a channel never
1343
+ // reaches the waiting route and the spawn/resume hangs until timeout.
1344
+ // ===========================================================================
1345
+
1346
+ describe("routing invariant: directResolve interactions resolve via guardian decisions", () => {
1347
+ beforeEach(() => resetTables());
1348
+
1349
+ function registerDirectResolveInteraction(
1350
+ requestId: string,
1351
+ conversationId: string,
1352
+ ): string[] {
1353
+ const decisions: string[] = [];
1354
+ pendingInteractions.register(requestId, {
1355
+ conversationId,
1356
+ kind: "confirmation",
1357
+ confirmationDetails: {
1358
+ toolName: "acp_spawn",
1359
+ input: { agent: "claude", task: "t", cwd: "/work" },
1360
+ riskLevel: "high",
1361
+ executionTarget: "host",
1362
+ allowlistOptions: [],
1363
+ scopeOptions: [],
1364
+ persistentDecisionsAllowed: false,
1365
+ },
1366
+ directResolve: (d) => decisions.push(d),
1367
+ });
1368
+ return decisions;
1369
+ }
1370
+
1371
+ test("channel approval fires directResolve('allow') with no live Conversation", async () => {
1372
+ const req = createCanonicalGuardianRequest({
1373
+ kind: "tool_approval",
1374
+ sourceType: "channel",
1375
+ conversationId: "conv-acp",
1376
+ guardianExternalUserId: "guardian-1",
1377
+ guardianPrincipalId: TEST_PRINCIPAL_ID,
1378
+ toolName: "acp_spawn",
1379
+ expiresAt: Date.now() + 60_000,
1380
+ });
1381
+ // Deliberately no _conversationMocks entry: the prompter path would have
1382
+ // failed with conversation_not_found, proving directResolve is what runs.
1383
+ const decisions = registerDirectResolveInteraction(req.id, "conv-acp");
1384
+
1385
+ const result = await routeGuardianReply(
1386
+ replyCtx({
1387
+ messageText: "",
1388
+ callbackData: `apr:${req.id}:approve_once`,
1389
+ conversationId: "conv-acp",
1390
+ }),
1391
+ );
1392
+
1393
+ expect(result.decisionApplied).toBe(true);
1394
+ expect(decisions).toEqual(["allow"]);
1395
+ expect(pendingInteractions.get(req.id)).toBeUndefined();
1396
+ expect(getCanonicalGuardianRequest(req.id)!.status).toBe("approved");
1397
+ });
1398
+
1399
+ test("channel rejection fires directResolve('deny')", async () => {
1400
+ const req = createCanonicalGuardianRequest({
1401
+ kind: "tool_approval",
1402
+ sourceType: "channel",
1403
+ conversationId: "conv-acp",
1404
+ guardianExternalUserId: "guardian-1",
1405
+ guardianPrincipalId: TEST_PRINCIPAL_ID,
1406
+ toolName: "acp_spawn",
1407
+ expiresAt: Date.now() + 60_000,
1408
+ });
1409
+ const decisions = registerDirectResolveInteraction(req.id, "conv-acp");
1410
+
1411
+ const result = await routeGuardianReply(
1412
+ replyCtx({
1413
+ messageText: "",
1414
+ callbackData: `apr:${req.id}:reject`,
1415
+ conversationId: "conv-acp",
1416
+ }),
1417
+ );
1418
+
1419
+ expect(result.decisionApplied).toBe(true);
1420
+ expect(decisions).toEqual(["deny"]);
1421
+ expect(pendingInteractions.get(req.id)).toBeUndefined();
1422
+ expect(getCanonicalGuardianRequest(req.id)!.status).toBe("denied");
1423
+ });
1424
+ });
1425
+
1337
1426
  // ===========================================================================
1338
1427
  // SECTION 9: Destination hints do not bypass principal binding for tool_approval
1339
1428
  // ===========================================================================
@@ -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,
@@ -40,6 +40,10 @@ const noopLogger: PluginLogger = {
40
40
  function makeCtx(messages: Message[]): UserPromptSubmitContext {
41
41
  return {
42
42
  conversationId: "conv-test",
43
+ userMessageId: "msg-test",
44
+ requestId: "req-test",
45
+ modelProfileKey: null,
46
+ isNonInteractive: false,
43
47
  prompt: "",
44
48
  originalMessages: messages,
45
49
  latestMessages: messages,
@@ -372,6 +372,49 @@ describe("HostAppControlProxy", () => {
372
372
  proxy.dispose();
373
373
  });
374
374
 
375
+ test("non-start tool with mismatched target client is rejected", async () => {
376
+ mockActorMap.set("client-A", "user-1");
377
+ mockActorMap.set("client-B", "user-1");
378
+ const proxy = new HostAppControlProxy("conv-1");
379
+ const startCtrl = new AbortController();
380
+
381
+ const pStart = proxy.request(
382
+ "app_control_start",
383
+ { tool: "start", app: "com.example.editor" },
384
+ "conv-1",
385
+ startCtrl.signal,
386
+ "user-1",
387
+ "client-A",
388
+ );
389
+ proxy.resolve(
390
+ (sentMessages[0] as Record<string, unknown>).requestId as string,
391
+ payload({ pngBase64: PNG_A }),
392
+ );
393
+ await pStart;
394
+ sentMessages.length = 0;
395
+
396
+ const result = await proxy.request(
397
+ "app_control_type",
398
+ {
399
+ tool: "type",
400
+ app: "com.example.editor",
401
+ text: "hello",
402
+ },
403
+ "conv-1",
404
+ new AbortController().signal,
405
+ "user-1",
406
+ "client-B",
407
+ );
408
+
409
+ expect(result.isError).toBe(true);
410
+ expect(result.content).toContain("client-A");
411
+ expect(result.content).toContain("client-B");
412
+ expect(result.content).toContain("app_control_start");
413
+ expect(sentMessages).toHaveLength(0);
414
+
415
+ proxy.dispose();
416
+ });
417
+
375
418
  test("non-start tool with case-different but otherwise matching app is allowed", async () => {
376
419
  const proxy = new HostAppControlProxy("conv-1");
377
420
  const startCtrl = new AbortController();
@@ -1297,6 +1340,7 @@ describe("HostAppControlProxy", () => {
1297
1340
  _setActiveAppControlSession({
1298
1341
  conversationId: "conv-1",
1299
1342
  app: "com.example.app",
1343
+ targetClientId: "client-A",
1300
1344
  });
1301
1345
 
1302
1346
  const resultPromise = proxy.request(
@@ -1332,6 +1376,7 @@ describe("HostAppControlProxy", () => {
1332
1376
  _setActiveAppControlSession({
1333
1377
  conversationId: "conv-1",
1334
1378
  app: "com.example.app",
1379
+ targetClientId: "client-A",
1335
1380
  });
1336
1381
 
1337
1382
  const resultPromise = proxy.request(
@@ -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