@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
@@ -10,12 +10,23 @@ import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
10
10
  let injectedProcessMessageForRunner:
11
11
  | ((conversationId: string, message: string) => Promise<unknown>)
12
12
  | null = null;
13
+ // Options of every mocked `runBackgroundJob` call, for asserting what the
14
+ // scheduler asked of the runner (e.g. that per-attempt failure emissions are
15
+ // suppressed in favor of the retries-exhausted alert).
16
+ const runnerCalls: Array<Record<string, unknown>> = [];
17
+ // When set, a failing mocked run reports this classification instead of the
18
+ // default generic exception, letting a test drive the cause-keyed alert path.
19
+ let runnerFailureClassification: {
20
+ errorKind: "timeout" | "model_provider" | "exception";
21
+ failureCode?: string;
22
+ } | null = null;
13
23
  mock.module("../runtime/background-job-runner.js", () => ({
14
24
  runBackgroundJob: async (opts: {
15
25
  jobName: string;
16
26
  prompt: string;
17
27
  onConversationCreated?: (conversationId: string) => void;
18
28
  }) => {
29
+ runnerCalls.push(opts as unknown as Record<string, unknown>);
19
30
  const conversationId = `mock-conv-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
20
31
  opts.onConversationCreated?.(conversationId);
21
32
  try {
@@ -25,11 +36,17 @@ mock.module("../runtime/background-job-runner.js", () => ({
25
36
  return { conversationId, ok: true };
26
37
  } catch (err) {
27
38
  const error = err instanceof Error ? err : new Error(String(err));
39
+ const classification = runnerFailureClassification ?? {
40
+ errorKind: "exception" as const,
41
+ };
28
42
  return {
29
43
  conversationId,
30
44
  ok: false,
31
45
  error,
32
- errorKind: "exception" as const,
46
+ errorKind: classification.errorKind,
47
+ ...(classification.failureCode !== undefined
48
+ ? { turnFailure: { failureCode: classification.failureCode } }
49
+ : {}),
33
50
  };
34
51
  }
35
52
  },
@@ -466,6 +483,171 @@ describe("scheduler retry integration", () => {
466
483
  });
467
484
  });
468
485
 
486
+ // ── Execute-mode failure notifications ────────────────────────────────
487
+
488
+ describe("execute-mode failure notifications", () => {
489
+ let scheduler: SchedulerHandle;
490
+
491
+ beforeEach(() => {
492
+ emitNotificationSignalImpl = async () => {};
493
+ runnerCalls.length = 0;
494
+ runnerFailureClassification = null;
495
+ const db = getDb();
496
+ db.run("DELETE FROM cron_runs");
497
+ db.run("DELETE FROM cron_jobs");
498
+ });
499
+
500
+ afterEach(() => {
501
+ scheduler?.stop();
502
+ runnerFailureClassification = null;
503
+ });
504
+
505
+ test("a failing schedule alerts once, at exhaustion, keyed on the failure cause", async () => {
506
+ const emitted: Array<Record<string, unknown>> = [];
507
+ emitNotificationSignalImpl = async (payload) => {
508
+ emitted.push(payload as Record<string, unknown>);
509
+ };
510
+ runnerFailureClassification = {
511
+ errorKind: "model_provider",
512
+ failureCode: "PROVIDER_BILLING",
513
+ };
514
+
515
+ const schedule = await createSchedule({
516
+ name: "Billing victim",
517
+ cronExpression: "0 * * * *",
518
+ message: "do work",
519
+ syntax: "cron",
520
+ maxRetries: 1,
521
+ retryBackoffMs: 1000,
522
+ });
523
+ forceScheduleDue(schedule.id);
524
+
525
+ scheduler = startScheduler(async () => {
526
+ throw new Error("PROVIDER_BILLING: Agent turn failed.");
527
+ });
528
+ await new Promise((resolve) => setTimeout(resolve, 500));
529
+
530
+ // First failure: a retry is pending, so nothing is user-visible yet, and
531
+ // the scheduler told the runner to keep its per-attempt emission quiet.
532
+ expect(emitted).toHaveLength(0);
533
+ expect(runnerCalls.length).toBeGreaterThanOrEqual(1);
534
+ for (const call of runnerCalls) {
535
+ expect(call.suppressFailureNotifications).toBe(true);
536
+ }
537
+
538
+ // Second failure exhausts retries (maxRetries: 1) and fires the single
539
+ // alert, deduped on the classified cause rather than the schedule.
540
+ forceScheduleDue(schedule.id);
541
+ await scheduler.runOnce();
542
+ scheduler.stop();
543
+
544
+ expect(emitted).toHaveLength(1);
545
+ const alert = emitted[0];
546
+ expect(alert.sourceEventName).toBe("activity.failed");
547
+ expect(alert.sourceContextId).toBe(schedule.id);
548
+ // Schedules snapshot a concrete inference profile at creation, and that
549
+ // profile is the alert's provider scope.
550
+ const scope = schedule.inferenceProfile;
551
+ expect(scope).toBeTruthy();
552
+ expect(alert.dedupeKey).toMatch(
553
+ new RegExp(
554
+ `^activity-failed:cause:PROVIDER_BILLING:${scope}:\\d{4}-\\d{2}-\\d{2}$`,
555
+ ),
556
+ );
557
+ expect(alert.contextPayload).toMatchObject({
558
+ jobName: "schedule:Billing victim",
559
+ errorKind: "model_provider",
560
+ });
561
+ });
562
+
563
+ test("schedules on different profiles alert separately for the same cause", async () => {
564
+ // Two provider connections can fail for the same class of reason with
565
+ // different remedies; schedule A's alert must not suppress schedule B's.
566
+ const emitted: Array<Record<string, unknown>> = [];
567
+ emitNotificationSignalImpl = async (payload) => {
568
+ emitted.push(payload as Record<string, unknown>);
569
+ };
570
+ runnerFailureClassification = {
571
+ errorKind: "model_provider",
572
+ failureCode: "PROVIDER_INVALID_KEY",
573
+ };
574
+
575
+ const scheduleA = await createSchedule({
576
+ name: "Profile A victim",
577
+ cronExpression: "0 * * * *",
578
+ message: "do work",
579
+ syntax: "cron",
580
+ maxRetries: 1,
581
+ retryBackoffMs: 1000,
582
+ inferenceProfile: "cost-optimized",
583
+ });
584
+ const scheduleB = await createSchedule({
585
+ name: "Profile B victim",
586
+ cronExpression: "0 * * * *",
587
+ message: "do work",
588
+ syntax: "cron",
589
+ maxRetries: 1,
590
+ retryBackoffMs: 1000,
591
+ inferenceProfile: "balanced",
592
+ });
593
+ forceScheduleDue(scheduleA.id);
594
+ forceScheduleDue(scheduleB.id);
595
+
596
+ scheduler = startScheduler(async () => {
597
+ throw new Error("provider rejected the key");
598
+ });
599
+ await new Promise((resolve) => setTimeout(resolve, 500));
600
+ forceScheduleDue(scheduleA.id);
601
+ forceScheduleDue(scheduleB.id);
602
+ await scheduler.runOnce();
603
+ scheduler.stop();
604
+
605
+ expect(emitted).toHaveLength(2);
606
+ const keys = emitted.map((signal) => signal.dedupeKey as string).sort();
607
+ expect(keys[0]).toMatch(
608
+ /^activity-failed:cause:PROVIDER_INVALID_KEY:balanced:\d{4}-\d{2}-\d{2}$/,
609
+ );
610
+ expect(keys[1]).toMatch(
611
+ /^activity-failed:cause:PROVIDER_INVALID_KEY:cost-optimized:\d{4}-\d{2}-\d{2}$/,
612
+ );
613
+ });
614
+
615
+ test("a cause-less failure keys the exhaustion alert per schedule", async () => {
616
+ const emitted: Array<Record<string, unknown>> = [];
617
+ emitNotificationSignalImpl = async (payload) => {
618
+ emitted.push(payload as Record<string, unknown>);
619
+ };
620
+
621
+ const schedule = await createSchedule({
622
+ name: "Crashing job",
623
+ cronExpression: "0 * * * *",
624
+ message: "do work",
625
+ syntax: "cron",
626
+ maxRetries: 1,
627
+ retryBackoffMs: 1000,
628
+ });
629
+ forceScheduleDue(schedule.id);
630
+
631
+ scheduler = startScheduler(async () => {
632
+ throw new Error("boom");
633
+ });
634
+ await new Promise((resolve) => setTimeout(resolve, 500));
635
+ forceScheduleDue(schedule.id);
636
+ await scheduler.runOnce();
637
+ scheduler.stop();
638
+
639
+ expect(emitted).toHaveLength(1);
640
+ expect(emitted[0].dedupeKey).toMatch(
641
+ new RegExp(
642
+ `^activity-failed:schedule:${schedule.id}:\\d{4}-\\d{2}-\\d{2}$`,
643
+ ),
644
+ );
645
+ expect(emitted[0].contextPayload).toMatchObject({
646
+ errorKind: "exception",
647
+ });
648
+ });
649
+ });
650
+
469
651
  // ── Crash recovery ────────────────────────────────────────────────────
470
652
 
471
653
  describe("crash recovery", () => {
@@ -667,7 +849,6 @@ describe("scheduler-recovery equivalence", () => {
667
849
  await applyRetryDecision({
668
850
  job: jobB,
669
851
  isOneShot: false,
670
- errorMsg: "fail",
671
852
  decision,
672
853
  scheduleRetry,
673
854
  failOneShotPermanently: () => {},
@@ -747,7 +928,6 @@ describe("scheduler-recovery equivalence", () => {
747
928
  await applyRetryDecision({
748
929
  job: jobB1,
749
930
  isOneShot: true,
750
- errorMsg: "fail",
751
931
  decision: decision1,
752
932
  scheduleRetry,
753
933
  failOneShotPermanently: (id: string) => {
@@ -774,7 +954,6 @@ describe("scheduler-recovery equivalence", () => {
774
954
  await applyRetryDecision({
775
955
  job: jobB2,
776
956
  isOneShot: true,
777
- errorMsg: "fail",
778
957
  decision: decision2,
779
958
  scheduleRetry,
780
959
  failOneShotPermanently: (id: string) => {
@@ -456,7 +456,7 @@ describe("scheduler talk-mode runner option propagation", () => {
456
456
  runBackgroundJobBootstrapFails = false;
457
457
  });
458
458
 
459
- test("talk-mode propagates conversationType=scheduled, scheduleJobId, and quiet=>suppressFailureNotifications", async () => {
459
+ test("talk-mode propagates conversationType=scheduled and scheduleJobId", async () => {
460
460
  const rruleExpr = buildEveryMinuteRrule();
461
461
  const schedule = await createSchedule({
462
462
  name: "Quiet Talk Mode",
@@ -475,10 +475,12 @@ describe("scheduler talk-mode runner option propagation", () => {
475
475
  expect(opts.conversationType).toBe("scheduled");
476
476
  expect(opts.scheduleJobId).toBe(schedule.id);
477
477
  expect(opts.groupId).toBe("system:scheduled");
478
- expect(opts.suppressFailureNotifications).toBe(true);
479
478
  });
480
479
 
481
- test("talk-mode without quiet leaves suppressFailureNotifications=false", async () => {
480
+ test("talk-mode always suppresses the runner's per-attempt failure emission", async () => {
481
+ // The retry policy owns the single user-facing alert for a failing
482
+ // schedule (fired once, at exhaustion), so the runner must stay quiet
483
+ // per attempt whether or not the schedule itself is quiet.
482
484
  const rruleExpr = buildEveryMinuteRrule();
483
485
  const schedule = await createSchedule({
484
486
  name: "Loud Talk Mode",
@@ -493,9 +495,7 @@ describe("scheduler talk-mode runner option propagation", () => {
493
495
  await runDueSchedulesOnce();
494
496
 
495
497
  expect(runBackgroundJobOptions).toHaveLength(1);
496
- expect(runBackgroundJobOptions[0]!.suppressFailureNotifications).toBe(
497
- false,
498
- );
498
+ expect(runBackgroundJobOptions[0]!.suppressFailureNotifications).toBe(true);
499
499
  });
500
500
 
501
501
  test("talk-mode bootstrap failure writes sentinel conversationId, not empty string", async () => {
@@ -12,6 +12,9 @@ let secureKeyStore: Record<string, string | undefined> = {};
12
12
  const metadataUpserts: Array<{ service: string; field: string }> = [];
13
13
  const metadataDeletes: Array<{ service: string; field: string }> = [];
14
14
  let providerRefreshCalls = 0;
15
+ let identitySyncCalls = 0;
16
+ let avatarSyncCalls = 0;
17
+ let failSecureKeyWrites = false;
15
18
 
16
19
  const PLATFORM_BASE_URL = "https://platform.example.com";
17
20
  const ASSISTANT_API_KEY_PATH = credentialKey("vellum", "assistant_api_key");
@@ -73,6 +76,9 @@ mock.module("../security/secure-keys.js", () => ({
73
76
  unreachable: false,
74
77
  }),
75
78
  setSecureKeyAsync: async (key: string, value: string) => {
79
+ if (failSecureKeyWrites) {
80
+ return false;
81
+ }
76
82
  secureKeyStore[key] = value;
77
83
  return true;
78
84
  },
@@ -99,6 +105,19 @@ mock.module("../plugins/defaults/memory/substrate/boot-maintenance.js", () => ({
99
105
  maybeReseedCapabilitiesAfterManagedCredential: async () => {},
100
106
  }));
101
107
 
108
+ // Live platform registration (`vellum login` against a running assistant)
109
+ // must re-enqueue the name and avatar syncs that no-op'd at startup.
110
+ mock.module("../platform/sync-identity.js", () => ({
111
+ syncWorkspaceIdentityToPlatform: () => {
112
+ identitySyncCalls++;
113
+ },
114
+ }));
115
+ mock.module("../platform/sync-avatar.js", () => ({
116
+ syncAvatarToPlatform: () => {
117
+ avatarSyncCalls++;
118
+ },
119
+ }));
120
+
102
121
  // secret-routes evicts conversations after a credential change so the next turn
103
122
  // rebuilds against the new providers; count the calls to assert that happens.
104
123
  mock.module("../daemon/conversation-store.js", () => ({
@@ -164,6 +183,9 @@ describe("secret routes managed proxy registry sync", () => {
164
183
  lastGeminiConstructorOpts = null;
165
184
  platformBaseUrlOverride = undefined;
166
185
  providerRefreshCalls = 0;
186
+ identitySyncCalls = 0;
187
+ avatarSyncCalls = 0;
188
+ failSecureKeyWrites = false;
167
189
  getDb()
168
190
  .delete(providerConnections)
169
191
  .where(eq(providerConnections.name, "openrouter-connection"))
@@ -301,6 +323,39 @@ describe("secret routes managed proxy registry sync", () => {
301
323
  expect(providerRefreshCalls).toBe(0);
302
324
  });
303
325
 
326
+ test("storing a platform registration credential enqueues name and avatar syncs", async () => {
327
+ for (const name of [
328
+ "vellum:assistant_api_key",
329
+ "vellum:platform_assistant_id",
330
+ "vellum:platform_base_url",
331
+ ]) {
332
+ identitySyncCalls = 0;
333
+ avatarSyncCalls = 0;
334
+ await addCredential(name, "value");
335
+ expect(identitySyncCalls).toBe(1);
336
+ expect(avatarSyncCalls).toBe(1);
337
+ }
338
+ });
339
+
340
+ test("unrelated credentials do not enqueue platform syncs", async () => {
341
+ await addCredential("openrouter:api_key", "openrouter-key");
342
+ await addCredential("vellum:platform_user_id", "user-1");
343
+
344
+ expect(identitySyncCalls).toBe(0);
345
+ expect(avatarSyncCalls).toBe(0);
346
+ });
347
+
348
+ test("a failed platform credential write does not enqueue platform syncs", async () => {
349
+ failSecureKeyWrites = true;
350
+
351
+ await expect(
352
+ addCredential("vellum:platform_assistant_id", "asst-1"),
353
+ ).rejects.toThrow("Failed to store credential");
354
+
355
+ expect(identitySyncCalls).toBe(0);
356
+ expect(avatarSyncCalls).toBe(0);
357
+ });
358
+
304
359
  test("storing vellum:platform_base_url sets override and triggers initializeProviders", async () => {
305
360
  await addCredential(
306
361
  "vellum:platform_base_url",
@@ -2,6 +2,8 @@ import { mkdirSync, symlinkSync, writeFileSync } from "node:fs";
2
2
  import { join } from "node:path";
3
3
  import { beforeEach, describe, expect, mock, test } from "bun:test";
4
4
 
5
+ import type { ClientOs } from "../channels/types.js";
6
+
5
7
  const TEST_DIR = process.env.VELLUM_WORKSPACE_DIR!;
6
8
 
7
9
  // Build a mock that covers every export from platform.ts — any function not
@@ -66,6 +68,7 @@ function writeToolsJson(
66
68
  input_schema?: Record<string, unknown>;
67
69
  executor?: string;
68
70
  execution_target?: string;
71
+ supported_client_os?: ClientOs[];
69
72
  }>,
70
73
  ): void {
71
74
  const skillDir = join(TEST_DIR, "skills", skillId);
@@ -80,6 +83,7 @@ function writeToolsJson(
80
83
  input_schema: t.input_schema ?? { type: "object", properties: {} },
81
84
  executor: t.executor ?? "scripts/run.sh",
82
85
  execution_target: t.execution_target ?? "host",
86
+ supported_client_os: t.supported_client_os,
83
87
  })),
84
88
  };
85
89
  writeFileSync(join(skillDir, "TOOLS.json"), JSON.stringify(manifest));
@@ -87,6 +91,7 @@ function writeToolsJson(
87
91
 
88
92
  async function executeSkillLoad(
89
93
  input: Record<string, unknown>,
94
+ clientOs?: ClientOs,
90
95
  ): Promise<{ content: string; isError: boolean }> {
91
96
  const tool = skillLoadTool;
92
97
 
@@ -94,6 +99,7 @@ async function executeSkillLoad(
94
99
  workingDir: "/tmp",
95
100
  conversationId: "conversation-1",
96
101
  trustClass: "guardian",
102
+ clientOs,
97
103
  });
98
104
  return { content: result.content, isError: result.isError };
99
105
  }
@@ -769,6 +775,34 @@ describe("skill_load tool", () => {
769
775
  expect(result.content).not.toContain("skill_execute");
770
776
  });
771
777
 
778
+ test("omits tools unsupported by the current client operating system", async () => {
779
+ writeSkill(
780
+ "platform-tools",
781
+ "Platform Tools",
782
+ "Has platform-specific tools",
783
+ "Body.",
784
+ );
785
+ writeToolsJson("platform-tools", [
786
+ {
787
+ name: "mac_only_action",
788
+ description: "Runs only on macOS",
789
+ supported_client_os: ["macos"],
790
+ },
791
+ {
792
+ name: "desktop_action",
793
+ description: "Runs on every desktop",
794
+ },
795
+ ]);
796
+
797
+ const result = await executeSkillLoad(
798
+ { skill: "platform-tools" },
799
+ "windows",
800
+ );
801
+
802
+ expect(result.content).not.toContain("mac_only_action");
803
+ expect(result.content).toContain("desktop_action");
804
+ });
805
+
772
806
  test("included child skill with TOOLS.json has its tool schemas in output", async () => {
773
807
  writeSkillWithIncludes(
774
808
  "parent-tools",
@@ -83,11 +83,16 @@ afterAll(async () => {
83
83
 
84
84
  describe("createSkillTool", () => {
85
85
  test("produces a tool with correct name, description, and category", () => {
86
- const tool = createSkillTool(makeEntry(), "/skills/my-skill", "v1:test");
86
+ const tool = createSkillTool(
87
+ makeEntry({ supported_client_os: ["macos"] }),
88
+ "/skills/my-skill",
89
+ "v1:test",
90
+ );
87
91
 
88
92
  expect(tool.name).toBe("test_tool");
89
93
  expect(tool.description).toBe("A test tool");
90
94
  expect(tool.category).toBe("testing");
95
+ expect(tool.supportedClientOs).toEqual(["macos"]);
91
96
  });
92
97
 
93
98
  // Removed "sets origin to skill" test — the factory no longer stamps an
@@ -165,6 +165,26 @@ describe("parseToolManifest", () => {
165
165
  }
166
166
  });
167
167
 
168
+ test("accepts supported client operating systems", () => {
169
+ const result = parseToolManifest(
170
+ makeManifest({
171
+ tools: [makeToolEntry({ supported_client_os: ["macos", "windows"] })],
172
+ }),
173
+ );
174
+
175
+ expect(result.tools[0].supported_client_os).toEqual(["macos", "windows"]);
176
+ });
177
+
178
+ test("rejects unknown supported client operating systems", () => {
179
+ expect(() =>
180
+ parseToolManifest(
181
+ makeManifest({
182
+ tools: [makeToolEntry({ supported_client_os: ["beos"] })],
183
+ }),
184
+ ),
185
+ ).toThrow(/supported_client_os/);
186
+ });
187
+
168
188
  test("accepts an empty input_schema object", () => {
169
189
  const raw = makeManifest({
170
190
  tools: [makeToolEntry({ input_schema: {} })],
@@ -78,6 +78,31 @@ describe("skills catalog loading", () => {
78
78
  expect(catalog.map((skill) => skill.id)).toEqual(["alpha", "zeta"]);
79
79
  });
80
80
 
81
+ test("parses supported host platforms from Vellum metadata", () => {
82
+ const skillDir = join(TEST_DIR, "skills", "platform-skill");
83
+ mkdirSync(skillDir, { recursive: true });
84
+ writeFileSync(
85
+ join(skillDir, "SKILL.md"),
86
+ `---
87
+ name: platform-skill
88
+ description: Platform-specific skill
89
+ metadata:
90
+ vellum:
91
+ platforms:
92
+ - macos
93
+ - linux
94
+ ---
95
+
96
+ Skill body
97
+ `,
98
+ );
99
+
100
+ const skill = loadUserSkillCatalog().find(
101
+ (entry) => entry.id === "platform-skill",
102
+ );
103
+ expect(skill?.platforms).toEqual(["macos", "linux"]);
104
+ });
105
+
81
106
  test("ignores stale SKILLS.md while discovering valid skill directories", () => {
82
107
  writeSkill("first", "First Skill", "First");
83
108
  writeSkill("second", "Second Skill", "Second");
@@ -533,6 +533,44 @@ describe("SubagentManager notifyParent (via runSubagent)", () => {
533
533
  });
534
534
  });
535
535
 
536
+ describe("SubagentManager hasActiveChildren", () => {
537
+ test("is true for pending, running, and awaiting_input children", () => {
538
+ const manager = new SubagentManager();
539
+ injectFakeSubagent(
540
+ manager,
541
+ "sub-running",
542
+ makeState("sub-running", { status: "running" }),
543
+ );
544
+
545
+ expect(manager.hasActiveChildren("parent-sess-1")).toBe(true);
546
+
547
+ asInternals(manager).subagents.get("sub-running")!.state.status =
548
+ "awaiting_input";
549
+ expect(manager.hasActiveChildren("parent-sess-1")).toBe(true);
550
+
551
+ asInternals(manager).subagents.get("sub-running")!.state.status = "pending";
552
+ expect(manager.hasActiveChildren("parent-sess-1")).toBe(true);
553
+ });
554
+
555
+ test("is false when every child is terminal or the parent has none", () => {
556
+ const manager = new SubagentManager();
557
+ expect(manager.hasActiveChildren("parent-sess-1")).toBe(false);
558
+
559
+ injectFakeSubagent(
560
+ manager,
561
+ "sub-done",
562
+ makeState("sub-done", { status: "completed" }),
563
+ );
564
+ injectFakeSubagent(
565
+ manager,
566
+ "sub-aborted",
567
+ makeState("sub-aborted", { status: "aborted" }),
568
+ );
569
+
570
+ expect(manager.hasActiveChildren("parent-sess-1")).toBe(false);
571
+ });
572
+ });
573
+
536
574
  describe("SubagentManager abortAllForParent", () => {
537
575
  test("aborts active children but keeps every child's state readable", () => {
538
576
  clearCaptured();
@@ -47,6 +47,7 @@ import {
47
47
  KATA_INJECTED_ENV_VARS,
48
48
  KATA_SAFE_ENV_VARS,
49
49
  SAFE_ENV_VARS,
50
+ WINDOWS_SAFE_ENV_VARS,
50
51
  } from "../tools/terminal/safe-env.js";
51
52
  import { shellTool } from "../tools/terminal/shell.js";
52
53
 
@@ -194,6 +195,7 @@ describe("buildSanitizedEnv", () => {
194
195
  ...KATA_SAFE_ENV_VARS,
195
196
  ...KATA_INJECTED_ENV_VARS,
196
197
  ...ALWAYS_INJECTED_ENV_VARS,
198
+ ...WINDOWS_SAFE_ENV_VARS,
197
199
  ];
198
200
  for (const key of keys) {
199
201
  expect(safeKeys).toContain(key);