@vellumai/assistant 0.11.3 → 0.11.4-dev.202608190019.b94dbf2

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 (596) hide show
  1. package/AGENTS.md +9 -3
  2. package/ARCHITECTURE.md +32 -10
  3. package/docs/architecture/memory.md +43 -5
  4. package/docs/architecture/security.md +96 -152
  5. package/docs/architecture/turn-actor.md +70 -0
  6. package/docs/browser-use-architecture-phase2.md +128 -56
  7. package/docs/flux-turn-detection-spike.md +248 -0
  8. package/docs/guardian-request-flow.md +35 -0
  9. package/docs/stt-provider-onboarding.md +3 -1
  10. package/knip.json +3 -0
  11. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/package.json +1 -0
  12. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/__tests__/url-normalization.test.ts +135 -0
  13. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/channels.ts +11 -0
  14. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
  15. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +60 -0
  16. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/url-normalization.ts +107 -0
  17. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/package.json +1 -0
  18. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/url-normalization.test.ts +135 -0
  19. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/channels.ts +11 -0
  20. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
  21. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +60 -0
  22. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/url-normalization.ts +107 -0
  23. package/node_modules/@vellumai/gateway-client/src/admission-policy-contract.ts +34 -0
  24. package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +166 -1
  25. package/node_modules/@vellumai/gateway-client/src/index.ts +2 -0
  26. package/node_modules/@vellumai/service-contracts/package.json +1 -0
  27. package/node_modules/@vellumai/service-contracts/src/__tests__/url-normalization.test.ts +135 -0
  28. package/node_modules/@vellumai/service-contracts/src/channels.ts +11 -0
  29. package/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
  30. package/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +60 -0
  31. package/node_modules/@vellumai/service-contracts/src/url-normalization.ts +107 -0
  32. package/openapi.yaml +289 -113
  33. package/package.json +1 -1
  34. package/scripts/voice-ttft-spike.ts +3 -3
  35. package/scripts/write-plugin-api-shim.ts +10 -0
  36. package/src/__tests__/app-compiler.test.ts +38 -3
  37. package/src/__tests__/app-control-flow.test.ts +1 -0
  38. package/src/__tests__/approval-routes-http.test.ts +2 -2
  39. package/src/__tests__/assistant-feature-flag-guard.test.ts +25 -3
  40. package/src/__tests__/attachments-store.test.ts +21 -12
  41. package/src/__tests__/byok-default-profile-ensure.test.ts +17 -0
  42. package/src/__tests__/call-setup-flow-name-capture.test.ts +0 -1
  43. package/src/__tests__/call-site-routing-provider.test.ts +1 -1
  44. package/src/__tests__/channel-availability-routes.test.ts +14 -1
  45. package/src/__tests__/channel-capabilities-dedupe.test.ts +214 -0
  46. package/src/__tests__/channel-delivery-store.test.ts +14 -14
  47. package/src/__tests__/channel-inbound-disk-pressure.test.ts +4 -4
  48. package/src/__tests__/channel-setup-panel-ack.test.ts +1 -1
  49. package/src/__tests__/checker.test.ts +20 -314
  50. package/src/__tests__/cli-memory-v2-reembed-skills.test.ts +6 -2
  51. package/src/__tests__/compaction-events.test.ts +8 -10
  52. package/src/__tests__/config-loader-backfill.test.ts +3 -3
  53. package/src/__tests__/config-schema.test.ts +25 -10
  54. package/src/__tests__/context-search-memory-v2-source.test.ts +0 -1
  55. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +8 -11
  56. package/src/__tests__/conversation-agent-loop-overflow.test.ts +8 -11
  57. package/src/__tests__/conversation-agent-loop.test.ts +32 -21
  58. package/src/__tests__/conversation-attachments.test.ts +0 -1
  59. package/src/__tests__/conversation-attention-store.test.ts +63 -0
  60. package/src/__tests__/conversation-confirmation-signals.test.ts +112 -0
  61. package/src/__tests__/conversation-delete-schedule-cleanup.test.ts +0 -4
  62. package/src/__tests__/conversation-fork-crud.test.ts +69 -0
  63. package/src/__tests__/conversation-fork-referential.test.ts +67 -0
  64. package/src/__tests__/conversation-fork-retrospective.test.ts +24 -0
  65. package/src/__tests__/conversation-load-history-repair.test.ts +209 -0
  66. package/src/__tests__/conversation-notifiers-provenance.test.ts +59 -0
  67. package/src/__tests__/conversation-queue.test.ts +154 -4
  68. package/src/__tests__/conversation-routes-disk-view.test.ts +1 -1
  69. package/src/__tests__/conversation-routes-enabled-plugins.test.ts +1 -1
  70. package/src/__tests__/conversation-routes-guardian-reply.test.ts +9 -9
  71. package/src/__tests__/conversation-routes-hidden-queue.test.ts +1 -1
  72. package/src/__tests__/conversation-routes-slash-commands.test.ts +1 -1
  73. package/src/__tests__/conversation-runtime-assembly.test.ts +187 -102
  74. package/src/__tests__/conversation-runtime-workspace.test.ts +14 -10
  75. package/src/__tests__/conversation-slash-queue.test.ts +3 -0
  76. package/src/__tests__/conversation-surfaces-action-delivery.test.ts +1 -0
  77. package/src/__tests__/conversation-surfaces-activation-emit.test.ts +1 -0
  78. package/src/__tests__/conversation-surfaces-app-control.test.ts +1 -0
  79. package/src/__tests__/conversation-surfaces-app-open.test.ts +1 -1
  80. package/src/__tests__/conversation-surfaces-data-persist.test.ts +1 -1
  81. package/src/__tests__/conversation-surfaces-history-restored-completion.test.ts +21 -14
  82. package/src/__tests__/conversation-surfaces-queued-emit.test.ts +1 -0
  83. package/src/__tests__/conversation-surfaces-standalone-payloads.test.ts +1 -0
  84. package/src/__tests__/conversation-surfaces-standalone.test.ts +1 -0
  85. package/src/__tests__/conversation-surfaces-state-update.test.ts +1 -1
  86. package/src/__tests__/conversation-surfaces-table-action.test.ts +1 -1
  87. package/src/__tests__/conversation-surfaces-task-progress.test.ts +1 -1
  88. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +1 -1
  89. package/src/__tests__/conversation-tool-setup-attribution.test.ts +1 -1
  90. package/src/__tests__/credential-prompt-route.test.ts +7 -10
  91. package/src/__tests__/credential-security-invariants.test.ts +1 -0
  92. package/src/__tests__/cu-unified-flow.test.ts +1 -0
  93. package/src/__tests__/custom-profile-ensure.test.ts +5 -1
  94. package/src/__tests__/default-plugin-names-guard.test.ts +12 -1
  95. package/src/__tests__/discord-access-request-privacy.test.ts +5 -1
  96. package/src/__tests__/discord-requester-notice-privacy.test.ts +3 -3
  97. package/src/__tests__/document-append-idempotency.test.ts +233 -0
  98. package/src/__tests__/document-sync-tags.test.ts +0 -75
  99. package/src/__tests__/dynamic-skill-background-guard.test.ts +0 -2
  100. package/src/__tests__/edit-propagation.test.ts +107 -11
  101. package/src/__tests__/file-ops-service.test.ts +163 -30
  102. package/src/__tests__/filesystem-tools.test.ts +23 -24
  103. package/src/__tests__/gateway-only-guard.test.ts +2 -5
  104. package/src/__tests__/helpers/gateway-classify-mock.ts +27 -6
  105. package/src/__tests__/helpers/mock-actor-context.ts +49 -0
  106. package/src/__tests__/helpers/mock-conversation.ts +13 -1
  107. package/src/__tests__/host-file-read-tool.test.ts +16 -19
  108. package/src/__tests__/http-user-message-parity.test.ts +1 -1
  109. package/src/__tests__/init-feature-flag-overrides.test.ts +49 -0
  110. package/src/__tests__/injector-chain.test.ts +63 -41
  111. package/src/__tests__/injector-disk-pressure.test.ts +11 -23
  112. package/src/__tests__/inline-skill-load-permissions.test.ts +47 -36
  113. package/src/__tests__/llm-context-resolution.test.ts +73 -1
  114. package/src/__tests__/llm-schema.test.ts +5 -2
  115. package/src/__tests__/managed-skill-lifecycle.test.ts +7 -0
  116. package/src/__tests__/mcp-list-plugin-servers.test.ts +250 -0
  117. package/src/__tests__/media-generate-image.test.ts +131 -21
  118. package/src/__tests__/memory-retrieval-hook.test.ts +100 -7
  119. package/src/__tests__/messages-read-boundary-guard.test.ts +134 -0
  120. package/src/__tests__/mtime-cache.test.ts +3 -1
  121. package/src/__tests__/non-member-access-request.test.ts +0 -20
  122. package/src/__tests__/outbound-slack-persistence.test.ts +40 -1
  123. package/src/__tests__/platform-bash-auto-approve.test.ts +0 -4
  124. package/src/__tests__/plugin-api-store-credential.test.ts +261 -0
  125. package/src/__tests__/plugin-api-webhook-url.test.ts +10 -7
  126. package/src/__tests__/plugin-disabled-state.test.ts +2 -0
  127. package/src/__tests__/plugin-effective-enabled-set.test.ts +55 -3
  128. package/src/__tests__/plugin-execution-context.test.ts +73 -0
  129. package/src/__tests__/plugin-import-boundary-guard.test.ts +5 -1
  130. package/src/__tests__/plugin-import-boundary-reverse-guard.test.ts +7 -4
  131. package/src/__tests__/plugin-secret-pattern-contribution.test.ts +1 -1
  132. package/src/__tests__/post-compaction-reinjection-idempotency.test.ts +14 -7
  133. package/src/__tests__/provider-commit-message-generator.test.ts +20 -0
  134. package/src/__tests__/proxy-approval-callback.test.ts +1 -0
  135. package/src/__tests__/qdrant-manager.test.ts +14 -1
  136. package/src/__tests__/reaction-persistence.test.ts +200 -7
  137. package/src/__tests__/require-fresh-approval.test.ts +0 -4
  138. package/src/__tests__/risk-classification-boundary-guard.test.ts +115 -0
  139. package/src/__tests__/run-conversation-turn-persistence.test.ts +460 -105
  140. package/src/__tests__/run-due-schedules.test.ts +21 -0
  141. package/src/__tests__/scaffold-managed-skill-tool.test.ts +187 -18
  142. package/src/__tests__/schedule-routes.test.ts +23 -0
  143. package/src/__tests__/schedule-store.test.ts +17 -0
  144. package/src/__tests__/scoped-approval-grants.test.ts +11 -6
  145. package/src/__tests__/secret-ingress-channel.test.ts +0 -1
  146. package/src/__tests__/secret-ingress-http.test.ts +1 -1
  147. package/src/__tests__/send-endpoint-busy.test.ts +3 -3
  148. package/src/__tests__/skills.test.ts +32 -0
  149. package/src/__tests__/slack-edit-ordering-characterization.test.ts +0 -1
  150. package/src/__tests__/starter-task-flow.test.ts +5 -4
  151. package/src/__tests__/subagent-call-site-routing.test.ts +31 -19
  152. package/src/__tests__/subagent-fork-prompt-role.test.ts +1 -1
  153. package/src/__tests__/subagent-spawn-and-await.test.ts +18 -17
  154. package/src/__tests__/subagent-tool-gate-mode.test.ts +1 -1
  155. package/src/__tests__/subagent-tools.test.ts +81 -101
  156. package/src/__tests__/surface-completion-in-flight-snapshot.test.ts +1 -0
  157. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +1 -1
  158. package/src/__tests__/tool-executor-lifecycle-events.test.ts +132 -5
  159. package/src/__tests__/tool-executor.test.ts +68 -39
  160. package/src/__tests__/turn-events-store.test.ts +43 -0
  161. package/src/__tests__/ui-choice-copy-surfaces.test.ts +1 -1
  162. package/src/__tests__/ui-shape-teaching.test.ts +33 -0
  163. package/src/__tests__/ui-visual-surface.test.ts +1 -1
  164. package/src/__tests__/ui-voice-picker-surface.test.ts +128 -0
  165. package/src/__tests__/ui-work-result-surface.test.ts +1 -1
  166. package/src/__tests__/user-plugin-loader.test.ts +3 -1
  167. package/src/__tests__/verification-control-plane-policy.test.ts +0 -2
  168. package/src/__tests__/visible-app-context.test.ts +16 -9
  169. package/src/__tests__/voice-config-update.test.ts +40 -0
  170. package/src/__tests__/voice-scoped-grant-consumer.test.ts +5 -3
  171. package/src/__tests__/voice-session-bridge.test.ts +85 -29
  172. package/src/__tests__/worker-entrypoint-guards.test.ts +54 -0
  173. package/src/__tests__/worker-plugin-surface.test.ts +77 -0
  174. package/src/__tests__/workspace-migration-142-consolidate-voice-front-door.test.ts +158 -0
  175. package/src/__tests__/workspace-migration-143-repair-deprecated-codex-model-id.test.ts +133 -0
  176. package/src/__tests__/workspace-migration-144-convert-stranded-subscription-openai-profiles.test.ts +316 -0
  177. package/src/__tests__/workspace-migration-145-collapse-profile-bindings-to-entries.test.ts +325 -0
  178. package/src/__tests__/workspace-migration-146-repair-retired-fireworks-deepseek-flash-model-id.test.ts +235 -0
  179. package/src/acp/__tests__/acp-claude-oauth.test.ts +10 -2
  180. package/src/acp/__tests__/auth-required.test.ts +161 -0
  181. package/src/acp/acp-claude-oauth.ts +19 -2
  182. package/src/acp/agent-process.test.ts +100 -0
  183. package/src/acp/agent-process.ts +29 -26
  184. package/src/acp/auth-required.ts +102 -0
  185. package/src/acp/session-manager.test.ts +119 -0
  186. package/src/acp/session-manager.ts +76 -3
  187. package/src/api/events/acp-auth-required.ts +55 -0
  188. package/src/api/events/host-file.ts +2 -2
  189. package/src/api/index.ts +7 -0
  190. package/src/api/surfaces.ts +12 -3
  191. package/src/apps/app-store.ts +3 -0
  192. package/src/bundler/package-resolver.ts +2 -30
  193. package/src/calls/__tests__/voice-session-bridge.test.ts +194 -11
  194. package/src/calls/__tests__/voice-triage-escalate.test.ts +102 -0
  195. package/src/calls/call-controller.ts +19 -3
  196. package/src/calls/call-setup-flow.ts +0 -1
  197. package/src/calls/media-stream-stt-session.ts +15 -0
  198. package/src/calls/voice-session-bridge.ts +115 -41
  199. package/src/calls/voice-triage-escalate.ts +105 -2
  200. package/src/channels/__tests__/plugin-channel-declarations.test.ts +161 -0
  201. package/src/channels/config.ts +13 -0
  202. package/src/channels/plugin-channel-declarations.ts +108 -0
  203. package/src/channels/types.ts +30 -0
  204. package/src/cli/AGENTS.md +5 -2
  205. package/src/cli/bundled-modules.ts +29 -0
  206. package/src/cli/commands/credentials.help.ts +2 -2
  207. package/src/cli/commands/db/repair.ts +4 -8
  208. package/src/cli/commands/domain.ts +6 -3
  209. package/src/cli/commands/email.ts +6 -3
  210. package/src/cli/commands/inference-providers.ts +1 -1
  211. package/src/cli/commands/keys.ts +8 -3
  212. package/src/cli/commands/mcp.help.ts +13 -4
  213. package/src/cli/commands/mcp.ts +9 -0
  214. package/src/cli/commands/memory/__tests__/memory-v2.test.ts +57 -7
  215. package/src/cli/commands/memory/__tests__/memory-v3.test.ts +128 -5
  216. package/src/cli/commands/memory/index.help.ts +84 -28
  217. package/src/cli/commands/memory/index.ts +2 -0
  218. package/src/cli/commands/memory/memory-v2.ts +58 -54
  219. package/src/cli/commands/memory/memory-v3.ts +64 -0
  220. package/src/cli/commands/memory/memory-validate.ts +18 -0
  221. package/src/cli/commands/plugins.ts +85 -36
  222. package/src/cli/commands/schedules.ts +35 -1
  223. package/src/cli/commands/stt.help.ts +27 -2
  224. package/src/cli/commands/trust.ts +3 -14
  225. package/src/cli/lib/__tests__/upgrade-plugin.test.ts +39 -0
  226. package/src/cli/lib/bundled-marketplace.json +14 -1
  227. package/src/cli/lib/upgrade-plugin.ts +42 -0
  228. package/src/config/__tests__/balanced-model-experiment.test.ts +278 -0
  229. package/src/config/__tests__/default-profile-catalog.test.ts +34 -2
  230. package/src/config/__tests__/default-provider.test.ts +6 -1
  231. package/src/config/__tests__/profile-materialization.test.ts +75 -19
  232. package/src/config/assistant-feature-flags.ts +36 -15
  233. package/src/config/balanced-model-experiment.ts +35 -0
  234. package/src/config/bundled-skills/acp/SKILL.md +6 -7
  235. package/src/config/bundled-skills/document-editor/SKILL.md +2 -2
  236. package/src/config/bundled-skills/document-editor/TOOLS.json +2 -2
  237. package/src/config/bundled-skills/image-studio/SKILL.md +5 -4
  238. package/src/config/bundled-skills/image-studio/TOOLS.json +1 -1
  239. package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +101 -0
  240. package/src/config/bundled-skills/media-processing/services/preprocess.ts +14 -4
  241. package/src/config/bundled-skills/settings/TOOLS.json +3 -3
  242. package/src/config/bundled-skills/settings/tools/navigate-settings-tab.test.ts +65 -0
  243. package/src/config/bundled-skills/settings/tools/navigate-settings-tab.ts +7 -1
  244. package/src/config/bundled-skills/settings/tools/shared.ts +16 -0
  245. package/src/config/bundled-skills/settings/tools/voice-config-update.ts +19 -1
  246. package/src/config/bundled-skills/skill-management/TOOLS.json +9 -3
  247. package/src/config/bundled-skills/subagent/SKILL.md +17 -12
  248. package/src/config/bundled-skills/subagent/TOOLS.json +4 -4
  249. package/src/config/bundled-skills/transcribe/tools/transcribe-media.test.ts +22 -1
  250. package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +9 -2
  251. package/src/config/call-site-defaults.ts +11 -5
  252. package/src/config/default-profile-catalog.ts +179 -16
  253. package/src/config/default-profile-names.ts +4 -1
  254. package/src/config/default-provider-resolution.ts +4 -0
  255. package/src/config/feature-flag-registry.json +12 -12
  256. package/src/config/llm-context-resolution.ts +11 -3
  257. package/src/config/llm-resolver.ts +28 -1
  258. package/src/config/profile-materialization.ts +70 -22
  259. package/src/config/schemas/__tests__/live-voice.test.ts +107 -4
  260. package/src/config/schemas/call-site-catalog.ts +4 -4
  261. package/src/config/schemas/live-voice.ts +57 -23
  262. package/src/config/schemas/llm.ts +59 -32
  263. package/src/config/schemas/mcp.ts +23 -0
  264. package/src/config/schemas/plugin-updates.ts +6 -2
  265. package/src/config/schemas/stt.ts +1 -0
  266. package/src/config/skills.ts +9 -2
  267. package/src/context/outbound-sanitize.ts +96 -1
  268. package/src/daemon/__tests__/conversation-surfaces-launch.test.ts +1 -1
  269. package/src/daemon/__tests__/plugin-mcp-reconcile.test.ts +82 -0
  270. package/src/daemon/conversation-agent-loop-handlers.ts +15 -10
  271. package/src/daemon/conversation-agent-loop.ts +31 -19
  272. package/src/daemon/conversation-messaging.ts +5 -1
  273. package/src/daemon/conversation-notifiers.ts +20 -10
  274. package/src/daemon/conversation-process.ts +20 -6
  275. package/src/daemon/conversation-runtime-assembly.ts +12 -6
  276. package/src/daemon/conversation-store.ts +14 -4
  277. package/src/daemon/conversation-surfaces.ts +75 -26
  278. package/src/daemon/conversation-tool-setup.ts +3 -6
  279. package/src/daemon/conversation.ts +154 -51
  280. package/src/daemon/doordash-steps.ts +2 -2
  281. package/src/daemon/lifecycle.ts +14 -1
  282. package/src/daemon/mcp-reload-service.ts +36 -6
  283. package/src/daemon/process-message.ts +13 -26
  284. package/src/daemon/providers-setup.ts +6 -3
  285. package/src/daemon/trust-context-types.ts +29 -0
  286. package/src/daemon/trust-context.ts +12 -7
  287. package/src/daemon/wake-conversation-ops.ts +3 -2
  288. package/src/daemon/windows-compiled-entry.ts +4 -0
  289. package/src/documents/document-store.ts +143 -240
  290. package/src/hooks/hook-loader.ts +3 -3
  291. package/src/hooks/registry.ts +103 -45
  292. package/src/hooks/types.ts +5 -0
  293. package/src/inbound/__tests__/oauth-callback-url.test.ts +83 -0
  294. package/src/inbound/oauth-callback-url.ts +61 -0
  295. package/src/ipc/gateway-client.test.ts +1 -1
  296. package/src/ipc/gateway-client.ts +14 -21
  297. package/src/ipc/gateway-flag-listener.ts +17 -3
  298. package/src/live-voice/__tests__/live-voice-agent-turn.test.ts +1 -104
  299. package/src/live-voice/__tests__/live-voice-events.test.ts +7 -8
  300. package/src/live-voice/__tests__/live-voice-flux-turn-end.test.ts +1050 -0
  301. package/src/live-voice/__tests__/live-voice-metrics.test.ts +115 -8
  302. package/src/live-voice/__tests__/live-voice-photo.test.ts +100 -0
  303. package/src/live-voice/__tests__/live-voice-progress.test.ts +60 -194
  304. package/src/live-voice/__tests__/live-voice-stt.test.ts +14 -0
  305. package/src/live-voice/__tests__/live-voice-triage-escalate.test.ts +29 -0
  306. package/src/live-voice/__tests__/live-voice-tts-session.test.ts +0 -483
  307. package/src/live-voice/__tests__/live-voice-vad.test.ts +0 -16
  308. package/src/live-voice/__tests__/progress-narration.test.ts +214 -0
  309. package/src/live-voice/live-voice-archive.ts +2 -0
  310. package/src/live-voice/live-voice-manager.ts +16 -3
  311. package/src/live-voice/live-voice-metrics.ts +57 -32
  312. package/src/live-voice/live-voice-photo.ts +1 -2
  313. package/src/live-voice/live-voice-session.ts +598 -323
  314. package/src/live-voice/progress-narration.ts +277 -0
  315. package/src/live-voice/protocol.ts +21 -1
  316. package/src/live-voice/windows-compiled-live-voice.ts +4 -0
  317. package/src/mcp/__tests__/effective-config.test.ts +238 -0
  318. package/src/mcp/__tests__/mcp-auth-orchestrator.test.ts +0 -1
  319. package/src/mcp/__tests__/mcp-oauth-client-registration.test.ts +200 -0
  320. package/src/mcp/__tests__/mcp-oauth-provider.test.ts +9 -9
  321. package/src/mcp/__tests__/plugin-server-credential-isolation.test.ts +95 -0
  322. package/src/mcp/client.ts +16 -11
  323. package/src/mcp/effective-config.ts +113 -0
  324. package/src/mcp/manager.ts +11 -6
  325. package/src/mcp/mcp-auth-orchestrator.ts +13 -22
  326. package/src/mcp/mcp-oauth-provider.ts +205 -240
  327. package/src/monitoring/__tests__/plugin-auto-update.test.ts +166 -3
  328. package/src/monitoring/control.ts +1 -0
  329. package/src/monitoring/db-integrity-sample.ts +4 -5
  330. package/src/monitoring/plugin-auto-update.ts +128 -24
  331. package/src/notifications/AGENTS.md +2 -0
  332. package/src/notifications/approval-card-data.ts +33 -0
  333. package/src/notifications/signal.ts +1 -0
  334. package/src/permissions/AGENTS.md +16 -0
  335. package/src/permissions/checker.test.ts +75 -122
  336. package/src/permissions/checker.ts +44 -560
  337. package/src/permissions/confirmation-guardian-request.test.ts +15 -11
  338. package/src/permissions/confirmation-guardian-request.ts +2 -2
  339. package/src/permissions/prompter.ts +1 -5
  340. package/src/permissions/question-guardian-request.test.ts +14 -6
  341. package/src/permissions/question-guardian-request.ts +1 -2
  342. package/src/persistence/attachments-store.ts +8 -1
  343. package/src/persistence/bookmark-crud.ts +3 -7
  344. package/src/persistence/conversation-attention-store.ts +16 -45
  345. package/src/persistence/conversation-crud.ts +33 -4
  346. package/src/persistence/conversation-lineage.ts +9 -0
  347. package/src/persistence/conversation-queries.ts +182 -52
  348. package/src/persistence/delivery-crud.ts +150 -36
  349. package/src/persistence/embeddings/qdrant-manager.ts +84 -49
  350. package/src/persistence/external-conversation-store.ts +32 -4
  351. package/src/persistence/llm-request-log-store.ts +4 -10
  352. package/src/persistence/llm-usage-store.ts +8 -3
  353. package/src/persistence/message-reads.test.ts +197 -0
  354. package/src/persistence/message-reads.ts +211 -0
  355. package/src/persistence/migrations/360-add-document-workspace-path.ts +5 -14
  356. package/src/persistence/migrations/366-chatgpt-subscription-row-identity.test.ts +120 -0
  357. package/src/persistence/migrations/366-chatgpt-subscription-row-identity.ts +62 -0
  358. package/src/persistence/real-user-turn-filter.ts +27 -3
  359. package/src/persistence/schema/documents.ts +4 -4
  360. package/src/persistence/steps.ts +9 -0
  361. package/src/plugin-api/__tests__/oauth-callback-url-export.test.ts +29 -0
  362. package/src/plugin-api/constants.ts +8 -0
  363. package/src/plugin-api/conversation-turn.ts +195 -5
  364. package/src/plugin-api/index.ts +38 -6
  365. package/src/plugin-api/resolve-credential.ts +3 -2
  366. package/src/plugin-api/store-credential.ts +146 -0
  367. package/src/plugin-api/vision-support.test.ts +1 -1
  368. package/src/plugin-api/webhook-url.ts +13 -11
  369. package/src/plugins/__tests__/mcp-servers.test.ts +371 -0
  370. package/src/plugins/defaults/main.ts +21 -7
  371. package/src/plugins/defaults/memory/AGENTS.md +15 -3
  372. package/src/plugins/defaults/memory/__tests__/buffer-format.test.ts +204 -0
  373. package/src/plugins/defaults/memory/__tests__/memory-retrospective-accounting.test.ts +72 -0
  374. package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +4 -1
  375. package/src/plugins/defaults/memory/__tests__/memory-retrospective-provider-path.test.ts +4 -1
  376. package/src/plugins/defaults/memory/__tests__/memory-tier-boundary-guard.test.ts +0 -1
  377. package/src/plugins/defaults/memory/buffer-format.ts +165 -0
  378. package/src/plugins/defaults/memory/context-search/sources/conversations.ts +6 -0
  379. package/src/plugins/defaults/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +77 -0
  380. package/src/plugins/defaults/memory/graph/conversation-graph-memory.ts +24 -6
  381. package/src/plugins/defaults/memory/graph/image-ref-utils.ts +3 -0
  382. package/src/plugins/defaults/memory/graph/tool-handlers.ts +1 -30
  383. package/src/plugins/defaults/memory/graph-topology/pending-buffer.test.ts +34 -0
  384. package/src/plugins/defaults/memory/graph-topology/pending-buffer.ts +17 -24
  385. package/src/plugins/defaults/memory/hooks/post-compact.ts +1 -4
  386. package/src/plugins/defaults/memory/hooks/user-prompt-submit.ts +53 -5
  387. package/src/plugins/defaults/memory/indexer.ts +3 -1
  388. package/src/plugins/defaults/memory/memory-retrospective-accounting.ts +19 -7
  389. package/src/plugins/defaults/memory/memory-retrospective-job.ts +4 -4
  390. package/src/plugins/defaults/memory/src/__tests__/memory-v3-gate-stats.test.ts +281 -0
  391. package/src/plugins/defaults/memory/src/memory-v2-routes.ts +12 -10
  392. package/src/plugins/defaults/memory/src/memory-v3-routes.ts +207 -0
  393. package/src/plugins/defaults/memory/substrate/__tests__/consolidation-job.test.ts +147 -0
  394. package/src/plugins/defaults/memory/substrate/__tests__/consolidation-prompt-flag-gating-guard.test.ts +8 -0
  395. package/src/plugins/defaults/memory/substrate/__tests__/edge-index.test.ts +0 -30
  396. package/src/plugins/defaults/memory/substrate/__tests__/ingest.test.ts +73 -1
  397. package/src/plugins/defaults/memory/substrate/__tests__/page-index.test.ts +37 -0
  398. package/src/plugins/defaults/memory/substrate/__tests__/page-links.test.ts +208 -0
  399. package/src/plugins/defaults/memory/substrate/__tests__/prompts-consolidation.test.ts +158 -1
  400. package/src/plugins/defaults/memory/substrate/__tests__/static-context.test.ts +195 -58
  401. package/src/plugins/defaults/memory/substrate/consolidation-job.ts +93 -24
  402. package/src/plugins/defaults/memory/substrate/edge-index.ts +0 -31
  403. package/src/plugins/defaults/memory/substrate/ingest.ts +59 -0
  404. package/src/plugins/defaults/memory/substrate/page-index.ts +35 -8
  405. package/src/plugins/defaults/memory/substrate/page-links.ts +133 -0
  406. package/src/plugins/defaults/memory/substrate/page-store.ts +10 -0
  407. package/src/plugins/defaults/memory/substrate/prompts/consolidation.ts +124 -34
  408. package/src/plugins/defaults/memory/substrate/skill-content.ts +8 -1
  409. package/src/plugins/defaults/memory/substrate/static-context.ts +160 -33
  410. package/src/plugins/defaults/memory/substrate/sweep-job.ts +2 -4
  411. package/src/plugins/defaults/memory/v1/graph/extraction.ts +3 -1
  412. package/src/plugins/defaults/memory/v3/__tests__/injection.test.ts +18 -0
  413. package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +66 -1
  414. package/src/plugins/defaults/memory/v3/card.ts +9 -9
  415. package/src/plugins/defaults/memory/v3/core-set.test.ts +7 -0
  416. package/src/plugins/defaults/memory/v3/core-set.ts +5 -1
  417. package/src/plugins/defaults/memory/v3/edge.ts +13 -57
  418. package/src/plugins/defaults/memory/v3/injector.ts +8 -0
  419. package/src/plugins/defaults/memory/v3/prune.ts +2 -0
  420. package/src/plugins/defaults/memory/v3/selection-log-store.ts +2 -0
  421. package/src/plugins/defaults/memory/v3/shadow-plugin.ts +23 -9
  422. package/src/plugins/defaults/memory/worker-control.ts +1 -0
  423. package/src/plugins/defaults/memory/worker.ts +6 -3
  424. package/src/plugins/defaults/worker-entrypoints.ts +3 -0
  425. package/src/plugins/external-plugin-loader.ts +47 -0
  426. package/src/plugins/mcp-servers.ts +361 -0
  427. package/src/plugins/mtime-cache.ts +37 -46
  428. package/src/plugins/pipeline.ts +14 -7
  429. package/src/plugins/plugin-execution-context.ts +35 -11
  430. package/src/plugins/worker-plugin-surface.ts +33 -0
  431. package/src/prompts/templates/system-sections.ts +0 -7
  432. package/src/providers/__tests__/connection-model-compat.test.ts +1 -1
  433. package/src/providers/__tests__/context-overflow-error.test.ts +24 -0
  434. package/src/providers/__tests__/dispatch-connection-routing.test.ts +214 -2
  435. package/src/providers/__tests__/preflight-resolved-config.test.ts +57 -0
  436. package/src/providers/__tests__/retry-callsite.test.ts +25 -2
  437. package/src/providers/anthropic/__tests__/pause-turn-continuation.test.ts +170 -0
  438. package/src/providers/anthropic/client.ts +332 -241
  439. package/src/providers/call-site-routing.ts +30 -3
  440. package/src/providers/connection-resolution.ts +194 -11
  441. package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +4 -0
  442. package/src/providers/inference/adapter-factory.ts +3 -0
  443. package/src/providers/inference/auth.ts +6 -0
  444. package/src/providers/inference/connection-availability.ts +24 -2
  445. package/src/providers/inference/connections.ts +2 -0
  446. package/src/providers/model-catalog.ts +3 -3
  447. package/src/providers/model-intents.ts +28 -8
  448. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +163 -40
  449. package/src/providers/openai/__tests__/tool-choice-mapping.test.ts +125 -2
  450. package/src/providers/openai/chat-completions-provider.ts +133 -36
  451. package/src/providers/openai/codex-models.ts +2 -1
  452. package/src/providers/provider-send-message.ts +32 -3
  453. package/src/providers/speech-to-text/__tests__/deepgram-flux-frames.test.ts +433 -0
  454. package/src/providers/speech-to-text/__tests__/deepgram-flux-realtime.test.ts +625 -0
  455. package/src/providers/speech-to-text/__tests__/provider-catalog.test.ts +34 -0
  456. package/src/providers/speech-to-text/__tests__/resolve.test.ts +285 -6
  457. package/src/providers/speech-to-text/deepgram-flux-frames.ts +395 -0
  458. package/src/providers/speech-to-text/deepgram-flux-realtime.ts +679 -0
  459. package/src/providers/speech-to-text/provider-catalog.ts +99 -8
  460. package/src/providers/speech-to-text/resolve.ts +25 -2
  461. package/src/routes/control.ts +1 -0
  462. package/src/routes/route-host-client.ts +1 -0
  463. package/src/routes/route-host-protocol.ts +7 -0
  464. package/src/routes/worker.ts +48 -15
  465. package/src/runtime/AGENTS.md +16 -17
  466. package/src/runtime/access-request-helper.ts +9 -12
  467. package/src/runtime/agent-wake.ts +18 -15
  468. package/src/runtime/effective-capabilities.test.ts +43 -0
  469. package/src/runtime/effective-capabilities.ts +27 -0
  470. package/src/runtime/guardian-reply-router.ts +6 -2
  471. package/src/runtime/pre-first-message-gate.ts +4 -0
  472. package/src/runtime/routes/__tests__/acp-claude-auth-routes.test.ts +12 -4
  473. package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +522 -2
  474. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +52 -0
  475. package/src/runtime/routes/__tests__/default-provider-routes.test.ts +61 -0
  476. package/src/runtime/routes/__tests__/inference-profiles-routes.test.ts +44 -0
  477. package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +102 -1
  478. package/src/runtime/routes/__tests__/plugins-routes.test.ts +132 -35
  479. package/src/runtime/routes/__tests__/schedule-routes-disarm-reason.test.ts +215 -0
  480. package/src/runtime/routes/__tests__/stt-routes.test.ts +25 -0
  481. package/src/runtime/routes/__tests__/surface-action-routes.test.ts +55 -6
  482. package/src/runtime/routes/__tests__/user-route-dispatcher-host.test.ts +52 -1
  483. package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +133 -1
  484. package/src/runtime/routes/channel-availability-routes.ts +32 -14
  485. package/src/runtime/routes/channel-route-shared.ts +0 -6
  486. package/src/runtime/routes/chatgpt-subscription-auth-routes.ts +6 -6
  487. package/src/runtime/routes/conversation-list-routes.ts +181 -23
  488. package/src/runtime/routes/conversation-management-routes.ts +17 -12
  489. package/src/runtime/routes/conversation-query-routes.ts +40 -27
  490. package/src/runtime/routes/conversation-routes.ts +43 -29
  491. package/src/runtime/routes/credential-prompt-routes.ts +4 -7
  492. package/src/runtime/routes/credential-routes.ts +26 -82
  493. package/src/runtime/routes/default-provider-routes.ts +15 -0
  494. package/src/runtime/routes/documents-routes.ts +3 -222
  495. package/src/runtime/routes/inbound-message-handler.ts +21 -45
  496. package/src/runtime/routes/inbound-stages/acl-enforcement.test.ts +0 -1
  497. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +0 -9
  498. package/src/runtime/routes/inbound-stages/admission-policy.ts +1 -17
  499. package/src/runtime/routes/inbound-stages/bootstrap-intercept.test.ts +0 -1
  500. package/src/runtime/routes/inbound-stages/bootstrap-intercept.ts +2 -3
  501. package/src/runtime/routes/inbound-stages/edit-intercept.ts +101 -79
  502. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.test.ts +0 -1
  503. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +12 -7
  504. package/src/runtime/routes/inbound-stages/reaction-intercept.test.ts +91 -8
  505. package/src/runtime/routes/inbound-stages/reaction-intercept.ts +72 -79
  506. package/src/runtime/routes/inbound-stages/secret-ingress-check.ts +2 -3
  507. package/src/runtime/routes/inference-profiles-routes.ts +20 -11
  508. package/src/runtime/routes/inference-provider-connection-routes.ts +77 -15
  509. package/src/runtime/routes/log-export-routes.ts +3 -0
  510. package/src/runtime/routes/mcp-auth-routes.ts +148 -57
  511. package/src/runtime/routes/playground/__tests__/inject-failures.test.ts +2 -0
  512. package/src/runtime/routes/playground/__tests__/reset-circuit.test.ts +3 -0
  513. package/src/runtime/routes/playground/inject-failures.ts +2 -2
  514. package/src/runtime/routes/playground/reset-circuit.ts +1 -1
  515. package/src/runtime/routes/plugins-routes.ts +38 -8
  516. package/src/runtime/routes/schedule-routes.ts +94 -7
  517. package/src/runtime/routes/settings-routes.ts +6 -6
  518. package/src/runtime/routes/stt-routes.ts +31 -25
  519. package/src/runtime/routes/surface-action-routes.ts +20 -19
  520. package/src/runtime/routes/surface-conversation-resolver.ts +3 -0
  521. package/src/runtime/routes/user-route-dispatcher.ts +69 -15
  522. package/src/runtime/routes/user-route-import.ts +108 -0
  523. package/src/runtime/routes/user-route-resolution.ts +18 -1
  524. package/src/runtime/routes/workspace-routes.ts +0 -9
  525. package/src/runtime/routes/workspace-utils.ts +3 -13
  526. package/src/runtime/services/conversation-serializer.ts +7 -2
  527. package/src/schedule/__tests__/plugin-schedule-declarations.test.ts +68 -5
  528. package/src/schedule/__tests__/plugin-schedule-reconciler.test.ts +81 -0
  529. package/src/schedule/plugin-schedule-availability.ts +58 -0
  530. package/src/schedule/plugin-schedule-declarations.ts +23 -27
  531. package/src/schedule/plugin-schedule-reconciler.ts +12 -3
  532. package/src/schedule/schedule-store.ts +5 -1
  533. package/src/schedule/scheduler.ts +9 -4
  534. package/src/schedule/worker-control.ts +1 -0
  535. package/src/schedule/worker.ts +6 -0
  536. package/src/security/oauth2.ts +6 -22
  537. package/src/stt/__tests__/daemon-batch-transcriber.test.ts +22 -0
  538. package/src/stt/__tests__/types.test.ts +94 -0
  539. package/src/stt/daemon-batch-transcriber.ts +10 -0
  540. package/src/stt/stt-stream-session.ts +8 -4
  541. package/src/stt/types.ts +103 -0
  542. package/src/subagent/__tests__/consult-prompt.test.ts +26 -15
  543. package/src/subagent/consult-context.ts +11 -11
  544. package/src/subagent/consult-prompt.ts +26 -35
  545. package/src/subagent/manager.ts +21 -40
  546. package/src/subagent/notify.ts +7 -1
  547. package/src/subagent/types.ts +15 -12
  548. package/src/tools/__tests__/tool-input-schemas.test.ts +7 -7
  549. package/src/tools/acp/spawn.test.ts +97 -0
  550. package/src/tools/acp/spawn.ts +38 -4
  551. package/src/tools/credentials/ref-parse.ts +35 -0
  552. package/src/tools/credentials/resolve.ts +4 -10
  553. package/src/tools/credentials/store.ts +168 -0
  554. package/src/tools/document/document-tool.ts +12 -3
  555. package/src/tools/executor.ts +53 -15
  556. package/src/tools/filesystem/read.ts +27 -10
  557. package/src/tools/host-filesystem/read.ts +27 -15
  558. package/src/tools/network/url-safety.ts +9 -16
  559. package/src/tools/permission-checker.ts +43 -52
  560. package/src/tools/registry.ts +2 -1
  561. package/src/tools/shared/filesystem/file-ops-service.ts +63 -35
  562. package/src/tools/shared/filesystem/legacy-read-args.ts +22 -0
  563. package/src/tools/shared/filesystem/types.ts +5 -5
  564. package/src/tools/skills/scaffold-managed.ts +25 -7
  565. package/src/tools/subagent/spawn.ts +28 -88
  566. package/src/tools/tool-types.ts +26 -13
  567. package/src/tools/types.ts +6 -6
  568. package/src/tools/ui-surface/surface-shape-docs.ts +12 -1
  569. package/src/tools/workflows/run-workflow.ts +1 -2
  570. package/src/tts/__tests__/reasoning-tag-filter.test.ts +78 -0
  571. package/src/tts/reasoning-tag-filter.ts +89 -0
  572. package/src/util/__tests__/worker-process-command.test.ts +37 -0
  573. package/src/util/logger.ts +16 -0
  574. package/src/util/think-tag-stream.ts +95 -0
  575. package/src/util/worker-process.ts +37 -4
  576. package/src/windows-compiled-cli.ts +32 -0
  577. package/src/windows-compiled-entry.ts +4 -0
  578. package/src/windows-compiled-logger.ts +6 -0
  579. package/src/windows-compiled-worker-entry.ts +29 -0
  580. package/src/workspace/byok-default-profile-ensure.ts +76 -24
  581. package/src/workspace/custom-profile-ensure.ts +4 -24
  582. package/src/workspace/migrations/142-consolidate-voice-front-door.ts +70 -0
  583. package/src/workspace/migrations/143-repair-deprecated-codex-model-id.ts +134 -0
  584. package/src/workspace/migrations/144-convert-stranded-subscription-openai-profiles.ts +265 -0
  585. package/src/workspace/migrations/145-collapse-profile-bindings-to-entries.ts +328 -0
  586. package/src/workspace/migrations/146-repair-retired-fireworks-deepseek-flash-model-id.ts +195 -0
  587. package/src/workspace/migrations/__tests__/141-stt-english-default-to-multilingual.test.ts +0 -10
  588. package/src/workspace/migrations/registry.ts +10 -0
  589. package/src/workspace/provider-commit-message-generator.ts +7 -5
  590. package/src/__tests__/document-workspace-file.test.ts +0 -467
  591. package/src/live-voice/__tests__/front-decision.test.ts +0 -645
  592. package/src/live-voice/front-decision.ts +0 -476
  593. package/src/permissions/ipc-risk-types.ts +0 -143
  594. package/src/permissions/risk-types.ts +0 -76
  595. package/src/subagent/__tests__/consult-transcript.test.ts +0 -184
  596. package/src/subagent/consult-transcript.ts +0 -90
@@ -0,0 +1,208 @@
1
+ /**
2
+ * Tests for `substrate/page-links.ts`: the single owner of `links:` entry and
3
+ * `[[wikilink]]` parsing, plus the dangling-link finder every write-side
4
+ * report uses.
5
+ */
6
+
7
+ import { describe, expect, test } from "bun:test";
8
+
9
+ import {
10
+ extractWikilinkTargets,
11
+ findDanglingLinks,
12
+ LINK_SEPARATOR,
13
+ parseLinkEntry,
14
+ WIKILINK_REGEX,
15
+ wikilinkTarget,
16
+ } from "../page-links.js";
17
+ import { isValidSlug } from "../page-store.js";
18
+
19
+ /** Build a `"<slug> <sep> <why>"` links entry without spelling the separator. */
20
+ const link = (target: string, why?: string): string =>
21
+ why === undefined ? target : `${target}${LINK_SEPARATOR}${why}`;
22
+
23
+ describe("LINK_SEPARATOR", () => {
24
+ test("is space, U+2014 EM DASH, space", () => {
25
+ expect(LINK_SEPARATOR).toHaveLength(3);
26
+ expect(LINK_SEPARATOR.codePointAt(1)).toBe(0x2014);
27
+ expect(LINK_SEPARATOR[0]).toBe(" ");
28
+ expect(LINK_SEPARATOR[2]).toBe(" ");
29
+ });
30
+ });
31
+
32
+ describe("parseLinkEntry", () => {
33
+ test("splits target and description on the first separator", () => {
34
+ expect(parseLinkEntry(link("alice", "the principal"))).toEqual({
35
+ target: "alice",
36
+ description: "the principal",
37
+ });
38
+ });
39
+
40
+ test("keeps later separators inside the description", () => {
41
+ const why = `why${LINK_SEPARATOR}with a second dash`;
42
+ expect(parseLinkEntry(link("alice", why))).toEqual({
43
+ target: "alice",
44
+ description: why,
45
+ });
46
+ });
47
+
48
+ test("a bare slug carries no description; whitespace is trimmed", () => {
49
+ expect(parseLinkEntry(" alice ")).toEqual({
50
+ target: "alice",
51
+ description: undefined,
52
+ });
53
+ });
54
+
55
+ test("an empty annotation after the separator reads as no description", () => {
56
+ expect(parseLinkEntry(`alice${LINK_SEPARATOR} `)).toEqual({
57
+ target: "alice",
58
+ description: undefined,
59
+ });
60
+ });
61
+ });
62
+
63
+ describe("wikilinkTarget / extractWikilinkTargets", () => {
64
+ test("strips |display and #anchor suffixes and trims", () => {
65
+ expect(wikilinkTarget("alice")).toBe("alice");
66
+ expect(wikilinkTarget("alice|Alice B.")).toBe("alice");
67
+ expect(wikilinkTarget("alice#role")).toBe("alice");
68
+ expect(wikilinkTarget(" tools/vs-code#setup|editor ")).toBe(
69
+ "tools/vs-code",
70
+ );
71
+ expect(wikilinkTarget("|label")).toBe("");
72
+ expect(wikilinkTarget("#anchor")).toBe("");
73
+ });
74
+
75
+ test("extracts every target in order, dropping empty ones, keeping duplicates", () => {
76
+ const body =
77
+ "See [[alice]] and [[bob|Bob]] then [[alice#role]] but not [[|x]] or [[#y]].";
78
+ expect(extractWikilinkTargets(body)).toEqual(["alice", "bob", "alice"]);
79
+ });
80
+
81
+ test("the regex is global and captures the raw inner text", () => {
82
+ const matches = [..."[[a|A]] [[b#c]]".matchAll(WIKILINK_REGEX)].map(
83
+ (m) => m[1],
84
+ );
85
+ expect(matches).toEqual(["a|A", "b#c"]);
86
+ });
87
+ });
88
+
89
+ describe("findDanglingLinks", () => {
90
+ const known = new Set(["alice", "bob", "hub", "skills/deploy"]);
91
+
92
+ test("reports links:, wikilink, and edges: targets absent from the known set", () => {
93
+ const result = findDanglingLinks(
94
+ [
95
+ {
96
+ slug: "hub",
97
+ frontmatter: {
98
+ links: [link("alice", "principal"), link("atl-1291", "ticket")],
99
+ edges: ["bob", "ghost-edge"],
100
+ },
101
+ body: "Lead mentions [[alice]] and [[ghost-wiki|Ghost]].",
102
+ },
103
+ ],
104
+ known,
105
+ isValidSlug,
106
+ );
107
+ expect(result).toEqual([
108
+ { from: "hub", to: "atl-1291", kind: "links" },
109
+ { from: "hub", to: "ghost-edge", kind: "edges" },
110
+ { from: "hub", to: "ghost-wiki", kind: "wikilink" },
111
+ ]);
112
+ });
113
+
114
+ test("resolves against synthetic slugs too when the caller counts them as known", () => {
115
+ expect(
116
+ findDanglingLinks(
117
+ [
118
+ {
119
+ slug: "alice",
120
+ frontmatter: { links: [link("skills/deploy")] },
121
+ body: "",
122
+ },
123
+ ],
124
+ known,
125
+ isValidSlug,
126
+ ),
127
+ ).toEqual([]);
128
+ });
129
+
130
+ test("ignores self-references and empty targets, dedupes per (from, to, kind)", () => {
131
+ const result = findDanglingLinks(
132
+ [
133
+ {
134
+ slug: "alice",
135
+ frontmatter: {
136
+ links: [
137
+ link("alice", "self"),
138
+ link("ghost"),
139
+ link("ghost", "twice"),
140
+ ],
141
+ },
142
+ body: "[[alice]] [[ghost]] [[ghost#sec]] [[|nothing]]",
143
+ },
144
+ ],
145
+ known,
146
+ isValidSlug,
147
+ );
148
+ expect(result).toEqual([
149
+ { from: "alice", to: "ghost", kind: "links" },
150
+ { from: "alice", to: "ghost", kind: "wikilink" },
151
+ ]);
152
+ });
153
+
154
+ test("output is sorted by (from, to, kind) regardless of page order", () => {
155
+ const pages = [
156
+ { slug: "zed", frontmatter: { links: [link("nope")] }, body: "[[nope]]" },
157
+ { slug: "amy", frontmatter: {}, body: "[[zzz]] [[aaa]]" },
158
+ ];
159
+ const forward = findDanglingLinks(pages, known, isValidSlug);
160
+ const reversed = findDanglingLinks(
161
+ [...pages].reverse(),
162
+ known,
163
+ isValidSlug,
164
+ );
165
+ expect(forward).toEqual(reversed);
166
+ expect(forward.map((d) => `${d.from}>${d.to}>${d.kind}`)).toEqual([
167
+ "amy>aaa>wikilink",
168
+ "amy>zzz>wikilink",
169
+ "zed>nope>links",
170
+ "zed>nope>wikilink",
171
+ ]);
172
+ });
173
+
174
+ test("targets that are not slug-shaped are not references at all", () => {
175
+ // Shell tests, code, and prose in double brackets must not be reported
176
+ // (and so never reach the repair step that would unwrap them).
177
+ expect(
178
+ findDanglingLinks(
179
+ [
180
+ {
181
+ slug: "procs/deploy",
182
+ frontmatter: { links: ["Not A Slug"] },
183
+ body: "Run `if [[ -f foo ]]; then` and [[ Some Prose ]] then [[real-missing]].",
184
+ },
185
+ ],
186
+ known,
187
+ isValidSlug,
188
+ ),
189
+ ).toEqual([{ from: "procs/deploy", to: "real-missing", kind: "wikilink" }]);
190
+ });
191
+
192
+ test("a fully resolved corpus yields an empty list", () => {
193
+ expect(
194
+ findDanglingLinks(
195
+ [
196
+ {
197
+ slug: "alice",
198
+ frontmatter: { links: [link("bob", "peer")] },
199
+ body: "[[hub]]",
200
+ },
201
+ { slug: "bob", frontmatter: { edges: ["alice"] }, body: "" },
202
+ ],
203
+ known,
204
+ isValidSlug,
205
+ ),
206
+ ).toEqual([]);
207
+ });
208
+ });
@@ -66,8 +66,10 @@ const {
66
66
  CORE_PAGES_CONSOLIDATION_SECTION,
67
67
  CORE_PAGES_PLACEHOLDER,
68
68
  CUTOFF_PLACEHOLDER,
69
+ DANGLING_LINKS_PLACEHOLDER,
69
70
  PARSE_FAILURES_PLACEHOLDER,
70
71
  renderConsolidationPrompt,
72
+ renderDanglingLinksSection,
71
73
  renderParseFailuresSection,
72
74
  resolveConsolidationPrompt,
73
75
  } = await import("../prompts/consolidation.js");
@@ -107,7 +109,15 @@ const bundledPrompt = (includeCorePagesSection = false): string =>
107
109
  ? (CORE_PAGES_CONSOLIDATION_SECTION as string)
108
110
  : "",
109
111
  )
110
- .replaceAll(PARSE_FAILURES_PLACEHOLDER, "");
112
+ .replaceAll(PARSE_FAILURES_PLACEHOLDER, "")
113
+ .replaceAll(DANGLING_LINKS_PLACEHOLDER, "");
114
+
115
+ /** Sample dangling links for the repair-section tests. */
116
+ const SAMPLE_DANGLING = [
117
+ { from: "atl-1290", to: "atl-1291", kind: "links" as const },
118
+ { from: "people/alice", to: "ghost-page", kind: "wikilink" as const },
119
+ { from: "arcs/day", to: "old-slug", kind: "edges" as const },
120
+ ];
111
121
 
112
122
  beforeEach(() => {
113
123
  warnCalls.length = 0;
@@ -316,6 +326,153 @@ describe("resolveConsolidationPrompt — parse-failures repair section", () => {
316
326
  });
317
327
  });
318
328
 
329
+ describe("resolveConsolidationPrompt: dangling-links repair section", () => {
330
+ test("no dangling links: no section and no placeholder residue, both article shapes", () => {
331
+ expect(renderDanglingLinksSection([])).toBe("");
332
+ for (const articleShape of ["v2", "v3"] as const) {
333
+ const result = renderConsolidationPrompt(CUTOFF, {
334
+ includeCorePagesSection: false,
335
+ articleShape,
336
+ });
337
+ expect(result).not.toContain("resolve dangling links");
338
+ expect(result).not.toContain(DANGLING_LINKS_PLACEHOLDER);
339
+ expect(result).toContain("# The work\n\n## 1. Read the buffer");
340
+ }
341
+ });
342
+
343
+ test("with dangling links: section renders once, before step 1, one line per reference with its kind", () => {
344
+ const result = renderConsolidationPrompt(CUTOFF, {
345
+ ...NO_CORE,
346
+ danglingLinks: SAMPLE_DANGLING,
347
+ });
348
+ expect(result.split("resolve dangling links").length - 1).toBe(1);
349
+ const sectionAt = result.indexOf("## 0. FIRST: resolve dangling links");
350
+ expect(sectionAt).toBeGreaterThan(result.indexOf("# The work"));
351
+ expect(sectionAt).toBeLessThan(result.indexOf("## 1. Read the buffer"));
352
+ expect(result).toContain(
353
+ "- `memory/concepts/atl-1290.md` → `atl-1291` (frontmatter `links:` entry)",
354
+ );
355
+ expect(result).toContain(
356
+ "- `memory/concepts/people/alice.md` → `ghost-page` (inline `[[wikilink]]` in the body)",
357
+ );
358
+ expect(result).toContain(
359
+ "- `memory/concepts/arcs/day.md` → `old-slug` (frontmatter `edges:` entry)",
360
+ );
361
+ // The three resolutions the agent must choose between.
362
+ expect(result).toContain("spawn it, a stub is fine, and keep the link");
363
+ expect(result).toContain("repoint the reference at the surviving slug");
364
+ expect(result).toContain("Prose stays prose");
365
+ expect(result).not.toContain("more, reported next pass");
366
+ });
367
+
368
+ test("renders after the parse-failures section when both are present", () => {
369
+ const result = renderConsolidationPrompt(CUTOFF, {
370
+ ...NO_CORE,
371
+ parseFailures: SAMPLE_FAILURES,
372
+ danglingLinks: SAMPLE_DANGLING,
373
+ });
374
+ expect(result.indexOf("repair unreadable pages")).toBeLessThan(
375
+ result.indexOf("resolve dangling links"),
376
+ );
377
+ expect(result.indexOf("resolve dangling links")).toBeLessThan(
378
+ result.indexOf("## 1. Read the buffer"),
379
+ );
380
+ });
381
+
382
+ test("caps the rendered list at 40 and counts the remainder", () => {
383
+ const many = Array.from({ length: 45 }, (_, i) => ({
384
+ from: `page-${String(i).padStart(2, "0")}`,
385
+ to: `missing-${i}`,
386
+ kind: "links" as const,
387
+ }));
388
+ const section = renderDanglingLinksSection(many);
389
+ const listLines = section
390
+ .split("\n")
391
+ .filter((line) => line.startsWith("- `memory/concepts/"));
392
+ expect(listLines).toHaveLength(40);
393
+ expect(section).toContain("`memory/concepts/page-39.md`");
394
+ expect(section).not.toContain("`memory/concepts/page-40.md`");
395
+ expect(section).toContain("...and 5 more, reported next pass");
396
+ });
397
+
398
+ test("sanitizes injection-shaped slugs and targets: newlines, backticks, oversize", () => {
399
+ // Both ends of a dangling link are page content: the source is a
400
+ // filename, the target is whatever the page wrote inside `[[...]]` or
401
+ // `links:`. Raw, either could break out of the list item.
402
+ const section = renderDanglingLinksSection([
403
+ {
404
+ from: "evil\n\n## Injected heading\n- `nested`",
405
+ to: "target`code`\n## also injected",
406
+ kind: "wikilink",
407
+ },
408
+ { from: "s".repeat(500), to: "t".repeat(500), kind: "links" },
409
+ ]);
410
+ const listLines = section
411
+ .split("\n")
412
+ .filter((line) => line.startsWith("- `memory/concepts/"));
413
+ expect(listLines).toHaveLength(2);
414
+ for (const line of listLines) {
415
+ // Source path, target, and the kind label's own inline-code span:
416
+ // six template backticks, no injected ones.
417
+ expect((line.match(/`/g) ?? []).length).toBe(6);
418
+ }
419
+ const headingLines = section
420
+ .split("\n")
421
+ .filter((line) => line.startsWith("## "));
422
+ expect(headingLines).toHaveLength(1);
423
+ expect(section).toContain("memory/concepts/evil ");
424
+ expect(section).toContain(`${"s".repeat(199)}…`);
425
+ expect(section).toContain(`${"t".repeat(199)}…`);
426
+ expect(section).not.toContain("s".repeat(200));
427
+ expect(section).not.toContain("t".repeat(200));
428
+ });
429
+
430
+ test("override containing the placeholder: substituted in place", () => {
431
+ const path = join(tmpWorkspace, "custom-prompt.md");
432
+ writeFileSync(path, "Top\n{{DANGLING_LINKS_SECTION}}Bottom\n");
433
+
434
+ const result = resolveConsolidationPrompt(path, CUTOFF, {
435
+ ...NO_CORE,
436
+ danglingLinks: SAMPLE_DANGLING,
437
+ });
438
+ expect(result).toContain("## 0. FIRST: resolve dangling links");
439
+ expect(result.indexOf("resolve dangling links")).toBeLessThan(
440
+ result.indexOf("Bottom"),
441
+ );
442
+ expect(result).not.toContain(DANGLING_LINKS_PLACEHOLDER);
443
+ });
444
+
445
+ test("override without either placeholder: both non-empty repair sections are appended, in order", () => {
446
+ const path = join(tmpWorkspace, "no-placeholder.md");
447
+ writeFileSync(path, "My custom prompt {{CUTOFF}}\n");
448
+
449
+ const result = resolveConsolidationPrompt(path, CUTOFF, {
450
+ ...NO_CORE,
451
+ parseFailures: SAMPLE_FAILURES,
452
+ danglingLinks: SAMPLE_DANGLING,
453
+ });
454
+ expect(result.startsWith(`My custom prompt ${CUTOFF}`)).toBe(true);
455
+ const parseAt = result.indexOf("repair unreadable pages");
456
+ const danglingAt = result.indexOf("resolve dangling links");
457
+ expect(parseAt).toBeGreaterThan(0);
458
+ expect(danglingAt).toBeGreaterThan(parseAt);
459
+ expect(result).toContain("`memory/concepts/atl-1290.md`");
460
+ });
461
+
462
+ test("override with only the parse placeholder + dangling links: dangling section appended", () => {
463
+ const path = join(tmpWorkspace, "custom-prompt.md");
464
+ writeFileSync(path, "Top\n{{PARSE_FAILURES_SECTION}}Bottom\n");
465
+
466
+ const result = resolveConsolidationPrompt(path, CUTOFF, {
467
+ ...NO_CORE,
468
+ danglingLinks: SAMPLE_DANGLING,
469
+ });
470
+ expect(result.indexOf("Bottom")).toBeLessThan(
471
+ result.indexOf("resolve dangling links"),
472
+ );
473
+ });
474
+ });
475
+
319
476
  describe("resolveConsolidationPrompt — with override", () => {
320
477
  test("loads an absolute path verbatim and substitutes {{CUTOFF}}", () => {
321
478
  const path = join(tmpWorkspace, "custom-prompt.md");
@@ -5,6 +5,7 @@
5
5
  * - Reads the four files in canonical order and joins them under headings.
6
6
  * - Skips empty / missing files.
7
7
  * - Returns null when every file is empty or missing.
8
+ * - Caps the Buffer section at `consolidation_max_buffer_lines`.
8
9
  */
9
10
 
10
11
  import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
@@ -33,17 +34,23 @@ mock.module("../../../../../util/logger.js", () => ({
33
34
 
34
35
  let configMemoryV2Enabled = true;
35
36
  let configMemoryEnabled = true;
37
+ // Mirrors the `memory.v2.consolidation_max_buffer_lines` schema default; the
38
+ // Buffer-cap tests move it.
39
+ let configMaxBufferLines: number | null = 100;
36
40
 
37
- // static-context reads its gates through the plugin's own config accessor.
41
+ // static-context reads its gates and the substrate tuning through the
42
+ // plugin's own config accessor.
38
43
  mock.module("../../config.js", () => ({
39
44
  getMemoryConfig: () => ({
40
45
  enabled: configMemoryEnabled,
41
- v2: { enabled: configMemoryV2Enabled },
46
+ v2: {
47
+ enabled: configMemoryV2Enabled,
48
+ consolidation_max_buffer_lines: configMaxBufferLines,
49
+ },
42
50
  }),
43
51
  }));
44
52
 
45
- const { readMemoryV2StaticContent, shouldExposePersonalMemory } =
46
- await import("../static-context.js");
53
+ const { readMemoryV2StaticContent } = await import("../static-context.js");
47
54
 
48
55
  const MEMORY_FILES = [
49
56
  "essentials.md",
@@ -70,6 +77,7 @@ describe("readMemoryV2StaticContent", () => {
70
77
  mkdirSync(TEST_DIR, { recursive: true });
71
78
  configMemoryV2Enabled = true;
72
79
  configMemoryEnabled = true;
80
+ configMaxBufferLines = 100;
73
81
  });
74
82
 
75
83
  afterEach(() => {
@@ -165,61 +173,190 @@ describe("readMemoryV2StaticContent", () => {
165
173
  });
166
174
  });
167
175
 
168
- describe("shouldExposePersonalMemory", () => {
169
- test("allows guardian-trusted local conversations", () => {
170
- expect(
171
- shouldExposePersonalMemory({
172
- sourceChannel: "vellum",
173
- isTrustedActor: true,
174
- }),
175
- ).toBe(true);
176
- });
177
-
178
- test("allows local-channel conversations even when trust class is unknown (analyze runs, dev)", () => {
179
- expect(
180
- shouldExposePersonalMemory({
181
- sourceChannel: "vellum",
182
- isTrustedActor: false,
183
- }),
184
- ).toBe(true);
185
- });
186
-
187
- test("allows turns with no trust context (work-item task runs, internal background)", () => {
188
- expect(
189
- shouldExposePersonalMemory({
190
- sourceChannel: undefined,
191
- isTrustedActor: false,
192
- }),
193
- ).toBe(true);
194
- });
195
-
196
- const REMOTE_CHANNELS = [
197
- "phone",
198
- "slack",
199
- "telegram",
200
- "whatsapp",
201
- "email",
202
- ] as const;
203
-
204
- test("allows guardian-trusted remote channels (user's own phone/Slack)", () => {
205
- for (const channel of REMOTE_CHANNELS) {
206
- expect(
207
- shouldExposePersonalMemory({
208
- sourceChannel: channel,
209
- isTrustedActor: true,
210
- }),
211
- ).toBe(true);
212
- }
176
+ describe("readMemoryV2StaticContent Buffer cap", () => {
177
+ /** `n` timestamped buffer entries, oldest first, in `remember()`'s shape. */
178
+ function bufferEntries(n: number): string {
179
+ return Array.from(
180
+ { length: n },
181
+ (_, i) => `- [Jan 1, 9:00 AM] entry-${i}`,
182
+ ).join("\n");
183
+ }
184
+
185
+ beforeEach(() => {
186
+ mkdirSync(TEST_DIR, { recursive: true });
187
+ configMemoryV2Enabled = true;
188
+ configMemoryEnabled = true;
189
+ configMaxBufferLines = 10;
213
190
  });
214
191
 
215
- test("blocks non-guardian remote-channel actors (the leak this gate exists to prevent)", () => {
216
- for (const channel of REMOTE_CHANNELS) {
217
- expect(
218
- shouldExposePersonalMemory({
219
- sourceChannel: channel,
220
- isTrustedActor: false,
221
- }),
222
- ).toBe(false);
223
- }
192
+ afterEach(() => {
193
+ cleanupMemoryDir();
194
+ });
195
+
196
+ test("passes a buffer at or under the cap through byte-identically", () => {
197
+ const buffer = bufferEntries(10);
198
+ writeMemoryFile("buffer.md", buffer);
199
+
200
+ expect(readMemoryV2StaticContent()).toBe(`## Buffer\n\n${buffer}`);
201
+ });
202
+
203
+ test("keeps the newest entries and drops the oldest when over the cap", () => {
204
+ writeMemoryFile("buffer.md", bufferEntries(30));
205
+
206
+ const text = readMemoryV2StaticContent()!;
207
+ expect(text).toContain(
208
+ "(Older entries trimmed. Read memory/buffer.md for the full backlog.)",
209
+ );
210
+ // The cap counts non-empty lines, matching the scheduler's
211
+ // `countBufferLines`, so exactly the newest 10 entries survive.
212
+ const kept = text
213
+ .split("\n")
214
+ .filter((line) => line.startsWith("- ["))
215
+ .map((line) => line.slice(line.lastIndexOf(" ") + 1));
216
+ expect(kept).toEqual(
217
+ Array.from({ length: 10 }, (_, i) => `entry-${i + 20}`),
218
+ );
219
+ });
220
+
221
+ test("never opens mid-entry when the cap lands inside a multiline fact", () => {
222
+ // The 5-line fact straddles the cap: counting back 10 non-empty lines
223
+ // lands on one of its continuation lines. Injecting from there would show
224
+ // orphan bullets with no timestamp and no opening clause.
225
+ const multiline = [
226
+ "- [Jan 1, 9:00 AM] the straddling fact",
227
+ " - [ ] a checklist item inside the fact",
228
+ " continuation prose",
229
+ " - another bullet",
230
+ " closing line",
231
+ ].join("\n");
232
+ writeMemoryFile(
233
+ "buffer.md",
234
+ `${bufferEntries(20)}\n${multiline}\n${bufferEntries(8)}`,
235
+ );
236
+
237
+ const text = readMemoryV2StaticContent()!;
238
+ const body = text.slice(text.indexOf("buffer.md for the full backlog.)\n"));
239
+ const firstLine = body.split("\n")[1]!;
240
+ expect(firstLine).toMatch(/^- \[Jan 1, 9:00 AM\]/);
241
+ // The partial entry is dropped whole: none of its continuation lines
242
+ // survive on their own.
243
+ expect(text).not.toContain("continuation prose");
244
+ expect(text).not.toContain("the straddling fact");
245
+ });
246
+
247
+ test("keeps the newest entry attributable when its body alone exceeds the cap", () => {
248
+ // The newest entry has no successor to fall back to: counting back 10
249
+ // non-empty lines lands inside its body and there is no later timestamped
250
+ // entry to open on. Dropping it would leave the section with no facts at
251
+ // all, so the entry's opening line is kept and its head elided.
252
+ writeMemoryFile(
253
+ "buffer.md",
254
+ [
255
+ ...Array.from(
256
+ { length: 20 },
257
+ (_, i) => `- [Jan 1, 9:00 AM] entry-${i}`,
258
+ ),
259
+ "- [Jan 2, 9:00 AM] the oversized fact",
260
+ ...Array.from({ length: 14 }, (_, i) => ` body line ${i}`),
261
+ ].join("\n"),
262
+ );
263
+
264
+ const text = readMemoryV2StaticContent()!;
265
+ const body = text
266
+ .slice(text.indexOf("full backlog.)\n") + "full backlog.)\n".length)
267
+ .split("\n")
268
+ .filter((line) => line.trim().length > 0);
269
+
270
+ // Opens on the entry's own timestamped line, never on an orphan body line.
271
+ expect(body[0]).toBe("- [Jan 2, 9:00 AM] the oversized fact");
272
+ expect(body[1]).toBe(
273
+ "(This entry's body was trimmed. Read memory/buffer.md for the rest of it.)",
274
+ );
275
+ // The retained tail is the newest end of the body, not its head.
276
+ expect(body.at(-1)).toBe(" body line 13");
277
+ expect(body).toContain(" body line 4");
278
+ expect(body).not.toContain(" body line 3");
279
+ // Bounded at the cap plus exactly the opening line and the marker, so the
280
+ // oversized entry cannot reintroduce an unbounded injection.
281
+ expect(body).toHaveLength(12);
282
+ // The older entries are still dropped.
283
+ expect(text).not.toContain("entry-19");
284
+ });
285
+
286
+ test("does not claim a trim when the newest entry's body exactly fills the cap", () => {
287
+ // The cut lands on the line right after the opening, so nothing of the
288
+ // entry was elided and the marker would be a lie.
289
+ writeMemoryFile(
290
+ "buffer.md",
291
+ [
292
+ ...Array.from(
293
+ { length: 20 },
294
+ (_, i) => `- [Jan 1, 9:00 AM] entry-${i}`,
295
+ ),
296
+ "- [Jan 2, 9:00 AM] the exact-fit fact",
297
+ ...Array.from({ length: 10 }, (_, i) => ` body line ${i}`),
298
+ ].join("\n"),
299
+ );
300
+
301
+ const text = readMemoryV2StaticContent()!;
302
+ const body = text
303
+ .slice(text.indexOf("full backlog.)\n") + "full backlog.)\n".length)
304
+ .split("\n")
305
+ .filter((line) => line.trim().length > 0);
306
+
307
+ expect(body[0]).toBe("- [Jan 2, 9:00 AM] the exact-fit fact");
308
+ expect(text).not.toContain("This entry's body was trimmed");
309
+ // The entry survives whole: opening line plus its full 10-line body.
310
+ expect(body).toHaveLength(11);
311
+ expect(body.at(-1)).toBe(" body line 9");
312
+ expect(body).toContain(" body line 0");
313
+ });
314
+
315
+ test("keeps a multiline entry intact when it sits fully inside the cap", () => {
316
+ const multiline = [
317
+ "- [Jan 1, 9:00 AM] a fact with a body",
318
+ " second line of the same fact",
319
+ ].join("\n");
320
+ writeMemoryFile("buffer.md", `${bufferEntries(20)}\n${multiline}`);
321
+
322
+ const text = readMemoryV2StaticContent()!;
323
+ expect(text).toContain("a fact with a body");
324
+ expect(text).toContain("second line of the same fact");
325
+ });
326
+
327
+ test("falls back to the line cut for a buffer with no timestamped entries", () => {
328
+ // A hand-written buffer that never went through `remember()` has no entry
329
+ // structure to preserve, so the line-based cut stands rather than dropping
330
+ // everything.
331
+ const handWritten = Array.from(
332
+ { length: 30 },
333
+ (_, i) => `just a line ${i}`,
334
+ ).join("\n");
335
+ writeMemoryFile("buffer.md", handWritten);
336
+
337
+ const text = readMemoryV2StaticContent()!;
338
+ expect(text).toContain("just a line 29");
339
+ expect(text).not.toContain("just a line 0");
340
+ });
341
+
342
+ test("leaves the buffer unbounded when the size trigger is disabled", () => {
343
+ configMaxBufferLines = null;
344
+ const buffer = bufferEntries(30);
345
+ writeMemoryFile("buffer.md", buffer);
346
+
347
+ expect(readMemoryV2StaticContent()).toBe(`## Buffer\n\n${buffer}`);
348
+ });
349
+
350
+ test("caps only the Buffer section, never the curated views", () => {
351
+ const long = Array.from({ length: 30 }, (_, i) => `line-${i}`).join("\n");
352
+ writeMemoryFile("essentials.md", long);
353
+ writeMemoryFile("threads.md", long);
354
+ writeMemoryFile("recent.md", long);
355
+ writeMemoryFile("buffer.md", bufferEntries(30));
356
+
357
+ const lines = readMemoryV2StaticContent()!.split("\n");
358
+ expect(lines.filter((line) => line === "line-0")).toHaveLength(3);
359
+ expect(lines.filter((line) => line === "line-29")).toHaveLength(3);
360
+ expect(lines.some((line) => line.endsWith("entry-0"))).toBe(false);
224
361
  });
225
362
  });