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

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 (444) hide show
  1. package/docs/activation-funnel-telemetry.md +24 -18
  2. package/node_modules/@vellumai/gateway-client/src/admission-policy-contract.ts +97 -0
  3. package/node_modules/@vellumai/gateway-client/src/inbound-contract.ts +10 -0
  4. package/node_modules/@vellumai/gateway-client/src/index.ts +15 -0
  5. package/openapi.yaml +881 -16
  6. package/package.json +1 -1
  7. package/src/__tests__/access-request-card-view.test.ts +98 -0
  8. package/src/__tests__/access-request-seed-content-blocks.test.ts +2 -4
  9. package/src/__tests__/actor-trust-resolver-address-fallback.test.ts +59 -7
  10. package/src/__tests__/agent-loop-compaction-strip.test.ts +17 -16
  11. package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +16 -13
  12. package/src/__tests__/app-compiler.test.ts +15 -1
  13. package/src/__tests__/approval-routes-http.test.ts +128 -0
  14. package/src/__tests__/auth-fallback-events-store.test.ts +6 -14
  15. package/src/__tests__/call-pointer-messages.test.ts +28 -0
  16. package/src/__tests__/cancel-clears-processing.test.ts +89 -0
  17. package/src/__tests__/channel-approval-routes.test.ts +0 -4
  18. package/src/__tests__/channel-inbound-disk-pressure.test.ts +5 -15
  19. package/src/__tests__/cli-memory-v2-reembed-skills.test.ts +3 -4
  20. package/src/__tests__/compactor-image-manifest-trust.test.ts +21 -1
  21. package/src/__tests__/compactor-summary-call-truncation.test.ts +223 -0
  22. package/src/__tests__/config-loader-backfill.test.ts +174 -30
  23. package/src/__tests__/config-schema.test.ts +35 -0
  24. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +2 -2
  25. package/src/__tests__/contact-store-user-file.test.ts +0 -6
  26. package/src/__tests__/contacts-tools.test.ts +29 -0
  27. package/src/__tests__/conversation-agent-loop-overflow.test.ts +1 -0
  28. package/src/__tests__/conversation-agent-loop.test.ts +58 -0
  29. package/src/__tests__/conversation-attention-telegram.test.ts +0 -1
  30. package/src/__tests__/conversation-lifecycle.test.ts +7 -9
  31. package/src/__tests__/conversation-load-history-repair.test.ts +101 -0
  32. package/src/__tests__/conversation-routes-guardian-reply.test.ts +15 -12
  33. package/src/__tests__/conversation-surfaces-activation-emit.test.ts +6 -3
  34. package/src/__tests__/conversation-title-service.test.ts +62 -0
  35. package/src/__tests__/credential-execution-shell-lockdown.test.ts +18 -11
  36. package/src/__tests__/credential-prompt-route.test.ts +1 -0
  37. package/src/__tests__/credential-security-invariants.test.ts +2 -0
  38. package/src/__tests__/disk-pressure-policy.test.ts +12 -0
  39. package/src/__tests__/disk-usage.test.ts +65 -0
  40. package/src/__tests__/document-tool-security.test.ts +9 -6
  41. package/src/__tests__/document-update-default-surface.test.ts +202 -0
  42. package/src/__tests__/dynamic-page-surface.test.ts +51 -0
  43. package/src/__tests__/gateway-flag-listener.test.ts +110 -1
  44. package/src/__tests__/guardian-binding-drift-heal.test.ts +1 -1
  45. package/src/__tests__/guardian-card-withdrawal.test.ts +403 -0
  46. package/src/__tests__/guardian-decision-primitive-canonical.test.ts +5 -3
  47. package/src/__tests__/guardian-grant-minting.test.ts +3 -35
  48. package/src/__tests__/guardian-routing-invariants.test.ts +64 -26
  49. package/src/__tests__/guardian-routing-state.test.ts +0 -1
  50. package/src/__tests__/headless-browser-mode.test.ts +10 -0
  51. package/src/__tests__/headless-browser-navigate.test.ts +8 -3
  52. package/src/__tests__/helpers/create-guardian-binding.ts +0 -1
  53. package/src/__tests__/host-browser-proxy.test.ts +87 -0
  54. package/src/__tests__/injector-v3-suppression.test.ts +27 -20
  55. package/src/__tests__/internal-telemetry-routes.test.ts +6 -14
  56. package/src/__tests__/invite-redemption-service.test.ts +0 -3
  57. package/src/__tests__/llm-catalog-parity.test.ts +30 -1
  58. package/src/__tests__/llm-resolver.test.ts +21 -0
  59. package/src/__tests__/llm-schema.test.ts +1 -0
  60. package/src/__tests__/managed-profile-guard.test.ts +163 -4
  61. package/src/__tests__/mcp-health-check.test.ts +6 -7
  62. package/src/__tests__/media-stream-server-integration.test.ts +317 -13
  63. package/src/__tests__/path-policy.test.ts +34 -0
  64. package/src/__tests__/persona-resolver.test.ts +38 -0
  65. package/src/__tests__/plugin-api-provider.test.ts +24 -0
  66. package/src/__tests__/plugin-tool-contribution.test.ts +6 -3
  67. package/src/__tests__/post-compaction-reinjection-idempotency.test.ts +214 -0
  68. package/src/__tests__/provider-send-message-override-profile.test.ts +76 -0
  69. package/src/__tests__/reaction-persistence.test.ts +150 -29
  70. package/src/__tests__/relay-server.test.ts +285 -0
  71. package/src/__tests__/runtime-attachment-metadata.test.ts +0 -1
  72. package/src/__tests__/scheduler-reuse-conversation.test.ts +8 -5
  73. package/src/__tests__/secret-response-routing.test.ts +35 -0
  74. package/src/__tests__/secret-routes-platform-proxy.test.ts +16 -2
  75. package/src/__tests__/skill-execute-input.test.ts +131 -0
  76. package/src/__tests__/skills.test.ts +51 -0
  77. package/src/__tests__/slack-notification-approval-card.test.ts +176 -0
  78. package/src/__tests__/slack-reaction-canonical-approval.test.ts +285 -0
  79. package/src/__tests__/subagent-tools.test.ts +150 -0
  80. package/src/__tests__/task-progress-nudge-hook.test.ts +1 -1
  81. package/src/__tests__/title-generate-hook.test.ts +100 -3
  82. package/src/__tests__/tool-approval-seed-content-blocks.test.ts +1 -1
  83. package/src/__tests__/tool-audit-listener.test.ts +7 -7
  84. package/src/__tests__/tool-executor-lifecycle-events.test.ts +6 -3
  85. package/src/__tests__/trusted-contact-approval-notifier.test.ts +4 -2
  86. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +220 -3
  87. package/src/__tests__/trusted-contact-verification.test.ts +2 -4
  88. package/src/__tests__/twilio-routes.test.ts +81 -1
  89. package/src/__tests__/voice-invite-redemption.test.ts +0 -1
  90. package/src/__tests__/weak-open-model.test.ts +30 -0
  91. package/src/__tests__/workspace-migration-105-enable-memory-v3-live-for-new-workspaces.test.ts +149 -0
  92. package/src/__tests__/workspace-migration-108-drop-balanced-economy-profile.test.ts +285 -0
  93. package/src/__tests__/workspace-migration-add-send-diagnostics.test.ts +1 -1
  94. package/src/__tests__/workspace-migration-drop-collect-usage-data.test.ts +118 -0
  95. package/src/__tests__/workspace-migration-drop-send-diagnostics.test.ts +118 -0
  96. package/src/a2a/__tests__/e2e-a2a-channel.test.ts +0 -4
  97. package/src/agent/loop.ts +33 -33
  98. package/src/api/events/tool-result.ts +6 -0
  99. package/src/api/events/workflow-completed.ts +53 -0
  100. package/src/api/events/workflow-leaf-finished.ts +38 -0
  101. package/src/api/events/workflow-leaf-started.ts +35 -0
  102. package/src/api/events/workflow-progress.ts +32 -0
  103. package/src/api/events/workflow-started.ts +31 -0
  104. package/src/api/index.ts +40 -0
  105. package/src/api/responses/conversation-message.ts +26 -0
  106. package/src/api/responses/home.ts +26 -0
  107. package/src/api/responses/workflow-journal.ts +53 -0
  108. package/src/approvals/guardian-card-withdrawal.ts +145 -0
  109. package/src/approvals/guardian-decision-primitive.ts +26 -3
  110. package/src/approvals/guardian-request-resolvers.ts +181 -78
  111. package/src/calls/__tests__/channel-admission-reader.test.ts +132 -0
  112. package/src/calls/__tests__/relay-setup-router.test.ts +350 -0
  113. package/src/calls/call-pointer-messages.ts +10 -4
  114. package/src/calls/channel-admission-reader.ts +104 -0
  115. package/src/calls/guardian-dispatch.ts +17 -45
  116. package/src/calls/media-stream-server.ts +84 -2
  117. package/src/calls/relay-server.ts +66 -0
  118. package/src/calls/relay-setup-router.ts +82 -1
  119. package/src/calls/twilio-routes.ts +17 -8
  120. package/src/cli/commands/clients.ts +3 -0
  121. package/src/cli/commands/{__tests__ → memory/__tests__}/memory-v2-compare-render.test.ts +1 -1
  122. package/src/cli/commands/{__tests__ → memory/__tests__}/memory-v2.test.ts +8 -7
  123. package/src/cli/commands/{__tests__ → memory/__tests__}/memory-v3.test.ts +5 -4
  124. package/src/cli/commands/memory/index.ts +30 -0
  125. package/src/cli/commands/{memory-v2-compare-render.ts → memory/memory-v2-compare-render.ts} +1 -1
  126. package/src/cli/commands/{memory-v2.ts → memory/memory-v2.ts} +6 -15
  127. package/src/cli/commands/{memory-v3.ts → memory/memory-v3.ts} +97 -11
  128. package/src/cli/commands/oauth/status.test.ts +36 -0
  129. package/src/cli/commands/oauth/status.ts +23 -3
  130. package/src/cli/commands/plugins.ts +57 -5
  131. package/src/cli/lib/__tests__/inspect-plugin.test.ts +54 -0
  132. package/src/cli/lib/__tests__/merge-plugin-tree.test.ts +134 -4
  133. package/src/cli/lib/__tests__/plugin-surfaces.test.ts +111 -0
  134. package/src/cli/lib/__tests__/upgrade-plugin.test.ts +53 -11
  135. package/src/cli/lib/inspect-plugin.ts +12 -1
  136. package/src/cli/lib/merge-plugin-tree.ts +149 -49
  137. package/src/cli/lib/plugin-surfaces.ts +104 -0
  138. package/src/cli/lib/upgrade-plugin.ts +64 -36
  139. package/src/cli/program.ts +2 -4
  140. package/src/config/__tests__/sync-gated-profiles.test.ts +368 -0
  141. package/src/config/assistant-feature-flags.ts +22 -7
  142. package/src/config/bundled-skills/contacts/tools/contact-search.ts +0 -1
  143. package/src/config/bundled-skills/document-editor/SKILL.md +3 -3
  144. package/src/config/bundled-skills/document-editor/TOOLS.json +2 -2
  145. package/src/config/bundled-skills/messaging/SKILL.md +6 -4
  146. package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +9 -8
  147. package/src/config/bundled-skills/workflows/SKILL.md +14 -7
  148. package/src/config/call-site-defaults.ts +3 -0
  149. package/src/config/feature-flag-registry.json +49 -18
  150. package/src/config/llm-resolver.ts +3 -0
  151. package/src/config/memory-v3-gate.ts +11 -0
  152. package/src/config/schema.ts +8 -6
  153. package/src/config/schemas/__tests__/memory-v3.test.ts +1 -0
  154. package/src/config/schemas/call-site-catalog.ts +7 -0
  155. package/src/config/schemas/channels.ts +11 -0
  156. package/src/config/schemas/llm.ts +31 -0
  157. package/src/config/schemas/memory-lifecycle.ts +3 -7
  158. package/src/config/schemas/memory-v3.ts +6 -0
  159. package/src/config/schemas/services.ts +18 -0
  160. package/src/config/seed-inference-profiles.ts +94 -34
  161. package/src/config/skills.ts +21 -0
  162. package/src/config/sync-gated-profiles.ts +220 -0
  163. package/src/contacts/contact-store.ts +2 -10
  164. package/src/contacts/contacts-write.ts +1 -2
  165. package/src/contacts/types.ts +0 -1
  166. package/src/context/compactor.ts +86 -52
  167. package/src/context/strip-injections.ts +58 -10
  168. package/src/context/token-estimator.ts +1 -1
  169. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +3 -2
  170. package/src/daemon/conversation-agent-loop-handlers.ts +2 -0
  171. package/src/daemon/conversation-agent-loop.ts +100 -19
  172. package/src/daemon/conversation-history.ts +1 -1
  173. package/src/daemon/conversation-lifecycle.ts +3 -5
  174. package/src/daemon/conversation-process.ts +13 -5
  175. package/src/daemon/conversation-runtime-assembly.ts +13 -15
  176. package/src/daemon/conversation-surfaces.ts +26 -0
  177. package/src/daemon/conversation-tool-setup.ts +27 -14
  178. package/src/daemon/conversation.ts +64 -14
  179. package/src/daemon/disk-pressure-policy.ts +5 -3
  180. package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +0 -1
  181. package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +0 -1
  182. package/src/daemon/handlers/config-a2a.ts +0 -2
  183. package/src/daemon/handlers/config-channels.ts +5 -10
  184. package/src/daemon/handlers/config-slack-channel.ts +20 -0
  185. package/src/daemon/handlers/conversations.ts +107 -0
  186. package/src/daemon/host-browser-proxy.ts +41 -0
  187. package/src/daemon/lifecycle.ts +55 -20
  188. package/src/daemon/memory-v2-startup.test.ts +131 -0
  189. package/src/daemon/memory-v2-startup.ts +100 -1
  190. package/src/daemon/message-provenance.ts +2 -0
  191. package/src/daemon/message-types/contacts.ts +0 -1
  192. package/src/daemon/message-types/web-activity.ts +7 -1
  193. package/src/daemon/message-types/workflows.ts +83 -1
  194. package/src/daemon/tool-setup-types.ts +4 -0
  195. package/src/daemon/trust-context.ts +1 -1
  196. package/src/events/tool-audit-listener.ts +2 -2
  197. package/src/home/feed-source-enrichment.test.ts +151 -0
  198. package/src/home/feed-source-enrichment.ts +176 -0
  199. package/src/instrument.ts +18 -6
  200. package/src/ipc/__tests__/binary-result-ipc.test.ts +81 -0
  201. package/src/ipc/__tests__/clients-list-ipc.test.ts +20 -0
  202. package/src/ipc/assistant-server.ts +37 -4
  203. package/src/ipc/gateway-flag-listener.ts +18 -2
  204. package/src/memory/__tests__/auto-analysis-enqueue.test.ts +5 -16
  205. package/src/memory/__tests__/jobs-store-enqueue-gate.test.ts +7 -11
  206. package/src/memory/__tests__/memory-retrospective-enqueue.test.ts +37 -7
  207. package/src/memory/__tests__/memory-retrospective-job.test.ts +34 -0
  208. package/src/memory/__tests__/onboarding-events-store.test.ts +7 -7
  209. package/src/memory/auth-fallback-events-store.ts +2 -2
  210. package/src/memory/auto-analysis-enqueue.ts +3 -5
  211. package/src/memory/canonical-guardian-store.ts +39 -1
  212. package/src/memory/conversation-crud.ts +9 -4
  213. package/src/memory/conversation-key-store.ts +17 -2
  214. package/src/memory/conversation-title-service.ts +64 -7
  215. package/src/memory/db-init.ts +10 -0
  216. package/src/memory/embedding-backend.ts +15 -1
  217. package/src/memory/jobs-worker.ts +2 -1
  218. package/src/memory/lifecycle-events-store.ts +2 -2
  219. package/src/memory/memory-retrospective-enqueue.ts +31 -6
  220. package/src/memory/memory-retrospective-job.ts +9 -0
  221. package/src/memory/migrations/129-contact-channels-access-fields.ts +18 -9
  222. package/src/memory/migrations/131-drop-legacy-member-guardian-tables.ts +14 -2
  223. package/src/memory/migrations/289-contact-channels-unique-ext-user.ts +10 -0
  224. package/src/memory/migrations/291-contact-channels-renormalize-addresses.ts +10 -0
  225. package/src/memory/migrations/292-schedule-default-no-reuse-conversation.test.ts +67 -0
  226. package/src/memory/migrations/292-schedule-default-no-reuse-conversation.ts +25 -0
  227. package/src/memory/migrations/293-workflow-journal-leaf-tokens.ts +32 -0
  228. package/src/memory/migrations/294-drop-external-user-id.ts +31 -0
  229. package/src/memory/migrations/295-drop-approval-prompt-ts-tracker.ts +20 -0
  230. package/src/memory/migrations/296-rewrite-balanced-economy-profile-pins.test.ts +110 -0
  231. package/src/memory/migrations/296-rewrite-balanced-economy-profile-pins.ts +68 -0
  232. package/src/memory/migrations/__tests__/131-drop-legacy-member-guardian-tables.test.ts +154 -0
  233. package/src/memory/migrations/__tests__/289-contact-channels-unique-ext-user.test.ts +31 -0
  234. package/src/memory/migrations/__tests__/291-contact-channels-renormalize-addresses.test.ts +30 -0
  235. package/src/memory/migrations/index.ts +5 -0
  236. package/src/memory/onboarding-events-store.ts +3 -3
  237. package/src/memory/schema/contacts.ts +0 -1
  238. package/src/memory/skill-loaded-events-store.test.ts +7 -15
  239. package/src/memory/skill-loaded-events-store.ts +2 -2
  240. package/src/memory/tool-executed-events-store.test.ts +7 -7
  241. package/src/memory/turn-trace-store.test.ts +736 -0
  242. package/src/memory/turn-trace-store.ts +364 -0
  243. package/src/memory/v2/__tests__/consolidation-job.test.ts +8 -0
  244. package/src/memory/v2/__tests__/skill-content.test.ts +30 -0
  245. package/src/memory/v2/consolidation-job.ts +2 -2
  246. package/src/memory/v2/skill-content.ts +25 -7
  247. package/src/memory/v2/skill-store.ts +7 -1
  248. package/src/memory/v3-eval/__tests__/eval-packets.test.ts +248 -0
  249. package/src/memory/v3-eval/eval-packets.ts +546 -0
  250. package/src/messaging/providers/slack/api.ts +31 -0
  251. package/src/messaging/providers/slack/send.test.ts +114 -2
  252. package/src/messaging/providers/slack/send.ts +30 -7
  253. package/src/messaging/providers/slack/withdraw.test.ts +200 -0
  254. package/src/messaging/providers/slack/withdraw.ts +161 -0
  255. package/src/notifications/AGENTS.md +2 -0
  256. package/src/notifications/access-request-copy.ts +72 -59
  257. package/src/notifications/adapters/slack.ts +55 -73
  258. package/src/notifications/approval-card-data.ts +333 -0
  259. package/src/notifications/broadcaster.ts +6 -2
  260. package/src/notifications/canonical-delivery-recorder.ts +139 -0
  261. package/src/notifications/copy-composer.ts +3 -3
  262. package/src/notifications/decision-engine.ts +4 -2
  263. package/src/notifications/destination-resolver.ts +4 -6
  264. package/src/notifications/guardian-question-mode.ts +10 -0
  265. package/src/notifications/home-feed-side-effect.ts +3 -13
  266. package/src/notifications/notification-utils.ts +2 -1
  267. package/src/notifications/signal.ts +79 -43
  268. package/src/notifications/types.ts +98 -128
  269. package/src/permissions/checker.test.ts +51 -0
  270. package/src/permissions/checker.ts +185 -26
  271. package/src/permissions/ipc-risk-types.ts +24 -0
  272. package/src/permissions/question-prompter.test.ts +27 -0
  273. package/src/permissions/question-prompter.ts +4 -0
  274. package/src/permissions/secret-prompter.ts +15 -1
  275. package/src/platform/client.test.ts +119 -0
  276. package/src/platform/client.ts +66 -0
  277. package/src/platform/consent-cache.test.ts +267 -0
  278. package/src/platform/consent-cache.ts +174 -0
  279. package/src/plugin-api/index.ts +27 -0
  280. package/src/plugins/defaults/advisor/__tests__/advisor-gate.test.ts +56 -0
  281. package/src/plugins/defaults/advisor/__tests__/advisor-state-store.test.ts +43 -0
  282. package/src/plugins/defaults/advisor/__tests__/agent-loop-integration.test.ts +137 -0
  283. package/src/plugins/defaults/advisor/__tests__/consult.test.ts +153 -0
  284. package/src/plugins/defaults/advisor/__tests__/hooks.test.ts +138 -0
  285. package/src/plugins/defaults/advisor/__tests__/transcript.test.ts +147 -0
  286. package/src/plugins/defaults/advisor/advisor-gate.ts +29 -0
  287. package/src/plugins/defaults/advisor/advisor-state-store.ts +94 -0
  288. package/src/plugins/defaults/advisor/config.ts +21 -0
  289. package/src/plugins/defaults/advisor/consult.ts +93 -0
  290. package/src/plugins/defaults/advisor/hooks/post-model-call.ts +34 -0
  291. package/src/plugins/defaults/advisor/hooks/pre-model-call.ts +30 -0
  292. package/src/plugins/defaults/advisor/hooks/user-prompt-submit.ts +19 -0
  293. package/src/plugins/defaults/advisor/package.json +14 -0
  294. package/src/plugins/defaults/advisor/steering.ts +67 -0
  295. package/src/plugins/defaults/advisor/tools/advisor.ts +65 -0
  296. package/src/plugins/defaults/advisor/transcript.ts +76 -0
  297. package/src/plugins/defaults/index.ts +35 -0
  298. package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +22 -9
  299. package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit.ts +2 -2
  300. package/src/plugins/defaults/memory-retrieval/tail-reinjection-strip.ts +64 -0
  301. package/src/plugins/defaults/memory-retrieval/unified-turn-context.ts +29 -21
  302. package/src/plugins/defaults/memory-v3-shadow/__tests__/carry-integration.test.ts +1 -0
  303. package/src/plugins/defaults/memory-v3-shadow/__tests__/injection.test.ts +1 -0
  304. package/src/plugins/defaults/memory-v3-shadow/__tests__/maintain-job.test.ts +75 -7
  305. package/src/plugins/defaults/memory-v3-shadow/__tests__/orchestrate.test.ts +31 -4
  306. package/src/plugins/defaults/memory-v3-shadow/__tests__/selection-log-store.test.ts +77 -2
  307. package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +1 -0
  308. package/src/plugins/defaults/memory-v3-shadow/injector.ts +7 -10
  309. package/src/plugins/defaults/memory-v3-shadow/maintain-job.ts +37 -4
  310. package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +32 -20
  311. package/src/plugins/defaults/memory-v3-shadow/selection-log-store.ts +56 -3
  312. package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +23 -2
  313. package/src/plugins/defaults/task-progress-nudge/hooks/post-tool-use.ts +2 -12
  314. package/src/plugins/defaults/title-generate/hooks/stop.ts +56 -21
  315. package/src/prompts/persona-resolver.ts +12 -2
  316. package/src/prompts/templates/system-sections.ts +7 -2
  317. package/src/providers/__tests__/provider-env-vars.test.ts +6 -0
  318. package/src/providers/__tests__/provider-secret-catalog.test.ts +1 -0
  319. package/src/providers/__tests__/retry-callsite.test.ts +176 -0
  320. package/src/providers/atlascloud/client.ts +85 -0
  321. package/src/providers/fetch-provider-catalog.ts +85 -0
  322. package/src/providers/inference/adapter-factory.ts +3 -0
  323. package/src/providers/model-catalog.ts +58 -0
  324. package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +33 -0
  325. package/src/providers/openai/chat-completions-provider.ts +7 -0
  326. package/src/providers/openai/responses-provider.ts +10 -0
  327. package/src/providers/provider-send-message.ts +11 -3
  328. package/src/providers/retry.ts +53 -12
  329. package/src/providers/search-provider-catalog.ts +10 -0
  330. package/src/providers/weak-open-model.ts +22 -0
  331. package/src/runtime/__tests__/agent-wake.test.ts +181 -0
  332. package/src/runtime/__tests__/client-health.test.ts +44 -0
  333. package/src/runtime/access-request-helper.ts +21 -53
  334. package/src/runtime/actor-trust-resolver.ts +49 -21
  335. package/src/runtime/agent-wake.ts +52 -0
  336. package/src/runtime/assistant-event-hub.ts +18 -4
  337. package/src/runtime/auth/__tests__/route-policy.test.ts +12 -0
  338. package/src/runtime/auth/require-bound-guardian.ts +1 -4
  339. package/src/runtime/capabilities.test.ts +120 -0
  340. package/src/runtime/capabilities.ts +197 -0
  341. package/src/runtime/channel-approval-types.ts +5 -1
  342. package/src/runtime/channel-retry-sweep.ts +1 -0
  343. package/src/runtime/channel-verification-service.ts +1 -2
  344. package/src/runtime/client-health.ts +26 -0
  345. package/src/runtime/confirmation-request-guardian-bridge.ts +38 -29
  346. package/src/runtime/effective-capabilities.test.ts +128 -0
  347. package/src/runtime/effective-capabilities.ts +84 -0
  348. package/src/runtime/guardian-reply-router.ts +106 -21
  349. package/src/runtime/invite-redemption-service.ts +6 -22
  350. package/src/runtime/migrations/__tests__/vbundle-builder-fd-leak.test.ts +123 -0
  351. package/src/runtime/migrations/vbundle-builder.ts +49 -20
  352. package/src/runtime/pending-interactions.ts +35 -0
  353. package/src/runtime/routes/__tests__/client-routes.test.ts +13 -0
  354. package/src/runtime/routes/__tests__/conversation-management-routes.test.ts +67 -0
  355. package/src/runtime/routes/__tests__/plugins-routes.test.ts +35 -13
  356. package/src/runtime/routes/approval-routes.ts +40 -9
  357. package/src/runtime/routes/browser-tabs-routes.ts +9 -0
  358. package/src/runtime/routes/canonical-guardian-expiry-sweep.ts +17 -8
  359. package/src/runtime/routes/client-routes.ts +10 -0
  360. package/src/runtime/routes/contact-routes.ts +31 -8
  361. package/src/runtime/routes/conversation-management-routes.ts +80 -1
  362. package/src/runtime/routes/conversation-query-routes.ts +68 -22
  363. package/src/runtime/routes/conversation-routes.ts +37 -12
  364. package/src/runtime/routes/events-routes.ts +1 -3
  365. package/src/runtime/routes/guardian-approval-interception.ts +14 -73
  366. package/src/runtime/routes/guardian-approval-prompt.ts +22 -4
  367. package/src/runtime/routes/home-feed-routes.ts +8 -3
  368. package/src/runtime/routes/inbound-message-handler.ts +214 -228
  369. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +88 -6
  370. package/src/runtime/routes/inbound-stages/admission-policy.test.ts +154 -0
  371. package/src/runtime/routes/inbound-stages/admission-policy.ts +140 -0
  372. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +3 -3
  373. package/src/runtime/routes/inbound-stages/background-dispatch.ts +11 -6
  374. package/src/runtime/routes/inbound-stages/escalation-intercept.ts +1 -2
  375. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +1 -2
  376. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.test.ts +7 -7
  377. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +47 -28
  378. package/src/runtime/routes/inbound-stages/reaction-intercept.ts +358 -0
  379. package/src/runtime/routes/index.ts +2 -0
  380. package/src/runtime/routes/integrations/slack/__tests__/channel.test.ts +8 -0
  381. package/src/runtime/routes/integrations/slack/channel.ts +36 -0
  382. package/src/runtime/routes/internal-telemetry-routes.ts +1 -1
  383. package/src/runtime/routes/mcp-auth-routes.ts +233 -41
  384. package/src/runtime/routes/memory-eval-routes.ts +87 -0
  385. package/src/runtime/routes/notification-routes.ts +122 -133
  386. package/src/runtime/routes/platform-routes.ts +2 -2
  387. package/src/runtime/routes/plugins-routes.ts +40 -7
  388. package/src/runtime/routes/secret-routes.ts +15 -0
  389. package/src/runtime/routes/surface-action-routes.ts +2 -1
  390. package/src/runtime/routes/tool-call-question-enrichment.test.ts +146 -0
  391. package/src/runtime/routes/tool-call-question-enrichment.ts +66 -0
  392. package/src/runtime/routes/workflow-routes.test.ts +225 -1
  393. package/src/runtime/routes/workflow-routes.ts +131 -1
  394. package/src/runtime/tool-grant-request-helper.ts +18 -16
  395. package/src/runtime/trust-context-resolver.ts +8 -5
  396. package/src/schedule/schedule-store.ts +1 -1
  397. package/src/schedule/scheduler-types.ts +5 -1
  398. package/src/security/__tests__/provider-key-env-fallback.test.ts +6 -0
  399. package/src/security/secret-patterns.ts +3 -0
  400. package/src/subagent/manager.ts +11 -4
  401. package/src/telemetry/trace-collection-policy.test.ts +28 -0
  402. package/src/telemetry/trace-collection-policy.ts +30 -0
  403. package/src/telemetry/types.ts +89 -0
  404. package/src/telemetry/usage-telemetry-reporter.test.ts +586 -36
  405. package/src/telemetry/usage-telemetry-reporter.ts +148 -41
  406. package/src/tools/browser/__tests__/browser-execution-acquire.test.ts +31 -0
  407. package/src/tools/browser/browser-execution.ts +29 -18
  408. package/src/tools/document/document-tool.ts +30 -6
  409. package/src/tools/executor.ts +5 -3
  410. package/src/tools/host-terminal/host-shell.ts +5 -4
  411. package/src/tools/memory/register.ts +2 -2
  412. package/src/tools/network/__tests__/web-fetch-firecrawl.test.ts +360 -0
  413. package/src/tools/network/__tests__/web-search.test.ts +143 -0
  414. package/src/tools/network/web-fetch.ts +372 -1
  415. package/src/tools/network/web-search.ts +213 -10
  416. package/src/tools/permission-checker.ts +3 -2
  417. package/src/tools/registry.ts +20 -0
  418. package/src/tools/schedule/create.ts +4 -3
  419. package/src/tools/schedule/update.ts +2 -1
  420. package/src/tools/shared/filesystem/path-policy.ts +39 -13
  421. package/src/tools/skills/execute.ts +67 -4
  422. package/src/tools/subagent/spawn.ts +37 -13
  423. package/src/tools/terminal/shell.ts +10 -4
  424. package/src/tools/tool-approval-handler.ts +17 -10
  425. package/src/tools/types.ts +9 -0
  426. package/src/tools/ui-surface/definitions.ts +25 -2
  427. package/src/tools/verification-control-plane-policy.ts +3 -1
  428. package/src/tools/workflows/run-workflow.ts +1 -0
  429. package/src/util/disk-usage.ts +78 -23
  430. package/src/util/platform.ts +8 -1
  431. package/src/watcher/telemetry.ts +2 -2
  432. package/src/workflows/engine.test.ts +175 -1
  433. package/src/workflows/engine.ts +82 -0
  434. package/src/workflows/journal-store.test.ts +70 -0
  435. package/src/workflows/journal-store.ts +18 -3
  436. package/src/workflows/run-manager.test.ts +171 -3
  437. package/src/workflows/run-manager.ts +64 -0
  438. package/src/workspace/migrations/105-enable-memory-v3-live-for-new-workspaces.ts +63 -0
  439. package/src/workspace/migrations/106-drop-collect-usage-data.ts +47 -0
  440. package/src/workspace/migrations/107-drop-send-diagnostics.ts +47 -0
  441. package/src/workspace/migrations/108-drop-balanced-economy-profile.ts +129 -0
  442. package/src/workspace/migrations/registry.ts +8 -0
  443. package/src/notifications/tool-approval-copy.ts +0 -142
  444. package/src/runtime/routes/approval-prompt-ts-tracker.ts +0 -78
@@ -0,0 +1,333 @@
1
+ /**
2
+ * Unified resolver + renderer for guardian approval card seed content.
3
+ *
4
+ * `contextPayload` resolves into a typed {@link ApprovalCardData} discriminated
5
+ * union, then renders into Surface `[ui_surface, text]` blocks via the shared
6
+ * {@link buildApprovalCardBlocks}. Access-request and tool-approval
7
+ * notifications share this single entry point, which parses and shapes the raw
8
+ * payload into card data.
9
+ *
10
+ * The Surface card data shape matches `CardSurfaceData` from
11
+ * `daemon/message-types/surfaces.ts`. Actions use the canonical
12
+ * `apr:<requestId>:<action>` callback format consumed by
13
+ * `surface-action-routes.ts` → `processGuardianDecision`.
14
+ */
15
+
16
+ import {
17
+ buildAccessRequestCardView,
18
+ buildAccessRequestContractText,
19
+ parseAccessRequestPayload,
20
+ } from "./access-request-copy.js";
21
+ import {
22
+ type ApprovalCardParams,
23
+ buildApprovalCardBlocks,
24
+ } from "./approval-card-builder.js";
25
+ import {
26
+ buildGuardianRequestCodeInstruction,
27
+ type GuardianQuestionPayload,
28
+ type LenientToolApprovalPayload,
29
+ LenientToolApprovalPayloadSchema,
30
+ parseGuardianQuestionPayload,
31
+ resolveGuardianInstructionModeFromFields,
32
+ resolveGuardianInstructionModeFromPayload,
33
+ } from "./guardian-question-mode.js";
34
+ import { nonEmpty, sanitizeIdentityField } from "./notification-utils.js";
35
+
36
+ // ── Surface ids ──────────────────────────────────────────────────────────────
37
+
38
+ /**
39
+ * `surfaceId` prefixes for the in-app approval cards. The full id is
40
+ * `${prefix}-${requestId}` (see {@link buildApprovalCardBlocks}). These are the
41
+ * single source of truth for the prefix, shared by the card builders below and
42
+ * by {@link approvalCardSurfaceId} so the withdrawal path can recompute the id.
43
+ */
44
+ const ACCESS_REQUEST_SURFACE_PREFIX = "access-request";
45
+ const TOOL_APPROVAL_SURFACE_PREFIX = "tool-approval";
46
+
47
+ /**
48
+ * Resolve the `ui_surface` id for a guardian request's in-app approval card
49
+ * from its kind, or `null` for kinds that never render an approval card.
50
+ *
51
+ * The card is rendered once at notification time; when the request is later
52
+ * resolved from a different surface the withdrawal path recomputes the id here
53
+ * to complete that card. Keeping this beside the builders ensures the two stay
54
+ * in lockstep.
55
+ */
56
+ export function approvalCardSurfaceId(
57
+ kind: string,
58
+ requestId: string,
59
+ ): string | null {
60
+ switch (kind) {
61
+ case "access_request":
62
+ return `${ACCESS_REQUEST_SURFACE_PREFIX}-${requestId}`;
63
+ case "tool_approval":
64
+ case "tool_grant_request":
65
+ case "pending_question":
66
+ return `${TOOL_APPROVAL_SURFACE_PREFIX}-${requestId}`;
67
+ default:
68
+ return null;
69
+ }
70
+ }
71
+
72
+ // ── Typed card data ─────────────────────────────────────────────────────────
73
+
74
+ /** Resolved card data for an access-request notification. */
75
+ export interface AccessRequestCardData {
76
+ kind: "access_request";
77
+ card: ApprovalCardParams;
78
+ }
79
+
80
+ /** Resolved card data for a tool-approval / tool-grant notification. */
81
+ export interface ToolApprovalCardData {
82
+ kind: "tool_approval";
83
+ card: ApprovalCardParams;
84
+ }
85
+
86
+ /**
87
+ * Channel-agnostic approval card content, resolved once from `contextPayload`.
88
+ * The discriminant `kind` lets consumers branch on the approval type without
89
+ * re-parsing the payload.
90
+ */
91
+ export type ApprovalCardData = AccessRequestCardData | ToolApprovalCardData;
92
+
93
+ // ── Access-request resolution ────────────────────────────────────────────────
94
+
95
+ /** Shape the parsed access-request payload into card params via the view model. */
96
+ function resolveAccessRequestCard(
97
+ payload: Record<string, unknown>,
98
+ ): ApprovalCardParams {
99
+ const view = buildAccessRequestCardView(parseAccessRequestPayload(payload));
100
+
101
+ const metadata: Array<{ label: string; value: string }> = [];
102
+
103
+ if (view.username) {
104
+ metadata.push({
105
+ label: "Username",
106
+ value: `@${view.username}`,
107
+ });
108
+ }
109
+
110
+ if (view.sourceChannel === "slack" && view.conversationExternalId) {
111
+ metadata.push({
112
+ label: "Source",
113
+ value: view.isSlackDm
114
+ ? "Slack — Direct message"
115
+ : `Slack — #${view.conversationExternalId}`,
116
+ });
117
+ } else if (view.sourceChannel) {
118
+ metadata.push({ label: "Source", value: view.sourceChannel });
119
+ }
120
+
121
+ const bodyParts: string[] = [];
122
+
123
+ if (view.messagePreview) {
124
+ bodyParts.push(`> "${view.messagePreview}"`);
125
+ }
126
+ for (const w of view.warnings) {
127
+ bodyParts.push(`⚠️ ${w}`);
128
+ }
129
+ if (view.messagePermalink) {
130
+ bodyParts.push(`[View message](${view.messagePermalink})`);
131
+ }
132
+
133
+ const body =
134
+ bodyParts.length > 0
135
+ ? bodyParts.join("\n\n")
136
+ : "No additional context available.";
137
+
138
+ return {
139
+ surfaceIdPrefix: ACCESS_REQUEST_SURFACE_PREFIX,
140
+ cardTitle: "Access Request",
141
+ requesterName: view.displayName,
142
+ subtitle: "Requesting access to the assistant",
143
+ body,
144
+ metadata,
145
+ requestId: view.requestId,
146
+ fallbackText: buildAccessRequestContractText(payload),
147
+ };
148
+ }
149
+
150
+ // ── Tool-approval resolution ─────────────────────────────────────────────────
151
+
152
+ /**
153
+ * Determine whether a typed guardian.question payload represents a tool
154
+ * approval (as opposed to a free-text answer). Uses the canonical mode
155
+ * resolver in `guardian-question-mode.ts` — the single source of truth
156
+ * for the requestKind → instructionMode mapping.
157
+ */
158
+ function isToolApprovalPayload(payload: GuardianQuestionPayload): boolean {
159
+ const { mode } = resolveGuardianInstructionModeFromPayload(payload);
160
+ return mode === "approval" && payload.requestKind !== "access_request";
161
+ }
162
+
163
+ /**
164
+ * Lenient approval detection for partially-constructed payloads that don't
165
+ * satisfy the strict discriminated union schema.
166
+ */
167
+ function isLenientToolApproval(payload: LenientToolApprovalPayload): boolean {
168
+ const modeResolution = resolveGuardianInstructionModeFromFields(
169
+ payload.requestKind,
170
+ payload.toolName,
171
+ );
172
+ if (!modeResolution) return false;
173
+ return (
174
+ modeResolution.mode === "approval" &&
175
+ payload.requestKind !== "access_request"
176
+ );
177
+ }
178
+
179
+ /** Shape a tool-approval/grant payload (strict or lenient) into card params. */
180
+ function extractToolApprovalCard(
181
+ p: GuardianQuestionPayload | LenientToolApprovalPayload,
182
+ ): ApprovalCardParams {
183
+ const toolName =
184
+ ("toolName" in p ? nonEmpty(p.toolName) : undefined) ?? "unknown tool";
185
+ const rawRequester = nonEmpty(p.requesterIdentifier);
186
+ const requester = rawRequester
187
+ ? sanitizeIdentityField(rawRequester)
188
+ : "Someone";
189
+
190
+ const isGrant = p.requestKind === "tool_grant_request";
191
+
192
+ const metadata: Array<{ label: string; value: string }> = [];
193
+ metadata.push({ label: "Tool", value: toolName });
194
+ const sourceChannel = nonEmpty(p.sourceChannel);
195
+ if (sourceChannel) {
196
+ metadata.push({ label: "Source", value: sourceChannel });
197
+ }
198
+
199
+ const body = p.questionText
200
+ ? `> ${p.questionText}`
201
+ : "No additional context available.";
202
+
203
+ // Fallback text with request-code instructions for older clients.
204
+ const baseFallback =
205
+ p.questionText ?? `${requester} is requesting approval to use ${toolName}`;
206
+ let fallbackText = baseFallback;
207
+ const requestCode = nonEmpty(p.requestCode);
208
+ if (requestCode) {
209
+ const modeResolution = resolveGuardianInstructionModeFromFields(
210
+ p.requestKind,
211
+ "toolName" in p ? (p.toolName ?? undefined) : undefined,
212
+ );
213
+ const mode = modeResolution?.mode ?? "approval";
214
+ const instruction = buildGuardianRequestCodeInstruction(
215
+ requestCode.trim().toUpperCase(),
216
+ mode,
217
+ );
218
+ fallbackText = `${baseFallback}\n\n${instruction}`;
219
+ }
220
+
221
+ return {
222
+ surfaceIdPrefix: TOOL_APPROVAL_SURFACE_PREFIX,
223
+ cardTitle: isGrant ? "Tool Grant Request" : "Tool Approval",
224
+ requesterName: requester,
225
+ subtitle: isGrant
226
+ ? "Requesting permission to use this tool"
227
+ : "Requesting approval to run this tool",
228
+ body,
229
+ metadata,
230
+ requestId: nonEmpty(p.requestId),
231
+ fallbackText,
232
+ };
233
+ }
234
+
235
+ /**
236
+ * Resolve a guardian.question payload into tool-approval card params, or
237
+ * `null` when it does not represent a tool approval.
238
+ *
239
+ * Tries strict Zod parsing first (full discriminated union), then falls back
240
+ * to lenient field extraction so cards still render when optional fields are
241
+ * absent.
242
+ */
243
+ function resolveToolApprovalCard(
244
+ payload: Record<string, unknown>,
245
+ ): ApprovalCardParams | null {
246
+ const strict = parseGuardianQuestionPayload(payload);
247
+ if (strict) {
248
+ if (!isToolApprovalPayload(strict)) return null;
249
+ return extractToolApprovalCard(strict);
250
+ }
251
+
252
+ const lenient = LenientToolApprovalPayloadSchema.safeParse(payload);
253
+ if (!lenient.success) return null;
254
+ if (!isLenientToolApproval(lenient.data)) return null;
255
+ return extractToolApprovalCard(lenient.data);
256
+ }
257
+
258
+ // ── Unified dispatcher + renderer ────────────────────────────────────────────
259
+
260
+ /**
261
+ * Resolve a notification's `contextPayload` into typed {@link ApprovalCardData}
262
+ * based on its source event, or `null` when the signal does not carry a
263
+ * renderable approval card. Single entry point so each consumer (copy
264
+ * composition, decision engine) shapes the payload identically.
265
+ */
266
+ export function resolveApprovalCardData(
267
+ sourceEventName: string,
268
+ contextPayload: Record<string, unknown> | undefined,
269
+ ): ApprovalCardData | null {
270
+ if (!contextPayload) return null;
271
+
272
+ if (sourceEventName === "ingress.access_request") {
273
+ return {
274
+ kind: "access_request",
275
+ card: resolveAccessRequestCard(contextPayload),
276
+ };
277
+ }
278
+
279
+ if (sourceEventName === "guardian.question") {
280
+ const card = resolveToolApprovalCard(contextPayload);
281
+ return card ? { kind: "tool_approval", card } : null;
282
+ }
283
+
284
+ return null;
285
+ }
286
+
287
+ /** Render resolved approval card data into Surface `[ui_surface, text]` blocks. */
288
+ export function renderApprovalCardData(data: ApprovalCardData): unknown[] {
289
+ return buildApprovalCardBlocks(data.card);
290
+ }
291
+
292
+ // ── Public seed-content builders ─────────────────────────────────────────────
293
+
294
+ /**
295
+ * Build structured content blocks for an access-request notification seed
296
+ * message. Produces a `ui_surface` card block that the web/macOS/iOS apps
297
+ * render as an interactive card via `SurfaceRouter → CardSurface`, plus a
298
+ * plain-text fallback block for search, CLI display, and backward-compatible
299
+ * clients that don't support surfaces.
300
+ */
301
+ export function buildAccessRequestSeedContentBlocks(
302
+ payload: Record<string, unknown>,
303
+ ): unknown[] {
304
+ return renderApprovalCardData({
305
+ kind: "access_request",
306
+ card: resolveAccessRequestCard(payload),
307
+ });
308
+ }
309
+
310
+ /**
311
+ * Build structured content blocks for a tool approval/grant notification seed
312
+ * message. Returns `null` when the payload does not represent a tool approval.
313
+ *
314
+ * Accepts both strict `GuardianQuestionPayload` (Zod-validated) and raw
315
+ * `Record<string, unknown>` payloads. For raw payloads, attempts strict
316
+ * parsing first, then falls back to lenient field extraction so cards
317
+ * still render when optional fields are absent.
318
+ */
319
+ export function buildToolApprovalSeedContentBlocks(
320
+ payload: GuardianQuestionPayload,
321
+ ): unknown[] | null;
322
+ export function buildToolApprovalSeedContentBlocks(
323
+ payload: Record<string, unknown>,
324
+ ): unknown[] | null;
325
+ export function buildToolApprovalSeedContentBlocks(
326
+ payload: GuardianQuestionPayload | Record<string, unknown>,
327
+ ): unknown[] | null {
328
+ const data = resolveApprovalCardData(
329
+ "guardian.question",
330
+ payload as Record<string, unknown>,
331
+ );
332
+ return data ? renderApprovalCardData(data) : null;
333
+ }
@@ -83,11 +83,15 @@ function resolveApprovalContext(
83
83
  // Extract tool context so channel adapters can render structured
84
84
  // approval cards without re-parsing contextPayload.
85
85
  let toolName: string | undefined;
86
+ let riskLevel: string | undefined;
87
+ let commandPreview: string | undefined;
86
88
  if (
87
89
  parsed.requestKind === "tool_approval" ||
88
90
  parsed.requestKind === "tool_grant_request"
89
91
  ) {
90
92
  toolName = nonEmpty(parsed.toolName);
93
+ riskLevel = nonEmpty(parsed.riskLevel);
94
+ commandPreview = nonEmpty(parsed.commandPreview);
91
95
  } else if (parsed.requestKind === "pending_question") {
92
96
  toolName = nonEmpty(parsed.toolName);
93
97
  }
@@ -99,8 +103,8 @@ function resolveApprovalContext(
99
103
  permissionDetails: toolName
100
104
  ? {
101
105
  toolName,
102
- riskLevel: "medium",
103
- toolInput: {},
106
+ riskLevel: riskLevel ?? "medium",
107
+ toolInput: commandPreview ? { _summary: commandPreview } : {},
104
108
  requesterIdentifier: nonEmpty(parsed.requesterIdentifier),
105
109
  }
106
110
  : undefined,
@@ -0,0 +1,139 @@
1
+ /**
2
+ * The single sink for the per-(request, surface) `canonical_guardian_deliveries`
3
+ * registry.
4
+ *
5
+ * Guardian-request producers (access requests, tool approvals, tool-grant
6
+ * escalations, voice questions, trusted-contact confirmations) emit a
7
+ * notification signal and then record one canonical delivery row per surface the
8
+ * card reached. Capturing the surface address here — the conversation id for the
9
+ * in-app vellum card, or the channel-native message id (e.g. Slack `ts`) for a
10
+ * channel card — is what lets a delivered card be addressed back to its request
11
+ * later:
12
+ *
13
+ * - to withdraw it in place when the request resolves
14
+ * (`approvals/guardian-card-withdrawal.ts`), and
15
+ * - to resolve an emoji reaction on the card to the right request when several
16
+ * are pending in the same chat
17
+ * (`getPendingCanonicalRequestByDestinationMessage`).
18
+ *
19
+ * Every producer records through here so the addressing convention lives in one
20
+ * place and cannot drift between the path that writes the row and the paths that
21
+ * read it back.
22
+ */
23
+
24
+ import {
25
+ type CanonicalGuardianDelivery,
26
+ createCanonicalGuardianDelivery,
27
+ updateCanonicalGuardianDelivery,
28
+ } from "../memory/canonical-guardian-store.js";
29
+ import { getLogger } from "../util/logger.js";
30
+ import type { NotificationDeliveryResult } from "./types.js";
31
+
32
+ const log = getLogger("canonical-delivery-recorder");
33
+
34
+ /**
35
+ * Where an approval card was delivered. Exactly one addressing modality is
36
+ * meaningful per channel:
37
+ *
38
+ * - vellum (in-app): addressed by `conversationId` + the card's surface id.
39
+ * - channels (slack/telegram/...): addressed by `chatId` + the channel-native
40
+ * `messageId` (Slack `ts`).
41
+ */
42
+ export interface ApprovalCardDeliveryAddress {
43
+ requestId: string;
44
+ channel: string;
45
+ /** In-app vellum addressing: the conversation the card was posted to. */
46
+ conversationId?: string;
47
+ /** Channel addressing: the chat the card was delivered to. */
48
+ chatId?: string;
49
+ /** Channel-native message id (e.g. Slack `ts`) — the reaction/withdrawal key. */
50
+ messageId?: string;
51
+ /** Initial delivery status (defaults to "pending"). */
52
+ status?: string;
53
+ }
54
+
55
+ /**
56
+ * Record where an approval card was delivered so it can be located later.
57
+ *
58
+ * Best-effort: a recording failure must never be mistaken for a delivery
59
+ * failure (which, in the prompt path, would trigger a fallback re-post), so
60
+ * errors are swallowed and logged and `null` is returned. Callers that need the
61
+ * row id (to apply a status afterwards) must null-check the result.
62
+ */
63
+ export function recordApprovalCardDelivery(
64
+ address: ApprovalCardDeliveryAddress,
65
+ ): CanonicalGuardianDelivery | null {
66
+ try {
67
+ return createCanonicalGuardianDelivery({
68
+ requestId: address.requestId,
69
+ destinationChannel: address.channel,
70
+ destinationConversationId: address.conversationId,
71
+ destinationChatId: address.chatId,
72
+ destinationMessageId: address.messageId,
73
+ ...(address.status ? { status: address.status } : {}),
74
+ });
75
+ } catch (err) {
76
+ log.error(
77
+ { err, requestId: address.requestId, channel: address.channel },
78
+ "Failed to record approval card delivery; reaction/withdrawal on this card will not resolve",
79
+ );
80
+ return null;
81
+ }
82
+ }
83
+
84
+ /**
85
+ * Record every delivery for a guardian request from a notification signal's
86
+ * results, persisting each delivery's terminal status.
87
+ *
88
+ * This is the shared post-broadcast recording loop for the signal-based
89
+ * producers. The vellum row is normally created up front in the signal's
90
+ * `onConversationCreated` callback so the in-app client sees it immediately; pass
91
+ * that row's id as `vellumDeliveryId` and it is reused (only its status applied)
92
+ * — otherwise the vellum row is created here from the result.
93
+ *
94
+ * The pipeline result carries the delivered address directly: a `vellum` result
95
+ * carries a `conversationId`; channel results carry the chat (`destination`) and
96
+ * channel-native id (`messageId`). A blank `destination` is recorded as unknown
97
+ * rather than persisting the literal channel name as a chat id. Status is
98
+ * diagnostic — the read paths key off addressing, not status.
99
+ *
100
+ * Returns the vellum delivery id (passed in, or created here) so a caller can
101
+ * record its own "pipeline produced no vellum delivery" fallback.
102
+ */
103
+ export function recordGuardianRequestDeliveries(params: {
104
+ requestId: string;
105
+ deliveryResults: NotificationDeliveryResult[];
106
+ vellumDeliveryId?: string;
107
+ }): string | undefined {
108
+ const { requestId, deliveryResults } = params;
109
+ let vellumDeliveryId = params.vellumDeliveryId;
110
+
111
+ for (const result of deliveryResults) {
112
+ let deliveryId: string | undefined;
113
+ if (result.channel === "vellum") {
114
+ if (!vellumDeliveryId) {
115
+ vellumDeliveryId = recordApprovalCardDelivery({
116
+ requestId,
117
+ channel: "vellum",
118
+ conversationId: result.conversationId,
119
+ })?.id;
120
+ }
121
+ deliveryId = vellumDeliveryId;
122
+ } else {
123
+ deliveryId = recordApprovalCardDelivery({
124
+ requestId,
125
+ channel: result.channel,
126
+ chatId: result.destination.length > 0 ? result.destination : undefined,
127
+ messageId: result.messageId,
128
+ })?.id;
129
+ }
130
+
131
+ if (deliveryId) {
132
+ updateCanonicalGuardianDelivery(deliveryId, {
133
+ status: result.status === "sent" ? "sent" : "failed",
134
+ });
135
+ }
136
+ }
137
+
138
+ return vellumDeliveryId;
139
+ }
@@ -9,10 +9,11 @@
9
9
  * values from the context payload.
10
10
  */
11
11
 
12
+ import { buildAccessRequestContractText } from "./access-request-copy.js";
12
13
  import {
13
- buildAccessRequestContractText,
14
14
  buildAccessRequestSeedContentBlocks,
15
- } from "./access-request-copy.js";
15
+ buildToolApprovalSeedContentBlocks,
16
+ } from "./approval-card-data.js";
16
17
  import {
17
18
  buildGuardianRequestCodeInstruction,
18
19
  parseGuardianQuestionPayload,
@@ -28,7 +29,6 @@ import type {
28
29
  NotificationSignal,
29
30
  NotificationSourceEventName,
30
31
  } from "./signal.js";
31
- import { buildToolApprovalSeedContentBlocks } from "./tool-approval-copy.js";
32
32
  import type { NotificationChannel, RenderedChannelCopy } from "./types.js";
33
33
 
34
34
  type CopyTemplate = (payload: Record<string, unknown>) => RenderedChannelCopy;
@@ -26,10 +26,13 @@ import { truncate } from "../util/truncate.js";
26
26
  import {
27
27
  buildAccessRequestContractText,
28
28
  buildAccessRequestInviteDirective,
29
- buildAccessRequestSeedContentBlocks,
30
29
  hasAccessRequestInstructions,
31
30
  hasInviteFlowDirective,
32
31
  } from "./access-request-copy.js";
32
+ import {
33
+ buildAccessRequestSeedContentBlocks,
34
+ buildToolApprovalSeedContentBlocks,
35
+ } from "./approval-card-data.js";
33
36
  import {
34
37
  buildConversationCandidates,
35
38
  type ConversationCandidateSet,
@@ -46,7 +49,6 @@ import {
46
49
  import { nonEmpty, readPayloadString } from "./notification-utils.js";
47
50
  import { getPreferenceSummary } from "./preference-summary.js";
48
51
  import type { NotificationSignal, RoutingIntent } from "./signal.js";
49
- import { buildToolApprovalSeedContentBlocks } from "./tool-approval-copy.js";
50
52
  import type {
51
53
  ConversationAction,
52
54
  NotificationChannel,
@@ -70,13 +70,12 @@ export function resolveDestinations(
70
70
  channel: channel as NotificationChannel,
71
71
  endpoint: externalChatId,
72
72
  metadata: {
73
- externalUserId: guardianResult.channel.externalUserId,
73
+ externalUserId: guardianResult.channel.address,
74
74
  },
75
75
  bindingContext: {
76
76
  sourceChannel: channel as NotificationChannel,
77
77
  externalChatId,
78
- externalUserId:
79
- guardianResult.channel.externalUserId ?? undefined,
78
+ externalUserId: guardianResult.channel.address,
80
79
  },
81
80
  });
82
81
  }
@@ -101,13 +100,12 @@ export function resolveDestinations(
101
100
  channel: "slack",
102
101
  endpoint: chatId,
103
102
  metadata: {
104
- externalUserId: guardianResult.channel.externalUserId,
103
+ externalUserId: guardianResult.channel.address,
105
104
  },
106
105
  bindingContext: {
107
106
  sourceChannel: "slack",
108
107
  externalChatId: chatId,
109
- externalUserId:
110
- guardianResult.channel.externalUserId ?? undefined,
108
+ externalUserId: guardianResult.channel.address,
111
109
  },
112
110
  });
113
111
  } else if (guardianResult && chatId) {
@@ -82,11 +82,19 @@ export const ToolApprovalPayloadSchema =
82
82
  GuardianQuestionPayloadBaseSchema.extend({
83
83
  requestKind: z.literal("tool_approval"),
84
84
  toolName: z.string().min(1),
85
+ /** Risk classification from the permission checker (e.g. "low", "medium", "high"). */
86
+ riskLevel: z.string().optional(),
87
+ /** Secret-redacted summary of the tool invocation arguments. */
88
+ commandPreview: z.string().optional(),
85
89
  });
86
90
 
87
91
  export const ToolGrantPayloadSchema = GuardianQuestionPayloadBaseSchema.extend({
88
92
  requestKind: z.literal("tool_grant_request"),
89
93
  toolName: z.string().min(1),
94
+ /** Risk classification from the permission checker (e.g. "low", "medium", "high"). */
95
+ riskLevel: z.string().optional(),
96
+ /** Secret-redacted summary of the tool invocation arguments. */
97
+ commandPreview: z.string().optional(),
90
98
  });
91
99
 
92
100
  export const AccessRequestGuardianPayloadSchema =
@@ -124,6 +132,8 @@ export const LenientToolApprovalPayloadSchema = z.object({
124
132
  requesterIdentifier: z.string().nullable().optional(),
125
133
  requesterExternalUserId: z.string().nullable().optional(),
126
134
  requesterChatId: z.string().nullable().optional(),
135
+ riskLevel: z.string().nullable().optional(),
136
+ commandPreview: z.string().nullable().optional(),
127
137
  });
128
138
 
129
139
  export type LenientToolApprovalPayload = z.infer<
@@ -15,7 +15,6 @@ import {
15
15
  type FeedItemCategory,
16
16
  type FeedItemDetailPanelKind,
17
17
  feedItemSchema,
18
- type FeedItemUrgency,
19
18
  } from "../home/feed-types.js";
20
19
  import { appendFeedItem } from "../home/feed-writer.js";
21
20
  import { getConversation } from "../memory/conversation-crud.js";
@@ -28,13 +27,6 @@ import type { NotificationDecision, RenderedChannelCopy } from "./types.js";
28
27
 
29
28
  const log = getLogger("home-feed-side-effect");
30
29
 
31
- const FEED_ITEM_URGENCIES: ReadonlySet<string> = new Set<FeedItemUrgency>([
32
- "low",
33
- "medium",
34
- "high",
35
- "critical",
36
- ]);
37
-
38
30
  /**
39
31
  * Append a `FeedItem` for the given notification signal when the
40
32
  * filter criteria pass.
@@ -97,9 +89,7 @@ export async function writeHomeFeedItemForSignal(
97
89
  return null;
98
90
  }
99
91
 
100
- const urgency = FEED_ITEM_URGENCIES.has(signal.attentionHints.urgency)
101
- ? (signal.attentionHints.urgency as FeedItemUrgency)
102
- : undefined;
92
+ const urgency = signal.attentionHints.urgency;
103
93
  const now = new Date().toISOString();
104
94
 
105
95
  const category = deriveCategory(signal);
@@ -109,7 +99,7 @@ export async function writeHomeFeedItemForSignal(
109
99
  signal.contextPayload &&
110
100
  typeof signal.contextPayload === "object" &&
111
101
  !Array.isArray(signal.contextPayload)
112
- ? { ...(signal.contextPayload as Record<string, unknown>) }
102
+ ? { ...signal.contextPayload }
113
103
  : undefined;
114
104
 
115
105
  // Link scheduled-run notifications back to their schedule. `notify`-mode
@@ -187,7 +177,7 @@ function deriveDetailPanelKind(
187
177
  const payload = signal.contextPayload;
188
178
  const kind =
189
179
  payload && typeof payload === "object" && "requestKind" in payload
190
- ? (payload as Record<string, unknown>).requestKind
180
+ ? payload.requestKind
191
181
  : undefined;
192
182
  if (kind === "tool_approval" || kind === "tool_grant_request") {
193
183
  return "permissionChat";
@@ -24,7 +24,8 @@ export function readPayloadString(
24
24
  payload: unknown,
25
25
  key: string,
26
26
  ): string | undefined {
27
- if (!payload || typeof payload !== "object") return undefined;
27
+ if (!payload || typeof payload !== "object" || Array.isArray(payload))
28
+ return undefined;
28
29
  const value = (payload as Record<string, unknown>)[key];
29
30
  return typeof value === "string" ? value : undefined;
30
31
  }