@vellumai/assistant 0.9.0 → 0.10.0-dev.202606192226.8ed8a6a

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 (624) hide show
  1. package/ARCHITECTURE.md +19 -35
  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 +1128 -119
  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__/agent-loop-pushes-post-hook-prompt.test.ts +306 -0
  28. package/src/__tests__/always-loaded-tools-guard.test.ts +2 -3
  29. package/src/__tests__/app-compiler.test.ts +15 -1
  30. package/src/__tests__/app-dir-path-guard.test.ts +0 -1
  31. package/src/__tests__/approval-cascade.test.ts +6 -0
  32. package/src/__tests__/approval-routes-http.test.ts +128 -0
  33. package/src/__tests__/assistant-feature-flag-guard.test.ts +1 -4
  34. package/src/__tests__/assistant-feature-flag-guardrails.test.ts +0 -2
  35. package/src/__tests__/auth-fallback-events-store.test.ts +6 -14
  36. package/src/__tests__/avatar-identity-sync.test.ts +2 -27
  37. package/src/__tests__/btw-routes.test.ts +6 -8
  38. package/src/__tests__/call-pointer-messages.test.ts +28 -0
  39. package/src/__tests__/cancel-clears-processing.test.ts +89 -0
  40. package/src/__tests__/channel-approval-routes.test.ts +0 -4
  41. package/src/__tests__/channel-inbound-disk-pressure.test.ts +5 -15
  42. package/src/__tests__/checker.test.ts +0 -3
  43. package/src/__tests__/cli-memory-v2-reembed-skills.test.ts +3 -4
  44. package/src/__tests__/compaction-events.test.ts +6 -0
  45. package/src/__tests__/compactor-image-manifest-trust.test.ts +21 -1
  46. package/src/__tests__/compactor-summary-call-truncation.test.ts +223 -0
  47. package/src/__tests__/config-loader-backfill.test.ts +268 -27
  48. package/src/__tests__/config-schema.test.ts +35 -0
  49. package/src/__tests__/config-watcher.test.ts +0 -18
  50. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +2 -2
  51. package/src/__tests__/contact-store-user-file.test.ts +0 -6
  52. package/src/__tests__/contacts-tools.test.ts +29 -0
  53. package/src/__tests__/conversation-abort-tool-results.test.ts +6 -0
  54. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +22 -0
  55. package/src/__tests__/conversation-agent-loop-overflow.test.ts +1 -0
  56. package/src/__tests__/conversation-agent-loop.test.ts +58 -0
  57. package/src/__tests__/conversation-attention-telegram.test.ts +0 -1
  58. package/src/__tests__/conversation-confirmation-signals.test.ts +6 -0
  59. package/src/__tests__/conversation-lifecycle.test.ts +7 -9
  60. package/src/__tests__/conversation-load-history-repair.test.ts +101 -0
  61. package/src/__tests__/conversation-process-callsite.test.ts +6 -0
  62. package/src/__tests__/conversation-provider-retry-repair.test.ts +6 -0
  63. package/src/__tests__/conversation-queue.test.ts +6 -0
  64. package/src/__tests__/conversation-routes-guardian-reply.test.ts +15 -12
  65. package/src/__tests__/conversation-slash-queue.test.ts +6 -0
  66. package/src/__tests__/conversation-slash-unknown.test.ts +6 -0
  67. package/src/__tests__/conversation-speed-override.test.ts +6 -0
  68. package/src/__tests__/conversation-surfaces-activation-emit.test.ts +6 -3
  69. package/src/__tests__/conversation-title-service.test.ts +62 -0
  70. package/src/__tests__/conversation-workspace-cache-state.test.ts +6 -0
  71. package/src/__tests__/conversation-workspace-injection.test.ts +6 -0
  72. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +6 -0
  73. package/src/__tests__/credential-broker.test.ts +449 -1
  74. package/src/__tests__/credential-execution-shell-lockdown.test.ts +18 -11
  75. package/src/__tests__/credential-execution-tools.test.ts +0 -1
  76. package/src/__tests__/credential-prompt-route.test.ts +4 -4
  77. package/src/__tests__/credential-routes.test.ts +360 -0
  78. package/src/__tests__/credential-security-invariants.test.ts +4 -13
  79. package/src/__tests__/disk-pressure-policy.test.ts +12 -0
  80. package/src/__tests__/disk-pressure-tools.test.ts +41 -1
  81. package/src/__tests__/disk-usage.test.ts +65 -0
  82. package/src/__tests__/document-tool-security.test.ts +9 -6
  83. package/src/__tests__/document-update-default-surface.test.ts +202 -0
  84. package/src/__tests__/dynamic-page-surface.test.ts +152 -1
  85. package/src/__tests__/fixtures/credential-security-fixtures.ts +2 -33
  86. package/src/__tests__/gateway-flag-listener.test.ts +110 -1
  87. package/src/__tests__/gateway-only-guard.test.ts +3 -7
  88. package/src/__tests__/gemini-count-tokens.test.ts +70 -0
  89. package/src/__tests__/guardian-binding-drift-heal.test.ts +1 -1
  90. package/src/__tests__/guardian-card-withdrawal.test.ts +403 -0
  91. package/src/__tests__/guardian-decision-primitive-canonical.test.ts +5 -3
  92. package/src/__tests__/guardian-grant-minting.test.ts +3 -35
  93. package/src/__tests__/guardian-routing-invariants.test.ts +64 -26
  94. package/src/__tests__/guardian-routing-state.test.ts +0 -1
  95. package/src/__tests__/headless-browser-mode.test.ts +10 -0
  96. package/src/__tests__/headless-browser-navigate.test.ts +8 -3
  97. package/src/__tests__/helpers/create-guardian-binding.ts +0 -1
  98. package/src/__tests__/host-browser-proxy.test.ts +87 -0
  99. package/src/__tests__/identity-routes.test.ts +0 -189
  100. package/src/__tests__/inbound-invite-redemption.test.ts +4 -4
  101. package/src/__tests__/injector-disk-pressure.test.ts +10 -5
  102. package/src/__tests__/injector-v3-suppression.test.ts +27 -20
  103. package/src/__tests__/internal-telemetry-routes.test.ts +6 -14
  104. package/src/__tests__/invite-redemption-service.test.ts +4 -7
  105. package/src/__tests__/llm-callsite-catalog.test.ts +5 -6
  106. package/src/__tests__/llm-catalog-parity.test.ts +30 -23
  107. package/src/__tests__/llm-resolver.test.ts +70 -24
  108. package/src/__tests__/llm-schema.test.ts +1 -0
  109. package/src/__tests__/managed-profile-guard.test.ts +163 -4
  110. package/src/__tests__/mcp-health-check.test.ts +6 -7
  111. package/src/__tests__/media-stream-server-integration.test.ts +317 -13
  112. package/src/__tests__/oauth-provider-seed-logos.test.ts +4 -6
  113. package/src/__tests__/onboarding-persona-write.test.ts +1 -1
  114. package/src/__tests__/path-policy.test.ts +34 -0
  115. package/src/__tests__/persona-resolver.test.ts +49 -14
  116. package/src/__tests__/plugin-api-model-profiles.test.ts +178 -0
  117. package/src/__tests__/plugin-api-provider.test.ts +24 -0
  118. package/src/__tests__/plugin-tool-contribution.test.ts +6 -3
  119. package/src/__tests__/post-compaction-reinjection-idempotency.test.ts +214 -0
  120. package/src/__tests__/provider-send-message-override-profile.test.ts +76 -0
  121. package/src/__tests__/reaction-persistence.test.ts +150 -29
  122. package/src/__tests__/registry.test.ts +2 -7
  123. package/src/__tests__/relay-server.test.ts +285 -0
  124. package/src/__tests__/runtime-attachment-metadata.test.ts +0 -1
  125. package/src/__tests__/schedule-routes-workflow-validation.test.ts +1 -10
  126. package/src/__tests__/schedule-routes.test.ts +0 -30
  127. package/src/__tests__/schedule-tools.test.ts +2 -18
  128. package/src/__tests__/scheduler-reuse-conversation.test.ts +8 -5
  129. package/src/__tests__/secret-response-routing.test.ts +35 -0
  130. package/src/__tests__/secret-routes-platform-proxy.test.ts +16 -2
  131. package/src/__tests__/skill-execute-input.test.ts +177 -1
  132. package/src/__tests__/skill-load-tool.test.ts +72 -0
  133. package/src/__tests__/skill-runtime-path.test.ts +2 -3
  134. package/src/__tests__/skills.test.ts +51 -0
  135. package/src/__tests__/slack-notification-approval-card.test.ts +176 -0
  136. package/src/__tests__/slack-reaction-canonical-approval.test.ts +285 -0
  137. package/src/__tests__/subagent-tools.test.ts +266 -0
  138. package/src/__tests__/surface-completion-nudge-hook.test.ts +367 -0
  139. package/src/__tests__/system-storage-cleanup-skill.test.ts +56 -0
  140. package/src/__tests__/task-progress-nudge-hook.test.ts +1 -1
  141. package/src/__tests__/title-generate-hook.test.ts +100 -3
  142. package/src/__tests__/token-estimator-accuracy.benchmark.test.ts +1 -29
  143. package/src/__tests__/token-manager.test.ts +519 -0
  144. package/src/__tests__/tool-approval-seed-content-blocks.test.ts +1 -1
  145. package/src/__tests__/tool-audit-listener.test.ts +7 -7
  146. package/src/__tests__/tool-executor-lifecycle-events.test.ts +6 -3
  147. package/src/__tests__/tool-executor.test.ts +0 -79
  148. package/src/__tests__/trusted-contact-approval-notifier.test.ts +4 -2
  149. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +220 -3
  150. package/src/__tests__/trusted-contact-multichannel.test.ts +3 -3
  151. package/src/__tests__/trusted-contact-verification.test.ts +8 -10
  152. package/src/__tests__/twilio-routes.test.ts +81 -1
  153. package/src/__tests__/voice-invite-redemption.test.ts +2 -3
  154. package/src/__tests__/weak-open-model.test.ts +30 -0
  155. package/src/__tests__/web-search-catalog-parity.test.ts +6 -25
  156. package/src/__tests__/workspace-greetings.test.ts +152 -0
  157. package/src/__tests__/workspace-migration-105-enable-memory-v3-live-for-new-workspaces.test.ts +149 -0
  158. package/src/__tests__/workspace-migration-108-drop-balanced-economy-profile.test.ts +285 -0
  159. package/src/__tests__/workspace-migration-add-send-diagnostics.test.ts +1 -1
  160. package/src/__tests__/workspace-migration-drop-collect-usage-data.test.ts +118 -0
  161. package/src/__tests__/workspace-migration-drop-send-diagnostics.test.ts +118 -0
  162. package/src/a2a/__tests__/e2e-a2a-channel.test.ts +0 -4
  163. package/src/agent/loop.ts +75 -29
  164. package/src/api/README.md +6 -6
  165. package/src/api/events/tool-result.ts +6 -0
  166. package/src/api/events/workflow-completed.ts +53 -0
  167. package/src/api/events/workflow-leaf-finished.ts +38 -0
  168. package/src/api/events/workflow-leaf-started.ts +35 -0
  169. package/src/api/events/workflow-progress.ts +32 -0
  170. package/src/api/events/workflow-started.ts +31 -0
  171. package/src/api/index.ts +40 -0
  172. package/src/api/responses/conversation-message.ts +28 -4
  173. package/src/api/responses/home.ts +26 -4
  174. package/src/api/responses/workflow-journal.ts +53 -0
  175. package/src/approvals/guardian-card-withdrawal.ts +145 -0
  176. package/src/approvals/guardian-decision-primitive.ts +26 -3
  177. package/src/approvals/guardian-request-resolvers.ts +183 -80
  178. package/src/calls/__tests__/channel-admission-reader.test.ts +132 -0
  179. package/src/calls/__tests__/relay-setup-router.test.ts +350 -0
  180. package/src/calls/call-pointer-messages.ts +10 -4
  181. package/src/calls/channel-admission-reader.ts +104 -0
  182. package/src/calls/guardian-dispatch.ts +17 -45
  183. package/src/calls/media-stream-server.ts +84 -2
  184. package/src/calls/relay-access-wait.ts +1 -1
  185. package/src/calls/relay-server.ts +66 -0
  186. package/src/calls/relay-setup-router.ts +82 -1
  187. package/src/calls/twilio-routes.ts +17 -8
  188. package/src/calls/voice-session-bridge.ts +2 -2
  189. package/src/cli/commands/clients.ts +3 -0
  190. package/src/cli/commands/db/status.ts +37 -3
  191. package/src/cli/commands/{__tests__ → memory/__tests__}/memory-v2-compare-render.test.ts +1 -1
  192. package/src/cli/commands/{__tests__ → memory/__tests__}/memory-v2.test.ts +8 -7
  193. package/src/cli/commands/{__tests__ → memory/__tests__}/memory-v3.test.ts +5 -4
  194. package/src/cli/commands/memory/index.ts +30 -0
  195. package/src/cli/commands/{memory-v2-compare-render.ts → memory/memory-v2-compare-render.ts} +1 -1
  196. package/src/cli/commands/{memory-v2.ts → memory/memory-v2.ts} +6 -15
  197. package/src/cli/commands/{memory-v3.ts → memory/memory-v3.ts} +97 -11
  198. package/src/cli/commands/oauth/status.test.ts +36 -0
  199. package/src/cli/commands/oauth/status.ts +23 -3
  200. package/src/cli/commands/plugins.ts +400 -14
  201. package/src/cli/lib/__tests__/diff-plugin.test.ts +443 -0
  202. package/src/cli/lib/__tests__/inspect-plugin.test.ts +54 -0
  203. package/src/cli/lib/__tests__/install-from-github.test.ts +31 -0
  204. package/src/cli/lib/__tests__/merge-plugin-tree.test.ts +443 -0
  205. package/src/cli/lib/__tests__/plugin-pin-history.test.ts +162 -0
  206. package/src/cli/lib/__tests__/plugin-surfaces.test.ts +111 -0
  207. package/src/cli/lib/__tests__/upgrade-plugin.test.ts +295 -2
  208. package/src/cli/lib/diff-plugin.ts +346 -0
  209. package/src/cli/lib/inspect-plugin.ts +12 -1
  210. package/src/cli/lib/install-from-github.ts +128 -19
  211. package/src/cli/lib/merge-plugin-tree.ts +328 -0
  212. package/src/cli/lib/plugin-fingerprint.ts +14 -0
  213. package/src/cli/lib/plugin-marketplace.ts +11 -0
  214. package/src/cli/lib/plugin-pin-history.ts +257 -0
  215. package/src/cli/lib/plugin-surfaces.ts +104 -0
  216. package/src/cli/lib/upgrade-plugin.ts +298 -10
  217. package/src/cli/program.ts +2 -6
  218. package/src/config/__tests__/sync-gated-profiles.test.ts +368 -0
  219. package/src/config/assistant-feature-flags.ts +22 -7
  220. package/src/config/bundled-skills/contacts/tools/contact-search.ts +0 -1
  221. package/src/config/bundled-skills/document-editor/SKILL.md +3 -3
  222. package/src/config/bundled-skills/document-editor/TOOLS.json +2 -2
  223. package/src/config/bundled-skills/messaging/SKILL.md +6 -4
  224. package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +9 -8
  225. package/src/config/bundled-skills/subagent/SKILL.md +4 -0
  226. package/src/config/bundled-skills/subagent/TOOLS.json +4 -0
  227. package/src/config/bundled-skills/system-storage-cleanup/SKILL.md +74 -0
  228. package/src/config/bundled-skills/workflows/SKILL.md +14 -8
  229. package/src/config/bundled-tool-registry.ts +2 -7
  230. package/src/config/call-site-defaults.ts +15 -2
  231. package/src/config/feature-flag-registry.json +46 -31
  232. package/src/config/inference-profile-validation.ts +26 -0
  233. package/src/config/llm-resolver.ts +3 -0
  234. package/src/config/loader.ts +4 -0
  235. package/src/config/memory-v3-gate.ts +11 -0
  236. package/src/config/profile-order.ts +28 -0
  237. package/src/config/schema.ts +8 -6
  238. package/src/config/schemas/__tests__/memory-v3.test.ts +1 -0
  239. package/src/config/schemas/call-site-catalog.ts +7 -0
  240. package/src/config/schemas/channels.ts +11 -0
  241. package/src/config/schemas/elevenlabs.ts +0 -1
  242. package/src/config/schemas/llm.ts +31 -0
  243. package/src/config/schemas/memory-lifecycle.ts +3 -7
  244. package/src/config/schemas/memory-v3.ts +6 -0
  245. package/src/config/schemas/platform.ts +0 -8
  246. package/src/config/schemas/services.ts +18 -0
  247. package/src/config/seed-inference-profiles.ts +109 -44
  248. package/src/config/skills.ts +21 -0
  249. package/src/config/sync-gated-profiles.ts +220 -0
  250. package/src/contacts/contact-store.ts +89 -106
  251. package/src/contacts/contacts-write.ts +5 -22
  252. package/src/contacts/types.ts +0 -1
  253. package/src/context/compactor.ts +88 -54
  254. package/src/context/strip-injections.ts +58 -10
  255. package/src/context/token-estimator.ts +1 -1
  256. package/src/credential-execution/process-manager.ts +55 -14
  257. package/src/credential-execution/prompted-credential.ts +2 -3
  258. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +3 -2
  259. package/src/daemon/__tests__/conversation-tool-setup.test.ts +44 -0
  260. package/src/daemon/config-watcher.ts +0 -4
  261. package/src/daemon/conversation-agent-loop-handlers.ts +2 -0
  262. package/src/daemon/conversation-agent-loop.ts +114 -22
  263. package/src/daemon/conversation-history.ts +1 -1
  264. package/src/daemon/conversation-lifecycle.ts +3 -5
  265. package/src/daemon/conversation-process.ts +13 -5
  266. package/src/daemon/conversation-runtime-assembly.ts +13 -15
  267. package/src/daemon/conversation-slash.ts +2 -23
  268. package/src/daemon/conversation-surfaces.ts +26 -0
  269. package/src/daemon/conversation-tool-setup.ts +53 -19
  270. package/src/daemon/conversation.ts +131 -20
  271. package/src/daemon/disk-pressure-policy.ts +5 -3
  272. package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +0 -1
  273. package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +0 -1
  274. package/src/daemon/handlers/config-a2a.ts +0 -2
  275. package/src/daemon/handlers/config-channels.ts +15 -16
  276. package/src/daemon/handlers/config-slack-channel.ts +22 -3
  277. package/src/daemon/handlers/conversations.ts +107 -0
  278. package/src/daemon/host-browser-proxy.ts +41 -0
  279. package/src/daemon/lifecycle.ts +55 -27
  280. package/src/daemon/memory-v2-startup.test.ts +131 -0
  281. package/src/daemon/memory-v2-startup.ts +100 -1
  282. package/src/daemon/message-provenance.ts +2 -0
  283. package/src/daemon/message-types/contacts.ts +0 -1
  284. package/src/daemon/message-types/conversations.ts +3 -3
  285. package/src/daemon/message-types/sync.ts +0 -1
  286. package/src/daemon/message-types/web-activity.ts +7 -1
  287. package/src/daemon/message-types/workflows.ts +83 -1
  288. package/src/daemon/orphan-reaper.test.ts +0 -19
  289. package/src/daemon/orphan-reaper.ts +2 -24
  290. package/src/daemon/server.ts +0 -10
  291. package/src/daemon/tool-setup-types.ts +4 -0
  292. package/src/daemon/trust-context.ts +1 -1
  293. package/src/daemon/wake-conversation-ops.ts +1 -0
  294. package/src/events/tool-audit-listener.ts +2 -2
  295. package/src/home/feed-source-enrichment.test.ts +151 -0
  296. package/src/home/feed-source-enrichment.ts +176 -0
  297. package/src/home/relationship-state.ts +2 -4
  298. package/src/instrument.ts +18 -6
  299. package/src/ipc/__tests__/binary-result-ipc.test.ts +81 -0
  300. package/src/ipc/__tests__/clients-list-ipc.test.ts +20 -0
  301. package/src/ipc/assistant-server.ts +37 -4
  302. package/src/ipc/gateway-flag-listener.ts +18 -2
  303. package/src/memory/__tests__/auto-analysis-enqueue.test.ts +5 -16
  304. package/src/memory/__tests__/db-logs-attach.test.ts +122 -0
  305. package/src/memory/__tests__/jobs-store-enqueue-gate.test.ts +7 -11
  306. package/src/memory/__tests__/memory-retrospective-enqueue.test.ts +37 -7
  307. package/src/memory/__tests__/memory-retrospective-job.test.ts +229 -401
  308. package/src/memory/__tests__/onboarding-events-store.test.ts +7 -7
  309. package/src/memory/auth-fallback-events-store.ts +2 -2
  310. package/src/memory/auto-analysis-enqueue.ts +3 -5
  311. package/src/memory/bookmark-crud.ts +1 -2
  312. package/src/memory/canonical-guardian-store.ts +39 -1
  313. package/src/memory/conversation-crud.ts +9 -4
  314. package/src/memory/conversation-key-store.ts +17 -2
  315. package/src/memory/conversation-title-service.ts +64 -7
  316. package/src/memory/db-async-query.ts +20 -2
  317. package/src/memory/db-connection.ts +49 -5
  318. package/src/memory/db-init.ts +17 -17
  319. package/src/memory/embedding-backend.ts +38 -1
  320. package/src/memory/embedding-billing-breaker.ts +96 -0
  321. package/src/memory/jobs-store.ts +25 -13
  322. package/src/memory/jobs-worker.ts +54 -1
  323. package/src/memory/lifecycle-events-store.ts +2 -2
  324. package/src/memory/memory-retrospective-constants.ts +4 -4
  325. package/src/memory/memory-retrospective-enqueue.ts +31 -6
  326. package/src/memory/memory-retrospective-job.ts +28 -227
  327. package/src/memory/migrations/129-contact-channels-access-fields.ts +18 -9
  328. package/src/memory/migrations/131-drop-legacy-member-guardian-tables.ts +14 -2
  329. package/src/memory/migrations/289-contact-channels-unique-ext-user.ts +10 -0
  330. package/src/memory/migrations/291-contact-channels-renormalize-addresses.ts +72 -0
  331. package/src/memory/migrations/292-schedule-default-no-reuse-conversation.test.ts +67 -0
  332. package/src/memory/migrations/292-schedule-default-no-reuse-conversation.ts +25 -0
  333. package/src/memory/migrations/293-workflow-journal-leaf-tokens.ts +32 -0
  334. package/src/memory/migrations/294-drop-external-user-id.ts +31 -0
  335. package/src/memory/migrations/295-drop-approval-prompt-ts-tracker.ts +20 -0
  336. package/src/memory/migrations/296-rewrite-balanced-economy-profile-pins.test.ts +110 -0
  337. package/src/memory/migrations/296-rewrite-balanced-economy-profile-pins.ts +68 -0
  338. package/src/memory/migrations/__tests__/131-drop-legacy-member-guardian-tables.test.ts +154 -0
  339. package/src/memory/migrations/__tests__/289-contact-channels-unique-ext-user.test.ts +31 -0
  340. package/src/memory/migrations/__tests__/291-contact-channels-renormalize-addresses.test.ts +341 -0
  341. package/src/memory/migrations/__tests__/run-migrations.test.ts +52 -0
  342. package/src/memory/migrations/index.ts +6 -0
  343. package/src/memory/migrations/run-migrations.ts +41 -0
  344. package/src/memory/migrations/validate-migration-state.ts +1 -1
  345. package/src/memory/onboarding-events-store.ts +3 -3
  346. package/src/memory/schema/contacts.ts +0 -5
  347. package/src/memory/skill-loaded-events-store.test.ts +7 -15
  348. package/src/memory/skill-loaded-events-store.ts +2 -2
  349. package/src/memory/tool-executed-events-store.test.ts +7 -7
  350. package/src/memory/turn-trace-store.test.ts +736 -0
  351. package/src/memory/turn-trace-store.ts +364 -0
  352. package/src/memory/v2/__tests__/consolidation-job.test.ts +8 -0
  353. package/src/memory/v2/__tests__/skill-content.test.ts +30 -0
  354. package/src/memory/v2/consolidation-job.ts +2 -2
  355. package/src/memory/v2/skill-content.ts +25 -7
  356. package/src/memory/v2/skill-store.ts +7 -1
  357. package/src/memory/v3-eval/__tests__/eval-packets.test.ts +248 -0
  358. package/src/memory/v3-eval/eval-packets.ts +546 -0
  359. package/src/messaging/providers/slack/adapter.ts +1 -1
  360. package/src/messaging/providers/slack/api.ts +31 -0
  361. package/src/messaging/providers/slack/send.test.ts +114 -2
  362. package/src/messaging/providers/slack/send.ts +30 -7
  363. package/src/messaging/providers/slack/withdraw.test.ts +200 -0
  364. package/src/messaging/providers/slack/withdraw.ts +161 -0
  365. package/src/notifications/AGENTS.md +2 -0
  366. package/src/notifications/access-request-copy.ts +72 -59
  367. package/src/notifications/adapters/shared.ts +29 -0
  368. package/src/notifications/adapters/slack.ts +58 -103
  369. package/src/notifications/adapters/telegram.ts +2 -20
  370. package/src/notifications/approval-card-data.ts +333 -0
  371. package/src/notifications/broadcaster.ts +16 -3
  372. package/src/notifications/canonical-delivery-recorder.ts +139 -0
  373. package/src/notifications/copy-composer.ts +3 -3
  374. package/src/notifications/decision-engine.ts +4 -2
  375. package/src/notifications/destination-resolver.ts +4 -6
  376. package/src/notifications/guardian-question-mode.ts +10 -0
  377. package/src/notifications/home-feed-side-effect.ts +7 -16
  378. package/src/notifications/notification-utils.ts +19 -20
  379. package/src/notifications/signal.ts +79 -43
  380. package/src/notifications/types.ts +98 -121
  381. package/src/oauth/AGENTS.md +5 -24
  382. package/src/permissions/checker.test.ts +51 -0
  383. package/src/permissions/checker.ts +185 -26
  384. package/src/permissions/ipc-risk-types.ts +24 -0
  385. package/src/permissions/question-prompter.test.ts +27 -0
  386. package/src/permissions/question-prompter.ts +4 -0
  387. package/src/permissions/secret-prompter.ts +15 -1
  388. package/src/platform/client.test.ts +119 -0
  389. package/src/platform/client.ts +66 -0
  390. package/src/platform/consent-cache.test.ts +267 -0
  391. package/src/platform/consent-cache.ts +174 -0
  392. package/src/plugin-api/constants.ts +1 -1
  393. package/src/plugin-api/index.ts +33 -1
  394. package/src/plugin-api/model-profiles.ts +33 -0
  395. package/src/plugin-api/types.ts +50 -2
  396. package/src/plugins/defaults/advisor/__tests__/advisor-gate.test.ts +56 -0
  397. package/src/plugins/defaults/advisor/__tests__/advisor-state-store.test.ts +43 -0
  398. package/src/plugins/defaults/advisor/__tests__/agent-loop-integration.test.ts +137 -0
  399. package/src/plugins/defaults/advisor/__tests__/consult.test.ts +153 -0
  400. package/src/plugins/defaults/advisor/__tests__/hooks.test.ts +138 -0
  401. package/src/plugins/defaults/advisor/__tests__/transcript.test.ts +147 -0
  402. package/src/plugins/defaults/advisor/advisor-gate.ts +29 -0
  403. package/src/plugins/defaults/advisor/advisor-state-store.ts +94 -0
  404. package/src/plugins/defaults/advisor/config.ts +21 -0
  405. package/src/plugins/defaults/advisor/consult.ts +93 -0
  406. package/src/plugins/defaults/advisor/hooks/post-model-call.ts +34 -0
  407. package/src/plugins/defaults/advisor/hooks/pre-model-call.ts +30 -0
  408. package/src/plugins/defaults/advisor/hooks/user-prompt-submit.ts +19 -0
  409. package/src/plugins/defaults/advisor/package.json +14 -0
  410. package/src/plugins/defaults/advisor/steering.ts +67 -0
  411. package/src/plugins/defaults/advisor/tools/advisor.ts +65 -0
  412. package/src/plugins/defaults/advisor/transcript.ts +76 -0
  413. package/src/plugins/defaults/compaction/window-manager.ts +18 -0
  414. package/src/plugins/defaults/index.ts +60 -0
  415. package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +22 -9
  416. package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit.ts +2 -2
  417. package/src/plugins/defaults/memory-retrieval/injectors.ts +4 -4
  418. package/src/plugins/defaults/memory-retrieval/tail-reinjection-strip.ts +64 -0
  419. package/src/plugins/defaults/memory-retrieval/unified-turn-context.ts +29 -21
  420. package/src/plugins/defaults/memory-v3-shadow/__tests__/carry-integration.test.ts +1 -0
  421. package/src/plugins/defaults/memory-v3-shadow/__tests__/injection.test.ts +1 -0
  422. package/src/plugins/defaults/memory-v3-shadow/__tests__/maintain-job.test.ts +129 -9
  423. package/src/plugins/defaults/memory-v3-shadow/__tests__/orchestrate.test.ts +31 -4
  424. package/src/plugins/defaults/memory-v3-shadow/__tests__/selection-log-store.test.ts +77 -2
  425. package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +1 -0
  426. package/src/plugins/defaults/memory-v3-shadow/injector.ts +7 -10
  427. package/src/plugins/defaults/memory-v3-shadow/maintain-job.ts +144 -11
  428. package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +32 -20
  429. package/src/plugins/defaults/memory-v3-shadow/selection-log-store.ts +56 -3
  430. package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +23 -2
  431. package/src/plugins/defaults/surface-completion-nudge/hooks/post-model-call.ts +276 -0
  432. package/src/plugins/defaults/surface-completion-nudge/hooks/stop.ts +22 -0
  433. package/src/plugins/defaults/surface-completion-nudge/nudge-state-store.ts +46 -0
  434. package/src/plugins/defaults/surface-completion-nudge/package.json +14 -0
  435. package/src/plugins/defaults/task-progress-nudge/hooks/post-tool-use.ts +3 -13
  436. package/src/plugins/defaults/title-generate/hooks/stop.ts +56 -21
  437. package/src/prompts/persona-resolver.ts +14 -4
  438. package/src/prompts/templates/system-sections.ts +7 -2
  439. package/src/providers/__tests__/count-tokens-forwarding.test.ts +98 -0
  440. package/src/providers/__tests__/provider-env-vars.test.ts +6 -0
  441. package/src/providers/__tests__/provider-secret-catalog.test.ts +1 -0
  442. package/src/providers/__tests__/retry-callsite.test.ts +176 -0
  443. package/src/providers/anthropic/client.ts +230 -175
  444. package/src/providers/atlascloud/client.ts +85 -0
  445. package/src/providers/call-site-routing.ts +10 -0
  446. package/src/providers/fetch-provider-catalog.ts +85 -0
  447. package/src/providers/gemini/client.ts +43 -0
  448. package/src/providers/inference/adapter-factory.ts +3 -0
  449. package/src/providers/model-catalog.ts +58 -0
  450. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +33 -0
  451. package/src/providers/openai/chat-completions-provider.ts +7 -0
  452. package/src/providers/openai/responses-provider.ts +10 -0
  453. package/src/providers/provider-send-message.ts +11 -3
  454. package/src/providers/ratelimit.ts +9 -0
  455. package/src/providers/retry.ts +62 -12
  456. package/src/providers/search-provider-catalog.ts +10 -0
  457. package/src/providers/types.ts +16 -0
  458. package/src/providers/usage-tracking.ts +7 -0
  459. package/src/providers/weak-open-model.ts +22 -0
  460. package/src/runtime/AGENTS.md +0 -1
  461. package/src/runtime/__tests__/agent-wake.test.ts +227 -0
  462. package/src/runtime/__tests__/client-health.test.ts +44 -0
  463. package/src/runtime/access-request-helper.ts +21 -53
  464. package/src/runtime/actor-trust-resolver.ts +59 -63
  465. package/src/runtime/agent-wake.ts +71 -0
  466. package/src/runtime/assistant-event-hub.ts +18 -4
  467. package/src/runtime/auth/__tests__/route-policy.test.ts +12 -0
  468. package/src/runtime/auth/require-bound-guardian.ts +1 -4
  469. package/src/runtime/btw-sidechain.ts +3 -6
  470. package/src/runtime/capabilities.test.ts +120 -0
  471. package/src/runtime/capabilities.ts +197 -0
  472. package/src/runtime/channel-approval-types.ts +22 -45
  473. package/src/runtime/channel-invite-transports/telegram.ts +4 -4
  474. package/src/runtime/channel-retry-sweep.ts +1 -0
  475. package/src/runtime/channel-verification-service.ts +3 -3
  476. package/src/runtime/client-health.ts +26 -0
  477. package/src/runtime/confirmation-request-guardian-bridge.ts +38 -29
  478. package/src/runtime/effective-capabilities.test.ts +128 -0
  479. package/src/runtime/effective-capabilities.ts +84 -0
  480. package/src/runtime/guardian-reply-router.ts +106 -21
  481. package/src/runtime/invite-redemption-service.ts +9 -25
  482. package/src/runtime/migrations/__tests__/vbundle-builder-fd-leak.test.ts +126 -0
  483. package/src/runtime/migrations/vbundle-builder.ts +49 -20
  484. package/src/runtime/pending-interactions.ts +35 -0
  485. package/src/runtime/routes/__tests__/client-routes.test.ts +13 -0
  486. package/src/runtime/routes/__tests__/conversation-management-routes.test.ts +67 -0
  487. package/src/runtime/routes/__tests__/plugins-routes.test.ts +383 -1
  488. package/src/runtime/routes/app-routes.ts +1 -1
  489. package/src/runtime/routes/approval-routes.ts +40 -9
  490. package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +2 -2
  491. package/src/runtime/routes/assets/vellum-design-system.css +1959 -0
  492. package/src/runtime/routes/browser-tabs-routes.ts +9 -0
  493. package/src/runtime/routes/btw-routes.ts +1 -27
  494. package/src/runtime/routes/canonical-guardian-expiry-sweep.ts +17 -8
  495. package/src/runtime/routes/client-routes.ts +10 -0
  496. package/src/runtime/routes/contact-routes.ts +31 -8
  497. package/src/runtime/routes/conversation-compaction-routes.ts +1 -1
  498. package/src/runtime/routes/conversation-management-routes.ts +80 -1
  499. package/src/runtime/routes/conversation-query-routes.ts +68 -22
  500. package/src/runtime/routes/conversation-routes.ts +39 -14
  501. package/src/runtime/routes/credential-routes.ts +40 -16
  502. package/src/runtime/routes/empty-state-greeting-cache.ts +1 -2
  503. package/src/runtime/routes/events-routes.ts +1 -3
  504. package/src/runtime/routes/guardian-approval-interception.ts +14 -73
  505. package/src/runtime/routes/guardian-approval-prompt.ts +22 -4
  506. package/src/runtime/routes/home-feed-routes.ts +8 -3
  507. package/src/runtime/routes/identity-routes.ts +1 -296
  508. package/src/runtime/routes/inbound-message-handler.ts +214 -228
  509. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +89 -7
  510. package/src/runtime/routes/inbound-stages/admission-policy.test.ts +154 -0
  511. package/src/runtime/routes/inbound-stages/admission-policy.ts +140 -0
  512. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +3 -3
  513. package/src/runtime/routes/inbound-stages/background-dispatch.ts +11 -6
  514. package/src/runtime/routes/inbound-stages/escalation-intercept.ts +1 -2
  515. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +1 -2
  516. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.test.ts +7 -7
  517. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +47 -28
  518. package/src/runtime/routes/inbound-stages/reaction-intercept.ts +358 -0
  519. package/src/runtime/routes/index.ts +2 -0
  520. package/src/runtime/routes/integrations/slack/__tests__/channel.test.ts +8 -0
  521. package/src/runtime/routes/integrations/slack/channel.ts +36 -0
  522. package/src/runtime/routes/internal-telemetry-routes.ts +1 -1
  523. package/src/runtime/routes/mcp-auth-routes.ts +233 -41
  524. package/src/runtime/routes/memory-eval-routes.ts +87 -0
  525. package/src/runtime/routes/notification-routes.ts +122 -133
  526. package/src/runtime/routes/platform-routes.ts +2 -2
  527. package/src/runtime/routes/plugins-routes.ts +366 -11
  528. package/src/runtime/routes/schedule-routes.ts +0 -22
  529. package/src/runtime/routes/secret-routes.ts +15 -0
  530. package/src/runtime/routes/surface-action-routes.ts +2 -1
  531. package/src/runtime/routes/tool-call-question-enrichment.test.ts +146 -0
  532. package/src/runtime/routes/tool-call-question-enrichment.ts +66 -0
  533. package/src/runtime/routes/workflow-routes.test.ts +229 -44
  534. package/src/runtime/routes/workflow-routes.ts +131 -29
  535. package/src/runtime/routes/workspace-greetings.ts +55 -0
  536. package/src/runtime/sync/resource-sync-events.ts +1 -11
  537. package/src/runtime/tool-grant-request-helper.ts +18 -16
  538. package/src/runtime/trust-context-resolver.ts +8 -5
  539. package/src/schedule/inference-profile.ts +2 -14
  540. package/src/schedule/schedule-store.ts +1 -1
  541. package/src/schedule/scheduler-types.ts +5 -1
  542. package/src/security/__tests__/provider-key-env-fallback.test.ts +6 -0
  543. package/src/security/secret-patterns.ts +3 -0
  544. package/src/subagent/manager.ts +17 -4
  545. package/src/subagent/types.ts +6 -0
  546. package/src/telemetry/trace-collection-policy.test.ts +28 -0
  547. package/src/telemetry/trace-collection-policy.ts +30 -0
  548. package/src/telemetry/types.ts +89 -0
  549. package/src/telemetry/usage-telemetry-reporter.test.ts +586 -36
  550. package/src/telemetry/usage-telemetry-reporter.ts +148 -41
  551. package/src/tools/AGENTS.md +3 -3
  552. package/src/tools/browser/__tests__/browser-execution-acquire.test.ts +31 -0
  553. package/src/tools/browser/browser-execution.ts +30 -19
  554. package/src/tools/document/document-tool.ts +30 -6
  555. package/src/tools/executor.ts +5 -3
  556. package/src/tools/host-terminal/host-shell.ts +5 -4
  557. package/src/tools/memory/register.ts +2 -2
  558. package/src/tools/network/__tests__/web-fetch-firecrawl.test.ts +360 -0
  559. package/src/tools/network/__tests__/web-search.test.ts +143 -0
  560. package/src/tools/network/web-fetch.ts +372 -1
  561. package/src/tools/network/web-search-error.ts +1 -1
  562. package/src/tools/network/web-search.ts +213 -10
  563. package/src/tools/permission-checker.ts +4 -3
  564. package/src/tools/registry.ts +20 -0
  565. package/src/tools/schedule/create.ts +7 -12
  566. package/src/tools/schedule/update.ts +4 -11
  567. package/src/tools/shared/filesystem/path-policy.ts +39 -13
  568. package/src/tools/side-effects.ts +2 -17
  569. package/src/tools/skills/execute.ts +99 -2
  570. package/src/tools/skills/load.ts +43 -2
  571. package/src/tools/subagent/spawn.ts +64 -15
  572. package/src/tools/terminal/shell.ts +10 -4
  573. package/src/tools/tool-approval-handler.ts +18 -13
  574. package/src/tools/tool-manifest.ts +0 -2
  575. package/src/tools/types.ts +10 -0
  576. package/src/tools/ui-surface/definitions.ts +64 -3
  577. package/src/tools/verification-control-plane-policy.ts +3 -1
  578. package/src/tools/workflows/run-workflow.test.ts +8 -18
  579. package/src/tools/workflows/run-workflow.ts +1 -0
  580. package/src/util/disk-usage.ts +78 -23
  581. package/src/util/logs-db-path.ts +22 -0
  582. package/src/util/platform.ts +10 -3
  583. package/src/watcher/telemetry.ts +2 -2
  584. package/src/workflows/capabilities.ts +2 -3
  585. package/src/workflows/engine.test.ts +175 -1
  586. package/src/workflows/engine.ts +82 -0
  587. package/src/workflows/journal-store.test.ts +70 -0
  588. package/src/workflows/journal-store.ts +18 -3
  589. package/src/workflows/run-manager.test.ts +171 -28
  590. package/src/workflows/run-manager.ts +66 -24
  591. package/src/workspace/migrations/105-enable-memory-v3-live-for-new-workspaces.ts +63 -0
  592. package/src/workspace/migrations/106-drop-collect-usage-data.ts +47 -0
  593. package/src/workspace/migrations/107-drop-send-diagnostics.ts +47 -0
  594. package/src/workspace/migrations/108-drop-balanced-economy-profile.ts +129 -0
  595. package/src/workspace/migrations/registry.ts +8 -0
  596. package/src/__tests__/app-control-no-global-cgevent.test.ts +0 -98
  597. package/src/__tests__/credential-security-e2e.test.ts +0 -362
  598. package/src/__tests__/credential-vault-unit.test.ts +0 -1528
  599. package/src/__tests__/credential-vault.test.ts +0 -1706
  600. package/src/__tests__/identity-intro-cache.test.ts +0 -315
  601. package/src/__tests__/secret-onetime-send.test.ts +0 -182
  602. package/src/cli/commands/__tests__/task.test.ts +0 -914
  603. package/src/cli/commands/task.ts +0 -771
  604. package/src/config/bundled-skills/personal-page/SKILL.md +0 -57
  605. package/src/config/bundled-skills/personal-page/TOOLS.json +0 -27
  606. package/src/config/bundled-skills/personal-page/tools/app-refresh.ts +0 -17
  607. package/src/config/preloaded-apps/personal-page/src/components/About.tsx +0 -22
  608. package/src/config/preloaded-apps/personal-page/src/components/App.tsx +0 -16
  609. package/src/config/preloaded-apps/personal-page/src/components/Features.tsx +0 -77
  610. package/src/config/preloaded-apps/personal-page/src/components/Hero.tsx +0 -57
  611. package/src/config/preloaded-apps/personal-page/src/components/Pending.tsx +0 -28
  612. package/src/config/preloaded-apps/personal-page/src/components/animations.tsx +0 -234
  613. package/src/config/preloaded-apps/personal-page/src/components/icons.tsx +0 -48
  614. package/src/config/preloaded-apps/personal-page/src/components/media.ts +0 -16
  615. package/src/config/preloaded-apps/personal-page/src/index.html +0 -20
  616. package/src/config/preloaded-apps/personal-page/src/main.tsx +0 -7
  617. package/src/config/preloaded-apps/personal-page/src/profile-data.ts +0 -82
  618. package/src/config/preloaded-apps/personal-page/src/styles.css +0 -759
  619. package/src/memory/__tests__/preloaded-apps.test.ts +0 -85
  620. package/src/memory/preloaded-apps.ts +0 -116
  621. package/src/notifications/tool-approval-copy.ts +0 -142
  622. package/src/runtime/routes/approval-prompt-ts-tracker.ts +0 -78
  623. package/src/runtime/routes/identity-intro-cache.ts +0 -172
  624. package/src/tools/credentials/vault.ts +0 -712
@@ -421,7 +421,7 @@ describe("schedule tools — workflow mode", () => {
421
421
  beforeEach(() => {
422
422
  getRawDb().run("DELETE FROM cron_runs");
423
423
  getRawDb().run("DELETE FROM cron_jobs");
424
- setOverridesForTesting({ workflows: true });
424
+ setOverridesForTesting({});
425
425
  });
426
426
  afterAll(() => {
427
427
  setOverridesForTesting({});
@@ -466,22 +466,6 @@ describe("schedule tools — workflow mode", () => {
466
466
  expect(result.content).toContain("workflow_name is required");
467
467
  });
468
468
 
469
- test("rejects workflow mode when the workflows flag is off", async () => {
470
- setOverridesForTesting({}); // flag off
471
- const result = await executeScheduleCreate(
472
- {
473
- name: "Flag off",
474
- expression: "0 8 * * *",
475
- mode: "workflow",
476
- workflow_name: "inbox-triage",
477
- },
478
- ctx,
479
- );
480
-
481
- expect(result.isError).toBe(true);
482
- expect(result.content).toContain("workflows are not enabled");
483
- });
484
-
485
469
  test("update to workflow mode requires a workflow_name", async () => {
486
470
  await executeScheduleCreate(
487
471
  { name: "To workflow", expression: "0 9 * * *", message: "test" },
@@ -544,7 +528,7 @@ describe("schedule_create — workflow capability manifest", () => {
544
528
  beforeEach(() => {
545
529
  getRawDb().run("DELETE FROM cron_runs");
546
530
  getRawDb().run("DELETE FROM cron_jobs");
547
- setOverridesForTesting({ workflows: true });
531
+ setOverridesForTesting({});
548
532
  // Deterministic registry so a declared side-effecting tool resolves.
549
533
  __clearRegistryForTesting();
550
534
  registerTool(makeFakeTool("file_write"));
@@ -210,10 +210,13 @@ describe("scheduler conversation reuse", () => {
210
210
  expect(runs2[0].conversationId).toBe(firstConversationId);
211
211
  });
212
212
 
213
- test("recurring schedule defaults to reuseConversation=true", async () => {
213
+ test("recurring schedule defaults to reuseConversation=false", async () => {
214
214
  /**
215
215
  * When no explicit reuseConversation is provided, recurring schedules
216
- * default to true — subsequent runs reuse the same conversation.
216
+ * default to false — each run gets a fresh conversation. This keeps a
217
+ * weak model's per-run context bounded instead of accumulating a long,
218
+ * self-similar transcript that primes it to repeat or drift; durable
219
+ * cross-run state belongs in workspace files and memory, not history.
217
220
  */
218
221
 
219
222
  // GIVEN a recurring schedule with no explicit reuseConversation
@@ -224,7 +227,7 @@ describe("scheduler conversation reuse", () => {
224
227
  message: "Default reuse message",
225
228
  syntax: "rrule",
226
229
  expression: rruleExpr,
227
- // no explicit reuseConversation — should default to true for recurring
230
+ // no explicit reuseConversation — should default to false
228
231
  });
229
232
 
230
233
  // WHEN the schedule fires for the first time
@@ -250,9 +253,9 @@ describe("scheduler conversation reuse", () => {
250
253
  await new Promise((resolve) => setTimeout(resolve, 500));
251
254
  scheduler2.stop();
252
255
 
253
- // THEN the same conversation is reused
256
+ // THEN a fresh conversation is created instead of reusing the first
254
257
  expect(processedMessages).toHaveLength(1);
255
- expect(processedMessages[0].conversationId).toBe(firstConversationId);
258
+ expect(processedMessages[0].conversationId).not.toBe(firstConversationId);
256
259
  });
257
260
 
258
261
  test("recurring schedule with reuseConversation=false creates new conversation each run", async () => {
@@ -106,6 +106,41 @@ describe("secret response routing", () => {
106
106
  await promise;
107
107
  });
108
108
 
109
+ test("prompt registers public secretDetails without the value", async () => {
110
+ const promise = prompter.prompt(
111
+ "github",
112
+ "token",
113
+ "GitHub Token",
114
+ "desc",
115
+ "placeholder",
116
+ "session-1",
117
+ "Push commits",
118
+ ["git_push"],
119
+ ["github.com"],
120
+ );
121
+ const msg = broadcastedMessages[0] as SecretRequestEvent;
122
+ const entry = _piStore.get(msg.requestId) as {
123
+ kind: string;
124
+ secretDetails?: Record<string, unknown>;
125
+ };
126
+ expect(entry.kind).toBe("secret");
127
+ expect(entry.secretDetails).toMatchObject({
128
+ service: "github",
129
+ field: "token",
130
+ label: "GitHub Token",
131
+ description: "desc",
132
+ placeholder: "placeholder",
133
+ purpose: "Push commits",
134
+ allowedTools: ["git_push"],
135
+ allowedDomains: ["github.com"],
136
+ });
137
+ // SECURITY: the secret value is never part of the registered metadata.
138
+ expect(JSON.stringify(entry.secretDetails)).not.toContain("test-value");
139
+ // Clean up
140
+ prompter.resolveSecret(msg.requestId, undefined);
141
+ await promise;
142
+ });
143
+
109
144
  test("resolveSecret for unknown requestId is a no-op", () => {
110
145
  // Should not throw
111
146
  prompter.resolveSecret("unknown-id", "value", "store");
@@ -12,7 +12,12 @@ let providerRefreshCalls = 0;
12
12
  const PLATFORM_BASE_URL = "https://platform.example.com";
13
13
  const ASSISTANT_API_KEY_PATH = credentialKey("vellum", "assistant_api_key");
14
14
  const PLATFORM_BASE_URL_PATH = credentialKey("vellum", "platform_base_url");
15
- const MANAGED_PROVIDERS = ["anthropic", "openai", "gemini", "fireworks"] as const;
15
+ const MANAGED_PROVIDERS = [
16
+ "anthropic",
17
+ "openai",
18
+ "gemini",
19
+ "fireworks",
20
+ ] as const;
16
21
 
17
22
  let platformBaseUrlOverride: string | undefined;
18
23
 
@@ -116,6 +121,13 @@ mock.module("../util/logger.js", () => ({
116
121
  }),
117
122
  }));
118
123
 
124
+ // `handleAddSecret` fires this detached when a managed-proxy credential lands —
125
+ // a v2-memory side effect outside this suite's provider-registry scope. Stub it
126
+ // to a no-op; its behavior is covered by memory-v2-startup.test.ts.
127
+ mock.module("../daemon/memory-v2-startup.js", () => ({
128
+ maybeReseedCapabilitiesAfterManagedCredential: async () => {},
129
+ }));
130
+
119
131
  import {
120
132
  getProviderRoutingSource,
121
133
  initializeProviders,
@@ -199,7 +211,9 @@ describe("secret routes managed proxy registry sync", () => {
199
211
  test("provider API key writes notify live-conversation refresh listeners", async () => {
200
212
  await addApiKey("fireworks", "fw-key");
201
213
 
202
- expect(secureKeyStore[credentialKey("fireworks", "api_key")]).toBe("fw-key");
214
+ expect(secureKeyStore[credentialKey("fireworks", "api_key")]).toBe(
215
+ "fw-key",
216
+ );
203
217
  expect(providerRefreshCalls).toBe(1);
204
218
 
205
219
  await deleteApiKey("fireworks");
@@ -1,6 +1,20 @@
1
1
  import { describe, expect, test } from "bun:test";
2
2
 
3
- import { resolveSkillExecuteInput } from "../tools/skills/execute.js";
3
+ import {
4
+ augmentSkillExecuteError,
5
+ recoverSkillExecuteEnvelope,
6
+ resolveSkillExecuteInput,
7
+ } from "../tools/skills/execute.js";
8
+
9
+ /** Schema with exactly one required string field (e.g. document_update). */
10
+ const SINGLE_REQUIRED_STRING_SCHEMA = {
11
+ type: "object",
12
+ properties: {
13
+ content: { type: "string" },
14
+ mode: { type: "string", enum: ["replace", "append"] },
15
+ },
16
+ required: ["content"],
17
+ };
4
18
 
5
19
  describe("resolveSkillExecuteInput", () => {
6
20
  test("returns a correctly nested object unchanged", () => {
@@ -82,4 +96,166 @@ describe("resolveSkillExecuteInput", () => {
82
96
  });
83
97
  expect(result).toEqual({ foo: "bar" });
84
98
  });
99
+
100
+ test("maps a bare (non-JSON) input string to the sole required string field", () => {
101
+ // The exact shape from the doc-writer incident: the full Markdown body
102
+ // passed as `input` instead of `{ "content": "..." }`.
103
+ const body = "# AI in 2026\n\nWe're halfway through the year.";
104
+ const result = resolveSkillExecuteInput(
105
+ { tool: "document_update", input: body, activity: "Streaming article" },
106
+ SINGLE_REQUIRED_STRING_SCHEMA,
107
+ );
108
+ expect(result).toEqual({ content: body });
109
+ });
110
+
111
+ test("does not map a bare string without the inner schema", () => {
112
+ const result = resolveSkillExecuteInput({
113
+ tool: "document_update",
114
+ input: "# AI in 2026",
115
+ activity: "Streaming article",
116
+ });
117
+ expect(result).toEqual({});
118
+ });
119
+
120
+ test("does not map a bare string when the schema has multiple required fields", () => {
121
+ const schema = {
122
+ type: "object",
123
+ properties: { a: { type: "string" }, b: { type: "string" } },
124
+ required: ["a", "b"],
125
+ };
126
+ const result = resolveSkillExecuteInput(
127
+ { tool: "t", input: "some text", activity: "x" },
128
+ schema,
129
+ );
130
+ expect(result).toEqual({});
131
+ });
132
+
133
+ test("does not map a bare string when the sole required field is not a string", () => {
134
+ const schema = {
135
+ type: "object",
136
+ properties: { count: { type: "number" } },
137
+ required: ["count"],
138
+ };
139
+ const result = resolveSkillExecuteInput(
140
+ { tool: "t", input: "42", activity: "x" },
141
+ schema,
142
+ );
143
+ // "42" parses as JSON but isn't an object, and the lone required field is
144
+ // not a string — no rescue applies.
145
+ expect(result).toEqual({});
146
+ });
147
+
148
+ test("a valid JSON-object string still wins over the bare-string rescue", () => {
149
+ const result = resolveSkillExecuteInput(
150
+ {
151
+ tool: "document_update",
152
+ input: '{"content":"hello","mode":"append"}',
153
+ activity: "x",
154
+ },
155
+ SINGLE_REQUIRED_STRING_SCHEMA,
156
+ );
157
+ expect(result).toEqual({ content: "hello", mode: "append" });
158
+ });
159
+
160
+ test("an empty input string is not rescued (nothing to map)", () => {
161
+ const result = resolveSkillExecuteInput(
162
+ { tool: "document_update", input: "", activity: "x" },
163
+ SINGLE_REQUIRED_STRING_SCHEMA,
164
+ );
165
+ expect(result).toEqual({});
166
+ });
167
+ });
168
+
169
+ describe("recoverSkillExecuteEnvelope", () => {
170
+ test("recovers a valid envelope wrapped under the _raw marker", () => {
171
+ // MiniMax coercion marks a bare-string `input` call unparseable even though
172
+ // the outer arguments are valid JSON.
173
+ const raw = JSON.stringify({
174
+ tool: "document_update",
175
+ input: "# AI in 2026\n\nbody",
176
+ activity: "Streaming",
177
+ });
178
+ const recovered = recoverSkillExecuteEnvelope({ _raw: raw });
179
+ expect(recovered).toEqual({
180
+ tool: "document_update",
181
+ input: "# AI in 2026\n\nbody",
182
+ activity: "Streaming",
183
+ });
184
+ });
185
+
186
+ test("leaves a genuinely unparseable (truncated) call wrapped", () => {
187
+ const wrapped = { _raw: '{"tool":"document_update","input":"# AI' };
188
+ expect(recoverSkillExecuteEnvelope(wrapped)).toBe(wrapped);
189
+ });
190
+
191
+ test("passes a normal envelope through untouched", () => {
192
+ const envelope = {
193
+ tool: "document_update",
194
+ input: { content: "hi" },
195
+ activity: "x",
196
+ };
197
+ expect(recoverSkillExecuteEnvelope(envelope)).toBe(envelope);
198
+ });
199
+
200
+ test("end-to-end: recovered bare-string envelope resolves to content", () => {
201
+ const body = "# Title\n\nThe full article body.";
202
+ const raw = JSON.stringify({
203
+ tool: "document_update",
204
+ input: body,
205
+ activity: "Streaming",
206
+ });
207
+ const envelope = recoverSkillExecuteEnvelope({ _raw: raw });
208
+ const resolved = resolveSkillExecuteInput(
209
+ envelope,
210
+ SINGLE_REQUIRED_STRING_SCHEMA,
211
+ );
212
+ expect(resolved).toEqual({ content: body });
213
+ });
214
+ });
215
+
216
+ describe("augmentSkillExecuteError", () => {
217
+ test("appends envelope guidance when an empty-input call errors", () => {
218
+ // The subagent_spawn failure: empty input, tool rejects with a field-level
219
+ // message that says nothing about the skill_execute envelope.
220
+ const result = augmentSkillExecuteError(
221
+ "subagent_spawn",
222
+ {},
223
+ { content: 'Both "label" and "objective" are required.', isError: true },
224
+ );
225
+ expect(result.isError).toBe(true);
226
+ expect(result.content).toContain(
227
+ 'Both "label" and "objective" are required.',
228
+ );
229
+ expect(result.content).toContain("carried no parameters");
230
+ expect(result.content).toContain('"tool": "subagent_spawn"');
231
+ expect(result.content).toContain("inside `input`");
232
+ // The guidance must not condemn the JSON-encoded-string form: the resolver
233
+ // accepts it (resolveSkillExecuteInput parses string input), and it is a
234
+ // shape weak models successfully use. Telling them it is wrong steers them
235
+ // toward dropping the payload entirely.
236
+ expect(result.content).not.toContain("JSON-encoded string");
237
+ });
238
+
239
+ test("leaves errors untouched when parameters were resolved", () => {
240
+ // A non-empty resolved input means the model structured the call; any error
241
+ // is a real tool-level failure, not an envelope-shape mistake.
242
+ const original = {
243
+ content: "Subagent quota exceeded.",
244
+ isError: true,
245
+ };
246
+ const result = augmentSkillExecuteError(
247
+ "subagent_spawn",
248
+ { label: "x", objective: "y" },
249
+ original,
250
+ );
251
+ expect(result).toBe(original);
252
+ });
253
+
254
+ test("leaves successful empty-input calls untouched", () => {
255
+ // Tools that legitimately accept no parameters (e.g. subagent_status) must
256
+ // not have guidance appended to their successful results.
257
+ const original = { content: "No subagents running.", isError: false };
258
+ const result = augmentSkillExecuteError("subagent_status", {}, original);
259
+ expect(result).toBe(original);
260
+ });
85
261
  });
@@ -100,6 +100,21 @@ async function executeSkillLoad(
100
100
  return { content: result.content, isError: result.isError };
101
101
  }
102
102
 
103
+ async function executeSkillLoadCleanup(
104
+ input: Record<string, unknown>,
105
+ ): Promise<{ content: string; isError: boolean }> {
106
+ const tool = getTool("skill_load");
107
+ if (!tool) throw new Error("skill_load tool was not registered");
108
+
109
+ const result = await tool.execute(input, {
110
+ workingDir: "/tmp",
111
+ conversationId: "conversation-1",
112
+ trustClass: "guardian",
113
+ diskPressureCleanupModeActive: true,
114
+ });
115
+ return { content: result.content, isError: result.isError };
116
+ }
117
+
103
118
  describe("skill_load tool", () => {
104
119
  beforeEach(() => {
105
120
  mkdirSync(join(TEST_DIR, "skills"), { recursive: true });
@@ -911,4 +926,61 @@ describe("skill_load tool", () => {
911
926
  expect(result.content).toContain("Suggested Included Skills (not loaded):");
912
927
  expect(installCount).toBeLessThanOrEqual(5);
913
928
  });
929
+
930
+ // During disk-pressure cleanup mode, skill_load is allowed but must not
931
+ // produce side effects: no catalog auto-install (workspace writes / bun
932
+ // install) and no inline command execution. Instructions are still returned.
933
+ test("does not auto-install a missing catalog skill during cleanup mode", async () => {
934
+ mockAutoInstall.mockImplementation((skillId: string) => {
935
+ writeSkill(skillId, "Should Not Install", "nope", "body");
936
+ return Promise.resolve(true);
937
+ });
938
+
939
+ const result = await executeSkillLoadCleanup({ skill: "absent-skill" });
940
+
941
+ expect(mockAutoInstall).not.toHaveBeenCalled();
942
+ expect(result.isError).toBe(true);
943
+ });
944
+
945
+ test("does not auto-install missing includes during cleanup mode", async () => {
946
+ writeSkillWithIncludes(
947
+ "cleanup-parent",
948
+ "Cleanup Parent",
949
+ "Has a missing include",
950
+ "Parent body",
951
+ ["cleanup-dep"],
952
+ );
953
+ mockAutoInstall.mockImplementation((skillId: string) => {
954
+ writeSkill(skillId, "Cleanup Dep", "should not install", "Dep body");
955
+ return Promise.resolve(true);
956
+ });
957
+
958
+ const result = await executeSkillLoadCleanup({ skill: "cleanup-parent" });
959
+
960
+ expect(mockAutoInstall).not.toHaveBeenCalled();
961
+ expect(result.isError).toBe(false);
962
+ expect(result.content).toContain("Skill: Cleanup Parent");
963
+ expect(result.content).toContain("Suggested Included Skills (not loaded):");
964
+ expect(result.content).toContain("cleanup-dep");
965
+ });
966
+
967
+ test("strips inline command tokens instead of executing them during cleanup mode", async () => {
968
+ writeSkill(
969
+ "cleanup-inline",
970
+ "Cleanup Inline",
971
+ "Has an inline command",
972
+ "Before !`echo SENTINEL_OUTPUT` after",
973
+ );
974
+
975
+ const result = await executeSkillLoadCleanup({ skill: "cleanup-inline" });
976
+
977
+ expect(result.isError).toBe(false);
978
+ expect(result.content).toContain(
979
+ "[inline command skipped: storage cleanup mode]",
980
+ );
981
+ expect(result.content).not.toContain("SENTINEL_OUTPUT");
982
+ expect(result.content).not.toContain("!`echo SENTINEL_OUTPUT`");
983
+ expect(result.content).toContain("Before");
984
+ expect(result.content).toContain("after");
985
+ });
914
986
  });
@@ -9,9 +9,8 @@
9
9
  * branches (macOS `.app` Resources, next-to-binary) key off
10
10
  * `import.meta.dir.startsWith("/$bunfs/")`, so at test time only the
11
11
  * source-mode early-return is exercised here; the compiled branch is
12
- * covered structurally via the signing + packaging step in
13
- * `clients/macos/build.sh` and will be exercised end-to-end by the
14
- * supervisor integration test added in PR 27.
12
+ * covered structurally via the Electron signing + packaging step in
13
+ * `clients/macos/scripts/pack.sh`.
15
14
  */
16
15
 
17
16
  import { mkdirSync, rmSync, writeFileSync } from "node:fs";
@@ -691,6 +691,57 @@ describe("category frontmatter parsing", () => {
691
691
  });
692
692
  });
693
693
 
694
+ describe("always-candidate frontmatter parsing", () => {
695
+ beforeEach(() => {
696
+ mkdirSync(join(TEST_DIR, "skills"), { recursive: true });
697
+ });
698
+
699
+ afterEach(() => {
700
+ const skillsDir = join(TEST_DIR, "skills");
701
+ if (existsSync(skillsDir))
702
+ rmSync(skillsDir, { recursive: true, force: true });
703
+ });
704
+
705
+ function writeSkillWithAlwaysCandidate(skillId: string, value: string): void {
706
+ const skillDir = join(TEST_DIR, "skills", skillId);
707
+ mkdirSync(skillDir, { recursive: true });
708
+ const metadata = `{"vellum":{"always-candidate":${value}}}`;
709
+ writeFileSync(
710
+ join(skillDir, "SKILL.md"),
711
+ `---\nname: "${skillId}"\ndescription: "test"\nmetadata: ${metadata}\n---\n\nBody.\n`,
712
+ );
713
+ }
714
+
715
+ test("parses always-candidate: true from metadata.vellum", () => {
716
+ writeSkillWithAlwaysCandidate("pinned", "true");
717
+ const skill = loadUserSkillCatalog().find((s) => s.id === "pinned");
718
+ expect(skill!.alwaysCandidate).toBe(true);
719
+ });
720
+
721
+ test("parses always-candidate: false", () => {
722
+ writeSkillWithAlwaysCandidate("unpinned", "false");
723
+ const skill = loadUserSkillCatalog().find((s) => s.id === "unpinned");
724
+ expect(skill!.alwaysCandidate).toBe(false);
725
+ });
726
+
727
+ test("returns undefined for a non-boolean always-candidate", () => {
728
+ writeSkillWithAlwaysCandidate("bad", '"yes"');
729
+ const skill = loadUserSkillCatalog().find((s) => s.id === "bad");
730
+ expect(skill!.alwaysCandidate).toBeUndefined();
731
+ });
732
+
733
+ test("skill without always-candidate has undefined", () => {
734
+ writeSkill("plain", "Plain", "Test");
735
+ const skill = loadUserSkillCatalog().find((s) => s.id === "plain");
736
+ expect(skill!.alwaysCandidate).toBeUndefined();
737
+ });
738
+
739
+ test("the bundled workflows skill is flagged always-candidate", () => {
740
+ const wf = loadSkillCatalog().find((s) => s.id === "workflows");
741
+ expect(wf?.alwaysCandidate).toBe(true);
742
+ });
743
+ });
744
+
694
745
  describe("bundled skill categories", () => {
695
746
  test("every bundled skill declares a valid category slug", () => {
696
747
  const yamlPath = join(
@@ -0,0 +1,176 @@
1
+ import { describe, expect, test } from "bun:test";
2
+
3
+ import { parseAccessRequestPayload } from "../notifications/access-request-copy.js";
4
+ import { buildApprovalNotificationBlocks } from "../notifications/adapters/slack.js";
5
+ import type { ChannelDeliveryPayload } from "../notifications/types.js";
6
+ import type { ApprovalUIMetadata } from "../runtime/channel-approval-types.js";
7
+
8
+ /**
9
+ * Pins the Slack approval-card block contract: the title, identity subtitle,
10
+ * quoted-preview body, action callback ids, source/permalink and requester-id
11
+ * context blocks, the security-warning context block, and guardian
12
+ * verification note that `buildApprovalNotificationBlocks` emits for an
13
+ * access request.
14
+ */
15
+
16
+ const APPROVAL: ApprovalUIMetadata = {
17
+ requestId: "req-123",
18
+ actions: [
19
+ { id: "approve_once", label: "Approve once" },
20
+ { id: "reject", label: "Reject" },
21
+ ],
22
+ plainTextFallback: 'Reply "ABC123 approve" or "ABC123 reject"',
23
+ };
24
+
25
+ function buildPayload(
26
+ raw: Record<string, unknown>,
27
+ approval: ApprovalUIMetadata = APPROVAL,
28
+ ): ChannelDeliveryPayload {
29
+ return {
30
+ sourceEventName: "ingress.access_request",
31
+ copy: { title: "Access Request", body: "Someone is requesting access" },
32
+ urgency: "high",
33
+ approvalContext: approval,
34
+ accessRequestContext: parseAccessRequestPayload(raw),
35
+ };
36
+ }
37
+
38
+ type Block = Record<string, unknown>;
39
+
40
+ function card(blocks: unknown[]): Block {
41
+ const c = (blocks as Block[]).find((b) => b.type === "card");
42
+ if (!c) throw new Error("no card block");
43
+ return c;
44
+ }
45
+
46
+ function contextTexts(blocks: unknown[]): string[] {
47
+ return (blocks as Block[])
48
+ .filter((b) => b.type === "context")
49
+ .map((b) => {
50
+ const elements = b.elements as Array<{ text: string }>;
51
+ return elements.map((e) => e.text).join("");
52
+ });
53
+ }
54
+
55
+ function text(node: unknown): string {
56
+ return (node as { text: string }).text;
57
+ }
58
+
59
+ const BASE: Record<string, unknown> = {
60
+ requestId: "req-123",
61
+ requestCode: "ABC123",
62
+ sourceChannel: "slack",
63
+ conversationExternalId: "C01ABC",
64
+ actorExternalId: "U999",
65
+ actorDisplayName: "Alice",
66
+ actorUsername: "alice",
67
+ senderIdentifier: "U999",
68
+ messagePreview: "Hello, I need help with something",
69
+ messageTs: "1700000000.000100",
70
+ };
71
+
72
+ describe("Slack access-request card blocks", () => {
73
+ test("card carries title, identity subtitle, and quoted preview body", () => {
74
+ const c = card(buildApprovalNotificationBlocks(buildPayload(BASE), "msg"));
75
+ expect(text(c.title)).toBe("Access Request");
76
+ expect(text(c.subtitle)).toBe("Alice (@alice) via slack");
77
+ expect(text(c.body)).toBe('> _"Hello, I need help with something"_');
78
+ });
79
+
80
+ test("card actions encode the apr:<requestId>:<action> callback ids", () => {
81
+ const c = card(buildApprovalNotificationBlocks(buildPayload(BASE), "msg"));
82
+ const actions = c.actions as Array<Record<string, unknown>>;
83
+ expect(actions).toHaveLength(2);
84
+ expect(actions[0].action_id).toBe("apr:req-123:approve_once");
85
+ expect(actions[0].style).toBe("primary");
86
+ expect(actions[1].action_id).toBe("apr:req-123:reject");
87
+ expect(actions[1].style).toBe("danger");
88
+ });
89
+
90
+ test("source context renders a channel mention with permalink", () => {
91
+ const texts = contextTexts(
92
+ buildApprovalNotificationBlocks(buildPayload(BASE), "msg"),
93
+ );
94
+ expect(texts).toContain(
95
+ "Source: Slack — <#C01ABC> · <https://slack.com/archives/C01ABC/p1700000000000100|View message>",
96
+ );
97
+ });
98
+
99
+ test("DM source renders as Direct message", () => {
100
+ const blocks = buildApprovalNotificationBlocks(
101
+ buildPayload({ ...BASE, conversationExternalId: "D01XYZ" }),
102
+ "msg",
103
+ );
104
+ expect(contextTexts(blocks)).toContain(
105
+ "Source: Slack — Direct message · <https://slack.com/archives/D01XYZ/p1700000000000100|View message>",
106
+ );
107
+ });
108
+
109
+ test("requester id block appears when external id adds info", () => {
110
+ const texts = contextTexts(
111
+ buildApprovalNotificationBlocks(buildPayload(BASE), "msg"),
112
+ );
113
+ expect(texts).toContain("ID: U999");
114
+ });
115
+
116
+ test("invite directive context block is always present", () => {
117
+ const texts = contextTexts(
118
+ buildApprovalNotificationBlocks(buildPayload(BASE), "msg"),
119
+ );
120
+ expect(texts).toContain(
121
+ 'Reply "open invite flow" to start Trusted Contacts invite flow.',
122
+ );
123
+ });
124
+
125
+ test("warnings render in a context block under the card", () => {
126
+ const blocks = buildApprovalNotificationBlocks(
127
+ buildPayload({
128
+ ...BASE,
129
+ isStranger: true,
130
+ isRestricted: true,
131
+ previousMemberStatus: "revoked",
132
+ }),
133
+ "msg",
134
+ );
135
+ // `subtext` is not a Slack card field; warnings must live in a real block
136
+ // or Slack drops them and the guardian never sees them.
137
+ expect(card(blocks).subtext).toBeUndefined();
138
+ const warning = contextTexts(blocks).find((t) => t.includes(":warning:"));
139
+ expect(warning).toBeDefined();
140
+ expect(warning).toContain(":warning: This user was previously revoked.");
141
+ expect(warning).toContain(
142
+ ":warning: External Slack user (not in this workspace).",
143
+ );
144
+ expect(warning).toContain(":warning: Guest / restricted account.");
145
+ });
146
+
147
+ test("no warning context block when there are no warnings", () => {
148
+ const blocks = buildApprovalNotificationBlocks(buildPayload(BASE), "msg");
149
+ expect(card(blocks).subtext).toBeUndefined();
150
+ expect(contextTexts(blocks).some((t) => t.includes(":warning:"))).toBe(
151
+ false,
152
+ );
153
+ });
154
+
155
+ test("body falls back to a default label when no preview", () => {
156
+ const c = card(
157
+ buildApprovalNotificationBlocks(
158
+ buildPayload({ ...BASE, messagePreview: undefined }),
159
+ "msg",
160
+ ),
161
+ );
162
+ expect(text(c.body)).toBe("Requesting access to the assistant");
163
+ });
164
+
165
+ test("guardian verification note appears for fallback guardian resolution", () => {
166
+ const texts = contextTexts(
167
+ buildApprovalNotificationBlocks(
168
+ buildPayload({ ...BASE, guardianResolutionSource: "vellum-anchor" }),
169
+ "msg",
170
+ ),
171
+ );
172
+ expect(
173
+ texts.some((t) => t.includes("haven't verified your identity on slack")),
174
+ ).toBe(true);
175
+ });
176
+ });