@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
@@ -0,0 +1,360 @@
1
+ import { beforeEach, describe, expect, mock, test } from "bun:test";
2
+
3
+ import { BadRequestError } from "../runtime/routes/errors.js";
4
+ import type { CredentialMetadata } from "../tools/credentials/metadata-store.js";
5
+
6
+ // ---------------------------------------------------------------------------
7
+ // Mutable mock state (closed over by the mock factories below)
8
+ // ---------------------------------------------------------------------------
9
+
10
+ let secureStore: Map<string, string>;
11
+ let metadataStore: Map<string, CredentialMetadata>;
12
+ let syncedServices: string[];
13
+ let disconnectedProviders: string[];
14
+ let credentialIdCounter: number;
15
+
16
+ function metaKey(service: string, field: string): string {
17
+ return `${service}:${field}`;
18
+ }
19
+
20
+ // ---------------------------------------------------------------------------
21
+ // Mocks for the routes' collaborators
22
+ // ---------------------------------------------------------------------------
23
+
24
+ mock.module("../runtime/auth/route-policy.js", () => ({
25
+ ACTOR_PRINCIPALS: [],
26
+ }));
27
+
28
+ mock.module("../security/credential-key.js", () => ({
29
+ credentialKey: (service: string, field: string) => `${service}:${field}`,
30
+ }));
31
+
32
+ mock.module("../security/secure-keys.js", () => ({
33
+ setSecureKeyAsync: mock(async (key: string, value: string) => {
34
+ secureStore.set(key, value);
35
+ return true;
36
+ }),
37
+ getSecureKeyAsync: mock(async (key: string) => secureStore.get(key)),
38
+ getSecureKeyResultAsync: mock(async (key: string) => ({
39
+ value: secureStore.get(key),
40
+ unreachable: false,
41
+ })),
42
+ deleteSecureKeyAsync: mock(async (key: string) =>
43
+ secureStore.delete(key) ? "deleted" : "not-found",
44
+ ),
45
+ getActiveBackendName: () => "encrypted-store",
46
+ getActiveBackendInfoAsync: mock(async () => ({ backend: "encrypted-store" })),
47
+ }));
48
+
49
+ mock.module("../tools/credentials/metadata-store.js", () => ({
50
+ assertMetadataWritable: () => {},
51
+ listCredentialMetadata: mock(() => Array.from(metadataStore.values())),
52
+ getCredentialMetadata: mock((service: string, field: string) =>
53
+ metadataStore.get(metaKey(service, field)),
54
+ ),
55
+ getCredentialMetadataById: mock((id: string) =>
56
+ Array.from(metadataStore.values()).find((m) => m.credentialId === id),
57
+ ),
58
+ upsertCredentialMetadata: mock(
59
+ (
60
+ service: string,
61
+ field: string,
62
+ policy?: {
63
+ allowedTools?: string[];
64
+ usageDescription?: string;
65
+ alias?: string | null;
66
+ },
67
+ ): CredentialMetadata => {
68
+ const key = metaKey(service, field);
69
+ const existing = metadataStore.get(key);
70
+ const now = Date.now();
71
+ const meta: CredentialMetadata = {
72
+ credentialId: existing?.credentialId ?? `cred-${++credentialIdCounter}`,
73
+ service,
74
+ field,
75
+ allowedTools: policy?.allowedTools ?? existing?.allowedTools ?? [],
76
+ allowedDomains: existing?.allowedDomains ?? [],
77
+ usageDescription:
78
+ policy?.usageDescription ?? existing?.usageDescription,
79
+ alias: policy?.alias ?? existing?.alias ?? undefined,
80
+ createdAt: existing?.createdAt ?? now,
81
+ updatedAt: now,
82
+ };
83
+ metadataStore.set(key, meta);
84
+ return meta;
85
+ },
86
+ ),
87
+ deleteCredentialMetadata: mock((service: string, field: string) =>
88
+ metadataStore.delete(metaKey(service, field)),
89
+ ),
90
+ }));
91
+
92
+ mock.module("../oauth/manual-token-connection.js", () => ({
93
+ syncManualTokenConnection: mock(async (service: string) => {
94
+ syncedServices.push(service);
95
+ }),
96
+ }));
97
+
98
+ mock.module("../oauth/oauth-store.js", () => ({
99
+ listConnections: mock(() => []),
100
+ getConnectionByProvider: mock(() => undefined),
101
+ disconnectOAuthProvider: mock(async (provider: string) => {
102
+ disconnectedProviders.push(provider);
103
+ return "not-found";
104
+ }),
105
+ }));
106
+
107
+ mock.module("../credential-execution/managed-catalog.js", () => ({
108
+ fetchManagedCatalog: mock(async () => ({ ok: true, descriptors: [] })),
109
+ }));
110
+
111
+ import { ROUTES } from "../runtime/routes/credential-routes.js";
112
+
113
+ const setRoute = ROUTES.find((r) => r.operationId === "credentials_set");
114
+ const listRoute = ROUTES.find((r) => r.operationId === "credentials_list");
115
+ const deleteRoute = ROUTES.find((r) => r.operationId === "credentials_delete");
116
+
117
+ type SetResponse = { credentialId: string; service: string; field: string };
118
+ type ListResponse = {
119
+ credentials: Array<{
120
+ service: string;
121
+ field: string;
122
+ scrubbedValue: string;
123
+ hasSecret: boolean;
124
+ }>;
125
+ managedCredentials: unknown[];
126
+ };
127
+ type DeleteResponse = { service: string; field: string };
128
+
129
+ const SECRET_VALUE = "super-secret-token-value";
130
+
131
+ describe("credentials routes", () => {
132
+ beforeEach(() => {
133
+ secureStore = new Map();
134
+ metadataStore = new Map();
135
+ syncedServices = [];
136
+ disconnectedProviders = [];
137
+ credentialIdCounter = 0;
138
+ });
139
+
140
+ describe("credentials_set", () => {
141
+ test("stores the secret and returns identifiers without leaking the value", async () => {
142
+ /**
143
+ * Storing a credential via the route persists the plaintext to secure
144
+ * storage and creates metadata, but the response surfaces only the
145
+ * credential identifiers — never the secret itself.
146
+ */
147
+ // GIVEN the credentials_set route is registered
148
+ expect(setRoute).toBeDefined();
149
+
150
+ // WHEN a credential is stored with a label, description, and allowed tools
151
+ const result = (await setRoute!.handler({
152
+ body: {
153
+ service: "vercel",
154
+ field: "api_token",
155
+ value: SECRET_VALUE,
156
+ label: "Vercel API Token",
157
+ description: "Used to deploy pages",
158
+ allowedTools: ["publish_page"],
159
+ },
160
+ })) as SetResponse;
161
+
162
+ // THEN the response carries the credential identifiers only
163
+ expect(result.service).toBe("vercel");
164
+ expect(result.field).toBe("api_token");
165
+ expect(result.credentialId).toBe("cred-1");
166
+
167
+ // AND the secret is persisted to secure storage
168
+ expect(secureStore.get("vercel:api_token")).toBe(SECRET_VALUE);
169
+
170
+ // AND a manual-token connection sync is triggered for the service
171
+ expect(syncedServices).toContain("vercel");
172
+
173
+ // AND the plaintext value never appears in the response
174
+ expect(JSON.stringify(result)).not.toContain(SECRET_VALUE);
175
+ });
176
+
177
+ test("rejects a request missing the value", async () => {
178
+ /**
179
+ * The route validates required fields and rejects a store request that
180
+ * omits the secret value before touching storage.
181
+ */
182
+ // GIVEN a store request with no value
183
+ const body = { service: "vercel", field: "api_token" };
184
+
185
+ // WHEN the handler runs
186
+ const call = setRoute!.handler({ body });
187
+
188
+ // THEN it rejects with a BadRequestError and stores nothing
189
+ await expect(call).rejects.toBeInstanceOf(BadRequestError);
190
+ expect(secureStore.size).toBe(0);
191
+ });
192
+ });
193
+
194
+ describe("credentials_list", () => {
195
+ test("returns masked metadata without exposing stored secrets", async () => {
196
+ /**
197
+ * Listing credentials returns metadata and a scrubbed preview for each
198
+ * stored secret; the raw plaintext is never included in the output.
199
+ */
200
+ // GIVEN a stored credential
201
+ await setRoute!.handler({
202
+ body: {
203
+ service: "vercel",
204
+ field: "api_token",
205
+ value: SECRET_VALUE,
206
+ label: "Vercel API Token",
207
+ },
208
+ });
209
+
210
+ // WHEN the credentials are listed
211
+ const result = (await listRoute!.handler({ body: {} })) as ListResponse;
212
+
213
+ // THEN the listing includes the credential with a masked value
214
+ expect(result.credentials).toHaveLength(1);
215
+ const entry = result.credentials[0];
216
+ expect(entry.service).toBe("vercel");
217
+ expect(entry.field).toBe("api_token");
218
+ expect(entry.hasSecret).toBe(true);
219
+ expect(entry.scrubbedValue).toBe("****alue");
220
+
221
+ // AND the raw secret never appears anywhere in the response
222
+ expect(JSON.stringify(result)).not.toContain(SECRET_VALUE);
223
+ });
224
+
225
+ test("filters credentials by search substring", async () => {
226
+ /**
227
+ * The optional `search` filter restricts the listing to credentials
228
+ * whose service, field, alias, or description match the query.
229
+ */
230
+ // GIVEN two stored credentials in different services
231
+ await setRoute!.handler({
232
+ body: { service: "vercel", field: "api_token", value: "v-secret" },
233
+ });
234
+ await setRoute!.handler({
235
+ body: { service: "github", field: "token", value: "g-secret" },
236
+ });
237
+
238
+ // WHEN the listing is filtered by a service substring
239
+ const result = (await listRoute!.handler({
240
+ body: { search: "git" },
241
+ })) as ListResponse;
242
+
243
+ // THEN only the matching credential is returned
244
+ expect(result.credentials).toHaveLength(1);
245
+ expect(result.credentials[0].service).toBe("github");
246
+ });
247
+ });
248
+
249
+ describe("credentials_delete", () => {
250
+ test("removes the secret and metadata and echoes the identifiers", async () => {
251
+ /**
252
+ * Deleting a credential removes both the stored secret and its metadata,
253
+ * returning the service and field that were deleted.
254
+ */
255
+ // GIVEN a stored credential
256
+ await setRoute!.handler({
257
+ body: { service: "vercel", field: "api_token", value: SECRET_VALUE },
258
+ });
259
+ expect(secureStore.has("vercel:api_token")).toBe(true);
260
+
261
+ // WHEN the credential is deleted
262
+ const result = (await deleteRoute!.handler({
263
+ body: { service: "vercel", field: "api_token" },
264
+ })) as DeleteResponse;
265
+
266
+ // THEN the response echoes the identifiers
267
+ expect(result).toEqual({ service: "vercel", field: "api_token" });
268
+
269
+ // AND the secret and metadata are gone
270
+ expect(secureStore.has("vercel:api_token")).toBe(false);
271
+ expect(metadataStore.has("vercel:api_token")).toBe(false);
272
+ });
273
+
274
+ test("deletes the Slack user_token surgically, preserving the OAuth connection", async () => {
275
+ /**
276
+ * The Slack user_token grants only read access to channels the bot isn't
277
+ * a member of; Socket Mode runs on the bot + app tokens. Deleting just the
278
+ * user_token removes that secret without disconnecting the provider, so
279
+ * the integration's connected state does not flap.
280
+ */
281
+ // GIVEN a connected Slack channel with bot, app, and user tokens
282
+ secureStore.set("slack_channel:bot_token", "xoxb-bot");
283
+ secureStore.set("slack_channel:app_token", "xapp-app");
284
+ secureStore.set("slack_channel:user_token", "xoxp-user");
285
+
286
+ // WHEN only the user_token is deleted
287
+ const result = (await deleteRoute!.handler({
288
+ body: { service: "slack_channel", field: "user_token" },
289
+ })) as DeleteResponse;
290
+
291
+ // THEN the response echoes the identifiers
292
+ expect(result).toEqual({ service: "slack_channel", field: "user_token" });
293
+
294
+ // AND only the user_token is removed; bot + app tokens remain
295
+ expect(secureStore.has("slack_channel:user_token")).toBe(false);
296
+ expect(secureStore.has("slack_channel:bot_token")).toBe(true);
297
+ expect(secureStore.has("slack_channel:app_token")).toBe(true);
298
+
299
+ // AND the OAuth provider is never disconnected
300
+ expect(disconnectedProviders).not.toContain("slack_channel");
301
+ });
302
+
303
+ test("rejects deleting an absent Slack user_token without disconnecting the provider", async () => {
304
+ /**
305
+ * Deleting a Slack user_token that was never stored surfaces the same
306
+ * not-found rejection as any other missing credential — it must not be
307
+ * reported as an internal storage error, and it must still skip the
308
+ * OAuth teardown so a connected channel's bot + app tokens are untouched.
309
+ */
310
+ // GIVEN a connected Slack channel with only bot + app tokens (no user_token)
311
+ secureStore.set("slack_channel:bot_token", "xoxb-bot");
312
+ secureStore.set("slack_channel:app_token", "xapp-app");
313
+
314
+ // WHEN the absent user_token is deleted
315
+ const call = deleteRoute!.handler({
316
+ body: { service: "slack_channel", field: "user_token" },
317
+ });
318
+
319
+ // THEN it rejects with a BadRequestError (not an InternalError)
320
+ await expect(call).rejects.toBeInstanceOf(BadRequestError);
321
+
322
+ // AND the bot + app tokens remain and the provider is never disconnected
323
+ expect(secureStore.has("slack_channel:bot_token")).toBe(true);
324
+ expect(secureStore.has("slack_channel:app_token")).toBe(true);
325
+ expect(disconnectedProviders).not.toContain("slack_channel");
326
+ });
327
+
328
+ test("disconnects the provider when a connection-critical Slack token is deleted", async () => {
329
+ /**
330
+ * Deleting a token that powers the Socket Mode connection (bot_token)
331
+ * follows the generic path, which tears down the OAuth connection.
332
+ */
333
+ // GIVEN a stored Slack bot_token
334
+ secureStore.set("slack_channel:bot_token", "xoxb-bot");
335
+
336
+ // WHEN the bot_token is deleted
337
+ await deleteRoute!.handler({
338
+ body: { service: "slack_channel", field: "bot_token" },
339
+ });
340
+
341
+ // THEN the generic path disconnects the provider
342
+ expect(disconnectedProviders).toContain("slack_channel");
343
+ });
344
+
345
+ test("rejects deleting a credential that does not exist", async () => {
346
+ /**
347
+ * Deleting a credential with no stored secret, metadata, or OAuth
348
+ * connection rejects with a not-found error.
349
+ */
350
+ // GIVEN no stored credential for the service+field
351
+ const body = { service: "ghost", field: "token" };
352
+
353
+ // WHEN the handler runs
354
+ const call = deleteRoute!.handler({ body });
355
+
356
+ // THEN it rejects with a BadRequestError
357
+ await expect(call).rejects.toBeInstanceOf(BadRequestError);
358
+ });
359
+ });
360
+ });
@@ -95,17 +95,7 @@ import {
95
95
 
96
96
  describe("Invariant 1: secrets never enter LLM context", () => {
97
97
  for (const tc of contextInjectionCases) {
98
- if (
99
- tc.vector === "tool_output" &&
100
- tc.tool === "credential_store" &&
101
- tc.input.action === "store"
102
- ) {
103
- // Store output never includes the value
104
- test(`${tc.label}: secret not in output`, () => {
105
- expect(tc.forbiddenValue).toBeTruthy();
106
- // Actual assertion is in credential-vault.test.ts baseline section
107
- });
108
- } else if (tc.vector === "confirmation_payload") {
98
+ if (tc.vector === "confirmation_payload") {
109
99
  // PR 23 added redaction to confirmation_request payloads via redactSensitiveFields
110
100
  test(`${tc.label}: secret redacted from confirmation payload`, () => {
111
101
  const payload = { ...tc.input };
@@ -132,7 +122,7 @@ describe("Invariant 1: secrets never enter LLM context", () => {
132
122
  }
133
123
  });
134
124
  } else {
135
- // tool_output cases for list and browser_fill — already passing via baselines
125
+ // tool_output cases (browser_fill_credential) — already passing via baselines
136
126
  test(`${tc.label}: secret not in output`, () => {
137
127
  expect(tc.forbiddenValue).toBeTruthy();
138
128
  });
@@ -165,10 +155,10 @@ describe("Invariant 2: no generic plaintext secret read API", () => {
165
155
  // Hard boundary: only these production files may import from secure-keys.
166
156
  // Any new import must be reviewed for secret-leak risk and added here.
167
157
  const ALLOWED_IMPORTERS = new Set([
168
- "tools/credentials/vault.ts", // credential store tool
169
158
  "credential-execution/prompted-credential.ts", // shared prompt-action persistence (stores secret via setSecureKeyAsync)
170
159
  "tools/credentials/broker.ts", // brokered credential access
171
160
  "tools/network/web-search.ts", // web search API key lookup
161
+ "tools/network/web-fetch.ts", // web fetch provider (Firecrawl) API key lookup
172
162
  "daemon/handlers/config-telegram.ts", // Telegram bot token management
173
163
  "daemon/handlers/config-vercel.ts", // Vercel API token management
174
164
  "runtime/routes/integrations/twilio.ts", // Twilio credential management (HTTP control-plane)
@@ -248,6 +238,7 @@ describe("Invariant 2: no generic plaintext secret read API", () => {
248
238
  "tools/credential-execution/make-authenticated-request.ts", // resolves the CES RPC client via getCesClient
249
239
  "tools/credential-execution/manage-secure-command-tool.ts", // resolves the CES RPC client via getCesClient
250
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)
251
242
  ]);
252
243
 
253
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();
@@ -5,7 +5,7 @@ import type {
5
5
  UiSurfaceShowDynamicPage,
6
6
  } from "../daemon/message-protocol.js";
7
7
  import { INTERACTIVE_SURFACE_TYPES } from "../daemon/message-protocol.js";
8
- import { uiShowTool } from "../tools/ui-surface/definitions.js";
8
+ import { uiShowTool, uiUpdateTool } from "../tools/ui-surface/definitions.js";
9
9
 
10
10
  // ---------------------------------------------------------------------------
11
11
  // DynamicPageSurfaceData shape
@@ -151,6 +151,57 @@ describe("ui_show dynamic_page app substitute guard", () => {
151
151
  expect(proxied).toBe(false);
152
152
  });
153
153
 
154
+ test("redirects a weak open model to a declarative surface, not 'resend HTML'", async () => {
155
+ let proxied = false;
156
+
157
+ const result = await uiShowTool.execute(
158
+ {
159
+ surface_type: "dynamic_page",
160
+ title: "Fable 5 vs MiniMax M3",
161
+ data: {},
162
+ },
163
+ {
164
+ conversationId: "conversation-123",
165
+ workingDir: "/tmp",
166
+ trustClass: "guardian",
167
+ attribution: {
168
+ resolvedModel: "accounts/fireworks/models/minimax-m3",
169
+ } as never,
170
+ proxyToolResolver: async () => {
171
+ proxied = true;
172
+ return { content: "proxied", isError: false };
173
+ },
174
+ },
175
+ );
176
+
177
+ expect(result.isError).toBe(true);
178
+ expect(result.content).toContain('surface_type: "table"');
179
+ expect(result.content).toContain("work_result");
180
+ expect(result.content).not.toContain("Resend ui_show with the full HTML");
181
+ expect(proxied).toBe(false);
182
+ });
183
+
184
+ test("keeps the resend-HTML hint for a capable model", async () => {
185
+ const result = await uiShowTool.execute(
186
+ {
187
+ surface_type: "dynamic_page",
188
+ title: "Fable 5 vs MiniMax M3",
189
+ data: {},
190
+ },
191
+ {
192
+ conversationId: "conversation-123",
193
+ workingDir: "/tmp",
194
+ trustClass: "guardian",
195
+ attribution: { resolvedModel: "claude-opus-4-8" } as never,
196
+ proxyToolResolver: async () => ({ content: "proxied", isError: false }),
197
+ },
198
+ );
199
+
200
+ expect(result.isError).toBe(true);
201
+ expect(result.content).toContain("Resend ui_show with the full HTML");
202
+ expect(result.content).not.toContain('surface_type: "table"');
203
+ });
204
+
154
205
  test("rejects dynamic_page with whitespace-only html and does not proxy", async () => {
155
206
  let proxied = false;
156
207
 
@@ -300,6 +351,106 @@ describe("ui_show empty card guard", () => {
300
351
  });
301
352
  });
302
353
 
354
+ describe("ui_update empty payload guard", () => {
355
+ function makeCtx(onProxy: () => void) {
356
+ return {
357
+ conversationId: "conversation-123",
358
+ workingDir: "/tmp",
359
+ trustClass: "guardian" as const,
360
+ proxyToolResolver: async () => {
361
+ onProxy();
362
+ return { content: "Surface updated", isError: false };
363
+ },
364
+ };
365
+ }
366
+
367
+ test("rejects an empty data object and does not proxy", async () => {
368
+ let proxied = false;
369
+ const result = await uiUpdateTool.execute(
370
+ { surface_id: "surf-1", data: {} },
371
+ makeCtx(() => {
372
+ proxied = true;
373
+ }),
374
+ );
375
+
376
+ expect(result.isError).toBe(true);
377
+ expect(result.content).toContain("empty `data` payload");
378
+ expect(proxied).toBe(false);
379
+ });
380
+
381
+ test("rejects missing data and does not proxy", async () => {
382
+ let proxied = false;
383
+ const result = await uiUpdateTool.execute(
384
+ { surface_id: "surf-1" },
385
+ makeCtx(() => {
386
+ proxied = true;
387
+ }),
388
+ );
389
+
390
+ expect(result.isError).toBe(true);
391
+ expect(proxied).toBe(false);
392
+ });
393
+
394
+ test("rejects data containing only nested empty objects", async () => {
395
+ let proxied = false;
396
+ const result = await uiUpdateTool.execute(
397
+ { surface_id: "surf-1", data: { templateData: {} } },
398
+ makeCtx(() => {
399
+ proxied = true;
400
+ }),
401
+ );
402
+
403
+ expect(result.isError).toBe(true);
404
+ expect(proxied).toBe(false);
405
+ });
406
+
407
+ test("rejects a task_progress update with an empty steps array", async () => {
408
+ let proxied = false;
409
+ const result = await uiUpdateTool.execute(
410
+ { surface_id: "surf-1", data: { templateData: { steps: [] } } },
411
+ makeCtx(() => {
412
+ proxied = true;
413
+ }),
414
+ );
415
+
416
+ expect(result.isError).toBe(true);
417
+ expect(proxied).toBe(false);
418
+ });
419
+
420
+ test("allows a task_progress step update", async () => {
421
+ let proxied = false;
422
+ const result = await uiUpdateTool.execute(
423
+ {
424
+ surface_id: "surf-1",
425
+ data: {
426
+ templateData: {
427
+ steps: [{ label: "Read memo", status: "completed" }],
428
+ },
429
+ },
430
+ },
431
+ makeCtx(() => {
432
+ proxied = true;
433
+ }),
434
+ );
435
+
436
+ expect(result.isError).toBe(false);
437
+ expect(proxied).toBe(true);
438
+ });
439
+
440
+ test("allows a status-only update without resending steps", async () => {
441
+ let proxied = false;
442
+ const result = await uiUpdateTool.execute(
443
+ { surface_id: "surf-1", data: { templateData: { status: "completed" } } },
444
+ makeCtx(() => {
445
+ proxied = true;
446
+ }),
447
+ );
448
+
449
+ expect(result.isError).toBe(false);
450
+ expect(proxied).toBe(true);
451
+ });
452
+ });
453
+
303
454
  // ---------------------------------------------------------------------------
304
455
  // task_progress ui_show appends the update hint to its return value
305
456
  // ---------------------------------------------------------------------------