@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
@@ -8,6 +8,7 @@
8
8
  import { z } from "zod";
9
9
 
10
10
  import { findConversation } from "../../daemon/conversation-registry.js";
11
+ import { HostBrowserProxy } from "../../daemon/host-browser-proxy.js";
11
12
  import { getCdpClient } from "../../tools/browser/cdp-client/factory.js";
12
13
  import {
13
14
  clearPinnedTab,
@@ -51,6 +52,14 @@ async function handleBrowserTabs({ body = {} }: RouteHandlerArgs) {
51
52
 
52
53
  const cdpOptions = { mode: "extension" as const, targetClientId };
53
54
 
55
+ // Every tabs command pins extension mode. Absorb a brief extension SSE
56
+ // reconnect blip so a flapping connection doesn't surface as a hard
57
+ // "no Chrome Extension connected" error.
58
+ await HostBrowserProxy.instance.waitForExtensionClient(
59
+ context.sourceActorPrincipalId,
60
+ targetClientId,
61
+ );
62
+
54
63
  if (command === "list") {
55
64
  const cdp = getCdpClient(context, cdpOptions);
56
65
  try {
@@ -27,18 +27,11 @@ import {
27
27
  setCachedEmptyStateGreeting,
28
28
  } from "./empty-state-greeting-cache.js";
29
29
  import { BadRequestError, ServiceUnavailableError } from "./errors.js";
30
- import {
31
- getCachedIntro,
32
- readWorkspaceGreetings,
33
- setCachedIntro,
34
- } from "./identity-intro-cache.js";
35
30
  import type { RouteDefinition, RouteHandlerArgs } from "./types.js";
31
+ import { readWorkspaceGreetings } from "./workspace-greetings.js";
36
32
 
37
33
  const log = getLogger("btw-routes");
38
34
 
39
- /** Conversation key used by the client for identity intro generation. */
40
- const IDENTITY_INTRO_KEY = "identity-intro";
41
-
42
35
  /** Conversation key used by the client for empty-state greeting generation. */
43
36
  const GREETING_KEY = "greeting";
44
37
 
@@ -72,15 +65,6 @@ async function handleBtw({
72
65
 
73
66
  const trimmedContent = content.trim();
74
67
 
75
- // ----- Cached identity intro fast-path -----
76
- if (conversationKey === IDENTITY_INTRO_KEY) {
77
- const fastText = getCachedIntro()?.greetings[0];
78
- if (fastText) {
79
- log.debug("Returning identity intro fast-path");
80
- return streamText(fastText);
81
- }
82
- }
83
-
84
68
  // ----- Empty-state greeting fast-path -----
85
69
  // User-authored `## Greetings` win; otherwise replay a cached greeting when
86
70
  // one is fresh within the configurable TTL (`ui.emptyStateGreetingCacheTtlMs`,
@@ -126,7 +110,6 @@ async function handleBtw({
126
110
  start(controller) {
127
111
  (async () => {
128
112
  try {
129
- const isIntroRequest = conversationKey === IDENTITY_INTRO_KEY;
130
113
  const isGreeting = conversationKey === GREETING_KEY;
131
114
  const result = await runBtwSidechain({
132
115
  content: effectiveContent,
@@ -153,15 +136,6 @@ async function handleBtw({
153
136
  );
154
137
  }
155
138
 
156
- if (isIntroRequest && result.text) {
157
- try {
158
- setCachedIntro([result.text]);
159
- log.debug("Cached identity intro text");
160
- } catch {
161
- // Non-fatal — next request will regenerate.
162
- }
163
- }
164
-
165
139
  if (isGreeting && result.text) {
166
140
  // setCachedEmptyStateGreeting is a no-op when the TTL is 0.
167
141
  setCachedEmptyStateGreeting(result.text);
@@ -18,6 +18,7 @@
18
18
  * clients polling prompts) observe the expired status directly.
19
19
  */
20
20
 
21
+ import { withdrawGuardianRequestCards } from "../../approvals/guardian-card-withdrawal.js";
21
22
  import {
22
23
  listCanonicalGuardianRequests,
23
24
  resolveCanonicalGuardianRequest,
@@ -42,7 +43,7 @@ let sweepInProgress = false;
42
43
  * concurrent decision that wins the race is never overwritten by the
43
44
  * sweep. Returns the count of requests transitioned to expired.
44
45
  */
45
- function sweepExpiredCanonicalGuardianRequests(): number {
46
+ async function sweepExpiredCanonicalGuardianRequests(): Promise<number> {
46
47
  const pending = listCanonicalGuardianRequests({ status: "pending" });
47
48
  const now = Date.now();
48
49
  let expiredCount = 0;
@@ -71,6 +72,14 @@ function sweepExpiredCanonicalGuardianRequests(): number {
71
72
  },
72
73
  "Expired canonical guardian request via sweep",
73
74
  );
75
+
76
+ // Withdraw the now-stale approval cards on every surface. No origin
77
+ // channel — the expiry is system-driven, so all surfaces (including
78
+ // in-app) are withdrawn. Best-effort and non-throwing.
79
+ await withdrawGuardianRequestCards({
80
+ request: resolved,
81
+ status: "expired",
82
+ });
74
83
  }
75
84
  }
76
85
 
@@ -93,13 +102,13 @@ export function startCanonicalGuardianExpirySweep(): void {
93
102
  sweepTimer = setInterval(() => {
94
103
  if (sweepInProgress) return;
95
104
  sweepInProgress = true;
96
- try {
97
- sweepExpiredCanonicalGuardianRequests();
98
- } catch (err) {
99
- log.error({ err }, "Canonical guardian expiry sweep failed");
100
- } finally {
101
- sweepInProgress = false;
102
- }
105
+ void sweepExpiredCanonicalGuardianRequests()
106
+ .catch((err) => {
107
+ log.error({ err }, "Canonical guardian expiry sweep failed");
108
+ })
109
+ .finally(() => {
110
+ sweepInProgress = false;
111
+ });
103
112
  }, SWEEP_INTERVAL_MS);
104
113
  }
105
114
 
@@ -12,6 +12,10 @@ import { isHttpAuthDisabled } from "../../config/env.js";
12
12
  import { datesToISO } from "../../util/json.js";
13
13
  import { assistantEventHub } from "../assistant-event-hub.js";
14
14
  import { ACTOR_PRINCIPALS } from "../auth/route-policy.js";
15
+ import {
16
+ DEFAULT_HEARTBEAT_INTERVAL_MS,
17
+ isClientDegraded,
18
+ } from "../client-health.js";
15
19
  import { NotFoundError } from "./errors.js";
16
20
  import type { RouteDefinition } from "./types.js";
17
21
 
@@ -65,6 +69,7 @@ export const ROUTES: RouteDefinition[] = [
65
69
  c.actorPrincipalId === callerPrincipalId,
66
70
  );
67
71
 
72
+ const now = new Date();
68
73
  return {
69
74
  clients: filtered.map((c) =>
70
75
  datesToISO({
@@ -74,6 +79,11 @@ export const ROUTES: RouteDefinition[] = [
74
79
  machineName: c.machineName,
75
80
  connectedAt: c.connectedAt,
76
81
  lastActiveAt: c.lastActiveAt,
82
+ degraded: isClientDegraded(
83
+ c.lastActiveAt,
84
+ now,
85
+ DEFAULT_HEARTBEAT_INTERVAL_MS,
86
+ ),
77
87
  }),
78
88
  ),
79
89
  };
@@ -50,6 +50,30 @@ function withGuardianNameOverride<
50
50
  return contact;
51
51
  }
52
52
 
53
+ /** Adds `externalUserId` (= `address`) to each channel for older macOS clients. */
54
+ function withChannelCompat<T extends { channels: { address: string }[] }>(
55
+ contact: T,
56
+ ): T {
57
+ return {
58
+ ...contact,
59
+ channels: contact.channels.map((ch) => ({
60
+ ...ch,
61
+ externalUserId: ch.address,
62
+ })),
63
+ };
64
+ }
65
+
66
+ /** Compose both response transforms (guardian display name + channel compat). */
67
+ function prepareContactResponse<
68
+ T extends {
69
+ role: string;
70
+ displayName: string;
71
+ channels: { address: string }[];
72
+ },
73
+ >(contact: T): T {
74
+ return withChannelCompat(withGuardianNameOverride(contact));
75
+ }
76
+
53
77
  const VALID_CONTACT_TYPES: readonly ContactType[] = ["human", "assistant"];
54
78
 
55
79
  const VALID_CHANNEL_STATUSES: readonly ChannelStatus[] = [
@@ -87,6 +111,7 @@ const contactChannelSchema = z.object({
87
111
  type: z.string(),
88
112
  address: z.string(),
89
113
  isPrimary: z.boolean(),
114
+ /** @deprecated Echoes `address` for backwards compatibility with older macOS clients. */
90
115
  externalUserId: z.string().nullable(),
91
116
  status: z.string(),
92
117
  policy: z.string(),
@@ -143,14 +168,14 @@ function handleListContacts(queryParams: Record<string, string>) {
143
168
  });
144
169
  return {
145
170
  ok: true,
146
- contacts: contacts.map(withGuardianNameOverride),
171
+ contacts: contacts.map(prepareContactResponse),
147
172
  };
148
173
  }
149
174
 
150
175
  const contacts = listContacts(limit, role, contactType);
151
176
  return {
152
177
  ok: true,
153
- contacts: contacts.map(withGuardianNameOverride),
178
+ contacts: contacts.map(prepareContactResponse),
154
179
  };
155
180
  }
156
181
 
@@ -165,7 +190,7 @@ function handleGetContact(contactId: string) {
165
190
  : undefined;
166
191
  return {
167
192
  ok: true,
168
- contact: withGuardianNameOverride(contact),
193
+ contact: prepareContactResponse(contact),
169
194
  assistantMetadata: assistantMeta ?? undefined,
170
195
  };
171
196
  }
@@ -508,7 +533,7 @@ export const ROUTES: RouteDefinition[] = [
508
533
  limit: z.number().optional(),
509
534
  })
510
535
  .parse(body);
511
- return searchContacts(parsed);
536
+ return searchContacts(parsed).map(prepareContactResponse);
512
537
  },
513
538
  },
514
539
 
@@ -604,7 +629,7 @@ function handleMergeContactsRoute(args: RouteHandlerArgs) {
604
629
 
605
630
  try {
606
631
  const contact = mergeContacts(keepId, mergeId);
607
- return { ok: true, contact: withGuardianNameOverride(contact) };
632
+ return { ok: true, contact: prepareContactResponse(contact) };
608
633
  } catch (err) {
609
634
  const message = err instanceof Error ? err.message : String(err);
610
635
  throw new BadRequestError(message);
@@ -667,8 +692,6 @@ function handleUpdateContactChannelRoute(args: RouteHandlerArgs) {
667
692
  const parentContact = getContact(updated.contactId);
668
693
  return {
669
694
  ok: true,
670
- contact: parentContact
671
- ? withGuardianNameOverride(parentContact)
672
- : undefined,
695
+ contact: parentContact ? prepareContactResponse(parentContact) : undefined,
673
696
  };
674
697
  }
@@ -72,7 +72,7 @@ const log = getLogger("conversation-compaction-routes");
72
72
  * `responseBody` (below) is the source-of-truth for the generated
73
73
  * OpenAPI client type the frontend imports
74
74
  * (`ConversationsByIdCompactionGetResponse` in
75
- * `apps/web/src/generated/daemon/types.gen`); regenerate the client with
75
+ * `clients/web/src/generated/daemon/types.gen`); regenerate the client with
76
76
  * `bun run openapi-ts` after changing the schema.
77
77
  *
78
78
  * `null` means the value isn't known for the underlying row — either the
@@ -26,6 +26,7 @@ import {
26
26
  cancelGeneration,
27
27
  clearAllConversations,
28
28
  regenerateResponse,
29
+ resolveMetaSlashCommand,
29
30
  switchConversation,
30
31
  undoLastMessage,
31
32
  } from "../../daemon/handlers/conversations.js";
@@ -93,11 +94,26 @@ function cancelScheduleIfLast(conversationId: string): void {
93
94
  function handleCreateConversation({ body = {}, headers }: RouteHandlerArgs) {
94
95
  const conversationKey =
95
96
  (body.conversationKey as string | undefined) ?? crypto.randomUUID();
97
+ // The shared route adapter does not runtime-validate the body against the
98
+ // Zod requestBody (it's codegen-only), so guard the type before trimming —
99
+ // a malformed `{ title: 123 }` would otherwise throw on `.trim()` and 500.
100
+ if (body.title !== undefined && typeof body.title !== "string") {
101
+ throw new BadRequestError("title must be a string");
102
+ }
103
+ const customTitle = body.title?.trim() || undefined;
96
104
  const result = getOrCreateConversation(conversationKey, {
97
105
  conversationType: "standard",
98
106
  });
99
107
  if (result.created) {
100
- updateConversationTitle(result.conversationId, "New Conversation");
108
+ // A caller-supplied title is user-set: persist it with isAutoTitle = 0 so
109
+ // the async LLM titler's safe-overwrite check leaves it untouched. Without
110
+ // one, fall back to the neutral "New Conversation" placeholder, which stays
111
+ // replaceable by the auto-titler once messages arrive.
112
+ if (customTitle) {
113
+ updateConversationTitle(result.conversationId, customTitle, 0);
114
+ } else {
115
+ updateConversationTitle(result.conversationId, "New Conversation");
116
+ }
101
117
  publishConversationListAndMetadataChanged(
102
118
  "created",
103
119
  result.conversationId,
@@ -452,6 +468,29 @@ async function handleUndoLastMessage({ pathParams = {} }: RouteHandlerArgs) {
452
468
  };
453
469
  }
454
470
 
471
+ async function handleResolveMetaSlashCommand({
472
+ pathParams = {},
473
+ body = {},
474
+ }: RouteHandlerArgs) {
475
+ const command = body.command as string | undefined;
476
+ if (!command || typeof command !== "string") {
477
+ throw new BadRequestError("Missing command");
478
+ }
479
+ let result: Awaited<ReturnType<typeof resolveMetaSlashCommand>>;
480
+ try {
481
+ result = await resolveMetaSlashCommand(pathParams.id!, command);
482
+ } catch (err) {
483
+ if (err instanceof UserError) {
484
+ throw new BadRequestError(err.message);
485
+ }
486
+ throw err;
487
+ }
488
+ if (!result) {
489
+ throw new NotFoundError(`No conversation for ${pathParams.id}`);
490
+ }
491
+ return result;
492
+ }
493
+
455
494
  async function handleRegenerateResponse({ pathParams = {} }: RouteHandlerArgs) {
456
495
  const conversationId = pathParams.id!;
457
496
  try {
@@ -526,6 +565,12 @@ export const ROUTES: RouteDefinition[] = [
526
565
  .literal("standard")
527
566
  .optional()
528
567
  .describe("Only standard conversations are created by this endpoint"),
568
+ title: z
569
+ .string()
570
+ .optional()
571
+ .describe(
572
+ "Explicit title for the conversation. When provided on creation, it is persisted as a user-set title (never overwritten by the auto-titler). Used by flows that mint a conversation up-front and don't want an auto-generated title.",
573
+ ),
529
574
  }),
530
575
  responseBody: z.object({
531
576
  id: z
@@ -820,6 +865,40 @@ export const ROUTES: RouteDefinition[] = [
820
865
  }),
821
866
  handler: handleUndoLastMessage,
822
867
  },
868
+ {
869
+ operationId: "resolveConversationSlashCommand",
870
+ endpoint: "conversations/:id/slash",
871
+ method: "POST",
872
+ policy: {
873
+ requiredScopes: ["chat.write"],
874
+ allowedPrincipalTypes: ACTOR_PRINCIPALS,
875
+ },
876
+ summary: "Resolve a local meta slash command",
877
+ description:
878
+ "Run a local meta slash command (/clean, /status, /commands, /models) " +
879
+ "without starting a turn: no messages are persisted and no turn events " +
880
+ "are emitted. /clean also strips runtime injections from the history. " +
881
+ "Returns the text to render and, for /clean, the post-strip context usage.",
882
+ tags: ["conversations"],
883
+ pathParams: [{ name: "id", type: "uuid" }],
884
+ requestBody: z.object({
885
+ command: z
886
+ .string()
887
+ .describe("The slash command text, e.g. `/clean` or `/status`."),
888
+ }),
889
+ responseBody: z.object({
890
+ kind: z.enum(["clean", "info"]),
891
+ text: z.string(),
892
+ contextUsage: z
893
+ .object({
894
+ tokens: z.number(),
895
+ maxTokens: z.number().nullable(),
896
+ fillRatio: z.number().nullable(),
897
+ })
898
+ .optional(),
899
+ }),
900
+ handler: handleResolveMetaSlashCommand,
901
+ },
823
902
  {
824
903
  operationId: "regenerateResponse",
825
904
  endpoint: "conversations/:id/regenerate",
@@ -129,9 +129,16 @@ const INFERENCE_PROFILE_UI_KEYS = new Set([
129
129
  "speed",
130
130
  "verbosity",
131
131
  "temperature",
132
+ "topP",
132
133
  "thinking",
133
134
  ]);
134
135
 
136
+ // Fields a MANAGED profile may edit. Beyond `label` (display name) and
137
+ // `status` (enabled/disabled), users can tune `topP` — the seed contract
138
+ // owns provider/model/connection, but top_p is a per-profile sampling knob
139
+ // the UI exposes on the managed Balanced profile.
140
+ const MANAGED_PROFILE_EDITABLE_KEYS = new Set(["label", "status", "topP"]);
141
+
135
142
  function asMutablePlainObject(value: unknown): Record<string, unknown> | null {
136
143
  if (value == null || typeof value !== "object" || Array.isArray(value)) {
137
144
  return null;
@@ -508,6 +515,8 @@ const ConfigGetResponseSchema = z
508
515
  profiles: z.record(z.string(), WireProfileEntry).optional(),
509
516
  profileOrder: z.array(z.string()).optional(),
510
517
  activeProfile: z.string().optional(),
518
+ // The profile the advisor consults; excluded from chat-profile pickers.
519
+ advisorProfile: z.string().optional(),
511
520
  callSites: z
512
521
  .record(
513
522
  z.string(),
@@ -536,6 +545,13 @@ const ConfigGetResponseSchema = z
536
545
  })
537
546
  .passthrough()
538
547
  .optional(),
548
+ "web-fetch": z
549
+ .object({
550
+ mode: ServiceModeSchema.optional(),
551
+ provider: z.string().optional(),
552
+ })
553
+ .passthrough()
554
+ .optional(),
539
555
  "image-generation": z
540
556
  .object({ mode: ServiceModeSchema.optional() })
541
557
  .passthrough()
@@ -609,6 +625,7 @@ const ConfigPatchRequestSchema = z
609
625
  .optional(),
610
626
  profileOrder: z.array(z.string()).optional(),
611
627
  activeProfile: z.string().nullable().optional(),
628
+ advisorProfile: z.string().nullable().optional(),
612
629
  callSites: z
613
630
  .record(z.string(), CallSiteOverrideDraftSchema.nullable())
614
631
  .optional(),
@@ -633,6 +650,14 @@ const ConfigPatchRequestSchema = z
633
650
  .passthrough()
634
651
  .nullable()
635
652
  .optional(),
653
+ "web-fetch": z
654
+ .object({
655
+ mode: ServiceModeSchema.optional(),
656
+ provider: z.string().optional(),
657
+ })
658
+ .passthrough()
659
+ .nullable()
660
+ .optional(),
636
661
  "image-generation": z
637
662
  .object({ mode: ServiceModeSchema.optional() })
638
663
  .passthrough()
@@ -730,8 +755,14 @@ function rejectManagedProfileDeletion(body: Record<string, unknown>): void {
730
755
  }
731
756
  const profiles = asMutablePlainObject(llm.profiles);
732
757
  if (!profiles) return;
758
+ const existingProfiles = asMutablePlainObject(getConfig().llm.profiles) ?? {};
733
759
  for (const name of Object.keys(profiles)) {
734
- if (profiles[name] === null && MANAGED_PROFILE_NAMES.has(name)) {
760
+ if (profiles[name] !== null || !MANAGED_PROFILE_NAMES.has(name)) continue;
761
+ // Only block deletion when the on-disk entry is Vellum-managed. A
762
+ // user-owned profile sharing a managed name carries a non-managed `source`
763
+ // and is freely deletable.
764
+ const existing = asMutablePlainObject(existingProfiles[name]);
765
+ if (existing?.source === "managed") {
735
766
  throw new BadRequestError(`Cannot delete managed profile "${name}".`);
736
767
  }
737
768
  }
@@ -904,21 +935,39 @@ async function handleReplaceInferenceProfile({
904
935
  const detail = parsed.error.issues.map((issue) => issue.message).join("; ");
905
936
  throw new BadRequestError(`Invalid profile fragment: ${detail}`);
906
937
  }
907
- const isManaged = MANAGED_PROFILE_NAMES.has(name);
938
+ // A managed name with no existing entry stays protected so users can't
939
+ // shadow a seeded managed profile. An existing entry carrying a non-managed
940
+ // `source` is user-owned and remains fully editable.
941
+ const existingProfile = asMutablePlainObject(
942
+ getConfig().llm.profiles?.[name],
943
+ );
944
+ const isManaged =
945
+ MANAGED_PROFILE_NAMES.has(name) &&
946
+ (existingProfile == null || existingProfile.source === "managed");
947
+ // A managed profile name with no materialized entry (e.g. a flag-gated profile
948
+ // whose flag is off) cannot be patched: writing label/status here would persist
949
+ // a source-less stub that later blocks the real managed profile from being
950
+ // seeded. Reject rather than create a placeholder.
951
+ if (MANAGED_PROFILE_NAMES.has(name) && existingProfile == null) {
952
+ throw new BadRequestError(
953
+ `Profile "${name}" is not currently available and cannot be edited.`,
954
+ );
955
+ }
908
956
  if (isManaged) {
909
- // Managed profiles are daemon-seeded — provider, model, advanced params,
910
- // and the connection binding all belong to the seed contract and can't
911
- // be reshaped by the user. The two fields that ARE user policy (display
912
- // label and enabled status) are allowed through so users can rename a
913
- // managed profile or temporarily disable it without duplicating it.
957
+ // Managed profiles are daemon-seeded — provider, model, and the
958
+ // connection binding all belong to the seed contract and can't be
959
+ // reshaped by the user. The fields that ARE user policy (display label,
960
+ // enabled status, and the topP sampling knob) are allowed through so
961
+ // users can rename a managed profile, temporarily disable it, or tune
962
+ // top_p without duplicating it.
914
963
  const requestedKeys = Object.keys(parsed.data);
915
964
  const disallowed = requestedKeys.filter(
916
- (k) => k !== "label" && k !== "status",
965
+ (k) => !MANAGED_PROFILE_EDITABLE_KEYS.has(k),
917
966
  );
918
967
  if (disallowed.length > 0) {
919
968
  throw new BadRequestError(
920
969
  `Cannot edit managed profile "${name}" fields [${disallowed.join(", ")}]. ` +
921
- `Only label and status may be edited; duplicate to a custom profile to change other fields.`,
970
+ `Only label, status, and topP may be edited; duplicate to a custom profile to change other fields.`,
922
971
  );
923
972
  }
924
973
  }
@@ -1019,7 +1068,7 @@ async function handleReplaceInferenceProfile({
1019
1068
  }
1020
1069
 
1021
1070
  /**
1022
- * Apply a `{label?, status?}` patch to a managed profile entry, preserving
1071
+ * Apply a `{label?, status?, topP?}` patch to a managed profile entry, preserving
1023
1072
  * every other field already on disk (provider, model, advanced params, etc).
1024
1073
  * Caller is responsible for having already restricted the fragment to the
1025
1074
  * managed-allowed keys.
@@ -1039,19 +1088,16 @@ function patchManagedProfileFields(
1039
1088
 
1040
1089
  const existingProfile = asMutablePlainObject(profiles[name]) ?? {};
1041
1090
  const nextProfile: Record<string, unknown> = { ...existingProfile };
1042
- // Send `null` to clear; omit to leave untouched.
1043
- if ("label" in fragment) {
1044
- if (fragment.label === null) {
1045
- delete nextProfile.label;
1046
- } else {
1047
- nextProfile.label = fragment.label;
1048
- }
1049
- }
1050
- if ("status" in fragment) {
1051
- if (fragment.status === null) {
1052
- delete nextProfile.status;
1091
+ // For each managed-editable key: send `null` to clear, a value to set,
1092
+ // omit to leave untouched. Iterating the allowlist keeps persistence in
1093
+ // lock-step with the guard above — a key can't slip through the gate
1094
+ // without also being written.
1095
+ for (const key of MANAGED_PROFILE_EDITABLE_KEYS) {
1096
+ if (!(key in fragment)) continue;
1097
+ if (fragment[key] === null) {
1098
+ delete nextProfile[key];
1053
1099
  } else {
1054
- nextProfile.status = fragment.status;
1100
+ nextProfile[key] = fragment[key];
1055
1101
  }
1056
1102
  }
1057
1103
  profiles[name] = nextProfile;
@@ -120,7 +120,10 @@ import { assistantEventHub, broadcastMessage } from "../assistant-event-hub.js";
120
120
  import { DAEMON_INTERNAL_ASSISTANT_ID } from "../assistant-scope.js";
121
121
  import { getPersistedSeq } from "../assistant-stream-state.js";
122
122
  import { ACTOR_PRINCIPALS } from "../auth/route-policy.js";
123
- import { routeGuardianReply } from "../guardian-reply-router.js";
123
+ import {
124
+ type GuardianPendingScope,
125
+ routeGuardianReply,
126
+ } from "../guardian-reply-router.js";
124
127
  import { healGuardianBindingDrift } from "../guardian-vellum-migration.js";
125
128
  import type {
126
129
  ApprovalConversationGenerator,
@@ -148,6 +151,10 @@ import {
148
151
  collectPendingConfirmations,
149
152
  enrichToolCallsWithConfirmation,
150
153
  } from "./tool-call-confirmation-enrichment.js";
154
+ import {
155
+ collectPendingQuestions,
156
+ enrichToolCallsWithQuestion,
157
+ } from "./tool-call-question-enrichment.js";
151
158
  import type { RouteDefinition, RouteHandlerArgs } from "./types.js";
152
159
  import { RouteResponse } from "./types.js";
153
160
 
@@ -486,12 +493,14 @@ async function tryConsumeCanonicalGuardianReply(params: {
486
493
  sourceChannel,
487
494
  conversation,
488
495
  );
489
- // Always pass the hints array (even when empty) so
490
- // findPendingCanonicalRequests respects the in-memory staleness filter
491
- // applied by collectCanonicalGuardianRequestHintIds. Converting empty
492
- // hints to `undefined` caused the router to fall through to raw DB
493
- // queries that rediscovered stale canonical requests.
494
- const pendingRequestIds = pendingRequestHintIds;
496
+ // An empty hint set is `blocked`, not absence: the in-memory staleness
497
+ // filter in collectCanonicalGuardianRequestHintIds found no live requests,
498
+ // so the router must not fall back to identity/DB lookup (which rediscovered
499
+ // stale canonical requests). A non-empty set scopes resolution to it.
500
+ const pendingScope: GuardianPendingScope =
501
+ pendingRequestHintIds.length > 0
502
+ ? { mode: "scoped", requestIds: pendingRequestHintIds }
503
+ : { mode: "blocked" };
495
504
 
496
505
  const routerResult = await routeGuardianReply({
497
506
  messageText: trimmedContent,
@@ -503,7 +512,7 @@ async function tryConsumeCanonicalGuardianReply(params: {
503
512
  guardianPrincipalId: verifiedActorPrincipalId,
504
513
  },
505
514
  conversationId,
506
- pendingRequestIds,
515
+ pendingScope,
507
516
  approvalConversationGenerator,
508
517
  emissionContext: {
509
518
  source: "inline_nl",
@@ -817,6 +826,7 @@ export function handleListMessages({
817
826
  const pendingConfirmations = collectPendingConfirmations(
818
827
  resolvedConversationId,
819
828
  );
829
+ const pendingQuestions = collectPendingQuestions(resolvedConversationId);
820
830
 
821
831
  const messages: RuntimeMessagePayload[] = parsed.map((m) => {
822
832
  const mergedMessageIds = m.id ? (mergedIdMap.get(m.id) ?? []) : [];
@@ -880,10 +890,13 @@ export function handleListMessages({
880
890
  m.id ?? undefined,
881
891
  );
882
892
 
883
- const toolCalls = enrichToolCallsWithConfirmation(rendered.toolCalls, {
884
- workspaceDir,
885
- pendingConfirmations,
886
- });
893
+ const toolCalls = enrichToolCallsWithQuestion(
894
+ enrichToolCallsWithConfirmation(rendered.toolCalls, {
895
+ workspaceDir,
896
+ pendingConfirmations,
897
+ }),
898
+ { pendingQuestions },
899
+ );
887
900
 
888
901
  // Strip <no_response/> markers from assistant messages so web/API clients
889
902
  // never see the raw sentinel. Only assistant messages produce it; user
@@ -944,8 +957,8 @@ export function handleListMessages({
944
957
  ...(mergedMessageIds.length > 0 ? { mergedMessageIds } : {}),
945
958
  ...(m.clientMessageId ? { clientMessageId: m.clientMessageId } : {}),
946
959
  role: m.role,
947
- // Flat plain-text body the legacy Swift client reads directly; see the
948
- // `content` field on ConversationMessageSchema for why this must stay.
960
+ // Flat plain-text body; see the `content` field on
961
+ // ConversationMessageSchema for why this must stay.
949
962
  content: text,
950
963
  timestamp: new Date(displayTimestamp).toISOString(),
951
964
  attachments: msgAttachments,
@@ -1133,6 +1146,7 @@ export async function handleSendMessage(
1133
1146
  bootstrapTemplate?: string;
1134
1147
  initialMessage?: string;
1135
1148
  skills?: string[];
1149
+ title?: string;
1136
1150
  };
1137
1151
  };
1138
1152
 
@@ -1310,8 +1324,13 @@ export async function handleSendMessage(
1310
1324
  : sourceChannel === "vellum"
1311
1325
  ? crypto.randomUUID()
1312
1326
  : `default:${sourceChannel}:${sourceInterface}`;
1327
+ // An onboarding flow may supply an explicit title for the conversation it
1328
+ // mints behind the scenes (e.g. the research pass) so it isn't left with an
1329
+ // auto-generated title. Applied only when this call creates the row.
1330
+ const onboardingTitle = body.onboarding?.title?.trim() || undefined;
1313
1331
  mapping = getOrCreateConversation(resolvedConversationKey, {
1314
1332
  conversationType: "standard",
1333
+ title: onboardingTitle,
1315
1334
  });
1316
1335
  }
1317
1336
 
@@ -2717,6 +2736,12 @@ export const ROUTES: RouteDefinition[] = [
2717
2736
  bootstrapTemplate: z.string().optional(),
2718
2737
  initialMessage: z.string().optional(),
2719
2738
  skills: z.array(z.string()).optional(),
2739
+ title: z
2740
+ .string()
2741
+ .optional()
2742
+ .describe(
2743
+ "Explicit title for the conversation minted on this first message. Persisted as a user-set title (never overwritten by the auto-titler). Used by onboarding flows that mint a conversation behind the scenes.",
2744
+ ),
2720
2745
  })
2721
2746
  .describe("PreChat onboarding context, sent on the first message only")
2722
2747
  .optional(),