@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
@@ -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 truesubsequent runs reuse the same conversation.
216
+ * default to falseeach 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 () => {
@@ -1,6 +1,9 @@
1
1
  import { describe, expect, test } from "bun:test";
2
2
 
3
- import { resolveSkillExecuteInput } from "../tools/skills/execute.js";
3
+ import {
4
+ augmentSkillExecuteError,
5
+ resolveSkillExecuteInput,
6
+ } from "../tools/skills/execute.js";
4
7
 
5
8
  describe("resolveSkillExecuteInput", () => {
6
9
  test("returns a correctly nested object unchanged", () => {
@@ -83,3 +86,50 @@ describe("resolveSkillExecuteInput", () => {
83
86
  expect(result).toEqual({ foo: "bar" });
84
87
  });
85
88
  });
89
+
90
+ describe("augmentSkillExecuteError", () => {
91
+ test("appends envelope guidance when an empty-input call errors", () => {
92
+ // The subagent_spawn failure: empty input, tool rejects with a field-level
93
+ // message that says nothing about the skill_execute envelope.
94
+ const result = augmentSkillExecuteError(
95
+ "subagent_spawn",
96
+ {},
97
+ { content: 'Both "label" and "objective" are required.', isError: true },
98
+ );
99
+ expect(result.isError).toBe(true);
100
+ expect(result.content).toContain(
101
+ 'Both "label" and "objective" are required.',
102
+ );
103
+ expect(result.content).toContain("carried no parameters");
104
+ expect(result.content).toContain('"tool": "subagent_spawn"');
105
+ expect(result.content).toContain("inside `input`");
106
+ // The guidance must not condemn the JSON-encoded-string form: the resolver
107
+ // accepts it (resolveSkillExecuteInput parses string input), and it is a
108
+ // shape weak models successfully use. Telling them it is wrong steers them
109
+ // toward dropping the payload entirely.
110
+ expect(result.content).not.toContain("JSON-encoded string");
111
+ });
112
+
113
+ test("leaves errors untouched when parameters were resolved", () => {
114
+ // A non-empty resolved input means the model structured the call; any error
115
+ // is a real tool-level failure, not an envelope-shape mistake.
116
+ const original = {
117
+ content: "Subagent quota exceeded.",
118
+ isError: true,
119
+ };
120
+ const result = augmentSkillExecuteError(
121
+ "subagent_spawn",
122
+ { label: "x", objective: "y" },
123
+ original,
124
+ );
125
+ expect(result).toBe(original);
126
+ });
127
+
128
+ test("leaves successful empty-input calls untouched", () => {
129
+ // Tools that legitimately accept no parameters (e.g. subagent_status) must
130
+ // not have guidance appended to their successful results.
131
+ const original = { content: "No subagents running.", isError: false };
132
+ const result = augmentSkillExecuteError("subagent_status", {}, original);
133
+ expect(result).toBe(original);
134
+ });
135
+ });
@@ -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
+ });
@@ -0,0 +1,285 @@
1
+ /**
2
+ * Tests for guardian approval-by-reaction on the canonical pipeline.
3
+ *
4
+ * A Slack emoji reaction (✅ / ❌) on a delivered approval card is routed
5
+ * through `routeGuardianReply` exactly like a button press or text reply. The
6
+ * emoji maps to an action and the reacted card's message id (`reactedMessageTs`)
7
+ * resolves the target request via its canonical delivery record — so the
8
+ * decision is applied precisely even when several cards are pending in the same
9
+ * chat, with no clarification prompt.
10
+ */
11
+
12
+ import { beforeEach, describe, expect, mock, test } from "bun:test";
13
+
14
+ mock.module("../util/logger.js", () => ({
15
+ getLogger: () =>
16
+ new Proxy({} as Record<string, unknown>, {
17
+ get: () => () => {},
18
+ }),
19
+ truncateForLog: (value: string) => value,
20
+ }));
21
+
22
+ const _conversationMocks = new Map<string, unknown>();
23
+ mock.module("../daemon/conversation-registry.js", () => ({
24
+ findConversation: (id: string) => _conversationMocks.get(id),
25
+ }));
26
+
27
+ import type { Conversation } from "../daemon/conversation.js";
28
+ import {
29
+ createCanonicalGuardianDelivery,
30
+ createCanonicalGuardianRequest,
31
+ getCanonicalGuardianRequest,
32
+ getPendingCanonicalRequestByDestinationMessage,
33
+ resolveCanonicalGuardianRequest,
34
+ } from "../memory/canonical-guardian-store.js";
35
+ import { getDb } from "../memory/db-connection.js";
36
+ import { initializeDb } from "../memory/db-init.js";
37
+ import { routeGuardianReply } from "../runtime/guardian-reply-router.js";
38
+ import * as pendingInteractions from "../runtime/pending-interactions.js";
39
+
40
+ initializeDb();
41
+
42
+ // ---------------------------------------------------------------------------
43
+ // Constants & helpers
44
+ // ---------------------------------------------------------------------------
45
+
46
+ const GUARDIAN_USER = "U_GUARDIAN";
47
+ const PRINCIPAL = "principal-1";
48
+ const CHAT_ID = "D_GUARDIAN_DM";
49
+ const TOOL_NAME = "execute_shell";
50
+
51
+ function resetTables(): void {
52
+ const db = getDb();
53
+ db.run("DELETE FROM canonical_guardian_deliveries");
54
+ db.run("DELETE FROM canonical_guardian_requests");
55
+ db.run("DELETE FROM scoped_approval_grants");
56
+ pendingInteractions.clear();
57
+ _conversationMocks.clear();
58
+ }
59
+
60
+ /**
61
+ * Seed a pending tool-approval (canonical request + delivery + pending
62
+ * interaction with a mock conversation) and return the mocked
63
+ * `handleConfirmationResponse` so callers can assert it was driven.
64
+ */
65
+ function seedApproval(opts: {
66
+ requestId: string;
67
+ ts: string;
68
+ chatId?: string;
69
+ principal?: string;
70
+ }): ReturnType<typeof mock> {
71
+ const chatId = opts.chatId ?? CHAT_ID;
72
+ const principal = opts.principal ?? PRINCIPAL;
73
+ const conversationId = `conv-${opts.requestId}`;
74
+
75
+ createCanonicalGuardianRequest({
76
+ id: opts.requestId,
77
+ kind: "tool_approval",
78
+ sourceType: "channel",
79
+ sourceChannel: "slack",
80
+ conversationId,
81
+ requesterExternalUserId: "requester-1",
82
+ requesterChatId: chatId,
83
+ guardianExternalUserId: GUARDIAN_USER,
84
+ guardianPrincipalId: principal,
85
+ toolName: TOOL_NAME,
86
+ status: "pending",
87
+ expiresAt: Date.now() + 300_000,
88
+ });
89
+
90
+ createCanonicalGuardianDelivery({
91
+ requestId: opts.requestId,
92
+ destinationChannel: "slack",
93
+ destinationChatId: chatId,
94
+ destinationMessageId: opts.ts,
95
+ });
96
+
97
+ const handleConfirmationResponse = mock(() => {});
98
+ _conversationMocks.set(conversationId, {
99
+ handleConfirmationResponse,
100
+ ensureActorScopedHistory: async () => {},
101
+ } as unknown as Conversation);
102
+ pendingInteractions.register(opts.requestId, {
103
+ conversationId,
104
+ kind: "confirmation",
105
+ confirmationDetails: {
106
+ toolName: TOOL_NAME,
107
+ input: { command: "echo hi" },
108
+ riskLevel: "high",
109
+ allowlistOptions: [{ label: "t", description: "t", pattern: "t" }],
110
+ scopeOptions: [{ label: "everywhere", scope: "everywhere" }],
111
+ },
112
+ });
113
+ return handleConfirmationResponse;
114
+ }
115
+
116
+ function reaction(opts: {
117
+ emoji: string;
118
+ reactedMessageTs?: string;
119
+ chatId?: string;
120
+ principal?: string;
121
+ }): Parameters<typeof routeGuardianReply>[0] {
122
+ return {
123
+ messageText: "",
124
+ channel: "slack",
125
+ actor: {
126
+ actorPrincipalId: opts.principal ?? PRINCIPAL,
127
+ actorExternalUserId: GUARDIAN_USER,
128
+ channel: "slack",
129
+ guardianPrincipalId: opts.principal ?? PRINCIPAL,
130
+ },
131
+ conversationId: "guardian-conv",
132
+ callbackData: `reaction:${opts.emoji}`,
133
+ reactedMessageTs: opts.reactedMessageTs,
134
+ channelDeliveryContext: {
135
+ replyCallbackUrl: "https://gateway.test/deliver",
136
+ guardianChatId: opts.chatId ?? CHAT_ID,
137
+ assistantId: "self",
138
+ },
139
+ };
140
+ }
141
+
142
+ // ---------------------------------------------------------------------------
143
+ // Store lookup
144
+ // ---------------------------------------------------------------------------
145
+
146
+ describe("getPendingCanonicalRequestByDestinationMessage", () => {
147
+ beforeEach(resetTables);
148
+
149
+ test("resolves the request whose delivery matches channel + chat + message id", () => {
150
+ seedApproval({ requestId: "req-1", ts: "1700000000.000001" });
151
+ const found = getPendingCanonicalRequestByDestinationMessage(
152
+ "slack",
153
+ CHAT_ID,
154
+ "1700000000.000001",
155
+ );
156
+ expect(found?.id).toBe("req-1");
157
+ });
158
+
159
+ test("returns null when no delivery matches the message id", () => {
160
+ seedApproval({ requestId: "req-1", ts: "1700000000.000001" });
161
+ expect(
162
+ getPendingCanonicalRequestByDestinationMessage(
163
+ "slack",
164
+ CHAT_ID,
165
+ "9999.0",
166
+ ),
167
+ ).toBeNull();
168
+ });
169
+
170
+ test("returns null once the matched request is no longer pending", () => {
171
+ seedApproval({ requestId: "req-1", ts: "1700000000.000001" });
172
+ resolveCanonicalGuardianRequest("req-1", "pending", { status: "approved" });
173
+ expect(
174
+ getPendingCanonicalRequestByDestinationMessage(
175
+ "slack",
176
+ CHAT_ID,
177
+ "1700000000.000001",
178
+ ),
179
+ ).toBeNull();
180
+ });
181
+ });
182
+
183
+ // ---------------------------------------------------------------------------
184
+ // Reaction routing
185
+ // ---------------------------------------------------------------------------
186
+
187
+ describe("routeGuardianReply / reactions", () => {
188
+ beforeEach(resetTables);
189
+
190
+ test("✅ on a delivered card approves the matching request", async () => {
191
+ const hcr = seedApproval({ requestId: "req-1", ts: "111.1" });
192
+
193
+ const result = await routeGuardianReply(
194
+ reaction({ emoji: "white_check_mark", reactedMessageTs: "111.1" }),
195
+ );
196
+
197
+ expect(result.consumed).toBe(true);
198
+ expect(result.decisionApplied).toBe(true);
199
+ expect(result.type).toBe("canonical_decision_applied");
200
+ expect(getCanonicalGuardianRequest("req-1")?.status).toBe("approved");
201
+ expect(hcr).toHaveBeenCalledTimes(1);
202
+ expect(hcr.mock.calls[0]?.[0]).toBe("req-1");
203
+ expect(hcr.mock.calls[0]?.[1]).toBe("allow");
204
+ });
205
+
206
+ test("❌ on a delivered card rejects the matching request", async () => {
207
+ const hcr = seedApproval({ requestId: "req-1", ts: "111.1" });
208
+
209
+ const result = await routeGuardianReply(
210
+ reaction({ emoji: "-1", reactedMessageTs: "111.1" }),
211
+ );
212
+
213
+ expect(result.decisionApplied).toBe(true);
214
+ expect(getCanonicalGuardianRequest("req-1")?.status).toBe("denied");
215
+ expect(hcr.mock.calls[0]?.[1]).toBe("deny");
216
+ });
217
+
218
+ test("reacting on a specific card resolves only that card (disambiguates N>1)", async () => {
219
+ const hcrA = seedApproval({ requestId: "req-A", ts: "100.1" });
220
+ const hcrB = seedApproval({ requestId: "req-B", ts: "200.2" });
221
+
222
+ // React on card B's message — only B should resolve.
223
+ const result = await routeGuardianReply(
224
+ reaction({ emoji: "white_check_mark", reactedMessageTs: "200.2" }),
225
+ );
226
+
227
+ expect(result.requestId).toBe("req-B");
228
+ expect(getCanonicalGuardianRequest("req-B")?.status).toBe("approved");
229
+ expect(getCanonicalGuardianRequest("req-A")?.status).toBe("pending");
230
+ expect(hcrB).toHaveBeenCalledTimes(1);
231
+ expect(hcrA).not.toHaveBeenCalled();
232
+ });
233
+
234
+ test("a reaction on an unknown message is not consumed (left for transcript)", async () => {
235
+ seedApproval({ requestId: "req-1", ts: "111.1" });
236
+
237
+ const result = await routeGuardianReply(
238
+ reaction({ emoji: "white_check_mark", reactedMessageTs: "no-such-ts" }),
239
+ );
240
+
241
+ expect(result.consumed).toBe(false);
242
+ expect(result.type).toBe("not_consumed");
243
+ expect(getCanonicalGuardianRequest("req-1")?.status).toBe("pending");
244
+ });
245
+
246
+ test("an unknown emoji is not consumed", async () => {
247
+ seedApproval({ requestId: "req-1", ts: "111.1" });
248
+
249
+ const result = await routeGuardianReply(
250
+ reaction({ emoji: "tada", reactedMessageTs: "111.1" }),
251
+ );
252
+
253
+ expect(result.consumed).toBe(false);
254
+ expect(getCanonicalGuardianRequest("req-1")?.status).toBe("pending");
255
+ });
256
+
257
+ test("a reaction without a reacted message id is not consumed", async () => {
258
+ seedApproval({ requestId: "req-1", ts: "111.1" });
259
+
260
+ const result = await routeGuardianReply(
261
+ reaction({ emoji: "white_check_mark" }),
262
+ );
263
+
264
+ expect(result.consumed).toBe(false);
265
+ });
266
+
267
+ test("a reaction from a different principal is surfaced, not silently dropped", async () => {
268
+ const hcr = seedApproval({ requestId: "req-1", ts: "111.1" });
269
+
270
+ const result = await routeGuardianReply(
271
+ reaction({
272
+ emoji: "white_check_mark",
273
+ reactedMessageTs: "111.1",
274
+ principal: "someone-else",
275
+ }),
276
+ );
277
+
278
+ // Consumed with a user-facing reply, request untouched, side effect not run.
279
+ expect(result.consumed).toBe(true);
280
+ expect(result.decisionApplied).toBe(false);
281
+ expect(result.replyText).toMatch(/permission/i);
282
+ expect(getCanonicalGuardianRequest("req-1")?.status).toBe("pending");
283
+ expect(hcr).not.toHaveBeenCalled();
284
+ });
285
+ });