@vellumai/assistant 0.9.1-staging.1 → 0.10.0-staging.2

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 (433) hide show
  1. package/docs/activation-funnel-telemetry.md +24 -18
  2. package/node_modules/@vellumai/gateway-client/src/admission-policy-contract.ts +97 -0
  3. package/node_modules/@vellumai/gateway-client/src/inbound-contract.ts +10 -0
  4. package/node_modules/@vellumai/gateway-client/src/index.ts +15 -0
  5. package/openapi.yaml +852 -15
  6. package/package.json +1 -1
  7. package/src/__tests__/access-request-card-view.test.ts +98 -0
  8. package/src/__tests__/access-request-seed-content-blocks.test.ts +2 -4
  9. package/src/__tests__/actor-trust-resolver-address-fallback.test.ts +59 -7
  10. package/src/__tests__/agent-loop-compaction-strip.test.ts +17 -16
  11. package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +16 -13
  12. package/src/__tests__/app-compiler.test.ts +15 -1
  13. package/src/__tests__/auth-fallback-events-store.test.ts +6 -14
  14. package/src/__tests__/call-pointer-messages.test.ts +28 -0
  15. package/src/__tests__/cancel-clears-processing.test.ts +89 -0
  16. package/src/__tests__/channel-approval-routes.test.ts +0 -4
  17. package/src/__tests__/channel-inbound-disk-pressure.test.ts +5 -15
  18. package/src/__tests__/cli-memory-v2-reembed-skills.test.ts +3 -4
  19. package/src/__tests__/compactor-image-manifest-trust.test.ts +21 -1
  20. package/src/__tests__/compactor-summary-call-truncation.test.ts +223 -0
  21. package/src/__tests__/config-loader-backfill.test.ts +174 -30
  22. package/src/__tests__/config-schema.test.ts +35 -0
  23. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +2 -2
  24. package/src/__tests__/contact-store-user-file.test.ts +0 -6
  25. package/src/__tests__/contacts-tools.test.ts +29 -0
  26. package/src/__tests__/conversation-agent-loop-overflow.test.ts +1 -0
  27. package/src/__tests__/conversation-agent-loop.test.ts +58 -0
  28. package/src/__tests__/conversation-attention-telegram.test.ts +0 -1
  29. package/src/__tests__/conversation-lifecycle.test.ts +7 -9
  30. package/src/__tests__/conversation-load-history-repair.test.ts +101 -0
  31. package/src/__tests__/conversation-routes-guardian-reply.test.ts +15 -12
  32. package/src/__tests__/conversation-surfaces-activation-emit.test.ts +6 -3
  33. package/src/__tests__/conversation-title-service.test.ts +62 -0
  34. package/src/__tests__/credential-execution-shell-lockdown.test.ts +18 -11
  35. package/src/__tests__/credential-prompt-route.test.ts +1 -0
  36. package/src/__tests__/credential-security-invariants.test.ts +2 -0
  37. package/src/__tests__/disk-pressure-policy.test.ts +12 -0
  38. package/src/__tests__/disk-usage.test.ts +65 -0
  39. package/src/__tests__/dynamic-page-surface.test.ts +51 -0
  40. package/src/__tests__/gateway-flag-listener.test.ts +110 -1
  41. package/src/__tests__/guardian-binding-drift-heal.test.ts +1 -1
  42. package/src/__tests__/guardian-card-withdrawal.test.ts +403 -0
  43. package/src/__tests__/guardian-decision-primitive-canonical.test.ts +5 -3
  44. package/src/__tests__/guardian-grant-minting.test.ts +3 -35
  45. package/src/__tests__/guardian-routing-invariants.test.ts +64 -26
  46. package/src/__tests__/guardian-routing-state.test.ts +0 -1
  47. package/src/__tests__/headless-browser-mode.test.ts +10 -0
  48. package/src/__tests__/headless-browser-navigate.test.ts +8 -3
  49. package/src/__tests__/helpers/create-guardian-binding.ts +0 -1
  50. package/src/__tests__/host-browser-proxy.test.ts +87 -0
  51. package/src/__tests__/injector-v3-suppression.test.ts +27 -20
  52. package/src/__tests__/internal-telemetry-routes.test.ts +6 -14
  53. package/src/__tests__/invite-redemption-service.test.ts +0 -3
  54. package/src/__tests__/llm-catalog-parity.test.ts +30 -1
  55. package/src/__tests__/llm-resolver.test.ts +21 -0
  56. package/src/__tests__/llm-schema.test.ts +1 -0
  57. package/src/__tests__/managed-profile-guard.test.ts +163 -4
  58. package/src/__tests__/mcp-health-check.test.ts +6 -7
  59. package/src/__tests__/media-stream-server-integration.test.ts +317 -13
  60. package/src/__tests__/path-policy.test.ts +34 -0
  61. package/src/__tests__/persona-resolver.test.ts +38 -0
  62. package/src/__tests__/plugin-api-provider.test.ts +24 -0
  63. package/src/__tests__/plugin-tool-contribution.test.ts +6 -3
  64. package/src/__tests__/post-compaction-reinjection-idempotency.test.ts +214 -0
  65. package/src/__tests__/provider-send-message-override-profile.test.ts +76 -0
  66. package/src/__tests__/reaction-persistence.test.ts +150 -29
  67. package/src/__tests__/relay-server.test.ts +285 -0
  68. package/src/__tests__/runtime-attachment-metadata.test.ts +0 -1
  69. package/src/__tests__/scheduler-reuse-conversation.test.ts +8 -5
  70. package/src/__tests__/skill-execute-input.test.ts +5 -0
  71. package/src/__tests__/skills.test.ts +51 -0
  72. package/src/__tests__/slack-notification-approval-card.test.ts +176 -0
  73. package/src/__tests__/slack-reaction-canonical-approval.test.ts +285 -0
  74. package/src/__tests__/subagent-tools.test.ts +150 -0
  75. package/src/__tests__/task-progress-nudge-hook.test.ts +1 -1
  76. package/src/__tests__/title-generate-hook.test.ts +100 -3
  77. package/src/__tests__/tool-approval-seed-content-blocks.test.ts +1 -1
  78. package/src/__tests__/tool-audit-listener.test.ts +7 -7
  79. package/src/__tests__/tool-executor-lifecycle-events.test.ts +6 -3
  80. package/src/__tests__/trusted-contact-approval-notifier.test.ts +4 -2
  81. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +220 -3
  82. package/src/__tests__/trusted-contact-verification.test.ts +2 -4
  83. package/src/__tests__/twilio-routes.test.ts +81 -1
  84. package/src/__tests__/voice-invite-redemption.test.ts +0 -1
  85. package/src/__tests__/weak-open-model.test.ts +30 -0
  86. package/src/__tests__/workspace-migration-105-enable-memory-v3-live-for-new-workspaces.test.ts +149 -0
  87. package/src/__tests__/workspace-migration-108-drop-balanced-economy-profile.test.ts +285 -0
  88. package/src/__tests__/workspace-migration-add-send-diagnostics.test.ts +1 -1
  89. package/src/__tests__/workspace-migration-drop-collect-usage-data.test.ts +118 -0
  90. package/src/__tests__/workspace-migration-drop-send-diagnostics.test.ts +118 -0
  91. package/src/a2a/__tests__/e2e-a2a-channel.test.ts +0 -4
  92. package/src/agent/loop.ts +33 -33
  93. package/src/api/events/tool-result.ts +6 -0
  94. package/src/api/events/workflow-completed.ts +53 -0
  95. package/src/api/events/workflow-leaf-finished.ts +38 -0
  96. package/src/api/events/workflow-leaf-started.ts +35 -0
  97. package/src/api/events/workflow-progress.ts +32 -0
  98. package/src/api/events/workflow-started.ts +31 -0
  99. package/src/api/index.ts +40 -0
  100. package/src/api/responses/conversation-message.ts +26 -0
  101. package/src/api/responses/home.ts +26 -0
  102. package/src/api/responses/workflow-journal.ts +53 -0
  103. package/src/approvals/guardian-card-withdrawal.ts +145 -0
  104. package/src/approvals/guardian-decision-primitive.ts +26 -3
  105. package/src/approvals/guardian-request-resolvers.ts +181 -78
  106. package/src/calls/__tests__/channel-admission-reader.test.ts +132 -0
  107. package/src/calls/__tests__/relay-setup-router.test.ts +350 -0
  108. package/src/calls/call-pointer-messages.ts +10 -4
  109. package/src/calls/channel-admission-reader.ts +104 -0
  110. package/src/calls/guardian-dispatch.ts +17 -45
  111. package/src/calls/media-stream-server.ts +84 -2
  112. package/src/calls/relay-server.ts +66 -0
  113. package/src/calls/relay-setup-router.ts +82 -1
  114. package/src/calls/twilio-routes.ts +17 -8
  115. package/src/cli/commands/clients.ts +3 -0
  116. package/src/cli/commands/{__tests__ → memory/__tests__}/memory-v2-compare-render.test.ts +1 -1
  117. package/src/cli/commands/{__tests__ → memory/__tests__}/memory-v2.test.ts +8 -7
  118. package/src/cli/commands/{__tests__ → memory/__tests__}/memory-v3.test.ts +5 -4
  119. package/src/cli/commands/memory/index.ts +30 -0
  120. package/src/cli/commands/{memory-v2-compare-render.ts → memory/memory-v2-compare-render.ts} +1 -1
  121. package/src/cli/commands/{memory-v2.ts → memory/memory-v2.ts} +6 -15
  122. package/src/cli/commands/{memory-v3.ts → memory/memory-v3.ts} +97 -11
  123. package/src/cli/commands/oauth/status.test.ts +36 -0
  124. package/src/cli/commands/oauth/status.ts +23 -3
  125. package/src/cli/commands/plugins.ts +57 -5
  126. package/src/cli/lib/__tests__/inspect-plugin.test.ts +54 -0
  127. package/src/cli/lib/__tests__/merge-plugin-tree.test.ts +134 -4
  128. package/src/cli/lib/__tests__/plugin-surfaces.test.ts +111 -0
  129. package/src/cli/lib/__tests__/upgrade-plugin.test.ts +53 -11
  130. package/src/cli/lib/inspect-plugin.ts +12 -1
  131. package/src/cli/lib/merge-plugin-tree.ts +149 -49
  132. package/src/cli/lib/plugin-surfaces.ts +104 -0
  133. package/src/cli/lib/upgrade-plugin.ts +64 -36
  134. package/src/cli/program.ts +2 -4
  135. package/src/config/__tests__/sync-gated-profiles.test.ts +368 -0
  136. package/src/config/assistant-feature-flags.ts +22 -7
  137. package/src/config/bundled-skills/contacts/tools/contact-search.ts +0 -1
  138. package/src/config/bundled-skills/messaging/SKILL.md +6 -4
  139. package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +9 -8
  140. package/src/config/bundled-skills/workflows/SKILL.md +14 -7
  141. package/src/config/call-site-defaults.ts +3 -0
  142. package/src/config/feature-flag-registry.json +49 -18
  143. package/src/config/llm-resolver.ts +3 -0
  144. package/src/config/memory-v3-gate.ts +11 -0
  145. package/src/config/schema.ts +8 -6
  146. package/src/config/schemas/__tests__/memory-v3.test.ts +1 -0
  147. package/src/config/schemas/call-site-catalog.ts +7 -0
  148. package/src/config/schemas/channels.ts +11 -0
  149. package/src/config/schemas/llm.ts +31 -0
  150. package/src/config/schemas/memory-lifecycle.ts +3 -7
  151. package/src/config/schemas/memory-v3.ts +6 -0
  152. package/src/config/schemas/services.ts +18 -0
  153. package/src/config/seed-inference-profiles.ts +94 -34
  154. package/src/config/skills.ts +21 -0
  155. package/src/config/sync-gated-profiles.ts +220 -0
  156. package/src/contacts/contact-store.ts +2 -10
  157. package/src/contacts/contacts-write.ts +1 -2
  158. package/src/contacts/types.ts +0 -1
  159. package/src/context/compactor.ts +86 -52
  160. package/src/context/strip-injections.ts +58 -10
  161. package/src/context/token-estimator.ts +1 -1
  162. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +3 -2
  163. package/src/daemon/conversation-agent-loop-handlers.ts +2 -0
  164. package/src/daemon/conversation-agent-loop.ts +100 -19
  165. package/src/daemon/conversation-history.ts +1 -1
  166. package/src/daemon/conversation-lifecycle.ts +3 -5
  167. package/src/daemon/conversation-process.ts +13 -5
  168. package/src/daemon/conversation-runtime-assembly.ts +13 -15
  169. package/src/daemon/conversation-surfaces.ts +26 -0
  170. package/src/daemon/conversation-tool-setup.ts +16 -11
  171. package/src/daemon/conversation.ts +64 -14
  172. package/src/daemon/disk-pressure-policy.ts +5 -3
  173. package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +0 -1
  174. package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +0 -1
  175. package/src/daemon/handlers/config-a2a.ts +0 -2
  176. package/src/daemon/handlers/config-channels.ts +5 -10
  177. package/src/daemon/handlers/config-slack-channel.ts +20 -0
  178. package/src/daemon/handlers/conversations.ts +107 -0
  179. package/src/daemon/host-browser-proxy.ts +41 -0
  180. package/src/daemon/lifecycle.ts +55 -20
  181. package/src/daemon/message-provenance.ts +2 -0
  182. package/src/daemon/message-types/contacts.ts +0 -1
  183. package/src/daemon/message-types/web-activity.ts +7 -1
  184. package/src/daemon/message-types/workflows.ts +83 -1
  185. package/src/daemon/tool-setup-types.ts +4 -0
  186. package/src/daemon/trust-context.ts +1 -1
  187. package/src/events/tool-audit-listener.ts +2 -2
  188. package/src/home/feed-source-enrichment.test.ts +151 -0
  189. package/src/home/feed-source-enrichment.ts +176 -0
  190. package/src/instrument.ts +18 -6
  191. package/src/ipc/__tests__/binary-result-ipc.test.ts +81 -0
  192. package/src/ipc/__tests__/clients-list-ipc.test.ts +20 -0
  193. package/src/ipc/assistant-server.ts +37 -4
  194. package/src/ipc/gateway-flag-listener.ts +18 -2
  195. package/src/memory/__tests__/auto-analysis-enqueue.test.ts +5 -16
  196. package/src/memory/__tests__/jobs-store-enqueue-gate.test.ts +7 -11
  197. package/src/memory/__tests__/memory-retrospective-enqueue.test.ts +37 -7
  198. package/src/memory/__tests__/memory-retrospective-job.test.ts +34 -0
  199. package/src/memory/__tests__/onboarding-events-store.test.ts +7 -7
  200. package/src/memory/auth-fallback-events-store.ts +2 -2
  201. package/src/memory/auto-analysis-enqueue.ts +3 -5
  202. package/src/memory/canonical-guardian-store.ts +39 -1
  203. package/src/memory/conversation-crud.ts +9 -4
  204. package/src/memory/conversation-key-store.ts +17 -2
  205. package/src/memory/conversation-title-service.ts +64 -7
  206. package/src/memory/db-init.ts +10 -0
  207. package/src/memory/embedding-backend.ts +15 -1
  208. package/src/memory/jobs-worker.ts +2 -1
  209. package/src/memory/lifecycle-events-store.ts +2 -2
  210. package/src/memory/memory-retrospective-enqueue.ts +31 -6
  211. package/src/memory/memory-retrospective-job.ts +9 -0
  212. package/src/memory/migrations/129-contact-channels-access-fields.ts +18 -9
  213. package/src/memory/migrations/131-drop-legacy-member-guardian-tables.ts +14 -2
  214. package/src/memory/migrations/289-contact-channels-unique-ext-user.ts +10 -0
  215. package/src/memory/migrations/291-contact-channels-renormalize-addresses.ts +10 -0
  216. package/src/memory/migrations/292-schedule-default-no-reuse-conversation.test.ts +67 -0
  217. package/src/memory/migrations/292-schedule-default-no-reuse-conversation.ts +25 -0
  218. package/src/memory/migrations/293-workflow-journal-leaf-tokens.ts +32 -0
  219. package/src/memory/migrations/294-drop-external-user-id.ts +31 -0
  220. package/src/memory/migrations/295-drop-approval-prompt-ts-tracker.ts +20 -0
  221. package/src/memory/migrations/296-rewrite-balanced-economy-profile-pins.test.ts +110 -0
  222. package/src/memory/migrations/296-rewrite-balanced-economy-profile-pins.ts +68 -0
  223. package/src/memory/migrations/__tests__/131-drop-legacy-member-guardian-tables.test.ts +154 -0
  224. package/src/memory/migrations/__tests__/289-contact-channels-unique-ext-user.test.ts +31 -0
  225. package/src/memory/migrations/__tests__/291-contact-channels-renormalize-addresses.test.ts +30 -0
  226. package/src/memory/migrations/index.ts +5 -0
  227. package/src/memory/onboarding-events-store.ts +3 -3
  228. package/src/memory/schema/contacts.ts +0 -1
  229. package/src/memory/skill-loaded-events-store.test.ts +7 -15
  230. package/src/memory/skill-loaded-events-store.ts +2 -2
  231. package/src/memory/tool-executed-events-store.test.ts +7 -7
  232. package/src/memory/turn-trace-store.test.ts +736 -0
  233. package/src/memory/turn-trace-store.ts +364 -0
  234. package/src/memory/v2/__tests__/consolidation-job.test.ts +8 -0
  235. package/src/memory/v2/__tests__/skill-content.test.ts +30 -0
  236. package/src/memory/v2/consolidation-job.ts +2 -2
  237. package/src/memory/v2/skill-content.ts +25 -7
  238. package/src/memory/v2/skill-store.ts +7 -1
  239. package/src/memory/v3-eval/__tests__/eval-packets.test.ts +248 -0
  240. package/src/memory/v3-eval/eval-packets.ts +546 -0
  241. package/src/messaging/providers/slack/api.ts +31 -0
  242. package/src/messaging/providers/slack/send.test.ts +114 -2
  243. package/src/messaging/providers/slack/send.ts +30 -7
  244. package/src/messaging/providers/slack/withdraw.test.ts +200 -0
  245. package/src/messaging/providers/slack/withdraw.ts +161 -0
  246. package/src/notifications/AGENTS.md +2 -0
  247. package/src/notifications/access-request-copy.ts +72 -59
  248. package/src/notifications/adapters/slack.ts +55 -73
  249. package/src/notifications/approval-card-data.ts +333 -0
  250. package/src/notifications/broadcaster.ts +6 -2
  251. package/src/notifications/canonical-delivery-recorder.ts +139 -0
  252. package/src/notifications/copy-composer.ts +3 -3
  253. package/src/notifications/decision-engine.ts +4 -2
  254. package/src/notifications/destination-resolver.ts +4 -6
  255. package/src/notifications/guardian-question-mode.ts +10 -0
  256. package/src/notifications/home-feed-side-effect.ts +3 -13
  257. package/src/notifications/notification-utils.ts +2 -1
  258. package/src/notifications/signal.ts +79 -43
  259. package/src/notifications/types.ts +98 -128
  260. package/src/permissions/checker.test.ts +51 -0
  261. package/src/permissions/checker.ts +185 -26
  262. package/src/permissions/ipc-risk-types.ts +24 -0
  263. package/src/permissions/question-prompter.test.ts +27 -0
  264. package/src/permissions/question-prompter.ts +4 -0
  265. package/src/platform/client.test.ts +119 -0
  266. package/src/platform/client.ts +66 -0
  267. package/src/platform/consent-cache.test.ts +267 -0
  268. package/src/platform/consent-cache.ts +174 -0
  269. package/src/plugin-api/index.ts +27 -0
  270. package/src/plugins/defaults/advisor/__tests__/advisor-gate.test.ts +56 -0
  271. package/src/plugins/defaults/advisor/__tests__/advisor-state-store.test.ts +43 -0
  272. package/src/plugins/defaults/advisor/__tests__/agent-loop-integration.test.ts +137 -0
  273. package/src/plugins/defaults/advisor/__tests__/consult.test.ts +153 -0
  274. package/src/plugins/defaults/advisor/__tests__/hooks.test.ts +138 -0
  275. package/src/plugins/defaults/advisor/__tests__/transcript.test.ts +147 -0
  276. package/src/plugins/defaults/advisor/advisor-gate.ts +29 -0
  277. package/src/plugins/defaults/advisor/advisor-state-store.ts +94 -0
  278. package/src/plugins/defaults/advisor/config.ts +21 -0
  279. package/src/plugins/defaults/advisor/consult.ts +93 -0
  280. package/src/plugins/defaults/advisor/hooks/post-model-call.ts +34 -0
  281. package/src/plugins/defaults/advisor/hooks/pre-model-call.ts +30 -0
  282. package/src/plugins/defaults/advisor/hooks/user-prompt-submit.ts +19 -0
  283. package/src/plugins/defaults/advisor/package.json +14 -0
  284. package/src/plugins/defaults/advisor/steering.ts +67 -0
  285. package/src/plugins/defaults/advisor/tools/advisor.ts +65 -0
  286. package/src/plugins/defaults/advisor/transcript.ts +76 -0
  287. package/src/plugins/defaults/index.ts +35 -0
  288. package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +22 -9
  289. package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit.ts +2 -2
  290. package/src/plugins/defaults/memory-retrieval/tail-reinjection-strip.ts +64 -0
  291. package/src/plugins/defaults/memory-retrieval/unified-turn-context.ts +29 -21
  292. package/src/plugins/defaults/memory-v3-shadow/__tests__/carry-integration.test.ts +1 -0
  293. package/src/plugins/defaults/memory-v3-shadow/__tests__/injection.test.ts +1 -0
  294. package/src/plugins/defaults/memory-v3-shadow/__tests__/maintain-job.test.ts +75 -7
  295. package/src/plugins/defaults/memory-v3-shadow/__tests__/orchestrate.test.ts +31 -4
  296. package/src/plugins/defaults/memory-v3-shadow/__tests__/selection-log-store.test.ts +77 -2
  297. package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +1 -0
  298. package/src/plugins/defaults/memory-v3-shadow/injector.ts +7 -10
  299. package/src/plugins/defaults/memory-v3-shadow/maintain-job.ts +37 -4
  300. package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +32 -20
  301. package/src/plugins/defaults/memory-v3-shadow/selection-log-store.ts +56 -3
  302. package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +23 -2
  303. package/src/plugins/defaults/task-progress-nudge/hooks/post-tool-use.ts +2 -12
  304. package/src/plugins/defaults/title-generate/hooks/stop.ts +56 -21
  305. package/src/prompts/persona-resolver.ts +12 -2
  306. package/src/prompts/templates/system-sections.ts +7 -2
  307. package/src/providers/__tests__/provider-env-vars.test.ts +6 -0
  308. package/src/providers/__tests__/provider-secret-catalog.test.ts +1 -0
  309. package/src/providers/__tests__/retry-callsite.test.ts +176 -0
  310. package/src/providers/atlascloud/client.ts +85 -0
  311. package/src/providers/fetch-provider-catalog.ts +85 -0
  312. package/src/providers/inference/adapter-factory.ts +3 -0
  313. package/src/providers/model-catalog.ts +58 -0
  314. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +33 -0
  315. package/src/providers/openai/chat-completions-provider.ts +7 -0
  316. package/src/providers/openai/responses-provider.ts +10 -0
  317. package/src/providers/provider-send-message.ts +11 -3
  318. package/src/providers/retry.ts +53 -12
  319. package/src/providers/search-provider-catalog.ts +10 -0
  320. package/src/providers/weak-open-model.ts +22 -0
  321. package/src/runtime/__tests__/agent-wake.test.ts +181 -0
  322. package/src/runtime/__tests__/client-health.test.ts +44 -0
  323. package/src/runtime/access-request-helper.ts +21 -53
  324. package/src/runtime/actor-trust-resolver.ts +49 -21
  325. package/src/runtime/agent-wake.ts +52 -0
  326. package/src/runtime/assistant-event-hub.ts +18 -4
  327. package/src/runtime/auth/__tests__/route-policy.test.ts +12 -0
  328. package/src/runtime/auth/require-bound-guardian.ts +1 -4
  329. package/src/runtime/capabilities.test.ts +120 -0
  330. package/src/runtime/capabilities.ts +197 -0
  331. package/src/runtime/channel-approval-types.ts +5 -1
  332. package/src/runtime/channel-retry-sweep.ts +1 -0
  333. package/src/runtime/channel-verification-service.ts +1 -2
  334. package/src/runtime/client-health.ts +26 -0
  335. package/src/runtime/confirmation-request-guardian-bridge.ts +38 -29
  336. package/src/runtime/effective-capabilities.test.ts +128 -0
  337. package/src/runtime/effective-capabilities.ts +84 -0
  338. package/src/runtime/guardian-reply-router.ts +106 -21
  339. package/src/runtime/invite-redemption-service.ts +6 -22
  340. package/src/runtime/migrations/__tests__/vbundle-builder-fd-leak.test.ts +123 -0
  341. package/src/runtime/migrations/vbundle-builder.ts +49 -20
  342. package/src/runtime/pending-interactions.ts +15 -0
  343. package/src/runtime/routes/__tests__/client-routes.test.ts +13 -0
  344. package/src/runtime/routes/__tests__/conversation-management-routes.test.ts +67 -0
  345. package/src/runtime/routes/__tests__/plugins-routes.test.ts +35 -13
  346. package/src/runtime/routes/browser-tabs-routes.ts +9 -0
  347. package/src/runtime/routes/canonical-guardian-expiry-sweep.ts +17 -8
  348. package/src/runtime/routes/client-routes.ts +10 -0
  349. package/src/runtime/routes/contact-routes.ts +31 -8
  350. package/src/runtime/routes/conversation-management-routes.ts +80 -1
  351. package/src/runtime/routes/conversation-query-routes.ts +68 -22
  352. package/src/runtime/routes/conversation-routes.ts +37 -12
  353. package/src/runtime/routes/events-routes.ts +1 -3
  354. package/src/runtime/routes/guardian-approval-interception.ts +14 -73
  355. package/src/runtime/routes/guardian-approval-prompt.ts +22 -4
  356. package/src/runtime/routes/home-feed-routes.ts +8 -3
  357. package/src/runtime/routes/inbound-message-handler.ts +214 -228
  358. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +88 -6
  359. package/src/runtime/routes/inbound-stages/admission-policy.test.ts +154 -0
  360. package/src/runtime/routes/inbound-stages/admission-policy.ts +140 -0
  361. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +3 -3
  362. package/src/runtime/routes/inbound-stages/background-dispatch.ts +11 -6
  363. package/src/runtime/routes/inbound-stages/escalation-intercept.ts +1 -2
  364. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +1 -2
  365. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.test.ts +7 -7
  366. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +47 -28
  367. package/src/runtime/routes/inbound-stages/reaction-intercept.ts +358 -0
  368. package/src/runtime/routes/index.ts +2 -0
  369. package/src/runtime/routes/integrations/slack/__tests__/channel.test.ts +8 -0
  370. package/src/runtime/routes/integrations/slack/channel.ts +36 -0
  371. package/src/runtime/routes/internal-telemetry-routes.ts +1 -1
  372. package/src/runtime/routes/mcp-auth-routes.ts +233 -41
  373. package/src/runtime/routes/memory-eval-routes.ts +87 -0
  374. package/src/runtime/routes/notification-routes.ts +122 -133
  375. package/src/runtime/routes/platform-routes.ts +2 -2
  376. package/src/runtime/routes/plugins-routes.ts +40 -7
  377. package/src/runtime/routes/secret-routes.ts +10 -0
  378. package/src/runtime/routes/surface-action-routes.ts +2 -1
  379. package/src/runtime/routes/tool-call-question-enrichment.test.ts +146 -0
  380. package/src/runtime/routes/tool-call-question-enrichment.ts +66 -0
  381. package/src/runtime/routes/workflow-routes.test.ts +225 -1
  382. package/src/runtime/routes/workflow-routes.ts +131 -1
  383. package/src/runtime/tool-grant-request-helper.ts +18 -16
  384. package/src/runtime/trust-context-resolver.ts +8 -5
  385. package/src/schedule/schedule-store.ts +1 -1
  386. package/src/schedule/scheduler-types.ts +5 -1
  387. package/src/security/__tests__/provider-key-env-fallback.test.ts +6 -0
  388. package/src/security/secret-patterns.ts +3 -0
  389. package/src/subagent/manager.ts +11 -4
  390. package/src/telemetry/trace-collection-policy.test.ts +28 -0
  391. package/src/telemetry/trace-collection-policy.ts +30 -0
  392. package/src/telemetry/types.ts +89 -0
  393. package/src/telemetry/usage-telemetry-reporter.test.ts +586 -36
  394. package/src/telemetry/usage-telemetry-reporter.ts +148 -41
  395. package/src/tools/browser/__tests__/browser-execution-acquire.test.ts +31 -0
  396. package/src/tools/browser/browser-execution.ts +29 -18
  397. package/src/tools/document/document-tool.ts +2 -3
  398. package/src/tools/executor.ts +5 -3
  399. package/src/tools/host-terminal/host-shell.ts +5 -4
  400. package/src/tools/memory/register.ts +2 -2
  401. package/src/tools/network/__tests__/web-fetch-firecrawl.test.ts +360 -0
  402. package/src/tools/network/__tests__/web-search.test.ts +143 -0
  403. package/src/tools/network/web-fetch.ts +372 -1
  404. package/src/tools/network/web-search.ts +213 -10
  405. package/src/tools/permission-checker.ts +3 -2
  406. package/src/tools/registry.ts +20 -0
  407. package/src/tools/schedule/create.ts +4 -3
  408. package/src/tools/schedule/update.ts +2 -1
  409. package/src/tools/shared/filesystem/path-policy.ts +39 -13
  410. package/src/tools/skills/execute.ts +1 -2
  411. package/src/tools/subagent/spawn.ts +37 -13
  412. package/src/tools/terminal/shell.ts +10 -4
  413. package/src/tools/tool-approval-handler.ts +17 -10
  414. package/src/tools/types.ts +9 -0
  415. package/src/tools/ui-surface/definitions.ts +25 -2
  416. package/src/tools/verification-control-plane-policy.ts +3 -1
  417. package/src/tools/workflows/run-workflow.ts +1 -0
  418. package/src/util/disk-usage.ts +78 -23
  419. package/src/util/platform.ts +8 -1
  420. package/src/watcher/telemetry.ts +2 -2
  421. package/src/workflows/engine.test.ts +175 -1
  422. package/src/workflows/engine.ts +82 -0
  423. package/src/workflows/journal-store.test.ts +70 -0
  424. package/src/workflows/journal-store.ts +18 -3
  425. package/src/workflows/run-manager.test.ts +171 -3
  426. package/src/workflows/run-manager.ts +64 -0
  427. package/src/workspace/migrations/105-enable-memory-v3-live-for-new-workspaces.ts +63 -0
  428. package/src/workspace/migrations/106-drop-collect-usage-data.ts +47 -0
  429. package/src/workspace/migrations/107-drop-send-diagnostics.ts +47 -0
  430. package/src/workspace/migrations/108-drop-balanced-economy-profile.ts +129 -0
  431. package/src/workspace/migrations/registry.ts +8 -0
  432. package/src/notifications/tool-approval-copy.ts +0 -142
  433. package/src/runtime/routes/approval-prompt-ts-tracker.ts +0 -78
@@ -828,7 +828,7 @@ describe("routing invariant: disambiguation stays fail-closed", () => {
828
828
  replyCtx({
829
829
  messageText: "approve",
830
830
  conversationId: "conv-guardian-conversation",
831
- pendingRequestIds: [req.id],
831
+ pendingScope: { mode: "scoped", requestIds: [req.id] },
832
832
  approvalConversationGenerator: undefined,
833
833
  }),
834
834
  );
@@ -857,7 +857,7 @@ describe("routing invariant: disambiguation stays fail-closed", () => {
857
857
  replyCtx({
858
858
  messageText: "ok, what is this for?",
859
859
  conversationId: "conv-guardian-conversation",
860
- pendingRequestIds: [req.id],
860
+ pendingScope: { mode: "scoped", requestIds: [req.id] },
861
861
  approvalConversationGenerator: undefined,
862
862
  }),
863
863
  );
@@ -870,7 +870,7 @@ describe("routing invariant: disambiguation stays fail-closed", () => {
870
870
  expect(unchanged!.status).toBe("pending");
871
871
  });
872
872
 
873
- test("explicit empty pendingRequestIds hint stays fail-closed for desktop actors", async () => {
873
+ test("explicit blocked scope stays fail-closed for desktop actors", async () => {
874
874
  createCanonicalGuardianRequest({
875
875
  kind: "tool_approval",
876
876
  sourceType: "channel",
@@ -887,7 +887,7 @@ describe("routing invariant: disambiguation stays fail-closed", () => {
887
887
  messageText: "approve",
888
888
  actor: trustedActor(),
889
889
  conversationId: "conv-unrelated",
890
- pendingRequestIds: [],
890
+ pendingScope: { mode: "blocked" },
891
891
  approvalConversationGenerator: undefined,
892
892
  }),
893
893
  );
@@ -897,6 +897,38 @@ describe("routing invariant: disambiguation stays fail-closed", () => {
897
897
  expect(result.decisionApplied).toBe(false);
898
898
  });
899
899
 
900
+ test("explicit request code still resolves under a blocked scope (cross-chat carve-out)", async () => {
901
+ // The Slack cross-chat guard blocks identity fallback, but an explicit
902
+ // request code carries its own target and must still resolve — otherwise a
903
+ // guardian could not approve-by-code from a chat where no card was delivered.
904
+ const req = createCanonicalGuardianRequest({
905
+ kind: "tool_approval",
906
+ sourceType: "channel",
907
+ conversationId: "conv-other",
908
+ guardianExternalUserId: "guardian-1",
909
+ guardianPrincipalId: TEST_PRINCIPAL_ID,
910
+ requestCode: "ABC123",
911
+ toolName: "shell",
912
+ expiresAt: Date.now() + 60_000,
913
+ });
914
+ registerPendingToolApprovalInteraction(req.id, "conv-other", "shell");
915
+
916
+ const result = await routeGuardianReply(
917
+ replyCtx({
918
+ messageText: "ABC123 approve",
919
+ actor: trustedActor(),
920
+ conversationId: "conv-unrelated",
921
+ pendingScope: { mode: "blocked" },
922
+ approvalConversationGenerator: undefined,
923
+ }),
924
+ );
925
+
926
+ expect(result.consumed).toBe(true);
927
+ expect(result.requestId).toBe(req.id);
928
+ expect(result.decisionApplied).toBe(true);
929
+ expect(getCanonicalGuardianRequest(req.id)!.status).toBe("approved");
930
+ });
931
+
900
932
  test("multiple hinted pending requests with plain-text approve returns disambiguation", async () => {
901
933
  const req1 = createCanonicalGuardianRequest({
902
934
  kind: "tool_approval",
@@ -924,7 +956,7 @@ describe("routing invariant: disambiguation stays fail-closed", () => {
924
956
  replyCtx({
925
957
  messageText: "approve",
926
958
  conversationId: "conv-guardian-conversation",
927
- pendingRequestIds: [req1.id, req2.id],
959
+ pendingScope: { mode: "scoped", requestIds: [req1.id, req2.id] },
928
960
  approvalConversationGenerator: undefined,
929
961
  }),
930
962
  );
@@ -977,7 +1009,7 @@ describe("routing invariant: disambiguation stays fail-closed", () => {
977
1009
  replyCtx({
978
1010
  messageText: "yes approve it",
979
1011
  conversationId: "conv-1",
980
- pendingRequestIds: [req1.id, req2.id],
1012
+ pendingScope: { mode: "scoped", requestIds: [req1.id, req2.id] },
981
1013
  approvalConversationGenerator: mockGenerator as any,
982
1014
  }),
983
1015
  );
@@ -1031,7 +1063,10 @@ describe("routing invariant: disambiguation stays fail-closed", () => {
1031
1063
  replyCtx({
1032
1064
  messageText: "approve",
1033
1065
  conversationId: "conv-guardian-conversation",
1034
- pendingRequestIds: [answerRequest.id, approvalRequest.id],
1066
+ pendingScope: {
1067
+ mode: "scoped",
1068
+ requestIds: [answerRequest.id, approvalRequest.id],
1069
+ },
1035
1070
  approvalConversationGenerator: undefined,
1036
1071
  }),
1037
1072
  );
@@ -1075,7 +1110,7 @@ describe("routing invariant: disambiguation stays fail-closed", () => {
1075
1110
  replyCtx({
1076
1111
  messageText: "yes",
1077
1112
  conversationId: "conv-1",
1078
- pendingRequestIds: [req.id],
1113
+ pendingScope: { mode: "scoped", requestIds: [req.id] },
1079
1114
  approvalConversationGenerator: mockGenerator as any,
1080
1115
  }),
1081
1116
  );
@@ -1104,7 +1139,7 @@ describe("routing invariant: disambiguation stays fail-closed", () => {
1104
1139
  replyCtx({
1105
1140
  messageText: "go for it",
1106
1141
  conversationId: "conv-1",
1107
- pendingRequestIds: [req.id],
1142
+ pendingScope: { mode: "scoped", requestIds: [req.id] },
1108
1143
  approvalConversationGenerator: undefined,
1109
1144
  }),
1110
1145
  );
@@ -1117,7 +1152,7 @@ describe("routing invariant: disambiguation stays fail-closed", () => {
1117
1152
  expect(resolved!.status).toBe("approved");
1118
1153
  });
1119
1154
 
1120
- test("code-based routing is constrained to caller-provided pendingRequestIds scope", async () => {
1155
+ test("code-based routing is constrained to caller-provided scope", async () => {
1121
1156
  const inScope = createCanonicalGuardianRequest({
1122
1157
  kind: "tool_approval",
1123
1158
  sourceType: "channel",
@@ -1145,7 +1180,7 @@ describe("routing invariant: disambiguation stays fail-closed", () => {
1145
1180
  replyCtx({
1146
1181
  messageText: "222BBB approve",
1147
1182
  conversationId: "conv-guardian-conversation",
1148
- pendingRequestIds: [inScope.id],
1183
+ pendingScope: { mode: "scoped", requestIds: [inScope.id] },
1149
1184
  approvalConversationGenerator: undefined,
1150
1185
  }),
1151
1186
  );
@@ -1438,7 +1473,7 @@ describe("routing invariant: directResolve interactions resolve via guardian dec
1438
1473
  describe("routing invariant: destination hints do not bypass tool_approval principal binding", () => {
1439
1474
  beforeEach(() => resetTables());
1440
1475
 
1441
- test("explicit pendingRequestIds still fail closed when guardianPrincipalId does not match", async () => {
1476
+ test("explicit scope still fails closed when guardianPrincipalId does not match", async () => {
1442
1477
  // Voice-originated tool approval with a different principal than the actor.
1443
1478
  const req = createCanonicalGuardianRequest({
1444
1479
  kind: "tool_approval",
@@ -1452,15 +1487,15 @@ describe("routing invariant: destination hints do not bypass tool_approval princ
1452
1487
  });
1453
1488
  registerPendingToolApprovalInteraction(req.id, "conv-voice-1", "shell");
1454
1489
 
1455
- // The channel inbound router would compute pendingRequestIds from
1456
- // delivery-scoped lookup and pass them here. Simulate that.
1490
+ // The channel inbound router would compute the pending scope from
1491
+ // delivery-scoped lookup and pass it here. Simulate that.
1457
1492
  const result = await routeGuardianReply(
1458
1493
  replyCtx({
1459
1494
  messageText: "approve",
1460
1495
  channel: "telegram",
1461
1496
  actor: guardianActor({ guardianPrincipalId: "different-principal" }),
1462
1497
  conversationId: "conv-guardian-chat",
1463
- pendingRequestIds: [req.id],
1498
+ pendingScope: { mode: "scoped", requestIds: [req.id] },
1464
1499
  approvalConversationGenerator: undefined,
1465
1500
  }),
1466
1501
  );
@@ -1486,7 +1521,7 @@ describe("routing invariant: destination hints do not bypass tool_approval princ
1486
1521
  expiresAt: Date.now() + 60_000,
1487
1522
  });
1488
1523
 
1489
- // No pendingRequestIds passed — identity-based fallback uses
1524
+ // No pendingScope passed — identity-based fallback uses
1490
1525
  // actor.actorExternalUserId which does not match any request's
1491
1526
  // guardianExternalUserId (since it's null).
1492
1527
  const result = await routeGuardianReply(
@@ -1495,7 +1530,7 @@ describe("routing invariant: destination hints do not bypass tool_approval princ
1495
1530
  channel: "telegram",
1496
1531
  actor: guardianActor({ actorExternalUserId: "guardian-tg-user" }),
1497
1532
  conversationId: "conv-guardian-chat",
1498
- // pendingRequestIds: undefined — no delivery hints
1533
+ // pendingScope omitted — no delivery hints
1499
1534
  approvalConversationGenerator: undefined,
1500
1535
  }),
1501
1536
  );
@@ -1534,7 +1569,7 @@ describe("routing invariant: invite handoff bypass for access requests", () => {
1534
1569
  replyCtx({
1535
1570
  messageText: "open invite flow",
1536
1571
  conversationId: "conv-guardian-conversation",
1537
- pendingRequestIds: [req.id],
1572
+ pendingScope: { mode: "scoped", requestIds: [req.id] },
1538
1573
  approvalConversationGenerator: undefined,
1539
1574
  }),
1540
1575
  );
@@ -1569,7 +1604,7 @@ describe("routing invariant: invite handoff bypass for access requests", () => {
1569
1604
  replyCtx({
1570
1605
  messageText: phrase,
1571
1606
  conversationId: "conv-test",
1572
- pendingRequestIds: [req.id],
1607
+ pendingScope: { mode: "scoped", requestIds: [req.id] },
1573
1608
  approvalConversationGenerator: undefined,
1574
1609
  }),
1575
1610
  );
@@ -1595,7 +1630,7 @@ describe("routing invariant: invite handoff bypass for access requests", () => {
1595
1630
  replyCtx({
1596
1631
  messageText: "open invite flow",
1597
1632
  conversationId: "conv-guardian-conversation",
1598
- pendingRequestIds: [req.id],
1633
+ pendingScope: { mode: "scoped", requestIds: [req.id] },
1599
1634
  approvalConversationGenerator: undefined,
1600
1635
  }),
1601
1636
  );
@@ -1625,7 +1660,7 @@ describe("routing invariant: invite handoff bypass for access requests", () => {
1625
1660
  replyCtx({
1626
1661
  messageText: "A00B01 approve",
1627
1662
  conversationId: "conv-guardian-conversation",
1628
- pendingRequestIds: [req.id],
1663
+ pendingScope: { mode: "scoped", requestIds: [req.id] },
1629
1664
  approvalConversationGenerator: undefined,
1630
1665
  }),
1631
1666
  );
@@ -1655,7 +1690,7 @@ describe("routing invariant: invite handoff bypass for access requests", () => {
1655
1690
  channel: "vellum",
1656
1691
  actor: trustedActor({ channel: "vellum" }),
1657
1692
  conversationId: "conv-guardian-conversation",
1658
- pendingRequestIds: [req.id],
1693
+ pendingScope: { mode: "scoped", requestIds: [req.id] },
1659
1694
  approvalConversationGenerator: undefined,
1660
1695
  });
1661
1696
 
@@ -1694,7 +1729,7 @@ describe("routing invariant: invite handoff bypass for access requests", () => {
1694
1729
  channel: "vellum",
1695
1730
  actor: trustedActor({ channel: "vellum" }),
1696
1731
  conversationId: "conv-guardian-conversation",
1697
- pendingRequestIds: [req.id],
1732
+ pendingScope: { mode: "scoped", requestIds: [req.id] },
1698
1733
  approvalConversationGenerator: approvalConversationGenerator as any,
1699
1734
  });
1700
1735
 
@@ -1746,7 +1781,7 @@ describe("routing invariant: expired requests are excluded from pending discover
1746
1781
  replyCtx({
1747
1782
  messageText: "approve",
1748
1783
  conversationId: "conv-guardian-conversation",
1749
- pendingRequestIds: [expired.id, active.id],
1784
+ pendingScope: { mode: "scoped", requestIds: [expired.id, active.id] },
1750
1785
  approvalConversationGenerator: undefined,
1751
1786
  }),
1752
1787
  );
@@ -1781,7 +1816,7 @@ describe("routing invariant: expired requests are excluded from pending discover
1781
1816
  replyCtx({
1782
1817
  messageText: "`approve`",
1783
1818
  conversationId: "conv-guardian-conversation",
1784
- pendingRequestIds: [req.id],
1819
+ pendingScope: { mode: "scoped", requestIds: [req.id] },
1785
1820
  approvalConversationGenerator: undefined,
1786
1821
  }),
1787
1822
  );
@@ -1821,7 +1856,10 @@ describe("routing invariant: expired requests are excluded from pending discover
1821
1856
  replyCtx({
1822
1857
  messageText: "approve",
1823
1858
  conversationId: "conv-guardian-conversation",
1824
- pendingRequestIds: [expired1.id, expired2.id],
1859
+ pendingScope: {
1860
+ mode: "scoped",
1861
+ requestIds: [expired1.id, expired2.id],
1862
+ },
1825
1863
  approvalConversationGenerator: undefined,
1826
1864
  }),
1827
1865
  );
@@ -162,7 +162,6 @@ describe("inbound-message-handler trusted-contact interactivity", () => {
162
162
  {
163
163
  type: "telegram",
164
164
  address: "telegram-user-default",
165
- externalUserId: "telegram-user-default",
166
165
  status: "active",
167
166
  policy: "allow",
168
167
  },
@@ -75,6 +75,16 @@ mock.module("../tools/browser/cdp-client/factory.js", () => ({
75
75
  },
76
76
  }));
77
77
 
78
+ // The real proxy is an always-present singleton; stub the grace wait so
79
+ // extension-pinned acquisition doesn't poll for the full window here.
80
+ mock.module("../daemon/host-browser-proxy.js", () => ({
81
+ HostBrowserProxy: {
82
+ get instance() {
83
+ return { waitForExtensionClient: async () => true };
84
+ },
85
+ },
86
+ }));
87
+
78
88
  // ── Minimal browserManager stub ──────────────────────────────────────
79
89
 
80
90
  /** Mutable memo shared between mock methods and tests. */
@@ -58,9 +58,14 @@ let mockExtensionAvailable = false;
58
58
  mock.module("../daemon/host-browser-proxy.js", () => ({
59
59
  HostBrowserProxy: {
60
60
  get instance() {
61
- return mockExtensionAvailable
62
- ? { isAvailable: () => true, request: mock(async () => ({})) }
63
- : undefined;
61
+ // The real proxy is a lazily-created singleton that always exists;
62
+ // `isAvailable`/`waitForExtensionClient` reflect whether an extension
63
+ // is connected rather than the instance being absent.
64
+ return {
65
+ isAvailable: () => mockExtensionAvailable,
66
+ waitForExtensionClient: async () => mockExtensionAvailable,
67
+ request: mock(async () => ({})),
68
+ };
64
69
  },
65
70
  },
66
71
  }));
@@ -55,7 +55,6 @@ export function createGuardianBinding(params: {
55
55
  {
56
56
  type: params.channel,
57
57
  address: canonicalId,
58
- externalUserId: canonicalId,
59
58
  externalChatId: params.guardianDeliveryChatId,
60
59
  status: "active",
61
60
  verifiedAt: Date.now(),
@@ -46,6 +46,8 @@ mock.module("../runtime/assistant-event-hub.js", () => ({
46
46
  mockClients.filter((c) => c.capabilities.includes(cap)),
47
47
  listClientsByInterface: (interfaceId: string) =>
48
48
  mockClients.filter((c) => c.interfaceId === interfaceId),
49
+ getClientById: (clientId: string) =>
50
+ mockClients.find((c) => c.clientId === clientId),
49
51
  getActorPrincipalIdForClient: (clientId: string) =>
50
52
  mockClients.find((c) => c.clientId === clientId)?.actorPrincipalId,
51
53
  },
@@ -108,6 +110,91 @@ describe("HostBrowserProxy", () => {
108
110
  pendingInteractions.clear();
109
111
  });
110
112
 
113
+ describe("waitForExtensionClient", () => {
114
+ const EXTENSION: MockClient = {
115
+ clientId: "ext-client",
116
+ interfaceId: "chrome-extension",
117
+ capabilities: ["host_browser"],
118
+ };
119
+
120
+ test("returns true immediately when an extension is connected", async () => {
121
+ mockClients = [EXTENSION];
122
+ expect(await proxy.waitForExtensionClient(undefined, undefined, 1_000)).toBe(
123
+ true,
124
+ );
125
+ });
126
+
127
+ test("returns false after the timeout when none connects", async () => {
128
+ mockClients = [DEFAULT_CLIENT]; // macos only, no extension
129
+ expect(await proxy.waitForExtensionClient(undefined, undefined, 100)).toBe(
130
+ false,
131
+ );
132
+ });
133
+
134
+ test("returns true when an extension appears within the window", async () => {
135
+ mockClients = [DEFAULT_CLIENT];
136
+ setTimeout(() => {
137
+ mockClients = [DEFAULT_CLIENT, EXTENSION];
138
+ }, 50);
139
+ expect(await proxy.waitForExtensionClient(undefined, undefined, 2_000)).toBe(
140
+ true,
141
+ );
142
+ });
143
+
144
+ test("respects sourceActorPrincipalId", async () => {
145
+ mockClients = [
146
+ {
147
+ clientId: "other-actor-ext",
148
+ interfaceId: "chrome-extension",
149
+ actorPrincipalId: "actor-b",
150
+ capabilities: ["host_browser"],
151
+ },
152
+ ];
153
+ // Caller is actor-a; the connected extension belongs to actor-b.
154
+ expect(await proxy.waitForExtensionClient("actor-a", undefined, 100)).toBe(
155
+ false,
156
+ );
157
+ });
158
+
159
+ test("with a targetClientId, waits for that exact client (not a sibling)", async () => {
160
+ // A sibling extension is connected, but the targeted client is not —
161
+ // the wait must not return early on the sibling's presence.
162
+ mockClients = [
163
+ {
164
+ clientId: "sibling-ext",
165
+ interfaceId: "chrome-extension",
166
+ capabilities: ["host_browser"],
167
+ },
168
+ ];
169
+ expect(
170
+ await proxy.waitForExtensionClient(undefined, "target-ext", 100),
171
+ ).toBe(false);
172
+ });
173
+
174
+ test("with a targetClientId, returns true once that client appears", async () => {
175
+ mockClients = [
176
+ {
177
+ clientId: "sibling-ext",
178
+ interfaceId: "chrome-extension",
179
+ capabilities: ["host_browser"],
180
+ },
181
+ ];
182
+ setTimeout(() => {
183
+ mockClients = [
184
+ ...mockClients,
185
+ {
186
+ clientId: "target-ext",
187
+ interfaceId: "chrome-extension",
188
+ capabilities: ["host_browser"],
189
+ },
190
+ ];
191
+ }, 50);
192
+ expect(
193
+ await proxy.waitForExtensionClient(undefined, "target-ext", 2_000),
194
+ ).toBe(true);
195
+ });
196
+ });
197
+
111
198
  describe("request/resolve lifecycle (happy path)", () => {
112
199
  test("sends host_browser_request and resolves with content", async () => {
113
200
  const resultPromise = proxy.request(
@@ -2,7 +2,7 @@
2
2
  * Tests for the memory-v3 step-0 branch in `applyRuntimeInjections`:
3
3
  * spotlight strip + v2 tail suppression.
4
4
  *
5
- * When the `memory-v3-live` flag is on AND the v3 injector (id `memory-v3`,
5
+ * When `memory.v3.live` is on AND the v3 injector (id `memory-v3`,
6
6
  * placement `after-memory-prefix`) produces a block — possibly EMPTY-TEXT on
7
7
  * an all-repeat turn — runtime assembly strips the v2 `<memory>` prefix from
8
8
  * the TAIL user message only before splicing the v3 block. Historical user
@@ -16,10 +16,11 @@
16
16
  * block lands at the tail.
17
17
  *
18
18
  * v2 suppression stays keyed off whether v3 produced a block, NOT off the
19
- * flag alone: a v3 failure (`produce()` → null) leaves v2's block intact
20
- * (fallback-to-v2). The flag-off path must be byte-for-byte identical to
19
+ * gate alone: a v3 failure (`produce()` → null) leaves v2's block intact
20
+ * (fallback-to-v2). The v3-off path must be byte-for-byte identical to
21
21
  * today — that is the load-bearing regression guard. `applyRuntimeInjections`
22
- * reads the flag itself, so these tests drive it through the override cache.
22
+ * reads the v3-live gate itself, so these tests drive it through the
23
+ * `isMemoryV3Live` mock slot.
23
24
  *
24
25
  * The strip discriminates v2's dynamic block by IDENTITY, not by prefix: v2's
25
26
  * `INJECTION_HEADER` and v3's `V3_CARDS_INJECTION_HEADER` are deliberately
@@ -42,7 +43,6 @@ import type {
42
43
  TurnContext,
43
44
  } from "../plugins/types.js";
44
45
  import type { Message } from "../providers/types.js";
45
- import { setOverridesForTesting } from "./feature-flag-test-helpers.js";
46
46
 
47
47
  // Drive the suppression branch by controlling the static injector chain that
48
48
  // `applyRuntimeInjections` walks. The slot is mutated per-test to stand in for
@@ -52,6 +52,13 @@ mock.module("../plugins/defaults/memory-retrieval/injector-chain.js", () => ({
52
52
  getInjectorChain: () => injectorChainSlot,
53
53
  }));
54
54
 
55
+ // `applyRuntimeInjections` reads the v3-live gate (`config.memory.v3.live`)
56
+ // via `isMemoryV3Live`; drive it directly through this slot per-test.
57
+ let memoryV3LiveSlot = false;
58
+ mock.module("../config/memory-v3-gate.js", () => ({
59
+ isMemoryV3Live: () => memoryV3LiveSlot,
60
+ }));
61
+
55
62
  const { applyRuntimeInjections } =
56
63
  await import("../daemon/conversation-runtime-assembly.js");
57
64
 
@@ -141,9 +148,9 @@ function tailTexts(messages: Message[]): string[] {
141
148
  describe("memory-v3-live v2 suppression", () => {
142
149
  beforeEach(() => {
143
150
  injectorChainSlot.length = 0;
144
- // Clean baseline: no overrides → `memory-v3-live` resolves to its registry
145
- // default (off). Each test seeds the flag it needs.
146
- setOverridesForTesting({});
151
+ // Clean baseline: v3-live off (registry/config default). Each test seeds
152
+ // the gate value it needs.
153
+ memoryV3LiveSlot = false;
147
154
  });
148
155
 
149
156
  afterEach(() => {
@@ -152,7 +159,7 @@ describe("memory-v3-live v2 suppression", () => {
152
159
  });
153
160
 
154
161
  test("flag ON + v3 produced a block → TAIL v2 stripped, historical memory blocks frozen in place", async () => {
155
- setOverridesForTesting({ "memory-v3-live": true });
162
+ memoryV3LiveSlot = true;
156
163
  injectorChainSlot.push(v3Injector("net-new cards"));
157
164
  seedV2Identity("fresh recalled fact");
158
165
 
@@ -194,7 +201,7 @@ describe("memory-v3-live v2 suppression", () => {
194
201
  });
195
202
 
196
203
  test("flag ON + EMPTY-TEXT v3 block (all-repeat turn) → tail v2 stripped, nothing attached", async () => {
197
- setOverridesForTesting({ "memory-v3-live": true });
204
+ memoryV3LiveSlot = true;
198
205
  injectorChainSlot.push(v3Injector(""));
199
206
  seedV2Identity("fresh recalled fact");
200
207
 
@@ -217,7 +224,7 @@ describe("memory-v3-live v2 suppression", () => {
217
224
  });
218
225
 
219
226
  test("stale spotlight blocks are stripped from EVERY user message; the new spotlight lands at the tail", async () => {
220
- setOverridesForTesting({ "memory-v3-live": true });
227
+ memoryV3LiveSlot = true;
221
228
  injectorChainSlot.push(v3Injector(""), spotlightInjector("fresh sections"));
222
229
 
223
230
  const staleSpotlight = {
@@ -253,7 +260,7 @@ describe("memory-v3-live v2 suppression", () => {
253
260
  });
254
261
 
255
262
  test("convergence re-entry: a tail leading with this turn's frozen v3 cards (and <info>) is NOT stripped", async () => {
256
- setOverridesForTesting({ "memory-v3-live": true });
263
+ memoryV3LiveSlot = true;
257
264
  // Re-entry shape: the memo returns the same selections, every slug is now
258
265
  // in the everInjected store, so the injector produces an EMPTY block —
259
266
  // while the tail still carries the v3 card block frozen on first entry
@@ -292,7 +299,7 @@ describe("memory-v3-live v2 suppression", () => {
292
299
  });
293
300
 
294
301
  test("first entry with a v2 prefix AND a frozen v3 block strips ONLY the v2 block", async () => {
295
- setOverridesForTesting({ "memory-v3-live": true });
302
+ memoryV3LiveSlot = true;
296
303
  injectorChainSlot.push(v3Injector(""));
297
304
  seedV2Identity("fresh recalled fact");
298
305
 
@@ -319,7 +326,7 @@ describe("memory-v3-live v2 suppression", () => {
319
326
  });
320
327
 
321
328
  test("REGRESSION: a v2 block leading with the REAL summary header (byte-identical to v3's) is stripped; v3 cards and <info> survive (first entry)", async () => {
322
- setOverridesForTesting({ "memory-v3-live": true });
329
+ memoryV3LiveSlot = true;
323
330
  injectorChainSlot.push(v3Injector(""));
324
331
 
325
332
  // The collision this guards: v2's router block leads with
@@ -358,7 +365,7 @@ describe("memory-v3-live v2 suppression", () => {
358
365
  });
359
366
 
360
367
  test("REGRESSION: re-entry with a header-bearing v2 identity keeps the first entry's frozen v3 cards", async () => {
361
- setOverridesForTesting({ "memory-v3-live": true });
368
+ memoryV3LiveSlot = true;
362
369
  // Re-entry: produce() returns the EMPTY block (cards already claimed by
363
370
  // the store) while the tail carries the FIRST entry's v3 block — and the
364
371
  // graph handle still holds the summary-bearing v2 identity from this
@@ -391,7 +398,7 @@ describe("memory-v3-live v2 suppression", () => {
391
398
  });
392
399
 
393
400
  test("v2 memory-image groups and legacy <memory __injected> blocks are stripped even without an identity", async () => {
394
- setOverridesForTesting({ "memory-v3-live": true });
401
+ memoryV3LiveSlot = true;
395
402
  injectorChainSlot.push(v3Injector("net-new cards"));
396
403
  // No live graph handle: identity unknown (null). Image groups and legacy
397
404
  // blocks are unambiguously v2's and are stripped regardless; the shared
@@ -430,7 +437,7 @@ describe("memory-v3-live v2 suppression", () => {
430
437
  });
431
438
 
432
439
  test("the v3 block's attachment-commit callback fires on a user tail", async () => {
433
- setOverridesForTesting({ "memory-v3-live": true });
440
+ memoryV3LiveSlot = true;
434
441
  const commit = mock(() => {});
435
442
  injectorChainSlot.push(v3Injector("net-new cards", commit));
436
443
 
@@ -443,7 +450,7 @@ describe("memory-v3-live v2 suppression", () => {
443
450
  });
444
451
 
445
452
  test("the commit callback is SKIPPED when the tail is not a user message (block never attaches)", async () => {
446
- setOverridesForTesting({ "memory-v3-live": true });
453
+ memoryV3LiveSlot = true;
447
454
  const commit = mock(() => {});
448
455
  injectorChainSlot.push(v3Injector("net-new cards", commit));
449
456
 
@@ -465,7 +472,7 @@ describe("memory-v3-live v2 suppression", () => {
465
472
  });
466
473
 
467
474
  test("flag ON but v3 produced NOTHING → v2 block left intact (fallback-to-v2)", async () => {
468
- setOverridesForTesting({ "memory-v3-live": true });
475
+ memoryV3LiveSlot = true;
469
476
  injectorChainSlot.push(v3Injector(null));
470
477
 
471
478
  const runMessages: Message[] = [
@@ -524,7 +531,7 @@ describe("memory-v3-live v2 suppression", () => {
524
531
  test("no v3 injector registered + flag ON → no stripping, messages untouched", async () => {
525
532
  // No injector named memory-v3 at all (e.g. plugin not loaded): the
526
533
  // suppression branch keys off the produced block, so nothing is stripped.
527
- setOverridesForTesting({ "memory-v3-live": true });
534
+ memoryV3LiveSlot = true;
528
535
  expect(injectorChainSlot).toHaveLength(0);
529
536
 
530
537
  const runMessages: Message[] = [
@@ -7,23 +7,15 @@ mock.module("../util/logger.js", () => ({
7
7
  }),
8
8
  }));
9
9
 
10
- // Toggle for the collectUsageData opt-out the real store consults. The store
10
+ // Toggle for the share_analytics opt-out the real store consults. The store
11
11
  // module is intentionally NOT mocked here — it has its own DB-backed tests, and
12
12
  // Bun's `mock.module` is process-global, so mocking it would leak into those
13
13
  // tests when files share an invocation. Exercising the real store keeps every
14
14
  // auth-fallback test order-independent.
15
- let collectUsageData = true;
15
+ let shareAnalytics = true;
16
16
 
17
- mock.module("../config/loader.js", () => ({
18
- getConfig: () => ({
19
- ui: {},
20
- model: "test",
21
- provider: "test",
22
- memory: { enabled: false },
23
- rateLimit: { maxRequestsPerMinute: 0 },
24
- secretDetection: { enabled: false },
25
- collectUsageData,
26
- }),
17
+ mock.module("../platform/consent-cache.js", () => ({
18
+ getCachedShareAnalytics: () => shareAnalytics,
27
19
  }));
28
20
 
29
21
  import { queryUnreportedAuthFallbackEvents } from "../memory/auth-fallback-events-store.js";
@@ -61,7 +53,7 @@ const VALID_BODY = {
61
53
 
62
54
  describe("internal-telemetry-routes: auth-fallback", () => {
63
55
  beforeEach(() => {
64
- collectUsageData = true;
56
+ shareAnalytics = true;
65
57
  getDb().delete(authFallbackEvents).run();
66
58
  });
67
59
 
@@ -90,7 +82,7 @@ describe("internal-telemetry-routes: auth-fallback", () => {
90
82
  });
91
83
 
92
84
  test("returns skipped and persists nothing under the opt-out", () => {
93
- collectUsageData = false;
85
+ shareAnalytics = false;
94
86
  expect(call(VALID_BODY)).toEqual({ skipped: true });
95
87
  expect(queryUnreportedAuthFallbackEvents(0, undefined, 100).length).toBe(0);
96
88
  });
@@ -292,7 +292,6 @@ describe("invite-redemption-service", () => {
292
292
  {
293
293
  type: "telegram",
294
294
  address: "guardian-tg-id",
295
- externalUserId: "guardian-tg-id",
296
295
  status: "revoked",
297
296
  },
298
297
  ],
@@ -346,7 +345,6 @@ describe("invite-redemption-service", () => {
346
345
  {
347
346
  type: "telegram",
348
347
  address: "guardian-own-id",
349
- externalUserId: "guardian-own-id",
350
348
  status: "revoked",
351
349
  },
352
350
  ],
@@ -381,7 +379,6 @@ describe("invite-redemption-service", () => {
381
379
  {
382
380
  type: "telegram",
383
381
  address: "guardian-code-id",
384
- externalUserId: "guardian-code-id",
385
382
  status: "revoked",
386
383
  },
387
384
  ],