@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
@@ -194,3 +194,64 @@ describe("compactor records llm_request_logs with call_site=compactionAgent", ()
194
194
  expect(recordRequestLogCalls[0]!.callSite).toBe("compactionAgent");
195
195
  });
196
196
  });
197
+
198
+ describe("compaction result reports what actually served the summary", () => {
199
+ beforeEach(() => {
200
+ recordRequestLogCalls.length = 0;
201
+ });
202
+
203
+ test("rerouted summary carries the backup provider and profile", async () => {
204
+ // `RetryProvider` escalated the summary call to a backup profile on
205
+ // another provider and stamped the response, so the result must report
206
+ // the backup even though the compactor resolved `primaryProfile`.
207
+ const provider: Provider = {
208
+ name: "openai",
209
+ sendMessage: async () => ({
210
+ content: [{ type: "text", text: compactionResponse }],
211
+ model: "claude-sonnet-5",
212
+ actualProvider: "anthropic",
213
+ actualInferenceProfile: "backupProfile",
214
+ usage: { inputTokens: 100, outputTokens: 50 },
215
+ stopReason: "end_turn",
216
+ rawRequest: RAW_REQUEST,
217
+ rawResponse: RAW_RESPONSE,
218
+ }),
219
+ };
220
+
221
+ const result = await runAssistantDrivenCompaction({
222
+ ...args(provider),
223
+ overrideProfile: "primaryProfile",
224
+ });
225
+
226
+ expect(result.summaryModel).toBe("claude-sonnet-5");
227
+ expect(result.summaryActualProvider).toBe("anthropic");
228
+ expect(result.summaryActualInferenceProfile).toBe("backupProfile");
229
+ // The primary's resolution is still reported, just outranked downstream.
230
+ expect(result.summaryOverrideProfile).toBe("primaryProfile");
231
+ // Same value the request log recorded: one source of truth, not two.
232
+ expect(recordRequestLogCalls[0]!.provider).toBe("anthropic");
233
+ });
234
+
235
+ test("non-rerouted summary carries the configured provider and no profile", async () => {
236
+ const provider: Provider = {
237
+ name: "openai",
238
+ sendMessage: async () => ({
239
+ content: [{ type: "text", text: compactionResponse }],
240
+ model: "mock-model",
241
+ usage: { inputTokens: 100, outputTokens: 50 },
242
+ stopReason: "end_turn",
243
+ rawRequest: RAW_REQUEST,
244
+ rawResponse: RAW_RESPONSE,
245
+ }),
246
+ };
247
+
248
+ const result = await runAssistantDrivenCompaction({
249
+ ...args(provider),
250
+ overrideProfile: "primaryProfile",
251
+ });
252
+
253
+ expect(result.summaryActualProvider).toBe("openai");
254
+ expect(result.summaryActualInferenceProfile).toBeUndefined();
255
+ expect(result.summaryOverrideProfile).toBe("primaryProfile");
256
+ });
257
+ });
@@ -1411,6 +1411,71 @@ describe("loadConfig startup behavior", () => {
1411
1411
  expect(effective.balanced?.provider_connection).toBe("anthropic-personal");
1412
1412
  });
1413
1413
 
1414
+ test("an active managed backup profile survives a reload", () => {
1415
+ // A managed backup resolves from the code catalog without being
1416
+ // materialized in `llm.profiles`, so selecting one persists nothing but
1417
+ // the reference. If the schema did not treat backup keys as
1418
+ // always-available references, the strip-and-reparse in `loadConfig`
1419
+ // would drop `activeProfile` and the seeder would reset the user's
1420
+ // chat-model selection to `balanced` on the next boot.
1421
+ writeConfig({
1422
+ llm: {
1423
+ defaultProvider: { provider: "vellum" },
1424
+ profiles: {},
1425
+ activeProfile: "balanced-backup",
1426
+ },
1427
+ });
1428
+
1429
+ const config = loadConfig();
1430
+ expect(config.llm.activeProfile).toBe("balanced-backup");
1431
+
1432
+ mergeDefaultConfigAndSeedInferenceProfiles();
1433
+
1434
+ const raw = JSON.parse(readFileSync(CONFIG_PATH, "utf-8"));
1435
+ expect(raw.llm.activeProfile).toBe("balanced-backup");
1436
+ expect(raw.llm.profiles["balanced-backup"]).toBeUndefined();
1437
+ });
1438
+
1439
+ test("an active managed backup profile is repaired on a BYOK install", () => {
1440
+ // Backups live on the managed column alone, so under a BYOK default
1441
+ // provider the selection names a profile that resolves to nothing. The
1442
+ // schema rejects the reference on load and the seeder, which resolves
1443
+ // availability through the same provider-aware view, repairs the stored
1444
+ // selection instead of preserving a dead pointer.
1445
+ writeConfig({
1446
+ llm: {
1447
+ defaultProvider: { provider: "anthropic" },
1448
+ profiles: {},
1449
+ activeProfile: "balanced-backup",
1450
+ },
1451
+ });
1452
+
1453
+ const config = loadConfig();
1454
+ expect(config.llm.activeProfile).toBeUndefined();
1455
+
1456
+ mergeDefaultConfigAndSeedInferenceProfiles();
1457
+
1458
+ const raw = JSON.parse(readFileSync(CONFIG_PATH, "utf-8"));
1459
+ expect(raw.llm.activeProfile).toBe("balanced");
1460
+ });
1461
+
1462
+ test("a call site pinned to a managed backup profile survives a reload", () => {
1463
+ writeConfig({
1464
+ llm: {
1465
+ defaultProvider: { provider: "vellum" },
1466
+ profiles: {},
1467
+ activeProfile: "balanced",
1468
+ callSites: { recall: { profile: "cost-optimized-backup" } },
1469
+ },
1470
+ });
1471
+
1472
+ const config = loadConfig();
1473
+ expect(config.llm.callSites.recall?.profile).toBe("cost-optimized-backup");
1474
+
1475
+ const raw = JSON.parse(readFileSync(CONFIG_PATH, "utf-8"));
1476
+ expect(raw.llm.callSites.recall.profile).toBe("cost-optimized-backup");
1477
+ });
1478
+
1414
1479
  test("still quarantines corrupt JSON", () => {
1415
1480
  // Corrupt-config quarantine is a recovery path: the broken file is
1416
1481
  // renamed to `config.json.corrupt-<ts>.json` and the daemon proceeds
@@ -1800,6 +1865,10 @@ describe("seedInferenceProfiles BYOK-mode default profiles", () => {
1800
1865
  "quality-optimized",
1801
1866
  "cost-optimized",
1802
1867
  "latency-optimized",
1868
+ "balanced-backup",
1869
+ "quality-optimized-backup",
1870
+ "cost-optimized-backup",
1871
+ "latency-optimized-backup",
1803
1872
  "my-custom",
1804
1873
  ]);
1805
1874
  });
@@ -959,8 +959,7 @@ describe("AssistantConfigSchema", () => {
959
959
  },
960
960
  },
961
961
  flux: {
962
- turnEnd: { enabled: false },
963
- model: "flux-general-en",
962
+ turnEnd: { enabled: true },
964
963
  eotThreshold: 0.7,
965
964
  eotTimeoutMs: 5000,
966
965
  },
@@ -989,7 +988,7 @@ describe("AssistantConfigSchema", () => {
989
988
  expect(result.liveVoice.vad.maxTurnDurationMs).toBe(30000);
990
989
  expect(result.liveVoice.maxSessionDurationSeconds).toBe(900);
991
990
  // A partial liveVoice override leaves Flux turn-end disabled by default.
992
- expect(result.liveVoice.flux.turnEnd.enabled).toBe(false);
991
+ expect(result.liveVoice.flux.turnEnd.enabled).toBe(true);
993
992
  });
994
993
 
995
994
  test("accepts a liveVoice.vad.bargeInMinSpeechMs of 0 (guard disabled)", () => {
@@ -23,6 +23,10 @@ import { resetPluginRegistryAndRegisterDefaults } from "../plugins/defaults/inde
23
23
  import { registerPlugin } from "../plugins/registry.js";
24
24
  import type { Message, Provider, ToolDefinition } from "../providers/types.js";
25
25
  import { ContextOverflowError } from "../providers/types.js";
26
+ import {
27
+ resolveUsageAttribution,
28
+ type UsageAttributionInput,
29
+ } from "../usage/attribution.js";
26
30
  import { getWorkspaceDir } from "../util/platform.js";
27
31
  import { setConfig } from "./helpers/set-config.js";
28
32
 
@@ -1763,6 +1767,108 @@ describe("session-agent-loop", () => {
1763
1767
  expect(mainAgentCall?.[3]).toBe("gpt-4.1-2026-03-01");
1764
1768
  });
1765
1769
 
1770
+ test("attributes a fallback serve to the profile that actually served", async () => {
1771
+ // The turn resolved to the primary managed profile, the request hit an
1772
+ // outage-shaped failure, and `RetryProvider` escalated to a backup
1773
+ // profile on a different provider, stamping `actualProvider` and
1774
+ // `actualInferenceProfile` on the response it returns.
1775
+ seedLlmConfig({
1776
+ profiles: {
1777
+ ...disabledCatalogDefaultProfiles,
1778
+ backupProfile: { provider: "anthropic", model: "claude-sonnet-5" },
1779
+ },
1780
+ });
1781
+
1782
+ const ctx = makeCtx({
1783
+ providerResponses: [
1784
+ {
1785
+ content: [{ type: "text", text: "Hi there." }],
1786
+ model: "claude-sonnet-5",
1787
+ usage: { inputTokens: 12, outputTokens: 3 },
1788
+ stopReason: "end_turn",
1789
+ actualProvider: "anthropic",
1790
+ actualInferenceProfile: "backupProfile",
1791
+ },
1792
+ ],
1793
+ provider: {
1794
+ name: "openai",
1795
+ sendMessage: async () => ({
1796
+ content: [{ type: "text", text: "title" }],
1797
+ model: "mock",
1798
+ usage: { inputTokens: 0, outputTokens: 0 },
1799
+ stopReason: "end_turn",
1800
+ }),
1801
+ } as unknown as Conversation["provider"],
1802
+ });
1803
+
1804
+ await runAgentLoopImpl(ctx, "hello", "msg-1", () => {});
1805
+
1806
+ const mainAgentCall = recordUsageMock.mock.calls.find(
1807
+ (call) => (call as unknown[])[5] === "main_agent",
1808
+ ) as unknown[] | undefined;
1809
+
1810
+ expect(mainAgentCall).toBeDefined();
1811
+ // All three attribution facets of the row must describe the same call.
1812
+ expect(mainAgentCall?.[0]).toMatchObject({ providerName: "anthropic" });
1813
+ expect(mainAgentCall?.[3]).toBe("claude-sonnet-5");
1814
+ const attribution = mainAgentCall?.[12] as UsageAttributionInput;
1815
+ expect(attribution.callSite).toBe("mainAgent");
1816
+ expect(attribution.overrideProfile).toBe("backupProfile");
1817
+ expect(attribution.forceOverrideProfile).toBe(true);
1818
+ // `inference_profile` on the persisted row is this snapshot's applied
1819
+ // profile, so resolve it the way `recordUsage` does and assert the
1820
+ // column value itself rather than only the input that feeds it.
1821
+ expect(resolveUsageAttribution(attribution).appliedProfile).toBe(
1822
+ "backupProfile",
1823
+ );
1824
+ });
1825
+
1826
+ test("leaves a non-fallback turn attributed to the conversation's own profile", async () => {
1827
+ // No reroute: the response carries no `actualInferenceProfile`, so the
1828
+ // attribution input stays exactly what the conversation resolved and
1829
+ // carries no forced override.
1830
+ seedLlmConfig({
1831
+ profiles: {
1832
+ ...disabledCatalogDefaultProfiles,
1833
+ backupProfile: { provider: "anthropic", model: "claude-sonnet-5" },
1834
+ },
1835
+ });
1836
+
1837
+ const ctx = makeCtx({
1838
+ providerResponses: [
1839
+ {
1840
+ content: [{ type: "text", text: "Hi there." }],
1841
+ model: "gpt-4.1-2026-03-01",
1842
+ usage: { inputTokens: 12, outputTokens: 3 },
1843
+ stopReason: "end_turn",
1844
+ },
1845
+ ],
1846
+ provider: {
1847
+ name: "openai",
1848
+ sendMessage: async () => ({
1849
+ content: [{ type: "text", text: "title" }],
1850
+ model: "mock",
1851
+ usage: { inputTokens: 0, outputTokens: 0 },
1852
+ stopReason: "end_turn",
1853
+ }),
1854
+ } as unknown as Conversation["provider"],
1855
+ });
1856
+
1857
+ await runAgentLoopImpl(ctx, "hello", "msg-1", () => {});
1858
+
1859
+ const mainAgentCall = recordUsageMock.mock.calls.find(
1860
+ (call) => (call as unknown[])[5] === "main_agent",
1861
+ ) as unknown[] | undefined;
1862
+
1863
+ expect(mainAgentCall).toBeDefined();
1864
+ expect(mainAgentCall?.[0]).toMatchObject({ providerName: "openai" });
1865
+ expect(mainAgentCall?.[3]).toBe("gpt-4.1-2026-03-01");
1866
+ expect(mainAgentCall?.[12]).toEqual({
1867
+ callSite: "mainAgent",
1868
+ overrideProfile: null,
1869
+ });
1870
+ });
1871
+
1766
1872
  test("persists the served model onto the assistant row's metadata at finalize", async () => {
1767
1873
  const events: AssistantEvent[] = [];
1768
1874
 
@@ -4037,6 +4143,138 @@ describe("session-agent-loop", () => {
4037
4143
  );
4038
4144
  });
4039
4145
 
4146
+ test("rerouted compaction records usage under the profile that actually served", async () => {
4147
+ const ctx = makeCtx();
4148
+
4149
+ await applyCompactionResult(
4150
+ ctx,
4151
+ {
4152
+ messages: [
4153
+ { role: "user", content: [{ type: "text", text: "summary" }] },
4154
+ ],
4155
+ compactedPersistedMessages: 4,
4156
+ previousEstimatedInputTokens: 12000,
4157
+ estimatedInputTokens: 3000,
4158
+ maxInputTokens: 100000,
4159
+ thresholdTokens: 80000,
4160
+ compactedMessages: 4,
4161
+ summaryCalls: 1,
4162
+ summaryInputTokens: 100,
4163
+ summaryOutputTokens: 20,
4164
+ summaryModel: "claude-sonnet-5",
4165
+ summaryText: "summary",
4166
+ summaryCallSite: "compactionAgent",
4167
+ // What the compactor resolved before the call...
4168
+ summaryOverrideProfile: "primaryProfile",
4169
+ // ...and what the reroute actually served.
4170
+ summaryActualProvider: "anthropic",
4171
+ summaryActualInferenceProfile: "backupProfile",
4172
+ },
4173
+ () => {},
4174
+ "req-1",
4175
+ );
4176
+
4177
+ const compactorCall = recordUsageMock.mock.calls.find(
4178
+ (call) => (call as unknown[])[5] === "context_compactor",
4179
+ ) as unknown[] | undefined;
4180
+
4181
+ expect(compactorCall).toBeDefined();
4182
+ // All three attribution facets of the row describe the same call.
4183
+ expect(compactorCall?.[0]).toMatchObject({ providerName: "anthropic" });
4184
+ expect(compactorCall?.[3]).toBe("claude-sonnet-5");
4185
+ expect(compactorCall?.[12]).toEqual({
4186
+ callSite: "compactionAgent",
4187
+ overrideProfile: "backupProfile",
4188
+ forceOverrideProfile: true,
4189
+ });
4190
+ });
4191
+
4192
+ test("non-rerouted compaction keeps the compactor's own attribution", async () => {
4193
+ const ctx = makeCtx();
4194
+
4195
+ await applyCompactionResult(
4196
+ ctx,
4197
+ {
4198
+ messages: [
4199
+ { role: "user", content: [{ type: "text", text: "summary" }] },
4200
+ ],
4201
+ compactedPersistedMessages: 4,
4202
+ previousEstimatedInputTokens: 12000,
4203
+ estimatedInputTokens: 3000,
4204
+ maxInputTokens: 100000,
4205
+ thresholdTokens: 80000,
4206
+ compactedMessages: 4,
4207
+ summaryCalls: 1,
4208
+ summaryInputTokens: 100,
4209
+ summaryOutputTokens: 20,
4210
+ summaryModel: "mock-model",
4211
+ summaryText: "summary",
4212
+ summaryCallSite: "compactionAgent",
4213
+ summaryOverrideProfile: "primaryProfile",
4214
+ summaryActualProvider: "openai",
4215
+ },
4216
+ () => {},
4217
+ "req-1",
4218
+ );
4219
+
4220
+ const compactorCall = recordUsageMock.mock.calls.find(
4221
+ (call) => (call as unknown[])[5] === "context_compactor",
4222
+ ) as unknown[] | undefined;
4223
+
4224
+ expect(compactorCall).toBeDefined();
4225
+ expect(compactorCall?.[0]).toMatchObject({ providerName: "openai" });
4226
+ expect(compactorCall?.[3]).toBe("mock-model");
4227
+ expect(compactorCall?.[12]).toEqual({
4228
+ callSite: "compactionAgent",
4229
+ overrideProfile: "primaryProfile",
4230
+ });
4231
+ });
4232
+
4233
+ test("compaction that served no call falls back to the conversation's provider", async () => {
4234
+ // The degraded shape: `emptyResult` paths record no served attribution
4235
+ // because no call happened. Provider must fall back to the
4236
+ // conversation's own, and the attribution input must be byte-identical
4237
+ // to what it was before served attribution existed.
4238
+ const ctx = makeCtx();
4239
+
4240
+ await applyCompactionResult(
4241
+ ctx,
4242
+ {
4243
+ messages: [
4244
+ { role: "user", content: [{ type: "text", text: "summary" }] },
4245
+ ],
4246
+ compactedPersistedMessages: 4,
4247
+ previousEstimatedInputTokens: 12000,
4248
+ estimatedInputTokens: 3000,
4249
+ maxInputTokens: 100000,
4250
+ thresholdTokens: 80000,
4251
+ compactedMessages: 4,
4252
+ summaryCalls: 1,
4253
+ summaryInputTokens: 100,
4254
+ summaryOutputTokens: 20,
4255
+ summaryModel: "mock-model",
4256
+ summaryText: "summary",
4257
+ summaryCallSite: "compactionAgent",
4258
+ summaryOverrideProfile: "primaryProfile",
4259
+ },
4260
+ () => {},
4261
+ "req-1",
4262
+ );
4263
+
4264
+ const compactorCall = recordUsageMock.mock.calls.find(
4265
+ (call) => (call as unknown[])[5] === "context_compactor",
4266
+ ) as unknown[] | undefined;
4267
+
4268
+ expect(compactorCall).toBeDefined();
4269
+ expect(compactorCall?.[0]).toMatchObject({
4270
+ providerName: "mock-provider",
4271
+ });
4272
+ expect(compactorCall?.[12]).toEqual({
4273
+ callSite: "compactionAgent",
4274
+ overrideProfile: "primaryProfile",
4275
+ });
4276
+ });
4277
+
4040
4278
  test("applyCompactionResult advances the persisted count from the trusted in-context boundary", async () => {
4041
4279
  // Trusted views slice past the already-compacted prefix, so a further
4042
4280
  // compaction advances the persisted count from the mirrored DB boundary.
@@ -28,7 +28,6 @@ mock.module("../permissions/types.js", () => ({
28
28
  Medium: "medium",
29
29
  High: "high",
30
30
  },
31
- isAllowDecision: () => true,
32
31
  }));
33
32
 
34
33
  import type { AssistantAttachmentDraft } from "../daemon/assistant-attachments.js";
@@ -1136,6 +1136,50 @@ describe("classifyConversationError", () => {
1136
1136
  });
1137
1137
 
1138
1138
  describe("reason-driven classification (ProviderError.reason)", () => {
1139
+ it("classifies reason=request_shape_unsupported as a friendly capability-mismatch message", () => {
1140
+ // GIVEN a chat-template 400 that the provider stamped with the
1141
+ // semantic reason (Together serving MiniMax M3)
1142
+ const err = new ProviderError(
1143
+ "Together AI API error (400): Failed to apply chat template: invalid operation: object is not callable (in chat:22)",
1144
+ "together",
1145
+ 400,
1146
+ { reason: "request_shape_unsupported" },
1147
+ );
1148
+
1149
+ // WHEN it is classified
1150
+ const result = classifyConversationError(err, baseCtx);
1151
+
1152
+ // THEN the user sees the capability-mismatch copy, not the raw
1153
+ // template error
1154
+ expect(result.code).toBe("PROVIDER_API");
1155
+ expect(result.errorCategory).toBe("request_shape_unsupported");
1156
+ expect(result.retryable).toBe(false);
1157
+ expect(result.userMessage).toContain(
1158
+ "couldn't process the request format",
1159
+ );
1160
+ expect(result.userMessage).not.toContain("chat template");
1161
+ });
1162
+
1163
+ it("classifies a reason-less chat-template 400 via the message ladder", () => {
1164
+ // GIVEN a chat-template 400 whose ProviderError carries no semantic
1165
+ // reason (e.g. thrown by a non-OpenAI-compatible wrapper)
1166
+ const err = new ProviderError(
1167
+ "Together AI API error (400): Failed to apply chat template: invalid operation: object is not callable (in chat:22)",
1168
+ "together",
1169
+ 400,
1170
+ );
1171
+
1172
+ // WHEN it is classified
1173
+ const result = classifyConversationError(err, baseCtx);
1174
+
1175
+ // THEN the regex ladder still lands on the capability-mismatch copy
1176
+ expect(result.code).toBe("PROVIDER_API");
1177
+ expect(result.errorCategory).toBe("request_shape_unsupported");
1178
+ expect(result.userMessage).toContain(
1179
+ "couldn't process the request format",
1180
+ );
1181
+ });
1182
+
1139
1183
  it("classifies reason=model_restricted on the skew-safe PROVIDER_API code with a specific errorCategory", () => {
1140
1184
  const err = new ProviderError(
1141
1185
  "Vercel AI Gateway API error (403): Model claude-opus-4 is restricted on your plan [type=no_providers_available]",
@@ -9,6 +9,13 @@ mock.module("../subagent/index.js", () => ({
9
9
  getSubagentManager: () => ({
10
10
  abortAllForParent: (id: string) => abortedParents.push(id),
11
11
  getChildrenOf: () => protectedChildren,
12
+ hasActiveChildren: () =>
13
+ protectedChildren.some(
14
+ (c) =>
15
+ c.status === "running" ||
16
+ c.status === "pending" ||
17
+ c.status === "awaiting_input",
18
+ ),
12
19
  }),
13
20
  }));
14
21
 
@@ -236,6 +243,18 @@ describe("ConversationEvictor", () => {
236
243
  expect(sessions.has("a")).toBe(true);
237
244
  expect(s1.disposed).toBe(false);
238
245
  });
246
+
247
+ test("skips conversations with awaiting_input subagents", () => {
248
+ protectedChildren = [{ status: "awaiting_input" }];
249
+ const s1 = createMockSession();
250
+ sessions.set("a", s1);
251
+
252
+ const result = evictor.sweep();
253
+
254
+ expect(result.skipped).toBe(1);
255
+ expect(sessions.has("a")).toBe(true);
256
+ expect(s1.disposed).toBe(false);
257
+ });
239
258
  });
240
259
 
241
260
  describe("remove()", () => {