@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
@@ -16,6 +16,13 @@
16
16
  * - `delete_credential` — Delete a credential by account name
17
17
  * - `list_credentials` — List all credential account names
18
18
  * - `bulk_set_credentials` — Store multiple credentials at once
19
+ *
20
+ * **Credential records** (non-secret identity + policy)
21
+ * - `get_credential_record` — Retrieve a credential record by account name
22
+ * - `set_credential_record` — Store or update a credential record
23
+ * - `delete_credential_record` — Delete a credential record by account name
24
+ * - `list_credential_records` — List all credential records
25
+ * - `bulk_set_credential_records` — Store multiple credential records at once
19
26
  */
20
27
 
21
28
  import { z } from "zod";
@@ -38,6 +45,16 @@ export const CesRpcMethod = {
38
45
  ListCredentials: "list_credentials",
39
46
  /** Bulk-import credentials (set multiple at once). */
40
47
  BulkSetCredentials: "bulk_set_credentials",
48
+ /** Retrieve a single credential record (identity + policy) by account name. */
49
+ GetCredentialRecord: "get_credential_record",
50
+ /** Store or update a credential record by account name. */
51
+ SetCredentialRecord: "set_credential_record",
52
+ /** Delete a credential record by account name. */
53
+ DeleteCredentialRecord: "delete_credential_record",
54
+ /** List all credential records. */
55
+ ListCredentialRecords: "list_credential_records",
56
+ /** Bulk-import credential records. */
57
+ BulkSetCredentialRecords: "bulk_set_credential_records",
41
58
  } as const;
42
59
 
43
60
  export type CesRpcMethod = (typeof CesRpcMethod)[keyof typeof CesRpcMethod];
@@ -168,6 +185,123 @@ export type BulkSetCredentialsResponse = z.infer<
168
185
  typeof BulkSetCredentialsResponseSchema
169
186
  >;
170
187
 
188
+ // ---------------------------------------------------------------------------
189
+ // Credential records (identity + policy, never secret values)
190
+ // ---------------------------------------------------------------------------
191
+
192
+ export const CredentialInjectionTemplateSchema = z.object({
193
+ hostPattern: z.string(),
194
+ injectionType: z.enum(["header", "query"]),
195
+ headerName: z.string().optional(),
196
+ valuePrefix: z.string().optional(),
197
+ queryParamName: z.string().optional(),
198
+ composeWith: z
199
+ .object({
200
+ service: z.string(),
201
+ field: z.string(),
202
+ separator: z.string(),
203
+ })
204
+ .optional(),
205
+ valueTransform: z.enum(["base64"]).optional(),
206
+ });
207
+ export type CredentialInjectionTemplate = z.infer<
208
+ typeof CredentialInjectionTemplateSchema
209
+ >;
210
+
211
+ export const CredentialRecordSchema = z.object({
212
+ credentialId: z.string(),
213
+ service: z.string(),
214
+ field: z.string(),
215
+ allowedTools: z.array(z.string()),
216
+ allowedDomains: z.array(z.string()),
217
+ usageDescription: z.string().optional(),
218
+ alias: z.string().optional(),
219
+ injectionTemplates: z.array(CredentialInjectionTemplateSchema).optional(),
220
+ createdAt: z.number(),
221
+ updatedAt: z.number(),
222
+ });
223
+ export type CredentialRecord = z.infer<typeof CredentialRecordSchema>;
224
+
225
+ export const GetCredentialRecordSchema = z.object({
226
+ /** The account name to look up (`credential/{service}/{field}`). */
227
+ account: z.string(),
228
+ });
229
+ export type GetCredentialRecord = z.infer<typeof GetCredentialRecordSchema>;
230
+
231
+ export const GetCredentialRecordResponseSchema = z.object({
232
+ found: z.boolean(),
233
+ record: CredentialRecordSchema.optional(),
234
+ });
235
+ export type GetCredentialRecordResponse = z.infer<
236
+ typeof GetCredentialRecordResponseSchema
237
+ >;
238
+
239
+ export const SetCredentialRecordSchema = z.object({
240
+ account: z.string(),
241
+ record: CredentialRecordSchema,
242
+ });
243
+ export type SetCredentialRecord = z.infer<typeof SetCredentialRecordSchema>;
244
+
245
+ export const SetCredentialRecordResponseSchema = z.object({
246
+ ok: z.boolean(),
247
+ });
248
+ export type SetCredentialRecordResponse = z.infer<
249
+ typeof SetCredentialRecordResponseSchema
250
+ >;
251
+
252
+ export const DeleteCredentialRecordSchema = z.object({
253
+ account: z.string(),
254
+ });
255
+ export type DeleteCredentialRecord = z.infer<
256
+ typeof DeleteCredentialRecordSchema
257
+ >;
258
+
259
+ export const DeleteCredentialRecordResponseSchema = z.object({
260
+ result: z.enum(["deleted", "not-found", "error"]),
261
+ });
262
+ export type DeleteCredentialRecordResponse = z.infer<
263
+ typeof DeleteCredentialRecordResponseSchema
264
+ >;
265
+
266
+ export const ListCredentialRecordsSchema = z.object({});
267
+ export type ListCredentialRecords = z.infer<typeof ListCredentialRecordsSchema>;
268
+
269
+ export const ListCredentialRecordsResponseSchema = z.object({
270
+ records: z.array(
271
+ z.object({
272
+ account: z.string(),
273
+ record: CredentialRecordSchema,
274
+ }),
275
+ ),
276
+ });
277
+ export type ListCredentialRecordsResponse = z.infer<
278
+ typeof ListCredentialRecordsResponseSchema
279
+ >;
280
+
281
+ export const BulkSetCredentialRecordsSchema = z.object({
282
+ records: z.array(
283
+ z.object({
284
+ account: z.string(),
285
+ record: CredentialRecordSchema,
286
+ }),
287
+ ),
288
+ });
289
+ export type BulkSetCredentialRecords = z.infer<
290
+ typeof BulkSetCredentialRecordsSchema
291
+ >;
292
+
293
+ export const BulkSetCredentialRecordsResponseSchema = z.object({
294
+ results: z.array(
295
+ z.object({
296
+ account: z.string(),
297
+ ok: z.boolean(),
298
+ }),
299
+ ),
300
+ });
301
+ export type BulkSetCredentialRecordsResponse = z.infer<
302
+ typeof BulkSetCredentialRecordsResponseSchema
303
+ >;
304
+
171
305
  // ---------------------------------------------------------------------------
172
306
  // Full RPC contract type map
173
307
  // ---------------------------------------------------------------------------
@@ -201,6 +335,26 @@ export interface CesRpcContract {
201
335
  request: BulkSetCredentials;
202
336
  response: BulkSetCredentialsResponse;
203
337
  };
338
+ [CesRpcMethod.GetCredentialRecord]: {
339
+ request: GetCredentialRecord;
340
+ response: GetCredentialRecordResponse;
341
+ };
342
+ [CesRpcMethod.SetCredentialRecord]: {
343
+ request: SetCredentialRecord;
344
+ response: SetCredentialRecordResponse;
345
+ };
346
+ [CesRpcMethod.DeleteCredentialRecord]: {
347
+ request: DeleteCredentialRecord;
348
+ response: DeleteCredentialRecordResponse;
349
+ };
350
+ [CesRpcMethod.ListCredentialRecords]: {
351
+ request: ListCredentialRecords;
352
+ response: ListCredentialRecordsResponse;
353
+ };
354
+ [CesRpcMethod.BulkSetCredentialRecords]: {
355
+ request: BulkSetCredentialRecords;
356
+ response: BulkSetCredentialRecordsResponse;
357
+ };
204
358
  }
205
359
 
206
360
  /**
@@ -231,4 +385,24 @@ export const CesRpcSchemas = {
231
385
  request: BulkSetCredentialsSchema,
232
386
  response: BulkSetCredentialsResponseSchema,
233
387
  },
388
+ [CesRpcMethod.GetCredentialRecord]: {
389
+ request: GetCredentialRecordSchema,
390
+ response: GetCredentialRecordResponseSchema,
391
+ },
392
+ [CesRpcMethod.SetCredentialRecord]: {
393
+ request: SetCredentialRecordSchema,
394
+ response: SetCredentialRecordResponseSchema,
395
+ },
396
+ [CesRpcMethod.DeleteCredentialRecord]: {
397
+ request: DeleteCredentialRecordSchema,
398
+ response: DeleteCredentialRecordResponseSchema,
399
+ },
400
+ [CesRpcMethod.ListCredentialRecords]: {
401
+ request: ListCredentialRecordsSchema,
402
+ response: ListCredentialRecordsResponseSchema,
403
+ },
404
+ [CesRpcMethod.BulkSetCredentialRecords]: {
405
+ request: BulkSetCredentialRecordsSchema,
406
+ response: BulkSetCredentialRecordsResponseSchema,
407
+ },
234
408
  } as const;
@@ -16,10 +16,14 @@
16
16
  * Auth: Bearer token from a caller-supplied config.
17
17
  */
18
18
 
19
+ import type { CredentialRecord } from "@vellumai/service-contracts/credential-rpc";
20
+
19
21
  // ---------------------------------------------------------------------------
20
22
  // Types
21
23
  // ---------------------------------------------------------------------------
22
24
 
25
+ export type { CredentialRecord };
26
+
23
27
  /** Result of a credential get operation. */
24
28
  export interface CesCredentialGetResult {
25
29
  value: string | undefined;
@@ -111,6 +115,28 @@ export interface CesHttpCredentialClient {
111
115
 
112
116
  /** List all credential account names. */
113
117
  list(): Promise<CesCredentialListResult>;
118
+
119
+ /** Retrieve a credential record (identity + policy) by account name. */
120
+ getRecord(
121
+ account: string,
122
+ ): Promise<{ record: CredentialRecord | undefined; unreachable: boolean }>;
123
+
124
+ /** Store or update a credential record. */
125
+ setRecord(account: string, record: CredentialRecord): Promise<boolean>;
126
+
127
+ /** Delete a credential record by account name. */
128
+ deleteRecord(account: string): Promise<CesDeleteResult>;
129
+
130
+ /** List all credential records. */
131
+ listRecords(): Promise<{
132
+ records: Array<{ account: string; record: CredentialRecord }>;
133
+ unreachable: boolean;
134
+ }>;
135
+
136
+ /** Bulk-set credential records. */
137
+ bulkSetRecords(
138
+ records: Array<{ account: string; record: CredentialRecord }>,
139
+ ): Promise<Array<{ account: string; ok: boolean }>>;
114
140
  }
115
141
 
116
142
  // ---------------------------------------------------------------------------
@@ -292,5 +318,140 @@ export function createCesHttpCredentialClient(
292
318
  return { accounts: [], unreachable: true };
293
319
  }
294
320
  },
321
+
322
+ async getRecord(account: string) {
323
+ try {
324
+ const res = await cesRequest(
325
+ config,
326
+ logger,
327
+ "GET",
328
+ `/v1/metadata/${encodeURIComponent(account)}`,
329
+ );
330
+ if (!res) {
331
+ return { record: undefined, unreachable: true };
332
+ }
333
+ if (res.status === 404) {
334
+ return { record: undefined, unreachable: false };
335
+ }
336
+ if (!res.ok) {
337
+ logger.warn(
338
+ { account, status: res.status },
339
+ "CES credential record get returned non-OK status",
340
+ );
341
+ return { record: undefined, unreachable: true };
342
+ }
343
+ const data = (await res.json()) as { record?: CredentialRecord };
344
+ return { record: data.record, unreachable: false };
345
+ } catch (err) {
346
+ logger.warn(
347
+ { err, account } as Record<string, unknown>,
348
+ "CES credential record get threw unexpectedly",
349
+ );
350
+ return { record: undefined, unreachable: true };
351
+ }
352
+ },
353
+
354
+ async setRecord(account: string, record: CredentialRecord) {
355
+ try {
356
+ const res = await cesRequest(
357
+ config,
358
+ logger,
359
+ "PUT",
360
+ `/v1/metadata/${encodeURIComponent(account)}`,
361
+ { record },
362
+ );
363
+ return !!res?.ok;
364
+ } catch (err) {
365
+ logger.warn(
366
+ { err, account } as Record<string, unknown>,
367
+ "CES credential record set threw unexpectedly",
368
+ );
369
+ return false;
370
+ }
371
+ },
372
+
373
+ async deleteRecord(account: string) {
374
+ try {
375
+ const res = await cesRequest(
376
+ config,
377
+ logger,
378
+ "DELETE",
379
+ `/v1/metadata/${encodeURIComponent(account)}`,
380
+ );
381
+ if (!res) {
382
+ return "error";
383
+ }
384
+ if (res.status === 404) {
385
+ return "not-found";
386
+ }
387
+ if (!res.ok) {
388
+ return "error";
389
+ }
390
+ return "deleted";
391
+ } catch (err) {
392
+ logger.warn(
393
+ { err, account } as Record<string, unknown>,
394
+ "CES credential record delete threw unexpectedly",
395
+ );
396
+ return "error";
397
+ }
398
+ },
399
+
400
+ async listRecords() {
401
+ try {
402
+ const res = await cesRequest(
403
+ config,
404
+ logger,
405
+ "GET",
406
+ "/v1/metadata",
407
+ );
408
+ if (!res) {
409
+ return { records: [], unreachable: true };
410
+ }
411
+ if (!res.ok) {
412
+ return { records: [], unreachable: true };
413
+ }
414
+ const data = (await res.json()) as {
415
+ records?: Array<{ account: string; record: CredentialRecord }>;
416
+ };
417
+ return { records: data.records ?? [], unreachable: false };
418
+ } catch (err) {
419
+ logger.warn(
420
+ { err } as Record<string, unknown>,
421
+ "CES credential record list threw unexpectedly",
422
+ );
423
+ return { records: [], unreachable: true };
424
+ }
425
+ },
426
+
427
+ async bulkSetRecords(
428
+ records: Array<{ account: string; record: CredentialRecord }>,
429
+ ) {
430
+ try {
431
+ const res = await cesRequest(
432
+ config,
433
+ logger,
434
+ "POST",
435
+ "/v1/metadata/bulk",
436
+ { records },
437
+ );
438
+ if (!res?.ok) {
439
+ return records.map((entry) => ({
440
+ account: entry.account,
441
+ ok: false,
442
+ }));
443
+ }
444
+ const data = (await res.json()) as {
445
+ results: Array<{ account: string; ok: boolean }>;
446
+ };
447
+ return data.results;
448
+ } catch (err) {
449
+ logger.warn(
450
+ { err } as Record<string, unknown>,
451
+ "CES credential record bulk set threw unexpectedly",
452
+ );
453
+ return records.map((entry) => ({ account: entry.account, ok: false }));
454
+ }
455
+ },
295
456
  };
296
457
  }
@@ -18,6 +18,7 @@ export {
18
18
  type CesCredentialGetResult,
19
19
  type CesCredentialListResult,
20
20
  type CesDeleteResult,
21
+ type CredentialRecord,
21
22
  } from "./http-credentials.js";
22
23
 
23
24
  export {
@@ -6,7 +6,8 @@
6
6
  "type": "module",
7
7
  "exports": {
8
8
  ".": "./src/index.ts",
9
- "./seeds": "./src/seeds.ts"
9
+ "./seeds": "./src/seeds.ts",
10
+ "./shell": "./src/shell.ts"
10
11
  },
11
12
  "scripts": {
12
13
  "typecheck": "bunx tsc --noEmit",
@@ -0,0 +1,56 @@
1
+ import { describe, expect, test } from "bun:test";
2
+
3
+ import {
4
+ buildShellInvocation,
5
+ pathListDelimiter,
6
+ prependUniquePathEntries,
7
+ } from "./shell.js";
8
+
9
+ describe("buildShellInvocation", () => {
10
+ test("uses Bash on POSIX hosts", () => {
11
+ expect(buildShellInvocation("printf hello", "linux")).toEqual({
12
+ command: "bash",
13
+ args: ["-c", "--", "printf hello"],
14
+ });
15
+ });
16
+
17
+ test("uses encoded non-interactive PowerShell on Windows", () => {
18
+ const invocation = buildShellInvocation(
19
+ "Write-Output 'hello 世界'",
20
+ "win32",
21
+ );
22
+
23
+ expect(invocation.command).toBe("powershell.exe");
24
+ expect(invocation.args.slice(0, -1)).toEqual([
25
+ "-NoProfile",
26
+ "-NonInteractive",
27
+ "-ExecutionPolicy",
28
+ "Bypass",
29
+ "-EncodedCommand",
30
+ ]);
31
+ const encoded = invocation.args.at(-1)!;
32
+ const decoded = Buffer.from(encoded, "base64").toString("utf16le");
33
+ expect(decoded).toContain("[Console]::OutputEncoding");
34
+ expect(decoded).toContain("$global:LASTEXITCODE = 0");
35
+ expect(decoded).toContain("Write-Output 'hello 世界'");
36
+ expect(decoded).toContain("exit $__vellumNativeExitCode");
37
+ expect(decoded).toEndWith("exit 0");
38
+ });
39
+ });
40
+
41
+ describe("path list handling", () => {
42
+ test("uses the platform delimiter", () => {
43
+ expect(pathListDelimiter("win32")).toBe(";");
44
+ expect(pathListDelimiter("linux")).toBe(":");
45
+ });
46
+
47
+ test("prepends Windows paths without splitting drive letters", () => {
48
+ expect(
49
+ prependUniquePathEntries(
50
+ "C:\\Windows\\System32;C:\\Tools",
51
+ ["C:\\Tools", "C:\\Users\\Alice\\.bun\\bin"],
52
+ "win32",
53
+ ),
54
+ ).toBe("C:\\Users\\Alice\\.bun\\bin;C:\\Windows\\System32;C:\\Tools");
55
+ });
56
+ });
@@ -0,0 +1,65 @@
1
+ import { delimiter } from "node:path";
2
+
3
+ export interface ShellInvocation {
4
+ command: string;
5
+ args: string[];
6
+ }
7
+
8
+ const WINDOWS_UTF8_PREAMBLE =
9
+ "try { [Console]::OutputEncoding = [System.Text.Encoding]::UTF8 } catch {}; " +
10
+ "$OutputEncoding = [System.Text.Encoding]::UTF8; " +
11
+ "$global:LASTEXITCODE = 0;";
12
+
13
+ const WINDOWS_EXIT_STATUS_SUFFIX = `
14
+ $__vellumCommandSucceeded = $?
15
+ $__vellumNativeExitCode = $LASTEXITCODE
16
+ if (-not $__vellumCommandSucceeded) {
17
+ if ($__vellumNativeExitCode -ne 0) { exit $__vellumNativeExitCode }
18
+ exit 1
19
+ }
20
+ exit 0`;
21
+
22
+ export function buildShellInvocation(
23
+ command: string,
24
+ hostPlatform: NodeJS.Platform = process.platform,
25
+ windowsExecutable = "powershell.exe",
26
+ ): ShellInvocation {
27
+ if (hostPlatform === "win32") {
28
+ return {
29
+ command: windowsExecutable,
30
+ args: [
31
+ "-NoProfile",
32
+ "-NonInteractive",
33
+ "-ExecutionPolicy",
34
+ "Bypass",
35
+ "-EncodedCommand",
36
+ Buffer.from(
37
+ `${WINDOWS_UTF8_PREAMBLE}\n${command}${WINDOWS_EXIT_STATUS_SUFFIX}`,
38
+ "utf16le",
39
+ ).toString("base64"),
40
+ ],
41
+ };
42
+ }
43
+ return { command: "bash", args: ["-c", "--", command] };
44
+ }
45
+
46
+ export function pathListDelimiter(
47
+ hostPlatform: NodeJS.Platform = process.platform,
48
+ ): string {
49
+ return hostPlatform === process.platform
50
+ ? delimiter
51
+ : hostPlatform === "win32"
52
+ ? ";"
53
+ : ":";
54
+ }
55
+
56
+ export function prependUniquePathEntries(
57
+ value: string | undefined,
58
+ entries: readonly string[],
59
+ hostPlatform: NodeJS.Platform = process.platform,
60
+ ): string {
61
+ const separator = pathListDelimiter(hostPlatform);
62
+ const current = value?.split(separator).filter(Boolean) ?? [];
63
+ const missing = entries.filter((entry) => !current.includes(entry));
64
+ return [...missing, ...current].join(separator);
65
+ }
@@ -6,8 +6,10 @@ import {
6
6
  normalizePublicBaseUrl,
7
7
  parseRecordedAssistantId,
8
8
  parseTunnelRecord,
9
+ selectRestartTunnelRecord,
9
10
  trimmedNonEmptyString,
10
11
  TUNNEL_PROVIDERS,
12
+ type TunnelRecord,
11
13
  velayHostForPlatformHost,
12
14
  } from "../ingress.js";
13
15
  import {
@@ -221,6 +223,41 @@ describe("parseTunnelRecord", () => {
221
223
  });
222
224
  });
223
225
 
226
+ describe("selectRestartTunnelRecord", () => {
227
+ const PAIRING: TunnelRecord = {
228
+ provider: "tailscale",
229
+ publicBaseUrl: "https://assistant-1.example.ts.net",
230
+ };
231
+ const LAST: TunnelRecord = {
232
+ provider: "ngrok",
233
+ publicBaseUrl: "https://assistant-1.ngrok.app",
234
+ };
235
+
236
+ test("prefers the pairing tunnel over the last one that ran", () => {
237
+ // The pairing tunnel deliberately left the older public record beside it,
238
+ // so naming that one would point at an ingress nobody is using.
239
+ expect(
240
+ selectRestartTunnelRecord({ lastTunnel: LAST, pairingTunnel: PAIRING }),
241
+ ).toEqual(PAIRING);
242
+ });
243
+
244
+ test("falls back to the last tunnel and to null", () => {
245
+ expect(selectRestartTunnelRecord({ lastTunnel: LAST })).toEqual(LAST);
246
+ for (const ingress of [undefined, null, {}, "nonsense", 42]) {
247
+ expect(selectRestartTunnelRecord(ingress)).toBeNull();
248
+ }
249
+ });
250
+
251
+ test("skips a malformed pairing record for a usable last one", () => {
252
+ expect(
253
+ selectRestartTunnelRecord({
254
+ lastTunnel: LAST,
255
+ pairingTunnel: { provider: "wireguard", publicBaseUrl: "nope" },
256
+ }),
257
+ ).toEqual(LAST);
258
+ });
259
+ });
260
+
224
261
  describe("trimmedNonEmptyString", () => {
225
262
  test("trims and rejects blank or non-string values", () => {
226
263
  expect(trimmedNonEmptyString(" assistant-1 ")).toBe("assistant-1");