@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
@@ -1,432 +0,0 @@
1
- /**
2
- * Tests for `assistant/src/memory/v3/shadow-plugin.ts`.
3
- *
4
- * The v3 plugin is flag-gated. These tests assert:
5
- * - both flags OFF → orchestrate is never called and no DB rows are written;
6
- * - either flag ON → orchestrate runs and selection rows land in
7
- * `memory_v3_selections`;
8
- * - shadow-only (live off) → the injector returns `null` (never mutates the
9
- * turn) but still logs;
10
- * - live on → the injector returns the rendered `<memory>` block;
11
- * - an empty selection under live → `null`;
12
- * - lazy-init runs the loaders only once across multiple turns.
13
- *
14
- * All heavy dependencies (config, flag resolver, conversation reads, v2 page
15
- * index + page store, v3 loaders, orchestrate) are mocked BEFORE importing the
16
- * plugin so the module observes them at load time. A real in-memory SQLite DB
17
- * backs the write assertions via the `memory_v3_selections` migration.
18
- */
19
-
20
- import { Database } from "bun:sqlite";
21
- import { afterAll, beforeEach, describe, expect, mock, test } from "bun:test";
22
-
23
- import { drizzle } from "drizzle-orm/bun-sqlite";
24
-
25
- import { migrateAddMemoryV3Selections } from "../../migrations/268-add-memory-v3-selections.js";
26
- import * as schema from "../../schema.js";
27
- import type { LeafTree, SelectionSource } from "../types.js";
28
-
29
- // `mock.module` is process-global and, in Bun, neither `mock.restore()` nor a
30
- // re-mock in `afterAll` reverts it for files that load LATER in the same
31
- // `bun test src/memory/v3/` run. Sibling files (orchestrate.test.ts,
32
- // tree.test.ts) import these same modules for real, so an unconditional stub
33
- // here would leak in and break them. Instead each stub below DELEGATES to the
34
- // real implementation unless this test is actively running (`shadowMockActive`,
35
- // toggled in beforeEach/afterAll).
36
- //
37
- // Snapshot the real exports into plain objects NOW: a module namespace object
38
- // is a live view, so reading `realTree.loadLeafTree` *after* the stub is
39
- // installed would resolve back to the stub (infinite recursion).
40
- const realTree = { ...(await import("../tree.js")) };
41
- const realCore = { ...(await import("../core.js")) };
42
- const realNeedle = { ...(await import("../needle.js")) };
43
- const realOrchestrate = { ...(await import("../orchestrate.js")) };
44
- const realPlatform = { ...(await import("../../../util/platform.js")) };
45
- const realPageStore = { ...(await import("../../v2/page-store.js")) };
46
- const realConversationCrud = {
47
- ...(await import("../../conversation-crud.js")),
48
- };
49
-
50
- let shadowMockActive = false;
51
-
52
- // ─── mutable test state, read by the mocks below ────────────────────────────
53
-
54
- // Per-flag toggles. The plugin reads `memory-v3-live` and `memory-v3-shadow`
55
- // independently, so the mock resolves by flag key rather than a single boolean.
56
- let liveEnabled = false;
57
- let shadowEnabled = false;
58
- let messages: Array<{ role: string; content: string }> = [];
59
- const orchestrateSpy = mock(async () => ({
60
- openedLeaves: [],
61
- currentSelections: [
62
- { slug: "domain-a/page-1", pinned: true },
63
- { slug: "domain-b/page-2", pinned: false },
64
- ],
65
- workingSetUnion: new Set<string>(["domain-a/page-1", "domain-b/page-2"]),
66
- finalInjection: ["domain-a/page-1", "domain-b/page-2", "domain-a/carried"],
67
- }));
68
- let treeLoads = 0;
69
- let coreLoads = 0;
70
- let needleBuilds = 0;
71
- let configL2Concurrency = 16;
72
-
73
- // Shared in-memory DB so writes are observable from the test. We hold the raw
74
- // sqlite handle alongside the drizzle wrapper so the test can both read rows
75
- // directly and feed the same handle through the mocked `getSqliteFrom`.
76
- let testSqlite: Database;
77
- let testDb = makeDb();
78
- function makeDb() {
79
- testSqlite = new Database(":memory:");
80
- testSqlite.exec("PRAGMA journal_mode=WAL");
81
- const db = drizzle(testSqlite, { schema });
82
- migrateAddMemoryV3Selections(db);
83
- return db;
84
- }
85
-
86
- // A tree where `domain-a/*` pages are owned by a core leaf and `domain-b/*`
87
- // are not, so attribution maps to core+l2 / l1+l2 respectively.
88
- const FAKE_TREE = {
89
- leaves: new Map([
90
- ["domain-a", { members: ["domain-a/page-1", "domain-a/carried"] }],
91
- ["domain-b", { members: ["domain-b/page-2"] }],
92
- ]),
93
- byPage: new Map(),
94
- } as unknown as LeafTree;
95
-
96
- // ─── module mocks (installed before the plugin import) ──────────────────────
97
-
98
- mock.module("../../../config/assistant-feature-flags.js", () => ({
99
- isAssistantFeatureFlagEnabled: (key: string) =>
100
- key === "memory-v3-live"
101
- ? liveEnabled
102
- : key === "memory-v3-shadow"
103
- ? shadowEnabled
104
- : false,
105
- }));
106
-
107
- mock.module("../../../config/loader.js", () => ({
108
- getConfig: () => ({
109
- memory: {
110
- v3: {
111
- workingSet: { maxPages: 150, evictWindow: 5 },
112
- l2Concurrency: configL2Concurrency,
113
- },
114
- },
115
- }),
116
- }));
117
-
118
- // Spread the real module so every export the live path transitively imports
119
- // (e.g. `getMessageById` via page-content.ts) stays present — replacing the
120
- // whole module with a bare `{ getMessages }` made those consumers fail to load.
121
- // Only `getMessages` is overridden, since that's the one the plugin reads.
122
- mock.module("../../conversation-crud.js", () => ({
123
- ...realConversationCrud,
124
- getMessages: () => messages.map((m, i) => ({ ...m, id: `m${i}` })),
125
- }));
126
-
127
- mock.module("../../db-connection.js", () => ({
128
- getDb: () => testDb,
129
- getSqliteFrom: () => testSqlite,
130
- }));
131
-
132
- mock.module("../v2/page-index.js", () => ({
133
- getPageIndex: async () => ({ bySlug: new Map() }),
134
- }));
135
-
136
- // `pageContent` (live mode) reads the full page via `readPage`/`renderPageContent`.
137
- // Stub them to return a deterministic body per slug so the rendered `<memory>`
138
- // block is assertable without touching the filesystem.
139
- mock.module("../../v2/page-store.js", () => ({
140
- ...realPageStore,
141
- readPage: async (workspaceDir: string, slug: string) =>
142
- shadowMockActive
143
- ? { slug, frontmatter: {}, body: `body for ${slug}` }
144
- : realPageStore.readPage(workspaceDir, slug),
145
- renderPageContent: (page: { slug: string; body: string }) =>
146
- shadowMockActive
147
- ? page.body
148
- : realPageStore.renderPageContent(
149
- page as Parameters<typeof realPageStore.renderPageContent>[0],
150
- ),
151
- }));
152
-
153
- mock.module("../../../util/platform.js", () => ({
154
- ...realPlatform,
155
- getWorkspaceDir: () =>
156
- shadowMockActive
157
- ? "/tmp/shadow-test-workspace"
158
- : realPlatform.getWorkspaceDir(),
159
- }));
160
-
161
- mock.module("../tree.js", () => ({
162
- ...realTree,
163
- resolveDataDir: () =>
164
- shadowMockActive ? "/tmp/shadow-test-data" : realTree.resolveDataDir(),
165
- loadLeafTree: async (dataDir: string) => {
166
- if (!shadowMockActive) return realTree.loadLeafTree(dataDir);
167
- treeLoads++;
168
- return FAKE_TREE;
169
- },
170
- membersOf: (tree: LeafTree, leaf: string) =>
171
- shadowMockActive
172
- ? ((tree.leaves.get(leaf) as unknown as { members?: string[] })
173
- ?.members ?? [])
174
- : realTree.membersOf(tree, leaf),
175
- }));
176
-
177
- mock.module("../core.js", () => ({
178
- ...realCore,
179
- loadCore: async (dataDir: string) => {
180
- if (!shadowMockActive) return realCore.loadCore(dataDir);
181
- coreLoads++;
182
- return new Set(["domain-a"]);
183
- },
184
- }));
185
-
186
- mock.module("../needle.js", () => ({
187
- ...realNeedle,
188
- buildNeedleIndex: async (
189
- ...args: Parameters<typeof realNeedle.buildNeedleIndex>
190
- ) => {
191
- if (!shadowMockActive) return realNeedle.buildNeedleIndex(...args);
192
- needleBuilds++;
193
- return { query: () => [] } as unknown as Awaited<
194
- ReturnType<typeof realNeedle.buildNeedleIndex>
195
- >;
196
- },
197
- }));
198
-
199
- mock.module("../orchestrate.js", () => ({
200
- ...realOrchestrate,
201
- orchestrate: (...args: Parameters<typeof realOrchestrate.orchestrate>) =>
202
- shadowMockActive
203
- ? orchestrateSpy(...(args as unknown as []))
204
- : realOrchestrate.orchestrate(...args),
205
- }));
206
-
207
- // Import AFTER mocks so the plugin binds to them.
208
- const {
209
- runShadowObservation,
210
- resetShadowLanesForTests,
211
- invalidateLanes,
212
- memoryV3Injector,
213
- } = await import("../shadow-plugin.js");
214
-
215
- // The module stubs above stay installed for the rest of the process (Bun can't
216
- // reliably uninstall them), but `shadowMockActive` gates their fake behavior to
217
- // this file's tests only, so later-loaded sibling files see real behavior.
218
- afterAll(() => {
219
- shadowMockActive = false;
220
- });
221
-
222
- function readRows() {
223
- return testSqlite
224
- .query(
225
- `SELECT slug, source, pinned FROM memory_v3_selections ORDER BY slug`,
226
- )
227
- .all() as Array<{ slug: string; source: SelectionSource; pinned: number }>;
228
- }
229
-
230
- beforeEach(() => {
231
- shadowMockActive = true;
232
- liveEnabled = false;
233
- shadowEnabled = false;
234
- messages = [
235
- {
236
- role: "user",
237
- content: JSON.stringify([{ type: "text", text: "hello world" }]),
238
- },
239
- ];
240
- orchestrateSpy.mockClear();
241
- treeLoads = 0;
242
- coreLoads = 0;
243
- needleBuilds = 0;
244
- configL2Concurrency = 16;
245
- testDb = makeDb();
246
- resetShadowLanesForTests();
247
- });
248
-
249
- /** Invoke the memory-v3 injector's `produce()` for a turn. */
250
- function produce(conversationId: string, turnIndex: number) {
251
- return memoryV3Injector.produce({
252
- requestId: "r1",
253
- conversationId,
254
- turnIndex,
255
- trust: {} as never,
256
- });
257
- }
258
-
259
- describe("memory-v3 shadow plugin", () => {
260
- test("shadow flag OFF → orchestrate not called, no DB writes", async () => {
261
- shadowEnabled = false;
262
- await runShadowObservation("conv-1", 0);
263
- expect(orchestrateSpy).not.toHaveBeenCalled();
264
- expect(treeLoads).toBe(0);
265
- expect(readRows()).toHaveLength(0);
266
- });
267
-
268
- test("shadow flag ON → orchestrate runs and rows are written", async () => {
269
- shadowEnabled = true;
270
- await runShadowObservation("conv-1", 2);
271
-
272
- expect(orchestrateSpy).toHaveBeenCalledTimes(1);
273
- const rows = readRows();
274
- expect(rows).toEqual([
275
- { slug: "domain-a/carried", source: "carry-forward", pinned: 0 },
276
- // page-1 belongs to the core leaf `domain-a` → core+l2, pinned.
277
- { slug: "domain-a/page-1", source: "core+l2", pinned: 1 },
278
- // page-2 belongs to non-core `domain-b` → l1+l2.
279
- { slug: "domain-b/page-2", source: "l1+l2", pinned: 0 },
280
- ]);
281
- });
282
-
283
- test("the turn passed to orchestrate carries the latest user message", async () => {
284
- shadowEnabled = true;
285
- await runShadowObservation("conv-1", 0);
286
- const turn = (
287
- orchestrateSpy.mock.calls as unknown as unknown[][]
288
- )[0]![0] as {
289
- conversationId: string;
290
- turnNumber: number;
291
- currentMessage: string;
292
- };
293
- expect(turn.conversationId).toBe("conv-1");
294
- expect(turn.turnNumber).toBe(0);
295
- expect(turn.currentMessage).toBe("hello world");
296
- });
297
-
298
- test("orchestrate receives the configured L2 concurrency", async () => {
299
- shadowEnabled = true;
300
- configL2Concurrency = 9;
301
- await runShadowObservation("conv-1", 0);
302
- const deps = (
303
- orchestrateSpy.mock.calls as unknown as unknown[][]
304
- )[0]![1] as {
305
- l2Concurrency?: number;
306
- };
307
- expect(deps.l2Concurrency).toBe(9);
308
- });
309
-
310
- test("both flags OFF → produce returns null, no orchestrate, no writes", async () => {
311
- liveEnabled = false;
312
- shadowEnabled = false;
313
- const block = await produce("conv-1", 0);
314
- expect(block).toBeNull();
315
- expect(orchestrateSpy).not.toHaveBeenCalled();
316
- expect(readRows()).toHaveLength(0);
317
- });
318
-
319
- test("shadow-only (live off) → produce returns null but still logs", async () => {
320
- liveEnabled = false;
321
- shadowEnabled = true;
322
- const block = await produce("conv-1", 0);
323
- expect(block).toBeNull();
324
- expect(orchestrateSpy).toHaveBeenCalledTimes(1);
325
- expect(readRows().length).toBeGreaterThan(0);
326
- });
327
-
328
- test("live on → produce returns the rendered <memory> block and logs", async () => {
329
- liveEnabled = true;
330
- shadowEnabled = false;
331
- const block = await produce("conv-1", 0);
332
- expect(block).not.toBeNull();
333
- expect(block!.placement).toBe("after-memory-prefix");
334
- expect(block!.text.startsWith("<memory>\n")).toBe(true);
335
- expect(block!.text.endsWith("\n</memory>")).toBe(true);
336
- // finalInjection slugs are rendered into the block in order.
337
- expect(block!.text).toContain("body for domain-a/page-1");
338
- expect(block!.text).toContain("body for domain-a/carried");
339
- // Selections are still logged in live mode.
340
- expect(readRows().length).toBeGreaterThan(0);
341
- });
342
-
343
- test("live on but empty selection → produce returns null", async () => {
344
- liveEnabled = true;
345
- shadowEnabled = false;
346
- orchestrateSpy.mockImplementationOnce(async () => ({
347
- openedLeaves: [],
348
- currentSelections: [],
349
- workingSetUnion: new Set<string>(),
350
- finalInjection: [],
351
- }));
352
- const block = await produce("conv-1", 0);
353
- expect(block).toBeNull();
354
- // Orchestration still ran (and logged nothing, since there were no rows).
355
- expect(orchestrateSpy).toHaveBeenCalledTimes(1);
356
- expect(readRows()).toHaveLength(0);
357
- });
358
-
359
- test("lazy-init runs the loaders only once across turns", async () => {
360
- shadowEnabled = true;
361
- await runShadowObservation("conv-1", 0);
362
- await runShadowObservation("conv-1", 1);
363
- await runShadowObservation("conv-1", 2);
364
- expect(treeLoads).toBe(1);
365
- expect(coreLoads).toBe(1);
366
- expect(needleBuilds).toBe(1);
367
- expect(orchestrateSpy).toHaveBeenCalledTimes(3);
368
- });
369
-
370
- test("invalidateLanes forces a one-time rebuild on the next turn", async () => {
371
- shadowEnabled = true;
372
- await runShadowObservation("conv-1", 0);
373
- await runShadowObservation("conv-1", 1);
374
- // One build so far; invalidation drops it.
375
- expect(treeLoads).toBe(1);
376
- expect(needleBuilds).toBe(1);
377
-
378
- invalidateLanes();
379
-
380
- // The next turn rebuilds (fresh tree load + fresh needle index)...
381
- await runShadowObservation("conv-1", 2);
382
- expect(treeLoads).toBe(2);
383
- expect(coreLoads).toBe(2);
384
- expect(needleBuilds).toBe(2);
385
-
386
- // ...and the rebuild is memoized again — no further builds until the next
387
- // invalidation.
388
- await runShadowObservation("conv-1", 3);
389
- expect(treeLoads).toBe(2);
390
- expect(needleBuilds).toBe(2);
391
- });
392
-
393
- test("resetShadowLanesForTests invalidates like invalidateLanes", async () => {
394
- shadowEnabled = true;
395
- await runShadowObservation("conv-1", 0);
396
- expect(treeLoads).toBe(1);
397
-
398
- resetShadowLanesForTests();
399
-
400
- await runShadowObservation("conv-1", 1);
401
- expect(treeLoads).toBe(2);
402
- });
403
-
404
- test("concurrent first turns after invalidation share a single build", async () => {
405
- shadowEnabled = true;
406
- await runShadowObservation("conv-1", 0);
407
- expect(treeLoads).toBe(1);
408
-
409
- invalidateLanes();
410
-
411
- // Both turns race the rebuild; memoization must collapse them to one build.
412
- await Promise.all([
413
- runShadowObservation("conv-1", 1),
414
- runShadowObservation("conv-1", 2),
415
- ]);
416
- expect(treeLoads).toBe(2);
417
- expect(needleBuilds).toBe(2);
418
- });
419
-
420
- test("no user message → no orchestrate, no writes", async () => {
421
- shadowEnabled = true;
422
- messages = [
423
- {
424
- role: "assistant",
425
- content: JSON.stringify([{ type: "text", text: "hi" }]),
426
- },
427
- ];
428
- await runShadowObservation("conv-1", 0);
429
- expect(orchestrateSpy).not.toHaveBeenCalled();
430
- expect(readRows()).toHaveLength(0);
431
- });
432
- });
@@ -1,168 +0,0 @@
1
- import { promises as fs } from "node:fs";
2
- import os from "node:os";
3
- import path from "node:path";
4
- import { afterEach, beforeEach, describe, expect, test } from "bun:test";
5
-
6
- import { restoreDataDir, snapshotDataDir } from "../snapshot.js";
7
-
8
- let tmpRoot: string;
9
- let dataDir: string;
10
-
11
- async function readJson(file: string): Promise<unknown> {
12
- return JSON.parse(await fs.readFile(file, "utf8")) as unknown;
13
- }
14
-
15
- async function listLeaves(dir: string): Promise<Record<string, string>> {
16
- const leavesDir = path.join(dir, "leaves");
17
- const out: Record<string, string> = {};
18
- async function walk(current: string, prefix: string): Promise<void> {
19
- let entries: import("node:fs").Dirent[];
20
- try {
21
- entries = await fs.readdir(current, { withFileTypes: true });
22
- } catch {
23
- return;
24
- }
25
- for (const entry of entries) {
26
- const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
27
- const abs = path.join(current, entry.name);
28
- if (entry.isDirectory()) {
29
- await walk(abs, rel);
30
- } else if (entry.isFile()) {
31
- out[rel] = await fs.readFile(abs, "utf8");
32
- }
33
- }
34
- }
35
- await walk(leavesDir, "");
36
- return out;
37
- }
38
-
39
- async function seedDataDir(): Promise<void> {
40
- await fs.mkdir(path.join(dataDir, "leaves", "alpha"), { recursive: true });
41
- await fs.writeFile(
42
- path.join(dataDir, "leaves", "alpha", "leaf-1.md"),
43
- "leaf one body",
44
- );
45
- await fs.writeFile(
46
- path.join(dataDir, "leaves", "leaf-2.md"),
47
- "leaf two body",
48
- );
49
- await fs.writeFile(
50
- path.join(dataDir, "assignments.json"),
51
- JSON.stringify({ "leaf-1": ["topic-a"] }),
52
- );
53
- await fs.writeFile(
54
- path.join(dataDir, "core.json"),
55
- JSON.stringify({ version: 1, root: "topic-a" }),
56
- );
57
- }
58
-
59
- beforeEach(async () => {
60
- tmpRoot = await fs.mkdtemp(path.join(os.tmpdir(), "v3-snapshot-"));
61
- dataDir = path.join(tmpRoot, "data");
62
- await fs.mkdir(dataDir, { recursive: true });
63
- });
64
-
65
- afterEach(async () => {
66
- await fs.rm(tmpRoot, { recursive: true, force: true });
67
- });
68
-
69
- describe("snapshotDataDir / restoreDataDir", () => {
70
- test("roundtrips leaves, assignments, and core", async () => {
71
- await seedDataDir();
72
- const before = {
73
- leaves: await listLeaves(dataDir),
74
- assignments: await readJson(path.join(dataDir, "assignments.json")),
75
- core: await readJson(path.join(dataDir, "core.json")),
76
- };
77
-
78
- const snapshotPath = await snapshotDataDir(dataDir, { label: "snap-1" });
79
-
80
- // Mutate the live data dir after snapshotting.
81
- await fs.writeFile(
82
- path.join(dataDir, "leaves", "alpha", "leaf-1.md"),
83
- "CORRUPTED",
84
- );
85
- await fs.rm(path.join(dataDir, "leaves", "leaf-2.md"));
86
- await fs.writeFile(
87
- path.join(dataDir, "assignments.json"),
88
- JSON.stringify({ broken: true }),
89
- );
90
- await fs.writeFile(
91
- path.join(dataDir, "core.json"),
92
- JSON.stringify({ broken: true }),
93
- );
94
-
95
- await restoreDataDir(snapshotPath, dataDir);
96
-
97
- expect(await listLeaves(dataDir)).toEqual(before.leaves);
98
- expect(await readJson(path.join(dataDir, "assignments.json"))).toEqual(
99
- before.assignments,
100
- );
101
- expect(await readJson(path.join(dataDir, "core.json"))).toEqual(
102
- before.core,
103
- );
104
- });
105
-
106
- test("roundtrips the pageRefs diff", async () => {
107
- await seedDataDir();
108
- const pageRefs = new Map<string, string[]>([
109
- ["slug-one", ["leaf-1", "leaf-2"]],
110
- ["slug-two", ["leaf-3"]],
111
- ]);
112
-
113
- const snapshotPath = await snapshotDataDir(dataDir, {
114
- label: "snap-refs",
115
- pageRefs,
116
- });
117
-
118
- const { pageRefs: restored } = await restoreDataDir(snapshotPath, dataDir);
119
- expect(restored).toBeDefined();
120
- expect(restored).toEqual(pageRefs);
121
- });
122
-
123
- test("returns no pageRefs when none were captured", async () => {
124
- await seedDataDir();
125
- const snapshotPath = await snapshotDataDir(dataDir, { label: "snap-none" });
126
- const result = await restoreDataDir(snapshotPath, dataDir);
127
- expect(result.pageRefs).toBeUndefined();
128
- });
129
-
130
- test("restoring removes leaves added after the snapshot", async () => {
131
- await seedDataDir();
132
- const snapshotPath = await snapshotDataDir(dataDir, { label: "snap-add" });
133
-
134
- await fs.writeFile(
135
- path.join(dataDir, "leaves", "leaf-extra.md"),
136
- "added later",
137
- );
138
-
139
- await restoreDataDir(snapshotPath, dataDir);
140
- const leaves = await listLeaves(dataDir);
141
- expect(leaves["leaf-extra.md"]).toBeUndefined();
142
- expect(leaves["leaf-2.md"]).toBe("leaf two body");
143
- });
144
-
145
- test("retention is bounded to the last N snapshots", async () => {
146
- await seedDataDir();
147
- // Zero-padded labels sort chronologically.
148
- const labels = Array.from(
149
- { length: 8 },
150
- (_, i) => `snap-${String(i).padStart(2, "0")}`,
151
- );
152
- for (const label of labels) {
153
- await snapshotDataDir(dataDir, { label });
154
- }
155
-
156
- const snapshotsRoot = path.join(tmpRoot, "v3-snapshots");
157
- const remaining = (await fs.readdir(snapshotsRoot)).sort();
158
- expect(remaining.length).toBe(5);
159
- // Oldest three pruned, newest five kept.
160
- expect(remaining).toEqual([
161
- "snap-03",
162
- "snap-04",
163
- "snap-05",
164
- "snap-06",
165
- "snap-07",
166
- ]);
167
- });
168
- });