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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (433) hide show
  1. package/docs/activation-funnel-telemetry.md +24 -18
  2. package/node_modules/@vellumai/gateway-client/src/admission-policy-contract.ts +97 -0
  3. package/node_modules/@vellumai/gateway-client/src/inbound-contract.ts +10 -0
  4. package/node_modules/@vellumai/gateway-client/src/index.ts +15 -0
  5. package/openapi.yaml +852 -15
  6. package/package.json +1 -1
  7. package/src/__tests__/access-request-card-view.test.ts +98 -0
  8. package/src/__tests__/access-request-seed-content-blocks.test.ts +2 -4
  9. package/src/__tests__/actor-trust-resolver-address-fallback.test.ts +59 -7
  10. package/src/__tests__/agent-loop-compaction-strip.test.ts +17 -16
  11. package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +16 -13
  12. package/src/__tests__/app-compiler.test.ts +15 -1
  13. package/src/__tests__/auth-fallback-events-store.test.ts +6 -14
  14. package/src/__tests__/call-pointer-messages.test.ts +28 -0
  15. package/src/__tests__/cancel-clears-processing.test.ts +89 -0
  16. package/src/__tests__/channel-approval-routes.test.ts +0 -4
  17. package/src/__tests__/channel-inbound-disk-pressure.test.ts +5 -15
  18. package/src/__tests__/cli-memory-v2-reembed-skills.test.ts +3 -4
  19. package/src/__tests__/compactor-image-manifest-trust.test.ts +21 -1
  20. package/src/__tests__/compactor-summary-call-truncation.test.ts +223 -0
  21. package/src/__tests__/config-loader-backfill.test.ts +174 -30
  22. package/src/__tests__/config-schema.test.ts +35 -0
  23. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +2 -2
  24. package/src/__tests__/contact-store-user-file.test.ts +0 -6
  25. package/src/__tests__/contacts-tools.test.ts +29 -0
  26. package/src/__tests__/conversation-agent-loop-overflow.test.ts +1 -0
  27. package/src/__tests__/conversation-agent-loop.test.ts +58 -0
  28. package/src/__tests__/conversation-attention-telegram.test.ts +0 -1
  29. package/src/__tests__/conversation-lifecycle.test.ts +7 -9
  30. package/src/__tests__/conversation-load-history-repair.test.ts +101 -0
  31. package/src/__tests__/conversation-routes-guardian-reply.test.ts +15 -12
  32. package/src/__tests__/conversation-surfaces-activation-emit.test.ts +6 -3
  33. package/src/__tests__/conversation-title-service.test.ts +62 -0
  34. package/src/__tests__/credential-execution-shell-lockdown.test.ts +18 -11
  35. package/src/__tests__/credential-prompt-route.test.ts +1 -0
  36. package/src/__tests__/credential-security-invariants.test.ts +2 -0
  37. package/src/__tests__/disk-pressure-policy.test.ts +12 -0
  38. package/src/__tests__/disk-usage.test.ts +65 -0
  39. package/src/__tests__/dynamic-page-surface.test.ts +51 -0
  40. package/src/__tests__/gateway-flag-listener.test.ts +110 -1
  41. package/src/__tests__/guardian-binding-drift-heal.test.ts +1 -1
  42. package/src/__tests__/guardian-card-withdrawal.test.ts +403 -0
  43. package/src/__tests__/guardian-decision-primitive-canonical.test.ts +5 -3
  44. package/src/__tests__/guardian-grant-minting.test.ts +3 -35
  45. package/src/__tests__/guardian-routing-invariants.test.ts +64 -26
  46. package/src/__tests__/guardian-routing-state.test.ts +0 -1
  47. package/src/__tests__/headless-browser-mode.test.ts +10 -0
  48. package/src/__tests__/headless-browser-navigate.test.ts +8 -3
  49. package/src/__tests__/helpers/create-guardian-binding.ts +0 -1
  50. package/src/__tests__/host-browser-proxy.test.ts +87 -0
  51. package/src/__tests__/injector-v3-suppression.test.ts +27 -20
  52. package/src/__tests__/internal-telemetry-routes.test.ts +6 -14
  53. package/src/__tests__/invite-redemption-service.test.ts +0 -3
  54. package/src/__tests__/llm-catalog-parity.test.ts +30 -1
  55. package/src/__tests__/llm-resolver.test.ts +21 -0
  56. package/src/__tests__/llm-schema.test.ts +1 -0
  57. package/src/__tests__/managed-profile-guard.test.ts +163 -4
  58. package/src/__tests__/mcp-health-check.test.ts +6 -7
  59. package/src/__tests__/media-stream-server-integration.test.ts +317 -13
  60. package/src/__tests__/path-policy.test.ts +34 -0
  61. package/src/__tests__/persona-resolver.test.ts +38 -0
  62. package/src/__tests__/plugin-api-provider.test.ts +24 -0
  63. package/src/__tests__/plugin-tool-contribution.test.ts +6 -3
  64. package/src/__tests__/post-compaction-reinjection-idempotency.test.ts +214 -0
  65. package/src/__tests__/provider-send-message-override-profile.test.ts +76 -0
  66. package/src/__tests__/reaction-persistence.test.ts +150 -29
  67. package/src/__tests__/relay-server.test.ts +285 -0
  68. package/src/__tests__/runtime-attachment-metadata.test.ts +0 -1
  69. package/src/__tests__/scheduler-reuse-conversation.test.ts +8 -5
  70. package/src/__tests__/skill-execute-input.test.ts +5 -0
  71. package/src/__tests__/skills.test.ts +51 -0
  72. package/src/__tests__/slack-notification-approval-card.test.ts +176 -0
  73. package/src/__tests__/slack-reaction-canonical-approval.test.ts +285 -0
  74. package/src/__tests__/subagent-tools.test.ts +150 -0
  75. package/src/__tests__/task-progress-nudge-hook.test.ts +1 -1
  76. package/src/__tests__/title-generate-hook.test.ts +100 -3
  77. package/src/__tests__/tool-approval-seed-content-blocks.test.ts +1 -1
  78. package/src/__tests__/tool-audit-listener.test.ts +7 -7
  79. package/src/__tests__/tool-executor-lifecycle-events.test.ts +6 -3
  80. package/src/__tests__/trusted-contact-approval-notifier.test.ts +4 -2
  81. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +220 -3
  82. package/src/__tests__/trusted-contact-verification.test.ts +2 -4
  83. package/src/__tests__/twilio-routes.test.ts +81 -1
  84. package/src/__tests__/voice-invite-redemption.test.ts +0 -1
  85. package/src/__tests__/weak-open-model.test.ts +30 -0
  86. package/src/__tests__/workspace-migration-105-enable-memory-v3-live-for-new-workspaces.test.ts +149 -0
  87. package/src/__tests__/workspace-migration-108-drop-balanced-economy-profile.test.ts +285 -0
  88. package/src/__tests__/workspace-migration-add-send-diagnostics.test.ts +1 -1
  89. package/src/__tests__/workspace-migration-drop-collect-usage-data.test.ts +118 -0
  90. package/src/__tests__/workspace-migration-drop-send-diagnostics.test.ts +118 -0
  91. package/src/a2a/__tests__/e2e-a2a-channel.test.ts +0 -4
  92. package/src/agent/loop.ts +33 -33
  93. package/src/api/events/tool-result.ts +6 -0
  94. package/src/api/events/workflow-completed.ts +53 -0
  95. package/src/api/events/workflow-leaf-finished.ts +38 -0
  96. package/src/api/events/workflow-leaf-started.ts +35 -0
  97. package/src/api/events/workflow-progress.ts +32 -0
  98. package/src/api/events/workflow-started.ts +31 -0
  99. package/src/api/index.ts +40 -0
  100. package/src/api/responses/conversation-message.ts +26 -0
  101. package/src/api/responses/home.ts +26 -0
  102. package/src/api/responses/workflow-journal.ts +53 -0
  103. package/src/approvals/guardian-card-withdrawal.ts +145 -0
  104. package/src/approvals/guardian-decision-primitive.ts +26 -3
  105. package/src/approvals/guardian-request-resolvers.ts +181 -78
  106. package/src/calls/__tests__/channel-admission-reader.test.ts +132 -0
  107. package/src/calls/__tests__/relay-setup-router.test.ts +350 -0
  108. package/src/calls/call-pointer-messages.ts +10 -4
  109. package/src/calls/channel-admission-reader.ts +104 -0
  110. package/src/calls/guardian-dispatch.ts +17 -45
  111. package/src/calls/media-stream-server.ts +84 -2
  112. package/src/calls/relay-server.ts +66 -0
  113. package/src/calls/relay-setup-router.ts +82 -1
  114. package/src/calls/twilio-routes.ts +17 -8
  115. package/src/cli/commands/clients.ts +3 -0
  116. package/src/cli/commands/{__tests__ → memory/__tests__}/memory-v2-compare-render.test.ts +1 -1
  117. package/src/cli/commands/{__tests__ → memory/__tests__}/memory-v2.test.ts +8 -7
  118. package/src/cli/commands/{__tests__ → memory/__tests__}/memory-v3.test.ts +5 -4
  119. package/src/cli/commands/memory/index.ts +30 -0
  120. package/src/cli/commands/{memory-v2-compare-render.ts → memory/memory-v2-compare-render.ts} +1 -1
  121. package/src/cli/commands/{memory-v2.ts → memory/memory-v2.ts} +6 -15
  122. package/src/cli/commands/{memory-v3.ts → memory/memory-v3.ts} +97 -11
  123. package/src/cli/commands/oauth/status.test.ts +36 -0
  124. package/src/cli/commands/oauth/status.ts +23 -3
  125. package/src/cli/commands/plugins.ts +57 -5
  126. package/src/cli/lib/__tests__/inspect-plugin.test.ts +54 -0
  127. package/src/cli/lib/__tests__/merge-plugin-tree.test.ts +134 -4
  128. package/src/cli/lib/__tests__/plugin-surfaces.test.ts +111 -0
  129. package/src/cli/lib/__tests__/upgrade-plugin.test.ts +53 -11
  130. package/src/cli/lib/inspect-plugin.ts +12 -1
  131. package/src/cli/lib/merge-plugin-tree.ts +149 -49
  132. package/src/cli/lib/plugin-surfaces.ts +104 -0
  133. package/src/cli/lib/upgrade-plugin.ts +64 -36
  134. package/src/cli/program.ts +2 -4
  135. package/src/config/__tests__/sync-gated-profiles.test.ts +368 -0
  136. package/src/config/assistant-feature-flags.ts +22 -7
  137. package/src/config/bundled-skills/contacts/tools/contact-search.ts +0 -1
  138. package/src/config/bundled-skills/messaging/SKILL.md +6 -4
  139. package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +9 -8
  140. package/src/config/bundled-skills/workflows/SKILL.md +14 -7
  141. package/src/config/call-site-defaults.ts +3 -0
  142. package/src/config/feature-flag-registry.json +49 -18
  143. package/src/config/llm-resolver.ts +3 -0
  144. package/src/config/memory-v3-gate.ts +11 -0
  145. package/src/config/schema.ts +8 -6
  146. package/src/config/schemas/__tests__/memory-v3.test.ts +1 -0
  147. package/src/config/schemas/call-site-catalog.ts +7 -0
  148. package/src/config/schemas/channels.ts +11 -0
  149. package/src/config/schemas/llm.ts +31 -0
  150. package/src/config/schemas/memory-lifecycle.ts +3 -7
  151. package/src/config/schemas/memory-v3.ts +6 -0
  152. package/src/config/schemas/services.ts +18 -0
  153. package/src/config/seed-inference-profiles.ts +94 -34
  154. package/src/config/skills.ts +21 -0
  155. package/src/config/sync-gated-profiles.ts +220 -0
  156. package/src/contacts/contact-store.ts +2 -10
  157. package/src/contacts/contacts-write.ts +1 -2
  158. package/src/contacts/types.ts +0 -1
  159. package/src/context/compactor.ts +86 -52
  160. package/src/context/strip-injections.ts +58 -10
  161. package/src/context/token-estimator.ts +1 -1
  162. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +3 -2
  163. package/src/daemon/conversation-agent-loop-handlers.ts +2 -0
  164. package/src/daemon/conversation-agent-loop.ts +100 -19
  165. package/src/daemon/conversation-history.ts +1 -1
  166. package/src/daemon/conversation-lifecycle.ts +3 -5
  167. package/src/daemon/conversation-process.ts +13 -5
  168. package/src/daemon/conversation-runtime-assembly.ts +13 -15
  169. package/src/daemon/conversation-surfaces.ts +26 -0
  170. package/src/daemon/conversation-tool-setup.ts +16 -11
  171. package/src/daemon/conversation.ts +64 -14
  172. package/src/daemon/disk-pressure-policy.ts +5 -3
  173. package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +0 -1
  174. package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +0 -1
  175. package/src/daemon/handlers/config-a2a.ts +0 -2
  176. package/src/daemon/handlers/config-channels.ts +5 -10
  177. package/src/daemon/handlers/config-slack-channel.ts +20 -0
  178. package/src/daemon/handlers/conversations.ts +107 -0
  179. package/src/daemon/host-browser-proxy.ts +41 -0
  180. package/src/daemon/lifecycle.ts +55 -20
  181. package/src/daemon/message-provenance.ts +2 -0
  182. package/src/daemon/message-types/contacts.ts +0 -1
  183. package/src/daemon/message-types/web-activity.ts +7 -1
  184. package/src/daemon/message-types/workflows.ts +83 -1
  185. package/src/daemon/tool-setup-types.ts +4 -0
  186. package/src/daemon/trust-context.ts +1 -1
  187. package/src/events/tool-audit-listener.ts +2 -2
  188. package/src/home/feed-source-enrichment.test.ts +151 -0
  189. package/src/home/feed-source-enrichment.ts +176 -0
  190. package/src/instrument.ts +18 -6
  191. package/src/ipc/__tests__/binary-result-ipc.test.ts +81 -0
  192. package/src/ipc/__tests__/clients-list-ipc.test.ts +20 -0
  193. package/src/ipc/assistant-server.ts +37 -4
  194. package/src/ipc/gateway-flag-listener.ts +18 -2
  195. package/src/memory/__tests__/auto-analysis-enqueue.test.ts +5 -16
  196. package/src/memory/__tests__/jobs-store-enqueue-gate.test.ts +7 -11
  197. package/src/memory/__tests__/memory-retrospective-enqueue.test.ts +37 -7
  198. package/src/memory/__tests__/memory-retrospective-job.test.ts +34 -0
  199. package/src/memory/__tests__/onboarding-events-store.test.ts +7 -7
  200. package/src/memory/auth-fallback-events-store.ts +2 -2
  201. package/src/memory/auto-analysis-enqueue.ts +3 -5
  202. package/src/memory/canonical-guardian-store.ts +39 -1
  203. package/src/memory/conversation-crud.ts +9 -4
  204. package/src/memory/conversation-key-store.ts +17 -2
  205. package/src/memory/conversation-title-service.ts +64 -7
  206. package/src/memory/db-init.ts +10 -0
  207. package/src/memory/embedding-backend.ts +15 -1
  208. package/src/memory/jobs-worker.ts +2 -1
  209. package/src/memory/lifecycle-events-store.ts +2 -2
  210. package/src/memory/memory-retrospective-enqueue.ts +31 -6
  211. package/src/memory/memory-retrospective-job.ts +9 -0
  212. package/src/memory/migrations/129-contact-channels-access-fields.ts +18 -9
  213. package/src/memory/migrations/131-drop-legacy-member-guardian-tables.ts +14 -2
  214. package/src/memory/migrations/289-contact-channels-unique-ext-user.ts +10 -0
  215. package/src/memory/migrations/291-contact-channels-renormalize-addresses.ts +10 -0
  216. package/src/memory/migrations/292-schedule-default-no-reuse-conversation.test.ts +67 -0
  217. package/src/memory/migrations/292-schedule-default-no-reuse-conversation.ts +25 -0
  218. package/src/memory/migrations/293-workflow-journal-leaf-tokens.ts +32 -0
  219. package/src/memory/migrations/294-drop-external-user-id.ts +31 -0
  220. package/src/memory/migrations/295-drop-approval-prompt-ts-tracker.ts +20 -0
  221. package/src/memory/migrations/296-rewrite-balanced-economy-profile-pins.test.ts +110 -0
  222. package/src/memory/migrations/296-rewrite-balanced-economy-profile-pins.ts +68 -0
  223. package/src/memory/migrations/__tests__/131-drop-legacy-member-guardian-tables.test.ts +154 -0
  224. package/src/memory/migrations/__tests__/289-contact-channels-unique-ext-user.test.ts +31 -0
  225. package/src/memory/migrations/__tests__/291-contact-channels-renormalize-addresses.test.ts +30 -0
  226. package/src/memory/migrations/index.ts +5 -0
  227. package/src/memory/onboarding-events-store.ts +3 -3
  228. package/src/memory/schema/contacts.ts +0 -1
  229. package/src/memory/skill-loaded-events-store.test.ts +7 -15
  230. package/src/memory/skill-loaded-events-store.ts +2 -2
  231. package/src/memory/tool-executed-events-store.test.ts +7 -7
  232. package/src/memory/turn-trace-store.test.ts +736 -0
  233. package/src/memory/turn-trace-store.ts +364 -0
  234. package/src/memory/v2/__tests__/consolidation-job.test.ts +8 -0
  235. package/src/memory/v2/__tests__/skill-content.test.ts +30 -0
  236. package/src/memory/v2/consolidation-job.ts +2 -2
  237. package/src/memory/v2/skill-content.ts +25 -7
  238. package/src/memory/v2/skill-store.ts +7 -1
  239. package/src/memory/v3-eval/__tests__/eval-packets.test.ts +248 -0
  240. package/src/memory/v3-eval/eval-packets.ts +546 -0
  241. package/src/messaging/providers/slack/api.ts +31 -0
  242. package/src/messaging/providers/slack/send.test.ts +114 -2
  243. package/src/messaging/providers/slack/send.ts +30 -7
  244. package/src/messaging/providers/slack/withdraw.test.ts +200 -0
  245. package/src/messaging/providers/slack/withdraw.ts +161 -0
  246. package/src/notifications/AGENTS.md +2 -0
  247. package/src/notifications/access-request-copy.ts +72 -59
  248. package/src/notifications/adapters/slack.ts +55 -73
  249. package/src/notifications/approval-card-data.ts +333 -0
  250. package/src/notifications/broadcaster.ts +6 -2
  251. package/src/notifications/canonical-delivery-recorder.ts +139 -0
  252. package/src/notifications/copy-composer.ts +3 -3
  253. package/src/notifications/decision-engine.ts +4 -2
  254. package/src/notifications/destination-resolver.ts +4 -6
  255. package/src/notifications/guardian-question-mode.ts +10 -0
  256. package/src/notifications/home-feed-side-effect.ts +3 -13
  257. package/src/notifications/notification-utils.ts +2 -1
  258. package/src/notifications/signal.ts +79 -43
  259. package/src/notifications/types.ts +98 -128
  260. package/src/permissions/checker.test.ts +51 -0
  261. package/src/permissions/checker.ts +185 -26
  262. package/src/permissions/ipc-risk-types.ts +24 -0
  263. package/src/permissions/question-prompter.test.ts +27 -0
  264. package/src/permissions/question-prompter.ts +4 -0
  265. package/src/platform/client.test.ts +119 -0
  266. package/src/platform/client.ts +66 -0
  267. package/src/platform/consent-cache.test.ts +267 -0
  268. package/src/platform/consent-cache.ts +174 -0
  269. package/src/plugin-api/index.ts +27 -0
  270. package/src/plugins/defaults/advisor/__tests__/advisor-gate.test.ts +56 -0
  271. package/src/plugins/defaults/advisor/__tests__/advisor-state-store.test.ts +43 -0
  272. package/src/plugins/defaults/advisor/__tests__/agent-loop-integration.test.ts +137 -0
  273. package/src/plugins/defaults/advisor/__tests__/consult.test.ts +153 -0
  274. package/src/plugins/defaults/advisor/__tests__/hooks.test.ts +138 -0
  275. package/src/plugins/defaults/advisor/__tests__/transcript.test.ts +147 -0
  276. package/src/plugins/defaults/advisor/advisor-gate.ts +29 -0
  277. package/src/plugins/defaults/advisor/advisor-state-store.ts +94 -0
  278. package/src/plugins/defaults/advisor/config.ts +21 -0
  279. package/src/plugins/defaults/advisor/consult.ts +93 -0
  280. package/src/plugins/defaults/advisor/hooks/post-model-call.ts +34 -0
  281. package/src/plugins/defaults/advisor/hooks/pre-model-call.ts +30 -0
  282. package/src/plugins/defaults/advisor/hooks/user-prompt-submit.ts +19 -0
  283. package/src/plugins/defaults/advisor/package.json +14 -0
  284. package/src/plugins/defaults/advisor/steering.ts +67 -0
  285. package/src/plugins/defaults/advisor/tools/advisor.ts +65 -0
  286. package/src/plugins/defaults/advisor/transcript.ts +76 -0
  287. package/src/plugins/defaults/index.ts +35 -0
  288. package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +22 -9
  289. package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit.ts +2 -2
  290. package/src/plugins/defaults/memory-retrieval/tail-reinjection-strip.ts +64 -0
  291. package/src/plugins/defaults/memory-retrieval/unified-turn-context.ts +29 -21
  292. package/src/plugins/defaults/memory-v3-shadow/__tests__/carry-integration.test.ts +1 -0
  293. package/src/plugins/defaults/memory-v3-shadow/__tests__/injection.test.ts +1 -0
  294. package/src/plugins/defaults/memory-v3-shadow/__tests__/maintain-job.test.ts +75 -7
  295. package/src/plugins/defaults/memory-v3-shadow/__tests__/orchestrate.test.ts +31 -4
  296. package/src/plugins/defaults/memory-v3-shadow/__tests__/selection-log-store.test.ts +77 -2
  297. package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +1 -0
  298. package/src/plugins/defaults/memory-v3-shadow/injector.ts +7 -10
  299. package/src/plugins/defaults/memory-v3-shadow/maintain-job.ts +37 -4
  300. package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +32 -20
  301. package/src/plugins/defaults/memory-v3-shadow/selection-log-store.ts +56 -3
  302. package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +23 -2
  303. package/src/plugins/defaults/task-progress-nudge/hooks/post-tool-use.ts +2 -12
  304. package/src/plugins/defaults/title-generate/hooks/stop.ts +56 -21
  305. package/src/prompts/persona-resolver.ts +12 -2
  306. package/src/prompts/templates/system-sections.ts +7 -2
  307. package/src/providers/__tests__/provider-env-vars.test.ts +6 -0
  308. package/src/providers/__tests__/provider-secret-catalog.test.ts +1 -0
  309. package/src/providers/__tests__/retry-callsite.test.ts +176 -0
  310. package/src/providers/atlascloud/client.ts +85 -0
  311. package/src/providers/fetch-provider-catalog.ts +85 -0
  312. package/src/providers/inference/adapter-factory.ts +3 -0
  313. package/src/providers/model-catalog.ts +58 -0
  314. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +33 -0
  315. package/src/providers/openai/chat-completions-provider.ts +7 -0
  316. package/src/providers/openai/responses-provider.ts +10 -0
  317. package/src/providers/provider-send-message.ts +11 -3
  318. package/src/providers/retry.ts +53 -12
  319. package/src/providers/search-provider-catalog.ts +10 -0
  320. package/src/providers/weak-open-model.ts +22 -0
  321. package/src/runtime/__tests__/agent-wake.test.ts +181 -0
  322. package/src/runtime/__tests__/client-health.test.ts +44 -0
  323. package/src/runtime/access-request-helper.ts +21 -53
  324. package/src/runtime/actor-trust-resolver.ts +49 -21
  325. package/src/runtime/agent-wake.ts +52 -0
  326. package/src/runtime/assistant-event-hub.ts +18 -4
  327. package/src/runtime/auth/__tests__/route-policy.test.ts +12 -0
  328. package/src/runtime/auth/require-bound-guardian.ts +1 -4
  329. package/src/runtime/capabilities.test.ts +120 -0
  330. package/src/runtime/capabilities.ts +197 -0
  331. package/src/runtime/channel-approval-types.ts +5 -1
  332. package/src/runtime/channel-retry-sweep.ts +1 -0
  333. package/src/runtime/channel-verification-service.ts +1 -2
  334. package/src/runtime/client-health.ts +26 -0
  335. package/src/runtime/confirmation-request-guardian-bridge.ts +38 -29
  336. package/src/runtime/effective-capabilities.test.ts +128 -0
  337. package/src/runtime/effective-capabilities.ts +84 -0
  338. package/src/runtime/guardian-reply-router.ts +106 -21
  339. package/src/runtime/invite-redemption-service.ts +6 -22
  340. package/src/runtime/migrations/__tests__/vbundle-builder-fd-leak.test.ts +123 -0
  341. package/src/runtime/migrations/vbundle-builder.ts +49 -20
  342. package/src/runtime/pending-interactions.ts +15 -0
  343. package/src/runtime/routes/__tests__/client-routes.test.ts +13 -0
  344. package/src/runtime/routes/__tests__/conversation-management-routes.test.ts +67 -0
  345. package/src/runtime/routes/__tests__/plugins-routes.test.ts +35 -13
  346. package/src/runtime/routes/browser-tabs-routes.ts +9 -0
  347. package/src/runtime/routes/canonical-guardian-expiry-sweep.ts +17 -8
  348. package/src/runtime/routes/client-routes.ts +10 -0
  349. package/src/runtime/routes/contact-routes.ts +31 -8
  350. package/src/runtime/routes/conversation-management-routes.ts +80 -1
  351. package/src/runtime/routes/conversation-query-routes.ts +68 -22
  352. package/src/runtime/routes/conversation-routes.ts +37 -12
  353. package/src/runtime/routes/events-routes.ts +1 -3
  354. package/src/runtime/routes/guardian-approval-interception.ts +14 -73
  355. package/src/runtime/routes/guardian-approval-prompt.ts +22 -4
  356. package/src/runtime/routes/home-feed-routes.ts +8 -3
  357. package/src/runtime/routes/inbound-message-handler.ts +214 -228
  358. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +88 -6
  359. package/src/runtime/routes/inbound-stages/admission-policy.test.ts +154 -0
  360. package/src/runtime/routes/inbound-stages/admission-policy.ts +140 -0
  361. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +3 -3
  362. package/src/runtime/routes/inbound-stages/background-dispatch.ts +11 -6
  363. package/src/runtime/routes/inbound-stages/escalation-intercept.ts +1 -2
  364. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +1 -2
  365. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.test.ts +7 -7
  366. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +47 -28
  367. package/src/runtime/routes/inbound-stages/reaction-intercept.ts +358 -0
  368. package/src/runtime/routes/index.ts +2 -0
  369. package/src/runtime/routes/integrations/slack/__tests__/channel.test.ts +8 -0
  370. package/src/runtime/routes/integrations/slack/channel.ts +36 -0
  371. package/src/runtime/routes/internal-telemetry-routes.ts +1 -1
  372. package/src/runtime/routes/mcp-auth-routes.ts +233 -41
  373. package/src/runtime/routes/memory-eval-routes.ts +87 -0
  374. package/src/runtime/routes/notification-routes.ts +122 -133
  375. package/src/runtime/routes/platform-routes.ts +2 -2
  376. package/src/runtime/routes/plugins-routes.ts +40 -7
  377. package/src/runtime/routes/secret-routes.ts +10 -0
  378. package/src/runtime/routes/surface-action-routes.ts +2 -1
  379. package/src/runtime/routes/tool-call-question-enrichment.test.ts +146 -0
  380. package/src/runtime/routes/tool-call-question-enrichment.ts +66 -0
  381. package/src/runtime/routes/workflow-routes.test.ts +225 -1
  382. package/src/runtime/routes/workflow-routes.ts +131 -1
  383. package/src/runtime/tool-grant-request-helper.ts +18 -16
  384. package/src/runtime/trust-context-resolver.ts +8 -5
  385. package/src/schedule/schedule-store.ts +1 -1
  386. package/src/schedule/scheduler-types.ts +5 -1
  387. package/src/security/__tests__/provider-key-env-fallback.test.ts +6 -0
  388. package/src/security/secret-patterns.ts +3 -0
  389. package/src/subagent/manager.ts +11 -4
  390. package/src/telemetry/trace-collection-policy.test.ts +28 -0
  391. package/src/telemetry/trace-collection-policy.ts +30 -0
  392. package/src/telemetry/types.ts +89 -0
  393. package/src/telemetry/usage-telemetry-reporter.test.ts +586 -36
  394. package/src/telemetry/usage-telemetry-reporter.ts +148 -41
  395. package/src/tools/browser/__tests__/browser-execution-acquire.test.ts +31 -0
  396. package/src/tools/browser/browser-execution.ts +29 -18
  397. package/src/tools/document/document-tool.ts +2 -3
  398. package/src/tools/executor.ts +5 -3
  399. package/src/tools/host-terminal/host-shell.ts +5 -4
  400. package/src/tools/memory/register.ts +2 -2
  401. package/src/tools/network/__tests__/web-fetch-firecrawl.test.ts +360 -0
  402. package/src/tools/network/__tests__/web-search.test.ts +143 -0
  403. package/src/tools/network/web-fetch.ts +372 -1
  404. package/src/tools/network/web-search.ts +213 -10
  405. package/src/tools/permission-checker.ts +3 -2
  406. package/src/tools/registry.ts +20 -0
  407. package/src/tools/schedule/create.ts +4 -3
  408. package/src/tools/schedule/update.ts +2 -1
  409. package/src/tools/shared/filesystem/path-policy.ts +39 -13
  410. package/src/tools/skills/execute.ts +1 -2
  411. package/src/tools/subagent/spawn.ts +37 -13
  412. package/src/tools/terminal/shell.ts +10 -4
  413. package/src/tools/tool-approval-handler.ts +17 -10
  414. package/src/tools/types.ts +9 -0
  415. package/src/tools/ui-surface/definitions.ts +25 -2
  416. package/src/tools/verification-control-plane-policy.ts +3 -1
  417. package/src/tools/workflows/run-workflow.ts +1 -0
  418. package/src/util/disk-usage.ts +78 -23
  419. package/src/util/platform.ts +8 -1
  420. package/src/watcher/telemetry.ts +2 -2
  421. package/src/workflows/engine.test.ts +175 -1
  422. package/src/workflows/engine.ts +82 -0
  423. package/src/workflows/journal-store.test.ts +70 -0
  424. package/src/workflows/journal-store.ts +18 -3
  425. package/src/workflows/run-manager.test.ts +171 -3
  426. package/src/workflows/run-manager.ts +64 -0
  427. package/src/workspace/migrations/105-enable-memory-v3-live-for-new-workspaces.ts +63 -0
  428. package/src/workspace/migrations/106-drop-collect-usage-data.ts +47 -0
  429. package/src/workspace/migrations/107-drop-send-diagnostics.ts +47 -0
  430. package/src/workspace/migrations/108-drop-balanced-economy-profile.ts +129 -0
  431. package/src/workspace/migrations/registry.ts +8 -0
  432. package/src/notifications/tool-approval-copy.ts +0 -142
  433. package/src/runtime/routes/approval-prompt-ts-tracker.ts +0 -78
@@ -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", () => {
@@ -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[][]
@@ -11,27 +11,33 @@
11
11
 
12
12
  import { describe, expect, test } from "bun:test";
13
13
 
14
- import { isUntrustedTrustClass } from "../runtime/actor-trust-resolver.js";
14
+ import { resolveCapabilities } from "../runtime/capabilities.js";
15
15
 
16
16
  // ---------------------------------------------------------------------------
17
17
  // Trust class categorization (foundational for lockdown decisions)
18
18
  // ---------------------------------------------------------------------------
19
19
 
20
20
  describe("trust class categorization for CES lockdown", () => {
21
- test("guardian is not untrusted", () => {
22
- expect(isUntrustedTrustClass("guardian")).toBe(false);
21
+ test("guardian runs an unsandboxed shell", () => {
22
+ expect(resolveCapabilities("guardian").canRunUnsandboxedShell).toBe(true);
23
23
  });
24
24
 
25
- test("trusted_contact is untrusted", () => {
26
- expect(isUntrustedTrustClass("trusted_contact")).toBe(true);
25
+ test("trusted_contact shell is sandboxed", () => {
26
+ expect(resolveCapabilities("trusted_contact").canRunUnsandboxedShell).toBe(false);
27
27
  });
28
28
 
29
- test("unknown is untrusted", () => {
30
- expect(isUntrustedTrustClass("unknown")).toBe(true);
29
+ test("unverified_contact shell is sandboxed", () => {
30
+ expect(resolveCapabilities("unverified_contact").canRunUnsandboxedShell).toBe(
31
+ false,
32
+ );
31
33
  });
32
34
 
33
- test("undefined is untrusted", () => {
34
- expect(isUntrustedTrustClass(undefined)).toBe(true);
35
+ test("unknown shell is sandboxed", () => {
36
+ expect(resolveCapabilities("unknown").canRunUnsandboxedShell).toBe(false);
37
+ });
38
+
39
+ test("undefined shell is sandboxed", () => {
40
+ expect(resolveCapabilities(undefined).canRunUnsandboxedShell).toBe(false);
35
41
  });
36
42
  });
37
43
 
@@ -91,7 +97,7 @@ describe("VELLUM_UNTRUSTED_SHELL env flag", () => {
91
97
  describe("CES shell lockdown activation", () => {
92
98
  test("lockdown is active only when both flag is enabled AND actor is untrusted", () => {
93
99
  // Simulates the condition used in shell.ts:
94
- // const shellLockdownActive = isCesShellLockdownEnabled(config) && isUntrustedTrustClass(context.trustClass);
100
+ // const shellLockdownActive = isCesShellLockdownEnabled(config) && !resolveCapabilities(context.trustClass).canRunUnsandboxedShell;
95
101
  const cases: Array<{
96
102
  flagEnabled: boolean;
97
103
  trustClass: "guardian" | "trusted_contact" | "unknown";
@@ -106,7 +112,8 @@ describe("CES shell lockdown activation", () => {
106
112
  ];
107
113
 
108
114
  for (const { flagEnabled, trustClass, expected } of cases) {
109
- const active = flagEnabled && isUntrustedTrustClass(trustClass);
115
+ const active =
116
+ flagEnabled && !resolveCapabilities(trustClass).canRunUnsandboxedShell;
110
117
  expect(active).toBe(expected);
111
118
  }
112
119
  });
@@ -110,6 +110,7 @@ function slackResult(
110
110
  hasAppToken: false,
111
111
  hasUserToken: false,
112
112
  connected: false,
113
+ threadMode: "mention_only",
113
114
  ...overrides,
114
115
  };
115
116
  }
@@ -158,6 +158,7 @@ describe("Invariant 2: no generic plaintext secret read API", () => {
158
158
  "credential-execution/prompted-credential.ts", // shared prompt-action persistence (stores secret via setSecureKeyAsync)
159
159
  "tools/credentials/broker.ts", // brokered credential access
160
160
  "tools/network/web-search.ts", // web search API key lookup
161
+ "tools/network/web-fetch.ts", // web fetch provider (Firecrawl) API key lookup
161
162
  "daemon/handlers/config-telegram.ts", // Telegram bot token management
162
163
  "daemon/handlers/config-vercel.ts", // Vercel API token management
163
164
  "runtime/routes/integrations/twilio.ts", // Twilio credential management (HTTP control-plane)
@@ -237,6 +238,7 @@ describe("Invariant 2: no generic plaintext secret read API", () => {
237
238
  "tools/credential-execution/make-authenticated-request.ts", // resolves the CES RPC client via getCesClient
238
239
  "tools/credential-execution/manage-secure-command-tool.ts", // resolves the CES RPC client via getCesClient
239
240
  "tools/executor.ts", // CES approval bridge resolves the CES RPC client via getCesClient
241
+ "tools/network/web-fetch.ts", // Firecrawl /scrape BYOK fetch provider API key lookup (firecrawl provider key)
240
242
  ]);
241
243
 
242
244
  const thisDir = dirname(fileURLToPath(import.meta.url));
@@ -108,6 +108,18 @@ describe("classifyDiskPressureTurnPolicy", () => {
108
108
  },
109
109
  expected: { action: "block", reason: "trusted-contact" },
110
110
  },
111
+ {
112
+ name: "unverified contact is blocked",
113
+ status: status(),
114
+ metadata: {
115
+ ...guardianTurn,
116
+ trustContext: {
117
+ sourceChannel: "telegram",
118
+ trustClass: "unverified_contact",
119
+ },
120
+ },
121
+ expected: { action: "block", reason: "trusted-contact" },
122
+ },
111
123
  {
112
124
  name: "non-guardian contact is blocked",
113
125
  status: status(),
@@ -6,6 +6,8 @@ const GIB = 1024 * MIB;
6
6
  let existingPaths = new Set<string>();
7
7
  const workspaceDir = "/workspace";
8
8
  let minikubeStorageSize: string | undefined;
9
+ let isContainerized = false;
10
+ let isPlatform = false;
9
11
  let statfsResult = {
10
12
  bsize: 4096,
11
13
  blocks: 0,
@@ -34,6 +36,8 @@ mock.module("node:child_process", () => ({
34
36
 
35
37
  mock.module("../config/env-registry.js", () => ({
36
38
  getMinikubeStorageSize: () => minikubeStorageSize,
39
+ getIsContainerized: () => isContainerized,
40
+ getIsPlatform: () => isPlatform,
37
41
  }));
38
42
 
39
43
  mock.module("../util/platform.js", () => ({
@@ -55,6 +59,8 @@ describe("disk usage sampler", () => {
55
59
  beforeEach(() => {
56
60
  existingPaths = new Set([workspaceDir]);
57
61
  minikubeStorageSize = undefined;
62
+ isContainerized = false;
63
+ isPlatform = false;
58
64
  statfsResult = statfsFor(100 * MIB, 25 * MIB);
59
65
  spawnResult = { status: 0, stdout: "" };
60
66
  spawnCalls = [];
@@ -119,6 +125,65 @@ describe("disk usage sampler", () => {
119
125
  ]);
120
126
  });
121
127
 
128
+ test("measures workspace du usage on a local Docker hatch", () => {
129
+ isContainerized = true;
130
+ isPlatform = false;
131
+ statfsResult = statfsFor(100 * GIB, 40 * GIB);
132
+ spawnResult = {
133
+ status: 0,
134
+ stdout: `${2 * GIB}\t/workspace\n`,
135
+ };
136
+
137
+ const usage = getDiskUsageInfo();
138
+
139
+ // Used reflects only the workspace (du), free reflects the host headroom
140
+ // the volume can grow into, and total is their sum.
141
+ expect(usage).toEqual({
142
+ path: "/workspace",
143
+ totalMb: 2048 + 40960,
144
+ usedMb: 2048,
145
+ freeMb: 40960,
146
+ });
147
+ expect(spawnCalls).toEqual([
148
+ { command: "du", args: ["-sb", "/workspace"] },
149
+ ]);
150
+ });
151
+
152
+ test("does not use du for platform-managed containerized instances", () => {
153
+ isContainerized = true;
154
+ isPlatform = true;
155
+ statfsResult = statfsFor(10 * GIB, 6 * GIB);
156
+
157
+ const usage = getDiskUsageInfo();
158
+
159
+ expect(usage).toEqual({
160
+ path: "/workspace",
161
+ totalMb: 10240,
162
+ usedMb: 4096,
163
+ freeMb: 6144,
164
+ });
165
+ expect(spawnCalls).toHaveLength(0);
166
+ });
167
+
168
+ test("falls back to statfs when du fails on a local Docker hatch", () => {
169
+ isContainerized = true;
170
+ isPlatform = false;
171
+ statfsResult = statfsFor(100 * GIB, 40 * GIB);
172
+ spawnResult = { status: 1, stdout: "" };
173
+
174
+ const usage = getDiskUsageInfo();
175
+
176
+ expect(usage).toEqual({
177
+ path: "/workspace",
178
+ totalMb: 102400,
179
+ usedMb: 61440,
180
+ freeMb: 40960,
181
+ });
182
+ expect(spawnCalls).toEqual([
183
+ { command: "du", args: ["-sb", "/workspace"] },
184
+ ]);
185
+ });
186
+
122
187
  test("returns null for malformed Kubernetes memory strings", () => {
123
188
  expect(parseK8sMemoryBytes("")).toBeNull();
124
189
  expect(parseK8sMemoryBytes("abc")).toBeNull();