@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
@@ -45,7 +45,11 @@
45
45
  * 6. Verify the run drained the buffer. `runResult.ok` only means the
46
46
  * background run completed — the trim itself is delegated to the agent.
47
47
  * A run that completes without shrinking the buffer is reported as
48
- * `invoked` with `noProgress: true` and enqueues no follow-ups.
48
+ * `invoked` with `noProgress: true` and enqueues no follow-ups. The
49
+ * post-run page index is also read for `danglingLinks` (structural
50
+ * references with no target page): reported on the outcome and in the
51
+ * log, and fed into the NEXT pass's prompt as a repair step like
52
+ * `parseFailures`. They never gate follow-ups.
49
53
  * 7. On progress, enqueue `memory_v2_reembed` (re-index any pages the agent
50
54
  * touched). Tracking touched pages via mtime would be more precise but
51
55
  * is fragile across filesystems; the embedder's content-hash cache makes
@@ -91,7 +95,10 @@ import {
91
95
  type MemoryJobType,
92
96
  } from "../../../../persistence/jobs-store.js";
93
97
  import { runBackgroundJob } from "../../../../runtime/background-job-runner.js";
94
- import { formatBufferTimestamp } from "../graph/tool-handlers.js";
98
+ import {
99
+ formatBufferTimestamp,
100
+ matchBufferEntryStart,
101
+ } from "../buffer-format.js";
95
102
  import { getLogger } from "../logging.js";
96
103
  import { getWorkspaceDir } from "../paths.js";
97
104
  import {
@@ -101,6 +108,7 @@ import {
101
108
  tryAcquireLock,
102
109
  } from "./consolidation-lock.js";
103
110
  import { getPageIndex, type PageParseFailure } from "./page-index.js";
111
+ import type { DanglingLink } from "./page-links.js";
104
112
  import { resolveConsolidationPrompt } from "./prompts/consolidation.js";
105
113
  import { resolveSubstrateTuning } from "./tuning.js";
106
114
 
@@ -293,8 +301,17 @@ export type ConsolidationOutcome =
293
301
  * follow-ups were enqueued.
294
302
  */
295
303
  noProgress: boolean;
304
+ /**
305
+ * Structural references (`links:`, `[[wikilinks]]`, `edges:`) in the
306
+ * post-run corpus whose target page does not exist; `null` when the
307
+ * post-run index could not be read.
308
+ */
309
+ danglingLinks: number | null;
296
310
  };
297
311
 
312
+ /** Dangling links named in the post-run warn line before the count takes over. */
313
+ const MAX_LOGGED_DANGLING_LINKS = 20;
314
+
298
315
  export async function memoryV2ConsolidateJob(
299
316
  _job: MemoryJob,
300
317
  config: AssistantConfig,
@@ -416,16 +433,20 @@ export async function memoryV2ConsolidateJob(
416
433
  // v2-only install must keep producing `summary:`-bearing fragment pages.
417
434
  const memoryV3Live = isMemoryV3Live(config);
418
435
  // Pages the index build dropped (malformed frontmatter) or degraded
419
- // (unterminated fence) — rendered into the prompt's repair step so the
420
- // agent fixes them this pass. Best-effort: prompt assembly must never
421
- // fail because the index build did.
436
+ // (unterminated fence), and structural references with no target page,
437
+ // rendered into the prompt's repair steps so the agent fixes them this
438
+ // pass. Best-effort: prompt assembly must never fail because the index
439
+ // build did.
422
440
  let parseFailures: PageParseFailure[] = [];
441
+ let danglingLinks: DanglingLink[] = [];
423
442
  try {
424
- parseFailures = (await getPageIndex(getWorkspaceDir())).parseFailures;
443
+ const index = await getPageIndex(getWorkspaceDir());
444
+ parseFailures = index.parseFailures;
445
+ danglingLinks = index.danglingLinks;
425
446
  } catch (err) {
426
447
  log.warn(
427
448
  { err },
428
- "consolidation: page-index read failed; omitting the repair section",
449
+ "consolidation: page-index read failed; omitting the repair sections",
429
450
  );
430
451
  }
431
452
  const prompt = resolveConsolidationPrompt(
@@ -435,6 +456,7 @@ export async function memoryV2ConsolidateJob(
435
456
  includeCorePagesSection: memoryV3Live,
436
457
  articleShape: memoryV3Live ? "v3" : "v2",
437
458
  parseFailures,
459
+ danglingLinks,
438
460
  },
439
461
  );
440
462
 
@@ -493,6 +515,13 @@ export async function memoryV2ConsolidateJob(
493
515
  const bufferLinesAfter = countBufferLines(bufferPath);
494
516
  const noProgress = bufferLinesAfter >= bufferLinesBefore;
495
517
 
518
+ // The agent's file-tool writes invalidate the page index, so this read
519
+ // sees the post-run corpus.
520
+ const danglingAfter = await readDanglingLinks(
521
+ runResult.conversationId,
522
+ danglingLinks.length,
523
+ );
524
+
496
525
  // Failure-state bookkeeping. A skipped run (`skipReason`) never invoked
497
526
  // the agent, so it neither clears nor records. A completed run that made
498
527
  // no progress behaves like a failure for scheduling — the size trigger
@@ -523,6 +552,7 @@ export async function memoryV2ConsolidateJob(
523
552
  deferredEntries,
524
553
  followUpJobIds: [],
525
554
  noProgress: true,
555
+ danglingLinks: danglingAfter,
526
556
  };
527
557
  }
528
558
 
@@ -574,12 +604,47 @@ export async function memoryV2ConsolidateJob(
574
604
  deferredEntries,
575
605
  followUpJobIds,
576
606
  noProgress: false,
607
+ danglingLinks: danglingAfter,
577
608
  };
578
609
  } finally {
579
610
  releaseLock(lockPath);
580
611
  }
581
612
  }
582
613
 
614
+ /**
615
+ * Post-run dangling-link count; warns (with a capped sample and the pre-run
616
+ * count) when any remain. `null` when the index cannot be read.
617
+ */
618
+ async function readDanglingLinks(
619
+ conversationId: string,
620
+ danglingBefore: number,
621
+ ): Promise<number | null> {
622
+ let dangling: DanglingLink[];
623
+ try {
624
+ dangling = (await getPageIndex(getWorkspaceDir())).danglingLinks;
625
+ } catch (err) {
626
+ log.warn(
627
+ { err, conversationId },
628
+ "consolidation: post-run page-index read failed; dangling links unknown",
629
+ );
630
+ return null;
631
+ }
632
+ if (dangling.length > 0) {
633
+ log.warn(
634
+ {
635
+ conversationId,
636
+ danglingBefore,
637
+ danglingAfter: dangling.length,
638
+ sample: dangling
639
+ .slice(0, MAX_LOGGED_DANGLING_LINKS)
640
+ .map((d) => `${d.from} -> ${d.to} (${d.kind})`),
641
+ },
642
+ "consolidation left structural links whose target page does not exist; the next pass renders them as a repair step",
643
+ );
644
+ }
645
+ return dangling.length;
646
+ }
647
+
583
648
  /**
584
649
  * Read `memory/buffer.md`. Missing file → empty string so the skip-on-empty
585
650
  * branch doesn't have to distinguish "no file" from "blank file".
@@ -597,32 +662,36 @@ function readBufferContent(bufferPath: string): string {
597
662
 
598
663
  /**
599
664
  * Extract the bracketed timestamp from a `buffer.md` entry line
600
- * (`- [Mon D, h:mm AM/PM] …`, see `formatBufferEntry`). Returned verbatim so
601
- * it can serve directly as a consolidation cutoff — both sides of the agent's
602
- * "timestamp ≥ cutoff" comparison then share the exact `formatBufferTimestamp`
603
- * shape.
665
+ * (`- [Mon D, h:mm AM/PM] …`, see {@link formatRememberEntry}). Returned
666
+ * verbatim so it can serve directly as a consolidation cutoff: both sides of
667
+ * the agent's "timestamp >= cutoff" comparison then share the exact
668
+ * {@link formatBufferTimestamp} shape.
604
669
  *
605
- * The bracket contents must match that shape exactly: a remembered fact's
606
- * continuation lines can themselves start with `- [` (markdown checklists
607
- * `- [ ] …`, wikilink bullets `- [[…]]`), and counting those as entries
608
- * would inflate the per-run budget — or worse, hand the agent a garbage
609
- * cutoff like a blank string. Returns `null` for anything that isn't a real
610
- * timestamped entry start.
670
+ * Recognition is delegated to the shared matcher, so a remembered fact's
671
+ * continuation lines never register as entries. That matters here beyond
672
+ * tidiness: counting a fact's `- [ ] …` checklist lines or an indented
673
+ * entry-shaped body line as entries would inflate the per-run budget, or hand
674
+ * the agent a cutoff drawn from the middle of a fact.
611
675
  */
612
676
  function extractBufferEntryTimestamp(line: string): string | null {
613
- const match = /^\s*-\s*\[([A-Z][a-z]{2} \d{1,2}, \d{1,2}:\d{2} [AP]M)\]/.exec(
614
- line,
615
- );
616
- return match ? match[1] : null;
677
+ return matchBufferEntryStart(line)?.timestamp ?? null;
617
678
  }
618
679
 
619
680
  /**
620
681
  * Count non-empty lines in `memory/buffer.md`. Used by the scheduler to
621
682
  * implement the size-based consolidation trigger. Missing file → 0.
622
683
  *
623
- * Each entry is one line (`- [Mon D, h:mm AM/PM] …\n`), so non-empty-line
624
- * count == entry count for a well-formed buffer; blank lines and trailing
625
- * newlines don't inflate the count.
684
+ * Lines, deliberately, not entries. A multiline `remember()` fact is one
685
+ * entry spread over several lines, so the two counts diverge and each answers
686
+ * a different question. This trigger and the injected-Buffer cap that reuses
687
+ * it (`capBufferSection` in `static-context.ts`) both care about how much
688
+ * context the buffer costs, which scales with lines. The per-run budget
689
+ * `consolidation_max_entries_per_run` cares about how many facts the agent
690
+ * must file, so it counts entry-start lines instead.
691
+ *
692
+ * Do not "fix" this to count entries: a single 200-line fact is a context
693
+ * problem the trigger should fire on, even though it is one entry. Blank
694
+ * lines and trailing newlines don't inflate the count.
626
695
  */
627
696
  export function countBufferLines(bufferPath: string): number {
628
697
  return countNonEmptyLines(readBufferContent(bufferPath));
@@ -166,37 +166,6 @@ export function getReachable(
166
166
  return result;
167
167
  }
168
168
 
169
- export interface EdgeValidationResult {
170
- ok: boolean;
171
- /**
172
- * Outgoing-edge targets that don't correspond to any known slug. Sorted by
173
- * `(from, to)` for deterministic output.
174
- */
175
- missing: Array<{ from: string; to: string }>;
176
- }
177
-
178
- /**
179
- * Validate every outgoing edge target against `knownSlugs`. Returns a sorted
180
- * list of `(from, to)` pairs whose `to` slug has no corresponding concept
181
- * page on disk.
182
- */
183
- export function validateEdgeTargets(
184
- index: EdgeIndex,
185
- knownSlugs: ReadonlySet<string>,
186
- ): EdgeValidationResult {
187
- const missing: Array<{ from: string; to: string }> = [];
188
- const sources = [...index.outgoing.keys()].sort();
189
- for (const from of sources) {
190
- const targets = [...(index.outgoing.get(from) ?? [])].sort();
191
- for (const to of targets) {
192
- if (!knownSlugs.has(to)) {
193
- missing.push({ from, to });
194
- }
195
- }
196
- }
197
- return { ok: missing.length === 0, missing };
198
- }
199
-
200
169
  /**
201
170
  * Total count of directed edges in the index — i.e. the sum of every page's
202
171
  * outgoing-edge fanout. Each (from, to) pair counts once.
@@ -29,7 +29,9 @@ import {
29
29
  tryAcquireLock,
30
30
  } from "./consolidation-lock.js";
31
31
  import { parseOriginDate } from "./page-index.js";
32
+ import { findDanglingLinks } from "./page-links.js";
32
33
  import {
34
+ isValidSlug,
33
35
  listPages,
34
36
  parsePageContent,
35
37
  validateSlug,
@@ -127,6 +129,14 @@ const REINDEX_JOB_TYPES: readonly MemoryJobType[] = [
127
129
  * consolidation lock, so a page committed by another writer between
128
130
  * validation and the write phase is still skipped rather than
129
131
  * overwritten.
132
+ * 5. One more non-blocking warning, once the on-disk snapshot exists: a
133
+ * `links:`, `[[wikilink]]`, or `edges:` target that is neither on disk
134
+ * nor a valid page in this batch. Retrieval drops such a reference, so
135
+ * the caller either stages the missing page or turns the reference into
136
+ * prose. Warned, not rejected: a page is not worth losing over one link,
137
+ * and a multi-batch import legitimately references a later batch.
138
+ * Targets under the synthetic capability prefixes are not checked (their
139
+ * catalog is not on disk).
130
140
  *
131
141
  * With `dryRun` the summary is returned without touching disk or the lock;
132
142
  * the dry-run collision snapshot is lock-free and therefore advisory.
@@ -250,6 +260,7 @@ export async function ingestPages(
250
260
  // concurrent writer can change what a later real run would do.
251
261
  const existingSlugs = new Set(await listPages(workspaceDir));
252
262
  classifyCollisions(pending, existingSlugs, overwrite);
263
+ warnDanglingLinks(pending, existingSlugs);
253
264
  return summarize(results, dryRun);
254
265
  }
255
266
 
@@ -264,6 +275,7 @@ export async function ingestPages(
264
275
  // writer after validation still classifies as a collision.
265
276
  const existingSlugs = new Set(await listPages(workspaceDir));
266
277
  const toWrite = classifyCollisions(pending, existingSlugs, overwrite);
278
+ warnDanglingLinks(pending, existingSlugs);
267
279
  const summary = summarize(results, dryRun);
268
280
 
269
281
  for (const page of toWrite) {
@@ -319,6 +331,53 @@ function classifyCollisions(
319
331
  return toWrite;
320
332
  }
321
333
 
334
+ /**
335
+ * Append a dangling-link warning to every page about to be written whose
336
+ * structural references name a slug that is neither on disk nor a valid page
337
+ * in this batch. Runs after collision classification so pages the batch will
338
+ * not write (`skipped_exists`) are not warned about.
339
+ */
340
+ function warnDanglingLinks(
341
+ pending: readonly PendingPage[],
342
+ existingSlugs: ReadonlySet<string>,
343
+ ): void {
344
+ const knownSlugs = new Set(existingSlugs);
345
+ for (const { page } of pending) {
346
+ knownSlugs.add(page.slug);
347
+ }
348
+ const toWrite = pending.filter(({ result }) => result.action === "written");
349
+ const dangling = findDanglingLinks(
350
+ toWrite.map(({ page }) => page),
351
+ knownSlugs,
352
+ isValidSlug,
353
+ ).filter(
354
+ (d) => !RESERVED_SLUG_PREFIXES.some((prefix) => d.to.startsWith(prefix)),
355
+ );
356
+ if (dangling.length === 0) {
357
+ return;
358
+ }
359
+ const targetsBySlug = new Map<string, string[]>();
360
+ for (const { from, to } of dangling) {
361
+ const targets = targetsBySlug.get(from) ?? [];
362
+ if (!targets.includes(to)) {
363
+ targets.push(to);
364
+ }
365
+ targetsBySlug.set(from, targets);
366
+ }
367
+ for (const { page, result } of toWrite) {
368
+ const targets = targetsBySlug.get(page.slug);
369
+ if (targets === undefined) {
370
+ continue;
371
+ }
372
+ result.warnings.push(
373
+ `link target${targets.length === 1 ? "" : "s"} not on disk or in ` +
374
+ `this batch: ${targets.join(", ")}; stage the missing page or ` +
375
+ `turn the reference into prose (retrieval drops a link whose ` +
376
+ `target page does not exist)`,
377
+ );
378
+ }
379
+ }
380
+
322
381
  function summarize(
323
382
  results: IngestPageResult[],
324
383
  dryRun: boolean,
@@ -20,7 +20,14 @@
20
20
  */
21
21
 
22
22
  import { getLogger } from "../logging.js";
23
- import { getPageMtimeMs, listPages, readPage } from "./page-store.js";
23
+ import { type DanglingLink, findDanglingLinks } from "./page-links.js";
24
+ import {
25
+ getPageMtimeMs,
26
+ isValidSlug,
27
+ listPages,
28
+ readPage,
29
+ } from "./page-store.js";
30
+ import type { ConceptPage } from "./types.js";
24
31
 
25
32
  // Dynamic import for `./skill-store.js` happens inside `getPageIndex` so that
26
33
  // modules that only need `invalidatePageIndex` (page-store.ts,
@@ -103,8 +110,9 @@ export interface PageParseFailure {
103
110
  * ASCII so IDs are deterministic across rebuilds with the same input. The
104
111
  * `bySlug` and `byId` maps are convenience lookups; `rendered` is the prompt
105
112
  * block consumed by the router. `parseFailures` lists the pages this build
106
- * dropped or degraded, so consumers (the consolidation prompt) can surface
107
- * them for repair.
113
+ * dropped or degraded and `danglingLinks` the structural references whose
114
+ * target has no entry, so consumers (the consolidation prompt, the validate
115
+ * route) can surface them for repair.
108
116
  */
109
117
  export interface PageIndex {
110
118
  entries: PageIndexEntry[];
@@ -112,6 +120,8 @@ export interface PageIndex {
112
120
  byId: Map<number, PageIndexEntry>;
113
121
  rendered: string;
114
122
  parseFailures: PageParseFailure[];
123
+ /** Resolved against every `bySlug` key, synthetic rows included. */
124
+ danglingLinks: DanglingLink[];
115
125
  }
116
126
 
117
127
  interface CachedIndex {
@@ -187,6 +197,7 @@ export async function getPageIndex(workspaceDir: string): Promise<PageIndex> {
187
197
 
188
198
  const drafts: DraftEntry[] = [];
189
199
  const parseFailures: PageParseFailure[] = [];
200
+ const linkSources: ConceptPage[] = [];
190
201
  for (let i = 0; i < settled.length; i++) {
191
202
  const result = settled[i];
192
203
  const slug = slugs[i];
@@ -232,6 +243,7 @@ export async function getPageIndex(workspaceDir: string): Promise<PageIndex> {
232
243
  modifiedAt: mtimeMs,
233
244
  freshAt: resolveFreshAt(page.frontmatter.origin_date, mtimeMs),
234
245
  });
246
+ linkSources.push(page);
235
247
  }
236
248
 
237
249
  for (const entry of skillEntries) {
@@ -276,8 +288,8 @@ export async function getPageIndex(workspaceDir: string): Promise<PageIndex> {
276
288
  return entry;
277
289
  });
278
290
 
279
- // Edges whose target slug isn't in the index are dropped silently — the
280
- // frontmatter sweep is responsible for surfacing those as warnings.
291
+ // Edges whose target slug isn't in the index are not resolved here; they are
292
+ // reported in `danglingLinks` below instead.
281
293
  for (let i = 0; i < entries.length; i++) {
282
294
  const draft = drafts[i];
283
295
  const resolved: number[] = [];
@@ -297,8 +309,21 @@ export async function getPageIndex(workspaceDir: string): Promise<PageIndex> {
297
309
  a.slug < b.slug ? -1 : a.slug > b.slug ? 1 : 0,
298
310
  );
299
311
 
312
+ const danglingLinks = findDanglingLinks(
313
+ linkSources,
314
+ new Set(bySlug.keys()),
315
+ isValidSlug,
316
+ );
317
+
300
318
  const rendered = renderIndex(entries);
301
- const index: PageIndex = { entries, bySlug, byId, rendered, parseFailures };
319
+ const index: PageIndex = {
320
+ entries,
321
+ bySlug,
322
+ byId,
323
+ rendered,
324
+ parseFailures,
325
+ danglingLinks,
326
+ };
302
327
  cache = { workspaceDir, index };
303
328
  return index;
304
329
  }
@@ -525,9 +550,10 @@ function buildLocalPageIndex(
525
550
  bySlug: localBySlug,
526
551
  byId: localById,
527
552
  rendered: renderIndex(localEntries),
528
- // Parse failures are a property of the workspace build, not of a carved
529
- // batch — the full index is the reporting surface.
553
+ // Parse failures and dangling links are properties of the workspace
554
+ // build, not of a carved batch: the full index is the reporting surface.
530
555
  parseFailures: [],
556
+ danglingLinks: [],
531
557
  };
532
558
  }
533
559
 
@@ -626,5 +652,6 @@ function emptyPageIndex(): PageIndex {
626
652
  byId: new Map(),
627
653
  rendered: "",
628
654
  parseFailures: [],
655
+ danglingLinks: [],
629
656
  };
630
657
  }
@@ -0,0 +1,133 @@
1
+ // SUBSTRATE (v2+v3).
2
+ /**
3
+ * Memory substrate: the single owner of concept-page link syntax.
4
+ *
5
+ * A `links:` frontmatter entry is `"<target-slug> <sep> <why>"` with the
6
+ * separator {@link LINK_SEPARATOR}, split on its first occurrence (no
7
+ * separator: bare slug). An inline `[[wikilink]]` may carry `|display` and/or
8
+ * `#anchor`, neither part of the target. `edges:` entries are bare slugs.
9
+ *
10
+ * Every reader of these shapes imports from here (the v3 edge lane, the
11
+ * pending-buffer hints, the page index, ingest); do not add a second wikilink
12
+ * matcher or `links:` splitter, for the same reason `buffer-format.ts` keeps
13
+ * one buffer-entry matcher.
14
+ *
15
+ * Leaf module: imports nothing from the rest of the substrate.
16
+ */
17
+
18
+ /** Space, U+2014 EM DASH, space. */
19
+ export const LINK_SEPARATOR = ` ${String.fromCodePoint(0x2014)} `;
20
+
21
+ /** Global; group 1 is the raw inner text. Pair with {@link wikilinkTarget}. */
22
+ export const WIKILINK_REGEX = /\[\[([^\]]+)\]\]/g;
23
+
24
+ export function parseLinkEntry(entry: string): {
25
+ target: string;
26
+ description: string | undefined;
27
+ } {
28
+ const sep = entry.indexOf(LINK_SEPARATOR);
29
+ if (sep === -1) {
30
+ return { target: entry.trim(), description: undefined };
31
+ }
32
+ return {
33
+ target: entry.slice(0, sep).trim(),
34
+ description: entry.slice(sep + LINK_SEPARATOR.length).trim() || undefined,
35
+ };
36
+ }
37
+
38
+ /** The slug a wikilink's inner text names; empty for `[[|label]]` / `[[#anchor]]`. */
39
+ export function wikilinkTarget(inner: string): string {
40
+ let target = inner;
41
+ const pipe = target.indexOf("|");
42
+ if (pipe !== -1) {
43
+ target = target.slice(0, pipe);
44
+ }
45
+ const hash = target.indexOf("#");
46
+ if (hash !== -1) {
47
+ target = target.slice(0, hash);
48
+ }
49
+ return target.trim();
50
+ }
51
+
52
+ /** Every wikilink target in `text`, in order, empty targets dropped, duplicates kept. */
53
+ export function extractWikilinkTargets(text: string): string[] {
54
+ const targets: string[] = [];
55
+ for (const match of text.matchAll(WIKILINK_REGEX)) {
56
+ const target = wikilinkTarget(match[1]);
57
+ if (target) {
58
+ targets.push(target);
59
+ }
60
+ }
61
+ return targets;
62
+ }
63
+
64
+ export type PageLinkKind = "links" | "wikilink" | "edges";
65
+
66
+ /** A structural reference from page `from` to slug `to` that has no page behind it. */
67
+ export interface DanglingLink {
68
+ from: string;
69
+ to: string;
70
+ kind: PageLinkKind;
71
+ }
72
+
73
+ /** The slice of a `ConceptPage` the finder reads (a parsed page satisfies it). */
74
+ export interface PageLinkSource {
75
+ slug: string;
76
+ frontmatter: {
77
+ links?: readonly string[] | undefined;
78
+ edges?: readonly string[] | undefined;
79
+ };
80
+ /** Frontmatter-stripped body. */
81
+ body: string;
82
+ }
83
+
84
+ /**
85
+ * Structural references on `pages` whose target is not in `knownSlugs`.
86
+ * Only slug-shaped targets (`isSlug`, the page store's rule) are references
87
+ * at all: `[[ -f foo ]]` in a shell snippet is not a link to a page. Self
88
+ * references are ignored, each `(from, to, kind)` is reported once, and the
89
+ * result is sorted by `(from, to, kind)` so renderings are byte-stable.
90
+ */
91
+ export function findDanglingLinks(
92
+ pages: readonly PageLinkSource[],
93
+ knownSlugs: ReadonlySet<string>,
94
+ isSlug: (target: string) => boolean,
95
+ ): DanglingLink[] {
96
+ const seen = new Set<string>();
97
+ const dangling: DanglingLink[] = [];
98
+ const report = (from: string, to: string, kind: PageLinkKind): void => {
99
+ if (to === from || knownSlugs.has(to) || !isSlug(to)) {
100
+ return;
101
+ }
102
+ const key = `${kind} ${from} ${to}`;
103
+ if (seen.has(key)) {
104
+ return;
105
+ }
106
+ seen.add(key);
107
+ dangling.push({ from, to, kind });
108
+ };
109
+
110
+ for (const page of pages) {
111
+ for (const entry of page.frontmatter.links ?? []) {
112
+ report(page.slug, parseLinkEntry(entry).target, "links");
113
+ }
114
+ for (const target of extractWikilinkTargets(page.body)) {
115
+ report(page.slug, target, "wikilink");
116
+ }
117
+ for (const target of page.frontmatter.edges ?? []) {
118
+ report(page.slug, target.trim(), "edges");
119
+ }
120
+ }
121
+
122
+ dangling.sort(
123
+ (a, b) =>
124
+ compareStrings(a.from, b.from) ||
125
+ compareStrings(a.to, b.to) ||
126
+ compareStrings(a.kind, b.kind),
127
+ );
128
+ return dangling;
129
+ }
130
+
131
+ function compareStrings(a: string, b: string): number {
132
+ return a < b ? -1 : a > b ? 1 : 0;
133
+ }
@@ -153,6 +153,16 @@ export function validateSlug(slug: string): void {
153
153
  }
154
154
  }
155
155
 
156
+ /** Boolean form of {@link validateSlug}. */
157
+ export function isValidSlug(slug: string): boolean {
158
+ try {
159
+ validateSlug(slug);
160
+ return true;
161
+ } catch {
162
+ return false;
163
+ }
164
+ }
165
+
156
166
  // ---------------------------------------------------------------------------
157
167
  // Path helpers
158
168
  // ---------------------------------------------------------------------------