@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
@@ -733,8 +733,33 @@ export async function runAgentLoopImpl(
733
733
  // the `finally` (before processing clears, so the reporter's settled-turn
734
734
  // barrier guarantees the stamp ships with the turn event). Unset = the turn
735
735
  // replied normally and carries no stamp.
736
+ const failedOutcomeFromClassification = (classified: {
737
+ code: string;
738
+ userMessage: string;
739
+ errorCategory: string;
740
+ connectionName?: string;
741
+ profileName?: string;
742
+ }): NonNullable<typeof abnormalOutcome> => ({
743
+ outcome: "failed",
744
+ failureCode: classified.code,
745
+ failureMessage: classified.userMessage,
746
+ failureCategory: classified.errorCategory,
747
+ ...(classified.connectionName
748
+ ? { failureConnection: classified.connectionName }
749
+ : {}),
750
+ ...(classified.profileName
751
+ ? { failureProfile: classified.profileName }
752
+ : {}),
753
+ });
736
754
  let abnormalOutcome:
737
- | { outcome: "failed" | "cancelled"; failureCode?: string }
755
+ | {
756
+ outcome: "failed" | "cancelled";
757
+ failureCode?: string;
758
+ failureMessage?: string;
759
+ failureCategory?: string;
760
+ failureConnection?: string;
761
+ failureProfile?: string;
762
+ }
738
763
  | undefined;
739
764
  // True once a replied terminal SSE (message_complete / generation_handoff)
740
765
  // has been emitted. Guards the catch block: an error thrown by the
@@ -777,6 +802,10 @@ export async function runAgentLoopImpl(
777
802
  try {
778
803
  stampTurnOutcome(userMessageId, abnormalOutcome.outcome, {
779
804
  failureCode: abnormalOutcome.failureCode,
805
+ failureMessage: abnormalOutcome.failureMessage,
806
+ failureCategory: abnormalOutcome.failureCategory,
807
+ failureConnection: abnormalOutcome.failureConnection,
808
+ failureProfile: abnormalOutcome.failureProfile,
780
809
  });
781
810
  } catch (err) {
782
811
  rlog.warn(
@@ -858,6 +887,7 @@ export async function runAgentLoopImpl(
858
887
  abnormalOutcome = {
859
888
  outcome: "failed",
860
889
  failureCode: DISK_PRESSURE_ERROR_CODE,
890
+ failureMessage: message,
861
891
  };
862
892
  rlog.warn(
863
893
  { reason: diskPressureDecision.reason },
@@ -1382,17 +1412,16 @@ export async function runAgentLoopImpl(
1382
1412
  );
1383
1413
  // Exhausted-overflow exit: the reduction ladder is spent and the turn
1384
1414
  // ends without a real reply, so label it failed for telemetry.
1385
- abnormalOutcome = { outcome: "failed", failureCode: classified.code };
1415
+ abnormalOutcome = failedOutcomeFromClassification(classified);
1386
1416
  onEvent(buildConversationErrorMessage(ctx.conversationId, classified));
1387
1417
  } else if (
1388
1418
  overflowTerminalReason === "budget_yield_unrecovered" &&
1389
1419
  !abortController.signal.aborted
1390
1420
  ) {
1391
1421
  budgetYieldClassification = budgetYieldUnrecoveredClassification();
1392
- abnormalOutcome = {
1393
- outcome: "failed",
1394
- failureCode: budgetYieldClassification.code,
1395
- };
1422
+ abnormalOutcome = failedOutcomeFromClassification(
1423
+ budgetYieldClassification,
1424
+ );
1396
1425
  onEvent(
1397
1426
  buildConversationErrorMessage(
1398
1427
  ctx.conversationId,
@@ -1548,6 +1577,16 @@ export async function runAgentLoopImpl(
1548
1577
  ...(state.providerErrorCode
1549
1578
  ? { failureCode: state.providerErrorCode }
1550
1579
  : {}),
1580
+ failureMessage: state.providerErrorUserMessage,
1581
+ ...(state.providerErrorCategory
1582
+ ? { failureCategory: state.providerErrorCategory }
1583
+ : {}),
1584
+ ...(state.providerErrorConnection
1585
+ ? { failureConnection: state.providerErrorConnection }
1586
+ : {}),
1587
+ ...(state.providerErrorProfile
1588
+ ? { failureProfile: state.providerErrorProfile }
1589
+ : {}),
1551
1590
  };
1552
1591
  // Drop any reservation stranded by the failed LLM call. The B3
1553
1592
  // pre-allocation path reserves an empty assistant row at
@@ -1684,9 +1723,23 @@ export async function runAgentLoopImpl(
1684
1723
  tokens: state.lastCallInputTokens,
1685
1724
  maxTokens: resolveCurrentMaxInputTokens(),
1686
1725
  },
1726
+ // When the turn's last LLM call was rerouted to a backup profile, that
1727
+ // profile is what served the tokens being recorded, so it outranks the
1728
+ // conversation's own resolution, which knows nothing about the
1729
+ // reroute. `provider` and `model` on this same row already follow the
1730
+ // backup (via `state.exchangeProviderName` / `state.model`), so
1731
+ // attributing the profile from the primary would write a row that
1732
+ // contradicts itself. `forceOverrideProfile` floats it above the
1733
+ // call-site profile exactly as the fallback dispatch did.
1687
1734
  {
1688
1735
  callSite: turnCallSite,
1689
- overrideProfile: resolveCurrentOverrideProfile() ?? null,
1736
+ overrideProfile:
1737
+ state.exchangeInferenceProfile ??
1738
+ resolveCurrentOverrideProfile() ??
1739
+ null,
1740
+ ...(state.exchangeInferenceProfile !== undefined
1741
+ ? { forceOverrideProfile: true }
1742
+ : {}),
1690
1743
  },
1691
1744
  turnCronRunId,
1692
1745
  );
@@ -1860,7 +1913,7 @@ export async function runAgentLoopImpl(
1860
1913
  ...turnErrorAttribution(),
1861
1914
  });
1862
1915
  if (!turnReplied) {
1863
- abnormalOutcome = { outcome: "failed", failureCode: classified.code };
1916
+ abnormalOutcome = failedOutcomeFromClassification(classified);
1864
1917
  }
1865
1918
  onEvent({
1866
1919
  type: "error",
@@ -1975,6 +2028,13 @@ function emitUsage(
1975
2028
  attribution?: {
1976
2029
  callSite: LLMCallSite | null;
1977
2030
  overrideProfile?: string | null;
2031
+ /**
2032
+ * Float `overrideProfile` above the call-site profile, matching how the
2033
+ * dispatch path floated it. Set when the profile being attributed is the
2034
+ * one a reroute actually served under rather than the caller's own
2035
+ * resolution.
2036
+ */
2037
+ forceOverrideProfile?: boolean;
1978
2038
  },
1979
2039
  cronRunId: string | null = null,
1980
2040
  ): void {
@@ -2051,6 +2111,8 @@ export async function applyCompactionResult(
2051
2111
  summaryRawResponses?: unknown[];
2052
2112
  summaryCallSite?: LLMCallSite;
2053
2113
  summaryOverrideProfile?: string | null;
2114
+ summaryActualProvider?: string;
2115
+ summaryActualInferenceProfile?: string;
2054
2116
  },
2055
2117
  onEvent: (msg: AssistantEvent) => void,
2056
2118
  reqId: string | null,
@@ -2125,12 +2187,26 @@ export async function applyCompactionResult(
2125
2187
  result.summaryCacheCreationInputTokens ?? 0,
2126
2188
  result.summaryCacheReadInputTokens ?? 0,
2127
2189
  collapseRawResponses(result.summaryRawResponses),
2128
- undefined /* providerName */,
2190
+ // The provider that actually served the summary, which follows a fallback
2191
+ // reroute the way `summaryModel` already did. Undefined only where no call
2192
+ // was made, and `emitUsage` then falls back to `ctx.provider.name` exactly
2193
+ // as before.
2194
+ result.summaryActualProvider /* providerName */,
2129
2195
  1 /* llmCallCount */,
2130
2196
  undefined /* contextWindow */,
2197
+ // Same rule as the main-agent row above, applied to the compaction row:
2198
+ // a rerouted summary is attributed to the profile that answered, not to
2199
+ // the one the compactor resolved before the call. The two call sites set
2200
+ // this parameter independently off their own state and never share it.
2131
2201
  {
2132
2202
  callSite: result.summaryCallSite ?? null,
2133
- overrideProfile: result.summaryOverrideProfile ?? null,
2203
+ overrideProfile:
2204
+ result.summaryActualInferenceProfile ??
2205
+ result.summaryOverrideProfile ??
2206
+ null,
2207
+ ...(result.summaryActualInferenceProfile !== undefined
2208
+ ? { forceOverrideProfile: true }
2209
+ : {}),
2134
2210
  },
2135
2211
  options.cronRunId ?? null,
2136
2212
  );
@@ -26,6 +26,7 @@ import {
26
26
  } from "../util/errors.js";
27
27
  import {
28
28
  INSUFFICIENT_CREDITS_PATTERNS,
29
+ isChatTemplateFailureError,
29
30
  isVisionNotSupportedError,
30
31
  } from "../util/provider-error-patterns.js";
31
32
 
@@ -564,6 +565,9 @@ function classifyCore(
564
565
  if (isVisionNotSupportedError(message)) {
565
566
  return visionNotSupportedClassification();
566
567
  }
568
+ if (isChatTemplateFailureError(message)) {
569
+ return requestShapeUnsupportedClassification();
570
+ }
567
571
  // Extract the provider detail after "API error (NNN): " prefix
568
572
  const detailMatch = message.match(/API error \(\d+\):\s*(.+)/i);
569
573
  const detail = detailMatch?.[1];
@@ -654,6 +658,8 @@ function reasonToClassification(
654
658
  return contextTooLargeClassification();
655
659
  case "vision_unsupported":
656
660
  return visionNotSupportedClassification();
661
+ case "request_shape_unsupported":
662
+ return requestShapeUnsupportedClassification();
657
663
  // Two producers share this reason: SDK transport failures that never got
658
664
  // a response (OpenAI APIConnectionError), and Gemini responses whose empty
659
665
  // body reveals a proxy/egress filter intercepting the request. The copy
@@ -908,6 +914,25 @@ function visionNotSupportedClassification(): Omit<
908
914
  };
909
915
  }
910
916
 
917
+ /**
918
+ * Classification for a request rejected by the endpoint's server-side
919
+ * chat-template renderer. These endpoints choke on richer request shapes
920
+ * (tool calls, images, structured message content), so the copy points the
921
+ * user at a capability mismatch instead of the raw template error.
922
+ */
923
+ function requestShapeUnsupportedClassification(): Omit<
924
+ ClassifiedConversationError,
925
+ "debugDetails"
926
+ > {
927
+ return {
928
+ code: "PROVIDER_API",
929
+ userMessage:
930
+ "This model's provider couldn't process the request format (tool calls or images may not be supported). Switch to a different model in Settings → Models & Services and try again.",
931
+ retryable: false,
932
+ errorCategory: "request_shape_unsupported",
933
+ };
934
+ }
935
+
911
936
  /**
912
937
  * Build a user-facing message that names the exact profile / connection
913
938
  * to fix when one is known, falling back to a generic phrase otherwise.
@@ -74,11 +74,9 @@ export class ConversationEvictor {
74
74
  getSubagentManager().abortAllForParent(conversationId);
75
75
  }
76
76
 
77
- /** Protect conversations with running or pending subagents from eviction. */
77
+ /** Protect conversations with in-flight subagents from eviction. */
78
78
  shouldProtect(conversationId: string): boolean {
79
- return getSubagentManager()
80
- .getChildrenOf(conversationId)
81
- .some((c) => c.status === "running" || c.status === "pending");
79
+ return getSubagentManager().hasActiveChildren(conversationId);
82
80
  }
83
81
 
84
82
  /** Record an access for the given conversation (resets its idle clock). */
@@ -745,6 +745,7 @@ export function stripNowScratchpad(messages: Message[]): Message[] {
745
745
  */
746
746
  export function buildChannelCapabilityBlock(
747
747
  caps: ChannelCapabilities,
748
+ clientOs: string | undefined = caps.clientOS,
748
749
  ): string | null {
749
750
  // Happy path: desktop with full capabilities and no special context — skip injection.
750
751
  if (
@@ -752,7 +753,8 @@ export function buildChannelCapabilityBlock(
752
753
  caps.supportsDynamicUi &&
753
754
  caps.supportsVoiceInput &&
754
755
  !isGroupChatType(caps.chatType) &&
755
- caps.clientOS !== "macos"
756
+ clientOs !== "macos" &&
757
+ clientOs !== "windows"
756
758
  ) {
757
759
  return null;
758
760
  }
@@ -762,17 +764,24 @@ export function buildChannelCapabilityBlock(
762
764
  lines.push(`dashboard_capable: ${caps.dashboardCapable}`);
763
765
  lines.push(`supports_dynamic_ui: ${caps.supportsDynamicUi}`);
764
766
  lines.push(`supports_voice_input: ${caps.supportsVoiceInput}`);
765
- if (caps.clientOS) {
766
- lines.push(`client_os: ${caps.clientOS}`);
767
+ if (clientOs) {
768
+ lines.push(`client_os: ${clientOs}`);
767
769
  }
768
770
 
769
- if (caps.clientOS === "macos") {
771
+ if (clientOs === "macos") {
770
772
  lines.push("");
771
773
  lines.push(
772
774
  "On macOS, prefer osascript/CLI via `host_bash` over computer use tools, which take over the user's cursor. Use foreground computer use only when no scripting alternative exists or the user explicitly asks.",
773
775
  );
774
776
  }
775
777
 
778
+ if (clientOs === "windows") {
779
+ lines.push("");
780
+ lines.push(
781
+ "On Windows, `host_bash` runs PowerShell. Use PowerShell syntax and Windows paths. Prefer PowerShell or CLI automation over foreground computer use when either can complete the task reliably.",
782
+ );
783
+ }
784
+
776
785
  if (!caps.dashboardCapable) {
777
786
  lines.push("");
778
787
  lines.push("CHANNEL CONSTRAINTS:");
@@ -834,8 +843,9 @@ export function buildChannelCapabilityBlock(
834
843
  export function injectChannelCapabilityContext(
835
844
  message: Message,
836
845
  caps: ChannelCapabilities,
846
+ clientOs?: string,
837
847
  ): Message {
838
- const block = buildChannelCapabilityBlock(caps);
848
+ const block = buildChannelCapabilityBlock(caps, clientOs);
839
849
  if (block === null) {
840
850
  return message;
841
851
  }
@@ -2642,8 +2652,10 @@ export async function applyRuntimeInjections(
2642
2652
  if (channelCapabilities) {
2643
2653
  const userTail = result[result.length - 1];
2644
2654
  if (userTail && userTail.role === "user") {
2645
- const channelCapabilityBlock =
2646
- buildChannelCapabilityBlock(channelCapabilities);
2655
+ const channelCapabilityBlock = buildChannelCapabilityBlock(
2656
+ channelCapabilities,
2657
+ clientOs,
2658
+ );
2647
2659
  if (channelCapabilityBlock !== null) {
2648
2660
  channelCapabilitiesCaptured = channelCapabilityBlock;
2649
2661
  result = [
@@ -24,6 +24,7 @@ import type { ActiveSkillEntry } from "../skills/active-skill-tools.js";
24
24
  import { deriveActiveSkills } from "../skills/active-skill-tools.js";
25
25
  import { getCachedCatalogSync } from "../skills/catalog-cache.js";
26
26
  import { readInstallMeta, touchSkillLastUsed } from "../skills/install-meta.js";
27
+ import { isSkillCompatibleWithPlatform } from "../skills/platform-compatibility.js";
27
28
  import { parseToolManifestFile } from "../skills/tool-manifest.js";
28
29
  import { computeSkillVersionHash } from "../skills/version-hash.js";
29
30
  import { recordSkillLoadedEvent } from "../telemetry/skill-loaded-events-store.js";
@@ -381,7 +382,10 @@ export function projectSkillTools(
381
382
  for (const id of allCandidateIds) {
382
383
  const skill = catalogById.get(id);
383
384
  const flagKey = skill ? skillFlagKey(skill) : undefined;
384
- if (!flagKey || isAssistantFeatureFlagEnabled(flagKey, config)) {
385
+ if (
386
+ (!skill || isSkillCompatibleWithPlatform(skill)) &&
387
+ (!flagKey || isAssistantFeatureFlagEnabled(flagKey, config))
388
+ ) {
385
389
  activeIds.add(id);
386
390
  }
387
391
  }
@@ -1,5 +1,5 @@
1
1
  import type { InterfaceId } from "../channels/types.js";
2
- import { getEffectiveProfilesForProvider } from "../config/default-profile-catalog.js";
2
+ import { getUserSelectableProfilesForProvider } from "../config/default-profile-catalog.js";
3
3
  import { resolveEffectiveContextWindow } from "../config/llm-context-resolution.js";
4
4
  import { resolveCallSiteConfig } from "../config/llm-resolver.js";
5
5
  import {
@@ -148,7 +148,7 @@ async function resolveModelCommand(
148
148
  parse: ModelCommandParse,
149
149
  ): Promise<SlashResolution> {
150
150
  const config = getConfig();
151
- const profiles = getEffectiveProfilesForProvider(
151
+ const profiles = getUserSelectableProfilesForProvider(
152
152
  config.llm.profiles,
153
153
  config.llm.defaultProvider ?? null,
154
154
  );
@@ -402,7 +402,9 @@ export function clearAllActiveConversations(): number {
402
402
  * Evict in-memory conversations after a config/prompt/skills reload so the next
403
403
  * turn rebuilds them against the new config. Idle conversations are disposed and
404
404
  * dropped; busy ones are marked stale so they're rebuilt once their current turn
405
- * finishes. Also used when provider credentials change.
405
+ * finishes. Conversations with in-flight subagents are left in place so a
406
+ * reload does not abort those children. Also used when provider credentials
407
+ * change.
406
408
  */
407
409
  export function evictConversationsForReload(): void {
408
410
  const subagentManager = getSubagentManager();
@@ -413,6 +415,11 @@ export function evictConversationsForReload(): void {
413
415
  // would silently drop the queued messages, so mark it stale instead and
414
416
  // let it rebuild once the queue has run.
415
417
  if (!conversation.isProcessing() && !conversation.hasQueuedMessages()) {
418
+ // Same protection the TTL/LRU evictor applies: an idle parent still
419
+ // owns mid-task children, and aborting them here would kill that work.
420
+ if (subagentManager.hasActiveChildren(id)) {
421
+ continue;
422
+ }
416
423
  subagentManager.abortAllForParent(id);
417
424
  conversation.dispose();
418
425
  deleteConversation(id);
@@ -9,6 +9,7 @@
9
9
  import type { AssistantEvent } from "../api/index.js";
10
10
  import {
11
11
  type HostProxyCapability,
12
+ parseClientOs,
12
13
  supportsHostProxy,
13
14
  } from "../channels/types.js";
14
15
  import { getIsPlatform } from "../config/env-registry.js";
@@ -23,6 +24,7 @@ import { isPluginDisabled } from "../plugins/disabled-state.js";
23
24
  import type { Message, ToolDefinition } from "../providers/types.js";
24
25
  import { assistantEventHub } from "../runtime/assistant-event-hub.js";
25
26
  import { registerConversationSender } from "../tools/browser/browser-screencast.js";
27
+ import { supportsClientOsForSkillTool } from "../tools/client-os.js";
26
28
  import type { ToolExecutor } from "../tools/executor.js";
27
29
  import {
28
30
  getAllPluginToolDefinitions,
@@ -410,6 +412,7 @@ export function createToolExecutor(
410
412
  sourceThreadId: turnTrust.sourceThreadId,
411
413
  requesterIdentifier: turnTrust.requesterIdentifier,
412
414
  requesterDisplayName: turnTrust.requesterDisplayName,
415
+ requesterContactId: turnTrust.requesterContactId,
413
416
  channelConversationType: turnTrust.conversationType,
414
417
  // The binding's external chat id is the canonical conversation address
415
418
  // for every channel adapter (Slack channel, Telegram chat, …); it keys
@@ -430,6 +433,8 @@ export function createToolExecutor(
430
433
  toolUseId,
431
434
  isPlatformHosted: getIsPlatform(),
432
435
  transportInterface: ctx.transportInterface,
436
+ clientOs:
437
+ parseClientOs(ctx.currentTurnClientOs ?? ctx.clientOs) ?? undefined,
433
438
  overrideProfile: ctx.currentTurnOverrideProfile,
434
439
  cronRunId: ctx.currentTurnCronRunId,
435
440
  invokingCallSite: ctx.currentCallSite ?? "mainAgent",
@@ -707,6 +712,32 @@ export const ALLOWLIST_ONLY_TOOL_NAMES = new Set<string>([
707
712
  "delete_memory_page",
708
713
  ]);
709
714
 
715
+ /**
716
+ * Windows parity gate: skill tools may declare `supported_client_os`; drop
717
+ * them when the turn's client OS (or pinned OS for wakes) is not listed.
718
+ */
719
+ function isToolSupportedOnClientOs(name: string, ctx: Conversation): boolean {
720
+ const supportedClientOs = getTool(name)?.supportedClientOs;
721
+ if (!supportedClientOs) {
722
+ return true;
723
+ }
724
+ const pin = ctx.toolContextPin;
725
+ const transportInterface = pin
726
+ ? pin.transportInterface
727
+ : ctx.transportInterface;
728
+ const clientOs = pin
729
+ ? pin.clientOs
730
+ : (parseClientOs(ctx.currentTurnClientOs ?? ctx.clientOs) ??
731
+ (transportInterface === "macos" || transportInterface === "windows"
732
+ ? transportInterface
733
+ : undefined));
734
+ return supportsClientOsForSkillTool(supportedClientOs, name, {
735
+ clientOs,
736
+ transportInterface,
737
+ sourceActorPrincipalId: ctx.getTurnActorPrincipalId?.(),
738
+ });
739
+ }
740
+
710
741
  /**
711
742
  * Determine whether a tool is part of the final exposed tool set for the
712
743
  * current turn. This helper mirrors the filtering applied by
@@ -729,6 +760,9 @@ export function isToolActiveForContext(
729
760
  const transportInterface = pin
730
761
  ? pin.transportInterface
731
762
  : ctx.transportInterface;
763
+ if (!isToolSupportedOnClientOs(name, ctx)) {
764
+ return false;
765
+ }
732
766
 
733
767
  // When the conversation is acting as a subagent, the parent orchestrator
734
768
  // restricts the tool list. A tool that isn't on the allowlist is not
@@ -1070,6 +1104,9 @@ export function createResolveToolsCallback(
1070
1104
  if (excluded.has(name)) {
1071
1105
  continue;
1072
1106
  }
1107
+ if (!isToolSupportedOnClientOs(name, ctx)) {
1108
+ continue;
1109
+ }
1073
1110
  turnAllowed.add(name);
1074
1111
  }
1075
1112
  // Record the full resolved inventory durably for read-only queries before
@@ -1917,8 +1917,6 @@ export class Conversation {
1917
1917
  requestId: string,
1918
1918
  decision: UserDecision,
1919
1919
  options?: {
1920
- selectedPattern?: string;
1921
- selectedScope?: string;
1922
1920
  decisionContext?: string;
1923
1921
  emissionContext?: {
1924
1922
  source?: ConfirmationStateChangedEvent["source"];
@@ -1937,8 +1935,6 @@ export class Conversation {
1937
1935
  const toolUseId = this.prompter.getToolUseId(requestId);
1938
1936
 
1939
1937
  this.prompter.resolveConfirmation(requestId, decision, {
1940
- selectedPattern: options?.selectedPattern,
1941
- selectedScope: options?.selectedScope,
1942
1938
  decisionContext: options?.decisionContext,
1943
1939
  });
1944
1940
 
@@ -0,0 +1,30 @@
1
+ import { z } from "zod";
2
+
3
+ /**
4
+ * What every channel's connect/disconnect handler answers, whatever the
5
+ * channel.
6
+ *
7
+ * Each channel's own schema extends this with its tail: Slack carries a
8
+ * workspace and a thread mode, Telegram a webhook secret and registered
9
+ * commands, Discord nothing. The shape is the same one the readiness probes
10
+ * have, a declared common part plus a per-channel remainder, and it exists so
11
+ * a caller can ask "is this connected" without knowing which channel it is.
12
+ *
13
+ * `connected` is the field that makes this worth extracting. Holding a
14
+ * credential and having a working connection are different questions, and a
15
+ * channel that answered only the first would read as set up while delivering
16
+ * nothing.
17
+ */
18
+ export const ChannelConfigResultBaseSchema = z.object({
19
+ /** Whether the operation itself succeeded. */
20
+ success: z.boolean(),
21
+ /** Whether a bot credential is stored, regardless of this call. */
22
+ hasBotToken: z.boolean(),
23
+ /** Whether the channel is actually connected, not merely credentialled. */
24
+ connected: z.boolean(),
25
+ error: z.string().optional(),
26
+ });
27
+
28
+ export type ChannelConfigResultBase = z.infer<
29
+ typeof ChannelConfigResultBaseSchema
30
+ >;