@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
@@ -0,0 +1,189 @@
1
+ /**
2
+ * Guard: the DEFAULT consolidation prompt must not leak flag-gated feature
3
+ * content to installs that don't have the feature enabled.
4
+ *
5
+ * Why this exists: the consolidation prompt ships to every assistant with no
6
+ * `memory.v2.consolidation_prompt_path` override, and it is processed without
7
+ * any flag check at run time — so flag-gated instructions added to the shared
8
+ * template reach the entire default population at their next consolidation.
9
+ * That happened once: the memory-v3 `core-pages.md` curation step landed
10
+ * inline and instructed v2-only installs to curate a file nothing on their
11
+ * install reads, under premises ("kept in reach every turn", "the hot set")
12
+ * that are false without the v3 lanes. This is the same leak class
13
+ * `workspace-release-notes-feature-flag-guard.test.ts` blocks for UPDATES.md,
14
+ * via a path that guard doesn't cover.
15
+ *
16
+ * The rule this enforces: flag-gated content enters the template only via a
17
+ * `{{...}}` placeholder substituted by `renderConsolidationPrompt` under a
18
+ * `ConsolidationPromptOptions` gate. When you add a gated section, register
19
+ * it in GATED_SECTIONS below so the guard covers it.
20
+ */
21
+ import { readFileSync } from "node:fs";
22
+ import { join } from "node:path";
23
+ import { describe, expect, test } from "bun:test";
24
+
25
+ import {
26
+ CONSOLIDATION_PROMPT_V3,
27
+ CORE_PAGES_CONSOLIDATION_SECTION,
28
+ renderConsolidationPrompt,
29
+ } from "../prompts/consolidation.js";
30
+
31
+ type Registry = {
32
+ flags: Array<{
33
+ key: string;
34
+ scope: string;
35
+ defaultEnabled: boolean;
36
+ }>;
37
+ };
38
+
39
+ /**
40
+ * Every flag-gated section of the consolidation prompt, with a short
41
+ * distinctive marker that would survive light rewording of the section body.
42
+ * The guard asserts neither appears in the all-gates-off rendering.
43
+ */
44
+ const GATED_SECTIONS: Array<{
45
+ name: string;
46
+ section: string;
47
+ marker: string;
48
+ }> = [
49
+ {
50
+ name: "memory-v3 core-pages curation (gate: memory-v3-shadow|live)",
51
+ section: CORE_PAGES_CONSOLIDATION_SECTION,
52
+ marker: "core-pages",
53
+ },
54
+ ];
55
+
56
+ /** All `ConsolidationPromptOptions` gates forced off — the v2-only rendering. */
57
+ const ALL_GATES_OFF = {
58
+ includeCorePagesSection: false,
59
+ articleShape: "v2" as const,
60
+ };
61
+
62
+ /**
63
+ * Phrases distinctive to the v3 article-shape template. The all-gates-off
64
+ * rendering must contain none of them — the v3 shape drops the `summary:`
65
+ * field that v2 injection depends on, so leaking it to a v2 install corrupts
66
+ * the corpus the install actually serves prompts from.
67
+ */
68
+ const V3_SHAPE_MARKERS = [
69
+ "The lead IS the card",
70
+ "status: cc-draft",
71
+ "kind: index",
72
+ ];
73
+
74
+ // Registry helpers mirror workspace-release-notes-feature-flag-guard.test.ts.
75
+ function loadDefaultDisabledAssistantFlagKeys(): string[] {
76
+ const registryPath = join(
77
+ process.cwd(),
78
+ "..",
79
+ "meta",
80
+ "feature-flags",
81
+ "feature-flag-registry.json",
82
+ );
83
+ const registry = JSON.parse(readFileSync(registryPath, "utf-8")) as Registry;
84
+ return registry.flags
85
+ .filter((flag) => flag.scope === "assistant" && !flag.defaultEnabled)
86
+ .map((flag) => flag.key)
87
+ .sort();
88
+ }
89
+
90
+ function escapeRegExp(value: string): string {
91
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
92
+ }
93
+
94
+ function featureFlagKeyPattern(key: string): RegExp {
95
+ const escaped = escapeRegExp(key);
96
+ if (key.includes("-")) {
97
+ return new RegExp(`(?:^|[^a-z0-9-])${escaped}(?:$|[^a-z0-9-])`, "i");
98
+ }
99
+ return new RegExp("[`'\"]" + escaped + "[`'\"]", "i");
100
+ }
101
+
102
+ describe("default consolidation prompt flag-gating guard", () => {
103
+ const rendered = renderConsolidationPrompt("Jan 1, 12:00 AM", ALL_GATES_OFF);
104
+
105
+ test("leaves no unsubstituted {{...}} placeholders", () => {
106
+ // A placeholder added to the template but not wired into
107
+ // renderConsolidationPrompt's substitutions would ship raw braces to
108
+ // every default-prompt install.
109
+ const residue = rendered.match(/\{\{[A-Z0-9_]+\}\}/g) ?? [];
110
+ expect(
111
+ residue,
112
+ "Unsubstituted placeholder(s) in the default consolidation prompt — " +
113
+ "wire them through renderConsolidationPrompt and gate them via " +
114
+ "ConsolidationPromptOptions.",
115
+ ).toEqual([]);
116
+ });
117
+
118
+ test("contains no flag-gated section content when all gates are off", () => {
119
+ const violations: string[] = [];
120
+ for (const { name, section, marker } of GATED_SECTIONS) {
121
+ if (rendered.includes(section)) {
122
+ violations.push(`${name}: full section text present`);
123
+ }
124
+ if (rendered.includes(marker)) {
125
+ violations.push(`${name}: marker "${marker}" present`);
126
+ }
127
+ }
128
+
129
+ const message = [
130
+ "The default consolidation prompt reaches every install with no prompt",
131
+ "override, regardless of feature flags. Flag-gated feature instructions",
132
+ "must enter via a {{...}} placeholder substituted under a",
133
+ "ConsolidationPromptOptions gate (see CORE_PAGES_PLACEHOLDER for the",
134
+ "pattern) — never inline in CONSOLIDATION_PROMPT.",
135
+ "",
136
+ "Violations:",
137
+ ...violations.map((violation) => ` - ${violation}`),
138
+ ].join("\n");
139
+ expect(violations, message).toEqual([]);
140
+ });
141
+
142
+ test("references no default-disabled assistant feature flags", () => {
143
+ const violations: string[] = [];
144
+ for (const key of loadDefaultDisabledAssistantFlagKeys()) {
145
+ if (featureFlagKeyPattern(key).test(rendered)) {
146
+ violations.push(`references default-disabled flag "${key}"`);
147
+ }
148
+ }
149
+ expect(
150
+ violations,
151
+ "The default consolidation prompt must not mention default-disabled " +
152
+ "assistant feature flags by name.\n" +
153
+ violations.map((violation) => ` - ${violation}`).join("\n"),
154
+ ).toEqual([]);
155
+ });
156
+
157
+ test("contains no v3 article-shape content when the shape gate is v2", () => {
158
+ const violations = V3_SHAPE_MARKERS.filter((marker) =>
159
+ rendered.includes(marker),
160
+ );
161
+ expect(
162
+ violations,
163
+ "The v2 (all-gates-off) rendering must not teach the v3 article shape — " +
164
+ "v2 installs depend on `summary:` fragment pages. v3-shape content " +
165
+ "belongs only in CONSOLIDATION_PROMPT_V3, selected via " +
166
+ "options.articleShape.\nLeaked markers: " +
167
+ violations.join(", "),
168
+ ).toEqual([]);
169
+ });
170
+
171
+ test("the v3 rendering teaches the v3 shape and drops the v2 summary requirement", () => {
172
+ const v3 = renderConsolidationPrompt("Jan 1, 12:00 AM", {
173
+ includeCorePagesSection: true,
174
+ articleShape: "v3",
175
+ });
176
+ for (const marker of V3_SHAPE_MARKERS) {
177
+ expect(v3).toContain(marker);
178
+ }
179
+ // The v3 shape must not instruct a required `summary:` field — its absence
180
+ // is the wiki↔v2 coupling the deploy sequence is built around.
181
+ expect(v3).not.toContain("The `summary` field is required");
182
+ // §10 rides along under the live flag.
183
+ expect(v3).toContain(CORE_PAGES_CONSOLIDATION_SECTION);
184
+ // The raw template keeps both placeholders wired.
185
+ expect(CONSOLIDATION_PROMPT_V3).toContain("{{CUTOFF}}");
186
+ expect(CONSOLIDATION_PROMPT_V3).toContain("{{CORE_PAGES_SECTION}}");
187
+ expect(v3.match(/\{\{[A-Z0-9_]+\}\}/g) ?? []).toEqual([]);
188
+ });
189
+ });
@@ -0,0 +1,90 @@
1
+ import { describe, expect, test } from "bun:test";
2
+
3
+ import {
4
+ extractInjectedConceptSlugs,
5
+ injectedConceptHeader,
6
+ readInjectedBlock,
7
+ } from "../injected-block-slugs.js";
8
+
9
+ describe("extractInjectedConceptSlugs", () => {
10
+ test("extracts nested concept slugs from page headers", () => {
11
+ const block = [
12
+ 'Use `file_read("memory/concepts/path/to/file.md")` to read the full pages for any of the injected memory summaries you want more information on.',
13
+ "",
14
+ "# memory/concepts/topics/page-a.md",
15
+ "Summary of page a.",
16
+ "",
17
+ "# memory/concepts/arcs/deep/nested/page-b.md",
18
+ "Summary of page b.",
19
+ ].join("\n");
20
+
21
+ expect(extractInjectedConceptSlugs(block)).toEqual([
22
+ "topics/page-a",
23
+ "arcs/deep/nested/page-b",
24
+ ]);
25
+ });
26
+
27
+ test("handles a <memory>-wrapped block the same as an unwrapped one", () => {
28
+ const wrapped =
29
+ "<memory>\n# memory/concepts/topics/page-a.md\nSummary.\n</memory>";
30
+ expect(extractInjectedConceptSlugs(wrapped)).toEqual(["topics/page-a"]);
31
+ });
32
+
33
+ test("ignores skill and CLI sections and non-header lines", () => {
34
+ const block = [
35
+ "# memory/concepts/topics/page-a.md",
36
+ "Summary mentioning memory/concepts/topics/page-x.md inline.",
37
+ "",
38
+ "### Skills You Can Use",
39
+ "- Meeting joiner skill → use skill_load to activate",
40
+ "",
41
+ "### CLI Commands You Can Use",
42
+ "Run `assistant <command> --help` for full usage.",
43
+ "- `assistant export`: export a conversation",
44
+ ].join("\n");
45
+
46
+ expect(extractInjectedConceptSlugs(block)).toEqual(["topics/page-a"]);
47
+ });
48
+
49
+ test("dedupes repeated headers and returns [] when none match", () => {
50
+ const block =
51
+ "# memory/concepts/topics/page-a.md\nA.\n\n# memory/concepts/topics/page-a.md\nA again.";
52
+ expect(extractInjectedConceptSlugs(block)).toEqual(["topics/page-a"]);
53
+ expect(extractInjectedConceptSlugs("no headers here")).toEqual([]);
54
+ });
55
+ });
56
+
57
+ describe("injectedConceptHeader", () => {
58
+ test("builds the header the extractor recovers (builder/parser round-trip)", () => {
59
+ const header = injectedConceptHeader("topics/page-a");
60
+ expect(header).toBe("# memory/concepts/topics/page-a.md");
61
+ expect(extractInjectedConceptSlugs(`${header}\nBody.`)).toEqual([
62
+ "topics/page-a",
63
+ ]);
64
+ });
65
+ });
66
+
67
+ describe("readInjectedBlock", () => {
68
+ test("reads the requested key off valid metadata JSON", () => {
69
+ const metadata = JSON.stringify({
70
+ memoryInjectedBlock: "v2 block",
71
+ memoryV3InjectedBlock: "v3 block",
72
+ });
73
+ expect(readInjectedBlock(metadata, "memoryInjectedBlock")).toBe("v2 block");
74
+ expect(readInjectedBlock(metadata, "memoryV3InjectedBlock")).toBe(
75
+ "v3 block",
76
+ );
77
+ });
78
+
79
+ test("returns null for absent, non-string, malformed, or non-object metadata", () => {
80
+ expect(readInjectedBlock(null, "memoryInjectedBlock")).toBeNull();
81
+ expect(readInjectedBlock(undefined, "memoryInjectedBlock")).toBeNull();
82
+ expect(readInjectedBlock("", "memoryInjectedBlock")).toBeNull();
83
+ expect(readInjectedBlock("not json", "memoryInjectedBlock")).toBeNull();
84
+ expect(readInjectedBlock('["array"]', "memoryInjectedBlock")).toBeNull();
85
+ expect(
86
+ readInjectedBlock('{"memoryInjectedBlock": 42}', "memoryInjectedBlock"),
87
+ ).toBeNull();
88
+ expect(readInjectedBlock("{}", "memoryInjectedBlock")).toBeNull();
89
+ });
90
+ });
@@ -208,6 +208,39 @@ describe("writePage + readPage round-trip", () => {
208
208
  ]);
209
209
  });
210
210
 
211
+ test("accepts and round-trips the v3 wiki-article frontmatter fields", async () => {
212
+ // The full shape CONSOLIDATION_PROMPT_V3 teaches and migrated corpora
213
+ // arrive in. readPage() throws on schema failure and one invalid page in
214
+ // a turn's top-K no-ops the whole v2 injection block, so rejecting these
215
+ // fields would break every article of a migrated corpus at once.
216
+ const page = makePage({
217
+ slug: "wiki-shaped",
218
+ frontmatter: {
219
+ edges: [],
220
+ ref_files: [],
221
+ ref_urls: [],
222
+ title: "Wiki Shaped — A Display Title",
223
+ slug: "wiki-shaped",
224
+ tags: ["topic-area", "index"],
225
+ main: "parent-hub",
226
+ kind: "index",
227
+ status: "cc-draft",
228
+ links: ["child-slug — why this link exists"],
229
+ },
230
+ });
231
+ await writePage(workspaceDir, page);
232
+
233
+ const read = await readPage(workspaceDir, "wiki-shaped");
234
+ expect(read).not.toBeNull();
235
+ expect(read!.frontmatter.title).toBe("Wiki Shaped — A Display Title");
236
+ expect(read!.frontmatter.tags).toEqual(["topic-area", "index"]);
237
+ expect(read!.frontmatter.main).toBe("parent-hub");
238
+ expect(read!.frontmatter.kind).toBe("index");
239
+ // The draft marker must survive a programmatic write → read round-trip:
240
+ // stripping it would silently un-draft a page mid-voice-pass.
241
+ expect(read!.frontmatter.status).toBe("cc-draft");
242
+ });
243
+
211
244
  test("renders frontmatter at the top with --- delimiters", async () => {
212
245
  const page = makePage();
213
246
  await writePage(workspaceDir, page);
@@ -60,13 +60,32 @@ afterAll(() => {
60
60
  rmSync(tmpWorkspace, { recursive: true, force: true });
61
61
  });
62
62
 
63
- const { CONSOLIDATION_PROMPT, CUTOFF_PLACEHOLDER, resolveConsolidationPrompt } =
64
- await import("../prompts/consolidation.js");
63
+ const {
64
+ CONSOLIDATION_PROMPT,
65
+ CORE_PAGES_CONSOLIDATION_SECTION,
66
+ CORE_PAGES_PLACEHOLDER,
67
+ CUTOFF_PLACEHOLDER,
68
+ resolveConsolidationPrompt,
69
+ } = await import("../prompts/consolidation.js");
65
70
 
66
71
  const CUTOFF = "2026-05-01T12:00:00.000Z";
67
72
 
68
- const bundledPrompt = (): string =>
69
- (CONSOLIDATION_PROMPT as string).replaceAll(CUTOFF_PLACEHOLDER, CUTOFF);
73
+ /** Options for tests not exercising the v3 core-pages gate. */
74
+ const NO_CORE = { includeCorePagesSection: false, articleShape: "v2" as const };
75
+ const WITH_CORE = {
76
+ includeCorePagesSection: true,
77
+ articleShape: "v2" as const,
78
+ };
79
+
80
+ const bundledPrompt = (includeCorePagesSection = false): string =>
81
+ (CONSOLIDATION_PROMPT as string)
82
+ .replaceAll(CUTOFF_PLACEHOLDER, CUTOFF)
83
+ .replaceAll(
84
+ CORE_PAGES_PLACEHOLDER,
85
+ includeCorePagesSection
86
+ ? (CORE_PAGES_CONSOLIDATION_SECTION as string)
87
+ : "",
88
+ );
70
89
 
71
90
  beforeEach(() => {
72
91
  warnCalls.length = 0;
@@ -88,7 +107,7 @@ afterEach(() => {
88
107
 
89
108
  describe("resolveConsolidationPrompt — no override", () => {
90
109
  test("returns the bundled prompt with {{CUTOFF}} substituted when overridePath is null", () => {
91
- const result = resolveConsolidationPrompt(null, CUTOFF);
110
+ const result = resolveConsolidationPrompt(null, CUTOFF, NO_CORE);
92
111
  expect(result).toContain("You are running memory consolidation");
93
112
  expect(result).toContain(CUTOFF);
94
113
  expect(result).not.toContain(CUTOFF_PLACEHOLDER);
@@ -96,12 +115,49 @@ describe("resolveConsolidationPrompt — no override", () => {
96
115
  });
97
116
  });
98
117
 
118
+ describe("resolveConsolidationPrompt — core-pages gate", () => {
119
+ test("omits the core-pages section (and any placeholder residue) when the v3 gate is off", () => {
120
+ // v2-only installs must not be told to curate a file nothing reads.
121
+ const result = resolveConsolidationPrompt(null, CUTOFF, NO_CORE);
122
+ expect(result).not.toContain("core-pages");
123
+ expect(result).not.toContain(CORE_PAGES_PLACEHOLDER);
124
+ // The section's slot collapses cleanly: §9 flows straight into the
125
+ // separator with no stray blank lines.
126
+ expect(result).toContain(
127
+ "never wholesale-clear.\n\n---\n\n# What NOT to do",
128
+ );
129
+ });
130
+
131
+ test("includes the core-pages section exactly once, in place, when the v3 gate is on", () => {
132
+ const result = resolveConsolidationPrompt(null, CUTOFF, WITH_CORE);
133
+ expect(result).toContain("## 10. Review `memory/core-pages.md`");
134
+ expect(result.split("## 10. Review").length - 1).toBe(1);
135
+ expect(result).not.toContain(CORE_PAGES_PLACEHOLDER);
136
+ // Positioned between §9 and the don'ts, as authored.
137
+ const sectionAt = result.indexOf("## 10. Review");
138
+ expect(sectionAt).toBeGreaterThan(result.indexOf("## 9. Trim"));
139
+ expect(sectionAt).toBeLessThan(result.indexOf("# What NOT to do"));
140
+ });
141
+
142
+ test("substitutes the placeholder in override files per the same gate", () => {
143
+ const path = join(tmpWorkspace, "custom-prompt.md");
144
+ writeFileSync(path, "Before\n{{CORE_PAGES_SECTION}}After {{CUTOFF}}\n");
145
+
146
+ const withCore = resolveConsolidationPrompt(path, CUTOFF, WITH_CORE);
147
+ expect(withCore).toContain("## 10. Review `memory/core-pages.md`");
148
+ expect(withCore).not.toContain(CORE_PAGES_PLACEHOLDER);
149
+
150
+ const withoutCore = resolveConsolidationPrompt(path, CUTOFF, NO_CORE);
151
+ expect(withoutCore).toBe(`Before\nAfter ${CUTOFF}\n`);
152
+ });
153
+ });
154
+
99
155
  describe("resolveConsolidationPrompt — with override", () => {
100
156
  test("loads an absolute path verbatim and substitutes {{CUTOFF}}", () => {
101
157
  const path = join(tmpWorkspace, "custom-prompt.md");
102
158
  writeFileSync(path, "Custom prompt at {{CUTOFF}}\n");
103
159
 
104
- const result = resolveConsolidationPrompt(path, CUTOFF);
160
+ const result = resolveConsolidationPrompt(path, CUTOFF, NO_CORE);
105
161
 
106
162
  expect(result).toBe(`Custom prompt at ${CUTOFF}\n`);
107
163
  expect(warnCalls).toHaveLength(0);
@@ -113,7 +169,11 @@ describe("resolveConsolidationPrompt — with override", () => {
113
169
  "Workspace-relative {{CUTOFF}}\n",
114
170
  );
115
171
 
116
- const result = resolveConsolidationPrompt("custom-prompt.md", CUTOFF);
172
+ const result = resolveConsolidationPrompt(
173
+ "custom-prompt.md",
174
+ CUTOFF,
175
+ NO_CORE,
176
+ );
117
177
 
118
178
  expect(result).toBe(`Workspace-relative ${CUTOFF}\n`);
119
179
  expect(warnCalls).toHaveLength(0);
@@ -124,7 +184,11 @@ describe("resolveConsolidationPrompt — with override", () => {
124
184
  const path = join(homedir(), filename);
125
185
  writeFileSync(path, "Home dir {{CUTOFF}}\n");
126
186
  try {
127
- const result = resolveConsolidationPrompt(`~/${filename}`, CUTOFF);
187
+ const result = resolveConsolidationPrompt(
188
+ `~/${filename}`,
189
+ CUTOFF,
190
+ NO_CORE,
191
+ );
128
192
  expect(result).toBe(`Home dir ${CUTOFF}\n`);
129
193
  expect(warnCalls).toHaveLength(0);
130
194
  } finally {
@@ -136,7 +200,11 @@ describe("resolveConsolidationPrompt — with override", () => {
136
200
  const body = "No placeholder here. Just a plain prompt.\n";
137
201
  writeFileSync(join(tmpWorkspace, "no-placeholder.md"), body);
138
202
 
139
- const result = resolveConsolidationPrompt("no-placeholder.md", CUTOFF);
203
+ const result = resolveConsolidationPrompt(
204
+ "no-placeholder.md",
205
+ CUTOFF,
206
+ NO_CORE,
207
+ );
140
208
 
141
209
  expect(result).toBe(body);
142
210
  expect(warnCalls).toHaveLength(0);
@@ -148,7 +216,11 @@ describe("resolveConsolidationPrompt — with override", () => {
148
216
  "{{CUTOFF}} ... {{CUTOFF}} ... {{CUTOFF}}",
149
217
  );
150
218
 
151
- const result = resolveConsolidationPrompt("custom-prompt.md", CUTOFF);
219
+ const result = resolveConsolidationPrompt(
220
+ "custom-prompt.md",
221
+ CUTOFF,
222
+ NO_CORE,
223
+ );
152
224
 
153
225
  expect(result).toBe(`${CUTOFF} ... ${CUTOFF} ... ${CUTOFF}`);
154
226
  expect(result).not.toContain(CUTOFF_PLACEHOLDER);
@@ -160,6 +232,7 @@ describe("resolveConsolidationPrompt — failure modes", () => {
160
232
  const result = resolveConsolidationPrompt(
161
233
  "/this/path/does/not/exist.md",
162
234
  CUTOFF,
235
+ NO_CORE,
163
236
  );
164
237
 
165
238
  expect(result).toBe(bundledPrompt());
@@ -173,7 +246,7 @@ describe("resolveConsolidationPrompt — failure modes", () => {
173
246
  const path = join(tmpWorkspace, "empty.md");
174
247
  writeFileSync(path, "");
175
248
 
176
- const result = resolveConsolidationPrompt(path, CUTOFF);
249
+ const result = resolveConsolidationPrompt(path, CUTOFF, NO_CORE);
177
250
 
178
251
  expect(result).toBe(bundledPrompt());
179
252
  expect(warnCalls).toHaveLength(1);
@@ -185,7 +258,7 @@ describe("resolveConsolidationPrompt — failure modes", () => {
185
258
  const path = join(tmpWorkspace, "empty.md");
186
259
  writeFileSync(path, " \n\n\t\n");
187
260
 
188
- const result = resolveConsolidationPrompt(path, CUTOFF);
261
+ const result = resolveConsolidationPrompt(path, CUTOFF, NO_CORE);
189
262
 
190
263
  expect(result).toBe(bundledPrompt());
191
264
  expect(warnCalls).toHaveLength(1);
@@ -198,7 +271,7 @@ describe("resolveConsolidationPrompt — failure modes", () => {
198
271
  // 1 MiB + 1 byte — just over the cap so we don't waste test memory.
199
272
  writeFileSync(path, Buffer.alloc(1 * 1024 * 1024 + 1, 0x61));
200
273
 
201
- const result = resolveConsolidationPrompt(path, CUTOFF);
274
+ const result = resolveConsolidationPrompt(path, CUTOFF, NO_CORE);
202
275
 
203
276
  expect(result).toBe(bundledPrompt());
204
277
  expect(warnCalls).toHaveLength(1);
@@ -213,7 +286,7 @@ describe("resolveConsolidationPrompt — failure modes", () => {
213
286
  const link = join(tmpWorkspace, "link.md");
214
287
  symlinkSync(target, link);
215
288
 
216
- const result = resolveConsolidationPrompt(link, CUTOFF);
289
+ const result = resolveConsolidationPrompt(link, CUTOFF, NO_CORE);
217
290
 
218
291
  expect(result).toBe(bundledPrompt());
219
292
  expect(warnCalls).toHaveLength(1);
@@ -230,7 +303,7 @@ describe("resolveConsolidationPrompt — failure modes", () => {
230
303
  return;
231
304
  }
232
305
 
233
- const result = resolveConsolidationPrompt(fifoPath, CUTOFF);
306
+ const result = resolveConsolidationPrompt(fifoPath, CUTOFF, NO_CORE);
234
307
 
235
308
  expect(result).toBe(bundledPrompt());
236
309
  expect(warnCalls).toHaveLength(1);
@@ -11,7 +11,11 @@ import { eq } from "drizzle-orm";
11
11
 
12
12
  import type { DrizzleDb } from "../db-connection.js";
13
13
  import { activationState } from "../schema.js";
14
- import { type ActivationState, ActivationStateSchema } from "./types.js";
14
+ import {
15
+ type ActivationState,
16
+ ActivationStateSchema,
17
+ type EverInjectedEntry,
18
+ } from "./types.js";
15
19
 
16
20
  /**
17
21
  * Load the activation state for a conversation, or `null` if no row exists.
@@ -118,6 +122,51 @@ export function forkActivationState(
118
122
  .run();
119
123
  }
120
124
 
125
+ /**
126
+ * Seed a truncated fork's activation row from the concept slugs whose
127
+ * `<memory>` attachments the child actually inherited.
128
+ *
129
+ * Truncated forks cannot take the wholesale `forkActivationState` copy: the
130
+ * parent row marks slugs injected on turns the child does not contain, so
131
+ * copying it would suppress those pages in the child forever (silent recall
132
+ * holes). Seeding nothing has the opposite failure — every attachment already
133
+ * present in the copied history gets re-selected and re-attached on the
134
+ * child's next turns as a duplicate. Deriving `everInjected` from the
135
+ * inherited attachments themselves is exact by construction.
136
+ *
137
+ * Activation scores and the turn counter intentionally start fresh: the turn
138
+ * counter is supplied by the graph tracker, which is also not copied for
139
+ * truncated forks, so both counters restart together. Inherited entries are
140
+ * stamped `turn: 0` — dedup is slug-set membership (`turn` is bookkeeping
141
+ * only) and compaction clears the list wholesale.
142
+ *
143
+ * No-op when the child inherited no memory attachments. Synchronous so it can
144
+ * run inside the transaction that wraps `forkConversation()`.
145
+ */
146
+ export function seedForkActivationState(
147
+ database: DrizzleDb,
148
+ newConversationId: string,
149
+ inheritedSlugs: string[],
150
+ ): void {
151
+ if (inheritedSlugs.length === 0) return;
152
+
153
+ const everInjected: EverInjectedEntry[] = inheritedSlugs.map((slug) => ({
154
+ slug,
155
+ turn: 0,
156
+ }));
157
+ database
158
+ .insert(activationState)
159
+ .values({
160
+ conversationId: newConversationId,
161
+ messageId: `${newConversationId}:turn:0`,
162
+ stateJson: "{}",
163
+ everInjectedJson: JSON.stringify(everInjected),
164
+ currentTurn: 0,
165
+ updatedAt: Date.now(),
166
+ })
167
+ .run();
168
+ }
169
+
121
170
  /**
122
171
  * Clear all `everInjected` entries. Used after compaction: the cached
123
172
  * `<memory>` attachments those slugs lived on are gone, so future turns