@vellumai/assistant 0.11.3 → 0.11.4-dev.202608190019.b94dbf2

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 (596) hide show
  1. package/AGENTS.md +9 -3
  2. package/ARCHITECTURE.md +32 -10
  3. package/docs/architecture/memory.md +43 -5
  4. package/docs/architecture/security.md +96 -152
  5. package/docs/architecture/turn-actor.md +70 -0
  6. package/docs/browser-use-architecture-phase2.md +128 -56
  7. package/docs/flux-turn-detection-spike.md +248 -0
  8. package/docs/guardian-request-flow.md +35 -0
  9. package/docs/stt-provider-onboarding.md +3 -1
  10. package/knip.json +3 -0
  11. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/package.json +1 -0
  12. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/__tests__/url-normalization.test.ts +135 -0
  13. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/channels.ts +11 -0
  14. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
  15. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +60 -0
  16. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/url-normalization.ts +107 -0
  17. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/package.json +1 -0
  18. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/url-normalization.test.ts +135 -0
  19. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/channels.ts +11 -0
  20. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
  21. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +60 -0
  22. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/url-normalization.ts +107 -0
  23. package/node_modules/@vellumai/gateway-client/src/admission-policy-contract.ts +34 -0
  24. package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +166 -1
  25. package/node_modules/@vellumai/gateway-client/src/index.ts +2 -0
  26. package/node_modules/@vellumai/service-contracts/package.json +1 -0
  27. package/node_modules/@vellumai/service-contracts/src/__tests__/url-normalization.test.ts +135 -0
  28. package/node_modules/@vellumai/service-contracts/src/channels.ts +11 -0
  29. package/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
  30. package/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +60 -0
  31. package/node_modules/@vellumai/service-contracts/src/url-normalization.ts +107 -0
  32. package/openapi.yaml +289 -113
  33. package/package.json +1 -1
  34. package/scripts/voice-ttft-spike.ts +3 -3
  35. package/scripts/write-plugin-api-shim.ts +10 -0
  36. package/src/__tests__/app-compiler.test.ts +38 -3
  37. package/src/__tests__/app-control-flow.test.ts +1 -0
  38. package/src/__tests__/approval-routes-http.test.ts +2 -2
  39. package/src/__tests__/assistant-feature-flag-guard.test.ts +25 -3
  40. package/src/__tests__/attachments-store.test.ts +21 -12
  41. package/src/__tests__/byok-default-profile-ensure.test.ts +17 -0
  42. package/src/__tests__/call-setup-flow-name-capture.test.ts +0 -1
  43. package/src/__tests__/call-site-routing-provider.test.ts +1 -1
  44. package/src/__tests__/channel-availability-routes.test.ts +14 -1
  45. package/src/__tests__/channel-capabilities-dedupe.test.ts +214 -0
  46. package/src/__tests__/channel-delivery-store.test.ts +14 -14
  47. package/src/__tests__/channel-inbound-disk-pressure.test.ts +4 -4
  48. package/src/__tests__/channel-setup-panel-ack.test.ts +1 -1
  49. package/src/__tests__/checker.test.ts +20 -314
  50. package/src/__tests__/cli-memory-v2-reembed-skills.test.ts +6 -2
  51. package/src/__tests__/compaction-events.test.ts +8 -10
  52. package/src/__tests__/config-loader-backfill.test.ts +3 -3
  53. package/src/__tests__/config-schema.test.ts +25 -10
  54. package/src/__tests__/context-search-memory-v2-source.test.ts +0 -1
  55. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +8 -11
  56. package/src/__tests__/conversation-agent-loop-overflow.test.ts +8 -11
  57. package/src/__tests__/conversation-agent-loop.test.ts +32 -21
  58. package/src/__tests__/conversation-attachments.test.ts +0 -1
  59. package/src/__tests__/conversation-attention-store.test.ts +63 -0
  60. package/src/__tests__/conversation-confirmation-signals.test.ts +112 -0
  61. package/src/__tests__/conversation-delete-schedule-cleanup.test.ts +0 -4
  62. package/src/__tests__/conversation-fork-crud.test.ts +69 -0
  63. package/src/__tests__/conversation-fork-referential.test.ts +67 -0
  64. package/src/__tests__/conversation-fork-retrospective.test.ts +24 -0
  65. package/src/__tests__/conversation-load-history-repair.test.ts +209 -0
  66. package/src/__tests__/conversation-notifiers-provenance.test.ts +59 -0
  67. package/src/__tests__/conversation-queue.test.ts +154 -4
  68. package/src/__tests__/conversation-routes-disk-view.test.ts +1 -1
  69. package/src/__tests__/conversation-routes-enabled-plugins.test.ts +1 -1
  70. package/src/__tests__/conversation-routes-guardian-reply.test.ts +9 -9
  71. package/src/__tests__/conversation-routes-hidden-queue.test.ts +1 -1
  72. package/src/__tests__/conversation-routes-slash-commands.test.ts +1 -1
  73. package/src/__tests__/conversation-runtime-assembly.test.ts +187 -102
  74. package/src/__tests__/conversation-runtime-workspace.test.ts +14 -10
  75. package/src/__tests__/conversation-slash-queue.test.ts +3 -0
  76. package/src/__tests__/conversation-surfaces-action-delivery.test.ts +1 -0
  77. package/src/__tests__/conversation-surfaces-activation-emit.test.ts +1 -0
  78. package/src/__tests__/conversation-surfaces-app-control.test.ts +1 -0
  79. package/src/__tests__/conversation-surfaces-app-open.test.ts +1 -1
  80. package/src/__tests__/conversation-surfaces-data-persist.test.ts +1 -1
  81. package/src/__tests__/conversation-surfaces-history-restored-completion.test.ts +21 -14
  82. package/src/__tests__/conversation-surfaces-queued-emit.test.ts +1 -0
  83. package/src/__tests__/conversation-surfaces-standalone-payloads.test.ts +1 -0
  84. package/src/__tests__/conversation-surfaces-standalone.test.ts +1 -0
  85. package/src/__tests__/conversation-surfaces-state-update.test.ts +1 -1
  86. package/src/__tests__/conversation-surfaces-table-action.test.ts +1 -1
  87. package/src/__tests__/conversation-surfaces-task-progress.test.ts +1 -1
  88. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +1 -1
  89. package/src/__tests__/conversation-tool-setup-attribution.test.ts +1 -1
  90. package/src/__tests__/credential-prompt-route.test.ts +7 -10
  91. package/src/__tests__/credential-security-invariants.test.ts +1 -0
  92. package/src/__tests__/cu-unified-flow.test.ts +1 -0
  93. package/src/__tests__/custom-profile-ensure.test.ts +5 -1
  94. package/src/__tests__/default-plugin-names-guard.test.ts +12 -1
  95. package/src/__tests__/discord-access-request-privacy.test.ts +5 -1
  96. package/src/__tests__/discord-requester-notice-privacy.test.ts +3 -3
  97. package/src/__tests__/document-append-idempotency.test.ts +233 -0
  98. package/src/__tests__/document-sync-tags.test.ts +0 -75
  99. package/src/__tests__/dynamic-skill-background-guard.test.ts +0 -2
  100. package/src/__tests__/edit-propagation.test.ts +107 -11
  101. package/src/__tests__/file-ops-service.test.ts +163 -30
  102. package/src/__tests__/filesystem-tools.test.ts +23 -24
  103. package/src/__tests__/gateway-only-guard.test.ts +2 -5
  104. package/src/__tests__/helpers/gateway-classify-mock.ts +27 -6
  105. package/src/__tests__/helpers/mock-actor-context.ts +49 -0
  106. package/src/__tests__/helpers/mock-conversation.ts +13 -1
  107. package/src/__tests__/host-file-read-tool.test.ts +16 -19
  108. package/src/__tests__/http-user-message-parity.test.ts +1 -1
  109. package/src/__tests__/init-feature-flag-overrides.test.ts +49 -0
  110. package/src/__tests__/injector-chain.test.ts +63 -41
  111. package/src/__tests__/injector-disk-pressure.test.ts +11 -23
  112. package/src/__tests__/inline-skill-load-permissions.test.ts +47 -36
  113. package/src/__tests__/llm-context-resolution.test.ts +73 -1
  114. package/src/__tests__/llm-schema.test.ts +5 -2
  115. package/src/__tests__/managed-skill-lifecycle.test.ts +7 -0
  116. package/src/__tests__/mcp-list-plugin-servers.test.ts +250 -0
  117. package/src/__tests__/media-generate-image.test.ts +131 -21
  118. package/src/__tests__/memory-retrieval-hook.test.ts +100 -7
  119. package/src/__tests__/messages-read-boundary-guard.test.ts +134 -0
  120. package/src/__tests__/mtime-cache.test.ts +3 -1
  121. package/src/__tests__/non-member-access-request.test.ts +0 -20
  122. package/src/__tests__/outbound-slack-persistence.test.ts +40 -1
  123. package/src/__tests__/platform-bash-auto-approve.test.ts +0 -4
  124. package/src/__tests__/plugin-api-store-credential.test.ts +261 -0
  125. package/src/__tests__/plugin-api-webhook-url.test.ts +10 -7
  126. package/src/__tests__/plugin-disabled-state.test.ts +2 -0
  127. package/src/__tests__/plugin-effective-enabled-set.test.ts +55 -3
  128. package/src/__tests__/plugin-execution-context.test.ts +73 -0
  129. package/src/__tests__/plugin-import-boundary-guard.test.ts +5 -1
  130. package/src/__tests__/plugin-import-boundary-reverse-guard.test.ts +7 -4
  131. package/src/__tests__/plugin-secret-pattern-contribution.test.ts +1 -1
  132. package/src/__tests__/post-compaction-reinjection-idempotency.test.ts +14 -7
  133. package/src/__tests__/provider-commit-message-generator.test.ts +20 -0
  134. package/src/__tests__/proxy-approval-callback.test.ts +1 -0
  135. package/src/__tests__/qdrant-manager.test.ts +14 -1
  136. package/src/__tests__/reaction-persistence.test.ts +200 -7
  137. package/src/__tests__/require-fresh-approval.test.ts +0 -4
  138. package/src/__tests__/risk-classification-boundary-guard.test.ts +115 -0
  139. package/src/__tests__/run-conversation-turn-persistence.test.ts +460 -105
  140. package/src/__tests__/run-due-schedules.test.ts +21 -0
  141. package/src/__tests__/scaffold-managed-skill-tool.test.ts +187 -18
  142. package/src/__tests__/schedule-routes.test.ts +23 -0
  143. package/src/__tests__/schedule-store.test.ts +17 -0
  144. package/src/__tests__/scoped-approval-grants.test.ts +11 -6
  145. package/src/__tests__/secret-ingress-channel.test.ts +0 -1
  146. package/src/__tests__/secret-ingress-http.test.ts +1 -1
  147. package/src/__tests__/send-endpoint-busy.test.ts +3 -3
  148. package/src/__tests__/skills.test.ts +32 -0
  149. package/src/__tests__/slack-edit-ordering-characterization.test.ts +0 -1
  150. package/src/__tests__/starter-task-flow.test.ts +5 -4
  151. package/src/__tests__/subagent-call-site-routing.test.ts +31 -19
  152. package/src/__tests__/subagent-fork-prompt-role.test.ts +1 -1
  153. package/src/__tests__/subagent-spawn-and-await.test.ts +18 -17
  154. package/src/__tests__/subagent-tool-gate-mode.test.ts +1 -1
  155. package/src/__tests__/subagent-tools.test.ts +81 -101
  156. package/src/__tests__/surface-completion-in-flight-snapshot.test.ts +1 -0
  157. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +1 -1
  158. package/src/__tests__/tool-executor-lifecycle-events.test.ts +132 -5
  159. package/src/__tests__/tool-executor.test.ts +68 -39
  160. package/src/__tests__/turn-events-store.test.ts +43 -0
  161. package/src/__tests__/ui-choice-copy-surfaces.test.ts +1 -1
  162. package/src/__tests__/ui-shape-teaching.test.ts +33 -0
  163. package/src/__tests__/ui-visual-surface.test.ts +1 -1
  164. package/src/__tests__/ui-voice-picker-surface.test.ts +128 -0
  165. package/src/__tests__/ui-work-result-surface.test.ts +1 -1
  166. package/src/__tests__/user-plugin-loader.test.ts +3 -1
  167. package/src/__tests__/verification-control-plane-policy.test.ts +0 -2
  168. package/src/__tests__/visible-app-context.test.ts +16 -9
  169. package/src/__tests__/voice-config-update.test.ts +40 -0
  170. package/src/__tests__/voice-scoped-grant-consumer.test.ts +5 -3
  171. package/src/__tests__/voice-session-bridge.test.ts +85 -29
  172. package/src/__tests__/worker-entrypoint-guards.test.ts +54 -0
  173. package/src/__tests__/worker-plugin-surface.test.ts +77 -0
  174. package/src/__tests__/workspace-migration-142-consolidate-voice-front-door.test.ts +158 -0
  175. package/src/__tests__/workspace-migration-143-repair-deprecated-codex-model-id.test.ts +133 -0
  176. package/src/__tests__/workspace-migration-144-convert-stranded-subscription-openai-profiles.test.ts +316 -0
  177. package/src/__tests__/workspace-migration-145-collapse-profile-bindings-to-entries.test.ts +325 -0
  178. package/src/__tests__/workspace-migration-146-repair-retired-fireworks-deepseek-flash-model-id.test.ts +235 -0
  179. package/src/acp/__tests__/acp-claude-oauth.test.ts +10 -2
  180. package/src/acp/__tests__/auth-required.test.ts +161 -0
  181. package/src/acp/acp-claude-oauth.ts +19 -2
  182. package/src/acp/agent-process.test.ts +100 -0
  183. package/src/acp/agent-process.ts +29 -26
  184. package/src/acp/auth-required.ts +102 -0
  185. package/src/acp/session-manager.test.ts +119 -0
  186. package/src/acp/session-manager.ts +76 -3
  187. package/src/api/events/acp-auth-required.ts +55 -0
  188. package/src/api/events/host-file.ts +2 -2
  189. package/src/api/index.ts +7 -0
  190. package/src/api/surfaces.ts +12 -3
  191. package/src/apps/app-store.ts +3 -0
  192. package/src/bundler/package-resolver.ts +2 -30
  193. package/src/calls/__tests__/voice-session-bridge.test.ts +194 -11
  194. package/src/calls/__tests__/voice-triage-escalate.test.ts +102 -0
  195. package/src/calls/call-controller.ts +19 -3
  196. package/src/calls/call-setup-flow.ts +0 -1
  197. package/src/calls/media-stream-stt-session.ts +15 -0
  198. package/src/calls/voice-session-bridge.ts +115 -41
  199. package/src/calls/voice-triage-escalate.ts +105 -2
  200. package/src/channels/__tests__/plugin-channel-declarations.test.ts +161 -0
  201. package/src/channels/config.ts +13 -0
  202. package/src/channels/plugin-channel-declarations.ts +108 -0
  203. package/src/channels/types.ts +30 -0
  204. package/src/cli/AGENTS.md +5 -2
  205. package/src/cli/bundled-modules.ts +29 -0
  206. package/src/cli/commands/credentials.help.ts +2 -2
  207. package/src/cli/commands/db/repair.ts +4 -8
  208. package/src/cli/commands/domain.ts +6 -3
  209. package/src/cli/commands/email.ts +6 -3
  210. package/src/cli/commands/inference-providers.ts +1 -1
  211. package/src/cli/commands/keys.ts +8 -3
  212. package/src/cli/commands/mcp.help.ts +13 -4
  213. package/src/cli/commands/mcp.ts +9 -0
  214. package/src/cli/commands/memory/__tests__/memory-v2.test.ts +57 -7
  215. package/src/cli/commands/memory/__tests__/memory-v3.test.ts +128 -5
  216. package/src/cli/commands/memory/index.help.ts +84 -28
  217. package/src/cli/commands/memory/index.ts +2 -0
  218. package/src/cli/commands/memory/memory-v2.ts +58 -54
  219. package/src/cli/commands/memory/memory-v3.ts +64 -0
  220. package/src/cli/commands/memory/memory-validate.ts +18 -0
  221. package/src/cli/commands/plugins.ts +85 -36
  222. package/src/cli/commands/schedules.ts +35 -1
  223. package/src/cli/commands/stt.help.ts +27 -2
  224. package/src/cli/commands/trust.ts +3 -14
  225. package/src/cli/lib/__tests__/upgrade-plugin.test.ts +39 -0
  226. package/src/cli/lib/bundled-marketplace.json +14 -1
  227. package/src/cli/lib/upgrade-plugin.ts +42 -0
  228. package/src/config/__tests__/balanced-model-experiment.test.ts +278 -0
  229. package/src/config/__tests__/default-profile-catalog.test.ts +34 -2
  230. package/src/config/__tests__/default-provider.test.ts +6 -1
  231. package/src/config/__tests__/profile-materialization.test.ts +75 -19
  232. package/src/config/assistant-feature-flags.ts +36 -15
  233. package/src/config/balanced-model-experiment.ts +35 -0
  234. package/src/config/bundled-skills/acp/SKILL.md +6 -7
  235. package/src/config/bundled-skills/document-editor/SKILL.md +2 -2
  236. package/src/config/bundled-skills/document-editor/TOOLS.json +2 -2
  237. package/src/config/bundled-skills/image-studio/SKILL.md +5 -4
  238. package/src/config/bundled-skills/image-studio/TOOLS.json +1 -1
  239. package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +101 -0
  240. package/src/config/bundled-skills/media-processing/services/preprocess.ts +14 -4
  241. package/src/config/bundled-skills/settings/TOOLS.json +3 -3
  242. package/src/config/bundled-skills/settings/tools/navigate-settings-tab.test.ts +65 -0
  243. package/src/config/bundled-skills/settings/tools/navigate-settings-tab.ts +7 -1
  244. package/src/config/bundled-skills/settings/tools/shared.ts +16 -0
  245. package/src/config/bundled-skills/settings/tools/voice-config-update.ts +19 -1
  246. package/src/config/bundled-skills/skill-management/TOOLS.json +9 -3
  247. package/src/config/bundled-skills/subagent/SKILL.md +17 -12
  248. package/src/config/bundled-skills/subagent/TOOLS.json +4 -4
  249. package/src/config/bundled-skills/transcribe/tools/transcribe-media.test.ts +22 -1
  250. package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +9 -2
  251. package/src/config/call-site-defaults.ts +11 -5
  252. package/src/config/default-profile-catalog.ts +179 -16
  253. package/src/config/default-profile-names.ts +4 -1
  254. package/src/config/default-provider-resolution.ts +4 -0
  255. package/src/config/feature-flag-registry.json +12 -12
  256. package/src/config/llm-context-resolution.ts +11 -3
  257. package/src/config/llm-resolver.ts +28 -1
  258. package/src/config/profile-materialization.ts +70 -22
  259. package/src/config/schemas/__tests__/live-voice.test.ts +107 -4
  260. package/src/config/schemas/call-site-catalog.ts +4 -4
  261. package/src/config/schemas/live-voice.ts +57 -23
  262. package/src/config/schemas/llm.ts +59 -32
  263. package/src/config/schemas/mcp.ts +23 -0
  264. package/src/config/schemas/plugin-updates.ts +6 -2
  265. package/src/config/schemas/stt.ts +1 -0
  266. package/src/config/skills.ts +9 -2
  267. package/src/context/outbound-sanitize.ts +96 -1
  268. package/src/daemon/__tests__/conversation-surfaces-launch.test.ts +1 -1
  269. package/src/daemon/__tests__/plugin-mcp-reconcile.test.ts +82 -0
  270. package/src/daemon/conversation-agent-loop-handlers.ts +15 -10
  271. package/src/daemon/conversation-agent-loop.ts +31 -19
  272. package/src/daemon/conversation-messaging.ts +5 -1
  273. package/src/daemon/conversation-notifiers.ts +20 -10
  274. package/src/daemon/conversation-process.ts +20 -6
  275. package/src/daemon/conversation-runtime-assembly.ts +12 -6
  276. package/src/daemon/conversation-store.ts +14 -4
  277. package/src/daemon/conversation-surfaces.ts +75 -26
  278. package/src/daemon/conversation-tool-setup.ts +3 -6
  279. package/src/daemon/conversation.ts +154 -51
  280. package/src/daemon/doordash-steps.ts +2 -2
  281. package/src/daemon/lifecycle.ts +14 -1
  282. package/src/daemon/mcp-reload-service.ts +36 -6
  283. package/src/daemon/process-message.ts +13 -26
  284. package/src/daemon/providers-setup.ts +6 -3
  285. package/src/daemon/trust-context-types.ts +29 -0
  286. package/src/daemon/trust-context.ts +12 -7
  287. package/src/daemon/wake-conversation-ops.ts +3 -2
  288. package/src/daemon/windows-compiled-entry.ts +4 -0
  289. package/src/documents/document-store.ts +143 -240
  290. package/src/hooks/hook-loader.ts +3 -3
  291. package/src/hooks/registry.ts +103 -45
  292. package/src/hooks/types.ts +5 -0
  293. package/src/inbound/__tests__/oauth-callback-url.test.ts +83 -0
  294. package/src/inbound/oauth-callback-url.ts +61 -0
  295. package/src/ipc/gateway-client.test.ts +1 -1
  296. package/src/ipc/gateway-client.ts +14 -21
  297. package/src/ipc/gateway-flag-listener.ts +17 -3
  298. package/src/live-voice/__tests__/live-voice-agent-turn.test.ts +1 -104
  299. package/src/live-voice/__tests__/live-voice-events.test.ts +7 -8
  300. package/src/live-voice/__tests__/live-voice-flux-turn-end.test.ts +1050 -0
  301. package/src/live-voice/__tests__/live-voice-metrics.test.ts +115 -8
  302. package/src/live-voice/__tests__/live-voice-photo.test.ts +100 -0
  303. package/src/live-voice/__tests__/live-voice-progress.test.ts +60 -194
  304. package/src/live-voice/__tests__/live-voice-stt.test.ts +14 -0
  305. package/src/live-voice/__tests__/live-voice-triage-escalate.test.ts +29 -0
  306. package/src/live-voice/__tests__/live-voice-tts-session.test.ts +0 -483
  307. package/src/live-voice/__tests__/live-voice-vad.test.ts +0 -16
  308. package/src/live-voice/__tests__/progress-narration.test.ts +214 -0
  309. package/src/live-voice/live-voice-archive.ts +2 -0
  310. package/src/live-voice/live-voice-manager.ts +16 -3
  311. package/src/live-voice/live-voice-metrics.ts +57 -32
  312. package/src/live-voice/live-voice-photo.ts +1 -2
  313. package/src/live-voice/live-voice-session.ts +598 -323
  314. package/src/live-voice/progress-narration.ts +277 -0
  315. package/src/live-voice/protocol.ts +21 -1
  316. package/src/live-voice/windows-compiled-live-voice.ts +4 -0
  317. package/src/mcp/__tests__/effective-config.test.ts +238 -0
  318. package/src/mcp/__tests__/mcp-auth-orchestrator.test.ts +0 -1
  319. package/src/mcp/__tests__/mcp-oauth-client-registration.test.ts +200 -0
  320. package/src/mcp/__tests__/mcp-oauth-provider.test.ts +9 -9
  321. package/src/mcp/__tests__/plugin-server-credential-isolation.test.ts +95 -0
  322. package/src/mcp/client.ts +16 -11
  323. package/src/mcp/effective-config.ts +113 -0
  324. package/src/mcp/manager.ts +11 -6
  325. package/src/mcp/mcp-auth-orchestrator.ts +13 -22
  326. package/src/mcp/mcp-oauth-provider.ts +205 -240
  327. package/src/monitoring/__tests__/plugin-auto-update.test.ts +166 -3
  328. package/src/monitoring/control.ts +1 -0
  329. package/src/monitoring/db-integrity-sample.ts +4 -5
  330. package/src/monitoring/plugin-auto-update.ts +128 -24
  331. package/src/notifications/AGENTS.md +2 -0
  332. package/src/notifications/approval-card-data.ts +33 -0
  333. package/src/notifications/signal.ts +1 -0
  334. package/src/permissions/AGENTS.md +16 -0
  335. package/src/permissions/checker.test.ts +75 -122
  336. package/src/permissions/checker.ts +44 -560
  337. package/src/permissions/confirmation-guardian-request.test.ts +15 -11
  338. package/src/permissions/confirmation-guardian-request.ts +2 -2
  339. package/src/permissions/prompter.ts +1 -5
  340. package/src/permissions/question-guardian-request.test.ts +14 -6
  341. package/src/permissions/question-guardian-request.ts +1 -2
  342. package/src/persistence/attachments-store.ts +8 -1
  343. package/src/persistence/bookmark-crud.ts +3 -7
  344. package/src/persistence/conversation-attention-store.ts +16 -45
  345. package/src/persistence/conversation-crud.ts +33 -4
  346. package/src/persistence/conversation-lineage.ts +9 -0
  347. package/src/persistence/conversation-queries.ts +182 -52
  348. package/src/persistence/delivery-crud.ts +150 -36
  349. package/src/persistence/embeddings/qdrant-manager.ts +84 -49
  350. package/src/persistence/external-conversation-store.ts +32 -4
  351. package/src/persistence/llm-request-log-store.ts +4 -10
  352. package/src/persistence/llm-usage-store.ts +8 -3
  353. package/src/persistence/message-reads.test.ts +197 -0
  354. package/src/persistence/message-reads.ts +211 -0
  355. package/src/persistence/migrations/360-add-document-workspace-path.ts +5 -14
  356. package/src/persistence/migrations/366-chatgpt-subscription-row-identity.test.ts +120 -0
  357. package/src/persistence/migrations/366-chatgpt-subscription-row-identity.ts +62 -0
  358. package/src/persistence/real-user-turn-filter.ts +27 -3
  359. package/src/persistence/schema/documents.ts +4 -4
  360. package/src/persistence/steps.ts +9 -0
  361. package/src/plugin-api/__tests__/oauth-callback-url-export.test.ts +29 -0
  362. package/src/plugin-api/constants.ts +8 -0
  363. package/src/plugin-api/conversation-turn.ts +195 -5
  364. package/src/plugin-api/index.ts +38 -6
  365. package/src/plugin-api/resolve-credential.ts +3 -2
  366. package/src/plugin-api/store-credential.ts +146 -0
  367. package/src/plugin-api/vision-support.test.ts +1 -1
  368. package/src/plugin-api/webhook-url.ts +13 -11
  369. package/src/plugins/__tests__/mcp-servers.test.ts +371 -0
  370. package/src/plugins/defaults/main.ts +21 -7
  371. package/src/plugins/defaults/memory/AGENTS.md +15 -3
  372. package/src/plugins/defaults/memory/__tests__/buffer-format.test.ts +204 -0
  373. package/src/plugins/defaults/memory/__tests__/memory-retrospective-accounting.test.ts +72 -0
  374. package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +4 -1
  375. package/src/plugins/defaults/memory/__tests__/memory-retrospective-provider-path.test.ts +4 -1
  376. package/src/plugins/defaults/memory/__tests__/memory-tier-boundary-guard.test.ts +0 -1
  377. package/src/plugins/defaults/memory/buffer-format.ts +165 -0
  378. package/src/plugins/defaults/memory/context-search/sources/conversations.ts +6 -0
  379. package/src/plugins/defaults/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +77 -0
  380. package/src/plugins/defaults/memory/graph/conversation-graph-memory.ts +24 -6
  381. package/src/plugins/defaults/memory/graph/image-ref-utils.ts +3 -0
  382. package/src/plugins/defaults/memory/graph/tool-handlers.ts +1 -30
  383. package/src/plugins/defaults/memory/graph-topology/pending-buffer.test.ts +34 -0
  384. package/src/plugins/defaults/memory/graph-topology/pending-buffer.ts +17 -24
  385. package/src/plugins/defaults/memory/hooks/post-compact.ts +1 -4
  386. package/src/plugins/defaults/memory/hooks/user-prompt-submit.ts +53 -5
  387. package/src/plugins/defaults/memory/indexer.ts +3 -1
  388. package/src/plugins/defaults/memory/memory-retrospective-accounting.ts +19 -7
  389. package/src/plugins/defaults/memory/memory-retrospective-job.ts +4 -4
  390. package/src/plugins/defaults/memory/src/__tests__/memory-v3-gate-stats.test.ts +281 -0
  391. package/src/plugins/defaults/memory/src/memory-v2-routes.ts +12 -10
  392. package/src/plugins/defaults/memory/src/memory-v3-routes.ts +207 -0
  393. package/src/plugins/defaults/memory/substrate/__tests__/consolidation-job.test.ts +147 -0
  394. package/src/plugins/defaults/memory/substrate/__tests__/consolidation-prompt-flag-gating-guard.test.ts +8 -0
  395. package/src/plugins/defaults/memory/substrate/__tests__/edge-index.test.ts +0 -30
  396. package/src/plugins/defaults/memory/substrate/__tests__/ingest.test.ts +73 -1
  397. package/src/plugins/defaults/memory/substrate/__tests__/page-index.test.ts +37 -0
  398. package/src/plugins/defaults/memory/substrate/__tests__/page-links.test.ts +208 -0
  399. package/src/plugins/defaults/memory/substrate/__tests__/prompts-consolidation.test.ts +158 -1
  400. package/src/plugins/defaults/memory/substrate/__tests__/static-context.test.ts +195 -58
  401. package/src/plugins/defaults/memory/substrate/consolidation-job.ts +93 -24
  402. package/src/plugins/defaults/memory/substrate/edge-index.ts +0 -31
  403. package/src/plugins/defaults/memory/substrate/ingest.ts +59 -0
  404. package/src/plugins/defaults/memory/substrate/page-index.ts +35 -8
  405. package/src/plugins/defaults/memory/substrate/page-links.ts +133 -0
  406. package/src/plugins/defaults/memory/substrate/page-store.ts +10 -0
  407. package/src/plugins/defaults/memory/substrate/prompts/consolidation.ts +124 -34
  408. package/src/plugins/defaults/memory/substrate/skill-content.ts +8 -1
  409. package/src/plugins/defaults/memory/substrate/static-context.ts +160 -33
  410. package/src/plugins/defaults/memory/substrate/sweep-job.ts +2 -4
  411. package/src/plugins/defaults/memory/v1/graph/extraction.ts +3 -1
  412. package/src/plugins/defaults/memory/v3/__tests__/injection.test.ts +18 -0
  413. package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +66 -1
  414. package/src/plugins/defaults/memory/v3/card.ts +9 -9
  415. package/src/plugins/defaults/memory/v3/core-set.test.ts +7 -0
  416. package/src/plugins/defaults/memory/v3/core-set.ts +5 -1
  417. package/src/plugins/defaults/memory/v3/edge.ts +13 -57
  418. package/src/plugins/defaults/memory/v3/injector.ts +8 -0
  419. package/src/plugins/defaults/memory/v3/prune.ts +2 -0
  420. package/src/plugins/defaults/memory/v3/selection-log-store.ts +2 -0
  421. package/src/plugins/defaults/memory/v3/shadow-plugin.ts +23 -9
  422. package/src/plugins/defaults/memory/worker-control.ts +1 -0
  423. package/src/plugins/defaults/memory/worker.ts +6 -3
  424. package/src/plugins/defaults/worker-entrypoints.ts +3 -0
  425. package/src/plugins/external-plugin-loader.ts +47 -0
  426. package/src/plugins/mcp-servers.ts +361 -0
  427. package/src/plugins/mtime-cache.ts +37 -46
  428. package/src/plugins/pipeline.ts +14 -7
  429. package/src/plugins/plugin-execution-context.ts +35 -11
  430. package/src/plugins/worker-plugin-surface.ts +33 -0
  431. package/src/prompts/templates/system-sections.ts +0 -7
  432. package/src/providers/__tests__/connection-model-compat.test.ts +1 -1
  433. package/src/providers/__tests__/context-overflow-error.test.ts +24 -0
  434. package/src/providers/__tests__/dispatch-connection-routing.test.ts +214 -2
  435. package/src/providers/__tests__/preflight-resolved-config.test.ts +57 -0
  436. package/src/providers/__tests__/retry-callsite.test.ts +25 -2
  437. package/src/providers/anthropic/__tests__/pause-turn-continuation.test.ts +170 -0
  438. package/src/providers/anthropic/client.ts +332 -241
  439. package/src/providers/call-site-routing.ts +30 -3
  440. package/src/providers/connection-resolution.ts +194 -11
  441. package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +4 -0
  442. package/src/providers/inference/adapter-factory.ts +3 -0
  443. package/src/providers/inference/auth.ts +6 -0
  444. package/src/providers/inference/connection-availability.ts +24 -2
  445. package/src/providers/inference/connections.ts +2 -0
  446. package/src/providers/model-catalog.ts +3 -3
  447. package/src/providers/model-intents.ts +28 -8
  448. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +163 -40
  449. package/src/providers/openai/__tests__/tool-choice-mapping.test.ts +125 -2
  450. package/src/providers/openai/chat-completions-provider.ts +133 -36
  451. package/src/providers/openai/codex-models.ts +2 -1
  452. package/src/providers/provider-send-message.ts +32 -3
  453. package/src/providers/speech-to-text/__tests__/deepgram-flux-frames.test.ts +433 -0
  454. package/src/providers/speech-to-text/__tests__/deepgram-flux-realtime.test.ts +625 -0
  455. package/src/providers/speech-to-text/__tests__/provider-catalog.test.ts +34 -0
  456. package/src/providers/speech-to-text/__tests__/resolve.test.ts +285 -6
  457. package/src/providers/speech-to-text/deepgram-flux-frames.ts +395 -0
  458. package/src/providers/speech-to-text/deepgram-flux-realtime.ts +679 -0
  459. package/src/providers/speech-to-text/provider-catalog.ts +99 -8
  460. package/src/providers/speech-to-text/resolve.ts +25 -2
  461. package/src/routes/control.ts +1 -0
  462. package/src/routes/route-host-client.ts +1 -0
  463. package/src/routes/route-host-protocol.ts +7 -0
  464. package/src/routes/worker.ts +48 -15
  465. package/src/runtime/AGENTS.md +16 -17
  466. package/src/runtime/access-request-helper.ts +9 -12
  467. package/src/runtime/agent-wake.ts +18 -15
  468. package/src/runtime/effective-capabilities.test.ts +43 -0
  469. package/src/runtime/effective-capabilities.ts +27 -0
  470. package/src/runtime/guardian-reply-router.ts +6 -2
  471. package/src/runtime/pre-first-message-gate.ts +4 -0
  472. package/src/runtime/routes/__tests__/acp-claude-auth-routes.test.ts +12 -4
  473. package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +522 -2
  474. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +52 -0
  475. package/src/runtime/routes/__tests__/default-provider-routes.test.ts +61 -0
  476. package/src/runtime/routes/__tests__/inference-profiles-routes.test.ts +44 -0
  477. package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +102 -1
  478. package/src/runtime/routes/__tests__/plugins-routes.test.ts +132 -35
  479. package/src/runtime/routes/__tests__/schedule-routes-disarm-reason.test.ts +215 -0
  480. package/src/runtime/routes/__tests__/stt-routes.test.ts +25 -0
  481. package/src/runtime/routes/__tests__/surface-action-routes.test.ts +55 -6
  482. package/src/runtime/routes/__tests__/user-route-dispatcher-host.test.ts +52 -1
  483. package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +133 -1
  484. package/src/runtime/routes/channel-availability-routes.ts +32 -14
  485. package/src/runtime/routes/channel-route-shared.ts +0 -6
  486. package/src/runtime/routes/chatgpt-subscription-auth-routes.ts +6 -6
  487. package/src/runtime/routes/conversation-list-routes.ts +181 -23
  488. package/src/runtime/routes/conversation-management-routes.ts +17 -12
  489. package/src/runtime/routes/conversation-query-routes.ts +40 -27
  490. package/src/runtime/routes/conversation-routes.ts +43 -29
  491. package/src/runtime/routes/credential-prompt-routes.ts +4 -7
  492. package/src/runtime/routes/credential-routes.ts +26 -82
  493. package/src/runtime/routes/default-provider-routes.ts +15 -0
  494. package/src/runtime/routes/documents-routes.ts +3 -222
  495. package/src/runtime/routes/inbound-message-handler.ts +21 -45
  496. package/src/runtime/routes/inbound-stages/acl-enforcement.test.ts +0 -1
  497. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +0 -9
  498. package/src/runtime/routes/inbound-stages/admission-policy.ts +1 -17
  499. package/src/runtime/routes/inbound-stages/bootstrap-intercept.test.ts +0 -1
  500. package/src/runtime/routes/inbound-stages/bootstrap-intercept.ts +2 -3
  501. package/src/runtime/routes/inbound-stages/edit-intercept.ts +101 -79
  502. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.test.ts +0 -1
  503. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +12 -7
  504. package/src/runtime/routes/inbound-stages/reaction-intercept.test.ts +91 -8
  505. package/src/runtime/routes/inbound-stages/reaction-intercept.ts +72 -79
  506. package/src/runtime/routes/inbound-stages/secret-ingress-check.ts +2 -3
  507. package/src/runtime/routes/inference-profiles-routes.ts +20 -11
  508. package/src/runtime/routes/inference-provider-connection-routes.ts +77 -15
  509. package/src/runtime/routes/log-export-routes.ts +3 -0
  510. package/src/runtime/routes/mcp-auth-routes.ts +148 -57
  511. package/src/runtime/routes/playground/__tests__/inject-failures.test.ts +2 -0
  512. package/src/runtime/routes/playground/__tests__/reset-circuit.test.ts +3 -0
  513. package/src/runtime/routes/playground/inject-failures.ts +2 -2
  514. package/src/runtime/routes/playground/reset-circuit.ts +1 -1
  515. package/src/runtime/routes/plugins-routes.ts +38 -8
  516. package/src/runtime/routes/schedule-routes.ts +94 -7
  517. package/src/runtime/routes/settings-routes.ts +6 -6
  518. package/src/runtime/routes/stt-routes.ts +31 -25
  519. package/src/runtime/routes/surface-action-routes.ts +20 -19
  520. package/src/runtime/routes/surface-conversation-resolver.ts +3 -0
  521. package/src/runtime/routes/user-route-dispatcher.ts +69 -15
  522. package/src/runtime/routes/user-route-import.ts +108 -0
  523. package/src/runtime/routes/user-route-resolution.ts +18 -1
  524. package/src/runtime/routes/workspace-routes.ts +0 -9
  525. package/src/runtime/routes/workspace-utils.ts +3 -13
  526. package/src/runtime/services/conversation-serializer.ts +7 -2
  527. package/src/schedule/__tests__/plugin-schedule-declarations.test.ts +68 -5
  528. package/src/schedule/__tests__/plugin-schedule-reconciler.test.ts +81 -0
  529. package/src/schedule/plugin-schedule-availability.ts +58 -0
  530. package/src/schedule/plugin-schedule-declarations.ts +23 -27
  531. package/src/schedule/plugin-schedule-reconciler.ts +12 -3
  532. package/src/schedule/schedule-store.ts +5 -1
  533. package/src/schedule/scheduler.ts +9 -4
  534. package/src/schedule/worker-control.ts +1 -0
  535. package/src/schedule/worker.ts +6 -0
  536. package/src/security/oauth2.ts +6 -22
  537. package/src/stt/__tests__/daemon-batch-transcriber.test.ts +22 -0
  538. package/src/stt/__tests__/types.test.ts +94 -0
  539. package/src/stt/daemon-batch-transcriber.ts +10 -0
  540. package/src/stt/stt-stream-session.ts +8 -4
  541. package/src/stt/types.ts +103 -0
  542. package/src/subagent/__tests__/consult-prompt.test.ts +26 -15
  543. package/src/subagent/consult-context.ts +11 -11
  544. package/src/subagent/consult-prompt.ts +26 -35
  545. package/src/subagent/manager.ts +21 -40
  546. package/src/subagent/notify.ts +7 -1
  547. package/src/subagent/types.ts +15 -12
  548. package/src/tools/__tests__/tool-input-schemas.test.ts +7 -7
  549. package/src/tools/acp/spawn.test.ts +97 -0
  550. package/src/tools/acp/spawn.ts +38 -4
  551. package/src/tools/credentials/ref-parse.ts +35 -0
  552. package/src/tools/credentials/resolve.ts +4 -10
  553. package/src/tools/credentials/store.ts +168 -0
  554. package/src/tools/document/document-tool.ts +12 -3
  555. package/src/tools/executor.ts +53 -15
  556. package/src/tools/filesystem/read.ts +27 -10
  557. package/src/tools/host-filesystem/read.ts +27 -15
  558. package/src/tools/network/url-safety.ts +9 -16
  559. package/src/tools/permission-checker.ts +43 -52
  560. package/src/tools/registry.ts +2 -1
  561. package/src/tools/shared/filesystem/file-ops-service.ts +63 -35
  562. package/src/tools/shared/filesystem/legacy-read-args.ts +22 -0
  563. package/src/tools/shared/filesystem/types.ts +5 -5
  564. package/src/tools/skills/scaffold-managed.ts +25 -7
  565. package/src/tools/subagent/spawn.ts +28 -88
  566. package/src/tools/tool-types.ts +26 -13
  567. package/src/tools/types.ts +6 -6
  568. package/src/tools/ui-surface/surface-shape-docs.ts +12 -1
  569. package/src/tools/workflows/run-workflow.ts +1 -2
  570. package/src/tts/__tests__/reasoning-tag-filter.test.ts +78 -0
  571. package/src/tts/reasoning-tag-filter.ts +89 -0
  572. package/src/util/__tests__/worker-process-command.test.ts +37 -0
  573. package/src/util/logger.ts +16 -0
  574. package/src/util/think-tag-stream.ts +95 -0
  575. package/src/util/worker-process.ts +37 -4
  576. package/src/windows-compiled-cli.ts +32 -0
  577. package/src/windows-compiled-entry.ts +4 -0
  578. package/src/windows-compiled-logger.ts +6 -0
  579. package/src/windows-compiled-worker-entry.ts +29 -0
  580. package/src/workspace/byok-default-profile-ensure.ts +76 -24
  581. package/src/workspace/custom-profile-ensure.ts +4 -24
  582. package/src/workspace/migrations/142-consolidate-voice-front-door.ts +70 -0
  583. package/src/workspace/migrations/143-repair-deprecated-codex-model-id.ts +134 -0
  584. package/src/workspace/migrations/144-convert-stranded-subscription-openai-profiles.ts +265 -0
  585. package/src/workspace/migrations/145-collapse-profile-bindings-to-entries.ts +328 -0
  586. package/src/workspace/migrations/146-repair-retired-fireworks-deepseek-flash-model-id.ts +195 -0
  587. package/src/workspace/migrations/__tests__/141-stt-english-default-to-multilingual.test.ts +0 -10
  588. package/src/workspace/migrations/registry.ts +10 -0
  589. package/src/workspace/provider-commit-message-generator.ts +7 -5
  590. package/src/__tests__/document-workspace-file.test.ts +0 -467
  591. package/src/live-voice/__tests__/front-decision.test.ts +0 -645
  592. package/src/live-voice/front-decision.ts +0 -476
  593. package/src/permissions/ipc-risk-types.ts +0 -143
  594. package/src/permissions/risk-types.ts +0 -76
  595. package/src/subagent/__tests__/consult-transcript.test.ts +0 -184
  596. package/src/subagent/consult-transcript.ts +0 -90
@@ -104,6 +104,7 @@ function makeContext(): Conversation {
104
104
  conversationId: CONVERSATION_ID,
105
105
  trustContext: { trustClass: "guardian", sourceChannel: "vellum" },
106
106
  sendToClient: () => {},
107
+ emit: () => {},
107
108
  pendingSurfaceActions: new Map<string, { surfaceType: SurfaceType }>(),
108
109
  lastSurfaceAction: new Map(),
109
110
  surfaceState: new Map(),
@@ -93,7 +93,7 @@ mock.module("../skills/version-hash.js", () => ({
93
93
  // In CI there's no gateway, so we return a sensible default.
94
94
  mock.module("../ipc/gateway-client.js", () => ({
95
95
  ipcClassifyRisk: async (): Promise<
96
- import("../permissions/ipc-risk-types.js").ClassificationResult
96
+ import("@vellumai/gateway-client").ClassifyRiskIpcResponse
97
97
  > => ({
98
98
  risk: "low",
99
99
  reason: "mock",
@@ -6,9 +6,11 @@ import type { UsageAttributionSnapshot } from "../usage/attribution.js";
6
6
  let checkerDecision: "allow" | "prompt" | "deny" = "allow";
7
7
  let checkerReason = "allowed";
8
8
  let checkerRisk = "low";
9
+ let classifyThrow: Error | null = null;
9
10
  let promptDecision: "allow" | "deny" = "allow";
10
11
  let fakeToolResult: ToolExecutionResult = { content: "ok", isError: false };
11
12
  let toolThrow: Error | null = null;
13
+ let grantConsumeOk = false;
12
14
 
13
15
  // ── audit-terminal captures ───────────────────────────────
14
16
  // The executor and its permission/approval collaborators no longer emit
@@ -86,13 +88,38 @@ mock.module("../platform/consent-cache.js", () => ({
86
88
 
87
89
  mock.module("../permissions/checker.js", () => ({
88
90
  isDynamicSkillLoadInvocation: () => false,
89
- classifyRisk: async () => ({ level: checkerRisk }),
91
+ // Mirrors the real classifier's two failure modes: it throws on an aborted
92
+ // signal before doing anything, and it throws when the gateway is
93
+ // unreachable.
94
+ classifyRisk: async (
95
+ _name: string,
96
+ _input: Record<string, unknown>,
97
+ _workingDir?: string,
98
+ _preParsed?: unknown,
99
+ _manifestOverride?: unknown,
100
+ signal?: AbortSignal,
101
+ ) => {
102
+ signal?.throwIfAborted();
103
+ if (classifyThrow) {
104
+ throw classifyThrow;
105
+ }
106
+ return { level: checkerRisk };
107
+ },
90
108
  check: async () => ({ decision: checkerDecision, reason: checkerReason }),
91
- generateAllowlistOptions: () => [
92
- { label: "exact", description: "exact", pattern: "exact" },
93
- ],
94
109
  generateScopeOptions: () => [{ label: "/tmp", scope: "/tmp" }],
95
- getCachedAssessment: () => undefined,
110
+ }));
111
+
112
+ // The scoped-grant consume the gate performs for a sensitive tool invoked by
113
+ // an unknown actor. `mintGrantFromDecision` is stubbed only so the module's
114
+ // other importer links; nothing here mints.
115
+ mock.module("../approvals/approval-primitive.js", () => ({
116
+ consumeGrantForInvocation: async () =>
117
+ grantConsumeOk
118
+ ? { ok: true, grant: { id: "grant-1" } }
119
+ : { ok: false, reason: "no_match" },
120
+ mintGrantFromDecision: async () => {
121
+ throw new Error("mintGrantFromDecision should not be called");
122
+ },
96
123
  }));
97
124
 
98
125
  mock.module("../persistence/conversation-crud.js", () => ({
@@ -255,9 +282,11 @@ describe("ToolExecutor audit terminals", () => {
255
282
  checkerDecision = "allow";
256
283
  checkerReason = "allowed";
257
284
  checkerRisk = "low";
285
+ classifyThrow = null;
258
286
  promptDecision = "allow";
259
287
  fakeToolResult = { content: "ok", isError: false };
260
288
  toolThrow = null;
289
+ grantConsumeOk = false;
261
290
  executedCaptures.length = 0;
262
291
  errorCaptures.length = 0;
263
292
  deniedCaptures.length = 0;
@@ -786,4 +815,102 @@ describe("ToolExecutor audit terminals", () => {
786
815
  expect(executedCaptures).toHaveLength(1);
787
816
  expect(executedCaptures[0].toolName).toBe("file_edit");
788
817
  });
818
+
819
+ // ── audit riskLevel on rows the permission check never produces ─────────
820
+ // The classifier runs before the pre-execution gates, so a gate denial, a
821
+ // gate error, and a grant-consumed execution all record the call's real
822
+ // risk rather than a placeholder (LUM-3159).
823
+
824
+ test("a gate-denied call records the classified risk, not a placeholder", async () => {
825
+ checkerRisk = "high";
826
+ const executor = new ToolExecutor(makePrompter());
827
+
828
+ // Verification control-plane paths are guardian-only; an unknown actor is
829
+ // denied by the gate before any permission check.
830
+ const result = await executor.execute(
831
+ "bash",
832
+ { command: "curl http://host/v1/channel-verification-sessions" },
833
+ makeContext({ trustClass: "unknown" }),
834
+ );
835
+
836
+ expect(result.isError).toBe(true);
837
+ expect(deniedCaptures).toHaveLength(1);
838
+ expect(deniedCaptures[0].riskLevel).toBe("high");
839
+ expect(deniedCaptures[0].wasPrompted).toBe(false);
840
+ });
841
+
842
+ test("a gate-errored call records the classified risk", async () => {
843
+ checkerRisk = "medium";
844
+ const executor = new ToolExecutor(makePrompter());
845
+
846
+ await executor.execute("unknown_tool", { test: true }, makeContext());
847
+
848
+ expect(errorCaptures).toHaveLength(1);
849
+ expect(errorCaptures[0].errorMessage).toContain("Unknown tool");
850
+ expect(errorCaptures[0].riskLevel).toBe("medium");
851
+ });
852
+
853
+ test("a grant-consumed execution records the classified risk", async () => {
854
+ checkerRisk = "high";
855
+ grantConsumeOk = true;
856
+ const executor = new ToolExecutor(makePrompter());
857
+
858
+ // A sensitive tool from an unknown actor routes through the scoped-grant
859
+ // consume; a consumed grant skips the permission check entirely, so this
860
+ // is the one executed row whose risk the permission check never supplies.
861
+ const result = await executor.execute(
862
+ "file_write",
863
+ { path: "/tmp/project/out.txt", content: "x" },
864
+ makeContext({ trustClass: "unknown" }),
865
+ );
866
+
867
+ expect(result).toMatchObject({
868
+ isError: false,
869
+ approvalReason: "grant_scoped_consumed",
870
+ });
871
+ expect(executedCaptures).toHaveLength(1);
872
+ expect(executedCaptures[0].decision).toBe("allow");
873
+ expect(executedCaptures[0].riskLevel).toBe("high");
874
+ });
875
+
876
+ test("a call whose classification never completes records 'unclassified', not 'low'", async () => {
877
+ classifyThrow = new Error("gateway unreachable");
878
+ const executor = new ToolExecutor(makePrompter());
879
+
880
+ // Gate error path: the row says the risk was never assessed.
881
+ await executor.execute("unknown_tool", { test: true }, makeContext());
882
+ expect(errorCaptures).toHaveLength(1);
883
+ expect(errorCaptures[0].riskLevel).toBe("unclassified");
884
+
885
+ // Past the gates, the permission check still requires a classification
886
+ // and fails closed; that error row is likewise honest about the risk.
887
+ errorCaptures.length = 0;
888
+ const result = await executor.execute(
889
+ "file_read",
890
+ { path: "README.md" },
891
+ makeContext(),
892
+ );
893
+ expect(result.isError).toBe(true);
894
+ expect(executedCaptures).toHaveLength(0);
895
+ expect(errorCaptures).toHaveLength(1);
896
+ expect(errorCaptures[0].errorMessage).toContain("gateway unreachable");
897
+ expect(errorCaptures[0].riskLevel).toBe("unclassified");
898
+ });
899
+
900
+ test("a call aborted before it starts records 'unclassified' on its cancelled row", async () => {
901
+ checkerRisk = "high";
902
+ const executor = new ToolExecutor(makePrompter());
903
+ const controller = new AbortController();
904
+ controller.abort();
905
+
906
+ const result = await executor.execute(
907
+ "bash",
908
+ { command: "rm -rf /tmp" },
909
+ makeContext({ signal: controller.signal }),
910
+ );
911
+
912
+ expect(result).toEqual({ content: "Cancelled", isError: true });
913
+ expect(errorCaptures).toHaveLength(1);
914
+ expect(errorCaptures[0].riskLevel).toBe("unclassified");
915
+ });
789
916
  });
@@ -43,8 +43,11 @@ let checkFnOverride:
43
43
  /** Override for generateScopeOptions — when set, returns this value instead of the default. */
44
44
  let scopeOptionsOverride: ScopeOption[] | undefined;
45
45
 
46
- /** Override for getCachedAssessment — when set, returns this value. */
47
- let cachedAssessmentOverride:
46
+ /**
47
+ * Override for the classification `classifyRisk` returns; when unset the
48
+ * classifier answers low with no reason and no options.
49
+ */
50
+ let classificationOverride:
48
51
  | {
49
52
  riskLevel: string;
50
53
  reason: string;
@@ -61,7 +64,14 @@ let cachedAssessmentOverride:
61
64
 
62
65
  mock.module("../permissions/checker.js", () => ({
63
66
  isDynamicSkillLoadInvocation: () => false,
64
- classifyRisk: async () => ({ level: "low" }),
67
+ classifyRisk: async () => ({
68
+ level: classificationOverride?.riskLevel ?? "low",
69
+ reason: classificationOverride?.reason,
70
+ scopeOptions: classificationOverride?.scopeOptions ?? [],
71
+ allowlistOptions: classificationOverride?.allowlistOptions,
72
+ directoryScopeOptions: classificationOverride?.directoryScopeOptions,
73
+ matchType: classificationOverride?.matchType ?? "unknown",
74
+ }),
65
75
  check: async (
66
76
  toolName: string,
67
77
  input: Record<string, unknown>,
@@ -77,12 +87,8 @@ mock.module("../permissions/checker.js", () => ({
77
87
  }
78
88
  return { decision: "allow", reason: "allowed" };
79
89
  },
80
- generateAllowlistOptions: () => [
81
- { label: "exact", description: "exact", pattern: "exact" },
82
- ],
83
90
  generateScopeOptions: () =>
84
91
  scopeOptionsOverride ?? [{ label: "/tmp", scope: "/tmp" }],
85
- getCachedAssessment: () => cachedAssessmentOverride,
86
92
  }));
87
93
 
88
94
  // Mock every export so downstream test files that dynamically import modules
@@ -167,7 +173,7 @@ describe("ToolExecutor allowedToolNames gating", () => {
167
173
  getAllToolsOverride = undefined;
168
174
  checkResultOverride = undefined;
169
175
  checkFnOverride = undefined;
170
- cachedAssessmentOverride = undefined;
176
+ classificationOverride = undefined;
171
177
  });
172
178
 
173
179
  test("executes normally when allowedToolNames is not set", async () => {
@@ -385,7 +391,7 @@ describe("ToolExecutor policy context plumbing", () => {
385
391
  getToolOverride = undefined;
386
392
  checkResultOverride = undefined;
387
393
  checkFnOverride = undefined;
388
- cachedAssessmentOverride = undefined;
394
+ classificationOverride = undefined;
389
395
  });
390
396
 
391
397
  test("passes PolicyContext with executionTarget for skill-origin tools", async () => {
@@ -604,7 +610,7 @@ describe("ToolExecutor forcePromptSideEffects enforcement", () => {
604
610
  getToolOverride = undefined;
605
611
  checkResultOverride = undefined;
606
612
  checkFnOverride = undefined;
607
- cachedAssessmentOverride = undefined;
613
+ classificationOverride = undefined;
608
614
  promptCalled = false;
609
615
  });
610
616
 
@@ -931,11 +937,30 @@ describe("integration regressions — prompt payload (PR 11)", () => {
931
937
  getToolOverride = undefined;
932
938
  });
933
939
 
934
- test("shell command prompt payload includes allowlist and scope options", async () => {
940
+ test("shell command prompt payload carries the classifier's ladder and the scope options", async () => {
935
941
  checkResultOverride = {
936
942
  decision: "prompt",
937
943
  reason: "Medium risk: requires approval",
938
944
  };
945
+ // The ladder is the gateway's; the daemon passes it through untouched.
946
+ classificationOverride = {
947
+ riskLevel: "medium",
948
+ reason: "npm install",
949
+ scopeOptions: [],
950
+ allowlistOptions: [
951
+ {
952
+ label: "npm install",
953
+ description: "Exact command",
954
+ pattern: "npm install",
955
+ },
956
+ {
957
+ label: "action:npm",
958
+ description: "Any npm command",
959
+ pattern: "action:npm",
960
+ },
961
+ ],
962
+ matchType: "registry",
963
+ };
939
964
 
940
965
  let capturedAllowlist: AllowlistOption[] | undefined;
941
966
  let capturedScopes: ScopeOption[] | undefined;
@@ -963,13 +988,11 @@ describe("integration regressions — prompt payload (PR 11)", () => {
963
988
  makeContext({ forcePromptSideEffects: true }),
964
989
  );
965
990
 
966
- // Verify that the prompter received allowlist options
967
- expect(capturedAllowlist).toBeDefined();
968
- expect(capturedAllowlist!.length).toBeGreaterThan(0);
969
- // The mock returns [{label: 'exact', description: 'exact', pattern: 'exact'}]
970
- expect(capturedAllowlist![0]).toHaveProperty("pattern");
971
- expect(capturedAllowlist![0]).toHaveProperty("label");
972
- expect(capturedAllowlist![0]).toHaveProperty("description");
991
+ // The prompt offers exactly what the classifier produced, in order.
992
+ expect(capturedAllowlist?.map((o) => o.pattern)).toEqual([
993
+ "npm install",
994
+ "action:npm",
995
+ ]);
973
996
 
974
997
  // Verify scope options are also passed
975
998
  expect(capturedScopes).toBeDefined();
@@ -989,11 +1012,11 @@ describe("ToolExecutionResult includes risk metadata from classifier assessment"
989
1012
  getToolOverride = undefined;
990
1013
  checkResultOverride = undefined;
991
1014
  checkFnOverride = undefined;
992
- cachedAssessmentOverride = undefined;
1015
+ classificationOverride = undefined;
993
1016
  });
994
1017
 
995
1018
  test("auto-approved tool result includes risk metadata when classifier assessment exists", async () => {
996
- cachedAssessmentOverride = {
1019
+ classificationOverride = {
997
1020
  riskLevel: "medium",
998
1021
  reason: "Writes to a file outside the workspace",
999
1022
  scopeOptions: [
@@ -1019,8 +1042,10 @@ describe("ToolExecutionResult includes risk metadata from classifier assessment"
1019
1042
  ]);
1020
1043
  });
1021
1044
 
1022
- test("tool result omits risk metadata when no classifier assessment exists (e.g. MCP tools)", async () => {
1023
- // cachedAssessmentOverride is undefined (no classifier ran)
1045
+ test("tool result carries the classification's risk metadata even when the classifier produced no options", async () => {
1046
+ // classificationOverride is unset: the classifier answered low with no
1047
+ // reason and no options, which is what a tool with no dedicated classifier
1048
+ // (registry default risk) looks like.
1024
1049
  const executor = new ToolExecutor(makePrompter());
1025
1050
  const result = await executor.execute(
1026
1051
  "file_read",
@@ -1029,9 +1054,9 @@ describe("ToolExecutionResult includes risk metadata from classifier assessment"
1029
1054
  );
1030
1055
 
1031
1056
  expect(result.isError).toBe(false);
1032
- expect(result.riskLevel).toBeUndefined();
1033
- expect(result.riskReason).toBeUndefined();
1034
- expect(result.riskScopeOptions).toBeUndefined();
1057
+ expect(result.riskLevel).toBe("low");
1058
+ expect(result.riskScopeOptions).toEqual([]);
1059
+ expect(result.riskAllowlistOptions).toBeUndefined();
1035
1060
  });
1036
1061
 
1037
1062
  test("denied tool result includes risk metadata", async () => {
@@ -1039,7 +1064,7 @@ describe("ToolExecutionResult includes risk metadata from classifier assessment"
1039
1064
  decision: "deny",
1040
1065
  reason: "Blocked by deny rule",
1041
1066
  };
1042
- cachedAssessmentOverride = {
1067
+ classificationOverride = {
1043
1068
  riskLevel: "high",
1044
1069
  reason: "Recursive force delete",
1045
1070
  scopeOptions: [{ pattern: "bash:rm -rf*", label: "rm -rf commands" }],
@@ -1066,7 +1091,7 @@ describe("ToolExecutionResult includes risk metadata from classifier assessment"
1066
1091
  decision: "prompt",
1067
1092
  reason: "Medium risk: requires approval",
1068
1093
  };
1069
- cachedAssessmentOverride = {
1094
+ classificationOverride = {
1070
1095
  riskLevel: "medium",
1071
1096
  reason: "Package manager installation",
1072
1097
  scopeOptions: [
@@ -1091,7 +1116,7 @@ describe("ToolExecutionResult includes risk metadata from classifier assessment"
1091
1116
  });
1092
1117
 
1093
1118
  test("tool result includes riskDirectoryScopeOptions when classifier emits directoryScopeOptions", async () => {
1094
- cachedAssessmentOverride = {
1119
+ classificationOverride = {
1095
1120
  riskLevel: "medium",
1096
1121
  reason: "Writes to file in workspace",
1097
1122
  scopeOptions: [
@@ -1124,7 +1149,7 @@ describe("ToolExecutionResult includes risk metadata from classifier assessment"
1124
1149
  });
1125
1150
 
1126
1151
  test("tool result omits riskDirectoryScopeOptions when classifier does not emit directoryScopeOptions", async () => {
1127
- cachedAssessmentOverride = {
1152
+ classificationOverride = {
1128
1153
  riskLevel: "low",
1129
1154
  reason: "Read-only operation",
1130
1155
  scopeOptions: [],
@@ -1144,7 +1169,7 @@ describe("ToolExecutionResult includes risk metadata from classifier assessment"
1144
1169
  });
1145
1170
 
1146
1171
  test("riskScopeOptions and riskDirectoryScopeOptions are independent — one does not clobber the other", async () => {
1147
- cachedAssessmentOverride = {
1172
+ classificationOverride = {
1148
1173
  riskLevel: "medium",
1149
1174
  reason: "Filesystem write",
1150
1175
  scopeOptions: [
@@ -1170,7 +1195,7 @@ describe("ToolExecutionResult includes risk metadata from classifier assessment"
1170
1195
  });
1171
1196
 
1172
1197
  test("auto-approved tool result includes riskAllowlistOptions when classifier emits them (Minimatch-glob shape for save path)", async () => {
1173
- cachedAssessmentOverride = {
1198
+ classificationOverride = {
1174
1199
  riskLevel: "medium",
1175
1200
  reason: "Reads workspace files",
1176
1201
  // Display ladder (regex shape — not for save).
@@ -1222,7 +1247,7 @@ describe("ToolExecutionResult includes risk metadata from classifier assessment"
1222
1247
  });
1223
1248
 
1224
1249
  test("riskAllowlistOptions is undefined when classifier did not produce allowlist (e.g. web-risk classifier)", async () => {
1225
- cachedAssessmentOverride = {
1250
+ classificationOverride = {
1226
1251
  riskLevel: "low",
1227
1252
  reason: "GET request to public URL",
1228
1253
  scopeOptions: [
@@ -1248,8 +1273,8 @@ describe("ToolExecutionResult includes risk metadata from classifier assessment"
1248
1273
  expect(result.riskAllowlistOptions).toBeUndefined();
1249
1274
  });
1250
1275
 
1251
- test("riskAllowlistOptions is undefined when no classifier ran (MCP tools)", async () => {
1252
- // cachedAssessmentOverride is undefined — no classifier ran.
1276
+ test("riskAllowlistOptions is undefined when the classifier produced none", async () => {
1277
+ // classificationOverride is unset: no allowlist options on the answer.
1253
1278
  const executor = new ToolExecutor(makePrompter());
1254
1279
  const result = await executor.execute(
1255
1280
  "file_read",
@@ -1258,13 +1283,13 @@ describe("ToolExecutionResult includes risk metadata from classifier assessment"
1258
1283
  );
1259
1284
 
1260
1285
  expect(result.isError).toBe(false);
1261
- expect(result.riskScopeOptions).toBeUndefined();
1286
+ expect(result.riskScopeOptions).toEqual([]);
1262
1287
  expect(result.riskAllowlistOptions).toBeUndefined();
1263
1288
  });
1264
1289
 
1265
1290
  test("denied tool result still carries riskAllowlistOptions for the rule editor save path", async () => {
1266
1291
  checkResultOverride = { decision: "deny", reason: "Blocked by deny rule" };
1267
- cachedAssessmentOverride = {
1292
+ classificationOverride = {
1268
1293
  riskLevel: "high",
1269
1294
  reason: "Recursive force delete",
1270
1295
  scopeOptions: [{ pattern: "^rm\\s+-rf", label: "rm -rf *" }],
@@ -1334,7 +1359,7 @@ describe("ToolExecutor thrown-value rendering", () => {
1334
1359
  getAllToolsOverride = undefined;
1335
1360
  checkResultOverride = undefined;
1336
1361
  checkFnOverride = undefined;
1337
- cachedAssessmentOverride = undefined;
1362
+ classificationOverride = undefined;
1338
1363
  });
1339
1364
 
1340
1365
  function throwingTool(name: string, thrown: unknown): void {
@@ -1406,7 +1431,7 @@ describe("ToolExecutor input-schema validation gate", () => {
1406
1431
  getAllToolsOverride = undefined;
1407
1432
  checkResultOverride = undefined;
1408
1433
  checkFnOverride = undefined;
1409
- cachedAssessmentOverride = undefined;
1434
+ classificationOverride = undefined;
1410
1435
  });
1411
1436
 
1412
1437
  /**
@@ -1457,7 +1482,11 @@ describe("ToolExecutor input-schema validation gate", () => {
1457
1482
  const executor = new ToolExecutor(makePrompter());
1458
1483
  const result = await executor.execute(
1459
1484
  "file_read",
1460
- { path: "a.txt", offset: "not-a-number", activity: "Reading a file" },
1485
+ {
1486
+ path: "a.txt",
1487
+ start_index: "not-a-number",
1488
+ activity: "Reading a file",
1489
+ },
1461
1490
  makeContext(),
1462
1491
  );
1463
1492
 
@@ -331,3 +331,46 @@ describe("queryUnreportedTurnEvents", () => {
331
331
  expect(() => stampTurnOutcome("no-such-message", "failed")).not.toThrow();
332
332
  });
333
333
  });
334
+
335
+ describe("unfinalized rows and turn accounting", () => {
336
+ test("an in-flight grouped tool-result row is not a real user turn", async () => {
337
+ const conv = createConversation({ conversationType: "standard" });
338
+ const first = await insertUserMessageAt(conv.id, "real question", 1000);
339
+ // The grouped tool-result row: user-role, reserved unfinalized while
340
+ // tool output streams, content a { ref } that does not contain the
341
+ // tool_result text the content exclusions match. Without the finalized
342
+ // predicate it counts as a phantom user turn until the fold.
343
+ const inflight = await insertUserMessageAt(
344
+ conv.id,
345
+ JSON.stringify({ ref: "conversations/conv/inflight/x.jsonl" }),
346
+ 2000,
347
+ );
348
+ getDb().run(`UPDATE messages SET finalized = 0 WHERE id = '${inflight}'`);
349
+ const second = await insertUserMessageAt(conv.id, "second question", 3000);
350
+
351
+ const events = queryUnreportedTurnEvents(0, undefined, 100);
352
+ const ids = events
353
+ .filter((e) => e.conversationId === conv.id)
354
+ .map((e) => e.id);
355
+
356
+ expect(ids).toEqual([first, second]);
357
+ const byId = Object.fromEntries(events.map((e) => [e.id, e]));
358
+ expect(byId[first].turnIndex).toBe(1);
359
+ expect(byId[second].turnIndex).toBe(2);
360
+ });
361
+
362
+ test("a persisted empty-display user row still counts as a turn", async () => {
363
+ const conv = createConversation({ conversationType: "standard" });
364
+ const real = await insertUserMessageAt(conv.id, "real question", 1000);
365
+ // A hidden send persists its user row with [] content (displayContent "")
366
+ // and still runs a turn, so [] rows must stay in turn accounting.
367
+ const hidden = await insertUserMessageAt(conv.id, "[]", 2000);
368
+
369
+ const events = queryUnreportedTurnEvents(0, undefined, 100);
370
+ const ids = events
371
+ .filter((e) => e.conversationId === conv.id)
372
+ .map((e) => e.id);
373
+
374
+ expect(ids).toEqual([real, hidden]);
375
+ });
376
+ });
@@ -22,7 +22,7 @@ import { asConversation } from "./helpers/mock-conversation.js";
22
22
  function makeContext(sent: AssistantEvent[] = []): Conversation {
23
23
  return asConversation({
24
24
  conversationId: "session-1",
25
- sendToClient: (msg) => sent.push(msg),
25
+ emit: (msg) => sent.push(msg),
26
26
  pendingSurfaceActions: new Map<string, { surfaceType: SurfaceType }>(),
27
27
  lastSurfaceAction: new Map<
28
28
  string,
@@ -5,6 +5,7 @@ import { uiShowTool } from "../tools/ui-surface/definitions.js";
5
5
  import {
6
6
  SURFACE_SHAPE_DOCS,
7
7
  SURFACE_TYPE_NAMES,
8
+ UI_SHOW_TYPE_DOCS,
8
9
  uiShowTeachingError,
9
10
  } from "../tools/ui-surface/surface-shape-docs.js";
10
11
 
@@ -290,6 +291,12 @@ describe("ui_show displayable payloads proxy through", () => {
290
291
  surfaceType: "channel_setup",
291
292
  input: { surface_type: "channel_setup", data: { channel: "telegram" } },
292
293
  },
294
+ {
295
+ // The picker reads its own voice and catalog, so an empty payload is
296
+ // the whole contract and there is no missing-content state to teach.
297
+ surfaceType: "voice_picker",
298
+ input: { surface_type: "voice_picker", data: {} },
299
+ },
293
300
  ];
294
301
 
295
302
  for (const { surfaceType, input } of cases) {
@@ -347,4 +354,30 @@ describe("uiShowTeachingError", () => {
347
354
  expect(uiShowTool.description).toContain(name);
348
355
  }
349
356
  });
357
+
358
+ test("voice_picker is a cold type that accepts an empty payload", () => {
359
+ expect(
360
+ uiShowTeachingError({ surface_type: "voice_picker", data: {} }),
361
+ ).toBeNull();
362
+ expect(UI_SHOW_TYPE_DOCS).toContain(
363
+ `voice_picker (${SURFACE_SHAPE_DOCS.voice_picker!.purpose})`,
364
+ );
365
+ expect(UI_SHOW_TYPE_DOCS).not.toContain("- voice_picker:");
366
+ });
367
+
368
+ test("voice_picker steering reaches the model through the cold index", () => {
369
+ // Asserted on the built description rather than the doc entry: a cold
370
+ // type's `shape` reaches the model only through a teaching error, and an
371
+ // empty payload has no missing-content state to raise one, so steering
372
+ // parked there would be dead.
373
+ for (const directive of [
374
+ "change, hear, or pick a voice",
375
+ "rather than describing voices in prose",
376
+ "attach no actions and never set await_action",
377
+ "during a live voice call answer in speech",
378
+ ]) {
379
+ expect(UI_SHOW_TYPE_DOCS).toContain(directive);
380
+ expect(uiShowTool.description).toContain(directive);
381
+ }
382
+ });
350
383
  });
@@ -32,7 +32,7 @@ const HTML = '<div style="color:var(--content-default)">Hello</div>';
32
32
  function makeContext(sent: AssistantEvent[] = []): Conversation {
33
33
  return asConversation({
34
34
  conversationId: "session-1",
35
- sendToClient: (msg) => sent.push(msg),
35
+ emit: (msg) => sent.push(msg),
36
36
  pendingSurfaceActions: new Map<string, { surfaceType: SurfaceType }>(),
37
37
  lastSurfaceAction: new Map<
38
38
  string,