@vellumai/assistant 0.10.8 → 0.10.9-dev.202607152225.ec6fdbf

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 (1453) hide show
  1. package/AGENTS.md +4 -0
  2. package/ARCHITECTURE.md +52 -88
  3. package/README.md +14 -35
  4. package/docs/activation-funnel-telemetry.md +20 -18
  5. package/docs/architecture/scheduling.md +0 -86
  6. package/docs/runbook-trusted-contacts.md +19 -19
  7. package/docs/trusted-contact-access.md +70 -69
  8. package/docs/workspace-tools.md +4 -2
  9. package/eslint-rules/__tests__/cli-no-daemon-internals.test.ts +62 -360
  10. package/eslint-rules/cli-no-daemon-internals.js +91 -275
  11. package/eslint.config.mjs +4 -4
  12. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/package.json +1 -0
  13. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/__tests__/redacted-credential.test.ts +200 -0
  14. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/redacted-credential.ts +226 -0
  15. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/package.json +1 -0
  16. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/redacted-credential.test.ts +200 -0
  17. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/redacted-credential.ts +226 -0
  18. package/node_modules/@vellumai/gateway-client/src/__tests__/guardian-request-contract.test.ts +575 -0
  19. package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +20 -0
  20. package/node_modules/@vellumai/gateway-client/src/guardian-request-contract.ts +687 -0
  21. package/node_modules/@vellumai/gateway-client/src/index.ts +82 -0
  22. package/node_modules/@vellumai/service-contracts/package.json +1 -0
  23. package/node_modules/@vellumai/service-contracts/src/__tests__/redacted-credential.test.ts +200 -0
  24. package/node_modules/@vellumai/service-contracts/src/redacted-credential.ts +226 -0
  25. package/openapi.yaml +353 -742
  26. package/package.json +2 -2
  27. package/scripts/build-test-fixtures.ts +3 -2
  28. package/scripts/test.ts +2 -7
  29. package/src/__tests__/activation-early-marking.test.ts +1 -8
  30. package/src/__tests__/actor-token-service.test.ts +0 -7
  31. package/src/__tests__/add-message-preassigned-id.test.ts +1 -1
  32. package/src/__tests__/add-message-skip-indexing.test.ts +164 -0
  33. package/src/__tests__/agent-image-optimize.test.ts +104 -1
  34. package/src/__tests__/agent-loop-callsite-precedence.test.ts +5 -15
  35. package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +0 -6
  36. package/src/__tests__/agent-loop-override-profile.test.ts +48 -50
  37. package/src/__tests__/agent-loop-pushes-post-hook-prompt.test.ts +4 -17
  38. package/src/__tests__/agent-loop-set-system-prompt.test.ts +3 -14
  39. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +0 -4
  40. package/src/__tests__/agent-wake-override-profile.test.ts +40 -71
  41. package/src/__tests__/always-loaded-tools-guard.test.ts +4 -7
  42. package/src/__tests__/annotate-activity-metadata.test.ts +4 -31
  43. package/src/__tests__/annotate-risk-options.test.ts +4 -31
  44. package/src/__tests__/anthropic-provider.test.ts +11 -0
  45. package/src/__tests__/app-asset-bytes.test.ts +72 -0
  46. package/src/__tests__/app-builder-tool-scripts.test.ts +0 -1
  47. package/src/__tests__/app-bundler.test.ts +4 -20
  48. package/src/__tests__/app-conversation-ids-backfill.test.ts +1 -8
  49. package/src/__tests__/app-executors.test.ts +2 -49
  50. package/src/__tests__/app-routes-csp.test.ts +126 -157
  51. package/src/__tests__/approval-cascade.test.ts +0 -82
  52. package/src/__tests__/approval-interception-trust-gates.test.ts +0 -7
  53. package/src/__tests__/approval-primitive.test.ts +1 -9
  54. package/src/__tests__/approval-routes-http.test.ts +5 -63
  55. package/src/__tests__/assistant-event-hub-machine-name.test.ts +1 -19
  56. package/src/__tests__/assistant-events-sse-hardening.test.ts +4 -21
  57. package/src/__tests__/attachment-stored-path-annotation.test.ts +1 -19
  58. package/src/__tests__/attachment-upload-trusted-source.test.ts +0 -13
  59. package/src/__tests__/attachments-store-heic-normalize.test.ts +0 -13
  60. package/src/__tests__/attachments-store.test.ts +5 -19
  61. package/src/__tests__/audit-log-rotation.test.ts +1 -9
  62. package/src/__tests__/auth-fallback-events-store.test.ts +43 -59
  63. package/src/__tests__/available-skills.test.ts +7 -15
  64. package/src/__tests__/avatar-e2e.test.ts +0 -19
  65. package/src/__tests__/avatar-generator.test.ts +0 -9
  66. package/src/__tests__/background-shell-bash.test.ts +0 -20
  67. package/src/__tests__/background-shell-host-bash.test.ts +0 -34
  68. package/src/__tests__/background-workers-disk-pressure.test.ts +1 -43
  69. package/src/__tests__/browser-fill-credential.test.ts +0 -7
  70. package/src/__tests__/browser-manager.test.ts +1 -8
  71. package/src/__tests__/browser-skill-endstate.test.ts +1 -5
  72. package/src/__tests__/btw-routes.test.ts +7 -30
  73. package/src/__tests__/build-persisted-content.test.ts +96 -29
  74. package/src/__tests__/bundle-scanner.test.ts +27 -1
  75. package/src/__tests__/call-controller.test.ts +96 -149
  76. package/src/__tests__/call-conversation-messages.test.ts +2 -9
  77. package/src/__tests__/call-domain.test.ts +21 -56
  78. package/src/__tests__/call-pointer-message-composer.test.ts +0 -8
  79. package/src/__tests__/call-pointer-messages.test.ts +1 -8
  80. package/src/__tests__/call-recovery.test.ts +4 -11
  81. package/src/__tests__/call-routes-http.test.ts +12 -47
  82. package/src/__tests__/call-site-routing-provider.test.ts +5 -16
  83. package/src/__tests__/call-start-guardian-guard.test.ts +0 -10
  84. package/src/__tests__/call-state.test.ts +1 -8
  85. package/src/__tests__/call-store.test.ts +4 -11
  86. package/src/__tests__/cancel-resolves-conversation-key.test.ts +0 -32
  87. package/src/__tests__/catalog-cache.test.ts +0 -8
  88. package/src/__tests__/catalog-files.test.ts +0 -8
  89. package/src/__tests__/catalog-install-normalize.test.ts +0 -7
  90. package/src/__tests__/ces-rpc-credential-backend.test.ts +0 -7
  91. package/src/__tests__/channel-approval-routes.test.ts +23 -34
  92. package/src/__tests__/channel-approvals.test.ts +0 -7
  93. package/src/__tests__/channel-delivery-store.test.ts +6 -17
  94. package/src/__tests__/channel-guardian.test.ts +0 -7
  95. package/src/__tests__/channel-inbound-disk-pressure.test.ts +15 -20
  96. package/src/__tests__/channel-readiness-routes.test.ts +41 -67
  97. package/src/__tests__/channel-readiness-service.test.ts +13 -45
  98. package/src/__tests__/channel-readiness-slack-remote.test.ts +5 -22
  99. package/src/__tests__/channel-reply-delivery.test.ts +18 -5
  100. package/src/__tests__/channel-retry-sweep.test.ts +0 -7
  101. package/src/__tests__/chat-credential-redaction.test.ts +1395 -0
  102. package/src/__tests__/chat-reveal-guard-priming.test.ts +791 -0
  103. package/src/__tests__/checker.test.ts +0 -24
  104. package/src/__tests__/clawhub-files.test.ts +0 -8
  105. package/src/__tests__/clawhub.test.ts +0 -7
  106. package/src/__tests__/clear-all-lexical.test.ts +0 -6
  107. package/src/__tests__/cli-memory-v2-reembed-skills.test.ts +3 -0
  108. package/src/__tests__/client-os-metadata-persistence.test.ts +0 -5
  109. package/src/__tests__/compaction-events.test.ts +26 -99
  110. package/src/__tests__/compaction-ledger-store.test.ts +1 -7
  111. package/src/__tests__/compaction-summary-head-persisted-count.test.ts +0 -27
  112. package/src/__tests__/compaction-trail-store.test.ts +1 -19
  113. package/src/__tests__/compaction.benchmark.test.ts +2 -6
  114. package/src/__tests__/compactor-call-site-logging.test.ts +0 -21
  115. package/src/__tests__/compactor-fixed-boundary.test.ts +0 -21
  116. package/src/__tests__/compactor-image-manifest-trust.test.ts +1 -10
  117. package/src/__tests__/compactor-low-watermark-cut.test.ts +0 -21
  118. package/src/__tests__/compactor-media-strip.test.ts +0 -21
  119. package/src/__tests__/compactor-preserved-tail-count.test.ts +0 -21
  120. package/src/__tests__/compactor-summary-call-truncation.test.ts +0 -21
  121. package/src/__tests__/compactor-web-search-strip.test.ts +0 -21
  122. package/src/__tests__/config-get-vision-flag.test.ts +24 -53
  123. package/src/__tests__/config-loader-backfill.test.ts +9 -25
  124. package/src/__tests__/config-loader-platform-defaults.test.ts +0 -21
  125. package/src/__tests__/config-schema-cmd.test.ts +10 -11
  126. package/src/__tests__/config-schema.test.ts +221 -275
  127. package/src/__tests__/config-set-route.test.ts +32 -62
  128. package/src/__tests__/config-watcher-cleanup-throttle.test.ts +44 -83
  129. package/src/__tests__/config-watcher-skill-reseed.test.ts +0 -16
  130. package/src/__tests__/config-watcher.test.ts +2 -17
  131. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +57 -63
  132. package/src/__tests__/contact-store-interaction-info.test.ts +1 -8
  133. package/src/__tests__/contact-store-user-file.test.ts +1 -8
  134. package/src/__tests__/contacts-tools.test.ts +21 -26
  135. package/src/__tests__/contacts-write.test.ts +1 -8
  136. package/src/__tests__/context-search-memory-v2-source.test.ts +0 -6
  137. package/src/__tests__/context-window-manager-compact-retry.test.ts +0 -27
  138. package/src/__tests__/context-window-manager-overflow-rung.test.ts +0 -21
  139. package/src/__tests__/conversation-abort-tool-results.test.ts +9 -90
  140. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +5 -41
  141. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +16 -54
  142. package/src/__tests__/conversation-agent-loop-overflow.test.ts +14 -79
  143. package/src/__tests__/conversation-agent-loop.test.ts +213 -136
  144. package/src/__tests__/conversation-app-control-instantiation.test.ts +10 -16
  145. package/src/__tests__/conversation-app-control-lifecycle.test.ts +0 -43
  146. package/src/__tests__/conversation-attachments.test.ts +0 -17
  147. package/src/__tests__/conversation-attention-store.test.ts +1 -9
  148. package/src/__tests__/conversation-attention-telegram.test.ts +0 -8
  149. package/src/__tests__/conversation-clear-safety.test.ts +20 -20
  150. package/src/__tests__/conversation-confirmation-signals.test.ts +0 -82
  151. package/src/__tests__/conversation-crud-enabled-plugins.test.ts +1 -7
  152. package/src/__tests__/conversation-crud-inference-profile.test.ts +1 -7
  153. package/src/__tests__/conversation-delete-schedule-cleanup.test.ts +0 -7
  154. package/src/__tests__/conversation-disk-view-integration.test.ts +1 -26
  155. package/src/__tests__/conversation-disk-view.test.ts +1 -26
  156. package/src/__tests__/conversation-enabledplugins-route.test.ts +3 -21
  157. package/src/__tests__/conversation-error.test.ts +21 -7
  158. package/src/__tests__/conversation-fork-crud.test.ts +35 -44
  159. package/src/__tests__/conversation-fork-retrospective.test.ts +9 -24
  160. package/src/__tests__/conversation-fork-route.test.ts +0 -32
  161. package/src/__tests__/conversation-history-web-search.test.ts +20 -17
  162. package/src/__tests__/conversation-inference-profile-list.test.ts +0 -17
  163. package/src/__tests__/conversation-inference-profile-route.test.ts +2 -24
  164. package/src/__tests__/conversation-init.benchmark.test.ts +3 -56
  165. package/src/__tests__/conversation-key-store-disk-view.test.ts +1 -33
  166. package/src/__tests__/conversation-lifecycle.test.ts +63 -103
  167. package/src/__tests__/conversation-list-source.test.ts +0 -18
  168. package/src/__tests__/conversation-load-history-repair.test.ts +39 -91
  169. package/src/__tests__/conversation-load-history-stripped.test.ts +12 -57
  170. package/src/__tests__/conversation-loop-db-lock-resilience.test.ts +10 -20
  171. package/src/__tests__/conversation-message-id-uuidv7.test.ts +1 -18
  172. package/src/__tests__/conversation-pairing.test.ts +0 -7
  173. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +0 -9
  174. package/src/__tests__/conversation-process-callsite.test.ts +15 -97
  175. package/src/__tests__/conversation-provider-retry-repair.test.ts +3 -84
  176. package/src/__tests__/conversation-queue.test.ts +12 -108
  177. package/src/__tests__/conversation-routes-disk-view.test.ts +8 -60
  178. package/src/__tests__/conversation-routes-enabled-plugins.test.ts +5 -63
  179. package/src/__tests__/conversation-routes-guardian-reply.test.ts +13 -22
  180. package/src/__tests__/conversation-routes-hidden-queue.test.ts +5 -64
  181. package/src/__tests__/conversation-routes-slash-commands.test.ts +19 -87
  182. package/src/__tests__/conversation-runtime-assembly.test.ts +34 -37
  183. package/src/__tests__/conversation-seed-composer.test.ts +3 -3
  184. package/src/__tests__/conversation-skill-tools.test.ts +19 -32
  185. package/src/__tests__/conversation-slash-queue.test.ts +3 -90
  186. package/src/__tests__/conversation-slash-unknown.test.ts +0 -87
  187. package/src/__tests__/conversation-speed-override.test.ts +19 -94
  188. package/src/__tests__/conversation-starter-routes.test.ts +0 -7
  189. package/src/__tests__/conversation-store.test.ts +72 -66
  190. package/src/__tests__/conversation-summarize-route.test.ts +0 -11
  191. package/src/__tests__/conversation-summarize-up-to.test.ts +4 -89
  192. package/src/__tests__/conversation-surfaces-action-delivery.test.ts +131 -0
  193. package/src/__tests__/conversation-surfaces-activation-emit.test.ts +36 -36
  194. package/src/__tests__/conversation-surfaces-data-persist.test.ts +4 -2
  195. package/src/__tests__/conversation-title-service.test.ts +0 -7
  196. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +0 -1
  197. package/src/__tests__/conversation-tool-setup-attribution.test.ts +37 -60
  198. package/src/__tests__/conversation-tool-setup-tools-disabled.test.ts +17 -17
  199. package/src/__tests__/conversation-unread-route.test.ts +0 -7
  200. package/src/__tests__/conversation-usage.test.ts +21 -71
  201. package/src/__tests__/conversation-wait-for-idle.test.ts +0 -82
  202. package/src/__tests__/conversation-workspace-cache-state.test.ts +0 -45
  203. package/src/__tests__/conversation-workspace-injection.test.ts +0 -88
  204. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +0 -88
  205. package/src/__tests__/conversations-defer-cli.test.ts +3 -0
  206. package/src/__tests__/conversations-import-system-filter.test.ts +0 -17
  207. package/src/__tests__/credential-broker-browser-fill.test.ts +0 -9
  208. package/src/__tests__/credential-broker-server-use.test.ts +0 -9
  209. package/src/__tests__/credential-health-service.test.ts +14 -31
  210. package/src/__tests__/credential-metadata-store.test.ts +0 -8
  211. package/src/__tests__/credential-prompt-route.test.ts +9 -15
  212. package/src/__tests__/credential-resolve.test.ts +0 -8
  213. package/src/__tests__/credential-routes.test.ts +250 -0
  214. package/src/__tests__/credential-security-invariants.test.ts +4 -8
  215. package/src/__tests__/credential-token-resolver.test.ts +0 -9
  216. package/src/__tests__/cross-provider-web-search.test.ts +12 -18
  217. package/src/__tests__/daemon-assistant-events.test.ts +1 -9
  218. package/src/__tests__/daemon-credential-client.test.ts +0 -7
  219. package/src/__tests__/db-conversation-fork-lineage-migration.test.ts +1 -8
  220. package/src/__tests__/db-conversation-inference-profile-migration.test.ts +1 -7
  221. package/src/__tests__/db-llm-request-log-provider-migration.test.ts +0 -8
  222. package/src/__tests__/db-readiness-http-gate.test.ts +0 -18
  223. package/src/__tests__/db-rename-inference-profile-snake-case-migration.test.ts +1 -7
  224. package/src/__tests__/db-test-helpers.ts +10 -3
  225. package/src/__tests__/delete-managed-skill-tool.test.ts +0 -7
  226. package/src/__tests__/delete-propagation.test.ts +0 -7
  227. package/src/__tests__/deterministic-verification-control-plane.test.ts +0 -7
  228. package/src/__tests__/device-id.test.ts +1 -9
  229. package/src/__tests__/disk-pressure-guard.test.ts +0 -4
  230. package/src/__tests__/disk-pressure-routes.test.ts +0 -4
  231. package/src/__tests__/disk-pressure-tools.test.ts +4 -42
  232. package/src/__tests__/dm-backfill.test.ts +0 -7
  233. package/src/__tests__/dm-persistence.test.ts +0 -5
  234. package/src/__tests__/docker-signing-key-bootstrap.test.ts +1 -8
  235. package/src/__tests__/drain-requeue-on-contention.test.ts +1 -8
  236. package/src/__tests__/dynamic-skill-background-guard.test.ts +0 -43
  237. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +2 -27
  238. package/src/__tests__/edit-propagation.test.ts +1 -8
  239. package/src/__tests__/email-invite-adapter.test.ts +8 -33
  240. package/src/__tests__/embedding-managed-proxy-selection.test.ts +0 -8
  241. package/src/__tests__/emit-event-signal.test.ts +1 -1
  242. package/src/__tests__/emit-signal-routing-intent.test.ts +0 -7
  243. package/src/__tests__/empty-state-greeting-cache.test.ts +9 -9
  244. package/src/__tests__/encrypted-store.test.ts +0 -9
  245. package/src/__tests__/ensure-conversation-exists.test.ts +1 -18
  246. package/src/__tests__/events-client-registration.test.ts +1 -19
  247. package/src/__tests__/events-dev-bypass-actor.test.ts +0 -7
  248. package/src/__tests__/events-tail-route.test.ts +1 -8
  249. package/src/__tests__/file-list-tool.test.ts +22 -15
  250. package/src/__tests__/followup-tools.test.ts +1 -16
  251. package/src/__tests__/gateway-only-enforcement.test.ts +3 -44
  252. package/src/__tests__/get-skill-detail-audit.test.ts +0 -7
  253. package/src/__tests__/google-calendar-watcher.test.ts +175 -0
  254. package/src/__tests__/guardian-action-sweep.test.ts +0 -7
  255. package/src/__tests__/guardian-binding-drift-heal.test.ts +0 -7
  256. package/src/__tests__/guardian-card-withdrawal.test.ts +60 -64
  257. package/src/__tests__/guardian-decision-primitive-canonical.test.ts +172 -135
  258. package/src/__tests__/guardian-dispatch.test.ts +58 -157
  259. package/src/__tests__/guardian-expiry-notifier.test.ts +56 -48
  260. package/src/__tests__/guardian-gateway-sim.ts +626 -0
  261. package/src/__tests__/guardian-outbound-http.test.ts +0 -7
  262. package/src/__tests__/guardian-routing-invariants.test.ts +229 -220
  263. package/src/__tests__/guardian-routing-state.test.ts +2 -9
  264. package/src/__tests__/guardian-verification-voice-binding.test.ts +0 -17
  265. package/src/__tests__/guardian-wait-controller.test.ts +44 -43
  266. package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +26 -91
  267. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +18 -103
  268. package/src/__tests__/headless-browser-interactions.test.ts +0 -7
  269. package/src/__tests__/headless-browser-mode.test.ts +0 -9
  270. package/src/__tests__/headless-browser-navigate.test.ts +0 -7
  271. package/src/__tests__/headless-browser-read-tools.test.ts +0 -7
  272. package/src/__tests__/headless-browser-snapshot.test.ts +0 -7
  273. package/src/__tests__/heartbeat-disk-pressure.test.ts +9 -38
  274. package/src/__tests__/heartbeat-service.test.ts +83 -106
  275. package/src/__tests__/helpers/channel-test-adapter.ts +10 -0
  276. package/src/__tests__/helpers/gateway-guardian-requests-store-bridge.ts +155 -0
  277. package/src/__tests__/helpers/set-config.ts +40 -0
  278. package/src/__tests__/host-bash-proxy.test.ts +18 -34
  279. package/src/__tests__/host-browser-proxy.test.ts +15 -20
  280. package/src/__tests__/host-shell-tool.test.ts +2 -34
  281. package/src/__tests__/http-conversation-lineage.test.ts +3 -19
  282. package/src/__tests__/http-user-message-parity.test.ts +3 -95
  283. package/src/__tests__/identity-routes.test.ts +1 -9
  284. package/src/__tests__/image-recovery-hook.test.ts +36 -0
  285. package/src/__tests__/inbound-admitted-introduction-nudge.test.ts +6 -12
  286. package/src/__tests__/inbound-invite-redemption.test.ts +0 -7
  287. package/src/__tests__/inbound-slack-persistence.test.ts +0 -5
  288. package/src/__tests__/inbound-trust-verdict.test.ts +2 -8
  289. package/src/__tests__/inference-no-mode-boot-e2e.test.ts +52 -8
  290. package/src/__tests__/inference-profile-reaper.test.ts +1 -7
  291. package/src/__tests__/inference-profile-session-handler.test.ts +4 -30
  292. package/src/__tests__/inference-profile-session-ipc.test.ts +3 -20
  293. package/src/__tests__/ingress-url-consistency.test.ts +1 -23
  294. package/src/__tests__/injector-background-turn.test.ts +10 -24
  295. package/src/__tests__/injector-chain.test.ts +4 -13
  296. package/src/__tests__/injector-pkb-v2-silenced.test.ts +19 -17
  297. package/src/__tests__/inline-command-runner.test.ts +0 -34
  298. package/src/__tests__/inline-skill-load-permissions.test.ts +1 -30
  299. package/src/__tests__/install-skill-routing.test.ts +7 -6
  300. package/src/__tests__/integration-status.test.ts +25 -17
  301. package/src/__tests__/intent-routing.test.ts +1 -50
  302. package/src/__tests__/internal-telemetry-routes.test.ts +41 -22
  303. package/src/__tests__/interrupted-turn-reconciler.test.ts +1 -8
  304. package/src/__tests__/introduction-card-resolver.test.ts +108 -117
  305. package/src/__tests__/invite-redeemed-ipc.test.ts +0 -7
  306. package/src/__tests__/invite-routes-http.test.ts +12 -23
  307. package/src/__tests__/jobs-store-has-pending-job.test.ts +59 -0
  308. package/src/__tests__/jobs-store-qdrant-breaker.test.ts +1 -14
  309. package/src/__tests__/jobs-store-skill-card-upsert.test.ts +191 -0
  310. package/src/__tests__/jobs-store-upsert-debounced.test.ts +1 -14
  311. package/src/__tests__/lexical-index-dual-write.test.ts +0 -6
  312. package/src/__tests__/list-all-apps.test.ts +200 -0
  313. package/src/__tests__/list-messages-attachments.test.ts +11 -20
  314. package/src/__tests__/list-messages-background-tool-completion.test.ts +6 -18
  315. package/src/__tests__/list-messages-client-message-id.test.ts +4 -16
  316. package/src/__tests__/list-messages-hidden-metadata.test.ts +6 -18
  317. package/src/__tests__/list-messages-page-latest.test.ts +3 -19
  318. package/src/__tests__/list-messages-queued.test.ts +1 -18
  319. package/src/__tests__/list-messages-tool-merge.test.ts +7 -18
  320. package/src/__tests__/llm-context-resolution.test.ts +32 -56
  321. package/src/__tests__/llm-context-route-provider.test.ts +1 -8
  322. package/src/__tests__/llm-request-log-agent-loop-exit-reason.test.ts +1 -19
  323. package/src/__tests__/llm-request-log-call-site.test.ts +1 -19
  324. package/src/__tests__/llm-request-log-clickhouse-routing.test.ts +114 -0
  325. package/src/__tests__/llm-request-log-clickhouse-writer-guards.test.ts +108 -0
  326. package/src/__tests__/llm-request-log-disabled-writes.test.ts +11 -30
  327. package/src/__tests__/llm-request-log-sink-clickhouse.test.ts +205 -0
  328. package/src/__tests__/llm-request-log-source-clickhouse.test.ts +1 -22
  329. package/src/__tests__/llm-request-log-source-factory.test.ts +23 -69
  330. package/src/__tests__/llm-request-log-turn-query.test.ts +1 -19
  331. package/src/__tests__/llm-resolver-override-or-default.test.ts +3 -52
  332. package/src/__tests__/llm-resolver.test.ts +342 -602
  333. package/src/__tests__/llm-schema.test.ts +79 -37
  334. package/src/__tests__/llm-usage-store.test.ts +1 -7
  335. package/src/__tests__/log-export-routes.test.ts +0 -7
  336. package/src/__tests__/log-export-workspace.test.ts +0 -7
  337. package/src/__tests__/managed-profile-guard.test.ts +125 -137
  338. package/src/__tests__/managed-skill-lifecycle.test.ts +6 -50
  339. package/src/__tests__/managed-speech-defaults.test.ts +138 -0
  340. package/src/__tests__/managed-store.test.ts +1 -16
  341. package/src/__tests__/mcp-auth-routes.test.ts +15 -26
  342. package/src/__tests__/mcp-config-secret-boundary.test.ts +49 -91
  343. package/src/__tests__/mcp-health-check.test.ts +14 -20
  344. package/src/__tests__/media-generate-image.test.ts +25 -32
  345. package/src/__tests__/media-stream-output.test.ts +0 -9
  346. package/src/__tests__/media-stream-server-integration.test.ts +19 -27
  347. package/src/__tests__/media-stream-stt-session.test.ts +16 -29
  348. package/src/__tests__/memory-jobs-worker-backoff.test.ts +6 -19
  349. package/src/__tests__/memory-jobs-worker-cleanup-cadence.test.ts +33 -12
  350. package/src/__tests__/memory-jobs-worker-lanes.test.ts +10 -29
  351. package/src/__tests__/memory-recall-log-store.test.ts +1 -19
  352. package/src/__tests__/memory-retrieval-hook.test.ts +0 -3
  353. package/src/__tests__/memory-upsert-concurrency.test.ts +12 -35
  354. package/src/__tests__/memory-v2-static-injector.test.ts +5 -17
  355. package/src/__tests__/messages-after-tiebreaker.test.ts +1 -7
  356. package/src/__tests__/messaging-send-tool.test.ts +67 -7
  357. package/src/__tests__/migration-cross-version-compatibility.test.ts +0 -18
  358. package/src/__tests__/migration-export-http.test.ts +10 -23
  359. package/src/__tests__/migration-export-streaming.test.ts +0 -18
  360. package/src/__tests__/migration-export-to-gcs.test.ts +0 -19
  361. package/src/__tests__/migration-import-commit-http.test.ts +11 -23
  362. package/src/__tests__/migration-import-from-gcs.test.ts +0 -19
  363. package/src/__tests__/migration-import-from-url.test.ts +0 -19
  364. package/src/__tests__/migration-import-preflight-http.test.ts +10 -22
  365. package/src/__tests__/migration-jobs-status.test.ts +8 -23
  366. package/src/__tests__/migration-validate-http.test.ts +2 -19
  367. package/src/__tests__/mtime-cache.test.ts +116 -2
  368. package/src/__tests__/native-web-search.test.ts +0 -21
  369. package/src/__tests__/non-member-access-request.test.ts +102 -93
  370. package/src/__tests__/notification-broadcaster.test.ts +32 -21
  371. package/src/__tests__/notification-candidate-guardian-context.test.ts +36 -34
  372. package/src/__tests__/notification-decision-fallback.test.ts +0 -7
  373. package/src/__tests__/notification-decision-identity.test.ts +0 -7
  374. package/src/__tests__/notification-decision-recipient-context.test.ts +0 -7
  375. package/src/__tests__/notification-deep-link.test.ts +0 -7
  376. package/src/__tests__/notification-guardian-path.test.ts +48 -93
  377. package/src/__tests__/notification-platform-adapter.test.ts +0 -7
  378. package/src/__tests__/notification-routing-intent.test.ts +1 -9
  379. package/src/__tests__/notification-schedule-notify-dedup.test.ts +1 -9
  380. package/src/__tests__/notification-telegram-adapter.test.ts +0 -7
  381. package/src/__tests__/oauth-cli.test.ts +0 -19
  382. package/src/__tests__/oauth-commands-routes.test.ts +28 -43
  383. package/src/__tests__/oauth-connect-orchestrator.test.ts +17 -26
  384. package/src/__tests__/oauth-connect-routes.test.ts +19 -18
  385. package/src/__tests__/oauth-provider-profiles.test.ts +3 -9
  386. package/src/__tests__/oauth-provider-visibility.test.ts +0 -7
  387. package/src/__tests__/oauth-store.test.ts +0 -7
  388. package/src/__tests__/oauth2-gateway-transport.test.ts +41 -43
  389. package/src/__tests__/openai-responses-prompt-cache.test.ts +355 -0
  390. package/src/__tests__/openai-responses-provider.test.ts +98 -9
  391. package/src/__tests__/openrouter-responses-transport.test.ts +170 -0
  392. package/src/__tests__/outbound-slack-persistence.test.ts +13 -46
  393. package/src/__tests__/outlook-email-watcher.test.ts +0 -9
  394. package/src/__tests__/outlook-messaging-provider.test.ts +71 -0
  395. package/src/__tests__/path-policy.test.ts +0 -7
  396. package/src/__tests__/persist-media-references.test.ts +1 -6
  397. package/src/__tests__/persist-unsendable-image-downscale.test.ts +6 -3
  398. package/src/__tests__/persist-unsendable-image.test.ts +39 -7
  399. package/src/__tests__/persistence-secret-redaction.test.ts +7 -28
  400. package/src/__tests__/platform-bash-auto-approve.test.ts +0 -46
  401. package/src/__tests__/platform-callback-registration.test.ts +10 -9
  402. package/src/__tests__/platform-proxy-context.test.ts +0 -8
  403. package/src/__tests__/playbook-execution.test.ts +0 -14
  404. package/src/__tests__/playbook-tools.test.ts +0 -14
  405. package/src/__tests__/plugin-api-model-profiles.test.ts +12 -35
  406. package/src/__tests__/plugin-app-serve-routes.test.ts +315 -0
  407. package/src/__tests__/plugin-import-boundary-guard.test.ts +5 -3
  408. package/src/__tests__/plugin-import-boundary-reverse-guard.test.ts +0 -2
  409. package/src/__tests__/plugin-types.test.ts +0 -7
  410. package/src/__tests__/prechat-onboarding-contract.test.ts +0 -27
  411. package/src/__tests__/pricing.test.ts +45 -0
  412. package/src/__tests__/process-message-background-slack.test.ts +7 -21
  413. package/src/__tests__/process-message-display-content.test.ts +37 -6
  414. package/src/__tests__/processing-flag-persist-failure.test.ts +0 -44
  415. package/src/__tests__/provider-commit-message-generator.test.ts +57 -63
  416. package/src/__tests__/provider-error-scenarios.test.ts +5 -6
  417. package/src/__tests__/provider-platform-proxy-integration.test.ts +21 -57
  418. package/src/__tests__/provider-registry-ollama.test.ts +8 -18
  419. package/src/__tests__/provider-send-message-override-profile.test.ts +28 -39
  420. package/src/__tests__/provider-streaming.benchmark.test.ts +1 -6
  421. package/src/__tests__/provider-usage-tracking.test.ts +13 -35
  422. package/src/__tests__/proxy-approval-callback.test.ts +0 -16
  423. package/src/__tests__/prune-old-conversations-job.test.ts +29 -21
  424. package/src/__tests__/prune-old-tool-invocations-job.test.ts +1 -9
  425. package/src/__tests__/public-ingress-urls.test.ts +1 -24
  426. package/src/__tests__/published-app-updater.test.ts +22 -16
  427. package/src/__tests__/qdrant-collection-migration.test.ts +0 -7
  428. package/src/__tests__/qdrant-manager.test.ts +0 -8
  429. package/src/__tests__/ratelimit.test.ts +1 -8
  430. package/src/__tests__/reaction-persistence.test.ts +18 -24
  431. package/src/__tests__/rebuild-index-graph-nodes.test.ts +0 -14
  432. package/src/__tests__/recording-handler.test.ts +0 -27
  433. package/src/__tests__/request-secret-standalone-channel-gate.test.ts +4 -16
  434. package/src/__tests__/require-fresh-approval.test.ts +0 -45
  435. package/src/__tests__/retry-openrouter-only-normalization.test.ts +24 -36
  436. package/src/__tests__/retry-prompt-cache-key.test.ts +123 -0
  437. package/src/__tests__/retry-thinking-adaptive-only.test.ts +45 -61
  438. package/src/__tests__/retry-thinking-tool-choice.test.ts +60 -80
  439. package/src/__tests__/retry-verbosity-normalization.test.ts +27 -37
  440. package/src/__tests__/reveal-success-registry.test.ts +123 -0
  441. package/src/__tests__/run-conversation-turn-persistence.test.ts +113 -0
  442. package/src/__tests__/{scheduler-worker-standdown.test.ts → run-due-schedules.test.ts} +5 -77
  443. package/src/__tests__/runtime-attachment-metadata.test.ts +5 -18
  444. package/src/__tests__/runtime-events-sse-bilingual.test.ts +1 -19
  445. package/src/__tests__/runtime-events-sse-parity.test.ts +1 -20
  446. package/src/__tests__/runtime-events-sse-reconnect.test.ts +1 -19
  447. package/src/__tests__/runtime-events-sse.test.ts +1 -20
  448. package/src/__tests__/scaffold-managed-skill-tool.test.ts +306 -5
  449. package/src/__tests__/schedule-retry.test.ts +22 -23
  450. package/src/__tests__/schedule-routes-workflow-validation.test.ts +18 -42
  451. package/src/__tests__/schedule-routes.test.ts +2 -150
  452. package/src/__tests__/schedule-store.test.ts +0 -8
  453. package/src/__tests__/schedule-tools.test.ts +1 -16
  454. package/src/__tests__/scheduler-disk-pressure.test.ts +0 -26
  455. package/src/__tests__/scheduler-recurrence.test.ts +43 -76
  456. package/src/__tests__/scheduler-reuse-conversation.test.ts +19 -35
  457. package/src/__tests__/scheduler-wake.test.ts +8 -24
  458. package/src/__tests__/scoped-approval-grants.test.ts +1 -9
  459. package/src/__tests__/scoped-grant-security-matrix.test.ts +1 -9
  460. package/src/__tests__/search-skills-unified.test.ts +0 -6
  461. package/src/__tests__/secret-allowlist.test.ts +1 -8
  462. package/src/__tests__/secret-fixtures.ts +9 -0
  463. package/src/__tests__/secret-ingress-channel.test.ts +4 -32
  464. package/src/__tests__/secret-ingress-cli.test.ts +3 -37
  465. package/src/__tests__/secret-ingress-http.test.ts +15 -78
  466. package/src/__tests__/secret-ingress.test.ts +14 -31
  467. package/src/__tests__/secret-prompt-log-hygiene.test.ts +4 -10
  468. package/src/__tests__/secret-prompter-channel-fallback.test.ts +3 -9
  469. package/src/__tests__/secret-routes-platform-proxy.test.ts +3 -40
  470. package/src/__tests__/secure-keys-managed-failover.test.ts +0 -7
  471. package/src/__tests__/secure-keys.test.ts +0 -9
  472. package/src/__tests__/send-endpoint-busy.test.ts +47 -91
  473. package/src/__tests__/sequence-store.test.ts +0 -9
  474. package/src/__tests__/server-history-render.test.ts +29 -8
  475. package/src/__tests__/set-permission-mode.test.ts +8 -16
  476. package/src/__tests__/settings-routes.test.ts +0 -7
  477. package/src/__tests__/shell-credential-ref.test.ts +0 -18
  478. package/src/__tests__/shell-observability.test.ts +0 -21
  479. package/src/__tests__/shell-tool-proxy-mode.test.ts +0 -34
  480. package/src/__tests__/skill-load-feature-flag.test.ts +0 -27
  481. package/src/__tests__/skill-load-inline-command.test.ts +0 -25
  482. package/src/__tests__/skill-load-inline-includes.test.ts +0 -25
  483. package/src/__tests__/skill-load-plugin-scope.test.ts +0 -24
  484. package/src/__tests__/skill-projection-feature-flag.test.ts +14 -33
  485. package/src/__tests__/skill-projection.benchmark.test.ts +14 -19
  486. package/src/__tests__/skill-script-runner-sandbox.test.ts +1 -39
  487. package/src/__tests__/skills-file-content-endpoint.test.ts +0 -7
  488. package/src/__tests__/skills-files-catalog-fallback.test.ts +0 -14
  489. package/src/__tests__/slack-channel-config.test.ts +24 -79
  490. package/src/__tests__/slack-inbound-verification.test.ts +5 -13
  491. package/src/__tests__/{slack-reaction-canonical-approval.test.ts → slack-reaction-guardian-approval.test.ts} +18 -74
  492. package/src/__tests__/slack-share-routes.test.ts +0 -1
  493. package/src/__tests__/slim-skill-category.test.ts +0 -14
  494. package/src/__tests__/stale-approval-dedup.test.ts +0 -7
  495. package/src/__tests__/subagent-call-site-routing.test.ts +72 -111
  496. package/src/__tests__/subagent-detail.test.ts +1 -1
  497. package/src/__tests__/subagent-fork-prompt-role.test.ts +0 -12
  498. package/src/__tests__/subagent-notify-parent.test.ts +4 -6
  499. package/src/__tests__/subagent-spawn-and-await.test.ts +0 -18
  500. package/src/__tests__/subagent-tool-filtering.test.ts +21 -31
  501. package/src/__tests__/subagent-tool-gate-mode.test.ts +6 -32
  502. package/src/__tests__/subagent-tools.test.ts +92 -67
  503. package/src/__tests__/suggestion-routes.test.ts +28 -42
  504. package/src/__tests__/summarize-boundary.test.ts +2 -1
  505. package/src/__tests__/system-prompt-ask-mode.test.ts +0 -25
  506. package/src/__tests__/system-prompt.test.ts +0 -33
  507. package/src/__tests__/task-memory-cleanup.test.ts +18 -192
  508. package/src/__tests__/telegram-bot-username-resolution.test.ts +24 -48
  509. package/src/__tests__/telegram-config.test.ts +0 -8
  510. package/src/__tests__/telemetry-routes.test.ts +100 -0
  511. package/src/__tests__/telephony-credential-preflight.test.ts +7 -11
  512. package/src/__tests__/telephony-tts-guard.test.ts +38 -21
  513. package/src/__tests__/terminal-tools.test.ts +0 -21
  514. package/src/__tests__/thread-backfill.test.ts +0 -7
  515. package/src/__tests__/title-generate-hook.test.ts +11 -10
  516. package/src/__tests__/token-manager.test.ts +0 -9
  517. package/src/__tests__/tool-approval-handler.test.ts +0 -8
  518. package/src/__tests__/tool-execution-abort-cleanup.test.ts +5 -33
  519. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +3 -23
  520. package/src/__tests__/tool-executor-lifecycle-events.test.ts +0 -48
  521. package/src/__tests__/tool-executor.test.ts +3 -49
  522. package/src/__tests__/tool-grant-request-escalation.test.ts +51 -153
  523. package/src/__tests__/tool-preview-lifecycle.test.ts +2 -29
  524. package/src/__tests__/tool-profiler.test.ts +72 -1
  525. package/src/__tests__/tool-result-metadata-plumbing.test.ts +0 -29
  526. package/src/__tests__/tool-side-effects-slack-dm.test.ts +0 -1
  527. package/src/__tests__/tool-start-timestamp.test.ts +4 -31
  528. package/src/__tests__/tools-get-route.test.ts +2 -1
  529. package/src/__tests__/trust-context-guards.test.ts +19 -7
  530. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +75 -79
  531. package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +14 -18
  532. package/src/__tests__/trusted-contact-multichannel.test.ts +0 -7
  533. package/src/__tests__/trusted-contact-verification.test.ts +1 -9
  534. package/src/__tests__/turn-boundary-resolution.test.ts +1 -19
  535. package/src/__tests__/turn-events-store.test.ts +1 -7
  536. package/src/__tests__/turn-outcome-read.test.ts +1 -7
  537. package/src/__tests__/twilio-config.test.ts +11 -28
  538. package/src/__tests__/twilio-provider.test.ts +2 -13
  539. package/src/__tests__/twilio-routes-twiml.test.ts +1 -8
  540. package/src/__tests__/twilio-routes.test.ts +52 -103
  541. package/src/__tests__/twilio-validation.test.ts +5 -22
  542. package/src/__tests__/ui-file-upload-surface.test.ts +6 -9
  543. package/src/__tests__/usage-attribution.test.ts +25 -56
  544. package/src/__tests__/usage-cache-backfill-migration.test.ts +8 -15
  545. package/src/__tests__/usage-routes.test.ts +1 -8
  546. package/src/__tests__/vbundle-pax-and-symlink.test.ts +0 -18
  547. package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +0 -24
  548. package/src/__tests__/vercel-config.test.ts +0 -7
  549. package/src/__tests__/verification-control-plane-policy.test.ts +0 -45
  550. package/src/__tests__/voice-approval-resolver-relay.test.ts +106 -75
  551. package/src/__tests__/voice-config-update.test.ts +114 -21
  552. package/src/__tests__/voice-ingress-preflight.test.ts +16 -15
  553. package/src/__tests__/voice-scoped-grant-consumer.test.ts +0 -30
  554. package/src/__tests__/voice-session-bridge-processing-wait.test.ts +1 -17
  555. package/src/__tests__/voice-session-bridge.test.ts +14 -46
  556. package/src/__tests__/web-search-backend-failure.test.ts +0 -21
  557. package/src/__tests__/whatsapp-invite-adapter.test.ts +11 -30
  558. package/src/__tests__/workspace-git-service.test.ts +801 -2
  559. package/src/__tests__/workspace-migration-009-backfill-conversation-disk-view.test.ts +1 -26
  560. package/src/__tests__/workspace-migration-013-repair-conversation-disk-view.test.ts +1 -26
  561. package/src/__tests__/workspace-migration-028-recover-conversations-from-disk-view.test.ts +1 -26
  562. package/src/a2a/__tests__/e2e-a2a-channel.test.ts +1 -9
  563. package/src/a2a/__tests__/task-store.test.ts +0 -9
  564. package/src/acp/__tests__/session-manager-persistence.test.ts +1 -8
  565. package/src/acp/__tests__/session-manager-resume.test.ts +0 -7
  566. package/src/acp/__tests__/session-manager-startup.test.ts +1 -8
  567. package/src/agent/image-optimize.ts +80 -0
  568. package/src/agent/loop.ts +20 -5
  569. package/src/api/events/open-conversation.test.ts +64 -0
  570. package/src/api/events/open-conversation.ts +33 -0
  571. package/src/api/index.ts +6 -0
  572. package/src/approvals/AGENTS.md +1 -1
  573. package/src/approvals/guardian-card-withdrawal.ts +26 -19
  574. package/src/approvals/guardian-decision-primitive.ts +172 -102
  575. package/src/approvals/guardian-expiry-notifier.ts +20 -19
  576. package/src/approvals/guardian-request-resolvers.ts +305 -275
  577. package/src/apps/app-store.ts +320 -15
  578. package/src/background-wake/background-wake-routes.test.ts +2 -3
  579. package/src/background-wake/next-wake.test.ts +23 -20
  580. package/src/background-wake/wake-intent-hooks.test.ts +0 -10
  581. package/src/bundler/app-bundler.ts +34 -48
  582. package/src/bundler/app-compiler.ts +39 -4
  583. package/src/bundler/bundle-scanner.ts +13 -0
  584. package/src/bundler/manifest.ts +1 -1
  585. package/src/calls/__tests__/call-setup-router.test.ts +84 -88
  586. package/src/calls/__tests__/fish-audio-client.test.ts +6 -11
  587. package/src/calls/__tests__/voice-control-protocol.test.ts +47 -0
  588. package/src/calls/__tests__/voice-session-bridge.test.ts +151 -16
  589. package/src/calls/__tests__/voice-triage-escalate.test.ts +128 -0
  590. package/src/calls/access-request-wait.ts +35 -35
  591. package/src/calls/call-controller.ts +48 -45
  592. package/src/calls/call-setup-flow.ts +2 -2
  593. package/src/calls/call-setup-router.ts +1 -24
  594. package/src/calls/guardian-dispatch.ts +47 -30
  595. package/src/calls/guardian-wait-controller.ts +31 -18
  596. package/src/calls/media-turn-detector.ts +11 -1
  597. package/src/calls/telephony-credential-preflight.ts +19 -1
  598. package/src/calls/telephony-tts-capability.ts +18 -1
  599. package/src/calls/voice-control-protocol.ts +12 -0
  600. package/src/calls/voice-session-bridge.ts +110 -15
  601. package/src/calls/voice-triage-escalate.ts +136 -0
  602. package/src/channels/__tests__/gateway-guardian-requests.test.ts +669 -0
  603. package/src/channels/gateway-guardian-requests.ts +410 -0
  604. package/src/channels/types.ts +10 -5
  605. package/src/cli/AGENTS.md +77 -51
  606. package/src/cli/__tests__/cli-command-help-coverage.test.ts +29 -0
  607. package/src/cli/commands/__tests__/conversations-import.test.ts +23 -24
  608. package/src/cli/commands/__tests__/gateway.test.ts +55 -0
  609. package/src/cli/commands/__tests__/inference-models.test.ts +3 -0
  610. package/src/cli/commands/__tests__/inference-profiles.test.ts +3 -0
  611. package/src/cli/commands/__tests__/inference-providers.test.ts +3 -0
  612. package/src/cli/commands/__tests__/inference-send.test.ts +0 -10
  613. package/src/cli/commands/__tests__/inference-session.test.ts +4 -11
  614. package/src/cli/commands/__tests__/ui.test.ts +2 -2
  615. package/src/cli/commands/apps.help.ts +46 -0
  616. package/src/cli/commands/apps.ts +51 -0
  617. package/src/cli/commands/avatar.help.ts +229 -0
  618. package/src/cli/commands/avatar.ts +58 -250
  619. package/src/cli/commands/backup.help.ts +183 -0
  620. package/src/cli/commands/backup.ts +107 -276
  621. package/src/cli/commands/bash.help.ts +38 -0
  622. package/src/cli/commands/bash.ts +51 -75
  623. package/src/cli/commands/browser.help.ts +193 -0
  624. package/src/cli/commands/browser.ts +234 -347
  625. package/src/cli/commands/cache.help.ts +123 -0
  626. package/src/cli/commands/cache.ts +72 -166
  627. package/src/cli/commands/changelog.help.ts +59 -0
  628. package/src/cli/commands/changelog.ts +29 -61
  629. package/src/cli/commands/channel-verification-sessions.help.ts +178 -0
  630. package/src/cli/commands/channel-verification-sessions.ts +71 -226
  631. package/src/cli/commands/channels/index.help.ts +67 -0
  632. package/src/cli/commands/channels/index.ts +55 -105
  633. package/src/cli/commands/clients.help.ts +72 -0
  634. package/src/cli/commands/clients.ts +100 -153
  635. package/src/cli/commands/completions.help.ts +33 -0
  636. package/src/cli/commands/completions.ts +43 -66
  637. package/src/cli/commands/config.help.ts +126 -0
  638. package/src/cli/commands/config.ts +59 -174
  639. package/src/cli/commands/contacts.help.ts +327 -0
  640. package/src/cli/commands/contacts.ts +318 -603
  641. package/src/cli/commands/conversations-defer.ts +141 -202
  642. package/src/cli/commands/conversations-import.ts +80 -113
  643. package/src/cli/commands/conversations.help.ts +364 -0
  644. package/src/cli/commands/conversations.ts +143 -328
  645. package/src/cli/commands/credentials.help.ts +290 -0
  646. package/src/cli/commands/credentials.ts +271 -487
  647. package/src/cli/commands/db/index.help.ts +27 -0
  648. package/src/cli/commands/db/index.ts +7 -3
  649. package/src/cli/commands/db/refresh.ts +131 -0
  650. package/src/cli/commands/db/repair-step-conversation-backfill.ts +19 -9
  651. package/src/cli/commands/db/repair.ts +41 -43
  652. package/src/cli/commands/db/status.ts +79 -83
  653. package/src/cli/commands/domain.help.ts +34 -0
  654. package/src/cli/commands/domain.ts +10 -15
  655. package/src/cli/commands/email.help.ts +176 -0
  656. package/src/cli/commands/email.ts +323 -412
  657. package/src/cli/commands/gateway.help.ts +115 -0
  658. package/src/cli/commands/gateway.ts +105 -123
  659. package/src/cli/commands/image-generation.help.ts +72 -0
  660. package/src/cli/commands/image-generation.ts +13 -62
  661. package/src/cli/commands/inference-callsites.ts +34 -49
  662. package/src/cli/commands/inference-models.ts +6 -21
  663. package/src/cli/commands/inference-profiles.ts +95 -156
  664. package/src/cli/commands/inference-providers-default.ts +27 -42
  665. package/src/cli/commands/inference-providers.ts +25 -87
  666. package/src/cli/commands/inference-session.ts +189 -249
  667. package/src/cli/commands/inference.help.ts +485 -0
  668. package/src/cli/commands/inference.ts +104 -165
  669. package/src/cli/commands/keys.help.ts +46 -0
  670. package/src/cli/commands/keys.ts +37 -71
  671. package/src/cli/commands/mcp.help.ts +167 -0
  672. package/src/cli/commands/mcp.ts +123 -267
  673. package/src/cli/commands/memory/__tests__/memory-items.test.ts +4 -0
  674. package/src/cli/commands/memory/__tests__/memory-v2.test.ts +4 -0
  675. package/src/cli/commands/memory/__tests__/memory-v3.test.ts +4 -0
  676. package/src/cli/commands/memory/__tests__/worker.test.ts +12 -5
  677. package/src/cli/commands/memory/index.help.ts +814 -0
  678. package/src/cli/commands/memory/index.ts +5 -27
  679. package/src/cli/commands/memory/items.ts +257 -404
  680. package/src/cli/commands/memory/memory-retrospective.ts +31 -71
  681. package/src/cli/commands/memory/memory-v2-compare-render.ts +2 -2
  682. package/src/cli/commands/memory/memory-v2.ts +435 -646
  683. package/src/cli/commands/memory/memory-v3.ts +158 -283
  684. package/src/cli/commands/memory/nodes.ts +154 -249
  685. package/src/cli/commands/memory/worker.ts +83 -128
  686. package/src/cli/commands/monitoring.help.ts +57 -0
  687. package/src/cli/commands/monitoring.ts +17 -41
  688. package/src/cli/commands/notifications.help.ts +293 -0
  689. package/src/cli/commands/notifications.ts +420 -657
  690. package/src/cli/commands/oauth/apps.ts +182 -315
  691. package/src/cli/commands/oauth/connect.ts +4 -35
  692. package/src/cli/commands/oauth/disconnect.ts +44 -71
  693. package/src/cli/commands/oauth/index.help.ts +855 -0
  694. package/src/cli/commands/oauth/index.ts +5 -31
  695. package/src/cli/commands/oauth/mode.ts +5 -28
  696. package/src/cli/commands/oauth/ping.ts +44 -66
  697. package/src/cli/commands/oauth/providers.ts +413 -772
  698. package/src/cli/commands/oauth/request.ts +6 -35
  699. package/src/cli/commands/oauth/shared.ts +1 -1
  700. package/src/cli/commands/oauth/status.test.ts +6 -6
  701. package/src/cli/commands/oauth/status.ts +45 -62
  702. package/src/cli/commands/oauth/token.ts +32 -71
  703. package/src/cli/commands/pending.help.ts +26 -0
  704. package/src/cli/commands/pending.ts +77 -78
  705. package/src/cli/commands/platform/__tests__/connect.test.ts +3 -1
  706. package/src/cli/commands/platform/__tests__/disconnect.test.ts +4 -4
  707. package/src/cli/commands/platform/__tests__/status.test.ts +0 -90
  708. package/src/cli/commands/platform/connect.ts +10 -21
  709. package/src/cli/commands/platform/disconnect.ts +11 -26
  710. package/src/cli/commands/platform/index.help.ts +187 -0
  711. package/src/cli/commands/platform/index.ts +22 -169
  712. package/src/cli/commands/plugins.help.ts +247 -0
  713. package/src/cli/commands/plugins.ts +356 -437
  714. package/src/cli/commands/ps.help.ts +26 -0
  715. package/src/cli/commands/ps.ts +5 -17
  716. package/src/cli/commands/routes.help.ts +72 -0
  717. package/src/cli/commands/routes.ts +78 -125
  718. package/src/cli/commands/schedules-worker.ts +70 -138
  719. package/src/cli/commands/schedules.help.ts +514 -0
  720. package/src/cli/commands/schedules.ts +494 -871
  721. package/src/cli/commands/sequence.help.ts +183 -0
  722. package/src/cli/commands/sequence.ts +181 -343
  723. package/src/cli/commands/skills.help.ts +159 -0
  724. package/src/cli/commands/skills.ts +230 -368
  725. package/src/cli/commands/status.help.ts +32 -0
  726. package/src/cli/commands/status.ts +38 -3
  727. package/src/cli/commands/stt.help.ts +52 -0
  728. package/src/cli/commands/stt.ts +9 -47
  729. package/src/cli/commands/telemetry.help.ts +14 -0
  730. package/src/cli/commands/telemetry.ts +19 -17
  731. package/src/cli/commands/tools-run.ts +30 -45
  732. package/src/cli/commands/tools.help.ts +81 -0
  733. package/src/cli/commands/tools.ts +43 -83
  734. package/src/cli/commands/trust.help.ts +43 -0
  735. package/src/cli/commands/trust.ts +82 -109
  736. package/src/cli/commands/tts.help.ts +88 -0
  737. package/src/cli/commands/tts.ts +116 -188
  738. package/src/cli/commands/ui.help.ts +213 -0
  739. package/src/cli/commands/ui.ts +363 -380
  740. package/src/cli/commands/usage.help.ts +147 -0
  741. package/src/cli/commands/usage.ts +87 -206
  742. package/src/cli/commands/watchers.help.ts +211 -0
  743. package/src/cli/commands/watchers.ts +261 -417
  744. package/src/cli/commands/webhooks.help.ts +95 -0
  745. package/src/cli/commands/webhooks.ts +44 -115
  746. package/src/cli/index.help.ts +91 -0
  747. package/src/cli/lib/__tests__/global-json-option.test.ts +97 -0
  748. package/src/cli/lib/__tests__/install-from-github.test.ts +144 -4
  749. package/src/cli/lib/__tests__/plugin-catalog-cache.test.ts +96 -132
  750. package/src/cli/lib/__tests__/plugin-catalog-local.test.ts +65 -0
  751. package/src/cli/lib/__tests__/plugin-catalog-platform.test.ts +167 -0
  752. package/src/cli/lib/__tests__/plugin-catalog-resolve.test.ts +197 -0
  753. package/src/cli/lib/__tests__/plugin-details.test.ts +349 -240
  754. package/src/cli/lib/__tests__/plugin-fingerprint.test.ts +31 -0
  755. package/src/cli/lib/__tests__/plugin-icon-file.test.ts +50 -1
  756. package/src/cli/lib/__tests__/plugin-marketplace.test.ts +94 -0
  757. package/src/cli/lib/__tests__/plugins-install-offline.test.ts +147 -0
  758. package/src/cli/lib/__tests__/search-plugins.test.ts +193 -309
  759. package/src/cli/lib/bundled-marketplace.json +439 -0
  760. package/src/cli/lib/cli-command-help.ts +83 -10
  761. package/src/cli/lib/global-json-option.ts +41 -0
  762. package/src/cli/lib/install-from-github.ts +69 -2
  763. package/src/cli/lib/plugin-catalog-cache.ts +22 -50
  764. package/src/cli/lib/plugin-catalog-local.ts +71 -0
  765. package/src/cli/lib/plugin-catalog-platform.ts +117 -0
  766. package/src/cli/lib/plugin-catalog-resolve.ts +72 -0
  767. package/src/cli/lib/plugin-details.ts +100 -34
  768. package/src/cli/lib/plugin-fingerprint.ts +18 -7
  769. package/src/cli/lib/plugin-icon-file.ts +38 -26
  770. package/src/cli/lib/plugin-marketplace.ts +15 -2
  771. package/src/cli/lib/publish-plugin.ts +3 -3
  772. package/src/cli/lib/search-plugins.ts +48 -111
  773. package/src/cli/program.ts +7 -0
  774. package/src/cli.ts +22 -13
  775. package/src/config/__tests__/default-profile-catalog.test.ts +8 -12
  776. package/src/config/__tests__/default-provider.test.ts +0 -21
  777. package/src/config/__tests__/deployment-context-defaults.test.ts +0 -21
  778. package/src/config/__tests__/feature-flag-registry-bundled.test.ts +2 -2
  779. package/src/config/__tests__/loader-callsite-strip-fallback.test.ts +0 -21
  780. package/src/config/__tests__/loader-last-known-good.test.ts +0 -21
  781. package/src/config/__tests__/loader-sparse-array-cleanup.test.ts +0 -21
  782. package/src/config/__tests__/platform-rehydration.test.ts +125 -0
  783. package/src/config/__tests__/profile-materialization.test.ts +1 -88
  784. package/src/config/__tests__/seed-default-provider.test.ts +0 -21
  785. package/src/config/__tests__/sync-gated-profiles.test.ts +0 -21
  786. package/src/config/bundled-skills/app-builder/SKILL.md +24 -1
  787. package/src/config/bundled-skills/app-builder/references/CUSTOM_ROUTES.md +30 -0
  788. package/src/config/bundled-skills/contacts/SKILL.md +1 -1
  789. package/src/config/bundled-skills/contacts/tools/contact-merge.ts +4 -2
  790. package/src/config/bundled-skills/messaging/TOOLS.json +2 -2
  791. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +28 -20
  792. package/src/config/bundled-skills/phone-calls/references/CONFIG.md +7 -7
  793. package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +3 -3
  794. package/src/config/bundled-skills/schedule/SKILL.md +0 -1
  795. package/src/config/bundled-skills/schedule/TOOLS.json +2 -2
  796. package/src/config/bundled-skills/settings/TOOLS.json +5 -3
  797. package/src/config/bundled-skills/settings/tools/voice-config-update.ts +84 -10
  798. package/src/config/bundled-skills/transcribe/tools/transcribe-media.test.ts +0 -7
  799. package/src/config/call-site-defaults.ts +3 -3
  800. package/src/config/feature-flag-registry.json +15 -63
  801. package/src/config/llm-resolver.ts +88 -475
  802. package/src/config/managed-speech-defaults.ts +115 -0
  803. package/src/config/platform-rehydration.ts +76 -0
  804. package/src/config/profile-materialization.ts +11 -11
  805. package/src/config/schema.ts +93 -129
  806. package/src/config/schemas/__tests__/live-voice.test.ts +24 -6
  807. package/src/config/schemas/__tests__/stt.test.ts +37 -0
  808. package/src/config/schemas/live-voice.ts +10 -4
  809. package/src/config/schemas/llm.ts +51 -15
  810. package/src/config/schemas/memory-lifecycle.ts +1 -13
  811. package/src/config/schemas/memory-retrospective.ts +8 -0
  812. package/src/config/schemas/memory.ts +0 -4
  813. package/src/config/schemas/stt.ts +33 -6
  814. package/src/config/schemas/tts.ts +53 -6
  815. package/src/config/schemas/workspace-git.ts +8 -0
  816. package/src/contacts/contact-store.ts +3 -58
  817. package/src/contacts/types.ts +1 -1
  818. package/src/conversations/__tests__/message-consolidation.test.ts +6 -5
  819. package/src/conversations/message-consolidation.ts +34 -112
  820. package/src/daemon/__tests__/conversation-tool-setup-exclude.test.ts +64 -7
  821. package/src/daemon/__tests__/conversation-tool-setup-plugin-scope.test.ts +3 -7
  822. package/src/daemon/__tests__/conversation-tool-setup.test.ts +8 -6
  823. package/src/daemon/__tests__/embedding-reconcile.test.ts +0 -11
  824. package/src/daemon/__tests__/inference-profile-notification.test.ts +5 -11
  825. package/src/daemon/__tests__/inflight-message-content.test.ts +219 -0
  826. package/src/daemon/__tests__/native-web-search-metadata.test.ts +5 -27
  827. package/src/daemon/__tests__/route-conversation-post.test.ts +158 -0
  828. package/src/daemon/__tests__/wake-conversation-ops-completion.test.ts +0 -6
  829. package/src/daemon/__tests__/web-search-status-text.test.ts +0 -29
  830. package/src/daemon/app-source-watcher.ts +17 -23
  831. package/src/daemon/chat-credential-redaction.ts +1365 -0
  832. package/src/daemon/conversation-agent-loop-handlers.ts +742 -77
  833. package/src/daemon/conversation-agent-loop.ts +10 -17
  834. package/src/daemon/conversation-error.ts +53 -16
  835. package/src/daemon/conversation-history.ts +6 -12
  836. package/src/daemon/conversation-messaging.ts +13 -1
  837. package/src/daemon/conversation-process.ts +16 -11
  838. package/src/daemon/conversation-runtime-assembly.ts +148 -52
  839. package/src/daemon/conversation-surfaces.ts +217 -100
  840. package/src/daemon/conversation-tool-setup.ts +59 -112
  841. package/src/daemon/conversation-turn-finalize.ts +18 -0
  842. package/src/daemon/conversation.ts +35 -66
  843. package/src/daemon/event-loop-watchdog.test.ts +1 -6
  844. package/src/daemon/external-plugins-bootstrap.ts +20 -74
  845. package/src/daemon/handlers/__tests__/config-a2a-accept.test.ts +0 -7
  846. package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +1 -8
  847. package/src/daemon/handlers/__tests__/config-a2a-invite.test.ts +1 -8
  848. package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +1 -8
  849. package/src/daemon/handlers/__tests__/config-a2a.test.ts +1 -8
  850. package/src/daemon/handlers/conversation-history.ts +6 -4
  851. package/src/daemon/handlers/conversations.ts +19 -9
  852. package/src/daemon/handlers/shared.ts +24 -1
  853. package/src/daemon/inflight-message-content.ts +228 -0
  854. package/src/daemon/lifecycle.ts +21 -31
  855. package/src/daemon/message-protocol.ts +2 -7
  856. package/src/daemon/message-provenance.ts +17 -8
  857. package/src/daemon/message-types/conversations.ts +6 -17
  858. package/src/daemon/message-types/host-ui-snapshot.ts +44 -0
  859. package/src/daemon/message-types/inbox.ts +3 -46
  860. package/src/daemon/orphan-reaper.test.ts +1 -6
  861. package/src/daemon/process-message.ts +9 -0
  862. package/src/daemon/providers-setup.ts +5 -75
  863. package/src/daemon/route-conversation-post.ts +158 -0
  864. package/src/daemon/shutdown-handlers.ts +11 -17
  865. package/src/daemon/sqlite-corruption-watchdog.test.ts +0 -6
  866. package/src/documents/document-comments-store.test.ts +1 -7
  867. package/src/export/__tests__/transcript-formatter.test.ts +8 -8
  868. package/src/export/formatter.ts +8 -17
  869. package/src/export/transcript-formatter.ts +4 -1
  870. package/src/heartbeat/__tests__/heartbeat-run-store.test.ts +0 -9
  871. package/src/heartbeat/__tests__/heartbeat-service.test.ts +29 -57
  872. package/src/home/__tests__/home-content-refresh.test.ts +0 -7
  873. package/src/home/__tests__/suggested-prompts.test.ts +0 -11
  874. package/src/ipc/__tests__/contacts-info-ipc.test.ts +12 -10
  875. package/src/ipc/__tests__/email-ipc.test.ts +90 -0
  876. package/src/ipc/assistant-server.ts +10 -22
  877. package/src/ipc/routes/__tests__/guardian-label-ipc-routes.test.ts +74 -0
  878. package/src/ipc/routes/__tests__/invite-ipc-routes.test.ts +0 -7
  879. package/src/ipc/routes/guardian-label-ipc-routes.ts +42 -0
  880. package/src/live-voice/__tests__/live-voice-archive.test.ts +1 -8
  881. package/src/live-voice/__tests__/live-voice-credential-preflight.test.ts +89 -49
  882. package/src/live-voice/__tests__/live-voice-integration.test.ts +116 -2
  883. package/src/live-voice/__tests__/live-voice-resume-registry.test.ts +58 -0
  884. package/src/live-voice/__tests__/live-voice-resume-turn.test.ts +280 -0
  885. package/src/live-voice/__tests__/live-voice-triage-escalate.test.ts +337 -0
  886. package/src/live-voice/__tests__/live-voice-tts.test.ts +0 -4
  887. package/src/live-voice/__tests__/live-voice-vad.test.ts +432 -12
  888. package/src/live-voice/__tests__/protocol.test.ts +122 -0
  889. package/src/live-voice/__tests__/runtime-websocket-shell.test.ts +0 -41
  890. package/src/live-voice/live-voice-credential-preflight.ts +27 -3
  891. package/src/live-voice/live-voice-resume-registry.ts +54 -0
  892. package/src/live-voice/live-voice-session.ts +602 -61
  893. package/src/live-voice/protocol.ts +143 -1
  894. package/src/mcp/__tests__/mcp-auth-orchestrator.test.ts +3 -7
  895. package/src/messaging/provider-types.ts +9 -0
  896. package/src/messaging/providers/a2a/__tests__/deliver.test.ts +0 -9
  897. package/src/messaging/providers/outlook/adapter.ts +17 -2
  898. package/src/messaging/providers/outlook/client.ts +11 -2
  899. package/src/messaging/providers/outlook/types.ts +9 -0
  900. package/src/messaging/providers/slack/__tests__/download.test.ts +1 -8
  901. package/src/messaging/providers/slack/binding-metadata.test.ts +23 -21
  902. package/src/messaging/providers/slack/send.test.ts +0 -7
  903. package/src/messaging/providers/slack/withdraw.test.ts +0 -6
  904. package/src/messaging/providers/telegram-bot/send.test.ts +0 -7
  905. package/src/monitoring/__tests__/plugin-source-watch.test.ts +40 -1
  906. package/src/monitoring/plugin-source-watch.ts +56 -62
  907. package/src/monitoring/recovery/__tests__/inflight-content.test.ts +166 -0
  908. package/src/monitoring/recovery/inflight-content.ts +260 -0
  909. package/src/monitoring/recovery/run-recovery.ts +72 -0
  910. package/src/monitoring/worker.ts +68 -7
  911. package/src/notifications/AGENTS.md +1 -1
  912. package/src/notifications/README.md +4 -4
  913. package/src/notifications/__tests__/broadcaster.test.ts +4 -17
  914. package/src/notifications/__tests__/connected-channels.test.ts +11 -8
  915. package/src/notifications/__tests__/decision-engine.test.ts +0 -7
  916. package/src/notifications/__tests__/destination-resolver.test.ts +18 -12
  917. package/src/notifications/__tests__/deterministic-checks.test.ts +1 -9
  918. package/src/notifications/adapters/macos.ts +3 -5
  919. package/src/notifications/adapters/platform.ts +1 -1
  920. package/src/notifications/broadcaster.ts +13 -4
  921. package/src/notifications/conversation-candidates.ts +21 -28
  922. package/src/notifications/copy-composer.ts +0 -6
  923. package/src/notifications/decision-engine.ts +1 -1
  924. package/src/notifications/{canonical-delivery-recorder.ts → guardian-delivery-recorder.ts} +47 -33
  925. package/src/notifications/home-feed-side-effect.ts +0 -2
  926. package/src/notifications/signal.ts +0 -4
  927. package/src/oauth/byo-connection.test.ts +0 -9
  928. package/src/oauth/connection-resolver.test.ts +18 -46
  929. package/src/oauth/seed-providers.ts +6 -0
  930. package/src/onboarding/__tests__/onboarding-events-store.test.ts +122 -44
  931. package/src/onboarding/onboarding-events-store.ts +67 -115
  932. package/src/onboarding/onboarding-research-events-store.test.ts +222 -0
  933. package/src/onboarding/onboarding-research-events-store.ts +92 -0
  934. package/src/permissions/checker.test.ts +10 -24
  935. package/src/permissions/confirmation-guardian-request.test.ts +94 -0
  936. package/src/permissions/{confirmation-canonical-request.ts → confirmation-guardian-request.ts} +44 -29
  937. package/src/permissions/gateway-threshold-reader.test.ts +0 -8
  938. package/src/permissions/prompter.ts +6 -4
  939. package/src/permissions/question-prompter.test.ts +7 -14
  940. package/src/persistence/__tests__/bulk-write-gate.test.ts +1 -7
  941. package/src/persistence/__tests__/conversation-queries-search.test.ts +0 -7
  942. package/src/persistence/__tests__/main-db-readonly-probe.ts +80 -0
  943. package/src/persistence/__tests__/main-db-readonly.test.ts +35 -0
  944. package/src/persistence/__tests__/message-content-file.test.ts +161 -52
  945. package/src/persistence/__tests__/raw-query-telemetry.test.ts +74 -0
  946. package/src/persistence/conversation-crud.ts +175 -45
  947. package/src/persistence/conversation-disk-view.ts +32 -15
  948. package/src/persistence/conversation-group-migration.test.ts +1 -8
  949. package/src/persistence/conversation-title-service.ts +59 -22
  950. package/src/persistence/db-connection.ts +46 -1
  951. package/src/persistence/db-singleton.ts +7 -1
  952. package/src/persistence/embeddings/__tests__/messages-lexical-index.test.ts +3 -7
  953. package/src/persistence/embeddings/messages-lexical-index.ts +11 -0
  954. package/src/persistence/job-handlers/__tests__/message-lexical-backfill.test.ts +0 -7
  955. package/src/persistence/job-handlers/__tests__/message-lexical-enqueue.test.ts +0 -6
  956. package/src/persistence/job-handlers/__tests__/message-lexical.test.ts +0 -7
  957. package/src/persistence/job-handlers/cleanup.ts +30 -16
  958. package/src/persistence/job-handlers/message-lexical-backfill.ts +8 -5
  959. package/src/persistence/job-handlers/message-lexical.ts +2 -2
  960. package/src/persistence/jobs-store.ts +123 -17
  961. package/src/persistence/lifecycle-events-store.test.ts +84 -0
  962. package/src/persistence/lifecycle-events-store.ts +27 -52
  963. package/src/persistence/llm-request-log-sink-clickhouse.ts +325 -0
  964. package/src/persistence/llm-request-log-store.ts +185 -106
  965. package/src/persistence/llm-request-log-writer-types.ts +80 -0
  966. package/src/persistence/llm-usage-store.ts +5 -2
  967. package/src/persistence/message-content-file.ts +182 -71
  968. package/src/persistence/message-content.ts +18 -10
  969. package/src/persistence/migrations/305-drop-contact-acl-columns.ts +3 -2
  970. package/src/persistence/migrations/325-create-config-setting-events.ts +40 -0
  971. package/src/persistence/migrations/326-move-injection-events-to-memory-db.ts +104 -0
  972. package/src/persistence/migrations/327-create-flush-checkpoints.ts +86 -0
  973. package/src/persistence/migrations/328-drop-memory-v3-learned-edge-tables.ts +16 -0
  974. package/src/persistence/migrations/329-move-onboarding-events-to-telemetry-db.test.ts +179 -0
  975. package/src/persistence/migrations/329-move-onboarding-events-to-telemetry-db.ts +119 -0
  976. package/src/persistence/migrations/330-move-auth-fallback-events-to-telemetry-db.test.ts +197 -0
  977. package/src/persistence/migrations/330-move-auth-fallback-events-to-telemetry-db.ts +108 -0
  978. package/src/persistence/migrations/331-move-lifecycle-events-to-telemetry-db.test.ts +159 -0
  979. package/src/persistence/migrations/331-move-lifecycle-events-to-telemetry-db.ts +97 -0
  980. package/src/persistence/migrations/332-move-skill-loaded-events-to-telemetry-db.test.ts +219 -0
  981. package/src/persistence/migrations/332-move-skill-loaded-events-to-telemetry-db.ts +128 -0
  982. package/src/persistence/migrations/333-create-telemetry-events-table.test.ts +66 -0
  983. package/src/persistence/migrations/333-create-telemetry-events-table.ts +43 -0
  984. package/src/persistence/migrations/334-backfill-telemetry-events-outbox.test.ts +584 -0
  985. package/src/persistence/migrations/334-backfill-telemetry-events-outbox.ts +386 -0
  986. package/src/persistence/migrations/335-collapse-memory-embed-backlog.test.ts +181 -0
  987. package/src/persistence/migrations/335-collapse-memory-embed-backlog.ts +177 -0
  988. package/src/persistence/migrations/__tests__/327-create-flush-checkpoints.test.ts +124 -0
  989. package/src/persistence/raw-query.ts +27 -1
  990. package/src/persistence/schema/calls.ts +0 -23
  991. package/src/persistence/schema/conversation-starters.ts +31 -0
  992. package/src/persistence/schema/conversations.ts +3 -3
  993. package/src/persistence/schema/guardian.ts +0 -150
  994. package/src/persistence/schema/index.ts +1 -0
  995. package/src/persistence/schema/infrastructure.ts +25 -72
  996. package/src/persistence/schema/memory-core.ts +0 -25
  997. package/src/persistence/schema/memory-injection.ts +3 -1
  998. package/src/persistence/schema/tasks.ts +1 -60
  999. package/src/persistence/steps.ts +22 -0
  1000. package/src/platform/client.test.ts +110 -0
  1001. package/src/platform/client.ts +24 -4
  1002. package/src/platform/consent-cache.test.ts +5 -18
  1003. package/src/platform/consent-cache.ts +8 -6
  1004. package/src/platform/managed-speech.test.ts +218 -0
  1005. package/src/platform/managed-speech.ts +204 -0
  1006. package/src/plugin-api/conversation-turn.ts +250 -0
  1007. package/src/plugin-api/index.ts +21 -0
  1008. package/src/plugin-api/model-profiles.test.ts +29 -28
  1009. package/src/plugin-api/vision-support.test.ts +25 -38
  1010. package/src/plugin-api/vision-support.ts +26 -17
  1011. package/src/plugins/AGENTS.md +8 -0
  1012. package/src/plugins/__tests__/source-fingerprint.test.ts +41 -0
  1013. package/src/plugins/collect-source-versions.ts +77 -0
  1014. package/src/plugins/defaults/image-recovery/detect.ts +32 -10
  1015. package/src/plugins/defaults/image-recovery/hooks/post-model-call.ts +20 -18
  1016. package/src/plugins/defaults/image-recovery/recover.ts +86 -59
  1017. package/src/plugins/defaults/memory/__tests__/activation-session-store.test.ts +1 -8
  1018. package/src/plugins/defaults/memory/__tests__/clear-stale-processing-flags.test.ts +1 -8
  1019. package/src/plugins/defaults/memory/__tests__/conversation-queries.test.ts +1 -8
  1020. package/src/plugins/defaults/memory/__tests__/conversation-row-batch-delete.test.ts +7 -18
  1021. package/src/plugins/defaults/memory/__tests__/db-memory-attach.test.ts +22 -18
  1022. package/src/plugins/defaults/memory/__tests__/embedding-cache.test.ts +0 -6
  1023. package/src/plugins/defaults/memory/__tests__/find-most-recent-retrospective-for.test.ts +1 -8
  1024. package/src/plugins/defaults/memory/__tests__/fork-message-copy.test.ts +7 -18
  1025. package/src/plugins/defaults/memory/__tests__/jobs-store-enqueue-gate.test.ts +29 -39
  1026. package/src/plugins/defaults/memory/__tests__/jobs-worker-memory-disabled.test.ts +11 -26
  1027. package/src/plugins/defaults/memory/__tests__/jobs-worker-v2-graph-trigger-embed.test.ts +3 -22
  1028. package/src/plugins/defaults/memory/__tests__/jobs-worker-v2-schedule.test.ts +215 -1
  1029. package/src/plugins/defaults/memory/__tests__/memory-retrospective-enqueue.test.ts +0 -7
  1030. package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +114 -16
  1031. package/src/plugins/defaults/memory/__tests__/memory-retrospective-prompt.test.ts +180 -0
  1032. package/src/plugins/defaults/memory/__tests__/memory-retrospective-skill-card.test.ts +153 -519
  1033. package/src/plugins/defaults/memory/__tests__/memory-retrospective-startup-cleanup.test.ts +0 -7
  1034. package/src/plugins/defaults/memory/__tests__/memory-retrospective-state.test.ts +6 -17
  1035. package/src/plugins/defaults/memory/__tests__/memory-retrospective-trigger-check.test.ts +0 -7
  1036. package/src/plugins/defaults/memory/__tests__/memory-v2-activation-log-store.test.ts +1 -19
  1037. package/src/plugins/defaults/memory/__tests__/memory-v2-concept-frequency.test.ts +0 -18
  1038. package/src/plugins/defaults/memory/__tests__/shutdown-hook.test.ts +8 -7
  1039. package/src/plugins/defaults/memory/__tests__/table-relocation.test.ts +49 -0
  1040. package/src/{persistence → plugins/defaults/memory}/__tests__/worker-control.test.ts +8 -10
  1041. package/src/plugins/defaults/memory/graph/capability-seed.ts +4 -6
  1042. package/src/plugins/defaults/memory/graph/extraction.ts +5 -1
  1043. package/src/plugins/defaults/memory/graph/graph-search.test.ts +2 -8
  1044. package/src/plugins/defaults/memory/graph/tool-handlers.test.ts +0 -6
  1045. package/src/plugins/defaults/memory/graph/tool-handlers.ts +2 -2
  1046. package/src/plugins/defaults/memory/graph/tools.ts +47 -13
  1047. package/src/plugins/defaults/memory/graph-topology/__tests__/build-memory-graph.test.ts +81 -2
  1048. package/src/plugins/defaults/memory/graph-topology/build-memory-graph.ts +67 -32
  1049. package/src/plugins/defaults/memory/hooks/shutdown.ts +8 -11
  1050. package/src/plugins/defaults/memory/indexer.ts +2 -1
  1051. package/src/plugins/defaults/memory/job-handlers/backfill.ts +8 -5
  1052. package/src/plugins/defaults/memory/job-handlers/embedding.test.ts +0 -7
  1053. package/src/plugins/defaults/memory/job-handlers/embedding.ts +2 -2
  1054. package/src/plugins/defaults/memory/job-handlers/index-maintenance.ts +7 -2
  1055. package/src/plugins/defaults/memory/jobs/__tests__/embed-concept-page.test.ts +73 -8
  1056. package/src/plugins/defaults/memory/jobs/embed-concept-page.ts +5 -3
  1057. package/src/plugins/defaults/memory/jobs/embed-pkb-file.test.ts +0 -13
  1058. package/src/plugins/defaults/memory/jobs-worker.ts +139 -107
  1059. package/src/plugins/defaults/memory/memory-db.ts +27 -0
  1060. package/src/plugins/defaults/memory/memory-retrospective-constants.ts +4 -4
  1061. package/src/plugins/defaults/memory/memory-retrospective-job.ts +56 -180
  1062. package/src/plugins/defaults/memory/memory-retrospective-prompt.ts +216 -0
  1063. package/src/plugins/defaults/memory/memory-retrospective-skill-card.ts +122 -202
  1064. package/src/plugins/defaults/memory/message-media.ts +2 -2
  1065. package/src/plugins/defaults/memory/paths.ts +3 -2
  1066. package/src/plugins/defaults/memory/pkb/pkb-index.test.ts +0 -12
  1067. package/src/plugins/defaults/memory/pkb/pkb-reconcile.test.ts +0 -9
  1068. package/src/plugins/defaults/memory/pkb/pkb-search.test.ts +0 -6
  1069. package/src/plugins/defaults/memory/routes/__tests__/memory-v2-simulate-route.test.ts +15 -92
  1070. package/src/plugins/defaults/memory/routes/__tests__/memory-worker-routes.test.ts +201 -0
  1071. package/src/plugins/defaults/memory/routes/memory-item-routes.test.ts +7 -21
  1072. package/src/plugins/defaults/memory/routes/memory-v2-routes.ts +3 -8
  1073. package/src/plugins/defaults/memory/routes/memory-worker-routes.ts +162 -0
  1074. package/src/plugins/defaults/memory/startup.ts +6 -7
  1075. package/src/plugins/defaults/memory/task-memory-cleanup.ts +11 -22
  1076. package/src/plugins/defaults/memory/tools.test.ts +40 -8
  1077. package/src/plugins/defaults/memory/tools.ts +12 -2
  1078. package/src/plugins/defaults/memory/v2/__tests__/activation.test.ts +0 -19
  1079. package/src/plugins/defaults/memory/v2/__tests__/backfill-jobs.test.ts +63 -40
  1080. package/src/plugins/defaults/memory/v2/__tests__/cli-command-store.test.ts +61 -130
  1081. package/src/plugins/defaults/memory/v2/__tests__/consolidation-job.test.ts +406 -10
  1082. package/src/plugins/defaults/memory/v2/__tests__/harness-compare.test.ts +1 -10
  1083. package/src/plugins/defaults/memory/v2/__tests__/harness-oracle.test.ts +1 -10
  1084. package/src/plugins/defaults/memory/v2/__tests__/harness-replay-input.test.ts +1 -10
  1085. package/src/plugins/defaults/memory/v2/__tests__/injection-events.test.ts +61 -69
  1086. package/src/plugins/defaults/memory/v2/__tests__/injection.test.ts +0 -19
  1087. package/src/plugins/defaults/memory/v2/__tests__/migration.test.ts +11 -10
  1088. package/src/plugins/defaults/memory/v2/__tests__/page-index.test.ts +0 -5
  1089. package/src/plugins/defaults/memory/v2/__tests__/qdrant.test.ts +6 -22
  1090. package/src/plugins/defaults/memory/v2/__tests__/reranker.test.ts +3 -5
  1091. package/src/plugins/defaults/memory/v2/__tests__/router.test.ts +7 -20
  1092. package/src/plugins/defaults/memory/v2/__tests__/sim.test.ts +0 -24
  1093. package/src/plugins/defaults/memory/v2/__tests__/skill-content.test.ts +17 -11
  1094. package/src/plugins/defaults/memory/v2/__tests__/skill-store.test.ts +0 -16
  1095. package/src/plugins/defaults/memory/v2/__tests__/static-context.test.ts +0 -10
  1096. package/src/plugins/defaults/memory/v2/__tests__/sweep-job.test.ts +0 -6
  1097. package/src/plugins/defaults/memory/v2/backfill-jobs.ts +5 -2
  1098. package/src/plugins/defaults/memory/v2/cli-command-content.ts +43 -12
  1099. package/src/plugins/defaults/memory/v2/cli-command-store.ts +34 -50
  1100. package/src/plugins/defaults/memory/v2/consolidation-job.ts +216 -10
  1101. package/src/plugins/defaults/memory/v2/harness/router-retriever.ts +1 -8
  1102. package/src/plugins/defaults/memory/v2/injection-events.ts +16 -14
  1103. package/src/plugins/defaults/memory/v2/injection.ts +1 -2
  1104. package/src/plugins/defaults/memory/v2/memory-v2-startup.test.ts +0 -5
  1105. package/src/plugins/defaults/memory/v2/migration.ts +55 -24
  1106. package/src/plugins/defaults/memory/v2/prompts/consolidation.ts +4 -0
  1107. package/src/plugins/defaults/memory/v2/router.ts +4 -14
  1108. package/src/plugins/defaults/memory/v2/sweep-job.ts +1 -0
  1109. package/src/plugins/defaults/memory/v3/__tests__/carry-integration.test.ts +10 -22
  1110. package/src/plugins/defaults/memory/v3/__tests__/dense.test.ts +0 -5
  1111. package/src/plugins/defaults/memory/v3/__tests__/injection.test.ts +18 -20
  1112. package/src/plugins/defaults/memory/v3/__tests__/section-dense-store.test.ts +0 -5
  1113. package/src/plugins/defaults/memory/v3/__tests__/selection-log-store.test.ts +5 -9
  1114. package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +51 -31
  1115. package/src/plugins/defaults/memory/v3/prune.test.ts +3 -11
  1116. package/src/{persistence → plugins/defaults/memory}/worker-control.ts +12 -21
  1117. package/src/plugins/defaults/memory/worker.ts +19 -2
  1118. package/src/plugins/mtime-cache.ts +166 -70
  1119. package/src/plugins/plugin-tree-walk.ts +41 -4
  1120. package/src/plugins/source-fingerprint.ts +9 -3
  1121. package/src/plugins/surface-import.ts +28 -0
  1122. package/src/plugins/types.ts +9 -23
  1123. package/src/prompts/__tests__/system-prompt.test.ts +0 -28
  1124. package/src/prompts/__tests__/task-progress-hint-section.test.ts +0 -28
  1125. package/src/providers/__tests__/connection-model-compat.test.ts +6 -19
  1126. package/src/providers/__tests__/count-tokens-forwarding.test.ts +1 -21
  1127. package/src/providers/__tests__/dispatch-connection-routing.test.ts +9 -26
  1128. package/src/providers/__tests__/registry-native-web-search.test.ts +4 -10
  1129. package/src/providers/__tests__/retry-callsite.test.ts +240 -235
  1130. package/src/providers/__tests__/satellite-connection-routing.test.ts +11 -36
  1131. package/src/providers/__tests__/vellum-mismatch-routing.test.ts +0 -5
  1132. package/src/providers/anthropic/client.ts +9 -3
  1133. package/src/providers/call-site-routing.ts +7 -3
  1134. package/src/providers/connection-resolution.ts +9 -11
  1135. package/src/providers/content-block-schema.ts +149 -0
  1136. package/src/providers/inference/__tests__/base-url-route-validation.test.ts +7 -11
  1137. package/src/providers/inference/__tests__/codex-token-refresh.test.ts +1 -15
  1138. package/src/providers/inference/auth.ts +23 -0
  1139. package/src/providers/inference/backfill.ts +25 -9
  1140. package/src/providers/model-catalog.ts +213 -5
  1141. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +54 -0
  1142. package/src/providers/openai/chat-completions-provider.ts +13 -2
  1143. package/src/providers/openai/responses-provider.ts +232 -5
  1144. package/src/providers/openrouter/client.ts +53 -0
  1145. package/src/providers/retry.ts +25 -2
  1146. package/src/providers/speech-to-text/__tests__/provider-catalog.test.ts +13 -0
  1147. package/src/providers/speech-to-text/__tests__/resolve.test.ts +211 -74
  1148. package/src/providers/speech-to-text/__tests__/vellum-managed-realtime.test.ts +408 -0
  1149. package/src/providers/speech-to-text/__tests__/vellum-managed.test.ts +121 -0
  1150. package/src/providers/speech-to-text/__tests__/vellum-speech-relay-connection.test.ts +126 -0
  1151. package/src/providers/speech-to-text/deepgram-realtime.ts +167 -37
  1152. package/src/providers/speech-to-text/incremental-batch-stream.ts +413 -0
  1153. package/src/providers/speech-to-text/openai-whisper-stream.ts +16 -363
  1154. package/src/providers/speech-to-text/provider-catalog.ts +26 -1
  1155. package/src/providers/speech-to-text/resolve.ts +74 -13
  1156. package/src/providers/speech-to-text/vellum-managed-realtime.ts +309 -0
  1157. package/src/providers/speech-to-text/vellum-managed.ts +71 -0
  1158. package/src/providers/speech-to-text/vellum-speech-relay-connection.ts +174 -0
  1159. package/src/providers/types.ts +23 -4
  1160. package/src/providers/vellum-model-routing.test.ts +26 -0
  1161. package/src/providers/vellum-model-routing.ts +30 -0
  1162. package/src/runtime/AGENTS.md +1 -1
  1163. package/src/runtime/__tests__/actor-trust-resolver.test.ts +2 -8
  1164. package/src/runtime/__tests__/agent-wake.test.ts +100 -27
  1165. package/src/runtime/__tests__/background-job-runner.test.ts +22 -0
  1166. package/src/runtime/__tests__/guardian-vellum-migration.test.ts +0 -5
  1167. package/src/runtime/__tests__/member-verdict-cache.test.ts +2 -9
  1168. package/src/runtime/__tests__/trust-verdict-consumer.test.ts +22 -5
  1169. package/src/runtime/access-request-helper.ts +84 -60
  1170. package/src/runtime/actor-trust-resolver.ts +2 -2
  1171. package/src/runtime/agent-wake.ts +75 -26
  1172. package/src/runtime/assistant-event-hub.ts +1 -0
  1173. package/src/runtime/auth/__tests__/middleware.test.ts +1 -10
  1174. package/src/runtime/auth/__tests__/route-policy.test.ts +0 -7
  1175. package/src/runtime/auth/__tests__/token-service.test.ts +1 -8
  1176. package/src/runtime/auth/same-actor.ts +2 -1
  1177. package/src/runtime/background-job-runner.ts +22 -0
  1178. package/src/runtime/channel-approval-types.ts +1 -1
  1179. package/src/runtime/channel-reply-delivery.ts +38 -19
  1180. package/src/runtime/confirmation-request-guardian-bridge.ts +45 -39
  1181. package/src/runtime/for-chat-mint-registry.ts +118 -0
  1182. package/src/runtime/guardian-action-service.ts +32 -27
  1183. package/src/runtime/guardian-decision-types.ts +2 -2
  1184. package/src/runtime/guardian-reply-router.ts +80 -98
  1185. package/src/runtime/http-server.ts +61 -41
  1186. package/src/runtime/introduction-policy.ts +2 -2
  1187. package/src/runtime/local-actor-identity.ts +19 -7
  1188. package/src/runtime/pending-interactions.ts +1 -0
  1189. package/src/runtime/reveal-nonce.ts +49 -0
  1190. package/src/runtime/reveal-success-registry.ts +306 -0
  1191. package/src/runtime/routes/__tests__/acp-routes.test.ts +0 -7
  1192. package/src/runtime/routes/__tests__/backup-routes.test.ts +195 -125
  1193. package/src/runtime/routes/__tests__/channel-verification-routes.test.ts +4 -1
  1194. package/src/runtime/routes/__tests__/client-routes.test.ts +0 -7
  1195. package/src/runtime/routes/__tests__/connection-routes-vs-cli-parity.test.ts +1 -19
  1196. package/src/runtime/routes/__tests__/consolidation-routes.test.ts +1 -8
  1197. package/src/runtime/routes/__tests__/contact-routes-merge-relay.test.ts +186 -0
  1198. package/src/runtime/routes/__tests__/contact-routes-update-channel-relay.test.ts +2 -3
  1199. package/src/runtime/routes/__tests__/content-source-routes.test.ts +0 -7
  1200. package/src/runtime/routes/__tests__/conversation-compaction-routes.test.ts +5 -27
  1201. package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +0 -7
  1202. package/src/runtime/routes/__tests__/conversation-management-routes.test.ts +12 -30
  1203. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +59 -56
  1204. package/src/runtime/routes/__tests__/conversation-surface-routes.test.ts +0 -7
  1205. package/src/runtime/routes/__tests__/default-provider-routes.test.ts +53 -73
  1206. package/src/runtime/routes/__tests__/gateway-status-routes.test.ts +84 -0
  1207. package/src/runtime/routes/__tests__/inference-callsites-routes.test.ts +2 -10
  1208. package/src/runtime/routes/__tests__/inference-profiles-routes.test.ts +58 -90
  1209. package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +161 -93
  1210. package/src/runtime/routes/__tests__/llm-call-sites-routes.test.ts +6 -1
  1211. package/src/runtime/routes/__tests__/monitoring-routes.test.ts +0 -8
  1212. package/src/runtime/routes/__tests__/plugins-routes.test.ts +218 -64
  1213. package/src/runtime/routes/__tests__/question-routes.test.ts +2 -4
  1214. package/src/runtime/routes/__tests__/retrospective-routes.test.ts +1 -8
  1215. package/src/runtime/routes/__tests__/sanity-routes.test.ts +0 -7
  1216. package/src/runtime/routes/__tests__/schedule-routes-create.test.ts +2 -1
  1217. package/src/runtime/routes/__tests__/schedule-worker-routes.test.ts +16 -56
  1218. package/src/runtime/routes/__tests__/stt-routes.test.ts +0 -9
  1219. package/src/runtime/routes/__tests__/suggest-trust-rule-routes.test.ts +0 -7
  1220. package/src/runtime/routes/__tests__/surface-action-routes.test.ts +5 -0
  1221. package/src/runtime/routes/__tests__/surface-content-routes.test.ts +354 -1
  1222. package/src/runtime/routes/__tests__/tts-routes.test.ts +0 -11
  1223. package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +163 -10
  1224. package/src/runtime/routes/__tests__/user-routes-cli.test.ts +154 -0
  1225. package/src/runtime/routes/__tests__/wake-conversation-routes.test.ts +0 -7
  1226. package/src/runtime/routes/acp-routes.ts +21 -9
  1227. package/src/runtime/routes/app-management-routes.ts +231 -60
  1228. package/src/runtime/routes/app-routes.ts +130 -83
  1229. package/src/runtime/routes/auth-routes.ts +3 -1
  1230. package/src/runtime/routes/background-wake-routes.ts +0 -1
  1231. package/src/runtime/routes/cache-routes.ts +3 -3
  1232. package/src/runtime/routes/call-routes.ts +5 -1
  1233. package/src/runtime/routes/channel-verification-routes.ts +2 -1
  1234. package/src/runtime/routes/contact-prompt-routes.ts +11 -5
  1235. package/src/runtime/routes/contact-routes.ts +34 -42
  1236. package/src/runtime/routes/conversation-attention-routes.ts +1 -1
  1237. package/src/runtime/routes/conversation-cli-routes.ts +1 -1
  1238. package/src/runtime/routes/conversation-query-routes.ts +4 -4
  1239. package/src/runtime/routes/conversation-routes.ts +111 -72
  1240. package/src/runtime/routes/credential-routes.ts +116 -3
  1241. package/src/runtime/routes/email-routes.ts +17 -1
  1242. package/src/runtime/routes/events-routes.ts +1 -0
  1243. package/src/runtime/routes/gateway-status-routes.ts +69 -0
  1244. package/src/runtime/routes/guardian-action-routes.ts +18 -16
  1245. package/src/runtime/routes/guardian-approval-interception.ts +6 -4
  1246. package/src/runtime/routes/guardian-approval-prompt.ts +7 -5
  1247. package/src/runtime/routes/guardian-expiry-sweep.ts +130 -0
  1248. package/src/runtime/routes/inbound-message-handler.ts +9 -33
  1249. package/src/runtime/routes/inbound-stages/acl-enforcement.test.ts +0 -5
  1250. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +9 -9
  1251. package/src/runtime/routes/inbound-stages/admission-policy.test.ts +24 -6
  1252. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +0 -7
  1253. package/src/runtime/routes/inbound-stages/bootstrap-intercept.test.ts +0 -5
  1254. package/src/runtime/routes/inbound-stages/bootstrap-intercept.ts +6 -3
  1255. package/src/runtime/routes/inbound-stages/edit-intercept.ts +5 -1
  1256. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.test.ts +0 -9
  1257. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +4 -1
  1258. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.test.ts +3 -12
  1259. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +16 -14
  1260. package/src/runtime/routes/inbound-stages/reaction-intercept.test.ts +8 -2
  1261. package/src/runtime/routes/inbound-stages/reaction-intercept.ts +15 -7
  1262. package/src/runtime/routes/inbound-stages/transcribe-audio.test.ts +0 -9
  1263. package/src/runtime/routes/index.ts +5 -5
  1264. package/src/runtime/routes/inference-provider-connection-routes.ts +73 -24
  1265. package/src/runtime/routes/inference-send-routes.ts +1 -1
  1266. package/src/runtime/routes/internal-telemetry-routes.ts +11 -2
  1267. package/src/runtime/routes/oauth-connect-routes.ts +27 -7
  1268. package/src/runtime/routes/platform-routes.ts +4 -14
  1269. package/src/runtime/routes/playground/__tests__/force-compact.test.ts +0 -4
  1270. package/src/runtime/routes/playground/__tests__/guard.test.ts +0 -4
  1271. package/src/runtime/routes/playground/__tests__/inject-failures.test.ts +0 -14
  1272. package/src/runtime/routes/playground/__tests__/reset-circuit.test.ts +0 -21
  1273. package/src/runtime/routes/playground/__tests__/seed-conversation.test.ts +0 -4
  1274. package/src/runtime/routes/playground/__tests__/seeded-conversations.test.ts +16 -18
  1275. package/src/runtime/routes/playground/__tests__/state.test.ts +0 -14
  1276. package/src/runtime/routes/playground/seed-conversation.ts +5 -4
  1277. package/src/runtime/routes/playground/seeded-conversations.ts +3 -2
  1278. package/src/runtime/routes/plugins-routes.ts +163 -25
  1279. package/src/runtime/routes/profiler-routes.ts +2 -5
  1280. package/src/runtime/routes/publish-routes.ts +15 -18
  1281. package/src/runtime/routes/question-routes.ts +8 -4
  1282. package/src/runtime/routes/schedule-routes.ts +20 -78
  1283. package/src/runtime/routes/schedule-worker-routes.ts +16 -84
  1284. package/src/runtime/routes/secret-routes.ts +14 -0
  1285. package/src/runtime/routes/settings-routes.ts +21 -25
  1286. package/src/runtime/routes/subagents-routes.ts +17 -18
  1287. package/src/runtime/routes/surface-action-routes.ts +18 -39
  1288. package/src/runtime/routes/surface-content-routes.ts +59 -1
  1289. package/src/runtime/routes/surface-conversation-resolver.ts +148 -4
  1290. package/src/runtime/routes/telemetry-routes.ts +74 -1
  1291. package/src/runtime/routes/tts-routes.ts +9 -5
  1292. package/src/runtime/routes/ui-snapshot-routes.test.ts +219 -0
  1293. package/src/runtime/routes/ui-snapshot-routes.ts +313 -0
  1294. package/src/runtime/routes/user-route-dispatcher.ts +52 -53
  1295. package/src/runtime/routes/user-route-resolution.ts +141 -0
  1296. package/src/runtime/routes/user-routes-cli.ts +56 -35
  1297. package/src/runtime/routes/user-routes.ts +9 -2
  1298. package/src/runtime/routes/vellum-actor-trust.ts +67 -0
  1299. package/src/runtime/services/__tests__/auto-analysis-guard.test.ts +1 -8
  1300. package/src/runtime/tool-grant-request-helper.ts +34 -30
  1301. package/src/runtime/trust-verdict-consumer.ts +4 -6
  1302. package/src/schedule/__tests__/worker-control.test.ts +3 -3
  1303. package/src/schedule/schedule-store.ts +0 -41
  1304. package/src/schedule/scheduler.ts +38 -257
  1305. package/src/schedule/worker-control.ts +11 -21
  1306. package/src/schedule/worker.ts +18 -5
  1307. package/src/security/__tests__/oauth2-device-code.test.ts +12 -60
  1308. package/src/security/__tests__/provider-key-env-fallback.test.ts +0 -10
  1309. package/src/security/auth-fallback-events-store.ts +34 -72
  1310. package/src/security/secret-scanner.ts +26 -1
  1311. package/src/sequence/engine.ts +15 -6
  1312. package/src/services/published-app-updater.ts +6 -11
  1313. package/src/stt/__tests__/daemon-batch-transcriber.test.ts +24 -4
  1314. package/src/stt/daemon-batch-transcriber.ts +33 -2
  1315. package/src/stt/stt-stream-session.ts +17 -11
  1316. package/src/stt/types.ts +2 -1
  1317. package/src/subagent/manager.ts +4 -3
  1318. package/src/telemetry/AGENTS.md +44 -0
  1319. package/src/telemetry/__tests__/config-setting-events-store.test.ts +63 -0
  1320. package/src/telemetry/__tests__/config-setting-snapshot.test.ts +107 -0
  1321. package/src/telemetry/__tests__/flush-checkpoints.test.ts +56 -0
  1322. package/src/telemetry/__tests__/outbox-test-harness.ts +83 -0
  1323. package/src/telemetry/__tests__/telemetry-event-fixtures.ts +258 -0
  1324. package/src/telemetry/__tests__/watchdog-events-store.test.ts +32 -122
  1325. package/src/telemetry/config-setting-events-store.ts +40 -0
  1326. package/src/telemetry/config-setting-snapshot.ts +135 -0
  1327. package/src/telemetry/flush-checkpoints.ts +70 -0
  1328. package/src/telemetry/skill-loaded-events-store.test.ts +54 -113
  1329. package/src/telemetry/skill-loaded-events-store.ts +21 -77
  1330. package/src/telemetry/telemetry-event-sources.test.ts +211 -0
  1331. package/src/telemetry/telemetry-event-sources.ts +582 -0
  1332. package/src/telemetry/telemetry-events-outbox.test.ts +330 -0
  1333. package/src/telemetry/telemetry-events-outbox.ts +204 -0
  1334. package/src/telemetry/telemetry-main-db.ts +48 -0
  1335. package/src/telemetry/telemetry-wire-source.json +4 -0
  1336. package/src/telemetry/telemetry-wire-validation.test.ts +157 -0
  1337. package/src/telemetry/telemetry-wire-validation.ts +125 -0
  1338. package/src/telemetry/telemetry-wire.generated.ts +433 -0
  1339. package/src/telemetry/tool-executed-events-store.test.ts +0 -8
  1340. package/src/telemetry/tool-executed-events-store.ts +2 -2
  1341. package/src/telemetry/turn-trace-store.test.ts +86 -7
  1342. package/src/telemetry/turn-trace-store.ts +29 -1
  1343. package/src/telemetry/types.test.ts +38 -0
  1344. package/src/telemetry/types.ts +302 -87
  1345. package/src/telemetry/usage-telemetry-reporter.test.ts +921 -364
  1346. package/src/telemetry/usage-telemetry-reporter.ts +235 -722
  1347. package/src/telemetry/watchdog-direct-emit.test.ts +0 -5
  1348. package/src/telemetry/watchdog-direct-emit.ts +7 -1
  1349. package/src/telemetry/watchdog-events-store.ts +11 -70
  1350. package/src/tools/acp/spawn.test.ts +0 -7
  1351. package/src/tools/apps/definitions.ts +1 -7
  1352. package/src/tools/apps/executors.ts +11 -36
  1353. package/src/tools/browser/__tests__/browser-execution-acquire.test.ts +11 -24
  1354. package/src/tools/browser/__tests__/browser-status.test.ts +47 -33
  1355. package/src/tools/browser/cdp-client/__tests__/cdp-inspect-client.test.ts +1 -11
  1356. package/src/tools/browser/cdp-client/__tests__/factory.test.ts +75 -58
  1357. package/src/tools/browser/cdp-client/__tests__/local-cdp-client.test.ts +0 -10
  1358. package/src/tools/browser/network-recorder.test.ts +0 -11
  1359. package/src/tools/document/document-comment-tool.test.ts +1 -7
  1360. package/src/tools/host-terminal/host-shell.test.ts +0 -32
  1361. package/src/tools/host-terminal/host-shell.ts +6 -2
  1362. package/src/tools/network/__tests__/web-fetch-firecrawl.test.ts +13 -15
  1363. package/src/tools/network/__tests__/web-search-metadata.test.ts +15 -18
  1364. package/src/tools/network/__tests__/web-search.test.ts +39 -50
  1365. package/src/tools/registry.ts +148 -196
  1366. package/src/tools/skills/find-similar-skills.test.ts +0 -7
  1367. package/src/tools/skills/sandbox-runner.ts +5 -2
  1368. package/src/tools/skills/scaffold-managed.ts +105 -12
  1369. package/src/tools/subagent/read.ts +6 -4
  1370. package/src/tools/subagent/spawn.ts +20 -29
  1371. package/src/tools/terminal/safe-env.ts +7 -2
  1372. package/src/tools/terminal/shell.test.ts +4 -8
  1373. package/src/tools/terminal/shell.ts +5 -2
  1374. package/src/tools/tool-approval-handler.ts +39 -23
  1375. package/src/tools/tool-input-summary.ts +2 -1
  1376. package/src/tools/tool-manifest.ts +20 -6
  1377. package/src/tools/tool-profiler.ts +37 -6
  1378. package/src/tools/ui-surface/definitions.ts +1 -7
  1379. package/src/tools/workflows/run-workflow.test.ts +8 -22
  1380. package/src/tts/__tests__/provider-adapters.test.ts +28 -22
  1381. package/src/tts/__tests__/provider-resolution.test.ts +26 -0
  1382. package/src/tts/__tests__/synthesis-stream.test.ts +6 -6
  1383. package/src/tts/__tests__/vellum-provider.test.ts +251 -0
  1384. package/src/tts/provider-catalog.ts +6 -0
  1385. package/src/tts/providers/__tests__/vellum-tts-socket.test.ts +266 -0
  1386. package/src/tts/providers/vellum-provider.ts +220 -0
  1387. package/src/tts/providers/vellum-tts-socket.ts +377 -0
  1388. package/src/tts/synthesize-text.ts +15 -5
  1389. package/src/tts/tts-config-resolver.ts +2 -7
  1390. package/src/tts/types.ts +1 -0
  1391. package/src/usage/attribution.ts +20 -114
  1392. package/src/util/abort-reasons.ts +1 -3
  1393. package/src/util/image-conversion.ts +23 -5
  1394. package/src/util/logger.ts +8 -3
  1395. package/src/{config/bundled-skills/messaging/tools/gmail-mime-helpers.ts → util/mime-type.ts} +4 -1
  1396. package/src/util/platform.ts +0 -60
  1397. package/src/util/pricing.ts +10 -1
  1398. package/src/util/telemetry-db-path.ts +9 -7
  1399. package/src/watcher/__tests__/telemetry.test.ts +41 -11
  1400. package/src/watcher/providers/google-calendar.ts +81 -49
  1401. package/src/watcher/telemetry.ts +22 -5
  1402. package/src/workflows/engine-integration.test.ts +0 -6
  1403. package/src/workflows/engine.test.ts +1 -9
  1404. package/src/workflows/journal-store.test.ts +1 -9
  1405. package/src/workflows/leaf-runner.test.ts +17 -50
  1406. package/src/workflows/library.test.ts +1 -9
  1407. package/src/workflows/run-manager.test.ts +1 -9
  1408. package/src/workspace/git-service.ts +932 -37
  1409. package/src/workspace/provider-commit-message-generator.ts +6 -4
  1410. package/__tests__/permissions/gateway-threshold-reader.test.ts +0 -450
  1411. package/src/__tests__/canonical-guardian-store.test.ts +0 -928
  1412. package/src/__tests__/guardian-principal-id-roundtrip.test.ts +0 -140
  1413. package/src/__tests__/plugin-route-contribution.test.ts +0 -245
  1414. package/src/__tests__/skill-runtime-path.test.ts +0 -63
  1415. package/src/__tests__/task-compiler.test.ts +0 -317
  1416. package/src/__tests__/task-management-tools.test.ts +0 -1128
  1417. package/src/__tests__/task-scheduler.test.ts +0 -786
  1418. package/src/config/schemas/schedules.ts +0 -35
  1419. package/src/contacts/canonical-guardian-store.ts +0 -902
  1420. package/src/daemon/message-types/work-items.ts +0 -243
  1421. package/src/runtime/routes/__tests__/memory-worker-routes.test.ts +0 -306
  1422. package/src/runtime/routes/__tests__/task-routes.test.ts +0 -644
  1423. package/src/runtime/routes/assets/vellum-design-system.css +0 -1959
  1424. package/src/runtime/routes/canonical-guardian-expiry-sweep.ts +0 -129
  1425. package/src/runtime/routes/inbound-stages/escalation-intercept.test.ts +0 -158
  1426. package/src/runtime/routes/inbound-stages/escalation-intercept.ts +0 -187
  1427. package/src/runtime/routes/memory-worker-routes.ts +0 -227
  1428. package/src/runtime/routes/task-routes.ts +0 -380
  1429. package/src/runtime/routes/work-items-routes.test.ts +0 -75
  1430. package/src/runtime/routes/work-items-routes.ts +0 -881
  1431. package/src/runtime/skill-route-registry.ts +0 -131
  1432. package/src/tasks/SPEC.md +0 -132
  1433. package/src/tasks/task-compiler.ts +0 -194
  1434. package/src/tasks/task-runner.ts +0 -119
  1435. package/src/tasks/task-scheduler.ts +0 -22
  1436. package/src/tasks/task-store.ts +0 -153
  1437. package/src/tasks/tool-sanitizer.ts +0 -38
  1438. package/src/tools/apps/registry.ts +0 -16
  1439. package/src/tools/system/register.ts +0 -9
  1440. package/src/tools/tasks/task-delete.ts +0 -121
  1441. package/src/tools/tasks/task-list.ts +0 -56
  1442. package/src/tools/tasks/task-run.ts +0 -122
  1443. package/src/tools/tasks/task-save.ts +0 -64
  1444. package/src/tools/tasks/work-item-enqueue.ts +0 -300
  1445. package/src/tools/tasks/work-item-list.ts +0 -65
  1446. package/src/tools/tasks/work-item-remove.ts +0 -94
  1447. package/src/tools/tasks/work-item-run.ts +0 -99
  1448. package/src/tools/tasks/work-item-update.ts +0 -166
  1449. package/src/tools/ui-surface/registry.ts +0 -14
  1450. package/src/work-items/resolve-required-tools.test.ts +0 -37
  1451. package/src/work-items/resolve-required-tools.ts +0 -26
  1452. package/src/work-items/work-item-runner.ts +0 -187
  1453. package/src/work-items/work-item-store.ts +0 -416
@@ -0,0 +1,1365 @@
1
+ /**
2
+ * Chat-persist credential redaction — sentinel markers with proven vault
3
+ * identity (LUM-2768).
4
+ *
5
+ * The legacy persist path replaces detected secrets with a dead
6
+ * `<redacted type="…" />` HTML marker that the chat markdown pipeline (no
7
+ * rehype-raw) can never render as an element, and that carries no way back
8
+ * to the vault entry it came from. This module implements the replacement
9
+ * behavior behind the `chat-credential-reveal` feature flag:
10
+ *
11
+ * 1. While a turn runs, every shell-style tool command is scanned for
12
+ * `credentials reveal` invocations and the referenced credentials
13
+ * (`--service`/`--field` flags or a positional UUID) are recorded as
14
+ * *candidates* ({@link collectRevealRefsFromCommand}).
15
+ * 2. At persist time, only those candidates' plaintexts are fetched from
16
+ * the credential store — a scoped read of secrets that were already in
17
+ * daemon memory this turn via the reveal stdout, never a vault scan
18
+ * ({@link resolveRevealCandidates}).
19
+ * 3. Each redacted span in persisted ASSISTANT TEXT is byte-compared
20
+ * against the candidate values (tool_result content keeps the legacy
21
+ * marker until the tool detail panel can render chips — see
22
+ * `buildToolResultBlocks`).
23
+ * An exact match *proves* the span is that credential, so the sentinel
24
+ * is enriched with `service:field` and the client can offer
25
+ * click-to-reveal. Anything else — hand-typed secrets, parse failures,
26
+ * value drift — degrades to a plain type-only sentinel that renders as
27
+ * a static badge ({@link redactSecretsForChat}).
28
+ *
29
+ * Failure direction is the design's spine: a wrong `service:field` would let
30
+ * the client reveal the *wrong* secret, so enrichment only ever happens on
31
+ * exact plaintext equality. Every uncertain path fails toward "badge",
32
+ * never toward "mislabeled chip".
33
+ *
34
+ * The legacy marker path (`redactSecrets`) is untouched — ingress blocking,
35
+ * tool-output scanning, and log redaction depend on its exact shape (see
36
+ * `security/AGENTS.md`). This module is additive and chat-persist-only.
37
+ */
38
+
39
+ import {
40
+ buildRedactedSentinel,
41
+ createRedactedSentinelRegex,
42
+ decodeRedactedSentinelMatch,
43
+ neutralizeRedactedSentinels,
44
+ REDACTED_SENTINEL_CLOSE,
45
+ REDACTED_SENTINEL_OPEN,
46
+ REDACTED_SENTINEL_TAG,
47
+ } from "@vellumai/service-contracts/redacted-credential";
48
+
49
+ import { revealedValuesSince } from "../runtime/reveal-success-registry.js";
50
+ import { credentialKey } from "../security/credential-key.js";
51
+ import {
52
+ redactSecrets,
53
+ redactSecretsWith,
54
+ scanText,
55
+ } from "../security/secret-scanner.js";
56
+ import { getSecureKeyAsync } from "../security/secure-keys.js";
57
+ import { getCredentialMetadataById } from "../tools/credentials/metadata-store.js";
58
+ import { getLogger } from "../util/logger.js";
59
+
60
+ const log = getLogger("chat-credential-redaction");
61
+
62
+ // ---------------------------------------------------------------------------
63
+ // Types
64
+ // ---------------------------------------------------------------------------
65
+
66
+ /** A credential reference parsed from a `credentials reveal` invocation. */
67
+ export interface RevealCandidateRef {
68
+ service?: string;
69
+ field?: string;
70
+ /** Opaque credential UUID (positional CLI arg) — resolved to service/field. */
71
+ id?: string;
72
+ /**
73
+ * The plaintext the reveal route served for this ref, captured by
74
+ * {@link filterRefsByRevealProof} at proof time. When present, candidate
75
+ * resolution redacts these EXACT bytes and skips the vault read — so a
76
+ * rotation/deletion between reveal and persist can't make the redacted
77
+ * value diverge from what the tool actually printed. Absent only for refs
78
+ * that were never proven (which resolution drops anyway).
79
+ */
80
+ provenValue?: string;
81
+ }
82
+
83
+ /** A candidate with its plaintext, ready for byte-comparison. */
84
+ export interface ResolvedRevealCandidate {
85
+ service: string;
86
+ field: string;
87
+ value: string;
88
+ }
89
+
90
+ // ---------------------------------------------------------------------------
91
+ // Command parsing
92
+ // ---------------------------------------------------------------------------
93
+
94
+ /** Matches `credentials reveal` with any CLI entry point (`assistant`, bin
95
+ * paths, …) — the two words are what identify the subcommand. */
96
+ const REVEAL_INVOCATION_RE = /\bcredentials\s+reveal\b/;
97
+
98
+ // An unquoted flag value ends at whitespace, a quote, or a shell
99
+ // metacharacter that can directly abut a word — `()`/backtick (command
100
+ // substitution) and `<>` (redirection) — so `--field token)` inside
101
+ // `$(… reveal --field token)` parses as `token`, the identity the route
102
+ // records proof under. Separators (`|&;`) never reach a segment unescaped;
103
+ // the splitter consumed them. A backslash escapes the next character.
104
+ const FLAG_VALUE = String.raw`(?:"((?:\\.|[^"\\])*)"|'([^']*)'|((?:\\.|[^\s"'\\()<>\`])+))`;
105
+ const SERVICE_FLAG_RE = new RegExp(String.raw`--service(?:=|\s+)${FLAG_VALUE}`);
106
+ const FIELD_FLAG_RE = new RegExp(String.raw`--field(?:=|\s+)${FLAG_VALUE}`);
107
+ const UUID_RE =
108
+ /\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\b/i;
109
+
110
+ /**
111
+ * Extract a flag's value from a segment, undoing the shell quoting the
112
+ * value arrived with. The shell strips quoting before the CLI sees argv,
113
+ * so the reveal route records proof under the UNESCAPED identity —
114
+ * `--service foo\ bar` must parse to `foo bar` or the staged ref can never
115
+ * match its proof and the reveal's output would bypass the guard and the
116
+ * candidate fallback. Single quotes are literal; a backslash escapes the
117
+ * next character when unquoted, and inside double quotes it escapes only
118
+ * the characters POSIX lets it escape that can appear in an identifier
119
+ * (`"` and `\` — the rest stay literal, matching the shell).
120
+ */
121
+ function flagValue(segment: string, re: RegExp): string | undefined {
122
+ const m = re.exec(segment);
123
+ if (!m) {
124
+ return undefined;
125
+ }
126
+ if (m[1] !== undefined) {
127
+ return m[1].replace(/\\(["\\])/g, "$1");
128
+ }
129
+ if (m[2] !== undefined) {
130
+ return m[2];
131
+ }
132
+ return m[3]?.replace(/\\(.)/g, "$1");
133
+ }
134
+
135
+ /**
136
+ * Split a shell command into segments at UNQUOTED separators (`&&`, `||`,
137
+ * `;`, `|`, a lone `&`, newline). A separator inside a quoted flag value —
138
+ * `--service 'R&D'` — is part of the value, and cutting there would orphan
139
+ * the invocation's flags across two broken segments: no ref gets staged,
140
+ * so the reveal's output could stream or persist raw even though the route
141
+ * served it. Minimal POSIX-ish quoting: single quotes are literal to the
142
+ * next single quote, double quotes honor backslash escapes, and a
143
+ * backslash outside quotes escapes the next character. An unterminated
144
+ * quote runs to the end of the string — the remainder stays one segment,
145
+ * which at worst stages nothing (the same safe direction as before).
146
+ * Quotes are kept in the output so the flag regexes see them.
147
+ */
148
+ function splitShellSegments(command: string): string[] {
149
+ const segments: string[] = [];
150
+ let current = "";
151
+ let quote: "'" | '"' | undefined;
152
+ let i = 0;
153
+ while (i < command.length) {
154
+ const ch = command[i]!;
155
+ if (quote === "'") {
156
+ current += ch;
157
+ if (ch === "'") {
158
+ quote = undefined;
159
+ }
160
+ i += 1;
161
+ continue;
162
+ }
163
+ if (quote === '"') {
164
+ if (ch === "\\" && i + 1 < command.length) {
165
+ current += ch + command[i + 1];
166
+ i += 2;
167
+ continue;
168
+ }
169
+ current += ch;
170
+ if (ch === '"') {
171
+ quote = undefined;
172
+ }
173
+ i += 1;
174
+ continue;
175
+ }
176
+ if (ch === "\\" && i + 1 < command.length) {
177
+ current += ch + command[i + 1];
178
+ i += 2;
179
+ continue;
180
+ }
181
+ if (ch === "'" || ch === '"') {
182
+ quote = ch;
183
+ current += ch;
184
+ i += 1;
185
+ continue;
186
+ }
187
+ if (ch === "&" || ch === "|") {
188
+ // `&&`/`||` consume both characters so the two-char operator is one
189
+ // boundary; a lone `&` (background job separator) or `|` splits too,
190
+ // so a second backgrounded/piped reveal is staged rather than
191
+ // swallowed into the first segment.
192
+ segments.push(current);
193
+ current = "";
194
+ i += command[i + 1] === ch ? 2 : 1;
195
+ continue;
196
+ }
197
+ if (ch === ";" || ch === "\n") {
198
+ segments.push(current);
199
+ current = "";
200
+ i += 1;
201
+ continue;
202
+ }
203
+ current += ch;
204
+ i += 1;
205
+ }
206
+ segments.push(current);
207
+ return segments;
208
+ }
209
+
210
+ /**
211
+ * Extract credential refs from a shell command that may contain one or more
212
+ * `credentials reveal` invocations (compound commands split on shell
213
+ * separators). Returns an empty array for commands that never touch reveal —
214
+ * the hot-path cost is one substring check.
215
+ *
216
+ * The parse is deliberately conservative: it recognizes the documented CLI
217
+ * shapes (`--service X --field Y`, `--service=X`, quoted values, positional
218
+ * UUID) and nothing else. An unparseable invocation yields no ref, which
219
+ * downstream means no candidate and a plain (non-revealable) sentinel —
220
+ * the safe direction.
221
+ */
222
+ /**
223
+ * Slice a segment at each `credentials reveal` occurrence: one slice per
224
+ * invocation, running from the invocation to the start of the next.
225
+ * Command substitution can nest several reveals in ONE segment —
226
+ * `echo "$(… reveal --service a …)" "$(… reveal --service b …)"` — and
227
+ * parsing only the segment's first flag pair would leave every later
228
+ * identity unstaged even though the route proves each one, so their
229
+ * opaque values could stream and persist raw.
230
+ */
231
+ function revealInvocationSlices(segment: string): string[] {
232
+ const re = new RegExp(REVEAL_INVOCATION_RE, "g");
233
+ const starts: number[] = [];
234
+ for (let m = re.exec(segment); m !== null; m = re.exec(segment)) {
235
+ starts.push(m.index);
236
+ }
237
+ return starts.map((start, i) => segment.slice(start, starts[i + 1]));
238
+ }
239
+
240
+ export function collectRevealRefsFromCommand(
241
+ command: string,
242
+ ): RevealCandidateRef[] {
243
+ if (!command.includes("reveal")) {
244
+ return [];
245
+ }
246
+ const refs: RevealCandidateRef[] = [];
247
+ // Split compound commands so flags from one invocation can't bleed into
248
+ // another (`reveal --service a --field b && reveal --service c --field d`),
249
+ // honoring quotes so a separator inside a flag value can't cut the
250
+ // invocation apart (see `splitShellSegments`). Within a segment, each
251
+ // reveal invocation is parsed from its own slice (see
252
+ // `revealInvocationSlices`).
253
+ const segments = splitShellSegments(command);
254
+ for (const segment of segments) {
255
+ if (!REVEAL_INVOCATION_RE.test(segment)) {
256
+ continue;
257
+ }
258
+ for (const invocation of revealInvocationSlices(segment)) {
259
+ const service = flagValue(invocation, SERVICE_FLAG_RE);
260
+ const field = flagValue(invocation, FIELD_FLAG_RE);
261
+ if (service !== undefined && field !== undefined) {
262
+ refs.push({ service, field });
263
+ continue;
264
+ }
265
+ const id = UUID_RE.exec(invocation)?.[0];
266
+ if (id) {
267
+ refs.push({ id });
268
+ }
269
+ // No parseable identity → no ref. The span still gets redacted at
270
+ // persist; it just won't be revealable.
271
+ }
272
+ }
273
+ return refs;
274
+ }
275
+
276
+ /**
277
+ * Resolve id-form refs to service/field EAGERLY, at staging time — a
278
+ * metadata-only lookup, no secret access. Proof runs when the tool result
279
+ * arrives, which can be well after the reveal executed, and a `credentials
280
+ * remove` in between (even in the same compound command) makes the id
281
+ * unresolvable then — dropping the ref although the route recorded the
282
+ * success, so the printed value would persist raw. The id stays on the ref
283
+ * so proof time can still fall back to a lookup for a ref whose metadata
284
+ * only appears later.
285
+ */
286
+ export function resolveRefIdentities(
287
+ refs: readonly RevealCandidateRef[],
288
+ ): RevealCandidateRef[] {
289
+ return refs.map((ref) => {
290
+ if (
291
+ ref.id === undefined ||
292
+ (ref.service !== undefined && ref.field !== undefined)
293
+ ) {
294
+ return ref;
295
+ }
296
+ try {
297
+ const meta = getCredentialMetadataById(ref.id);
298
+ if (!meta) {
299
+ return ref;
300
+ }
301
+ return { ...ref, service: meta.service, field: meta.field };
302
+ } catch (err) {
303
+ log.debug(
304
+ { err },
305
+ "eager reveal ref id lookup failed; deferring to proof time",
306
+ );
307
+ return ref;
308
+ }
309
+ });
310
+ }
311
+
312
+ /**
313
+ * Keep only refs whose identity the reveal route ACTUALLY served after
314
+ * `watermark` was captured (see `reveal-success-registry`). A shell tool's
315
+ * overall success is not proof the nested reveal ran — `reveal … || true`
316
+ * succeeds when the route failed, and an `echo` containing the command
317
+ * text never calls the route — so candidate resolution (which reads
318
+ * plaintext from the store) must be gated on the route's own record.
319
+ * Identity for id-form refs is normally captured at staging (see
320
+ * `resolveRefIdentities`); the metadata lookup here is only a fallback —
321
+ * metadata-only, no secret access. Unresolvable refs are dropped — the
322
+ * safe direction.
323
+ */
324
+ export function filterRefsByRevealProof(
325
+ refs: readonly RevealCandidateRef[],
326
+ watermark: number,
327
+ nonce: string,
328
+ ): RevealCandidateRef[] {
329
+ const proven: RevealCandidateRef[] = [];
330
+ const seenValues = new Set<string>();
331
+ for (const ref of refs) {
332
+ let service = ref.service;
333
+ let field = ref.field;
334
+ if (
335
+ ref.id !== undefined &&
336
+ (service === undefined || field === undefined)
337
+ ) {
338
+ try {
339
+ const meta = getCredentialMetadataById(ref.id);
340
+ if (!meta) {
341
+ continue;
342
+ }
343
+ service = meta.service;
344
+ field = meta.field;
345
+ } catch (err) {
346
+ log.debug({ err }, "reveal proof id lookup failed; dropping ref");
347
+ continue;
348
+ }
349
+ }
350
+ if (service === undefined || field === undefined) {
351
+ continue;
352
+ }
353
+ // Capture the plaintexts the route served alongside the proof, in the
354
+ // same lookup. Resolving service/field here too means each returned ref
355
+ // is fully self-describing, so candidate resolution needs neither the
356
+ // metadata lookup nor the vault read again. One ref PER DISTINCT VALUE:
357
+ // a rotate-and-re-reveal inside the window (reveal v1 → `credentials
358
+ // set` → reveal v2) prints two different plaintexts to stdout, and
359
+ // keeping only one would let the other persist raw whenever the scanner
360
+ // cannot classify it.
361
+ for (const provenValue of revealedValuesSince(
362
+ watermark,
363
+ service,
364
+ field,
365
+ nonce,
366
+ )) {
367
+ const dedupeKey = JSON.stringify([service, field, provenValue]);
368
+ if (seenValues.has(dedupeKey)) {
369
+ continue;
370
+ }
371
+ seenValues.add(dedupeKey);
372
+ proven.push({ service, field, provenValue });
373
+ }
374
+ }
375
+ return proven;
376
+ }
377
+
378
+ // ---------------------------------------------------------------------------
379
+ // Candidate resolution (scoped store read)
380
+ // ---------------------------------------------------------------------------
381
+
382
+ /**
383
+ * Minimum plaintext length for the exact-match candidate fallback. The
384
+ * fallback replaces EVERY occurrence of a candidate's bytes across the
385
+ * rest of the turn — assistant text, tool output, persisted rows — and
386
+ * the set route validates values only as non-empty, so a credential whose
387
+ * plaintext is `1` or `ok` would turn every unrelated occurrence of that
388
+ * token into a marker/chip. Below this floor the value carries too little
389
+ * entropy for redaction to protect anything, while the false-positive
390
+ * damage is guaranteed; at or above it, collisions with incidental text
391
+ * are rare. Scanner-recognized secrets are unaffected — every scanner
392
+ * pattern requires far longer tokens than this.
393
+ */
394
+ const MIN_CANDIDATE_VALUE_LENGTH = 6;
395
+
396
+ /**
397
+ * Append a candidate in EVERY stdout encoding of its plaintext, deduped on
398
+ * identity+value. `credentials reveal --json` writes the value through
399
+ * `JSON.stringify` (see the CLI's `writeOutput`), so a value containing
400
+ * quotes, backslashes, or control characters — a PEM key's newlines —
401
+ * appears in the tool's stdout ESCAPED: bytes a raw exact-match list can
402
+ * never find, leaving the escaped body to the scanner (which only knows
403
+ * the header). Registering the escaped encoding as its own candidate
404
+ * covers that representation on every surface (persist byte-compare, live
405
+ * guard, tool_result fallback, output chunks). `JSON.stringify` is
406
+ * injective on the value, so an escaped-form match proves the same
407
+ * identity the raw form does.
408
+ */
409
+ function appendCandidateEncodings(
410
+ candidates: ResolvedRevealCandidate[],
411
+ seen: Set<string>,
412
+ service: string,
413
+ field: string,
414
+ value: string,
415
+ ): void {
416
+ for (const encoded of [value, JSON.stringify(value).slice(1, -1)]) {
417
+ if (encoded.length < MIN_CANDIDATE_VALUE_LENGTH) {
418
+ continue;
419
+ }
420
+ const dedupeKey = JSON.stringify([service, field, encoded]);
421
+ if (seen.has(dedupeKey)) {
422
+ continue;
423
+ }
424
+ seen.add(dedupeKey);
425
+ candidates.push({ service, field, value: encoded });
426
+ }
427
+ }
428
+
429
+ /**
430
+ * Sync twin of {@link resolveRevealCandidates} for refs that already carry
431
+ * a proven value (the shape `filterRefsByRevealProof` returns) — used on
432
+ * the live tool-output path, whose handler must stay synchronous and may
433
+ * not touch the vault. Refs without a proven value are skipped, never
434
+ * vault-resolved.
435
+ */
436
+ export function resolveProvenRevealCandidates(
437
+ refs: readonly RevealCandidateRef[],
438
+ ): ResolvedRevealCandidate[] {
439
+ const seen = new Set<string>();
440
+ const candidates: ResolvedRevealCandidate[] = [];
441
+ for (const ref of refs) {
442
+ if (ref.service === undefined || ref.field === undefined) {
443
+ continue;
444
+ }
445
+ if (ref.provenValue === undefined || ref.provenValue.length === 0) {
446
+ continue;
447
+ }
448
+ appendCandidateEncodings(
449
+ candidates,
450
+ seen,
451
+ ref.service,
452
+ ref.field,
453
+ ref.provenValue,
454
+ );
455
+ }
456
+ return candidates;
457
+ }
458
+
459
+ /**
460
+ * Resolve parsed refs to `{service, field, value}` candidates via scoped
461
+ * credential-store reads — one `getSecureKeyAsync` per distinct ref, only
462
+ * for credentials a reveal command in this turn already named. Refs that
463
+ * fail to resolve (unknown id, missing secret, store unreachable) are
464
+ * dropped; the corresponding spans persist as plain sentinels.
465
+ */
466
+ export async function resolveRevealCandidates(
467
+ refs: readonly RevealCandidateRef[],
468
+ ): Promise<ResolvedRevealCandidate[]> {
469
+ // Candidates dedupe on identity AND value, not identity alone: the same
470
+ // credential legitimately yields two candidates when it was rotated and
471
+ // re-revealed within one turn — both plaintexts hit a tool's stdout, so
472
+ // both must stay redactable. Vault reads still dedupe per identity (one
473
+ // scoped read per credential, and the vault only has the current value).
474
+ const seenCandidates = new Set<string>();
475
+ const vaultReadKeys = new Set<string>();
476
+ const candidates: ResolvedRevealCandidate[] = [];
477
+ const pushCandidate = (service: string, field: string, value: string) =>
478
+ appendCandidateEncodings(candidates, seenCandidates, service, field, value);
479
+ for (const ref of refs) {
480
+ let service = ref.service;
481
+ let field = ref.field;
482
+ if (
483
+ ref.id !== undefined &&
484
+ (service === undefined || field === undefined)
485
+ ) {
486
+ try {
487
+ const meta = getCredentialMetadataById(ref.id);
488
+ if (!meta) {
489
+ continue;
490
+ }
491
+ service = meta.service;
492
+ field = meta.field;
493
+ } catch (err) {
494
+ log.debug({ err }, "reveal candidate id lookup failed; skipping");
495
+ continue;
496
+ }
497
+ }
498
+ if (service === undefined || field === undefined) {
499
+ continue;
500
+ }
501
+ // Prefer the plaintext the reveal route actually served (captured at proof
502
+ // time). Re-reading the vault here would return a rotated or deleted
503
+ // value, so the guard/fallback could miss the exact bytes the tool
504
+ // printed. Only fall back to a store read when a ref carries no proven
505
+ // value — which today means it was constructed outside the proof path.
506
+ if (ref.provenValue !== undefined && ref.provenValue.length > 0) {
507
+ pushCandidate(service, field, ref.provenValue);
508
+ continue;
509
+ }
510
+ const key = credentialKey(service, field);
511
+ if (vaultReadKeys.has(key)) {
512
+ continue;
513
+ }
514
+ vaultReadKeys.add(key);
515
+ try {
516
+ const value = await getSecureKeyAsync(key);
517
+ if (value != null && value.length > 0) {
518
+ pushCandidate(service, field, value);
519
+ }
520
+ } catch (err) {
521
+ // Store unreachable → no candidate → plain sentinel. Never block or
522
+ // fail the persist over enrichment.
523
+ log.debug(
524
+ { err, service, field },
525
+ "reveal candidate fetch failed; span will persist as plain sentinel",
526
+ );
527
+ }
528
+ }
529
+ return candidates;
530
+ }
531
+
532
+ // ---------------------------------------------------------------------------
533
+ // Daemon-minted sentinels and re-mint authority
534
+ // ---------------------------------------------------------------------------
535
+
536
+ /**
537
+ * A sentinel identity the guard may restore to its canonical daemon-minted
538
+ * form. Sources: route-recorded `--for-chat` mints (validated against the
539
+ * conversation's reveal nonce and staged identities by the agent loop
540
+ * before they reach the guard) and candidates proven by this
541
+ * conversation's own plain reveals (nonce-bound at the proof registry).
542
+ */
543
+ export interface SentinelRemintAuthority {
544
+ service: string;
545
+ field: string;
546
+ /** The canonical sentinel to restore for this identity. */
547
+ sentinel: string;
548
+ }
549
+
550
+ /**
551
+ * The canonical daemon-minted sentinel for a credential. Used by the
552
+ * `--for-chat` reveal channel (the route returns this instead of the
553
+ * plaintext) and by candidate-derived re-mint authorities, so every path
554
+ * that mints for the same identity produces the same bytes. The type label
555
+ * comes from the same anchored scanner classification persist-time
556
+ * enrichment uses ({@link classifyCandidateType}).
557
+ */
558
+ export function buildForChatSentinel(candidate: {
559
+ service: string;
560
+ field: string;
561
+ value: string;
562
+ }): string {
563
+ return buildRedactedSentinel({
564
+ type: classifyCandidateType(candidate.value),
565
+ service: candidate.service,
566
+ field: candidate.field,
567
+ });
568
+ }
569
+
570
+ /**
571
+ * Re-mint authorities derived from this turn's PROVEN reveal candidates.
572
+ *
573
+ * A model that legitimately revealed a credential this turn holds its
574
+ * plaintext and could echo it verbatim — which the guards would swap into
575
+ * a genuine enriched sentinel. When it instead retypes the sentinel it saw
576
+ * in its (redacted) history, byte-equality can't prove anything, but the
577
+ * identity can: a sentinel-shaped span claiming a `service:field` that was
578
+ * proven this turn grants nothing a plaintext echo wouldn't. Re-minting it
579
+ * (from the candidate's own metadata — never from the span's claimed
580
+ * label) turns the confusing neutralized-glyph outcome into the same chip
581
+ * the echo would have produced. Unproven identities still neutralize.
582
+ *
583
+ * Deduped by identity: encoding-variant candidates share a service:field
584
+ * and must not produce duplicate authorities. Conversation scoping is
585
+ * inherent — candidates live in per-run state that only this
586
+ * conversation's own proven reveals populate.
587
+ */
588
+ export function remintAuthoritiesFromCandidates(
589
+ candidates: readonly ResolvedRevealCandidate[],
590
+ ): SentinelRemintAuthority[] {
591
+ const byIdentity = new Map<string, SentinelRemintAuthority>();
592
+ for (const candidate of candidates) {
593
+ const key = credentialKey(candidate.service, candidate.field);
594
+ if (byIdentity.has(key)) {
595
+ continue;
596
+ }
597
+ byIdentity.set(key, {
598
+ service: candidate.service,
599
+ field: candidate.field,
600
+ sentinel: buildForChatSentinel(candidate),
601
+ });
602
+ }
603
+ return [...byIdentity.values()];
604
+ }
605
+
606
+ /**
607
+ * Sentinel guard for text that may legitimately contain daemon-minted
608
+ * sentinels. The forgery-guard spine assumes raw model output never
609
+ * carries a genuine sentinel — with two sanctioned, turn-scoped
610
+ * exceptions, both keyed by identity rather than trust in the span's own
611
+ * bytes:
612
+ *
613
+ * 1. `--for-chat` reveals: the daemon returned the sentinel as the reveal
614
+ * tool's result precisely so the model can copy it into its reply
615
+ * without ever touching the plaintext. Authority is the mint the
616
+ * reveal ROUTE recorded this turn ({@link ForChatMint}) — the route is
617
+ * the sole authority; a string parse of the requested command is not,
618
+ * because a segment that merely quotes a reveal invocation never
619
+ * executed one.
620
+ * 2. Proven plain-reveal candidates
621
+ * ({@link remintAuthoritiesFromCandidates}): the model provably held
622
+ * the plaintext this turn, so a span claiming that identity grants
623
+ * nothing a verbatim echo wouldn't.
624
+ *
625
+ * An enriched span whose decoded `service:field` identity matches an
626
+ * authority is RE-MINTED — replaced with the daemon's own canonical
627
+ * sentinel, so a tampered type label can't survive. Everything else —
628
+ * plain shapes, unknown identities, malformed escapes, partial triggers —
629
+ * flows through `transformRest` (neutralization by default; the persist
630
+ * path passes its neutralize+scan pipeline so a secret embedded inside a
631
+ * forged sentinel is still caught). Re-minted spans are inserted verbatim
632
+ * and never fed to `transformRest`: a minted sentinel is not
633
+ * scanner-inert — its service/field segments are arbitrary user strings
634
+ * that can look secret-shaped, and a scanner rewrite inside a fresh
635
+ * sentinel would corrupt it. With no authorities this is byte-identical
636
+ * to `transformRest(text)`.
637
+ */
638
+ export function guardForChatSentinels(
639
+ text: string,
640
+ mints: readonly SentinelRemintAuthority[],
641
+ transformRest: (segment: string) => string = neutralizeRedactedSentinels,
642
+ ): string {
643
+ if (mints.length === 0) {
644
+ return transformRest(text);
645
+ }
646
+ const re = createRedactedSentinelRegex();
647
+ let out = "";
648
+ let last = 0;
649
+ for (const m of text.matchAll(re)) {
650
+ const decoded = decodeRedactedSentinelMatch(m as RegExpExecArray);
651
+ const hit =
652
+ decoded.service !== undefined && decoded.field !== undefined
653
+ ? mints.find(
654
+ (c) => c.service === decoded.service && c.field === decoded.field,
655
+ )
656
+ : undefined;
657
+ // Between-match text can still hold partial/malformed triggers —
658
+ // transform it segment-wise. Splits are at full-sentinel boundaries,
659
+ // so no trigger ever spans a segment edge.
660
+ out += transformRest(text.slice(last, m.index));
661
+ out += hit ? hit.sentinel : transformRest(m[0]);
662
+ last = m.index + m[0].length;
663
+ }
664
+ out += transformRest(text.slice(last));
665
+ return out;
666
+ }
667
+
668
+ // ---------------------------------------------------------------------------
669
+ // Live-stream forgery guard
670
+ // ---------------------------------------------------------------------------
671
+
672
+ const SENTINEL_TRIGGER = `${REDACTED_SENTINEL_OPEN}${REDACTED_SENTINEL_TAG}:`;
673
+
674
+ /**
675
+ * A reveal candidate prepared for live-stream substitution: when the model
676
+ * echoes `value` into its reply, the stream guard swaps it for `replacement`
677
+ * (the same enriched sentinel persist-time redaction produces) before the
678
+ * bytes reach the wire — so the plaintext never flashes in the live
679
+ * transcript and never leaves the daemon.
680
+ */
681
+ export interface LiveRevealGuardEntry {
682
+ /** Credential plaintext exactly as it would appear in model output. */
683
+ value: string;
684
+ /** The sentinel `redactSecretsForChat` produces for those bytes. */
685
+ replacement: string;
686
+ }
687
+
688
+ /**
689
+ * Fallback type label for a candidate whose bare value the scanner cannot
690
+ * classify on its own (context-sensitive patterns like `password=…`).
691
+ */
692
+ const FALLBACK_SENTINEL_TYPE = "Credential";
693
+
694
+ /**
695
+ * Precompute live-swap entries from resolved reveal candidates. Every
696
+ * candidate gets an entry — the guard's job is to keep revealed plaintext
697
+ * off the wire, whatever the surrounding text turns out to be — and each
698
+ * replacement is exactly what persist-time redaction produces for the
699
+ * bare value, so the live stream and the persisted row agree
700
+ * byte-for-byte. `redactSecretsForChat` guarantees a candidate value
701
+ * never survives: the scanner classifies what it can, and the exact-match
702
+ * fallback (see `swapCandidateValueFallbacks`) covers the rest — opaque
703
+ * manual tokens and values the scanner only matches with surrounding
704
+ * context. The lone residual divergence: a contextual persist match
705
+ * (`password=<value>`) may carry a more specific type label than the
706
+ * bare-value chip streamed live — cosmetic, same service:field identity.
707
+ */
708
+ export function buildLiveRevealGuardEntries(
709
+ candidates: readonly ResolvedRevealCandidate[],
710
+ ): LiveRevealGuardEntry[] {
711
+ const entries: LiveRevealGuardEntry[] = [];
712
+ const seen = new Set<string>();
713
+ for (const candidate of candidates) {
714
+ // Dedupe by value — duplicate plaintexts would otherwise register two
715
+ // entries and split/join order would silently pick one.
716
+ if (seen.has(candidate.value)) {
717
+ continue;
718
+ }
719
+ seen.add(candidate.value);
720
+ // Derive the replacement against the FULL candidate list, exactly as
721
+ // the persist seam does: a value shared by two identities degrades to
722
+ // the plain sentinel there (see `uniqueCandidateForValue`), and the
723
+ // live swap must emit the same bytes or the streamed text and the
724
+ // persisted row would disagree on reconnect.
725
+ entries.push({
726
+ value: candidate.value,
727
+ replacement: redactSecretsForChat(candidate.value, candidates),
728
+ });
729
+ }
730
+ return entries;
731
+ }
732
+
733
+ /** A non-overlapping occurrence of a target in raw text. */
734
+ interface RawSpan {
735
+ start: number;
736
+ end: number;
737
+ /** Index into the caller's target list — lower = higher priority. */
738
+ priority: number;
739
+ }
740
+
741
+ /**
742
+ * Resolve the non-overlapping occurrence spans of `targets` in `text`:
743
+ * targets in priority order (list index — callers sort longest-first so a
744
+ * value that is a substring of another cannot pre-empt the larger match),
745
+ * each target's occurrences greedy left-to-right, skipping any occurrence
746
+ * that overlaps a higher-priority span. This is the single definition of
747
+ * the transforms' occurrence semantics: every swap in this module rebuilds
748
+ * its output from these spans, and the streaming stability analysis
749
+ * (`stableEmitLen`) resolves through the same function, so the two can
750
+ * never drift.
751
+ *
752
+ * `skipOccurrence` vetoes individual occurrences (the persist paths use it
753
+ * to let a longer scanner match outrank a candidate substring inside it —
754
+ * see `containedInLongerScannerMatch`); a vetoed occurrence is skipped
755
+ * exactly like an overlap loss, and scanning continues from the next byte.
756
+ */
757
+ function resolveRawSpans(
758
+ text: string,
759
+ targets: readonly string[],
760
+ skipOccurrence?: (start: number, end: number) => boolean,
761
+ ): RawSpan[] {
762
+ const spans: RawSpan[] = [];
763
+ targets.forEach((target, priority) => {
764
+ if (target.length === 0) {
765
+ return;
766
+ }
767
+ let scanFrom = 0;
768
+ for (;;) {
769
+ const idx = text.indexOf(target, scanFrom);
770
+ if (idx === -1) {
771
+ break;
772
+ }
773
+ const end = idx + target.length;
774
+ // An occurrence overlapping a higher-priority span loses to it — the
775
+ // higher-priority replacement consumes those bytes — but keep
776
+ // scanning from the next byte for a later disjoint occurrence.
777
+ if (
778
+ spans.some((s) => idx < s.end && end > s.start) ||
779
+ skipOccurrence?.(idx, end) === true
780
+ ) {
781
+ scanFrom = idx + 1;
782
+ continue;
783
+ }
784
+ spans.push({ start: idx, end, priority });
785
+ scanFrom = end;
786
+ }
787
+ });
788
+ return spans;
789
+ }
790
+
791
+ /**
792
+ * Replace each resolved raw span with its target's replacement, rebuilding
793
+ * the string in ONE pass over the original text. A sequential
794
+ * split/join-per-target pass would re-scan earlier replacements — and a
795
+ * replacement sentinel is not inert text: it embeds type/service/field
796
+ * segments, so a candidate whose plaintext happens to equal another's
797
+ * service name (e.g. `openai`) would match inside the just-emitted
798
+ * sentinel and corrupt it into nested, unrenderable markers. Resolving all
799
+ * spans against the raw text first makes replacements structurally
800
+ * invisible to later targets.
801
+ *
802
+ * `transformGap` is applied to the text BETWEEN spans (and to the whole
803
+ * text when nothing matches). Callers use it for forgery neutralization:
804
+ * it must run over raw bytes the spans do not claim, and never over the
805
+ * spans themselves — a candidate value may itself contain the sentinel
806
+ * trigger, and mutating it before the exact-match resolution would break
807
+ * the strongest guarantee this module makes.
808
+ */
809
+ function replaceRawSpans(
810
+ text: string,
811
+ targets: readonly string[],
812
+ replacementFor: (priority: number) => string,
813
+ transformGap: (segment: string) => string = (segment) => segment,
814
+ skipOccurrence?: (start: number, end: number) => boolean,
815
+ ): string {
816
+ const spans = resolveRawSpans(text, targets, skipOccurrence);
817
+ if (spans.length === 0) {
818
+ return transformGap(text);
819
+ }
820
+ spans.sort((a, b) => a.start - b.start);
821
+ let out = "";
822
+ let cursor = 0;
823
+ for (const span of spans) {
824
+ out +=
825
+ transformGap(text.slice(cursor, span.start)) +
826
+ replacementFor(span.priority);
827
+ cursor = span.end;
828
+ }
829
+ return out + transformGap(text.slice(cursor));
830
+ }
831
+
832
+ /**
833
+ * Replace every occurrence of each entry's plaintext with its sentinel.
834
+ * Longest value first: when one candidate's plaintext is a prefix (or
835
+ * substring) of another's, insertion order would let the shorter entry
836
+ * consume the head of the longer value — emitting the shorter chip plus
837
+ * the longer credential's unmatched suffix as raw text, while persist-time
838
+ * redaction (whose scanner prefers the longer span) redacts the whole
839
+ * value. Descending-length ordering keeps the live swap consistent with
840
+ * persistence, and the span-based rebuild (`replaceRawSpans`) guarantees a
841
+ * later entry can never rewrite inside an earlier entry's emitted
842
+ * sentinel.
843
+ */
844
+ export function swapLiveRevealValues(
845
+ text: string,
846
+ entries: readonly LiveRevealGuardEntry[],
847
+ ): string {
848
+ const ordered = [...entries].sort((a, b) => b.value.length - a.value.length);
849
+ return replaceRawSpans(
850
+ text,
851
+ ordered.map((entry) => entry.value),
852
+ (priority) => ordered[priority].replacement,
853
+ );
854
+ }
855
+
856
+ /**
857
+ * The live-emit transform: swap candidate values for their sentinels AND
858
+ * neutralize forged sentinels — resolved together on the RAW bytes, with
859
+ * neutralization applied only to the text between candidate spans.
860
+ * Sequencing them instead (`swapLiveRevealValues(neutralizeRedactedSentinels(text))`)
861
+ * breaks when a candidate's plaintext itself contains the sentinel trigger
862
+ * (manual values are arbitrary strings): neutralization would mutate the
863
+ * value's occurrence first, the exact-match swap would miss it, and an
864
+ * opaque secret would cross the wire almost intact. Candidate spans win;
865
+ * forgery neutralization covers everything they don't claim — exactly the
866
+ * occurrence semantics `stableEmitLen` models when it analyzes the trigger
867
+ * and the values as one priority-ordered target set.
868
+ */
869
+ export function neutralizeAndSwapLiveRevealValues(
870
+ text: string,
871
+ entries: readonly LiveRevealGuardEntry[],
872
+ forChatMints: readonly SentinelRemintAuthority[] = [],
873
+ ): string {
874
+ const ordered = [...entries].sort((a, b) => b.value.length - a.value.length);
875
+ return replaceRawSpans(
876
+ text,
877
+ ordered.map((entry) => entry.value),
878
+ (priority) => ordered[priority].replacement,
879
+ (segment) => guardForChatSentinels(segment, forChatMints),
880
+ );
881
+ }
882
+
883
+ /**
884
+ * Length of the prefix of `buffer` whose transform result is STABLE —
885
+ * i.e. cannot change no matter what bytes arrive next — under the
886
+ * transform's occurrence semantics: targets resolved in priority order
887
+ * (the caller passes them highest-priority first, matching
888
+ * `swapLiveRevealValues`' longest-value-first stable sort), each target's
889
+ * occurrences greedy left-to-right, non-overlapping with higher-priority
890
+ * spans. Everything past the returned offset must be held back.
891
+ *
892
+ * Two steps:
893
+ *
894
+ * 1. Resolve the consumed spans of the CURRENT buffer exactly as the
895
+ * swap would.
896
+ * 2. Find every "threat": a suffix of the buffer that is a proper prefix
897
+ * of some target `T` — bytes a future chunk could complete into a
898
+ * `T`-occurrence. Each threat pulls the stable boundary back:
899
+ * - threat starts OUTSIDE any consumed span → hold from the threat
900
+ * start (the classic split-value hold);
901
+ * - threat starts INSIDE a consumed span of occurrence `E`:
902
+ * - `priority(T)` higher than `priority(E)` → the future `T` match
903
+ * would beat `E` in the full-text swap, so `E` cannot be
904
+ * committed yet — hold from `E`'s START (emitting any part of
905
+ * `E` would leak raw bytes on one continuation or emit the wrong
906
+ * replacement on the other);
907
+ * - otherwise → `E` wins regardless of future bytes (greedy
908
+ * left-to-right for the same target, priority order across
909
+ * targets), so the threat is moot — `E` commits whole.
910
+ *
911
+ * Each case guards a real leak shape: a self-overlapping value chunked at
912
+ * the overlap boundary (`abcabc` as `abc`+`abc`: the own-tail threat is
913
+ * moot, the occurrence commits whole); a candidate whose tail is a prefix
914
+ * of a longer candidate (the threat outranks it, so it is held whole —
915
+ * emitting it would leak the longer value's suffix raw once it
916
+ * completes); equal-length overlapping entries (`aba`/`bab`: the
917
+ * earlier-sorted entry outranks, so a completed `bab` holds while `aba`
918
+ * may still complete — keeping chunked output identical to the unchunked
919
+ * swap).
920
+ *
921
+ * The boundary never splits a consumed span: it lands either outside
922
+ * all spans or exactly on a span's start.
923
+ */
924
+ function stableEmitLen(buffer: string, targets: readonly string[]): number {
925
+ // Same span resolution as the swap transforms — shared so the stability
926
+ // analysis can never disagree with what the transform actually consumes.
927
+ const consumed = resolveRawSpans(buffer, targets);
928
+ let emitLen = buffer.length;
929
+ targets.forEach((target, priority) => {
930
+ const maxLen = Math.min(target.length - 1, buffer.length);
931
+ for (let len = maxLen; len > 0; len--) {
932
+ if (!buffer.endsWith(target.slice(0, len))) {
933
+ continue;
934
+ }
935
+ const start = buffer.length - len;
936
+ const host = consumed.find((s) => s.start <= start && start < s.end);
937
+ if (host === undefined) {
938
+ if (start < emitLen) {
939
+ emitLen = start;
940
+ }
941
+ } else if (priority < host.priority && host.start < emitLen) {
942
+ emitLen = host.start;
943
+ }
944
+ }
945
+ });
946
+ return emitLen;
947
+ }
948
+
949
+ /**
950
+ * Streaming redaction guard for live `assistant_text_delta` emission
951
+ * (mirrors `drainDirectiveDisplayBuffer`'s hold-back pattern). Two jobs:
952
+ *
953
+ * 1. **Forgery neutralization** — genuine sentinels are created by the
954
+ * daemon, so a sentinel-shaped string the model streams neutralizes
955
+ * unless a re-mint authority for its identity exists this turn
956
+ * (`--for-chat` mints and proven reveal candidates — see
957
+ * `guardForChatSentinels`), in which case it is restored to the
958
+ * daemon's canonical mint.
959
+ * 2. **Live reveal swap** — a complete occurrence of a reveal candidate's
960
+ * plaintext is replaced with its enriched sentinel (`entries`), so the
961
+ * secret never flashes in the live stream: the client renders the chip
962
+ * immediately and the persisted row (which redacts the same bytes at
963
+ * persist time) matches what was streamed.
964
+ *
965
+ * A trailing PARTIAL trigger or candidate-value prefix (split across
966
+ * streaming chunks) is held back in `bufferedRemainder` so the next chunk
967
+ * decides whether it completes; when that partial prefix could outrank a
968
+ * completed occurrence it starts inside, the whole occurrence is held with
969
+ * it (see `stableEmitLen`), so the remainder is bounded by roughly twice
970
+ * the longest target. Callers must re-prepend the remainder to
971
+ * the next chunk and, at end-of-message, flush it through
972
+ * `neutralizeAndSwapLiveRevealValues` — nothing can complete a partial
973
+ * prefix after the message ends.
974
+ *
975
+ * `consumedRaw` is the untransformed input the emit covers. Callers that
976
+ * mirror streamed text for partial persistence must mirror `consumedRaw`,
977
+ * not `emitText`: persist-time redaction re-derives the sentinel from the
978
+ * raw bytes, whereas a mirrored already-swapped sentinel would be
979
+ * indistinguishable from a forgery and get neutralized.
980
+ */
981
+ /**
982
+ * Upper bound on how much tail the guard will hold back waiting for an
983
+ * in-flight daemon-minted sentinel to close. A genuine sentinel is far
984
+ * shorter (type + encoded service/field); a trigger that runs this long
985
+ * without closing is forged garbage and is released (neutralized) rather
986
+ * than buffered forever.
987
+ */
988
+ const UNCLOSED_SENTINEL_HOLD_CAP = 512;
989
+
990
+ export function drainSentinelGuardedText(
991
+ buffer: string,
992
+ entries: readonly LiveRevealGuardEntry[] = [],
993
+ forChatMints: readonly SentinelRemintAuthority[] = [],
994
+ ): {
995
+ emitText: string;
996
+ consumedRaw: string;
997
+ bufferedRemainder: string;
998
+ } {
999
+ // Priority order must mirror the transforms exactly:
1000
+ // `swapLiveRevealValues` sorts entries longest-value-first (stable),
1001
+ // and the trigger joins the same ordering so its complete occurrences
1002
+ // (consumed by neutralization) participate in the stability analysis.
1003
+ const targets = [
1004
+ SENTINEL_TRIGGER,
1005
+ ...entries.map((entry) => entry.value),
1006
+ ].sort((a, b) => b.length - a.length);
1007
+ let emitLen = stableEmitLen(buffer, targets);
1008
+ // With re-mint authorities in play, a COMPLETE trigger in the emit
1009
+ // region may be the head of a genuine daemon-minted sentinel still
1010
+ // streaming in. The plain guard neutralizes complete triggers eagerly
1011
+ // (any live sentinel is forged by definition); here that would mangle
1012
+ // the sanctioned case — so hold from the first trigger whose sentinel
1013
+ // has not fully landed inside the emit region (bounded by the cap
1014
+ // above), letting `guardForChatSentinels` decide re-mint vs neutralize
1015
+ // on the whole span once it closes. Triggers consumed inside a
1016
+ // candidate-value span are skipped: the span swaps wholesale and its
1017
+ // bytes never reach the gap transform.
1018
+ if (forChatMints.length > 0) {
1019
+ const spans = resolveRawSpans(buffer, targets);
1020
+ let searchFrom = 0;
1021
+ for (;;) {
1022
+ const t = buffer.indexOf(SENTINEL_TRIGGER, searchFrom);
1023
+ if (t === -1) {
1024
+ break;
1025
+ }
1026
+ const host = spans.find((s) => s.start <= t && t < s.end);
1027
+ if (host !== undefined && targets[host.priority] !== SENTINEL_TRIGGER) {
1028
+ searchFrom = t + SENTINEL_TRIGGER.length;
1029
+ continue;
1030
+ }
1031
+ const close = buffer.indexOf(REDACTED_SENTINEL_CLOSE, t);
1032
+ const sentinelEnd = close === -1 ? -1 : close + 1;
1033
+ if (close !== -1 && sentinelEnd <= emitLen) {
1034
+ searchFrom = sentinelEnd;
1035
+ continue;
1036
+ }
1037
+ if (buffer.length - t <= UNCLOSED_SENTINEL_HOLD_CAP) {
1038
+ emitLen = Math.min(emitLen, t);
1039
+ }
1040
+ break;
1041
+ }
1042
+ }
1043
+ const consumedRaw = buffer.slice(0, emitLen);
1044
+ return {
1045
+ emitText: neutralizeAndSwapLiveRevealValues(
1046
+ consumedRaw,
1047
+ entries,
1048
+ forChatMints,
1049
+ ),
1050
+ consumedRaw,
1051
+ bufferedRemainder: buffer.slice(emitLen),
1052
+ };
1053
+ }
1054
+
1055
+ /**
1056
+ * Streaming redaction guard for live `tool_output_chunk` emission — the
1057
+ * tool-output twin of {@link drainSentinelGuardedText}. A foreground
1058
+ * reveal's stdout streams to the client WHILE the tool runs, before the
1059
+ * tool_result seam redacts the final content, so each chunk must be
1060
+ * redacted on the way out or the plaintext shows in the tool drawer until
1061
+ * the redacted result replaces it. Complete occurrences of candidate
1062
+ * values become the legacy marker (this surface renders no chips, and the
1063
+ * final tool_result row uses the same treatment, so live and persisted
1064
+ * bytes agree); a trailing partial occurrence is held back in
1065
+ * `bufferedRemainder` for the tool's next chunk. The tool_result seam
1066
+ * DISCARDS whatever remains: a held partial is raw credential prefix
1067
+ * bytes that complete-value redaction cannot mask, and the redacted final
1068
+ * result supersedes the streamed view immediately.
1069
+ */
1070
+ export function drainCandidateGuardedChunk(
1071
+ buffer: string,
1072
+ candidates: readonly ResolvedRevealCandidate[],
1073
+ ): { emitText: string; bufferedRemainder: string } {
1074
+ const seen = new Set<string>();
1075
+ const values: string[] = [];
1076
+ for (const candidate of candidates) {
1077
+ if (candidate.value.length === 0 || seen.has(candidate.value)) {
1078
+ continue;
1079
+ }
1080
+ seen.add(candidate.value);
1081
+ values.push(candidate.value);
1082
+ }
1083
+ values.sort((a, b) => b.length - a.length);
1084
+ const emitLen = stableEmitLen(buffer, values);
1085
+ return {
1086
+ emitText: redactCandidateValuesLegacy(buffer.slice(0, emitLen), candidates),
1087
+ bufferedRemainder: buffer.slice(emitLen),
1088
+ };
1089
+ }
1090
+
1091
+ // ---------------------------------------------------------------------------
1092
+ // Sentinel redaction
1093
+ // ---------------------------------------------------------------------------
1094
+
1095
+ /**
1096
+ * Redact secrets in chat-persisted text using sentinels instead of the
1097
+ * legacy HTML marker. A span whose bytes exactly equal a candidate's
1098
+ * plaintext gets the enriched (revealable) shape — but only when that value
1099
+ * maps to exactly one vault identity; everything else gets the plain
1100
+ * type-only shape. Detection itself is unchanged — same scanner, same
1101
+ * patterns, same overlap handling as `redactSecrets`.
1102
+ */
1103
+ export function redactSecretsForChat(
1104
+ text: string,
1105
+ candidates: readonly ResolvedRevealCandidate[],
1106
+ forChatMints: readonly SentinelRemintAuthority[] = [],
1107
+ ): string {
1108
+ // Candidate spans resolve FIRST, on the raw bytes; forgery
1109
+ // neutralization and the scanner then run ONLY over the text between
1110
+ // them (the gap transform below). Raw bytes first because a manual
1111
+ // candidate value may itself contain the sentinel trigger — mutating it
1112
+ // up front would break the exact-match pass, the strongest guarantee
1113
+ // here. Before the scanner because the scanner may only recognize a
1114
+ // SUBSTRING of a proven plaintext (the `Private Key` pattern consumes
1115
+ // just the `-----BEGIN … -----` header, leaving the key body), and once
1116
+ // it has replaced that substring the full candidate value is no longer
1117
+ // present for an exact-match pass to find — the body would
1118
+ // stream/persist raw. Gaps only because a minted sentinel is not
1119
+ // scanner-inert: its service/field segments are arbitrary user strings
1120
+ // that can themselves look secret-shaped, and a scanner match inside a
1121
+ // fresh sentinel would corrupt it into a nested, unrenderable marker.
1122
+ //
1123
+ // Within each gap the scanner still redacts secrets the reveal registry
1124
+ // never proved (defense in depth against any secret the model emitted
1125
+ // that the vault did not serve), with the precise type label for values
1126
+ // it classifies inline. Sentinel-shaped spans in the gaps neutralize —
1127
+ // except identities a re-mint authority vouches for this turn, which are
1128
+ // restored to the daemon's canonical mint and kept out of the scanner's
1129
+ // reach (see `guardForChatSentinels`); everything around them still runs
1130
+ // the full neutralize+scan pipeline, so a secret embedded inside a
1131
+ // forged sentinel cannot ride through unredacted.
1132
+ const neutralizeAndScan = (segment: string): string =>
1133
+ redactSecretsWith(
1134
+ neutralizeRedactedSentinels(segment),
1135
+ (match, rawValue) => {
1136
+ const hit = uniqueCandidateForValue(candidates, rawValue);
1137
+ return buildRedactedSentinel(
1138
+ hit
1139
+ ? { type: match.type, service: hit.service, field: hit.field }
1140
+ : { type: match.type },
1141
+ );
1142
+ },
1143
+ );
1144
+ const redactGap = (segment: string): string =>
1145
+ guardForChatSentinels(segment, forChatMints, neutralizeAndScan);
1146
+ return protectCandidateSpans(text, candidates, redactGap);
1147
+ }
1148
+
1149
+ /**
1150
+ * Replace every occurrence of a proven candidate plaintext with its sentinel,
1151
+ * applying `transformGap` (the caller's neutralize+scan pipeline) only to the
1152
+ * text between the spans. Candidate spans are resolved on the RAW bytes (a
1153
+ * manual value may itself contain the sentinel trigger, and neutralizing
1154
+ * first would break the exact match), and protection precedes the scanner so
1155
+ * a value the scanner would only partially match (e.g. a PEM key, where only
1156
+ * the header is recognized) is still redacted as a whole — its body can
1157
+ * never survive into the streamed/persisted text. Minted sentinels are never
1158
+ * fed back through `transformGap`: their service/field segments are
1159
+ * arbitrary user strings that can look secret-shaped, and a scanner rewrite
1160
+ * inside a fresh sentinel would corrupt it.
1161
+ *
1162
+ * Longest value first (mirroring `swapLiveRevealValues`) so a value that is a
1163
+ * substring of another does not pre-empt the larger match; the span-based
1164
+ * rebuild (`replaceRawSpans`) keeps one value's sentinel safe from another
1165
+ * value that happens to appear inside it. Each value is
1166
+ * classified by scanning it in isolation: when a single scanner match spans
1167
+ * the ENTIRE value the precise type label is preserved (a recognizable key
1168
+ * keeps "Anthropic API Key" rather than degrading to the generic type);
1169
+ * otherwise the generic `Credential` type is used. Identity enrichment still
1170
+ * requires a unique vault match, exactly as the scanner path.
1171
+ */
1172
+ function protectCandidateSpans(
1173
+ text: string,
1174
+ candidates: readonly ResolvedRevealCandidate[],
1175
+ transformGap: (segment: string) => string,
1176
+ ): string {
1177
+ if (candidates.length === 0) {
1178
+ return transformGap(text);
1179
+ }
1180
+ const seen = new Set<string>();
1181
+ const values: string[] = [];
1182
+ for (const candidate of candidates) {
1183
+ if (candidate.value.length === 0 || seen.has(candidate.value)) {
1184
+ continue;
1185
+ }
1186
+ seen.add(candidate.value);
1187
+ values.push(candidate.value);
1188
+ }
1189
+ values.sort((a, b) => b.length - a.length);
1190
+ const sentinelCache = new Map<string, string>();
1191
+ const sentinelForValue = (value: string): string => {
1192
+ let sentinel = sentinelCache.get(value);
1193
+ if (sentinel === undefined) {
1194
+ const hit = uniqueCandidateForValue(candidates, value);
1195
+ sentinel = buildRedactedSentinel(
1196
+ hit
1197
+ ? {
1198
+ type: classifyCandidateType(value),
1199
+ service: hit.service,
1200
+ field: hit.field,
1201
+ }
1202
+ : { type: classifyCandidateType(value) },
1203
+ );
1204
+ sentinelCache.set(value, sentinel);
1205
+ }
1206
+ return sentinel;
1207
+ };
1208
+ return replaceRawSpans(
1209
+ text,
1210
+ values,
1211
+ (priority) => sentinelForValue(values[priority]),
1212
+ transformGap,
1213
+ containedInLongerScannerMatch(text),
1214
+ );
1215
+ }
1216
+
1217
+ /**
1218
+ * Occurrence veto that lets a LONGER scanner match outrank a candidate
1219
+ * substring inside it. A proven value can be a strict substring of a
1220
+ * DIFFERENT scanner-detectable secret — revealing a manual value `sk-pro`
1221
+ * must not carve the prefix out of a full `sk-proj-…` key in the same
1222
+ * text and leave the key's suffix raw. Vetoing the occurrence keeps the
1223
+ * enclosing secret contiguous in a gap, where the scanner pass redacts it
1224
+ * whole. The reverse containment keeps the candidate: a scanner match
1225
+ * INSIDE a candidate value (a PEM key, where only the header is
1226
+ * recognized) must not break whole-value protection, and an equal-length
1227
+ * match is the candidate itself — the enriched sentinel wins.
1228
+ */
1229
+ function containedInLongerScannerMatch(
1230
+ text: string,
1231
+ ): (start: number, end: number) => boolean {
1232
+ const matches = scanText(text);
1233
+ if (matches.length === 0) {
1234
+ return () => false;
1235
+ }
1236
+ return (start, end) =>
1237
+ matches.some(
1238
+ (m) =>
1239
+ m.startIndex <= start &&
1240
+ end <= m.endIndex &&
1241
+ m.endIndex - m.startIndex > end - start,
1242
+ );
1243
+ }
1244
+
1245
+ /**
1246
+ * The scanner type label for a candidate value, or the generic `Credential`
1247
+ * type when the scanner does not recognize it. The value is scanned in
1248
+ * isolation (so no neighbouring match bleeds in) and a match ANCHORED AT THE
1249
+ * START is used — a value that begins with a recognized signature is that
1250
+ * type even when the pattern consumes only a prefix (the `Private Key`
1251
+ * pattern matches just the `-----BEGIN … -----` header, yet the value is
1252
+ * unambiguously a private key). The longest such anchored match wins.
1253
+ * Matches that merely appear mid-value are ignored: they could be a
1254
+ * coincidental substring, and mislabelling a chip is worse than the generic
1255
+ * type. Identity enrichment is still gated on a unique vault match upstream,
1256
+ * so the label never drives which secret a chip can reveal.
1257
+ */
1258
+ function classifyCandidateType(value: string): string {
1259
+ let best: { type: string; endIndex: number } | undefined;
1260
+ for (const m of scanText(value)) {
1261
+ if (m.startIndex !== 0) {
1262
+ continue;
1263
+ }
1264
+ if (best === undefined || m.endIndex > best.endIndex) {
1265
+ best = { type: m.type, endIndex: m.endIndex };
1266
+ }
1267
+ }
1268
+ return best?.type ?? FALLBACK_SENTINEL_TYPE;
1269
+ }
1270
+
1271
+ /**
1272
+ * Legacy-marker twin of the persist fallback, for surfaces that keep the
1273
+ * `<redacted type/>` marker instead of sentinels (persisted tool_result
1274
+ * blocks — see `buildToolResultBlocks`). A `credentials reveal` whose
1275
+ * stdout is an opaque/manual value with no scanner-recognizable shape
1276
+ * would otherwise persist raw in the tool-result row and surface in the
1277
+ * tool detail panel and history, even though every other surface redacts
1278
+ * it. Exact occurrences of proven candidate plaintexts (deduped, longest
1279
+ * value first) become the generic legacy marker; no identity is carried —
1280
+ * these surfaces render no chips.
1281
+ */
1282
+ export function redactCandidateValuesLegacy(
1283
+ text: string,
1284
+ candidates: readonly ResolvedRevealCandidate[],
1285
+ ): string {
1286
+ // Protect every proven candidate value as an atomic legacy marker
1287
+ // (neutralizing forged sentinels in the text between the spans), THEN run
1288
+ // the scanner over what remains. Candidate spans resolve on the RAW
1289
+ // bytes: a manual value may itself contain the sentinel trigger, and
1290
+ // neutralizing the whole text first would mutate the occurrence and
1291
+ // break the exact match. Candidate protection must precede the scanner:
1292
+ // a value the scanner only partially recognizes (a PEM key, where the
1293
+ // `Private Key` pattern consumes just the header) would otherwise have
1294
+ // its recognized substring replaced first, destroying the full-value
1295
+ // match so the key body persists raw. The inserted `<redacted … />`
1296
+ // markers contain no secret-shaped bytes, so the scanner pass can
1297
+ // neither re-match nor corrupt them.
1298
+ const protectedText = protectCandidateValuesLegacy(text, candidates);
1299
+ return redactSecrets(protectedText);
1300
+ }
1301
+
1302
+ /**
1303
+ * Replace every occurrence of a proven candidate plaintext with the generic
1304
+ * legacy marker BEFORE the scanner runs (longest value first, mirroring
1305
+ * `swapLiveRevealValues`). Twin of {@link protectCandidateSpans} for surfaces
1306
+ * that keep the `<redacted type="…" />` marker instead of sentinels; no
1307
+ * identity is carried — these surfaces render no chips, so the generic type
1308
+ * is always used.
1309
+ */
1310
+ function protectCandidateValuesLegacy(
1311
+ text: string,
1312
+ candidates: readonly ResolvedRevealCandidate[],
1313
+ ): string {
1314
+ if (candidates.length === 0) {
1315
+ return neutralizeRedactedSentinels(text);
1316
+ }
1317
+ const seen = new Set<string>();
1318
+ const values: string[] = [];
1319
+ for (const candidate of candidates) {
1320
+ if (candidate.value.length === 0 || seen.has(candidate.value)) {
1321
+ continue;
1322
+ }
1323
+ seen.add(candidate.value);
1324
+ values.push(candidate.value);
1325
+ }
1326
+ values.sort((a, b) => b.length - a.length);
1327
+ return replaceRawSpans(
1328
+ text,
1329
+ values,
1330
+ () => `<redacted type="${FALLBACK_SENTINEL_TYPE}" />`,
1331
+ neutralizeRedactedSentinels,
1332
+ containedInLongerScannerMatch(text),
1333
+ );
1334
+ }
1335
+
1336
+ /**
1337
+ * Resolve a matched plaintext to a candidate identity — but only when the
1338
+ * mapping is unambiguous. A byte match proves the VALUE, not the vault
1339
+ * identity: if two revealed credentials happen to share the same plaintext
1340
+ * (the same API key stored under two services), picking either would mint a
1341
+ * chip whose label — and whose click-to-reveal path — names a credential the
1342
+ * user may not have revealed, and whose value silently diverges if that
1343
+ * credential later rotates. Duplicate-value spans therefore degrade to the
1344
+ * plain type-only sentinel. Multiple candidate entries that agree on
1345
+ * service:field (the same reveal parsed twice) are still unique.
1346
+ */
1347
+ function uniqueCandidateForValue(
1348
+ candidates: readonly ResolvedRevealCandidate[],
1349
+ rawValue: string,
1350
+ ): ResolvedRevealCandidate | undefined {
1351
+ let hit: ResolvedRevealCandidate | undefined;
1352
+ for (const candidate of candidates) {
1353
+ if (candidate.value !== rawValue) {
1354
+ continue;
1355
+ }
1356
+ if (
1357
+ hit !== undefined &&
1358
+ (hit.service !== candidate.service || hit.field !== candidate.field)
1359
+ ) {
1360
+ return undefined;
1361
+ }
1362
+ hit = candidate;
1363
+ }
1364
+ return hit;
1365
+ }