@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,456 @@
1
+ import { afterEach, describe, expect, test } from "bun:test";
2
+
3
+ import { setOverridesForTesting } from "../../../../__tests__/feature-flag-test-helpers.js";
4
+ import type { AssistantConfig } from "../../../../config/types.js";
5
+ import type { MemoryJob } from "../../../../memory/jobs-store.js";
6
+ import { renderCapabilityContent } from "../capabilities.js";
7
+ import {
8
+ backfillAllSections,
9
+ type BackfillJobDeps,
10
+ type ChangedPageCandidate,
11
+ computeChangedPages,
12
+ maintainJob,
13
+ type MaintainJobDeps,
14
+ } from "../maintain-job.js";
15
+ import { buildSectionIndex } from "../sections.js";
16
+ import type { Section, SectionIndex, Slug } from "../types.js";
17
+
18
+ const FLAG_SHADOW = "memory-v3-shadow";
19
+ const FLAG_LIVE = "memory-v3-live";
20
+
21
+ // The flag resolver ignores the passed config and reads the override cache; the
22
+ // config arg only satisfies the signature. Flags are driven via
23
+ // `setOverridesForTesting` below.
24
+ const CONFIG = {} as AssistantConfig;
25
+
26
+ function makeSection(article: Slug, ordinal: number): Section {
27
+ return {
28
+ article,
29
+ title: "",
30
+ text: `text for ${article}#${ordinal}`,
31
+ ordinal,
32
+ };
33
+ }
34
+
35
+ function makeIndex(slugs: Slug[]): SectionIndex {
36
+ const sections: Section[] = slugs.map((slug) => makeSection(slug, 0));
37
+ const byArticle = new Map<Slug, number[]>();
38
+ sections.forEach((s, i) => byArticle.set(s.article, [i]));
39
+ return { sections, byArticle };
40
+ }
41
+
42
+ const JOB = { id: "job-1", type: "memory_v3_maintain" } as unknown as MemoryJob;
43
+
44
+ describe("maintainJob", () => {
45
+ afterEach(() => {
46
+ setOverridesForTesting({});
47
+ });
48
+
49
+ function deps(overrides: Partial<MaintainJobDeps> = {}): {
50
+ deps: MaintainJobDeps;
51
+ calls: {
52
+ built: Slug[][];
53
+ deleted: string[];
54
+ upserted: Section[][];
55
+ invalidate: number;
56
+ commit: number;
57
+ };
58
+ } {
59
+ const calls = {
60
+ built: [] as Slug[][],
61
+ deleted: [] as string[],
62
+ upserted: [] as Section[][],
63
+ invalidate: 0,
64
+ commit: 0,
65
+ };
66
+ const base: MaintainJobDeps = {
67
+ config: CONFIG,
68
+ selectChangedPages: async () => [],
69
+ buildSectionIndex: async (slugs) => {
70
+ calls.built.push(slugs);
71
+ return makeIndex(slugs);
72
+ },
73
+ readPageBody: async (slug) => `body for ${slug}`,
74
+ deleteSectionsForArticle: async (_config, article) => {
75
+ calls.deleted.push(article);
76
+ },
77
+ upsertSections: async (_config, sections) => {
78
+ calls.upserted.push(sections);
79
+ },
80
+ commitEmbedHighWater: () => {
81
+ calls.commit += 1;
82
+ },
83
+ // Prune stage off by default: no stored articles ⇒ nothing to prune. The
84
+ // dedicated prune tests below override both collaborators.
85
+ listSectionArticles: async () => [],
86
+ listIndexedSlugs: async () => [],
87
+ invalidateLanes: () => {
88
+ calls.invalidate += 1;
89
+ },
90
+ };
91
+ return { deps: { ...base, ...overrides }, calls };
92
+ }
93
+
94
+ test("no-op when both v3 flags are off", async () => {
95
+ setOverridesForTesting({ [FLAG_SHADOW]: false, [FLAG_LIVE]: false });
96
+ const { deps: d, calls } = deps({
97
+ selectChangedPages: async () => ["page-a"],
98
+ });
99
+ const outcome = await maintainJob(JOB, CONFIG, d);
100
+ expect(outcome.disabled).toBe(true);
101
+ expect(calls.built.length).toBe(0);
102
+ expect(calls.invalidate).toBe(0);
103
+ });
104
+
105
+ test("re-chunks + re-embeds changed pages and invalidates lanes (shadow on)", async () => {
106
+ setOverridesForTesting({ [FLAG_SHADOW]: true });
107
+ const { deps: d, calls } = deps({
108
+ selectChangedPages: async () => ["page-a", "page-b"],
109
+ });
110
+ const outcome = await maintainJob(JOB, CONFIG, d);
111
+
112
+ expect(outcome.disabled).toBe(false);
113
+ expect(outcome.reembedded).toBe(2);
114
+ expect(outcome.reembedFailures).toBe(0);
115
+ expect(outcome.invalidated).toBe(true);
116
+
117
+ // Each changed page: delete its stale sections, then upsert fresh ones.
118
+ expect(calls.built).toEqual([["page-a"], ["page-b"]]);
119
+ expect(calls.deleted).toEqual(["page-a", "page-b"]);
120
+ expect(calls.upserted.flat().map((s) => s.article)).toEqual([
121
+ "page-a",
122
+ "page-b",
123
+ ]);
124
+ expect(calls.invalidate).toBe(1);
125
+ expect(calls.commit).toBe(1);
126
+ });
127
+
128
+ test("runs when only the live flag is on", async () => {
129
+ setOverridesForTesting({ [FLAG_LIVE]: true });
130
+ const { deps: d, calls } = deps({
131
+ selectChangedPages: async () => ["page-a"],
132
+ });
133
+ await maintainJob(JOB, CONFIG, d);
134
+ expect(calls.built).toEqual([["page-a"]]);
135
+ expect(calls.invalidate).toBe(1);
136
+ });
137
+
138
+ test("skips the dense store entirely when no pages changed", async () => {
139
+ setOverridesForTesting({ [FLAG_SHADOW]: true });
140
+ const { deps: d, calls } = deps({ selectChangedPages: async () => [] });
141
+ const outcome = await maintainJob(JOB, CONFIG, d);
142
+ expect(outcome.reembedded).toBe(0);
143
+ expect(calls.built.length).toBe(0);
144
+ expect(calls.deleted.length).toBe(0);
145
+ expect(calls.upserted.length).toBe(0);
146
+ // The pass still advances the high-water mark and invalidates lanes.
147
+ expect(calls.commit).toBe(1);
148
+ expect(calls.invalidate).toBe(1);
149
+ });
150
+
151
+ test("a single failing page is contained; other pages still re-embed", async () => {
152
+ setOverridesForTesting({ [FLAG_SHADOW]: true });
153
+ const { deps: d, calls } = deps({
154
+ selectChangedPages: async () => ["page-ok", "page-bad", "page-ok-2"],
155
+ upsertSections: async (_config, sections) => {
156
+ if (sections.some((s) => s.article === "page-bad")) {
157
+ throw new Error("embed boom");
158
+ }
159
+ calls.upserted.push(sections);
160
+ },
161
+ });
162
+ const outcome = await maintainJob(JOB, CONFIG, d);
163
+
164
+ expect(outcome.reembedded).toBe(2);
165
+ expect(outcome.reembedFailures).toBe(1);
166
+ // Both good pages were upserted; the lanes were still invalidated.
167
+ expect(calls.upserted.flat().map((s) => s.article)).toEqual([
168
+ "page-ok",
169
+ "page-ok-2",
170
+ ]);
171
+ expect(calls.invalidate).toBe(1);
172
+ expect(outcome.invalidated).toBe(true);
173
+ // The checkpoint is HELD when any page failed: the failed page was
174
+ // delete-then-upsert'd (so its sections are gone), and advancing past its
175
+ // mtime would hide it from `computeChangedPages` forever. Holding the mark
176
+ // lets the next pass re-select and re-embed it.
177
+ expect(calls.commit).toBe(0);
178
+ });
179
+
180
+ test("a thrown re-embed stage does not abort lane invalidation", async () => {
181
+ setOverridesForTesting({ [FLAG_SHADOW]: true });
182
+ const { deps: d, calls } = deps({
183
+ selectChangedPages: async () => {
184
+ throw new Error("select boom");
185
+ },
186
+ });
187
+ const outcome = await maintainJob(JOB, CONFIG, d);
188
+ expect(outcome.failures).toContain("reembed");
189
+ expect(calls.commit).toBe(0);
190
+ expect(calls.invalidate).toBe(1);
191
+ expect(outcome.invalidated).toBe(true);
192
+ });
193
+
194
+ test("advances the high-water mark after a successful re-embed pass", async () => {
195
+ setOverridesForTesting({ [FLAG_SHADOW]: true });
196
+ const { deps: d, calls } = deps({
197
+ selectChangedPages: async () => ["page-a"],
198
+ });
199
+ await maintainJob(JOB, CONFIG, d);
200
+ expect(calls.commit).toBe(1);
201
+ });
202
+
203
+ test("does not advance the high-water mark when selection throws", async () => {
204
+ setOverridesForTesting({ [FLAG_SHADOW]: true });
205
+ const { deps: d, calls } = deps({
206
+ selectChangedPages: async () => {
207
+ throw new Error("select boom");
208
+ },
209
+ });
210
+ const outcome = await maintainJob(JOB, CONFIG, d);
211
+ expect(outcome.failures).toContain("reembed");
212
+ expect(calls.commit).toBe(0);
213
+ });
214
+
215
+ test("prunes sections for an article absent from the page index", async () => {
216
+ setOverridesForTesting({ [FLAG_SHADOW]: true });
217
+ // The dense store holds points for a live page, a deleted page, and a
218
+ // synthetic capability row; the page index holds only the live + synthetic
219
+ // slugs. Only the deleted page's sections are pruned. `selectChangedPages`
220
+ // stays empty so `calls.deleted` reflects prune deletes only.
221
+ const { deps: d, calls } = deps({
222
+ listSectionArticles: async () => [
223
+ "page-live",
224
+ "page-deleted",
225
+ "skills/example",
226
+ ],
227
+ listIndexedSlugs: async () => ["page-live", "skills/example"],
228
+ });
229
+ const outcome = await maintainJob(JOB, CONFIG, d);
230
+
231
+ expect(outcome.pruned).toBe(1);
232
+ expect(outcome.pruneFailures).toBe(0);
233
+ // The live page and the synthetic capability row are NOT pruned.
234
+ expect(calls.deleted).toEqual(["page-deleted"]);
235
+ // The pass still invalidates the lanes.
236
+ expect(calls.invalidate).toBe(1);
237
+ expect(outcome.invalidated).toBe(true);
238
+ });
239
+
240
+ test("prunes nothing when every stored article is still in the index", async () => {
241
+ setOverridesForTesting({ [FLAG_SHADOW]: true });
242
+ const { deps: d, calls } = deps({
243
+ listSectionArticles: async () => ["page-a", "page-b"],
244
+ listIndexedSlugs: async () => ["page-a", "page-b", "page-c"],
245
+ });
246
+ const outcome = await maintainJob(JOB, CONFIG, d);
247
+
248
+ expect(outcome.pruned).toBe(0);
249
+ expect(calls.deleted).toEqual([]);
250
+ });
251
+
252
+ test("a single failing prune delete is contained; other deletions proceed", async () => {
253
+ setOverridesForTesting({ [FLAG_SHADOW]: true });
254
+ const { deps: d, calls } = deps({
255
+ listSectionArticles: async () => ["gone-1", "gone-bad", "gone-2"],
256
+ listIndexedSlugs: async () => [],
257
+ deleteSectionsForArticle: async (_config, article) => {
258
+ if (article === "gone-bad") throw new Error("delete boom");
259
+ calls.deleted.push(article);
260
+ },
261
+ });
262
+ const outcome = await maintainJob(JOB, CONFIG, d);
263
+
264
+ expect(outcome.pruned).toBe(2);
265
+ expect(outcome.pruneFailures).toBe(1);
266
+ // Both good deletions ran; the contained failure did not abort them.
267
+ expect(calls.deleted).toEqual(["gone-1", "gone-2"]);
268
+ // A contained per-article failure is NOT a stage failure, and the lanes
269
+ // were still invalidated.
270
+ expect(outcome.failures).not.toContain("prune");
271
+ expect(calls.invalidate).toBe(1);
272
+ });
273
+
274
+ test("a thrown prune stage is contained and does not abort lane invalidation", async () => {
275
+ setOverridesForTesting({ [FLAG_SHADOW]: true });
276
+ const { deps: d, calls } = deps({
277
+ listSectionArticles: async () => {
278
+ throw new Error("scroll boom");
279
+ },
280
+ });
281
+ const outcome = await maintainJob(JOB, CONFIG, d);
282
+
283
+ expect(outcome.failures).toContain("prune");
284
+ expect(outcome.pruned).toBe(0);
285
+ // Invalidation still runs after a thrown prune stage.
286
+ expect(calls.invalidate).toBe(1);
287
+ expect(outcome.invalidated).toBe(true);
288
+ });
289
+ });
290
+
291
+ describe("computeChangedPages", () => {
292
+ const page = (slug: string, modifiedAt: number): ChangedPageCandidate => ({
293
+ slug,
294
+ modifiedAt,
295
+ });
296
+
297
+ test("first run (null high-water) selects every real page", () => {
298
+ const changed = computeChangedPages(
299
+ [page("page-a", 100), page("page-b", 200)],
300
+ null,
301
+ );
302
+ expect(changed).toEqual(["page-a", "page-b"]);
303
+ });
304
+
305
+ test("selects only pages edited since the high-water", () => {
306
+ const changed = computeChangedPages(
307
+ [page("stale", 150), page("fresh", 300)],
308
+ 200,
309
+ );
310
+ expect(changed).toEqual(["fresh"]);
311
+ });
312
+
313
+ test("skips a page untouched since the high-water (no self-trigger)", () => {
314
+ const changed = computeChangedPages([page("stale", 150)], 200);
315
+ expect(changed).toEqual([]);
316
+ });
317
+
318
+ test("excludes synthetic skill/CLI rows (modifiedAt 0)", () => {
319
+ const changed = computeChangedPages(
320
+ [page("skills/meet-join", 0), page("real", 300)],
321
+ 200,
322
+ );
323
+ expect(changed).toEqual(["real"]);
324
+ });
325
+
326
+ test("excludes synthetic rows even on the first run", () => {
327
+ const changed = computeChangedPages(
328
+ [page("skills/x", 0), page("real", 10)],
329
+ null,
330
+ );
331
+ expect(changed).toEqual(["real"]);
332
+ });
333
+ });
334
+
335
+ describe("backfillAllSections", () => {
336
+ afterEach(() => {
337
+ setOverridesForTesting({});
338
+ });
339
+
340
+ function deps(overrides: Partial<BackfillJobDeps> = {}): {
341
+ deps: BackfillJobDeps;
342
+ calls: {
343
+ ensured: number;
344
+ built: Slug[][];
345
+ deleted: string[];
346
+ upserted: Section[][];
347
+ committed: number[];
348
+ };
349
+ } {
350
+ const calls = {
351
+ ensured: 0,
352
+ built: [] as Slug[][],
353
+ deleted: [] as string[],
354
+ upserted: [] as Section[][],
355
+ committed: [] as number[],
356
+ };
357
+ const base: BackfillJobDeps = {
358
+ config: CONFIG,
359
+ selectAllPages: async () => [],
360
+ ensureSectionCollection: async () => {
361
+ calls.ensured += 1;
362
+ },
363
+ // Real builder + chunker so the synthetic capability content is genuinely
364
+ // turned into section points (not a hand-rolled stub).
365
+ buildSectionIndex: async (slugs, pageBody) => {
366
+ calls.built.push(slugs);
367
+ return buildSectionIndex(slugs, pageBody);
368
+ },
369
+ readPageBody: async (slug) => `body for ${slug}`,
370
+ deleteSectionsForArticle: async (_config, article) => {
371
+ calls.deleted.push(article);
372
+ },
373
+ upsertSections: async (_config, sections) => {
374
+ calls.upserted.push(sections);
375
+ },
376
+ commitEmbedHighWater: (ms) => {
377
+ calls.committed.push(ms);
378
+ },
379
+ nowMs: () => 4242,
380
+ };
381
+ return { deps: { ...base, ...overrides }, calls };
382
+ }
383
+
384
+ test("embeds EVERY page incl a synthetic capability slug, with its capability content", async () => {
385
+ // A capability-aware reader, exactly as the production default deps wire it:
386
+ // the real renderer resolves the synthetic skill row's content (injected
387
+ // resolvers keep it deterministic, mirroring `capabilities.test.ts`).
388
+ const resolvers = {
389
+ skill: (slug: Slug) =>
390
+ slug === "skills/example"
391
+ ? { id: "example", content: "example capability body" }
392
+ : null,
393
+ cli: () => null,
394
+ };
395
+ const readPageBody = async (slug: Slug): Promise<string> =>
396
+ renderCapabilityContent(slug, resolvers) ?? `body for ${slug}`;
397
+
398
+ const { deps: d, calls } = deps({
399
+ selectAllPages: async () => ["page-a", "skills/example"],
400
+ readPageBody,
401
+ });
402
+ const outcome = await backfillAllSections(CONFIG, d);
403
+
404
+ // Both the real page AND the synthetic capability row were embedded.
405
+ expect(outcome.articles).toBe(2);
406
+ expect(outcome.failures).toBe(0);
407
+ expect(calls.ensured).toBe(1);
408
+ expect(calls.built).toEqual([["page-a"], ["skills/example"]]);
409
+ expect(calls.deleted).toEqual(["page-a", "skills/example"]);
410
+
411
+ // The synthetic slug's upsert carries its rendered capability content, not a
412
+ // blank/on-disk read.
413
+ const syntheticSections = calls.upserted
414
+ .flat()
415
+ .filter((s) => s.article === "skills/example");
416
+ expect(syntheticSections.length).toBeGreaterThan(0);
417
+ expect(syntheticSections.map((s) => s.text).join("\n")).toContain(
418
+ "example capability body",
419
+ );
420
+ expect(outcome.sections).toBe(calls.upserted.flat().length);
421
+ });
422
+
423
+ test("advances the high-water checkpoint to the injected now", async () => {
424
+ const { deps: d, calls } = deps({
425
+ selectAllPages: async () => ["page-a"],
426
+ nowMs: () => 99999,
427
+ });
428
+ await backfillAllSections(CONFIG, d);
429
+ expect(calls.committed).toEqual([99999]);
430
+ });
431
+
432
+ test("contains a single failing page; others still embed but checkpoint is HELD", async () => {
433
+ const { deps: d, calls } = deps({
434
+ selectAllPages: async () => ["page-ok", "page-bad", "page-ok-2"],
435
+ upsertSections: async (_config, sections) => {
436
+ if (sections.some((s) => s.article === "page-bad")) {
437
+ throw new Error("embed boom");
438
+ }
439
+ calls.upserted.push(sections);
440
+ },
441
+ });
442
+ const outcome = await backfillAllSections(CONFIG, d);
443
+
444
+ expect(outcome.articles).toBe(2);
445
+ expect(outcome.failures).toBe(1);
446
+ expect(calls.upserted.flat().map((s) => s.article)).toEqual([
447
+ "page-ok",
448
+ "page-ok-2",
449
+ ]);
450
+ // The checkpoint is HELD on any failure: the failed page was
451
+ // delete-then-upsert'd (sections gone), so advancing past its mtime would
452
+ // hide it from the incremental selector forever. Holding the mark lets the
453
+ // next pass re-embed it.
454
+ expect(calls.committed).toEqual([]);
455
+ });
456
+ });