@vellumai/assistant 0.9.0 → 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 (572) hide show
  1. package/ARCHITECTURE.md +18 -34
  2. package/bun.lock +7 -8
  3. package/docs/activation-funnel-telemetry.md +28 -22
  4. package/docs/architecture/security.md +29 -28
  5. package/docs/stt-provider-onboarding.md +3 -5
  6. package/docs/workflows-testing.md +13 -44
  7. package/docs/workflows.md +3 -5
  8. package/node_modules/@vellumai/ces-client/src/__tests__/ces-client.test.ts +47 -0
  9. package/node_modules/@vellumai/ces-client/src/rpc-client.ts +28 -5
  10. package/node_modules/@vellumai/environments/src/seeds.ts +2 -5
  11. package/node_modules/@vellumai/gateway-client/src/admission-policy-contract.ts +97 -0
  12. package/node_modules/@vellumai/gateway-client/src/inbound-contract.ts +10 -0
  13. package/node_modules/@vellumai/gateway-client/src/index.ts +32 -6
  14. package/node_modules/@vellumai/gateway-client/src/outbound-contract.ts +119 -0
  15. package/node_modules/@vellumai/gateway-client/src/types.ts +15 -84
  16. package/openapi.yaml +976 -63
  17. package/package.json +2 -1
  18. package/scripts/sync-llm-catalog.ts +6 -15
  19. package/scripts/sync-web-search-catalog.ts +3 -11
  20. package/src/__tests__/access-request-card-view.test.ts +98 -0
  21. package/src/__tests__/access-request-seed-content-blocks.test.ts +2 -4
  22. package/src/__tests__/actor-trust-resolver-address-fallback.test.ts +72 -32
  23. package/src/__tests__/agent-loop-compaction-strip.test.ts +241 -0
  24. package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +16 -13
  25. package/src/__tests__/agent-loop-output-hooks.test.ts +69 -0
  26. package/src/__tests__/agent-loop-override-profile.test.ts +25 -0
  27. package/src/__tests__/always-loaded-tools-guard.test.ts +2 -3
  28. package/src/__tests__/app-compiler.test.ts +15 -1
  29. package/src/__tests__/app-dir-path-guard.test.ts +0 -1
  30. package/src/__tests__/assistant-feature-flag-guard.test.ts +1 -4
  31. package/src/__tests__/assistant-feature-flag-guardrails.test.ts +0 -2
  32. package/src/__tests__/auth-fallback-events-store.test.ts +6 -14
  33. package/src/__tests__/avatar-identity-sync.test.ts +2 -27
  34. package/src/__tests__/btw-routes.test.ts +6 -8
  35. package/src/__tests__/call-pointer-messages.test.ts +28 -0
  36. package/src/__tests__/cancel-clears-processing.test.ts +89 -0
  37. package/src/__tests__/channel-approval-routes.test.ts +0 -4
  38. package/src/__tests__/channel-inbound-disk-pressure.test.ts +5 -15
  39. package/src/__tests__/checker.test.ts +0 -3
  40. package/src/__tests__/cli-memory-v2-reembed-skills.test.ts +3 -4
  41. package/src/__tests__/compactor-image-manifest-trust.test.ts +21 -1
  42. package/src/__tests__/compactor-summary-call-truncation.test.ts +223 -0
  43. package/src/__tests__/config-loader-backfill.test.ts +268 -27
  44. package/src/__tests__/config-schema.test.ts +35 -0
  45. package/src/__tests__/config-watcher.test.ts +0 -18
  46. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +2 -2
  47. package/src/__tests__/contact-store-user-file.test.ts +0 -6
  48. package/src/__tests__/contacts-tools.test.ts +29 -0
  49. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +22 -0
  50. package/src/__tests__/conversation-agent-loop-overflow.test.ts +1 -0
  51. package/src/__tests__/conversation-agent-loop.test.ts +58 -0
  52. package/src/__tests__/conversation-attention-telegram.test.ts +0 -1
  53. package/src/__tests__/conversation-lifecycle.test.ts +7 -9
  54. package/src/__tests__/conversation-load-history-repair.test.ts +101 -0
  55. package/src/__tests__/conversation-routes-guardian-reply.test.ts +15 -12
  56. package/src/__tests__/conversation-surfaces-activation-emit.test.ts +6 -3
  57. package/src/__tests__/conversation-title-service.test.ts +62 -0
  58. package/src/__tests__/credential-broker.test.ts +449 -1
  59. package/src/__tests__/credential-execution-shell-lockdown.test.ts +18 -11
  60. package/src/__tests__/credential-execution-tools.test.ts +0 -1
  61. package/src/__tests__/credential-prompt-route.test.ts +4 -4
  62. package/src/__tests__/credential-routes.test.ts +360 -0
  63. package/src/__tests__/credential-security-invariants.test.ts +4 -13
  64. package/src/__tests__/disk-pressure-policy.test.ts +12 -0
  65. package/src/__tests__/disk-usage.test.ts +65 -0
  66. package/src/__tests__/dynamic-page-surface.test.ts +152 -1
  67. package/src/__tests__/fixtures/credential-security-fixtures.ts +2 -33
  68. package/src/__tests__/gateway-flag-listener.test.ts +110 -1
  69. package/src/__tests__/gateway-only-guard.test.ts +3 -7
  70. package/src/__tests__/guardian-binding-drift-heal.test.ts +1 -1
  71. package/src/__tests__/guardian-card-withdrawal.test.ts +403 -0
  72. package/src/__tests__/guardian-decision-primitive-canonical.test.ts +5 -3
  73. package/src/__tests__/guardian-grant-minting.test.ts +3 -35
  74. package/src/__tests__/guardian-routing-invariants.test.ts +64 -26
  75. package/src/__tests__/guardian-routing-state.test.ts +0 -1
  76. package/src/__tests__/headless-browser-mode.test.ts +10 -0
  77. package/src/__tests__/headless-browser-navigate.test.ts +8 -3
  78. package/src/__tests__/helpers/create-guardian-binding.ts +0 -1
  79. package/src/__tests__/host-browser-proxy.test.ts +87 -0
  80. package/src/__tests__/identity-routes.test.ts +0 -189
  81. package/src/__tests__/inbound-invite-redemption.test.ts +4 -4
  82. package/src/__tests__/injector-v3-suppression.test.ts +27 -20
  83. package/src/__tests__/internal-telemetry-routes.test.ts +6 -14
  84. package/src/__tests__/invite-redemption-service.test.ts +4 -7
  85. package/src/__tests__/llm-callsite-catalog.test.ts +5 -6
  86. package/src/__tests__/llm-catalog-parity.test.ts +30 -23
  87. package/src/__tests__/llm-resolver.test.ts +70 -24
  88. package/src/__tests__/llm-schema.test.ts +1 -0
  89. package/src/__tests__/managed-profile-guard.test.ts +163 -4
  90. package/src/__tests__/mcp-health-check.test.ts +6 -7
  91. package/src/__tests__/media-stream-server-integration.test.ts +317 -13
  92. package/src/__tests__/oauth-provider-seed-logos.test.ts +4 -6
  93. package/src/__tests__/onboarding-persona-write.test.ts +1 -1
  94. package/src/__tests__/path-policy.test.ts +34 -0
  95. package/src/__tests__/persona-resolver.test.ts +49 -14
  96. package/src/__tests__/plugin-api-model-profiles.test.ts +178 -0
  97. package/src/__tests__/plugin-api-provider.test.ts +24 -0
  98. package/src/__tests__/plugin-tool-contribution.test.ts +6 -3
  99. package/src/__tests__/post-compaction-reinjection-idempotency.test.ts +214 -0
  100. package/src/__tests__/provider-send-message-override-profile.test.ts +76 -0
  101. package/src/__tests__/reaction-persistence.test.ts +150 -29
  102. package/src/__tests__/registry.test.ts +2 -7
  103. package/src/__tests__/relay-server.test.ts +285 -0
  104. package/src/__tests__/runtime-attachment-metadata.test.ts +0 -1
  105. package/src/__tests__/schedule-routes-workflow-validation.test.ts +1 -10
  106. package/src/__tests__/schedule-routes.test.ts +0 -30
  107. package/src/__tests__/schedule-tools.test.ts +2 -18
  108. package/src/__tests__/scheduler-reuse-conversation.test.ts +8 -5
  109. package/src/__tests__/skill-execute-input.test.ts +51 -1
  110. package/src/__tests__/skill-runtime-path.test.ts +2 -3
  111. package/src/__tests__/skills.test.ts +51 -0
  112. package/src/__tests__/slack-notification-approval-card.test.ts +176 -0
  113. package/src/__tests__/slack-reaction-canonical-approval.test.ts +285 -0
  114. package/src/__tests__/subagent-tools.test.ts +266 -0
  115. package/src/__tests__/surface-completion-nudge-hook.test.ts +367 -0
  116. package/src/__tests__/task-progress-nudge-hook.test.ts +1 -1
  117. package/src/__tests__/title-generate-hook.test.ts +100 -3
  118. package/src/__tests__/token-estimator-accuracy.benchmark.test.ts +1 -29
  119. package/src/__tests__/token-manager.test.ts +519 -0
  120. package/src/__tests__/tool-approval-seed-content-blocks.test.ts +1 -1
  121. package/src/__tests__/tool-audit-listener.test.ts +7 -7
  122. package/src/__tests__/tool-executor-lifecycle-events.test.ts +6 -3
  123. package/src/__tests__/tool-executor.test.ts +0 -79
  124. package/src/__tests__/trusted-contact-approval-notifier.test.ts +4 -2
  125. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +220 -3
  126. package/src/__tests__/trusted-contact-multichannel.test.ts +3 -3
  127. package/src/__tests__/trusted-contact-verification.test.ts +8 -10
  128. package/src/__tests__/twilio-routes.test.ts +81 -1
  129. package/src/__tests__/voice-invite-redemption.test.ts +2 -3
  130. package/src/__tests__/weak-open-model.test.ts +30 -0
  131. package/src/__tests__/web-search-catalog-parity.test.ts +6 -25
  132. package/src/__tests__/workspace-greetings.test.ts +152 -0
  133. package/src/__tests__/workspace-migration-105-enable-memory-v3-live-for-new-workspaces.test.ts +149 -0
  134. package/src/__tests__/workspace-migration-108-drop-balanced-economy-profile.test.ts +285 -0
  135. package/src/__tests__/workspace-migration-add-send-diagnostics.test.ts +1 -1
  136. package/src/__tests__/workspace-migration-drop-collect-usage-data.test.ts +118 -0
  137. package/src/__tests__/workspace-migration-drop-send-diagnostics.test.ts +118 -0
  138. package/src/a2a/__tests__/e2e-a2a-channel.test.ts +0 -4
  139. package/src/agent/loop.ts +49 -29
  140. package/src/api/README.md +6 -6
  141. package/src/api/events/tool-result.ts +6 -0
  142. package/src/api/events/workflow-completed.ts +53 -0
  143. package/src/api/events/workflow-leaf-finished.ts +38 -0
  144. package/src/api/events/workflow-leaf-started.ts +35 -0
  145. package/src/api/events/workflow-progress.ts +32 -0
  146. package/src/api/events/workflow-started.ts +31 -0
  147. package/src/api/index.ts +40 -0
  148. package/src/api/responses/conversation-message.ts +28 -4
  149. package/src/api/responses/home.ts +26 -4
  150. package/src/api/responses/workflow-journal.ts +53 -0
  151. package/src/approvals/guardian-card-withdrawal.ts +145 -0
  152. package/src/approvals/guardian-decision-primitive.ts +26 -3
  153. package/src/approvals/guardian-request-resolvers.ts +183 -80
  154. package/src/calls/__tests__/channel-admission-reader.test.ts +132 -0
  155. package/src/calls/__tests__/relay-setup-router.test.ts +350 -0
  156. package/src/calls/call-pointer-messages.ts +10 -4
  157. package/src/calls/channel-admission-reader.ts +104 -0
  158. package/src/calls/guardian-dispatch.ts +17 -45
  159. package/src/calls/media-stream-server.ts +84 -2
  160. package/src/calls/relay-access-wait.ts +1 -1
  161. package/src/calls/relay-server.ts +66 -0
  162. package/src/calls/relay-setup-router.ts +82 -1
  163. package/src/calls/twilio-routes.ts +17 -8
  164. package/src/calls/voice-session-bridge.ts +2 -2
  165. package/src/cli/commands/clients.ts +3 -0
  166. package/src/cli/commands/{__tests__ → memory/__tests__}/memory-v2-compare-render.test.ts +1 -1
  167. package/src/cli/commands/{__tests__ → memory/__tests__}/memory-v2.test.ts +8 -7
  168. package/src/cli/commands/{__tests__ → memory/__tests__}/memory-v3.test.ts +5 -4
  169. package/src/cli/commands/memory/index.ts +30 -0
  170. package/src/cli/commands/{memory-v2-compare-render.ts → memory/memory-v2-compare-render.ts} +1 -1
  171. package/src/cli/commands/{memory-v2.ts → memory/memory-v2.ts} +6 -15
  172. package/src/cli/commands/{memory-v3.ts → memory/memory-v3.ts} +97 -11
  173. package/src/cli/commands/oauth/status.test.ts +36 -0
  174. package/src/cli/commands/oauth/status.ts +23 -3
  175. package/src/cli/commands/plugins.ts +197 -4
  176. package/src/cli/lib/__tests__/diff-plugin.test.ts +443 -0
  177. package/src/cli/lib/__tests__/inspect-plugin.test.ts +54 -0
  178. package/src/cli/lib/__tests__/merge-plugin-tree.test.ts +443 -0
  179. package/src/cli/lib/__tests__/plugin-surfaces.test.ts +111 -0
  180. package/src/cli/lib/__tests__/upgrade-plugin.test.ts +295 -2
  181. package/src/cli/lib/diff-plugin.ts +346 -0
  182. package/src/cli/lib/inspect-plugin.ts +12 -1
  183. package/src/cli/lib/install-from-github.ts +105 -17
  184. package/src/cli/lib/merge-plugin-tree.ts +328 -0
  185. package/src/cli/lib/plugin-fingerprint.ts +14 -0
  186. package/src/cli/lib/plugin-surfaces.ts +104 -0
  187. package/src/cli/lib/upgrade-plugin.ts +298 -10
  188. package/src/cli/program.ts +2 -6
  189. package/src/config/__tests__/sync-gated-profiles.test.ts +368 -0
  190. package/src/config/assistant-feature-flags.ts +22 -7
  191. package/src/config/bundled-skills/contacts/tools/contact-search.ts +0 -1
  192. package/src/config/bundled-skills/messaging/SKILL.md +6 -4
  193. package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +9 -8
  194. package/src/config/bundled-skills/subagent/SKILL.md +4 -0
  195. package/src/config/bundled-skills/subagent/TOOLS.json +4 -0
  196. package/src/config/bundled-skills/workflows/SKILL.md +14 -8
  197. package/src/config/bundled-tool-registry.ts +2 -7
  198. package/src/config/call-site-defaults.ts +15 -2
  199. package/src/config/feature-flag-registry.json +46 -31
  200. package/src/config/inference-profile-validation.ts +26 -0
  201. package/src/config/llm-resolver.ts +3 -0
  202. package/src/config/loader.ts +4 -0
  203. package/src/config/memory-v3-gate.ts +11 -0
  204. package/src/config/profile-order.ts +28 -0
  205. package/src/config/schema.ts +8 -6
  206. package/src/config/schemas/__tests__/memory-v3.test.ts +1 -0
  207. package/src/config/schemas/call-site-catalog.ts +7 -0
  208. package/src/config/schemas/channels.ts +11 -0
  209. package/src/config/schemas/elevenlabs.ts +0 -1
  210. package/src/config/schemas/llm.ts +31 -0
  211. package/src/config/schemas/memory-lifecycle.ts +3 -7
  212. package/src/config/schemas/memory-v3.ts +6 -0
  213. package/src/config/schemas/platform.ts +0 -8
  214. package/src/config/schemas/services.ts +18 -0
  215. package/src/config/seed-inference-profiles.ts +109 -44
  216. package/src/config/skills.ts +21 -0
  217. package/src/config/sync-gated-profiles.ts +220 -0
  218. package/src/contacts/contact-store.ts +89 -106
  219. package/src/contacts/contacts-write.ts +5 -22
  220. package/src/contacts/types.ts +0 -1
  221. package/src/context/compactor.ts +88 -54
  222. package/src/context/strip-injections.ts +58 -10
  223. package/src/context/token-estimator.ts +1 -1
  224. package/src/credential-execution/process-manager.ts +55 -14
  225. package/src/credential-execution/prompted-credential.ts +2 -3
  226. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +3 -2
  227. package/src/daemon/config-watcher.ts +0 -4
  228. package/src/daemon/conversation-agent-loop-handlers.ts +2 -0
  229. package/src/daemon/conversation-agent-loop.ts +114 -22
  230. package/src/daemon/conversation-history.ts +1 -1
  231. package/src/daemon/conversation-lifecycle.ts +3 -5
  232. package/src/daemon/conversation-process.ts +13 -5
  233. package/src/daemon/conversation-runtime-assembly.ts +13 -15
  234. package/src/daemon/conversation-slash.ts +2 -23
  235. package/src/daemon/conversation-surfaces.ts +26 -0
  236. package/src/daemon/conversation-tool-setup.ts +27 -14
  237. package/src/daemon/conversation.ts +66 -14
  238. package/src/daemon/disk-pressure-policy.ts +5 -3
  239. package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +0 -1
  240. package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +0 -1
  241. package/src/daemon/handlers/config-a2a.ts +0 -2
  242. package/src/daemon/handlers/config-channels.ts +15 -16
  243. package/src/daemon/handlers/config-slack-channel.ts +22 -3
  244. package/src/daemon/handlers/conversations.ts +107 -0
  245. package/src/daemon/host-browser-proxy.ts +41 -0
  246. package/src/daemon/lifecycle.ts +55 -27
  247. package/src/daemon/message-provenance.ts +2 -0
  248. package/src/daemon/message-types/contacts.ts +0 -1
  249. package/src/daemon/message-types/conversations.ts +3 -3
  250. package/src/daemon/message-types/sync.ts +0 -1
  251. package/src/daemon/message-types/web-activity.ts +7 -1
  252. package/src/daemon/message-types/workflows.ts +83 -1
  253. package/src/daemon/orphan-reaper.test.ts +0 -19
  254. package/src/daemon/orphan-reaper.ts +2 -24
  255. package/src/daemon/server.ts +0 -10
  256. package/src/daemon/tool-setup-types.ts +4 -0
  257. package/src/daemon/trust-context.ts +1 -1
  258. package/src/events/tool-audit-listener.ts +2 -2
  259. package/src/home/feed-source-enrichment.test.ts +151 -0
  260. package/src/home/feed-source-enrichment.ts +176 -0
  261. package/src/home/relationship-state.ts +2 -4
  262. package/src/instrument.ts +18 -6
  263. package/src/ipc/__tests__/binary-result-ipc.test.ts +81 -0
  264. package/src/ipc/__tests__/clients-list-ipc.test.ts +20 -0
  265. package/src/ipc/assistant-server.ts +37 -4
  266. package/src/ipc/gateway-flag-listener.ts +18 -2
  267. package/src/memory/__tests__/auto-analysis-enqueue.test.ts +5 -16
  268. package/src/memory/__tests__/jobs-store-enqueue-gate.test.ts +7 -11
  269. package/src/memory/__tests__/memory-retrospective-enqueue.test.ts +37 -7
  270. package/src/memory/__tests__/memory-retrospective-job.test.ts +229 -401
  271. package/src/memory/__tests__/onboarding-events-store.test.ts +7 -7
  272. package/src/memory/auth-fallback-events-store.ts +2 -2
  273. package/src/memory/auto-analysis-enqueue.ts +3 -5
  274. package/src/memory/bookmark-crud.ts +1 -2
  275. package/src/memory/canonical-guardian-store.ts +39 -1
  276. package/src/memory/conversation-crud.ts +9 -4
  277. package/src/memory/conversation-key-store.ts +17 -2
  278. package/src/memory/conversation-title-service.ts +64 -7
  279. package/src/memory/db-init.ts +17 -17
  280. package/src/memory/embedding-backend.ts +38 -1
  281. package/src/memory/embedding-billing-breaker.ts +96 -0
  282. package/src/memory/jobs-store.ts +25 -13
  283. package/src/memory/jobs-worker.ts +54 -1
  284. package/src/memory/lifecycle-events-store.ts +2 -2
  285. package/src/memory/memory-retrospective-constants.ts +4 -4
  286. package/src/memory/memory-retrospective-enqueue.ts +31 -6
  287. package/src/memory/memory-retrospective-job.ts +28 -227
  288. package/src/memory/migrations/129-contact-channels-access-fields.ts +18 -9
  289. package/src/memory/migrations/131-drop-legacy-member-guardian-tables.ts +14 -2
  290. package/src/memory/migrations/289-contact-channels-unique-ext-user.ts +10 -0
  291. package/src/memory/migrations/291-contact-channels-renormalize-addresses.ts +72 -0
  292. package/src/memory/migrations/292-schedule-default-no-reuse-conversation.test.ts +67 -0
  293. package/src/memory/migrations/292-schedule-default-no-reuse-conversation.ts +25 -0
  294. package/src/memory/migrations/293-workflow-journal-leaf-tokens.ts +32 -0
  295. package/src/memory/migrations/294-drop-external-user-id.ts +31 -0
  296. package/src/memory/migrations/295-drop-approval-prompt-ts-tracker.ts +20 -0
  297. package/src/memory/migrations/296-rewrite-balanced-economy-profile-pins.test.ts +110 -0
  298. package/src/memory/migrations/296-rewrite-balanced-economy-profile-pins.ts +68 -0
  299. package/src/memory/migrations/__tests__/131-drop-legacy-member-guardian-tables.test.ts +154 -0
  300. package/src/memory/migrations/__tests__/289-contact-channels-unique-ext-user.test.ts +31 -0
  301. package/src/memory/migrations/__tests__/291-contact-channels-renormalize-addresses.test.ts +341 -0
  302. package/src/memory/migrations/__tests__/run-migrations.test.ts +52 -0
  303. package/src/memory/migrations/index.ts +6 -0
  304. package/src/memory/migrations/run-migrations.ts +41 -0
  305. package/src/memory/migrations/validate-migration-state.ts +1 -1
  306. package/src/memory/onboarding-events-store.ts +3 -3
  307. package/src/memory/schema/contacts.ts +0 -5
  308. package/src/memory/skill-loaded-events-store.test.ts +7 -15
  309. package/src/memory/skill-loaded-events-store.ts +2 -2
  310. package/src/memory/tool-executed-events-store.test.ts +7 -7
  311. package/src/memory/turn-trace-store.test.ts +736 -0
  312. package/src/memory/turn-trace-store.ts +364 -0
  313. package/src/memory/v2/__tests__/consolidation-job.test.ts +8 -0
  314. package/src/memory/v2/__tests__/skill-content.test.ts +30 -0
  315. package/src/memory/v2/consolidation-job.ts +2 -2
  316. package/src/memory/v2/skill-content.ts +25 -7
  317. package/src/memory/v2/skill-store.ts +7 -1
  318. package/src/memory/v3-eval/__tests__/eval-packets.test.ts +248 -0
  319. package/src/memory/v3-eval/eval-packets.ts +546 -0
  320. package/src/messaging/providers/slack/adapter.ts +1 -1
  321. package/src/messaging/providers/slack/api.ts +31 -0
  322. package/src/messaging/providers/slack/send.test.ts +114 -2
  323. package/src/messaging/providers/slack/send.ts +30 -7
  324. package/src/messaging/providers/slack/withdraw.test.ts +200 -0
  325. package/src/messaging/providers/slack/withdraw.ts +161 -0
  326. package/src/notifications/AGENTS.md +2 -0
  327. package/src/notifications/access-request-copy.ts +72 -59
  328. package/src/notifications/adapters/shared.ts +29 -0
  329. package/src/notifications/adapters/slack.ts +58 -103
  330. package/src/notifications/adapters/telegram.ts +2 -20
  331. package/src/notifications/approval-card-data.ts +333 -0
  332. package/src/notifications/broadcaster.ts +16 -3
  333. package/src/notifications/canonical-delivery-recorder.ts +139 -0
  334. package/src/notifications/copy-composer.ts +3 -3
  335. package/src/notifications/decision-engine.ts +4 -2
  336. package/src/notifications/destination-resolver.ts +4 -6
  337. package/src/notifications/guardian-question-mode.ts +10 -0
  338. package/src/notifications/home-feed-side-effect.ts +7 -16
  339. package/src/notifications/notification-utils.ts +19 -20
  340. package/src/notifications/signal.ts +79 -43
  341. package/src/notifications/types.ts +98 -121
  342. package/src/oauth/AGENTS.md +5 -24
  343. package/src/permissions/checker.test.ts +51 -0
  344. package/src/permissions/checker.ts +185 -26
  345. package/src/permissions/ipc-risk-types.ts +24 -0
  346. package/src/permissions/question-prompter.test.ts +27 -0
  347. package/src/permissions/question-prompter.ts +4 -0
  348. package/src/platform/client.test.ts +119 -0
  349. package/src/platform/client.ts +66 -0
  350. package/src/platform/consent-cache.test.ts +267 -0
  351. package/src/platform/consent-cache.ts +174 -0
  352. package/src/plugin-api/constants.ts +1 -1
  353. package/src/plugin-api/index.ts +33 -1
  354. package/src/plugin-api/model-profiles.ts +33 -0
  355. package/src/plugin-api/types.ts +50 -2
  356. package/src/plugins/defaults/advisor/__tests__/advisor-gate.test.ts +56 -0
  357. package/src/plugins/defaults/advisor/__tests__/advisor-state-store.test.ts +43 -0
  358. package/src/plugins/defaults/advisor/__tests__/agent-loop-integration.test.ts +137 -0
  359. package/src/plugins/defaults/advisor/__tests__/consult.test.ts +153 -0
  360. package/src/plugins/defaults/advisor/__tests__/hooks.test.ts +138 -0
  361. package/src/plugins/defaults/advisor/__tests__/transcript.test.ts +147 -0
  362. package/src/plugins/defaults/advisor/advisor-gate.ts +29 -0
  363. package/src/plugins/defaults/advisor/advisor-state-store.ts +94 -0
  364. package/src/plugins/defaults/advisor/config.ts +21 -0
  365. package/src/plugins/defaults/advisor/consult.ts +93 -0
  366. package/src/plugins/defaults/advisor/hooks/post-model-call.ts +34 -0
  367. package/src/plugins/defaults/advisor/hooks/pre-model-call.ts +30 -0
  368. package/src/plugins/defaults/advisor/hooks/user-prompt-submit.ts +19 -0
  369. package/src/plugins/defaults/advisor/package.json +14 -0
  370. package/src/plugins/defaults/advisor/steering.ts +67 -0
  371. package/src/plugins/defaults/advisor/tools/advisor.ts +65 -0
  372. package/src/plugins/defaults/advisor/transcript.ts +76 -0
  373. package/src/plugins/defaults/index.ts +60 -0
  374. package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +22 -9
  375. package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit.ts +2 -2
  376. package/src/plugins/defaults/memory-retrieval/tail-reinjection-strip.ts +64 -0
  377. package/src/plugins/defaults/memory-retrieval/unified-turn-context.ts +29 -21
  378. package/src/plugins/defaults/memory-v3-shadow/__tests__/carry-integration.test.ts +1 -0
  379. package/src/plugins/defaults/memory-v3-shadow/__tests__/injection.test.ts +1 -0
  380. package/src/plugins/defaults/memory-v3-shadow/__tests__/maintain-job.test.ts +129 -9
  381. package/src/plugins/defaults/memory-v3-shadow/__tests__/orchestrate.test.ts +31 -4
  382. package/src/plugins/defaults/memory-v3-shadow/__tests__/selection-log-store.test.ts +77 -2
  383. package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +1 -0
  384. package/src/plugins/defaults/memory-v3-shadow/injector.ts +7 -10
  385. package/src/plugins/defaults/memory-v3-shadow/maintain-job.ts +144 -11
  386. package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +32 -20
  387. package/src/plugins/defaults/memory-v3-shadow/selection-log-store.ts +56 -3
  388. package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +23 -2
  389. package/src/plugins/defaults/surface-completion-nudge/hooks/post-model-call.ts +276 -0
  390. package/src/plugins/defaults/surface-completion-nudge/hooks/stop.ts +22 -0
  391. package/src/plugins/defaults/surface-completion-nudge/nudge-state-store.ts +46 -0
  392. package/src/plugins/defaults/surface-completion-nudge/package.json +14 -0
  393. package/src/plugins/defaults/task-progress-nudge/hooks/post-tool-use.ts +3 -13
  394. package/src/plugins/defaults/title-generate/hooks/stop.ts +56 -21
  395. package/src/prompts/persona-resolver.ts +14 -4
  396. package/src/prompts/templates/system-sections.ts +7 -2
  397. package/src/providers/__tests__/provider-env-vars.test.ts +6 -0
  398. package/src/providers/__tests__/provider-secret-catalog.test.ts +1 -0
  399. package/src/providers/__tests__/retry-callsite.test.ts +176 -0
  400. package/src/providers/atlascloud/client.ts +85 -0
  401. package/src/providers/fetch-provider-catalog.ts +85 -0
  402. package/src/providers/inference/adapter-factory.ts +3 -0
  403. package/src/providers/model-catalog.ts +58 -0
  404. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +33 -0
  405. package/src/providers/openai/chat-completions-provider.ts +7 -0
  406. package/src/providers/openai/responses-provider.ts +10 -0
  407. package/src/providers/provider-send-message.ts +11 -3
  408. package/src/providers/retry.ts +53 -12
  409. package/src/providers/search-provider-catalog.ts +10 -0
  410. package/src/providers/weak-open-model.ts +22 -0
  411. package/src/runtime/AGENTS.md +0 -1
  412. package/src/runtime/__tests__/agent-wake.test.ts +181 -0
  413. package/src/runtime/__tests__/client-health.test.ts +44 -0
  414. package/src/runtime/access-request-helper.ts +21 -53
  415. package/src/runtime/actor-trust-resolver.ts +59 -63
  416. package/src/runtime/agent-wake.ts +52 -0
  417. package/src/runtime/assistant-event-hub.ts +18 -4
  418. package/src/runtime/auth/__tests__/route-policy.test.ts +12 -0
  419. package/src/runtime/auth/require-bound-guardian.ts +1 -4
  420. package/src/runtime/btw-sidechain.ts +3 -6
  421. package/src/runtime/capabilities.test.ts +120 -0
  422. package/src/runtime/capabilities.ts +197 -0
  423. package/src/runtime/channel-approval-types.ts +22 -45
  424. package/src/runtime/channel-invite-transports/telegram.ts +4 -4
  425. package/src/runtime/channel-retry-sweep.ts +1 -0
  426. package/src/runtime/channel-verification-service.ts +3 -3
  427. package/src/runtime/client-health.ts +26 -0
  428. package/src/runtime/confirmation-request-guardian-bridge.ts +38 -29
  429. package/src/runtime/effective-capabilities.test.ts +128 -0
  430. package/src/runtime/effective-capabilities.ts +84 -0
  431. package/src/runtime/guardian-reply-router.ts +106 -21
  432. package/src/runtime/invite-redemption-service.ts +9 -25
  433. package/src/runtime/migrations/__tests__/vbundle-builder-fd-leak.test.ts +123 -0
  434. package/src/runtime/migrations/vbundle-builder.ts +49 -20
  435. package/src/runtime/pending-interactions.ts +15 -0
  436. package/src/runtime/routes/__tests__/client-routes.test.ts +13 -0
  437. package/src/runtime/routes/__tests__/conversation-management-routes.test.ts +67 -0
  438. package/src/runtime/routes/__tests__/plugins-routes.test.ts +240 -1
  439. package/src/runtime/routes/app-routes.ts +1 -1
  440. package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +2 -2
  441. package/src/runtime/routes/assets/vellum-design-system.css +1959 -0
  442. package/src/runtime/routes/browser-tabs-routes.ts +9 -0
  443. package/src/runtime/routes/btw-routes.ts +1 -27
  444. package/src/runtime/routes/canonical-guardian-expiry-sweep.ts +17 -8
  445. package/src/runtime/routes/client-routes.ts +10 -0
  446. package/src/runtime/routes/contact-routes.ts +31 -8
  447. package/src/runtime/routes/conversation-compaction-routes.ts +1 -1
  448. package/src/runtime/routes/conversation-management-routes.ts +80 -1
  449. package/src/runtime/routes/conversation-query-routes.ts +68 -22
  450. package/src/runtime/routes/conversation-routes.ts +39 -14
  451. package/src/runtime/routes/credential-routes.ts +40 -16
  452. package/src/runtime/routes/empty-state-greeting-cache.ts +1 -2
  453. package/src/runtime/routes/events-routes.ts +1 -3
  454. package/src/runtime/routes/guardian-approval-interception.ts +14 -73
  455. package/src/runtime/routes/guardian-approval-prompt.ts +22 -4
  456. package/src/runtime/routes/home-feed-routes.ts +8 -3
  457. package/src/runtime/routes/identity-routes.ts +1 -296
  458. package/src/runtime/routes/inbound-message-handler.ts +214 -228
  459. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +89 -7
  460. package/src/runtime/routes/inbound-stages/admission-policy.test.ts +154 -0
  461. package/src/runtime/routes/inbound-stages/admission-policy.ts +140 -0
  462. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +3 -3
  463. package/src/runtime/routes/inbound-stages/background-dispatch.ts +11 -6
  464. package/src/runtime/routes/inbound-stages/escalation-intercept.ts +1 -2
  465. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +1 -2
  466. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.test.ts +7 -7
  467. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +47 -28
  468. package/src/runtime/routes/inbound-stages/reaction-intercept.ts +358 -0
  469. package/src/runtime/routes/index.ts +2 -0
  470. package/src/runtime/routes/integrations/slack/__tests__/channel.test.ts +8 -0
  471. package/src/runtime/routes/integrations/slack/channel.ts +36 -0
  472. package/src/runtime/routes/internal-telemetry-routes.ts +1 -1
  473. package/src/runtime/routes/mcp-auth-routes.ts +233 -41
  474. package/src/runtime/routes/memory-eval-routes.ts +87 -0
  475. package/src/runtime/routes/notification-routes.ts +122 -133
  476. package/src/runtime/routes/platform-routes.ts +2 -2
  477. package/src/runtime/routes/plugins-routes.ts +202 -3
  478. package/src/runtime/routes/schedule-routes.ts +0 -22
  479. package/src/runtime/routes/secret-routes.ts +10 -0
  480. package/src/runtime/routes/surface-action-routes.ts +2 -1
  481. package/src/runtime/routes/tool-call-question-enrichment.test.ts +146 -0
  482. package/src/runtime/routes/tool-call-question-enrichment.ts +66 -0
  483. package/src/runtime/routes/workflow-routes.test.ts +229 -44
  484. package/src/runtime/routes/workflow-routes.ts +131 -29
  485. package/src/runtime/routes/workspace-greetings.ts +55 -0
  486. package/src/runtime/sync/resource-sync-events.ts +1 -11
  487. package/src/runtime/tool-grant-request-helper.ts +18 -16
  488. package/src/runtime/trust-context-resolver.ts +8 -5
  489. package/src/schedule/inference-profile.ts +2 -14
  490. package/src/schedule/schedule-store.ts +1 -1
  491. package/src/schedule/scheduler-types.ts +5 -1
  492. package/src/security/__tests__/provider-key-env-fallback.test.ts +6 -0
  493. package/src/security/secret-patterns.ts +3 -0
  494. package/src/subagent/manager.ts +17 -4
  495. package/src/subagent/types.ts +6 -0
  496. package/src/telemetry/trace-collection-policy.test.ts +28 -0
  497. package/src/telemetry/trace-collection-policy.ts +30 -0
  498. package/src/telemetry/types.ts +89 -0
  499. package/src/telemetry/usage-telemetry-reporter.test.ts +586 -36
  500. package/src/telemetry/usage-telemetry-reporter.ts +148 -41
  501. package/src/tools/AGENTS.md +3 -3
  502. package/src/tools/browser/__tests__/browser-execution-acquire.test.ts +31 -0
  503. package/src/tools/browser/browser-execution.ts +30 -19
  504. package/src/tools/document/document-tool.ts +2 -3
  505. package/src/tools/executor.ts +5 -3
  506. package/src/tools/host-terminal/host-shell.ts +5 -4
  507. package/src/tools/memory/register.ts +2 -2
  508. package/src/tools/network/__tests__/web-fetch-firecrawl.test.ts +360 -0
  509. package/src/tools/network/__tests__/web-search.test.ts +143 -0
  510. package/src/tools/network/web-fetch.ts +372 -1
  511. package/src/tools/network/web-search-error.ts +1 -1
  512. package/src/tools/network/web-search.ts +213 -10
  513. package/src/tools/permission-checker.ts +4 -3
  514. package/src/tools/registry.ts +20 -0
  515. package/src/tools/schedule/create.ts +7 -12
  516. package/src/tools/schedule/update.ts +4 -11
  517. package/src/tools/shared/filesystem/path-policy.ts +39 -13
  518. package/src/tools/side-effects.ts +2 -17
  519. package/src/tools/skills/execute.ts +33 -0
  520. package/src/tools/subagent/spawn.ts +61 -12
  521. package/src/tools/terminal/shell.ts +10 -4
  522. package/src/tools/tool-approval-handler.ts +18 -13
  523. package/src/tools/tool-manifest.ts +0 -2
  524. package/src/tools/types.ts +9 -0
  525. package/src/tools/ui-surface/definitions.ts +64 -3
  526. package/src/tools/verification-control-plane-policy.ts +3 -1
  527. package/src/tools/workflows/run-workflow.test.ts +8 -18
  528. package/src/tools/workflows/run-workflow.ts +1 -0
  529. package/src/util/disk-usage.ts +78 -23
  530. package/src/util/platform.ts +10 -3
  531. package/src/watcher/telemetry.ts +2 -2
  532. package/src/workflows/capabilities.ts +2 -3
  533. package/src/workflows/engine.test.ts +175 -1
  534. package/src/workflows/engine.ts +82 -0
  535. package/src/workflows/journal-store.test.ts +70 -0
  536. package/src/workflows/journal-store.ts +18 -3
  537. package/src/workflows/run-manager.test.ts +171 -28
  538. package/src/workflows/run-manager.ts +66 -24
  539. package/src/workspace/migrations/105-enable-memory-v3-live-for-new-workspaces.ts +63 -0
  540. package/src/workspace/migrations/106-drop-collect-usage-data.ts +47 -0
  541. package/src/workspace/migrations/107-drop-send-diagnostics.ts +47 -0
  542. package/src/workspace/migrations/108-drop-balanced-economy-profile.ts +129 -0
  543. package/src/workspace/migrations/registry.ts +8 -0
  544. package/src/__tests__/app-control-no-global-cgevent.test.ts +0 -98
  545. package/src/__tests__/credential-security-e2e.test.ts +0 -362
  546. package/src/__tests__/credential-vault-unit.test.ts +0 -1528
  547. package/src/__tests__/credential-vault.test.ts +0 -1706
  548. package/src/__tests__/identity-intro-cache.test.ts +0 -315
  549. package/src/__tests__/secret-onetime-send.test.ts +0 -182
  550. package/src/cli/commands/__tests__/task.test.ts +0 -914
  551. package/src/cli/commands/task.ts +0 -771
  552. package/src/config/bundled-skills/personal-page/SKILL.md +0 -57
  553. package/src/config/bundled-skills/personal-page/TOOLS.json +0 -27
  554. package/src/config/bundled-skills/personal-page/tools/app-refresh.ts +0 -17
  555. package/src/config/preloaded-apps/personal-page/src/components/About.tsx +0 -22
  556. package/src/config/preloaded-apps/personal-page/src/components/App.tsx +0 -16
  557. package/src/config/preloaded-apps/personal-page/src/components/Features.tsx +0 -77
  558. package/src/config/preloaded-apps/personal-page/src/components/Hero.tsx +0 -57
  559. package/src/config/preloaded-apps/personal-page/src/components/Pending.tsx +0 -28
  560. package/src/config/preloaded-apps/personal-page/src/components/animations.tsx +0 -234
  561. package/src/config/preloaded-apps/personal-page/src/components/icons.tsx +0 -48
  562. package/src/config/preloaded-apps/personal-page/src/components/media.ts +0 -16
  563. package/src/config/preloaded-apps/personal-page/src/index.html +0 -20
  564. package/src/config/preloaded-apps/personal-page/src/main.tsx +0 -7
  565. package/src/config/preloaded-apps/personal-page/src/profile-data.ts +0 -82
  566. package/src/config/preloaded-apps/personal-page/src/styles.css +0 -759
  567. package/src/memory/__tests__/preloaded-apps.test.ts +0 -85
  568. package/src/memory/preloaded-apps.ts +0 -116
  569. package/src/notifications/tool-approval-copy.ts +0 -142
  570. package/src/runtime/routes/approval-prompt-ts-tracker.ts +0 -78
  571. package/src/runtime/routes/identity-intro-cache.ts +0 -172
  572. package/src/tools/credentials/vault.ts +0 -712
@@ -9,9 +9,9 @@ import { statSync } from "node:fs";
9
9
  import { join } from "node:path";
10
10
 
11
11
  import { type ChannelId, parseInterfaceId } from "../channels/types.js";
12
- import { isAssistantFeatureFlagEnabled } from "../config/assistant-feature-flags.js";
13
12
  import { resolveCallSiteConfig } from "../config/llm-resolver.js";
14
13
  import { getConfig } from "../config/loader.js";
14
+ import { isMemoryV3Live } from "../config/memory-v3-gate.js";
15
15
  import type { LLMCallSite, LLMConfig } from "../config/schemas/llm.js";
16
16
  import {
17
17
  NOW_SCRATCHPAD_STRIP_PREFIXES,
@@ -62,11 +62,11 @@ import type {
62
62
  import type { ContentBlock, Message } from "../providers/types.js";
63
63
  import {
64
64
  type ActorTrustContext,
65
- isUntrustedTrustClass,
66
65
  resolveActorTrust,
67
66
  type TrustClass,
68
67
  } from "../runtime/actor-trust-resolver.js";
69
68
  import { DAEMON_INTERNAL_ASSISTANT_ID } from "../runtime/assistant-scope.js";
69
+ import { resolveCapabilities } from "../runtime/capabilities.js";
70
70
  import { channelStatusToMemberStatus } from "../runtime/routes/inbound-stages/acl-enforcement.js";
71
71
  import { getSubagentManager } from "../subagent/index.js";
72
72
  import type { SubagentState } from "../subagent/types.js";
@@ -125,8 +125,8 @@ export interface InboundActorContext {
125
125
  actorSenderDisplayName?: string;
126
126
  /** Guardian-managed display name from the contact record. */
127
127
  actorMemberDisplayName?: string;
128
- /** Trust classification: guardian, trusted_contact, or unknown. */
129
- trustClass: "guardian" | "trusted_contact" | "unknown";
128
+ /** Trust classification: see TrustClass. */
129
+ trustClass: TrustClass;
130
130
  /** Guardian identity for this (assistant, channel) binding. */
131
131
  guardianIdentity?: string;
132
132
  /** Member status when the actor has a contact record. */
@@ -916,7 +916,7 @@ function filterSlackConversationRowsForActor(
916
916
  rows: MessageRow[],
917
917
  trustClass: TrustClass | undefined,
918
918
  ): MessageRow[] {
919
- if (!isUntrustedTrustClass(trustClass)) return rows;
919
+ if (resolveCapabilities(trustClass).canAccessMemory) return rows;
920
920
  const nonSlackVisibleRows = filterMessagesForUntrustedActor(rows);
921
921
  const nonSlackVisibleIds = new Set(nonSlackVisibleRows.map((row) => row.id));
922
922
  return rows.filter((row) => {
@@ -1009,6 +1009,7 @@ function rowToRenderable(row: SlackTranscriptInputRow): RenderableSlackMessage {
1009
1009
  if (
1010
1010
  outer.provenanceTrustClass === "guardian" ||
1011
1011
  outer.provenanceTrustClass === "trusted_contact" ||
1012
+ outer.provenanceTrustClass === "unverified_contact" ||
1012
1013
  outer.provenanceTrustClass === "unknown"
1013
1014
  ) {
1014
1015
  provenanceTrustClass = outer.provenanceTrustClass;
@@ -1322,9 +1323,9 @@ export function loadSlackChronologicalContext(
1322
1323
  options,
1323
1324
  );
1324
1325
  return assembleSlackChronologicalContext(rows, capabilities, {
1325
- contextSummary: isUntrustedTrustClass(options.trustClass)
1326
- ? null
1327
- : options.contextSummary,
1326
+ contextSummary: resolveCapabilities(options.trustClass).canAccessMemory
1327
+ ? options.contextSummary
1328
+ : null,
1328
1329
  });
1329
1330
  }
1330
1331
 
@@ -1536,8 +1537,8 @@ export interface RuntimeInjectionBlocks {
1536
1537
  */
1537
1538
  memoryV3InjectedBlock?: string;
1538
1539
  /**
1539
- * True when memory-v3 superseded v2 as this turn's `<memory>` source — the
1540
- * `memory-v3-live` flag is on AND the v3 injector produced a block (possibly
1540
+ * True when memory-v3 superseded v2 as this turn's `<memory>` source —
1541
+ * `memory.v3.live` is on AND the v3 injector produced a block (possibly
1541
1542
  * empty-text on an all-repeat turn), i.e. exactly when assembly stripped
1542
1543
  * v2's fresh tail block. The user-prompt-submit hook keys v2's
1543
1544
  * `memoryInjectedBlock` metadata persist off this so a stripped v2 block is
@@ -2177,7 +2178,7 @@ export async function applyRuntimeInjections(
2177
2178
  // no-op, keeping the v2 path bit-for-bit identical.
2178
2179
  let runMessagesForAssembly = stripSpotlightInjections(runMessages);
2179
2180
 
2180
- // v2 suppression: when the `memory-v3-live` flag is on AND the v3 injector
2181
+ // v2 suppression: when `memory.v3.live` is on AND the v3 injector
2181
2182
  // produced a block this turn (possibly empty-text on an all-repeat turn), v3
2182
2183
  // owns the `<memory>` layer. v2's `prepareMemory` already prepended its own
2183
2184
  // fresh `<memory>` block to the tail user message — strip the TAIL's v2
@@ -2196,10 +2197,7 @@ export async function applyRuntimeInjections(
2196
2197
  // (`produce()` → null) leaves v2's block intact — fallback rather than a
2197
2198
  // memory-less turn. Idempotent: re-injection sites that already stripped
2198
2199
  // see no change. Flag off → bit-for-bit identical to the v2 path.
2199
- const suppressV2MemoryForV3 = isAssistantFeatureFlagEnabled(
2200
- "memory-v3-live",
2201
- getConfig(),
2202
- );
2200
+ const suppressV2MemoryForV3 = isMemoryV3Live(getConfig());
2203
2201
  const v3ProducedBlock = afterMemory.some((b) => b.id === MEMORY_V3_BLOCK_ID);
2204
2202
  const memoryV3Active = suppressV2MemoryForV3 && v3ProducedBlock;
2205
2203
  if (memoryV3Active) {
@@ -7,6 +7,7 @@ import {
7
7
  loadRawConfig,
8
8
  saveRawConfig,
9
9
  } from "../config/loader.js";
10
+ import { orderProfileKeys } from "../config/profile-order.js";
10
11
  import { getConversationOverrideProfile } from "../memory/conversation-crud.js";
11
12
  import { getConfiguredProviders } from "../providers/provider-availability.js";
12
13
  import { getVisibleProviderCatalog } from "../providers/provider-catalog-visibility.js";
@@ -130,28 +131,6 @@ function parseModelCommand(trimmed: string): ModelCommandParse | null {
130
131
  return { kind: "switch", profileName: rest };
131
132
  }
132
133
 
133
- function orderedProfileNames(
134
- profiles: Record<
135
- string,
136
- { label?: string; description?: string; status?: "active" | "disabled" }
137
- >,
138
- profileOrder: readonly string[] | undefined,
139
- ): string[] {
140
- const order = profileOrder ?? [];
141
- const seen = new Set<string>();
142
- const ordered: string[] = [];
143
- for (const name of order) {
144
- if (profiles[name] != null && !seen.has(name)) {
145
- ordered.push(name);
146
- seen.add(name);
147
- }
148
- }
149
- const tail = Object.keys(profiles)
150
- .filter((n) => !seen.has(n))
151
- .sort();
152
- return [...ordered, ...tail];
153
- }
154
-
155
134
  async function resolveModelCommand(
156
135
  parse: ModelCommandParse,
157
136
  ): Promise<SlashResolution> {
@@ -160,7 +139,7 @@ async function resolveModelCommand(
160
139
  string,
161
140
  { label?: string; description?: string; status?: "active" | "disabled" }
162
141
  >;
163
- const profileNames = orderedProfileNames(profiles, config.llm.profileOrder);
142
+ const profileNames = orderProfileKeys(profiles, config.llm.profileOrder);
164
143
  const activeProfile = config.llm.activeProfile;
165
144
 
166
145
  if (parse.kind === "list") {
@@ -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
@@ -26,7 +26,10 @@ import {
26
26
  ACTIVITY_SKIP_SET,
27
27
  injectActivityField,
28
28
  } from "../tools/schema-transforms.js";
29
- import { resolveSkillExecuteInput } from "../tools/skills/execute.js";
29
+ import {
30
+ augmentSkillExecuteError,
31
+ resolveSkillExecuteInput,
32
+ } from "../tools/skills/execute.js";
30
33
  import { resolveToolInvocationAlias } from "../tools/tool-name-aliases.js";
31
34
  import {
32
35
  isDiskPressureCleanupToolName,
@@ -52,7 +55,7 @@ import {
52
55
  } from "./doordash-steps.js";
53
56
  import type { ServerMessage, UiSurfaceShow } from "./message-protocol.js";
54
57
  import { runPostExecutionSideEffects } from "./tool-side-effects.js";
55
- import { resolveTrustClass } from "./trust-context.js";
58
+ import { FALLBACK_TURN_TRUST, resolveTrustClass } from "./trust-context.js";
56
59
 
57
60
  const log = getLogger("conversation-tool-setup");
58
61
 
@@ -178,28 +181,32 @@ export function createToolExecutor(
178
181
  markDoordashStepInProgress(ctx, executionInput);
179
182
  }
180
183
 
181
- // Build the context object shared by both the skill_execute interception
182
- // 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
+
183
190
  const toolContext: ToolContext = {
184
191
  workingDir: ctx.workingDir,
185
192
  conversationId: ctx.conversationId,
186
193
  assistantId: ctx.assistantId,
187
194
  requestId: ctx.currentRequestId,
188
195
  taskRunId: ctx.taskRunId,
189
- trustClass: resolveTrustClass(ctx.trustContext),
190
- executionChannel: ctx.trustContext?.sourceChannel,
191
- sourceActorPrincipalId: ctx.trustContext?.guardianPrincipalId,
196
+ trustClass: resolveTrustClass(turnTrust),
197
+ executionChannel: turnTrust.sourceChannel,
198
+ sourceActorPrincipalId: turnTrust.guardianPrincipalId,
192
199
  callSessionId: ctx.callSessionId,
193
200
  triggeredBySurfaceAction:
194
201
  ctx.surfaceActionRequestIds?.has(ctx.currentRequestId ?? "") ?? false,
195
202
  approvedViaPrompt: ctx.approvedViaPromptThisTurn || undefined,
196
203
  batchAuthorizedByTask: false,
197
- requesterExternalUserId: ctx.trustContext?.requesterExternalUserId,
198
- requesterChatId: ctx.trustContext?.requesterChatId,
199
- requesterIdentifier: ctx.trustContext?.requesterIdentifier,
200
- requesterDisplayName: ctx.trustContext?.requesterDisplayName,
204
+ requesterExternalUserId: turnTrust.requesterExternalUserId,
205
+ requesterChatId: turnTrust.requesterChatId,
206
+ requesterIdentifier: turnTrust.requesterIdentifier,
207
+ requesterDisplayName: turnTrust.requesterDisplayName,
201
208
  channelPermissionChannelId:
202
- ctx.trustContext?.sourceChannel === "slack"
209
+ turnTrust.sourceChannel === "slack"
203
210
  ? getBindingByConversation(ctx.conversationId)?.externalChatId
204
211
  : undefined,
205
212
  onOutput,
@@ -211,6 +218,7 @@ export function createToolExecutor(
211
218
  isPlatformHosted: getIsPlatform(),
212
219
  transportInterface: ctx.transportInterface,
213
220
  overrideProfile: ctx.currentTurnOverrideProfile,
221
+ invokingCallSite: ctx.currentCallSite ?? "mainAgent",
214
222
  attribution: resolveConversationAttribution(ctx),
215
223
  onToolLifecycleEvent: handleToolLifecycleEvent,
216
224
  sendToClient: (msg) => {
@@ -320,7 +328,12 @@ export function createToolExecutor(
320
328
  const innerRejection = rejectNonAllowlistedTool(toolName);
321
329
  if (innerRejection) return innerRejection;
322
330
 
323
- const result = await executor.execute(toolName, toolInput, toolContext);
331
+ const rawResult = await executor.execute(
332
+ toolName,
333
+ toolInput,
334
+ toolContext,
335
+ );
336
+ const result = augmentSkillExecuteError(toolName, toolInput, rawResult);
324
337
  if (toolContext.approvedViaPrompt) {
325
338
  ctx.approvedViaPromptThisTurn = true;
326
339
  }
@@ -371,7 +384,7 @@ export function createProxyApprovalCallback(
371
384
  * history or explicit preactivation. Without this, their tools are
372
385
  * unavailable in fresh conversations until `skill_load` is called.
373
386
  */
374
- const DEFAULT_PREACTIVATED_SKILL_IDS = ["tasks", "notifications", "subagent"];
387
+ const DEFAULT_PREACTIVATED_SKILL_IDS = ["notifications", "subagent"];
375
388
 
376
389
  /**
377
390
  * Subset of Conversation state that the resolveTools callback reads at each
@@ -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
@@ -1847,6 +1897,8 @@ export class Conversation {
1847
1897
  * this value via {@link SubagentManager.spawn}.
1848
1898
  */
1849
1899
  overrideProfile?: string;
1900
+ /** Float `overrideProfile` above call-site layers for this run. */
1901
+ forceOverrideProfile?: boolean;
1850
1902
  },
1851
1903
  ): Promise<void> {
1852
1904
  const { onEvent, ...rest } = options ?? {};
@@ -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
  },
@@ -204,7 +204,7 @@ export function revokeVerificationForChannel(
204
204
  // revocation becomes a no-op (wrong reason or skipped entirely).
205
205
  const contactResult = findContactChannel({
206
206
  channelType: resolvedChannel,
207
- externalUserId: bindingBeforeRevoke.guardianExternalUserId,
207
+ address: bindingBeforeRevoke.guardianExternalUserId,
208
208
  externalChatId: bindingBeforeRevoke.guardianDeliveryChatId,
209
209
  });
210
210
 
@@ -331,7 +331,10 @@ export async function verifyTrustedContact(
331
331
  const sessionResult = createOutboundSession({
332
332
  channel: verificationChannel,
333
333
  expectedChatId: channel.externalChatId,
334
- expectedExternalUserId: channel.externalUserId ?? undefined,
334
+ expectedExternalUserId:
335
+ channel.address !== channel.externalChatId
336
+ ? channel.address
337
+ : undefined,
335
338
  identityBindingStatus: "bound",
336
339
  destinationAddress: effectiveDestination,
337
340
  verificationPurpose: "trusted_contact",
@@ -404,22 +407,14 @@ export async function verifyTrustedContact(
404
407
 
405
408
  // --- Slack verification ---
406
409
  if (verificationChannel === "slack") {
407
- const slackUserId = channel.externalUserId ?? destination;
408
-
409
- const hasIdentityBinding = Boolean(
410
- channel.externalUserId || channel.externalChatId,
411
- );
412
- if (!hasIdentityBinding) {
413
- return {
414
- success: false,
415
- error:
416
- "Slack verification requires an externalUserId or externalChatId for identity binding",
417
- };
418
- }
410
+ const slackUserId = channel.address;
419
411
 
420
412
  const sessionResult = createOutboundSession({
421
413
  channel: verificationChannel,
422
- expectedExternalUserId: channel.externalUserId ?? undefined,
414
+ expectedExternalUserId:
415
+ channel.address !== channel.externalChatId
416
+ ? channel.address
417
+ : undefined,
423
418
  expectedChatId: channel.externalChatId ?? undefined,
424
419
  identityBindingStatus: "bound",
425
420
  destinationAddress: slackUserId,
@@ -560,7 +555,11 @@ export async function handleChannelVerificationSession(
560
555
  const { to, text, subject, assistantId: aid } = result._pendingEmail;
561
556
  deliverVerificationEmail(to, text, subject, aid);
562
557
  }
563
- const { _pendingSlackDm: _, _pendingEmail: __, ...publicResult } = result;
558
+ const {
559
+ _pendingSlackDm: _,
560
+ _pendingEmail: __,
561
+ ...publicResult
562
+ } = result;
564
563
  broadcastMessage({
565
564
  type: "channel_verification_session_response",
566
565
  ...publicResult,
@@ -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
  }
@@ -440,9 +449,8 @@ export async function setSlackChannelConfig(
440
449
  * the bot_token, app_token, oauth_connection row, and Slack config metadata
441
450
  * untouched so the Socket Mode connection stays up. Returns a
442
451
  * `SlackChannelConfigResult` reflecting the remaining state. A `not-found`
443
- * delete outcome is reported as a failure to match the credential_store
444
- * delete semantics (callers and automation rely on missing-credential
445
- * detection).
452
+ * delete outcome is reported as a failure to match the credential delete
453
+ * semantics (callers and automation rely on missing-credential detection).
446
454
  */
447
455
  export async function clearSlackUserToken(): Promise<SlackChannelConfigResult> {
448
456
  const result = await deleteSecureKeyAsync(
@@ -467,6 +475,7 @@ export async function clearSlackUserToken(): Promise<SlackChannelConfigResult> {
467
475
  hasUserToken,
468
476
  connected:
469
477
  !!(conn && conn.status === "active") && hasBotToken && hasAppToken,
478
+ threadMode: getConfig().slack.threadMode,
470
479
  error:
471
480
  result === "not-found"
472
481
  ? "Slack user token not found in secure storage"
@@ -498,6 +507,7 @@ export async function clearSlackUserToken(): Promise<SlackChannelConfigResult> {
498
507
  ...(teamUrl ? { teamUrl } : {}),
499
508
  ...(botUserId ? { botUserId } : {}),
500
509
  ...(botUsername ? { botUsername } : {}),
510
+ threadMode: getConfig().slack.threadMode,
501
511
  };
502
512
  }
503
513
 
@@ -531,6 +541,7 @@ export async function clearSlackChannelConfig(): Promise<SlackChannelConfigResul
531
541
  hasUserToken,
532
542
  connected:
533
543
  !!(conn && conn.status === "active") && hasBotToken && hasAppToken,
544
+ threadMode: getConfig().slack.threadMode,
534
545
  error: "Failed to delete Slack channel credentials from secure storage",
535
546
  };
536
547
  }
@@ -557,5 +568,13 @@ export async function clearSlackChannelConfig(): Promise<SlackChannelConfigResul
557
568
  hasAppToken: false,
558
569
  hasUserToken: false,
559
570
  connected: false,
571
+ threadMode: "mention_only",
560
572
  };
561
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
+ }