@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,54 +1,57 @@
1
1
  /**
2
- * Memory v3 — orchestrator composing the section-grain lanes into one turn.
2
+ * Memory v3 — orchestrator composing the candidate lanes into one turn.
3
3
  *
4
4
  * Each turn runs:
5
- * 1. Candidate generation over three deterministic lanes:
5
+ * 1. Candidate generation over three deterministic finder lanes:
6
6
  * - the section-grain BM25 needle (`SectionNeedle.query`, KB articles),
7
7
  * - the dense lane (`denseLane`, KD articles), and
8
8
  * - link-graph edge expansion (`edgeExpand`) over the top needle+dense
9
9
  * article seeds.
10
10
  * Each lane only ever ADDS candidates, so the pool is recall-safe by
11
11
  * construction.
12
- * 2. Build the unified candidate pool of `{ slug, descriptor }`. The
13
- * descriptor is the matched section's text for needle/dense hits (resolved
14
- * via the section index); for edge-only pages it is the curated `links`
15
- * description when the traversed edge carried one, else the page's best
16
- * section against the query. Edge-only pages record NO matched section, so
17
- * they inject the FULL page (the lead the query never hit is often empty;
18
- * the link-relevant content is elsewhere) — the best-section text is only a
19
- * descriptor fallback. Synthetic capability pages (skills / CLI commands)
20
- * carry sections in the section index too, so they enter the pool through
21
- * the lanes like any other page rather than being always-added.
12
+ * 2. Build the candidate pool in CACHE ORDER: the stable prefix —
13
+ * `[...core (file order), ...hot (score order)]`, both computed at lane
14
+ * init — followed by the finder candidates (needle → dense → edge
15
+ * surfacing order). The stable prefix is identical across consecutive
16
+ * turns while the lanes are unchanged (lane invalidation at consolidation
17
+ * is the recompute cadence), so the selector input's leading segment
18
+ * rides the provider KV cache (the cache breakpoint itself lives in
19
+ * `pool-select.ts`).
20
+ *
21
+ * Stable-prefix candidates render as FULL CARDS (`renderCard` — head
22
+ * section + TOC), pre-rendered at lane init (`prefixCards`) so the
23
+ * rendered prefix is byte-identical across turns. Finder candidates
24
+ * render as compact snippet lines: the matched section's text (or the
25
+ * curated `links` description for an edge hit), falling back to the
26
+ * page's lead-section text when no match text exists.
27
+ *
28
+ * The finder tail is NOT deduped against the stable prefix: a finder hit
29
+ * on a core/hot page keeps its matched-section line (and its
30
+ * `matchedSections` ref + `lanes.finder` entry), so the selector and the
31
+ * section spotlight see that page's CURRENT relevance even though the
32
+ * page itself sits in the stable prefix. The selector dedupes selections
33
+ * by slug.
22
34
  * 3. A SINGLE forced-tool select (`selectPool`) over the whole pool. The
23
- * result is this turn's selections.
24
- * 4. Age the carry-forward working set to this turn (evict stale non-pinned
25
- * entries, then the cap) and snapshot it — the pages carried in from
26
- * EARLIER turns.
27
- * 5. Final injection = unique union of this turn's selected slugs and that
28
- * carried-forward set, so pages selected on earlier turns carry forward
29
- * even when this turn does not re-select them.
30
- * 6. Record this turn's selections into the working set for LATER turns. This
31
- * runs AFTER the snapshot so the cap is spent on genuinely carried pages,
32
- * not on this turn's selections (which are injected directly) — otherwise a
33
- * turn selecting more pages than the cap would evict the entire carry.
35
+ * result is this turn's selections — current turn only. Cross-turn
36
+ * persistence is the injector's job (net-new blocks frozen into history),
37
+ * not a per-turn re-rendered carry set.
34
38
  */
35
39
 
36
40
  import type { AssistantConfig } from "../../../config/schema.js";
37
41
  import { denseLane } from "./dense.js";
38
42
  import type { EdgeGraph } from "./edge.js";
39
43
  import { edgeExpand } from "./edge.js";
40
- import type { PoolCandidate } from "./pool-select.js";
44
+ import type { PoolCandidate, StableCandidate } from "./pool-select.js";
41
45
  import { selectPool } from "./pool-select.js";
42
46
  import type { SectionNeedle } from "./section-needle.js";
43
47
  import type {
44
- CandidateLane,
48
+ FinderLane,
45
49
  MemoryRoutingTurn,
46
50
  Section,
47
51
  SectionIndex,
48
52
  SelectedPage,
49
53
  Slug,
50
54
  } from "./types.js";
51
- import { WorkingSet } from "./working-set.js";
52
55
 
53
56
  /** Default number of BM25 needle articles to fold into the pool. */
54
57
  export const DEFAULT_NEEDLE_K = 100;
@@ -62,7 +65,18 @@ export interface OrchestrateDeps {
62
65
  * collection. */
63
66
  denseConfig: AssistantConfig;
64
67
  edgeGraph: EdgeGraph;
65
- workingSet: WorkingSet;
68
+ /** The curated core set in file order (existence-filtered at lane init).
69
+ * Heads the stable prefix of the candidate pool. */
70
+ coreSlugs: Slug[];
71
+ /** The frecency hot set in score order (computed at lane init with the core
72
+ * set excluded). Follows core in the stable prefix. */
73
+ hotSlugs: Slug[];
74
+ /** Pre-rendered FULL cards for the stable-prefix slugs, keyed by slug.
75
+ * Rendered ONCE at lane init so the selector's stable prefix is
76
+ * byte-identical across turns (the cache contract). Every core/hot slug
77
+ * MUST have an entry — a missing card is a lane-init bug and throws
78
+ * (silently degrading would violate the byte-stable-prefix contract). */
79
+ prefixCards: ReadonlyMap<Slug, string>;
66
80
  /** Number of BM25 needle articles. Defaults to {@link DEFAULT_NEEDLE_K}. */
67
81
  needleK?: number;
68
82
  /** Number of dense-lane articles. Defaults to {@link DEFAULT_DENSE_K}. */
@@ -78,23 +92,43 @@ export interface OrchestrateDeps {
78
92
  edgeCap?: number;
79
93
  }
80
94
 
95
+ /** A finder-lane candidate: the slug, the descriptor that justified it, and
96
+ * the finder lane that FIRST surfaced it (needle → dense → edge precedence). */
97
+ export interface FinderCandidate {
98
+ slug: Slug;
99
+ descriptor: string;
100
+ lane: FinderLane;
101
+ }
102
+
103
+ /**
104
+ * The three candidate lanes in cache order. `core` and `hot` are the stable
105
+ * prefix (byte-identical across turns while lanes are unchanged); `finder` is
106
+ * the dynamic tail and MAY repeat a stable-prefix slug (a finder hit on a
107
+ * core/hot page is kept so its current relevance stays visible downstream).
108
+ */
109
+ export interface OrchestrateLanes {
110
+ /** Curated core set, file order. */
111
+ core: Slug[];
112
+ /** Frecency hot set, score order (never overlaps core). */
113
+ hot: Slug[];
114
+ /** Finder candidates in surfacing order, deduped among themselves only. */
115
+ finder: FinderCandidate[];
116
+ }
117
+
81
118
  export interface OrchestrateResult {
82
- /** This turn's selections, deduped by slug (pinned flags ORed). */
83
- currentSelections: SelectedPage[];
84
- /** The carried-forward set: selections from EARLIER turns, aged to this turn
85
- * (snapshotted before this turn's selections are recorded). */
86
- workingSetUnion: Set<Slug>;
87
- /** Slugs to inject: this turn's selections ∪ the carried-forward working set. */
88
- finalInjection: Slug[];
89
- /** The matched `Section` for each pooled slug that had one, keyed by slug.
90
- * Populated from the lane hits and consumed by the injector to render each
91
- * selected slug's matched section (progressive disclosure). */
92
- sectionBySlug: Map<Slug, Section>;
93
- /** The candgen lane that FIRST surfaced each pooled slug, keyed by slug.
94
- * Insertion order is needle → dense → edge, so a page surfaced by more than
95
- * one lane records the highest-precedence one. Consumed by the selection
96
- * telemetry to attribute each selection to its true lane. */
97
- laneBySlug: Map<Slug, CandidateLane>;
119
+ /** This turn's selections, deduped by slug (pinned flags ORed; the dedup is
120
+ * `selectPool`'s contract). Current turn only — there is no
121
+ * carried-forward set unioned in. */
122
+ selections: SelectedPage[];
123
+ /** The matched `Section` for each candidate slug that had one, keyed by slug.
124
+ * Populated from the finder-lane hits — including hits on core/hot pages —
125
+ * and consumed by the injector to render each selected slug's matched
126
+ * section (progressive disclosure). */
127
+ matchedSections: Map<Slug, Section>;
128
+ /** The candidate lanes in cache order; see {@link OrchestrateLanes}. Consumed
129
+ * by the selection telemetry (lane attribution) and the downstream selector
130
+ * rendering/spotlight. */
131
+ lanes: OrchestrateLanes;
98
132
  }
99
133
 
100
134
  /** Stable-order de-duplication preserving first occurrence. */
@@ -110,6 +144,13 @@ export async function orchestrate(
110
144
  const denseK = deps.denseK ?? DEFAULT_DENSE_K;
111
145
  const { sections } = deps.sectionIndex;
112
146
 
147
+ // The stable prefix: core (file order) then hot (score order). Hot is
148
+ // computed with core excluded at lane init; the filter here is a cheap
149
+ // defensive dedup so a misbehaving lane can never double-list a slug.
150
+ const core = deps.coreSlugs;
151
+ const hot = deps.hotSlugs.filter((slug) => !core.includes(slug));
152
+ const stablePrefix = new Set<Slug>([...core, ...hot]);
153
+
113
154
  // Step 1: needle (sync BM25) and dense (async embed + Qdrant) lanes run in
114
155
  // parallel. Both return distinct articles each tagged with their best-scoring
115
156
  // section index/ordinal.
@@ -118,43 +159,42 @@ export async function orchestrate(
118
159
  denseLane(deps.denseConfig, turn.currentMessage, denseK),
119
160
  ]);
120
161
 
121
- // The pool accumulates one entry per distinct article. `sectionBySlug` records
122
- // the matched `Section` (when one is known) for downstream injection;
123
- // `laneBySlug` records the lane that first surfaced each slug for telemetry.
124
- const poolBySlug = new Map<Slug, PoolCandidate>();
125
- const sectionBySlug = new Map<Slug, Section>();
126
- const laneBySlug = new Map<Slug, CandidateLane>();
162
+ // `matchedSections` records the matched `Section` (when one is known) for
163
+ // every finder hit — INCLUDING hits on stable-prefix slugs — for downstream
164
+ // injection/spotlight. `finder` accumulates one entry per distinct
165
+ // finder-surfaced article; the first lane to surface a slug wins the entry,
166
+ // so the needle → dense → edge call order encodes lane precedence.
167
+ const matchedSections = new Map<Slug, Section>();
168
+ const finder: FinderCandidate[] = [];
169
+ const finderSeen = new Set<Slug>();
127
170
 
128
- // Add one pool candidate, recording its matched `Section` (when known) for
129
- // downstream injection and the `lane` that surfaced it for telemetry.
130
- // `descriptor` overrides the section text when supplied (the edge lane prefers
131
- // a curated `links` description). The first lane to surface a slug wins the
132
- // pool entry, the recorded section, AND the recorded lane — so the
133
- // needle → dense → edge call order encodes lane precedence.
134
- const addCandidate = (
171
+ // `descriptor` overrides the section text when supplied (the edge lane
172
+ // prefers a curated `links` description).
173
+ const addFinder = (
135
174
  slug: Slug,
136
175
  section: Section | undefined,
137
176
  descriptor: string | undefined,
138
- lane: CandidateLane,
177
+ lane: FinderLane,
139
178
  ): void => {
140
- if (section && !sectionBySlug.has(slug)) {
141
- sectionBySlug.set(slug, section);
179
+ if (section && !matchedSections.has(slug)) {
180
+ matchedSections.set(slug, section);
142
181
  }
143
- if (poolBySlug.has(slug)) return;
144
- laneBySlug.set(slug, lane);
145
- poolBySlug.set(slug, {
182
+ if (finderSeen.has(slug)) return;
183
+ finderSeen.add(slug);
184
+ finder.push({
146
185
  slug,
147
186
  descriptor: descriptor ?? section?.text ?? "",
187
+ lane,
148
188
  });
149
189
  };
150
190
 
151
- // Step 2a: needle hits — descriptor is the matched section's text. `section`
191
+ // Step 1a: needle hits — descriptor is the matched section's text. `section`
152
192
  // is an index into `sections`.
153
193
  for (const hit of needled) {
154
- addCandidate(hit.article, sections[hit.section], undefined, "needle");
194
+ addFinder(hit.article, sections[hit.section], undefined, "needle");
155
195
  }
156
196
 
157
- // Step 2b: dense hits — `section` is the matched ORDINAL; resolve it to the
197
+ // Step 1b: dense hits — `section` is the matched ORDINAL; resolve it to the
158
198
  // concrete `Section` via the section index. Falls back to undefined (blank
159
199
  // descriptor) if the ordinal is not in the in-memory index.
160
200
  for (const hit of densed) {
@@ -163,7 +203,7 @@ export async function orchestrate(
163
203
  // so `byArticle` holds exactly the live pages (synthetic capability slugs
164
204
  // included) — only truly-deleted pages are dropped here.
165
205
  if (!deps.sectionIndex.byArticle.has(hit.article)) continue;
166
- addCandidate(
206
+ addFinder(
167
207
  hit.article,
168
208
  sectionByOrdinal(deps.sectionIndex, hit.article, hit.section),
169
209
  undefined,
@@ -171,16 +211,19 @@ export async function orchestrate(
171
211
  );
172
212
  }
173
213
 
174
- // Step 2c: edge expansion over the top needle+dense article seeds. `alive`
175
- // skips slugs already pooled. An edge-only page was surfaced because its
176
- // NEIGHBOUR matched — the query did not lexically hit the page itself, so
177
- // `bestSection` returns its first/lead section on a zero-score match. That
178
- // lead is often empty for heading-structured pages, and it is the curated
179
- // `links` description (not the lead) that made the candidate relevant. So we
180
- // record NO matched section for edge-only pages (pass `undefined`), which
181
- // makes injection fall back to the FULL page — where the link-relevant content
182
- // lives. `bestSection`'s text is kept only as the select-pool DESCRIPTOR
183
- // fallback for when the traversed edge carried no curated `links` description.
214
+ // Step 1c: edge expansion over the top needle+dense article seeds. `alive`
215
+ // skips slugs already in the pool — finder-surfaced AND stable-prefix slugs
216
+ // (an edge hit carries no matched section, so re-surfacing a core/hot page
217
+ // adds nothing; needle/dense hits on those pages ARE kept for their
218
+ // sections). An edge-only page was surfaced because its NEIGHBOUR matched —
219
+ // the query did not lexically hit the page itself, so `bestSection` returns
220
+ // its first/lead section on a zero-score match. That lead is often empty for
221
+ // heading-structured pages, and it is the curated `links` description (not
222
+ // the lead) that made the candidate relevant. So we record NO matched
223
+ // section for edge-only pages (pass `undefined`), which makes injection fall
224
+ // back to the FULL page — where the link-relevant content lives.
225
+ // `bestSection`'s text is kept only as the select-pool DESCRIPTOR fallback
226
+ // for when the traversed edge carried no curated `links` description.
184
227
  const seeds = unique<Slug>([
185
228
  ...needled.map((h) => h.article),
186
229
  ...densed.map((h) => h.article),
@@ -189,12 +232,12 @@ export async function orchestrate(
189
232
  seedCount: deps.edgeSeeds,
190
233
  perSeed: deps.edgePerSeed,
191
234
  cap: deps.edgeCap,
192
- alive: (slug) => !poolBySlug.has(slug),
235
+ alive: (slug) => !finderSeen.has(slug) && !stablePrefix.has(slug),
193
236
  });
194
237
  for (const neighbor of surfaced) {
195
238
  const best = deps.needle.bestSection(neighbor.article, turn.currentMessage);
196
239
  const fallbackDescriptor = best >= 0 ? sections[best]?.text : undefined;
197
- addCandidate(
240
+ addFinder(
198
241
  neighbor.article,
199
242
  undefined,
200
243
  neighbor.description ?? fallbackDescriptor,
@@ -202,45 +245,45 @@ export async function orchestrate(
202
245
  );
203
246
  }
204
247
 
205
- // Step 3: a SINGLE forced-tool select over the unified pool.
206
- const selected = await selectPool([...poolBySlug.values()], turn);
207
- const bySlug = new Map<Slug, SelectedPage>();
208
- for (const page of selected) {
209
- const existing = bySlug.get(page.slug);
210
- bySlug.set(page.slug, {
211
- slug: page.slug,
212
- pinned: (existing?.pinned ?? false) || page.pinned,
213
- });
214
- }
215
- const currentSelections = [...bySlug.values()];
216
-
217
- // Step 4: age the carry-forward set to this turn (drop stale non-pinned
218
- // entries, then the cap) and snapshot it. This is the set carried in from
219
- // EARLIER turns; recording this turn happens afterward (step 6) so the cap is
220
- // spent on genuinely carried pages, not on this turn's selections (which are
221
- // injected directly anyway).
222
- deps.workingSet.evict(turn.turnNumber);
223
- const workingSetUnion = deps.workingSet.union();
224
-
225
- // Step 5: final injection = this turn's selections ∪ the carried-forward set,
226
- // so pages selected on earlier turns carry forward even when this turn does
227
- // not re-select them.
228
- const finalInjection = unique<Slug>([
229
- ...currentSelections.map((s) => s.slug),
230
- ...workingSetUnion,
231
- ]);
248
+ // Step 2: assemble the selector pool in cache order — the stable prefix
249
+ // (core then hot) as FULL CARDS, then the finder tail. Cards are
250
+ // pre-rendered at lane init (`prefixCards`): query- AND
251
+ // conversation-state-INDEPENDENT by design, so the rendered prefix is
252
+ // byte-identical across turns while the lanes are unchanged. The tail is
253
+ // NOT deduped against the prefix — a finder hit on a core/hot page renders
254
+ // its own snippet line so its CURRENT relevance stays visible; `selectPool`
255
+ // dedupes selections by slug. Finder candidates with no match text fall
256
+ // back to the page's lead-section snippet.
257
+ const stable: StableCandidate[] = [...core, ...hot].map((slug) => {
258
+ const card = deps.prefixCards.get(slug);
259
+ if (card === undefined) {
260
+ // Lane init renders a card for every core/hot slug; a hole here means
261
+ // the lanes and the card map are out of sync. Throw rather than render
262
+ // a degraded card — the caller (observeTurn) logs and skips the turn,
263
+ // which is better than silently breaking the byte-stable prefix.
264
+ throw new Error(
265
+ `memory-v3: no pre-rendered card for stable-prefix slug "${slug}"`,
266
+ );
267
+ }
268
+ return { slug, card };
269
+ });
270
+ const finderTail: PoolCandidate[] = finder.map((c) => ({
271
+ slug: c.slug,
272
+ descriptor:
273
+ c.descriptor.trim().length > 0
274
+ ? c.descriptor
275
+ : leadSectionText(deps.sectionIndex, c.slug),
276
+ }));
232
277
 
233
- // Step 6: record this turn's selections so they carry forward to LATER turns.
234
- for (const sel of currentSelections) {
235
- deps.workingSet.recordSelection(sel.slug, turn.turnNumber, sel.pinned);
236
- }
278
+ // Step 3: a SINGLE forced-tool select over the cache-ordered pool. The
279
+ // selections come back slug-deduped (pinned flags ORed) — `selectPool`'s
280
+ // contract.
281
+ const selections = await selectPool({ stable, finder: finderTail }, turn);
237
282
 
238
283
  return {
239
- currentSelections,
240
- workingSetUnion,
241
- finalInjection,
242
- sectionBySlug,
243
- laneBySlug,
284
+ selections,
285
+ matchedSections,
286
+ lanes: { core, hot, finder },
244
287
  };
245
288
  }
246
289
 
@@ -264,3 +307,15 @@ function sectionByOrdinal(
264
307
  }
265
308
  return undefined;
266
309
  }
310
+
311
+ /**
312
+ * The text of an article's LEAD (first) section, or `""` when the article has
313
+ * no indexed sections. Used as the finder-line snippet fallback when a
314
+ * candidate carries no match text (e.g. an edge hit with neither a curated
315
+ * description nor a scoring section) — the lead is the closest free
316
+ * approximation of the card head.
317
+ */
318
+ function leadSectionText(index: SectionIndex, article: Slug): string {
319
+ const first = index.byArticle.get(article)?.[0];
320
+ return first === undefined ? "" : (index.sections[first]?.text ?? "");
321
+ }
@@ -1,22 +1,24 @@
1
+ import { injectedConceptHeader } from "../../../memory/v2/injected-block-slugs.js";
1
2
  import { readPage, renderPageContent } from "../../../memory/v2/page-store.js";
2
3
  import { getWorkspaceDir } from "../../../util/platform.js";
3
4
  import { renderCapabilityContent } from "./capabilities.js";
5
+ import { renderCard } from "./card.js";
4
6
  import type { Section, Slug } from "./types.js";
5
7
 
6
8
  /**
7
9
  * Prefix `body` with the `# memory/concepts/<slug>.md` header marker the v3
8
- * `<memory>` block uses. Both the full-page and matched-section renderers emit
9
- * this exact marker, and the v2 stripper recognizes pages by it — so the two
10
- * call sites MUST stay byte-identical, which is why this lives in one place.
10
+ * `<memory>` block uses (shared builder: `injectedConceptHeader`, so the
11
+ * renderers, the card renderer, and the read-side parsers all agree on the
12
+ * exact bytes).
11
13
  */
12
14
  function withConceptHeader(slug: Slug, body: string): string {
13
- return `# memory/concepts/${slug}.md\n${body}`;
15
+ return `${injectedConceptHeader(slug)}\n${body}`;
14
16
  }
15
17
 
16
18
  /**
17
19
  * Render a selected page's full content for the v3 `<memory>` block. Mirrors
18
20
  * the v2 dynamic-memory layout (`# memory/concepts/<slug>.md\n<frontmatter+body>`)
19
- * so the working-set block reads like v2's. A missing page (or any read
21
+ * so the v3 block reads like v2's. A missing page (or any read
20
22
  * failure) degrades to "" — `renderMemoryBlock` still emits a line for the
21
23
  * slug, and a blank section is preferable to throwing into the turn.
22
24
  *
@@ -25,9 +27,11 @@ function withConceptHeader(slug: Slug, body: string): string {
25
27
  * `readPage`. A non-null result (including "") means the slug was a capability
26
28
  * slug and was handled here.
27
29
  *
28
- * Shared by the live injector (`shadow-plugin.ts`) and the inspector
29
- * selection-log store (`selection-log-store.ts`) so the inspector's rendered
30
- * block is byte-identical to what live injection produces.
30
+ * INSPECTOR-ONLY (as the `renderV3SectionContent` fallback): live injection
31
+ * freezes compact CARDS into history via {@link renderV3CardContent} — it no
32
+ * longer renders full pages, so this output is an APPROXIMATE reconstruction
33
+ * for the inspector selection log (`selection-log-store.ts`), not what the
34
+ * model saw.
31
35
  */
32
36
  export async function renderV3PageContent(slug: Slug): Promise<string> {
33
37
  const capability = renderCapabilityContent(slug);
@@ -44,18 +48,45 @@ export async function renderV3PageContent(slug: Slug): Promise<string> {
44
48
  }
45
49
 
46
50
  /**
47
- * Render a slug's matched section for the v3 `<memory>` block (progressive
48
- * disclosure: inject only the section the lanes matched, not the full page).
49
- * Uses the same `# memory/concepts/<slug>.md` header marker as
50
- * {@link renderV3PageContent} so the block reads like v2's and the v2 stripper
51
- * recognizes it.
51
+ * Render a selected page's compact CARD for the v3 frozen-injection layer:
52
+ * the `# memory/concepts/<slug>.md` header, the page's head section, and a
53
+ * one-line section TOC (see `card.ts`). Cards are the persistent injection
54
+ * unit — frozen into history once and deduped by the everInjected store.
55
+ *
56
+ * - Capability slugs (skills, `assistant` CLI commands) have no on-disk page
57
+ * and no meaningful head/TOC split, so they render their full capability
58
+ * content via {@link renderCapabilityContent} (its own `# Skill:` /
59
+ * `# CLI command:` header) instead of a card.
60
+ * - A missing page or any read failure degrades to "" — the injector skips
61
+ * empty cards rather than throwing into the turn.
62
+ */
63
+ export async function renderV3CardContent(slug: Slug): Promise<string> {
64
+ const capability = renderCapabilityContent(slug);
65
+ if (capability !== null) return capability;
66
+ try {
67
+ const page = await readPage(getWorkspaceDir(), slug);
68
+ if (!page) return "";
69
+ return renderCard(slug, renderPageContent(page));
70
+ } catch {
71
+ return "";
72
+ }
73
+ }
74
+
75
+ /**
76
+ * Render a slug's matched section under the `# memory/concepts/<slug>.md`
77
+ * header marker, the same marker as {@link renderV3PageContent}.
78
+ *
79
+ * INSPECTOR-ONLY: live injection freezes cards ({@link renderV3CardContent})
80
+ * and re-renders the ephemeral spotlight separately; this per-section render
81
+ * remains for the inspector selection log (`selection-log-store.ts`), which
82
+ * reconstructs an approximate view of a turn's selection after the fact.
52
83
  *
53
84
  * - Capability slugs (skills, `assistant` CLI commands) have no on-disk
54
85
  * section, so they always render their capability content via
55
86
  * {@link renderCapabilityContent}, never a section.
56
- * - When `section` is undefined (e.g. a carry-forward page with no current
57
- * match), fall back to {@link renderV3PageContent} (the full/lead page) so the
58
- * slug still contributes content.
87
+ * - When `section` is undefined (e.g. an edge-only or stable-prefix page with
88
+ * no current match), fall back to {@link renderV3PageContent} (the full/lead
89
+ * page) so the slug still contributes content.
59
90
  */
60
91
  export async function renderV3SectionContent(
61
92
  slug: Slug,