@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
@@ -85,6 +85,8 @@ import type {
85
85
  } from "./message-protocol.js";
86
86
  import { INTERACTIVE_SURFACE_TYPES } from "./message-protocol.js";
87
87
  import { isRowVisibleToUntrustedActor } from "./message-provenance.js";
88
+ import type { TrustContext } from "./trust-context-types.js";
89
+ import { restingTrust } from "./trust-context-types.js";
88
90
  export {
89
91
  buildSurfaceShowPair,
90
92
  type CurrentTurnSurface,
@@ -138,16 +140,40 @@ const ModelActionSchema = SurfaceActionSchema.extend({
138
140
 
139
141
  const MAX_UNDO_DEPTH = 10;
140
142
 
143
+ /**
144
+ * Whether a surface-action turn (a user clicked a button or submitted a form on
145
+ * a UI surface) runs with a human declared present. The handler does not yet
146
+ * learn the actor's interface, so it cannot declare presence the way the send
147
+ * route does; the turn runs non-interactive, which is what the state-derived
148
+ * fallback resolved to between turns. Deriving it from the actor's interface
149
+ * is a policy change tracked separately.
150
+ */
151
+ const SURFACE_ACTION_TURN_IS_INTERACTIVE = false;
152
+
141
153
  /**
142
154
  * Pending surface types that do not hold the one-interactive-surface-at-a-time
143
- * lock. Both render content the user reads rather than a question they must
144
- * answer, so a live one must not block the next surface.
155
+ * lock. Each renders content the user reads (or settles on its own) rather
156
+ * than a question they must answer, so a live one must not block the next
157
+ * surface.
145
158
  */
146
159
  const NON_BLOCKING_PENDING_SURFACE_TYPES = new Set<SurfaceType>([
147
160
  "dynamic_page",
148
161
  "visual",
162
+ "voice_picker",
149
163
  ]);
150
164
 
165
+ /**
166
+ * Surface types that carry no terminal action: the card settles when the user
167
+ * interacts with it, so no click could ever satisfy an attached `actions`
168
+ * entry or an explicit `await_action`. Both are generic ui_show params though,
169
+ * so nothing stops the model attaching them here, and doing so wedges the
170
+ * turn: the client latches `awaiting_user_input` on the presence of actions
171
+ * alone and no action ever arrives to clear it, leaving the composer disabled
172
+ * and Stop hidden while the daemon is still streaming. Stripping them makes
173
+ * the "never blocks a turn" contract structural rather than advisory.
174
+ */
175
+ const ACTIONLESS_SURFACE_TYPES = new Set<SurfaceType>(["voice_picker"]);
176
+
151
177
  /**
152
178
  * Debounce window for persisting `ui_surface_update` data back to the
153
179
  * message row. Surfaces typically receive bursts of updates (e.g. a
@@ -1358,7 +1384,7 @@ export function openChannelSetupPanel(
1358
1384
  data: safeParseSurfaceData("channel_setup", data) ?? {},
1359
1385
  });
1360
1386
 
1361
- ctx.sendToClient({
1387
+ ctx.emit({
1362
1388
  type: "open_panel",
1363
1389
  panelType: "channel_setup",
1364
1390
  data,
@@ -1496,7 +1522,7 @@ function pushUndoState(
1496
1522
  export function handleSurfaceUndo(ctx: Conversation, surfaceId: string): void {
1497
1523
  const stack = ctx.surfaceUndoStacks.get(surfaceId);
1498
1524
  if (!stack || stack.length === 0) {
1499
- ctx.sendToClient({
1525
+ ctx.emit({
1500
1526
  type: "ui_surface_undo_result",
1501
1527
  conversationId: ctx.conversationId,
1502
1528
  surfaceId,
@@ -1509,7 +1535,7 @@ export function handleSurfaceUndo(ctx: Conversation, surfaceId: string): void {
1509
1535
  const previousHtml = stack.pop()!;
1510
1536
  const stored = ctx.surfaceState.get(surfaceId);
1511
1537
  if (!stored || stored.surfaceType !== "dynamic_page") {
1512
- ctx.sendToClient({
1538
+ ctx.emit({
1513
1539
  type: "ui_surface_undo_result",
1514
1540
  conversationId: ctx.conversationId,
1515
1541
  surfaceId,
@@ -1543,7 +1569,7 @@ export function handleSurfaceUndo(ctx: Conversation, surfaceId: string): void {
1543
1569
  html: previousHtml,
1544
1570
  };
1545
1571
  s.data = revertedData;
1546
- ctx.sendToClient({
1572
+ ctx.emit({
1547
1573
  type: "ui_surface_update",
1548
1574
  conversationId: ctx.conversationId,
1549
1575
  surfaceId: sid,
@@ -1580,7 +1606,7 @@ export function handleSurfaceUndo(ctx: Conversation, surfaceId: string): void {
1580
1606
  html: previousHtml,
1581
1607
  };
1582
1608
  stored.data = revertedData;
1583
- ctx.sendToClient({
1609
+ ctx.emit({
1584
1610
  type: "ui_surface_update",
1585
1611
  conversationId: ctx.conversationId,
1586
1612
  surfaceId,
@@ -1588,7 +1614,7 @@ export function handleSurfaceUndo(ctx: Conversation, surfaceId: string): void {
1588
1614
  });
1589
1615
  }
1590
1616
 
1591
- ctx.sendToClient({
1617
+ ctx.emit({
1592
1618
  type: "ui_surface_undo_result",
1593
1619
  conversationId: ctx.conversationId,
1594
1620
  surfaceId,
@@ -1880,7 +1906,13 @@ export async function handleSurfaceAction(
1880
1906
  // reconstruct the same-user binding for host proxies (CU / app-control),
1881
1907
  // mirroring the normal message path.
1882
1908
  sourceActorPrincipalId?: string,
1909
+ // Trust of the actor committing this action, resolved by the route from the
1910
+ // verified requester. Passed rather than read back off the conversation so a
1911
+ // click that lands mid-turn is attributed to its clicker instead of to
1912
+ // whoever occupies the resting slot.
1913
+ requesterTrustContext?: TrustContext,
1883
1914
  ): Promise<SurfaceActionResult> {
1915
+ const actionTrustContext = requesterTrustContext ?? restingTrust(ctx);
1884
1916
  // ── Standalone surface interception ──────────────────────────────
1885
1917
  // Daemon-driven surfaces (from `requestInteractiveUi`) register a
1886
1918
  // pending entry in `pendingStandaloneSurfaces`. When the user clicks
@@ -2026,7 +2058,7 @@ export async function handleSurfaceAction(
2026
2058
  // `await` resolves as soon as the conversation is created + titled +
2027
2059
  // published to the event hub. The HTTP POST /v1/surface-actions
2028
2060
  // response returns promptly — the seed turn runs in the background.
2029
- const originTrustContext = ctx.trustContext;
2061
+ const originTrustContext = actionTrustContext;
2030
2062
  const { conversationId } = await launchConversation({
2031
2063
  title,
2032
2064
  seedPrompt,
@@ -2045,14 +2077,11 @@ export async function handleSurfaceAction(
2045
2077
  return { accepted: true, conversationId };
2046
2078
  }
2047
2079
 
2048
- // Trust of the actor committing THIS action. `POST /v1/surface-actions`
2049
- // stamps the verified requester's trust onto the conversation before
2050
- // dispatching here, and `loadFromDb` derives the live history filter from
2051
- // the same value, so the completion path's persisted read stays scoped to
2052
- // exactly what this actor's live view would have held. Unresolvable trust
2053
- // fails closed to the untrusted filter.
2080
+ // Trust of the actor committing THIS action, so the completion path's
2081
+ // persisted read stays scoped to exactly what this actor's live view would
2082
+ // have held. Unresolvable trust fails closed to the untrusted filter.
2054
2083
  const requesterCanAccessMemory = resolveCapabilities(
2055
- ctx.trustContext?.trustClass,
2084
+ actionTrustContext?.trustClass,
2056
2085
  ).canAccessMemory;
2057
2086
 
2058
2087
  const pending = ctx.pendingSurfaceActions.get(surfaceId);
@@ -2198,6 +2227,8 @@ export async function handleSurfaceAction(
2198
2227
  activeSurfaceId: surfaceId,
2199
2228
  displayContent,
2200
2229
  sourceActorPrincipalId,
2230
+ trustContext: actionTrustContext,
2231
+ isInteractive: SURFACE_ACTION_TURN_IS_INTERACTIVE,
2201
2232
  // Rides the metadata bag rather than a typed option: the queue
2202
2233
  // round-trips `metadata` but not `PersistMessageOptions`.
2203
2234
  metadata: { scripted: isSyntheticSurfaceActionContent(content) },
@@ -2260,6 +2291,10 @@ export async function handleSurfaceAction(
2260
2291
  activeSurfaceId: surfaceId,
2261
2292
  displayContent,
2262
2293
  sourceActorPrincipalId,
2294
+ // Reached only when `enqueueMessage` declined to queue: the click is
2295
+ // starting this turn, so it is the actor the run belongs to.
2296
+ trustContext: actionTrustContext,
2297
+ isInteractive: SURFACE_ACTION_TURN_IS_INTERACTIVE,
2263
2298
  scripted: isSyntheticSurfaceActionContent(content),
2264
2299
  })
2265
2300
  .catch((err) => {
@@ -2446,6 +2481,8 @@ export async function handleSurfaceAction(
2446
2481
  activeSurfaceId: surfaceId,
2447
2482
  displayContent,
2448
2483
  sourceActorPrincipalId,
2484
+ trustContext: actionTrustContext,
2485
+ isInteractive: SURFACE_ACTION_TURN_IS_INTERACTIVE,
2449
2486
  // Rides the metadata bag rather than a typed option: the queue
2450
2487
  // round-trips `metadata` but not `PersistMessageOptions`.
2451
2488
  metadata: { scripted: isSyntheticSurfaceActionContent(content) },
@@ -2518,6 +2555,10 @@ export async function handleSurfaceAction(
2518
2555
  activeSurfaceId: surfaceId,
2519
2556
  displayContent,
2520
2557
  sourceActorPrincipalId,
2558
+ // Same as the history-restored branch: the enqueue declined, so this
2559
+ // click is the turn about to run.
2560
+ trustContext: actionTrustContext,
2561
+ isInteractive: SURFACE_ACTION_TURN_IS_INTERACTIVE,
2521
2562
  scripted: isSyntheticSurfaceActionContent(content),
2522
2563
  })
2523
2564
  .catch((err) => {
@@ -2582,7 +2623,7 @@ export function refreshSurfacesForApp(
2582
2623
  }
2583
2624
 
2584
2625
  // Push the update to the client
2585
- ctx.sendToClient({
2626
+ ctx.emit({
2586
2627
  type: "ui_surface_update",
2587
2628
  conversationId: ctx.conversationId,
2588
2629
  surfaceId,
@@ -3279,8 +3320,12 @@ export async function surfaceProxyResolver(
3279
3320
  }
3280
3321
  inputActions = valid.length > 0 ? valid : undefined;
3281
3322
  }
3282
- const actions =
3283
- choiceData !== undefined ? buildChoiceActions(choiceData) : inputActions;
3323
+ const isActionless = ACTIONLESS_SURFACE_TYPES.has(surfaceType);
3324
+ const actions = isActionless
3325
+ ? undefined
3326
+ : choiceData !== undefined
3327
+ ? buildChoiceActions(choiceData)
3328
+ : inputActions;
3284
3329
  const hasActions = Array.isArray(actions) && actions.length > 0;
3285
3330
  if (surfaceType === "choice" && !hasActions) {
3286
3331
  return {
@@ -3332,7 +3377,11 @@ export async function surfaceProxyResolver(
3332
3377
  : surfaceType === "table"
3333
3378
  ? hasActions
3334
3379
  : INTERACTIVE_SURFACE_TYPES.includes(surfaceType);
3335
- const awaitAction = (input.await_action as boolean) ?? isInteractive;
3380
+ // An explicit `await_action: true` is honored for every other type; an
3381
+ // actionless surface has nothing to await, so it is forced false.
3382
+ const awaitAction = isActionless
3383
+ ? false
3384
+ : ((input.await_action as boolean) ?? isInteractive);
3336
3385
 
3337
3386
  // Only one non-persistent interactive surface at a time. If another
3338
3387
  // surface is already awaiting user input, reject this one so the LLM
@@ -3411,7 +3460,7 @@ export async function surfaceProxyResolver(
3411
3460
  "Sending ui_surface_show to client",
3412
3461
  );
3413
3462
 
3414
- ctx.sendToClient({
3463
+ ctx.emit({
3415
3464
  type: "ui_surface_show",
3416
3465
  conversationId: ctx.conversationId,
3417
3466
  surfaceId,
@@ -3530,7 +3579,7 @@ export async function surfaceProxyResolver(
3530
3579
  mergedData = patch;
3531
3580
  }
3532
3581
 
3533
- ctx.sendToClient({
3582
+ ctx.emit({
3534
3583
  type: "ui_surface_update",
3535
3584
  conversationId: ctx.conversationId,
3536
3585
  surfaceId,
@@ -3588,7 +3637,7 @@ export async function surfaceProxyResolver(
3588
3637
  isError: true,
3589
3638
  };
3590
3639
  }
3591
- ctx.sendToClient({
3640
+ ctx.emit({
3592
3641
  type: "ui_surface_complete",
3593
3642
  conversationId: ctx.conversationId,
3594
3643
  surfaceId,
@@ -3596,7 +3645,7 @@ export async function surfaceProxyResolver(
3596
3645
  submittedData: lastAction.data,
3597
3646
  });
3598
3647
  } else {
3599
- ctx.sendToClient({
3648
+ ctx.emit({
3600
3649
  type: "ui_surface_dismiss",
3601
3650
  conversationId: ctx.conversationId,
3602
3651
  surfaceId,
@@ -3698,7 +3747,7 @@ export async function surfaceProxyResolver(
3698
3747
  // Inline-only preview card emitted during app_create — do not open a
3699
3748
  // workspace panel and do not register surface state. The client renders
3700
3749
  // this as a tappable inline card that opens the app on demand.
3701
- ctx.sendToClient({
3750
+ ctx.emit({
3702
3751
  type: "ui_surface_show",
3703
3752
  conversationId: ctx.conversationId,
3704
3753
  surfaceId,
@@ -3728,7 +3777,7 @@ export async function surfaceProxyResolver(
3728
3777
  title: app.name,
3729
3778
  });
3730
3779
 
3731
- ctx.sendToClient({
3780
+ ctx.emit({
3732
3781
  type: "ui_surface_show",
3733
3782
  conversationId: ctx.conversationId,
3734
3783
  surfaceId,
@@ -245,9 +245,7 @@ export function createToolExecutor(
245
245
  toolUseId?: string,
246
246
  ) => Promise<ToolExecutionResult> {
247
247
  // Register the conversation's sendToClient for browser screencast surface messages
248
- registerConversationSender(ctx.conversationId, (msg) =>
249
- ctx.sendToClient(msg),
250
- );
248
+ registerConversationSender(ctx.conversationId, (msg) => ctx.emit(msg));
251
249
 
252
250
  // Execution-layer allowlist gate (`subagentToolGateMode === "execution"`,
253
251
  // see {@link SubagentToolGateMode}): rejects non-allowlisted calls BEFORE
@@ -389,8 +387,7 @@ export function createToolExecutor(
389
387
  // Per-turn trust snapshot: prefer the snapshot captured at turn start so
390
388
  // a concurrent owner meta command (/status, /clean) that mutates the live
391
389
  // trustContext cannot elevate the in-flight turn to guardian.
392
- const turnTrust =
393
- ctx.currentTurnTrustContext ?? ctx.trustContext ?? FALLBACK_TURN_TRUST;
390
+ const turnTrust = ctx.getTurnOrRestingTrust() ?? FALLBACK_TURN_TRUST;
394
391
 
395
392
  const toolContext: ToolContext = {
396
393
  workingDir: ctx.workingDir,
@@ -441,7 +438,7 @@ export function createToolExecutor(
441
438
  sendToClient: (msg) => {
442
439
  // Tool context's sendToClient uses a loose { type: string; [key: string]: unknown }
443
440
  // signature, but at runtime these are always AssistantEvent instances.
444
- ctx.sendToClient(msg as AssistantEvent);
441
+ ctx.emit(msg as AssistantEvent);
445
442
  if (msg.type === "ui_surface_show") {
446
443
  // The tool-context sendToClient signature is loose, so the show
447
444
  // message's fields are untyped here; map them through the same
@@ -41,6 +41,7 @@ import {
41
41
  derefToolResultReReads,
42
42
  postTurnTruncateToolResults,
43
43
  } from "../context/post-turn-tool-result-truncation.js";
44
+ import { isGuardianCardRow } from "../notifications/approval-card-data.js";
44
45
  import { PermissionPrompter } from "../permissions/prompter.js";
45
46
  import { SecretPrompter } from "../permissions/secret-prompter.js";
46
47
  import type { UserDecision } from "../permissions/types.js";
@@ -319,7 +320,21 @@ export class Conversation {
319
320
  /** @internal */ prompter: PermissionPrompter;
320
321
  /** @internal */ secretPrompter: SecretPrompter;
321
322
  private executor: ToolExecutor;
322
- /** @internal */ sendToClient: (msg: AssistantEvent) => void;
323
+ /**
324
+ * The conversation's event sink, fixed for its whole life. Top-level
325
+ * conversations deliver to the SSE hub, so every subscribed client sees
326
+ * every event without any per-turn wiring; a subagent's sink re-envelopes
327
+ * its events under the parent conversation. Reached only through
328
+ * {@link emit}, which also notifies {@link addEventObserver} observers.
329
+ */
330
+ private readonly sendToClient: (msg: AssistantEvent) => void;
331
+ /**
332
+ * Observers notified after every {@link emit}, in registration order. An
333
+ * observer sees the event after the sink delivered it, so anything it does
334
+ * in response (e.g. voice auto-resolving a confirmation) lands on the wire
335
+ * after the event itself.
336
+ */
337
+ private readonly eventObservers = new Set<(msg: AssistantEvent) => void>();
323
338
  /** @internal */ workingDir: string;
324
339
  /** @internal */ allowedToolNames?: Set<string>;
325
340
  /**
@@ -435,7 +450,18 @@ export class Conversation {
435
450
  * @internal
436
451
  */
437
452
  currentCallSite?: LLMCallSite;
438
- /** @internal */ hasNoClient = false;
453
+ /**
454
+ * Whether no human is present to see UI or answer prompts. Derived from the
455
+ * in-flight turn's interactivity ({@link currentTurnIsNonInteractive}); a
456
+ * conversation with no turn in flight has no client. Presence is a property
457
+ * of the turn, never of where events are delivered, so there is no setter:
458
+ * dispatch paths declare interactivity per turn (`isInteractive` on
459
+ * `runAgentLoop`, or a wake's pin), and this reads it.
460
+ * @internal
461
+ */
462
+ get hasNoClient(): boolean {
463
+ return this.currentTurnIsNonInteractive ?? true;
464
+ }
439
465
  /**
440
466
  * For subagent conversations, the id of the parent that spawned this one; set
441
467
  * once at construction and never reassigned. `undefined` for top-level
@@ -757,10 +783,10 @@ export class Conversation {
757
783
  this.workingDir = workingDir;
758
784
  this.sendToClient = sendToClient;
759
785
  this.graphMemory = new ConversationGraphMemory(conversationId);
760
- this.prompter = new PermissionPrompter(sendToClient);
786
+ // The prompter emits through the conversation so its confirmation_request
787
+ // reaches the sink and every observer (voice policy) like any other event.
788
+ this.prompter = new PermissionPrompter((msg) => this.emit(msg));
761
789
  this.prompter.setOnStateChanged((requestId, state, source, toolUseId) => {
762
- // Route through emitConfirmationStateChanged so the event reaches
763
- // the client via sendToClient (wired to the SSE hub for HTTP conversations).
764
790
  this.emitConfirmationStateChanged({
765
791
  conversationId: this.conversationId,
766
792
  requestId,
@@ -1316,6 +1342,16 @@ export class Conversation {
1316
1342
  parsedMessages.length,
1317
1343
  );
1318
1344
  for (const [index, message] of parsedMessages.entries()) {
1345
+ // Applied after the compaction slice, never before it: the slice and
1346
+ // `rowToHistoryIndex` are both computed against the full row list, so
1347
+ // dropping earlier would shift them. A dropped row maps to a null
1348
+ // history index exactly like a fully-injected user row that strips to
1349
+ // nothing. `index` is shared with `slicedDbMessages`, which
1350
+ // `parsedMessages` maps 1:1.
1351
+ if (isGuardianCardRow(slicedDbMessages[index]?.content)) {
1352
+ preRepairIndexBySlicedRow[index] = null;
1353
+ continue;
1354
+ }
1319
1355
  const stripped =
1320
1356
  index < preStrippedCount
1321
1357
  ? stripInjectionsForCompaction([message])
@@ -1418,7 +1454,7 @@ export class Conversation {
1418
1454
  messageCount: this.messages.length,
1419
1455
  });
1420
1456
 
1421
- this.restoreSurfaceStateFromHistory();
1457
+ this.restoreSurfaceStateFromHistory(parsedMessages);
1422
1458
  this.graphMemory.restoreState();
1423
1459
 
1424
1460
  // Row→history correspondence for this load: slice offset, then the
@@ -1450,14 +1486,19 @@ export class Conversation {
1450
1486
  * populate surfaceState so that findConversationBySurfaceId works for
1451
1487
  * surfaces restored from history (e.g. after daemon restart).
1452
1488
  *
1453
- * Only scans live (non-compacted) messages in this.messages — not all DB
1454
- * rows — because surface IDs are not globally unique and restoring stale
1455
- * compacted surfaces would let findConversationBySurfaceId route actions
1456
- * to the wrong conversation.
1489
+ * Scans the live (non-compacted) window only, never all DB rows, because
1490
+ * surface IDs are not globally unique and restoring stale compacted
1491
+ * surfaces would let findConversationBySurfaceId route actions to the wrong
1492
+ * conversation.
1493
+ *
1494
+ * Takes that window as rows rather than reading `this.messages`, because a
1495
+ * surface's lifecycle and the model's context are different questions. A
1496
+ * guardian card is absent from `this.messages`, but it is exactly the card
1497
+ * whose Approve/Reject buttons must still route after a restart.
1457
1498
  */
1458
- private restoreSurfaceStateFromHistory(): void {
1499
+ private restoreSurfaceStateFromHistory(liveWindow: Message[]): void {
1459
1500
  this.surfaceState.clear();
1460
- for (const msg of this.messages) {
1501
+ for (const msg of liveWindow) {
1461
1502
  if (!Array.isArray(msg.content)) {
1462
1503
  continue;
1463
1504
  }
@@ -1489,31 +1530,55 @@ export class Conversation {
1489
1530
  await this.loadFromDb();
1490
1531
  }
1491
1532
 
1492
- updateClient(
1493
- sendToClient: (msg: AssistantEvent) => void,
1494
- hasNoClient = false,
1495
- ): void {
1496
- this.sendToClient = sendToClient;
1497
- this.hasNoClient = hasNoClient;
1498
- this.prompter.updateSender(sendToClient);
1499
-
1500
- // Replay last activity state so a reconnecting client sees the current phase
1501
- // instead of being stuck on the last state it received before disconnection.
1502
- if (!hasNoClient && this.lastActivityStateMsg) {
1533
+ /**
1534
+ * Deliver an event through the conversation's sink, then to every observer.
1535
+ * The single emission point for conversation-level events (activity state,
1536
+ * confirmation prompts and state, notifier output, out-of-turn pushes); the
1537
+ * agent loop's own stream rides its per-turn `onEvent`, which defaults to
1538
+ * this when the caller passes none.
1539
+ */
1540
+ readonly emit = (msg: AssistantEvent): void => {
1541
+ try {
1542
+ this.sendToClient(msg);
1543
+ } catch (err) {
1544
+ log.warn(
1545
+ { err, conversationId: this.conversationId, type: msg.type },
1546
+ "conversation sink threw",
1547
+ );
1548
+ }
1549
+ for (const observer of this.eventObservers) {
1503
1550
  try {
1504
- sendToClient(this.lastActivityStateMsg);
1551
+ observer(msg);
1505
1552
  } catch (err) {
1506
1553
  log.warn(
1507
- { err, conversationId: this.conversationId },
1508
- "Failed to replay activity state on client reconnection",
1554
+ { err, conversationId: this.conversationId, type: msg.type },
1555
+ "conversation event observer threw",
1509
1556
  );
1510
1557
  }
1511
1558
  }
1559
+ };
1560
+
1561
+ /**
1562
+ * Observe every event this conversation emits, after the sink delivered it.
1563
+ * For policy layered on delivery (voice auto-resolves approval prompts it
1564
+ * has no UI for), not for delivery itself. Returns the disposer.
1565
+ */
1566
+ addEventObserver(observer: (msg: AssistantEvent) => void): () => void {
1567
+ this.eventObservers.add(observer);
1568
+ return () => {
1569
+ this.eventObservers.delete(observer);
1570
+ };
1512
1571
  }
1513
1572
 
1514
- /** Returns the current sendToClient reference for identity comparison. */
1515
- getCurrentSender(): (msg: AssistantEvent) => void {
1516
- return this.sendToClient;
1573
+ /**
1574
+ * Re-emit the last activity state so a client that reconnected mid-phase
1575
+ * sees the current phase instead of the last one it received before
1576
+ * disconnecting. The send route calls this on every interactive send.
1577
+ */
1578
+ replayActivityState(): void {
1579
+ if (this.lastActivityStateMsg) {
1580
+ this.emit(this.lastActivityStateMsg);
1581
+ }
1517
1582
  }
1518
1583
 
1519
1584
  setSubagentAllowedTools(tools: Set<string> | undefined): void {
@@ -1801,7 +1866,7 @@ export class Conversation {
1801
1866
  } {
1802
1867
  return enqueueMessageImpl(this, {
1803
1868
  ...options,
1804
- onEvent: options.onEvent ?? this.sendToClient,
1869
+ onEvent: options.onEvent ?? this.emit,
1805
1870
  });
1806
1871
  }
1807
1872
 
@@ -1976,14 +2041,7 @@ export class Conversation {
1976
2041
  type: "confirmation_state_changed",
1977
2042
  ...params,
1978
2043
  } as AssistantEvent;
1979
- try {
1980
- this.sendToClient(msg);
1981
- } catch (err) {
1982
- log.warn(
1983
- { err, conversationId: this.conversationId },
1984
- "sendToClient threw in emitConfirmationStateChanged",
1985
- );
1986
- }
2044
+ this.emit(msg);
1987
2045
  }
1988
2046
 
1989
2047
  emitActivityState(
@@ -2008,14 +2066,7 @@ export class Conversation {
2008
2066
  ...(statusText ? { statusText } : {}),
2009
2067
  } as AssistantEvent;
2010
2068
  this.lastActivityStateMsg = msg;
2011
- try {
2012
- this.sendToClient(msg);
2013
- } catch (err) {
2014
- log.warn(
2015
- { err, conversationId: this.conversationId },
2016
- "sendToClient threw in emitActivityState",
2017
- );
2018
- }
2069
+ this.emit(msg);
2019
2070
  }
2020
2071
 
2021
2072
  /**
@@ -2069,7 +2120,7 @@ export class Conversation {
2069
2120
  onEvent?: (msg: AssistantEvent) => void,
2070
2121
  ): void {
2071
2122
  try {
2072
- (onEvent ?? this.sendToClient)({
2123
+ (onEvent ?? this.emit)({
2073
2124
  type: "context_window_usage",
2074
2125
  conversationId: this.conversationId,
2075
2126
  tokens,
@@ -2421,11 +2472,11 @@ export class Conversation {
2421
2472
  ) {
2422
2473
  await this.agentLoop.compactionCircuit.recordOutcome(
2423
2474
  result.summaryFailed,
2424
- this.sendToClient,
2475
+ this.emit,
2425
2476
  );
2426
2477
  }
2427
2478
  if (result.compacted) {
2428
- await applyCompactionResult(this, result, this.sendToClient, null, {
2479
+ await applyCompactionResult(this, result, this.emit, null, {
2429
2480
  slackContextCompactionWatermarkTs:
2430
2481
  fixedBoundarySlackWatermarkTs ??
2431
2482
  getSlackCompactionWatermarkForPrefix(
@@ -2488,6 +2539,54 @@ export class Conversation {
2488
2539
  return this.authContext;
2489
2540
  }
2490
2541
 
2542
+ /**
2543
+ * Trust the in-flight turn is executing under.
2544
+ *
2545
+ * Use this for authorization and for routing a reply to the requester:
2546
+ * cases where substituting the conversation's owner would be wrong rather
2547
+ * than approximate. Provenance is not such a case; see
2548
+ * {@link getTurnOrRestingTrust} and `docs/architecture/turn-actor.md`.
2549
+ *
2550
+ * `undefined` when no turn recorded one, which is a gap in the entry point
2551
+ * rather than an answer. Deliberately does not fall back to the
2552
+ * conversation's trust: a caller that can accept the conversation's owner
2553
+ * instead spells `?? getTrustContext()`, so the substitution is visible
2554
+ * where it happens.
2555
+ */
2556
+ getTurnTrust(): TrustContext | undefined {
2557
+ return this.currentTurnTrustContext;
2558
+ }
2559
+
2560
+ /**
2561
+ * Trust of the actor the conversation belongs to, independent of any turn.
2562
+ *
2563
+ * Use this where there is no turn to speak of: routes reporting on a
2564
+ * conversation, hydration, and persisting conversation-level options. A
2565
+ * caller that wants the conversation's owner *rather than* whoever is
2566
+ * currently acting should be obviously doing so; if it is not obvious,
2567
+ * {@link getTurnTrust} is probably the one meant.
2568
+ */
2569
+ getTrustContext(): TrustContext | undefined {
2570
+ return this.trustContext;
2571
+ }
2572
+
2573
+ /**
2574
+ * Trust of the in-flight turn, or the conversation's owner when the turn
2575
+ * recorded none. The substitution is in the name: callers that can accept
2576
+ * the owner as a stand-in ask this, including provenance stamping, whose
2577
+ * readers treat an absent trust class as more trusted than `"unknown"`.
2578
+ * Callers for which the owner would be wrong rather than approximate call
2579
+ * {@link getTurnTrust} and handle `undefined`.
2580
+ *
2581
+ * The fallback half is load-bearing, not transitional politeness: a
2582
+ * deferred wake fires with no inbound actor, and refusing it an answer
2583
+ * denies every sensitive tool in the resumed turn (LUM-2929). It becomes
2584
+ * removable in one place when every entry point records a turn actor.
2585
+ */
2586
+ getTurnOrRestingTrust(): TrustContext | undefined {
2587
+ return this.currentTurnTrustContext ?? this.trustContext;
2588
+ }
2589
+
2491
2590
  /**
2492
2591
  * The actor principal that owns the current turn, for host-proxy routing.
2493
2592
  * Prefers the in-flight turn's actor over the conversation's resting
@@ -2633,6 +2732,8 @@ export class Conversation {
2633
2732
  titleText?: string;
2634
2733
  /** See {@link runAgentLoopImpl} — hidden machine-signal turn marker. */
2635
2734
  isHiddenPrompt?: boolean;
2735
+ /** See {@link runAgentLoopImpl}: triggering row's daemon-authored kind. */
2736
+ messageKind?: string;
2636
2737
  /**
2637
2738
  * See {@link runAgentLoopImpl}: the row the end-of-turn reply
2638
2739
  * notification treats as the prompt this turn answers.
@@ -2674,7 +2775,7 @@ export class Conversation {
2674
2775
  this,
2675
2776
  content,
2676
2777
  userMessageId,
2677
- onEvent ?? this.sendToClient,
2778
+ onEvent ?? this.emit,
2678
2779
  rest,
2679
2780
  );
2680
2781
  }
@@ -2700,7 +2801,7 @@ export class Conversation {
2700
2801
  this.cacheWarmAbort = undefined;
2701
2802
  return processMessageImpl(this, {
2702
2803
  ...options,
2703
- onEvent: options.onEvent ?? this.sendToClient,
2804
+ onEvent: options.onEvent ?? this.emit,
2704
2805
  });
2705
2806
  }
2706
2807
 
@@ -2728,6 +2829,7 @@ export class Conversation {
2728
2829
  actionId: string,
2729
2830
  data?: Record<string, unknown>,
2730
2831
  sourceActorPrincipalId?: string,
2832
+ requesterTrustContext?: TrustContext,
2731
2833
  ): Promise<SurfaceActionResult> {
2732
2834
  return handleSurfaceActionImpl(
2733
2835
  this,
@@ -2735,6 +2837,7 @@ export class Conversation {
2735
2837
  actionId,
2736
2838
  data,
2737
2839
  sourceActorPrincipalId,
2840
+ requesterTrustContext,
2738
2841
  );
2739
2842
  }
2740
2843
 
@@ -130,7 +130,7 @@ function pushStepsUpdate(
130
130
  };
131
131
  const updatedData = { ...card, templateData: updatedTemplateData };
132
132
  stored.data = updatedData;
133
- ctx.sendToClient({
133
+ ctx.emit({
134
134
  type: "ui_surface_update",
135
135
  conversationId: ctx.conversationId,
136
136
  surfaceId: SURFACE_ID,
@@ -218,7 +218,7 @@ export function updateDoordashProgress(
218
218
  },
219
219
  } satisfies CardSurfaceData;
220
220
  ctx.surfaceState.set(SURFACE_ID, { surfaceType: "card", data });
221
- ctx.sendToClient({
221
+ ctx.emit({
222
222
  type: "ui_surface_show",
223
223
  conversationId: ctx.conversationId,
224
224
  surfaceId: SURFACE_ID,