@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
@@ -291,6 +291,32 @@ export function markSurfaceCompleted(
291
291
  }
292
292
  }
293
293
 
294
+ /**
295
+ * Complete a `ui_surface` card and notify live clients, addressed only by
296
+ * conversation + surface id.
297
+ *
298
+ * Unlike {@link completeSurfaceFromAction}, this needs no live `Conversation`
299
+ * instance, so it can run from flows that don't own one — projecting a
300
+ * terminal guardian-request status onto its in-app approval card when the
301
+ * request was resolved on another surface (or by the expiry sweep). Persists
302
+ * the completion (reload-safe) and broadcasts `ui_surface_complete` so every
303
+ * connected client of this guardian converges. No-ops when the surface block
304
+ * isn't found in the conversation.
305
+ */
306
+ export function completeSurfaceAndNotify(
307
+ conversationId: string,
308
+ surfaceId: string,
309
+ summary: string,
310
+ ): void {
311
+ markSurfaceCompleted({ conversationId }, surfaceId, summary);
312
+ broadcastMessage({
313
+ type: "ui_surface_complete",
314
+ conversationId,
315
+ surfaceId,
316
+ summary,
317
+ });
318
+ }
319
+
294
320
  /**
295
321
  * Remove a `ui_surface` content block from history so a passively dismissed
296
322
  * surface does not survive a reload. The live client drops a dismissed surface
@@ -55,7 +55,7 @@ import {
55
55
  } from "./doordash-steps.js";
56
56
  import type { ServerMessage, UiSurfaceShow } from "./message-protocol.js";
57
57
  import { runPostExecutionSideEffects } from "./tool-side-effects.js";
58
- import { resolveTrustClass } from "./trust-context.js";
58
+ import { FALLBACK_TURN_TRUST, resolveTrustClass } from "./trust-context.js";
59
59
 
60
60
  const log = getLogger("conversation-tool-setup");
61
61
 
@@ -181,28 +181,32 @@ export function createToolExecutor(
181
181
  markDoordashStepInProgress(ctx, executionInput);
182
182
  }
183
183
 
184
- // Build the context object shared by both the skill_execute interception
185
- // path and the regular executor path.
184
+ // Per-turn trust snapshot: prefer the snapshot captured at turn start so
185
+ // a concurrent owner meta command (/status, /clean) that mutates the live
186
+ // trustContext cannot elevate the in-flight turn to guardian.
187
+ const turnTrust =
188
+ ctx.currentTurnTrustContext ?? ctx.trustContext ?? FALLBACK_TURN_TRUST;
189
+
186
190
  const toolContext: ToolContext = {
187
191
  workingDir: ctx.workingDir,
188
192
  conversationId: ctx.conversationId,
189
193
  assistantId: ctx.assistantId,
190
194
  requestId: ctx.currentRequestId,
191
195
  taskRunId: ctx.taskRunId,
192
- trustClass: resolveTrustClass(ctx.trustContext),
193
- executionChannel: ctx.trustContext?.sourceChannel,
194
- sourceActorPrincipalId: ctx.trustContext?.guardianPrincipalId,
196
+ trustClass: resolveTrustClass(turnTrust),
197
+ executionChannel: turnTrust.sourceChannel,
198
+ sourceActorPrincipalId: turnTrust.guardianPrincipalId,
195
199
  callSessionId: ctx.callSessionId,
196
200
  triggeredBySurfaceAction:
197
201
  ctx.surfaceActionRequestIds?.has(ctx.currentRequestId ?? "") ?? false,
198
202
  approvedViaPrompt: ctx.approvedViaPromptThisTurn || undefined,
199
203
  batchAuthorizedByTask: false,
200
- requesterExternalUserId: ctx.trustContext?.requesterExternalUserId,
201
- requesterChatId: ctx.trustContext?.requesterChatId,
202
- requesterIdentifier: ctx.trustContext?.requesterIdentifier,
203
- requesterDisplayName: ctx.trustContext?.requesterDisplayName,
204
+ requesterExternalUserId: turnTrust.requesterExternalUserId,
205
+ requesterChatId: turnTrust.requesterChatId,
206
+ requesterIdentifier: turnTrust.requesterIdentifier,
207
+ requesterDisplayName: turnTrust.requesterDisplayName,
204
208
  channelPermissionChannelId:
205
- ctx.trustContext?.sourceChannel === "slack"
209
+ turnTrust.sourceChannel === "slack"
206
210
  ? getBindingByConversation(ctx.conversationId)?.externalChatId
207
211
  : undefined,
208
212
  onOutput,
@@ -214,6 +218,7 @@ export function createToolExecutor(
214
218
  isPlatformHosted: getIsPlatform(),
215
219
  transportInterface: ctx.transportInterface,
216
220
  overrideProfile: ctx.currentTurnOverrideProfile,
221
+ invokingCallSite: ctx.currentCallSite ?? "mainAgent",
217
222
  attribution: resolveConversationAttribution(ctx),
218
223
  onToolLifecycleEvent: handleToolLifecycleEvent,
219
224
  sendToClient: (msg) => {
@@ -80,12 +80,10 @@ import {
80
80
  } from "../prompts/system-prompt.js";
81
81
  import type { ContentBlock, Message } from "../providers/types.js";
82
82
  import type { Provider } from "../providers/types.js";
83
- import {
84
- isUntrustedTrustClass,
85
- type TrustClass,
86
- } from "../runtime/actor-trust-resolver.js";
83
+ import { type TrustClass } from "../runtime/actor-trust-resolver.js";
87
84
  import { broadcastMessage } from "../runtime/assistant-event-hub.js";
88
85
  import type { AuthContext } from "../runtime/auth/types.js";
86
+ import { resolveCapabilities } from "../runtime/capabilities.js";
89
87
  import type { InteractiveUiResult } from "../runtime/interactive-ui.js";
90
88
  import { publishSyncInvalidation } from "../runtime/sync/sync-publisher.js";
91
89
  import {
@@ -107,7 +105,7 @@ import {
107
105
  runAgentLoopImpl,
108
106
  } from "./conversation-agent-loop.js";
109
107
  import type { HistoryConversationContext } from "./conversation-history.js";
110
- import { undo as undoImpl } from "./conversation-history.js";
108
+ import { isToolResultBlock, undo as undoImpl } from "./conversation-history.js";
111
109
  import {
112
110
  abortConversation,
113
111
  disposeConversation,
@@ -175,6 +173,36 @@ import { TraceEmitter } from "./trace-emitter.js";
175
173
 
176
174
  const log = getLogger("conversation");
177
175
 
176
+ /**
177
+ * Whether a persisted message starts a new conversation turn. A turn is
178
+ * delimited by a "real" user message; a user message whose content is entirely
179
+ * tool_result blocks is a continuation within the current turn, and assistant
180
+ * messages never start one. Mirrors the turn-boundary definition used by
181
+ * `getAssistantMessageIdsInTurn`/`getTurnTimeBounds` and the agent loop's
182
+ * per-turn `turnCount++`, so counting these reconstructs `turnCount` on load.
183
+ */
184
+ function startsNewTurn(role: string, content: string): boolean {
185
+ if (role !== "user") return false;
186
+ try {
187
+ const parsed = JSON.parse(content);
188
+ if (
189
+ Array.isArray(parsed) &&
190
+ parsed.length > 0 &&
191
+ parsed.every(
192
+ (block: unknown) =>
193
+ block != null &&
194
+ typeof block === "object" &&
195
+ isToolResultBlock(block as Record<string, unknown>),
196
+ )
197
+ ) {
198
+ return false;
199
+ }
200
+ } catch {
201
+ // Non-JSON content is a plain user message — a turn boundary.
202
+ }
203
+ return true;
204
+ }
205
+
178
206
  export interface CleanResult {
179
207
  previousEstimatedInputTokens: number;
180
208
  estimatedInputTokens: number;
@@ -366,6 +394,13 @@ export class Conversation {
366
394
  workspaceDir: string,
367
395
  ) => Pick<WorkspaceGitService, "ensureInitialized">;
368
396
  /** @internal */ commitTurnChanges?: typeof commitTurnChanges;
397
+ /**
398
+ * Abort-watchdog timeout (ms) for the agent loop's bounded-unwind backstop.
399
+ * Overridable in tests to fire the watchdog quickly; defaults to the
400
+ * production constant in the agent loop when unset.
401
+ * @internal
402
+ */
403
+ abortWatchdogMs?: number;
369
404
  /**
370
405
  * The conversation's immutable creation type (`interactive`, `background`,
371
406
  * `scheduled`, …) as stored on the DB row. Cached on load (and set directly
@@ -824,10 +859,25 @@ export class Conversation {
824
859
 
825
860
  async loadFromDb(): Promise<void> {
826
861
  const trustClass = this.trustContext?.trustClass;
862
+ const canAccessMemory = resolveCapabilities(trustClass).canAccessMemory;
827
863
  const allDbMessages = getMessages(this.conversationId);
828
- const dbMessages = isUntrustedTrustClass(trustClass)
829
- ? filterMessagesForUntrustedActor(allDbMessages)
830
- : allDbMessages;
864
+ const dbMessages = canAccessMemory
865
+ ? allDbMessages
866
+ : filterMessagesForUntrustedActor(allDbMessages);
867
+
868
+ // Rehydrate the in-memory turn counter from persisted history. `turnCount`
869
+ // is otherwise a fresh-zero field, so a reloaded conversation (eviction,
870
+ // restart, fork) would restart its turn numbering at 0 and the next turn
871
+ // would reuse `turnIndex` 0 — colliding with the conversation's first turn.
872
+ // The memory-v3 selector memoizes per (conversationId, turnIndex) for the
873
+ // life of the daemon process, so a collided turnIndex serves a stale
874
+ // selection and skips retrieval. One turn per real (turn-starting) user
875
+ // message, matching the agent loop's per-turn `turnCount++`. Counted from
876
+ // the full unsliced history so it survives compaction and is independent of
877
+ // the viewer's trust class.
878
+ this.turnCount = allDbMessages.filter((m) =>
879
+ startsNewTurn(m.role, m.content),
880
+ ).length;
831
881
 
832
882
  const conv = getConversation(this.conversationId);
833
883
  this.conversationType = conv?.conversationType ?? undefined;
@@ -855,12 +905,12 @@ export class Conversation {
855
905
  // than exist. Slack chronological context is a separate consumer that
856
906
  // applies its own trust filtering downstream, so it reads the raw
857
907
  // mirrored count rather than this in-context boundary.
858
- const inContextCompactedCount = isUntrustedTrustClass(trustClass)
859
- ? 0
860
- : Math.min(this.contextCompactedMessageCount, dbMessages.length);
861
- const contextSummaryForHistory = isUntrustedTrustClass(trustClass)
862
- ? null
863
- : this.contextSummary?.trim() || null;
908
+ const inContextCompactedCount = canAccessMemory
909
+ ? Math.min(this.contextCompactedMessageCount, dbMessages.length)
910
+ : 0;
911
+ const contextSummaryForHistory = canAccessMemory
912
+ ? this.contextSummary?.trim() || null
913
+ : null;
864
914
 
865
915
  // Every injection-strip event (`/clean` or compaction) updates
866
916
  // `historyStrippedAt`. Messages older than this should skip metadata
@@ -1,5 +1,6 @@
1
1
  import type { InterfaceId } from "../channels/types.js";
2
2
  import type { LLMCallSite } from "../config/schemas/llm.js";
3
+ import { resolveCapabilities } from "../runtime/capabilities.js";
3
4
  import type { DiskPressureStatus } from "./disk-pressure-guard.js";
4
5
  import type { ConversationType } from "./message-types/shared.js";
5
6
  import type { TrustContext } from "./trust-context.js";
@@ -74,11 +75,11 @@ export function classifyDiskPressureTurnPolicy(
74
75
  }
75
76
 
76
77
  const trustClass = metadata.trustContext?.trustClass;
77
- if (trustClass === "guardian") {
78
+ if (resolveCapabilities(trustClass).canActUnderDiskPressureCleanup) {
78
79
  return { action: "allow-cleanup-mode", reason: "guardian" };
79
80
  }
80
81
 
81
- if (trustClass === "trusted_contact") {
82
+ if (trustClass === "trusted_contact" || trustClass === "unverified_contact") {
82
83
  return { action: "block", reason: "trusted-contact" };
83
84
  }
84
85
 
@@ -156,6 +157,7 @@ function isExplicitLocalOwnerCleanupTurn(
156
157
  }
157
158
  return (
158
159
  metadata.trustContext == null ||
159
- metadata.trustContext.trustClass === "guardian"
160
+ resolveCapabilities(metadata.trustContext.trustClass)
161
+ .canActUnderDiskPressureCleanup
160
162
  );
161
163
  }
@@ -121,7 +121,6 @@ describe("completeA2AInvite", () => {
121
121
  const invite = findById(created.inviteId!);
122
122
  const contact = getContact(invite!.contactId);
123
123
  expect(contact!.channels[0]!.address).toBe("upper-case-id-123");
124
- expect(contact!.channels[0]!.externalUserId).toBe("UPPER-Case-ID-123");
125
124
  });
126
125
 
127
126
  test("assistantContactMetadata has correct assistantId and gatewayUrl", () => {
@@ -120,7 +120,6 @@ describe("redeemA2AInvite", () => {
120
120
 
121
121
  const contact = getContact(result.contactId!);
122
122
  expect(contact!.channels[0]!.address).toBe("upper-case-sender-id");
123
- expect(contact!.channels[0]!.externalUserId).toBe("UPPER-Case-SENDER-ID");
124
123
  });
125
124
 
126
125
  test("does not make outbound fetch calls", () => {
@@ -221,7 +221,6 @@ export function completeA2AInvite(params: {
221
221
  {
222
222
  type: "a2a",
223
223
  address: params.acceptor.assistantId.toLowerCase(),
224
- externalUserId: params.acceptor.assistantId,
225
224
  status: "active",
226
225
  policy: "allow",
227
226
  },
@@ -289,7 +288,6 @@ export function redeemA2AInvite(params: {
289
288
  {
290
289
  type: "a2a",
291
290
  address: params.sender.assistantId.toLowerCase(),
292
- externalUserId: params.sender.assistantId,
293
291
  status: "active",
294
292
  policy: "allow",
295
293
  },
@@ -332,11 +332,9 @@ export async function verifyTrustedContact(
332
332
  channel: verificationChannel,
333
333
  expectedChatId: channel.externalChatId,
334
334
  expectedExternalUserId:
335
- channel.externalUserId ??
336
- (channel.address !== channel.externalChatId
335
+ channel.address !== channel.externalChatId
337
336
  ? channel.address
338
- : undefined) ??
339
- undefined,
337
+ : undefined,
340
338
  identityBindingStatus: "bound",
341
339
  destinationAddress: effectiveDestination,
342
340
  verificationPurpose: "trusted_contact",
@@ -409,17 +407,14 @@ export async function verifyTrustedContact(
409
407
 
410
408
  // --- Slack verification ---
411
409
  if (verificationChannel === "slack") {
412
- const slackUserId =
413
- channel.externalUserId ?? channel.address ?? destination;
410
+ const slackUserId = channel.address;
414
411
 
415
412
  const sessionResult = createOutboundSession({
416
413
  channel: verificationChannel,
417
414
  expectedExternalUserId:
418
- channel.externalUserId ??
419
- (channel.address !== channel.externalChatId
415
+ channel.address !== channel.externalChatId
420
416
  ? channel.address
421
- : undefined) ??
422
- undefined,
417
+ : undefined,
423
418
  expectedChatId: channel.externalChatId ?? undefined,
424
419
  identityBindingStatus: "bound",
425
420
  destinationAddress: slackUserId,
@@ -28,6 +28,9 @@ import { log as _log } from "./shared.js";
28
28
 
29
29
  // -- Result type --
30
30
 
31
+ export const SlackThreadMode = z.enum(["mention_only", "mention_then_thread"]);
32
+ export type SlackThreadMode = z.infer<typeof SlackThreadMode>;
33
+
31
34
  export const SlackChannelConfigResultSchema = z.object({
32
35
  success: z.boolean(),
33
36
  hasBotToken: z.boolean(),
@@ -39,6 +42,7 @@ export const SlackChannelConfigResultSchema = z.object({
39
42
  teamUrl: z.string().optional(),
40
43
  botUserId: z.string().optional(),
41
44
  botUsername: z.string().optional(),
45
+ threadMode: SlackThreadMode,
42
46
  error: z.string().optional(),
43
47
  warning: z.string().optional(),
44
48
  });
@@ -129,6 +133,8 @@ export async function getSlackChannelConfig(): Promise<SlackChannelConfigResult>
129
133
  ensureUserTokenInjectionTemplates();
130
134
  }
131
135
 
136
+ const { threadMode } = getConfig().slack;
137
+
132
138
  return {
133
139
  success: true,
134
140
  hasBotToken,
@@ -140,6 +146,7 @@ export async function getSlackChannelConfig(): Promise<SlackChannelConfigResult>
140
146
  ...(teamUrl ? { teamUrl } : {}),
141
147
  ...(botUserId ? { botUserId } : {}),
142
148
  ...(botUsername ? { botUsername } : {}),
149
+ threadMode,
143
150
  };
144
151
  }
145
152
 
@@ -166,6 +173,7 @@ async function currentErrorSnapshot(
166
173
  !!(errConn && errConn.status === "active") &&
167
174
  errHasBotToken &&
168
175
  errHasAppToken,
176
+ threadMode: getConfig().slack.threadMode,
169
177
  error,
170
178
  };
171
179
  }
@@ -429,6 +437,7 @@ export async function setSlackChannelConfig(
429
437
  hasUserToken,
430
438
  connected: hasBotToken && hasAppToken,
431
439
  ...metadata,
440
+ threadMode: getConfig().slack.threadMode,
432
441
  ...(warning ? { warning } : {}),
433
442
  };
434
443
  }
@@ -466,6 +475,7 @@ export async function clearSlackUserToken(): Promise<SlackChannelConfigResult> {
466
475
  hasUserToken,
467
476
  connected:
468
477
  !!(conn && conn.status === "active") && hasBotToken && hasAppToken,
478
+ threadMode: getConfig().slack.threadMode,
469
479
  error:
470
480
  result === "not-found"
471
481
  ? "Slack user token not found in secure storage"
@@ -497,6 +507,7 @@ export async function clearSlackUserToken(): Promise<SlackChannelConfigResult> {
497
507
  ...(teamUrl ? { teamUrl } : {}),
498
508
  ...(botUserId ? { botUserId } : {}),
499
509
  ...(botUsername ? { botUsername } : {}),
510
+ threadMode: getConfig().slack.threadMode,
500
511
  };
501
512
  }
502
513
 
@@ -530,6 +541,7 @@ export async function clearSlackChannelConfig(): Promise<SlackChannelConfigResul
530
541
  hasUserToken,
531
542
  connected:
532
543
  !!(conn && conn.status === "active") && hasBotToken && hasAppToken,
544
+ threadMode: getConfig().slack.threadMode,
533
545
  error: "Failed to delete Slack channel credentials from secure storage",
534
546
  };
535
547
  }
@@ -556,5 +568,13 @@ export async function clearSlackChannelConfig(): Promise<SlackChannelConfigResul
556
568
  hasAppToken: false,
557
569
  hasUserToken: false,
558
570
  connected: false,
571
+ threadMode: "mention_only",
559
572
  };
560
573
  }
574
+
575
+ export function patchSlackChannelConfig(threadMode: SlackThreadMode): void {
576
+ const raw = loadRawConfig();
577
+ setNestedValue(raw, "slack.threadMode", threadMode);
578
+ saveRawConfig(raw);
579
+ invalidateConfigCache();
580
+ }
@@ -3,14 +3,21 @@ import { v4 as uuid } from "uuid";
3
3
  import { clearAll, getConversation } from "../../memory/conversation-crud.js";
4
4
  import { resolveConversationId } from "../../memory/conversation-key-store.js";
5
5
  import { broadcastMessage } from "../../runtime/assistant-event-hub.js";
6
+ import { resolveCapabilities } from "../../runtime/capabilities.js";
6
7
  import { getSubagentManager } from "../../subagent/index.js";
7
8
  import { createAbortReason } from "../../util/abort-reasons.js";
9
+ import { UserError } from "../../util/errors.js";
8
10
  import { truncate } from "../../util/truncate.js";
9
11
  import { regenerate } from "../conversation-history.js";
12
+ import {
13
+ buildSlashContext,
14
+ formatCleanResult,
15
+ } from "../conversation-process.js";
10
16
  import {
11
17
  conversationEntries,
12
18
  findConversation,
13
19
  } from "../conversation-registry.js";
20
+ import { resolveSlash } from "../conversation-slash.js";
14
21
  import {
15
22
  clearAllActiveConversations,
16
23
  getOrCreateConversation,
@@ -18,6 +25,7 @@ import {
18
25
  } from "../conversation-store.js";
19
26
  import type { ConfirmationResponse } from "../message-protocol.js";
20
27
  import { normalizeConversationType } from "../message-protocol.js";
28
+ import { INTERNAL_GUARDIAN_TRUST_CONTEXT } from "../trust-context.js";
21
29
  import { log } from "./shared.js";
22
30
 
23
31
  export function handleConfirmationResponse(msg: ConfirmationResponse): void {
@@ -98,6 +106,12 @@ export function cancelGeneration(conversationId: string): boolean {
98
106
  // being cancelled, so enqueuing synthetic messages would trigger
99
107
  // unwanted model activity after the user pressed stop.
100
108
  getSubagentManager().abortAllForParent(conversationId);
109
+ // The processing flag is cleared by the in-flight turn's `finally`, not here.
110
+ // Abort propagates into the provider call and tool execution (and is backed
111
+ // by the agent loop's abort watchdog), so the turn reaches its `finally`
112
+ // within a bounded time and tears down its own state there — which publishes
113
+ // the metadata sync invalidation that drives clients to the authoritative
114
+ // idle state.
101
115
  return true;
102
116
  }
103
117
 
@@ -118,6 +132,99 @@ export async function undoLastMessage(
118
132
  const removedCount = conversation.undo();
119
133
  return { removedCount };
120
134
  }
135
+
136
+ export interface MetaSlashCommandResult {
137
+ kind: "clean" | "info";
138
+ /** User-facing text to render (clean stats card or info listing). */
139
+ text: string;
140
+ /** Present for `/clean`: the post-strip context-window usage. */
141
+ contextUsage?: {
142
+ tokens: number;
143
+ maxTokens: number | null;
144
+ fillRatio: number | null;
145
+ };
146
+ }
147
+
148
+ /**
149
+ * Resolve a local meta slash command (`/clean`, `/status`, `/commands`,
150
+ * `/models`) for a conversation without running a turn: no user/assistant
151
+ * messages are persisted and no streaming/turn events are emitted. `/clean`
152
+ * additionally strips runtime injections via `forceClean`.
153
+ *
154
+ * Returns null if the conversation cannot be resolved. Throws `UserError` for
155
+ * commands that are not local meta commands (`/compact`, passthrough) — those
156
+ * must be sent as a normal message so they run a real turn.
157
+ */
158
+ export async function resolveMetaSlashCommand(
159
+ conversationId: string,
160
+ command: string,
161
+ ): Promise<MetaSlashCommandResult | null> {
162
+ const resolvedId = resolveConversationId(conversationId);
163
+ if (!resolvedId) {
164
+ return null;
165
+ }
166
+ const conversation = await getOrCreateConversation(resolvedId);
167
+ touchConversation(resolvedId);
168
+
169
+ // Meta commands reload (and, for `/clean`, strip) the in-memory history
170
+ // array. Running that against an in-flight turn would corrupt the messages
171
+ // the active agent loop is iterating over, and mutating trustContext would
172
+ // elevate that turn's actor trust to guardian for subsequent tool calls.
173
+ if (conversation.isProcessing()) {
174
+ throw new UserError(
175
+ `\`${command.trim()}\` cannot run while the assistant is responding.`,
176
+ );
177
+ }
178
+
179
+ // Owner self-maintenance operates on the full (guardian) history. Without a
180
+ // trusted context, `loadFromDb` filters to non-guardian provenance — so a
181
+ // guardian-authored conversation would report 0 preserved / 0 messages.
182
+ // Temporarily apply the guardian context for hydration and restore it
183
+ // afterward so the elevated class never leaks into a later turn's snapshot.
184
+ const priorTrustContext = conversation.trustContext;
185
+ if (!resolveCapabilities(priorTrustContext?.trustClass).canAccessMemory) {
186
+ conversation.setTrustContext(INTERNAL_GUARDIAN_TRUST_CONTEXT);
187
+ }
188
+ try {
189
+ await conversation.ensureActorScopedHistory();
190
+
191
+ const slashResult = await resolveSlash(
192
+ command,
193
+ buildSlashContext(command, conversation),
194
+ );
195
+
196
+ if (slashResult.kind === "clean") {
197
+ const result = await conversation.forceClean();
198
+ return {
199
+ kind: "clean",
200
+ text: formatCleanResult(result),
201
+ contextUsage: {
202
+ tokens: result.estimatedInputTokens,
203
+ maxTokens: result.maxInputTokens,
204
+ fillRatio:
205
+ result.maxInputTokens > 0
206
+ ? result.estimatedInputTokens / result.maxInputTokens
207
+ : null,
208
+ },
209
+ };
210
+ }
211
+
212
+ if (slashResult.kind === "unknown") {
213
+ return { kind: "info", text: slashResult.message };
214
+ }
215
+
216
+ // `compact` / `passthrough` are real turns, not local meta commands.
217
+ throw new UserError(`\`${command.trim()}\` is not a local meta command.`);
218
+ } finally {
219
+ // Only undo the temporary guardian context this handler installed. If a
220
+ // new turn started at an `await` boundary and legitimately updated
221
+ // trustContext, the reference will differ and we leave it alone.
222
+ if (conversation.trustContext === INTERNAL_GUARDIAN_TRUST_CONTEXT) {
223
+ conversation.setTrustContext(priorTrustContext ?? null);
224
+ }
225
+ }
226
+ }
227
+
121
228
  /**
122
229
  * Regenerate the last assistant response for a conversation. The caller provides
123
230
  * a `sendEvent` callback for delivering streaming events via HTTP/SSE.
@@ -9,6 +9,7 @@ import * as pendingInteractions from "../runtime/pending-interactions.js";
9
9
  import type { ToolExecutionResult } from "../tools/types.js";
10
10
  import { AssistantError, ErrorCode } from "../util/errors.js";
11
11
  import { getLogger } from "../util/logger.js";
12
+ import { sleep } from "../util/retry.js";
12
13
  import type { HostBrowserRequest } from "./message-types/host-browser.js";
13
14
 
14
15
  /** Distributive omit that preserves union variant fields. */
@@ -24,6 +25,16 @@ export type HostBrowserInput = DistributiveOmit<
24
25
 
25
26
  const log = getLogger("host-browser-proxy");
26
27
 
28
+ /**
29
+ * Grace window for a flapping extension SSE connection. The Chrome
30
+ * extension's stream can briefly drop and reconnect (MV3 service-worker
31
+ * churn); without a short wait, a dispatch landing in a "down" blip
32
+ * hard-fails even though the extension is effectively connected. Well
33
+ * under the request-layer timeout (default 30s).
34
+ */
35
+ const EXTENSION_RECONNECT_GRACE_MS = 3_000;
36
+ const EXTENSION_RECONNECT_POLL_MS = 250;
37
+
27
38
  /**
28
39
  * Extension-only pseudo-CDP methods (Vellum.listTabs, Vellum.selectTab,
29
40
  * Vellum.closeTab, Vellum.createTab, Vellum.findTab, Vellum.attach,
@@ -196,6 +207,36 @@ export class HostBrowserProxy {
196
207
  );
197
208
  }
198
209
 
210
+ /**
211
+ * Poll until the extension client that dispatch will route to is
212
+ * connected, or `timeoutMs` elapses. Absorbs brief SSE reconnect blips
213
+ * so extension-pinned dispatch doesn't hard-fail on a momentary gap.
214
+ *
215
+ * When `targetClientId` is supplied, waits for that *exact* client —
216
+ * `request()` resolves the target by id, so waiting for any sibling
217
+ * extension (multi-install setups) would return early and still fail.
218
+ * Otherwise waits for any extension owned by the actor, matching the
219
+ * auto-resolution preference. Returns the final availability; callers
220
+ * proceed to dispatch regardless (which still surfaces the typed "no
221
+ * Chrome Extension connected" error if absent).
222
+ */
223
+ async waitForExtensionClient(
224
+ sourceActorPrincipalId?: string,
225
+ targetClientId?: string,
226
+ timeoutMs = EXTENSION_RECONNECT_GRACE_MS,
227
+ ): Promise<boolean> {
228
+ const connected = () =>
229
+ targetClientId != null
230
+ ? assistantEventHub.getClientById(targetClientId) !== undefined
231
+ : this.hasExtensionClient(sourceActorPrincipalId);
232
+ const deadline = Date.now() + timeoutMs;
233
+ for (;;) {
234
+ if (connected()) return true;
235
+ if (Date.now() >= deadline) return false;
236
+ await sleep(EXTENSION_RECONNECT_POLL_MS);
237
+ }
238
+ }
239
+
199
240
  /**
200
241
  * Send a host_browser request to the connected extension/macOS bridge.
201
242
  *