@vellumai/assistant 0.10.9 → 0.10.10-dev.202607162206.d08e98e

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 (456) hide show
  1. package/ARCHITECTURE.md +1 -1
  2. package/Dockerfile +8 -0
  3. package/docs/activation-funnel-telemetry.md +13 -7
  4. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/package.json +1 -0
  5. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/__tests__/redacted-credential.test.ts +200 -0
  6. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/redacted-credential.ts +226 -0
  7. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/package.json +1 -0
  8. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/redacted-credential.test.ts +200 -0
  9. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/redacted-credential.ts +226 -0
  10. package/node_modules/@vellumai/service-contracts/package.json +1 -0
  11. package/node_modules/@vellumai/service-contracts/src/__tests__/redacted-credential.test.ts +200 -0
  12. package/node_modules/@vellumai/service-contracts/src/redacted-credential.ts +226 -0
  13. package/openapi.yaml +530 -107
  14. package/package.json +1 -1
  15. package/scripts/generate-openapi.ts +8 -0
  16. package/src/__tests__/activation-early-marking.test.ts +6 -5
  17. package/src/__tests__/agent-loop-override-profile.test.ts +22 -25
  18. package/src/__tests__/agent-wake-override-profile.test.ts +21 -48
  19. package/src/__tests__/app-builder-tool-scripts.test.ts +0 -1
  20. package/src/__tests__/app-bundler.test.ts +4 -12
  21. package/src/__tests__/app-executors.test.ts +2 -49
  22. package/src/__tests__/app-routes-csp.test.ts +114 -146
  23. package/src/__tests__/auth-fallback-events-store.test.ts +8 -1
  24. package/src/__tests__/build-persisted-content.test.ts +96 -0
  25. package/src/__tests__/bundle-scanner.test.ts +27 -1
  26. package/src/__tests__/call-controller.test.ts +291 -0
  27. package/src/__tests__/call-site-routing-connection-auto-resolve.test.ts +165 -0
  28. package/src/__tests__/chat-credential-redaction.test.ts +1395 -0
  29. package/src/__tests__/chat-reveal-guard-priming.test.ts +791 -0
  30. package/src/__tests__/compaction.benchmark.test.ts +2 -1
  31. package/src/__tests__/compactor-image-manifest-trust.test.ts +50 -0
  32. package/src/__tests__/config-loader-backfill.test.ts +9 -4
  33. package/src/__tests__/config-schema-cmd.test.ts +10 -11
  34. package/src/__tests__/config-schema.test.ts +190 -257
  35. package/src/__tests__/conversation-agent-loop-fatal-cleanup.test.ts +208 -0
  36. package/src/__tests__/conversation-agent-loop-overflow.test.ts +5 -19
  37. package/src/__tests__/conversation-agent-loop.test.ts +22 -9
  38. package/src/__tests__/conversation-error.test.ts +31 -0
  39. package/src/__tests__/conversation-load-history-repair.test.ts +110 -0
  40. package/src/__tests__/conversation-process-callsite.test.ts +12 -19
  41. package/src/__tests__/conversation-routes-slash-commands.test.ts +7 -21
  42. package/src/__tests__/conversation-summarize-route.test.ts +36 -44
  43. package/src/__tests__/conversation-surfaces-action-delivery.test.ts +1 -0
  44. package/src/__tests__/conversation-surfaces-activation-emit.test.ts +7 -7
  45. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +0 -1
  46. package/src/__tests__/conversation-tool-setup-attribution.test.ts +0 -1
  47. package/src/__tests__/conversation-usage.test.ts +4 -12
  48. package/src/__tests__/credential-routes.test.ts +250 -0
  49. package/src/__tests__/credential-security-invariants.test.ts +3 -0
  50. package/src/__tests__/db-migration-rollback.test.ts +22 -0
  51. package/src/__tests__/empty-response-hook.test.ts +186 -1
  52. package/src/__tests__/external-plugin-loader.test.ts +11 -5
  53. package/src/__tests__/heartbeat-service.test.ts +0 -28
  54. package/src/__tests__/host-shell-tool.test.ts +2 -0
  55. package/src/__tests__/inactive-tool-error-messages.test.ts +2 -2
  56. package/src/__tests__/inference-no-mode-boot-e2e.test.ts +52 -8
  57. package/src/__tests__/internal-telemetry-routes.test.ts +23 -4
  58. package/src/__tests__/invite-routes-http.test.ts +12 -16
  59. package/src/__tests__/list-all-apps.test.ts +0 -4
  60. package/src/__tests__/llm-context-resolution.test.ts +32 -56
  61. package/src/__tests__/llm-request-log-turn-query.test.ts +109 -0
  62. package/src/__tests__/llm-resolver-override-or-default.test.ts +3 -52
  63. package/src/__tests__/llm-resolver.test.ts +342 -602
  64. package/src/__tests__/llm-schema.test.ts +79 -37
  65. package/src/__tests__/max-tokens-continue-hook.test.ts +19 -0
  66. package/src/__tests__/media-stream-output.test.ts +259 -3
  67. package/src/__tests__/media-stream-server-integration.test.ts +22 -1
  68. package/src/__tests__/media-stream-stt-session.test.ts +47 -0
  69. package/src/__tests__/memory-jobs-worker-cleanup-cadence.test.ts +33 -0
  70. package/src/__tests__/memory-recall-log-store.test.ts +47 -13
  71. package/src/__tests__/mock-gateway-ipc.ts +46 -1
  72. package/src/__tests__/mtime-cache.test.ts +61 -0
  73. package/src/__tests__/navigate-settings-tab.test.ts +2 -0
  74. package/src/__tests__/normalize-onboarding.test.ts +33 -0
  75. package/src/__tests__/onboarding-persona-write.test.ts +26 -0
  76. package/src/__tests__/plugin-api-resolve-credential.test.ts +140 -0
  77. package/src/__tests__/plugin-app-serve-routes.test.ts +166 -14
  78. package/src/__tests__/plugin-import-boundary-reverse-guard.test.ts +2 -0
  79. package/src/__tests__/post-turn-tool-result-truncation.test.ts +38 -0
  80. package/src/__tests__/provider-commit-message-generator.test.ts +27 -20
  81. package/src/__tests__/provider-connections-backfill.test.ts +138 -0
  82. package/src/__tests__/provider-platform-proxy-integration.test.ts +10 -31
  83. package/src/__tests__/provider-registry-ollama.test.ts +8 -18
  84. package/src/__tests__/provider-send-message-override-profile.test.ts +23 -23
  85. package/src/__tests__/provider-usage-tracking.test.ts +9 -19
  86. package/src/__tests__/prune-old-conversations-job.test.ts +12 -0
  87. package/src/__tests__/published-app-updater.test.ts +22 -16
  88. package/src/__tests__/registry.test.ts +5 -16
  89. package/src/__tests__/retry-openrouter-only-normalization.test.ts +22 -23
  90. package/src/__tests__/retry-thinking-adaptive-only.test.ts +43 -48
  91. package/src/__tests__/retry-thinking-tool-choice.test.ts +57 -66
  92. package/src/__tests__/retry-verbosity-normalization.test.ts +24 -23
  93. package/src/__tests__/reveal-success-registry.test.ts +123 -0
  94. package/src/__tests__/run-conversation-turn-persistence.test.ts +130 -0
  95. package/src/__tests__/secret-fixtures.ts +9 -0
  96. package/src/__tests__/server-history-render.test.ts +28 -0
  97. package/src/__tests__/skills.test.ts +9 -4
  98. package/src/__tests__/slack-share-routes.test.ts +0 -1
  99. package/src/__tests__/stt-stream-session.test.ts +6 -5
  100. package/src/__tests__/subagent-call-site-routing.test.ts +69 -95
  101. package/src/__tests__/subagent-disposal.test.ts +2 -0
  102. package/src/__tests__/subagent-fork-notifications.test.ts +2 -0
  103. package/src/__tests__/subagent-fork-spawn.test.ts +2 -0
  104. package/src/__tests__/subagent-manager-notify.test.ts +2 -0
  105. package/src/__tests__/subagent-role-registry.test.ts +37 -0
  106. package/src/__tests__/subagent-spawn-and-await.test.ts +1 -0
  107. package/src/__tests__/subagent-terminal-message.test.ts +50 -0
  108. package/src/__tests__/subagent-tool-gate-mode.test.ts +78 -5
  109. package/src/__tests__/surface-completion-nudge-hook.test.ts +19 -0
  110. package/src/__tests__/telemetry-routes.test.ts +99 -19
  111. package/src/__tests__/tool-audit.test.ts +34 -4
  112. package/src/__tests__/tool-executor-lifecycle-events.test.ts +1 -1
  113. package/src/__tests__/tool-profiler.test.ts +72 -1
  114. package/src/__tests__/tool-result-spool.test.ts +49 -4
  115. package/src/__tests__/tool-side-effects-slack-dm.test.ts +0 -1
  116. package/src/__tests__/ui-channel-variants.test.ts +108 -0
  117. package/src/__tests__/ui-shape-teaching.test.ts +255 -0
  118. package/src/__tests__/usage-attribution.test.ts +18 -41
  119. package/src/__tests__/user-plugin-loader.test.ts +4 -4
  120. package/src/__tests__/voice-config-update.test.ts +46 -0
  121. package/src/__tests__/voice-session-bridge.test.ts +216 -84
  122. package/src/__tests__/workspace-migration-131-drop-web-fetch-mode.test.ts +120 -0
  123. package/src/agent/loop.ts +4 -3
  124. package/src/api/events/open-conversation.test.ts +64 -0
  125. package/src/api/events/open-conversation.ts +33 -0
  126. package/src/api/index.ts +6 -0
  127. package/src/api/responses/conversation-message.ts +5 -0
  128. package/src/apps/app-store.ts +25 -35
  129. package/src/bundler/app-bundler.ts +34 -48
  130. package/src/bundler/app-compiler.ts +39 -4
  131. package/src/bundler/bundle-scanner.ts +13 -0
  132. package/src/bundler/manifest.ts +1 -1
  133. package/src/calls/__tests__/voice-session-bridge.test.ts +47 -0
  134. package/src/calls/call-constants.ts +5 -0
  135. package/src/calls/call-controller.ts +100 -32
  136. package/src/calls/call-transport.ts +9 -0
  137. package/src/calls/media-stream-output.ts +107 -4
  138. package/src/calls/media-stream-server.ts +29 -9
  139. package/src/calls/media-stream-stt-session.ts +7 -1
  140. package/src/calls/media-turn-detector.ts +11 -1
  141. package/src/calls/voice-session-bridge.ts +84 -63
  142. package/src/cli/commands/__tests__/inference-providers.test.ts +270 -33
  143. package/src/cli/commands/__tests__/notifications.test.ts +24 -3
  144. package/src/cli/commands/config.help.ts +6 -6
  145. package/src/cli/commands/credentials.help.ts +13 -0
  146. package/src/cli/commands/credentials.ts +6 -1
  147. package/src/cli/commands/email.help.ts +7 -0
  148. package/src/cli/commands/email.ts +35 -1
  149. package/src/cli/commands/inference-providers.ts +168 -107
  150. package/src/cli/commands/inference.help.ts +118 -46
  151. package/src/cli/commands/memory/index.help.ts +23 -0
  152. package/src/cli/commands/memory/nodes.ts +146 -0
  153. package/src/cli/commands/notifications.help.ts +10 -10
  154. package/src/cli/commands/oauth/connect-surface-guidance.test.ts +40 -0
  155. package/src/cli/commands/oauth/connect-surface-guidance.ts +54 -0
  156. package/src/cli/commands/oauth/connect.test.ts +126 -0
  157. package/src/cli/commands/oauth/connect.ts +29 -6
  158. package/src/cli/commands/oauth/index.help.ts +7 -1
  159. package/src/cli/commands/oauth/status.test.ts +69 -3
  160. package/src/cli/commands/oauth/status.ts +50 -12
  161. package/src/cli/commands/plugins.help.ts +13 -2
  162. package/src/cli/commands/plugins.ts +61 -7
  163. package/src/cli/commands/telemetry.help.ts +13 -0
  164. package/src/cli/commands/telemetry.ts +45 -5
  165. package/src/cli/lib/__tests__/plugin-catalog-local.test.ts +8 -2
  166. package/src/cli/lib/__tests__/upgrade-plugin.test.ts +213 -9
  167. package/src/cli/lib/bundled-marketplace.json +93 -36
  168. package/src/cli/lib/inspect-plugin.ts +5 -1
  169. package/src/cli/lib/install-from-github.ts +68 -4
  170. package/src/cli/lib/plugin-catalog-local.ts +6 -2
  171. package/src/cli/lib/plugin-fingerprint.ts +3 -3
  172. package/src/cli/lib/upgrade-plugin.ts +236 -35
  173. package/src/config/__tests__/default-profile-catalog.test.ts +8 -12
  174. package/src/config/__tests__/plugin-resident-skill-discovery.test.ts +137 -0
  175. package/src/config/__tests__/profile-materialization.test.ts +1 -88
  176. package/src/config/bundled-skills/AGENTS.md +3 -30
  177. package/src/config/bundled-skills/app-builder/SKILL.md +5 -3
  178. package/src/config/bundled-skills/app-builder/TOOLS.json +23 -0
  179. package/src/config/bundled-skills/app-builder/tools/app-open.ts +32 -0
  180. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +1 -1
  181. package/src/config/bundled-skills/phone-calls/references/CONFIG.md +7 -7
  182. package/src/config/bundled-skills/schedule/SKILL.md +1 -1
  183. package/src/config/bundled-skills/schedule/TOOLS.json +1 -1
  184. package/src/config/bundled-skills/settings/TOOLS.json +3 -1
  185. package/src/config/bundled-skills/settings/tools/navigate-settings-tab.ts +2 -0
  186. package/src/config/bundled-skills/settings/tools/voice-config-update.ts +18 -10
  187. package/src/config/bundled-skills/subagent/SKILL.md +2 -0
  188. package/src/config/bundled-skills/subagent/TOOLS.json +1 -1
  189. package/src/config/call-site-defaults.ts +3 -3
  190. package/src/config/feature-flag-registry.json +24 -39
  191. package/src/config/llm-resolver.ts +88 -475
  192. package/src/config/profile-materialization.ts +11 -11
  193. package/src/config/schema.ts +93 -127
  194. package/src/config/schemas/__tests__/live-voice.test.ts +24 -6
  195. package/src/config/schemas/__tests__/stt.test.ts +31 -3
  196. package/src/config/schemas/live-voice.ts +10 -4
  197. package/src/config/schemas/llm.ts +52 -15
  198. package/src/config/schemas/memory-lifecycle.ts +1 -1
  199. package/src/config/schemas/memory-retrospective.ts +8 -0
  200. package/src/config/schemas/memory-v2.ts +2 -2
  201. package/src/config/schemas/memory-v3.ts +1 -1
  202. package/src/config/schemas/services.ts +6 -3
  203. package/src/config/schemas/stt.ts +38 -21
  204. package/src/config/schemas/tts.ts +13 -18
  205. package/src/config/skills.ts +31 -21
  206. package/src/context/compactor.ts +44 -7
  207. package/src/context/post-turn-tool-result-truncation.ts +4 -2
  208. package/src/context/tool-result-spool.ts +26 -5
  209. package/src/conversations/__tests__/message-consolidation.test.ts +48 -0
  210. package/src/conversations/message-consolidation.ts +22 -2
  211. package/src/daemon/__tests__/conversation-tool-setup.test.ts +5 -12
  212. package/src/daemon/app-source-watcher.ts +17 -23
  213. package/src/daemon/chat-credential-redaction.ts +1365 -0
  214. package/src/daemon/conversation-agent-loop-handlers.ts +627 -34
  215. package/src/daemon/conversation-agent-loop.ts +71 -31
  216. package/src/daemon/conversation-error.ts +36 -14
  217. package/src/daemon/conversation-process.ts +22 -0
  218. package/src/daemon/conversation-store.ts +35 -0
  219. package/src/daemon/conversation-surfaces.ts +25 -25
  220. package/src/daemon/conversation-tool-setup.ts +33 -7
  221. package/src/daemon/conversation.ts +55 -5
  222. package/src/daemon/handlers/shared.ts +33 -2
  223. package/src/daemon/lifecycle.ts +4 -4
  224. package/src/daemon/message-types/conversations.ts +6 -17
  225. package/src/daemon/providers-setup.ts +8 -0
  226. package/src/daemon/tool-setup-types.ts +7 -1
  227. package/src/daemon/wake-conversation-ops.ts +10 -1
  228. package/src/hooks/types.ts +9 -0
  229. package/src/ipc/__tests__/email-ipc.test.ts +90 -0
  230. package/src/ipc/gateway-client.test.ts +59 -0
  231. package/src/ipc/gateway-client.ts +70 -27
  232. package/src/live-voice/__tests__/live-voice-events.test.ts +14 -2
  233. package/src/live-voice/__tests__/live-voice-integration.test.ts +116 -2
  234. package/src/live-voice/__tests__/live-voice-vad.test.ts +804 -13
  235. package/src/live-voice/__tests__/protocol.test.ts +122 -0
  236. package/src/live-voice/live-voice-session.ts +443 -40
  237. package/src/live-voice/protocol.ts +143 -1
  238. package/src/monitoring/__tests__/plugin-source-watch.test.ts +3 -1
  239. package/src/monitoring/plugin-source-watch.ts +3 -62
  240. package/src/notifications/README.md +1 -1
  241. package/src/permissions/checker.ts +8 -4
  242. package/src/persistence/__tests__/db-init-migrations-ok.test.ts +26 -0
  243. package/src/persistence/conversation-crud.ts +104 -2
  244. package/src/persistence/db-init.ts +14 -3
  245. package/src/persistence/job-handlers/cleanup.ts +15 -7
  246. package/src/persistence/llm-request-log-store.ts +88 -52
  247. package/src/persistence/migrations/298-move-memory-jobs-to-memory-db.ts +7 -31
  248. package/src/persistence/migrations/305-drop-contact-acl-columns.ts +3 -2
  249. package/src/persistence/migrations/326-move-injection-events-to-memory-db.ts +8 -34
  250. package/src/persistence/migrations/336-move-memory-v2-activation-logs-to-memory-db.ts +90 -0
  251. package/src/persistence/migrations/337-move-memory-recall-logs-to-memory-db.ts +114 -0
  252. package/src/persistence/migrations/338-move-memory-v3-selections-to-memory-db.ts +84 -0
  253. package/src/persistence/migrations/339-move-activation-sessions-to-memory-db.ts +52 -0
  254. package/src/persistence/migrations/__tests__/run-migrations.test.ts +155 -0
  255. package/src/persistence/migrations/helpers/relocation.ts +44 -1
  256. package/src/persistence/migrations/run-migrations.ts +25 -1
  257. package/src/persistence/schema/infrastructure.ts +9 -0
  258. package/src/persistence/schema/memory-core.ts +3 -0
  259. package/src/persistence/schema/memory-injection.ts +2 -0
  260. package/src/persistence/steps.ts +36 -0
  261. package/src/platform/client.test.ts +1 -44
  262. package/src/platform/client.ts +10 -20
  263. package/src/platform/consent-cache.test.ts +89 -23
  264. package/src/platform/consent-cache.ts +71 -33
  265. package/src/plugin-api/constants.ts +12 -0
  266. package/src/plugin-api/conversation-turn.ts +37 -14
  267. package/src/plugin-api/index.ts +11 -1
  268. package/src/plugin-api/resolve-credential.ts +75 -0
  269. package/src/plugin-api/vision-support.test.ts +8 -19
  270. package/src/plugin-api/vision-support.ts +26 -17
  271. package/src/plugins/collect-source-versions.ts +77 -0
  272. package/src/plugins/defaults/compaction/compact.ts +6 -0
  273. package/src/plugins/defaults/compaction/window-manager.ts +9 -0
  274. package/src/plugins/defaults/empty-response/hooks/post-model-call.ts +18 -24
  275. package/src/plugins/defaults/empty-response/hooks/user-prompt-submit.ts +38 -0
  276. package/src/plugins/defaults/empty-response/refusal-quarantine.ts +99 -0
  277. package/src/plugins/defaults/image-fallback/__tests__/caption-cache-persistence.test.ts +7 -3
  278. package/src/plugins/defaults/index.ts +8 -1
  279. package/src/plugins/defaults/max-tokens-continue/hooks/post-model-call.ts +4 -1
  280. package/src/plugins/defaults/memory/__tests__/activation-session-store.test.ts +48 -6
  281. package/src/plugins/defaults/memory/__tests__/db-memory-attach.test.ts +28 -22
  282. package/src/plugins/defaults/memory/__tests__/memory-log-stores-degraded.test.ts +148 -0
  283. package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +80 -8
  284. package/src/plugins/defaults/memory/__tests__/memory-retrospective-prompt.test.ts +202 -0
  285. package/src/plugins/defaults/memory/__tests__/memory-v2-activation-log-store.test.ts +64 -25
  286. package/src/plugins/defaults/memory/__tests__/memory-v2-concept-frequency.test.ts +24 -11
  287. package/src/plugins/defaults/memory/__tests__/prompt-override.test.ts +70 -6
  288. package/src/plugins/defaults/memory/__tests__/table-relocation.test.ts +278 -0
  289. package/src/plugins/defaults/memory/activation-session-store.ts +27 -20
  290. package/src/plugins/defaults/memory/context-search/sources/memory-v2.ts +2 -9
  291. package/src/plugins/defaults/memory/context-search/sources/workspace.ts +1 -8
  292. package/src/plugins/defaults/memory/graph/retriever.test.ts +6 -6
  293. package/src/plugins/defaults/memory/graph/store.ts +114 -0
  294. package/src/plugins/defaults/memory/graph/tool-handlers.ts +36 -1
  295. package/src/plugins/defaults/memory/graph/tools.ts +47 -13
  296. package/src/plugins/defaults/memory/graph-topology/build-memory-graph.ts +16 -35
  297. package/src/plugins/defaults/memory/jobs-worker.ts +10 -1
  298. package/src/plugins/defaults/memory/memory-db.ts +3 -2
  299. package/src/plugins/defaults/memory/memory-recall-log-store.ts +129 -66
  300. package/src/plugins/defaults/memory/memory-retrospective-constants.ts +8 -0
  301. package/src/plugins/defaults/memory/memory-retrospective-job.ts +11 -122
  302. package/src/plugins/defaults/memory/memory-retrospective-prompt.ts +216 -0
  303. package/src/plugins/defaults/memory/memory-v2-activation-log-store.ts +84 -46
  304. package/src/plugins/defaults/memory/memory-v2-concept-frequency.ts +42 -32
  305. package/src/plugins/defaults/memory/path-containment.ts +21 -0
  306. package/src/plugins/defaults/memory/prompt-override.ts +51 -13
  307. package/src/plugins/defaults/memory/tools.test.ts +34 -0
  308. package/src/plugins/defaults/memory/tools.ts +12 -2
  309. package/src/plugins/defaults/memory/v2/__tests__/harness-compare.test.ts +19 -15
  310. package/src/plugins/defaults/memory/v2/__tests__/harness-oracle.test.ts +24 -19
  311. package/src/plugins/defaults/memory/v2/__tests__/harness-replay-input.test.ts +19 -15
  312. package/src/plugins/defaults/memory/v2/__tests__/injection.test.ts +22 -2
  313. package/src/plugins/defaults/memory/v2/__tests__/prompts-consolidation.test.ts +5 -3
  314. package/src/plugins/defaults/memory/v2/harness/oracle.ts +59 -41
  315. package/src/plugins/defaults/memory/v2/harness/replay-input.ts +29 -25
  316. package/src/plugins/defaults/memory/v2/migration.ts +46 -16
  317. package/src/plugins/defaults/memory/v2/prompts/consolidation.ts +4 -0
  318. package/src/plugins/defaults/memory/v3/__tests__/carry-integration.test.ts +17 -15
  319. package/src/plugins/defaults/memory/v3/__tests__/gate.test.ts +24 -22
  320. package/src/plugins/defaults/memory/v3/__tests__/injection.test.ts +9 -5
  321. package/src/plugins/defaults/memory/v3/__tests__/orchestrate.test.ts +277 -10
  322. package/src/plugins/defaults/memory/v3/__tests__/selection-log-store.test.ts +57 -5
  323. package/src/plugins/defaults/memory/v3/__tests__/shadow-integration.test.ts +9 -7
  324. package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +38 -7
  325. package/src/plugins/defaults/memory/v3/hot-set.test.ts +36 -18
  326. package/src/plugins/defaults/memory/v3/hot-set.ts +12 -14
  327. package/src/plugins/defaults/memory/v3/learned-edges.test.ts +47 -27
  328. package/src/plugins/defaults/memory/v3/learned-edges.ts +12 -14
  329. package/src/plugins/defaults/memory/v3/orchestrate.ts +139 -22
  330. package/src/plugins/defaults/memory/v3/prune.test.ts +10 -3
  331. package/src/plugins/defaults/memory/v3/prune.ts +17 -11
  332. package/src/plugins/defaults/memory/v3/selection-log-store.ts +23 -11
  333. package/src/plugins/defaults/memory/v3/shadow-plugin.ts +70 -53
  334. package/src/plugins/defaults/surface-completion-nudge/hooks/post-model-call.ts +9 -6
  335. package/src/plugins/external-plugin-loader.ts +15 -15
  336. package/src/plugins/mtime-cache.ts +47 -0
  337. package/src/plugins/pipeline.ts +9 -1
  338. package/src/plugins/plugin-execution-context.ts +44 -0
  339. package/src/plugins/plugin-tree-walk.ts +31 -24
  340. package/src/plugins/source-fingerprint.ts +3 -4
  341. package/src/prompts/normalize-onboarding.ts +12 -0
  342. package/src/prompts/persona-resolver.ts +8 -0
  343. package/src/prompts/templates/BOOTSTRAP-ACTIVATION-RAIL.md +1 -1
  344. package/src/providers/__tests__/dispatch-connection-routing.test.ts +6 -9
  345. package/src/providers/__tests__/registry-native-web-search.test.ts +4 -10
  346. package/src/providers/__tests__/retry-callsite.test.ts +235 -215
  347. package/src/providers/__tests__/satellite-connection-routing.test.ts +8 -16
  348. package/src/providers/atlascloud/client.ts +10 -49
  349. package/src/providers/baseten/client.ts +43 -0
  350. package/src/providers/call-site-routing.ts +26 -13
  351. package/src/providers/connection-resolution.ts +9 -11
  352. package/src/providers/fetch-provider-catalog.ts +4 -2
  353. package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +29 -4
  354. package/src/providers/inference/__tests__/connection-availability-keyless.test.ts +78 -0
  355. package/src/providers/inference/adapter-factory.ts +27 -6
  356. package/src/providers/inference/auth.ts +29 -1
  357. package/src/providers/inference/backfill.ts +61 -10
  358. package/src/providers/inference/connection-availability.ts +3 -2
  359. package/src/providers/inference/resolve-auth.ts +9 -1
  360. package/src/providers/model-catalog.ts +37 -0
  361. package/src/providers/openai/__tests__/api-error-normalization.test.ts +24 -2
  362. package/src/providers/openai/api-key-validation.ts +70 -0
  363. package/src/providers/retry.ts +2 -0
  364. package/src/providers/types.ts +11 -4
  365. package/src/providers/vellum-model-routing.test.ts +26 -0
  366. package/src/providers/vellum-model-routing.ts +30 -0
  367. package/src/providers/voice-error-copy.ts +47 -0
  368. package/src/runtime/agent-wake.ts +40 -24
  369. package/src/runtime/for-chat-mint-registry.ts +118 -0
  370. package/src/runtime/reveal-nonce.ts +49 -0
  371. package/src/runtime/reveal-success-registry.ts +306 -0
  372. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +88 -3
  373. package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +161 -30
  374. package/src/runtime/routes/__tests__/migration-vellum-metadata-reconcile.test.ts +7 -0
  375. package/src/runtime/routes/__tests__/schedule-worker-routes.test.ts +15 -0
  376. package/src/runtime/routes/app-management-routes.ts +152 -45
  377. package/src/runtime/routes/app-routes.ts +16 -77
  378. package/src/runtime/routes/canned-message-complete.ts +16 -16
  379. package/src/runtime/routes/conversation-management-routes.ts +17 -14
  380. package/src/runtime/routes/conversation-query-routes.ts +54 -6
  381. package/src/runtime/routes/conversation-routes.ts +48 -2
  382. package/src/runtime/routes/credential-routes.ts +116 -3
  383. package/src/runtime/routes/email-routes.ts +17 -1
  384. package/src/runtime/routes/inbound-stages/transcribe-audio.test.ts +33 -5
  385. package/src/runtime/routes/inbound-stages/transcribe-audio.ts +6 -5
  386. package/src/runtime/routes/inference-provider-connection-routes.ts +77 -28
  387. package/src/runtime/routes/inference-send-routes.ts +1 -1
  388. package/src/runtime/routes/internal-telemetry-routes.ts +7 -6
  389. package/src/runtime/routes/plugins-routes.ts +37 -6
  390. package/src/runtime/routes/publish-routes.ts +15 -18
  391. package/src/runtime/routes/schedule-worker-routes.ts +9 -0
  392. package/src/runtime/routes/secret-routes.ts +10 -0
  393. package/src/runtime/routes/telemetry-routes.ts +149 -45
  394. package/src/schedule/__tests__/schedule-timezone.test.ts +101 -0
  395. package/src/schedule/__tests__/worker-watchdog.test.ts +209 -0
  396. package/src/schedule/schedule-store.ts +12 -1
  397. package/src/schedule/schedule-timezone.ts +63 -0
  398. package/src/schedule/scheduler.ts +100 -6
  399. package/src/schedule/worker-control.ts +19 -0
  400. package/src/security/auth-fallback-events-store.ts +7 -6
  401. package/src/security/secret-scanner.ts +26 -1
  402. package/src/services/published-app-updater.ts +6 -11
  403. package/src/stt/stt-stream-session.ts +36 -16
  404. package/src/subagent/manager.ts +34 -8
  405. package/src/telemetry/AGENTS.md +30 -1
  406. package/src/telemetry/__tests__/config-setting-snapshot.test.ts +18 -0
  407. package/src/telemetry/__tests__/outbox-test-harness.ts +5 -3
  408. package/src/telemetry/config-setting-snapshot.ts +44 -10
  409. package/src/telemetry/telemetry-event-sources.test.ts +124 -30
  410. package/src/telemetry/telemetry-event-sources.ts +137 -83
  411. package/src/telemetry/telemetry-events-outbox.test.ts +46 -1
  412. package/src/telemetry/telemetry-events-outbox.ts +60 -9
  413. package/src/telemetry/telemetry-wire-source.json +1 -1
  414. package/src/telemetry/telemetry-wire-validation.ts +39 -2
  415. package/src/telemetry/telemetry-wire.generated.ts +8 -0
  416. package/src/telemetry/tool-audit.ts +15 -9
  417. package/src/telemetry/tool-executed-events-store.test.ts +1 -1
  418. package/src/telemetry/turn-events-store.ts +20 -0
  419. package/src/telemetry/types.ts +45 -12
  420. package/src/telemetry/usage-telemetry-reporter.test.ts +294 -25
  421. package/src/telemetry/usage-telemetry-reporter.ts +117 -18
  422. package/src/telemetry/watchdog-direct-emit.test.ts +11 -3
  423. package/src/telemetry/watchdog-direct-emit.ts +11 -6
  424. package/src/tools/apps/executors.ts +11 -36
  425. package/src/tools/executor.ts +14 -1
  426. package/src/tools/host-terminal/host-shell.ts +6 -2
  427. package/src/tools/network/__tests__/web-fetch-firecrawl.test.ts +1 -1
  428. package/src/tools/network/__tests__/web-fetch-metadata.test.ts +25 -0
  429. package/src/tools/network/web-fetch.ts +6 -2
  430. package/src/tools/skills/sandbox-runner.ts +5 -2
  431. package/src/tools/subagent/spawn.ts +7 -11
  432. package/src/tools/terminal/shell.ts +5 -2
  433. package/src/tools/tool-manifest.ts +0 -2
  434. package/src/tools/tool-profiler.ts +37 -6
  435. package/src/tools/ui-surface/channel-variants.ts +101 -0
  436. package/src/tools/ui-surface/definitions.ts +23 -67
  437. package/src/tools/ui-surface/surface-shape-docs.ts +229 -0
  438. package/src/tts/__tests__/provider-adapters.test.ts +18 -0
  439. package/src/tts/provider-catalog.ts +2 -4
  440. package/src/tts/providers/deepgram-provider.ts +10 -1
  441. package/src/types/onboarding-context.ts +8 -0
  442. package/src/usage/attribution.ts +18 -112
  443. package/src/{config/bundled-skills/messaging/tools/gmail-mime-helpers.ts → util/mime-type.ts} +4 -1
  444. package/src/util/provider-error-patterns.ts +7 -1
  445. package/src/util/worker-process.ts +105 -0
  446. package/src/watcher/__tests__/telemetry.test.ts +17 -4
  447. package/src/watcher/telemetry.ts +5 -5
  448. package/src/workspace/migrations/131-drop-web-fetch-mode.ts +61 -0
  449. package/src/workspace/migrations/registry.ts +2 -0
  450. package/src/workspace/provider-commit-message-generator.ts +6 -4
  451. package/src/__tests__/app-open-proxy.test.ts +0 -67
  452. package/src/onboarding/onboarding-research-events-store.test.ts +0 -230
  453. package/src/onboarding/onboarding-research-events-store.ts +0 -104
  454. package/src/runtime/routes/assets/vellum-design-system.css +0 -2236
  455. package/src/tools/apps/definitions.ts +0 -73
  456. package/src/tools/apps/open-proxy.ts +0 -43
@@ -0,0 +1,118 @@
1
+ /**
2
+ * for-chat-mint-registry — daemon-side record of `--for-chat` reveals that
3
+ * actually executed.
4
+ *
5
+ * The chat-credential-reveal persist guard re-mints sentinel-shaped spans
6
+ * whose `service:field` identity matches a `--for-chat` reveal from the
7
+ * current turn (see `guardForChatSentinels`). The authority for "a
8
+ * `--for-chat` reveal happened" must be the reveal ROUTE itself — the one
9
+ * place that enforces the feature flag and proves the credential exists —
10
+ * never a string parse of the requested shell command: a segment that
11
+ * merely quotes or comments out a reveal invocation (`echo 'assistant
12
+ * credentials reveal --for-chat …'`) would otherwise allowlist an identity
13
+ * for re-minting without the route ever running, letting forged sentinels
14
+ * survive persistence.
15
+ *
16
+ * So: `handleCredentialsReveal` records every successful `--for-chat` mint
17
+ * here (identity + the exact canonical sentinel it returned), and the
18
+ * conversation loop asks for mints recorded since its turn started. The
19
+ * stored sentinel doubles as the re-mint replacement — no vault re-read,
20
+ * and a tampered type label can't survive because the replacement is the
21
+ * daemon's own original mint.
22
+ *
23
+ * Scope: records are bound to the executing conversation by the reveal
24
+ * NONCE (see `reveal-nonce.ts`) — a daemon-held secret the shell tools
25
+ * export to the tool subprocess and the CLI forwards with the reveal. A
26
+ * predictable identifier (a conversation id in a body field or env var)
27
+ * would not do: the command under execution can override its subprocess
28
+ * env and redirect the mint to any conversation it can name, and staging
29
+ * alone cannot disambiguate CONCURRENT turns (staging is a parse — a turn
30
+ * that merely quotes a reveal command stages the same identity an
31
+ * overlapping turn legitimately executes). The consumer accepts a mint
32
+ * only when its nonce matches the consumer's own conversation AND its
33
+ * identity was staged by that run's own `tool_use` commands (see
34
+ * `turnForChatMints` in `conversation-agent-loop-handlers.ts`).
35
+ * `credentials reveal` is a high-risk, approval-gated command; earning a
36
+ * mint for a conversation requires an actual route call from that
37
+ * conversation's own tool shell.
38
+ */
39
+
40
+ /** A successful `--for-chat` reveal: identity plus the minted sentinel. */
41
+ export interface ForChatMint {
42
+ service: string;
43
+ field: string;
44
+ /** The canonical sentinel the reveal route returned for this credential. */
45
+ sentinel: string;
46
+ /**
47
+ * The conversation-bound reveal nonce the CLI forwarded (see
48
+ * `reveal-nonce.ts`). Consumers match on it exactly.
49
+ */
50
+ nonce: string;
51
+ }
52
+
53
+ interface MintRecord extends ForChatMint {
54
+ seq: number;
55
+ atMs: number;
56
+ }
57
+
58
+ /**
59
+ * Retention guards. Records are tiny (one short sentinel string each), so
60
+ * the bounds exist only to keep a pathological reveal loop from growing the
61
+ * array unboundedly. The age bound comfortably outlives any single turn —
62
+ * a watermark from a turn older than this has long since been superseded.
63
+ */
64
+ const MAX_RECORDS = 256;
65
+ const MAX_AGE_MS = 6 * 60 * 60 * 1000;
66
+
67
+ let counter = 0;
68
+ let records: MintRecord[] = [];
69
+
70
+ /**
71
+ * Watermark for "now". A turn captures this at start; mints recorded after
72
+ * the capture are the ones `forChatMintsSince` returns for that turn.
73
+ */
74
+ export function currentForChatMintWatermark(): number {
75
+ return counter;
76
+ }
77
+
78
+ /** Record a successful `--for-chat` reveal. Called by the reveal route. */
79
+ export function recordForChatMint(mint: ForChatMint): void {
80
+ counter += 1;
81
+ records.push({ ...mint, seq: counter, atMs: Date.now() });
82
+ const cutoff = Date.now() - MAX_AGE_MS;
83
+ if (records.length > MAX_RECORDS || records[0]!.atMs < cutoff) {
84
+ records = records.filter((r) => r.atMs >= cutoff).slice(-MAX_RECORDS);
85
+ }
86
+ }
87
+
88
+ /**
89
+ * Mints recorded after `watermark`, deduplicated per nonce+identity (latest
90
+ * wins — one conversation re-revealing the same credential re-mints the
91
+ * same canonical sentinel). The nonce is part of the dedupe key on purpose:
92
+ * consumers filter the result by THEIR nonce afterwards, so an
93
+ * identity-only dedupe would let a concurrent conversation's later reveal
94
+ * of the same credential clobber this conversation's legitimate mint and
95
+ * neutralize its echoed sentinel. Callers MUST intersect the result with
96
+ * their own trusted staging context before treating a mint as re-mint
97
+ * authority — see the module doc.
98
+ */
99
+ export function forChatMintsSince(watermark: number): ForChatMint[] {
100
+ const byNonceAndIdentity = new Map<string, ForChatMint>();
101
+ for (const r of records) {
102
+ if (r.seq > watermark) {
103
+ byNonceAndIdentity.set(`${r.nonce}\u0000${r.service}\u0000${r.field}`, {
104
+ service: r.service,
105
+ field: r.field,
106
+ sentinel: r.sentinel,
107
+ nonce: r.nonce,
108
+ });
109
+ }
110
+ }
111
+ return [...byNonceAndIdentity.values()];
112
+ }
113
+
114
+ /** Test-only: drop all records and reset the watermark counter. */
115
+ export function resetForChatMintRegistryForTest(): void {
116
+ counter = 0;
117
+ records = [];
118
+ }
@@ -0,0 +1,49 @@
1
+ /**
2
+ * reveal-nonce — per-conversation secret binding reveal-derived authority
3
+ * to the conversation whose tool shell actually executed the reveal.
4
+ *
5
+ * The reveal route's records (plaintext proofs and `--for-chat` mints) are
6
+ * process-global, and the identities a run STAGES are parsed from command
7
+ * text — so with two turns running concurrently, conversation B could
8
+ * quote a reveal command (staging the identity) while conversation A
9
+ * executes a real reveal for the same identity, and B's guard would treat
10
+ * A's record as its own authority. Every caller-visible conversation
11
+ * identifier (`__CONVERSATION_ID`, a body field) is overridable by the
12
+ * command under execution, so the binding must be a value other
13
+ * conversations cannot know.
14
+ *
15
+ * This module holds one random nonce per conversation, created lazily and
16
+ * kept only in daemon memory. The shell tools export it to the tool
17
+ * subprocess as `__REVEAL_NONCE`; the CLI forwards it on reveal calls; the
18
+ * route stamps it onto every record; and the conversation loop accepts
19
+ * only records carrying its own nonce. A quoted/commented-out invocation
20
+ * never reaches the route, so it never produces a record with any nonce.
21
+ * A model can read its own conversation's nonce from the tool env, but
22
+ * spending it requires an actual route call from its own approval-gated
23
+ * tool shell — which is precisely the authority the binding encodes.
24
+ * Another conversation's nonce appears in no transcript, log, or URL;
25
+ * stealing one requires an exfiltration channel between two already-
26
+ * compromised conversations, which defeats any per-conversation binding.
27
+ *
28
+ * Nonces are not persisted: a daemon restart rotates them, which only
29
+ * invalidates records that are themselves turn-scoped and in-memory.
30
+ */
31
+
32
+ import { randomBytes } from "node:crypto";
33
+
34
+ const nonces = new Map<string, string>();
35
+
36
+ /** The secret reveal nonce for a conversation (lazily created). */
37
+ export function conversationRevealNonce(conversationId: string): string {
38
+ let nonce = nonces.get(conversationId);
39
+ if (nonce === undefined) {
40
+ nonce = randomBytes(16).toString("hex");
41
+ nonces.set(conversationId, nonce);
42
+ }
43
+ return nonce;
44
+ }
45
+
46
+ /** Test-only: drop all nonces so each test starts unbound. */
47
+ export function _resetRevealNoncesForTest(): void {
48
+ nonces.clear();
49
+ }
@@ -0,0 +1,306 @@
1
+ /**
2
+ * In-process registry of successful `credentials_reveal` route executions.
3
+ *
4
+ * The chat-credential-reveal persist seams need PROOF that a reveal
5
+ * actually ran before candidate resolution may read a plaintext from the
6
+ * store: a shell tool's overall success is not that proof (`assistant
7
+ * credentials reveal … || true` succeeds when the route failed, and an
8
+ * `echo` containing the command text never calls the route at all). The
9
+ * reveal route handler is the single place plaintext legitimately leaves
10
+ * the store for a reveal, so it records each success here and the agent
11
+ * loop promotes staged candidate refs only when a matching identity was
12
+ * recorded between the tool's proposal and its result.
13
+ *
14
+ * Watermarks are a monotonic sequence counter: callers capture the
15
+ * watermark when a tool_use stages refs and query "did identity X succeed
16
+ * since?" at result time — records from earlier turns or long-dead tools
17
+ * never match. The registry is process-local (route handlers and the
18
+ * conversation loop share the daemon process) and bounded by count and
19
+ * age so it cannot grow without limit (recent records get a short grace
20
+ * from the count cap so an active tool window cannot lose its proof).
21
+ *
22
+ * Only DIRECT local-principal reveals record here (the unix-socket IPC
23
+ * identity a tool shell's CLI invocation carries — see the route handler's
24
+ * gate): a web or gateway-proxied reveal (Settings row, chat chips) is not
25
+ * evidence any tool ran a reveal and must never become proof — including
26
+ * local-mode web calls, where the gateway derives the `local` principal
27
+ * from the JWT but always stamps `x-vellum-proxy-server: ipc`.
28
+ *
29
+ * Records are conversation-bound via the reveal NONCE (see
30
+ * `reveal-nonce.ts`): the shell tools export each conversation's secret
31
+ * nonce to the tool subprocess, the CLI forwards it, the route stamps it
32
+ * onto the record, and readers require an exact match. A concurrent
33
+ * conversation's reveal of the SAME identity therefore cannot satisfy
34
+ * another conversation's proof check — its records carry a different
35
+ * nonce — and a reveal with no nonce (outside any conversation's tool
36
+ * shell) records nothing.
37
+ */
38
+
39
+ interface RevealSuccessRecord {
40
+ readonly seq: number;
41
+ readonly service: string;
42
+ readonly field: string;
43
+ /**
44
+ * The plaintext the route actually served for this success. Retained so the
45
+ * persist seams redact the EXACT bytes the tool printed, rather than a later
46
+ * vault read that a rotation/deletion between reveal and persist could make
47
+ * diverge (or empty). This is the same secret the model already received in
48
+ * the tool result — holding it in the process-local, count-and-age-bounded
49
+ * registry for the length of one turn adds no new exposure surface.
50
+ */
51
+ readonly value: string;
52
+ /**
53
+ * The conversation-bound reveal nonce the CLI forwarded (see
54
+ * `reveal-nonce.ts`). Readers match on it exactly, so a record can only
55
+ * ever prove refs for the conversation whose tool shell executed the
56
+ * reveal.
57
+ */
58
+ readonly nonce: string;
59
+ readonly recordedAtMs: number;
60
+ }
61
+
62
+ const MAX_RECORDS = 256;
63
+ const MAX_AGE_MS = 6 * 60 * 60 * 1000;
64
+ /**
65
+ * Records younger than this are exempt from the count cap: a success may
66
+ * still be inside an active propose→result window (a single command with
67
+ * hundreds of reveal invocations, or concurrent turns), and count-evicting
68
+ * it would silently drop the proof — the tool's stdout then persists raw.
69
+ * Memory stays bounded: the age prune always applies, and the count cap
70
+ * resumes for anything older than the grace period.
71
+ */
72
+ const COUNT_PRUNE_GRACE_MS = 15 * 60 * 1000;
73
+
74
+ let seqCounter = 0;
75
+ let records: RevealSuccessRecord[] = [];
76
+ /**
77
+ * Evicts expired records even when the registry goes idle. Records hold
78
+ * credential plaintext, and pruning only on the next write would retain a
79
+ * lone reveal's secret in memory indefinitely — there may never be another
80
+ * reveal. Armed on every write for the earliest record's expiry,
81
+ * self-reschedules while records remain, and unref'd so it never holds the
82
+ * process open. Reads prune too, so an active conversation drops expired
83
+ * records without waiting for the timer.
84
+ */
85
+ let pruneTimer: ReturnType<typeof setTimeout> | undefined;
86
+
87
+ function prune(nowMs: number): void {
88
+ records = records.filter((r) => nowMs - r.recordedAtMs <= MAX_AGE_MS);
89
+ let excess = records.length - MAX_RECORDS;
90
+ if (excess > 0) {
91
+ // Oldest-first eviction (records are in seq order), skipping anything
92
+ // still inside the grace window.
93
+ records = records.filter((r) => {
94
+ if (excess > 0 && nowMs - r.recordedAtMs > COUNT_PRUNE_GRACE_MS) {
95
+ excess -= 1;
96
+ return false;
97
+ }
98
+ return true;
99
+ });
100
+ }
101
+ }
102
+
103
+ function schedulePrune(nowMs: number): void {
104
+ if (pruneTimer !== undefined || records.length === 0) {
105
+ return;
106
+ }
107
+ const earliestExpiryMs = records[0]!.recordedAtMs + MAX_AGE_MS;
108
+ const timer = setTimeout(
109
+ () => {
110
+ pruneTimer = undefined;
111
+ const t = Date.now();
112
+ prune(t);
113
+ schedulePrune(t);
114
+ },
115
+ Math.max(earliestExpiryMs - nowMs, 1000),
116
+ );
117
+ timer.unref?.();
118
+ pruneTimer = timer;
119
+ }
120
+
121
+ /**
122
+ * Current watermark. Capture BEFORE the reveal tool executes; only
123
+ * successes recorded after this point satisfy {@link hasRevealSuccessSince}.
124
+ */
125
+ export function currentRevealSuccessWatermark(): number {
126
+ return seqCounter;
127
+ }
128
+
129
+ /**
130
+ * Open proof windows — one per tool_use that STAGED reveal refs, closed
131
+ * when its tool_result consumes the proof. Plaintext is only ever recorded
132
+ * while at least one window is open: a user's own local CLI reveal outside
133
+ * any assistant tool turn has no pending proof to satisfy, and retaining
134
+ * its value for the age bound would expand every CLI reveal's exposure for
135
+ * nothing. Windows auto-expire at the age bound so a leaked window (a tool
136
+ * whose result never arrives) cannot arm recording forever.
137
+ */
138
+ interface RevealProofWindow {
139
+ readonly token: number;
140
+ readonly openedAtMs: number;
141
+ }
142
+
143
+ let windowCounter = 0;
144
+ let openWindows: RevealProofWindow[] = [];
145
+
146
+ function expireWindows(nowMs: number): void {
147
+ openWindows = openWindows.filter((w) => nowMs - w.openedAtMs <= MAX_AGE_MS);
148
+ clearRecordsIfNoWindows();
149
+ }
150
+
151
+ /**
152
+ * Drop every retained record the moment no proof window remains open.
153
+ * Proof consumption is the records' only purpose, and once the last
154
+ * window closes nothing can ever read them again: the consuming
155
+ * `filterRefsByRevealProof` runs BEFORE its window is closed, promoted
156
+ * refs carry the plaintext themselves, and any future staging captures a
157
+ * watermark at the current sequence counter — above every existing
158
+ * record's seq. Waiting for the age bound would keep credential plaintext
159
+ * in memory long after the proof was needed.
160
+ */
161
+ function clearRecordsIfNoWindows(): void {
162
+ if (openWindows.length > 0 || records.length === 0) {
163
+ return;
164
+ }
165
+ records = [];
166
+ if (pruneTimer !== undefined) {
167
+ clearTimeout(pruneTimer);
168
+ pruneTimer = undefined;
169
+ }
170
+ }
171
+
172
+ /**
173
+ * Open a proof window. Call when a tool_use stages reveal refs — BEFORE
174
+ * the tool executes, so the route's success during the run is recordable.
175
+ * Returns a token the staging seam must pass to
176
+ * {@link closeRevealProofWindow} when the tool's result consumes the proof.
177
+ */
178
+ export function openRevealProofWindow(): number {
179
+ expireWindows(Date.now());
180
+ windowCounter += 1;
181
+ openWindows.push({ token: windowCounter, openedAtMs: Date.now() });
182
+ return windowCounter;
183
+ }
184
+
185
+ /** Close a proof window once its staged refs have been consumed. */
186
+ export function closeRevealProofWindow(token: number): void {
187
+ openWindows = openWindows.filter((w) => w.token !== token);
188
+ clearRecordsIfNoWindows();
189
+ }
190
+
191
+ /**
192
+ * Record a successful reveal. Call ONLY from the `credentials_reveal`
193
+ * route handler, after the plaintext has been located AND the caller was
194
+ * verified as the `local` principal (a tool shell's direct-IPC CLI call) —
195
+ * this is the ground truth the promotion check trusts. `value` is the
196
+ * plaintext the route served, retained so persist redacts the exact
197
+ * printed bytes. A no-op while no proof window is open — with no staged
198
+ * tool reveal anywhere, there is nothing the plaintext could prove.
199
+ */
200
+ export function recordRevealSuccess(
201
+ service: string,
202
+ field: string,
203
+ value: string,
204
+ nonce: string,
205
+ ): void {
206
+ const nowMs = Date.now();
207
+ expireWindows(nowMs);
208
+ if (openWindows.length === 0) {
209
+ return;
210
+ }
211
+ seqCounter += 1;
212
+ records.push({
213
+ seq: seqCounter,
214
+ service,
215
+ field,
216
+ value,
217
+ nonce,
218
+ recordedAtMs: nowMs,
219
+ });
220
+ prune(nowMs);
221
+ schedulePrune(nowMs);
222
+ }
223
+
224
+ /**
225
+ * Whether the reveal route succeeded for `service`/`field` after
226
+ * `watermark` was captured.
227
+ */
228
+ export function hasRevealSuccessSince(
229
+ watermark: number,
230
+ service: string,
231
+ field: string,
232
+ nonce: string,
233
+ ): boolean {
234
+ return revealedValueSince(watermark, service, field, nonce) !== undefined;
235
+ }
236
+
237
+ /**
238
+ * The most recent plaintext the reveal route served for `service`/`field`
239
+ * after `watermark`, or `undefined` if no matching success was recorded.
240
+ */
241
+ export function revealedValueSince(
242
+ watermark: number,
243
+ service: string,
244
+ field: string,
245
+ nonce: string,
246
+ ): string | undefined {
247
+ return revealedValuesSince(watermark, service, field, nonce)[0];
248
+ }
249
+
250
+ /**
251
+ * ALL distinct plaintexts the reveal route served for `service`/`field`
252
+ * after `watermark`, most recent first. Usually a single value, but a
253
+ * rotate-and-re-reveal inside one window (reveal v1 → `credentials set` →
254
+ * reveal v2) legitimately serves two different plaintexts — both were
255
+ * printed to a tool's stdout, so persist redaction must treat each as a
256
+ * candidate; surfacing only the latest would leave the earlier bytes
257
+ * unprotected. Callers use these to redact the exact served bytes instead
258
+ * of re-reading the vault, which a rotation/deletion between reveal and
259
+ * persist could make diverge.
260
+ */
261
+ export function revealedValuesSince(
262
+ watermark: number,
263
+ service: string,
264
+ field: string,
265
+ nonce: string,
266
+ ): string[] {
267
+ const nowMs = Date.now();
268
+ // Evict, don't just filter: expired records hold plaintext and must
269
+ // leave memory as soon as any registry activity notices them.
270
+ prune(nowMs);
271
+ const matches = records
272
+ .filter(
273
+ (r) =>
274
+ r.seq > watermark &&
275
+ r.service === service &&
276
+ r.field === field &&
277
+ r.nonce === nonce,
278
+ )
279
+ .sort((a, b) => b.seq - a.seq);
280
+ const seen = new Set<string>();
281
+ const values: string[] = [];
282
+ for (const r of matches) {
283
+ if (!seen.has(r.value)) {
284
+ seen.add(r.value);
285
+ values.push(r.value);
286
+ }
287
+ }
288
+ return values;
289
+ }
290
+
291
+ /** Test-only: number of retained records, for retention assertions. */
292
+ export function _recordCountForTest(): number {
293
+ return records.length;
294
+ }
295
+
296
+ /** Test-only: clear records, windows, the prune timer, and the watermark. */
297
+ export function _resetRevealSuccessRegistryForTest(): void {
298
+ seqCounter = 0;
299
+ records = [];
300
+ windowCounter = 0;
301
+ openWindows = [];
302
+ if (pruneTimer !== undefined) {
303
+ clearTimeout(pruneTimer);
304
+ pruneTimer = undefined;
305
+ }
306
+ }
@@ -53,13 +53,16 @@ mock.module("../../../persistence/embeddings/embedding-backend.js", () => ({
53
53
  import { loadRawConfig } from "../../../config/loader.js";
54
54
  import { LLMConfigBase } from "../../../config/schemas/llm.js";
55
55
  import type { ConversationCreateType } from "../../../persistence/conversation-types.js";
56
- import { getDb, getLogsDb } from "../../../persistence/db-connection.js";
56
+ import {
57
+ getDb,
58
+ getLogsDb,
59
+ getMemorySqlite,
60
+ } from "../../../persistence/db-connection.js";
57
61
  import { initializeDb } from "../../../persistence/db-init.js";
58
62
  import {
59
63
  conversationKeys,
60
64
  conversations,
61
65
  llmRequestLogs,
62
- memoryV2ActivationLogs,
63
66
  messages,
64
67
  providerConnections,
65
68
  } from "../../../persistence/schema/index.js";
@@ -106,7 +109,7 @@ function dispatchConversationLlmContext(queryParams: Record<string, string>) {
106
109
  function clearTables(): void {
107
110
  const db = getDb();
108
111
  getLogsDb()!.delete(llmRequestLogs).run();
109
- db.delete(memoryV2ActivationLogs).run();
112
+ getMemorySqlite()!.exec(`DELETE FROM memory_v2_activation_logs`);
110
113
  db.delete(messages).run();
111
114
  db.delete(conversationKeys).run();
112
115
  db.delete(conversations).run();
@@ -1396,6 +1399,88 @@ describe("custom profile write normalization (complete overrides)", () => {
1396
1399
  });
1397
1400
  });
1398
1401
 
1402
+ describe("call-site override tuning backfill", () => {
1403
+ const configPatchRoute = ROUTES.find(
1404
+ (r) => r.operationId === "config_patch",
1405
+ )!;
1406
+
1407
+ const savedCallSites = () =>
1408
+ (
1409
+ loadRawConfig().llm as {
1410
+ callSites?: Record<string, Record<string, unknown>>;
1411
+ }
1412
+ ).callSites ?? {};
1413
+
1414
+ beforeEach(() => {
1415
+ rawConfigFixture = { llm: {} };
1416
+ seedRawConfig();
1417
+ });
1418
+
1419
+ test("PATCH creating a bare { profile } entry backfills shipped tuning", async () => {
1420
+ await configPatchRoute.handler({
1421
+ body: {
1422
+ llm: {
1423
+ callSites: {
1424
+ memoryRouter: { profile: "mine", provider: null, model: null },
1425
+ commitMessage: { profile: "mine", provider: null, model: null },
1426
+ },
1427
+ },
1428
+ },
1429
+ });
1430
+ const memoryRouter = savedCallSites().memoryRouter!;
1431
+ expect(memoryRouter.profile).toBe("mine");
1432
+ expect(memoryRouter.contextWindow).toEqual({ maxInputTokens: 1_000_000 });
1433
+ const commitMessage = savedCallSites().commitMessage!;
1434
+ expect(commitMessage.profile).toBe("mine");
1435
+ expect(commitMessage.maxTokens).toBe(120);
1436
+ expect(commitMessage.effort).toBe("low");
1437
+ });
1438
+
1439
+ test("explicit patch values win over shipped tuning on a new entry", async () => {
1440
+ await configPatchRoute.handler({
1441
+ body: {
1442
+ llm: {
1443
+ callSites: { commitMessage: { profile: "mine", maxTokens: 500 } },
1444
+ },
1445
+ },
1446
+ });
1447
+ const saved = savedCallSites().commitMessage!;
1448
+ expect(saved.maxTokens).toBe(500);
1449
+ expect(saved.temperature).toBe(0.2);
1450
+ });
1451
+
1452
+ test("existing entries are never backfilled — customization is preserved", async () => {
1453
+ rawConfigFixture = {
1454
+ llm: { callSites: { recall: { profile: "old", maxTokens: 200 } } },
1455
+ };
1456
+ seedRawConfig();
1457
+ await configPatchRoute.handler({
1458
+ body: {
1459
+ llm: {
1460
+ callSites: {
1461
+ recall: { profile: "mine", provider: null, model: null },
1462
+ },
1463
+ },
1464
+ },
1465
+ });
1466
+ const saved = savedCallSites().recall!;
1467
+ expect(saved.profile).toBe("mine");
1468
+ expect(saved.maxTokens).toBe(200);
1469
+ expect(saved.disableCache).toBeUndefined();
1470
+ });
1471
+
1472
+ test("deleting an entry (null) is untouched by the backfill", async () => {
1473
+ rawConfigFixture = {
1474
+ llm: { callSites: { recall: { profile: "old" } } },
1475
+ };
1476
+ seedRawConfig();
1477
+ await configPatchRoute.handler({
1478
+ body: { llm: { callSites: { recall: null } } },
1479
+ });
1480
+ expect(savedCallSites().recall).toBeUndefined();
1481
+ });
1482
+ });
1483
+
1399
1484
  describe("config invariant flag enrichment", () => {
1400
1485
  const configGetRoute = ROUTES.find((r) => r.operationId === "config_get")!;
1401
1486
  const configPatchRoute = ROUTES.find(