@vellumai/assistant 0.11.2 → 0.11.3-staging.1

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 (709) hide show
  1. package/ARCHITECTURE.md +58 -3
  2. package/docs/trusted-contact-access.md +9 -5
  3. package/docs/vellum-doctor.md +195 -0
  4. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/channels.ts +39 -0
  5. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/ingress.ts +10 -0
  6. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +6 -0
  7. package/node_modules/@vellumai/ces-client/src/index.ts +1 -0
  8. package/node_modules/@vellumai/ces-client/src/rpc-client.ts +19 -2
  9. package/node_modules/@vellumai/environments/package.json +2 -1
  10. package/node_modules/@vellumai/environments/src/__tests__/cloud-assistant-hub-url.test.ts +38 -0
  11. package/node_modules/@vellumai/environments/src/__tests__/install-layout.test.ts +98 -0
  12. package/node_modules/@vellumai/environments/src/__tests__/package-boundary.test.ts +5 -5
  13. package/node_modules/@vellumai/environments/src/index.ts +17 -6
  14. package/node_modules/@vellumai/environments/src/install-layout.ts +49 -0
  15. package/node_modules/@vellumai/environments/src/seeds.ts +29 -0
  16. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/channels.ts +39 -0
  17. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/ingress.ts +10 -0
  18. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +6 -0
  19. package/node_modules/@vellumai/gateway-client/src/index.ts +4 -0
  20. package/node_modules/@vellumai/gateway-client/src/verification-session-contract.ts +91 -6
  21. package/node_modules/@vellumai/ipc-server-utils/src/endpoint.test.ts +36 -0
  22. package/node_modules/@vellumai/ipc-server-utils/src/endpoint.ts +142 -0
  23. package/node_modules/@vellumai/ipc-server-utils/src/index.ts +2 -0
  24. package/node_modules/@vellumai/ipc-server-utils/src/listen-options.ts +3 -0
  25. package/node_modules/@vellumai/ipc-server-utils/src/socket-watchdog.test.ts +15 -1
  26. package/node_modules/@vellumai/ipc-server-utils/src/socket-watchdog.ts +17 -2
  27. package/node_modules/@vellumai/service-contracts/src/channels.ts +39 -0
  28. package/node_modules/@vellumai/service-contracts/src/ingress.ts +10 -0
  29. package/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +6 -0
  30. package/node_modules/@vellumai/slack-text/src/index.ts +16 -2
  31. package/openapi.yaml +589 -30
  32. package/package.json +1 -1
  33. package/src/__tests__/agent-image-optimize.test.ts +4 -4
  34. package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +113 -66
  35. package/src/__tests__/agent-loop-override-profile.test.ts +38 -48
  36. package/src/__tests__/agent-loop-subagent-billing-headers.test.ts +166 -0
  37. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +1 -0
  38. package/src/__tests__/agent-wake-override-profile.test.ts +1 -0
  39. package/src/__tests__/answered-question-persistence.test.ts +240 -0
  40. package/src/__tests__/anthropic-provider.test.ts +165 -30
  41. package/src/__tests__/app-control-flow.test.ts +9 -8
  42. package/src/__tests__/assistant-event-hub.test.ts +48 -0
  43. package/src/__tests__/attachments-store-heic-normalize.test.ts +16 -12
  44. package/src/__tests__/attachments-store.test.ts +130 -68
  45. package/src/__tests__/attachments.test.ts +36 -36
  46. package/src/__tests__/background-workers-disk-pressure.test.ts +1 -1
  47. package/src/__tests__/call-start-guardian-guard.test.ts +18 -4
  48. package/src/__tests__/canned-reply-release.test.ts +86 -0
  49. package/src/__tests__/channel-guardian.test.ts +59 -29
  50. package/src/__tests__/channel-readiness-telegram-remote.test.ts +154 -0
  51. package/src/__tests__/channel-setup-panel-ack.test.ts +6 -5
  52. package/src/__tests__/client-os-metadata-persistence.test.ts +103 -4
  53. package/src/__tests__/compactor-image-manifest-trust.test.ts +1 -1
  54. package/src/__tests__/consult-deadline.test.ts +65 -0
  55. package/src/__tests__/conversation-agent-loop.test.ts +438 -2
  56. package/src/__tests__/conversation-disk-view-integration.test.ts +5 -1
  57. package/src/__tests__/conversation-disk-view.test.ts +4 -4
  58. package/src/__tests__/conversation-error.test.ts +22 -3
  59. package/src/__tests__/conversation-fork-crud.test.ts +5 -1
  60. package/src/__tests__/conversation-fork-referential.test.ts +352 -0
  61. package/src/__tests__/conversation-fork-retrospective.test.ts +5 -1
  62. package/src/__tests__/conversation-group-tools.test.ts +32 -2
  63. package/src/__tests__/conversation-inference-profile-route.test.ts +27 -1
  64. package/src/__tests__/conversation-initial-prompt.test.ts +51 -22
  65. package/src/__tests__/conversation-key-store-bootstrap-cleanup.test.ts +119 -0
  66. package/src/__tests__/conversation-key-store-origin.test.ts +105 -0
  67. package/src/__tests__/conversation-list-recency-ordering.test.ts +104 -0
  68. package/src/__tests__/conversation-origin-at-creation.test.ts +153 -0
  69. package/src/__tests__/conversation-origin-channel-filter.test.ts +95 -0
  70. package/src/__tests__/conversation-placement-promotion.test.ts +172 -0
  71. package/src/__tests__/conversation-queue.test.ts +265 -1
  72. package/src/__tests__/conversation-retry-route.test.ts +3 -1
  73. package/src/__tests__/conversation-store-ephemeral.test.ts +4 -0
  74. package/src/__tests__/conversation-store.test.ts +26 -10
  75. package/src/__tests__/conversation-summarize-route.test.ts +1 -1
  76. package/src/__tests__/conversation-surfaces-action-delivery.test.ts +15 -6
  77. package/src/__tests__/conversation-surfaces-activation-emit.test.ts +6 -5
  78. package/src/__tests__/conversation-surfaces-app-control.test.ts +12 -11
  79. package/src/__tests__/conversation-surfaces-app-open.test.ts +11 -5
  80. package/src/__tests__/conversation-surfaces-data-persist.test.ts +5 -4
  81. package/src/__tests__/conversation-surfaces-history-restored-completion.test.ts +967 -0
  82. package/src/__tests__/conversation-surfaces-persisted-info.test.ts +312 -0
  83. package/src/__tests__/conversation-surfaces-queued-emit.test.ts +6 -5
  84. package/src/__tests__/conversation-surfaces-standalone-payloads.test.ts +12 -8
  85. package/src/__tests__/conversation-surfaces-standalone.test.ts +21 -11
  86. package/src/__tests__/conversation-surfaces-state-update.test.ts +7 -11
  87. package/src/__tests__/conversation-surfaces-table-action.test.ts +7 -6
  88. package/src/__tests__/conversation-surfaces-task-progress.test.ts +11 -6
  89. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +9 -6
  90. package/src/__tests__/conversation-tool-setup-attribution.test.ts +70 -7
  91. package/src/__tests__/conversation-wait-for-idle.test.ts +6 -11
  92. package/src/__tests__/credential-prompt-route.test.ts +57 -7
  93. package/src/__tests__/credential-security-invariants.test.ts +1 -0
  94. package/src/__tests__/cu-unified-flow.test.ts +89 -28
  95. package/src/__tests__/custom-group-listing-visibility.test.ts +1 -1
  96. package/src/__tests__/db-schedule-syntax-migration.test.ts +4 -1
  97. package/src/__tests__/discord-access-request-privacy.test.ts +366 -0
  98. package/src/__tests__/discord-requester-notice-privacy.test.ts +87 -0
  99. package/src/__tests__/disk-pressure-guard.test.ts +55 -55
  100. package/src/__tests__/disk-pressure-routes.test.ts +10 -10
  101. package/src/__tests__/disk-pressure-tools.test.ts +2 -2
  102. package/src/__tests__/disk-usage.test.ts +35 -20
  103. package/src/__tests__/document-sync-tags.test.ts +379 -0
  104. package/src/__tests__/document-update-default-surface.test.ts +28 -0
  105. package/src/__tests__/dynamic-skill-background-guard.test.ts +2 -2
  106. package/src/__tests__/emit-signal-routing-intent.test.ts +3 -4
  107. package/src/__tests__/ensure-conversation-exists.test.ts +8 -5
  108. package/src/__tests__/events-dev-bypass-actor.test.ts +183 -1
  109. package/src/__tests__/evict-conversations-for-reload.test.ts +90 -0
  110. package/src/__tests__/file-list-tool.test.ts +12 -10
  111. package/src/__tests__/file-ops-service.test.ts +151 -44
  112. package/src/__tests__/file-read-container-boundary.test.ts +137 -0
  113. package/src/__tests__/file-read-tool.test.ts +21 -9
  114. package/src/__tests__/file-url-path-guard.test.ts +83 -0
  115. package/src/__tests__/filesystem-tools.test.ts +82 -66
  116. package/src/__tests__/guardian-outbound-http.test.ts +8 -8
  117. package/src/__tests__/helpers/gateway-verification-sessions-stub.ts +14 -1
  118. package/src/__tests__/helpers/hub-clients.ts +42 -0
  119. package/src/__tests__/helpers/mock-conversation.ts +46 -0
  120. package/src/__tests__/helpers/verification-sessions-ipc-sim.ts +61 -57
  121. package/src/__tests__/history-repair.test.ts +112 -12
  122. package/src/__tests__/host-bash-proxy.test.ts +10 -5
  123. package/src/__tests__/host-bash-routes.test.ts +67 -0
  124. package/src/__tests__/host-browser-proxy.test.ts +2 -2
  125. package/src/__tests__/host-cu-proxy.test.ts +3 -7
  126. package/src/__tests__/host-file-proxy.test.ts +3 -7
  127. package/src/__tests__/host-file-read-tool.test.ts +5 -1
  128. package/src/__tests__/host-transfer-proxy-targeted.test.ts +6 -4
  129. package/src/__tests__/http-conversation-lineage.test.ts +2 -5
  130. package/src/__tests__/http-user-message-parity.test.ts +44 -0
  131. package/src/__tests__/identity-routes.test.ts +13 -13
  132. package/src/__tests__/image-conversion.test.ts +29 -26
  133. package/src/__tests__/image-source-path-reinject.test.ts +2 -2
  134. package/src/__tests__/inference-profile-session-handler.test.ts +95 -0
  135. package/src/__tests__/inference-profile-session-ipc.test.ts +25 -0
  136. package/src/__tests__/introduction-card-resolver.test.ts +6 -1
  137. package/src/__tests__/list-messages-attachments.test.ts +56 -32
  138. package/src/__tests__/list-messages-background-tool-completion.test.ts +4 -4
  139. package/src/__tests__/list-messages-client-message-id.test.ts +2 -2
  140. package/src/__tests__/list-messages-hidden-metadata.test.ts +16 -16
  141. package/src/__tests__/list-messages-page-latest.test.ts +61 -61
  142. package/src/__tests__/list-messages-provider-error.test.ts +6 -6
  143. package/src/__tests__/list-messages-queued.test.ts +49 -6
  144. package/src/__tests__/list-messages-system-card.test.ts +4 -4
  145. package/src/__tests__/list-messages-tool-merge.test.ts +9 -9
  146. package/src/__tests__/llm-resolver-override-or-default.test.ts +141 -0
  147. package/src/__tests__/llm-usage-store.test.ts +55 -0
  148. package/src/__tests__/manual-token-reconciliation.test.ts +45 -0
  149. package/src/__tests__/mcp-tool-annotations-risk.test.ts +133 -0
  150. package/src/__tests__/media-stream-server-integration.test.ts +36 -0
  151. package/src/__tests__/migration-export-http.test.ts +1 -1
  152. package/src/__tests__/migration-import-commit-http.test.ts +1 -1
  153. package/src/__tests__/migration-import-preflight-http.test.ts +1 -1
  154. package/src/__tests__/migration-validate-http.test.ts +1 -1
  155. package/src/__tests__/mtime-cache.test.ts +65 -3
  156. package/src/__tests__/notification-platform-adapter.test.ts +104 -0
  157. package/src/__tests__/notification-schedule-notify-dedup.test.ts +1 -1
  158. package/src/__tests__/oauth-provider-profiles.test.ts +4 -2
  159. package/src/__tests__/oauth-provider-serializer.test.ts +17 -0
  160. package/src/__tests__/oauth-providers-routes.test.ts +1 -0
  161. package/src/__tests__/openai-responses-prompt-cache.test.ts +5 -2
  162. package/src/__tests__/path-policy.test.ts +164 -1
  163. package/src/__tests__/persist-media-references.test.ts +2 -2
  164. package/src/__tests__/persist-unsendable-image-downscale.test.ts +5 -5
  165. package/src/__tests__/persist-unsendable-image.test.ts +23 -23
  166. package/src/__tests__/persona-resolver.test.ts +154 -0
  167. package/src/__tests__/plugin-api-webhook-url.test.ts +170 -0
  168. package/src/__tests__/plugin-import-boundary-guard.test.ts +6 -0
  169. package/src/__tests__/processing-flag-persist-failure.test.ts +15 -10
  170. package/src/__tests__/profile-availability-incomplete.test.ts +113 -0
  171. package/src/__tests__/profiler-routes.test.ts +13 -13
  172. package/src/__tests__/prompt-cache-cross-turn-stability.test.ts +593 -0
  173. package/src/__tests__/proxy-approval-callback.test.ts +3 -3
  174. package/src/__tests__/queued-message-cancel-and-steer.test.ts +189 -0
  175. package/src/__tests__/queued-message-steer-actor-scoping.test.ts +164 -0
  176. package/src/__tests__/resolve-trust-class.test.ts +20 -0
  177. package/src/__tests__/run-conversation-turn-persistence.test.ts +1 -1
  178. package/src/__tests__/run-due-schedules.test.ts +174 -0
  179. package/src/__tests__/runtime-attachment-metadata.test.ts +15 -7
  180. package/src/__tests__/scaffold-managed-skill-tool.test.ts +166 -0
  181. package/src/__tests__/schedule-routes.test.ts +730 -8
  182. package/src/__tests__/schedule-store.test.ts +540 -0
  183. package/src/__tests__/schedule-tools.test.ts +103 -0
  184. package/src/__tests__/scheduler-wake.test.ts +5 -0
  185. package/src/__tests__/secret-routes-platform-proxy.test.ts +29 -1
  186. package/src/__tests__/secure-keys.test.ts +91 -0
  187. package/src/__tests__/server-history-render.test.ts +97 -7
  188. package/src/__tests__/size-guard.test.ts +8 -8
  189. package/src/__tests__/skill-docs-sync-guard.test.ts +3 -2
  190. package/src/__tests__/skill-tool-factory.test.ts +247 -0
  191. package/src/__tests__/skills-install-staging.test.ts +15 -6
  192. package/src/__tests__/slack-edit-ordering-characterization.test.ts +103 -0
  193. package/src/__tests__/slack-inbound-verification.test.ts +4 -1
  194. package/src/__tests__/slack-mention-binding-invariance.test.ts +103 -0
  195. package/src/__tests__/slack-mention-provider-leak-audit.test.ts +135 -0
  196. package/src/__tests__/starter-task-flow.test.ts +5 -4
  197. package/src/__tests__/steer-on-enqueue-question.test.ts +3 -2
  198. package/src/__tests__/stt-language-catalog-parity.test.ts +38 -0
  199. package/src/__tests__/subagent-allowlist-validation.test.ts +26 -4
  200. package/src/__tests__/subagent-cron-run-attribution.test.ts +301 -0
  201. package/src/__tests__/subagent-disposal.test.ts +10 -0
  202. package/src/__tests__/subagent-fork-notifications.test.ts +10 -0
  203. package/src/__tests__/subagent-fork-prompt-role.test.ts +85 -11
  204. package/src/__tests__/subagent-fork-spawn.test.ts +15 -7
  205. package/src/__tests__/subagent-manager-notify.test.ts +148 -0
  206. package/src/__tests__/subagent-notify-parent.test.ts +1 -1
  207. package/src/__tests__/subagent-persistence.test.ts +141 -0
  208. package/src/__tests__/subagent-role-registry.test.ts +172 -63
  209. package/src/__tests__/subagent-role-resolution.test.ts +88 -0
  210. package/src/__tests__/subagent-spawn-and-await.test.ts +48 -0
  211. package/src/__tests__/subagent-spawn-tool-fork.test.ts +111 -10
  212. package/src/__tests__/subagent-terminal-message.test.ts +88 -1
  213. package/src/__tests__/subagent-tool-gate-mode.test.ts +225 -10
  214. package/src/__tests__/subagent-tools.test.ts +1901 -108
  215. package/src/__tests__/surface-completion-compaction-boundary.test.ts +124 -0
  216. package/src/__tests__/surface-completion-in-flight-snapshot.test.ts +298 -0
  217. package/src/__tests__/system-prompt.test.ts +82 -7
  218. package/src/__tests__/tool-result-spool.test.ts +74 -18
  219. package/src/__tests__/tool-side-effects-documents.test.ts +174 -0
  220. package/src/__tests__/tool-side-effects-hook-failures.test.ts +129 -0
  221. package/src/__tests__/tools-audio-read.test.ts +10 -10
  222. package/src/__tests__/tools-get-route.test.ts +85 -12
  223. package/src/__tests__/tools-list-cli.test.ts +171 -0
  224. package/src/__tests__/top-level-renderer.test.ts +54 -1
  225. package/src/__tests__/trusted-contact-multichannel.test.ts +4 -1
  226. package/src/__tests__/turn-tail-chain.test.ts +135 -0
  227. package/src/__tests__/ui-choice-copy-surfaces.test.ts +6 -4
  228. package/src/__tests__/ui-visual-surface.test.ts +5 -4
  229. package/src/__tests__/ui-work-result-surface.test.ts +5 -4
  230. package/src/__tests__/unified-turn-context-visible-app.test.ts +7 -8
  231. package/src/__tests__/usage-routes.test.ts +62 -0
  232. package/src/__tests__/verification-outbound-delivery.test.ts +176 -0
  233. package/src/__tests__/visible-app-context.test.ts +20 -4
  234. package/src/__tests__/web-search-history.test.ts +96 -0
  235. package/src/__tests__/workspace-migration-139-clear-renamed-cost-profile-label.test.ts +6 -6
  236. package/src/__tests__/workspace-migration-140-repair-seed-pinned-memory-v3-live.test.ts +330 -0
  237. package/src/acp/failure-error.ts +3 -5
  238. package/src/agent/attachments.ts +26 -24
  239. package/src/agent/history-repair/history-repair.ts +27 -26
  240. package/src/agent/image-optimize.ts +6 -6
  241. package/src/agent/loop.ts +40 -22
  242. package/src/agent/message-types.ts +3 -3
  243. package/src/api/constants/document-tools.ts +55 -0
  244. package/src/api/events/question-answered.ts +53 -0
  245. package/src/api/events/tool-result.ts +7 -0
  246. package/src/api/index.ts +13 -0
  247. package/src/api/responses/conversation-message.ts +11 -0
  248. package/src/api/surfaces-oauth-connect.test.ts +96 -0
  249. package/src/api/surfaces.ts +36 -8
  250. package/src/approvals/guardian-channel-delivery.ts +83 -0
  251. package/src/approvals/guardian-expiry-notifier.ts +13 -11
  252. package/src/approvals/guardian-request-resolvers.ts +216 -132
  253. package/src/calls/__tests__/voice-session-bridge.test.ts +50 -0
  254. package/src/calls/media-stream-server.ts +9 -1
  255. package/src/calls/voice-session-bridge.ts +38 -0
  256. package/src/channels/__tests__/gateway-verification-sessions.test.ts +3 -3
  257. package/src/channels/config.ts +7 -2
  258. package/src/channels/gateway-verification-sessions.ts +7 -2
  259. package/src/channels/types.ts +7 -1
  260. package/src/cli/commands/__tests__/cache.test.ts +7 -4
  261. package/src/cli/commands/__tests__/cli-test-harness.ts +82 -0
  262. package/src/cli/commands/__tests__/conversations-wake.test.ts +51 -6
  263. package/src/cli/commands/__tests__/inference-models.test.ts +14 -0
  264. package/src/cli/commands/__tests__/inference-profiles.test.ts +44 -1
  265. package/src/cli/commands/__tests__/inference-providers.test.ts +7 -0
  266. package/src/cli/commands/__tests__/plugins.test.ts +692 -0
  267. package/src/cli/commands/__tests__/schedules.test.ts +136 -37
  268. package/src/cli/commands/conversations.ts +7 -3
  269. package/src/cli/commands/credentials.help.ts +7 -0
  270. package/src/cli/commands/inference-profiles.ts +9 -3
  271. package/src/cli/commands/inference.help.ts +16 -2
  272. package/src/cli/commands/mcp.help.ts +4 -2
  273. package/src/cli/commands/memory/memory-retrospective.ts +18 -0
  274. package/src/cli/commands/notifications.ts +3 -6
  275. package/src/cli/commands/oauth/connect-surface-guidance.test.ts +16 -0
  276. package/src/cli/commands/oauth/connect-surface-guidance.ts +25 -7
  277. package/src/cli/commands/oauth/connect.test.ts +26 -1
  278. package/src/cli/commands/oauth/connect.ts +4 -1
  279. package/src/cli/commands/oauth/index.help.ts +3 -1
  280. package/src/cli/commands/oauth/providers.ts +3 -0
  281. package/src/cli/commands/plugins.help.ts +25 -2
  282. package/src/cli/commands/plugins.ts +289 -10
  283. package/src/cli/commands/schedules.help.ts +14 -12
  284. package/src/cli/commands/schedules.ts +19 -5
  285. package/src/cli/commands/tools.help.ts +4 -2
  286. package/src/cli/commands/tools.ts +38 -0
  287. package/src/cli/lib/__tests__/inspect-plugin.test.ts +1 -0
  288. package/src/cli/lib/__tests__/install-from-github.test.ts +79 -0
  289. package/src/cli/lib/__tests__/install-from-platform.test.ts +47 -1
  290. package/src/cli/lib/__tests__/plugin-surfaces.test.ts +86 -7
  291. package/src/cli/lib/__tests__/upgrade-plugin.test.ts +122 -0
  292. package/src/cli/lib/install-from-github.ts +61 -0
  293. package/src/cli/lib/install-from-platform.ts +13 -2
  294. package/src/cli/lib/plugin-surfaces.ts +124 -4
  295. package/src/cli/lib/upgrade-plugin.ts +18 -0
  296. package/src/cli.ts +1 -1
  297. package/src/config/__tests__/deployment-context-defaults.test.ts +73 -0
  298. package/src/config/__tests__/memory-retrospective-schema.test.ts +40 -0
  299. package/src/config/__tests__/plugin-updates-schema.test.ts +43 -0
  300. package/src/config/__tests__/profile-tool-support.test.ts +131 -0
  301. package/src/config/bundled-skills/AGENTS.md +1 -1
  302. package/src/config/bundled-skills/app-builder/SKILL.md +1 -1
  303. package/src/config/bundled-skills/app-builder/references/RESPONSIVE.md +3 -3
  304. package/src/config/bundled-skills/schedule/SKILL.md +5 -1
  305. package/src/config/bundled-skills/schedule/TOOLS.json +2 -2
  306. package/src/config/bundled-skills/settings/TOOLS.json +2 -2
  307. package/src/config/bundled-skills/settings/tools/voice-config-update.ts +1 -0
  308. package/src/config/bundled-skills/subagent/SKILL.md +53 -22
  309. package/src/config/bundled-skills/subagent/TOOLS.json +14 -13
  310. package/src/config/bundled-skills/visualize/SKILL.md +1 -1
  311. package/src/config/default-profile-catalog.ts +6 -2
  312. package/src/config/feature-flag-registry.json +25 -17
  313. package/src/config/llm-resolver.ts +45 -15
  314. package/src/config/loader.ts +70 -1
  315. package/src/config/profile-tool-support.ts +110 -0
  316. package/src/config/sanitize-for-transfer.ts +11 -0
  317. package/src/config/schema.ts +4 -0
  318. package/src/config/schemas/__tests__/stt.test.ts +20 -5
  319. package/src/config/schemas/channels.ts +13 -0
  320. package/src/config/schemas/memory-retrospective.ts +25 -0
  321. package/src/config/schemas/plugin-updates.ts +55 -0
  322. package/src/config/schemas/services.ts +8 -4
  323. package/src/config/schemas/stt.ts +17 -5
  324. package/src/contacts/__tests__/guardian-delivery-reader.test.ts +16 -0
  325. package/src/contacts/guardian-delivery-reader.ts +29 -2
  326. package/src/context/compactor.ts +4 -4
  327. package/src/context/post-turn-tool-result-truncation.ts +32 -16
  328. package/src/context/strip-injections.ts +42 -0
  329. package/src/context/tool-result-spool.ts +32 -18
  330. package/src/credential-execution/ces-runtime.ts +3 -0
  331. package/src/credential-execution/client.ts +22 -1
  332. package/src/credential-execution/process-manager.test.ts +99 -0
  333. package/src/credential-execution/process-manager.ts +62 -16
  334. package/src/daemon/__tests__/abort-null-controller.test.ts +141 -5
  335. package/src/daemon/__tests__/conversation-surfaces-launch.test.ts +8 -12
  336. package/src/daemon/__tests__/install-assistant-command.test.ts +234 -0
  337. package/src/daemon/__tests__/turn-tail-assistant-reply-notify.test.ts +2 -4
  338. package/src/daemon/__tests__/turn-tail-deleted-conversation.test.ts +20 -29
  339. package/src/daemon/channel-ui-capability.ts +4 -4
  340. package/src/daemon/conversation-agent-loop-handlers.ts +117 -61
  341. package/src/daemon/conversation-agent-loop.ts +296 -145
  342. package/src/daemon/conversation-attachments.ts +1 -1
  343. package/src/daemon/conversation-error.ts +8 -1
  344. package/src/daemon/conversation-initial-prompt.ts +16 -38
  345. package/src/daemon/conversation-lifecycle.ts +154 -32
  346. package/src/daemon/conversation-messaging.ts +85 -42
  347. package/src/daemon/conversation-process.ts +76 -28
  348. package/src/daemon/conversation-runtime-assembly.ts +9 -4
  349. package/src/daemon/conversation-store.ts +52 -3
  350. package/src/daemon/conversation-surface-state.ts +9 -0
  351. package/src/daemon/conversation-surfaces.ts +403 -210
  352. package/src/daemon/conversation-tool-setup.ts +56 -7
  353. package/src/daemon/conversation-turn-finalize.ts +134 -89
  354. package/src/daemon/conversation.ts +105 -27
  355. package/src/daemon/disk-pressure-guard-lifecycle.ts +2 -2
  356. package/src/daemon/disk-pressure-guard.ts +3 -3
  357. package/src/daemon/doordash-steps.ts +5 -5
  358. package/src/daemon/external-plugins-bootstrap.ts +7 -1
  359. package/src/daemon/handlers/config-channels.ts +48 -5
  360. package/src/daemon/handlers/conversation-history.ts +1 -1
  361. package/src/daemon/handlers/conversations.ts +62 -17
  362. package/src/daemon/handlers/shared.ts +12 -0
  363. package/src/daemon/handlers/skills.ts +11 -1
  364. package/src/daemon/identity-helpers.ts +38 -10
  365. package/src/daemon/install-assistant-command.ts +322 -0
  366. package/src/daemon/lifecycle.ts +14 -3
  367. package/src/daemon/message-types/conversations.ts +6 -7
  368. package/src/daemon/message-types/sync.ts +1 -0
  369. package/src/daemon/persist-media-references.ts +23 -14
  370. package/src/daemon/process-message.ts +6 -6
  371. package/src/daemon/tool-setup-types.ts +19 -122
  372. package/src/daemon/tool-side-effects.ts +17 -94
  373. package/src/daemon/trust-context.ts +14 -6
  374. package/src/daemon/turn-tail-chain.ts +73 -0
  375. package/src/daemon/web-search-history.ts +47 -33
  376. package/src/heartbeat/heartbeat-service.ts +6 -6
  377. package/src/ipc/__tests__/socket-path.test.ts +21 -0
  378. package/src/ipc/assistant-server.ts +17 -10
  379. package/src/ipc/cli-client.ts +10 -1
  380. package/src/ipc/routes/__tests__/documents-sync-ipc-routes.test.ts +56 -0
  381. package/src/ipc/routes/documents-sync-ipc-routes.ts +42 -0
  382. package/src/ipc/socket-cleanup.ts +9 -7
  383. package/src/ipc/socket-path.ts +4 -96
  384. package/src/live-voice/__tests__/live-voice-archive.test.ts +16 -16
  385. package/src/live-voice/__tests__/live-voice-attach-image.test.ts +209 -0
  386. package/src/live-voice/__tests__/live-voice-connection.test.ts +20 -0
  387. package/src/live-voice/__tests__/live-voice-integration.test.ts +7 -5
  388. package/src/live-voice/__tests__/live-voice-session-telemetry.test.ts +265 -0
  389. package/src/live-voice/__tests__/live-voice-stt.test.ts +6 -1
  390. package/src/live-voice/__tests__/live-voice-vad.test.ts +35 -15
  391. package/src/live-voice/__tests__/protocol.test.ts +52 -0
  392. package/src/live-voice/__tests__/runtime-websocket-shell.test.ts +3 -0
  393. package/src/live-voice/live-voice-archive.ts +13 -13
  394. package/src/live-voice/live-voice-connection.ts +7 -1
  395. package/src/live-voice/live-voice-photo.ts +169 -0
  396. package/src/live-voice/live-voice-session.ts +108 -4
  397. package/src/live-voice/protocol.ts +89 -1
  398. package/src/mcp/client.ts +10 -0
  399. package/src/messaging/providers/__tests__/callback-routing.test.ts +5 -1
  400. package/src/messaging/providers/__tests__/transport-dispatch.test.ts +66 -4
  401. package/src/messaging/providers/callback-routing.ts +1 -0
  402. package/src/messaging/providers/discord/api.ts +312 -0
  403. package/src/messaging/providers/discord/dm-delivery.test.ts +204 -0
  404. package/src/messaging/providers/discord/render.test.ts +204 -0
  405. package/src/messaging/providers/discord/render.ts +195 -0
  406. package/src/messaging/providers/discord/send.test.ts +179 -0
  407. package/src/messaging/providers/discord/send.ts +212 -0
  408. package/src/messaging/providers/discord/transport.ts +81 -0
  409. package/src/messaging/providers/index.ts +3 -1
  410. package/src/messaging/providers/retry-policy.test.ts +247 -0
  411. package/src/messaging/providers/retry-policy.ts +206 -0
  412. package/src/messaging/providers/slack/mention-source.test.ts +285 -0
  413. package/src/messaging/providers/slack/mention-source.ts +287 -0
  414. package/src/messaging/providers/telegram-bot/api.ts +47 -130
  415. package/src/messaging/providers/whatsapp/api.ts +47 -136
  416. package/src/monitoring/__tests__/db-integrity-sample.test.ts +4 -1
  417. package/src/monitoring/__tests__/plugin-auto-update.test.ts +332 -0
  418. package/src/monitoring/db-integrity-sample.ts +6 -1
  419. package/src/monitoring/plugin-auto-update.ts +359 -0
  420. package/src/monitoring/resource-sampler.ts +21 -5
  421. package/src/monitoring/worker.ts +13 -0
  422. package/src/notifications/__tests__/assistant-reply-producer.test.ts +375 -2
  423. package/src/notifications/__tests__/broadcaster.test.ts +37 -1
  424. package/src/notifications/__tests__/copy-composer.test.ts +165 -0
  425. package/src/notifications/__tests__/emit-signal-pipeline-failure.test.ts +205 -0
  426. package/src/notifications/__tests__/notification-utils.test.ts +249 -0
  427. package/src/notifications/adapters/platform.ts +31 -2
  428. package/src/notifications/assistant-reply-producer.ts +79 -32
  429. package/src/notifications/broadcaster.ts +30 -10
  430. package/src/notifications/copy-composer.ts +69 -0
  431. package/src/notifications/emit-signal.ts +82 -15
  432. package/src/notifications/events-store.ts +10 -2
  433. package/src/notifications/home-feed-side-effect.ts +6 -3
  434. package/src/notifications/notification-utils.ts +173 -5
  435. package/src/notifications/signal.ts +12 -0
  436. package/src/oauth/byo-connection.ts +6 -1
  437. package/src/oauth/credential-token-resolver.ts +3 -10
  438. package/src/oauth/manual-token-connection.ts +29 -64
  439. package/src/oauth/manual-token-providers.ts +62 -0
  440. package/src/oauth/provider-serializer.ts +13 -0
  441. package/src/oauth/seed-providers.ts +56 -8
  442. package/src/onboarding/onboarding-events-store.ts +91 -1
  443. package/src/permissions/question-prompter.test.ts +4 -0
  444. package/src/permissions/question-prompter.ts +21 -4
  445. package/src/persistence/__tests__/conversation-lineage.test.ts +248 -0
  446. package/src/persistence/attachments-store.ts +15 -15
  447. package/src/persistence/conversation-attention-store.ts +30 -10
  448. package/src/persistence/conversation-bootstrap.ts +17 -0
  449. package/src/persistence/conversation-crud.ts +363 -97
  450. package/src/persistence/conversation-group-migration.ts +1 -1
  451. package/src/persistence/conversation-key-store.ts +38 -10
  452. package/src/persistence/conversation-lineage.ts +226 -0
  453. package/src/persistence/conversation-plugin-facade.ts +28 -3
  454. package/src/persistence/conversation-queries.ts +207 -33
  455. package/src/persistence/conversation-types.ts +107 -1
  456. package/src/persistence/jobs-store.ts +32 -8
  457. package/src/persistence/llm-usage-store.ts +22 -0
  458. package/src/persistence/migrations/361-normalize-managed-connection-rows.test.ts +168 -0
  459. package/src/persistence/migrations/361-normalize-managed-connection-rows.ts +76 -0
  460. package/src/persistence/migrations/362-add-conversation-subagent-kind.test.ts +187 -0
  461. package/src/persistence/migrations/362-add-conversation-subagent-kind.ts +93 -0
  462. package/src/persistence/migrations/363-backfill-schedule-inference-profile.test.ts +229 -0
  463. package/src/persistence/migrations/363-backfill-schedule-inference-profile.ts +127 -0
  464. package/src/persistence/migrations/364-add-schedule-source-key.test.ts +120 -0
  465. package/src/persistence/migrations/364-add-schedule-source-key.ts +43 -0
  466. package/src/persistence/migrations/365-add-conversation-fork-strategy.ts +27 -0
  467. package/src/persistence/schema/conversations.ts +32 -0
  468. package/src/persistence/schema/infrastructure.ts +3 -0
  469. package/src/persistence/steps.ts +30 -0
  470. package/src/persistence/subagent-store.ts +148 -6
  471. package/src/platform/managed-speech.test.ts +20 -0
  472. package/src/platform/managed-speech.ts +9 -0
  473. package/src/plugin-api/index.ts +8 -0
  474. package/src/plugin-api/webhook-url.ts +151 -0
  475. package/src/plugins/__tests__/installed-plugin-dirs.test.ts +82 -0
  476. package/src/plugins/__tests__/source-fingerprint.test.ts +5 -1
  477. package/src/plugins/collect-source-versions.ts +4 -27
  478. package/src/plugins/defaults/exploration-drift/hooks/post-tool-use.ts +5 -5
  479. package/src/plugins/defaults/exploration-drift/package.json +1 -1
  480. package/src/plugins/defaults/image-recovery/hooks/post-model-call.ts +4 -2
  481. package/src/plugins/defaults/image-recovery/recover.ts +67 -57
  482. package/src/plugins/defaults/index.ts +1 -1
  483. package/src/plugins/defaults/memory/__tests__/conversation-queries.test.ts +64 -18
  484. package/src/plugins/defaults/memory/__tests__/jobs-worker-outcome-contract.test.ts +242 -0
  485. package/src/plugins/defaults/memory/__tests__/jobs-worker-retrospective-sweep.test.ts +24 -1
  486. package/src/plugins/defaults/memory/__tests__/memory-retrospective-enqueue.test.ts +48 -3
  487. package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +511 -19
  488. package/src/plugins/defaults/memory/__tests__/memory-retrospective-prompt.test.ts +39 -3
  489. package/src/plugins/defaults/memory/__tests__/memory-retrospective-provider-path.test.ts +493 -0
  490. package/src/plugins/defaults/memory/__tests__/memory-retrospective-wake-chain.test.ts +5 -4
  491. package/src/plugins/defaults/memory/__tests__/memory-tier-boundary-guard.test.ts +5 -1
  492. package/src/plugins/defaults/memory/job-handlers.ts +87 -5
  493. package/src/plugins/defaults/memory/jobs-worker.ts +60 -6
  494. package/src/plugins/defaults/memory/memory-retrospective-constants.ts +11 -0
  495. package/src/plugins/defaults/memory/memory-retrospective-enqueue.ts +29 -0
  496. package/src/plugins/defaults/memory/memory-retrospective-job.ts +342 -60
  497. package/src/plugins/defaults/memory/memory-retrospective-prompt.ts +23 -3
  498. package/src/plugins/defaults/memory/substrate/consolidation-lock.ts +9 -4
  499. package/src/plugins/defaults/memory/substrate/sweep-job.ts +7 -0
  500. package/src/plugins/defaults/memory/v1/graph/injection.ts +1 -1
  501. package/src/plugins/defaults/memory/v2/router.ts +8 -0
  502. package/src/plugins/defaults/turn-context/unified-turn-context.ts +1 -2
  503. package/src/plugins/external-plugin-loader.ts +17 -8
  504. package/src/plugins/installed-plugin-dirs.ts +101 -0
  505. package/src/plugins/mtime-cache.ts +34 -22
  506. package/src/plugins/types.ts +69 -8
  507. package/src/prompts/persona-resolver.ts +6 -2
  508. package/src/prompts/system-prompt.ts +72 -17
  509. package/src/providers/__tests__/inference.test.ts +40 -0
  510. package/src/providers/__tests__/preflight-resolved-config.test.ts +58 -1
  511. package/src/providers/__tests__/retry-callsite.test.ts +67 -0
  512. package/src/providers/__tests__/vellum-connection-routing.test.ts +42 -11
  513. package/src/providers/anthropic/client.ts +112 -95
  514. package/src/providers/connection-resolution.ts +35 -1
  515. package/src/providers/gemini/client.ts +5 -5
  516. package/src/providers/inference/adapter-factory.ts +16 -10
  517. package/src/providers/inference/auth.ts +16 -0
  518. package/src/providers/inference/connection-availability.ts +164 -12
  519. package/src/providers/inference/connections.ts +25 -1
  520. package/src/providers/media-resolve.ts +21 -11
  521. package/src/providers/model-catalog.ts +15 -0
  522. package/src/providers/openai/chat-completions-provider.ts +4 -4
  523. package/src/providers/openai/responses-provider.ts +12 -25
  524. package/src/providers/registry.ts +9 -4
  525. package/src/providers/retry.ts +25 -0
  526. package/src/providers/server-tool-pairing.ts +134 -0
  527. package/src/providers/speech-to-text/__tests__/resolve.test.ts +187 -6
  528. package/src/providers/speech-to-text/deepgram.ts +1 -0
  529. package/src/providers/speech-to-text/resolve.ts +80 -10
  530. package/src/providers/speech-to-text/vellum-managed.ts +7 -1
  531. package/src/providers/speech-to-text/vellum-speech-relay-connection.ts +3 -1
  532. package/src/providers/types.ts +26 -6
  533. package/src/providers/vellum-model-routing.ts +6 -7
  534. package/src/runtime/AGENTS.md +2 -0
  535. package/src/runtime/__tests__/agent-wake.test.ts +16 -16
  536. package/src/runtime/__tests__/background-job-runner.test.ts +0 -144
  537. package/src/runtime/__tests__/desktop-presence.test.ts +162 -0
  538. package/src/runtime/agent-wake.ts +63 -24
  539. package/src/runtime/assistant-event-hub.ts +87 -12
  540. package/src/runtime/auth/__tests__/guard-tests.test.ts +2 -0
  541. package/src/runtime/auth/__tests__/scopes.test.ts +20 -0
  542. package/src/runtime/auth/same-actor.ts +56 -8
  543. package/src/runtime/auth/scopes.ts +12 -0
  544. package/src/runtime/auth/types.ts +2 -0
  545. package/src/runtime/background-job-runner.ts +5 -27
  546. package/src/runtime/channel-readiness-service.ts +59 -3
  547. package/src/runtime/channel-readiness-types.ts +18 -0
  548. package/src/runtime/desktop-presence.ts +69 -0
  549. package/src/runtime/http-server.ts +12 -0
  550. package/src/runtime/local-actor-identity.ts +11 -9
  551. package/src/runtime/migrations/__tests__/vbundle-symlink-streaming-importer.test.ts +0 -0
  552. package/src/runtime/routes/__tests__/channel-verification-routes.test.ts +1 -1
  553. package/src/runtime/routes/__tests__/client-routes.test.ts +186 -14
  554. package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +547 -1
  555. package/src/runtime/routes/__tests__/conversation-management-routes.test.ts +162 -7
  556. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +14 -10
  557. package/src/runtime/routes/__tests__/inference-profiles-routes.test.ts +340 -3
  558. package/src/runtime/routes/__tests__/llm-call-sites-routes.test.ts +28 -1
  559. package/src/runtime/routes/__tests__/plugins-routes.test.ts +173 -1
  560. package/src/runtime/routes/__tests__/question-routes.test.ts +64 -0
  561. package/src/runtime/routes/__tests__/retrospective-routes.test.ts +20 -0
  562. package/src/runtime/routes/__tests__/skill-history-route.test.ts +116 -0
  563. package/src/runtime/routes/__tests__/wake-conversation-routes.test.ts +60 -2
  564. package/src/runtime/routes/archive-utils.ts +16 -12
  565. package/src/runtime/routes/attachment-routes.ts +13 -6
  566. package/src/runtime/routes/canned-reply-release.ts +54 -0
  567. package/src/runtime/routes/channel-verification-routes.ts +2 -27
  568. package/src/runtime/routes/chatgpt-subscription-auth-routes.ts +5 -0
  569. package/src/runtime/routes/client-routes.ts +114 -2
  570. package/src/runtime/routes/conversation-cli-routes.ts +10 -12
  571. package/src/runtime/routes/conversation-list-routes.ts +59 -10
  572. package/src/runtime/routes/conversation-management-routes.ts +47 -18
  573. package/src/runtime/routes/conversation-query-routes.ts +32 -53
  574. package/src/runtime/routes/conversation-routes.ts +350 -293
  575. package/src/runtime/routes/credential-prompt-routes.ts +23 -2
  576. package/src/runtime/routes/debug-routes.ts +4 -1
  577. package/src/runtime/routes/default-provider-routes.ts +7 -11
  578. package/src/runtime/routes/documents-routes.ts +44 -1
  579. package/src/runtime/routes/events-routes.ts +28 -1
  580. package/src/runtime/routes/host-app-control-routes.ts +2 -0
  581. package/src/runtime/routes/host-bash-routes.ts +2 -0
  582. package/src/runtime/routes/host-browser-routes.ts +2 -0
  583. package/src/runtime/routes/host-cu-routes.ts +2 -0
  584. package/src/runtime/routes/host-file-routes.ts +2 -0
  585. package/src/runtime/routes/host-transfer-routes.ts +4 -0
  586. package/src/runtime/routes/identity-routes.ts +6 -6
  587. package/src/runtime/routes/inbound-message-handler.ts +5 -5
  588. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +7 -3
  589. package/src/runtime/routes/inbound-stages/background-dispatch.ts +14 -0
  590. package/src/runtime/routes/inbound-stages/bootstrap-intercept.ts +2 -2
  591. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.test.ts +14 -11
  592. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +14 -17
  593. package/src/runtime/routes/inference-profile-availability-guard.ts +171 -0
  594. package/src/runtime/routes/inference-profile-session-handler.ts +26 -2
  595. package/src/runtime/routes/inference-profile-session-routes.ts +2 -1
  596. package/src/runtime/routes/inference-profiles-routes.ts +152 -73
  597. package/src/runtime/routes/inference-provider-connection-routes.ts +0 -16
  598. package/src/runtime/routes/llm-call-sites-routes.ts +13 -0
  599. package/src/runtime/routes/log-export-routes.ts +1 -1
  600. package/src/runtime/routes/notification-routes.ts +0 -14
  601. package/src/runtime/routes/oauth-providers.ts +1 -0
  602. package/src/runtime/routes/plugins-routes.ts +106 -1
  603. package/src/runtime/routes/profiler-routes.ts +4 -2
  604. package/src/runtime/routes/question-routes.ts +28 -10
  605. package/src/runtime/routes/retrospective-routes.ts +9 -3
  606. package/src/runtime/routes/schedule-routes.ts +210 -12
  607. package/src/runtime/routes/secret-routes.ts +40 -23
  608. package/src/runtime/routes/settings-routes.ts +41 -13
  609. package/src/runtime/routes/skills-routes.ts +79 -0
  610. package/src/runtime/routes/ui-snapshot-routes.ts +1 -0
  611. package/src/runtime/routes/wake-conversation-routes.ts +23 -0
  612. package/src/runtime/services/__tests__/conversation-serializer.test.ts +1 -0
  613. package/src/runtime/services/conversation-serializer.ts +24 -9
  614. package/src/runtime/sync/documents-sidecar-publish.test.ts +101 -0
  615. package/src/runtime/sync/resource-sync-events.ts +79 -1
  616. package/src/runtime/sync/worker-daemon-notify.test.ts +34 -0
  617. package/src/runtime/sync/worker-daemon-notify.ts +44 -6
  618. package/src/runtime/verification-outbound-actions.ts +250 -222
  619. package/src/runtime/verification-templates.ts +41 -30
  620. package/src/schedule/__tests__/plugin-schedule-declarations.test.ts +614 -0
  621. package/src/schedule/__tests__/plugin-schedule-reconciler.test.ts +1066 -0
  622. package/src/schedule/inference-profile.ts +64 -3
  623. package/src/schedule/plugin-schedule-declarations.ts +518 -0
  624. package/src/schedule/plugin-schedule-reconciler.ts +605 -0
  625. package/src/schedule/plugin-schedules-gate.ts +23 -0
  626. package/src/schedule/schedule-store.ts +520 -26
  627. package/src/schedule/scheduler.ts +38 -0
  628. package/src/security/credential-backend.ts +51 -4
  629. package/src/security/encrypted-store.ts +64 -42
  630. package/src/security/secure-keys.ts +55 -7
  631. package/src/skills/catalog-install.ts +20 -7
  632. package/src/skills/inline-command-runner.ts +2 -7
  633. package/src/skills/skill-history.test.ts +269 -0
  634. package/src/skills/skill-history.ts +250 -0
  635. package/src/skills/skillssh-registry.ts +1 -1
  636. package/src/stt/daemon-batch-transcriber.ts +8 -6
  637. package/src/subagent/__tests__/consult-prompt.test.ts +25 -0
  638. package/src/subagent/consult-prompt.ts +3 -1
  639. package/src/subagent/consult-transcript.ts +1 -1
  640. package/src/subagent/manager.ts +295 -42
  641. package/src/subagent/progress-events.ts +35 -0
  642. package/src/subagent/role-resolution.ts +88 -0
  643. package/src/subagent/types.ts +302 -67
  644. package/src/subagent/validate-allowlists.ts +3 -4
  645. package/src/telegram/__tests__/webhook-health.test.ts +73 -1
  646. package/src/telegram/webhook-health.ts +101 -3
  647. package/src/telemetry/__tests__/telemetry-event-fixtures.ts +2 -0
  648. package/src/telemetry/activation-funnel.ts +6 -1
  649. package/src/telemetry/live-voice-funnel.ts +75 -0
  650. package/src/telemetry/telemetry-event-sources.ts +6 -0
  651. package/src/telemetry/telemetry-wire-source.json +1 -1
  652. package/src/telemetry/telemetry-wire.generated.ts +2 -0
  653. package/src/telemetry/types.ts +23 -4
  654. package/src/telemetry/usage-telemetry-reporter.test.ts +4 -0
  655. package/src/tools/__tests__/subagent-workflow-tool-input-schemas.test.ts +1 -1
  656. package/src/tools/ask-question/ask-question-tool.test.ts +118 -3
  657. package/src/tools/ask-question/ask-question-tool.ts +56 -2
  658. package/src/tools/browser/browser-execution.ts +1 -1
  659. package/src/tools/calls/call-start.ts +8 -5
  660. package/src/tools/conversation-groups/move_to_group.ts +10 -12
  661. package/src/tools/document/document-tool.ts +11 -4
  662. package/src/tools/executor.ts +7 -6
  663. package/src/tools/filesystem/edit.ts +1 -1
  664. package/src/tools/filesystem/list.ts +1 -1
  665. package/src/tools/filesystem/read.ts +7 -13
  666. package/src/tools/filesystem/search.ts +23 -21
  667. package/src/tools/filesystem/write.ts +4 -1
  668. package/src/tools/host-filesystem/edit.ts +7 -2
  669. package/src/tools/host-filesystem/read.ts +21 -8
  670. package/src/tools/host-filesystem/write.ts +10 -2
  671. package/src/tools/host-terminal/host-shell.ts +2 -2
  672. package/src/tools/mcp/mcp-tool-factory.ts +25 -1
  673. package/src/tools/permission-checker.ts +8 -3
  674. package/src/tools/schedule/delete.ts +13 -1
  675. package/src/tools/schedule/list.ts +14 -3
  676. package/src/tools/schedule/update.ts +56 -33
  677. package/src/tools/shared/filesystem/audio-read.ts +6 -4
  678. package/src/tools/shared/filesystem/file-ops-service.ts +176 -116
  679. package/src/tools/shared/filesystem/image-read.ts +12 -12
  680. package/src/tools/shared/filesystem/path-policy.ts +70 -5
  681. package/src/tools/shared/filesystem/size-guard.ts +6 -6
  682. package/src/tools/shared/input-misuse.ts +72 -0
  683. package/src/tools/skills/scaffold-managed.ts +82 -0
  684. package/src/tools/skills/skill-tool-factory.ts +14 -1
  685. package/src/tools/subagent/consult-deadline.ts +12 -7
  686. package/src/tools/subagent/message.ts +3 -1
  687. package/src/tools/subagent/read.ts +66 -4
  688. package/src/tools/subagent/spawn.ts +500 -78
  689. package/src/tools/types.ts +22 -3
  690. package/src/tools/ui-surface/surface-shape-docs.ts +1 -1
  691. package/src/tools/ui-surface/visual-validation.ts +2 -0
  692. package/src/usage/__tests__/subagent-attribution.test.ts +109 -0
  693. package/src/usage/subagent-attribution.ts +108 -0
  694. package/src/util/__tests__/sqlite-retry.test.ts +112 -0
  695. package/src/util/abort-reasons.ts +16 -0
  696. package/src/util/ansi.ts +44 -0
  697. package/src/util/disk-usage.ts +17 -13
  698. package/src/util/image-conversion.ts +20 -13
  699. package/src/util/schedule-source-key.ts +23 -0
  700. package/src/util/worker-process.ts +7 -1
  701. package/src/workspace/git-service.ts +33 -9
  702. package/src/workspace/migrations/140-repair-seed-pinned-memory-v3-live.ts +260 -0
  703. package/src/workspace/migrations/141-stt-english-default-to-multilingual.ts +111 -0
  704. package/src/workspace/migrations/__tests__/141-stt-english-default-to-multilingual.test.ts +126 -0
  705. package/src/workspace/migrations/registry.ts +4 -0
  706. package/src/workspace/top-level-renderer.ts +15 -2
  707. package/src/__tests__/tool-side-effects-slack-dm.test.ts +0 -357
  708. package/src/daemon/install-symlink.ts +0 -207
  709. package/src/notifications/deferred-emit.ts +0 -147
@@ -110,10 +110,10 @@ export function upscaleTargetDimensions(dims: {
110
110
  * Reactive only: called by the image-recovery plugin after an actual
111
111
  * "Could not process image" rejection, never on the pre-send path.
112
112
  */
113
- export function upscaleImageToMinimum(
113
+ export async function upscaleImageToMinimum(
114
114
  base64Data: string,
115
115
  mediaType: string,
116
- ): { data: string; mediaType: string } | null {
116
+ ): Promise<{ data: string; mediaType: string } | null> {
117
117
  const dims = parseImageDimensions(base64Data, mediaType);
118
118
  if (!dims) {
119
119
  return null;
@@ -122,7 +122,7 @@ export function upscaleImageToMinimum(
122
122
  if (!target) {
123
123
  return null;
124
124
  }
125
- const upscaled = convertImageToJpeg(Buffer.from(base64Data, "base64"), {
125
+ const upscaled = await convertImageToJpeg(Buffer.from(base64Data, "base64"), {
126
126
  resizeToPx: target,
127
127
  quality: JPEG_QUALITY,
128
128
  });
@@ -144,10 +144,10 @@ export function upscaleImageToMinimum(
144
144
  * Conversion (and its content-addressed disk cache) is shared with attachment
145
145
  * storage normalization — see `util/image-conversion.ts`.
146
146
  */
147
- export function optimizeImageForTransport(
147
+ export async function optimizeImageForTransport(
148
148
  base64Data: string,
149
149
  mediaType: string,
150
- ): { data: string; mediaType: string } {
150
+ ): Promise<{ data: string; mediaType: string }> {
151
151
  const rawBytes = Buffer.from(base64Data, "base64");
152
152
  const dims = parseImageDimensions(base64Data, mediaType);
153
153
 
@@ -155,7 +155,7 @@ export function optimizeImageForTransport(
155
155
  return { data: base64Data, mediaType };
156
156
  }
157
157
 
158
- const optimized = convertImageToJpeg(rawBytes, {
158
+ const optimized = await convertImageToJpeg(rawBytes, {
159
159
  maxDimensionPx: MAX_DIMENSION,
160
160
  quality: JPEG_QUALITY,
161
161
  });
package/src/agent/loop.ts CHANGED
@@ -1,8 +1,8 @@
1
- import { getConfig } from "../config/loader.js";
2
- import { isMemoryV3Live } from "../config/memory-v3-gate.js";
1
+ import type { AnsweredQuestion } from "../api/events/question-answered.js";
3
2
  import type { LLMCallSite } from "../config/schemas/llm.js";
4
3
  import { recordEstimate } from "../context/estimator-calibration.js";
5
4
  import { preModelCallSanitize } from "../context/outbound-sanitize.js";
5
+ import { turnStartUserMessageHasSpotlight } from "../context/strip-injections.js";
6
6
  import {
7
7
  estimatePromptTokensRaw,
8
8
  estimatePromptTokensWithTools,
@@ -116,9 +116,9 @@ export interface AgentLoopConfig {
116
116
  * This is a SERVER tool the provider runs itself, distinct from the client
117
117
  * tool list (`tools` / `resolveTools`): it is never executed by
118
118
  * {@link AgentLoopConstructorOptions.toolExecutor} and does not require any
119
- * client-tool allowlist entry. Used by the tool-less advisor consult to
120
- * ground its guidance with live web access while staying one-shot for client
121
- * tools. Defaults to false — existing behavior.
119
+ * client-tool allowlist entry. Used by the advisor consult to ground its
120
+ * guidance with live web access on top of its read-only client tools.
121
+ * Defaults to false.
122
122
  */
123
123
  enableNativeWebSearch?: boolean;
124
124
  }
@@ -289,6 +289,8 @@ export type AgentEvent =
289
289
  approvalReason?: string;
290
290
  riskThreshold?: string;
291
291
  activityMetadata?: ToolActivityMetadata;
292
+ /** Answered `ask_question` record (see `ToolExecutionResult.answeredQuestion`). */
293
+ answeredQuestion?: AnsweredQuestion;
292
294
  /** Stable machine-readable error classification (see `ToolExecutionResult.errorCode`). */
293
295
  errorCode?: string;
294
296
  /**
@@ -698,6 +700,7 @@ export type LoopToolExecutor = (
698
700
  approvalReason?: string;
699
701
  riskThreshold?: string;
700
702
  activityMetadata?: ToolActivityMetadata;
703
+ answeredQuestion?: AnsweredQuestion;
701
704
  errorCode?: string;
702
705
  }>;
703
706
 
@@ -1355,9 +1358,9 @@ export class AgentLoop {
1355
1358
  // `this.provider.supportsNativeWebSearch` snapshot; providers without
1356
1359
  // the routing-aware probe fall back to the static flag. This is a SERVER
1357
1360
  // tool — it bypasses the client allowlist and the tool executor — so the
1358
- // tool-less advisor consult can ground its guidance with live web access
1359
- // while staying one-shot for client tools. Skip when a `web_search` tool
1360
- // is already present so we never duplicate the name.
1361
+ // advisor consult can ground its guidance with live web access on top of
1362
+ // its read-only client tools. Skip when a `web_search` tool is already
1363
+ // present so we never duplicate the name.
1361
1364
  const supportsRoutedNativeWebSearch = this.provider
1362
1365
  .supportsNativeWebSearchFor
1363
1366
  ? this.provider.supportsNativeWebSearchFor(
@@ -1420,9 +1423,7 @@ export class AgentLoop {
1420
1423
  if (this.config.toolChoice) {
1421
1424
  providerConfig.tool_choice = this.config.toolChoice;
1422
1425
  } else if (attachNativeWebSearch) {
1423
- // The native web-search tool is the only tool on this turn (the
1424
- // advisor consult is otherwise tool-less). Let the model decide
1425
- // whether to search rather than forcing it.
1426
+ // Let the model decide whether to search rather than forcing it.
1426
1427
  providerConfig.tool_choice = { type: "auto" };
1427
1428
  }
1428
1429
 
@@ -1430,14 +1431,18 @@ export class AgentLoop {
1430
1431
  providerConfig.cacheTtl = this.config.cacheTtl;
1431
1432
  }
1432
1433
 
1433
- // Cache-anchor signal for volatile latest-user-message turns: when
1434
- // memory-v3 is live it injects a per-turn `<memory>` block into the
1435
- // latest user message, so the provider must anchor its long-TTL cache
1436
- // breakpoint on the most recent STABLE user message instead of the
1437
- // volatile latest one. Read here alongside the rest of the provider
1438
- // config; only set when true so the wire/config stays byte-identical
1439
- // when off.
1440
- if (isMemoryV3Live(getConfig())) {
1434
+ // Cache-anchor signal for turns whose opening message is volatile. The
1435
+ // memory-v3 `<memory_spotlight>` block is the only injected block that
1436
+ // is strip-and-replaced from every user message each turn, so when it
1437
+ // is present that message's bytes do not recur next turn and a
1438
+ // long-TTL breakpoint on it could never be read back. The provider
1439
+ // marks it at the short TTL instead. Derived from the history actually
1440
+ // being sent rather than from configuration, so turns where memory
1441
+ // contributed no spotlight keep a normal anchor. Read off the
1442
+ // turn-starting message, so the signal holds for every request in the
1443
+ // turn rather than flipping once tool results arrive. Only set when
1444
+ // true so the wire/config stays byte-identical when absent.
1445
+ if (turnStartUserMessageHasSpotlight(history)) {
1441
1446
  providerConfig.mutableLatestUserMessage = true;
1442
1447
  }
1443
1448
 
@@ -1459,8 +1464,17 @@ export class AgentLoop {
1459
1464
  // conversation id). Absent for standalone `AgentLoop` instances
1460
1465
  // (unit tests constructed without a conversation id) — those fall
1461
1466
  // back to per-call random mix selection.
1467
+ //
1468
+ // The same id also travels as `conversationId`, which
1469
+ // `RetryProvider.normalizeSendMessageOptions` uses to resolve the
1470
+ // conversation's subagent role and spawn mode for the runtime
1471
+ // proxy's billing-attribution headers. Both are stripped before any
1472
+ // provider wire request; they are carried separately because
1473
+ // `selectionSeed` is an opaque hashing input that callers may set to
1474
+ // something other than a conversation id.
1462
1475
  if (this.conversationId) {
1463
1476
  providerConfig.selectionSeed = this.conversationId;
1477
+ providerConfig.conversationId = this.conversationId;
1464
1478
  }
1465
1479
  }
1466
1480
 
@@ -2201,13 +2215,16 @@ export class AgentLoop {
2201
2215
  // message between calls would invalidate the prompt-cache prefix on
2202
2216
  // every iteration).
2203
2217
  if (conversationDir) {
2204
- const toolNameByUseId = new Map(
2205
- toolUseBlocks.map((tu) => [tu.id, tu.name]),
2218
+ const toolCallByUseId = new Map(
2219
+ toolUseBlocks.map((tu) => [
2220
+ tu.id,
2221
+ { name: tu.name, input: tu.input },
2222
+ ]),
2206
2223
  );
2207
2224
  try {
2208
2225
  spoolAndStubOversizedToolResults(rawResultBlocks, {
2209
2226
  conversationDir,
2210
- toolNameById: (id) => toolNameByUseId.get(id),
2227
+ toolCallById: (id) => toolCallByUseId.get(id),
2211
2228
  });
2212
2229
  } catch (err) {
2213
2230
  rlog.warn(
@@ -2286,6 +2303,7 @@ export class AgentLoop {
2286
2303
  approvalReason: result.approvalReason,
2287
2304
  riskThreshold: result.riskThreshold,
2288
2305
  activityMetadata: result.activityMetadata,
2306
+ answeredQuestion: result.answeredQuestion,
2289
2307
  errorCode: result.errorCode,
2290
2308
  });
2291
2309
  }
@@ -4,15 +4,15 @@ import {
4
4
  type MessageAttachmentInput,
5
5
  } from "./attachments.js";
6
6
 
7
- export function createUserMessage(
7
+ export async function createUserMessage(
8
8
  text: string,
9
9
  attachments: MessageAttachmentInput[] = [],
10
- ): Message {
10
+ ): Promise<Message> {
11
11
  const content = [] as Message["content"];
12
12
  if (text.trim().length > 0) {
13
13
  content.push({ type: "text", text });
14
14
  }
15
- content.push(...attachmentsToContentBlocks(attachments));
15
+ content.push(...(await attachmentsToContentBlocks(attachments)));
16
16
  return { role: "user", content };
17
17
  }
18
18
 
@@ -0,0 +1,55 @@
1
+ /**
2
+ * The document tools that mutate the stored document set, and what each one
3
+ * leaves behind. This is the single declaration both the daemon and the web
4
+ * client derive their tool sets from, so a new mutating document tool is added
5
+ * here once and every consumer picks it up.
6
+ *
7
+ * Tool names are the ones the runner sees. Calls arriving through the bundled
8
+ * `document-editor` skill reach the daemon already unwrapped to the inner tool
9
+ * name, and the web client unwraps the `skill_execute` envelope itself.
10
+ */
11
+
12
+ export interface DocumentMutationTool {
13
+ /** The executed tool name. */
14
+ readonly name: string;
15
+ /**
16
+ * Whether the document is still openable once the tool has run. A deleted
17
+ * document has nothing to open, so it cannot anchor a link back to itself.
18
+ */
19
+ readonly reopenable: boolean;
20
+ /**
21
+ * Whether the tool writes into an already-created document, as opposed to
22
+ * the create that opened it.
23
+ */
24
+ readonly edit: boolean;
25
+ }
26
+
27
+ export const DOCUMENT_MUTATION_TOOLS: readonly DocumentMutationTool[] = [
28
+ { name: "document_create", reopenable: true, edit: false },
29
+ { name: "document_update", reopenable: true, edit: true },
30
+ { name: "document_replace_text", reopenable: true, edit: true },
31
+ { name: "document_delete", reopenable: false, edit: false },
32
+ ];
33
+
34
+ /**
35
+ * Every tool that changes the document list. The daemon hooks these to publish
36
+ * a documents-changed broadcast, because they carry no list-level change signal
37
+ * of their own and client asset lists would otherwise keep serving a cached
38
+ * list after an edit and a deleted document would linger as a ghost row.
39
+ */
40
+ export const DOCUMENT_MUTATION_TOOL_NAMES: readonly string[] =
41
+ DOCUMENT_MUTATION_TOOLS.map((tool) => tool.name);
42
+
43
+ /**
44
+ * The mutating tools that leave the document openable. The web transcript
45
+ * anchors its changed-document chips on these, so a delete never produces a
46
+ * chip pointing at a document that is gone.
47
+ */
48
+ export const REOPENABLE_DOCUMENT_MUTATION_TOOL_NAMES: readonly string[] =
49
+ DOCUMENT_MUTATION_TOOLS.filter((tool) => tool.reopenable).map(
50
+ (tool) => tool.name,
51
+ );
52
+
53
+ /** The mutating tools that write into an already-created document. */
54
+ export const DOCUMENT_EDIT_TOOL_NAMES: readonly string[] =
55
+ DOCUMENT_MUTATION_TOOLS.filter((tool) => tool.edit).map((tool) => tool.name);
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Durable record of an `ask_question` prompt the user has answered.
3
+ *
4
+ * The pending counterpart (`question_request` / `PendingToolQuestion`) is live
5
+ * registry state: it exists only while the prompt is outstanding and vanishes
6
+ * the moment the interaction resolves. This record is the opposite. It is
7
+ * produced by the `ask_question` executor once the prompt settles, rides the
8
+ * `tool_result` event, and is persisted on the tool_use block, so the answered
9
+ * question and the user's choice stay readable in the transcript across
10
+ * conversation switches, reloads, and history reopens.
11
+ *
12
+ * `questions` mirrors the `question_request` event's `questions[]` (same
13
+ * daemon-assigned `q1`, `q2`, ... ids) so the answered card renders from the
14
+ * same shape the interactive card did. `responses` is ordered to match, one
15
+ * entry per question.
16
+ *
17
+ * Only user-driven outcomes are recorded: a completed batch, or a card the
18
+ * user closed without answering (every question `skipped`). A prompt that
19
+ * timed out or was aborted produced no user decision, so it carries no record
20
+ * and the tool's own error result stands on its own.
21
+ *
22
+ * Canonical wire-contract source. Daemon code imports the type directly from
23
+ * this file; external consumers import via `@vellumai/assistant-api`.
24
+ */
25
+
26
+ import { z } from "zod";
27
+
28
+ import { QuestionEntrySchema } from "./question-request.js";
29
+
30
+ export const AnsweredQuestionResponseSchema = z.object({
31
+ /** Daemon-assigned question id (`q1`, `q2`, ...) this answer belongs to. */
32
+ questionId: z.string(),
33
+ decision: z.enum(["option", "free_text", "skipped"]),
34
+ /** Chosen option id. Present only when `decision` is `"option"`. */
35
+ optionId: z.string().optional(),
36
+ /** The user's typed answer. Present only when `decision` is `"free_text"`. */
37
+ text: z.string().optional(),
38
+ });
39
+
40
+ export type AnsweredQuestionResponse = z.infer<
41
+ typeof AnsweredQuestionResponseSchema
42
+ >;
43
+
44
+ export const AnsweredQuestionSchema = z.object({
45
+ /** The resolved interaction's request id, matching the `question_request` that raised it. */
46
+ requestId: z.string(),
47
+ questions: z.array(QuestionEntrySchema),
48
+ responses: z.array(AnsweredQuestionResponseSchema),
49
+ /** `"completed"` when the user submitted a batch, `"closed"` when they dismissed the card. */
50
+ overall: z.enum(["completed", "closed"]),
51
+ });
52
+
53
+ export type AnsweredQuestion = z.infer<typeof AnsweredQuestionSchema>;
@@ -34,6 +34,7 @@ import {
34
34
  ConfirmationDiffSchema,
35
35
  DirectoryScopeOptionSchema,
36
36
  } from "./confirmation-request.js";
37
+ import { AnsweredQuestionSchema } from "./question-answered.js";
37
38
 
38
39
  export const RiskScopeOptionSchema = z.object({
39
40
  pattern: z.string(),
@@ -131,6 +132,12 @@ export const ToolResultEventSchema = z.object({
131
132
  approvalReason: z.string().optional(),
132
133
  riskThreshold: z.string().optional(),
133
134
  activityMetadata: ToolActivityMetadataSchema.optional(),
135
+ /**
136
+ * Set only by `ask_question`: the questions asked and the answers the user
137
+ * gave. Carried here so the answered card renders the instant the prompt
138
+ * resolves, from the same record the daemon persists on the tool_use block.
139
+ */
140
+ answeredQuestion: AnsweredQuestionSchema.optional(),
134
141
  /**
135
142
  * Stable, machine-readable classification for an error result (only set when
136
143
  * `isError`). Lets a client branch on a known failure — e.g.
package/src/api/index.ts CHANGED
@@ -137,6 +137,13 @@ export {
137
137
  CALL_SITE_COMPACTION_AGENT,
138
138
  CALL_SITE_SYNTHETIC_AGENT_ERROR_MESSAGE,
139
139
  } from "./constants/call-sites.js";
140
+ export {
141
+ DOCUMENT_EDIT_TOOL_NAMES,
142
+ DOCUMENT_MUTATION_TOOL_NAMES,
143
+ DOCUMENT_MUTATION_TOOLS,
144
+ type DocumentMutationTool,
145
+ REOPENABLE_DOCUMENT_MUTATION_TOOL_NAMES,
146
+ } from "./constants/document-tools.js";
140
147
  export {
141
148
  SSE_REPLAY_RING_AGE_LIMIT_MS,
142
149
  SSE_REPLAY_RING_COUNT_LIMIT,
@@ -493,6 +500,12 @@ export {
493
500
  type PlatformDisconnectedEvent,
494
501
  PlatformDisconnectedEventSchema,
495
502
  } from "./events/platform-disconnected.js";
503
+ export {
504
+ type AnsweredQuestion,
505
+ type AnsweredQuestionResponse,
506
+ AnsweredQuestionResponseSchema,
507
+ AnsweredQuestionSchema,
508
+ } from "./events/question-answered.js";
496
509
  export {
497
510
  type QuestionEntry,
498
511
  QuestionEntrySchema,
@@ -29,6 +29,7 @@ import {
29
29
  DirectoryScopeOptionSchema,
30
30
  ScopeOptionSchema,
31
31
  } from "../events/confirmation-request.js";
32
+ import { AnsweredQuestionSchema } from "../events/question-answered.js";
32
33
  import { QuestionEntrySchema } from "../events/question-request.js";
33
34
  import { ToolActivityMetadataSchema } from "../events/tool-result.js";
34
35
 
@@ -242,6 +243,16 @@ export const ConversationMessageToolCallSchema = z.object({
242
243
  * render time). Lets a cold reconnect restore the inline question card.
243
244
  */
244
245
  pendingQuestion: PendingToolQuestionSchema.optional(),
246
+ /**
247
+ * The answered counterpart to `pendingQuestion`: the questions an
248
+ * `ask_question` call asked and the answers the user gave. Persisted on the
249
+ * tool_use block rather than stamped from the live registry, so it survives a
250
+ * conversation switch, a reload, and a history reopen. A tool call carries at
251
+ * most one of the two: the pending prompt while it is outstanding, the
252
+ * answered record once it settles. Absent on history rows written before the
253
+ * daemon recorded it.
254
+ */
255
+ answeredQuestion: AnsweredQuestionSchema.optional(),
245
256
  });
246
257
  export type ConversationMessageToolCall = z.infer<
247
258
  typeof ConversationMessageToolCallSchema
@@ -0,0 +1,96 @@
1
+ import { describe, expect, test } from "bun:test";
2
+
3
+ import {
4
+ type OAuthConnectSurfaceData,
5
+ OAuthConnectSurfaceDataSchema,
6
+ } from "./surfaces.js";
7
+
8
+ describe("OAuthConnectSurfaceDataSchema requestedScopes", () => {
9
+ test("parses a valid scopes array and preserves order", () => {
10
+ const parsed = OAuthConnectSurfaceDataSchema.parse({
11
+ providerKey: "google",
12
+ requestedScopes: [
13
+ "https://www.googleapis.com/auth/tasks",
14
+ "https://www.googleapis.com/auth/calendar",
15
+ "openid",
16
+ ],
17
+ });
18
+ expect(parsed.requestedScopes).toEqual([
19
+ "https://www.googleapis.com/auth/tasks",
20
+ "https://www.googleapis.com/auth/calendar",
21
+ "openid",
22
+ ]);
23
+ });
24
+
25
+ test("absent requestedScopes parses to undefined", () => {
26
+ const parsed = OAuthConnectSurfaceDataSchema.parse({
27
+ providerKey: "google",
28
+ });
29
+ expect(parsed.requestedScopes).toBeUndefined();
30
+ });
31
+
32
+ test("an empty array collapses to undefined", () => {
33
+ const parsed = OAuthConnectSurfaceDataSchema.parse({
34
+ providerKey: "google",
35
+ requestedScopes: [],
36
+ });
37
+ expect(parsed.requestedScopes).toBeUndefined();
38
+ });
39
+
40
+ test("non-array values collapse to undefined instead of rejecting", () => {
41
+ for (const value of [
42
+ "https://www.googleapis.com/auth/tasks, openid",
43
+ 42,
44
+ true,
45
+ { scope: "openid" },
46
+ null,
47
+ ]) {
48
+ const parsed = OAuthConnectSurfaceDataSchema.parse({
49
+ providerKey: "google",
50
+ requestedScopes: value,
51
+ });
52
+ expect(parsed.requestedScopes).toBeUndefined();
53
+ }
54
+ });
55
+
56
+ test("an array of blanks collapses to undefined", () => {
57
+ const parsed = OAuthConnectSurfaceDataSchema.parse({
58
+ providerKey: "google",
59
+ requestedScopes: ["", " ", "\t"],
60
+ });
61
+ expect(parsed.requestedScopes).toBeUndefined();
62
+ });
63
+
64
+ test("entries are trimmed and blank or non-string junk is dropped", () => {
65
+ const parsed = OAuthConnectSurfaceDataSchema.parse({
66
+ providerKey: "google",
67
+ requestedScopes: [
68
+ " https://www.googleapis.com/auth/tasks ",
69
+ "",
70
+ null,
71
+ { nope: true },
72
+ "openid",
73
+ ],
74
+ });
75
+ expect(parsed.requestedScopes).toEqual([
76
+ "https://www.googleapis.com/auth/tasks",
77
+ "openid",
78
+ ]);
79
+ });
80
+
81
+ test("a bare string is not split on commas (scopes are opaque URIs)", () => {
82
+ const parsed = OAuthConnectSurfaceDataSchema.parse({
83
+ providerKey: "google",
84
+ requestedScopes: "a,b,c",
85
+ });
86
+ expect(parsed.requestedScopes).toBeUndefined();
87
+ });
88
+
89
+ test("legacy payload { providerKey: 'google' } still parses (skew guard)", () => {
90
+ const parsed: OAuthConnectSurfaceData = OAuthConnectSurfaceDataSchema.parse(
91
+ { providerKey: "google" },
92
+ );
93
+ expect(parsed.providerKey).toBe("google");
94
+ expect(parsed.requestedScopes).toBeUndefined();
95
+ });
96
+ });
@@ -120,6 +120,21 @@ export const CardSurfaceDataSchema = z.object({
120
120
  });
121
121
  export type CardSurfaceData = z.infer<typeof CardSurfaceDataSchema>;
122
122
 
123
+ /**
124
+ * Stringify+trim entries, drop blanks and non-scalars, and collapse an
125
+ * empty result to `undefined` so a schema treats it as "field absent".
126
+ */
127
+ function cleanedStringEntries(items: unknown[]): string[] | undefined {
128
+ const cleaned = items
129
+ .map((item) =>
130
+ typeof item === "string" || typeof item === "number"
131
+ ? String(item).trim()
132
+ : "",
133
+ )
134
+ .filter((item) => item.length > 0);
135
+ return cleaned.length > 0 ? cleaned : undefined;
136
+ }
137
+
123
138
  /**
124
139
  * Accepted MIME-type / extension patterns for a `file_upload` surface.
125
140
  *
@@ -137,14 +152,7 @@ const FileUploadAcceptedTypesSchema = z.preprocess((value) => {
137
152
  : Array.isArray(value)
138
153
  ? value
139
154
  : [];
140
- const cleaned = items
141
- .map((item) =>
142
- typeof item === "string" || typeof item === "number"
143
- ? String(item).trim()
144
- : "",
145
- )
146
- .filter((item) => item.length > 0);
147
- return cleaned.length > 0 ? cleaned : undefined;
155
+ return cleanedStringEntries(items);
148
156
  }, z.array(z.string()).optional());
149
157
 
150
158
  export const FileUploadSurfaceDataSchema = z.object({
@@ -238,6 +246,19 @@ export const ChoiceSurfaceDataSchema = z.object({
238
246
  });
239
247
  export type ChoiceSurfaceData = z.infer<typeof ChoiceSurfaceDataSchema>;
240
248
 
249
+ /**
250
+ * Requested OAuth scopes for an `oauth_connect` surface.
251
+ *
252
+ * Unlike `FileUploadAcceptedTypesSchema`, a bare string is NOT split on
253
+ * commas: OAuth scopes are opaque URIs, so only arrays are accepted.
254
+ * Entries are stringified and trimmed, blanks are dropped, and an empty
255
+ * result or any non-array value collapses to `undefined` (use defaults).
256
+ */
257
+ const RequestedScopesSchema = z.preprocess(
258
+ (value) => (Array.isArray(value) ? cleanedStringEntries(value) : undefined),
259
+ z.array(z.string()).optional(),
260
+ );
261
+
241
262
  export const OAuthConnectSurfaceDataSchema = z.object({
242
263
  /** OAuth provider key from the managed provider catalog, e.g. "google". */
243
264
  providerKey: z
@@ -246,6 +267,13 @@ export const OAuthConnectSurfaceDataSchema = z.object({
246
267
  z.string(),
247
268
  )
248
269
  .catch(""),
270
+ /**
271
+ * Optional OAuth scopes to request for the managed connection. A full
272
+ * replacement set: when present, the platform uses exactly these scopes
273
+ * instead of its defaults (it does not merge). Omit to use the platform's
274
+ * default scopes for the provider.
275
+ */
276
+ requestedScopes: RequestedScopesSchema,
249
277
  /** Optional display label. The client falls back to the provider catalog. */
250
278
  displayName: tolerantString(),
251
279
  /** Optional helper text. The client falls back to the provider catalog. */
@@ -15,6 +15,11 @@
15
15
  * guardian decided off-channel (desktop), or the expiry sweep fired on a timer
16
16
  * with no originating request in hand. Returns null for channels that have no
17
17
  * deliverable route (e.g. email, the in-app vellum surface).
18
+ *
19
+ * Discord carries `dm=1`, which tells its transport that the `chatId` it is
20
+ * handed names a person to open a DM with rather than a channel to post in.
21
+ * Pair it with {@link resolveRequesterDeliveryTarget}, which supplies that
22
+ * person.
18
23
  */
19
24
  export function resolveDeliverCallbackUrlForChannel(
20
25
  channel: string,
@@ -24,7 +29,85 @@ export function resolveDeliverCallbackUrlForChannel(
24
29
  case "whatsapp":
25
30
  case "slack":
26
31
  return `/deliver/${channel}`;
32
+ case "discord":
33
+ return "/deliver/discord?dm=1";
27
34
  default:
28
35
  return null;
29
36
  }
30
37
  }
38
+
39
+ /**
40
+ * Whether a message posted back in-band on this channel can be kept to one
41
+ * reader.
42
+ *
43
+ * Slack can, with `chat.postEphemeral`. Discord cannot: it has no ephemeral
44
+ * message outside an interaction response, so anything posted into the room a
45
+ * guardian replied in, or the room a request came from, is readable by every
46
+ * member of the server. That applies to a decision notice and to a
47
+ * verification code alike, which is why the callers treat an in-band reply
48
+ * context as unusable for Discord and fall through to the DM route instead.
49
+ *
50
+ * Telegram and WhatsApp are trivially true: their conversation already has one
51
+ * reader.
52
+ */
53
+ export function channelCanAddressOneReaderInBand(channel: string): boolean {
54
+ return channel !== "discord";
55
+ }
56
+
57
+ /**
58
+ * Whether a notice can be addressed to one person by their user id rather than
59
+ * to the conversation the request arrived in.
60
+ *
61
+ * Slack opens a 1:1 DM when a `U…` id is posted as the channel. Discord
62
+ * resolves a user snowflake to a DM channel on its `dm`-marked route. This is
63
+ * an OUTBOUND question only: it says a message can be put in front of one
64
+ * person, and nothing about whether they can answer it.
65
+ */
66
+ export function channelDeliversToUserId(channel: string): boolean {
67
+ return channel === "slack" || channel === "discord";
68
+ }
69
+
70
+ /**
71
+ * Whether a verification code can be sent straight to the requester, rather
72
+ * than handed to the guardian to relay out of band.
73
+ *
74
+ * Deliberately NOT the same set as {@link channelDeliversToUserId}, though it
75
+ * looks like it. That one asks whether a message reaches one person; this asks
76
+ * whether a code handshake can COMPLETE there, which additionally needs the
77
+ * reply to be heard. The copy this gates says "reply with it here", so a
78
+ * channel that can send into a DM but not receive from one would strand the
79
+ * requester holding a code they can never spend.
80
+ *
81
+ * Discord qualifies on both halves: its `dm`-marked route reaches the person,
82
+ * and `gateway/src/discord/admit.ts` admits DMs so the reply is heard.
83
+ */
84
+ export function channelCanCompleteCodeHandshakeInDm(channel: string): boolean {
85
+ return channel === "slack" || channel === "discord";
86
+ }
87
+
88
+ /**
89
+ * Resolve who a requester notice is addressed to on the callback-less route.
90
+ *
91
+ * A request's `requesterChatId` is wherever the request came from, and where
92
+ * the channel has a private route to a user id that room is one other people
93
+ * can read. "Your request was denied" posted into a community channel is worse
94
+ * than not sending it, so those channels address the requester's own user id
95
+ * instead and let their transport turn it into a DM.
96
+ *
97
+ * Telegram and WhatsApp fall through to the chat id because theirs already is
98
+ * the private one-to-one conversation. So does a request with no actor
99
+ * identity: there is nobody to open a DM with, and on the channels that need
100
+ * one the transport reports that as a delivery failure rather than posting to
101
+ * the room.
102
+ */
103
+ export function resolveRequesterDeliveryTarget(params: {
104
+ channel: string;
105
+ requesterChatId: string;
106
+ requesterExternalUserId: string;
107
+ }): string {
108
+ const { channel, requesterChatId, requesterExternalUserId } = params;
109
+ if (channelDeliversToUserId(channel) && requesterExternalUserId) {
110
+ return requesterExternalUserId;
111
+ }
112
+ return requesterChatId;
113
+ }