@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
@@ -145,6 +145,40 @@ describe("AssistantConfigSchema", () => {
145
145
  expect(result.services["web-search"].mode).toBe("your-own");
146
146
  });
147
147
 
148
+ test("accepts Firecrawl as a web search provider", () => {
149
+ const result = AssistantConfigSchema.parse({
150
+ services: {
151
+ "web-search": { mode: "your-own", provider: "firecrawl" },
152
+ },
153
+ });
154
+
155
+ expect(result.services["web-search"].provider).toBe("firecrawl");
156
+ expect(result.services["web-search"].mode).toBe("your-own");
157
+ });
158
+
159
+ test("defaults the web-fetch provider to the built-in fetcher", () => {
160
+ const result = AssistantConfigSchema.parse({});
161
+ expect(result.services["web-fetch"].provider).toBe("default");
162
+ });
163
+
164
+ test("accepts Firecrawl as a web fetch provider", () => {
165
+ const result = AssistantConfigSchema.parse({
166
+ services: {
167
+ "web-fetch": { mode: "your-own", provider: "firecrawl" },
168
+ },
169
+ });
170
+
171
+ expect(result.services["web-fetch"].provider).toBe("firecrawl");
172
+ });
173
+
174
+ test("rejects an unknown web-fetch provider", () => {
175
+ expect(() =>
176
+ AssistantConfigSchema.parse({
177
+ services: { "web-fetch": { provider: "nope" } },
178
+ }),
179
+ ).toThrow();
180
+ });
181
+
148
182
  test("accepts valid complete config", () => {
149
183
  const input = {
150
184
  llm: {
@@ -185,6 +219,7 @@ describe("AssistantConfigSchema", () => {
185
219
  speed: "standard",
186
220
  verbosity: "medium",
187
221
  temperature: null,
222
+ topP: null,
188
223
  thinking: { enabled: true, streamThinking: true },
189
224
  contextWindow: {
190
225
  enabled: true,
@@ -231,24 +231,6 @@ describe("ConfigWatcher workspace file handlers", () => {
231
231
  expect(evictCallCount).toBe(1);
232
232
  });
233
233
 
234
- test("SOUL.md change triggers identity intro refetch notification", async () => {
235
- let introCallCount = 0;
236
- watcher.start(
237
- onConversationEvict,
238
- undefined,
239
- undefined,
240
- undefined,
241
- undefined,
242
- undefined,
243
- () => {
244
- introCallCount += 1;
245
- },
246
- );
247
- simulateFileChange(WORKSPACE_DIR, "SOUL.md");
248
- await new Promise((r) => setTimeout(r, WAIT_MS));
249
- expect(introCallCount).toBe(1);
250
- });
251
-
252
234
  test("IDENTITY.md change triggers onConversationEvict", async () => {
253
235
  watcher.start(onConversationEvict);
254
236
  simulateFileChange(WORKSPACE_DIR, "IDENTITY.md");
@@ -178,7 +178,7 @@ describe("bridgeConfirmationRequestToGuardian", () => {
178
178
 
179
179
  expect("skipped" in result && result.skipped).toBe(true);
180
180
  if ("skipped" in result) {
181
- expect(result.reason).toBe("not_trusted_contact");
181
+ expect(result.reason).toBe("not_bridgeable_trust_class");
182
182
  }
183
183
  expect(emittedSignals).toHaveLength(0);
184
184
  });
@@ -199,7 +199,7 @@ describe("bridgeConfirmationRequestToGuardian", () => {
199
199
 
200
200
  expect("skipped" in result && result.skipped).toBe(true);
201
201
  if ("skipped" in result) {
202
- expect(result.reason).toBe("not_trusted_contact");
202
+ expect(result.reason).toBe("not_bridgeable_trust_class");
203
203
  }
204
204
  expect(emittedSignals).toHaveLength(0);
205
205
  });
@@ -75,7 +75,6 @@ describe("upsertContact user_file selection", () => {
75
75
  {
76
76
  type: "vellum",
77
77
  address: "vellum-principal-abc",
78
- externalUserId: "vellum-principal-abc",
79
78
  },
80
79
  ],
81
80
  });
@@ -91,7 +90,6 @@ describe("upsertContact user_file selection", () => {
91
90
  {
92
91
  type: "slack",
93
92
  address: "u123456",
94
- externalUserId: "U123456",
95
93
  externalChatId: "D987654",
96
94
  },
97
95
  ],
@@ -109,7 +107,6 @@ describe("upsertContact user_file selection", () => {
109
107
  {
110
108
  type: "slack",
111
109
  address: "ualice",
112
- externalUserId: "UALICE",
113
110
  externalChatId: "DALICE",
114
111
  },
115
112
  ],
@@ -125,7 +122,6 @@ describe("upsertContact user_file selection", () => {
125
122
  {
126
123
  type: "slack",
127
124
  address: "ubob1",
128
- externalUserId: "UBOB1",
129
125
  externalChatId: "DBOB1",
130
126
  },
131
127
  ],
@@ -137,7 +133,6 @@ describe("upsertContact user_file selection", () => {
137
133
  {
138
134
  type: "slack",
139
135
  address: "ubob2",
140
- externalUserId: "UBOB2",
141
136
  externalChatId: "DBOB2",
142
137
  },
143
138
  ],
@@ -164,7 +159,6 @@ describe("upsertContact user_file selection", () => {
164
159
  {
165
160
  type: "phone",
166
161
  address: "+15550000",
167
- externalUserId: "+15550000",
168
162
  externalChatId: "+15550000",
169
163
  },
170
164
  ],
@@ -240,6 +240,35 @@ describe("contact_search tool", () => {
240
240
  });
241
241
  });
242
242
 
243
+ // ── search_contacts route (HTTP/IPC compat shim) ─────────────────────
244
+
245
+ describe("search_contacts route", () => {
246
+ beforeEach(clearContacts);
247
+
248
+ test("includes externalUserId (= address) on channels for older clients", () => {
249
+ const seeded = upsertFixture({
250
+ display_name: "Dana",
251
+ channels: [{ type: "slack", address: "U12345ABC" }],
252
+ });
253
+ const seededAddress = seeded.channels[0]!.address;
254
+
255
+ const searchRoute = ROUTES.find(
256
+ (r) => r.operationId === "search_contacts",
257
+ )!;
258
+ const contacts = searchRoute.handler({
259
+ body: { channelAddress: seededAddress },
260
+ }) as unknown as Array<{
261
+ channels: Array<{ address: string; externalUserId?: string }>;
262
+ }>;
263
+
264
+ expect(contacts.length).toBeGreaterThanOrEqual(1);
265
+ const channel = contacts[0]!.channels[0]!;
266
+ // The route re-derives the compat field from address, so SDK/macOS
267
+ // clients that read externalUserId keep working.
268
+ expect(channel.externalUserId).toBe(channel.address);
269
+ });
270
+ });
271
+
243
272
  // ── contact_merge ───────────────────────────────────────────────────
244
273
 
245
274
  describe("contact_merge tool", () => {
@@ -366,6 +366,7 @@ import { runAgentLoopImpl } from "../daemon/conversation-agent-loop.js";
366
366
  interface CapturedAgentLoopRun {
367
367
  callSite: LLMCallSite | undefined;
368
368
  overrideProfile: string | undefined;
369
+ forceOverrideProfile: boolean | undefined;
369
370
  resolvedOverrideProfile: string | undefined;
370
371
  resolvedMaxInputTokens: number | undefined;
371
372
  }
@@ -383,6 +384,7 @@ function makeCtx(
383
384
  captured.push({
384
385
  callSite: options.callSite,
385
386
  overrideProfile: options.overrideProfile,
387
+ forceOverrideProfile: options.forceOverrideProfile,
386
388
  resolvedOverrideProfile: options.resolveOverrideProfile?.(),
387
389
  resolvedMaxInputTokens: options.resolveContextWindow?.().maxInputTokens,
388
390
  });
@@ -620,6 +622,26 @@ describe("runAgentLoopImpl — per-conversation inferenceProfile", () => {
620
622
  expect(captured.length).toBeGreaterThan(0);
621
623
  for (const call of captured) {
622
624
  expect(call.overrideProfile).toBe("fast");
625
+ expect(call.forceOverrideProfile).toBeUndefined();
626
+ }
627
+ });
628
+
629
+ test("explicit options.forceOverrideProfile is passed to AgentLoop.run", async () => {
630
+ const captured: CapturedAgentLoopRun[] = [];
631
+ const ctx = makeCtx(captured, {
632
+ conversationType: "background",
633
+ inferenceProfile: null,
634
+ });
635
+
636
+ await runAgentLoopImpl(ctx, "hello", "msg-1", () => {}, {
637
+ overrideProfile: "fast",
638
+ forceOverrideProfile: true,
639
+ });
640
+
641
+ expect(captured.length).toBeGreaterThan(0);
642
+ for (const call of captured) {
643
+ expect(call.overrideProfile).toBe("fast");
644
+ expect(call.forceOverrideProfile).toBe(true);
623
645
  }
624
646
  });
625
647
 
@@ -68,6 +68,7 @@ const defaultLlmConfig: LLMConfig = {
68
68
  speed: "standard" as const,
69
69
  verbosity: "medium" as const,
70
70
  temperature: null,
71
+ topP: null,
71
72
  thinking: { enabled: false, streamThinking: true },
72
73
  contextWindow: {
73
74
  enabled: true,
@@ -1748,6 +1748,64 @@ describe("session-agent-loop", () => {
1748
1748
  // THEN the queue is drained with the loop-complete reason
1749
1749
  expect(drainReason).toBe("loop_complete");
1750
1750
  });
1751
+
1752
+ test("abort watchdog drives a wedged turn to its finally", async () => {
1753
+ // GIVEN a provider whose call wedges: it acknowledges the user cancel
1754
+ // (aborts the signal) but its promise never settles and never observes
1755
+ // the signal — the exact condition that latched `processing` true.
1756
+ const events: ServerMessage[] = [];
1757
+ const abortController = new AbortController();
1758
+ let drainReason: string | undefined;
1759
+ // The provider's call wedges on this promise. It settles only on test
1760
+ // teardown so the abandoned `run()` can unwind cleanly instead of leaking
1761
+ // background work (e.g. partial-persist debounce timers) into later tests.
1762
+ let releaseHang: (reason: unknown) => void = () => {};
1763
+ const hang = new Promise<never>((_, reject) => {
1764
+ releaseHang = reject;
1765
+ });
1766
+ const provider: Provider = {
1767
+ name: "mock-provider",
1768
+ sendMessage(_messages, _options) {
1769
+ abortController.abort();
1770
+ // Never observes the signal — the exact condition that latched
1771
+ // `processing` true before the watchdog existed.
1772
+ return hang;
1773
+ },
1774
+ };
1775
+ const ctx = makeCtx({
1776
+ loopProvider: provider,
1777
+ abortController,
1778
+ // Fire the watchdog quickly instead of the ~45s production default.
1779
+ abortWatchdogMs: 30,
1780
+ drainQueue: (reason: string) => {
1781
+ drainReason = reason;
1782
+ },
1783
+ } as unknown as Partial<Conversation>);
1784
+
1785
+ try {
1786
+ // WHEN the orchestrator runs the turn
1787
+ await runAgentLoopImpl(ctx, "hi", "msg-1", (msg) => events.push(msg));
1788
+
1789
+ // THEN the watchdog forces the turn to its finally: processing clears,
1790
+ // the abort controller is torn down, the queue drains, and the user
1791
+ // sees a cancellation (not an error).
1792
+ expect(ctx.isProcessing()).toBe(false);
1793
+ expect(ctx.abortController).toBeNull();
1794
+ expect(drainReason).toBe("loop_complete");
1795
+ expect(
1796
+ events.find((e) => e.type === "generation_cancelled"),
1797
+ ).toBeDefined();
1798
+ expect(
1799
+ events.find((e) => e.type === "conversation_error"),
1800
+ ).toBeUndefined();
1801
+ } finally {
1802
+ // Let the abandoned run() reject and unwind, then flush microtasks.
1803
+ releaseHang(
1804
+ new DOMException("The operation was aborted", "AbortError"),
1805
+ );
1806
+ await new Promise((resolve) => setTimeout(resolve, 0));
1807
+ }
1808
+ });
1751
1809
  });
1752
1810
 
1753
1811
  describe("stale pending surface cleanup", () => {
@@ -76,7 +76,6 @@ function ensureTestContact(): void {
76
76
  {
77
77
  type: "telegram",
78
78
  address: "telegram-user-default",
79
- externalUserId: "telegram-user-default",
80
79
  status: "active",
81
80
  policy: "allow",
82
81
  },
@@ -674,15 +674,13 @@ describe("loadFromDb metadata injection rehydration", () => {
674
674
  });
675
675
 
676
676
  test("internal-channel trusted_contact view still rehydrates memoryV2StaticBlock", async () => {
677
- // Regression: the prior `!isUntrustedTrustClass(trustClass)` gate
678
- // blocked any non-guardian view from rehydrating personal memory,
679
- // including legitimate internal flows (e.g. trusted_contact actors
680
- // arriving over the internal `"vellum"` channel). Injection time
681
- // uses `shouldExposePersonalMemory`, which keys on `sourceChannel`
682
- // rather than `trustClass` and exposes personal memory for
683
- // `sourceChannel === "vellum"` regardless of actor trust class. The
684
- // rehydrate gate must match so a daemon-restart reload of the same
685
- // conversation produces an identical prefix.
677
+ // Rehydration keys on `sourceChannel`, not `trustClass`: injection uses
678
+ // `shouldExposePersonalMemory`, which exposes personal memory whenever
679
+ // `sourceChannel === "vellum"` regardless of actor trust class. So a
680
+ // trusted_contact view arriving over the internal `"vellum"` channel
681
+ // rehydrates `memoryV2StaticBlock`. The rehydrate gate must match
682
+ // injection so a daemon-restart reload of the same conversation produces
683
+ // an identical prefix.
686
684
  mockConversation = defaultConv();
687
685
  mockDbMessages = [
688
686
  {
@@ -552,3 +552,104 @@ describe("loadFromDb history repair", () => {
552
552
  ]);
553
553
  });
554
554
  });
555
+
556
+ describe("loadFromDb turn-count rehydration", () => {
557
+ beforeEach(() => {
558
+ nextMockMessageId = 1;
559
+ mockConversation = {
560
+ id: "conv-1",
561
+ contextSummary: null,
562
+ contextCompactedMessageCount: 0,
563
+ totalInputTokens: 0,
564
+ totalOutputTokens: 0,
565
+ totalEstimatedCost: 0,
566
+ };
567
+ });
568
+
569
+ const userText = (text: string) => ({
570
+ role: "user",
571
+ content: JSON.stringify([{ type: "text", text }]),
572
+ });
573
+ const assistantText = (text: string) => ({
574
+ role: "assistant",
575
+ content: JSON.stringify([{ type: "text", text }]),
576
+ });
577
+ const assistantToolUse = (id: string) => ({
578
+ role: "assistant",
579
+ content: JSON.stringify([
580
+ { type: "tool_use", id, name: "bash", input: { cmd: "ls" } },
581
+ ]),
582
+ });
583
+ const toolResult = (id: string) => ({
584
+ role: "user",
585
+ content: JSON.stringify([
586
+ { type: "tool_result", tool_use_id: id, content: "ok" },
587
+ ]),
588
+ });
589
+ const withIds = (msgs: Array<{ role: string; content: string }>) =>
590
+ msgs.map((m, i) => ({ id: `m${i}`, ...m }));
591
+
592
+ test("restores turnCount from persisted history rather than resetting to 0", async () => {
593
+ // Three completed human turns persisted before this conversation object
594
+ // was (re)created — e.g. after an idle eviction or daemon restart.
595
+ mockDbMessages = withIds([
596
+ userText("Hello"),
597
+ assistantText("Hi"),
598
+ userText("How are you?"),
599
+ assistantText("Good"),
600
+ userText("Bye"),
601
+ assistantText("Later"),
602
+ ]);
603
+
604
+ const conversation = makeConversation();
605
+ // Fresh object starts at 0 (the bug: it would stay 0 after reload).
606
+ expect(conversation.turnCount).toBe(0);
607
+
608
+ await conversation.loadFromDb();
609
+
610
+ expect(conversation.turnCount).toBe(3);
611
+ });
612
+
613
+ test("counts a multi-iteration tool-use turn as a single turn", async () => {
614
+ // One real user message; the tool_result user messages are continuations
615
+ // within the same turn, not new turns.
616
+ mockDbMessages = withIds([
617
+ userText("convert the voice memo"),
618
+ assistantToolUse("tu_1"),
619
+ toolResult("tu_1"),
620
+ assistantToolUse("tu_2"),
621
+ toolResult("tu_2"),
622
+ assistantText("done"),
623
+ ]);
624
+
625
+ const conversation = makeConversation();
626
+ await conversation.loadFromDb();
627
+
628
+ expect(conversation.turnCount).toBe(1);
629
+ });
630
+
631
+ test("counts only real user turns when tool iterations are interleaved", async () => {
632
+ mockDbMessages = withIds([
633
+ userText("q1"),
634
+ assistantToolUse("tu_1"),
635
+ toolResult("tu_1"),
636
+ assistantText("a1"),
637
+ userText("q2"),
638
+ assistantText("a2"),
639
+ ]);
640
+
641
+ const conversation = makeConversation();
642
+ await conversation.loadFromDb();
643
+
644
+ expect(conversation.turnCount).toBe(2);
645
+ });
646
+
647
+ test("empty history yields turnCount 0", async () => {
648
+ mockDbMessages = [];
649
+
650
+ const conversation = makeConversation();
651
+ await conversation.loadFromDb();
652
+
653
+ expect(conversation.turnCount).toBe(0);
654
+ });
655
+ });
@@ -222,13 +222,16 @@ describe("handleSendMessage canonical guardian reply interception", () => {
222
222
  const routerCall = (routeGuardianReplyMock as any).mock
223
223
  .calls[0][0] as Record<string, unknown>;
224
224
  expect(routerCall.messageText).toBe("05BECB approve");
225
- expect(routerCall.pendingRequestIds).toEqual(["access-req-1"]);
225
+ expect(routerCall.pendingScope).toEqual({
226
+ mode: "scoped",
227
+ requestIds: ["access-req-1"],
228
+ });
226
229
  expect(addMessageMock).toHaveBeenCalledTimes(2);
227
230
  expect(persistUserMessage).toHaveBeenCalledTimes(0);
228
231
  expect(runAgentLoop).toHaveBeenCalledTimes(0);
229
232
  });
230
233
 
231
- test("passes empty pendingRequestIds array when no canonical hints are found", async () => {
234
+ test("passes a blocked scope when no canonical hints are found", async () => {
232
235
  listPendingByDestinationMock.mockReturnValue([]);
233
236
  listCanonicalMock.mockReturnValue([]);
234
237
  routeGuardianReplyMock.mockResolvedValue({
@@ -301,7 +304,7 @@ describe("handleSendMessage canonical guardian reply interception", () => {
301
304
  expect(routeGuardianReplyMock).toHaveBeenCalledTimes(1);
302
305
  const routerCall = (routeGuardianReplyMock as any).mock
303
306
  .calls[0][0] as Record<string, unknown>;
304
- expect(routerCall.pendingRequestIds).toEqual([]);
307
+ expect(routerCall.pendingScope).toEqual({ mode: "blocked" });
305
308
  expect(persistUserMessage).toHaveBeenCalledTimes(1);
306
309
  expect(runAgentLoop).toHaveBeenCalledTimes(1);
307
310
  });
@@ -384,15 +387,15 @@ describe("handleSendMessage canonical guardian reply interception", () => {
384
387
  expect(routeGuardianReplyMock).toHaveBeenCalledTimes(1);
385
388
  const routerCall = (routeGuardianReplyMock as any).mock
386
389
  .calls[0][0] as Record<string, unknown>;
387
- expect(routerCall.pendingRequestIds).toEqual([
388
- "tool-approval-live",
389
- "access-req-1",
390
- ]);
391
- expect(
392
- (routerCall.pendingRequestIds as string[]).includes(
393
- "tool-approval-stale",
394
- ),
395
- ).toBe(false);
390
+ const scope = routerCall.pendingScope as {
391
+ mode: string;
392
+ requestIds: string[];
393
+ };
394
+ expect(scope).toEqual({
395
+ mode: "scoped",
396
+ requestIds: ["tool-approval-live", "access-req-1"],
397
+ });
398
+ expect(scope.requestIds.includes("tool-approval-stale")).toBe(false);
396
399
  });
397
400
 
398
401
  test("text fallback: request-code approve routes through guardian reply router", async () => {
@@ -10,9 +10,11 @@ mock.module("../util/logger.js", () => ({
10
10
  }),
11
11
  }));
12
12
 
13
- // Usage-data collection is enabled so recordActivationEvent writes rows.
14
- mock.module("../config/loader.js", () => ({
15
- getConfig: () => ({ collectUsageData: true }),
13
+ // Analytics consent is granted so recordActivationEvent writes rows.
14
+ let shareAnalytics = true;
15
+
16
+ mock.module("../platform/consent-cache.js", () => ({
17
+ getCachedShareAnalytics: () => shareAnalytics,
16
18
  }));
17
19
 
18
20
  let broadcastedMessages: ServerMessage[] = [];
@@ -117,6 +119,7 @@ async function showTaggedChoice(
117
119
 
118
120
  describe("activation moment emission from ui_show surface commits", () => {
119
121
  beforeEach(() => {
122
+ shareAnalytics = true;
120
123
  broadcastedMessages = [];
121
124
  resetTables();
122
125
  });
@@ -69,10 +69,38 @@ import {
69
69
  describe("conversation-title-service", () => {
70
70
  beforeEach(() => {
71
71
  mockRunBtwSidechain.mockClear();
72
+ mockRunBtwSidechain.mockImplementation(
73
+ async (_params: Record<string, unknown>) => ({
74
+ text: "Project kickoff",
75
+ hadTextDeltas: true,
76
+ response: {
77
+ content: [{ type: "text", text: "Project kickoff" }],
78
+ model: "test-model",
79
+ usage: { inputTokens: 10, outputTokens: 5 },
80
+ stopReason: "end_turn",
81
+ },
82
+ }),
83
+ );
72
84
  mockGetConversation.mockClear();
85
+ mockGetConversation.mockImplementation(
86
+ (_conversationId: string) =>
87
+ ({
88
+ title: "Generating title...",
89
+ isAutoTitle: 1,
90
+ }) as {
91
+ title: string;
92
+ isAutoTitle: number;
93
+ },
94
+ );
73
95
  mockGetMessages.mockClear();
96
+ mockGetMessages.mockImplementation(() => [
97
+ { role: "user", content: "first message" },
98
+ { role: "assistant", content: "first reply" },
99
+ { role: "user", content: "follow-up" },
100
+ ]);
74
101
  mockUpdateConversationTitle.mockClear();
75
102
  mockGetConfiguredProvider.mockClear();
103
+ mockGetConfiguredProvider.mockImplementation(async () => null);
76
104
  mockPublishConversationTitleChanged.mockClear();
77
105
  });
78
106
 
@@ -232,6 +260,30 @@ describe("conversation-title-service", () => {
232
260
  );
233
261
  });
234
262
 
263
+ test("fallback retry skips regeneration after a successful initial title", async () => {
264
+ mockGetConversation.mockReturnValueOnce({
265
+ title: "Project kickoff",
266
+ isAutoTitle: 1,
267
+ });
268
+
269
+ const provider = {
270
+ name: "test-provider",
271
+ sendMessage: mock(async () => {
272
+ throw new Error("should not call directly");
273
+ }),
274
+ };
275
+
276
+ const result = await regenerateConversationTitle({
277
+ conversationId: "conv-1",
278
+ provider,
279
+ onlyIfReplaceable: true,
280
+ });
281
+
282
+ expect(result).toEqual({ title: "Project kickoff", updated: false });
283
+ expect(mockRunBtwSidechain).not.toHaveBeenCalled();
284
+ expect(mockUpdateConversationTitle).not.toHaveBeenCalled();
285
+ });
286
+
235
287
  test("rejects meta-failure outputs like 'Missing Context' and uses fallback", async () => {
236
288
  mockRunBtwSidechain.mockImplementationOnce(async () => ({
237
289
  text: "Missing Context",
@@ -482,6 +534,16 @@ describe("conversation-title-service", () => {
482
534
 
483
535
  // Both calls went through — failure didn't break the chain
484
536
  expect(mockRunBtwSidechain).toHaveBeenCalledTimes(2);
537
+ const firstUpdate = (
538
+ mockUpdateConversationTitle.mock.calls as unknown as Array<
539
+ [string, string, number?]
540
+ >
541
+ ).find((c) => c[0] === "conv-1");
542
+ expect(firstUpdate).toEqual([
543
+ "conv-1",
544
+ "Untitled Conversation",
545
+ AUTO_TITLE_DETERMINISTIC,
546
+ ]);
485
547
  // Second conversation got a proper title
486
548
  const secondUpdate = (
487
549
  mockUpdateConversationTitle.mock.calls as unknown as string[][]