@vellumai/assistant 0.8.10 → 0.8.11-staging.1

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 (400) hide show
  1. package/bun.lock +62 -1
  2. package/docs/workspace-tools.md +196 -0
  3. package/examples/plugins/echo/README.md +3 -3
  4. package/knip.json +1 -0
  5. package/openapi.yaml +460 -128
  6. package/package.json +2 -1
  7. package/scripts/build-plugin-api.ts +299 -0
  8. package/src/__tests__/agent-loop-callsite-precedence.test.ts +7 -0
  9. package/src/__tests__/agent-loop-compaction-events.test.ts +197 -0
  10. package/src/__tests__/agent-loop-exit-reason.test.ts +93 -96
  11. package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +2 -0
  12. package/src/__tests__/agent-loop-output-hooks.test.ts +274 -1
  13. package/src/__tests__/agent-loop-override-profile.test.ts +3 -0
  14. package/src/__tests__/agent-loop-provider-error-recording.test.ts +4 -0
  15. package/src/__tests__/agent-loop-thinking.test.ts +4 -0
  16. package/src/__tests__/agent-loop.test.ts +578 -5
  17. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +0 -1
  18. package/src/__tests__/approval-cascade.test.ts +1 -0
  19. package/src/__tests__/background-workers-disk-pressure.test.ts +0 -2
  20. package/src/__tests__/btw-routes.test.ts +0 -1
  21. package/src/__tests__/build-persisted-content.test.ts +75 -1
  22. package/src/__tests__/catalog-install-normalize.test.ts +141 -0
  23. package/src/__tests__/ces-startup-timeout.test.ts +60 -0
  24. package/src/__tests__/compaction-events.test.ts +1 -0
  25. package/src/__tests__/config-managed-gemini-defaults.test.ts +2 -46
  26. package/src/__tests__/context-overflow-reducer.test.ts +264 -124
  27. package/src/__tests__/context-window-manager-overflow-rung.test.ts +351 -0
  28. package/src/__tests__/conversation-abort-tool-results.test.ts +1 -1
  29. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +13 -5
  30. package/src/__tests__/conversation-agent-loop-overflow.test.ts +284 -455
  31. package/src/__tests__/conversation-agent-loop.test.ts +131 -551
  32. package/src/__tests__/conversation-app-control-instantiation.test.ts +13 -0
  33. package/src/__tests__/conversation-confirmation-signals.test.ts +1 -0
  34. package/src/__tests__/conversation-fork-crud.test.ts +259 -0
  35. package/src/__tests__/conversation-history-web-search.test.ts +1 -1
  36. package/src/__tests__/conversation-lifecycle.test.ts +257 -1
  37. package/src/__tests__/conversation-process-callsite.test.ts +1 -0
  38. package/src/__tests__/conversation-provider-retry-repair.test.ts +38 -377
  39. package/src/__tests__/conversation-queue.test.ts +1 -39
  40. package/src/__tests__/conversation-runtime-assembly.test.ts +119 -8
  41. package/src/__tests__/conversation-skill-tools.test.ts +491 -5
  42. package/src/__tests__/conversation-slash-queue.test.ts +1 -1
  43. package/src/__tests__/conversation-slash-unknown.test.ts +1 -0
  44. package/src/__tests__/conversation-speed-override.test.ts +1 -0
  45. package/src/__tests__/conversation-store.test.ts +74 -0
  46. package/src/__tests__/conversation-surfaces-app-control.test.ts +4 -1
  47. package/src/__tests__/conversation-tool-setup-attribution.test.ts +323 -0
  48. package/src/__tests__/conversation-tool-setup-tools-disabled.test.ts +34 -0
  49. package/src/__tests__/conversation-workspace-cache-state.test.ts +1 -0
  50. package/src/__tests__/conversation-workspace-injection.test.ts +1 -1
  51. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +1 -0
  52. package/src/__tests__/corrected-target.test.ts +93 -0
  53. package/src/__tests__/credential-execution-feature-gates.test.ts +3 -5
  54. package/src/__tests__/credential-execution-tools.test.ts +23 -11
  55. package/src/__tests__/credential-security-invariants.test.ts +6 -1
  56. package/src/__tests__/db-schedule-syntax-migration.test.ts +80 -0
  57. package/src/__tests__/device-id.test.ts +70 -1
  58. package/src/__tests__/embedding-managed-proxy-selection.test.ts +6 -40
  59. package/src/__tests__/empty-response-hook.test.ts +242 -66
  60. package/src/__tests__/external-plugin-loader.test.ts +0 -31
  61. package/src/__tests__/get-skill-detail-audit.test.ts +43 -1
  62. package/src/__tests__/guardian-routing-invariants.test.ts +91 -0
  63. package/src/__tests__/history-repair-hook.test.ts +228 -3
  64. package/src/__tests__/host-app-control-proxy.test.ts +45 -0
  65. package/src/__tests__/host-browser-proxy.test.ts +254 -9
  66. package/src/__tests__/identity-routes.test.ts +1 -0
  67. package/src/__tests__/image-recovery-hook.test.ts +387 -0
  68. package/src/__tests__/injector-chain.test.ts +5 -4
  69. package/src/__tests__/injector-v3-suppression.test.ts +373 -47
  70. package/src/__tests__/intent-routing.test.ts +7 -0
  71. package/src/__tests__/memory-retrieval-hook.test.ts +117 -15
  72. package/src/__tests__/notification-decision-strategy.test.ts +3 -3
  73. package/src/__tests__/oauth-store.test.ts +0 -85
  74. package/src/__tests__/{context-overflow-policy.test.ts → overflow-policy.test.ts} +1 -1
  75. package/src/__tests__/parallel-tool.benchmark.test.ts +4 -0
  76. package/src/__tests__/persist-unsendable-image-downscale.test.ts +29 -9
  77. package/src/__tests__/persist-unsendable-image.test.ts +4 -4
  78. package/src/__tests__/persistence-secret-redaction.test.ts +78 -0
  79. package/src/__tests__/plugin-bootstrap.test.ts +82 -73
  80. package/src/__tests__/plugin-tool-contribution.test.ts +7 -4
  81. package/src/__tests__/plugin-types.test.ts +0 -8
  82. package/src/__tests__/provider-catalog-visibility.test.ts +1 -9
  83. package/src/__tests__/prune-old-conversations-job.test.ts +99 -0
  84. package/src/__tests__/registry.test.ts +240 -1
  85. package/src/__tests__/require-fresh-approval.test.ts +3 -0
  86. package/src/__tests__/schedule-routes.test.ts +116 -1
  87. package/src/__tests__/schedule-store.test.ts +28 -0
  88. package/src/__tests__/schedule-tools.test.ts +94 -1
  89. package/src/__tests__/server-history-render.test.ts +39 -0
  90. package/src/__tests__/skill-projection-feature-flag.test.ts +13 -0
  91. package/src/__tests__/skill-projection.benchmark.test.ts +25 -7
  92. package/src/__tests__/skills.test.ts +202 -0
  93. package/src/__tests__/slim-skill-category.test.ts +195 -0
  94. package/src/__tests__/strip-memory-injections.test.ts +33 -39
  95. package/src/__tests__/test-support/tool-invocation-seed.ts +79 -0
  96. package/src/__tests__/title-generate-hook.test.ts +9 -7
  97. package/src/__tests__/tool-audit-listener.test.ts +264 -1
  98. package/src/__tests__/tool-error-hook.test.ts +4 -3
  99. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +1 -0
  100. package/src/__tests__/tool-executor-lifecycle-events.test.ts +273 -0
  101. package/src/__tests__/tool-result-truncate-hook.test.ts +1 -0
  102. package/src/__tests__/tool-start-timestamp.test.ts +218 -0
  103. package/src/__tests__/tools-get-route.test.ts +202 -0
  104. package/src/__tests__/workspace-tool-loader.test.ts +319 -0
  105. package/src/__tests__/workspace-tools-watcher-flag.test.ts +70 -0
  106. package/src/agent/loop.ts +569 -319
  107. package/src/api/events/tool-result.ts +9 -0
  108. package/src/api/events/tool-use-start.ts +7 -0
  109. package/src/api/index.ts +10 -0
  110. package/src/api/responses/conversation-message.ts +135 -27
  111. package/src/api/responses/memory-v3-selection-log.ts +4 -4
  112. package/src/approvals/guardian-request-resolvers.ts +26 -0
  113. package/src/browser-session/backends/host-bridge.ts +29 -0
  114. package/src/browser-session/index.ts +1 -0
  115. package/src/browser-session/types.ts +5 -1
  116. package/src/cli/commands/__tests__/schedules.test.ts +62 -4
  117. package/src/cli/commands/__tests__/skills.test.ts +53 -0
  118. package/src/cli/commands/channel-verification-sessions.ts +6 -6
  119. package/src/cli/commands/inference-providers.ts +0 -8
  120. package/src/cli/commands/plugins.ts +2 -2
  121. package/src/cli/commands/schedules.ts +27 -4
  122. package/src/cli/commands/skills.ts +187 -146
  123. package/src/cli/commands/tools.ts +106 -0
  124. package/src/cli/lib/__tests__/install-from-github.test.ts +256 -328
  125. package/src/cli/lib/__tests__/plugin-catalog-cache.test.ts +6 -2
  126. package/src/cli/lib/__tests__/plugin-details.test.ts +10 -16
  127. package/src/cli/lib/__tests__/plugin-marketplace.test.ts +2 -2
  128. package/src/cli/lib/__tests__/search-plugins.test.ts +145 -240
  129. package/src/cli/lib/install-from-github.ts +187 -117
  130. package/src/cli/lib/plugin-catalog-cache.ts +9 -9
  131. package/src/cli/lib/plugin-details.ts +38 -68
  132. package/src/cli/lib/plugin-marketplace.ts +42 -14
  133. package/src/cli/lib/search-plugins.ts +29 -129
  134. package/src/cli/program.ts +2 -0
  135. package/src/config/bundled-skills/acp/SKILL.md +1 -0
  136. package/src/config/bundled-skills/app-builder/SKILL.md +1 -0
  137. package/src/config/bundled-skills/app-control/SKILL.md +1 -0
  138. package/src/config/bundled-skills/computer-use/SKILL.md +1 -0
  139. package/src/config/bundled-skills/contacts/SKILL.md +1 -0
  140. package/src/config/bundled-skills/document-editor/SKILL.md +1 -0
  141. package/src/config/bundled-skills/followups/SKILL.md +1 -0
  142. package/src/config/bundled-skills/image-studio/SKILL.md +1 -0
  143. package/src/config/bundled-skills/media-processing/SKILL.md +1 -0
  144. package/src/config/bundled-skills/messaging/SKILL.md +1 -0
  145. package/src/config/bundled-skills/phone-calls/SKILL.md +1 -0
  146. package/src/config/bundled-skills/playbooks/SKILL.md +1 -0
  147. package/src/config/bundled-skills/schedule/SKILL.md +1 -0
  148. package/src/config/bundled-skills/schedule/TOOLS.json +11 -3
  149. package/src/config/bundled-skills/sequences/SKILL.md +1 -0
  150. package/src/config/bundled-skills/settings/SKILL.md +1 -0
  151. package/src/config/bundled-skills/skill-management/SKILL.md +101 -1
  152. package/src/config/bundled-skills/subagent/SKILL.md +1 -0
  153. package/src/config/bundled-skills/transcribe/SKILL.md +1 -0
  154. package/src/config/env-registry.ts +23 -0
  155. package/src/config/feature-flag-registry.json +17 -81
  156. package/src/config/loader.ts +5 -22
  157. package/src/config/schema.ts +2 -0
  158. package/src/config/schemas/__tests__/compaction-logs.test.ts +56 -0
  159. package/src/config/schemas/__tests__/memory-v2.test.ts +0 -1
  160. package/src/config/schemas/__tests__/memory-v3.test.ts +61 -1
  161. package/src/config/schemas/compaction-logs.ts +79 -0
  162. package/src/config/schemas/memory-v2.ts +0 -8
  163. package/src/config/schemas/memory-v3.ts +104 -33
  164. package/src/config/seed-inference-profiles.ts +1 -1
  165. package/src/config/skills.ts +117 -47
  166. package/src/context/compactor.ts +11 -0
  167. package/src/context/strip-injections.ts +38 -4
  168. package/src/credential-execution/feature-gates.ts +0 -21
  169. package/src/credential-execution/startup-timeout.ts +32 -4
  170. package/src/daemon/__tests__/conversation-tool-setup-exclude.test.ts +18 -0
  171. package/src/daemon/conversation-agent-loop-handlers.ts +140 -91
  172. package/src/daemon/conversation-agent-loop.ts +123 -658
  173. package/src/daemon/conversation-error.ts +6 -33
  174. package/src/daemon/conversation-lifecycle.ts +1 -1
  175. package/src/daemon/conversation-runtime-assembly.ts +183 -22
  176. package/src/daemon/conversation-skill-tools.ts +137 -8
  177. package/src/daemon/conversation-slash.ts +0 -14
  178. package/src/daemon/conversation-store.ts +2 -19
  179. package/src/daemon/conversation-tool-setup.ts +87 -1
  180. package/src/daemon/conversation.ts +119 -50
  181. package/src/daemon/external-plugins-bootstrap.ts +36 -95
  182. package/src/daemon/handlers/config-channels.ts +11 -2
  183. package/src/daemon/handlers/shared.ts +9 -1
  184. package/src/daemon/handlers/skills.ts +10 -4
  185. package/src/daemon/host-app-control-proxy.ts +72 -57
  186. package/src/daemon/host-browser-proxy.ts +117 -22
  187. package/src/daemon/lifecycle.ts +34 -5
  188. package/src/daemon/message-protocol.ts +0 -7
  189. package/src/daemon/message-types/schedules.ts +1 -0
  190. package/src/daemon/message-types/skills.ts +17 -0
  191. package/src/daemon/providers-setup.ts +3 -0
  192. package/src/daemon/server.ts +3 -3
  193. package/src/daemon/tool-setup-types.ts +9 -3
  194. package/src/daemon/trust-context.ts +23 -0
  195. package/src/daemon/workspace-tools-watcher.ts +324 -0
  196. package/src/events/tool-audit-listener.ts +78 -16
  197. package/src/events/tool-metrics-listener.ts +2 -5
  198. package/src/memory/__tests__/compaction-log-writer-clickhouse.test.ts +227 -0
  199. package/src/memory/__tests__/conversation-queries.test.ts +176 -0
  200. package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +20 -32
  201. package/src/memory/compaction-log-writer-clickhouse.ts +418 -0
  202. package/src/memory/conversation-crud.ts +134 -3
  203. package/src/memory/conversation-queries.ts +64 -7
  204. package/src/memory/db-init.ts +14 -0
  205. package/src/memory/embedding-backend.test.ts +130 -1
  206. package/src/memory/embedding-backend.ts +79 -106
  207. package/src/memory/embedding-gemini.ts +5 -0
  208. package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +12 -0
  209. package/src/memory/graph/__tests__/handle-remember-v2.test.ts +19 -0
  210. package/src/memory/graph/conversation-graph-memory.ts +36 -25
  211. package/src/memory/graph/tool-handlers.ts +3 -0
  212. package/src/memory/job-handlers/cleanup.ts +3 -1
  213. package/src/memory/jobs-store.ts +0 -28
  214. package/src/memory/jobs-worker.ts +10 -22
  215. package/src/memory/memory-marker.ts +29 -0
  216. package/src/memory/memory-retrospective-startup-cleanup.ts +1 -1
  217. package/src/memory/migrations/268-add-memory-v3-selections.ts +6 -0
  218. package/src/memory/migrations/270-schedule-description.ts +36 -0
  219. package/src/memory/migrations/275-tool-invocations-add-skill-id.test.ts +81 -0
  220. package/src/memory/migrations/275-tool-invocations-add-skill-id.ts +20 -0
  221. package/src/memory/migrations/276-tool-invocations-created-at-id-index.test.ts +68 -0
  222. package/src/memory/migrations/276-tool-invocations-created-at-id-index.ts +20 -0
  223. package/src/memory/migrations/277-add-memory-v3-ever-injected.ts +29 -0
  224. package/src/memory/migrations/278-tool-invocations-telemetry-columns.test.ts +96 -0
  225. package/src/memory/migrations/278-tool-invocations-telemetry-columns.ts +39 -0
  226. package/src/memory/migrations/279-create-skill-loaded-events.test.ts +84 -0
  227. package/src/memory/migrations/279-create-skill-loaded-events.ts +26 -0
  228. package/src/memory/migrations/280-conversations-surfaced-at.test.ts +88 -0
  229. package/src/memory/migrations/280-conversations-surfaced-at.ts +24 -0
  230. package/src/memory/migrations/index.ts +10 -0
  231. package/src/memory/migrations/registry.ts +8 -0
  232. package/src/memory/schema/conversations.ts +16 -0
  233. package/src/memory/schema/infrastructure.ts +26 -0
  234. package/src/memory/skill-loaded-events-store.test.ts +160 -0
  235. package/src/memory/skill-loaded-events-store.ts +95 -0
  236. package/src/memory/tool-executed-events-store.test.ts +219 -0
  237. package/src/memory/tool-executed-events-store.ts +102 -0
  238. package/src/memory/tool-usage-store.ts +15 -3
  239. package/src/memory/v2/__tests__/consolidation-job.test.ts +117 -12
  240. package/src/memory/v2/__tests__/consolidation-prompt-flag-gating-guard.test.ts +189 -0
  241. package/src/memory/v2/__tests__/injected-block-slugs.test.ts +90 -0
  242. package/src/memory/v2/__tests__/page-store.test.ts +33 -0
  243. package/src/memory/v2/__tests__/prompts-consolidation.test.ts +88 -15
  244. package/src/memory/v2/activation-store.ts +50 -1
  245. package/src/memory/v2/consolidation-job.ts +113 -29
  246. package/src/memory/v2/injected-block-slugs.ts +79 -0
  247. package/src/memory/v2/injection.ts +6 -1
  248. package/src/memory/v2/prompts/consolidation.ts +414 -13
  249. package/src/memory/v2/router.ts +2 -28
  250. package/src/memory/v2/static-context.ts +1 -1
  251. package/src/memory/v2/types.ts +16 -0
  252. package/src/notifications/__tests__/copy-composer.test.ts +244 -0
  253. package/src/notifications/access-request-copy.ts +298 -0
  254. package/src/notifications/adapters/slack.ts +3 -3
  255. package/src/notifications/adapters/telegram.ts +2 -1
  256. package/src/notifications/copy-composer.ts +49 -267
  257. package/src/notifications/decision-engine.ts +16 -35
  258. package/src/notifications/home-feed-side-effect.ts +1 -6
  259. package/src/oauth/oauth-store.ts +0 -9
  260. package/src/permissions/checker.test.ts +83 -1
  261. package/src/permissions/checker.ts +25 -2
  262. package/src/permissions/gateway-threshold-reader.test.ts +182 -0
  263. package/src/permissions/gateway-threshold-reader.ts +80 -0
  264. package/src/platform/client.ts +1 -3
  265. package/src/platform/feature-gate.ts +3 -12
  266. package/src/plugin-api/constants.ts +4 -2
  267. package/src/plugin-api/index.ts +63 -11
  268. package/src/plugin-api/types.ts +236 -71
  269. package/src/plugins/defaults/compaction/compact.ts +66 -2
  270. package/src/plugins/defaults/compaction/context-overflow-reducer.ts +240 -32
  271. package/src/plugins/defaults/compaction/corrected-target.ts +53 -0
  272. package/src/{daemon/context-overflow-policy.ts → plugins/defaults/compaction/overflow-policy.ts} +1 -1
  273. package/src/plugins/defaults/compaction/window-manager.ts +303 -1
  274. package/src/plugins/defaults/empty-response/hooks/post-model-call.ts +173 -0
  275. package/src/plugins/defaults/empty-response/hooks/stop.ts +11 -115
  276. package/src/plugins/defaults/empty-response/nudge-state-store.ts +46 -0
  277. package/src/plugins/defaults/history-repair/hooks/post-model-call.ts +50 -0
  278. package/src/plugins/defaults/history-repair/hooks/stop.ts +22 -0
  279. package/src/plugins/defaults/history-repair/repair-state-store.ts +51 -0
  280. package/src/plugins/defaults/history-repair/terminal.ts +39 -2
  281. package/src/plugins/defaults/image-recovery/detect.ts +25 -0
  282. package/src/plugins/defaults/image-recovery/hooks/post-model-call.ts +73 -0
  283. package/src/plugins/defaults/image-recovery/hooks/stop.ts +22 -0
  284. package/src/plugins/defaults/image-recovery/image-recovery-state-store.ts +48 -0
  285. package/src/plugins/defaults/image-recovery/package.json +14 -0
  286. package/src/{daemon/persist-unsendable-image.ts → plugins/defaults/image-recovery/recover.ts} +67 -14
  287. package/src/plugins/defaults/index.ts +71 -5
  288. package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +76 -112
  289. package/src/plugins/defaults/memory-retrieval/hooks/{user-prompt-submit-temp.ts → user-prompt-submit.ts} +83 -74
  290. package/src/plugins/defaults/memory-retrieval/injector-chain.ts +14 -8
  291. package/src/plugins/defaults/memory-retrieval/injectors.ts +2 -18
  292. package/src/plugins/defaults/memory-retrieval/package.json +14 -0
  293. package/src/plugins/defaults/memory-v3-shadow/__tests__/carry-integration.test.ts +1157 -0
  294. package/src/plugins/defaults/memory-v3-shadow/__tests__/injection.test.ts +683 -0
  295. package/src/plugins/defaults/memory-v3-shadow/__tests__/live-integration.test.ts +161 -140
  296. package/src/plugins/defaults/memory-v3-shadow/__tests__/maintain-job.test.ts +160 -0
  297. package/src/plugins/defaults/memory-v3-shadow/__tests__/orchestrate.test.ts +335 -316
  298. package/src/plugins/defaults/memory-v3-shadow/__tests__/pool-select.test.ts +145 -53
  299. package/src/plugins/defaults/memory-v3-shadow/__tests__/render-injection.test.ts +38 -1
  300. package/src/plugins/defaults/memory-v3-shadow/__tests__/selection-log-store.test.ts +18 -8
  301. package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-integration.test.ts +112 -71
  302. package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +192 -51
  303. package/src/plugins/defaults/memory-v3-shadow/__tests__/types.test.ts +4 -16
  304. package/src/plugins/defaults/memory-v3-shadow/card.test.ts +173 -0
  305. package/src/plugins/defaults/memory-v3-shadow/card.ts +116 -0
  306. package/src/plugins/defaults/memory-v3-shadow/core-set.test.ts +104 -0
  307. package/src/plugins/defaults/memory-v3-shadow/core-set.ts +59 -0
  308. package/src/plugins/defaults/memory-v3-shadow/ever-injected-store.test.ts +305 -0
  309. package/src/plugins/defaults/memory-v3-shadow/ever-injected-store.ts +278 -0
  310. package/src/plugins/defaults/memory-v3-shadow/hot-set.test.ts +138 -0
  311. package/src/plugins/defaults/memory-v3-shadow/hot-set.ts +85 -0
  312. package/src/plugins/defaults/memory-v3-shadow/injector.ts +331 -24
  313. package/src/plugins/defaults/memory-v3-shadow/maintain-job.ts +119 -13
  314. package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +169 -114
  315. package/src/plugins/defaults/memory-v3-shadow/page-content.ts +47 -16
  316. package/src/plugins/defaults/memory-v3-shadow/pool-select.ts +144 -66
  317. package/src/plugins/defaults/memory-v3-shadow/prune.test.ts +758 -0
  318. package/src/plugins/defaults/memory-v3-shadow/prune.ts +471 -0
  319. package/src/plugins/defaults/memory-v3-shadow/render-injection.ts +68 -16
  320. package/src/plugins/defaults/memory-v3-shadow/selection-log-store.ts +19 -12
  321. package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +96 -43
  322. package/src/plugins/defaults/memory-v3-shadow/types.ts +34 -17
  323. package/src/plugins/defaults/title-generate/hooks/stop.ts +9 -11
  324. package/src/plugins/pipeline.ts +8 -5
  325. package/src/plugins/types.ts +5 -51
  326. package/src/providers/cache-control.ts +26 -0
  327. package/src/providers/inference/__tests__/base-url-route-validation.test.ts +1 -2
  328. package/src/providers/model-catalog.ts +13 -1
  329. package/src/providers/openai/__tests__/tool-choice-mapping.test.ts +147 -0
  330. package/src/providers/openai/chat-completions-provider.ts +46 -0
  331. package/src/providers/openai/responses-provider.ts +45 -0
  332. package/src/providers/registry.ts +0 -8
  333. package/src/runtime/__tests__/agent-wake.test.ts +0 -1
  334. package/src/runtime/agent-wake.ts +13 -0
  335. package/src/runtime/routes/__tests__/acp-routes.test.ts +151 -0
  336. package/src/runtime/routes/__tests__/consolidation-routes.test.ts +12 -50
  337. package/src/runtime/routes/__tests__/conversation-surface-routes.test.ts +322 -0
  338. package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +0 -62
  339. package/src/runtime/routes/__tests__/plugins-routes.test.ts +31 -11
  340. package/src/runtime/routes/acp-routes.test.ts +106 -0
  341. package/src/runtime/routes/acp-routes.ts +248 -2
  342. package/src/runtime/routes/browser-tabs-routes.ts +1 -1
  343. package/src/runtime/routes/channel-verification-routes.ts +14 -5
  344. package/src/runtime/routes/chatgpt-subscription-auth-routes.ts +0 -14
  345. package/src/runtime/routes/consolidation-routes.ts +6 -82
  346. package/src/runtime/routes/conversation-list-routes.ts +6 -0
  347. package/src/runtime/routes/conversation-management-routes.ts +71 -0
  348. package/src/runtime/routes/identity-routes.ts +8 -0
  349. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +33 -34
  350. package/src/runtime/routes/inference-provider-connection-routes.ts +0 -45
  351. package/src/runtime/routes/plugins-routes.ts +34 -45
  352. package/src/runtime/routes/schedule-routes.ts +43 -5
  353. package/src/runtime/routes/settings-routes.ts +140 -15
  354. package/src/runtime/routes/skills-routes.ts +18 -6
  355. package/src/runtime/services/__tests__/conversation-serializer.test.ts +140 -0
  356. package/src/runtime/services/conversation-serializer.ts +38 -1
  357. package/src/runtime/verification-outbound-actions.ts +147 -2
  358. package/src/runtime/verification-templates.ts +29 -3
  359. package/src/schedule/schedule-store.ts +19 -0
  360. package/src/skills/catalog-install.ts +77 -13
  361. package/src/tasks/task-scheduler.ts +1 -0
  362. package/src/telemetry/types.ts +66 -1
  363. package/src/telemetry/usage-telemetry-reporter.test.ts +542 -13
  364. package/src/telemetry/usage-telemetry-reporter.ts +213 -20
  365. package/src/tools/browser/__tests__/browser-execution-acquire.test.ts +49 -2
  366. package/src/tools/browser/__tests__/browser-status.test.ts +29 -5
  367. package/src/tools/browser/browser-execution.ts +27 -9
  368. package/src/tools/browser/cdp-client/__tests__/factory.test.ts +380 -4
  369. package/src/tools/browser/cdp-client/__tests__/host-bridge-cdp-client.test.ts +107 -0
  370. package/src/tools/browser/cdp-client/__tests__/types.test.ts +6 -1
  371. package/src/tools/browser/cdp-client/factory.ts +217 -17
  372. package/src/tools/browser/cdp-client/host-bridge-cdp-client.ts +67 -0
  373. package/src/tools/browser/cdp-client/types.ts +22 -2
  374. package/src/tools/credential-execution/make-authenticated-request.ts +2 -1
  375. package/src/tools/credential-execution/manage-secure-command-tool.ts +169 -164
  376. package/src/tools/credential-execution/run-authenticated-command.ts +2 -1
  377. package/src/tools/executor.ts +39 -7
  378. package/src/tools/registry.ts +387 -5
  379. package/src/tools/schedule/create.ts +16 -0
  380. package/src/tools/schedule/list.ts +12 -4
  381. package/src/tools/schedule/update.ts +12 -0
  382. package/src/tools/skills/load.ts +11 -6
  383. package/src/tools/terminal/safe-env.ts +2 -0
  384. package/src/tools/types.ts +65 -9
  385. package/src/tools/workspace-tools/loader.ts +673 -0
  386. package/src/usage/attribution.ts +28 -0
  387. package/src/util/device-id.ts +17 -3
  388. package/src/util/platform.ts +16 -0
  389. package/tsconfig.plugin-api.json +13 -0
  390. package/src/__tests__/plugin-external-api.test.ts +0 -68
  391. package/src/__tests__/plugin-skill-contribution.test.ts +0 -355
  392. package/src/daemon/message-types/browser.ts +0 -10
  393. package/src/notifications/__tests__/emit-signal-home-feed.test.ts +0 -187
  394. package/src/plugins/defaults/memory-v3-shadow/__tests__/fixtures/eval-turns.json +0 -36
  395. package/src/plugins/defaults/memory-v3-shadow/__tests__/fixtures/live-turns.json +0 -37
  396. package/src/plugins/defaults/memory-v3-shadow/__tests__/working-set-eviction.test.ts +0 -106
  397. package/src/plugins/defaults/memory-v3-shadow/__tests__/working-set-skeleton.test.ts +0 -44
  398. package/src/plugins/defaults/memory-v3-shadow/working-set.ts +0 -91
  399. package/src/plugins/external-api.ts +0 -114
  400. package/src/plugins/plugin-skill-contributions.ts +0 -292
@@ -1,18 +1,25 @@
1
1
  /**
2
- * Tests for `pool-select.ts` — the single forced-tool selector over the unified
3
- * candidate pool.
2
+ * Tests for `pool-select.ts` — the single forced-tool selector over the
3
+ * two-segment candidate pool (stable-prefix cards + dynamic finder tail).
4
4
  *
5
5
  * Coverage matrix:
6
- * - Numbered candidates render with their descriptors (truncated).
7
- * - Returned IDs map to the right candidate slugs by 1-based index, with
8
- * `pinned` driven by `pinned_ids`.
9
- * - Omitted `ids` → keep ALL candidates (recall-safe).
6
+ * - Segment ordering: the stable prefix (full cards, `[1]…[m]`) renders as
7
+ * its own content block carrying the `cache_control` breakpoint; the
8
+ * dynamic tail (finder lines `[m+1]…` + per-turn context) follows in an
9
+ * un-cached block.
10
+ * - Numbering stability: for identical stable lanes the rendered prefix
11
+ * block is byte-identical across renders; only the tail varies.
12
+ * - Returned IDs map over the CONCATENATED numbering, with `pinned` driven
13
+ * by `pinned_ids`; out-of-range IDs dropped; selections deduped by slug
14
+ * (a page can appear as both a card and a finder line; pinned flags OR).
15
+ * - Omitted `ids` → keep ALL candidates (recall-safe, slug-deduped).
10
16
  * - Explicit `ids: []` → keep none (deliberate abstention).
11
- * - Out-of-range / duplicate IDs ignored without throwing.
17
+ * - Finder snippets are whitespace-collapsed and truncated (~300 chars).
12
18
  * - No provider / missing tool_use / schema mismatch / throw → keep none
13
19
  * (degrade to deterministic lanes), the last three after a re-prompt retry.
14
- * - One forced-tool `select_pages` call on the v3 L2 call site, with NO cache
15
- * breakpoint (the pool is dynamic per turn).
20
+ * - One forced-tool `select_pages` call on the v3 L2 call site with
21
+ * `disableTurnStartCache` (the tail varies per turn — the provider's
22
+ * auto-anchor would never hit).
16
23
  *
17
24
  * The provider is stubbed so no network calls fire; mirrors selector.test.ts.
18
25
  */
@@ -55,7 +62,7 @@ mock.module("../../../../util/logger.js", () => ({
55
62
  }));
56
63
 
57
64
  const { selectPool } = await import("../pool-select.js");
58
- type PoolCandidate = Parameters<typeof selectPool>[0][number];
65
+ type SelectorPool = Parameters<typeof selectPool>[0];
59
66
 
60
67
  // ---------------------------------------------------------------------------
61
68
  // Helpers.
@@ -118,12 +125,23 @@ function makeThrowingProvider(): Provider {
118
125
  };
119
126
  }
120
127
 
121
- function makePool(): PoolCandidate[] {
122
- return [
123
- { slug: "page-a", descriptor: "section: the alpha rollout plan" },
124
- { slug: "page-b", descriptor: "section: beta metrics dashboard" },
125
- { slug: "topic-x", descriptor: "linked page about topic x" },
126
- ];
128
+ const CARD_A =
129
+ "# memory/concepts/page-a.md\nlead for page a\n\n[sections: §Alpha · §Beta]";
130
+ const CARD_B = "# memory/concepts/page-b.md\nlead for page b";
131
+
132
+ /** Two stable-prefix cards (`[1] page-a`, `[2] page-b`) and two finder lines
133
+ * (`[3] topic-x`, `[4] page-a` — a finder hit on a stable-prefix page). */
134
+ function makePool(): SelectorPool {
135
+ return {
136
+ stable: [
137
+ { slug: "page-a", card: CARD_A },
138
+ { slug: "page-b", card: CARD_B },
139
+ ],
140
+ finder: [
141
+ { slug: "topic-x", descriptor: "section: about topic x" },
142
+ { slug: "page-a", descriptor: "section: the alpha rollout plan" },
143
+ ],
144
+ };
127
145
  }
128
146
 
129
147
  function makeTurn(currentMessage: string): MemoryRoutingTurn {
@@ -135,17 +153,28 @@ function makeTurn(currentMessage: string): MemoryRoutingTurn {
135
153
  };
136
154
  }
137
155
 
156
+ interface RenderedBlock {
157
+ type: string;
158
+ text: string;
159
+ cache_control?: { type: string; ttl?: string };
160
+ }
161
+
162
+ function sentBlocks(callIndex = 0): RenderedBlock[] {
163
+ return providerCalls[callIndex]!.messages[0]!
164
+ .content as unknown as RenderedBlock[];
165
+ }
166
+
138
167
  beforeEach(() => {
139
168
  providerStub = null;
140
169
  providerCalls.length = 0;
141
170
  });
142
171
 
143
172
  // ---------------------------------------------------------------------------
144
- // selectPool — id mapping.
173
+ // selectPool — id mapping over the concatenated numbering.
145
174
  // ---------------------------------------------------------------------------
146
175
 
147
176
  describe("selectPool — id mapping", () => {
148
- test("returned IDs map to candidate slugs, pinned driven by pinned_ids", async () => {
177
+ test("IDs map over cards then finder lines, pinned driven by pinned_ids", async () => {
149
178
  providerStub = makeProvider(
150
179
  toolUseResponse({ ids: [3, 1], pinned_ids: [1] }),
151
180
  );
@@ -156,7 +185,17 @@ describe("selectPool — id mapping", () => {
156
185
  ]);
157
186
  });
158
187
 
159
- test("omitted ids keeps ALL candidates (recall-safe)", async () => {
188
+ test("a page selected as both card and finder line dedupes to one slug, pinned ORed", async () => {
189
+ // page-a is id 1 (card) AND id 4 (finder line); pinned only via the
190
+ // finder-line id.
191
+ providerStub = makeProvider(
192
+ toolUseResponse({ ids: [1, 4], pinned_ids: [4] }),
193
+ );
194
+ const result = await selectPool(makePool(), makeTurn("the alpha plan"));
195
+ expect(result).toEqual([{ slug: "page-a", pinned: true }]);
196
+ });
197
+
198
+ test("omitted ids keeps ALL candidates, deduped by slug (recall-safe)", async () => {
160
199
  providerStub = makeProvider(toolUseResponse({}));
161
200
  const result = await selectPool(makePool(), makeTurn("anything"));
162
201
  expect(result).toEqual([
@@ -180,7 +219,7 @@ describe("selectPool — id mapping", () => {
180
219
 
181
220
  test("empty pool returns no pages and never calls the provider", async () => {
182
221
  providerStub = makeProvider(toolUseResponse({ ids: [1] }));
183
- const result = await selectPool([], makeTurn("hi"));
222
+ const result = await selectPool({ stable: [], finder: [] }, makeTurn("hi"));
184
223
  expect(result).toEqual([]);
185
224
  expect(providerCalls).toHaveLength(0);
186
225
  });
@@ -231,11 +270,12 @@ describe("selectPool — degradation on failure", () => {
231
270
  });
232
271
 
233
272
  // ---------------------------------------------------------------------------
234
- // selectPool — request shape (no cache breakpoint; pool is dynamic per turn).
273
+ // selectPool — request shape: stable-prefix cards block with the cache
274
+ // breakpoint, dynamic tail block without.
235
275
  // ---------------------------------------------------------------------------
236
276
 
237
277
  describe("selectPool — request shape", () => {
238
- test("forces tool_choice to select_pages with the v3 L2 call site", async () => {
278
+ test("forces tool_choice to select_pages on the v3 L2 call site with disableTurnStartCache", async () => {
239
279
  providerStub = makeProvider(toolUseResponse({ ids: [1] }));
240
280
  await selectPool(makePool(), makeTurn("rollout?"));
241
281
 
@@ -244,63 +284,115 @@ describe("selectPool — request shape", () => {
244
284
  const cfg = call.options?.config as Record<string, unknown>;
245
285
  expect(cfg?.callSite).toBe("memoryV3SelectL2");
246
286
  expect(cfg?.tool_choice).toEqual({ type: "tool", name: "select_pages" });
287
+ expect(cfg?.disableTurnStartCache).toBe(true);
247
288
  expect(call.options?.tools?.[0]?.name).toBe("select_pages");
248
289
  });
249
290
 
250
- test("renders numbered candidates with descriptors and NO cache breakpoint", async () => {
291
+ test("stable prefix renders full cards in its own block carrying cache_control", async () => {
251
292
  providerStub = makeProvider(toolUseResponse({ ids: [1] }));
252
293
  await selectPool(makePool(), makeTurn("rollout?"));
253
294
 
254
- const content = providerCalls[0].messages[0].content as Array<{
255
- type: string;
256
- text: string;
257
- cache_control?: unknown;
258
- }>;
259
- // A single text block — the dynamic pool has no static prefix to cache.
260
- expect(content).toHaveLength(1);
261
- const [block] = content;
262
- expect(block.type).toBe("text");
263
- expect(block.text).toContain(
264
- "[1] page-a — section: the alpha rollout plan",
295
+ const blocks = sentBlocks();
296
+ expect(blocks).toHaveLength(2);
297
+
298
+ const [prefix, tail] = blocks;
299
+ expect(prefix.type).toBe("text");
300
+ // FULL cards, numbered in pool order, inside the cards segment.
301
+ expect(prefix.text).toContain(`[1] ${CARD_A}`);
302
+ expect(prefix.text).toContain(`[2] ${CARD_B}`);
303
+ expect(prefix.text.startsWith("<candidate_cards>\n")).toBe(true);
304
+ expect(prefix.text.endsWith("\n</candidate_cards>")).toBe(true);
305
+ // The breakpoint rides THIS block (preserved by toAnthropicBlockSafe).
306
+ expect(prefix.cache_control).toEqual({ type: "ephemeral", ttl: "1h" });
307
+
308
+ // The tail continues the numbering after the cards and is NOT cached.
309
+ expect(tail.type).toBe("text");
310
+ expect(tail.text).toContain("[3] topic-x — section: about topic x");
311
+ expect(tail.text).toContain("[4] page-a — section: the alpha rollout plan");
312
+ expect(tail.text).toContain("<current_message>rollout?</current_message>");
313
+ expect(tail.text).toContain("<recent_context>");
314
+ expect(tail.cache_control).toBeUndefined();
315
+ // No cards leak into the tail.
316
+ expect(tail.text).not.toContain("<candidate_cards>");
317
+ });
318
+
319
+ test("the rendered prefix is byte-identical across turns; only the tail varies", async () => {
320
+ providerStub = makeProvider(toolUseResponse({ ids: [1] }));
321
+ await selectPool(makePool(), makeTurn("first question"));
322
+
323
+ // Same stable lanes, different finder hits + message (a new turn).
324
+ const pool2 = makePool();
325
+ pool2.finder = [{ slug: "page-c", descriptor: "section: something else" }];
326
+ await selectPool(pool2, makeTurn("second question"));
327
+
328
+ const [prefix1, tail1] = sentBlocks(0);
329
+ const [prefix2, tail2] = sentBlocks(1);
330
+ expect(prefix2.text).toBe(prefix1.text);
331
+ expect(prefix2.cache_control).toEqual(prefix1.cache_control!);
332
+ expect(tail2.text).not.toBe(tail1.text);
333
+ });
334
+
335
+ test("an empty stable prefix renders a single un-cached block", async () => {
336
+ providerStub = makeProvider(toolUseResponse({ ids: [1] }));
337
+ await selectPool(
338
+ { stable: [], finder: [{ slug: "page-a", descriptor: "d" }] },
339
+ makeTurn("x"),
265
340
  );
266
- expect(block.text).toContain(
267
- "[2] page-b — section: beta metrics dashboard",
341
+ const blocks = sentBlocks();
342
+ expect(blocks).toHaveLength(1);
343
+ expect(blocks[0].text).toContain("[1] page-a — d");
344
+ expect(blocks[0].cache_control).toBeUndefined();
345
+ });
346
+
347
+ test("long finder snippets are whitespace-collapsed and truncated (~300 chars)", async () => {
348
+ providerStub = makeProvider(toolUseResponse({ ids: [1] }));
349
+ const longDescriptor = `padded ${"z".repeat(1000)}`;
350
+ await selectPool(
351
+ { stable: [], finder: [{ slug: "page-a", descriptor: longDescriptor }] },
352
+ makeTurn("x"),
268
353
  );
269
- expect(block.text).toContain("[3] topic-x — linked page about topic x");
270
- expect(block.text).toContain("<current_message>rollout?</current_message>");
271
- expect(block.text).toContain("<recent_context>");
272
- expect(block.cache_control).toBeUndefined();
354
+ const [block] = sentBlocks();
355
+ const line = block.text
356
+ .split("\n")
357
+ .find((l) => l.startsWith("[1] page-a — "))!;
358
+ expect(line).toContain("...");
359
+ expect(line).not.toContain("z".repeat(1000));
360
+ // snippet cap (300) + the `[1] page-a — ` prefix.
361
+ expect(line.length).toBeLessThanOrEqual(300 + "[1] page-a — ".length);
273
362
  });
274
363
 
275
- test("long descriptors are truncated in the candidate list", async () => {
364
+ test("a finder candidate with an empty descriptor renders without a dangling dash", async () => {
276
365
  providerStub = makeProvider(toolUseResponse({ ids: [1] }));
277
- const longDescriptor = "z".repeat(1000);
278
366
  await selectPool(
279
- [{ slug: "page-a", descriptor: longDescriptor }],
367
+ { stable: [], finder: [{ slug: "page-a", descriptor: " " }] },
280
368
  makeTurn("x"),
281
369
  );
282
- const text = (providerCalls[0].messages[0].content[0] as { text: string })
283
- .text;
284
- expect(text).toContain("...");
285
- expect(text).not.toContain("z".repeat(1000));
370
+ const [block] = sentBlocks();
371
+ expect(block.text).toContain("[1] page-a\n");
372
+ expect(block.text).not.toContain("[1] page-a — ");
286
373
  });
287
374
 
288
- test("situational context renders in the user message when present", async () => {
375
+ test("situational context renders in the tail when present", async () => {
289
376
  providerStub = makeProvider(toolUseResponse({ ids: [1] }));
290
377
  await selectPool(makePool(), {
291
378
  ...makeTurn("rollout?"),
292
379
  situationalContext: "Today is Saturday. The launch is today.",
293
380
  });
294
- const text = (providerCalls[0].messages[0].content[0] as { text: string })
295
- .text;
296
- expect(text).toContain(
381
+ const blocks = sentBlocks();
382
+ expect(blocks[1].text).toContain(
297
383
  "<situation>Today is Saturday. The launch is today.</situation>",
298
384
  );
299
385
  });
300
386
 
301
- test("system prompt mentions pinned (locks the pinning commitment)", async () => {
387
+ test("system prompt is carry-aware and generous (persistence + pinned + no limit)", async () => {
302
388
  providerStub = makeProvider(toolUseResponse({ ids: [1] }));
303
389
  await selectPool(makePool(), makeTurn("x"));
304
- expect(providerCalls[0].options?.systemPrompt).toMatch(/pinned/);
390
+ const prompt = providerCalls[0].options?.systemPrompt ?? "";
391
+ // Carry-aware: previously selected pages persist automatically.
392
+ expect(prompt).toMatch(/persist/);
393
+ // Generous: explicitly no selection limit.
394
+ expect(prompt).toMatch(/no limit/i);
395
+ // Pinning commitment.
396
+ expect(prompt).toMatch(/pinned/);
305
397
  });
306
398
  });
@@ -1,7 +1,12 @@
1
1
  import { describe, expect, test } from "bun:test";
2
2
 
3
3
  import { wrapMemoryBlock } from "../../../../memory/memory-marker.js";
4
- import { renderMemoryBlock } from "../render-injection.js";
4
+ import {
5
+ renderCardsBlockInner,
6
+ renderMemoryBlock,
7
+ renderSpotlightInner,
8
+ V3_CARDS_INJECTION_HEADER,
9
+ } from "../render-injection.js";
5
10
  import type { Section, Slug } from "../types.js";
6
11
 
7
12
  function section(article: Slug, text: string): Section {
@@ -17,6 +22,38 @@ const resolver =
17
22
  async (slug: Slug, sec: Section | undefined): Promise<string> =>
18
23
  sec ? sec.text : (fullBodies[slug] ?? "");
19
24
 
25
+ describe("renderCardsBlockInner", () => {
26
+ test("prefixes the v2 read-affordance header and joins cards with blank lines", () => {
27
+ const inner = renderCardsBlockInner([
28
+ "# memory/concepts/page-a.md\nhead a",
29
+ "# memory/concepts/page-b.md\nhead b",
30
+ ]);
31
+ expect(inner).toBe(
32
+ `${V3_CARDS_INJECTION_HEADER}\n\n# memory/concepts/page-a.md\nhead a\n\n# memory/concepts/page-b.md\nhead b`,
33
+ );
34
+ });
35
+
36
+ test("empty card list renders the empty string (no header-only block)", () => {
37
+ expect(renderCardsBlockInner([])).toBe("");
38
+ });
39
+ });
40
+
41
+ describe("renderSpotlightInner", () => {
42
+ test("renders each entry as a § header plus section text", () => {
43
+ const inner = renderSpotlightInner([
44
+ { slug: "page-a", title: "Alpha", text: "alpha text" },
45
+ { slug: "page-b", title: "Beta", text: "beta text" },
46
+ ]);
47
+ expect(inner).toBe(
48
+ "## memory/concepts/page-a.md § Alpha\nalpha text\n\n## memory/concepts/page-b.md § Beta\nbeta text",
49
+ );
50
+ });
51
+
52
+ test("empty window renders the empty string", () => {
53
+ expect(renderSpotlightInner([])).toBe("");
54
+ });
55
+ });
56
+
20
57
  describe("renderMemoryBlock", () => {
21
58
  test("renders the matched section (not the full body) for each slug", async () => {
22
59
  const sections = new Map<Slug, Section>([
@@ -151,15 +151,17 @@ describe("getMemoryV3SelectionForInspector", () => {
151
151
  });
152
152
 
153
153
  test("maps source/pinned and renders the <memory> block", async () => {
154
+ // The second row carries a retired free-text source label (the column is
155
+ // permissive); the inspector passes it through verbatim.
154
156
  seed("conv-4", 1, [
155
157
  { slug: "domain-a/page-1", source: "edge", pinned: true },
156
- { slug: "domain-b/page-2", source: "carry-forward", pinned: false },
158
+ { slug: "domain-b/page-2", source: "legacy-carry", pinned: false },
157
159
  ]);
158
160
 
159
161
  const log = await getMemoryV3SelectionForInspector("conv-4", 1);
160
162
  expect(log?.selections).toEqual([
161
163
  { slug: "domain-a/page-1", source: "edge", pinned: true },
162
- { slug: "domain-b/page-2", source: "carry-forward", pinned: false },
164
+ { slug: "domain-b/page-2", source: "legacy-carry", pinned: false },
163
165
  ]);
164
166
  expect(log?.injectedText).toContain("<memory>");
165
167
  expect(log?.injectedText).toContain("body for domain-a/page-1");
@@ -188,20 +190,21 @@ describe("summarizeSelections", () => {
188
190
  { slug: "domain-a/page-1", source: "needle" },
189
191
  { slug: "domain-b/page-2", source: "edge" },
190
192
  ]);
191
- // Turn 2: page-1 re-selected (needle) + page-2 carried forward.
193
+ // Turn 2: page-1 re-selected (needle) + page-2 re-surfaced by edge.
192
194
  seed("conv-a", 2, [
193
195
  { slug: "domain-a/page-1", source: "needle", pinned: true },
194
- { slug: "domain-b/page-2", source: "carry-forward" },
196
+ { slug: "domain-b/page-2", source: "edge" },
195
197
  ]);
196
198
  // A different conversation must not bleed into the aggregate.
197
199
  seed("conv-b", 1, [{ slug: "domain-c/page-9", source: "dense" }]);
198
200
 
199
201
  const summary = summarizeSelections("conv-a");
200
202
  expect(summary.bySource).toEqual({
203
+ core: 0,
204
+ hot: 0,
201
205
  needle: 2,
202
206
  dense: 0,
203
- edge: 1,
204
- "carry-forward": 1,
207
+ edge: 2,
205
208
  });
206
209
  expect(summary.turns).toBe(2);
207
210
  // page-1 and page-2 — distinct across the two turns.
@@ -210,14 +213,21 @@ describe("summarizeSelections", () => {
210
213
 
211
214
  test("returns zeroed counts for a conversation with no rows", () => {
212
215
  expect(summarizeSelections("conv-none")).toEqual({
213
- bySource: { needle: 0, dense: 0, edge: 0, "carry-forward": 0 },
216
+ bySource: {
217
+ core: 0,
218
+ hot: 0,
219
+ needle: 0,
220
+ dense: 0,
221
+ edge: 0,
222
+ },
214
223
  turns: 0,
215
224
  distinctSlugs: 0,
216
225
  });
217
226
  });
218
227
 
219
228
  test("ignores unknown/free-text historical source labels in bySource but counts the turn", () => {
220
- // A pre-lane historical row with a legacy label (column is free-text).
229
+ // A pre-lane historical row with a legacy label (column is free-text) —
230
+ // retired labels like the old per-turn carry source land here too.
221
231
  seed("conv-c", 1, [
222
232
  { slug: "domain-a/page-1", source: "l1+l2" },
223
233
  { slug: "domain-a/page-2", source: "needle" },