@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
@@ -8,6 +8,7 @@ const LIVE_VOICE_CLIENT_FRAME_TYPES = [
8
8
  "end",
9
9
  "update_config",
10
10
  "attach_image",
11
+ "text",
11
12
  ] as const;
12
13
 
13
14
  type LiveVoiceClientFrameType = (typeof LIVE_VOICE_CLIENT_FRAME_TYPES)[number];
@@ -101,6 +102,22 @@ export interface LiveVoiceClientStartFrame {
101
102
  * [{@link MIN_BARGE_IN_MIN_SPEECH_MS}, {@link MAX_BARGE_IN_MIN_SPEECH_MS}].
102
103
  */
103
104
  readonly bargeInMinSpeechMs?: number;
105
+ /**
106
+ * The client has a text input affordance, so it can take a turn without the
107
+ * microphone (see {@link LiveVoiceClientTextTurnFrame}).
108
+ *
109
+ * Load-bearing at startup, not just a feature announcement: a session whose
110
+ * speech-to-text leg has no working credential is normally rejected outright
111
+ * (`credentials_unavailable`), because a session that cannot hear is a
112
+ * session that cannot be used. A client that can type is the exception: for
113
+ * it, a missing STT leg is degradation rather than failure, so the session
114
+ * starts text-only and says so on `ready` via `audioInput: false`.
115
+ *
116
+ * Absent means false: a client that predates the field has no way to take a
117
+ * turn without the microphone, so a broken STT leg must still fail its
118
+ * session rather than open one it cannot speak into.
119
+ */
120
+ readonly textInput?: boolean;
104
121
  /**
105
122
  * Which client opened the session. Absent from clients that predate the
106
123
  * field, in which case the originating client is simply unknown.
@@ -129,6 +146,15 @@ export const MAX_SILENCE_THRESHOLD_MS = 5_000;
129
146
  export const MIN_BARGE_IN_MIN_SPEECH_MS = 0;
130
147
  export const MAX_BARGE_IN_MIN_SPEECH_MS = 3_000;
131
148
 
149
+ /**
150
+ * Longest typed turn accepted on a `text` frame.
151
+ *
152
+ * Generous next to anything a person says in one breath, and far below what
153
+ * would make the reply unspeakable. The cap exists so a socket tuned for 50 ms
154
+ * audio frames cannot be handed a whole document to synthesize.
155
+ */
156
+ export const MAX_TEXT_TURN_CHARS = 4_000;
157
+
132
158
  export interface LiveVoiceClientAudioFrame {
133
159
  readonly type: "audio";
134
160
  readonly dataBase64: string;
@@ -180,6 +206,45 @@ export interface LiveVoiceClientAttachImageFrame {
180
206
  readonly attachmentId: string;
181
207
  }
182
208
 
209
+ /**
210
+ * A user turn the client already has as text, taken without the microphone.
211
+ *
212
+ * The session runs it through the same pipeline a spoken turn takes, joining
213
+ * at the point STT would have handed over a finished transcript: same turn
214
+ * runner, same streaming TTS, same barge-in and progress narration. Only the
215
+ * capture half is skipped, so the reply is spoken exactly as it would be for
216
+ * speech.
217
+ *
218
+ * Deliberately not routed through `processMessage` into the conversation the
219
+ * session owns. That path diverges from the voice one in ways that have
220
+ * already produced their own bug class (missing user_message_echo, an
221
+ * unpersisted conversation row, missing trustContext), and a typed turn that
222
+ * behaved differently from a spoken one would reintroduce all of it.
223
+ *
224
+ * Accepted at any point in a session, not only as its first turn. A user
225
+ * whose microphone stops working mid-session types the rest of the
226
+ * conversation; a client with no microphone at all (see the start frame's
227
+ * `textInput`) types every turn.
228
+ */
229
+ export interface LiveVoiceClientTextTurnFrame {
230
+ readonly type: "text";
231
+ readonly text: string;
232
+ /**
233
+ * Marks the turn as an internal instruction rather than something the user
234
+ * typed. The row still persists and still drives the turn, so the model
235
+ * sees it, but it is suppressed from the transcript: no live echo, and
236
+ * `/messages` filters it after a reload.
237
+ *
238
+ * For machine signals the user never wrote, such as the greeting that opens
239
+ * a voice session. A turn the user actually typed leaves it unset.
240
+ *
241
+ * Optional, and a daemon that does not understand it simply persists the
242
+ * turn visibly, so a client cannot tell from the frame alone whether it was
243
+ * honored.
244
+ */
245
+ readonly hidden?: boolean;
246
+ }
247
+
183
248
  export type LiveVoiceClientFrame =
184
249
  | LiveVoiceClientStartFrame
185
250
  | LiveVoiceClientAudioFrame
@@ -187,7 +252,8 @@ export type LiveVoiceClientFrame =
187
252
  | LiveVoiceClientInterruptFrame
188
253
  | LiveVoiceClientEndFrame
189
254
  | LiveVoiceClientUpdateConfigFrame
190
- | LiveVoiceClientAttachImageFrame;
255
+ | LiveVoiceClientAttachImageFrame
256
+ | LiveVoiceClientTextTurnFrame;
191
257
 
192
258
  interface LiveVoiceBinaryAudioFrame {
193
259
  readonly type: "binary_audio";
@@ -209,11 +275,55 @@ export interface LiveVoiceReadyServerFrame extends LiveVoiceServerFrameBase {
209
275
  * (older daemons) means "manual".
210
276
  */
211
277
  readonly turnDetection?: LiveVoiceTurnDetectionMode;
278
+ /**
279
+ * Whether this daemon will accept `text` frames on the session. Absent
280
+ * (older daemons) means no, which is what lets a client that asked for
281
+ * `textInput` fall back rather than type into a socket that would answer
282
+ * every typed turn with an `unknown_type` error.
283
+ */
284
+ readonly textInput?: boolean;
285
+ /**
286
+ * Whether the session's speech-to-text leg is live. Absent means yes, which
287
+ * is the only thing an older daemon can have meant: it rejects a session it
288
+ * cannot transcribe, so every session it readies can hear.
289
+ *
290
+ * False is reachable only for a client that declared `textInput`, and tells
291
+ * it to present the session as typed rather than draw a microphone that
292
+ * will never hear anything.
293
+ */
294
+ readonly audioInput?: boolean;
295
+ }
296
+
297
+ /**
298
+ * Where the session holding the daemon's single live-voice slot is running,
299
+ * as much of it as the daemon knows.
300
+ *
301
+ * Display only: it is what lets a client refused a session say where the one
302
+ * that blocked it is. Nothing routes, authorizes, or decides capability on
303
+ * it, and every field is optional, so a holder that names neither its surface
304
+ * nor a conversation is simply less specific.
305
+ */
306
+ export interface LiveVoiceSessionHolder {
307
+ /**
308
+ * The OS surface the holder is running on, from its `start` frame. A
309
+ * {@link ClientOs} carries no transport meaning by contract (see
310
+ * `channels/types.ts`), which is exactly right for a label: it says which
311
+ * device the user should go looking on, and answers nothing else.
312
+ */
313
+ readonly client?: ClientOs;
314
+ /** The conversation the holder is talking in, once it has one. */
315
+ readonly conversationId?: string;
212
316
  }
213
317
 
214
318
  export interface LiveVoiceBusyServerFrame extends LiveVoiceServerFrameBase {
215
319
  readonly type: "busy";
216
320
  readonly activeSessionId: string;
321
+ /**
322
+ * Absent from daemons that predate it, and from a holder that revealed
323
+ * neither field, so a client must degrade to the unspecific copy rather
324
+ * than assume.
325
+ */
326
+ readonly holder?: LiveVoiceSessionHolder;
217
327
  }
218
328
 
219
329
  /**
@@ -544,7 +654,73 @@ export function validateLiveVoiceClientFrame(
544
654
  return validateUpdateConfigFrame(value);
545
655
  case "attach_image":
546
656
  return validateAttachImageFrame(value);
657
+ case "text":
658
+ return validateTextTurnFrame(value);
659
+ }
660
+ }
661
+
662
+ function validateTextTurnFrame(
663
+ value: Record<string, unknown>,
664
+ ): LiveVoiceParseResult<LiveVoiceClientTextTurnFrame> {
665
+ if (!("text" in value)) {
666
+ return protocolError(
667
+ "missing_required_field",
668
+ "text frame is missing required field text",
669
+ "text",
670
+ "text",
671
+ );
672
+ }
673
+
674
+ if (typeof value.text !== "string") {
675
+ return protocolError(
676
+ "invalid_field",
677
+ "text frame field text must be a string",
678
+ "text",
679
+ "text",
680
+ );
681
+ }
682
+
683
+ // Trimmed before the emptiness check, so a frame carrying only whitespace is
684
+ // rejected here rather than reaching the session as a turn with nothing in
685
+ // it. The trimmed value is what travels, so the turn the session runs is the
686
+ // one that was validated.
687
+ const text = value.text.trim();
688
+
689
+ if (text.length === 0) {
690
+ return protocolError(
691
+ "invalid_field",
692
+ "text frame field text must not be empty",
693
+ "text",
694
+ "text",
695
+ );
696
+ }
697
+
698
+ if (text.length > MAX_TEXT_TURN_CHARS) {
699
+ return protocolError(
700
+ "invalid_field",
701
+ `text frame field text must be at most ${MAX_TEXT_TURN_CHARS} characters`,
702
+ "text",
703
+ "text",
704
+ );
705
+ }
706
+
707
+ if ("hidden" in value && typeof value.hidden !== "boolean") {
708
+ return protocolError(
709
+ "invalid_field",
710
+ "text frame field hidden must be a boolean",
711
+ "hidden",
712
+ "text",
713
+ );
547
714
  }
715
+
716
+ return {
717
+ ok: true,
718
+ frame: {
719
+ type: "text",
720
+ text,
721
+ ...(value.hidden === true ? { hidden: true } : {}),
722
+ },
723
+ };
548
724
  }
549
725
 
550
726
  function validateAttachImageFrame(
@@ -744,6 +920,15 @@ function validateStartFrame(
744
920
  );
745
921
  }
746
922
 
923
+ if ("textInput" in value && typeof value.textInput !== "boolean") {
924
+ return protocolError(
925
+ "invalid_field",
926
+ "start frame field textInput must be a boolean",
927
+ "textInput",
928
+ "start",
929
+ );
930
+ }
931
+
747
932
  // An unrecognized client is dropped rather than rejected: the field is an
748
933
  // analytics dimension, and failing a session's startup over it would trade a
749
934
  // gap in a chart for a user who cannot talk to their assistant.
@@ -767,6 +952,7 @@ function validateStartFrame(
767
952
  ...(typeof value.bargeInMinSpeechMs === "number"
768
953
  ? { bargeInMinSpeechMs: value.bargeInMinSpeechMs }
769
954
  : {}),
955
+ ...(value.textInput === true ? { textInput: true } : {}),
770
956
  },
771
957
  };
772
958
  }
@@ -21,7 +21,8 @@ import { retryableCall } from "../retry-policy.js";
21
21
 
22
22
  const log = getLogger("discord-api");
23
23
 
24
- const DISCORD_API_BASE_URL = "https://discord.com/api/v10";
24
+ /** Exported so a caller validating a not-yet-stored token uses the same base. */
25
+ export const DISCORD_API_BASE_URL = "https://discord.com/api/v10";
25
26
 
26
27
  /**
27
28
  * Discord requires HTTP API clients to identify themselves with a User-Agent
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Withdraw a Discord approval card when its underlying guardian request
3
+ * resolves.
4
+ *
5
+ * A Discord card is plain text whose actionable tail (the typed-command
6
+ * instructions) rides the last chunk, and that chunk's id is what the
7
+ * delivery row captured. Rewriting it to the terminal outcome removes every
8
+ * live affordance and shows the decision in one edit, so no separate status
9
+ * reply is needed. The delivery row's chat id is the guardian's *user*
10
+ * snowflake, never a room: the DM channel is resolved from the person at
11
+ * withdrawal time, the same way the adapter resolved it to deliver.
12
+ */
13
+
14
+ import {
15
+ isParkAction,
16
+ resolveDecisionStatusWord,
17
+ } from "../../../runtime/channel-approval-types.js";
18
+ import { getLogger } from "../../../util/logger.js";
19
+ import { openDiscordDmChannel } from "./api.js";
20
+ import { editDiscordMessage } from "./send.js";
21
+
22
+ const log = getLogger("discord-withdraw");
23
+
24
+ const STATUS_GLYPH: Record<string, string> = {
25
+ approved: "✅",
26
+ denied: "❌",
27
+ expired: "⌛",
28
+ };
29
+
30
+ const PARK_STATUS_GLYPH = "⏸️";
31
+
32
+ function buildStatusText(status: string, decidedAction?: string): string {
33
+ const park = status === "denied" && isParkAction(decidedAction);
34
+ const glyph = park ? PARK_STATUS_GLYPH : (STATUS_GLYPH[status] ?? "");
35
+ const word = resolveDecisionStatusWord(status, decidedAction);
36
+ return glyph ? `${glyph} ${word}` : word;
37
+ }
38
+
39
+ export interface WithdrawDiscordApprovalCardParams {
40
+ /** The guardian's user snowflake from the delivery row. */
41
+ guardianUserId: string;
42
+ /** The card's last-chunk message id from the delivery row. */
43
+ messageId: string;
44
+ status: string;
45
+ decidedAction?: string;
46
+ }
47
+
48
+ export async function withdrawDiscordApprovalCard(
49
+ params: WithdrawDiscordApprovalCardParams,
50
+ ): Promise<void> {
51
+ const channelId = await openDiscordDmChannel(params.guardianUserId);
52
+ await editDiscordMessage(
53
+ { channelId },
54
+ params.messageId,
55
+ buildStatusText(params.status, params.decidedAction),
56
+ { emphasis: "muted" },
57
+ );
58
+ log.info(
59
+ { guardianUserId: params.guardianUserId, messageId: params.messageId },
60
+ "Discord approval card withdrawn",
61
+ );
62
+ }
@@ -1,3 +1,4 @@
1
+ import type { ChannelId } from "../channels/types.js";
1
2
  import { safeParseRecord } from "../util/json.js";
2
3
  import {
3
4
  type ProviderMessageMetadata,
@@ -49,3 +50,34 @@ export function readProviderMetadata(
49
50
  const slackMeta = readSlackMetadataFromMessageMetadata(metadata, opts);
50
51
  return slackMeta ? slackMetadataAsProviderMetadata(slackMeta) : null;
51
52
  }
53
+
54
+ /**
55
+ * Merge an edit or delete stamp into a row's serialized metadata, producing
56
+ * the neutral envelope to store back under `providerMeta`.
57
+ *
58
+ * The base is whatever the row already says about itself through
59
+ * {@link readProviderMetadata}, flat-legacy Slack rows included, so fields
60
+ * the old envelope carried (thread, actor, display name) survive the stamp
61
+ * rather than being shadowed by a minimal synthesis. A row that says
62
+ * nothing gets the envelope synthesized from the caller's lookup-derived
63
+ * facts, so the result always validates on read.
64
+ */
65
+ export function mergeProviderMessageMetadata(
66
+ rowMetadata: string | null,
67
+ seed: {
68
+ source: ChannelId;
69
+ conversationExternalId: string;
70
+ messageId: string;
71
+ threadId?: string;
72
+ },
73
+ patch: { editedAt?: number; deletedAt?: number },
74
+ ): string {
75
+ const base = readProviderMetadata(rowMetadata, { allowFlatLegacy: true }) ?? {
76
+ source: seed.source,
77
+ conversationExternalId: seed.conversationExternalId,
78
+ messageId: seed.messageId,
79
+ ...(seed.threadId ? { threadId: seed.threadId } : {}),
80
+ eventKind: "message" as const,
81
+ };
82
+ return JSON.stringify({ ...base, ...patch });
83
+ }
@@ -7,6 +7,7 @@
7
7
  import { describe, expect, test } from "bun:test";
8
8
 
9
9
  import {
10
+ collectFdUsage,
10
11
  createFdPressureState,
11
12
  evaluateFdPressure,
12
13
  parseOpenFileLimits,
@@ -68,6 +69,37 @@ describe("parseOpenFileLimits", () => {
68
69
  });
69
70
  });
70
71
 
72
+ describe("collectFdUsage", () => {
73
+ test("uses Win32 handle counts on Windows", () => {
74
+ expect(
75
+ collectFdUsage({
76
+ platform: "win32",
77
+ processTable: [
78
+ { pid: 10, ppid: 4, command: "low.exe", handleCount: 5 },
79
+ { pid: 11, ppid: 4, command: "high.exe", handleCount: 20 },
80
+ ],
81
+ }),
82
+ ).toEqual([
83
+ {
84
+ pid: 11,
85
+ command: "high.exe",
86
+ openCount: 20,
87
+ softLimit: null,
88
+ hardLimit: null,
89
+ ratio: null,
90
+ },
91
+ {
92
+ pid: 10,
93
+ command: "low.exe",
94
+ openCount: 5,
95
+ softLimit: null,
96
+ hardLimit: null,
97
+ ratio: null,
98
+ },
99
+ ]);
100
+ });
101
+ });
102
+
71
103
  describe("evaluateFdPressure", () => {
72
104
  const options = { thresholdRatio: 0.8, warnCooldownMs: 60_000 };
73
105
 
@@ -1,6 +1,10 @@
1
1
  import { describe, expect, test } from "bun:test";
2
2
 
3
- import { effectiveSizeBytes, parseSmapsRollup } from "../process-memory.js";
3
+ import {
4
+ effectiveSizeBytes,
5
+ parseSmapsRollup,
6
+ topProcessesByMemory,
7
+ } from "../process-memory.js";
4
8
 
5
9
  const SMAPS_ROLLUP = `555c98516000-7ffe248ca000 ---p 00000000 00:00 0 [rollup]
6
10
  Rss: 524288 kB
@@ -65,3 +69,32 @@ describe("effectiveSizeBytes", () => {
65
69
  ).toBe(0);
66
70
  });
67
71
  });
72
+
73
+ describe("topProcessesByMemory", () => {
74
+ test("uses Win32 working-set data on Windows", () => {
75
+ const result = topProcessesByMemory(1, {
76
+ platform: "win32",
77
+ processTable: [
78
+ { pid: 10, ppid: 4, command: "helper.exe", rssBytes: 1024 },
79
+ {
80
+ pid: 11,
81
+ ppid: 4,
82
+ command: '"C:\\Program Files\\Bun\\bun.exe" worker.ts',
83
+ rssBytes: 4096,
84
+ },
85
+ ],
86
+ });
87
+
88
+ expect(result).toEqual([
89
+ {
90
+ pid: 11,
91
+ command: "worker",
92
+ rssBytes: 4096,
93
+ pssBytes: null,
94
+ pssAnonBytes: null,
95
+ pssFileBytes: null,
96
+ pssShmemBytes: null,
97
+ },
98
+ ]);
99
+ });
100
+ });
@@ -66,6 +66,7 @@ async function runCheckSubprocess(
66
66
  );
67
67
  const child = Bun.spawn({
68
68
  cmd: [...command, dbPath],
69
+ windowsHide: true,
69
70
  stdio: ["ignore", "pipe", "ignore"],
70
71
  });
71
72
  activeChild = child;
@@ -22,7 +22,12 @@ import { readdirSync, readFileSync } from "node:fs";
22
22
 
23
23
  import type { MonitoringConfig } from "../config/schemas/monitoring.js";
24
24
  import { getLogger } from "../util/logger.js";
25
- import { readProcessCommand } from "../util/process-tree.js";
25
+ import {
26
+ getProcessTableRows,
27
+ type ProcessTableOptions,
28
+ type ProcessTableRow,
29
+ } from "../util/process-table.js";
30
+ import { deriveName, readProcessCommand } from "../util/process-tree.js";
26
31
 
27
32
  const log = getLogger("file-descriptors");
28
33
 
@@ -40,7 +45,7 @@ export interface ProcessFdUsage {
40
45
  pid: number;
41
46
  /** Redacted process descriptor (see {@link readProcessCommand}). */
42
47
  command: string;
43
- /** Entries in `/proc/<pid>/fd`. */
48
+ /** Open descriptor count, or handle count on Windows. */
44
49
  openCount: number;
45
50
  softLimit: number | null;
46
51
  hardLimit: number | null;
@@ -108,9 +113,34 @@ function readProcessFdUsage(pid: number): ProcessFdUsage | null {
108
113
  /**
109
114
  * Descriptor usage for every readable process, closest to its soft limit first
110
115
  * (processes with an unknown limit sort last, by raw count). Empty when
111
- * `/proc` is unavailable, e.g. on macOS.
116
+ * `/proc` is unavailable. Windows reports process handle counts without a
117
+ * comparable soft limit.
112
118
  */
113
- export function collectFdUsage(): ProcessFdUsage[] {
119
+ export function collectFdUsage(
120
+ options: ProcessTableOptions = {},
121
+ ): ProcessFdUsage[] {
122
+ const platform = options.platform ?? process.platform;
123
+ if (platform === "win32") {
124
+ try {
125
+ return getProcessTableRows(options)
126
+ .filter(
127
+ (row): row is ProcessTableRow & { handleCount: number } =>
128
+ row.handleCount != null,
129
+ )
130
+ .map((row) => ({
131
+ pid: row.pid,
132
+ command: deriveName(row.command),
133
+ openCount: row.handleCount,
134
+ softLimit: null,
135
+ hardLimit: null,
136
+ ratio: null,
137
+ }))
138
+ .sort((a, b) => b.openCount - a.openCount);
139
+ } catch {
140
+ return [];
141
+ }
142
+ }
143
+
114
144
  let entries: string[];
115
145
  try {
116
146
  entries = readdirSync("/proc").filter((entry) => /^\d+$/.test(entry));
@@ -133,8 +163,11 @@ export function collectFdUsage(): ProcessFdUsage[] {
133
163
  }
134
164
 
135
165
  /** The top `limit` processes by descriptor pressure, most pressured first. */
136
- export function topProcessesByFd(limit: number): ProcessFdUsage[] {
137
- return collectFdUsage().slice(0, limit);
166
+ export function topProcessesByFd(
167
+ limit: number,
168
+ options: ProcessTableOptions = {},
169
+ ): ProcessFdUsage[] {
170
+ return collectFdUsage(options).slice(0, limit);
138
171
  }
139
172
 
140
173
  /** What a pass concluded about the container's descriptor pressure. */
@@ -11,7 +11,12 @@
11
11
 
12
12
  import { readdirSync, readFileSync } from "node:fs";
13
13
 
14
- import { readProcessCommand } from "../util/process-tree.js";
14
+ import {
15
+ getProcessTableRows,
16
+ type ProcessTableOptions,
17
+ type ProcessTableRow,
18
+ } from "../util/process-table.js";
19
+ import { deriveName, readProcessCommand } from "../util/process-tree.js";
15
20
 
16
21
  /** Linux page size assumed when converting `/proc/<pid>/statm` pages to bytes. */
17
22
  const PAGE_SIZE_BYTES = 4096;
@@ -82,11 +87,38 @@ function readProcessMemory(pid: number): SmapsRollup | null {
82
87
 
83
88
  /**
84
89
  * Best-effort snapshot of the top `limit` processes by PSS (RSS when PSS is
85
- * unavailable), largest first. Empty when `/proc` is unavailable (e.g. macOS)
86
- * — the high-memory snapshot's process *tree* still captures what was running
87
- * in that case.
90
+ * unavailable), largest first. Windows uses Win32 working-set sizes. Other
91
+ * hosts without `/proc` return an empty list while the process tree still
92
+ * captures what was running.
88
93
  */
89
- export function topProcessesByMemory(limit: number): ProcessMemory[] {
94
+ export function topProcessesByMemory(
95
+ limit: number,
96
+ options: ProcessTableOptions = {},
97
+ ): ProcessMemory[] {
98
+ const platform = options.platform ?? process.platform;
99
+ if (platform === "win32") {
100
+ try {
101
+ return getProcessTableRows(options)
102
+ .filter(
103
+ (row): row is ProcessTableRow & { rssBytes: number } =>
104
+ row.rssBytes != null,
105
+ )
106
+ .map((row) => ({
107
+ pid: row.pid,
108
+ command: deriveName(row.command),
109
+ rssBytes: row.rssBytes,
110
+ pssBytes: null,
111
+ pssAnonBytes: null,
112
+ pssFileBytes: null,
113
+ pssShmemBytes: null,
114
+ }))
115
+ .sort((a, b) => b.rssBytes - a.rssBytes)
116
+ .slice(0, limit);
117
+ } catch {
118
+ return [];
119
+ }
120
+ }
121
+
90
122
  let pids: string[];
91
123
  try {
92
124
  pids = readdirSync("/proc").filter((e) => /^\d+$/.test(e));
@@ -34,7 +34,15 @@ import { diffCounters } from "../util/counter-diff.js";
34
34
  import { getDiskUsageInfo } from "../util/disk-usage.js";
35
35
  import { getLogger } from "../util/logger.js";
36
36
  import { getMonitoringDataDir } from "../util/platform.js";
37
- import { buildProcessTree, listProcesses } from "../util/process-tree.js";
37
+ import {
38
+ listProcessTableAsync,
39
+ type ProcessTableRow,
40
+ } from "../util/process-table.js";
41
+ import {
42
+ buildProcessTree,
43
+ buildSystemProcessTree,
44
+ listProcesses,
45
+ } from "../util/process-tree.js";
38
46
  import { readActiveConversations } from "./active-conversations.js";
39
47
  import { topProcessesByFd } from "./file-descriptors.js";
40
48
  import { getTrackedDataFiles, readFileResidency } from "./page-cache.js";
@@ -149,10 +157,16 @@ export async function writeSnapshot(
149
157
  mkdirSync(snapshotsDir, { recursive: true });
150
158
 
151
159
  let tree: unknown = null;
160
+ let processTable: ProcessTableRow[] | null = null;
152
161
  try {
153
- const procs = await listProcesses();
154
- // pid 1 is the container init; its subtree is the whole container.
155
- tree = buildProcessTree(procs, 1);
162
+ processTable = await listProcessTableAsync();
163
+ const procs = await listProcesses(processTable);
164
+ // pid 1 is the container init; its subtree is the whole container. Windows
165
+ // has no such root, so every process hangs off a synthetic one.
166
+ tree =
167
+ process.platform === "win32"
168
+ ? buildSystemProcessTree(procs)
169
+ : buildProcessTree(procs, 1);
156
170
  } catch (err) {
157
171
  log.warn({ err }, "Failed to enumerate process tree for snapshot");
158
172
  }
@@ -166,21 +180,24 @@ export async function writeSnapshot(
166
180
  log.warn({ err }, "Failed to read page-cache residency for snapshot");
167
181
  }
168
182
 
183
+ const processOptions = { platform: process.platform, processTable };
169
184
  const snapshot = {
170
185
  ts: sample.ts,
171
186
  kind,
172
187
  sample,
173
188
  fileResidency,
174
189
  // PSS-ranked with per-process anon/file split; PSS sums reconcile against
175
- // the cgroup total where an RSS sum double-counts shared pages.
176
- topProcesses: topProcessesByMemory(15),
190
+ // the cgroup total where an RSS sum double-counts shared pages. Windows
191
+ // reports working-set size instead.
192
+ topProcesses: topProcessesByMemory(15, processOptions),
177
193
  // Slab memory belongs to no process; without this, cgroup usage that
178
194
  // exceeds the per-process sum has no visible owner.
179
195
  topSlabCaches: topSlabCaches(10),
180
196
  // Descriptor pressure, ranked against each process's own soft limit. An
181
197
  // EMFILE storm and a memory spike look alike from the outside (unrelated
182
- // failures across the daemon), so the capture records both.
183
- topProcessesByFd: topProcessesByFd(15),
198
+ // failures across the daemon), so the capture records both. Windows
199
+ // records raw handle counts since it has no comparable soft limit.
200
+ topProcessesByFd: topProcessesByFd(15, processOptions),
184
201
  processTree: tree,
185
202
  };
186
203