@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
@@ -19,6 +19,7 @@ import { z } from "zod";
19
19
 
20
20
  import { getConfig } from "../../../../config/loader.js";
21
21
  import type { AssistantConfig } from "../../../../config/types.js";
22
+ import { getTelemetrySqlite } from "../../../../persistence/db-connection.js";
22
23
  import {
23
24
  ACTOR_PRINCIPALS,
24
25
  type RoutePolicy,
@@ -26,6 +27,7 @@ import {
26
27
  import type { RouteDefinition } from "../../../../runtime/routes/types.js";
27
28
  import { getLogger } from "../logging.js";
28
29
  import { backfillAllSections } from "../v3/maintain-job.js";
30
+ import { MEMORY_V3_INJECTION_GATE_CHECK_NAME } from "../v3/orchestrate.js";
29
31
  import { invalidateLanes } from "../v3/shadow-plugin.js";
30
32
 
31
33
  const log = getLogger("memory-v3-routes");
@@ -101,6 +103,211 @@ const WRITE_POLICY: RoutePolicy = {
101
103
  allowedPrincipalTypes: ACTOR_PRINCIPALS,
102
104
  };
103
105
 
106
+ // ---------------------------------------------------------------------------
107
+ // gate-stats
108
+ // ---------------------------------------------------------------------------
109
+
110
+ const DEFAULT_LOOKBACK_DAYS = 30;
111
+ const MAX_LOOKBACK_DAYS = 90;
112
+ const MS_PER_DAY = 24 * 60 * 60 * 1000;
113
+
114
+ /** Corpus size buckets for gate-stats aggregation (in concept pages). */
115
+ const CORPUS_BUCKETS = [
116
+ { label: "0–9", min: 0, max: 9 },
117
+ { label: "10–49", min: 10, max: 49 },
118
+ { label: "50–199", min: 50, max: 199 },
119
+ { label: "200+", min: 200, max: Infinity },
120
+ ] as const;
121
+
122
+ type BucketLabel = (typeof CORPUS_BUCKETS)[number]["label"];
123
+
124
+ interface BucketAcc {
125
+ total: number;
126
+ scored: number;
127
+ passed: number;
128
+ /** Passes that came from scored runs — the numerator for scoredPassRate. */
129
+ scoredPassed: number;
130
+ reasons: Record<string, number>;
131
+ }
132
+
133
+ function corpusBucketLabel(pageCount: number): BucketLabel {
134
+ for (const b of CORPUS_BUCKETS) {
135
+ if (pageCount <= b.max) {
136
+ return b.label;
137
+ }
138
+ }
139
+ return "200+";
140
+ }
141
+
142
+ interface GatePayloadDetail {
143
+ pass?: boolean;
144
+ reason?: string;
145
+ scored?: boolean;
146
+ real_concept_page_count?: number;
147
+ }
148
+
149
+ const GateStatsBucketSchema = z.object({
150
+ pageCountRange: z
151
+ .string()
152
+ .describe("Concept page count range (e.g. '10–49')"),
153
+ total: z
154
+ .number()
155
+ .describe("All gate runs in this bucket (scored + pass-open)"),
156
+ scored: z
157
+ .number()
158
+ .describe("Runs that weighed scores (dense lane available)"),
159
+ passed: z.number().describe("Runs that passed the gate"),
160
+ scoredPassRate: z
161
+ .number()
162
+ .nullable()
163
+ .describe(
164
+ "scoredPasses / scored (contested decisions only); null when no scored runs in bucket",
165
+ ),
166
+ reasons: z
167
+ .record(z.string(), z.number())
168
+ .describe("Gate reason code → run count (dense_pass, fail_no_signal, …)"),
169
+ });
170
+
171
+ export const GateStatsResponseSchema = z.object({
172
+ lookbackDays: z.number().describe("Lookback window applied"),
173
+ totalRuns: z.number().describe("Total gate runs found in the window"),
174
+ buckets: z
175
+ .array(GateStatsBucketSchema)
176
+ .describe("Stats by concept page count range, lean→large"),
177
+ unknownPageCount: z
178
+ .object({
179
+ total: z.number(),
180
+ passed: z.number(),
181
+ reasons: z.record(z.string(), z.number()),
182
+ })
183
+ .describe("Runs with no real_concept_page_count in telemetry detail"),
184
+ });
185
+
186
+ export type GateStatsResponse = z.infer<typeof GateStatsResponseSchema>;
187
+
188
+ /**
189
+ * Aggregate memory v3 injection gate runs from the telemetry outbox for the
190
+ * given lookback window. Groups runs by concept page count bucket and computes
191
+ * pass rates and reason distributions per bucket.
192
+ *
193
+ * Pass-open shortcuts (dense disabled/unavailable, gate threw) are included in
194
+ * `total` but not in `scored`, so `scoredPassRate` reflects only contested gate
195
+ * decisions — the signal that actually reveals whether the gate thresholds are
196
+ * calibrated correctly.
197
+ */
198
+ export function handleMemoryV3GateStats(
199
+ lookbackDays: number = DEFAULT_LOOKBACK_DAYS,
200
+ db: ReturnType<typeof getTelemetrySqlite> = getTelemetrySqlite(),
201
+ ): GateStatsResponse {
202
+ const days = Math.min(
203
+ Math.max(1, Math.floor(lookbackDays)),
204
+ MAX_LOOKBACK_DAYS,
205
+ );
206
+ const since = Date.now() - days * MS_PER_DAY;
207
+
208
+ const emptyBuckets = (): GateStatsResponse["buckets"] =>
209
+ CORPUS_BUCKETS.map((b) => ({
210
+ pageCountRange: b.label,
211
+ total: 0,
212
+ scored: 0,
213
+ passed: 0,
214
+ scoredPassRate: null,
215
+ reasons: {},
216
+ }));
217
+ if (!db) {
218
+ return {
219
+ lookbackDays: days,
220
+ totalRuns: 0,
221
+ buckets: emptyBuckets(),
222
+ unknownPageCount: { total: 0, passed: 0, reasons: {} },
223
+ };
224
+ }
225
+
226
+ const rows = db
227
+ .query(
228
+ /*sql*/ `
229
+ SELECT payload FROM telemetry_events
230
+ WHERE name = 'watchdog'
231
+ AND created_at >= ?
232
+ AND json_extract(payload, '$.check_name') = ?
233
+ `,
234
+ )
235
+ .all(since, MEMORY_V3_INJECTION_GATE_CHECK_NAME) as Array<{
236
+ payload: string;
237
+ }>;
238
+
239
+ const bucketMap = new Map<BucketLabel, BucketAcc>(
240
+ CORPUS_BUCKETS.map((b) => [
241
+ b.label,
242
+ { total: 0, scored: 0, passed: 0, scoredPassed: 0, reasons: {} },
243
+ ]),
244
+ );
245
+ const unknown: BucketAcc = {
246
+ total: 0,
247
+ scored: 0,
248
+ passed: 0,
249
+ scoredPassed: 0,
250
+ reasons: {},
251
+ };
252
+
253
+ for (const row of rows) {
254
+ let detail: GatePayloadDetail;
255
+ try {
256
+ const parsed = JSON.parse(row.payload) as { detail?: GatePayloadDetail };
257
+ detail = parsed.detail ?? {};
258
+ } catch {
259
+ continue;
260
+ }
261
+
262
+ const pageCount =
263
+ typeof detail.real_concept_page_count === "number"
264
+ ? detail.real_concept_page_count
265
+ : null;
266
+
267
+ const acc =
268
+ pageCount !== null
269
+ ? (bucketMap.get(corpusBucketLabel(pageCount)) ?? unknown)
270
+ : unknown;
271
+
272
+ acc.total += 1;
273
+ if (detail.scored === true) {
274
+ acc.scored += 1;
275
+ if (detail.pass === true) {
276
+ acc.scoredPassed += 1;
277
+ }
278
+ }
279
+ if (detail.pass === true) {
280
+ acc.passed += 1;
281
+ }
282
+ const reason =
283
+ typeof detail.reason === "string" ? detail.reason : "unknown";
284
+ acc.reasons[reason] = (acc.reasons[reason] ?? 0) + 1;
285
+ }
286
+
287
+ const buckets = CORPUS_BUCKETS.map((b) => {
288
+ const acc = bucketMap.get(b.label)!;
289
+ return {
290
+ pageCountRange: b.label,
291
+ total: acc.total,
292
+ scored: acc.scored,
293
+ passed: acc.passed,
294
+ scoredPassRate: acc.scored > 0 ? acc.scoredPassed / acc.scored : null,
295
+ reasons: acc.reasons,
296
+ };
297
+ });
298
+
299
+ return {
300
+ lookbackDays: days,
301
+ totalRuns: rows.length,
302
+ buckets,
303
+ unknownPageCount: {
304
+ total: unknown.total,
305
+ passed: unknown.passed,
306
+ reasons: unknown.reasons,
307
+ },
308
+ };
309
+ }
310
+
104
311
  export const ROUTES: RouteDefinition[] = [
105
312
  {
106
313
  operationId: "memory_v3_rebuild_index",
@@ -197,6 +197,7 @@ const { memoryV2ConsolidateJob, CONSOLIDATION_FAILURE_CHECKPOINT_KEY } =
197
197
  const { CUTOFF_PLACEHOLDER, CONSOLIDATION_PROMPT } =
198
198
  await import("../prompts/consolidation.js");
199
199
  const { invalidatePageIndex } = await import("../page-index.js");
200
+ const { formatRememberEntry } = await import("../../buffer-format.js");
200
201
 
201
202
  // The handler only reads `config.memory.enabled`, `config.memory.v2.enabled`,
202
203
  // `config.memory.v2.consolidation_prompt_path`, and
@@ -437,6 +438,38 @@ describe("memoryV2ConsolidateJob — chunked cutoff (consolidation_max_entries_p
437
438
  expect(outcome.cutoff).not.toBe("[meeting-notes]");
438
439
  });
439
440
 
441
+ test("a fact whose body holds a canonical entry line counts once and never supplies the cutoff", async () => {
442
+ // Written through the real writer, so this covers the round trip rather
443
+ // than a hand-built buffer: the body line carries the exact shape an entry
444
+ // opening has. Counting it would both inflate the per-run budget and let
445
+ // the cutoff be drawn from inside a fact, deferring entries against a
446
+ // timestamp the user never filed anything at.
447
+ writeFileSync(
448
+ bufferPath(),
449
+ formatRememberEntry(
450
+ "Alice's rollout plan:\n- [Apr 28, 9:00 AM] cutover",
451
+ new Date(2026, 3, 27, 9, 0),
452
+ ) +
453
+ formatRememberEntry(
454
+ "Bob takes his coffee black.",
455
+ new Date(2026, 3, 27, 9, 1),
456
+ ),
457
+ );
458
+
459
+ const outcome = await memoryV2ConsolidateJob(
460
+ makeJob(),
461
+ configWithMaxEntries(2),
462
+ );
463
+
464
+ expect(outcome.kind).toBe("invoked");
465
+ if (outcome.kind !== "invoked") {
466
+ throw new Error("unreachable");
467
+ }
468
+ // Two facts, cap 2 → no chunking. Three would chunk and defer one.
469
+ expect(outcome.deferredEntries).toBe(0);
470
+ expect(outcome.cutoff).not.toBe("Apr 28, 9:00 AM");
471
+ });
472
+
440
473
  test("multi-line entries under the cap → full-buffer cutoff even when raw line count exceeds it", async () => {
441
474
  writeFileSync(
442
475
  bufferPath(),
@@ -582,6 +615,32 @@ describe("memoryV2ConsolidateJob — non-empty buffer", () => {
582
615
  }
583
616
  });
584
617
 
618
+ test("threads page-index dangling links into the prompt's repair step", async () => {
619
+ const conceptsDir = join(tmpWorkspace, "memory", "concepts");
620
+ const sourcePath = join(conceptsDir, "atl-1290.md");
621
+ // A `links:` entry naming a page that does not exist (the LUM-3316 shape).
622
+ writeFileSync(
623
+ sourcePath,
624
+ "---\nlinks:\n - atl-1291\n---\n# ATL-1290\n\nSee [[atl-1291]].\n",
625
+ );
626
+ invalidatePageIndex();
627
+ try {
628
+ const result = await memoryV2ConsolidateJob(makeJob(), CONFIG);
629
+ expect(result.kind).toBe("invoked");
630
+ const prompt = runnerLastArgs?.prompt as string;
631
+ expect(prompt).toContain("resolve dangling links");
632
+ expect(prompt).toContain(
633
+ "- `memory/concepts/atl-1290.md` → `atl-1291` (frontmatter `links:` entry)",
634
+ );
635
+ expect(prompt).toContain(
636
+ "- `memory/concepts/atl-1290.md` → `atl-1291` (inline `[[wikilink]]` in the body)",
637
+ );
638
+ } finally {
639
+ rmSync(sourcePath, { force: true });
640
+ invalidatePageIndex();
641
+ }
642
+ });
643
+
585
644
  test("dispatches with skipPromptIndexing so the kickoff prompt is not indexed", async () => {
586
645
  // The prompt is a static instruction manual — indexing it would write
587
646
  // near-identical memory segments, embeddings, and a lexical entry on
@@ -847,6 +906,94 @@ describe("memoryV2ConsolidateJob — progress check and follow-up coalescing", (
847
906
  expect(enqueuedJobs).toHaveLength(0);
848
907
  });
849
908
 
909
+ test("a run that leaves dangling links reports the count and still enqueues follow-ups", async () => {
910
+ const conceptsDir = join(tmpWorkspace, "memory", "concepts");
911
+ runnerImpl = async () => {
912
+ // The agent files the buffer into a page whose links: names a page it
913
+ // never spawned. Writing through the file tools would invalidate the
914
+ // index via the tool hook; the stub does so by hand.
915
+ writeFileSync(
916
+ join(conceptsDir, "atl-1290.md"),
917
+ "---\nlinks:\n - atl-1291\n---\n# ATL-1290\n",
918
+ );
919
+ writeFileSync(bufferPath(), "");
920
+ invalidatePageIndex();
921
+ return { conversationId: "conv-1", ok: true };
922
+ };
923
+ try {
924
+ const result = await memoryV2ConsolidateJob(makeJob(), CONFIG);
925
+
926
+ expect(result.kind).toBe("invoked");
927
+ if (result.kind !== "invoked") {
928
+ throw new Error("unreachable");
929
+ }
930
+ expect(result.noProgress).toBe(false);
931
+ expect(result.danglingLinks).toBe(1);
932
+ // Surfaced, not gating: the page that was written still gets indexed.
933
+ expect(result.followUpJobIds).toEqual(["job-1"]);
934
+ expect(enqueuedJobs.map((j) => j.type)).toEqual(["memory_v2_reembed"]);
935
+ } finally {
936
+ rmSync(join(conceptsDir, "atl-1290.md"), { force: true });
937
+ invalidatePageIndex();
938
+ }
939
+ });
940
+
941
+ test("a run that spawns the missing target clears the pre-run dangling link", async () => {
942
+ const conceptsDir = join(tmpWorkspace, "memory", "concepts");
943
+ writeFileSync(
944
+ join(conceptsDir, "atl-1290.md"),
945
+ "---\nlinks:\n - atl-1291\n---\n# ATL-1290\n",
946
+ );
947
+ invalidatePageIndex();
948
+ runnerImpl = async () => {
949
+ writeFileSync(join(conceptsDir, "atl-1291.md"), "# ATL-1291\n");
950
+ writeFileSync(bufferPath(), "");
951
+ invalidatePageIndex();
952
+ return { conversationId: "conv-1", ok: true };
953
+ };
954
+ try {
955
+ const result = await memoryV2ConsolidateJob(makeJob(), CONFIG);
956
+
957
+ expect(result.kind).toBe("invoked");
958
+ if (result.kind !== "invoked") {
959
+ throw new Error("unreachable");
960
+ }
961
+ // The pre-run index fed the repair step...
962
+ expect(runnerLastArgs?.prompt as string).toContain(
963
+ "resolve dangling links",
964
+ );
965
+ // ...and the post-run count reflects the repair.
966
+ expect(result.danglingLinks).toBe(0);
967
+ } finally {
968
+ rmSync(join(conceptsDir, "atl-1290.md"), { force: true });
969
+ rmSync(join(conceptsDir, "atl-1291.md"), { force: true });
970
+ invalidatePageIndex();
971
+ }
972
+ });
973
+
974
+ test("a no-progress run still reports its dangling-link count", async () => {
975
+ const conceptsDir = join(tmpWorkspace, "memory", "concepts");
976
+ writeFileSync(
977
+ join(conceptsDir, "atl-1290.md"),
978
+ "---\nlinks:\n - atl-1291\n---\n# ATL-1290\n",
979
+ );
980
+ invalidatePageIndex();
981
+ runnerImpl = async () => ({ conversationId: "conv-1", ok: true });
982
+ try {
983
+ const result = await memoryV2ConsolidateJob(makeJob(), CONFIG);
984
+
985
+ expect(result.kind).toBe("invoked");
986
+ if (result.kind !== "invoked") {
987
+ throw new Error("unreachable");
988
+ }
989
+ expect(result.noProgress).toBe(true);
990
+ expect(result.danglingLinks).toBe(1);
991
+ } finally {
992
+ rmSync(join(conceptsDir, "atl-1290.md"), { force: true });
993
+ invalidatePageIndex();
994
+ }
995
+ });
996
+
850
997
  test("a pending reembed suppresses its duplicate; v3 maintain still enqueues", async () => {
851
998
  v3FlagOn = true;
852
999
  pendingJobTypes.add("memory_v2_reembed");
@@ -26,6 +26,7 @@ import {
26
26
  CONSOLIDATION_PROMPT_V3,
27
27
  CORE_PAGES_CONSOLIDATION_SECTION,
28
28
  renderConsolidationPrompt,
29
+ renderDanglingLinksSection,
29
30
  renderParseFailuresSection,
30
31
  } from "../prompts/consolidation.js";
31
32
 
@@ -59,6 +60,13 @@ const GATED_SECTIONS: Array<{
59
60
  ]),
60
61
  marker: "repair unreadable pages",
61
62
  },
63
+ {
64
+ name: "dangling-link repair step (gate: index-reported dangling links)",
65
+ section: renderDanglingLinksSection([
66
+ { from: "example-page", to: "example-target", kind: "links" },
67
+ ]),
68
+ marker: "resolve dangling links",
69
+ },
62
70
  ];
63
71
 
64
72
  /** All `ConsolidationPromptOptions` gates forced off — the v2-only rendering. */
@@ -15,7 +15,6 @@ import {
15
15
  getReachable,
16
16
  invalidateEdgeIndex,
17
17
  totalEdgeCount,
18
- validateEdgeTargets,
19
18
  } from "../edge-index.js";
20
19
  import { deletePage, writePage } from "../page-store.js";
21
20
  import type { ConceptPage } from "../types.js";
@@ -228,35 +227,6 @@ describe("getReachable", () => {
228
227
  });
229
228
  });
230
229
 
231
- // ---------------------------------------------------------------------------
232
- // validateEdgeTargets
233
- // ---------------------------------------------------------------------------
234
-
235
- describe("validateEdgeTargets", () => {
236
- test("ok=true when every outgoing target has a known slug", async () => {
237
- await writePage(workspaceDir, makePage("alice", ["bob"]));
238
- await writePage(workspaceDir, makePage("bob"));
239
-
240
- const idx = await getEdgeIndex(workspaceDir);
241
- const result = validateEdgeTargets(idx, new Set(["alice", "bob"]));
242
- expect(result).toEqual({ ok: true, missing: [] });
243
- });
244
-
245
- test("flags outgoing targets that don't correspond to a known slug", async () => {
246
- await writePage(workspaceDir, makePage("alice", ["ghost", "bob"]));
247
- await writePage(workspaceDir, makePage("bob", ["phantom"]));
248
-
249
- const idx = await getEdgeIndex(workspaceDir);
250
- const result = validateEdgeTargets(idx, new Set(["alice", "bob"]));
251
-
252
- expect(result.ok).toBe(false);
253
- expect(result.missing).toEqual([
254
- { from: "alice", to: "ghost" },
255
- { from: "bob", to: "phantom" },
256
- ]);
257
- });
258
- });
259
-
260
230
  // ---------------------------------------------------------------------------
261
231
  // totalEdgeCount
262
232
  // ---------------------------------------------------------------------------
@@ -12,7 +12,8 @@
12
12
  * - Batch over the cap: RangeError.
13
13
  * - In-batch duplicate slug: later occurrence `invalid`.
14
14
  * - Non-blocking warnings: missing `source`, unparseable `origin_date`,
15
- * frontmatter `slug` differing from the storage slug.
15
+ * frontmatter `slug` differing from the storage slug, link targets
16
+ * neither on disk nor in the batch.
16
17
  * - Under-lock collision snapshot: a page committed between validation and
17
18
  * the write phase is skipped, and `listPages` runs after `tryAcquireLock`.
18
19
  * - Partial write failure: the reindex fan-out still runs for committed
@@ -363,6 +364,77 @@ describe("ingestPages", () => {
363
364
  expect(await listPages(workspace)).toEqual(["people/alice", "people/bob"]);
364
365
  });
365
366
 
367
+ test("link targets neither on disk nor in the batch warn without blocking the write", async () => {
368
+ // `people/bob` already exists on disk; `projects/garden` arrives in the
369
+ // same batch; `atl-1291` and `ghost` exist nowhere.
370
+ await ingestPages(workspace, [
371
+ { slug: "people/bob", content: validPage("Bob.") },
372
+ ]);
373
+ enqueuedJobs.length = 0;
374
+
375
+ const summary = await ingestPages(workspace, [
376
+ {
377
+ slug: "people/alice",
378
+ content:
379
+ "---\nsource: import:chatgpt\norigin_date: 2025-03-14\n" +
380
+ "links:\n - people/bob\n - projects/garden\n - atl-1291\n" +
381
+ "edges: [ghost]\n---\n" +
382
+ "Alice mentions [[projects/garden]] and [[people/bob]] and [[skills/deploy]].\n",
383
+ },
384
+ { slug: "projects/garden", content: validPage("Garden.") },
385
+ ]);
386
+
387
+ expect(summary.written).toBe(2);
388
+ expect(summary.invalid).toBe(0);
389
+ const [alice, garden] = summary.results;
390
+ expect(alice.action).toBe("written");
391
+ expect(alice.warnings).toHaveLength(1);
392
+ // Every unresolved target on one line, sorted; targets that resolve on
393
+ // disk, in the batch, or under a synthetic prefix stay silent.
394
+ expect(alice.warnings[0]).toContain(
395
+ "link targets not on disk or in this batch: atl-1291, ghost",
396
+ );
397
+ expect(alice.warnings[0]).not.toContain("people/bob");
398
+ expect(alice.warnings[0]).not.toContain("projects/garden");
399
+ expect(alice.warnings[0]).not.toContain("skills/deploy");
400
+ expect(garden.warnings).toEqual([]);
401
+ // Written regardless, and reindexed.
402
+ expect(await readPage(workspace, "people/alice")).not.toBeNull();
403
+ expect(enqueuedJobs.map((j) => j.type).sort()).toEqual([
404
+ "memory_v2_reembed",
405
+ "memory_v3_maintain",
406
+ ]);
407
+ });
408
+
409
+ test("dangling-link warnings are reported on dry-run and skipped for pages the batch will not write", async () => {
410
+ await ingestPages(workspace, [
411
+ { slug: "people/bob", content: validPage("Bob.") },
412
+ ]);
413
+
414
+ const summary = await ingestPages(
415
+ workspace,
416
+ [
417
+ {
418
+ slug: "people/alice",
419
+ content: validPage("Alice mentions [[nowhere]]."),
420
+ },
421
+ // Exists on disk and overwrite is off, so this content is never
422
+ // written; its dangling link is not this batch's business.
423
+ {
424
+ slug: "people/bob",
425
+ content: validPage("Bob mentions [[also-nowhere]]."),
426
+ },
427
+ ],
428
+ { dryRun: true },
429
+ );
430
+
431
+ expect(summary.results[0].action).toBe("written");
432
+ expect(summary.results[0].warnings.join(" ")).toContain("nowhere");
433
+ expect(summary.results[1].action).toBe("skipped_exists");
434
+ expect(summary.results[1].warnings).toEqual([]);
435
+ expect(await readPage(workspace, "people/alice")).toBeNull();
436
+ });
437
+
366
438
  test("a pending reindex job of the same type suppresses its duplicate enqueue", async () => {
367
439
  pendingJobTypes = new Set(["memory_v2_reembed"]);
368
440
  await ingestPages(workspace, [
@@ -56,6 +56,7 @@ const {
56
56
  } = await import("../page-index.js");
57
57
  const { writePage } = await import("../page-store.js");
58
58
  const { invalidateEdgeIndex } = await import("../edge-index.js");
59
+ const { LINK_SEPARATOR } = await import("../page-links.js");
59
60
 
60
61
  let workspaceDir: string;
61
62
 
@@ -272,6 +273,42 @@ describe("getPageIndex", () => {
272
273
  const alice = idx.bySlug.get("alice")!;
273
274
  const bob = idx.bySlug.get("bob")!;
274
275
  expect(alice.edges).toEqual([bob.id]);
276
+ // Dropped from the numeric edges, reported on the index.
277
+ expect(idx.danglingLinks).toEqual([
278
+ { from: "alice", to: "ghost", kind: "edges" },
279
+ ]);
280
+ });
281
+
282
+ test("danglingLinks covers links:, body wikilinks, and edges:, resolved against pages and synthetic rows", async () => {
283
+ skillState.entries = [{ id: "browser", content: "Drive a browser." }];
284
+ const alice = makePage("alice", {
285
+ summary: "A",
286
+ edges: ["bob"],
287
+ body: "Alice uses [[skills/browser]] and [[bob|Bob]] but also [[nobody#sec]].",
288
+ });
289
+ alice.frontmatter.links = [
290
+ `bob${LINK_SEPARATOR}peer`,
291
+ `atl-1291${LINK_SEPARATOR}ticket`,
292
+ ];
293
+ await writePage(workspaceDir, alice);
294
+ await writePage(workspaceDir, makePage("bob", { summary: "B" }));
295
+
296
+ const idx = await getPageIndex(workspaceDir);
297
+ expect(idx.danglingLinks).toEqual([
298
+ { from: "alice", to: "atl-1291", kind: "links" },
299
+ { from: "alice", to: "nobody", kind: "wikilink" },
300
+ ]);
301
+ });
302
+
303
+ test("danglingLinks is empty when every reference resolves", async () => {
304
+ await writePage(
305
+ workspaceDir,
306
+ makePage("alice", { summary: "A", edges: ["bob"], body: "See [[bob]]." }),
307
+ );
308
+ await writePage(workspaceDir, makePage("bob", { summary: "B" }));
309
+
310
+ const idx = await getPageIndex(workspaceDir);
311
+ expect(idx.danglingLinks).toEqual([]);
275
312
  });
276
313
 
277
314
  test("exposes frontmatter leaves in the index entry", async () => {