@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
@@ -2,12 +2,12 @@
2
2
  * Regression test for the subagent provider routing fix.
3
3
  *
4
4
  * Before the fix, `SubagentManager.spawn()` constructed the Conversation with
5
- * `getProvider(appConfig.llm.default.provider)` directly, which meant per-call
6
- * `llm.callSites.subagentSpawn.provider` overrides only changed the request
7
- * *metadata* the downstream client saw — the actual HTTP transport still
8
- * belonged to `llm.default.provider`. After the fix, the provider is wrapped
9
- * in `CallSiteRoutingProvider`, which consults the resolver per call and
10
- * routes to the resolved provider's transport when it differs from the
5
+ * a `getProvider()` lookup of the default provider directly, which meant
6
+ * per-call `llm.callSites.subagentSpawn.provider` overrides only changed the
7
+ * request *metadata* the downstream client saw — the actual HTTP transport
8
+ * still belonged to the default provider. After the fix, the provider is
9
+ * wrapped in `CallSiteRoutingProvider`, which consults the resolver per call
10
+ * and routes to the resolved provider's transport when it differs from the
11
11
  * default.
12
12
  *
13
13
  * This test stubs the `Conversation` constructor and the provider registry
@@ -15,19 +15,11 @@
15
15
  * `Conversation`, then verify it's a `CallSiteRoutingProvider` that selects
16
16
  * the right transport for the `subagentSpawn` callSite.
17
17
  */
18
- import { beforeAll, describe, expect, mock, test } from "bun:test";
19
-
20
- import { setOverridesForTesting } from "./feature-flag-test-helpers.js";
21
- import { setConfig } from "./helpers/set-config.js";
22
-
23
- // Legacy-shaped fixtures (llm.default-centric resolution): pinned to the
24
- // flag-off cascade. Override-or-default (flag-on) semantics are pinned by
25
- // llm-resolver-override-or-default.test.ts and its companion suites.
26
- beforeAll(() => {
27
- setOverridesForTesting({ "override-or-default-resolution": false });
28
- });
18
+ import { afterAll, describe, expect, mock, test } from "bun:test";
29
19
 
30
20
  import type { ServerMessage } from "../daemon/message-protocol.js";
21
+ import { getProvider as getProviderImport } from "../providers/registry.js";
22
+ import { setConfig } from "./helpers/set-config.js";
31
23
 
32
24
  // Capture the provider passed to Conversation.
33
25
  let capturedProvider: unknown = undefined;
@@ -126,15 +118,27 @@ mock.module("../prompts/system-prompt.js", () => ({
126
118
  }));
127
119
 
128
120
  // Provider registry + connection resolver — routing goes through
129
- // `provider_connection` exclusively. `getProvider` is kept here purely
130
- // because the registry module still exports it; the production code under
131
- // test does not call it.
121
+ // `provider_connection` exclusively. `getProvider` throws while this file's
122
+ // tests run (the production code under test must not call it), and delegates
123
+ // to the real registry afterwards: `mock.module` patches persist for the rest
124
+ // of the `bun test` process, and later test files (e.g. the platform-proxy
125
+ // integration suite) call the real `getProvider`. The real function is
126
+ // snapshotted before `mock.module` rebinds the import.
132
127
  const anthropicStub = { name: "anthropic" };
133
128
  const openaiStub = { name: "openai" };
134
129
 
130
+ const realGetProvider = getProviderImport;
131
+ let legacyGetProviderGuardArmed = true;
132
+ afterAll(() => {
133
+ legacyGetProviderGuardArmed = false;
134
+ });
135
+
135
136
  mock.module("../providers/registry.js", () => ({
136
137
  getProvider: (name: string) => {
137
- throw new Error(`legacy getProvider should not be called: ${name}`);
138
+ if (legacyGetProviderGuardArmed) {
139
+ throw new Error(`legacy getProvider should not be called: ${name}`);
140
+ }
141
+ return realGetProvider(name);
138
142
  },
139
143
  resolveProviderFromConnection: async (connection: { name: string }) => {
140
144
  if (connection.name === "anthropic-conn") {
@@ -189,19 +193,36 @@ function setLlmConfig(raw: unknown): void {
189
193
  setConfig("llm", raw);
190
194
  }
191
195
 
192
- describe("SubagentManager — provider call-site routing", () => {
193
- test("wraps the default provider in CallSiteRoutingProvider", async () => {
194
- setLlmConfig({
195
- default: {
196
+ /**
197
+ * Base LLM fixture: the workspace's default identity is a complete
198
+ * (provider + model) active profile carrying the `provider_connection` —
199
+ * `activeProfile` wins mainAgent resolution, and connections can only be
200
+ * declared on profiles (see the note on the second test below).
201
+ */
202
+ function makeLlmFixture(
203
+ extras: {
204
+ profiles?: Record<string, unknown>;
205
+ callSites?: Record<string, unknown>;
206
+ } = {},
207
+ ): Record<string, unknown> {
208
+ return {
209
+ profiles: {
210
+ primary: {
211
+ source: "user",
196
212
  provider: "anthropic",
197
213
  provider_connection: "anthropic-conn",
198
214
  model: "claude-opus-4-7",
199
215
  },
200
- profiles: {
201
- // Disable the catalog default so resolution lands on llm.default.
202
- balanced: { source: "managed", status: "disabled" },
203
- },
204
- });
216
+ ...extras.profiles,
217
+ },
218
+ activeProfile: "primary",
219
+ ...(extras.callSites ? { callSites: extras.callSites } : {}),
220
+ };
221
+ }
222
+
223
+ describe("SubagentManager — provider call-site routing", () => {
224
+ test("wraps the default provider in CallSiteRoutingProvider", async () => {
225
+ setLlmConfig(makeLlmFixture());
205
226
 
206
227
  capturedProvider = undefined;
207
228
  const manager = new SubagentManager();
@@ -224,25 +245,20 @@ describe("SubagentManager — provider call-site routing", () => {
224
245
  // entry would be silently stripped by Zod. The correct shape for an
225
246
  // alternate-provider call-site override is a profile reference, defined
226
247
  // here as `altOpenai`.
227
- setLlmConfig({
228
- default: {
229
- provider: "anthropic",
230
- provider_connection: "anthropic-conn",
231
- model: "claude-opus-4-7",
232
- },
233
- profiles: {
234
- // Disable the catalog default so resolution lands on llm.default.
235
- balanced: { source: "managed", status: "disabled" },
236
- altOpenai: {
237
- provider: "openai",
238
- provider_connection: "openai-conn",
239
- model: "gpt-5.4",
248
+ setLlmConfig(
249
+ makeLlmFixture({
250
+ profiles: {
251
+ altOpenai: {
252
+ provider: "openai",
253
+ provider_connection: "openai-conn",
254
+ model: "gpt-5.4",
255
+ },
240
256
  },
241
- },
242
- callSites: {
243
- subagentSpawn: { profile: "altOpenai" },
244
- },
245
- });
257
+ callSites: {
258
+ subagentSpawn: { profile: "altOpenai" },
259
+ },
260
+ }),
261
+ );
246
262
 
247
263
  capturedProvider = undefined;
248
264
  const manager = new SubagentManager();
@@ -266,18 +282,8 @@ describe("SubagentManager — provider call-site routing", () => {
266
282
  });
267
283
 
268
284
  test("falls back to default provider when subagentSpawn callSite is absent", async () => {
269
- setLlmConfig({
270
- default: {
271
- provider: "anthropic",
272
- provider_connection: "anthropic-conn",
273
- model: "claude-opus-4-7",
274
- },
275
- profiles: {
276
- // Disable the catalog default so resolution lands on llm.default.
277
- balanced: { source: "managed", status: "disabled" },
278
- },
279
- // No subagentSpawn override.
280
- });
285
+ // No subagentSpawn override.
286
+ setLlmConfig(makeLlmFixture());
281
287
 
282
288
  capturedProvider = undefined;
283
289
  const manager = new SubagentManager();
@@ -296,17 +302,7 @@ describe("SubagentManager — provider call-site routing", () => {
296
302
  });
297
303
 
298
304
  test("copies parent guardian and auth context into spawned conversation", async () => {
299
- setLlmConfig({
300
- default: {
301
- provider: "anthropic",
302
- provider_connection: "anthropic-conn",
303
- model: "claude-opus-4-7",
304
- },
305
- profiles: {
306
- // Disable the catalog default so resolution lands on llm.default.
307
- balanced: { source: "managed", status: "disabled" },
308
- },
309
- });
305
+ setLlmConfig(makeLlmFixture());
310
306
 
311
307
  const parentTrustContext = {
312
308
  sourceChannel: "vellum",
@@ -351,17 +347,7 @@ describe("SubagentManager — provider call-site routing", () => {
351
347
  });
352
348
 
353
349
  test("copies the parent's plugin scope into the spawned conversation (by value)", async () => {
354
- setLlmConfig({
355
- default: {
356
- provider: "anthropic",
357
- provider_connection: "anthropic-conn",
358
- model: "claude-opus-4-7",
359
- },
360
- profiles: {
361
- // Disable the catalog default so resolution lands on llm.default.
362
- balanced: { source: "managed", status: "disabled" },
363
- },
364
- });
350
+ setLlmConfig(makeLlmFixture());
365
351
 
366
352
  const parentScope = ["caveman", "data"];
367
353
 
@@ -392,17 +378,7 @@ describe("SubagentManager — provider call-site routing", () => {
392
378
  });
393
379
 
394
380
  test("propagates a null (unrestricted) parent scope unchanged", async () => {
395
- setLlmConfig({
396
- default: {
397
- provider: "anthropic",
398
- provider_connection: "anthropic-conn",
399
- model: "claude-opus-4-7",
400
- },
401
- profiles: {
402
- // Disable the catalog default so resolution lands on llm.default.
403
- balanced: { source: "managed", status: "disabled" },
404
- },
405
- });
381
+ setLlmConfig(makeLlmFixture());
406
382
 
407
383
  capturedConversations.length = 0;
408
384
  clearConversations();
@@ -431,7 +407,6 @@ describe("SubagentManager — provider call-site routing", () => {
431
407
  describe("CallSiteRoutingProvider — selectProvider behavior", () => {
432
408
  test("routes to the resolved provider when callSite resolves to a profile with provider_connection", async () => {
433
409
  setLlmConfig({
434
- default: { provider: "anthropic", model: "claude-opus-4-7" },
435
410
  profiles: {
436
411
  altOpenai: {
437
412
  provider: "openai",
@@ -492,7 +467,6 @@ describe("CallSiteRoutingProvider — selectProvider behavior", () => {
492
467
 
493
468
  test("routes to default when no callSite provided", async () => {
494
469
  setLlmConfig({
495
- default: { provider: "anthropic", model: "claude-opus-4-7" },
496
470
  callSites: {
497
471
  subagentSpawn: { provider: "openai", model: "gpt-5.4" },
498
472
  },
@@ -22,6 +22,7 @@ interface FakeManagedSubagent {
22
22
  outputTokens: number;
23
23
  estimatedCost: number;
24
24
  };
25
+ subagentDeniedToolNames: Set<string>;
25
26
  } | null;
26
27
  state: SubagentState;
27
28
  parentSendToClient: (msg: ServerMessage) => void;
@@ -51,6 +52,7 @@ function makeFakeConversation(): NonNullable<
51
52
  messages: [],
52
53
  sendToClient: () => {},
53
54
  usageStats: { inputTokens: 100, outputTokens: 50, estimatedCost: 0.005 },
55
+ subagentDeniedToolNames: new Set<string>(),
54
56
  };
55
57
  }
56
58
 
@@ -51,6 +51,7 @@ interface FakeManagedSubagent {
51
51
  outputTokens: number;
52
52
  estimatedCost: number;
53
53
  };
54
+ subagentDeniedToolNames: Set<string>;
54
55
  } | null;
55
56
  state: SubagentState;
56
57
  parentSendToClient: (msg: ServerMessage) => void;
@@ -80,6 +81,7 @@ function injectFakeSubagent(
80
81
  messages: [],
81
82
  sendToClient: () => {},
82
83
  usageStats: { inputTokens: 100, outputTokens: 50, estimatedCost: 0.005 },
84
+ subagentDeniedToolNames: new Set<string>(),
83
85
  };
84
86
 
85
87
  const internals = asInternals(manager);
@@ -28,6 +28,7 @@ interface FakeManagedSubagent {
28
28
  outputTokens: number;
29
29
  estimatedCost: number;
30
30
  };
31
+ subagentDeniedToolNames: Set<string>;
31
32
  } | null;
32
33
  state: SubagentState;
33
34
  parentSendToClient: (msg: ServerMessage) => void;
@@ -57,6 +58,7 @@ function makeFakeConversation(): NonNullable<
57
58
  messages: [],
58
59
  sendToClient: () => {},
59
60
  usageStats: { inputTokens: 100, outputTokens: 50, estimatedCost: 0.005 },
61
+ subagentDeniedToolNames: new Set<string>(),
60
62
  };
61
63
  }
62
64
 
@@ -51,6 +51,7 @@ interface FakeManagedSubagent {
51
51
  outputTokens: number;
52
52
  estimatedCost: number;
53
53
  };
54
+ subagentDeniedToolNames: Set<string>;
54
55
  } | null;
55
56
  state: SubagentState;
56
57
  parentSendToClient: (msg: ServerMessage) => void;
@@ -84,6 +85,7 @@ function injectFakeSubagent(
84
85
  messages: [],
85
86
  sendToClient: () => {},
86
87
  usageStats: { inputTokens: 100, outputTokens: 50, estimatedCost: 0.005 },
88
+ subagentDeniedToolNames: new Set<string>(),
87
89
  };
88
90
 
89
91
  const internals = asInternals(manager);
@@ -5,6 +5,8 @@ import {
5
5
  SUBAGENT_ROLE_REGISTRY,
6
6
  type SubagentRole,
7
7
  } from "../subagent/index.js";
8
+ import { buildSubagentSystemPrompt } from "../subagent/manager.js";
9
+ import type { SubagentConfig } from "../subagent/types.js";
8
10
 
9
11
  /** All roles defined in the SubagentRole union. */
10
12
  const ALL_ROLES: SubagentRole[] = [
@@ -127,6 +129,41 @@ describe("SUBAGENT_ROLE_REGISTRY", () => {
127
129
  });
128
130
  });
129
131
 
132
+ describe("buildSubagentSystemPrompt — blocked-signal constraint", () => {
133
+ const cfg = (objective: string): SubagentConfig => ({
134
+ id: "sub-1",
135
+ parentConversationId: "conv-1",
136
+ label: "task",
137
+ objective,
138
+ });
139
+
140
+ test("every role's constructed prompt tells it to signal blocked instead of fabricating a result", () => {
141
+ for (const role of ALL_ROLES) {
142
+ const prompt = buildSubagentSystemPrompt(
143
+ cfg("write the results to a CSV file"),
144
+ role,
145
+ );
146
+ expect(prompt).toContain("notify_parent");
147
+ expect(prompt).toContain('urgency "blocked"');
148
+ expect(prompt).toContain("do NOT fabricate");
149
+ }
150
+ });
151
+
152
+ test("the blocked-signal guidance is shared (Constraints), not baked into per-role preambles", () => {
153
+ // A read-only role and a write-capable role both receive it (role-agnostic).
154
+ expect(
155
+ buildSubagentSystemPrompt(cfg("save output"), "researcher"),
156
+ ).toContain("do NOT fabricate");
157
+ expect(buildSubagentSystemPrompt(cfg("save output"), "coder")).toContain(
158
+ "do NOT fabricate",
159
+ );
160
+ // It is NOT duplicated into the researcher role's own preamble.
161
+ expect(
162
+ SUBAGENT_ROLE_REGISTRY.researcher.systemPromptPreamble,
163
+ ).not.toContain("do NOT fabricate");
164
+ });
165
+ });
166
+
130
167
  describe("mergeSkillIds", () => {
131
168
  test("removes duplicates between role and config skill IDs", () => {
132
169
  expect(mergeSkillIds(["a", "b"], ["b", "c"])).toEqual(["a", "b", "c"]);
@@ -56,6 +56,7 @@ let lastPersistedUserMessage: string | undefined;
56
56
  class FakeConversation {
57
57
  messages: Message[];
58
58
  usageStats = { inputTokens: 10, outputTokens: 5, estimatedCost: 0.001 };
59
+ subagentDeniedToolNames = new Set<string>();
59
60
  conversationType = "background";
60
61
  hasSystemPromptOverride = false;
61
62
 
@@ -96,4 +96,54 @@ describe("buildSubagentTerminalMessage", () => {
96
96
  expect(msg).toContain("Error: boom");
97
97
  expect(msg).toContain("Do NOT re-spawn or retry");
98
98
  });
99
+
100
+ test("appends a denied-tools note to an inlined completion", () => {
101
+ const msg = buildSubagentTerminalMessage({
102
+ label: "write-report",
103
+ subagentId: "sa-7",
104
+ isFork: false,
105
+ outcome: "completed",
106
+ silent: false,
107
+ finalText: "Here is what I found.",
108
+ deniedTools: ["file_write"],
109
+ });
110
+
111
+ expect(msg).toContain("Here is what I found.");
112
+ expect(msg).toContain("attempted file_write");
113
+ expect(msg).toContain("does not permit it");
114
+ expect(msg).toContain("re-spawn with a role that includes it");
115
+ expect(msg).toContain("coder");
116
+ });
117
+
118
+ test("appends a pluralized denied-tools note to a read-pointer completion", () => {
119
+ const msg = buildSubagentTerminalMessage({
120
+ label: "empty-write",
121
+ subagentId: "sa-8",
122
+ isFork: false,
123
+ outcome: "completed",
124
+ silent: false,
125
+ finalText: " ",
126
+ deniedTools: ["file_write", "file_edit"],
127
+ });
128
+
129
+ expect(msg).toContain('subagent_read with subagent_id "sa-8"');
130
+ expect(msg).toContain("attempted file_write, file_edit");
131
+ expect(msg).toContain("does not permit them");
132
+ expect(msg).toContain("re-spawn with a role that includes them");
133
+ });
134
+
135
+ test("omits the denied-tools note when none were denied", () => {
136
+ const msg = buildSubagentTerminalMessage({
137
+ label: "clean",
138
+ subagentId: "sa-9",
139
+ isFork: false,
140
+ outcome: "completed",
141
+ silent: false,
142
+ finalText: "All done.",
143
+ deniedTools: [],
144
+ });
145
+
146
+ expect(msg).not.toContain("does not permit");
147
+ expect(msg).not.toContain("re-spawn");
148
+ });
99
149
  });
@@ -52,7 +52,6 @@ mock.module("../tools/browser/browser-screencast.js", () => ({
52
52
  mock.module("../apps/app-store.js", () => ({
53
53
  getApp: mock(() => null),
54
54
  getAppDirPath: mock(() => "/tmp/test-apps/dummy"),
55
- isMultifileApp: mock(() => false),
56
55
  getAppsDir: mock(() => "/tmp/test-apps"),
57
56
  resolveAppIdByDirName: mock(() => null),
58
57
  resolveAppIdFromPath: mock(() => null),
@@ -225,13 +224,13 @@ describe("createResolveToolsCallback — subagentToolGateMode", () => {
225
224
 
226
225
  describe("createResolveToolsCallback — toolContextPin", () => {
227
226
  // Core defs spanning each client-gated family: host proxy (host_bash),
228
- // dynamic UI (ui_show), connected-client (app_open), client-platform
227
+ // dynamic UI (ui_show), connected-client (ask_question), client-platform
229
228
  // (request_system_permission), plus the always-on remember.
230
229
  const CLIENT_GATED_DEFS = [
231
230
  makeToolDef("remember"),
232
231
  makeToolDef("host_bash"),
233
232
  makeToolDef("ui_show"),
234
- makeToolDef("app_open"),
233
+ makeToolDef("ask_question"),
235
234
  makeToolDef("request_system_permission"),
236
235
  ];
237
236
 
@@ -271,8 +270,9 @@ describe("createResolveToolsCallback — toolContextPin", () => {
271
270
  .sort();
272
271
  // request_system_permission stays out: it keys on
273
272
  // channelCapabilities.clientOS, which desktop HTTP live turns never set
274
- // either — exclusion IS parity there.
275
- expect(names).toEqual(["app_open", "host_bash", "remember", "ui_show"]);
273
+ // either — exclusion IS parity there. ask_question stays IN: its
274
+ // macOS-specific hide also keys on clientOS, which the pin leaves unset.
275
+ expect(names).toEqual(["ask_question", "host_bash", "remember", "ui_show"]);
276
276
  });
277
277
 
278
278
  test("the pin REPLACES the live context — absent pin fields do not fall through", () => {
@@ -365,6 +365,79 @@ describe("createToolExecutor — execution-layer allowlist gate", () => {
365
365
  expect(calls[0]!.name).toBe("remember");
366
366
  });
367
367
 
368
+ test("execution mode: a denied call is recorded on subagentDeniedToolNames", async () => {
369
+ const denied = new Set<string>();
370
+ const { executor } = makeCapturingExecutor();
371
+ const toolFn = makeToolFn(
372
+ executor,
373
+ makeSetupCtx({
374
+ subagentAllowedTools: new Set(["remember"]),
375
+ subagentToolGateMode: "execution",
376
+ subagentDeniedToolNames: denied,
377
+ }),
378
+ );
379
+
380
+ await toolFn("bash", { command: "echo hi" });
381
+
382
+ expect([...denied]).toEqual(["bash"]);
383
+ });
384
+
385
+ test("skill_execute records the resolved inner tool, not the wrapper", async () => {
386
+ const denied = new Set<string>();
387
+ const { executor } = makeCapturingExecutor();
388
+ const toolFn = makeToolFn(
389
+ executor,
390
+ makeSetupCtx({
391
+ subagentAllowedTools: new Set(["remember"]),
392
+ subagentToolGateMode: "execution",
393
+ subagentDeniedToolNames: denied,
394
+ }),
395
+ );
396
+
397
+ await toolFn("skill_execute", {
398
+ tool: "bash",
399
+ input: { command: "echo hi" },
400
+ });
401
+
402
+ expect(denied.has("bash")).toBe(true);
403
+ expect(denied.has("skill_execute")).toBe(false);
404
+ });
405
+
406
+ test("records a non-allowlisted attempt even in wire gate mode (observation only)", async () => {
407
+ const denied = new Set<string>();
408
+ const { executor } = makeCapturingExecutor();
409
+ const toolFn = makeToolFn(
410
+ executor,
411
+ // No subagentToolGateMode → "wire": the executor does not reject here, but
412
+ // the out-of-allowlist attempt is still recorded for parent reporting.
413
+ makeSetupCtx({
414
+ subagentAllowedTools: new Set(["remember"]),
415
+ subagentDeniedToolNames: denied,
416
+ }),
417
+ );
418
+
419
+ await toolFn("bash", { command: "echo hi" });
420
+
421
+ expect(denied.has("bash")).toBe(true);
422
+ });
423
+
424
+ test("an allowlisted call records nothing", async () => {
425
+ const denied = new Set<string>();
426
+ const { executor } = makeCapturingExecutor();
427
+ const toolFn = makeToolFn(
428
+ executor,
429
+ makeSetupCtx({
430
+ subagentAllowedTools: new Set(["remember"]),
431
+ subagentToolGateMode: "execution",
432
+ subagentDeniedToolNames: denied,
433
+ }),
434
+ );
435
+
436
+ await toolFn("remember", { content: "a fact" });
437
+
438
+ expect([...denied]).toEqual([]);
439
+ });
440
+
368
441
  test("execution mode: skill_execute gates the resolved inner tool, executor never invoked", async () => {
369
442
  const { executor, calls } = makeCapturingExecutor();
370
443
  const toolFn = makeToolFn(
@@ -22,6 +22,7 @@
22
22
 
23
23
  import { beforeEach, describe, expect, test } from "bun:test";
24
24
 
25
+ import { INTERNAL_NUDGE_OUTPUT_SUPPRESSION } from "../plugin-api/constants.js";
25
26
  import type {
26
27
  PluginLogger,
27
28
  PostModelCallContext,
@@ -152,6 +153,24 @@ beforeEach(() => {
152
153
 
153
154
  // ─── Nudges when a progress surface is left open ──────────────────────────────
154
155
 
156
+ describe("surface-completion-nudge — internal-notice suppression", () => {
157
+ test("appends the shared suppression clause inside the notice wrapper", () => {
158
+ expect(SURFACE_COMPLETION_NUDGE_TEXT).toContain(
159
+ INTERNAL_NUDGE_OUTPUT_SUPPRESSION,
160
+ );
161
+ expect(SURFACE_COMPLETION_NUDGE_TEXT.startsWith("<system_notice>")).toBe(
162
+ true,
163
+ );
164
+ expect(SURFACE_COMPLETION_NUDGE_TEXT.endsWith("</system_notice>")).toBe(
165
+ true,
166
+ );
167
+ // The surface-advance + final-reply instruction still leads.
168
+ expect(SURFACE_COMPLETION_NUDGE_TEXT).toContain(
169
+ "Then give your final reply",
170
+ );
171
+ });
172
+ });
173
+
155
174
  describe("surface-completion-nudge — nudges on a dangling progress surface", () => {
156
175
  test("task_progress card shown and never completed → continue with nudge", async () => {
157
176
  const shown = showSurface(taskProgressShow("in_progress"));