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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (456) hide show
  1. package/ARCHITECTURE.md +1 -1
  2. package/Dockerfile +8 -0
  3. package/docs/activation-funnel-telemetry.md +13 -7
  4. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/package.json +1 -0
  5. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/__tests__/redacted-credential.test.ts +200 -0
  6. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/redacted-credential.ts +226 -0
  7. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/package.json +1 -0
  8. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/redacted-credential.test.ts +200 -0
  9. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/redacted-credential.ts +226 -0
  10. package/node_modules/@vellumai/service-contracts/package.json +1 -0
  11. package/node_modules/@vellumai/service-contracts/src/__tests__/redacted-credential.test.ts +200 -0
  12. package/node_modules/@vellumai/service-contracts/src/redacted-credential.ts +226 -0
  13. package/openapi.yaml +530 -107
  14. package/package.json +1 -1
  15. package/scripts/generate-openapi.ts +8 -0
  16. package/src/__tests__/activation-early-marking.test.ts +6 -5
  17. package/src/__tests__/agent-loop-override-profile.test.ts +22 -25
  18. package/src/__tests__/agent-wake-override-profile.test.ts +21 -48
  19. package/src/__tests__/app-builder-tool-scripts.test.ts +0 -1
  20. package/src/__tests__/app-bundler.test.ts +4 -12
  21. package/src/__tests__/app-executors.test.ts +2 -49
  22. package/src/__tests__/app-routes-csp.test.ts +114 -146
  23. package/src/__tests__/auth-fallback-events-store.test.ts +8 -1
  24. package/src/__tests__/build-persisted-content.test.ts +96 -0
  25. package/src/__tests__/bundle-scanner.test.ts +27 -1
  26. package/src/__tests__/call-controller.test.ts +291 -0
  27. package/src/__tests__/call-site-routing-connection-auto-resolve.test.ts +165 -0
  28. package/src/__tests__/chat-credential-redaction.test.ts +1395 -0
  29. package/src/__tests__/chat-reveal-guard-priming.test.ts +791 -0
  30. package/src/__tests__/compaction.benchmark.test.ts +2 -1
  31. package/src/__tests__/compactor-image-manifest-trust.test.ts +50 -0
  32. package/src/__tests__/config-loader-backfill.test.ts +9 -4
  33. package/src/__tests__/config-schema-cmd.test.ts +10 -11
  34. package/src/__tests__/config-schema.test.ts +190 -257
  35. package/src/__tests__/conversation-agent-loop-fatal-cleanup.test.ts +208 -0
  36. package/src/__tests__/conversation-agent-loop-overflow.test.ts +5 -19
  37. package/src/__tests__/conversation-agent-loop.test.ts +22 -9
  38. package/src/__tests__/conversation-error.test.ts +31 -0
  39. package/src/__tests__/conversation-load-history-repair.test.ts +110 -0
  40. package/src/__tests__/conversation-process-callsite.test.ts +12 -19
  41. package/src/__tests__/conversation-routes-slash-commands.test.ts +7 -21
  42. package/src/__tests__/conversation-summarize-route.test.ts +36 -44
  43. package/src/__tests__/conversation-surfaces-action-delivery.test.ts +1 -0
  44. package/src/__tests__/conversation-surfaces-activation-emit.test.ts +7 -7
  45. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +0 -1
  46. package/src/__tests__/conversation-tool-setup-attribution.test.ts +0 -1
  47. package/src/__tests__/conversation-usage.test.ts +4 -12
  48. package/src/__tests__/credential-routes.test.ts +250 -0
  49. package/src/__tests__/credential-security-invariants.test.ts +3 -0
  50. package/src/__tests__/db-migration-rollback.test.ts +22 -0
  51. package/src/__tests__/empty-response-hook.test.ts +186 -1
  52. package/src/__tests__/external-plugin-loader.test.ts +11 -5
  53. package/src/__tests__/heartbeat-service.test.ts +0 -28
  54. package/src/__tests__/host-shell-tool.test.ts +2 -0
  55. package/src/__tests__/inactive-tool-error-messages.test.ts +2 -2
  56. package/src/__tests__/inference-no-mode-boot-e2e.test.ts +52 -8
  57. package/src/__tests__/internal-telemetry-routes.test.ts +23 -4
  58. package/src/__tests__/invite-routes-http.test.ts +12 -16
  59. package/src/__tests__/list-all-apps.test.ts +0 -4
  60. package/src/__tests__/llm-context-resolution.test.ts +32 -56
  61. package/src/__tests__/llm-request-log-turn-query.test.ts +109 -0
  62. package/src/__tests__/llm-resolver-override-or-default.test.ts +3 -52
  63. package/src/__tests__/llm-resolver.test.ts +342 -602
  64. package/src/__tests__/llm-schema.test.ts +79 -37
  65. package/src/__tests__/max-tokens-continue-hook.test.ts +19 -0
  66. package/src/__tests__/media-stream-output.test.ts +259 -3
  67. package/src/__tests__/media-stream-server-integration.test.ts +22 -1
  68. package/src/__tests__/media-stream-stt-session.test.ts +47 -0
  69. package/src/__tests__/memory-jobs-worker-cleanup-cadence.test.ts +33 -0
  70. package/src/__tests__/memory-recall-log-store.test.ts +47 -13
  71. package/src/__tests__/mock-gateway-ipc.ts +46 -1
  72. package/src/__tests__/mtime-cache.test.ts +61 -0
  73. package/src/__tests__/navigate-settings-tab.test.ts +2 -0
  74. package/src/__tests__/normalize-onboarding.test.ts +33 -0
  75. package/src/__tests__/onboarding-persona-write.test.ts +26 -0
  76. package/src/__tests__/plugin-api-resolve-credential.test.ts +140 -0
  77. package/src/__tests__/plugin-app-serve-routes.test.ts +166 -14
  78. package/src/__tests__/plugin-import-boundary-reverse-guard.test.ts +2 -0
  79. package/src/__tests__/post-turn-tool-result-truncation.test.ts +38 -0
  80. package/src/__tests__/provider-commit-message-generator.test.ts +27 -20
  81. package/src/__tests__/provider-connections-backfill.test.ts +138 -0
  82. package/src/__tests__/provider-platform-proxy-integration.test.ts +10 -31
  83. package/src/__tests__/provider-registry-ollama.test.ts +8 -18
  84. package/src/__tests__/provider-send-message-override-profile.test.ts +23 -23
  85. package/src/__tests__/provider-usage-tracking.test.ts +9 -19
  86. package/src/__tests__/prune-old-conversations-job.test.ts +12 -0
  87. package/src/__tests__/published-app-updater.test.ts +22 -16
  88. package/src/__tests__/registry.test.ts +5 -16
  89. package/src/__tests__/retry-openrouter-only-normalization.test.ts +22 -23
  90. package/src/__tests__/retry-thinking-adaptive-only.test.ts +43 -48
  91. package/src/__tests__/retry-thinking-tool-choice.test.ts +57 -66
  92. package/src/__tests__/retry-verbosity-normalization.test.ts +24 -23
  93. package/src/__tests__/reveal-success-registry.test.ts +123 -0
  94. package/src/__tests__/run-conversation-turn-persistence.test.ts +130 -0
  95. package/src/__tests__/secret-fixtures.ts +9 -0
  96. package/src/__tests__/server-history-render.test.ts +28 -0
  97. package/src/__tests__/skills.test.ts +9 -4
  98. package/src/__tests__/slack-share-routes.test.ts +0 -1
  99. package/src/__tests__/stt-stream-session.test.ts +6 -5
  100. package/src/__tests__/subagent-call-site-routing.test.ts +69 -95
  101. package/src/__tests__/subagent-disposal.test.ts +2 -0
  102. package/src/__tests__/subagent-fork-notifications.test.ts +2 -0
  103. package/src/__tests__/subagent-fork-spawn.test.ts +2 -0
  104. package/src/__tests__/subagent-manager-notify.test.ts +2 -0
  105. package/src/__tests__/subagent-role-registry.test.ts +37 -0
  106. package/src/__tests__/subagent-spawn-and-await.test.ts +1 -0
  107. package/src/__tests__/subagent-terminal-message.test.ts +50 -0
  108. package/src/__tests__/subagent-tool-gate-mode.test.ts +78 -5
  109. package/src/__tests__/surface-completion-nudge-hook.test.ts +19 -0
  110. package/src/__tests__/telemetry-routes.test.ts +99 -19
  111. package/src/__tests__/tool-audit.test.ts +34 -4
  112. package/src/__tests__/tool-executor-lifecycle-events.test.ts +1 -1
  113. package/src/__tests__/tool-profiler.test.ts +72 -1
  114. package/src/__tests__/tool-result-spool.test.ts +49 -4
  115. package/src/__tests__/tool-side-effects-slack-dm.test.ts +0 -1
  116. package/src/__tests__/ui-channel-variants.test.ts +108 -0
  117. package/src/__tests__/ui-shape-teaching.test.ts +255 -0
  118. package/src/__tests__/usage-attribution.test.ts +18 -41
  119. package/src/__tests__/user-plugin-loader.test.ts +4 -4
  120. package/src/__tests__/voice-config-update.test.ts +46 -0
  121. package/src/__tests__/voice-session-bridge.test.ts +216 -84
  122. package/src/__tests__/workspace-migration-131-drop-web-fetch-mode.test.ts +120 -0
  123. package/src/agent/loop.ts +4 -3
  124. package/src/api/events/open-conversation.test.ts +64 -0
  125. package/src/api/events/open-conversation.ts +33 -0
  126. package/src/api/index.ts +6 -0
  127. package/src/api/responses/conversation-message.ts +5 -0
  128. package/src/apps/app-store.ts +25 -35
  129. package/src/bundler/app-bundler.ts +34 -48
  130. package/src/bundler/app-compiler.ts +39 -4
  131. package/src/bundler/bundle-scanner.ts +13 -0
  132. package/src/bundler/manifest.ts +1 -1
  133. package/src/calls/__tests__/voice-session-bridge.test.ts +47 -0
  134. package/src/calls/call-constants.ts +5 -0
  135. package/src/calls/call-controller.ts +100 -32
  136. package/src/calls/call-transport.ts +9 -0
  137. package/src/calls/media-stream-output.ts +107 -4
  138. package/src/calls/media-stream-server.ts +29 -9
  139. package/src/calls/media-stream-stt-session.ts +7 -1
  140. package/src/calls/media-turn-detector.ts +11 -1
  141. package/src/calls/voice-session-bridge.ts +84 -63
  142. package/src/cli/commands/__tests__/inference-providers.test.ts +270 -33
  143. package/src/cli/commands/__tests__/notifications.test.ts +24 -3
  144. package/src/cli/commands/config.help.ts +6 -6
  145. package/src/cli/commands/credentials.help.ts +13 -0
  146. package/src/cli/commands/credentials.ts +6 -1
  147. package/src/cli/commands/email.help.ts +7 -0
  148. package/src/cli/commands/email.ts +35 -1
  149. package/src/cli/commands/inference-providers.ts +168 -107
  150. package/src/cli/commands/inference.help.ts +118 -46
  151. package/src/cli/commands/memory/index.help.ts +23 -0
  152. package/src/cli/commands/memory/nodes.ts +146 -0
  153. package/src/cli/commands/notifications.help.ts +10 -10
  154. package/src/cli/commands/oauth/connect-surface-guidance.test.ts +40 -0
  155. package/src/cli/commands/oauth/connect-surface-guidance.ts +54 -0
  156. package/src/cli/commands/oauth/connect.test.ts +126 -0
  157. package/src/cli/commands/oauth/connect.ts +29 -6
  158. package/src/cli/commands/oauth/index.help.ts +7 -1
  159. package/src/cli/commands/oauth/status.test.ts +69 -3
  160. package/src/cli/commands/oauth/status.ts +50 -12
  161. package/src/cli/commands/plugins.help.ts +13 -2
  162. package/src/cli/commands/plugins.ts +61 -7
  163. package/src/cli/commands/telemetry.help.ts +13 -0
  164. package/src/cli/commands/telemetry.ts +45 -5
  165. package/src/cli/lib/__tests__/plugin-catalog-local.test.ts +8 -2
  166. package/src/cli/lib/__tests__/upgrade-plugin.test.ts +213 -9
  167. package/src/cli/lib/bundled-marketplace.json +93 -36
  168. package/src/cli/lib/inspect-plugin.ts +5 -1
  169. package/src/cli/lib/install-from-github.ts +68 -4
  170. package/src/cli/lib/plugin-catalog-local.ts +6 -2
  171. package/src/cli/lib/plugin-fingerprint.ts +3 -3
  172. package/src/cli/lib/upgrade-plugin.ts +236 -35
  173. package/src/config/__tests__/default-profile-catalog.test.ts +8 -12
  174. package/src/config/__tests__/plugin-resident-skill-discovery.test.ts +137 -0
  175. package/src/config/__tests__/profile-materialization.test.ts +1 -88
  176. package/src/config/bundled-skills/AGENTS.md +3 -30
  177. package/src/config/bundled-skills/app-builder/SKILL.md +5 -3
  178. package/src/config/bundled-skills/app-builder/TOOLS.json +23 -0
  179. package/src/config/bundled-skills/app-builder/tools/app-open.ts +32 -0
  180. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +1 -1
  181. package/src/config/bundled-skills/phone-calls/references/CONFIG.md +7 -7
  182. package/src/config/bundled-skills/schedule/SKILL.md +1 -1
  183. package/src/config/bundled-skills/schedule/TOOLS.json +1 -1
  184. package/src/config/bundled-skills/settings/TOOLS.json +3 -1
  185. package/src/config/bundled-skills/settings/tools/navigate-settings-tab.ts +2 -0
  186. package/src/config/bundled-skills/settings/tools/voice-config-update.ts +18 -10
  187. package/src/config/bundled-skills/subagent/SKILL.md +2 -0
  188. package/src/config/bundled-skills/subagent/TOOLS.json +1 -1
  189. package/src/config/call-site-defaults.ts +3 -3
  190. package/src/config/feature-flag-registry.json +24 -39
  191. package/src/config/llm-resolver.ts +88 -475
  192. package/src/config/profile-materialization.ts +11 -11
  193. package/src/config/schema.ts +93 -127
  194. package/src/config/schemas/__tests__/live-voice.test.ts +24 -6
  195. package/src/config/schemas/__tests__/stt.test.ts +31 -3
  196. package/src/config/schemas/live-voice.ts +10 -4
  197. package/src/config/schemas/llm.ts +52 -15
  198. package/src/config/schemas/memory-lifecycle.ts +1 -1
  199. package/src/config/schemas/memory-retrospective.ts +8 -0
  200. package/src/config/schemas/memory-v2.ts +2 -2
  201. package/src/config/schemas/memory-v3.ts +1 -1
  202. package/src/config/schemas/services.ts +6 -3
  203. package/src/config/schemas/stt.ts +38 -21
  204. package/src/config/schemas/tts.ts +13 -18
  205. package/src/config/skills.ts +31 -21
  206. package/src/context/compactor.ts +44 -7
  207. package/src/context/post-turn-tool-result-truncation.ts +4 -2
  208. package/src/context/tool-result-spool.ts +26 -5
  209. package/src/conversations/__tests__/message-consolidation.test.ts +48 -0
  210. package/src/conversations/message-consolidation.ts +22 -2
  211. package/src/daemon/__tests__/conversation-tool-setup.test.ts +5 -12
  212. package/src/daemon/app-source-watcher.ts +17 -23
  213. package/src/daemon/chat-credential-redaction.ts +1365 -0
  214. package/src/daemon/conversation-agent-loop-handlers.ts +627 -34
  215. package/src/daemon/conversation-agent-loop.ts +71 -31
  216. package/src/daemon/conversation-error.ts +36 -14
  217. package/src/daemon/conversation-process.ts +22 -0
  218. package/src/daemon/conversation-store.ts +35 -0
  219. package/src/daemon/conversation-surfaces.ts +25 -25
  220. package/src/daemon/conversation-tool-setup.ts +33 -7
  221. package/src/daemon/conversation.ts +55 -5
  222. package/src/daemon/handlers/shared.ts +33 -2
  223. package/src/daemon/lifecycle.ts +4 -4
  224. package/src/daemon/message-types/conversations.ts +6 -17
  225. package/src/daemon/providers-setup.ts +8 -0
  226. package/src/daemon/tool-setup-types.ts +7 -1
  227. package/src/daemon/wake-conversation-ops.ts +10 -1
  228. package/src/hooks/types.ts +9 -0
  229. package/src/ipc/__tests__/email-ipc.test.ts +90 -0
  230. package/src/ipc/gateway-client.test.ts +59 -0
  231. package/src/ipc/gateway-client.ts +70 -27
  232. package/src/live-voice/__tests__/live-voice-events.test.ts +14 -2
  233. package/src/live-voice/__tests__/live-voice-integration.test.ts +116 -2
  234. package/src/live-voice/__tests__/live-voice-vad.test.ts +804 -13
  235. package/src/live-voice/__tests__/protocol.test.ts +122 -0
  236. package/src/live-voice/live-voice-session.ts +443 -40
  237. package/src/live-voice/protocol.ts +143 -1
  238. package/src/monitoring/__tests__/plugin-source-watch.test.ts +3 -1
  239. package/src/monitoring/plugin-source-watch.ts +3 -62
  240. package/src/notifications/README.md +1 -1
  241. package/src/permissions/checker.ts +8 -4
  242. package/src/persistence/__tests__/db-init-migrations-ok.test.ts +26 -0
  243. package/src/persistence/conversation-crud.ts +104 -2
  244. package/src/persistence/db-init.ts +14 -3
  245. package/src/persistence/job-handlers/cleanup.ts +15 -7
  246. package/src/persistence/llm-request-log-store.ts +88 -52
  247. package/src/persistence/migrations/298-move-memory-jobs-to-memory-db.ts +7 -31
  248. package/src/persistence/migrations/305-drop-contact-acl-columns.ts +3 -2
  249. package/src/persistence/migrations/326-move-injection-events-to-memory-db.ts +8 -34
  250. package/src/persistence/migrations/336-move-memory-v2-activation-logs-to-memory-db.ts +90 -0
  251. package/src/persistence/migrations/337-move-memory-recall-logs-to-memory-db.ts +114 -0
  252. package/src/persistence/migrations/338-move-memory-v3-selections-to-memory-db.ts +84 -0
  253. package/src/persistence/migrations/339-move-activation-sessions-to-memory-db.ts +52 -0
  254. package/src/persistence/migrations/__tests__/run-migrations.test.ts +155 -0
  255. package/src/persistence/migrations/helpers/relocation.ts +44 -1
  256. package/src/persistence/migrations/run-migrations.ts +25 -1
  257. package/src/persistence/schema/infrastructure.ts +9 -0
  258. package/src/persistence/schema/memory-core.ts +3 -0
  259. package/src/persistence/schema/memory-injection.ts +2 -0
  260. package/src/persistence/steps.ts +36 -0
  261. package/src/platform/client.test.ts +1 -44
  262. package/src/platform/client.ts +10 -20
  263. package/src/platform/consent-cache.test.ts +89 -23
  264. package/src/platform/consent-cache.ts +71 -33
  265. package/src/plugin-api/constants.ts +12 -0
  266. package/src/plugin-api/conversation-turn.ts +37 -14
  267. package/src/plugin-api/index.ts +11 -1
  268. package/src/plugin-api/resolve-credential.ts +75 -0
  269. package/src/plugin-api/vision-support.test.ts +8 -19
  270. package/src/plugin-api/vision-support.ts +26 -17
  271. package/src/plugins/collect-source-versions.ts +77 -0
  272. package/src/plugins/defaults/compaction/compact.ts +6 -0
  273. package/src/plugins/defaults/compaction/window-manager.ts +9 -0
  274. package/src/plugins/defaults/empty-response/hooks/post-model-call.ts +18 -24
  275. package/src/plugins/defaults/empty-response/hooks/user-prompt-submit.ts +38 -0
  276. package/src/plugins/defaults/empty-response/refusal-quarantine.ts +99 -0
  277. package/src/plugins/defaults/image-fallback/__tests__/caption-cache-persistence.test.ts +7 -3
  278. package/src/plugins/defaults/index.ts +8 -1
  279. package/src/plugins/defaults/max-tokens-continue/hooks/post-model-call.ts +4 -1
  280. package/src/plugins/defaults/memory/__tests__/activation-session-store.test.ts +48 -6
  281. package/src/plugins/defaults/memory/__tests__/db-memory-attach.test.ts +28 -22
  282. package/src/plugins/defaults/memory/__tests__/memory-log-stores-degraded.test.ts +148 -0
  283. package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +80 -8
  284. package/src/plugins/defaults/memory/__tests__/memory-retrospective-prompt.test.ts +202 -0
  285. package/src/plugins/defaults/memory/__tests__/memory-v2-activation-log-store.test.ts +64 -25
  286. package/src/plugins/defaults/memory/__tests__/memory-v2-concept-frequency.test.ts +24 -11
  287. package/src/plugins/defaults/memory/__tests__/prompt-override.test.ts +70 -6
  288. package/src/plugins/defaults/memory/__tests__/table-relocation.test.ts +278 -0
  289. package/src/plugins/defaults/memory/activation-session-store.ts +27 -20
  290. package/src/plugins/defaults/memory/context-search/sources/memory-v2.ts +2 -9
  291. package/src/plugins/defaults/memory/context-search/sources/workspace.ts +1 -8
  292. package/src/plugins/defaults/memory/graph/retriever.test.ts +6 -6
  293. package/src/plugins/defaults/memory/graph/store.ts +114 -0
  294. package/src/plugins/defaults/memory/graph/tool-handlers.ts +36 -1
  295. package/src/plugins/defaults/memory/graph/tools.ts +47 -13
  296. package/src/plugins/defaults/memory/graph-topology/build-memory-graph.ts +16 -35
  297. package/src/plugins/defaults/memory/jobs-worker.ts +10 -1
  298. package/src/plugins/defaults/memory/memory-db.ts +3 -2
  299. package/src/plugins/defaults/memory/memory-recall-log-store.ts +129 -66
  300. package/src/plugins/defaults/memory/memory-retrospective-constants.ts +8 -0
  301. package/src/plugins/defaults/memory/memory-retrospective-job.ts +11 -122
  302. package/src/plugins/defaults/memory/memory-retrospective-prompt.ts +216 -0
  303. package/src/plugins/defaults/memory/memory-v2-activation-log-store.ts +84 -46
  304. package/src/plugins/defaults/memory/memory-v2-concept-frequency.ts +42 -32
  305. package/src/plugins/defaults/memory/path-containment.ts +21 -0
  306. package/src/plugins/defaults/memory/prompt-override.ts +51 -13
  307. package/src/plugins/defaults/memory/tools.test.ts +34 -0
  308. package/src/plugins/defaults/memory/tools.ts +12 -2
  309. package/src/plugins/defaults/memory/v2/__tests__/harness-compare.test.ts +19 -15
  310. package/src/plugins/defaults/memory/v2/__tests__/harness-oracle.test.ts +24 -19
  311. package/src/plugins/defaults/memory/v2/__tests__/harness-replay-input.test.ts +19 -15
  312. package/src/plugins/defaults/memory/v2/__tests__/injection.test.ts +22 -2
  313. package/src/plugins/defaults/memory/v2/__tests__/prompts-consolidation.test.ts +5 -3
  314. package/src/plugins/defaults/memory/v2/harness/oracle.ts +59 -41
  315. package/src/plugins/defaults/memory/v2/harness/replay-input.ts +29 -25
  316. package/src/plugins/defaults/memory/v2/migration.ts +46 -16
  317. package/src/plugins/defaults/memory/v2/prompts/consolidation.ts +4 -0
  318. package/src/plugins/defaults/memory/v3/__tests__/carry-integration.test.ts +17 -15
  319. package/src/plugins/defaults/memory/v3/__tests__/gate.test.ts +24 -22
  320. package/src/plugins/defaults/memory/v3/__tests__/injection.test.ts +9 -5
  321. package/src/plugins/defaults/memory/v3/__tests__/orchestrate.test.ts +277 -10
  322. package/src/plugins/defaults/memory/v3/__tests__/selection-log-store.test.ts +57 -5
  323. package/src/plugins/defaults/memory/v3/__tests__/shadow-integration.test.ts +9 -7
  324. package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +38 -7
  325. package/src/plugins/defaults/memory/v3/hot-set.test.ts +36 -18
  326. package/src/plugins/defaults/memory/v3/hot-set.ts +12 -14
  327. package/src/plugins/defaults/memory/v3/learned-edges.test.ts +47 -27
  328. package/src/plugins/defaults/memory/v3/learned-edges.ts +12 -14
  329. package/src/plugins/defaults/memory/v3/orchestrate.ts +139 -22
  330. package/src/plugins/defaults/memory/v3/prune.test.ts +10 -3
  331. package/src/plugins/defaults/memory/v3/prune.ts +17 -11
  332. package/src/plugins/defaults/memory/v3/selection-log-store.ts +23 -11
  333. package/src/plugins/defaults/memory/v3/shadow-plugin.ts +70 -53
  334. package/src/plugins/defaults/surface-completion-nudge/hooks/post-model-call.ts +9 -6
  335. package/src/plugins/external-plugin-loader.ts +15 -15
  336. package/src/plugins/mtime-cache.ts +47 -0
  337. package/src/plugins/pipeline.ts +9 -1
  338. package/src/plugins/plugin-execution-context.ts +44 -0
  339. package/src/plugins/plugin-tree-walk.ts +31 -24
  340. package/src/plugins/source-fingerprint.ts +3 -4
  341. package/src/prompts/normalize-onboarding.ts +12 -0
  342. package/src/prompts/persona-resolver.ts +8 -0
  343. package/src/prompts/templates/BOOTSTRAP-ACTIVATION-RAIL.md +1 -1
  344. package/src/providers/__tests__/dispatch-connection-routing.test.ts +6 -9
  345. package/src/providers/__tests__/registry-native-web-search.test.ts +4 -10
  346. package/src/providers/__tests__/retry-callsite.test.ts +235 -215
  347. package/src/providers/__tests__/satellite-connection-routing.test.ts +8 -16
  348. package/src/providers/atlascloud/client.ts +10 -49
  349. package/src/providers/baseten/client.ts +43 -0
  350. package/src/providers/call-site-routing.ts +26 -13
  351. package/src/providers/connection-resolution.ts +9 -11
  352. package/src/providers/fetch-provider-catalog.ts +4 -2
  353. package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +29 -4
  354. package/src/providers/inference/__tests__/connection-availability-keyless.test.ts +78 -0
  355. package/src/providers/inference/adapter-factory.ts +27 -6
  356. package/src/providers/inference/auth.ts +29 -1
  357. package/src/providers/inference/backfill.ts +61 -10
  358. package/src/providers/inference/connection-availability.ts +3 -2
  359. package/src/providers/inference/resolve-auth.ts +9 -1
  360. package/src/providers/model-catalog.ts +37 -0
  361. package/src/providers/openai/__tests__/api-error-normalization.test.ts +24 -2
  362. package/src/providers/openai/api-key-validation.ts +70 -0
  363. package/src/providers/retry.ts +2 -0
  364. package/src/providers/types.ts +11 -4
  365. package/src/providers/vellum-model-routing.test.ts +26 -0
  366. package/src/providers/vellum-model-routing.ts +30 -0
  367. package/src/providers/voice-error-copy.ts +47 -0
  368. package/src/runtime/agent-wake.ts +40 -24
  369. package/src/runtime/for-chat-mint-registry.ts +118 -0
  370. package/src/runtime/reveal-nonce.ts +49 -0
  371. package/src/runtime/reveal-success-registry.ts +306 -0
  372. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +88 -3
  373. package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +161 -30
  374. package/src/runtime/routes/__tests__/migration-vellum-metadata-reconcile.test.ts +7 -0
  375. package/src/runtime/routes/__tests__/schedule-worker-routes.test.ts +15 -0
  376. package/src/runtime/routes/app-management-routes.ts +152 -45
  377. package/src/runtime/routes/app-routes.ts +16 -77
  378. package/src/runtime/routes/canned-message-complete.ts +16 -16
  379. package/src/runtime/routes/conversation-management-routes.ts +17 -14
  380. package/src/runtime/routes/conversation-query-routes.ts +54 -6
  381. package/src/runtime/routes/conversation-routes.ts +48 -2
  382. package/src/runtime/routes/credential-routes.ts +116 -3
  383. package/src/runtime/routes/email-routes.ts +17 -1
  384. package/src/runtime/routes/inbound-stages/transcribe-audio.test.ts +33 -5
  385. package/src/runtime/routes/inbound-stages/transcribe-audio.ts +6 -5
  386. package/src/runtime/routes/inference-provider-connection-routes.ts +77 -28
  387. package/src/runtime/routes/inference-send-routes.ts +1 -1
  388. package/src/runtime/routes/internal-telemetry-routes.ts +7 -6
  389. package/src/runtime/routes/plugins-routes.ts +37 -6
  390. package/src/runtime/routes/publish-routes.ts +15 -18
  391. package/src/runtime/routes/schedule-worker-routes.ts +9 -0
  392. package/src/runtime/routes/secret-routes.ts +10 -0
  393. package/src/runtime/routes/telemetry-routes.ts +149 -45
  394. package/src/schedule/__tests__/schedule-timezone.test.ts +101 -0
  395. package/src/schedule/__tests__/worker-watchdog.test.ts +209 -0
  396. package/src/schedule/schedule-store.ts +12 -1
  397. package/src/schedule/schedule-timezone.ts +63 -0
  398. package/src/schedule/scheduler.ts +100 -6
  399. package/src/schedule/worker-control.ts +19 -0
  400. package/src/security/auth-fallback-events-store.ts +7 -6
  401. package/src/security/secret-scanner.ts +26 -1
  402. package/src/services/published-app-updater.ts +6 -11
  403. package/src/stt/stt-stream-session.ts +36 -16
  404. package/src/subagent/manager.ts +34 -8
  405. package/src/telemetry/AGENTS.md +30 -1
  406. package/src/telemetry/__tests__/config-setting-snapshot.test.ts +18 -0
  407. package/src/telemetry/__tests__/outbox-test-harness.ts +5 -3
  408. package/src/telemetry/config-setting-snapshot.ts +44 -10
  409. package/src/telemetry/telemetry-event-sources.test.ts +124 -30
  410. package/src/telemetry/telemetry-event-sources.ts +137 -83
  411. package/src/telemetry/telemetry-events-outbox.test.ts +46 -1
  412. package/src/telemetry/telemetry-events-outbox.ts +60 -9
  413. package/src/telemetry/telemetry-wire-source.json +1 -1
  414. package/src/telemetry/telemetry-wire-validation.ts +39 -2
  415. package/src/telemetry/telemetry-wire.generated.ts +8 -0
  416. package/src/telemetry/tool-audit.ts +15 -9
  417. package/src/telemetry/tool-executed-events-store.test.ts +1 -1
  418. package/src/telemetry/turn-events-store.ts +20 -0
  419. package/src/telemetry/types.ts +45 -12
  420. package/src/telemetry/usage-telemetry-reporter.test.ts +294 -25
  421. package/src/telemetry/usage-telemetry-reporter.ts +117 -18
  422. package/src/telemetry/watchdog-direct-emit.test.ts +11 -3
  423. package/src/telemetry/watchdog-direct-emit.ts +11 -6
  424. package/src/tools/apps/executors.ts +11 -36
  425. package/src/tools/executor.ts +14 -1
  426. package/src/tools/host-terminal/host-shell.ts +6 -2
  427. package/src/tools/network/__tests__/web-fetch-firecrawl.test.ts +1 -1
  428. package/src/tools/network/__tests__/web-fetch-metadata.test.ts +25 -0
  429. package/src/tools/network/web-fetch.ts +6 -2
  430. package/src/tools/skills/sandbox-runner.ts +5 -2
  431. package/src/tools/subagent/spawn.ts +7 -11
  432. package/src/tools/terminal/shell.ts +5 -2
  433. package/src/tools/tool-manifest.ts +0 -2
  434. package/src/tools/tool-profiler.ts +37 -6
  435. package/src/tools/ui-surface/channel-variants.ts +101 -0
  436. package/src/tools/ui-surface/definitions.ts +23 -67
  437. package/src/tools/ui-surface/surface-shape-docs.ts +229 -0
  438. package/src/tts/__tests__/provider-adapters.test.ts +18 -0
  439. package/src/tts/provider-catalog.ts +2 -4
  440. package/src/tts/providers/deepgram-provider.ts +10 -1
  441. package/src/types/onboarding-context.ts +8 -0
  442. package/src/usage/attribution.ts +18 -112
  443. package/src/{config/bundled-skills/messaging/tools/gmail-mime-helpers.ts → util/mime-type.ts} +4 -1
  444. package/src/util/provider-error-patterns.ts +7 -1
  445. package/src/util/worker-process.ts +105 -0
  446. package/src/watcher/__tests__/telemetry.test.ts +17 -4
  447. package/src/watcher/telemetry.ts +5 -5
  448. package/src/workspace/migrations/131-drop-web-fetch-mode.ts +61 -0
  449. package/src/workspace/migrations/registry.ts +2 -0
  450. package/src/workspace/provider-commit-message-generator.ts +6 -4
  451. package/src/__tests__/app-open-proxy.test.ts +0 -67
  452. package/src/onboarding/onboarding-research-events-store.test.ts +0 -230
  453. package/src/onboarding/onboarding-research-events-store.ts +0 -104
  454. package/src/runtime/routes/assets/vellum-design-system.css +0 -2236
  455. package/src/tools/apps/definitions.ts +0 -73
  456. package/src/tools/apps/open-proxy.ts +0 -43
@@ -1,31 +1,32 @@
1
1
  import { describe, expect, mock, test } from "bun:test";
2
2
 
3
3
  // Fake app records keyed by ID
4
- const legacyApp = {
5
- id: "legacy-1",
6
- name: "Legacy App",
7
- htmlDefinition: "<div>Hello</div>",
4
+ const compiledApp = {
5
+ id: "app-1",
6
+ name: "Compiled App",
7
+ htmlDefinition: "",
8
8
  schemaJson: "{}",
9
9
  createdAt: 0,
10
10
  updatedAt: 0,
11
- // No formatVersion → legacy
11
+ formatVersion: 2,
12
12
  };
13
13
 
14
- const multifileApp = {
15
- id: "multi-1",
16
- name: "Multifile App",
17
- htmlDefinition: "",
14
+ const legacyApp = {
15
+ id: "legacy-1",
16
+ name: "Legacy App",
17
+ htmlDefinition: "<div>Hello</div>",
18
18
  schemaJson: "{}",
19
19
  createdAt: 0,
20
20
  updatedAt: 0,
21
- formatVersion: 2,
22
21
  };
23
22
 
24
- const apps = new Map<string, typeof legacyApp | typeof multifileApp>([
23
+ const apps = new Map<string, typeof compiledApp | typeof legacyApp>([
24
+ ["app-1", compiledApp],
25
25
  ["legacy-1", legacyApp],
26
- ["multi-1", multifileApp],
27
26
  ]);
28
27
 
28
+ const UNSUPPORTED_HTML = `<p>This app uses the retired single-file format and can no longer be opened.</p>`;
29
+
29
30
  mock.module("../apps/app-store.js", () => ({
30
31
  resolveAppSource: (id: string) => {
31
32
  const app = apps.get(id);
@@ -36,10 +37,13 @@ mock.module("../apps/app-store.js", () => ({
36
37
  dirName: id,
37
38
  sourceDir: `/fake/apps/${id}`,
38
39
  origin: { kind: "workspace" as const },
39
- formatVersion: (app as { formatVersion?: number }).formatVersion ?? 1,
40
40
  };
41
41
  },
42
42
  readAppFileBytesFromDir: () => Buffer.from(""),
43
+ // Only the legacy app's directory has a root index.html and no src/.
44
+ isLegacySingleFileDir: (sourceDir: string) =>
45
+ sourceDir === "/fake/apps/legacy-1",
46
+ UNSUPPORTED_LEGACY_APP_HTML: UNSUPPORTED_HTML,
43
47
  }));
44
48
 
45
49
  // Mock shared-app-links-store (imported by app-routes but unused here)
@@ -50,14 +54,13 @@ mock.module("../apps/shared-app-links-store.js", () => ({
50
54
  deleteSharedAppLinkByToken: () => false,
51
55
  }));
52
56
 
53
- // Stub fs so the multifile path finds a fake dist/index.html
57
+ // Stub fs so the serve path finds a fake dist/index.html for the compiled app
54
58
  mock.module("node:fs", () => ({
55
- existsSync: (p: string) => p === "/fake/apps/multi-1/dist/index.html",
59
+ existsSync: (p: string) => p === "/fake/apps/app-1/dist/index.html",
56
60
  readFileSync: (p: string, _enc?: string) => {
57
- if (p === "/fake/apps/multi-1/dist/index.html") {
61
+ if (p === "/fake/apps/app-1/dist/index.html") {
58
62
  return '<!DOCTYPE html><html><head></head><body><script src="main.js"></script></body></html>';
59
63
  }
60
- // Design system CSS — return empty string
61
64
  return "";
62
65
  },
63
66
  }));
@@ -99,157 +102,122 @@ function parseCsp(header: string): Record<string, string> {
99
102
  }
100
103
 
101
104
  describe("app-routes CSP headers", () => {
102
- describe("legacy apps", () => {
103
- test("includes 'unsafe-inline' in script-src", () => {
104
- const headers = getResponseHeaders("pages_serve", {
105
- pathParams: { appId: "legacy-1" },
106
- });
107
- const directives = parseCsp(headers["Content-Security-Policy"]);
108
- expect(directives["script-src"]).toContain("'unsafe-inline'");
109
- });
110
-
111
- test("includes 'unsafe-inline' in style-src", () => {
112
- const headers = getResponseHeaders("pages_serve", {
113
- pathParams: { appId: "legacy-1" },
114
- });
115
- const directives = parseCsp(headers["Content-Security-Policy"]);
116
- expect(directives["style-src"]).toContain("'unsafe-inline'");
105
+ test("does NOT include 'unsafe-inline' in script-src", () => {
106
+ const headers = getResponseHeaders("pages_serve", {
107
+ pathParams: { appId: "app-1" },
117
108
  });
109
+ const directives = parseCsp(headers["Content-Security-Policy"]);
110
+ expect(directives["script-src"]).not.toContain("'unsafe-inline'");
111
+ });
118
112
 
119
- test("has img-src with self, data, and https", () => {
120
- const headers = getResponseHeaders("pages_serve", {
121
- pathParams: { appId: "legacy-1" },
122
- });
123
- const directives = parseCsp(headers["Content-Security-Policy"]);
124
- expect(directives["img-src"]).toContain("'self'");
125
- expect(directives["img-src"]).toContain("data:");
126
- expect(directives["img-src"]).toContain("https:");
113
+ test("includes 'self' in script-src for external main.js", () => {
114
+ const headers = getResponseHeaders("pages_serve", {
115
+ pathParams: { appId: "app-1" },
127
116
  });
117
+ const directives = parseCsp(headers["Content-Security-Policy"]);
118
+ expect(directives["script-src"]).toContain("'self'");
128
119
  });
129
120
 
130
- describe("multifile apps", () => {
131
- test("does NOT include 'unsafe-inline' in script-src", () => {
132
- const headers = getResponseHeaders("pages_serve", {
133
- pathParams: { appId: "multi-1" },
134
- });
135
- const directives = parseCsp(headers["Content-Security-Policy"]);
136
- expect(directives["script-src"]).not.toContain("'unsafe-inline'");
121
+ test("includes 'unsafe-inline' in style-src", () => {
122
+ const headers = getResponseHeaders("pages_serve", {
123
+ pathParams: { appId: "app-1" },
137
124
  });
125
+ const directives = parseCsp(headers["Content-Security-Policy"]);
126
+ expect(directives["style-src"]).toContain("'unsafe-inline'");
127
+ });
138
128
 
139
- test("includes 'self' in script-src for external main.js", () => {
140
- const headers = getResponseHeaders("pages_serve", {
141
- pathParams: { appId: "multi-1" },
142
- });
143
- const directives = parseCsp(headers["Content-Security-Policy"]);
144
- expect(directives["script-src"]).toContain("'self'");
129
+ test("has img-src with self, data, and https", () => {
130
+ const headers = getResponseHeaders("pages_serve", {
131
+ pathParams: { appId: "app-1" },
145
132
  });
133
+ const directives = parseCsp(headers["Content-Security-Policy"]);
134
+ expect(directives["img-src"]).toContain("'self'");
135
+ expect(directives["img-src"]).toContain("data:");
136
+ expect(directives["img-src"]).toContain("https:");
137
+ });
138
+ });
146
139
 
147
- test("includes 'unsafe-inline' in style-src", () => {
148
- const headers = getResponseHeaders("pages_serve", {
149
- pathParams: { appId: "multi-1" },
150
- });
151
- const directives = parseCsp(headers["Content-Security-Policy"]);
152
- expect(directives["style-src"]).toContain("'unsafe-inline'");
153
- });
140
+ describe("handleServePage", () => {
141
+ const pageHandler = getRoute("pages_serve").handler;
154
142
 
155
- test("has img-src with self, data, and https", () => {
156
- const headers = getResponseHeaders("pages_serve", {
157
- pathParams: { appId: "multi-1" },
158
- });
159
- const directives = parseCsp(headers["Content-Security-Policy"]);
160
- expect(directives["img-src"]).toContain("'self'");
161
- expect(directives["img-src"]).toContain("data:");
162
- expect(directives["img-src"]).toContain("https:");
163
- });
143
+ test("serves compiled dist/index.html for compiled apps", () => {
144
+ const html = pageHandler({ pathParams: { appId: "app-1" } }) as string;
145
+ expect(html).toContain("/v1/apps/app-1/dist/main.js");
164
146
  });
165
147
 
166
- describe("handleServeDistFile appId validation", () => {
167
- const distHandler = getRoute("apps_dist_file").handler;
148
+ test("serves the unsupported-format message for legacy single-file apps", () => {
149
+ const html = pageHandler({ pathParams: { appId: "legacy-1" } }) as string;
150
+ expect(html).toContain(UNSUPPORTED_HTML);
151
+ expect(html).not.toContain("<div>Hello</div>");
152
+ });
168
153
 
169
- test("rejects appId with encoded path traversal (..)", () => {
170
- expect(() =>
171
- distHandler({ pathParams: { appId: "..", filename: "main.js" } }),
172
- ).toThrow(BadRequestError);
173
- });
154
+ test("throws NotFoundError for unknown apps", () => {
155
+ expect(() => pageHandler({ pathParams: { appId: "nope" } })).toThrow(
156
+ NotFoundError,
157
+ );
158
+ });
159
+ });
174
160
 
175
- test("rejects appId with forward slash", () => {
176
- expect(() =>
177
- distHandler({
178
- pathParams: { appId: "../../etc", filename: "main.js" },
179
- }),
180
- ).toThrow(BadRequestError);
181
- });
161
+ describe("handleServeDistFile appId validation", () => {
162
+ const distHandler = getRoute("apps_dist_file").handler;
182
163
 
183
- test("rejects appId with backslash", () => {
184
- expect(() =>
185
- distHandler({
186
- pathParams: { appId: "foo\\bar", filename: "main.js" },
187
- }),
188
- ).toThrow(BadRequestError);
189
- });
164
+ test("rejects appId with encoded path traversal (..)", () => {
165
+ expect(() =>
166
+ distHandler({ pathParams: { appId: "..", filename: "main.js" } }),
167
+ ).toThrow(BadRequestError);
168
+ });
190
169
 
191
- test("rejects empty appId", () => {
192
- expect(() =>
193
- distHandler({ pathParams: { appId: "", filename: "main.js" } }),
194
- ).toThrow(BadRequestError);
195
- });
170
+ test("rejects appId with forward slash", () => {
171
+ expect(() =>
172
+ distHandler({
173
+ pathParams: { appId: "../../etc", filename: "main.js" },
174
+ }),
175
+ ).toThrow(BadRequestError);
176
+ });
196
177
 
197
- test("rejects appId with leading whitespace", () => {
198
- expect(() =>
199
- distHandler({
200
- pathParams: { appId: " multi-1", filename: "main.js" },
201
- }),
202
- ).toThrow(BadRequestError);
203
- });
178
+ test("rejects appId with backslash", () => {
179
+ expect(() =>
180
+ distHandler({
181
+ pathParams: { appId: "foo\\bar", filename: "main.js" },
182
+ }),
183
+ ).toThrow(BadRequestError);
184
+ });
204
185
 
205
- test("rejects appId with trailing whitespace", () => {
206
- expect(() =>
207
- distHandler({
208
- pathParams: { appId: "multi-1 ", filename: "main.js" },
209
- }),
210
- ).toThrow(BadRequestError);
211
- });
186
+ test("rejects empty appId", () => {
187
+ expect(() =>
188
+ distHandler({ pathParams: { appId: "", filename: "main.js" } }),
189
+ ).toThrow(BadRequestError);
190
+ });
212
191
 
213
- test("rejects appId containing .. in the middle", () => {
214
- expect(() =>
215
- distHandler({
216
- pathParams: { appId: "foo..bar", filename: "main.js" },
217
- }),
218
- ).toThrow(BadRequestError);
219
- });
192
+ test("rejects appId with leading whitespace", () => {
193
+ expect(() =>
194
+ distHandler({
195
+ pathParams: { appId: " app-1", filename: "main.js" },
196
+ }),
197
+ ).toThrow(BadRequestError);
198
+ });
220
199
 
221
- test("allows valid appId and filename (file not found throws NotFoundError)", () => {
222
- expect(() =>
223
- distHandler({
224
- pathParams: { appId: "multi-1", filename: "main.js" },
225
- }),
226
- ).toThrow(NotFoundError);
227
- });
200
+ test("rejects appId with trailing whitespace", () => {
201
+ expect(() =>
202
+ distHandler({
203
+ pathParams: { appId: "app-1 ", filename: "main.js" },
204
+ }),
205
+ ).toThrow(BadRequestError);
228
206
  });
229
207
 
230
- describe("consistent directives across formats", () => {
231
- test("both formats share the same style-src policy", () => {
232
- const legacyHeaders = getResponseHeaders("pages_serve", {
233
- pathParams: { appId: "legacy-1" },
234
- });
235
- const multiHeaders = getResponseHeaders("pages_serve", {
236
- pathParams: { appId: "multi-1" },
237
- });
238
- const legacyCsp = parseCsp(legacyHeaders["Content-Security-Policy"]);
239
- const multiCsp = parseCsp(multiHeaders["Content-Security-Policy"]);
240
- expect(legacyCsp["style-src"]).toBe(multiCsp["style-src"]);
241
- });
208
+ test("rejects appId containing .. in the middle", () => {
209
+ expect(() =>
210
+ distHandler({
211
+ pathParams: { appId: "foo..bar", filename: "main.js" },
212
+ }),
213
+ ).toThrow(BadRequestError);
214
+ });
242
215
 
243
- test("both formats share the same img-src policy", () => {
244
- const legacyHeaders = getResponseHeaders("pages_serve", {
245
- pathParams: { appId: "legacy-1" },
246
- });
247
- const multiHeaders = getResponseHeaders("pages_serve", {
248
- pathParams: { appId: "multi-1" },
249
- });
250
- const legacyCsp = parseCsp(legacyHeaders["Content-Security-Policy"]);
251
- const multiCsp = parseCsp(multiHeaders["Content-Security-Policy"]);
252
- expect(legacyCsp["img-src"]).toBe(multiCsp["img-src"]);
253
- });
216
+ test("allows valid appId and filename (file not found throws NotFoundError)", () => {
217
+ expect(() =>
218
+ distHandler({
219
+ pathParams: { appId: "app-1", filename: "main.js" },
220
+ }),
221
+ ).toThrow(NotFoundError);
254
222
  });
255
223
  });
@@ -69,13 +69,20 @@ describe("auth-fallback-events-store", () => {
69
69
  });
70
70
  });
71
71
 
72
- test("honors the share_analytics opt-out (records nothing)", () => {
72
+ test("honors a confirmed share_analytics opt-out (records nothing)", () => {
73
73
  setShareAnalytics(false);
74
74
  const recorded = recordAuthFallbackCounts(1000, 2000, SAMPLE);
75
75
  expect(recorded).toBe(0);
76
76
  expect(pendingOutboxRows("auth_fallback").length).toBe(0);
77
77
  });
78
78
 
79
+ test("records while consent is unknown (a cold cache never drops data)", () => {
80
+ setShareAnalytics("unknown");
81
+ const recorded = recordAuthFallbackCounts(1000, 2000, SAMPLE);
82
+ expect(recorded).toBe(2);
83
+ expect(pendingOutboxRows("auth_fallback").length).toBe(2);
84
+ });
85
+
79
86
  test("empty counts batch is a no-op", () => {
80
87
  expect(recordAuthFallbackCounts(1000, 2000, [])).toBe(0);
81
88
  expect(pendingOutboxRows("auth_fallback").length).toBe(0);
@@ -27,12 +27,20 @@ mock.module("../persistence/llm-request-log-store.js", () => ({
27
27
  }));
28
28
 
29
29
  // ── Imports (after mocks) ─────────────────────────────────────────────────────
30
+ import { REDACTED_SENTINEL_OPEN } from "@vellumai/service-contracts/redacted-credential";
31
+
32
+ import type { ResolvedRevealCandidate } from "../daemon/chat-credential-redaction.js";
30
33
  import {
31
34
  buildPersistedAssistantContent,
32
35
  stampThinkingTiming,
33
36
  } from "../daemon/conversation-agent-loop-handlers.js";
34
37
  import type { ToolActivityMetadata } from "../daemon/message-types/web-activity.js";
35
38
  import type { ContentBlock } from "../providers/types.js";
39
+ import {
40
+ OPENAI_PROJECT_KEY_REDACTION_MARKER,
41
+ SYNTHETIC_OPAQUE_CREDENTIAL,
42
+ SYNTHETIC_OPENAI_PROJECT_KEY,
43
+ } from "./secret-fixtures.js";
36
44
 
37
45
  const webSearchActivity: ToolActivityMetadata = {
38
46
  webSearch: {
@@ -229,3 +237,91 @@ describe("stampThinkingTiming", () => {
229
237
  expect(stamped[1]).not.toHaveProperty("_startedAt");
230
238
  });
231
239
  });
240
+
241
+ /**
242
+ * The `chat-credential-reveal` gate lives one layer up in
243
+ * `chatRevealCandidates`, which yields `undefined` when the flag is off — so
244
+ * `revealCandidates: undefined` here is the flag-off persist path, and the
245
+ * default configuration. Redaction is not what the flag selects: a
246
+ * route-proven reveal plaintext must stay out of the persisted row in both
247
+ * modes, via the flag-independent `legacyFallbackCandidates` seam.
248
+ */
249
+ describe("buildPersistedAssistantContent — legacy (flag-off) redaction", () => {
250
+ const openaiCandidate: ResolvedRevealCandidate = {
251
+ service: "openai",
252
+ field: "api_key",
253
+ value: SYNTHETIC_OPENAI_PROJECT_KEY,
254
+ };
255
+ const opaqueCandidate: ResolvedRevealCandidate = {
256
+ service: "acme",
257
+ field: "token",
258
+ value: SYNTHETIC_OPAQUE_CREDENTIAL,
259
+ };
260
+
261
+ function persistText(
262
+ text: string,
263
+ revealCandidates?: readonly ResolvedRevealCandidate[],
264
+ legacyFallbackCandidates: readonly ResolvedRevealCandidate[] = [],
265
+ ): string {
266
+ const built = buildPersistedAssistantContent(
267
+ [{ type: "text", text }] as unknown as ContentBlock[],
268
+ [],
269
+ undefined,
270
+ revealCandidates,
271
+ legacyFallbackCandidates,
272
+ ) as unknown as Array<Record<string, unknown>>;
273
+ return built[0].text as string;
274
+ }
275
+
276
+ test("emits the legacy marker for a scanner match, never sentinel glyphs", () => {
277
+ const out = persistText(`key: ${SYNTHETIC_OPENAI_PROJECT_KEY}`);
278
+
279
+ expect(out).toBe(`key: ${OPENAI_PROJECT_KEY_REDACTION_MARKER}`);
280
+ expect(out).not.toContain(SYNTHETIC_OPENAI_PROJECT_KEY);
281
+ expect(out).not.toContain(REDACTED_SENTINEL_OPEN);
282
+ });
283
+
284
+ test("redacts a proven candidate the scanner cannot classify", () => {
285
+ // The opaque value has no scanner-recognizable shape, so the fallback is
286
+ // the only thing standing between it and a raw persisted row.
287
+ const out = persistText(
288
+ `token: ${SYNTHETIC_OPAQUE_CREDENTIAL}`,
289
+ undefined,
290
+ [opaqueCandidate],
291
+ );
292
+
293
+ expect(out).not.toContain(SYNTHETIC_OPAQUE_CREDENTIAL);
294
+ expect(out).toContain("<redacted");
295
+ expect(out).not.toContain(REDACTED_SENTINEL_OPEN);
296
+ });
297
+
298
+ test("marks the block neutralization-aware in legacy mode too", () => {
299
+ // `renderHistoryContent` neutralizes any block lacking the rider, so a
300
+ // legacy-mode row must still carry it or its own text gets re-scanned.
301
+ const built = buildPersistedAssistantContent(
302
+ [{ type: "text", text: "no secrets here" }] as unknown as ContentBlock[],
303
+ [],
304
+ ) as unknown as Array<Record<string, unknown>>;
305
+
306
+ expect(built[0]._redactionVersion).toBe(2);
307
+ });
308
+
309
+ test("sentinel mode enriches the same candidate the legacy path flattens", () => {
310
+ // Candidate spans are protected ahead of the scanner in both modes, so the
311
+ // legacy marker reports the generic type while the sentinel carries the
312
+ // vault identity the chip needs.
313
+ const legacy = persistText(
314
+ `key: ${SYNTHETIC_OPENAI_PROJECT_KEY}`,
315
+ undefined,
316
+ [openaiCandidate],
317
+ );
318
+ const sentinel = persistText(`key: ${SYNTHETIC_OPENAI_PROJECT_KEY}`, [
319
+ openaiCandidate,
320
+ ]);
321
+
322
+ expect(legacy).toBe('key: <redacted type="Credential" />');
323
+ expect(sentinel).toBe(
324
+ "key: 〔redacted:OpenAI Project Key:openai:api_key〕",
325
+ );
326
+ });
327
+ });
@@ -12,7 +12,7 @@ import { scanBundle, type ScanFinding } from "../bundler/bundle-scanner.js";
12
12
  // ---------------------------------------------------------------------------
13
13
 
14
14
  const MINIMAL_MANIFEST = {
15
- format_version: "1",
15
+ format_version: "2",
16
16
  name: "test-app",
17
17
  created_at: "2025-01-01T00:00:00Z",
18
18
  created_by: "test",
@@ -62,6 +62,32 @@ function findByCode(
62
62
  return findings.find((f) => f.code === code);
63
63
  }
64
64
 
65
+ // ---------------------------------------------------------------------------
66
+ // Format version gate (block)
67
+ // ---------------------------------------------------------------------------
68
+
69
+ describe("format_version gate", () => {
70
+ test("blocks legacy format_version 1 bundles", async () => {
71
+ const path = await createBundle(
72
+ { "index.html": "<html><body>Hello</body></html>" },
73
+ { ...MINIMAL_MANIFEST, format_version: 1 },
74
+ );
75
+ const result = await scanBundle(path);
76
+ const f = findByCode(result.findings, "unsupported_format");
77
+ expect(f).toBeDefined();
78
+ expect(f!.level).toBe("block");
79
+ expect(result.passed).toBe(false);
80
+ });
81
+
82
+ test("passes format_version 2 bundles", async () => {
83
+ const path = await createBundle({
84
+ "index.html": "<html><body>Hello</body></html>",
85
+ });
86
+ const result = await scanBundle(path);
87
+ expect(findByCode(result.findings, "unsupported_format")).toBeUndefined();
88
+ });
89
+ });
90
+
65
91
  // ---------------------------------------------------------------------------
66
92
  // SVG <script> tags (block)
67
93
  // ---------------------------------------------------------------------------