@vellumai/assistant 0.8.8 → 0.8.9-dev.202606091926.ebb2d62

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 (490) 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 +273 -121
  12. package/package.json +2 -2
  13. package/scripts/sync-llm-catalog.ts +2 -0
  14. package/src/__tests__/activation-early-marking.test.ts +120 -0
  15. package/src/__tests__/agent-loop-callsite-precedence.test.ts +69 -14
  16. package/src/__tests__/agent-loop-exit-reason.test.ts +204 -144
  17. package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +50 -35
  18. package/src/__tests__/agent-loop-output-hooks.test.ts +357 -0
  19. package/src/__tests__/agent-loop-override-profile.test.ts +25 -6
  20. package/src/__tests__/agent-loop-provider-error-recording.test.ts +41 -21
  21. package/src/__tests__/agent-loop-thinking.test.ts +36 -20
  22. package/src/__tests__/agent-loop.test.ts +441 -96
  23. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +14 -14
  24. package/src/__tests__/agent-wake-override-profile.test.ts +17 -21
  25. package/src/__tests__/anthropic-provider.test.ts +23 -8
  26. package/src/__tests__/app-builder-tool-scripts.test.ts +21 -0
  27. package/src/__tests__/app-control-flow.test.ts +1 -1
  28. package/src/__tests__/app-dir-path-guard.test.ts +1 -0
  29. package/src/__tests__/app-executors.test.ts +132 -0
  30. package/src/__tests__/approval-cascade.test.ts +6 -5
  31. package/src/__tests__/approval-routes-http.test.ts +4 -1
  32. package/src/__tests__/background-workers-disk-pressure.test.ts +1 -1
  33. package/src/__tests__/build-persisted-content.test.ts +75 -1
  34. package/src/__tests__/channel-approval-routes.test.ts +1 -1
  35. package/src/__tests__/channel-approvals.test.ts +1 -1
  36. package/src/__tests__/compaction-circuit.test.ts +258 -0
  37. package/src/__tests__/compaction-direct.test.ts +146 -0
  38. package/src/__tests__/compaction-events.test.ts +7 -7
  39. package/src/__tests__/compaction.benchmark.test.ts +1 -1
  40. package/src/__tests__/compactor-web-search-strip.test.ts +213 -0
  41. package/src/__tests__/config-managed-gemini-defaults.test.ts +2 -46
  42. package/src/__tests__/context-overflow-reducer.test.ts +265 -125
  43. package/src/__tests__/context-window-manager-compact-retry.test.ts +121 -15
  44. package/src/__tests__/conversation-abort-tool-results.test.ts +7 -5
  45. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +7 -10
  46. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +78 -119
  47. package/src/__tests__/conversation-agent-loop-overflow.test.ts +158 -234
  48. package/src/__tests__/conversation-agent-loop.test.ts +342 -623
  49. package/src/__tests__/conversation-clean-command.test.ts +5 -2
  50. package/src/__tests__/conversation-confirmation-signals.test.ts +6 -5
  51. package/src/__tests__/conversation-crud-inference-profile.test.ts +7 -9
  52. package/src/__tests__/conversation-error.test.ts +15 -1
  53. package/src/__tests__/conversation-history-web-search.test.ts +6 -1
  54. package/src/__tests__/conversation-media-retry.test.ts +1 -1
  55. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +44 -4
  56. package/src/__tests__/conversation-process-callsite.test.ts +15 -15
  57. package/src/__tests__/conversation-provider-retry-repair.test.ts +66 -65
  58. package/src/__tests__/conversation-queue.test.ts +10 -10
  59. package/src/__tests__/conversation-runtime-assembly.test.ts +1059 -231
  60. package/src/__tests__/conversation-runtime-workspace.test.ts +115 -20
  61. package/src/__tests__/conversation-slash-queue.test.ts +7 -5
  62. package/src/__tests__/conversation-slash-unknown.test.ts +7 -5
  63. package/src/__tests__/conversation-speed-override.test.ts +11 -10
  64. package/src/__tests__/conversation-starter-routes.test.ts +14 -6
  65. package/src/__tests__/conversation-surfaces-activation-emit.test.ts +395 -0
  66. package/src/__tests__/conversation-surfaces-app-control.test.ts +48 -1
  67. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +73 -4
  68. package/src/__tests__/conversation-undo.test.ts +2 -2
  69. package/src/__tests__/conversation-workspace-cache-state.test.ts +24 -21
  70. package/src/__tests__/conversation-workspace-injection.test.ts +69 -7
  71. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +15 -12
  72. package/src/__tests__/conversations-import-system-filter.test.ts +101 -0
  73. package/src/__tests__/corrected-target.test.ts +93 -0
  74. package/src/__tests__/credential-execution-feature-gates.test.ts +5 -4
  75. package/src/__tests__/credential-security-invariants.test.ts +1 -0
  76. package/src/__tests__/cu-unified-flow.test.ts +36 -0
  77. package/src/__tests__/db-acp-history.test.ts +101 -0
  78. package/src/__tests__/dynamic-page-surface.test.ts +31 -0
  79. package/src/__tests__/embedding-managed-proxy-selection.test.ts +6 -40
  80. package/src/__tests__/empty-response-hook.test.ts +1 -1
  81. package/src/__tests__/file-write-tool.test.ts +63 -0
  82. package/src/__tests__/gateway-only-guard.test.ts +17 -2
  83. package/src/__tests__/guardian-grant-minting.test.ts +1 -1
  84. package/src/__tests__/guardian-routing-invariants.test.ts +93 -4
  85. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +1 -1
  86. package/src/__tests__/heartbeat-disk-pressure.test.ts +1 -0
  87. package/src/__tests__/heartbeat-service.test.ts +1 -0
  88. package/src/__tests__/history-repair-hook.test.ts +5 -1
  89. package/src/__tests__/host-app-control-proxy.test.ts +45 -0
  90. package/src/__tests__/host-app-control-routes.test.ts +1 -1
  91. package/src/__tests__/host-cu-routes-targeted.test.ts +3 -3
  92. package/src/__tests__/inference-profile-reaper.test.ts +62 -0
  93. package/src/__tests__/inference-profile-session-handler.test.ts +86 -0
  94. package/src/__tests__/injector-background-turn.test.ts +13 -23
  95. package/src/__tests__/injector-chain.test.ts +268 -44
  96. package/src/__tests__/injector-disk-pressure.test.ts +210 -52
  97. package/src/__tests__/injector-document-comments.test.ts +97 -114
  98. package/src/__tests__/injector-pkb-v2-silenced.test.ts +2 -2
  99. package/src/__tests__/injector-v3-suppression.test.ts +4 -4
  100. package/src/__tests__/list-messages-client-message-id.test.ts +91 -0
  101. package/src/__tests__/list-messages-hidden-metadata.test.ts +38 -0
  102. package/src/__tests__/llm-catalog-parity.test.ts +4 -0
  103. package/src/__tests__/llm-resolver.test.ts +73 -0
  104. package/src/__tests__/memory-retrieval-hook.test.ts +156 -38
  105. package/src/__tests__/memory-v2-static-injector.test.ts +86 -8
  106. package/src/__tests__/notification-decision-strategy.test.ts +3 -3
  107. package/src/__tests__/parallel-tool.benchmark.test.ts +35 -8
  108. package/src/__tests__/persist-unsendable-image-downscale.test.ts +145 -0
  109. package/src/__tests__/persist-unsendable-image.test.ts +97 -1
  110. package/src/__tests__/persistence-secret-redaction.test.ts +78 -0
  111. package/src/__tests__/plugin-api-shim.test.ts +6 -9
  112. package/src/__tests__/plugin-bootstrap.test.ts +91 -81
  113. package/src/__tests__/plugin-registry.test.ts +3 -49
  114. package/src/__tests__/plugin-skill-contribution.test.ts +15 -14
  115. package/src/__tests__/plugin-tool-contribution.test.ts +7 -4
  116. package/src/__tests__/plugin-types.test.ts +0 -70
  117. package/src/__tests__/post-turn-tool-result-truncation.test.ts +69 -0
  118. package/src/__tests__/pre-model-call-sanitize.test.ts +109 -0
  119. package/src/__tests__/published-app-updater.test.ts +138 -0
  120. package/src/__tests__/reaction-persistence.test.ts +1 -1
  121. package/src/__tests__/retry-thinking-adaptive-only.test.ts +245 -0
  122. package/src/__tests__/send-endpoint-busy.test.ts +4 -1
  123. package/src/__tests__/server-history-render.test.ts +39 -0
  124. package/src/__tests__/skill-feature-flags-integration.test.ts +31 -0
  125. package/src/__tests__/steer-tool-repair.test.ts +1 -1
  126. package/src/__tests__/subagent-call-site-routing.test.ts +1 -1
  127. package/src/__tests__/subagent-detail.test.ts +25 -7
  128. package/src/__tests__/subagent-fork-notifications.test.ts +1 -3
  129. package/src/__tests__/subagent-fork-spawn.test.ts +1 -1
  130. package/src/__tests__/subagent-manager-notify.test.ts +1 -3
  131. package/src/__tests__/subagent-notify-parent.test.ts +1 -3
  132. package/src/__tests__/subagent-spawn-tool-fork.test.ts +1 -1
  133. package/src/__tests__/title-generate-hook.test.ts +5 -1
  134. package/src/__tests__/tool-error-hook.test.ts +1 -1
  135. package/src/__tests__/tool-result-truncate-hook.test.ts +1 -1
  136. package/src/__tests__/tool-start-timestamp.test.ts +218 -0
  137. package/src/__tests__/user-plugin-loader.test.ts +54 -286
  138. package/src/__tests__/web-fetch.test.ts +45 -0
  139. package/src/acp/__tests__/agent-process.test.ts +161 -0
  140. package/src/acp/__tests__/client-handler.test.ts +40 -0
  141. package/src/acp/__tests__/helpers/acp-config-stub.ts +0 -2
  142. package/src/acp/__tests__/helpers/acp-history-db.ts +82 -0
  143. package/src/acp/__tests__/helpers/exec-file-stub.ts +106 -0
  144. package/src/acp/__tests__/prepare-agent-env.test.ts +97 -0
  145. package/src/acp/__tests__/session-manager-persistence.test.ts +95 -28
  146. package/src/acp/__tests__/session-manager-resume.test.ts +888 -0
  147. package/src/acp/agent-process.ts +61 -1
  148. package/src/acp/auto-install.test.ts +280 -0
  149. package/src/acp/auto-install.ts +232 -0
  150. package/src/acp/client-handler.ts +31 -0
  151. package/src/acp/prepare-agent-env.ts +80 -27
  152. package/src/acp/resolve-agent.test.ts +188 -28
  153. package/src/acp/resolve-agent.ts +119 -42
  154. package/src/acp/resume-hint.ts +23 -0
  155. package/src/acp/session-manager.ts +507 -73
  156. package/src/agent/compaction-circuit.ts +60 -102
  157. package/src/agent/loop.ts +403 -251
  158. package/src/api/events/tool-result.ts +9 -0
  159. package/src/api/events/tool-use-start.ts +7 -0
  160. package/src/api/responses/conversation-message.ts +72 -13
  161. package/src/api/responses/memory-v3-selection-log.ts +19 -10
  162. package/src/approvals/guardian-request-resolvers.ts +27 -1
  163. package/src/background-wake/next-wake.ts +1 -0
  164. package/src/cli/commands/__tests__/memory-v3.test.ts +191 -210
  165. package/src/cli/commands/channel-verification-sessions.ts +6 -6
  166. package/src/cli/commands/db/__tests__/repair.test.ts +3 -1
  167. package/src/cli/commands/memory-v3.ts +57 -199
  168. package/src/cli/commands/plugins.ts +43 -37
  169. package/src/cli/lib/__tests__/install-from-github.test.ts +790 -111
  170. package/src/cli/lib/__tests__/plugin-catalog-cache.test.ts +196 -0
  171. package/src/cli/lib/__tests__/plugin-details.test.ts +381 -0
  172. package/src/cli/lib/__tests__/plugin-marketplace.test.ts +270 -0
  173. package/src/cli/lib/__tests__/search-plugins.test.ts +233 -32
  174. package/src/cli/lib/install-from-github.ts +797 -60
  175. package/src/cli/lib/plugin-catalog-cache.ts +84 -0
  176. package/src/cli/lib/plugin-details.ts +416 -0
  177. package/src/cli/lib/plugin-marketplace.ts +215 -0
  178. package/src/cli/lib/search-plugins.ts +201 -29
  179. package/src/config/__tests__/feature-flag-registry-guard.test.ts +2 -2
  180. package/src/config/acp-defaults.test.ts +10 -0
  181. package/src/config/acp-defaults.ts +9 -3
  182. package/src/config/acp-schema.ts +1 -7
  183. package/src/config/bundled-skills/acp/SKILL.md +80 -41
  184. package/src/config/bundled-skills/acp/TOOLS.json +4 -4
  185. package/src/config/bundled-skills/app-builder/SKILL.md +224 -381
  186. package/src/config/bundled-skills/app-builder/TOOLS.json +72 -2
  187. package/src/config/bundled-skills/app-builder/references/DESIGN_SYSTEM.md +48 -0
  188. package/src/config/bundled-skills/app-builder/references/RESPONSIVE.md +57 -0
  189. package/src/config/bundled-skills/app-builder/references/SLIDES.md +38 -0
  190. package/src/config/bundled-skills/app-builder/tools/app-list.ts +62 -0
  191. package/src/config/bundled-skills/app-builder/tools/app-update.ts +18 -0
  192. package/src/config/bundled-skills/document-editor/SKILL.md +28 -23
  193. package/src/config/bundled-skills/document-editor/TOOLS.json +1 -1
  194. package/src/config/bundled-tool-registry.ts +4 -0
  195. package/src/config/call-site-defaults.ts +0 -3
  196. package/src/config/feature-flag-registry.json +10 -56
  197. package/src/config/llm-resolver.ts +39 -7
  198. package/src/config/loader.ts +5 -22
  199. package/src/config/schemas/__tests__/memory-v3.test.ts +25 -9
  200. package/src/config/schemas/call-site-catalog.ts +0 -21
  201. package/src/config/schemas/heartbeat.ts +9 -0
  202. package/src/config/schemas/llm.ts +17 -3
  203. package/src/config/schemas/memory-v3.ts +58 -8
  204. package/src/config/seed-inference-profiles.ts +18 -0
  205. package/src/context/compactor.ts +22 -4
  206. package/src/context/post-turn-tool-result-truncation.ts +39 -1
  207. package/src/context/strip-injections.ts +8 -2
  208. package/src/daemon/conversation-agent-loop-handlers.ts +120 -42
  209. package/src/daemon/conversation-agent-loop.ts +308 -1297
  210. package/src/daemon/conversation-error.ts +31 -4
  211. package/src/daemon/conversation-history.ts +1 -1
  212. package/src/daemon/conversation-lifecycle.ts +11 -255
  213. package/src/daemon/conversation-media-retry.ts +19 -6
  214. package/src/daemon/conversation-messaging.ts +17 -0
  215. package/src/daemon/conversation-process.ts +22 -141
  216. package/src/daemon/conversation-queue-manager.ts +8 -0
  217. package/src/daemon/conversation-registry.ts +159 -0
  218. package/src/daemon/conversation-runtime-assembly.ts +361 -393
  219. package/src/daemon/conversation-store.ts +9 -90
  220. package/src/daemon/conversation-surfaces.ts +165 -11
  221. package/src/daemon/conversation-workspace.ts +17 -0
  222. package/src/daemon/conversation.ts +444 -62
  223. package/src/daemon/disk-pressure-policy.ts +0 -1
  224. package/src/daemon/external-plugins-bootstrap.ts +36 -46
  225. package/src/daemon/handlers/config-channels.ts +11 -2
  226. package/src/daemon/handlers/conversations.ts +3 -1
  227. package/src/daemon/handlers/shared.ts +9 -1
  228. package/src/daemon/handlers/skills.ts +4 -1
  229. package/src/daemon/host-app-control-proxy.ts +72 -57
  230. package/src/daemon/host-proxy-preactivation.ts +1 -3
  231. package/src/daemon/lifecycle.ts +21 -7
  232. package/src/daemon/persist-unsendable-image.ts +62 -25
  233. package/src/daemon/process-message.ts +1 -1
  234. package/src/daemon/server.ts +2 -0
  235. package/src/daemon/tool-side-effects.ts +15 -0
  236. package/src/daemon/wake-conversation-ops.ts +269 -0
  237. package/src/embedded/plugin-api.ts +2 -2
  238. package/src/export/__tests__/transcript-formatter.test.ts +5 -0
  239. package/src/heartbeat/__tests__/heartbeat-service.test.ts +3 -0
  240. package/src/heartbeat/heartbeat-run-store.ts +23 -1
  241. package/src/heartbeat/heartbeat-service.ts +26 -0
  242. package/src/ipc/__tests__/browser-ipc.test.ts +1 -1
  243. package/src/ipc/__tests__/ui-request-route.test.ts +3 -3
  244. package/src/ipc/skill-routes/__tests__/memory.test.ts +15 -0
  245. package/src/ipc/skill-routes/memory.ts +4 -2
  246. package/src/memory/__tests__/activation-session-store.test.ts +41 -0
  247. package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +87 -0
  248. package/src/memory/__tests__/onboarding-events-store.test.ts +80 -0
  249. package/src/memory/activation-session-store.ts +43 -0
  250. package/src/memory/conversation-crud.ts +29 -19
  251. package/src/memory/conversation-starter-checkpoints.ts +1 -0
  252. package/src/memory/db-init.ts +6 -0
  253. package/src/memory/embedding-backend.ts +5 -22
  254. package/src/memory/job-handlers/conversation-starters.ts +13 -2
  255. package/src/memory/jobs/__tests__/embed-concept-page.test.ts +5 -4
  256. package/src/memory/jobs-worker.ts +25 -1
  257. package/src/memory/migrations/272-acp-session-history-cwd.ts +36 -0
  258. package/src/memory/migrations/273-onboarding-events-funnel-columns.ts +46 -0
  259. package/src/memory/migrations/274-create-activation-sessions.ts +15 -0
  260. package/src/memory/migrations/index.ts +3 -0
  261. package/src/memory/onboarding-events-store.ts +66 -18
  262. package/src/memory/schema/acp.ts +4 -0
  263. package/src/memory/schema/infrastructure.ts +13 -0
  264. package/src/memory/v2/__tests__/consolidation-job.test.ts +4 -97
  265. package/src/memory/v2/__tests__/page-store.test.ts +22 -0
  266. package/src/memory/v2/consolidation-job.ts +2 -63
  267. package/src/memory/v2/types.ts +5 -0
  268. package/src/messaging/providers/telegram-bot/api.ts +14 -5
  269. package/src/notifications/__tests__/copy-composer.test.ts +244 -0
  270. package/src/notifications/access-request-copy.ts +298 -0
  271. package/src/notifications/adapters/slack.ts +3 -3
  272. package/src/notifications/adapters/telegram.ts +9 -2
  273. package/src/notifications/copy-composer.ts +49 -267
  274. package/src/notifications/decision-engine.ts +16 -35
  275. package/src/notifications/home-feed-side-effect.ts +1 -6
  276. package/src/plugin-api/constants.ts +4 -0
  277. package/src/plugin-api/index.ts +40 -11
  278. package/src/plugin-api/types.ts +108 -0
  279. package/src/plugins/defaults/compaction/compact.ts +106 -0
  280. package/src/{daemon → plugins/defaults/compaction}/context-overflow-reducer.ts +250 -42
  281. package/src/plugins/defaults/compaction/corrected-target.ts +53 -0
  282. package/src/plugins/defaults/compaction/manager-store.ts +57 -0
  283. package/src/plugins/defaults/compaction/package.json +1 -2
  284. package/src/{context → plugins/defaults/compaction}/window-manager.ts +95 -25
  285. package/src/plugins/defaults/empty-response/package.json +0 -1
  286. package/src/plugins/defaults/history-repair/package.json +0 -1
  287. package/src/plugins/defaults/index.ts +156 -26
  288. package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +100 -52
  289. package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit.ts +285 -0
  290. package/src/plugins/defaults/memory-retrieval/injector-chain.ts +2 -2
  291. package/src/plugins/defaults/{injectors/register.ts → memory-retrieval/injectors.ts} +148 -73
  292. package/src/plugins/defaults/memory-retrieval/package.json +14 -0
  293. package/src/plugins/defaults/memory-retrieval/unified-turn-context.ts +223 -0
  294. package/src/plugins/defaults/memory-v3-shadow/__tests__/capabilities.test.ts +71 -0
  295. package/src/plugins/defaults/memory-v3-shadow/__tests__/dense.test.ts +181 -0
  296. package/src/plugins/defaults/memory-v3-shadow/__tests__/edge.test.ts +247 -0
  297. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/live-integration.test.ts +147 -134
  298. package/src/plugins/defaults/memory-v3-shadow/__tests__/maintain-job.test.ts +456 -0
  299. package/src/plugins/defaults/memory-v3-shadow/__tests__/orchestrate.test.ts +662 -0
  300. package/src/plugins/defaults/memory-v3-shadow/__tests__/pool-select.test.ts +306 -0
  301. package/src/plugins/defaults/memory-v3-shadow/__tests__/render-injection.test.ts +91 -0
  302. package/src/plugins/defaults/memory-v3-shadow/__tests__/section-dense-store.test.ts +402 -0
  303. package/src/plugins/defaults/memory-v3-shadow/__tests__/section-needle.test.ts +135 -0
  304. package/src/plugins/defaults/memory-v3-shadow/__tests__/sections.test.ts +125 -0
  305. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/selection-log-store.test.ts +74 -19
  306. package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-integration.test.ts +446 -0
  307. package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +592 -0
  308. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/types.test.ts +2 -24
  309. package/src/plugins/defaults/memory-v3-shadow/capabilities.ts +91 -0
  310. package/src/plugins/defaults/memory-v3-shadow/dense.ts +97 -0
  311. package/src/plugins/defaults/memory-v3-shadow/edge.ts +252 -0
  312. package/src/plugins/defaults/memory-v3-shadow/hooks/post-compact.ts +14 -0
  313. package/src/plugins/defaults/memory-v3-shadow/hooks/user-prompt-submit.ts +19 -0
  314. package/src/plugins/defaults/memory-v3-shadow/injector.ts +76 -0
  315. package/src/plugins/defaults/memory-v3-shadow/maintain-job.ts +555 -0
  316. package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +266 -0
  317. package/src/plugins/defaults/memory-v3-shadow/package.json +14 -0
  318. package/src/plugins/defaults/memory-v3-shadow/page-content.ts +71 -0
  319. package/src/plugins/defaults/memory-v3-shadow/pool-select.ts +204 -0
  320. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/render-injection.ts +16 -8
  321. package/src/plugins/defaults/memory-v3-shadow/section-dense-store.ts +236 -0
  322. package/src/plugins/defaults/memory-v3-shadow/section-needle.ts +200 -0
  323. package/src/plugins/defaults/memory-v3-shadow/sections.ts +115 -0
  324. package/src/plugins/defaults/memory-v3-shadow/selection-log-store.ts +156 -0
  325. package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +382 -0
  326. package/src/plugins/defaults/memory-v3-shadow/types.ts +92 -0
  327. package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/working-set.ts +4 -1
  328. package/src/plugins/defaults/title-generate/package.json +0 -1
  329. package/src/plugins/defaults/tool-error/package.json +0 -1
  330. package/src/plugins/defaults/tool-result-truncate/package.json +0 -1
  331. package/src/plugins/external-api.ts +12 -2
  332. package/src/plugins/pipeline.ts +6 -293
  333. package/src/plugins/registry.ts +9 -37
  334. package/src/plugins/types.ts +76 -381
  335. package/src/plugins/user-loader.ts +30 -127
  336. package/src/prompts/__tests__/system-prompt.test.ts +6 -0
  337. package/src/prompts/system-prompt.ts +61 -10
  338. package/src/prompts/templates/BOOTSTRAP-ACTIVATION-RAIL.md +71 -4
  339. package/src/providers/anthropic/client.ts +9 -10
  340. package/src/providers/inference/kimi-cjk-token-ids.ts +493 -0
  341. package/src/providers/inference/logit-bias.ts +55 -0
  342. package/src/providers/model-catalog.ts +57 -0
  343. package/src/providers/openai/__tests__/vision-not-supported.test.ts +75 -0
  344. package/src/providers/openai/chat-completions-provider.ts +44 -1
  345. package/src/providers/retry.ts +57 -7
  346. package/src/providers/types.ts +6 -0
  347. package/src/runtime/__tests__/agent-wake.test.ts +555 -691
  348. package/src/runtime/__tests__/interactive-ui.test.ts +1 -1
  349. package/src/runtime/agent-wake.ts +108 -209
  350. package/src/runtime/assistant-event-hub.ts +1 -1
  351. package/src/runtime/channel-approvals.ts +1 -1
  352. package/src/runtime/interactive-ui.ts +1 -1
  353. package/src/runtime/routes/__tests__/acp-routes.test.ts +466 -55
  354. package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +1 -1
  355. package/src/runtime/routes/__tests__/plugins-routes.test.ts +423 -73
  356. package/src/runtime/routes/__tests__/stt-routes.test.ts +112 -0
  357. package/src/runtime/routes/__tests__/surface-action-routes.test.ts +5 -4
  358. package/src/runtime/routes/__tests__/surface-content-routes.test.ts +4 -1
  359. package/src/runtime/routes/acp-routes.test.ts +185 -32
  360. package/src/runtime/routes/acp-routes.ts +328 -30
  361. package/src/runtime/routes/app-management-routes.ts +3 -0
  362. package/src/runtime/routes/approval-routes.ts +1 -1
  363. package/src/runtime/routes/browser-routes.ts +1 -1
  364. package/src/runtime/routes/browser-tabs-routes.ts +6 -10
  365. package/src/runtime/routes/channel-verification-routes.ts +14 -5
  366. package/src/runtime/routes/conversation-cli-routes.ts +1 -1
  367. package/src/runtime/routes/conversation-list-routes.ts +1 -1
  368. package/src/runtime/routes/conversation-query-routes.ts +1 -1
  369. package/src/runtime/routes/conversation-routes.ts +30 -2
  370. package/src/runtime/routes/conversation-starter-routes.ts +13 -7
  371. package/src/runtime/routes/conversations-import-routes.ts +24 -7
  372. package/src/runtime/routes/host-app-control-routes.ts +1 -1
  373. package/src/runtime/routes/host-cu-routes.ts +1 -1
  374. package/src/runtime/routes/identity-routes.ts +18 -3
  375. package/src/runtime/routes/inbound-message-handler.ts +1 -1
  376. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +33 -34
  377. package/src/runtime/routes/inference-profile-session-handler.ts +11 -0
  378. package/src/runtime/routes/inference-profile-session-reaper.ts +6 -0
  379. package/src/runtime/routes/memory-v3-routes.ts +65 -305
  380. package/src/runtime/routes/playground/__tests__/force-compact.test.ts +1 -1
  381. package/src/runtime/routes/playground/helpers.ts +1 -1
  382. package/src/runtime/routes/plugins-routes.ts +337 -35
  383. package/src/runtime/routes/stt-routes.ts +45 -12
  384. package/src/runtime/routes/surface-conversation-resolver.ts +4 -3
  385. package/src/runtime/routes/work-items-routes.ts +2 -4
  386. package/src/runtime/routes/workspace-routes.ts +50 -15
  387. package/src/runtime/services/conversation-serializer.ts +1 -1
  388. package/src/runtime/verification-outbound-actions.ts +147 -2
  389. package/src/runtime/verification-templates.ts +29 -3
  390. package/src/services/published-app-updater.ts +30 -8
  391. package/src/signals/cancel.ts +2 -4
  392. package/src/subagent/manager.ts +21 -5
  393. package/src/telemetry/__tests__/activation-funnel.test.ts +95 -0
  394. package/src/telemetry/activation-funnel.ts +167 -0
  395. package/src/telemetry/types.ts +13 -0
  396. package/src/telemetry/usage-telemetry-reporter.test.ts +154 -0
  397. package/src/telemetry/usage-telemetry-reporter.ts +26 -1
  398. package/src/tools/acp/context.ts +20 -0
  399. package/src/tools/acp/list-agents.test.ts +9 -19
  400. package/src/tools/acp/list-agents.ts +3 -15
  401. package/src/tools/acp/spawn.test.ts +173 -202
  402. package/src/tools/acp/spawn.ts +37 -172
  403. package/src/tools/acp/steer.test.ts +105 -8
  404. package/src/tools/acp/steer.ts +48 -17
  405. package/src/tools/apps/executors.ts +166 -50
  406. package/src/tools/browser/browser-execution.ts +12 -2
  407. package/src/tools/filesystem/write.ts +34 -0
  408. package/src/tools/network/web-fetch.ts +65 -24
  409. package/src/tools/skills/load.ts +1 -1
  410. package/src/tools/subagent/spawn.ts +2 -4
  411. package/src/tools/ui-surface/definitions.ts +32 -5
  412. package/src/workspace/migrations/051-seed-conversation-summarization-callsite.ts +4 -5
  413. package/src/workspace/migrations/097-enable-adaptive-thinking-managed-profiles.ts +69 -45
  414. package/docs/plugins.md +0 -832
  415. package/examples/plugins/echo/register.ts +0 -143
  416. package/src/__tests__/circuit-breaker-pipeline.test.ts +0 -405
  417. package/src/__tests__/compaction-pipeline.test.ts +0 -210
  418. package/src/__tests__/compaction-timeout-recovery.test.ts +0 -251
  419. package/src/__tests__/overflow-reduce-pipeline.test.ts +0 -667
  420. package/src/__tests__/pipeline-runner.test.ts +0 -554
  421. package/src/daemon/wake-target-adapter.ts +0 -253
  422. package/src/memory/v3/__tests__/assign.test.ts +0 -242
  423. package/src/memory/v3/__tests__/capabilities.test.ts +0 -118
  424. package/src/memory/v3/__tests__/core.test.ts +0 -39
  425. package/src/memory/v3/__tests__/health.test.ts +0 -219
  426. package/src/memory/v3/__tests__/maintain-job.test.ts +0 -288
  427. package/src/memory/v3/__tests__/needle.test.ts +0 -107
  428. package/src/memory/v3/__tests__/orchestrate.test.ts +0 -436
  429. package/src/memory/v3/__tests__/provider-blocks.test.ts +0 -13
  430. package/src/memory/v3/__tests__/reconcile.test.ts +0 -274
  431. package/src/memory/v3/__tests__/render-injection.test.ts +0 -61
  432. package/src/memory/v3/__tests__/router.test.ts +0 -332
  433. package/src/memory/v3/__tests__/selector.test.ts +0 -470
  434. package/src/memory/v3/__tests__/shadow-plugin.test.ts +0 -432
  435. package/src/memory/v3/__tests__/snapshot.test.ts +0 -168
  436. package/src/memory/v3/__tests__/tree.test.ts +0 -192
  437. package/src/memory/v3/assign.ts +0 -268
  438. package/src/memory/v3/capabilities.ts +0 -124
  439. package/src/memory/v3/core.ts +0 -26
  440. package/src/memory/v3/health.ts +0 -0
  441. package/src/memory/v3/maintain-job.ts +0 -314
  442. package/src/memory/v3/needle.ts +0 -115
  443. package/src/memory/v3/orchestrate.ts +0 -126
  444. package/src/memory/v3/page-content.ts +0 -34
  445. package/src/memory/v3/provider-blocks.ts +0 -26
  446. package/src/memory/v3/reconcile.ts +0 -523
  447. package/src/memory/v3/router.ts +0 -190
  448. package/src/memory/v3/selection-log-store.ts +0 -84
  449. package/src/memory/v3/selector.ts +0 -226
  450. package/src/memory/v3/shadow-plugin.ts +0 -411
  451. package/src/memory/v3/snapshot.ts +0 -209
  452. package/src/memory/v3/tree.ts +0 -174
  453. package/src/memory/v3/types.ts +0 -59
  454. package/src/notifications/__tests__/emit-signal-home-feed.test.ts +0 -187
  455. package/src/plugins/defaults/circuit-breaker/middlewares/circuitBreaker.ts +0 -93
  456. package/src/plugins/defaults/circuit-breaker/package.json +0 -15
  457. package/src/plugins/defaults/circuit-breaker/register.ts +0 -39
  458. package/src/plugins/defaults/compaction/middlewares/compaction.ts +0 -25
  459. package/src/plugins/defaults/compaction/register.ts +0 -35
  460. package/src/plugins/defaults/compaction/terminal.ts +0 -73
  461. package/src/plugins/defaults/empty-response/register.ts +0 -23
  462. package/src/plugins/defaults/history-repair/register.ts +0 -24
  463. package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit-temp.ts +0 -216
  464. package/src/plugins/defaults/overflow-reduce/middlewares/overflowReduce.ts +0 -126
  465. package/src/plugins/defaults/overflow-reduce/package.json +0 -15
  466. package/src/plugins/defaults/overflow-reduce/register.ts +0 -42
  467. package/src/plugins/defaults/title-generate/register.ts +0 -35
  468. package/src/plugins/defaults/tool-error/register.ts +0 -23
  469. package/src/plugins/defaults/tool-result-truncate/register.ts +0 -24
  470. package/src/proactive-artifact/aux-message-injector.ts +0 -97
  471. package/src/proactive-artifact/decision.test.ts +0 -226
  472. package/src/proactive-artifact/decision.ts +0 -165
  473. package/src/proactive-artifact/index.ts +0 -7
  474. package/src/proactive-artifact/job.test.ts +0 -962
  475. package/src/proactive-artifact/job.ts +0 -372
  476. package/src/proactive-artifact/message-copy.ts +0 -58
  477. package/src/proactive-artifact/trigger-state.test.ts +0 -286
  478. package/src/proactive-artifact/trigger-state.ts +0 -123
  479. package/src/util/map-limit.ts +0 -27
  480. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/fixtures/eval-turns.json +0 -0
  481. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/fixtures/live-turns.json +0 -0
  482. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/working-set-eviction.test.ts +0 -0
  483. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/__tests__/working-set-skeleton.test.ts +0 -0
  484. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/README.md +0 -0
  485. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/assignments.json +0 -0
  486. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/core.json +0 -0
  487. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-a/topic-x.md +0 -0
  488. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-a/topic-y.md +0 -0
  489. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/data/leaves/domain-b/topic-z.md +0 -0
  490. /package/src/{memory/v3 → plugins/defaults/memory-v3-shadow}/llm-retry.ts +0 -0
@@ -0,0 +1,258 @@
1
+ /**
2
+ * Tests for the `CompactionCircuit` class.
3
+ *
4
+ * The breaker logic lives as direct methods on the per-conversation
5
+ * `CompactionCircuit` state container (`agent/compaction-circuit.ts`). These
6
+ * tests assert the threshold (3 consecutive failures) and cooldown (1 hour)
7
+ * exactly match the user-visible behavior:
8
+ * (a) counter increments on each failure outcome
9
+ * (b) circuit opens after exactly 3 consecutive failures
10
+ * (c) successful compaction resets counter and clears the circuit
11
+ * (d) isOpen() reflects state and cooldown expiry
12
+ * (e) circuit re-opens after cooldown expiry when 3 more failures
13
+ * accumulate (guards the stale-timestamp regression)
14
+ * (f) isOpen() is query-only and never mutates the counter
15
+ * (g) open→closed transition emits `compaction_circuit_closed` exactly once
16
+ * (h) closed→closed transition emits nothing
17
+ */
18
+
19
+ import { afterEach, beforeEach, describe, expect, test } from "bun:test";
20
+
21
+ import {
22
+ COMPACTION_CIRCUIT_COOLDOWN_MS,
23
+ COMPACTION_CIRCUIT_FAILURE_THRESHOLD,
24
+ CompactionCircuit,
25
+ } from "../agent/compaction-circuit.js";
26
+ import type { CompactionCircuitEvent } from "../plugins/types.js";
27
+
28
+ // ─── Fixtures ───────────────────────────────────────────────────────────────
29
+
30
+ const CONVERSATION_ID = "conv-breaker-test";
31
+
32
+ function collectEvents(): {
33
+ events: CompactionCircuitEvent[];
34
+ onEvent: (msg: CompactionCircuitEvent) => void;
35
+ } {
36
+ const events: CompactionCircuitEvent[] = [];
37
+ return { events, onEvent: (msg) => events.push(msg) };
38
+ }
39
+
40
+ describe("CompactionCircuit", () => {
41
+ let originalDateNow: () => number;
42
+
43
+ beforeEach(() => {
44
+ originalDateNow = Date.now;
45
+ });
46
+
47
+ afterEach(() => {
48
+ Date.now = originalDateNow;
49
+ });
50
+
51
+ test("threshold and cooldown constants match the user-visible contract", () => {
52
+ expect(COMPACTION_CIRCUIT_FAILURE_THRESHOLD).toBe(3);
53
+ expect(COMPACTION_CIRCUIT_COOLDOWN_MS).toBe(60 * 60 * 1000);
54
+ });
55
+
56
+ test("(a) counter increments on each failure outcome", async () => {
57
+ const circuit = new CompactionCircuit(CONVERSATION_ID);
58
+ const { onEvent, events } = collectEvents();
59
+
60
+ await circuit.recordOutcome(true, onEvent);
61
+ expect(circuit.consecutiveCompactionFailures).toBe(1);
62
+ expect(circuit.compactionCircuitOpenUntil).toBeNull();
63
+ expect(events).toHaveLength(0);
64
+
65
+ await circuit.recordOutcome(true, onEvent);
66
+ expect(circuit.consecutiveCompactionFailures).toBe(2);
67
+ expect(circuit.compactionCircuitOpenUntil).toBeNull();
68
+ expect(events).toHaveLength(0);
69
+ });
70
+
71
+ test("(b) circuit opens after exactly 3 consecutive failures", async () => {
72
+ const fixedNow = 1_700_000_000_000;
73
+ Date.now = () => fixedNow;
74
+
75
+ const circuit = new CompactionCircuit(CONVERSATION_ID);
76
+ const { onEvent, events } = collectEvents();
77
+
78
+ await circuit.recordOutcome(true, onEvent);
79
+ await circuit.recordOutcome(true, onEvent);
80
+ // Two failures — circuit still closed.
81
+ expect(circuit.compactionCircuitOpenUntil).toBeNull();
82
+ expect(events).toHaveLength(0);
83
+
84
+ await circuit.recordOutcome(true, onEvent);
85
+ // Third failure — circuit trips and fires the event exactly once.
86
+ expect(circuit.consecutiveCompactionFailures).toBe(3);
87
+ expect(circuit.compactionCircuitOpenUntil).toBe(fixedNow + 60 * 60 * 1000);
88
+ expect(await circuit.isOpen()).toBe(true);
89
+ expect(events).toHaveLength(1);
90
+ expect(events[0]).toEqual({
91
+ type: "compaction_circuit_open",
92
+ conversationId: CONVERSATION_ID,
93
+ reason: "3_consecutive_failures",
94
+ openUntil: fixedNow + 60 * 60 * 1000,
95
+ });
96
+
97
+ // Further failures do not re-fire the event while the circuit is open.
98
+ await circuit.recordOutcome(true, onEvent);
99
+ expect(circuit.consecutiveCompactionFailures).toBe(4);
100
+ expect(events).toHaveLength(1);
101
+ });
102
+
103
+ test("(c) successful outcome resets counter and clears circuit", async () => {
104
+ const fixedNow = 1_700_000_000_000;
105
+ Date.now = () => fixedNow;
106
+
107
+ const circuit = new CompactionCircuit(CONVERSATION_ID);
108
+ const { onEvent } = collectEvents();
109
+
110
+ // Trip the breaker.
111
+ await circuit.recordOutcome(true, onEvent);
112
+ await circuit.recordOutcome(true, onEvent);
113
+ await circuit.recordOutcome(true, onEvent);
114
+ expect(circuit.compactionCircuitOpenUntil).not.toBeNull();
115
+
116
+ // Success resets state.
117
+ await circuit.recordOutcome(false, onEvent);
118
+ expect(circuit.consecutiveCompactionFailures).toBe(0);
119
+ expect(circuit.compactionCircuitOpenUntil).toBeNull();
120
+ });
121
+
122
+ test("(d) isOpen() reflects state and expiry", async () => {
123
+ const fixedNow = 1_700_000_000_000;
124
+ Date.now = () => fixedNow;
125
+
126
+ const circuit = new CompactionCircuit(CONVERSATION_ID);
127
+ const { onEvent } = collectEvents();
128
+
129
+ // Fresh state: closed.
130
+ expect(await circuit.isOpen()).toBe(false);
131
+
132
+ // Trip the breaker.
133
+ await circuit.recordOutcome(true, onEvent);
134
+ await circuit.recordOutcome(true, onEvent);
135
+ await circuit.recordOutcome(true, onEvent);
136
+
137
+ // While open.
138
+ expect(await circuit.isOpen()).toBe(true);
139
+
140
+ // After cooldown expires the breaker reports closed again, even without
141
+ // an explicit reset — the open-until timestamp is the only source of
142
+ // truth for the gate.
143
+ Date.now = () => fixedNow + 60 * 60 * 1000 + 1;
144
+ expect(await circuit.isOpen()).toBe(false);
145
+ });
146
+
147
+ test("(e) circuit re-opens after cooldown expiry when 3 more failures accumulate", async () => {
148
+ // Regression: opening the breaker a second time must not require
149
+ // `compactionCircuitOpenUntil === null`. Once a cooldown expires, the
150
+ // gate correctly reports "closed" but a stale past-timestamp stays on the
151
+ // state, so the next 3-strike window must still trip a new cooldown. Any
152
+ // expired timestamp is treated the same as null.
153
+ const t0 = 1_700_000_000_000;
154
+ Date.now = () => t0;
155
+
156
+ const circuit = new CompactionCircuit(CONVERSATION_ID);
157
+ const { onEvent, events } = collectEvents();
158
+
159
+ // Trip the breaker the first time.
160
+ await circuit.recordOutcome(true, onEvent);
161
+ await circuit.recordOutcome(true, onEvent);
162
+ await circuit.recordOutcome(true, onEvent);
163
+ expect(circuit.compactionCircuitOpenUntil).toBe(t0 + 60 * 60 * 1000);
164
+ expect(events).toHaveLength(1);
165
+
166
+ // Advance past the cooldown window. Reset the counter — in production this
167
+ // happens when a subsequent `maybeCompact` call succeeds after the
168
+ // cooldown elapses, but the bug manifests even when the counter is reset:
169
+ // the stale `compactionCircuitOpenUntil` is what breaks re-opening.
170
+ const t1 = t0 + 60 * 60 * 1000 + 1;
171
+ Date.now = () => t1;
172
+ expect(await circuit.isOpen()).toBe(false);
173
+ circuit.consecutiveCompactionFailures = 0;
174
+ // `compactionCircuitOpenUntil` is deliberately left as the old timestamp
175
+ // to reproduce the bug condition.
176
+ expect(circuit.compactionCircuitOpenUntil).toBe(t0 + 60 * 60 * 1000);
177
+
178
+ // Three more failures must trip a fresh cooldown even though the old
179
+ // timestamp is still set.
180
+ await circuit.recordOutcome(true, onEvent);
181
+ await circuit.recordOutcome(true, onEvent);
182
+ await circuit.recordOutcome(true, onEvent);
183
+ expect(circuit.consecutiveCompactionFailures).toBe(3);
184
+ expect(circuit.compactionCircuitOpenUntil).toBe(t1 + 60 * 60 * 1000);
185
+ expect(events).toHaveLength(2);
186
+ expect(events[1]).toEqual({
187
+ type: "compaction_circuit_open",
188
+ conversationId: CONVERSATION_ID,
189
+ reason: "3_consecutive_failures",
190
+ openUntil: t1 + 60 * 60 * 1000,
191
+ });
192
+ });
193
+
194
+ test("(f) isOpen() is query-only and never mutates the counter", async () => {
195
+ // `maybeCompact()` early-return paths skip `recordOutcome` entirely and
196
+ // only gate on `isOpen()`. The query must never touch the 3-strike
197
+ // counter so those early returns can't silently reset it.
198
+ const circuit = new CompactionCircuit(CONVERSATION_ID);
199
+ const { onEvent } = collectEvents();
200
+
201
+ await circuit.recordOutcome(true, onEvent);
202
+ await circuit.recordOutcome(true, onEvent);
203
+ expect(circuit.consecutiveCompactionFailures).toBe(2);
204
+
205
+ // Query-only — must NOT touch the counter.
206
+ expect(await circuit.isOpen()).toBe(false);
207
+ expect(circuit.consecutiveCompactionFailures).toBe(2);
208
+
209
+ // A third real failure then trips the breaker as expected.
210
+ await circuit.recordOutcome(true, onEvent);
211
+ expect(circuit.consecutiveCompactionFailures).toBe(3);
212
+ expect(circuit.compactionCircuitOpenUntil).not.toBeNull();
213
+ });
214
+
215
+ test("(g) open→closed transition emits compaction_circuit_closed exactly once", async () => {
216
+ // Regression: the reset branch must notify the client on open→closed.
217
+ // Otherwise the Swift banner set from `compaction_circuit_open` stays
218
+ // visible until the original `openUntil` deadline (up to 1h),
219
+ // misrepresenting the live state.
220
+ const fixedNow = 1_700_000_000_000;
221
+ Date.now = () => fixedNow;
222
+
223
+ const circuit = new CompactionCircuit(CONVERSATION_ID);
224
+ const { onEvent, events } = collectEvents();
225
+
226
+ // Force the circuit into the open state directly — the emitted-event
227
+ // transition logic is what we're testing, not the tripping path.
228
+ circuit.compactionCircuitOpenUntil = fixedNow + 60 * 60 * 1000;
229
+ circuit.consecutiveCompactionFailures = 3;
230
+
231
+ await circuit.recordOutcome(false, onEvent);
232
+
233
+ expect(circuit.consecutiveCompactionFailures).toBe(0);
234
+ expect(circuit.compactionCircuitOpenUntil).toBeNull();
235
+ expect(events).toHaveLength(1);
236
+ expect(events[0]).toEqual({
237
+ type: "compaction_circuit_closed",
238
+ conversationId: CONVERSATION_ID,
239
+ });
240
+ });
241
+
242
+ test("(h) successful outcome against an already-closed circuit emits no event", async () => {
243
+ // Emitting `compaction_circuit_closed` on every successful compaction
244
+ // would spam the client (the breaker is closed in the common case).
245
+ // Only the open→closed transition is meaningful.
246
+ const circuit = new CompactionCircuit(CONVERSATION_ID);
247
+ const { onEvent, events } = collectEvents();
248
+
249
+ expect(circuit.compactionCircuitOpenUntil).toBeNull();
250
+ await circuit.recordOutcome(false, onEvent);
251
+ expect(circuit.compactionCircuitOpenUntil).toBeNull();
252
+ expect(events).toHaveLength(0);
253
+
254
+ // A second successful outcome while still closed — still no event.
255
+ await circuit.recordOutcome(false, onEvent);
256
+ expect(events).toHaveLength(0);
257
+ });
258
+ });
@@ -0,0 +1,146 @@
1
+ /**
2
+ * Tests for the default compaction plugin (`defaultCompact`).
3
+ *
4
+ * The agent loop calls {@link defaultCompact} directly with a
5
+ * {@link CompactionContext} rather than routing through a middleware pipeline.
6
+ * These tests assert that the default implementation resolves the
7
+ * conversation's {@link ContextWindowManager} from the compaction store and
8
+ * forwards the request's conversational options verbatim. The orchestrator
9
+ * integration path (conversation-agent-loop) is exercised by
10
+ * `conversation-agent-loop-overflow.test.ts`.
11
+ */
12
+
13
+ import { describe, expect, mock, test } from "bun:test";
14
+
15
+ // `defaultCompact` resolves the manager from the per-conversation compaction
16
+ // store. Register each test's canned stub in a map the mocked store reads from.
17
+ const fakeContextWindowManagers = new Map<string, unknown>();
18
+ mock.module("../plugins/defaults/compaction/manager-store.js", () => ({
19
+ createContextWindowManager: () => undefined,
20
+ getContextWindowManager: (conversationId: string) =>
21
+ fakeContextWindowManagers.get(conversationId),
22
+ disposeContextWindowManager: (conversationId: string) => {
23
+ fakeContextWindowManagers.delete(conversationId);
24
+ },
25
+ }));
26
+
27
+ const { defaultCompact } =
28
+ await import("../plugins/defaults/compaction/compact.js");
29
+
30
+ type ContextWindowResultShape = {
31
+ compacted: boolean;
32
+ summaryText: string;
33
+ messages: unknown[];
34
+ previousEstimatedInputTokens: number;
35
+ estimatedInputTokens: number;
36
+ maxInputTokens: number;
37
+ thresholdTokens: number;
38
+ compactedMessages: number;
39
+ compactedPersistedMessages: number;
40
+ summaryCalls: number;
41
+ summaryInputTokens: number;
42
+ summaryOutputTokens: number;
43
+ summaryModel: string;
44
+ reason?: string;
45
+ };
46
+
47
+ function makeResult(
48
+ overrides: Partial<ContextWindowResultShape> = {},
49
+ ): ContextWindowResultShape {
50
+ return {
51
+ compacted: true,
52
+ summaryText: "default-summary",
53
+ messages: [],
54
+ previousEstimatedInputTokens: 1000,
55
+ estimatedInputTokens: 100,
56
+ maxInputTokens: 100000,
57
+ thresholdTokens: 80000,
58
+ compactedMessages: 3,
59
+ compactedPersistedMessages: 3,
60
+ summaryCalls: 1,
61
+ summaryInputTokens: 500,
62
+ summaryOutputTokens: 120,
63
+ summaryModel: "default-model",
64
+ ...overrides,
65
+ };
66
+ }
67
+
68
+ /** Register a stub manager under a conversation id and return its recorder. */
69
+ function registerManager(
70
+ conversationId: string,
71
+ result: ContextWindowResultShape,
72
+ ) {
73
+ const observed: {
74
+ messages: unknown;
75
+ signal: unknown;
76
+ options: unknown;
77
+ }[] = [];
78
+ fakeContextWindowManagers.set(conversationId, {
79
+ maybeCompact: async (
80
+ messages: unknown,
81
+ signal: unknown,
82
+ options: unknown,
83
+ ) => {
84
+ observed.push({ messages, signal, options });
85
+ return result;
86
+ },
87
+ });
88
+ return observed;
89
+ }
90
+
91
+ describe("defaultCompact", () => {
92
+ test("delegates to the manager and returns its result unchanged", async () => {
93
+ // GIVEN a manager whose maybeCompact records its arguments
94
+ const expected = makeResult({
95
+ summaryText: "manager-summary",
96
+ compactedMessages: 7,
97
+ });
98
+ const observed = registerManager("conv-delegates", expected);
99
+ const messages = [{ role: "user", content: "hi" }] as never;
100
+ const signal = new AbortController().signal;
101
+
102
+ // WHEN defaultCompact runs with those messages and a signal
103
+ const result = (await defaultCompact({
104
+ conversationId: "conv-delegates",
105
+ messages,
106
+ signal,
107
+ })) as unknown as ContextWindowResultShape;
108
+
109
+ // THEN the manager saw the same messages and signal
110
+ expect(observed).toHaveLength(1);
111
+ expect(observed[0]!.messages).toBe(messages);
112
+ expect(observed[0]!.signal).toBe(signal);
113
+
114
+ // AND the returned result is the manager's object, unmodified
115
+ expect(result).toBe(expected);
116
+ expect(result.summaryText).toBe("manager-summary");
117
+ expect(result.compactedMessages).toBe(7);
118
+ });
119
+
120
+ test("forwards the request's compaction options to the manager", async () => {
121
+ // GIVEN a manager and a fully-populated compaction context
122
+ const observed = registerManager("conv-options", makeResult());
123
+
124
+ // WHEN defaultCompact runs with force/profile/trust options
125
+ await defaultCompact({
126
+ conversationId: "conv-options",
127
+ messages: [] as never,
128
+ force: true,
129
+ overrideProfile: "fast-profile",
130
+ precomputedEstimate: 1234,
131
+ minKeepRecentUserTurns: 0,
132
+ actorTrustClass: "guardian",
133
+ });
134
+
135
+ // THEN the manager received exactly those options, and the conversation id,
136
+ // messages, and signal are not leaked into the options bag
137
+ expect(observed).toHaveLength(1);
138
+ expect(observed[0]!.options).toEqual({
139
+ force: true,
140
+ overrideProfile: "fast-profile",
141
+ precomputedEstimate: 1234,
142
+ minKeepRecentUserTurns: 0,
143
+ actorTrustClass: "guardian",
144
+ });
145
+ });
146
+ });
@@ -12,8 +12,8 @@ import { describe, expect, mock, test } from "bun:test";
12
12
 
13
13
  import { CompactionCircuit } from "../agent/compaction-circuit.js";
14
14
  import type { AgentEvent } from "../agent/loop.js";
15
- import type { ContextWindowResult } from "../context/window-manager.js";
16
15
  import type { ServerMessage } from "../daemon/message-protocol.js";
16
+ import type { ContextWindowResult } from "../plugins/defaults/compaction/window-manager.js";
17
17
  import type { Message, ProviderResponse } from "../providers/types.js";
18
18
 
19
19
  // ---------------------------------------------------------------------------
@@ -187,11 +187,11 @@ let mockCompactResult: ContextWindowResult = {
187
187
  summaryText: "",
188
188
  };
189
189
 
190
- mock.module("../context/window-manager.js", () => ({
190
+ mock.module("../plugins/defaults/compaction/window-manager.js", () => ({
191
191
  ContextWindowManager: class {
192
192
  nonPersistedPrefixCount = 0;
193
- summaryIsInjected = false;
194
193
  constructor() {}
194
+ updateConfig() {}
195
195
  shouldCompact() {
196
196
  return { needed: false, estimatedTokens: 0 };
197
197
  }
@@ -228,10 +228,10 @@ mock.module("../agent/loop.js", () => ({
228
228
  getActiveModel() {
229
229
  return undefined;
230
230
  }
231
- async run(
232
- _messages: Message[],
233
- _onEvent: (event: AgentEvent) => void,
234
- ): Promise<Message[]> {
231
+ async run(_options: {
232
+ messages: Message[];
233
+ onEvent: (event: AgentEvent) => void;
234
+ }): Promise<Message[]> {
235
235
  return [];
236
236
  }
237
237
  },
@@ -12,7 +12,7 @@ import { describe, expect, mock, test } from "bun:test";
12
12
 
13
13
  import { DEFAULT_CONFIG } from "../config/defaults.js";
14
14
  import { estimatePromptTokens } from "../context/token-estimator.js";
15
- import { ContextWindowManager } from "../context/window-manager.js";
15
+ import { ContextWindowManager } from "../plugins/defaults/compaction/window-manager.js";
16
16
  import type { Message, Provider } from "../providers/types.js";
17
17
 
18
18
  mock.module("../util/logger.js", () => ({
@@ -0,0 +1,213 @@
1
+ import { describe, expect, mock, test } from "bun:test";
2
+
3
+ function makeLoggerStub(): Record<string, unknown> {
4
+ const stub: Record<string, unknown> = {};
5
+ for (const m of [
6
+ "info",
7
+ "warn",
8
+ "error",
9
+ "debug",
10
+ "trace",
11
+ "fatal",
12
+ "silent",
13
+ "child",
14
+ ]) {
15
+ stub[m] = m === "child" ? () => makeLoggerStub() : () => {};
16
+ }
17
+ return stub;
18
+ }
19
+
20
+ mock.module("../util/logger.js", () => ({
21
+ getLogger: () => makeLoggerStub(),
22
+ }));
23
+
24
+ mock.module("../memory/conversation-crud.js", () => ({
25
+ getMessages: () => [],
26
+ reserveMessage: mock(async () => ({ id: "msg-reserve" })),
27
+ }));
28
+
29
+ mock.module("../memory/attachments-store.js", () => ({
30
+ getAttachmentMetadataForMessage: () => [],
31
+ getAttachmentContent: () => null,
32
+ }));
33
+
34
+ import {
35
+ runAssistantDrivenCompaction,
36
+ runEmergencyCompaction,
37
+ } from "../context/compactor.js";
38
+ import type { Message, Provider } from "../providers/types.js";
39
+
40
+ const TAIL_TIMESTAMP =
41
+ "2026-05-21 (Thursday) 10:00:00 -05:00 (America/Chicago)";
42
+
43
+ const compactionResponse = `
44
+ <compaction_result>
45
+ <summary>
46
+ Earlier turns summarized here.
47
+ </summary>
48
+
49
+ <key_state>
50
+ - Nothing critical pending.
51
+ </key_state>
52
+
53
+ <tail_start timestamp="${TAIL_TIMESTAMP}" preview="tail anchor message" />
54
+ </compaction_result>
55
+ `;
56
+
57
+ const ENCRYPTED_TOKEN = "expired_encrypted_token_abc123";
58
+
59
+ const userText = (text: string): Message => ({
60
+ role: "user",
61
+ content: [{ type: "text", text }],
62
+ });
63
+
64
+ const userTextWithTurnContext = (text: string, timestamp: string): Message => ({
65
+ role: "user",
66
+ content: [
67
+ {
68
+ type: "text",
69
+ text: `<turn_context>\ncurrent_time: ${timestamp}\n</turn_context>\n${text}`,
70
+ },
71
+ ],
72
+ });
73
+
74
+ const assistantWithWebSearch = (): Message => ({
75
+ role: "assistant",
76
+ content: [
77
+ {
78
+ type: "server_tool_use",
79
+ id: "stu_1",
80
+ name: "web_search",
81
+ input: { query: "cats" },
82
+ },
83
+ {
84
+ type: "web_search_tool_result",
85
+ tool_use_id: "stu_1",
86
+ content: [
87
+ {
88
+ type: "web_search_result",
89
+ url: "https://cats.example",
90
+ title: "All About Cats",
91
+ encrypted_content: ENCRYPTED_TOKEN,
92
+ },
93
+ ],
94
+ },
95
+ ],
96
+ });
97
+
98
+ const assistantToolUse = (): Message => ({
99
+ role: "assistant",
100
+ content: [
101
+ { type: "tool_use", id: "tu_1", name: "bash", input: { cmd: "ls" } },
102
+ ],
103
+ });
104
+
105
+ const toolResult = (): Message => ({
106
+ role: "user",
107
+ content: [{ type: "tool_result", tool_use_id: "tu_1", content: "ok" }],
108
+ });
109
+
110
+ function serializeBlocks(messages: Message[]): string {
111
+ return JSON.stringify(messages);
112
+ }
113
+
114
+ // Records the exact message list the provider is asked to summarize.
115
+ function recordingProvider(sink: { sent: Message[] }): Provider {
116
+ return {
117
+ name: "mock-provider",
118
+ sendMessage: async (msgs: Message[]) => {
119
+ sink.sent = msgs;
120
+ return {
121
+ content: [{ type: "text", text: compactionResponse }],
122
+ model: "mock-model",
123
+ usage: { inputTokens: 100, outputTokens: 50 },
124
+ stopReason: "end_turn",
125
+ };
126
+ },
127
+ };
128
+ }
129
+
130
+ // Expired Anthropic web-search tokens must never reach the summarization LLM:
131
+ // both compaction provider calls funnel through the same request builder, so
132
+ // each is guarded here.
133
+ describe("compaction summary calls — historical web-search sanitization", () => {
134
+ test("runAssistantDrivenCompaction strips expired encrypted_content from the request while leaving durable history untouched", async () => {
135
+ // GIVEN a history whose older turn contains a native web_search_tool_result
136
+ // block carrying an opaque, expired `encrypted_content` token
137
+ const messages: Message[] = [
138
+ userText("old user turn"),
139
+ assistantWithWebSearch(),
140
+ userTextWithTurnContext("tail anchor message", TAIL_TIMESTAMP),
141
+ assistantWithWebSearch(),
142
+ ];
143
+
144
+ // AND a snapshot of the caller's array so we can assert it is not mutated
145
+ const inputSnapshot = serializeBlocks(messages);
146
+ const sink = { sent: [] as Message[] };
147
+
148
+ // WHEN compaction runs over that history
149
+ const result = await runAssistantDrivenCompaction({
150
+ conversationId: "conv-web-search",
151
+ messages,
152
+ provider: recordingProvider(sink),
153
+ systemPrompt: "system",
154
+ compaction: { enabled: true, autoThreshold: 0.7 },
155
+ maxInputTokens: 1000,
156
+ previousEstimatedInputTokens: 900,
157
+ });
158
+
159
+ // THEN the summary request the provider received carries no expired token
160
+ // and no native web_search blocks, but keeps the search content as text
161
+ const sentSerialized = serializeBlocks(sink.sent);
162
+ expect(sentSerialized).not.toContain(ENCRYPTED_TOKEN);
163
+ expect(sentSerialized).not.toContain("web_search_tool_result");
164
+ expect(sentSerialized).not.toContain("server_tool_use");
165
+ expect(sentSerialized).toContain("All About Cats");
166
+ expect(sentSerialized).toContain("https://cats.example");
167
+
168
+ // AND the caller's durable history array is left byte-for-byte untouched,
169
+ // so persisted messages keep the original rich blocks
170
+ expect(serializeBlocks(messages)).toBe(inputSnapshot);
171
+ expect(result.compacted).toBe(true);
172
+ });
173
+
174
+ test("runEmergencyCompaction strips expired encrypted_content from the prefix request", async () => {
175
+ // GIVEN a mid-turn history whose prefix (before the last tool pair) holds a
176
+ // web_search_tool_result block with an expired token
177
+ const messages: Message[] = [
178
+ userText("old user turn"),
179
+ assistantWithWebSearch(),
180
+ userText("keep going"),
181
+ assistantToolUse(),
182
+ toolResult(),
183
+ ];
184
+
185
+ const inputSnapshot = serializeBlocks(messages);
186
+ const sink = { sent: [] as Message[] };
187
+
188
+ // WHEN emergency compaction splits at the last tool pair and summarizes the
189
+ // prefix
190
+ const result = await runEmergencyCompaction({
191
+ conversationId: "conv-web-search-emergency",
192
+ messages,
193
+ provider: recordingProvider(sink),
194
+ systemPrompt: "system",
195
+ compaction: { enabled: true, autoThreshold: 0.7 },
196
+ // Large budget so the prefix is not front-truncated — this test exercises
197
+ // the web-search sanitizer, not the truncation fallback.
198
+ maxInputTokens: 100000,
199
+ previousEstimatedInputTokens: 90000,
200
+ });
201
+
202
+ // THEN the prefix sent to the provider carries no expired token
203
+ const sentSerialized = serializeBlocks(sink.sent);
204
+ expect(sentSerialized).not.toContain(ENCRYPTED_TOKEN);
205
+ expect(sentSerialized).not.toContain("web_search_tool_result");
206
+ expect(sentSerialized).not.toContain("server_tool_use");
207
+ expect(sentSerialized).toContain("All About Cats");
208
+
209
+ // AND the caller's durable history is untouched
210
+ expect(serializeBlocks(messages)).toBe(inputSnapshot);
211
+ expect(result.compacted).toBe(true);
212
+ });
213
+ });