@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
@@ -0,0 +1,139 @@
1
+ import { existsSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+
4
+ import type { WorkspaceMigration } from "./types.js";
5
+
6
+ /**
7
+ * Fold the Flux provider ids back into their parent plus a model family.
8
+ *
9
+ * Flux shipped as its own `services.stt.provider` value, which made a model
10
+ * choice look like a provider choice. That cost an entry in the provider
11
+ * enum, a row in every settings picker, and a place in each
12
+ * managed-versus-BYOK predicate for a backend that shares its parent's
13
+ * credential and account. Model families now live where the rest of a
14
+ * provider's settings do:
15
+ *
16
+ * provider: "deepgram-flux" -> provider: "deepgram"
17
+ * providers.deepgram.model: "flux"
18
+ *
19
+ * The same rewrite applies to the managed pair. Any other provider value is
20
+ * left untouched, and an existing `providers.<parent>` entry keeps every key
21
+ * it already had.
22
+ *
23
+ * The same pass drops any `providers.<id>.model` a known provider cannot
24
+ * serve. That key was free-form before this change and read by nothing, so
25
+ * existing values carry no meaning, but the schema validates it now: leaving
26
+ * a stale one behind would fail the parse and drop the workspace onto the
27
+ * loader's salvage path, which resets the entire `services` section and takes
28
+ * unrelated STT and TTS settings with it.
29
+ *
30
+ * Idempotent: a config already naming a parent provider has nothing to
31
+ * rewrite, and a model key already naming a family it can serve is left
32
+ * alone.
33
+ */
34
+ const FLUX_PROVIDER_PARENTS: Record<string, string> = {
35
+ "deepgram-flux": "deepgram",
36
+ "vellum-flux": "vellum",
37
+ };
38
+
39
+ /**
40
+ * The model families each provider served when this migration was written,
41
+ * inlined rather than read from the provider catalog.
42
+ *
43
+ * A published migration has to behave identically every time it runs, and it
44
+ * reruns on newly initialized workspaces and after an interrupted checkpoint.
45
+ * Reading the live catalog would let a later change to it alter what this
46
+ * migration deletes from configs it has already seen.
47
+ */
48
+ const KNOWN_MODEL_FAMILIES: Record<string, readonly string[]> = {
49
+ deepgram: ["nova-3", "flux"],
50
+ vellum: ["nova-3", "flux"],
51
+ };
52
+
53
+ export const sttFluxProviderToModelFamilyMigration: WorkspaceMigration = {
54
+ id: "150-stt-flux-provider-to-model-family",
55
+ description:
56
+ "Rewrite services.stt.provider Flux ids to the parent provider plus providers.<id>.model",
57
+ run(workspaceDir: string): void {
58
+ const configPath = join(workspaceDir, "config.json");
59
+ if (!existsSync(configPath)) {
60
+ return;
61
+ }
62
+
63
+ let config: Record<string, unknown>;
64
+ try {
65
+ config = JSON.parse(readFileSync(configPath, "utf8")) as Record<
66
+ string,
67
+ unknown
68
+ >;
69
+ } catch {
70
+ // A config that does not parse is the loader's problem to report, not
71
+ // this migration's to guess at.
72
+ return;
73
+ }
74
+
75
+ const services = asRecord(config.services);
76
+ const stt = asRecord(services?.stt);
77
+ if (!stt || typeof stt.provider !== "string") {
78
+ return;
79
+ }
80
+
81
+ const providers = asRecord(stt.providers) ?? {};
82
+ let changed = dropUnservableModels(providers);
83
+
84
+ const parent = FLUX_PROVIDER_PARENTS[stt.provider];
85
+ if (parent !== undefined) {
86
+ stt.provider = parent;
87
+ const parentSettings = asRecord(providers[parent]) ?? {};
88
+ parentSettings.model = "flux";
89
+ providers[parent] = parentSettings;
90
+ changed = true;
91
+ }
92
+
93
+ if (!changed) {
94
+ return;
95
+ }
96
+
97
+ stt.providers = providers;
98
+ writeFileSync(configPath, `${JSON.stringify(config, null, 2)}\n`);
99
+ },
100
+
101
+ down(_workspaceDir: string): void {
102
+ // Forward-only: the Flux provider ids are gone from the config enum, so
103
+ // restoring one would produce a config the schema rejects.
104
+ },
105
+ };
106
+
107
+ /**
108
+ * Remove `model` values a provider known at the time of writing cannot serve.
109
+ * Entries for any other provider are left untouched: that map is deliberately
110
+ * open to ids from future builds, and the schema does not validate them
111
+ * either.
112
+ */
113
+ function dropUnservableModels(providers: Record<string, unknown>): boolean {
114
+ let changed = false;
115
+ for (const [providerId, value] of Object.entries(providers)) {
116
+ const settings = asRecord(value);
117
+ if (!settings || settings.model === undefined) {
118
+ continue;
119
+ }
120
+ const families = KNOWN_MODEL_FAMILIES[providerId];
121
+ if (families === undefined) {
122
+ continue;
123
+ }
124
+ if (
125
+ typeof settings.model !== "string" ||
126
+ !families.includes(settings.model)
127
+ ) {
128
+ delete settings.model;
129
+ changed = true;
130
+ }
131
+ }
132
+ return changed;
133
+ }
134
+
135
+ function asRecord(value: unknown): Record<string, unknown> | null {
136
+ return typeof value === "object" && value !== null && !Array.isArray(value)
137
+ ? (value as Record<string, unknown>)
138
+ : null;
139
+ }
@@ -0,0 +1,160 @@
1
+ import { mkdtempSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { tmpdir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { describe, expect, test } from "bun:test";
5
+
6
+ import { sttFluxProviderToModelFamilyMigration } from "../150-stt-flux-provider-to-model-family.js";
7
+ import { WORKSPACE_MIGRATIONS } from "../registry.js";
8
+
9
+ function workspaceWith(config: unknown): string {
10
+ const dir = mkdtempSync(join(tmpdir(), "stt-flux-migration-"));
11
+ writeFileSync(join(dir, "config.json"), JSON.stringify(config, null, 2));
12
+ return dir;
13
+ }
14
+
15
+ function readConfig(dir: string): any {
16
+ return JSON.parse(readFileSync(join(dir, "config.json"), "utf8"));
17
+ }
18
+
19
+ describe("150-stt-flux-provider-to-model-family", () => {
20
+ test("is registered as the newest migration", () => {
21
+ expect(sttFluxProviderToModelFamilyMigration.id).toBe(
22
+ "150-stt-flux-provider-to-model-family",
23
+ );
24
+ expect(WORKSPACE_MIGRATIONS.at(-1)?.id).toBe(
25
+ "150-stt-flux-provider-to-model-family",
26
+ );
27
+ });
28
+
29
+ test("rewrites a Flux provider id to its parent plus a model family", () => {
30
+ const dir = workspaceWith({
31
+ services: { stt: { provider: "deepgram-flux", providers: {} } },
32
+ });
33
+
34
+ sttFluxProviderToModelFamilyMigration.run(dir);
35
+
36
+ expect(readConfig(dir).services.stt).toEqual({
37
+ provider: "deepgram",
38
+ providers: { deepgram: { model: "flux" } },
39
+ });
40
+ });
41
+
42
+ test("rewrites the managed pair the same way", () => {
43
+ const dir = workspaceWith({
44
+ services: { stt: { provider: "vellum-flux", providers: {} } },
45
+ });
46
+
47
+ sttFluxProviderToModelFamilyMigration.run(dir);
48
+
49
+ expect(readConfig(dir).services.stt.provider).toBe("vellum");
50
+ expect(readConfig(dir).services.stt.providers.vellum.model).toBe("flux");
51
+ });
52
+
53
+ test("keeps existing settings on the parent's entry", () => {
54
+ const dir = workspaceWith({
55
+ services: {
56
+ stt: {
57
+ provider: "deepgram-flux",
58
+ providers: { deepgram: { someExistingKey: 1 } },
59
+ },
60
+ },
61
+ });
62
+
63
+ sttFluxProviderToModelFamilyMigration.run(dir);
64
+
65
+ expect(readConfig(dir).services.stt.providers.deepgram).toEqual({
66
+ someExistingKey: 1,
67
+ model: "flux",
68
+ });
69
+ });
70
+
71
+ test("drops a model a known provider cannot serve", () => {
72
+ // That key was free-form and read by nothing before this change, so an
73
+ // existing value is meaningless -- but the schema validates it now, and
74
+ // leaving one behind drops the workspace onto the loader's salvage path,
75
+ // which resets the whole services section.
76
+ const dir = workspaceWith({
77
+ services: {
78
+ stt: {
79
+ provider: "deepgram",
80
+ providers: { deepgram: { model: "nova-2", keepMe: true } },
81
+ },
82
+ },
83
+ });
84
+
85
+ sttFluxProviderToModelFamilyMigration.run(dir);
86
+
87
+ expect(readConfig(dir).services.stt.providers.deepgram).toEqual({
88
+ keepMe: true,
89
+ });
90
+ });
91
+
92
+ test("keeps a model the provider can serve", () => {
93
+ const dir = workspaceWith({
94
+ services: {
95
+ stt: {
96
+ provider: "deepgram",
97
+ providers: { deepgram: { model: "flux" } },
98
+ },
99
+ },
100
+ });
101
+
102
+ sttFluxProviderToModelFamilyMigration.run(dir);
103
+
104
+ expect(readConfig(dir).services.stt.providers.deepgram.model).toBe("flux");
105
+ });
106
+
107
+ test("leaves entries for providers the catalog does not know", () => {
108
+ // The map is deliberately open to ids from future builds, and the schema
109
+ // does not validate those either.
110
+ const before = {
111
+ services: {
112
+ stt: {
113
+ provider: "deepgram",
114
+ providers: { "future-provider": { model: "next-gen" } },
115
+ },
116
+ },
117
+ };
118
+ const dir = workspaceWith(before);
119
+
120
+ sttFluxProviderToModelFamilyMigration.run(dir);
121
+
122
+ expect(readConfig(dir)).toEqual(before);
123
+ });
124
+
125
+ test("leaves a non-Flux provider untouched", () => {
126
+ const before = {
127
+ services: { stt: { provider: "deepgram", providers: {} } },
128
+ };
129
+ const dir = workspaceWith(before);
130
+
131
+ sttFluxProviderToModelFamilyMigration.run(dir);
132
+
133
+ expect(readConfig(dir)).toEqual(before);
134
+ });
135
+
136
+ test("is idempotent", () => {
137
+ const dir = workspaceWith({
138
+ services: { stt: { provider: "deepgram-flux", providers: {} } },
139
+ });
140
+
141
+ sttFluxProviderToModelFamilyMigration.run(dir);
142
+ const once = readConfig(dir);
143
+ sttFluxProviderToModelFamilyMigration.run(dir);
144
+
145
+ expect(readConfig(dir)).toEqual(once);
146
+ });
147
+
148
+ test("does nothing without a config file, or with an unparseable one", () => {
149
+ const empty = mkdtempSync(join(tmpdir(), "stt-flux-migration-"));
150
+ expect(() =>
151
+ sttFluxProviderToModelFamilyMigration.run(empty),
152
+ ).not.toThrow();
153
+
154
+ const broken = mkdtempSync(join(tmpdir(), "stt-flux-migration-"));
155
+ writeFileSync(join(broken, "config.json"), "{not json");
156
+ expect(() =>
157
+ sttFluxProviderToModelFamilyMigration.run(broken),
158
+ ).not.toThrow();
159
+ });
160
+ });
@@ -144,6 +144,10 @@ import { repairDeprecatedCodexModelIdMigration } from "./143-repair-deprecated-c
144
144
  import { convertStrandedSubscriptionOpenaiProfilesMigration } from "./144-convert-stranded-subscription-openai-profiles.js";
145
145
  import { collapseProfileBindingsToEntriesMigration } from "./145-collapse-profile-bindings-to-entries.js";
146
146
  import { repairRetiredFireworksDeepseekFlashModelIdMigration } from "./146-repair-retired-fireworks-deepseek-flash-model-id.js";
147
+ import { renameCollidingBackupProfileNamesMigration } from "./147-rename-colliding-backup-profile-names.js";
148
+ import { stripUnsupportedFallbackProfilesMigration } from "./148-strip-unsupported-fallback-profiles.js";
149
+ import { repointBackupProfileSelectionsMigration } from "./149-repoint-backup-profile-selections.js";
150
+ import { sttFluxProviderToModelFamilyMigration } from "./150-stt-flux-provider-to-model-family.js";
147
151
  import { migrateToWorkspaceVolumeMigration } from "./migrate-to-workspace-volume.js";
148
152
  import type { WorkspaceMigration } from "./types.js";
149
153
 
@@ -303,4 +307,8 @@ export const WORKSPACE_MIGRATIONS: WorkspaceMigration[] = [
303
307
  convertStrandedSubscriptionOpenaiProfilesMigration,
304
308
  collapseProfileBindingsToEntriesMigration,
305
309
  repairRetiredFireworksDeepseekFlashModelIdMigration,
310
+ renameCollidingBackupProfileNamesMigration,
311
+ stripUnsupportedFallbackProfilesMigration,
312
+ repointBackupProfileSelectionsMigration,
313
+ sttFluxProviderToModelFamilyMigration,
306
314
  ];
@@ -1,17 +0,0 @@
1
- import { describe, expect, test } from "bun:test";
2
-
3
- import { isAllowDecision } from "../permissions/types.js";
4
-
5
- // ---------------------------------------------------------------------------
6
- // Tests
7
- // ---------------------------------------------------------------------------
8
-
9
- describe("isAllowDecision", () => {
10
- test("returns true for 'allow'", () => {
11
- expect(isAllowDecision("allow")).toBe(true);
12
- });
13
-
14
- test("returns false for 'deny'", () => {
15
- expect(isAllowDecision("deny")).toBe(false);
16
- });
17
- });
@@ -1,32 +0,0 @@
1
- /**
2
- * Daemon-side reader for how much Discord admits.
3
- *
4
- * The gateway owns the allow-list and the drop decision it feeds; the
5
- * readiness surface that reports channel setup to the user lives here. This is
6
- * the relay between them, and it exists for the same reason the socket-health
7
- * relay does: the fact lives on one side of the boundary and is reported from
8
- * the other.
9
- */
10
-
11
- import {
12
- type DiscordAdmissionIpcResponse,
13
- DiscordAdmissionIpcResponseSchema,
14
- } from "@vellumai/gateway-client/gateway-ipc-contracts";
15
-
16
- import { ipcCallPersistentValidated } from "../ipc/gateway-validated-call.js";
17
-
18
- /**
19
- * Ask the gateway how many channels Discord's allow-list admits.
20
- *
21
- * Throws on transport failure rather than reporting zero: an unreachable
22
- * gateway is a fact about the gateway, and answering zero would tell an
23
- * operator their allow-list is empty when nobody looked. Callers treat a throw
24
- * as indeterminate.
25
- */
26
- export async function readDiscordAdmission(): Promise<DiscordAdmissionIpcResponse> {
27
- return ipcCallPersistentValidated(
28
- "discord_admission",
29
- {},
30
- DiscordAdmissionIpcResponseSchema,
31
- );
32
- }
@@ -1,239 +0,0 @@
1
- // ---------------------------------------------------------------------------
2
- // Fork message-copy — off-event-loop bulk copy of a conversation's messages.
3
- // ---------------------------------------------------------------------------
4
- //
5
- // The fork-based memory retrospective copies the visible tail of the source
6
- // conversation's message rows (rows at-or-after the inherited compaction
7
- // boundary) into a throwaway background conversation. Done in-process via
8
- // `bun:sqlite` (synchronous), that copy is the single longest uninterruptible
9
- // block on the daemon's event loop — on a multi-GB database it pegs the CPU
10
- // for minutes, stalling `/healthz` and the gateway's IPC calls (see the
11
- // runtime-freeze investigation).
12
- //
13
- // This module moves the message-row copy off the event loop via
14
- // {@link runAsyncSqlite}, which runs the statements in a `sqlite3` CLI
15
- // subprocess (its own connection; SQLite file-locking arbitrates with the
16
- // in-process handle). The content/metadata never round-trip through JS — they
17
- // are copied SELECT→INSERT inside SQLite — so there is no string-escaping
18
- // hazard and the heavy bytes stay in the database.
19
- //
20
- // Two properties matter for correctness:
21
- //
22
- // 1. **Lock-friendly batching.** The main connection runs `busy_timeout=5000`
23
- // (`db-connection.ts`). A single `INSERT … SELECT` over the whole history
24
- // is one implicit transaction that holds the write lock for its full
25
- // duration — a concurrent in-process write (a live user turn persisting a
26
- // message) would then block up to 5 s and throw `SQLITE_BUSY`. So the copy
27
- // is split into batches that each auto-commit, releasing the lock between
28
- // batches so in-process writers slip in.
29
- //
30
- // 2. **`cloneForkMessageMetadata` parity.** The in-process fork stamps
31
- // `forkSourceMessageId` onto each copied row's metadata. The SQL `CASE`
32
- // below is a faithful translation of that helper's three branches
33
- // (object → preserve-or-stamp; null/non-object/invalid → fresh object).
34
- // `client_message_id` is intentionally NOT copied, matching the
35
- // in-process fork (a forked row is not a client-submitted message).
36
-
37
- import { setTimeout as sleep } from "node:timers/promises";
38
-
39
- import { BULK_BATCH_TIMEOUT_MS, withBulkWriteGate } from "./bulk-write-gate.js";
40
- import {
41
- type AsyncSqliteBackend,
42
- type AsyncSqliteResult,
43
- runAsyncSqlite,
44
- } from "./db-async-query.js";
45
-
46
- /**
47
- * Default batch size for the chunked copy. Each batch is one `INSERT … SELECT`
48
- * that auto-commits, so this bounds how long the subprocess holds the write
49
- * lock before releasing it to in-process writers. Kept small so the worst-case
50
- * wait for a contending foreground write is one short batch, even on a bloated
51
- * database; per-batch statement overhead stays negligible against the row copy
52
- * and fork latency doesn't matter (nobody waits on a background fork).
53
- */
54
- export const DEFAULT_FORK_COPY_BATCH_SIZE = 50;
55
-
56
- /**
57
- * Pause inserted between batch subprocess calls. Without it the copy releases
58
- * the write lock on each batch's auto-commit but greedily re-acquires it
59
- * microseconds later, so a concurrent in-process writer (a live user turn
60
- * persisting a message) can lose every race. A brief yield lets foreground
61
- * writes reliably slip in between batches. The extra fork latency is free —
62
- * nothing waits on a background fork — so we trade copy speed for foreground
63
- * fairness.
64
- */
65
- export const DEFAULT_FORK_COPY_INTER_BATCH_DELAY_MS = 25;
66
-
67
- /**
68
- * A single source→fork message-id mapping. `oldId` is an existing source
69
- * message id; `newId` is the freshly-generated id the fork row will carry.
70
- * Generated by the caller (in JS) so the same map drives both this copy and
71
- * the in-process attachment relink that follows.
72
- */
73
- export interface ForkIdPair {
74
- oldId: string;
75
- newId: string;
76
- }
77
-
78
- export interface CopyForkMessagesOptions {
79
- /** The fork conversation id every copied row is assigned to. */
80
- forkConversationId: string;
81
- /** Ordered source→fork id pairs for every message to copy. */
82
- idPairs: readonly ForkIdPair[];
83
- /** Override the per-batch row count (see {@link DEFAULT_FORK_COPY_BATCH_SIZE}). */
84
- batchSize?: number;
85
- /** Test-only passthrough to force the in-process backend. */
86
- forceInProcess?: boolean;
87
- }
88
-
89
- // Server-generated ids only ever use this charset. We interpolate ids (never
90
- // message content) into the SQL script, so reject anything outside it as a
91
- // defense-in-depth guard against a malformed id breaking out of the literal.
92
- const SAFE_ID = /^[A-Za-z0-9_-]{1,64}$/;
93
-
94
- function assertSafeId(id: string): void {
95
- if (!SAFE_ID.test(id)) {
96
- throw new Error(
97
- `fork message-copy: unsafe id literal: ${JSON.stringify(id)}`,
98
- );
99
- }
100
- }
101
-
102
- /**
103
- * SQL translation of `cloneForkMessageMetadata`. `m` is the source `messages`
104
- * row alias; the stamped value is the SOURCE message id (`m.id`), preserving an
105
- * existing `forkSourceMessageId` when the metadata is already an object.
106
- */
107
- const METADATA_CLONE_EXPR = `CASE
108
- WHEN json_valid(m.metadata) AND json_type(m.metadata) = 'object'
109
- THEN json_set(m.metadata, '$.forkSourceMessageId',
110
- coalesce(json_extract(m.metadata, '$.forkSourceMessageId'), m.id))
111
- ELSE json_object('forkSourceMessageId', m.id)
112
- END`;
113
-
114
- /**
115
- * Build the multi-statement SQL script that copies the given id pairs into the
116
- * fork conversation in lock-friendly batches. Exported for unit testing the
117
- * generated SQL without spawning a subprocess.
118
- */
119
- export function buildForkCopyScript(options: CopyForkMessagesOptions): string {
120
- const { forkConversationId, idPairs } = options;
121
- assertSafeId(forkConversationId);
122
- const batchSize = Math.max(
123
- 1,
124
- options.batchSize ?? DEFAULT_FORK_COPY_BATCH_SIZE,
125
- );
126
-
127
- const statements: string[] = [
128
- // Connection-scoped staging table holding the current batch's id map. The
129
- // unqualified `messages` reference in the SELECT resolves to the main DB.
130
- `CREATE TEMP TABLE IF NOT EXISTS _fork_id_map (old_id TEXT PRIMARY KEY, new_id TEXT NOT NULL);`,
131
- ];
132
-
133
- for (let i = 0; i < idPairs.length; i += batchSize) {
134
- const batch = idPairs.slice(i, i + batchSize);
135
- const values = batch
136
- .map(({ oldId, newId }) => {
137
- assertSafeId(oldId);
138
- assertSafeId(newId);
139
- return `('${oldId}','${newId}')`;
140
- })
141
- .join(",");
142
-
143
- // Each batch auto-commits (no surrounding BEGIN), so the write lock is
144
- // released between batches for in-process writers.
145
- statements.push(`DELETE FROM _fork_id_map;`);
146
- statements.push(
147
- `INSERT INTO _fork_id_map (old_id, new_id) VALUES ${values};`,
148
- );
149
- statements.push(
150
- `INSERT INTO messages (id, conversation_id, role, content, created_at, metadata)
151
- SELECT map.new_id, '${forkConversationId}', m.role, m.content, m.created_at, ${METADATA_CLONE_EXPR}
152
- FROM messages m JOIN _fork_id_map map ON map.old_id = m.id;`,
153
- );
154
- }
155
-
156
- // Drop the staging table so the in-process fallback (which runs on the shared
157
- // daemon connection) does not leave it lingering; harmless for the subprocess
158
- // backend, whose connection is discarded on exit.
159
- statements.push(`DROP TABLE IF EXISTS _fork_id_map;`);
160
-
161
- return statements.join("\n");
162
- }
163
-
164
- /**
165
- * Copy the message rows for a fork off the event loop, in lock-friendly
166
- * batches. Each batch runs as its own subprocess call with a brief yield in
167
- * between (see {@link DEFAULT_FORK_COPY_INTER_BATCH_DELAY_MS}) so foreground
168
- * writers reliably acquire the write lock between batches instead of losing
169
- * every race to the copy's greedy lock re-acquisition. The whole batch stream
170
- * runs under the process-wide {@link withBulkWriteGate} so two copies (or a
171
- * copy and a batched delete) never convoy on the write lock — and the yields
172
- * go to foreground writers, not a sibling bulk stream. Resolves once every
173
- * batch has committed (or returns the failing batch's `ok: false` result on
174
- * subprocess failure — the caller is responsible for cleaning up the
175
- * partially-built fork).
176
- *
177
- * No-op (returns a synthetic ok result) when there is nothing to copy.
178
- */
179
- export async function copyForkMessagesViaSubprocess(
180
- options: CopyForkMessagesOptions,
181
- ): Promise<AsyncSqliteResult> {
182
- if (options.idPairs.length === 0) {
183
- return {
184
- ok: true,
185
- backend: "in-process-blocking",
186
- error: null,
187
- elapsedMs: 0,
188
- };
189
- }
190
-
191
- return await withBulkWriteGate(
192
- `fork-message-copy:${options.forkConversationId}`,
193
- () => copyBatches(options),
194
- );
195
- }
196
-
197
- async function copyBatches(
198
- options: CopyForkMessagesOptions,
199
- ): Promise<AsyncSqliteResult> {
200
- const batchSize = Math.max(
201
- 1,
202
- options.batchSize ?? DEFAULT_FORK_COPY_BATCH_SIZE,
203
- );
204
- const runOptions = options.forceInProcess
205
- ? { forceBackend: "in-process-blocking" as const }
206
- : {};
207
-
208
- let totalElapsedMs = 0;
209
- let backend: AsyncSqliteBackend = "in-process-blocking";
210
-
211
- for (let i = 0; i < options.idPairs.length; i += batchSize) {
212
- const batch = options.idPairs.slice(i, i + batchSize);
213
- // One subprocess call per batch. Each generated script is self-contained
214
- // (it creates and drops its own staging table), so splitting execution
215
- // across calls is safe — and it's what lets us yield between batches below.
216
- const sql = buildForkCopyScript({
217
- forkConversationId: options.forkConversationId,
218
- idPairs: batch,
219
- batchSize,
220
- });
221
- const result = await runAsyncSqlite(
222
- sql,
223
- `fork-message-copy:copy-batch:${options.forkConversationId}`,
224
- { ...runOptions, timeoutMs: BULK_BATCH_TIMEOUT_MS },
225
- );
226
- totalElapsedMs += result.elapsedMs;
227
- backend = result.backend;
228
- if (!result.ok) {
229
- return { ...result, elapsedMs: totalElapsedMs };
230
- }
231
-
232
- const isLastBatch = i + batchSize >= options.idPairs.length;
233
- if (!isLastBatch) {
234
- await sleep(DEFAULT_FORK_COPY_INTER_BATCH_DELAY_MS);
235
- }
236
- }
237
-
238
- return { ok: true, backend, error: null, elapsedMs: totalElapsedMs };
239
- }