@vellumai/assistant 0.11.6 → 0.11.7-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 (447) hide show
  1. package/ARCHITECTURE.md +31 -19
  2. package/Dockerfile +1 -0
  3. package/docs/architecture/security.md +3 -3
  4. package/docs/flux-turn-detection-spike.md +8 -7
  5. package/docs/runbook-trusted-contacts.md +14 -0
  6. package/docs/stt-provider-onboarding.md +8 -4
  7. package/knip.json +1 -0
  8. package/node_modules/@vellumai/avatar-manifest/package.json +18 -0
  9. package/node_modules/@vellumai/avatar-manifest/src/__tests__/manifest.test.ts +173 -0
  10. package/node_modules/@vellumai/avatar-manifest/src/__tests__/read.test.ts +114 -0
  11. package/node_modules/@vellumai/avatar-manifest/src/index.ts +29 -0
  12. package/node_modules/@vellumai/avatar-manifest/src/layout.ts +18 -0
  13. package/node_modules/@vellumai/avatar-manifest/src/manifest.ts +156 -0
  14. package/node_modules/@vellumai/avatar-manifest/src/read.ts +109 -0
  15. package/node_modules/@vellumai/avatar-manifest/tsconfig.json +20 -0
  16. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/__tests__/ingress.test.ts +37 -0
  17. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/__tests__/remote-web-pairing.test.ts +389 -0
  18. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/ingress.ts +22 -0
  19. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +508 -18
  20. package/node_modules/@vellumai/environments/package.json +2 -1
  21. package/node_modules/@vellumai/environments/src/shell.test.ts +56 -0
  22. package/node_modules/@vellumai/environments/src/shell.ts +65 -0
  23. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/ingress.test.ts +37 -0
  24. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/remote-web-pairing.test.ts +389 -0
  25. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/ingress.ts +22 -0
  26. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +508 -18
  27. package/node_modules/@vellumai/gateway-client/src/__tests__/contact-read-contracts.test.ts +32 -0
  28. package/node_modules/@vellumai/gateway-client/src/__tests__/gateway-client.test.ts +1 -0
  29. package/node_modules/@vellumai/gateway-client/src/__tests__/inbound-event-kind.test.ts +82 -0
  30. package/node_modules/@vellumai/gateway-client/src/__tests__/plugin-admission-denied-contract.test.ts +56 -0
  31. package/node_modules/@vellumai/gateway-client/src/__tests__/trust-verdict-contract.test.ts +18 -0
  32. package/node_modules/@vellumai/gateway-client/src/admission-policy-contract.ts +5 -11
  33. package/node_modules/@vellumai/gateway-client/src/channel-permission-contract.ts +3 -1
  34. package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +9 -0
  35. package/node_modules/@vellumai/gateway-client/src/inbound-contract.ts +4 -0
  36. package/node_modules/@vellumai/gateway-client/src/inbound-event-kind.ts +75 -0
  37. package/node_modules/@vellumai/gateway-client/src/index.ts +17 -0
  38. package/node_modules/@vellumai/gateway-client/src/outbound-contract.ts +8 -0
  39. package/node_modules/@vellumai/gateway-client/src/plugin-admission-denied-contract.ts +47 -0
  40. package/node_modules/@vellumai/gateway-client/src/trust-verdict-contract.ts +11 -0
  41. package/node_modules/@vellumai/service-contracts/src/__tests__/ingress.test.ts +37 -0
  42. package/node_modules/@vellumai/service-contracts/src/__tests__/remote-web-pairing.test.ts +389 -0
  43. package/node_modules/@vellumai/service-contracts/src/ingress.ts +22 -0
  44. package/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +508 -18
  45. package/openapi.yaml +419 -55
  46. package/package.json +4 -1
  47. package/src/__tests__/approval-card-edit-inbound-handoff.test.ts +199 -0
  48. package/src/__tests__/approval-card-edit.test.ts +153 -0
  49. package/src/__tests__/attachments-store-heic-normalize.test.ts +1 -1
  50. package/src/__tests__/available-skills.test.ts +54 -0
  51. package/src/__tests__/avatar-identity-sync.test.ts +5 -1
  52. package/src/__tests__/catalog-install-normalize.test.ts +9 -1
  53. package/src/__tests__/channel-policy.test.ts +1 -3
  54. package/src/__tests__/channel-readiness-discord.test.ts +9 -70
  55. package/src/__tests__/compactor-call-site-logging.test.ts +61 -0
  56. package/src/__tests__/config-loader-backfill.test.ts +69 -0
  57. package/src/__tests__/config-schema.test.ts +2 -3
  58. package/src/__tests__/conversation-agent-loop.test.ts +238 -0
  59. package/src/__tests__/conversation-attachments.test.ts +0 -1
  60. package/src/__tests__/conversation-error.test.ts +44 -0
  61. package/src/__tests__/conversation-evictor.test.ts +19 -0
  62. package/src/__tests__/conversation-fork-referential.test.ts +73 -63
  63. package/src/__tests__/conversation-fork-retrospective.test.ts +99 -74
  64. package/src/__tests__/conversation-runtime-assembly.test.ts +23 -0
  65. package/src/__tests__/conversation-slash.test.ts +9 -0
  66. package/src/__tests__/conversation-title-service.test.ts +97 -0
  67. package/src/__tests__/conversation-tool-setup-attribution.test.ts +16 -0
  68. package/src/__tests__/credential-broker-server-use.test.ts +0 -218
  69. package/src/__tests__/credential-broker.test.ts +43 -420
  70. package/src/__tests__/credential-routes.test.ts +17 -1
  71. package/src/__tests__/credential-security-invariants.test.ts +8 -9
  72. package/src/__tests__/default-profile-catalog-fallback.test.ts +289 -0
  73. package/src/__tests__/discord-callback-round-trip.test.ts +93 -0
  74. package/src/__tests__/discord-channel-config.test.ts +237 -0
  75. package/src/__tests__/evict-conversations-for-reload.test.ts +51 -1
  76. package/src/__tests__/fallback-breaker.test.ts +1335 -0
  77. package/src/__tests__/gateway-threshold-reader-mock.ts +6 -0
  78. package/src/__tests__/guardian-card-withdrawal.test.ts +30 -0
  79. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +0 -2
  80. package/src/__tests__/image-conversion.test.ts +38 -4
  81. package/src/__tests__/install-skill-routing.test.ts +25 -0
  82. package/src/__tests__/llm-resolver.test.ts +74 -0
  83. package/src/__tests__/llm-schema-fallback-profile.test.ts +359 -0
  84. package/src/__tests__/managed-fallback-dispatch.test.ts +430 -0
  85. package/src/__tests__/managed-profile-guard.test.ts +28 -0
  86. package/src/__tests__/managed-speech-defaults.test.ts +196 -1
  87. package/src/__tests__/mcp-cli.test.ts +23 -1
  88. package/src/__tests__/mcp-tool-annotations-risk.test.ts +43 -34
  89. package/src/__tests__/media-stream-stt-session.test.ts +2 -0
  90. package/src/__tests__/notification-decision-fallback.test.ts +106 -0
  91. package/src/__tests__/notification-discord-adapter.test.ts +162 -0
  92. package/src/__tests__/persist-unsendable-image-downscale.test.ts +6 -8
  93. package/src/__tests__/persist-unsendable-image.test.ts +3 -3
  94. package/src/__tests__/platform.test.ts +19 -1
  95. package/src/__tests__/plugin-import-boundary-guard.test.ts +5 -1
  96. package/src/__tests__/provider-usage-tracking.test.ts +56 -0
  97. package/src/__tests__/require-fresh-approval.test.ts +5 -2
  98. package/src/__tests__/retry-fallback-escalation.test.ts +1093 -0
  99. package/src/__tests__/schedule-retry.test.ts +183 -4
  100. package/src/__tests__/scheduler-reuse-conversation.test.ts +6 -6
  101. package/src/__tests__/secret-routes-platform-proxy.test.ts +55 -0
  102. package/src/__tests__/skill-load-tool.test.ts +34 -0
  103. package/src/__tests__/skill-tool-factory.test.ts +6 -1
  104. package/src/__tests__/skill-tool-manifest.test.ts +20 -0
  105. package/src/__tests__/skills.test.ts +25 -0
  106. package/src/__tests__/subagent-manager-notify.test.ts +38 -0
  107. package/src/__tests__/terminal-tools.test.ts +2 -0
  108. package/src/__tests__/tool-approval-handler.test.ts +146 -2
  109. package/src/__tests__/tool-approval-seed-content-blocks.test.ts +108 -2
  110. package/src/__tests__/tool-audit.test.ts +111 -40
  111. package/src/__tests__/tool-executor-lifecycle-events.test.ts +228 -18
  112. package/src/__tests__/ui-shape-teaching.test.ts +1 -1
  113. package/src/__tests__/verification-control-plane-policy.test.ts +6 -2
  114. package/src/__tests__/voice-config-update.test.ts +43 -0
  115. package/src/__tests__/workspace-migration-147-rename-colliding-backup-profile-names.test.ts +480 -0
  116. package/src/__tests__/workspace-migration-148-strip-unsupported-fallback-profiles.test.ts +167 -0
  117. package/src/__tests__/workspace-migration-149-repoint-backup-profile-selections.test.ts +144 -0
  118. package/src/agent/loop.ts +31 -2
  119. package/src/approvals/guardian-card-withdrawal.ts +22 -0
  120. package/src/avatar/__tests__/ensure-raster.test.ts +291 -0
  121. package/src/avatar/avatar-manifest.ts +48 -100
  122. package/src/avatar/avatar-store.ts +7 -7
  123. package/src/avatar/ensure-raster.ts +146 -0
  124. package/src/avatar/resvg-lazy.test.ts +0 -1
  125. package/src/avatar/traits-png-sync.ts +9 -10
  126. package/src/background-wake/wake-intent-hooks.test.ts +0 -1
  127. package/src/backup/__tests__/paths.test.ts +6 -2
  128. package/src/backup/paths.ts +6 -4
  129. package/src/bundler/app-compiler.ts +2 -2
  130. package/src/bundler/compiler-tools.test.ts +36 -0
  131. package/src/bundler/compiler-tools.ts +54 -18
  132. package/src/calls/__tests__/telephony-synthesis-language.test.ts +81 -0
  133. package/src/calls/__tests__/voice-session-bridge.test.ts +20 -0
  134. package/src/calls/media-stream-stt-session.ts +2 -1
  135. package/src/calls/telephony-synthesis-language.ts +13 -5
  136. package/src/calls/voice-session-bridge.ts +6 -4
  137. package/src/channels/config.ts +23 -12
  138. package/src/cli/AGENTS.md +3 -1
  139. package/src/cli/commands/backup.help.ts +10 -13
  140. package/src/cli/commands/contacts.help.ts +16 -4
  141. package/src/cli/commands/contacts.ts +5 -1
  142. package/src/cli/commands/credentials.help.ts +1 -1
  143. package/src/cli/commands/inference-providers.ts +18 -53
  144. package/src/cli/commands/mcp.help.ts +6 -5
  145. package/src/cli/commands/mcp.ts +1 -1
  146. package/src/cli/commands/memory/memory-retrospective.ts +1 -0
  147. package/src/cli/commands/stt.help.ts +2 -2
  148. package/src/config/__tests__/backup-schema.test.ts +1 -1
  149. package/src/config/__tests__/default-profile-catalog.test.ts +83 -2
  150. package/src/config/__tests__/loader-sparse-array-cleanup.test.ts +1 -1
  151. package/src/config/__tests__/memory-retrospective-schema.test.ts +12 -15
  152. package/src/config/bundled-skills/computer-use/SKILL.md +6 -6
  153. package/src/config/bundled-skills/computer-use/TOOLS.json +9 -6
  154. package/src/config/bundled-skills/media-processing/services/audio-transcribe.ts +3 -1
  155. package/src/config/bundled-skills/media-processing/services/preprocess.ts +1 -1
  156. package/src/config/bundled-skills/phone-calls/SKILL.md +1 -1
  157. package/src/config/bundled-skills/phone-calls/references/CONFIG.md +16 -15
  158. package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +2 -1
  159. package/src/config/bundled-skills/settings/TOOLS.json +11 -5
  160. package/src/config/bundled-skills/settings/tools/open-system-settings.test.ts +85 -0
  161. package/src/config/bundled-skills/settings/tools/open-system-settings.ts +33 -8
  162. package/src/config/bundled-skills/settings/tools/voice-config-update.ts +54 -3
  163. package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +1 -1
  164. package/src/config/default-profile-catalog.ts +195 -10
  165. package/src/config/default-profile-names.ts +41 -0
  166. package/src/config/feature-flag-registry.json +18 -2
  167. package/src/config/inference-profile-validation.ts +11 -2
  168. package/src/config/llm-resolver.ts +25 -0
  169. package/src/config/loader.ts +3 -0
  170. package/src/config/managed-speech-defaults.ts +160 -10
  171. package/src/config/schema.ts +2 -0
  172. package/src/config/schemas/__tests__/live-voice.test.ts +7 -6
  173. package/src/config/schemas/__tests__/stt.test.ts +94 -0
  174. package/src/config/schemas/backup.ts +1 -1
  175. package/src/config/schemas/channels.ts +32 -0
  176. package/src/config/schemas/live-voice.ts +13 -4
  177. package/src/config/schemas/llm.ts +259 -11
  178. package/src/config/schemas/mcp.ts +10 -2
  179. package/src/config/schemas/memory-retrospective.ts +1 -19
  180. package/src/config/schemas/services.ts +24 -0
  181. package/src/config/schemas/stt.ts +128 -5
  182. package/src/config/seed-inference-profiles.ts +32 -3
  183. package/src/config/skill-state.ts +5 -0
  184. package/src/config/skills.ts +21 -0
  185. package/src/contacts/notify-contacts-changed.ts +5 -2
  186. package/src/context/compactor.ts +74 -1
  187. package/src/daemon/__tests__/conversation-tool-setup.test.ts +138 -1
  188. package/src/daemon/config-watcher.ts +2 -1
  189. package/src/daemon/conversation-agent-loop-handlers.ts +27 -0
  190. package/src/daemon/conversation-agent-loop.ts +86 -10
  191. package/src/daemon/conversation-error.ts +25 -0
  192. package/src/daemon/conversation-evictor.ts +2 -4
  193. package/src/daemon/conversation-runtime-assembly.ts +19 -7
  194. package/src/daemon/conversation-skill-tools.ts +5 -1
  195. package/src/daemon/conversation-slash.ts +2 -2
  196. package/src/daemon/conversation-store.ts +8 -1
  197. package/src/daemon/conversation-tool-setup.ts +37 -0
  198. package/src/daemon/conversation.ts +0 -4
  199. package/src/daemon/handlers/channel-config-result.ts +30 -0
  200. package/src/daemon/handlers/config-discord-channel.ts +301 -0
  201. package/src/daemon/handlers/config-ingress.ts +9 -0
  202. package/src/daemon/handlers/config-slack-channel.ts +13 -15
  203. package/src/daemon/handlers/config-telegram.ts +2 -5
  204. package/src/daemon/handlers/conversations.ts +0 -2
  205. package/src/daemon/handlers/skills.ts +25 -7
  206. package/src/daemon/lifecycle.ts +3 -1
  207. package/src/daemon/message-types/messages.ts +0 -2
  208. package/src/daemon/tool-setup-types.ts +5 -3
  209. package/src/daemon/trust-context-types.test.ts +33 -0
  210. package/src/heartbeat/__tests__/heartbeat-service.test.ts +0 -1
  211. package/src/live-voice/__tests__/live-voice-flux-turn-end.test.ts +58 -4
  212. package/src/live-voice/__tests__/live-voice-managed-speech-fallback.test.ts +8 -3
  213. package/src/live-voice/__tests__/live-voice-session-manager.test.ts +73 -11
  214. package/src/live-voice/__tests__/live-voice-session-preflight.test.ts +148 -2
  215. package/src/live-voice/__tests__/live-voice-stt.test.ts +3 -0
  216. package/src/live-voice/__tests__/live-voice-text-turn.test.ts +391 -0
  217. package/src/live-voice/__tests__/protocol.test.ts +224 -0
  218. package/src/live-voice/live-voice-credential-preflight.ts +5 -1
  219. package/src/live-voice/live-voice-session-manager.ts +39 -2
  220. package/src/live-voice/live-voice-session.ts +213 -8
  221. package/src/live-voice/protocol.ts +187 -1
  222. package/src/messaging/providers/discord/api.ts +2 -1
  223. package/src/messaging/providers/discord/withdraw.ts +62 -0
  224. package/src/monitoring/__tests__/file-descriptors.test.ts +32 -0
  225. package/src/monitoring/__tests__/process-memory.test.ts +34 -1
  226. package/src/monitoring/db-integrity-sample.ts +1 -0
  227. package/src/monitoring/file-descriptors.ts +39 -6
  228. package/src/monitoring/process-memory.ts +37 -5
  229. package/src/monitoring/resource-sampler.ts +25 -8
  230. package/src/notifications/__tests__/activity-failed-dedupe.test.ts +161 -0
  231. package/src/notifications/__tests__/background-failure-signal.test.ts +107 -0
  232. package/src/notifications/__tests__/connected-channels.test.ts +12 -0
  233. package/src/notifications/__tests__/copy-composer.test.ts +83 -0
  234. package/src/notifications/__tests__/destination-resolver.test.ts +38 -0
  235. package/src/notifications/activity-failed-dedupe.ts +95 -0
  236. package/src/notifications/adapters/discord.ts +132 -0
  237. package/src/notifications/adapters/shared.ts +16 -0
  238. package/src/notifications/adapters/slack.ts +6 -1
  239. package/src/notifications/adapters/telegram.ts +5 -8
  240. package/src/notifications/approval-card-data.ts +94 -1
  241. package/src/notifications/background-failure-signal.ts +116 -0
  242. package/src/notifications/broadcaster.ts +3 -9
  243. package/src/notifications/copy-composer.ts +41 -8
  244. package/src/notifications/decision-engine.ts +55 -0
  245. package/src/notifications/destination-resolver.ts +45 -5
  246. package/src/notifications/emit-signal.ts +19 -5
  247. package/src/notifications/guardian-question-mode.ts +57 -0
  248. package/src/notifications/types.ts +2 -8
  249. package/src/oauth/__tests__/seed-providers-managed.test.ts +18 -0
  250. package/src/oauth/seed-providers.ts +261 -0
  251. package/src/permissions/checker.test.ts +21 -0
  252. package/src/permissions/checker.ts +6 -0
  253. package/src/permissions/gateway-threshold-reader.test.ts +184 -0
  254. package/src/permissions/gateway-threshold-reader.ts +144 -10
  255. package/src/permissions/prompter.ts +0 -6
  256. package/src/permissions/types.ts +5 -4
  257. package/src/persistence/__tests__/bulk-write-gate.test.ts +0 -38
  258. package/src/persistence/bulk-write-gate.ts +9 -10
  259. package/src/persistence/conversation-crud.ts +85 -133
  260. package/src/persistence/conversation-title-service.ts +43 -29
  261. package/src/persistence/embeddings/embedding-local.ts +14 -85
  262. package/src/persistence/embeddings/embedding-runtime-manager.ts +1 -0
  263. package/src/persistence/lifecycle-events-store.test.ts +50 -0
  264. package/src/persistence/lifecycle-events-store.ts +75 -5
  265. package/src/platform/platform-patch-queue.test.ts +368 -0
  266. package/src/platform/platform-patch-queue.ts +190 -0
  267. package/src/platform/sync-avatar.test.ts +527 -0
  268. package/src/platform/sync-avatar.ts +209 -0
  269. package/src/platform/sync-identity.test.ts +75 -0
  270. package/src/platform/sync-identity.ts +17 -110
  271. package/src/plugin-api/model-profiles.test.ts +2 -2
  272. package/src/plugin-api/model-profiles.ts +7 -7
  273. package/src/plugin-api/transcription-session.ts +1 -1
  274. package/src/plugins/defaults/compaction/window-manager.ts +4 -0
  275. package/src/plugins/defaults/image-recovery/recover.ts +2 -2
  276. package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +133 -34
  277. package/src/plugins/defaults/memory/__tests__/memory-retrospective-prompt.test.ts +21 -31
  278. package/src/plugins/defaults/memory/__tests__/memory-retrospective-provider-path.test.ts +13 -9
  279. package/src/plugins/defaults/memory/__tests__/memory-retrospective-wake-chain.test.ts +31 -4
  280. package/src/plugins/defaults/memory/memory-retrospective-constants.ts +0 -11
  281. package/src/plugins/defaults/memory/memory-retrospective-fork-boundary.ts +27 -13
  282. package/src/plugins/defaults/memory/memory-retrospective-job.ts +183 -113
  283. package/src/plugins/defaults/memory/memory-retrospective-prompt.ts +13 -13
  284. package/src/plugins/defaults/memory/src/memory-v2-routes.ts +2 -2
  285. package/src/plugins/defaults/memory/substrate/__tests__/consolidation-job.test.ts +1 -1
  286. package/src/plugins/defaults/memory/substrate/consolidation-job.ts +4 -2
  287. package/src/plugins/defaults/memory/substrate/sweep-job.ts +9 -42
  288. package/src/plugins/defaults/memory/v2/rerank-local.ts +1 -0
  289. package/src/plugins/defaults/memory/worker.ts +7 -0
  290. package/src/providers/__tests__/retry-callsite.test.ts +55 -0
  291. package/src/providers/connection-resolution.ts +2 -17
  292. package/src/providers/fallback-breaker.ts +389 -0
  293. package/src/providers/inference/__tests__/adapter-factory-opencode.test.ts +92 -0
  294. package/src/providers/inference/__tests__/base-url-route-validation.test.ts +16 -0
  295. package/src/providers/inference/__tests__/base-url-security.test.ts +12 -0
  296. package/src/providers/inference/adapter-factory.ts +231 -3
  297. package/src/providers/inference/auth.ts +1 -0
  298. package/src/providers/inference/chatgpt-subscription-auth.ts +70 -0
  299. package/src/providers/inference/chatgpt-subscription-credentials.ts +84 -0
  300. package/src/providers/inference/resolve-auth.ts +1 -1
  301. package/src/providers/media-resolve.ts +2 -3
  302. package/src/providers/model-catalog.ts +75 -0
  303. package/src/providers/openai/__tests__/api-error-normalization.test.ts +22 -0
  304. package/src/providers/openai/__tests__/chat-template-flatten.test.ts +198 -0
  305. package/src/providers/openai/api-error-normalization.ts +13 -0
  306. package/src/providers/openai/chat-completions-provider.ts +84 -0
  307. package/src/providers/opencode/client.test.ts +150 -0
  308. package/src/providers/opencode/client.ts +61 -0
  309. package/src/providers/provider-resolvability.ts +20 -0
  310. package/src/providers/retry.ts +956 -68
  311. package/src/providers/speech-to-text/__tests__/deepgram-flux-frames.test.ts +42 -0
  312. package/src/providers/speech-to-text/__tests__/deepgram-flux-realtime.test.ts +136 -0
  313. package/src/providers/speech-to-text/__tests__/provider-catalog.test.ts +120 -0
  314. package/src/providers/speech-to-text/__tests__/resolve.test.ts +316 -12
  315. package/src/providers/speech-to-text/deepgram-flux-frames.ts +83 -3
  316. package/src/providers/speech-to-text/deepgram-flux-realtime.ts +132 -4
  317. package/src/providers/speech-to-text/provider-catalog.ts +191 -3
  318. package/src/providers/speech-to-text/resolve.ts +132 -24
  319. package/src/providers/speech-to-text/vellum-managed-flux-realtime.ts +296 -0
  320. package/src/providers/types.ts +39 -0
  321. package/src/providers/usage-tracking.ts +12 -1
  322. package/src/runtime/AGENTS.md +1 -2
  323. package/src/runtime/__tests__/agent-wake.test.ts +105 -3
  324. package/src/runtime/__tests__/background-job-runner.test.ts +89 -3
  325. package/src/runtime/agent-wake.ts +44 -5
  326. package/src/runtime/background-job-runner.ts +68 -50
  327. package/src/runtime/channel-readiness-service.ts +0 -39
  328. package/src/runtime/http-server.ts +12 -4
  329. package/src/runtime/routes/__tests__/avatar-state-routes.test.ts +18 -18
  330. package/src/runtime/routes/__tests__/backup-routes.test.ts +9 -5
  331. package/src/runtime/routes/__tests__/chatgpt-subscription-auth-routes.test.ts +372 -0
  332. package/src/runtime/routes/__tests__/contact-routes.test.ts +7 -0
  333. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +96 -0
  334. package/src/runtime/routes/__tests__/inference-profiles-routes.test.ts +37 -0
  335. package/src/runtime/routes/__tests__/ingress-status-routes.test.ts +2 -1
  336. package/src/runtime/routes/__tests__/mcp-add-default-risk.test.ts +87 -0
  337. package/src/runtime/routes/acp-claude-auth-routes.ts +25 -54
  338. package/src/runtime/routes/approval-routes.ts +0 -12
  339. package/src/runtime/routes/avatar-routes.ts +17 -30
  340. package/src/runtime/routes/backup-routes.ts +2 -2
  341. package/src/runtime/routes/chatgpt-subscription-auth-routes.ts +296 -113
  342. package/src/runtime/routes/contact-prompt-routes.ts +65 -6
  343. package/src/runtime/routes/contact-routes.ts +20 -15
  344. package/src/runtime/routes/conversation-query-routes.ts +98 -10
  345. package/src/runtime/routes/conversation-routes.ts +2 -2
  346. package/src/runtime/routes/credential-routes.ts +2 -1
  347. package/src/runtime/routes/guardian-approval-interception.ts +12 -9
  348. package/src/runtime/routes/inbound-message-handler.ts +21 -9
  349. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +18 -13
  350. package/src/runtime/routes/inbound-stages/admission-policy.ts +2 -2
  351. package/src/runtime/routes/inbound-stages/reaction-intercept.ts +9 -8
  352. package/src/runtime/routes/inbound-stages/transcribe-audio.ts +1 -1
  353. package/src/runtime/routes/index.ts +2 -0
  354. package/src/runtime/routes/inference-profile-session-handler.ts +2 -2
  355. package/src/runtime/routes/inference-profiles-routes.ts +12 -7
  356. package/src/runtime/routes/inference-provider-connection-routes.ts +2 -2
  357. package/src/runtime/routes/inference-send-routes.ts +2 -2
  358. package/src/runtime/routes/ingress-status-routes.ts +2 -2
  359. package/src/runtime/routes/integrations/discord.ts +119 -0
  360. package/src/runtime/routes/llm-call-sites-routes.ts +2 -2
  361. package/src/runtime/routes/mcp-auth-routes.ts +12 -6
  362. package/src/runtime/routes/oauth-pending-flows.ts +136 -0
  363. package/src/runtime/routes/secret-routes.ts +7 -0
  364. package/src/runtime/routes/stt-routes.ts +38 -6
  365. package/src/runtime/routes/wake-conversation-routes.ts +8 -1
  366. package/src/runtime/routes/watch-routes.ts +9 -5
  367. package/src/runtime/sync/resource-sync-events.ts +2 -0
  368. package/src/runtime/trust-verdict-consumer.ts +3 -3
  369. package/src/schedule/__tests__/worker-feature-flags.test.ts +58 -0
  370. package/src/schedule/retry-policy.ts +8 -8
  371. package/src/schedule/run-script.ts +14 -23
  372. package/src/schedule/schedule-recovery.ts +0 -1
  373. package/src/schedule/scheduler.ts +89 -51
  374. package/src/schedule/worker.ts +57 -0
  375. package/src/security/__tests__/openai-device-auth.test.ts +355 -0
  376. package/src/security/oauth2.ts +7 -0
  377. package/src/security/openai-device-auth.ts +364 -0
  378. package/src/skills/available-skills.ts +10 -1
  379. package/src/skills/catalog-install.ts +19 -1
  380. package/src/skills/inline-command-runner.ts +6 -2
  381. package/src/skills/platform-compatibility.test.ts +49 -0
  382. package/src/skills/platform-compatibility.ts +65 -0
  383. package/src/skills/tool-manifest.ts +20 -0
  384. package/src/stt/__tests__/roles.test.ts +152 -0
  385. package/src/stt/daemon-batch-transcriber.ts +4 -1
  386. package/src/stt/roles.ts +180 -0
  387. package/src/stt/stt-stream-session.ts +3 -2
  388. package/src/stt/types.ts +33 -3
  389. package/src/subagent/manager.ts +12 -0
  390. package/src/telemetry/__tests__/live-voice-funnel.test.ts +34 -0
  391. package/src/telemetry/live-voice-funnel.ts +12 -2
  392. package/src/telemetry/telemetry-wire.generated.ts +6 -0
  393. package/src/telemetry/tool-audit.ts +83 -30
  394. package/src/telemetry/tool-executed-events-store.test.ts +0 -1
  395. package/src/telemetry/turn-outcome.ts +75 -4
  396. package/src/tools/client-os.ts +115 -0
  397. package/src/tools/computer-use/definitions.ts +11 -7
  398. package/src/tools/computer-use/skill-proxy-bridge.ts +2 -2
  399. package/src/tools/credentials/broker-types.ts +0 -71
  400. package/src/tools/credentials/broker.ts +6 -201
  401. package/src/tools/executor.ts +2 -8
  402. package/src/tools/host-shell.test.ts +75 -0
  403. package/src/tools/host-shell.ts +24 -0
  404. package/src/tools/host-terminal/host-shell.ts +15 -44
  405. package/src/tools/mcp/mcp-tool-factory.ts +44 -9
  406. package/src/tools/permission-checker.ts +73 -21
  407. package/src/tools/policy-context.ts +1 -0
  408. package/src/tools/shared/filesystem/image-read.ts +1 -1
  409. package/src/tools/skills/find-similar-skills.ts +10 -2
  410. package/src/tools/skills/load.ts +41 -6
  411. package/src/tools/skills/sandbox-runner.ts +8 -16
  412. package/src/tools/skills/skill-tool-factory.ts +1 -0
  413. package/src/tools/terminal/__tests__/safe-env.test.ts +64 -1
  414. package/src/tools/terminal/safe-env.ts +39 -9
  415. package/src/tools/terminal/shell.ts +12 -31
  416. package/src/tools/tool-approval-handler.ts +64 -10
  417. package/src/tools/tool-types.ts +10 -0
  418. package/src/tools/types.ts +19 -3
  419. package/src/tools/ui-surface/surface-shape-docs.ts +4 -3
  420. package/src/tools/workflows/manage-workflows.ts +2 -2
  421. package/src/tools/workflows/run-workflow.test.ts +2 -1
  422. package/src/util/__tests__/file-use.test.ts +61 -0
  423. package/src/util/__tests__/process-table.test.ts +66 -0
  424. package/src/util/__tests__/process-tree.test.ts +46 -0
  425. package/src/util/errors.ts +1 -0
  426. package/src/util/file-use.ts +91 -0
  427. package/src/util/host-process.test.ts +17 -0
  428. package/src/util/host-process.ts +75 -0
  429. package/src/util/image-conversion.ts +39 -7
  430. package/src/util/platform.ts +32 -13
  431. package/src/util/process-table.ts +249 -0
  432. package/src/util/process-tree.ts +85 -129
  433. package/src/util/provider-error-patterns.ts +20 -0
  434. package/src/util/spawn.ts +9 -2
  435. package/src/workspace/git-service.ts +4 -14
  436. package/src/workspace/migrations/147-rename-colliding-backup-profile-names.ts +360 -0
  437. package/src/workspace/migrations/148-strip-unsupported-fallback-profiles.ts +117 -0
  438. package/src/workspace/migrations/149-repoint-backup-profile-selections.ts +204 -0
  439. package/src/workspace/migrations/150-stt-flux-provider-to-model-family.ts +139 -0
  440. package/src/workspace/migrations/__tests__/150-stt-flux-provider-to-model-family.test.ts +160 -0
  441. package/src/workspace/migrations/registry.ts +8 -0
  442. package/src/__tests__/permission-types.test.ts +0 -17
  443. package/src/channels/gateway-discord-admission.ts +0 -32
  444. package/src/persistence/fork-message-copy.ts +0 -239
  445. package/src/plugins/defaults/memory/__tests__/fork-message-copy.test.ts +0 -221
  446. package/src/security/__tests__/oauth2-device-code.test.ts +0 -431
  447. package/src/security/oauth2-device-code.ts +0 -312
@@ -1,23 +1,14 @@
1
- import { v4 as uuid } from "uuid";
2
-
3
1
  import { credentialKey } from "../../security/credential-key.js";
4
2
  import { getSecureKeyAsync } from "../../security/secure-keys.js";
5
3
  import { getLogger } from "../../util/logger.js";
6
4
  import type {
7
- AuthorizeRequest,
8
- AuthorizeResult,
9
5
  BrowserFillRequest,
10
6
  BrowserFillResult,
11
- ConsumeResult,
12
- ServerUseByIdRequest,
13
- ServerUseByIdResult,
14
7
  ServerUseRequest,
15
8
  ServerUseResult,
16
- UsageToken,
17
9
  } from "./broker-types.js";
18
10
  import { isDomainAllowed } from "./domain-policy.js";
19
11
  import { getCredentialMetadata } from "./metadata-store.js";
20
- import { resolveById } from "./resolve.js";
21
12
  import {
22
13
  isToolAllowed,
23
14
  serverUseDenialReason,
@@ -26,21 +17,17 @@ import {
26
17
 
27
18
  const log = getLogger("credential-broker");
28
19
 
29
- /** Tokens expire after 5 minutes to limit the window for using stale/revoked credentials. */
30
- const TOKEN_TTL_MS = 5 * 60 * 1000;
31
-
32
20
  /**
33
- * Credential broker that issues single-use tokens for policy-checked credential access.
21
+ * Credential broker that mediates policy-checked credential access.
34
22
  *
35
- * The broker never exposes plaintext secret values. Instead, it:
23
+ * The broker never exposes plaintext secret values to callers. It:
36
24
  * 1. Checks that a credential exists and has metadata
37
- * 2. Issues a single-use token for the authorized usage
38
- * 3. On consumption, returns the storage key so the caller can read the secret internally
25
+ * 2. Enforces tool and domain policy
26
+ * 3. Reads the secret internally and passes it to an opaque callback
39
27
  *
40
- * Tool policy is enforced at authorize/fill time; domain policy is enforced at fill time.
28
+ * Tool policy is enforced at fill/use time; domain policy is enforced at fill time.
41
29
  */
42
30
  export class CredentialBroker {
43
- private tokens = new Map<string, UsageToken>();
44
31
  /** Transient values for one-time send: consumed on first read, never persisted.
45
32
  * Values are wrapped in objects so post-await guards use reference identity
46
33
  * (not string value equality) to detect concurrent replacements. */
@@ -48,7 +35,7 @@ export class CredentialBroker {
48
35
 
49
36
  /**
50
37
  * Inject a value for one-time use. The value is consumed on the next
51
- * browserFill or consume call for this service/field pair, then discarded.
38
+ * browserFill or serverUse call for this service/field pair, then discarded.
52
39
  */
53
40
  injectTransient(service: string, field: string, value: string): void {
54
41
  const key = credentialKey(service, field);
@@ -59,113 +46,6 @@ export class CredentialBroker {
59
46
  );
60
47
  }
61
48
 
62
- /**
63
- * Authorize the use of a credential for a specific tool and optional domain.
64
- * Returns a single-use token on success, or a denial reason on failure.
65
- */
66
- authorize(request: AuthorizeRequest): AuthorizeResult {
67
- const metadata = getCredentialMetadata(request.service, request.field);
68
- if (!metadata) {
69
- return {
70
- authorized: false,
71
- reason: `No credential found for ${request.service}/${request.field}`,
72
- };
73
- }
74
-
75
- // Tool policy enforcement - deny if tool is not in the credential's allowed list
76
- if (!isToolAllowed(request.toolName, metadata.allowedTools)) {
77
- return {
78
- authorized: false,
79
- reason: toolNotAllowedReason(
80
- request.toolName,
81
- request.service,
82
- request.field,
83
- metadata.allowedTools,
84
- ),
85
- };
86
- }
87
-
88
- const token: UsageToken = {
89
- tokenId: uuid(),
90
- credentialId: metadata.credentialId,
91
- service: request.service,
92
- field: request.field,
93
- toolName: request.toolName,
94
- createdAt: Date.now(),
95
- consumed: false,
96
- };
97
-
98
- this.tokens.set(token.tokenId, token);
99
- log.info(
100
- {
101
- tokenId: token.tokenId,
102
- service: request.service,
103
- field: request.field,
104
- tool: request.toolName,
105
- },
106
- "Usage token issued",
107
- );
108
-
109
- return { authorized: true, token: { ...token } };
110
- }
111
-
112
- /**
113
- * Consume a previously issued token. Returns the storage key on success.
114
- * Each token can only be consumed once.
115
- */
116
- consume(tokenId: string): ConsumeResult {
117
- const token = this.tokens.get(tokenId);
118
- if (!token) {
119
- return { success: false, reason: "Token not found or already revoked" };
120
- }
121
- if (token.consumed) {
122
- return { success: false, reason: "Token already consumed" };
123
- }
124
- if (Date.now() - token.createdAt > TOKEN_TTL_MS) {
125
- this.tokens.delete(tokenId);
126
- log.info({ tokenId }, "Token expired (TTL exceeded)");
127
- return { success: false, reason: "Token expired" };
128
- }
129
-
130
- token.consumed = true;
131
- const storageKey = credentialKey(token.service, token.field);
132
- // Check for transient value first (one-time send) - consume and return the value
133
- // directly since transient values are never persisted to secure storage.
134
- const transient = this.transientValues.get(storageKey);
135
- if (transient !== undefined) {
136
- this.transientValues.delete(storageKey);
137
- log.info(
138
- { tokenId, storageKey, transient: true },
139
- "Usage token consumed (transient)",
140
- );
141
- return { success: true, storageKey, value: transient.value };
142
- }
143
-
144
- log.info({ tokenId, storageKey }, "Usage token consumed");
145
- return { success: true, storageKey };
146
- }
147
-
148
- /**
149
- * Revoke a token, removing it from the active set.
150
- * Returns true if the token existed and was revoked.
151
- */
152
- revoke(tokenId: string): boolean {
153
- const existed = this.tokens.delete(tokenId);
154
- if (existed) {
155
- log.info({ tokenId }, "Usage token revoked");
156
- }
157
- return existed;
158
- }
159
-
160
- /** Revoke all tokens (e.g. on conversation teardown). */
161
- revokeAll(): void {
162
- const count = this.tokens.size;
163
- this.tokens.clear();
164
- if (count > 0) {
165
- log.info({ count }, "All usage tokens revoked");
166
- }
167
- }
168
-
169
49
  /**
170
50
  * Fill a browser field using a credential without exposing plaintext to the caller.
171
51
  *
@@ -317,81 +197,6 @@ export class CredentialBroker {
317
197
  return { success: false, reason: "Credential use failed" };
318
198
  }
319
199
  }
320
-
321
- /**
322
- * Look up a credential by its opaque ID for proxy injection.
323
- *
324
- * Returns metadata and injection templates so the proxy knows how to
325
- * inject the credential into outbound requests. The secret value is
326
- * never included in the result - the proxy reads it separately via
327
- * the secure key backend at injection time.
328
- */
329
- async serverUseById(
330
- request: ServerUseByIdRequest,
331
- ): Promise<ServerUseByIdResult> {
332
- const resolved = resolveById(request.credentialId);
333
- if (!resolved) {
334
- return {
335
- success: false,
336
- reason: `No credential found for id "${request.credentialId}"`,
337
- };
338
- }
339
-
340
- const { metadata } = resolved;
341
-
342
- // Shared server-use policy: the ID lookup above is the only by-ID-specific
343
- // gate, so tool and domain enforcement come from the same helper serverUse
344
- // uses.
345
- const denialReason = serverUseDenialReason(
346
- metadata,
347
- request.requestingTool,
348
- metadata.service,
349
- metadata.field,
350
- );
351
- if (denialReason) {
352
- return { success: false, reason: denialReason };
353
- }
354
-
355
- // Fail-closed: verify the secret value actually exists in secure storage.
356
- // Without this, downstream proxy code would attempt unauthenticated requests.
357
- const value = await getSecureKeyAsync(resolved.storageKey);
358
- if (!value) {
359
- return {
360
- success: false,
361
- reason: `Credential metadata exists but no stored value for ${metadata.service}/${metadata.field}`,
362
- };
363
- }
364
-
365
- log.info(
366
- {
367
- credentialId: request.credentialId,
368
- service: metadata.service,
369
- field: metadata.field,
370
- tool: request.requestingTool,
371
- },
372
- "Server-side credential lookup by ID completed",
373
- );
374
-
375
- return {
376
- success: true,
377
- credentialId: resolved.credentialId,
378
- service: resolved.service,
379
- field: resolved.field,
380
- injectionTemplates: resolved.injectionTemplates,
381
- };
382
- }
383
-
384
- /** Return the number of active (non-consumed, non-revoked, non-expired) tokens. */
385
- get activeTokenCount(): number {
386
- const now = Date.now();
387
- let count = 0;
388
- for (const token of this.tokens.values()) {
389
- if (!token.consumed && now - token.createdAt <= TOKEN_TTL_MS) {
390
- count++;
391
- }
392
- }
393
- return count;
394
- }
395
200
  }
396
201
 
397
202
  /** Shared singleton broker instance used by vault and browser tools. */
@@ -33,6 +33,7 @@ import { getWorkflowRunManager } from "../workflows/run-manager.js";
33
33
  import { executeWithTimeout, safeTimeoutMs } from "./execution-timeout.js";
34
34
  import { fileEditInputSchema } from "./filesystem/edit.js";
35
35
  import { fileWriteInputSchema } from "./filesystem/write.js";
36
+ import { getHostShell } from "./host-shell.js";
36
37
  import { PermissionChecker } from "./permission-checker.js";
37
38
  import { getToolOwner } from "./registry.js";
38
39
  import { extractAndSanitize } from "./sensitive-output-placeholders.js";
@@ -89,6 +90,7 @@ export class ToolExecutor {
89
90
  undefined,
90
91
  undefined,
91
92
  context.signal,
93
+ getHostShell(context, input),
92
94
  );
93
95
  } catch {
94
96
  // Stays undefined; the audit row records RISK_LEVEL_UNCLASSIFIED.
@@ -108,12 +110,6 @@ export class ToolExecutor {
108
110
  let permApprovalMode: string | undefined;
109
111
  let permApprovalReason: string | undefined;
110
112
  let permRiskThreshold: string | undefined;
111
- // Whether THIS invocation was interactively prompted. Distinct from the
112
- // turn-level `context.approvedViaPrompt` (seeded from
113
- // `approvedViaPromptThisTurn`, which stays true for later auto-approved
114
- // tools in the same turn): the `permission_decided` telemetry must reflect
115
- // a prompt for this specific call, not any prompt earlier in the turn.
116
- let wasPromptedThisInvocation = false;
117
113
  // Run pre-execution approval gates (abort, guardian policy,
118
114
  // allowed-tool-set, task-run preflight, tool registry lookup). The gate
119
115
  // resolves the tool's sandbox/host target internally.
@@ -253,7 +249,6 @@ export class ToolExecutor {
253
249
 
254
250
  if (permResult.wasPrompted) {
255
251
  context.approvedViaPrompt = true;
256
- wasPromptedThisInvocation = true;
257
252
  }
258
253
  } else {
259
254
  // Grant consumed — permission check was skipped. Set provenance explicitly
@@ -324,7 +319,6 @@ export class ToolExecutor {
324
319
  matchedTrustRuleId: permMatchedTrustRuleId,
325
320
  durationMs,
326
321
  attribution: context.attribution ?? null,
327
- wasPrompted: wasPromptedThisInvocation,
328
322
  });
329
323
  recordToolCompletion(
330
324
  context.conversationId,
@@ -0,0 +1,75 @@
1
+ import { afterEach, describe, expect, test } from "bun:test";
2
+
3
+ import { assistantEventHub } from "../runtime/assistant-event-hub.js";
4
+ import { getHostShell } from "./host-shell.js";
5
+
6
+ const subscriptions: Array<{ dispose(): void }> = [];
7
+
8
+ afterEach(() => {
9
+ for (const subscription of subscriptions.splice(0)) {
10
+ subscription.dispose();
11
+ }
12
+ });
13
+
14
+ function connectHostBashClient(
15
+ clientId: string,
16
+ interfaceId: "macos" | "windows",
17
+ ): void {
18
+ subscriptions.push(
19
+ assistantEventHub.subscribe({
20
+ type: "client",
21
+ clientId,
22
+ interfaceId,
23
+ capabilities: ["host_bash"],
24
+ actorPrincipalId: "actor-1",
25
+ callback: () => {},
26
+ }),
27
+ );
28
+ }
29
+
30
+ describe("getHostShell", () => {
31
+ test("uses the explicitly targeted Windows client's shell", () => {
32
+ connectHostBashClient("windows-client", "windows");
33
+
34
+ expect(
35
+ getHostShell(
36
+ {
37
+ clientOs: "web",
38
+ transportInterface: "web",
39
+ sourceActorPrincipalId: "actor-1",
40
+ },
41
+ { target_client_id: "windows-client" },
42
+ ),
43
+ ).toBe("powershell");
44
+ });
45
+
46
+ test("uses the single same-user Windows client's shell", () => {
47
+ connectHostBashClient("windows-client", "windows");
48
+
49
+ expect(
50
+ getHostShell(
51
+ {
52
+ clientOs: "web",
53
+ transportInterface: "web",
54
+ sourceActorPrincipalId: "actor-1",
55
+ },
56
+ {},
57
+ ),
58
+ ).toBe("powershell");
59
+ });
60
+
61
+ test("keeps Bash classification for a targeted macOS client", () => {
62
+ connectHostBashClient("mac-client", "macos");
63
+
64
+ expect(
65
+ getHostShell(
66
+ {
67
+ clientOs: "web",
68
+ transportInterface: "web",
69
+ sourceActorPrincipalId: "actor-1",
70
+ },
71
+ { target_client_id: "mac-client" },
72
+ ),
73
+ ).toBeUndefined();
74
+ });
75
+ });
@@ -0,0 +1,24 @@
1
+ import type { ClassifyRiskIpcParams } from "@vellumai/gateway-client";
2
+
3
+ import { resolveInvocationHostClientOs } from "./client-os.js";
4
+ import type { ToolContext } from "./types.js";
5
+
6
+ export function getHostShell(
7
+ context: Pick<
8
+ ToolContext,
9
+ "clientOs" | "transportInterface" | "sourceActorPrincipalId"
10
+ >,
11
+ input: Record<string, unknown>,
12
+ ): ClassifyRiskIpcParams["shell"] {
13
+ const targetClientOs = resolveInvocationHostClientOs(
14
+ "host_bash",
15
+ input,
16
+ context,
17
+ );
18
+ return targetClientOs === "windows" ||
19
+ (targetClientOs === undefined &&
20
+ (context.clientOs === "windows" ||
21
+ context.transportInterface === "windows"))
22
+ ? "powershell"
23
+ : undefined;
24
+ }
@@ -9,7 +9,7 @@
9
9
  import { spawn } from "node:child_process";
10
10
  import { existsSync } from "node:fs";
11
11
  import { homedir } from "node:os";
12
- import { isAbsolute } from "node:path";
12
+ import { isAbsolute, join } from "node:path";
13
13
 
14
14
  import { z } from "zod";
15
15
 
@@ -24,6 +24,11 @@ import {
24
24
  } from "../../runtime/assistant-event-hub.js";
25
25
  import { conversationRevealNonce } from "../../runtime/reveal-nonce.js";
26
26
  import { redactSecrets } from "../../security/secret-scanner.js";
27
+ import {
28
+ buildShellInvocation,
29
+ prependUniquePathEntries,
30
+ terminateProcessTree,
31
+ } from "../../util/host-process.js";
27
32
  import { getLogger } from "../../util/logger.js";
28
33
  import type { CompletedBackgroundTool } from "../background-tool-registry.js";
29
34
  import {
@@ -67,15 +72,10 @@ const HOST_BASH_PROXY_ENV_KEYS = [
67
72
  function buildHostShellEnv(): Record<string, string> {
68
73
  const env = buildSanitizedEnv();
69
74
  // Ensure ~/.local/bin and ~/.bun/bin are in PATH so `vellum` and `bun` are
70
- // always reachable, even when the daemon is launched from a macOS app
71
- // bundle that inherits a minimal PATH.
75
+ // reachable when a desktop app launches the assistant with a minimal PATH.
72
76
  const home = homedir();
73
- const extraDirs = [`${home}/.local/bin`, `${home}/.bun/bin`];
74
- const currentPath = env.PATH ?? "";
75
- const missing = extraDirs.filter((d) => !currentPath.split(":").includes(d));
76
- if (missing.length > 0) {
77
- env.PATH = [...missing, currentPath].filter(Boolean).join(":");
78
- }
77
+ const extraDirs = [join(home, ".local", "bin"), join(home, ".bun", "bin")];
78
+ env.PATH = prependUniquePathEntries(env.PATH, extraDirs);
79
79
  return env;
80
80
  }
81
81
 
@@ -466,7 +466,8 @@ export const hostShellTool = {
466
466
  const bgId = generateBackgroundToolId();
467
467
  const startedAt = Date.now();
468
468
 
469
- const child = spawn("bash", ["-c", "--", command], {
469
+ const wrapped = buildShellInvocation(command);
470
+ const child = spawn(wrapped.command, wrapped.args, {
470
471
  cwd: workingDir,
471
472
  env: hostEnv,
472
473
  stdio: ["ignore", "pipe", "pipe"],
@@ -480,21 +481,7 @@ export const hostShellTool = {
480
481
  // event reports "cancelled" rather than "failed".
481
482
  let aborted = false;
482
483
 
483
- const killTree = () => {
484
- if (child.pid != null) {
485
- try {
486
- process.kill(-child.pid, "SIGKILL");
487
- return;
488
- } catch {
489
- // Process group may have already exited — fall through.
490
- }
491
- }
492
- try {
493
- child.kill("SIGKILL");
494
- } catch {
495
- // Child may have already exited.
496
- }
497
- };
484
+ const killTree = () => terminateProcessTree(child);
498
485
 
499
486
  const timer = setTimeout(() => {
500
487
  timedOut = true;
@@ -666,31 +653,15 @@ export const hostShellTool = {
666
653
  const stderrChunks: Buffer[] = [];
667
654
  let timedOut = false;
668
655
 
669
- const child = spawn("bash", ["-c", "--", command], {
656
+ const wrapped = buildShellInvocation(command);
657
+ const child = spawn(wrapped.command, wrapped.args, {
670
658
  cwd: workingDir,
671
659
  env: hostEnv,
672
660
  stdio: ["ignore", "pipe", "pipe"],
673
661
  detached: true,
674
662
  });
675
663
 
676
- // Kill the entire process tree. Tries the process group first
677
- // (negative PID), then falls back to killing the direct child if the
678
- // PID is unavailable or the group kill fails.
679
- const killTree = () => {
680
- if (child.pid != null) {
681
- try {
682
- process.kill(-child.pid, "SIGKILL");
683
- return;
684
- } catch {
685
- // Process group may have already exited — fall through.
686
- }
687
- }
688
- try {
689
- child.kill("SIGKILL");
690
- } catch {
691
- // Child may have already exited.
692
- }
693
- };
664
+ const killTree = () => terminateProcessTree(child);
694
665
 
695
666
  const timer = setTimeout(() => {
696
667
  timedOut = true;
@@ -27,24 +27,59 @@ export interface McpToolMetadata {
27
27
  annotations?: McpToolAnnotations;
28
28
  }
29
29
 
30
+ /** Risk levels ordered low to high, so a hint can step one place along it. */
31
+ const RISK_LADDER: readonly RiskLevel[] = [
32
+ RiskLevel.Low,
33
+ RiskLevel.Medium,
34
+ RiskLevel.High,
35
+ ];
36
+
37
+ function stepRisk(risk: RiskLevel, direction: -1 | 1): RiskLevel {
38
+ const index = RISK_LADDER.indexOf(risk);
39
+ if (index === -1) {
40
+ return risk;
41
+ }
42
+ const next = Math.min(Math.max(index + direction, 0), RISK_LADDER.length - 1);
43
+ return RISK_LADDER[next] ?? risk;
44
+ }
45
+
30
46
  /**
31
47
  * Resolve the risk level a tool carries.
32
48
  *
33
- * `readOnlyHint` is self-reported by the server, so it only refines risk
34
- * downward and only for servers the user has already configured below the
35
- * high-trust ceiling. Servers left at the default "high" are unaffected, and
36
- * the hint never raises risk above the server default.
49
+ * The server's configured level is the anchor, and a tool's own MCP
50
+ * annotations move it at most one step along {@link RISK_LADDER}.
51
+ *
52
+ * `destructiveHint` steps up. Raising is the safe direction, so it applies at
53
+ * any server level.
54
+ *
55
+ * `readOnlyHint` steps down, and only from {@link RiskLevel.Medium}. Medium is
56
+ * the schema default, so a server sitting at High got there because the user
57
+ * put it there. A hint is self-reported by the server, and letting one lower a
58
+ * deliberately pinned server would hand a lying server the auto-approval the
59
+ * user withheld: under the Relaxed preset Medium carries no prompt at all. High
60
+ * is therefore the floor for downward hints.
61
+ *
62
+ * `destructiveHint` wins when a server sends both, since a tool that both reads
63
+ * and destroys is a tool that destroys.
64
+ *
65
+ * The MCP spec defaults `destructiveHint` to true when it is absent. This
66
+ * deliberately does not, because the server's configured level already answers
67
+ * that question: choosing a level for a server is the user's statement about
68
+ * what its unlabeled tools are worth. Honoring the spec default instead would
69
+ * put every tool of every unannotated server back at High, which is the
70
+ * behavior this replaces.
37
71
  */
38
72
  function resolveRiskLevel(
39
73
  metadata: McpToolMetadata,
40
74
  serverConfig: McpServerConfig,
41
75
  ): RiskLevel {
42
76
  const serverRisk = riskMap[serverConfig.defaultRiskLevel] ?? RiskLevel.High;
43
- if (
44
- metadata.annotations?.readOnlyHint === true &&
45
- serverRisk !== RiskLevel.High
46
- ) {
47
- return RiskLevel.Low;
77
+ const annotations = metadata.annotations;
78
+ if (annotations?.destructiveHint === true) {
79
+ return stepRisk(serverRisk, 1);
80
+ }
81
+ if (annotations?.readOnlyHint === true && serverRisk === RiskLevel.Medium) {
82
+ return stepRisk(serverRisk, -1);
48
83
  }
49
84
  return serverRisk;
50
85
  }