@vellumai/assistant 0.11.6 → 0.11.7-staging.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (478) 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/ces-client/node_modules/@vellumai/service-contracts/src/rpc.ts +174 -0
  21. package/node_modules/@vellumai/ces-client/src/http-credentials.ts +161 -0
  22. package/node_modules/@vellumai/ces-client/src/index.ts +1 -0
  23. package/node_modules/@vellumai/environments/package.json +2 -1
  24. package/node_modules/@vellumai/environments/src/shell.test.ts +56 -0
  25. package/node_modules/@vellumai/environments/src/shell.ts +65 -0
  26. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/ingress.test.ts +37 -0
  27. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/remote-web-pairing.test.ts +389 -0
  28. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/ingress.ts +22 -0
  29. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +508 -18
  30. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/rpc.ts +174 -0
  31. package/node_modules/@vellumai/gateway-client/src/__tests__/contact-read-contracts.test.ts +32 -0
  32. package/node_modules/@vellumai/gateway-client/src/__tests__/gateway-client.test.ts +1 -0
  33. package/node_modules/@vellumai/gateway-client/src/__tests__/inbound-event-kind.test.ts +82 -0
  34. package/node_modules/@vellumai/gateway-client/src/__tests__/plugin-admission-denied-contract.test.ts +56 -0
  35. package/node_modules/@vellumai/gateway-client/src/__tests__/trust-verdict-contract.test.ts +18 -0
  36. package/node_modules/@vellumai/gateway-client/src/admission-policy-contract.ts +5 -11
  37. package/node_modules/@vellumai/gateway-client/src/channel-permission-contract.ts +3 -1
  38. package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +9 -0
  39. package/node_modules/@vellumai/gateway-client/src/inbound-contract.ts +4 -0
  40. package/node_modules/@vellumai/gateway-client/src/inbound-event-kind.ts +75 -0
  41. package/node_modules/@vellumai/gateway-client/src/index.ts +17 -0
  42. package/node_modules/@vellumai/gateway-client/src/outbound-contract.ts +8 -0
  43. package/node_modules/@vellumai/gateway-client/src/plugin-admission-denied-contract.ts +47 -0
  44. package/node_modules/@vellumai/gateway-client/src/trust-verdict-contract.ts +11 -0
  45. package/node_modules/@vellumai/service-contracts/src/__tests__/ingress.test.ts +37 -0
  46. package/node_modules/@vellumai/service-contracts/src/__tests__/remote-web-pairing.test.ts +389 -0
  47. package/node_modules/@vellumai/service-contracts/src/ingress.ts +22 -0
  48. package/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +508 -18
  49. package/node_modules/@vellumai/service-contracts/src/rpc.ts +174 -0
  50. package/openapi.yaml +419 -55
  51. package/package.json +4 -1
  52. package/scripts/sync-llm-catalog.ts +6 -0
  53. package/src/__tests__/approval-card-edit-inbound-handoff.test.ts +199 -0
  54. package/src/__tests__/approval-card-edit.test.ts +153 -0
  55. package/src/__tests__/attachments-store-heic-normalize.test.ts +1 -1
  56. package/src/__tests__/available-skills.test.ts +54 -0
  57. package/src/__tests__/avatar-identity-sync.test.ts +5 -1
  58. package/src/__tests__/catalog-install-normalize.test.ts +9 -1
  59. package/src/__tests__/channel-policy.test.ts +1 -3
  60. package/src/__tests__/channel-readiness-discord.test.ts +9 -70
  61. package/src/__tests__/compactor-call-site-logging.test.ts +61 -0
  62. package/src/__tests__/config-loader-backfill.test.ts +69 -0
  63. package/src/__tests__/config-schema.test.ts +2 -3
  64. package/src/__tests__/conversation-agent-loop.test.ts +238 -0
  65. package/src/__tests__/conversation-attachments.test.ts +0 -1
  66. package/src/__tests__/conversation-error.test.ts +44 -0
  67. package/src/__tests__/conversation-evictor.test.ts +19 -0
  68. package/src/__tests__/conversation-fork-referential.test.ts +73 -63
  69. package/src/__tests__/conversation-fork-retrospective.test.ts +99 -74
  70. package/src/__tests__/conversation-runtime-assembly.test.ts +23 -0
  71. package/src/__tests__/conversation-slash.test.ts +9 -0
  72. package/src/__tests__/conversation-title-service.test.ts +97 -0
  73. package/src/__tests__/conversation-tool-setup-attribution.test.ts +16 -0
  74. package/src/__tests__/credential-broker-server-use.test.ts +0 -218
  75. package/src/__tests__/credential-broker.test.ts +43 -420
  76. package/src/__tests__/credential-record-write-through.test.ts +78 -0
  77. package/src/__tests__/credential-routes.test.ts +17 -1
  78. package/src/__tests__/credential-security-invariants.test.ts +8 -9
  79. package/src/__tests__/default-profile-catalog-fallback.test.ts +289 -0
  80. package/src/__tests__/delete-propagation.test.ts +92 -2
  81. package/src/__tests__/discord-callback-round-trip.test.ts +93 -0
  82. package/src/__tests__/discord-channel-config.test.ts +237 -0
  83. package/src/__tests__/edit-propagation.test.ts +42 -2
  84. package/src/__tests__/evict-conversations-for-reload.test.ts +51 -1
  85. package/src/__tests__/fallback-breaker.test.ts +1335 -0
  86. package/src/__tests__/gateway-threshold-reader-mock.ts +6 -0
  87. package/src/__tests__/guardian-card-withdrawal.test.ts +30 -0
  88. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +0 -2
  89. package/src/__tests__/image-conversion.test.ts +38 -4
  90. package/src/__tests__/install-skill-routing.test.ts +25 -0
  91. package/src/__tests__/llm-catalog-parity.test.ts +4 -0
  92. package/src/__tests__/llm-resolver.test.ts +74 -0
  93. package/src/__tests__/llm-schema-fallback-profile.test.ts +359 -0
  94. package/src/__tests__/managed-fallback-dispatch.test.ts +430 -0
  95. package/src/__tests__/managed-profile-guard.test.ts +28 -0
  96. package/src/__tests__/managed-speech-defaults.test.ts +196 -1
  97. package/src/__tests__/mcp-cli.test.ts +23 -1
  98. package/src/__tests__/mcp-tool-annotations-risk.test.ts +43 -34
  99. package/src/__tests__/media-stream-stt-session.test.ts +2 -0
  100. package/src/__tests__/notification-decision-fallback.test.ts +106 -0
  101. package/src/__tests__/notification-discord-adapter.test.ts +162 -0
  102. package/src/__tests__/oauth-connect-orchestrator.test.ts +95 -0
  103. package/src/__tests__/persist-unsendable-image-downscale.test.ts +6 -8
  104. package/src/__tests__/persist-unsendable-image.test.ts +3 -3
  105. package/src/__tests__/platform.test.ts +19 -1
  106. package/src/__tests__/plugin-import-boundary-guard.test.ts +5 -1
  107. package/src/__tests__/provider-catalog-visibility.test.ts +17 -0
  108. package/src/__tests__/provider-platform-proxy-integration.test.ts +8 -1
  109. package/src/__tests__/provider-usage-tracking.test.ts +56 -0
  110. package/src/__tests__/require-fresh-approval.test.ts +5 -2
  111. package/src/__tests__/retry-fallback-escalation.test.ts +1093 -0
  112. package/src/__tests__/schedule-retry.test.ts +183 -4
  113. package/src/__tests__/scheduler-reuse-conversation.test.ts +6 -6
  114. package/src/__tests__/secret-routes-platform-proxy.test.ts +55 -0
  115. package/src/__tests__/skill-load-tool.test.ts +34 -0
  116. package/src/__tests__/skill-tool-factory.test.ts +6 -1
  117. package/src/__tests__/skill-tool-manifest.test.ts +20 -0
  118. package/src/__tests__/skills.test.ts +25 -0
  119. package/src/__tests__/subagent-manager-notify.test.ts +38 -0
  120. package/src/__tests__/terminal-tools.test.ts +2 -0
  121. package/src/__tests__/tool-approval-handler.test.ts +146 -2
  122. package/src/__tests__/tool-approval-seed-content-blocks.test.ts +108 -2
  123. package/src/__tests__/tool-audit.test.ts +111 -40
  124. package/src/__tests__/tool-executor-lifecycle-events.test.ts +228 -18
  125. package/src/__tests__/ui-shape-teaching.test.ts +1 -1
  126. package/src/__tests__/verification-control-plane-policy.test.ts +6 -2
  127. package/src/__tests__/voice-config-update.test.ts +43 -0
  128. package/src/__tests__/workspace-migration-147-rename-colliding-backup-profile-names.test.ts +480 -0
  129. package/src/__tests__/workspace-migration-148-strip-unsupported-fallback-profiles.test.ts +167 -0
  130. package/src/__tests__/workspace-migration-149-repoint-backup-profile-selections.test.ts +144 -0
  131. package/src/agent/loop.ts +31 -2
  132. package/src/approvals/guardian-card-withdrawal.ts +22 -0
  133. package/src/avatar/__tests__/ensure-raster.test.ts +291 -0
  134. package/src/avatar/avatar-manifest.ts +48 -100
  135. package/src/avatar/avatar-store.ts +7 -7
  136. package/src/avatar/ensure-raster.ts +146 -0
  137. package/src/avatar/resvg-lazy.test.ts +0 -1
  138. package/src/avatar/traits-png-sync.ts +9 -10
  139. package/src/background-wake/wake-intent-hooks.test.ts +0 -1
  140. package/src/backup/__tests__/paths.test.ts +6 -2
  141. package/src/backup/paths.ts +6 -4
  142. package/src/bundler/app-compiler.ts +2 -2
  143. package/src/bundler/compiler-tools.test.ts +36 -0
  144. package/src/bundler/compiler-tools.ts +54 -18
  145. package/src/calls/__tests__/telephony-synthesis-language.test.ts +81 -0
  146. package/src/calls/__tests__/voice-session-bridge.test.ts +57 -0
  147. package/src/calls/media-stream-stt-session.ts +2 -1
  148. package/src/calls/telephony-synthesis-language.ts +13 -5
  149. package/src/calls/voice-session-bridge.ts +19 -4
  150. package/src/channels/config.ts +23 -12
  151. package/src/cli/AGENTS.md +3 -1
  152. package/src/cli/commands/backup.help.ts +10 -13
  153. package/src/cli/commands/contacts.help.ts +16 -4
  154. package/src/cli/commands/contacts.ts +5 -1
  155. package/src/cli/commands/credentials.help.ts +1 -1
  156. package/src/cli/commands/inference-providers.ts +18 -53
  157. package/src/cli/commands/mcp.help.ts +6 -5
  158. package/src/cli/commands/mcp.ts +1 -1
  159. package/src/cli/commands/memory/memory-retrospective.ts +1 -0
  160. package/src/cli/commands/stt.help.ts +2 -2
  161. package/src/config/__tests__/backup-schema.test.ts +1 -1
  162. package/src/config/__tests__/default-profile-catalog.test.ts +83 -2
  163. package/src/config/__tests__/loader-sparse-array-cleanup.test.ts +1 -1
  164. package/src/config/__tests__/memory-retrospective-schema.test.ts +12 -15
  165. package/src/config/bundled-skills/computer-use/SKILL.md +6 -6
  166. package/src/config/bundled-skills/computer-use/TOOLS.json +9 -6
  167. package/src/config/bundled-skills/media-processing/services/audio-transcribe.ts +3 -1
  168. package/src/config/bundled-skills/media-processing/services/preprocess.ts +1 -1
  169. package/src/config/bundled-skills/phone-calls/SKILL.md +1 -1
  170. package/src/config/bundled-skills/phone-calls/references/CONFIG.md +16 -15
  171. package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +2 -1
  172. package/src/config/bundled-skills/settings/TOOLS.json +11 -5
  173. package/src/config/bundled-skills/settings/tools/open-system-settings.test.ts +85 -0
  174. package/src/config/bundled-skills/settings/tools/open-system-settings.ts +33 -8
  175. package/src/config/bundled-skills/settings/tools/voice-config-update.ts +54 -3
  176. package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +1 -1
  177. package/src/config/default-profile-catalog.ts +195 -10
  178. package/src/config/default-profile-names.ts +41 -0
  179. package/src/config/feature-flag-registry.json +34 -2
  180. package/src/config/inference-profile-validation.ts +11 -2
  181. package/src/config/llm-resolver.ts +25 -0
  182. package/src/config/loader.ts +3 -0
  183. package/src/config/managed-speech-defaults.ts +160 -10
  184. package/src/config/schema.ts +2 -0
  185. package/src/config/schemas/__tests__/live-voice.test.ts +7 -6
  186. package/src/config/schemas/__tests__/stt.test.ts +94 -0
  187. package/src/config/schemas/backup.ts +1 -1
  188. package/src/config/schemas/channels.ts +32 -0
  189. package/src/config/schemas/live-voice.ts +13 -4
  190. package/src/config/schemas/llm.ts +263 -15
  191. package/src/config/schemas/mcp.ts +10 -2
  192. package/src/config/schemas/memory-retrospective.ts +1 -19
  193. package/src/config/schemas/services.ts +24 -0
  194. package/src/config/schemas/stt.ts +128 -5
  195. package/src/config/seed-inference-profiles.ts +32 -3
  196. package/src/config/skill-state.ts +5 -0
  197. package/src/config/skills.ts +21 -0
  198. package/src/contacts/notify-contacts-changed.ts +5 -2
  199. package/src/context/compactor.ts +74 -1
  200. package/src/daemon/__tests__/conversation-tool-setup.test.ts +138 -1
  201. package/src/daemon/config-watcher.ts +2 -1
  202. package/src/daemon/conversation-agent-loop-handlers.ts +27 -0
  203. package/src/daemon/conversation-agent-loop.ts +86 -10
  204. package/src/daemon/conversation-error.ts +25 -0
  205. package/src/daemon/conversation-evictor.ts +2 -4
  206. package/src/daemon/conversation-runtime-assembly.ts +19 -7
  207. package/src/daemon/conversation-skill-tools.ts +5 -1
  208. package/src/daemon/conversation-slash.ts +2 -2
  209. package/src/daemon/conversation-store.ts +8 -1
  210. package/src/daemon/conversation-tool-setup.ts +37 -0
  211. package/src/daemon/conversation.ts +0 -4
  212. package/src/daemon/handlers/channel-config-result.ts +30 -0
  213. package/src/daemon/handlers/config-discord-channel.ts +301 -0
  214. package/src/daemon/handlers/config-ingress.ts +9 -0
  215. package/src/daemon/handlers/config-slack-channel.ts +13 -15
  216. package/src/daemon/handlers/config-telegram.ts +2 -5
  217. package/src/daemon/handlers/conversations.ts +0 -2
  218. package/src/daemon/handlers/skills.ts +25 -7
  219. package/src/daemon/lifecycle.ts +3 -1
  220. package/src/daemon/message-types/messages.ts +0 -2
  221. package/src/daemon/tool-setup-types.ts +5 -3
  222. package/src/daemon/trust-context-types.test.ts +33 -0
  223. package/src/heartbeat/__tests__/heartbeat-service.test.ts +0 -1
  224. package/src/live-voice/__tests__/live-voice-flux-turn-end.test.ts +58 -4
  225. package/src/live-voice/__tests__/live-voice-managed-speech-fallback.test.ts +8 -3
  226. package/src/live-voice/__tests__/live-voice-session-manager.test.ts +73 -11
  227. package/src/live-voice/__tests__/live-voice-session-preflight.test.ts +148 -2
  228. package/src/live-voice/__tests__/live-voice-session-telemetry.test.ts +64 -0
  229. package/src/live-voice/__tests__/live-voice-stt.test.ts +3 -0
  230. package/src/live-voice/__tests__/live-voice-text-turn.test.ts +391 -0
  231. package/src/live-voice/__tests__/protocol.test.ts +224 -0
  232. package/src/live-voice/live-voice-credential-preflight.ts +5 -1
  233. package/src/live-voice/live-voice-session-manager.ts +39 -2
  234. package/src/live-voice/live-voice-session.ts +224 -9
  235. package/src/live-voice/protocol.ts +187 -1
  236. package/src/messaging/providers/discord/api.ts +2 -1
  237. package/src/messaging/providers/discord/withdraw.ts +62 -0
  238. package/src/messaging/read-provider-metadata.ts +32 -0
  239. package/src/monitoring/__tests__/file-descriptors.test.ts +32 -0
  240. package/src/monitoring/__tests__/process-memory.test.ts +34 -1
  241. package/src/monitoring/db-integrity-sample.ts +1 -0
  242. package/src/monitoring/file-descriptors.ts +39 -6
  243. package/src/monitoring/process-memory.ts +37 -5
  244. package/src/monitoring/resource-sampler.ts +25 -8
  245. package/src/notifications/__tests__/activity-failed-dedupe.test.ts +161 -0
  246. package/src/notifications/__tests__/background-failure-signal.test.ts +107 -0
  247. package/src/notifications/__tests__/connected-channels.test.ts +12 -0
  248. package/src/notifications/__tests__/copy-composer.test.ts +83 -0
  249. package/src/notifications/__tests__/destination-resolver.test.ts +38 -0
  250. package/src/notifications/activity-failed-dedupe.ts +95 -0
  251. package/src/notifications/adapters/discord.ts +132 -0
  252. package/src/notifications/adapters/shared.ts +16 -0
  253. package/src/notifications/adapters/slack.ts +6 -1
  254. package/src/notifications/adapters/telegram.ts +5 -8
  255. package/src/notifications/approval-card-data.ts +94 -1
  256. package/src/notifications/background-failure-signal.ts +116 -0
  257. package/src/notifications/broadcaster.ts +3 -9
  258. package/src/notifications/copy-composer.ts +41 -8
  259. package/src/notifications/decision-engine.ts +55 -0
  260. package/src/notifications/destination-resolver.ts +45 -5
  261. package/src/notifications/emit-signal.ts +19 -5
  262. package/src/notifications/guardian-question-mode.ts +57 -0
  263. package/src/notifications/types.ts +2 -8
  264. package/src/oauth/__tests__/seed-providers-managed.test.ts +18 -0
  265. package/src/oauth/connect-orchestrator.ts +16 -0
  266. package/src/oauth/seed-providers.ts +262 -0
  267. package/src/permissions/checker.test.ts +21 -0
  268. package/src/permissions/checker.ts +6 -0
  269. package/src/permissions/gateway-threshold-reader.test.ts +184 -0
  270. package/src/permissions/gateway-threshold-reader.ts +144 -10
  271. package/src/permissions/prompter.ts +0 -6
  272. package/src/permissions/types.ts +5 -4
  273. package/src/persistence/__tests__/bulk-write-gate.test.ts +0 -38
  274. package/src/persistence/bulk-write-gate.ts +9 -10
  275. package/src/persistence/conversation-crud.ts +88 -134
  276. package/src/persistence/conversation-title-service.ts +43 -29
  277. package/src/persistence/embeddings/embedding-local.ts +14 -85
  278. package/src/persistence/embeddings/embedding-runtime-manager.ts +1 -0
  279. package/src/persistence/lifecycle-events-store.test.ts +50 -0
  280. package/src/persistence/lifecycle-events-store.ts +75 -5
  281. package/src/platform/platform-patch-queue.test.ts +368 -0
  282. package/src/platform/platform-patch-queue.ts +190 -0
  283. package/src/platform/sync-avatar.test.ts +527 -0
  284. package/src/platform/sync-avatar.ts +209 -0
  285. package/src/platform/sync-identity.test.ts +75 -0
  286. package/src/platform/sync-identity.ts +17 -110
  287. package/src/plugin-api/model-profiles.test.ts +2 -2
  288. package/src/plugin-api/model-profiles.ts +7 -7
  289. package/src/plugin-api/transcription-session.ts +1 -1
  290. package/src/plugins/defaults/compaction/window-manager.ts +4 -0
  291. package/src/plugins/defaults/image-recovery/recover.ts +2 -2
  292. package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +133 -34
  293. package/src/plugins/defaults/memory/__tests__/memory-retrospective-prompt.test.ts +21 -31
  294. package/src/plugins/defaults/memory/__tests__/memory-retrospective-provider-path.test.ts +13 -9
  295. package/src/plugins/defaults/memory/__tests__/memory-retrospective-wake-chain.test.ts +31 -4
  296. package/src/plugins/defaults/memory/memory-retrospective-constants.ts +0 -11
  297. package/src/plugins/defaults/memory/memory-retrospective-fork-boundary.ts +27 -13
  298. package/src/plugins/defaults/memory/memory-retrospective-job.ts +183 -113
  299. package/src/plugins/defaults/memory/memory-retrospective-prompt.ts +13 -13
  300. package/src/plugins/defaults/memory/src/memory-v2-routes.ts +2 -2
  301. package/src/plugins/defaults/memory/substrate/__tests__/consolidation-job.test.ts +1 -1
  302. package/src/plugins/defaults/memory/substrate/consolidation-job.ts +4 -2
  303. package/src/plugins/defaults/memory/substrate/sweep-job.ts +9 -42
  304. package/src/plugins/defaults/memory/v2/rerank-local.ts +1 -0
  305. package/src/plugins/defaults/memory/worker.ts +7 -0
  306. package/src/providers/__tests__/provider-secret-catalog.test.ts +1 -0
  307. package/src/providers/__tests__/retry-callsite.test.ts +55 -0
  308. package/src/providers/__tests__/vellum-connection-routing.test.ts +21 -3
  309. package/src/providers/connection-resolution.ts +2 -17
  310. package/src/providers/fallback-breaker.ts +389 -0
  311. package/src/providers/inference/__tests__/adapter-factory-opencode.test.ts +92 -0
  312. package/src/providers/inference/__tests__/base-url-route-validation.test.ts +16 -0
  313. package/src/providers/inference/__tests__/base-url-security.test.ts +12 -0
  314. package/src/providers/inference/adapter-factory.ts +246 -3
  315. package/src/providers/inference/auth.ts +15 -14
  316. package/src/providers/inference/chatgpt-subscription-auth.ts +70 -0
  317. package/src/providers/inference/chatgpt-subscription-credentials.ts +84 -0
  318. package/src/providers/inference/resolve-auth.ts +1 -1
  319. package/src/providers/media-resolve.ts +2 -3
  320. package/src/providers/model-catalog.ts +100 -0
  321. package/src/providers/openai/__tests__/api-error-normalization.test.ts +22 -0
  322. package/src/providers/openai/__tests__/chat-template-flatten.test.ts +198 -0
  323. package/src/providers/openai/api-error-normalization.ts +13 -0
  324. package/src/providers/openai/chat-completions-provider.ts +84 -0
  325. package/src/providers/opencode/client.test.ts +150 -0
  326. package/src/providers/opencode/client.ts +61 -0
  327. package/src/providers/platform-proxy/constants.ts +5 -0
  328. package/src/providers/provider-resolvability.ts +20 -0
  329. package/src/providers/provider-secret-catalog.ts +3 -2
  330. package/src/providers/retry.ts +956 -68
  331. package/src/providers/speech-to-text/__tests__/deepgram-flux-frames.test.ts +42 -0
  332. package/src/providers/speech-to-text/__tests__/deepgram-flux-realtime.test.ts +136 -0
  333. package/src/providers/speech-to-text/__tests__/provider-catalog.test.ts +120 -0
  334. package/src/providers/speech-to-text/__tests__/resolve.test.ts +316 -12
  335. package/src/providers/speech-to-text/deepgram-flux-frames.ts +83 -3
  336. package/src/providers/speech-to-text/deepgram-flux-realtime.ts +132 -4
  337. package/src/providers/speech-to-text/provider-catalog.ts +191 -3
  338. package/src/providers/speech-to-text/resolve.ts +132 -24
  339. package/src/providers/speech-to-text/vellum-managed-flux-realtime.ts +296 -0
  340. package/src/providers/types.ts +39 -0
  341. package/src/providers/usage-tracking.ts +12 -1
  342. package/src/providers/vellum/client.ts +29 -0
  343. package/src/providers/vellum-model-routing.test.ts +2 -0
  344. package/src/providers/vellum-model-routing.ts +3 -3
  345. package/src/runtime/AGENTS.md +1 -2
  346. package/src/runtime/__tests__/agent-wake.test.ts +105 -3
  347. package/src/runtime/__tests__/background-job-runner.test.ts +89 -3
  348. package/src/runtime/agent-wake.ts +44 -5
  349. package/src/runtime/background-job-runner.ts +68 -50
  350. package/src/runtime/channel-readiness-service.ts +0 -39
  351. package/src/runtime/http-server.ts +12 -4
  352. package/src/runtime/routes/__tests__/avatar-state-routes.test.ts +18 -18
  353. package/src/runtime/routes/__tests__/backup-routes.test.ts +9 -5
  354. package/src/runtime/routes/__tests__/chatgpt-subscription-auth-routes.test.ts +372 -0
  355. package/src/runtime/routes/__tests__/contact-routes.test.ts +7 -0
  356. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +96 -0
  357. package/src/runtime/routes/__tests__/inference-profiles-routes.test.ts +37 -0
  358. package/src/runtime/routes/__tests__/ingress-status-routes.test.ts +2 -1
  359. package/src/runtime/routes/__tests__/mcp-add-default-risk.test.ts +87 -0
  360. package/src/runtime/routes/acp-claude-auth-routes.ts +25 -54
  361. package/src/runtime/routes/approval-routes.ts +0 -12
  362. package/src/runtime/routes/avatar-routes.ts +17 -30
  363. package/src/runtime/routes/backup-routes.ts +2 -2
  364. package/src/runtime/routes/chatgpt-subscription-auth-routes.ts +296 -113
  365. package/src/runtime/routes/contact-prompt-routes.ts +65 -6
  366. package/src/runtime/routes/contact-routes.ts +20 -15
  367. package/src/runtime/routes/conversation-query-routes.ts +98 -10
  368. package/src/runtime/routes/conversation-routes.ts +2 -2
  369. package/src/runtime/routes/credential-routes.ts +2 -1
  370. package/src/runtime/routes/guardian-approval-interception.ts +12 -9
  371. package/src/runtime/routes/inbound-message-handler.ts +47 -48
  372. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +18 -13
  373. package/src/runtime/routes/inbound-stages/admission-policy.ts +2 -2
  374. package/src/runtime/routes/inbound-stages/edit-intercept.ts +20 -2
  375. package/src/runtime/routes/inbound-stages/reaction-intercept.ts +9 -8
  376. package/src/runtime/routes/inbound-stages/transcribe-audio.ts +1 -1
  377. package/src/runtime/routes/index.ts +2 -0
  378. package/src/runtime/routes/inference-profile-session-handler.ts +2 -2
  379. package/src/runtime/routes/inference-profiles-routes.ts +12 -7
  380. package/src/runtime/routes/inference-provider-connection-routes.ts +2 -2
  381. package/src/runtime/routes/inference-send-routes.ts +2 -2
  382. package/src/runtime/routes/ingress-status-routes.ts +2 -2
  383. package/src/runtime/routes/integrations/discord.ts +119 -0
  384. package/src/runtime/routes/llm-call-sites-routes.ts +2 -2
  385. package/src/runtime/routes/mcp-auth-routes.ts +12 -6
  386. package/src/runtime/routes/migration-routes.ts +3 -1
  387. package/src/runtime/routes/oauth-pending-flows.ts +136 -0
  388. package/src/runtime/routes/secret-routes.ts +7 -0
  389. package/src/runtime/routes/stt-routes.ts +38 -6
  390. package/src/runtime/routes/wake-conversation-routes.ts +8 -1
  391. package/src/runtime/routes/watch-routes.ts +9 -5
  392. package/src/runtime/sync/resource-sync-events.ts +2 -0
  393. package/src/runtime/trust-verdict-consumer.ts +3 -3
  394. package/src/schedule/__tests__/worker-feature-flags.test.ts +58 -0
  395. package/src/schedule/retry-policy.ts +8 -8
  396. package/src/schedule/run-script.ts +14 -23
  397. package/src/schedule/schedule-recovery.ts +0 -1
  398. package/src/schedule/scheduler.ts +89 -51
  399. package/src/schedule/worker.ts +57 -0
  400. package/src/security/__tests__/openai-device-auth.test.ts +355 -0
  401. package/src/security/ces-rpc-record-backend.ts +123 -0
  402. package/src/security/oauth2.ts +7 -0
  403. package/src/security/openai-device-auth.ts +364 -0
  404. package/src/security/secure-keys.ts +15 -0
  405. package/src/skills/available-skills.ts +10 -1
  406. package/src/skills/catalog-install.ts +19 -1
  407. package/src/skills/inline-command-runner.ts +6 -2
  408. package/src/skills/platform-compatibility.test.ts +49 -0
  409. package/src/skills/platform-compatibility.ts +65 -0
  410. package/src/skills/tool-manifest.ts +20 -0
  411. package/src/stt/__tests__/roles.test.ts +152 -0
  412. package/src/stt/daemon-batch-transcriber.ts +4 -1
  413. package/src/stt/roles.ts +180 -0
  414. package/src/stt/stt-stream-session.ts +3 -2
  415. package/src/stt/types.ts +33 -3
  416. package/src/subagent/manager.ts +12 -0
  417. package/src/telemetry/__tests__/live-voice-funnel.test.ts +34 -0
  418. package/src/telemetry/live-voice-funnel.ts +12 -2
  419. package/src/telemetry/telemetry-wire.generated.ts +6 -0
  420. package/src/telemetry/tool-audit.ts +83 -30
  421. package/src/telemetry/tool-executed-events-store.test.ts +0 -1
  422. package/src/telemetry/turn-outcome.ts +75 -4
  423. package/src/tools/client-os.ts +115 -0
  424. package/src/tools/computer-use/definitions.ts +11 -7
  425. package/src/tools/computer-use/skill-proxy-bridge.ts +2 -2
  426. package/src/tools/credentials/broker-types.ts +0 -71
  427. package/src/tools/credentials/broker.ts +6 -201
  428. package/src/tools/credentials/metadata-store.ts +65 -16
  429. package/src/tools/credentials/store.ts +2 -0
  430. package/src/tools/executor.ts +2 -8
  431. package/src/tools/host-shell.test.ts +75 -0
  432. package/src/tools/host-shell.ts +24 -0
  433. package/src/tools/host-terminal/host-shell.ts +15 -44
  434. package/src/tools/mcp/mcp-tool-factory.ts +44 -9
  435. package/src/tools/permission-checker.ts +73 -21
  436. package/src/tools/policy-context.ts +1 -0
  437. package/src/tools/shared/filesystem/image-read.ts +1 -1
  438. package/src/tools/skills/find-similar-skills.ts +10 -2
  439. package/src/tools/skills/load.ts +41 -6
  440. package/src/tools/skills/sandbox-runner.ts +8 -16
  441. package/src/tools/skills/skill-tool-factory.ts +1 -0
  442. package/src/tools/terminal/__tests__/safe-env.test.ts +64 -1
  443. package/src/tools/terminal/safe-env.ts +39 -9
  444. package/src/tools/terminal/shell.ts +12 -31
  445. package/src/tools/tool-approval-handler.ts +64 -10
  446. package/src/tools/tool-types.ts +10 -0
  447. package/src/tools/types.ts +19 -3
  448. package/src/tools/ui-surface/surface-shape-docs.ts +4 -3
  449. package/src/tools/workflows/manage-workflows.ts +2 -2
  450. package/src/tools/workflows/run-workflow.test.ts +2 -1
  451. package/src/util/__tests__/file-use.test.ts +61 -0
  452. package/src/util/__tests__/process-table.test.ts +66 -0
  453. package/src/util/__tests__/process-tree.test.ts +46 -0
  454. package/src/util/errors.ts +1 -0
  455. package/src/util/file-use.ts +91 -0
  456. package/src/util/host-process.test.ts +17 -0
  457. package/src/util/host-process.ts +75 -0
  458. package/src/util/image-conversion.ts +39 -7
  459. package/src/util/platform.ts +32 -13
  460. package/src/util/process-table.ts +249 -0
  461. package/src/util/process-tree.ts +85 -129
  462. package/src/util/provider-error-patterns.ts +20 -0
  463. package/src/util/spawn.ts +9 -2
  464. package/src/watch/__tests__/watch-retro.test.ts +48 -1
  465. package/src/watch/watch-retro.ts +46 -4
  466. package/src/workspace/git-service.ts +4 -14
  467. package/src/workspace/migrations/147-rename-colliding-backup-profile-names.ts +360 -0
  468. package/src/workspace/migrations/148-strip-unsupported-fallback-profiles.ts +117 -0
  469. package/src/workspace/migrations/149-repoint-backup-profile-selections.ts +204 -0
  470. package/src/workspace/migrations/150-stt-flux-provider-to-model-family.ts +139 -0
  471. package/src/workspace/migrations/__tests__/150-stt-flux-provider-to-model-family.test.ts +160 -0
  472. package/src/workspace/migrations/registry.ts +8 -0
  473. package/src/__tests__/permission-types.test.ts +0 -17
  474. package/src/channels/gateway-discord-admission.ts +0 -32
  475. package/src/persistence/fork-message-copy.ts +0 -239
  476. package/src/plugins/defaults/memory/__tests__/fork-message-copy.test.ts +0 -221
  477. package/src/security/__tests__/oauth2-device-code.test.ts +0 -431
  478. package/src/security/oauth2-device-code.ts +0 -312
@@ -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
  }
@@ -25,11 +25,15 @@ import type {
25
25
  import { RiskLevel } from "../permissions/types.js";
26
26
  import { resolveCapabilities } from "../runtime/capabilities.js";
27
27
  import {
28
+ type PermissionPromptOutcome,
29
+ type PermissionPromptTelemetry,
28
30
  recordToolDenied,
31
+ recordToolPermissionDecided,
29
32
  recordToolPermissionPrompted,
30
33
  } from "../telemetry/tool-audit.js";
31
34
  import { getLogger } from "../util/logger.js";
32
35
  import { resolveExecutionTarget } from "./execution-target.js";
36
+ import { getHostShell } from "./host-shell.js";
33
37
  import { buildPolicyContext } from "./policy-context.js";
34
38
  import { isSideEffectTool } from "./side-effects.js";
35
39
  import type { Tool, ToolContext } from "./types.js";
@@ -124,6 +128,7 @@ export class PermissionChecker {
124
128
  undefined,
125
129
  undefined,
126
130
  context.signal,
131
+ getHostShell(context, input),
127
132
  );
128
133
  const { level: risk, reason: riskReason } = classification;
129
134
  const riskLevel: string = risk;
@@ -168,6 +173,7 @@ export class PermissionChecker {
168
173
  policyContext.conversationId,
169
174
  policyContext.executionContext,
170
175
  cellQuery,
176
+ policyContext.requesterContactId,
171
177
  );
172
178
  const riskThreshold = conversationThreshold as RiskThreshold;
173
179
 
@@ -210,7 +216,6 @@ export class PermissionChecker {
210
216
  reason: result.reason,
211
217
  riskLevel,
212
218
  durationMs: Date.now() - startTime,
213
- wasPrompted: false,
214
219
  });
215
220
  const provenance = mapApprovalProvenance("denied", {});
216
221
  return {
@@ -249,7 +254,6 @@ export class PermissionChecker {
249
254
  "Inline-command skill load requires human approval; no interactive client connected",
250
255
  riskLevel,
251
256
  durationMs: Date.now() - startTime,
252
- wasPrompted: false,
253
257
  });
254
258
  return {
255
259
  allowed: false,
@@ -316,6 +320,7 @@ export class PermissionChecker {
316
320
  context.conversationId,
317
321
  "background",
318
322
  cellQuery,
323
+ context.requesterContactId,
319
324
  );
320
325
  const thresholdOrdinal: Record<string, number> = {
321
326
  none: -1,
@@ -361,7 +366,6 @@ export class PermissionChecker {
361
366
  reason: "Non-interactive session: no client to approve prompt",
362
367
  riskLevel,
363
368
  durationMs: Date.now() - startTime,
364
- wasPrompted: false,
365
369
  });
366
370
  return {
367
371
  allowed: false,
@@ -385,27 +389,76 @@ export class PermissionChecker {
385
389
  persistentDecisionsAllowed: !context.requireFreshApproval,
386
390
  };
387
391
 
388
- recordToolPermissionPrompted(name);
389
-
390
- const response = await this.prompter.prompt(
391
- name,
392
- input,
392
+ // Grouping dimensions shared by this prompt and its decision, so the
393
+ // two series can be diffed by risk and by access preset without
394
+ // parsing anything out of the event name.
395
+ const promptTelemetry: PermissionPromptTelemetry = {
396
+ toolName: name,
393
397
  riskLevel,
394
- promptOptions.allowlistOptions,
395
- promptOptions.scopeOptions,
396
- previewDiff,
397
- context.conversationId,
398
- executionTarget,
399
- promptOptions.persistentDecisionsAllowed,
400
- context.signal,
401
- context.toolUseId,
402
- riskReason,
403
- getIsContainerized(),
404
- classification.directoryScopeOptions,
405
- );
398
+ riskThreshold,
399
+ surface: context.executionChannel,
400
+ conversationId: context.conversationId,
401
+ };
402
+ // An already-aborted signal makes the prompter return without
403
+ // registering or sending a confirmation request, so nothing reaches
404
+ // the user and neither half of the pair is recorded. `prompt()` reads
405
+ // the same signal synchronously on entry, with no await in between, so
406
+ // this check and its check always agree.
407
+ const promptIsSurfaced = context.signal?.aborted !== true;
408
+ if (promptIsSurfaced) {
409
+ recordToolPermissionPrompted(promptTelemetry);
410
+ }
411
+
412
+ let response: Awaited<ReturnType<PermissionPrompter["prompt"]>>;
413
+ try {
414
+ response = await this.prompter.prompt(
415
+ name,
416
+ input,
417
+ riskLevel,
418
+ promptOptions.allowlistOptions,
419
+ promptOptions.scopeOptions,
420
+ previewDiff,
421
+ context.conversationId,
422
+ executionTarget,
423
+ promptOptions.persistentDecisionsAllowed,
424
+ context.signal,
425
+ context.toolUseId,
426
+ riskReason,
427
+ getIsContainerized(),
428
+ classification.directoryScopeOptions,
429
+ );
430
+ } catch (err) {
431
+ // The prompter rejected rather than resolved: it was disposed while
432
+ // this prompt was outstanding (client disconnect, conversation
433
+ // teardown). Nobody answered, so the prompt is abandoned.
434
+ if (promptIsSurfaced) {
435
+ recordToolPermissionDecided(promptTelemetry, "abandoned");
436
+ }
437
+ throw err;
438
+ }
406
439
 
407
440
  const decision = response.decision;
408
441
 
442
+ // A prompt that ends without a human answer is abandoned, not denied.
443
+ // The daemon resolves a timeout, a superseding user message, and a
444
+ // turn abort to `deny` so the agent loop stops, but none of the three
445
+ // is a decision the user made.
446
+ let outcome: PermissionPromptOutcome;
447
+ if (
448
+ response.wasTimeout === true ||
449
+ response.wasSystemCancel === true ||
450
+ response.wasAbort === true
451
+ ) {
452
+ outcome = "abandoned";
453
+ } else if (decision === "deny") {
454
+ outcome = "deny";
455
+ } else {
456
+ outcome = "allow";
457
+ }
458
+ if (promptIsSurfaced) {
459
+ recordToolPermissionDecided(promptTelemetry, outcome);
460
+ }
461
+
409
462
  if (decision === "deny") {
410
463
  const contextualDenial =
411
464
  typeof response.decisionContext === "string"
@@ -426,7 +479,6 @@ export class PermissionChecker {
426
479
  reason: denialReason,
427
480
  riskLevel,
428
481
  durationMs: Date.now() - startTime,
429
- wasPrompted: true,
430
482
  });
431
483
  return {
432
484
  allowed: false,
@@ -62,6 +62,7 @@ export function buildPolicyContext(
62
62
  const originSignals = {
63
63
  requestOrigin: context?.requestOrigin,
64
64
  ...channelCoordinatesFromToolContext(context),
65
+ requesterContactId: context?.requesterContactId,
65
66
  // Precompute the proc-to-skills gate — the v3 tier being active, i.e.
66
67
  // memory on AND v3 live — here so the permission checker, a leaf module
67
68
  // that must not read config, can deny the memory-retrospective
@@ -76,7 +76,7 @@ async function buildImageToolResult(
76
76
  }
77
77
 
78
78
  // Detect actual format from magic bytes - never trust the file extension
79
- // alone, since sips converts to JPEG and files can be misnamed.
79
+ // alone, since conversion produces JPEG and files can be misnamed.
80
80
  const detectedType = detectMediaType(buffer);
81
81
  if (!detectedType) {
82
82
  return {
@@ -3,6 +3,10 @@ import { loadSkillCatalog } from "../../config/skills.js";
3
3
  import { nearestExistingSkills } from "../../plugins/defaults/memory/v3/candidate-match.js";
4
4
  import { readInstallMeta } from "../../skills/install-meta.js";
5
5
  import { getManagedSkillDir } from "../../skills/managed-store.js";
6
+ import {
7
+ filterSkillsByPlatform,
8
+ type SkillPlatform,
9
+ } from "../../skills/platform-compatibility.js";
6
10
  import type { OwnerInfo, ToolContext, ToolExecutionResult } from "../types.js";
7
11
 
8
12
  /**
@@ -45,6 +49,7 @@ export async function executeFindSimilarSkills(
45
49
  description: string;
46
50
  source: SkillSource;
47
51
  owner?: OwnerInfo;
52
+ platforms?: SkillPlatform[];
48
53
  }[];
49
54
  } = {},
50
55
  ): Promise<ToolExecutionResult> {
@@ -92,8 +97,11 @@ export async function executeFindSimilarSkills(
92
97
  // slices only those. Filtering after the limit would let out-of-scope
93
98
  // high-rank matches consume slots and starve usable in-scope skills out of
94
99
  // the result. `null` set = pass the catalog through unchanged.
95
- const scopedCatalog =
96
- enabledPluginSet === null ? catalog : catalog.filter((s) => !outOfScope(s));
100
+ const scopedCatalog = filterSkillsByPlatform(
101
+ enabledPluginSet === null
102
+ ? catalog
103
+ : catalog.filter((skill) => !outOfScope(skill)),
104
+ );
97
105
 
98
106
  const hits = await findNearest(goal, {
99
107
  limit,
@@ -21,10 +21,15 @@ import {
21
21
  validateIncludeCycles,
22
22
  } from "../../skills/include-graph.js";
23
23
  import { renderInlineCommands } from "../../skills/inline-command-render.js";
24
+ import {
25
+ isSkillCompatibleWithPlatform,
26
+ skillPlatformUnavailableMessage,
27
+ } from "../../skills/platform-compatibility.js";
24
28
  import { parseToolManifestFile } from "../../skills/tool-manifest.js";
25
29
  import { computeSkillVersionHash } from "../../skills/version-hash.js";
26
30
  import { getLogger } from "../../util/logger.js";
27
31
  import { getWorkspaceDirDisplay } from "../../util/platform.js";
32
+ import { supportsClientOsForSkillTool } from "../client-os.js";
28
33
  import type {
29
34
  ToolContext,
30
35
  ToolDefinition,
@@ -83,8 +88,23 @@ function loadToolManifest(
83
88
  */
84
89
  function formatToolSchemas(
85
90
  manifest: SkillToolManifest,
91
+ context: Pick<
92
+ ToolContext,
93
+ "clientOs" | "transportInterface" | "sourceActorPrincipalId"
94
+ >,
86
95
  childSkillName?: string,
87
- ): string {
96
+ ): string | undefined {
97
+ const tools = manifest.tools.filter((tool) =>
98
+ supportsClientOsForSkillTool(tool.supported_client_os, tool.name, {
99
+ clientOs: context.clientOs,
100
+ transportInterface: context.transportInterface,
101
+ sourceActorPrincipalId: context.sourceActorPrincipalId,
102
+ }),
103
+ );
104
+ if (tools.length === 0) {
105
+ return undefined;
106
+ }
107
+
88
108
  const lines: string[] = childSkillName
89
109
  ? [`### Tools from ${childSkillName}`, ""]
90
110
  : [
@@ -96,7 +116,7 @@ function formatToolSchemas(
96
116
 
97
117
  const toolHeadingLevel = childSkillName ? "####" : "###";
98
118
 
99
- for (const tool of manifest.tools) {
119
+ for (const tool of tools) {
100
120
  lines.push(`${toolHeadingLevel} ${tool.name}`);
101
121
  lines.push(
102
122
  tool.description.replaceAll("{workspaceDir}", getWorkspaceDirDisplay()),
@@ -211,6 +231,13 @@ export const skillLoadTool = {
211
231
 
212
232
  const skill = loaded.skill;
213
233
 
234
+ if (!isSkillCompatibleWithPlatform(skill)) {
235
+ return {
236
+ content: `Error: ${skillPlatformUnavailableMessage(skill.id, skill)}`,
237
+ isError: true,
238
+ };
239
+ }
240
+
214
241
  // Per-chat plugin scope gate: a plugin-owned skill whose owning plugin is
215
242
  // outside the conversation's effective set must not have its instructions
216
243
  // loaded. Mirrors the projection/tools filter's owner-id lookup
@@ -403,7 +430,7 @@ export const skillLoadTool = {
403
430
  // Load tool schemas for the main skill
404
431
  const mainManifest = loadToolManifest(skill.directoryPath);
405
432
  const toolSchemasSection = mainManifest
406
- ? formatToolSchemas(mainManifest)
433
+ ? formatToolSchemas(mainManifest, context)
407
434
  : undefined;
408
435
 
409
436
  // Build immediate children metadata section and load included skill bodies
@@ -422,6 +449,9 @@ export const skillLoadTool = {
422
449
  if (childOutOfPluginScope(child)) {
423
450
  continue;
424
451
  }
452
+ if (!isSkillCompatibleWithPlatform(child)) {
453
+ continue;
454
+ }
425
455
  const childFlagKey = skillFlagKey(child);
426
456
  if (
427
457
  childFlagKey &&
@@ -518,10 +548,15 @@ export const skillLoadTool = {
518
548
  childLoaded.skill.directoryPath,
519
549
  );
520
550
  if (childManifest) {
521
- anyChildHasTools = true;
522
- includedBodies.push(
523
- formatToolSchemas(childManifest, childLoaded.skill.displayName),
551
+ const childSchemas = formatToolSchemas(
552
+ childManifest,
553
+ context,
554
+ childLoaded.skill.displayName,
524
555
  );
556
+ if (childSchemas) {
557
+ anyChildHasTools = true;
558
+ includedBodies.push(childSchemas);
559
+ }
525
560
  }
526
561
  }
527
562
  }
@@ -5,6 +5,10 @@ import { join, resolve } from "node:path";
5
5
 
6
6
  import { conversationRevealNonce } from "../../runtime/reveal-nonce.js";
7
7
  import { computeSkillVersionHash } from "../../skills/version-hash.js";
8
+ import {
9
+ buildShellInvocation,
10
+ terminateProcessTree,
11
+ } from "../../util/host-process.js";
8
12
  import { safeStringSlice } from "../../util/unicode.js";
9
13
  import { buildSanitizedEnv } from "../terminal/safe-env.js";
10
14
  import type { ToolContext, ToolExecutionResult } from "../types.js";
@@ -139,7 +143,7 @@ function spawnRunner(
139
143
  let timedOut = false;
140
144
 
141
145
  const bunRunCmd = "bun run __skill_runner.ts";
142
- const wrapped = { command: "bash", args: ["-c", "--", bunRunCmd] };
146
+ const wrapped = buildShellInvocation(bunRunCmd);
143
147
 
144
148
  const env = buildSanitizedEnv();
145
149
  env.__SKILL_INPUT_JSON = JSON.stringify(input);
@@ -161,28 +165,16 @@ function spawnRunner(
161
165
 
162
166
  const timer = setTimeout(() => {
163
167
  timedOut = true;
164
- try {
165
- process.kill(-child.pid!, "SIGKILL");
166
- } catch {
167
- // Process group may have already exited.
168
- }
168
+ terminateProcessTree(child);
169
169
  }, timeoutMs);
170
170
 
171
171
  // Cooperative cancellation via AbortSignal
172
172
  const onAbort = () => {
173
- try {
174
- process.kill(-child.pid!, "SIGKILL");
175
- } catch {
176
- // Process group may have already exited.
177
- }
173
+ terminateProcessTree(child);
178
174
  };
179
175
  if (context.signal) {
180
176
  if (context.signal.aborted) {
181
- try {
182
- process.kill(-child.pid!, "SIGKILL");
183
- } catch {
184
- // Process group may have already exited.
185
- }
177
+ terminateProcessTree(child);
186
178
  } else {
187
179
  context.signal.addEventListener("abort", onAbort, { once: true });
188
180
  }
@@ -39,6 +39,7 @@ export function createSkillTool(
39
39
  category: entry.category,
40
40
  defaultRiskLevel: riskMap[entry.risk],
41
41
  executionTarget: entry.execution_target as ExecutionTarget,
42
+ supportedClientOs: entry.supported_client_os,
42
43
 
43
44
  input_schema: entry.input_schema as object,
44
45
 
@@ -1,6 +1,10 @@
1
1
  import { afterEach, describe, expect, test } from "bun:test";
2
2
 
3
- import { buildSanitizedEnv, SAFE_ENV_VARS } from "../safe-env.js";
3
+ import {
4
+ buildSanitizedEnv,
5
+ SAFE_ENV_VARS,
6
+ WINDOWS_SAFE_ENV_VARS,
7
+ } from "../safe-env.js";
4
8
 
5
9
  describe("safe-env Qdrant forwarding", () => {
6
10
  const priorPort = process.env.QDRANT_HTTP_PORT;
@@ -35,3 +39,62 @@ describe("safe-env Qdrant forwarding", () => {
35
39
  expect(env.QDRANT_URL).toBeUndefined();
36
40
  });
37
41
  });
42
+
43
+ describe("safe-env Windows forwarding", () => {
44
+ test("forwards runtime paths only to Windows children", () => {
45
+ const keys = [
46
+ "SystemRoot",
47
+ "COMSPEC",
48
+ "USERPROFILE",
49
+ "LOCALAPPDATA",
50
+ "PATHEXT",
51
+ ] as const;
52
+ const previous = Object.fromEntries(
53
+ keys.map((key) => [key, process.env[key]]),
54
+ );
55
+
56
+ try {
57
+ process.env.SystemRoot = "C:\\Windows";
58
+ process.env.COMSPEC = "C:\\Windows\\System32\\cmd.exe";
59
+ process.env.USERPROFILE = "C:\\Users\\Alice";
60
+ process.env.LOCALAPPDATA = "C:\\Users\\Alice\\AppData\\Local";
61
+ process.env.PATHEXT = ".COM;.EXE;.BAT;.CMD";
62
+
63
+ expect(WINDOWS_SAFE_ENV_VARS).toContain("SystemRoot");
64
+ const windowsEnv = buildSanitizedEnv("win32");
65
+ expect(windowsEnv.SystemRoot).toBe("C:\\Windows");
66
+ expect(windowsEnv.COMSPEC).toBe("C:\\Windows\\System32\\cmd.exe");
67
+ expect(windowsEnv.USERPROFILE).toBe("C:\\Users\\Alice");
68
+ expect(windowsEnv.LOCALAPPDATA).toBe("C:\\Users\\Alice\\AppData\\Local");
69
+ expect(windowsEnv.PATHEXT).toBe(".COM;.EXE;.BAT;.CMD");
70
+
71
+ const posixEnv = buildSanitizedEnv("linux");
72
+ expect(posixEnv.SystemRoot).toBeUndefined();
73
+ expect(posixEnv.COMSPEC).toBeUndefined();
74
+ expect(posixEnv.USERPROFILE).toBeUndefined();
75
+ } finally {
76
+ for (const key of keys) {
77
+ const value = previous[key];
78
+ if (value == null) {
79
+ delete process.env[key];
80
+ } else {
81
+ process.env[key] = value;
82
+ }
83
+ }
84
+ }
85
+ });
86
+
87
+ test("matches Windows environment names case-insensitively", () => {
88
+ const windowsEnv = buildSanitizedEnv("win32", {
89
+ Path: "C:\\Windows\\System32;C:\\Program Files\\Vellum",
90
+ systemroot: "C:\\Windows",
91
+ ComSpec: "C:\\Windows\\System32\\cmd.exe",
92
+ });
93
+
94
+ expect(windowsEnv.PATH).toBe(
95
+ "C:\\Windows\\System32;C:\\Program Files\\Vellum",
96
+ );
97
+ expect(windowsEnv.SystemRoot).toBe("C:\\Windows");
98
+ expect(windowsEnv.COMSPEC).toBe("C:\\Windows\\System32\\cmd.exe");
99
+ });
100
+ });
@@ -7,6 +7,8 @@
7
7
  */
8
8
  import { readdirSync } from "node:fs";
9
9
 
10
+ import { pathListDelimiter } from "@vellumai/environments/shell";
11
+
10
12
  import { getGatewayInternalBaseUrl } from "../../config/env.js";
11
13
  import { getDataDir, getWorkspaceDir } from "../../util/platform.js";
12
14
 
@@ -76,6 +78,18 @@ export const SAFE_ENV_VARS = [
76
78
  "VELLUM_BACKUP_KEY_PATH",
77
79
  ] as const;
78
80
 
81
+ export const WINDOWS_SAFE_ENV_VARS = [
82
+ "SystemRoot",
83
+ "COMSPEC",
84
+ "USERPROFILE",
85
+ "APPDATA",
86
+ "LOCALAPPDATA",
87
+ "TEMP",
88
+ "TMP",
89
+ "PATHEXT",
90
+ "SystemDrive",
91
+ ] as const;
92
+
79
93
  export const KATA_SAFE_ENV_VARS = [
80
94
  "VELLUM_APT_DATA_ROOT",
81
95
  "VELLUM_APT_DATA_SUITE",
@@ -165,26 +179,42 @@ export const ALWAYS_INJECTED_ENV_VARS = [
165
179
  function appendUniquePathEntries(
166
180
  value: string | undefined,
167
181
  entries: readonly string[],
182
+ separator = pathListDelimiter(),
168
183
  ): string {
169
- const parts = value ? value.split(":").filter(Boolean) : [];
184
+ const parts = value ? value.split(separator).filter(Boolean) : [];
170
185
  for (const entry of entries) {
171
186
  if (!parts.includes(entry)) {
172
187
  parts.push(entry);
173
188
  }
174
189
  }
175
- return parts.join(":");
190
+ return parts.join(separator);
176
191
  }
177
192
 
178
- export function buildSanitizedEnv(): Record<string, string> {
193
+ export function buildSanitizedEnv(
194
+ hostPlatform: NodeJS.Platform = process.platform,
195
+ sourceEnv: NodeJS.ProcessEnv = process.env,
196
+ ): Record<string, string> {
179
197
  const env: Record<string, string> = {};
180
- const isKataRuntime = isKataFamilyRuntime(process.env.VELLUM_SANDBOX_RUNTIME);
198
+ const isKataRuntime = isKataFamilyRuntime(sourceEnv.VELLUM_SANDBOX_RUNTIME);
199
+ const platformVars =
200
+ hostPlatform === "win32" ? WINDOWS_SAFE_ENV_VARS : ([] as const);
181
201
  const safeEnvVars = isKataRuntime
182
- ? [...SAFE_ENV_VARS, ...KATA_SAFE_ENV_VARS]
183
- : SAFE_ENV_VARS;
202
+ ? [...SAFE_ENV_VARS, ...platformVars, ...KATA_SAFE_ENV_VARS]
203
+ : [...SAFE_ENV_VARS, ...platformVars];
184
204
 
205
+ const windowsEnv =
206
+ hostPlatform === "win32"
207
+ ? new Map(
208
+ Object.entries(sourceEnv).map(([key, value]) => [
209
+ key.toLowerCase(),
210
+ value,
211
+ ]),
212
+ )
213
+ : null;
185
214
  for (const key of safeEnvVars) {
186
- if (process.env[key] != null) {
187
- env[key] = process.env[key]!;
215
+ const value = windowsEnv?.get(key.toLowerCase()) ?? sourceEnv[key];
216
+ if (value != null) {
217
+ env[key] = value;
188
218
  }
189
219
  }
190
220
  if (isKataRuntime) {
@@ -207,7 +237,7 @@ export function buildSanitizedEnv(): Record<string, string> {
207
237
  env.BUN_INSTALL = `${env.HOME}/.bun`;
208
238
  env.PATH = appendUniquePathEntries(
209
239
  `${env.PYTHONUSERBASE}/bin:${env.BUN_INSTALL}/bin`,
210
- env.PATH.split(":").filter(Boolean),
240
+ env.PATH.split(pathListDelimiter()).filter(Boolean),
211
241
  );
212
242
  }
213
243
  }