@vellumai/assistant 0.7.0 → 0.7.1

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 (666) hide show
  1. package/ARCHITECTURE.md +6 -7
  2. package/Dockerfile +1 -0
  3. package/README.md +2 -2
  4. package/__tests__/permissions/gateway-threshold-reader.test.ts +79 -139
  5. package/bun.lock +3 -0
  6. package/docs/architecture/security.md +18 -16
  7. package/knip.json +1 -0
  8. package/node_modules/@vellumai/skill-host-contracts/__tests__/client.test.ts +1 -5
  9. package/node_modules/@vellumai/skill-host-contracts/src/assistant-event.ts +0 -5
  10. package/node_modules/@vellumai/skill-host-contracts/src/client.ts +10 -16
  11. package/node_modules/@vellumai/skill-host-contracts/src/skill-host.ts +1 -9
  12. package/node_modules/@vellumai/skill-host-contracts/src/tool-types.ts +12 -12
  13. package/node_modules/@vellumai/slack-text/bun.lock +24 -0
  14. package/node_modules/@vellumai/slack-text/package.json +18 -0
  15. package/node_modules/@vellumai/slack-text/src/index.test.ts +153 -0
  16. package/node_modules/@vellumai/slack-text/src/index.ts +235 -0
  17. package/node_modules/@vellumai/slack-text/tsconfig.json +20 -0
  18. package/openapi.yaml +294 -107
  19. package/package.json +4 -2
  20. package/scripts/generate-openapi.ts +16 -111
  21. package/src/__tests__/agent-wake-override-profile.test.ts +23 -1
  22. package/src/__tests__/anthropic-provider.test.ts +56 -13
  23. package/src/__tests__/app-conversation-ids-backfill.test.ts +278 -0
  24. package/src/__tests__/app-conversation-ids.test.ts +151 -0
  25. package/src/__tests__/approval-cascade.test.ts +0 -15
  26. package/src/__tests__/approval-routes-http.test.ts +6 -17
  27. package/src/__tests__/assistant-event-hub.test.ts +126 -77
  28. package/src/__tests__/assistant-event.test.ts +0 -5
  29. package/src/__tests__/assistant-events-sse-hardening.test.ts +37 -15
  30. package/src/__tests__/assistant-feature-flags-integration.test.ts +0 -29
  31. package/src/__tests__/background-shell-host-bash.test.ts +34 -43
  32. package/src/__tests__/call-controller.test.ts +1 -1
  33. package/src/__tests__/call-site-routing-provider.test.ts +193 -0
  34. package/src/__tests__/channel-approval-routes.test.ts +10 -296
  35. package/src/__tests__/channel-approvals.test.ts +25 -17
  36. package/src/__tests__/channel-guardian.test.ts +100 -146
  37. package/src/__tests__/checker.test.ts +20 -34
  38. package/src/__tests__/compact-event-conversation-id-guard.test.ts +50 -0
  39. package/src/__tests__/compaction-events.test.ts +2 -0
  40. package/src/__tests__/config-schema.test.ts +6 -48
  41. package/src/__tests__/config-watcher.test.ts +12 -0
  42. package/src/__tests__/connection-policy.test.ts +1 -52
  43. package/src/__tests__/contacts-write.test.ts +2 -64
  44. package/src/__tests__/context-image-dimensions.test.ts +1 -1
  45. package/src/__tests__/context-search-memory-source.test.ts +120 -1
  46. package/src/__tests__/context-search-memory-v2-source.test.ts +383 -0
  47. package/src/__tests__/context-search-pkb-source.test.ts +49 -0
  48. package/src/__tests__/context-search-workspace-source.test.ts +9 -22
  49. package/src/__tests__/context-window-manager.test.ts +46 -0
  50. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +2 -0
  51. package/src/__tests__/conversation-agent-loop-overflow.test.ts +102 -29
  52. package/src/__tests__/conversation-agent-loop.test.ts +980 -13
  53. package/src/__tests__/conversation-analysis-routes.test.ts +12 -10
  54. package/src/__tests__/conversation-attention-telegram.test.ts +11 -3
  55. package/src/__tests__/conversation-confirmation-signals.test.ts +0 -291
  56. package/src/__tests__/conversation-history-web-search.test.ts +4 -3
  57. package/src/__tests__/conversation-inference-profile-route.test.ts +12 -23
  58. package/src/__tests__/conversation-lifecycle.test.ts +4 -4
  59. package/src/__tests__/conversation-process-callsite.test.ts +79 -2
  60. package/src/__tests__/conversation-queue.test.ts +3 -8
  61. package/src/__tests__/conversation-routes-disk-view.test.ts +1 -161
  62. package/src/__tests__/conversation-routes-guardian-reply.test.ts +0 -32
  63. package/src/__tests__/conversation-routes-slash-commands.test.ts +75 -66
  64. package/src/__tests__/conversation-runtime-assembly.test.ts +257 -3
  65. package/src/__tests__/conversation-slash-commands.test.ts +24 -4
  66. package/src/__tests__/conversation-slash-queue.test.ts +2 -0
  67. package/src/__tests__/conversation-speed-override.test.ts +0 -3
  68. package/src/__tests__/conversation-starter-routes.test.ts +79 -2
  69. package/src/__tests__/conversation-surfaces-standalone-payloads.test.ts +12 -5
  70. package/src/__tests__/conversation-surfaces-standalone.test.ts +18 -14
  71. package/src/__tests__/conversation-surfaces-state-update.test.ts +3 -2
  72. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +8 -46
  73. package/src/__tests__/conversation-usage.test.ts +253 -3
  74. package/src/__tests__/credential-execution-shell-lockdown.test.ts +0 -39
  75. package/src/__tests__/credential-health-service.test.ts +68 -0
  76. package/src/__tests__/credential-security-e2e.test.ts +4 -3
  77. package/src/__tests__/credential-security-invariants.test.ts +1 -5
  78. package/src/__tests__/credential-token-resolver.test.ts +180 -0
  79. package/src/__tests__/cu-unified-flow.test.ts +33 -16
  80. package/src/__tests__/daemon-assistant-events.test.ts +34 -21
  81. package/src/__tests__/daemon-credential-client.test.ts +4 -1
  82. package/src/__tests__/db-connection-isolation.test.ts +125 -0
  83. package/src/__tests__/db-migration-rollback.test.ts +101 -0
  84. package/src/__tests__/db-slack-compaction-watermark-migration.test.ts +169 -0
  85. package/src/__tests__/deterministic-verification-control-plane.test.ts +7 -80
  86. package/src/__tests__/document-conversations.test.ts +332 -0
  87. package/src/__tests__/embedding-managed-proxy-selection.test.ts +2 -2
  88. package/src/__tests__/emit-event-signal.test.ts +4 -6
  89. package/src/__tests__/events-client-registration.test.ts +193 -49
  90. package/src/__tests__/filing-service.test.ts +58 -7
  91. package/src/__tests__/first-greeting.test.ts +156 -150
  92. package/src/__tests__/fixtures/mock-chrome-extension.ts +108 -66
  93. package/src/__tests__/get-skill-detail-audit.test.ts +3 -8
  94. package/src/__tests__/guardian-binding-drift-heal.test.ts +1 -1
  95. package/src/__tests__/guardian-dispatch.test.ts +1 -1
  96. package/src/__tests__/guardian-grant-minting.test.ts +7 -2
  97. package/src/__tests__/guardian-routing-invariants.test.ts +7 -2
  98. package/src/__tests__/guardian-routing-state.test.ts +1 -1
  99. package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +32 -11
  100. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +2 -83
  101. package/src/__tests__/headless-browser-mode.test.ts +4 -9
  102. package/src/__tests__/headless-browser-navigate.test.ts +21 -20
  103. package/src/__tests__/heartbeat-service.test.ts +289 -7
  104. package/src/__tests__/helpers/channel-test-adapter.ts +2 -2
  105. package/src/__tests__/helpers/create-guardian-binding.ts +91 -0
  106. package/src/__tests__/host-bash-proxy.test.ts +46 -122
  107. package/src/__tests__/host-browser-e2e-cloud.test.ts +36 -497
  108. package/src/__tests__/host-browser-e2e-self-hosted-capability.test.ts +26 -96
  109. package/src/__tests__/host-browser-proxy.test.ts +111 -185
  110. package/src/__tests__/host-browser-routes.test.ts +45 -75
  111. package/src/__tests__/host-browser-ws-events-e2e.test.ts +26 -30
  112. package/src/__tests__/host-cu-proxy.test.ts +56 -111
  113. package/src/__tests__/host-file-proxy.test.ts +44 -98
  114. package/src/__tests__/host-file-read-tool.test.ts +42 -21
  115. package/src/__tests__/host-shell-tool.test.ts +33 -68
  116. package/src/__tests__/host-transfer-pending-interactions.test.ts +2 -18
  117. package/src/__tests__/host-transfer-proxy.test.ts +43 -53
  118. package/src/__tests__/http-user-message-parity.test.ts +0 -6
  119. package/src/__tests__/inbound-slack-persistence.test.ts +31 -0
  120. package/src/__tests__/injector-chain.test.ts +10 -5
  121. package/src/__tests__/injector-pkb-v2-silenced.test.ts +124 -0
  122. package/src/__tests__/inline-command-runner.test.ts +0 -66
  123. package/src/__tests__/inline-skill-load-permissions.test.ts +0 -2
  124. package/src/__tests__/install-skill-routing.test.ts +1 -13
  125. package/src/__tests__/llm-callsite-catalog.test.ts +34 -0
  126. package/src/__tests__/llm-catalog-parity.test.ts +90 -0
  127. package/src/__tests__/llm-context-resolution.test.ts +180 -0
  128. package/src/__tests__/llm-resolver.test.ts +80 -12
  129. package/src/__tests__/llm-usage-store.test.ts +269 -4
  130. package/src/__tests__/log-export-routes.test.ts +89 -0
  131. package/src/__tests__/managed-profile-guard.test.ts +225 -0
  132. package/src/__tests__/managed-skill-lifecycle.test.ts +0 -10
  133. package/src/__tests__/manual-token-reconciliation.test.ts +334 -0
  134. package/src/__tests__/memory-v2-static-injector.test.ts +95 -0
  135. package/src/__tests__/migration-cross-version-compatibility.test.ts +197 -291
  136. package/src/__tests__/migration-export-http.test.ts +33 -26
  137. package/src/__tests__/migration-export-streaming.test.ts +18 -10
  138. package/src/__tests__/migration-export-to-gcs.test.ts +49 -9
  139. package/src/__tests__/migration-import-commit-http.test.ts +66 -21
  140. package/src/__tests__/migration-import-from-gcs.test.ts +50 -9
  141. package/src/__tests__/migration-import-from-url.test.ts +20 -6
  142. package/src/__tests__/migration-import-preflight-http.test.ts +95 -95
  143. package/src/__tests__/migration-parity-persistence.test.ts +62 -25
  144. package/src/__tests__/migration-transport.test.ts +115 -23
  145. package/src/__tests__/migration-validate-http.test.ts +105 -80
  146. package/src/__tests__/migration-wizard.test.ts +133 -27
  147. package/src/__tests__/non-member-access-request.test.ts +1 -1
  148. package/src/__tests__/notification-guardian-path.test.ts +1 -1
  149. package/src/__tests__/oauth-store.test.ts +19 -0
  150. package/src/__tests__/platform-bash-auto-approve.test.ts +21 -12
  151. package/src/__tests__/prechat-onboarding-contract.test.ts +31 -7
  152. package/src/__tests__/pricing.test.ts +68 -4
  153. package/src/__tests__/process-message-background-slack.test.ts +331 -0
  154. package/src/__tests__/provider-managed-proxy-integration.test.ts +153 -17
  155. package/src/__tests__/provider-send-message-override-profile.test.ts +50 -0
  156. package/src/__tests__/provider-usage-tracking.test.ts +208 -0
  157. package/src/__tests__/reaction-persistence.test.ts +9 -6
  158. package/src/__tests__/rebind-secrets-screen.test.ts +53 -16
  159. package/src/__tests__/recording-handler.test.ts +64 -81
  160. package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +4 -3
  161. package/src/__tests__/relay-server.test.ts +18 -13
  162. package/src/__tests__/require-fresh-approval.test.ts +13 -22
  163. package/src/__tests__/runtime-attachment-metadata.test.ts +1 -1
  164. package/src/__tests__/runtime-events-sse-parity.test.ts +3 -4
  165. package/src/__tests__/runtime-events-sse.test.ts +3 -12
  166. package/src/__tests__/search-skills-unified.test.ts +9 -15
  167. package/src/__tests__/secret-ingress-cli.test.ts +2 -5
  168. package/src/__tests__/secret-ingress-http.test.ts +0 -4
  169. package/src/__tests__/secret-onetime-send.test.ts +4 -2
  170. package/src/__tests__/secret-prompt-log-hygiene.test.ts +24 -7
  171. package/src/__tests__/secret-prompter-channel-fallback.test.ts +42 -47
  172. package/src/__tests__/secret-response-routing.test.ts +29 -15
  173. package/src/__tests__/secret-routes-managed-proxy.test.ts +5 -1
  174. package/src/__tests__/secret-scanner.test.ts +2 -545
  175. package/src/__tests__/send-endpoint-busy.test.ts +9 -24
  176. package/src/__tests__/settings-routes.test.ts +1 -1
  177. package/src/__tests__/shell-credential-ref.test.ts +0 -8
  178. package/src/__tests__/shell-tool-proxy-mode.test.ts +0 -56
  179. package/src/__tests__/skill-script-runner-sandbox.test.ts +0 -11
  180. package/src/__tests__/skill-tool-factory.test.ts +97 -0
  181. package/src/__tests__/skills-file-content-endpoint.test.ts +9 -30
  182. package/src/__tests__/skills-files-catalog-fallback.test.ts +11 -17
  183. package/src/__tests__/slack-inbound-verification.test.ts +1 -62
  184. package/src/__tests__/subagent-fork-notifications.test.ts +57 -47
  185. package/src/__tests__/subagent-manager-notify.test.ts +70 -70
  186. package/src/__tests__/subagent-notify-parent.test.ts +80 -83
  187. package/src/__tests__/system-prompt.test.ts +115 -13
  188. package/src/__tests__/terminal-tools.test.ts +0 -89
  189. package/src/__tests__/thread-backfill.test.ts +945 -31
  190. package/src/__tests__/tool-domain-event-publisher.test.ts +0 -36
  191. package/src/__tests__/tool-execute-pipeline.test.ts +0 -6
  192. package/src/__tests__/tool-execution-abort-cleanup.test.ts +0 -16
  193. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +9 -19
  194. package/src/__tests__/tool-executor-lifecycle-events.test.ts +4 -7
  195. package/src/__tests__/tool-executor.test.ts +12 -19
  196. package/src/__tests__/tool-metrics-listener.test.ts +0 -35
  197. package/src/__tests__/tool-side-effects-slack-dm.test.ts +1 -0
  198. package/src/__tests__/tool-trace-listener.test.ts +0 -17
  199. package/src/__tests__/transfer-progress-screen.test.ts +63 -26
  200. package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +2 -149
  201. package/src/__tests__/trusted-contact-multichannel.test.ts +2 -4
  202. package/src/__tests__/trusted-contact-verification.test.ts +1 -1
  203. package/src/__tests__/tts-catalog-parity.test.ts +16 -5
  204. package/src/__tests__/usage-attribution.test.ts +247 -0
  205. package/src/__tests__/usage-cli.test.ts +143 -0
  206. package/src/__tests__/usage-grouped-buckets.test.ts +155 -0
  207. package/src/__tests__/usage-routes.test.ts +150 -0
  208. package/src/__tests__/validation-results-screen.test.ts +39 -16
  209. package/src/__tests__/vbundle-pax-and-symlink.test.ts +12 -3
  210. package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +49 -137
  211. package/src/__tests__/verification-control-plane-policy.test.ts +4 -7
  212. package/src/__tests__/voice-session-bridge.test.ts +5 -5
  213. package/src/__tests__/workspace-migration-062-drop-memory-v2-edges-json.test.ts +103 -0
  214. package/src/__tests__/workspace-migration-063-release-notes-dynamic-model-context.test.ts +77 -0
  215. package/src/__tests__/workspace-migration-064-unwind-main-agent-opus-seed.test.ts +225 -0
  216. package/src/__tests__/workspace-migration-memory-v2-init.test.ts +8 -30
  217. package/src/acp/index.ts +0 -15
  218. package/src/acp/session-manager.ts +37 -34
  219. package/src/agent/loop.ts +16 -1
  220. package/src/approvals/AGENTS.md +4 -0
  221. package/src/approvals/__tests__/guardian-feed-event.test.ts +10 -3
  222. package/src/approvals/guardian-request-resolvers.ts +10 -2
  223. package/src/backup/__tests__/backup-worker.test.ts +36 -8
  224. package/src/backup/__tests__/paths.test.ts +2 -2
  225. package/src/backup/__tests__/restore.test.ts +45 -28
  226. package/src/backup/backup-worker.ts +36 -2
  227. package/src/backup/paths.ts +9 -6
  228. package/src/browser-session/events.ts +0 -9
  229. package/src/calls/call-store.ts +1 -34
  230. package/src/calls/guardian-question-copy.ts +0 -108
  231. package/src/calls/relay-server.ts +0 -24
  232. package/src/calls/twilio-rest.ts +0 -38
  233. package/src/calls/twilio-routes.ts +1 -1
  234. package/src/calls/voice-session-bridge.ts +7 -38
  235. package/src/channels/types.ts +1 -36
  236. package/src/cli/commands/__tests__/cache.test.ts +152 -5
  237. package/src/cli/commands/__tests__/memory-v2.test.ts +14 -28
  238. package/src/cli/commands/__tests__/trust.test.ts +21 -387
  239. package/src/cli/commands/backup.ts +4 -4
  240. package/src/cli/commands/cache-fs.ts +8 -0
  241. package/src/cli/commands/cache.ts +153 -82
  242. package/src/cli/commands/clients.ts +63 -5
  243. package/src/cli/commands/completions.ts +3 -3
  244. package/src/cli/commands/contacts.ts +231 -76
  245. package/src/cli/commands/keys.ts +4 -1
  246. package/src/cli/commands/memory-v2.ts +24 -52
  247. package/src/cli/commands/oauth/shared.ts +2 -29
  248. package/src/cli/commands/pending.ts +102 -0
  249. package/src/cli/commands/skills.ts +77 -35
  250. package/src/cli/commands/trust.ts +70 -430
  251. package/src/cli/commands/usage.ts +25 -16
  252. package/src/cli/lib/daemon-credential-client.ts +14 -0
  253. package/src/cli/program.ts +2 -0
  254. package/src/cli.ts +0 -21
  255. package/src/config/__tests__/feature-flag-registry-guard.test.ts +2 -2
  256. package/src/config/bundled-skills/messaging/TOOLS.json +14 -4
  257. package/src/config/env-registry.ts +12 -2
  258. package/src/config/env.ts +3 -14
  259. package/src/config/feature-flag-registry.json +30 -30
  260. package/src/config/llm-callsite-catalog.ts +12 -0
  261. package/src/config/llm-context-resolution.ts +80 -0
  262. package/src/config/llm-resolver.ts +58 -22
  263. package/src/config/loader.ts +3 -3
  264. package/src/config/schema.ts +2 -158
  265. package/src/config/schemas/__tests__/memory-v2.test.ts +1 -0
  266. package/src/config/schemas/call-site-catalog.ts +271 -0
  267. package/src/config/schemas/calls.ts +5 -5
  268. package/src/config/schemas/inference.ts +1 -1
  269. package/src/config/schemas/ingress.ts +1 -1
  270. package/src/config/schemas/llm.ts +31 -3
  271. package/src/config/schemas/memory-retrieval.ts +2 -2
  272. package/src/config/schemas/memory-v2.ts +9 -0
  273. package/src/config/schemas/security.ts +1 -42
  274. package/src/config/schemas/services.ts +6 -6
  275. package/src/config/schemas/skills.ts +5 -5
  276. package/src/config/schemas/tts.ts +1 -1
  277. package/src/config/seed-inference-profiles.ts +117 -0
  278. package/src/config/skills.ts +0 -90
  279. package/src/config/types.ts +3 -6
  280. package/src/contacts/contact-store.ts +0 -17
  281. package/src/contacts/contacts-write.ts +1 -105
  282. package/src/context/window-manager.ts +44 -5
  283. package/src/credential-execution/process-manager.ts +34 -10
  284. package/src/credential-health/credential-health-service.ts +21 -16
  285. package/src/daemon/__tests__/conversation-surfaces-launch.test.ts +75 -82
  286. package/src/daemon/__tests__/daemon-skill-host.test.ts +2 -9
  287. package/src/daemon/connection-policy.ts +1 -26
  288. package/src/daemon/conversation-agent-loop-handlers.ts +53 -4
  289. package/src/daemon/conversation-agent-loop.ts +277 -36
  290. package/src/daemon/conversation-history.ts +8 -8
  291. package/src/daemon/conversation-launch.ts +20 -135
  292. package/src/daemon/conversation-lifecycle.ts +1 -1
  293. package/src/daemon/conversation-messaging.ts +1 -0
  294. package/src/daemon/conversation-process.ts +83 -163
  295. package/src/daemon/conversation-runtime-assembly.ts +219 -76
  296. package/src/daemon/conversation-slash.ts +47 -5
  297. package/src/daemon/conversation-store.ts +7 -31
  298. package/src/daemon/conversation-surfaces.ts +22 -28
  299. package/src/daemon/conversation-tool-setup.ts +3 -33
  300. package/src/daemon/conversation-usage.ts +36 -0
  301. package/src/daemon/conversation.ts +117 -233
  302. package/src/daemon/daemon-control.ts +3 -71
  303. package/src/daemon/daemon-skill-host.ts +8 -11
  304. package/src/daemon/dictation-profile-store.ts +2 -26
  305. package/src/daemon/first-greeting.ts +44 -156
  306. package/src/daemon/handlers/config-channels.ts +12 -12
  307. package/src/daemon/handlers/config-ingress.ts +4 -165
  308. package/src/daemon/handlers/config-model.ts +1 -1
  309. package/src/daemon/handlers/config-voice.ts +0 -42
  310. package/src/daemon/handlers/conversations.ts +11 -190
  311. package/src/daemon/handlers/recording.ts +26 -158
  312. package/src/daemon/handlers/shared.ts +23 -71
  313. package/src/daemon/handlers/skills.ts +42 -93
  314. package/src/daemon/host-bash-proxy.ts +67 -45
  315. package/src/daemon/host-browser-proxy.ts +65 -27
  316. package/src/daemon/host-cu-proxy.ts +40 -39
  317. package/src/daemon/host-file-proxy.ts +58 -37
  318. package/src/daemon/host-transfer-proxy.ts +84 -46
  319. package/src/daemon/lifecycle.ts +49 -15
  320. package/src/daemon/message-types/conversations.ts +7 -0
  321. package/src/daemon/message-types/host-bash.ts +1 -0
  322. package/src/daemon/message-types/host-cu.ts +1 -0
  323. package/src/daemon/message-types/host-file.ts +1 -0
  324. package/src/daemon/message-types/host-transfer.ts +1 -0
  325. package/src/daemon/message-types/messages.ts +10 -9
  326. package/src/daemon/message-types/workspace.ts +1 -1
  327. package/src/daemon/process-message.ts +102 -239
  328. package/src/daemon/server.ts +13 -462
  329. package/src/daemon/shutdown-handlers.ts +2 -2
  330. package/src/daemon/tool-side-effects.ts +125 -107
  331. package/src/daemon/trust-context.ts +13 -0
  332. package/src/daemon/wake-target-adapter.ts +4 -9
  333. package/src/events/domain-events.ts +0 -8
  334. package/src/events/tool-audit-listener.ts +3 -1
  335. package/src/events/tool-domain-event-publisher.ts +0 -10
  336. package/src/events/tool-metrics-listener.ts +0 -17
  337. package/src/events/tool-trace-listener.ts +0 -14
  338. package/src/filing/filing-service.ts +13 -1
  339. package/src/heartbeat/__tests__/heartbeat-feed-event.test.ts +6 -2
  340. package/src/heartbeat/heartbeat-service.ts +23 -5
  341. package/src/home/__tests__/feed-writer.test.ts +0 -4
  342. package/src/home/__tests__/relationship-state-writer.test.ts +30 -0
  343. package/src/home/feed-writer.ts +1 -2
  344. package/src/home/relationship-state-writer.ts +16 -3
  345. package/src/ipc/__tests__/browser-ipc.test.ts +2 -12
  346. package/src/ipc/__tests__/skill-server-bidirectional.test.ts +0 -1
  347. package/src/ipc/assistant-server.ts +3 -10
  348. package/src/ipc/routes/__tests__/memory-v2-backfill.test.ts +39 -20
  349. package/src/ipc/routes/route-adapter.ts +1 -1
  350. package/src/ipc/routes/trust-rules.test.ts +0 -95
  351. package/src/ipc/skill-ipc-types.ts +41 -0
  352. package/src/ipc/skill-routes/__tests__/events-ipc.test.ts +13 -27
  353. package/src/ipc/skill-routes/__tests__/identity.test.ts +4 -23
  354. package/src/ipc/skill-routes/events.ts +12 -23
  355. package/src/ipc/skill-routes/identity.ts +4 -17
  356. package/src/ipc/skill-routes/index.ts +1 -1
  357. package/src/ipc/skill-server.ts +6 -39
  358. package/src/live-voice/__tests__/runtime-websocket-shell.test.ts +0 -8
  359. package/src/live-voice/protocol.ts +4 -13
  360. package/src/mcp/manager.ts +0 -5
  361. package/src/memory/__tests__/fixtures/memory-v2-activation-fixtures.ts +55 -0
  362. package/src/memory/__tests__/memory-v2-activation-log-store.test.ts +127 -0
  363. package/src/memory/app-git-service.ts +0 -32
  364. package/src/memory/app-store.ts +154 -0
  365. package/src/memory/attachments-store.ts +6 -0
  366. package/src/memory/context-search/sources/memory-v2.ts +578 -0
  367. package/src/memory/context-search/sources/memory.ts +5 -0
  368. package/src/memory/context-search/sources/pkb.ts +10 -1
  369. package/src/memory/context-search/sources/workspace.ts +3 -2
  370. package/src/memory/conversation-crud.ts +29 -4
  371. package/src/memory/conversation-disk-view.ts +1 -5
  372. package/src/memory/conversation-starter-checkpoints.ts +63 -0
  373. package/src/memory/db-connection.ts +62 -0
  374. package/src/memory/db-init.ts +14 -0
  375. package/src/memory/embedding-backend.ts +3 -21
  376. package/src/memory/embedding-gemini.ts +0 -2
  377. package/src/memory/embedding-local.ts +6 -6
  378. package/src/memory/embedding-ollama.ts +6 -6
  379. package/src/memory/embedding-openai.ts +6 -6
  380. package/src/memory/embedding-types.ts +21 -0
  381. package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +3 -7
  382. package/src/memory/graph/conversation-graph-memory.ts +35 -13
  383. package/src/memory/graph/injection.test.ts +2 -2
  384. package/src/memory/graph/injection.ts +1 -1
  385. package/src/memory/guardian-action-store.ts +0 -83
  386. package/src/memory/guardian-approvals.ts +0 -48
  387. package/src/memory/indexer.ts +1 -15
  388. package/src/memory/job-handlers/conversation-starters.ts +36 -53
  389. package/src/memory/job-utils.ts +0 -6
  390. package/src/memory/jobs-store.ts +0 -1
  391. package/src/memory/jobs-worker.ts +2 -16
  392. package/src/memory/llm-request-log-store.ts +0 -41
  393. package/src/memory/llm-usage-store.ts +129 -43
  394. package/src/memory/memory-v2-activation-log-store.ts +115 -0
  395. package/src/memory/migrations/233-document-conversations.ts +54 -0
  396. package/src/memory/migrations/234-memory-v2-activation-logs.ts +55 -0
  397. package/src/memory/migrations/235-llm-usage-attribution.ts +31 -0
  398. package/src/memory/migrations/235-slack-compaction-watermark.ts +44 -0
  399. package/src/memory/migrations/236-tool-invocations-matched-rule-id.ts +26 -0
  400. package/src/memory/migrations/__tests__/234-memory-v2-activation-logs.test.ts +182 -0
  401. package/src/memory/migrations/index.ts +14 -0
  402. package/src/memory/migrations/registry.ts +24 -0
  403. package/src/memory/raw-query.ts +2 -68
  404. package/src/memory/schema/conversations.ts +7 -0
  405. package/src/memory/schema/infrastructure.ts +25 -0
  406. package/src/memory/search/semantic.ts +5 -16
  407. package/src/memory/tool-usage-store.ts +2 -0
  408. package/src/memory/usage-buckets.ts +40 -1
  409. package/src/memory/usage-grouped-buckets.ts +127 -0
  410. package/src/memory/v2/__tests__/activation.test.ts +289 -90
  411. package/src/memory/v2/__tests__/backfill-jobs.test.ts +2 -129
  412. package/src/memory/v2/__tests__/consolidation-job.test.ts +28 -11
  413. package/src/memory/v2/__tests__/edge-index.test.ts +278 -0
  414. package/src/memory/v2/__tests__/injection.test.ts +384 -15
  415. package/src/memory/v2/__tests__/migration.test.ts +64 -36
  416. package/src/memory/v2/__tests__/page-store.test.ts +191 -8
  417. package/src/memory/v2/__tests__/prompts-consolidation.test.ts +181 -0
  418. package/src/memory/v2/__tests__/skill-store.test.ts +115 -3
  419. package/src/memory/v2/__tests__/static-context.test.ts +153 -0
  420. package/src/memory/v2/activation.ts +168 -97
  421. package/src/memory/v2/backfill-jobs.ts +15 -100
  422. package/src/memory/v2/consolidation-job.ts +14 -12
  423. package/src/memory/v2/edge-index.ts +191 -0
  424. package/src/memory/v2/injection.ts +182 -58
  425. package/src/memory/v2/migration.ts +57 -64
  426. package/src/memory/v2/now-text.ts +2 -3
  427. package/src/memory/v2/page-store.ts +168 -31
  428. package/src/memory/v2/prompts/consolidation.ts +118 -42
  429. package/src/memory/v2/prompts/sweep.ts +3 -3
  430. package/src/memory/v2/skill-store.ts +55 -7
  431. package/src/memory/v2/static-context.ts +62 -0
  432. package/src/memory/v2/types.ts +10 -20
  433. package/src/memory/validation.ts +0 -11
  434. package/src/messaging/draft-store.ts +0 -6
  435. package/src/messaging/provider-types.ts +8 -0
  436. package/src/messaging/provider.ts +7 -0
  437. package/src/messaging/providers/gmail/client.ts +1 -121
  438. package/src/messaging/providers/outlook/client.ts +0 -73
  439. package/src/messaging/providers/slack/__tests__/adapter-mention-rendering.test.ts +226 -0
  440. package/src/messaging/providers/slack/adapter.ts +122 -21
  441. package/src/messaging/providers/slack/backfill.test.ts +95 -6
  442. package/src/messaging/providers/slack/backfill.ts +89 -11
  443. package/src/messaging/providers/slack/client.ts +10 -124
  444. package/src/messaging/providers/slack/message-metadata.ts +12 -2
  445. package/src/messaging/providers/slack/render-transcript.test.ts +56 -0
  446. package/src/messaging/providers/slack/render-transcript.ts +126 -25
  447. package/src/messaging/providers/slack/types.ts +1 -0
  448. package/src/oauth/connection-resolver.test.ts +8 -0
  449. package/src/oauth/connection-resolver.ts +8 -16
  450. package/src/oauth/credential-token-resolver.ts +97 -0
  451. package/src/oauth/manual-token-connection.ts +30 -34
  452. package/src/oauth/oauth-store.ts +6 -4
  453. package/src/outbound-proxy/certs.ts +0 -7
  454. package/src/outbound-proxy/config.ts +0 -74
  455. package/src/outbound-proxy/health.ts +0 -44
  456. package/src/outbound-proxy/index.ts +0 -22
  457. package/src/permissions/approval-provenance.test.ts +184 -0
  458. package/src/permissions/approval-provenance.ts +70 -0
  459. package/src/permissions/checker.ts +4 -1
  460. package/src/permissions/gateway-threshold-reader.ts +4 -1
  461. package/src/permissions/prompter.ts +9 -2
  462. package/src/permissions/secret-prompter.ts +21 -48
  463. package/src/permissions/types.ts +33 -0
  464. package/src/permissions/workspace-policy.ts +0 -5
  465. package/src/platform/sync-identity.ts +0 -8
  466. package/src/plugins/defaults/injectors.ts +69 -2
  467. package/src/plugins/defaults/overflow-reduce.ts +3 -2
  468. package/src/plugins/types.ts +8 -0
  469. package/src/prompts/system-prompt.ts +34 -70
  470. package/src/prompts/templates/BOOTSTRAP.md +52 -6
  471. package/src/prompts/update-bulletin-job.ts +2 -0
  472. package/src/providers/__tests__/retry-callsite.test.ts +138 -1
  473. package/src/providers/anthropic/client.ts +72 -33
  474. package/src/providers/call-site-routing.ts +42 -3
  475. package/src/providers/gemini/client.ts +18 -2
  476. package/src/providers/managed-proxy/context.ts +0 -5
  477. package/src/providers/model-catalog.ts +105 -19
  478. package/src/providers/openai/chat-completions-provider.ts +6 -0
  479. package/src/providers/openai/responses-provider.ts +7 -1
  480. package/src/providers/provider-send-message.ts +45 -2
  481. package/src/providers/ratelimit.ts +7 -2
  482. package/src/providers/registry.ts +14 -9
  483. package/src/providers/retry.ts +96 -8
  484. package/src/providers/types.ts +13 -0
  485. package/src/providers/usage-tracking.ts +96 -0
  486. package/src/runtime/AGENTS.md +10 -6
  487. package/src/runtime/__tests__/agent-wake.test.ts +89 -0
  488. package/src/runtime/agent-wake.ts +39 -2
  489. package/src/runtime/assistant-event-hub.ts +541 -45
  490. package/src/runtime/assistant-event.ts +1 -6
  491. package/src/runtime/auth/context.ts +0 -9
  492. package/src/runtime/auth/middleware.ts +1 -1
  493. package/src/runtime/auth/route-policy.ts +11 -9
  494. package/src/runtime/auth/token-service.ts +0 -11
  495. package/src/runtime/channel-approvals.ts +6 -2
  496. package/src/runtime/channel-verification-service.ts +3 -5
  497. package/src/runtime/http-errors.ts +0 -34
  498. package/src/runtime/http-router.ts +6 -3
  499. package/src/runtime/http-server.ts +22 -82
  500. package/src/runtime/http-types.ts +5 -0
  501. package/src/runtime/interactive-ui.ts +0 -1
  502. package/src/runtime/middleware/auth.ts +0 -20
  503. package/src/runtime/migrations/__tests__/v1-test-helpers.ts +112 -0
  504. package/src/runtime/migrations/__tests__/vbundle-builder-credentials.test.ts +11 -4
  505. package/src/runtime/migrations/__tests__/vbundle-builder-v1-shape.test.ts +253 -0
  506. package/src/runtime/migrations/__tests__/vbundle-import-credentials.test.ts +19 -6
  507. package/src/runtime/migrations/__tests__/vbundle-legacy-user-md.test.ts +71 -27
  508. package/src/runtime/migrations/__tests__/vbundle-metadata-merge-integration.test.ts +41 -2
  509. package/src/runtime/migrations/__tests__/vbundle-streaming-importer.test.ts +143 -79
  510. package/src/runtime/migrations/__tests__/vbundle-streaming-validator.test.ts +143 -23
  511. package/src/runtime/migrations/__tests__/vbundle-tar-stream.test.ts +2 -2
  512. package/src/runtime/migrations/__tests__/vbundle-validator-v1-schema.test.ts +371 -0
  513. package/src/runtime/migrations/migration-transport.ts +46 -13
  514. package/src/runtime/migrations/migration-wizard.ts +2 -2
  515. package/src/runtime/migrations/origin-mode.ts +40 -0
  516. package/src/runtime/migrations/vbundle-builder.ts +133 -79
  517. package/src/runtime/migrations/vbundle-import-analyzer.ts +9 -7
  518. package/src/runtime/migrations/vbundle-importer.ts +7 -7
  519. package/src/runtime/migrations/vbundle-metadata-merge.ts +1 -1
  520. package/src/runtime/migrations/vbundle-streaming-importer.ts +3 -3
  521. package/src/runtime/migrations/vbundle-streaming-validator.ts +48 -26
  522. package/src/runtime/migrations/vbundle-validator.ts +214 -41
  523. package/src/runtime/pending-interactions.ts +13 -4
  524. package/src/runtime/routes/__tests__/acp-routes.test.ts +0 -1
  525. package/src/runtime/routes/__tests__/backup-routes.test.ts +28 -19
  526. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +235 -0
  527. package/src/runtime/routes/__tests__/llm-call-sites-routes.test.ts +58 -0
  528. package/src/runtime/routes/__tests__/migration-export-secrets-redacted.test.ts +54 -0
  529. package/src/runtime/routes/__tests__/migration-import-credential-filter.test.ts +19 -6
  530. package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +7 -7
  531. package/src/runtime/routes/acp-routes.test.ts +0 -3
  532. package/src/runtime/routes/acp-routes.ts +3 -7
  533. package/src/runtime/routes/app-management-routes.ts +18 -9
  534. package/src/runtime/routes/approval-routes.ts +55 -14
  535. package/src/runtime/routes/avatar-routes.ts +3 -5
  536. package/src/runtime/routes/browser-routes.ts +1 -15
  537. package/src/runtime/routes/channel-guardian-routes.ts +1 -5
  538. package/src/runtime/routes/channel-readiness-routes.ts +3 -7
  539. package/src/runtime/routes/channel-route-shared.ts +2 -28
  540. package/src/runtime/routes/client-routes.ts +45 -12
  541. package/src/runtime/routes/consolidation-routes.ts +115 -0
  542. package/src/runtime/routes/conversation-list-routes.ts +12 -29
  543. package/src/runtime/routes/conversation-management-routes.ts +14 -51
  544. package/src/runtime/routes/conversation-query-routes.ts +120 -8
  545. package/src/runtime/routes/conversation-routes.ts +44 -528
  546. package/src/runtime/routes/conversation-starter-routes.ts +19 -40
  547. package/src/runtime/routes/documents-routes.ts +53 -18
  548. package/src/runtime/routes/events-routes.ts +59 -91
  549. package/src/runtime/routes/filing-routes.ts +18 -1
  550. package/src/runtime/routes/guardian-action-routes.ts +4 -9
  551. package/src/runtime/routes/host-bash-routes.ts +3 -2
  552. package/src/runtime/routes/host-browser-routes.ts +9 -33
  553. package/src/runtime/routes/host-cu-routes.ts +6 -1
  554. package/src/runtime/routes/host-file-routes.ts +3 -2
  555. package/src/runtime/routes/host-transfer-routes.ts +11 -15
  556. package/src/runtime/routes/identity-routes.ts +78 -6
  557. package/src/runtime/routes/inbound-message-handler.ts +580 -137
  558. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +2 -88
  559. package/src/runtime/routes/inbound-stages/background-dispatch.ts +3 -0
  560. package/src/runtime/routes/index.ts +4 -0
  561. package/src/runtime/routes/integrations/slack/channel.ts +0 -24
  562. package/src/runtime/routes/llm-call-sites-routes.ts +22 -0
  563. package/src/runtime/routes/memory-v2-routes.ts +10 -15
  564. package/src/runtime/routes/migration-routes.ts +188 -31
  565. package/src/runtime/routes/playground/guard.ts +1 -1
  566. package/src/runtime/routes/playground/index.ts +0 -2
  567. package/src/runtime/routes/recording-routes.ts +4 -24
  568. package/src/runtime/routes/rename-conversation-routes.ts +2 -6
  569. package/src/runtime/routes/schedule-routes.ts +3 -6
  570. package/src/runtime/routes/secret-routes.ts +87 -18
  571. package/src/runtime/routes/settings-routes.ts +29 -28
  572. package/src/runtime/routes/skills-routes.ts +12 -31
  573. package/src/runtime/routes/suggest-trust-rule-routes.ts +32 -1
  574. package/src/runtime/routes/task-routes.ts +6 -6
  575. package/src/runtime/routes/trust-rules-routes.ts +3 -94
  576. package/src/runtime/routes/types.ts +4 -4
  577. package/src/runtime/routes/upgrade-broadcast-routes.ts +3 -10
  578. package/src/runtime/routes/usage-routes.ts +87 -10
  579. package/src/runtime/routes/user-routes.ts +17 -31
  580. package/src/runtime/routes/work-items-routes.ts +1 -4
  581. package/src/runtime/services/__tests__/analyze-conversation.test.ts +2 -2
  582. package/src/runtime/services/analyze-conversation.ts +7 -17
  583. package/src/runtime/services/conversation-serializer.ts +2 -4
  584. package/src/runtime/verification-outbound-actions.ts +1 -1
  585. package/src/runtime/verification-rate-limiter.ts +1 -1
  586. package/src/schedule/schedule-store.ts +0 -16
  587. package/src/security/secret-scanner.ts +14 -547
  588. package/src/security/secure-keys.ts +31 -11
  589. package/src/security/token-manager.ts +7 -3
  590. package/src/signals/cancel.ts +16 -25
  591. package/src/signals/conversation-undo.ts +2 -27
  592. package/src/signals/emit-event.ts +1 -2
  593. package/src/signals/user-message.ts +108 -22
  594. package/src/skills/catalog-install.ts +1 -0
  595. package/src/skills/clawhub.ts +2 -2
  596. package/src/skills/inline-command-runner.ts +1 -7
  597. package/src/subagent/manager.ts +67 -84
  598. package/src/tasks/task-store.ts +1 -28
  599. package/src/telemetry/types.ts +6 -0
  600. package/src/telemetry/usage-telemetry-reporter.test.ts +38 -15
  601. package/src/telemetry/usage-telemetry-reporter.ts +3 -5
  602. package/src/tools/acp/spawn.test.ts +1 -2
  603. package/src/tools/acp/steer.test.ts +1 -2
  604. package/src/tools/browser/__tests__/browser-status.test.ts +44 -127
  605. package/src/tools/browser/browser-execution.ts +31 -147
  606. package/src/tools/browser/cdp-client/__tests__/factory.test.ts +92 -68
  607. package/src/tools/browser/cdp-client/factory.ts +48 -76
  608. package/src/tools/browser/cdp-client/index.ts +1 -14
  609. package/src/tools/executor.ts +44 -31
  610. package/src/tools/host-filesystem/edit.ts +3 -2
  611. package/src/tools/host-filesystem/read.ts +3 -2
  612. package/src/tools/host-filesystem/transfer.test.ts +45 -42
  613. package/src/tools/host-filesystem/transfer.ts +4 -3
  614. package/src/tools/host-filesystem/write.ts +3 -2
  615. package/src/tools/host-terminal/host-shell.ts +4 -3
  616. package/src/tools/network/script-proxy/index.ts +1 -10
  617. package/src/tools/permission-checker.ts +66 -1
  618. package/src/tools/skills/sandbox-runner.ts +1 -6
  619. package/src/tools/skills/skill-tool-factory.ts +32 -0
  620. package/src/tools/terminal/safe-env.ts +1 -0
  621. package/src/tools/terminal/shell.ts +2 -78
  622. package/src/tools/types.ts +12 -39
  623. package/src/tts/__tests__/provider-catalog.test.ts +2 -2
  624. package/src/tts/provider-catalog.ts +1 -1
  625. package/src/usage/actors.ts +2 -1
  626. package/src/usage/attribution.ts +185 -0
  627. package/src/usage/pricing.ts +166 -0
  628. package/src/usage/types.ts +14 -0
  629. package/src/util/json.ts +13 -0
  630. package/src/util/logger.ts +3 -3
  631. package/src/util/pricing.ts +50 -3
  632. package/src/work-items/work-item-runner.ts +15 -42
  633. package/src/workspace/migrations/050-seed-main-agent-opus-callsite.ts +4 -3
  634. package/src/workspace/migrations/052-seed-default-inference-profiles.ts +3 -3
  635. package/src/workspace/migrations/060-memory-v2-init.ts +2 -18
  636. package/src/workspace/migrations/061-move-backup-key-to-workspace.ts +59 -0
  637. package/src/workspace/migrations/062-drop-memory-v2-edges-json.ts +27 -0
  638. package/src/workspace/migrations/063-release-notes-dynamic-model-context.ts +70 -0
  639. package/src/workspace/migrations/064-unwind-main-agent-opus-seed.ts +64 -0
  640. package/src/workspace/migrations/registry.ts +8 -0
  641. package/src/workspace/provider-commit-message-generator.ts +3 -3
  642. package/src/__tests__/sandbox-diagnostics.test.ts +0 -138
  643. package/src/__tests__/sandbox-host-parity.test.ts +0 -1024
  644. package/src/__tests__/secret-detection-handler.test.ts +0 -67
  645. package/src/__tests__/secret-scanner-executor.test.ts +0 -450
  646. package/src/__tests__/tcc-sandbox-deny.test.ts +0 -198
  647. package/src/__tests__/terminal-sandbox.test.ts +0 -374
  648. package/src/__tests__/tool-notification-listener.test.ts +0 -65
  649. package/src/context/__tests__/microcompact.test.ts +0 -805
  650. package/src/context/microcompact.ts +0 -443
  651. package/src/daemon/handlers/slack-channel-oauth-install.ts +0 -197
  652. package/src/events/tool-notification-listener.ts +0 -17
  653. package/src/ipc/routes/__tests__/memory-v2-validate.test.ts +0 -219
  654. package/src/memory/v2/__tests__/edges.test.ts +0 -435
  655. package/src/memory/v2/edges.ts +0 -217
  656. package/src/prompts/__tests__/system-prompt-memory-v2.test.ts +0 -197
  657. package/src/runtime/__tests__/chrome-extension-registry.test.ts +0 -518
  658. package/src/runtime/__tests__/client-registry.test.ts +0 -271
  659. package/src/runtime/chrome-extension-registry.ts +0 -368
  660. package/src/runtime/client-registry.ts +0 -254
  661. package/src/runtime/routes/inbound-stages/verification-intercept.ts +0 -329
  662. package/src/tools/secret-detection-handler.ts +0 -269
  663. package/src/tools/terminal/backends/native.ts +0 -327
  664. package/src/tools/terminal/backends/types.ts +0 -37
  665. package/src/tools/terminal/sandbox-diagnostics.ts +0 -87
  666. package/src/tools/terminal/sandbox.ts +0 -40
@@ -2,18 +2,35 @@
2
2
  * In-process pub/sub hub for assistant events.
3
3
  *
4
4
  * Provides subscribe / publish primitives used by the daemon send paths
5
- * and the SSE route. No runtime route or daemon integration is wired here.
5
+ * and the SSE route.
6
+ *
7
+ * Subscribers are typed via a discriminated union:
8
+ * - **ClientEntry** — an SSE-connected client (macos, chrome-extension, …)
9
+ * with identity, capabilities, and timestamps.
10
+ * - **ProcessEntry** — an in-process consumer (future: file-append logger).
11
+ *
12
+ * Client-oriented queries (list, find-by-capability) are methods on the hub.
6
13
  */
7
14
 
15
+ import type { HostProxyCapability, InterfaceId } from "../channels/types.js";
16
+ import type { ServerMessage } from "../daemon/message-protocol.js";
17
+ import { emitFeedEvent } from "../home/emit-feed-event.js";
18
+ import { rewriteCommandPreview } from "../home/rewrite-command-preview.js";
19
+ import { redactSecrets } from "../security/secret-scanner.js";
20
+ import { appendEventToStream } from "../signals/event-stream.js";
21
+ import { summarizeToolInput } from "../tools/tool-input-summary.js";
22
+ import { getLogger } from "../util/logger.js";
8
23
  import type { AssistantEvent } from "./assistant-event.js";
24
+ import { buildAssistantEvent } from "./assistant-event.js";
25
+ import * as pendingInteractions from "./pending-interactions.js";
26
+
27
+ const log = getLogger("assistant-event-hub");
9
28
 
10
29
  // ── Types ─────────────────────────────────────────────────────────────────────
11
30
 
12
- /** Predicate that determines whether a subscriber wants a given event. */
31
+ /** Filter that determines which events a subscriber receives. */
13
32
  export type AssistantEventFilter = {
14
- /** Only deliver events for this assistant. */
15
- assistantId: string;
16
- /** When set, further restrict to this conversation. */
33
+ /** When set, restrict delivery to events for this conversation. */
17
34
  conversationId?: string;
18
35
  };
19
36
 
@@ -28,24 +45,57 @@ export interface AssistantEventSubscription {
28
45
  readonly active: boolean;
29
46
  }
30
47
 
31
- // ── Hub ───────────────────────────────────────────────────────────────────────
48
+ // ── Subscriber entries (discriminated union) ─────────────────────────────────
32
49
 
33
- interface SubscriberEntry {
50
+ interface BaseSubscriberEntry {
34
51
  filter: AssistantEventFilter;
35
52
  callback: AssistantEventCallback;
36
53
  active: boolean;
37
- /** Called by the hub when this entry is evicted to make room for a new subscriber. */
38
- onEvict?: () => void;
54
+ onEvict: () => void;
55
+ connectedAt: Date;
56
+ lastActiveAt: Date;
57
+ }
58
+
59
+ export interface ClientEntry extends BaseSubscriberEntry {
60
+ type: "client";
61
+ clientId: string;
62
+ interfaceId: InterfaceId;
63
+ capabilities: HostProxyCapability[];
64
+ }
65
+
66
+ export interface ProcessEntry extends BaseSubscriberEntry {
67
+ type: "process";
39
68
  }
40
69
 
70
+ export type SubscriberEntry = ClientEntry | ProcessEntry;
71
+
72
+ /** Distributive Omit that preserves union discrimination. */
73
+ type DistributiveOmit<T, K extends PropertyKey> = T extends unknown
74
+ ? Omit<T, K>
75
+ : never;
76
+
77
+ /** Input shape for `subscribe()` — hub fills `active`, `connectedAt`, `lastActiveAt` and defaults `filter`/`onEvict`. */
78
+ export type SubscriberInput = DistributiveOmit<
79
+ SubscriberEntry,
80
+ "active" | "connectedAt" | "lastActiveAt" | "filter" | "onEvict"
81
+ > & {
82
+ filter?: AssistantEventFilter;
83
+ onEvict?: () => void;
84
+ };
85
+
86
+ // ── Hub ───────────────────────────────────────────────────────────────────────
87
+
41
88
  /**
42
89
  * Lightweight pub/sub hub for `AssistantEvent` messages.
43
90
  *
44
- * Filtering is applied at subscription level — subscribers receive only
45
- * events that match their `assistantId` (and optionally `conversationId`).
91
+ * Filtering is applied at subscription level:
92
+ * - `conversationId`: scoped events match subscribers with same conversationId
93
+ * or no conversationId filter (broadcast to all).
94
+ * - `targetCapability` (on publish): targeted events only reach subscribers
95
+ * whose capabilities include the target. Untargeted events fan out to all.
46
96
  *
47
- * The hub is intentionally simple: synchronous fanout, no buffering, no
48
- * backpressure. Slow-consumer protection lives in the SSE route.
97
+ * Client connections register as subscribers with metadata and are queryable
98
+ * via `listClients()`, `getMostRecentClientByCapability()`, etc.
49
99
  */
50
100
  export class AssistantEventHub {
51
101
  private readonly subscribers = new Set<SubscriberEntry>();
@@ -58,26 +108,48 @@ export class AssistantEventHub {
58
108
  /**
59
109
  * Register a subscriber that will be called for each matching event.
60
110
  *
111
+ * **Client deduplication:** When a client subscriber is registered with a
112
+ * `clientId` that already exists, all stale entries for that clientId are
113
+ * disposed first. This prevents subscriber stacking when clients reconnect
114
+ * (e.g. Chrome extension reload, SSE token refresh) before the old
115
+ * connection's abort signal fires.
116
+ *
61
117
  * When the subscriber cap (`maxSubscribers`) has been reached, the **oldest**
62
118
  * subscriber is evicted to make room: its `onEvict` callback is invoked (so
63
119
  * it can close its SSE stream) and its entry is removed from the hub.
64
- *
65
- * The only case that throws is when `maxSubscribers` is 0 — there is nothing
66
- * to evict and no room to add.
67
- *
68
- * @param options.onEvict Called if this subscriber is later evicted by a newer one.
69
- * @returns A subscription handle. Call `dispose()` to unsubscribe.
70
120
  */
71
- subscribe(
72
- filter: AssistantEventFilter,
73
- callback: AssistantEventCallback,
74
- options?: { onEvict?: () => void },
75
- ): AssistantEventSubscription {
121
+ subscribe(subscriber: SubscriberInput): AssistantEventSubscription {
122
+ // Deduplicate: dispose stale subscribers for the same clientId.
123
+ if (subscriber.type === "client") {
124
+ const stale: SubscriberEntry[] = [];
125
+ for (const existing of this.subscribers) {
126
+ if (
127
+ existing.type === "client" &&
128
+ existing.clientId === subscriber.clientId
129
+ ) {
130
+ stale.push(existing);
131
+ }
132
+ }
133
+ for (const entry of stale) {
134
+ entry.active = false;
135
+ this.subscribers.delete(entry);
136
+ try {
137
+ entry.onEvict();
138
+ } catch {
139
+ /* ignore eviction callback errors */
140
+ }
141
+ }
142
+ if (stale.length > 0) {
143
+ log.info(
144
+ { clientId: subscriber.clientId, count: stale.length },
145
+ "disposed stale subscribers for reconnecting client",
146
+ );
147
+ }
148
+ }
149
+
76
150
  if (this.subscribers.size >= this.maxSubscribers) {
77
- // Evict the oldest subscriber (Sets maintain insertion order).
78
151
  const [oldest] = this.subscribers;
79
152
  if (!oldest) {
80
- // maxSubscribers is 0 — nothing to evict, nothing to add.
81
153
  throw new RangeError(
82
154
  `AssistantEventHub: subscriber cap reached (${this.maxSubscribers})`,
83
155
  );
@@ -85,17 +157,35 @@ export class AssistantEventHub {
85
157
  oldest.active = false;
86
158
  this.subscribers.delete(oldest);
87
159
  try {
88
- oldest.onEvict?.();
160
+ oldest.onEvict();
89
161
  } catch {
90
162
  /* ignore eviction callback errors */
91
163
  }
92
164
  }
165
+
166
+ const now = new Date();
93
167
  const entry: SubscriberEntry = {
94
- filter,
95
- callback,
168
+ ...subscriber,
169
+ filter: subscriber.filter ?? {},
170
+ onEvict: subscriber.onEvict ?? (() => {}),
96
171
  active: true,
97
- onEvict: options?.onEvict,
98
- };
172
+ connectedAt: now,
173
+ lastActiveAt: now,
174
+ } as SubscriberEntry;
175
+
176
+ if (entry.type === "client") {
177
+ log.info(
178
+ {
179
+ clientId: entry.clientId,
180
+ interfaceId: entry.interfaceId,
181
+ capabilities: entry.capabilities,
182
+ },
183
+ "subscriber registered (client)",
184
+ );
185
+ } else {
186
+ log.info("subscriber registered (process)");
187
+ }
188
+
99
189
  this.subscribers.add(entry);
100
190
 
101
191
  return {
@@ -103,6 +193,17 @@ export class AssistantEventHub {
103
193
  if (entry.active) {
104
194
  entry.active = false;
105
195
  this.subscribers.delete(entry);
196
+ if (entry.type === "client") {
197
+ log.info(
198
+ {
199
+ clientId: entry.clientId,
200
+ interfaceId: entry.interfaceId,
201
+ },
202
+ "subscriber unregistered (client)",
203
+ );
204
+ } else {
205
+ log.info("subscriber unregistered (process)");
206
+ }
106
207
  }
107
208
  },
108
209
  get active() {
@@ -115,32 +216,51 @@ export class AssistantEventHub {
115
216
  * Publish an event to all matching subscribers.
116
217
  *
117
218
  * Matching rules:
118
- * - `event.assistantId` must equal `filter.assistantId`
119
219
  * - if `filter.conversationId` is set, `event.conversationId` must equal it
220
+ * - if `targetCapability` is set, only subscribers whose capabilities include
221
+ * it receive the event; untargeted events go to all
120
222
  *
121
223
  * Fanout is isolated: a throwing or rejecting subscriber does not abort
122
- * delivery to remaining subscribers. All callbacks (sync and async) are
123
- * awaited and their errors collected; any errors are re-thrown together
124
- * as an `AggregateError` after all callbacks have been invoked.
125
- *
126
- * Subscribers are snapshotted at the start of each publish call so that
127
- * callbacks adding new subscriptions do not receive the in-flight event.
224
+ * delivery to remaining subscribers.
128
225
  */
129
- async publish(event: AssistantEvent): Promise<void> {
226
+ async publish(
227
+ event: AssistantEvent,
228
+ options?: { targetCapability?: HostProxyCapability },
229
+ ): Promise<void> {
230
+ if (event.conversationId) {
231
+ try {
232
+ appendEventToStream(event.conversationId, event);
233
+ } catch {
234
+ // Best-effort; file I/O failures must not block subscriber fanout.
235
+ }
236
+ }
237
+
238
+ const targetCapability = options?.targetCapability;
130
239
  const snapshot = Array.from(this.subscribers);
131
240
  const errors: unknown[] = [];
132
241
 
133
242
  for (const entry of snapshot) {
134
243
  if (!entry.active) continue;
135
- if (entry.filter.assistantId !== event.assistantId) continue;
136
- // System events (no conversationId) match all subscribers; scoped events
137
- // must match the subscriber's conversationId filter when present.
244
+
245
+ // Conversation scoping: scoped events skip subscribers filtering on a
246
+ // different conversation.
138
247
  if (
139
248
  event.conversationId != null &&
140
249
  entry.filter.conversationId != null &&
141
250
  entry.filter.conversationId !== event.conversationId
142
251
  )
143
252
  continue;
253
+
254
+ // Capability targeting: targeted events only go to subscribers that
255
+ // declare the required capability.
256
+ if (targetCapability != null) {
257
+ if (
258
+ entry.type !== "client" ||
259
+ !entry.capabilities.includes(targetCapability)
260
+ )
261
+ continue;
262
+ }
263
+
144
264
  try {
145
265
  await entry.callback(event);
146
266
  } catch (err) {
@@ -158,14 +278,13 @@ export class AssistantEventHub {
158
278
 
159
279
  /**
160
280
  * Returns true when at least one active subscriber would receive the given
161
- * event based on the same assistant/conversation matching rules as publish().
281
+ * event based on the same conversation matching rules as publish().
162
282
  */
163
283
  hasSubscribersForEvent(
164
- event: Pick<AssistantEvent, "assistantId" | "conversationId">,
284
+ event: Pick<AssistantEvent, "conversationId">,
165
285
  ): boolean {
166
286
  for (const entry of this.subscribers) {
167
287
  if (!entry.active) continue;
168
- if (entry.filter.assistantId !== event.assistantId) continue;
169
288
  if (
170
289
  event.conversationId != null &&
171
290
  entry.filter.conversationId != null &&
@@ -178,6 +297,106 @@ export class AssistantEventHub {
178
297
  return false;
179
298
  }
180
299
 
300
+ // ── Client queries ──────────────────────────────────────────────────────────
301
+
302
+ private clientEntries(): ClientEntry[] {
303
+ const clients: ClientEntry[] = [];
304
+ for (const entry of this.subscribers) {
305
+ if (entry.active && entry.type === "client") {
306
+ clients.push(entry);
307
+ }
308
+ }
309
+ return clients.sort(
310
+ (a, b) => b.lastActiveAt.getTime() - a.lastActiveAt.getTime(),
311
+ );
312
+ }
313
+
314
+ /**
315
+ * Return all active client subscribers, sorted by `lastActiveAt` descending.
316
+ */
317
+ listClients(): ClientEntry[] {
318
+ return this.clientEntries();
319
+ }
320
+
321
+ /**
322
+ * Return all client subscribers that support the given capability,
323
+ * sorted by `lastActiveAt` descending.
324
+ */
325
+ listClientsByCapability(capability: HostProxyCapability): ClientEntry[] {
326
+ return this.clientEntries().filter((c) =>
327
+ c.capabilities.includes(capability),
328
+ );
329
+ }
330
+
331
+ /**
332
+ * Return the most recently active client that supports the given
333
+ * capability, or `undefined` if none exists.
334
+ */
335
+ getMostRecentClientByCapability(
336
+ capability: HostProxyCapability,
337
+ ): ClientEntry | undefined {
338
+ return this.listClientsByCapability(capability)[0];
339
+ }
340
+
341
+ /**
342
+ * Return all client subscribers with the given interface type,
343
+ * sorted by `lastActiveAt` descending.
344
+ */
345
+ listClientsByInterface(interfaceId: InterfaceId): ClientEntry[] {
346
+ return this.clientEntries().filter((c) => c.interfaceId === interfaceId);
347
+ }
348
+
349
+ /**
350
+ * Touch a client subscriber — update `lastActiveAt`. Used by heartbeat.
351
+ */
352
+ touchClient(clientId: string): void {
353
+ const now = new Date();
354
+ for (const entry of this.subscribers) {
355
+ if (
356
+ entry.active &&
357
+ entry.type === "client" &&
358
+ entry.clientId === clientId
359
+ ) {
360
+ entry.lastActiveAt = now;
361
+ }
362
+ }
363
+ }
364
+
365
+ /**
366
+ * Force-disconnect a client by disposing all subscribers for the given
367
+ * `clientId`. Returns the number of disposed entries.
368
+ *
369
+ * Used by `assistant clients disconnect <clientId>` to forcibly remove
370
+ * stale or unwanted client connections.
371
+ */
372
+ disposeClient(clientId: string): number {
373
+ const targets: SubscriberEntry[] = [];
374
+ for (const entry of this.subscribers) {
375
+ if (
376
+ entry.type === "client" &&
377
+ entry.clientId === clientId
378
+ ) {
379
+ targets.push(entry);
380
+ }
381
+ }
382
+ for (const entry of targets) {
383
+ entry.active = false;
384
+ this.subscribers.delete(entry);
385
+ try {
386
+ entry.onEvict();
387
+ } catch {
388
+ /* ignore eviction callback errors */
389
+ }
390
+ }
391
+ if (targets.length > 0) {
392
+ log.info(
393
+ { clientId, count: targets.length },
394
+ "force-disposed client subscribers",
395
+ );
396
+ }
397
+ return targets.length;
398
+ }
399
+
181
400
  /** Number of currently active subscribers (useful for tests and caps). */
182
401
  subscriberCount(): number {
183
402
  return this.subscribers.size;
@@ -197,3 +416,280 @@ export class AssistantEventHub {
197
416
  * Import and use this in daemon send paths and the SSE route.
198
417
  */
199
418
  export const assistantEventHub = new AssistantEventHub({ maxSubscribers: 100 });
419
+
420
+ // ── Convenience: ServerMessage → AssistantEvent publish ───────────────────────
421
+
422
+ /**
423
+ * Promise chain that serializes publishes so subscribers always observe
424
+ * events in send order.
425
+ */
426
+ let _hubChain = Promise.resolve();
427
+
428
+ /**
429
+ * Wraps a `ServerMessage` in an `AssistantEvent` envelope and publishes it
430
+ * to the process-level hub.
431
+ *
432
+ * When `conversationId` is omitted, it is auto-extracted from the message
433
+ * payload (if present).
434
+ *
435
+ * This is the primary entrypoint for emitting events — handlers, routes, and
436
+ * services should call this directly instead of threading a broadcast callback.
437
+ */
438
+ export function broadcastMessage(
439
+ msg: ServerMessage,
440
+ conversationId?: string,
441
+ options?: { targetCapability?: HostProxyCapability },
442
+ ): void {
443
+ const resolvedConversationId = conversationId ?? extractConversationId(msg);
444
+
445
+ // Register pending interactions so approval/host prompts are tracked
446
+ // regardless of which path triggered the broadcast.
447
+ if (resolvedConversationId) {
448
+ registerPendingInteraction(msg, resolvedConversationId);
449
+ }
450
+
451
+ // Emit feed events for confirmation requests (tool approval prompts).
452
+ if (msg.type === "confirmation_request" && resolvedConversationId) {
453
+ void emitConfirmationFeedEvent(msg, resolvedConversationId);
454
+ }
455
+
456
+ // `conversation_list_invalidated` is a list-level system event — publish
457
+ // it unscoped so every subscriber refreshes its sidebar.
458
+ const scopedConversationId =
459
+ msg.type === "conversation_list_invalidated"
460
+ ? undefined
461
+ : resolvedConversationId;
462
+ const event = buildAssistantEvent(msg, scopedConversationId);
463
+ _hubChain = _hubChain
464
+ .then(() => assistantEventHub.publish(event, options))
465
+ .then(() => {
466
+ // When a conversation title changes, also broadcast an unscoped
467
+ // `conversation_list_invalidated` so every connected client's sidebar
468
+ // refreshes — not just the client viewing this conversation.
469
+ if (msg.type === "conversation_title_updated") {
470
+ return assistantEventHub
471
+ .publish(
472
+ buildAssistantEvent({
473
+ type: "conversation_list_invalidated",
474
+ reason: "renamed",
475
+ }),
476
+ )
477
+ .catch((err: unknown) => {
478
+ log.warn(
479
+ { err },
480
+ "Failed to publish conversation_list_invalidated after title update",
481
+ );
482
+ });
483
+ }
484
+ })
485
+ .catch((err: unknown) => {
486
+ log.warn({ err }, "assistant-events hub subscriber threw during publish");
487
+ });
488
+ }
489
+
490
+ function extractConversationId(msg: ServerMessage): string | undefined {
491
+ const record = msg as unknown as Record<string, unknown>;
492
+ if ("conversationId" in msg && typeof record.conversationId === "string") {
493
+ return record.conversationId as string;
494
+ }
495
+ return undefined;
496
+ }
497
+
498
+ // ── Pending interaction registration ──────────────────────────────────────────
499
+
500
+ function resolveCanonicalRequestSourceType(
501
+ sourceChannel: string,
502
+ ): "desktop" | "channel" | "voice" {
503
+ if (sourceChannel === "phone") return "voice";
504
+ if (sourceChannel === "vellum") return "desktop";
505
+ return "channel";
506
+ }
507
+
508
+ /**
509
+ * Register pending interactions for request-type messages so approval and
510
+ * host prompts are tracked regardless of which code path broadcasts them.
511
+ *
512
+ * Heavy dependencies (conversation-store, canonical-guardian-store, etc.) are
513
+ * imported lazily so that loading this module during tests doesn't trigger
514
+ * config/data-dir side effects.
515
+ */
516
+ function registerPendingInteraction(
517
+ msg: ServerMessage,
518
+ conversationId: string,
519
+ ): void {
520
+ if (msg.type === "confirmation_request") {
521
+ pendingInteractions.register(msg.requestId, {
522
+ conversationId,
523
+ kind: "confirmation",
524
+ confirmationDetails: {
525
+ toolName: msg.toolName,
526
+ input: msg.input,
527
+ riskLevel: msg.riskLevel,
528
+ executionTarget: msg.executionTarget,
529
+ allowlistOptions: msg.allowlistOptions,
530
+ scopeOptions: msg.scopeOptions,
531
+ persistentDecisionsAllowed: msg.persistentDecisionsAllowed,
532
+ },
533
+ });
534
+
535
+ // Create canonical guardian request asynchronously — heavy deps are
536
+ // imported lazily to avoid pulling in conversation-store (and
537
+ // transitively config/loader → ensureDataDir) at module-load time.
538
+ void createCanonicalRequestForConfirmation(msg, conversationId);
539
+ } else if (msg.type === "secret_request") {
540
+ pendingInteractions.register(msg.requestId, {
541
+ conversationId,
542
+ kind: "secret",
543
+ });
544
+ } else if (msg.type === "host_bash_request") {
545
+ pendingInteractions.register(msg.requestId, {
546
+ conversationId,
547
+ kind: "host_bash",
548
+ });
549
+ } else if (msg.type === "host_browser_request") {
550
+ pendingInteractions.register(msg.requestId, {
551
+ conversationId,
552
+ kind: "host_browser",
553
+ });
554
+ } else if (msg.type === "host_file_request") {
555
+ pendingInteractions.register(msg.requestId, {
556
+ conversationId,
557
+ kind: "host_file",
558
+ });
559
+ } else if (msg.type === "host_cu_request") {
560
+ pendingInteractions.register(msg.requestId, {
561
+ conversationId,
562
+ kind: "host_cu",
563
+ });
564
+ } else if (msg.type === "host_transfer_request") {
565
+ pendingInteractions.register(msg.requestId, {
566
+ conversationId,
567
+ kind: "host_transfer",
568
+ });
569
+ }
570
+ }
571
+
572
+ /**
573
+ * Lazily load heavy dependencies and create a canonical guardian request +
574
+ * bridge for a confirmation_request message. Runs fire-and-forget from
575
+ * registerPendingInteraction.
576
+ */
577
+ async function createCanonicalRequestForConfirmation(
578
+ msg: ServerMessage & { type: "confirmation_request" },
579
+ conversationId: string,
580
+ ): Promise<void> {
581
+ try {
582
+ const [
583
+ { findConversation },
584
+ { createCanonicalGuardianRequest, generateCanonicalRequestCode },
585
+ { redactSecrets },
586
+ { summarizeToolInput },
587
+ { DAEMON_INTERNAL_ASSISTANT_ID },
588
+ { bridgeConfirmationRequestToGuardian },
589
+ ] = await Promise.all([
590
+ import("../daemon/conversation-store.js"),
591
+ import("../memory/canonical-guardian-store.js"),
592
+ import("../security/secret-scanner.js"),
593
+ import("../tools/tool-input-summary.js"),
594
+ import("./assistant-scope.js"),
595
+ import("./confirmation-request-guardian-bridge.js"),
596
+ ]);
597
+
598
+ const conversation = findConversation(conversationId);
599
+ const trustContext = conversation?.trustContext;
600
+ const sourceChannel = trustContext?.sourceChannel ?? "vellum";
601
+ const inputRecord = msg.input as Record<string, unknown>;
602
+ const activityRaw =
603
+ (typeof inputRecord.activity === "string"
604
+ ? inputRecord.activity
605
+ : undefined) ??
606
+ (typeof inputRecord.reason === "string" ? inputRecord.reason : undefined);
607
+ const canonicalRequest = createCanonicalGuardianRequest({
608
+ id: msg.requestId,
609
+ kind: "tool_approval",
610
+ sourceType: resolveCanonicalRequestSourceType(sourceChannel),
611
+ sourceChannel,
612
+ conversationId,
613
+ requesterExternalUserId: trustContext?.requesterExternalUserId,
614
+ requesterChatId: trustContext?.requesterChatId,
615
+ guardianExternalUserId: trustContext?.guardianExternalUserId,
616
+ guardianPrincipalId: trustContext?.guardianPrincipalId ?? undefined,
617
+ toolName: msg.toolName,
618
+ commandPreview:
619
+ redactSecrets(summarizeToolInput(msg.toolName, inputRecord)) ||
620
+ undefined,
621
+ riskLevel: msg.riskLevel,
622
+ activityText: activityRaw ? redactSecrets(activityRaw) : undefined,
623
+ executionTarget: msg.executionTarget,
624
+ status: "pending",
625
+ requestCode: generateCanonicalRequestCode(),
626
+ expiresAt: Date.now() + 5 * 60 * 1000,
627
+ });
628
+
629
+ if (trustContext && conversation) {
630
+ bridgeConfirmationRequestToGuardian({
631
+ canonicalRequest,
632
+ trustContext,
633
+ conversationId,
634
+ toolName: msg.toolName,
635
+ assistantId: conversation.assistantId ?? DAEMON_INTERNAL_ASSISTANT_ID,
636
+ });
637
+ }
638
+ } catch (err) {
639
+ log.debug(
640
+ { err, conversationId },
641
+ "Failed to create canonical request from broadcast",
642
+ );
643
+ }
644
+ }
645
+
646
+ // ── Feed events for confirmation requests ─────────────────────────────────────
647
+
648
+ /**
649
+ * Emit a feed event when a confirmation request (tool approval prompt) is
650
+ * broadcast. Emits immediately with a technical preview, then rewrites
651
+ * into prose in the background and updates the feed item.
652
+ */
653
+ async function emitConfirmationFeedEvent(
654
+ msg: ServerMessage & { type: "confirmation_request" },
655
+ conversationId: string,
656
+ ): Promise<void> {
657
+ try {
658
+ const inputRecord = msg.input as Record<string, unknown>;
659
+ const commandPreview =
660
+ redactSecrets(summarizeToolInput(msg.toolName, inputRecord)) || undefined;
661
+ const technicalTitle = commandPreview
662
+ ? `Requesting permission: ${commandPreview}`
663
+ : `Requesting approval to use ${msg.toolName}.`;
664
+ const dedupKey = `tool-approval:${msg.requestId}`;
665
+
666
+ await emitFeedEvent({
667
+ source: "assistant",
668
+ title: technicalTitle,
669
+ summary: technicalTitle,
670
+ dedupKey,
671
+ urgency: msg.riskLevel === "high" ? "high" : "medium",
672
+ conversationId,
673
+ detailPanel: { kind: "toolPermission" },
674
+ });
675
+
676
+ // Background: rewrite into prose and update the feed item.
677
+ if (commandPreview) {
678
+ const prose = await rewriteCommandPreview(msg.toolName, commandPreview);
679
+ if (prose) {
680
+ const proseTitle = `Requesting permission: ${prose}`;
681
+ await emitFeedEvent({
682
+ source: "assistant",
683
+ title: proseTitle,
684
+ summary: proseTitle,
685
+ dedupKey,
686
+ urgency: msg.riskLevel === "high" ? "high" : "medium",
687
+ conversationId,
688
+ detailPanel: { kind: "toolPermission" },
689
+ });
690
+ }
691
+ }
692
+ } catch (err) {
693
+ log.warn({ err }, "Failed to emit confirmation feed event from broadcast");
694
+ }
695
+ }