@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
@@ -28,7 +28,7 @@
28
28
 
29
29
  import { getPlatformOrganizationId, getPlatformUserId } from "../config/env.js";
30
30
  import { VellumPlatformClient } from "../platform/client.js";
31
- import { getCachedShareAnalytics } from "../platform/consent-cache.js";
31
+ import { getRawShareAnalytics } from "../platform/consent-cache.js";
32
32
  import { arePlatformFeaturesEnabled } from "../platform/feature-gate.js";
33
33
  import { getDeviceId } from "../util/device-id.js";
34
34
  import { getLogger } from "../util/logger.js";
@@ -45,6 +45,10 @@ import {
45
45
  TOOL_EXECUTED_SOURCE_ID,
46
46
  watermarkKeysForSource,
47
47
  } from "./telemetry-event-sources.js";
48
+ import {
49
+ deleteTelemetryOutboxEventsBefore,
50
+ OUTBOX_MAX_ROW_AGE_MS,
51
+ } from "./telemetry-events-outbox.js";
48
52
  import { useReadOnlyMainDbForTelemetry } from "./telemetry-main-db.js";
49
53
  import { validateWireEvents } from "./telemetry-wire-validation.js";
50
54
 
@@ -65,6 +69,43 @@ const BATCH_SIZE = 500;
65
69
  const MAX_CONSECUTIVE_BATCHES = 10;
66
70
  const TELEMETRY_PATH = "/v1/telemetry/ingest/";
67
71
 
72
+ /**
73
+ * Outcome of a {@link UsageTelemetryReporter.flush} call. `flushed: true` means
74
+ * at least one batch reached the platform (2xx). `sent` is the count the daemon
75
+ * POSTed; `persisted` is the count the platform confirmed written; `dropped` is
76
+ * `sent - persisted` — folding server-side drops (opt-out, unauthenticated) and
77
+ * events silently skipped at ingest validation (unknown/invalid type) into one
78
+ * "did not land" number. `flushed: false` carries a kebab-case skip reason.
79
+ */
80
+ export type TelemetryFlushSummary =
81
+ | { flushed: true; sent: number; persisted: number; dropped: number }
82
+ | { flushed: false; reason: string };
83
+
84
+ interface FlushAccumulator {
85
+ sent: number;
86
+ persisted: number;
87
+ }
88
+
89
+ /**
90
+ * Read `persisted` from an ingest response body. Falls back to `fallback`
91
+ * (the POSTed count — assume all landed) on a malformed body or an older
92
+ * platform that omits the field. Never throws.
93
+ */
94
+ function parsePersistedCount(body: string, fallback: number): number {
95
+ try {
96
+ const parsed: unknown = JSON.parse(body);
97
+ if (parsed && typeof parsed === "object") {
98
+ const p = (parsed as { persisted?: unknown }).persisted;
99
+ if (typeof p === "number" && Number.isFinite(p) && p >= 0) {
100
+ return p;
101
+ }
102
+ }
103
+ } catch {
104
+ // Malformed/empty body — assume everything persisted.
105
+ }
106
+ return fallback;
107
+ }
108
+
68
109
  // ---------------------------------------------------------------------------
69
110
  // Singleton access
70
111
  // ---------------------------------------------------------------------------
@@ -193,7 +234,7 @@ export function initToolExecutedWatermarkIfAbsent(
193
234
  export class UsageTelemetryReporter {
194
235
  private initialFlushTimer: ReturnType<typeof setTimeout> | null = null;
195
236
  private timer: ReturnType<typeof setInterval> | null = null;
196
- private activeFlush: Promise<void> | null = null;
237
+ private activeFlush: Promise<TelemetryFlushSummary> | null = null;
197
238
  private readonly sources: readonly TelemetryEventSource[];
198
239
  private readonly checkpoints: FlushCheckpointStore;
199
240
 
@@ -248,22 +289,53 @@ export class UsageTelemetryReporter {
248
289
  await this.flush();
249
290
  }
250
291
 
251
- async flush(): Promise<void> {
292
+ async flush(): Promise<TelemetryFlushSummary> {
252
293
  if (this.activeFlush) {
253
- return; // overlap guard
294
+ return this.activeFlush; // overlap guard — share the in-flight result
254
295
  }
255
296
  this.activeFlush = this._doFlush();
256
297
  try {
257
- await this.activeFlush;
298
+ return await this.activeFlush;
258
299
  } finally {
259
300
  this.activeFlush = null;
260
301
  }
261
302
  }
262
303
 
263
- private async _doFlush(batchCount = 0): Promise<void> {
304
+ private async _doFlush(
305
+ batchCount = 0,
306
+ acc: FlushAccumulator = { sent: 0, persisted: 0 },
307
+ ): Promise<TelemetryFlushSummary> {
308
+ // Once any batch has shipped (acc.sent > 0), a later skip/defer/error is
309
+ // just the natural end of the flush — report what landed, not a failure.
310
+ const flushed = (): TelemetryFlushSummary => ({
311
+ flushed: true,
312
+ sent: acc.sent,
313
+ persisted: acc.persisted,
314
+ dropped: Math.max(0, acc.sent - acc.persisted),
315
+ });
316
+ const settle = (reason: string): TelemetryFlushSummary =>
317
+ acc.sent > 0 ? flushed() : { flushed: false, reason };
318
+
264
319
  try {
265
320
  if (batchCount >= MAX_CONSECUTIVE_BATCHES) {
266
- return;
321
+ return flushed();
322
+ }
323
+
324
+ // Age-bound the ack-mode outbox before any skip gate (platform,
325
+ // checkpoint store, consent three-way), so the bound holds in every
326
+ // state — including the unknown-consent deferral below. Only the
327
+ // reporter partition that flushes the outbox (ack-mode sources)
328
+ // prunes it.
329
+ if (batchCount === 0 && this.sources.some((source) => source.ack)) {
330
+ const prunedCount = deleteTelemetryOutboxEventsBefore(
331
+ Date.now() - OUTBOX_MAX_ROW_AGE_MS,
332
+ );
333
+ if (prunedCount > 0) {
334
+ log.debug(
335
+ { prunedCount },
336
+ "Telemetry flush: pruned expired outbox rows",
337
+ );
338
+ }
267
339
  }
268
340
 
269
341
  // Skip when platform features are disabled (VELLUM_DISABLE_PLATFORM in
@@ -272,7 +344,7 @@ export class UsageTelemetryReporter {
272
344
  // is a deployment/local-mode toggle, not a privacy opt-out, so the unsent
273
345
  // backlog ships once the flag is cleared.
274
346
  if (!arePlatformFeaturesEnabled()) {
275
- return;
347
+ return settle("disabled");
276
348
  }
277
349
 
278
350
  // Skip when the flush-checkpoint store (telemetry DB) is unreachable.
@@ -283,10 +355,29 @@ export class UsageTelemetryReporter {
283
355
  log.warn(
284
356
  "Telemetry flush: flush-checkpoint store unavailable — skipping, will retry next cycle",
285
357
  );
286
- return;
358
+ return settle("checkpoint-unavailable");
359
+ }
360
+
361
+ // Tri-state consent gate: `true` ships, `false` purges, `"unknown"`
362
+ // defers.
363
+ const shareAnalytics = getRawShareAnalytics();
364
+
365
+ // `"unknown"` (boot before the first successful consent refresh, no
366
+ // platform session, no resolvable owner) is not an opt-out: nothing is
367
+ // sent, acked, discarded, or advanced — the same shape as the
368
+ // checkpoint-unavailable skip above. Purging here would silently
369
+ // destroy events recorded before the cache warmed up; instead the
370
+ // backlog ships (or is purged) on a later cycle once the 5-minute
371
+ // refresh loop resolves the cache to a confirmed value. Bounded by
372
+ // the age prune above.
373
+ if (shareAnalytics === "unknown") {
374
+ log.debug(
375
+ "Telemetry flush: share_analytics consent unknown — skipping, will retry next cycle",
376
+ );
377
+ return settle("unknown-consent");
287
378
  }
288
379
 
289
- // Respect opt-out: if the platform owner has not granted
380
+ // Respect a confirmed opt-out: if the platform owner declined
290
381
  // `share_analytics` consent, skip the flush and advance watermarks so
291
382
  // events recorded during the opt-out window are never sent
292
383
  // retroactively. The daemon runs the reporter even when opted out
@@ -302,7 +393,7 @@ export class UsageTelemetryReporter {
302
393
  // under builds predating the audit listener's write-time gate — new
303
394
  // opted-out tool_invocations rows persist NULL telemetry columns and
304
395
  // are unreportable by construction regardless of watermark timing.
305
- if (!getCachedShareAnalytics()) {
396
+ if (shareAnalytics === false) {
306
397
  // Ack-mode sources drop their pending rows outright. Watermark-mode
307
398
  // sources advance the timestamp watermarks and pin the ID watermarks
308
399
  // to a sentinel that sorts above any real UUID. The sentinel (rather
@@ -322,7 +413,7 @@ export class UsageTelemetryReporter {
322
413
  this.checkpoints.set(keys.at, now);
323
414
  this.checkpoints.set(keys.id, OPT_OUT_WATERMARK_ID_SENTINEL);
324
415
  }
325
- return;
416
+ return settle("opted-out");
326
417
  }
327
418
 
328
419
  // Read each source's watermark (compound cursor: createdAt + id) and
@@ -371,7 +462,7 @@ export class UsageTelemetryReporter {
371
462
  }
372
463
 
373
464
  if (batches.every(({ batch }) => batch.events.length === 0)) {
374
- return;
465
+ return settle("nothing-pending");
375
466
  }
376
467
 
377
468
  // Resolve auth context. We send authenticated-only: if no platform
@@ -388,7 +479,7 @@ export class UsageTelemetryReporter {
388
479
  },
389
480
  "Telemetry flush: no platform credentials — skipping, will retry next cycle",
390
481
  );
391
- return;
482
+ return settle("no-credentials");
392
483
  }
393
484
  log.debug(
394
485
  {
@@ -437,9 +528,14 @@ export class UsageTelemetryReporter {
437
528
  { status: resp.status, body },
438
529
  "Usage telemetry POST failed — will retry next cycle",
439
530
  );
440
- return;
531
+ return settle("post-failed");
441
532
  }
442
- await resp.text(); // consume body to release connection
533
+
534
+ // Read the ingest response so `persisted` reflects what actually landed;
535
+ // consuming the body also releases the connection.
536
+ const body = await resp.text();
537
+ acc.sent += typedEvents.length;
538
+ acc.persisted += parsePersistedCount(body, typedEvents.length);
443
539
 
444
540
  // Acknowledge each source's shipped rows: ack-mode sources delete them
445
541
  // (never touching flush_checkpoints), watermark-mode sources advance
@@ -456,12 +552,15 @@ export class UsageTelemetryReporter {
456
552
  }
457
553
  }
458
554
 
459
- // If any source produced a full batch, there may be more — recurse.
555
+ // If any source produced a full batch, there may be more — recurse,
556
+ // threading the accumulator so counts span every batch in this flush.
460
557
  if (batches.some(({ batch }) => batch.fullBatch)) {
461
- await this._doFlush(batchCount + 1);
558
+ return this._doFlush(batchCount + 1, acc);
462
559
  }
560
+ return flushed();
463
561
  } catch (err) {
464
562
  log.warn({ err }, "Usage telemetry flush error — non-fatal, will retry");
563
+ return settle("error");
465
564
  }
466
565
  }
467
566
  }
@@ -8,9 +8,11 @@
8
8
  */
9
9
  import { beforeEach, describe, expect, mock, test } from "bun:test";
10
10
 
11
- let shareAnalytics = true;
11
+ import type { ConsentState } from "../platform/consent-cache.js";
12
+
13
+ let shareAnalytics: ConsentState = true;
12
14
  mock.module("../platform/consent-cache.js", () => ({
13
- getCachedShareAnalytics: () => shareAnalytics,
15
+ getRawShareAnalytics: () => shareAnalytics,
14
16
  }));
15
17
 
16
18
  let platformEnabled = true;
@@ -84,12 +86,18 @@ describe("emitWatchdogEventDirect", () => {
84
86
  });
85
87
  });
86
88
 
87
- test("sends nothing when share_analytics is opted out", async () => {
89
+ test("sends nothing when share_analytics is a confirmed opt-out", async () => {
88
90
  shareAnalytics = false;
89
91
  await emitWatchdogEventDirect("sqlite_corrupted", { database: "main" });
90
92
  expect(fetchCalls).toHaveLength(0);
91
93
  });
92
94
 
95
+ test("emits while consent is unknown (no buffer to defer into)", async () => {
96
+ shareAnalytics = "unknown";
97
+ await emitWatchdogEventDirect("sqlite_corrupted", { database: "main" });
98
+ expect(fetchCalls).toHaveLength(1);
99
+ });
100
+
93
101
  test("sends nothing when platform features are disabled", async () => {
94
102
  platformEnabled = false;
95
103
  await emitWatchdogEventDirect("sqlite_corrupted", { database: "main" });
@@ -20,7 +20,7 @@ import { v4 as uuid } from "uuid";
20
20
 
21
21
  import { getPlatformOrganizationId, getPlatformUserId } from "../config/env.js";
22
22
  import { VellumPlatformClient } from "../platform/client.js";
23
- import { getCachedShareAnalytics } from "../platform/consent-cache.js";
23
+ import { getRawShareAnalytics } from "../platform/consent-cache.js";
24
24
  import { arePlatformFeaturesEnabled } from "../platform/feature-gate.js";
25
25
  import { getDeviceId } from "../util/device-id.js";
26
26
  import { getLogger } from "../util/logger.js";
@@ -35,9 +35,11 @@ const TELEMETRY_INGEST_PATH = "/v1/telemetry/ingest/";
35
35
 
36
36
  /**
37
37
  * POST one `watchdog` event directly to the platform, bypassing the SQLite
38
- * buffer. Honors the `share_analytics` opt-out and the platform-features gate,
39
- * matching the batched reporter. Never throws the caller is on a query hot
40
- * path.
38
+ * buffer. Honors a confirmed `share_analytics` opt-out and the
39
+ * platform-features gate. An unknown consent state emits: this path reports
40
+ * SQLite corruption and has no outbox to defer into, and platform ingest
41
+ * re-gates on consent authoritatively server-side. Never throws — the caller
42
+ * is on a query hot path.
41
43
  */
42
44
  export async function emitWatchdogEventDirect(
43
45
  checkName: string,
@@ -45,8 +47,11 @@ export async function emitWatchdogEventDirect(
45
47
  value: number | null = null,
46
48
  ): Promise<void> {
47
49
  try {
48
- // Opt-out and deployment gates, mirroring the batched reporter's flush.
49
- if (!getCachedShareAnalytics()) return;
50
+ // Drop only on a confirmed opt-out; unknown emits (no buffer to defer
51
+ // into, and platform ingest re-gates on consent server-side).
52
+ if (getRawShareAnalytics() === false) {
53
+ return;
54
+ }
50
55
  if (!arePlatformFeaturesEnabled()) return;
51
56
 
52
57
  // Authenticated-only. Null before the CES handshake resolves credentials;
@@ -43,7 +43,6 @@ export interface AppStoreWriter {
43
43
  icon?: string;
44
44
  schemaJson: string;
45
45
  htmlDefinition: string;
46
- formatVersion?: number;
47
46
  }): AppDefinition;
48
47
  updateApp(
49
48
  id: string,
@@ -217,7 +216,6 @@ export async function executeAppCreate(
217
216
  icon,
218
217
  schemaJson,
219
218
  htmlDefinition: "",
220
- formatVersion: 2,
221
219
  });
222
220
 
223
221
  // Associate the app with its conversation at creation so subsequent
@@ -407,28 +405,17 @@ export async function executeAppRefresh(
407
405
  // the client side.
408
406
  const updated = store.updateApp(input.app_id, {});
409
407
 
410
- // Multifile apps need an explicit compile so the LLM sees any errors
411
- // (bad imports, syntax issues, etc.) instead of silently serving the
412
- // stale scaffold placeholder from the initial app_create.
413
- if (app.formatVersion === 2) {
414
- const appDir = getAppDirPath(input.app_id);
415
- const compileResult = await compileApp(appDir);
416
- return {
417
- content: JSON.stringify({
418
- refreshed: true,
419
- appId: updated.id,
420
- name: updated.name,
421
- ...compileResultPayload(compileResult),
422
- }),
423
- isError: false,
424
- };
425
- }
426
-
408
+ // Compile explicitly so the LLM sees any errors (bad imports, syntax
409
+ // issues, etc.) instead of silently serving the stale scaffold placeholder
410
+ // from the initial app_create.
411
+ const appDir = getAppDirPath(input.app_id);
412
+ const compileResult = await compileApp(appDir);
427
413
  return {
428
414
  content: JSON.stringify({
429
415
  refreshed: true,
430
416
  appId: updated.id,
431
417
  name: updated.name,
418
+ ...compileResultPayload(compileResult),
432
419
  }),
433
420
  isError: false,
434
421
  };
@@ -485,29 +472,17 @@ export async function executeAppUpdate(
485
472
  // An empty update still bumps updatedAt, triggering a client surface refresh.
486
473
  const updated = store.updateApp(input.app_id, updates);
487
474
 
488
- // Multifile apps recompile so the agent sees any errors from the edited
489
- // source instead of serving a stale dist (mirrors app_refresh).
490
- if (app.formatVersion === 2) {
491
- const appDir = getAppDirPath(input.app_id);
492
- const compileResult = await compileApp(appDir);
493
- return {
494
- content: JSON.stringify({
495
- updated: true,
496
- appId: updated.id,
497
- name: updated.name,
498
- description: updated.description,
499
- ...compileResultPayload(compileResult),
500
- }),
501
- isError: false,
502
- };
503
- }
504
-
475
+ // Recompile so the agent sees any errors from the edited source instead of
476
+ // serving a stale dist (mirrors app_refresh).
477
+ const appDir = getAppDirPath(input.app_id);
478
+ const compileResult = await compileApp(appDir);
505
479
  return {
506
480
  content: JSON.stringify({
507
481
  updated: true,
508
482
  appId: updated.id,
509
483
  name: updated.name,
510
484
  description: updated.description,
485
+ ...compileResultPayload(compileResult),
511
486
  }),
512
487
  isError: false,
513
488
  };
@@ -3,6 +3,7 @@ import { readFileSync } from "node:fs";
3
3
  import { getConfig } from "../config/loader.js";
4
4
  import { PermissionPrompter } from "../permissions/prompter.js";
5
5
  import { RiskLevel } from "../permissions/types.js";
6
+ import { runInPluginContext } from "../plugins/plugin-execution-context.js";
6
7
  import { TokenExpiredError } from "../security/token-manager.js";
7
8
  import {
8
9
  recordToolError,
@@ -19,6 +20,7 @@ import {
19
20
  import { getWorkflowRunManager } from "../workflows/run-manager.js";
20
21
  import { executeWithTimeout, safeTimeoutMs } from "./execution-timeout.js";
21
22
  import { PermissionChecker } from "./permission-checker.js";
23
+ import { getToolOwner } from "./registry.js";
22
24
  import { extractAndSanitize } from "./sensitive-output-placeholders.js";
23
25
  import { applyEdit } from "./shared/filesystem/edit-engine.js";
24
26
  import { sandboxPolicy } from "./shared/filesystem/path-policy.js";
@@ -223,8 +225,19 @@ export class ToolExecutor {
223
225
  const toolTimeoutMs = computePerToolTimeoutMs(name, input);
224
226
  const execContext = context;
225
227
 
228
+ // Mark the owning plugin as in context (via AsyncLocalStorage) so host
229
+ // APIs the tool reaches — e.g. resolveCredential — can scope to it. The
230
+ // context must be established around the `execute()` call itself so the
231
+ // returned promise carries the binding across its awaits. Non-plugin
232
+ // tools (default/skill/mcp/workspace) establish no context.
233
+ const owner = getToolOwner(name);
234
+ const execPromise =
235
+ owner?.kind === "plugin"
236
+ ? runInPluginContext(owner.id, () => tool.execute(input, execContext))
237
+ : tool.execute(input, execContext);
238
+
226
239
  let execResult: ToolExecutionResult = await executeWithTimeout(
227
- tool.execute(input, execContext),
240
+ execPromise,
228
241
  toolTimeoutMs,
229
242
  name,
230
243
  );
@@ -20,6 +20,7 @@ import {
20
20
  assistantEventHub,
21
21
  broadcastMessage,
22
22
  } from "../../runtime/assistant-event-hub.js";
23
+ import { conversationRevealNonce } from "../../runtime/reveal-nonce.js";
23
24
  import { redactSecrets } from "../../security/secret-scanner.js";
24
25
  import { getLogger } from "../../util/logger.js";
25
26
  import type { CompletedBackgroundTool } from "../background-tool-registry.js";
@@ -84,6 +85,8 @@ function buildHostBashProxyEnv(conversationId: string): Record<string, string> {
84
85
  // Keep nested `assistant` CLI calls in host_bash aligned with the
85
86
  // originating conversation so browser IPC can resolve live proxy context.
86
87
  env.__CONVERSATION_ID = conversationId;
88
+ // Secret binding for reveal-derived chat authority — see reveal-nonce.ts.
89
+ env.__REVEAL_NONCE = conversationRevealNonce(conversationId);
87
90
  return env;
88
91
  }
89
92
 
@@ -438,6 +441,7 @@ export const hostShellTool = {
438
441
  // Match `bash` tool behavior so nested assistant CLI calls can bind to
439
442
  // the active conversation when running through host_bash.
440
443
  hostEnv.__CONVERSATION_ID = context.conversationId;
444
+ hostEnv.__REVEAL_NONCE = conversationRevealNonce(context.conversationId);
441
445
 
442
446
  if (background) {
443
447
  // Check the registry limit BEFORE spawning so we never leak an
@@ -496,7 +500,7 @@ export const hostShellTool = {
496
500
  let completed = false;
497
501
 
498
502
  child.on("close", (code) => {
499
- if (completed) return;
503
+ if (completed) {return;}
500
504
  completed = true;
501
505
  clearTimeout(timer);
502
506
  const stdout = Buffer.concat(stdoutChunks).toString();
@@ -568,7 +572,7 @@ export const hostShellTool = {
568
572
  });
569
573
 
570
574
  child.on("error", (err) => {
571
- if (completed) return;
575
+ if (completed) {return;}
572
576
  completed = true;
573
577
  clearTimeout(timer);
574
578
  const status = aborted ? "cancelled" : "failed";
@@ -7,7 +7,7 @@ let mockFirecrawlSecureKey: string | undefined;
7
7
 
8
8
  /** Seed the active web-fetch provider into the workspace config for real. */
9
9
  function seedWebFetch(provider: string): void {
10
- setConfig("services", { "web-fetch": { mode: "your-own", provider } });
10
+ setConfig("services", { "web-fetch": { provider } });
11
11
  }
12
12
 
13
13
  mock.module("../../../security/secure-keys.js", () => ({
@@ -212,6 +212,31 @@ describe("web_fetch activityMetadata", () => {
212
212
  expect(result.status ?? "").not.toContain("JavaScript-rendered");
213
213
  });
214
214
 
215
+ test("does not flag mayRequireJavaScript for markup-heavy pages that still yield substantial text", async () => {
216
+ // Mirrors a server-rendered GitHub PR page: <5% text-to-byte ratio, yet
217
+ // the extraction is complete — thousands of chars of real content.
218
+ const paragraph =
219
+ "<p>" +
220
+ "The quick brown fox jumps over the lazy dog. ".repeat(140) +
221
+ "</p>";
222
+ const scriptPayload = `var x = ${JSON.stringify("a".repeat(200_000))};`;
223
+ const body =
224
+ "<!doctype html><html><head><title>Article</title></head>" +
225
+ `<body>${paragraph}<script>${scriptPayload}</script></body></html>`;
226
+ globalThis.fetch = (async () =>
227
+ new Response(body, {
228
+ status: 200,
229
+ headers: { "content-type": "text/html; charset=utf-8" },
230
+ })) as unknown as typeof globalThis.fetch;
231
+
232
+ const result = await executeWithMockFetch({ url: "https://example.com/" });
233
+ expect(result.isError).toBe(false);
234
+
235
+ const meta = result.activityMetadata?.webFetch;
236
+ expect(meta?.mayRequireJavaScript).toBeUndefined();
237
+ expect(result.status ?? "").not.toContain("JavaScript-rendered");
238
+ });
239
+
215
240
  test("populates metadata for blocked private-network targets", async () => {
216
241
  const result = await executeWithMockFetch({
217
242
  url: "http://127.0.0.1/admin",
@@ -945,10 +945,14 @@ export async function executeWebFetch(
945
945
  }
946
946
  // Detect likely JS-rendered SPAs: text is absolutely tiny, or a non-trivial
947
947
  // HTML payload compresses to almost nothing (a shell page whose meaningful
948
- // content is painted after fetch + document.body rewrite).
948
+ // content is painted after fetch + document.body rewrite). The ratio arm
949
+ // also requires a small absolute yield — markup-heavy pages (e.g. GitHub)
950
+ // extract at <5% while still producing thousands of chars of complete text.
949
951
  const lowAbsolute = processed.length < 200;
950
952
  const lowRatio =
951
- body.bytesRead >= 10_000 && processed.length / body.bytesRead < 0.05;
953
+ processed.length < 5_000 &&
954
+ body.bytesRead >= 10_000 &&
955
+ processed.length / body.bytesRead < 0.05;
952
956
  const mayRequireJavaScript = html && !rawMode && (lowAbsolute || lowRatio);
953
957
  if (mayRequireJavaScript) {
954
958
  const pct =
@@ -3,6 +3,7 @@ import { randomUUID } from "node:crypto";
3
3
  import { mkdirSync, rmSync, writeFileSync } from "node:fs";
4
4
  import { join, resolve } from "node:path";
5
5
 
6
+ import { conversationRevealNonce } from "../../runtime/reveal-nonce.js";
6
7
  import { computeSkillVersionHash } from "../../skills/version-hash.js";
7
8
  import { safeStringSlice } from "../../util/unicode.js";
8
9
  import { buildSanitizedEnv } from "../terminal/safe-env.js";
@@ -148,6 +149,8 @@ function spawnRunner(
148
149
  conversationId: context.conversationId,
149
150
  });
150
151
  env.__CONVERSATION_ID = context.conversationId;
152
+ // Secret binding for reveal-derived chat authority — see reveal-nonce.ts.
153
+ env.__REVEAL_NONCE = conversationRevealNonce(context.conversationId);
151
154
 
152
155
  const child = spawn(wrapped.command, wrapped.args, {
153
156
  cwd: runDir,
@@ -255,7 +258,7 @@ function parseSkillResult(
255
258
  let searchFrom = stdout.length;
256
259
  while (searchFrom > 0) {
257
260
  const markerIdx = stdout.lastIndexOf("__skill_result", searchFrom - 1);
258
- if (markerIdx === -1) break;
261
+ if (markerIdx === -1) {break;}
259
262
 
260
263
  const lineStart = stdout.lastIndexOf("\n", markerIdx) + 1;
261
264
  const lineEnd = stdout.indexOf("\n", markerIdx);
@@ -279,7 +282,7 @@ function parseSkillResult(
279
282
  searchFrom = stdout.length;
280
283
  while (searchFrom > 0) {
281
284
  const markerIdx = stdout.lastIndexOf("__skill_error", searchFrom - 1);
282
- if (markerIdx === -1) break;
285
+ if (markerIdx === -1) {break;}
283
286
 
284
287
  const lineStart = stdout.lastIndexOf("\n", markerIdx) + 1;
285
288
  const lineEnd = stdout.indexOf("\n", markerIdx);
@@ -153,20 +153,16 @@ export async function executeSubagentSpawn(
153
153
  // profile of the call site that invoked us. That last fallback is what makes
154
154
  // a subagent match its invoker when the invoking turn ran purely on its
155
155
  // call-site default — the workspace `activeProfile` for `mainAgent`, or the
156
- // call site's own catalog default (e.g. `cost-optimized`) for a
157
- // heartbeat/background invoker. `resolveDefaultProfileKey` does not reflect a
158
- // static `llm.callSites.<callSite>` profile override (only the
159
- // activeProfile-for-mainAgent path plus the catalog default), a narrow gap
160
- // that only surfaces with no `activeProfile` set and a hand-tuned call-site
161
- // profile.
156
+ // call site's own winning default (a `llm.callSites.<callSite>` pin or the
157
+ // catalog intent, e.g. `cost-optimized`) for a heartbeat/background
158
+ // invoker (`resolveDefaultProfileKey` runs the same winner selection as
159
+ // dispatch, minus a per-turn override).
162
160
  //
163
161
  // An explicit `llm.callSites.subagentSpawn` profile must still win over
164
162
  // the invoker-default tier: that tier is a matching heuristic, not a user
165
- // choice, and any override outranks the call-site pin under
166
- // override-or-default resolution — so the heuristic is only forwarded
167
- // when no explicit pin exists. (Under the legacy cascade a non-forced
168
- // override already lost to the pin, so this guard is behavior-identical
169
- // there.) An explicit `inference_profile` argument keeps
163
+ // choice, and any override outranks the call-site pin under single-winner
164
+ // resolution — so the heuristic is only forwarded when no explicit pin
165
+ // exists. An explicit `inference_profile` argument keeps
170
166
  // `forceOverrideProfile` and wins outright; the row read short-circuits
171
167
  // to `undefined` for the background subagent conversation and for tool
172
168
  // calls outside an agent-loop turn.
@@ -6,6 +6,7 @@ import type { BackgroundToolCompleted } from "../../daemon/message-types/backgro
6
6
  import { RiskLevel } from "../../permissions/types.js";
7
7
  import { wakeAgentForOpportunity } from "../../runtime/agent-wake.js";
8
8
  import { broadcastMessage } from "../../runtime/assistant-event-hub.js";
9
+ import { conversationRevealNonce } from "../../runtime/reveal-nonce.js";
9
10
  import { redactSecrets } from "../../security/secret-scanner.js";
10
11
  import { getLogger } from "../../util/logger.js";
11
12
  import { getDataDir } from "../../util/platform.js";
@@ -271,6 +272,8 @@ export const shellTool = {
271
272
 
272
273
  const env = buildSanitizedEnv();
273
274
  env.__CONVERSATION_ID = context.conversationId;
275
+ // Secret binding for reveal-derived chat authority — see reveal-nonce.ts.
276
+ env.__REVEAL_NONCE = conversationRevealNonce(context.conversationId);
274
277
  // Surface the resolving model to assistant CLI commands so they can tailor
275
278
  // remediation guidance for weak open models (see isWeakOpenModel).
276
279
  if (context.attribution?.resolvedModel) {
@@ -337,7 +340,7 @@ export const shellTool = {
337
340
  let completed = false;
338
341
 
339
342
  child.on("close", (code, signal) => {
340
- if (completed) return;
343
+ if (completed) {return;}
341
344
  completed = true;
342
345
  clearTimeout(timer);
343
346
  removeBackgroundTool(bgId);
@@ -427,7 +430,7 @@ export const shellTool = {
427
430
  });
428
431
 
429
432
  child.on("error", (err) => {
430
- if (completed) return;
433
+ if (completed) {return;}
431
434
  completed = true;
432
435
  clearTimeout(timer);
433
436
  removeBackgroundTool(bgId);
@@ -7,7 +7,6 @@
7
7
  */
8
8
 
9
9
  import { recallTool, rememberTool } from "../plugins/defaults/memory/tools.js";
10
- import { appOpenTool } from "./apps/definitions.js";
11
10
  import { askQuestionTool } from "./ask-question/ask-question-tool.js";
12
11
  import { fileEditTool } from "./filesystem/edit.js";
13
12
  import { fileListTool } from "./filesystem/list.js";
@@ -69,5 +68,4 @@ export const explicitTools: ToolDefinition[] = [
69
68
  uiShowTool,
70
69
  uiUpdateTool,
71
70
  uiDismissTool,
72
- appOpenTool,
73
71
  ];