@vellumai/assistant 0.11.7 → 0.11.8-staging.1

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 (446) hide show
  1. package/ARCHITECTURE.md +10 -10
  2. package/Dockerfile +1 -0
  3. package/docs/architecture/security.md +1 -1
  4. package/docs/error-handling.md +1 -2
  5. package/docs/guardian-request-flow.md +51 -5
  6. package/docs/trusted-contact-access.md +13 -12
  7. package/knip.json +1 -0
  8. package/node_modules/@vellumai/avatar-catalog/package.json +19 -0
  9. package/{src/avatar/character-components.ts → node_modules/@vellumai/avatar-catalog/src/catalog.ts} +12 -44
  10. package/node_modules/@vellumai/avatar-catalog/src/colors.ts +19 -0
  11. package/node_modules/@vellumai/avatar-catalog/src/index.ts +25 -0
  12. package/node_modules/@vellumai/avatar-catalog/src/types.ts +47 -0
  13. package/node_modules/@vellumai/avatar-catalog/tsconfig.json +20 -0
  14. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/package.json +1 -0
  15. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/channels.ts +7 -0
  16. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
  17. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/no-response.ts +84 -0
  18. package/node_modules/@vellumai/credential-storage/src/static-credentials.ts +43 -12
  19. package/node_modules/@vellumai/environments/package.json +2 -1
  20. package/node_modules/@vellumai/environments/src/__tests__/test-path-guard.test.ts +89 -0
  21. package/node_modules/@vellumai/environments/src/test-path-guard.ts +113 -0
  22. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/package.json +1 -0
  23. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/channels.ts +7 -0
  24. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
  25. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/no-response.ts +84 -0
  26. package/node_modules/@vellumai/gateway-client/src/__tests__/guardian-request-contract.test.ts +8 -18
  27. package/node_modules/@vellumai/gateway-client/src/__tests__/inbound-event-kind.test.ts +46 -0
  28. package/node_modules/@vellumai/gateway-client/src/__tests__/invite-contract.test.ts +1 -0
  29. package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +46 -0
  30. package/node_modules/@vellumai/gateway-client/src/guardian-request-contract.ts +62 -21
  31. package/node_modules/@vellumai/gateway-client/src/inbound-contract.ts +14 -0
  32. package/node_modules/@vellumai/gateway-client/src/inbound-event-kind.ts +53 -0
  33. package/node_modules/@vellumai/gateway-client/src/index.ts +11 -5
  34. package/node_modules/@vellumai/gateway-client/src/invite-contract.ts +1 -0
  35. package/node_modules/@vellumai/service-contracts/package.json +1 -0
  36. package/node_modules/@vellumai/service-contracts/src/channels.ts +7 -0
  37. package/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
  38. package/node_modules/@vellumai/service-contracts/src/no-response.ts +84 -0
  39. package/openapi.yaml +481 -9
  40. package/package.json +3 -1
  41. package/scripts/sync-llm-catalog.ts +3 -0
  42. package/src/__tests__/agent-loop-callsite-precedence.test.ts +16 -0
  43. package/src/__tests__/agent-loop-compacted-history-transform.test.ts +304 -0
  44. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +3 -0
  45. package/src/__tests__/agent-wake-override-profile.test.ts +3 -0
  46. package/src/__tests__/anthropic-provider.test.ts +56 -0
  47. package/src/__tests__/approval-cascade.test.ts +0 -13
  48. package/src/__tests__/attachments-store.test.ts +117 -6
  49. package/src/__tests__/attachments.test.ts +122 -0
  50. package/src/__tests__/bundled-asset.test.ts +22 -1
  51. package/src/__tests__/channel-approval.test.ts +14 -7
  52. package/src/__tests__/channel-approvals.test.ts +3 -3
  53. package/src/__tests__/channel-delivery-store.test.ts +58 -0
  54. package/src/__tests__/channel-inbound-disk-pressure.test.ts +1 -0
  55. package/src/__tests__/channel-invite-transport.test.ts +1 -0
  56. package/src/__tests__/channel-readiness-slack-remote.test.ts +3 -3
  57. package/src/__tests__/compaction-events.test.ts +0 -12
  58. package/src/__tests__/compactor-call-site-logging.test.ts +7 -1
  59. package/src/__tests__/compactor-retained-image-validation.test.ts +15 -0
  60. package/src/__tests__/config-schema.test.ts +35 -0
  61. package/src/__tests__/contacts-write.test.ts +141 -3
  62. package/src/__tests__/conversation-abort-tool-results.test.ts +0 -13
  63. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +0 -12
  64. package/src/__tests__/conversation-agent-loop-overflow.test.ts +0 -13
  65. package/src/__tests__/conversation-agent-loop.test.ts +95 -13
  66. package/src/__tests__/conversation-confirmation-signals.test.ts +0 -13
  67. package/src/__tests__/conversation-load-history-repair.test.ts +418 -0
  68. package/src/__tests__/conversation-media-retry.test.ts +33 -2
  69. package/src/__tests__/conversation-pairing.test.ts +54 -0
  70. package/src/__tests__/conversation-process-callsite.test.ts +0 -12
  71. package/src/__tests__/conversation-provider-retry-repair.test.ts +0 -13
  72. package/src/__tests__/conversation-queue.test.ts +4 -13
  73. package/src/__tests__/conversation-retry-route.test.ts +5 -0
  74. package/src/__tests__/conversation-routes-disk-view.test.ts +1 -1
  75. package/src/__tests__/conversation-routes-enabled-plugins.test.ts +1 -1
  76. package/src/__tests__/conversation-routes-hidden-queue.test.ts +1 -1
  77. package/src/__tests__/conversation-routes-slash-commands.test.ts +1 -1
  78. package/src/__tests__/conversation-sight-frames.test.ts +615 -0
  79. package/src/__tests__/conversation-slash-queue.test.ts +0 -13
  80. package/src/__tests__/conversation-slash-unknown.test.ts +0 -13
  81. package/src/__tests__/conversation-speed-override.test.ts +0 -12
  82. package/src/__tests__/conversation-store-ephemeral.test.ts +0 -12
  83. package/src/__tests__/conversation-summarize-route.test.ts +5 -0
  84. package/src/__tests__/conversation-summarize-up-to.test.ts +102 -0
  85. package/src/__tests__/conversation-wait-for-idle.test.ts +0 -12
  86. package/src/__tests__/conversation-workspace-cache-state.test.ts +0 -5
  87. package/src/__tests__/conversation-workspace-injection.test.ts +0 -17
  88. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +0 -17
  89. package/src/__tests__/credential-health-service.test.ts +45 -0
  90. package/src/__tests__/credential-record-retire.test.ts +256 -0
  91. package/src/__tests__/credential-record-write-through.test.ts +8 -4
  92. package/src/__tests__/db-conversation-fork-lineage-migration.test.ts +0 -4
  93. package/src/__tests__/db-conversation-inference-profile-migration.test.ts +0 -4
  94. package/src/__tests__/db-llm-request-log-provider-migration.test.ts +0 -8
  95. package/src/__tests__/delete-propagation.test.ts +121 -3
  96. package/src/__tests__/evict-conversations-for-reload.test.ts +46 -6
  97. package/src/__tests__/file-ops-service.test.ts +8 -3
  98. package/src/__tests__/gemini-provider.test.ts +64 -0
  99. package/src/__tests__/guardian-action-service-scope.test.ts +118 -0
  100. package/src/__tests__/guardian-card-withdrawal.test.ts +299 -5
  101. package/src/__tests__/guardian-expiry-notifier.test.ts +117 -13
  102. package/src/__tests__/guardian-gateway-sim.test.ts +119 -0
  103. package/src/__tests__/guardian-gateway-sim.ts +56 -20
  104. package/src/__tests__/guardian-reply-router-answer-mode.test.ts +6 -0
  105. package/src/__tests__/guardian-request-status-sync.test.ts +162 -0
  106. package/src/__tests__/guardian-wait-controller.test.ts +6 -0
  107. package/src/__tests__/headless-browser-mode.test.ts +27 -0
  108. package/src/__tests__/helpers/mock-conversation.ts +2 -0
  109. package/src/__tests__/list-messages-system-card.test.ts +70 -0
  110. package/src/__tests__/llm-catalog-parity.test.ts +2 -0
  111. package/src/__tests__/managed-profile-guard.test.ts +2 -2
  112. package/src/__tests__/media-resolve-image-validation.test.ts +106 -0
  113. package/src/__tests__/media-stream-server-integration.test.ts +6 -0
  114. package/src/__tests__/non-member-access-request.test.ts +12 -0
  115. package/src/__tests__/notification-discord-adapter.test.ts +76 -4
  116. package/src/__tests__/oauth-commands-routes.test.ts +21 -0
  117. package/src/__tests__/openai-provider.test.ts +63 -0
  118. package/src/__tests__/outbound-slack-persistence.test.ts +114 -2
  119. package/src/__tests__/persist-unsendable-image-downscale.test.ts +63 -1
  120. package/src/__tests__/persist-unsendable-image.test.ts +19 -0
  121. package/src/__tests__/plugin-app-serve-routes.test.ts +2 -2
  122. package/src/__tests__/plugin-import-boundary-guard.test.ts +1 -0
  123. package/src/__tests__/process-message-background-slack.test.ts +1 -1
  124. package/src/__tests__/process-message-display-content.test.ts +1 -1
  125. package/src/__tests__/processing-flag-persist-failure.test.ts +0 -5
  126. package/src/__tests__/prompt-cache-cross-turn-stability.test.ts +0 -17
  127. package/src/__tests__/qdrant-manager.test.ts +6 -0
  128. package/src/__tests__/reaction-persistence.test.ts +58 -62
  129. package/src/__tests__/retry-thinking-adaptive-only.test.ts +67 -0
  130. package/src/__tests__/server-history-render.test.ts +22 -0
  131. package/src/__tests__/sight-frame-retention.test.ts +362 -0
  132. package/src/__tests__/slack-reaction-approvals.test.ts +44 -24
  133. package/src/__tests__/slack-reaction-guardian-approval.test.ts +29 -1
  134. package/src/__tests__/subagent-fork-notifications.test.ts +2 -0
  135. package/src/__tests__/subagent-manager-notify.test.ts +2 -0
  136. package/src/__tests__/subagent-notify-parent.test.ts +2 -0
  137. package/src/__tests__/subagent-notify-stale-rebuild.test.ts +85 -0
  138. package/src/__tests__/subagent-tool-gate-mode.test.ts +4 -4
  139. package/src/__tests__/terminal-tools.test.ts +9 -9
  140. package/src/__tests__/tool-result-spool.test.ts +5 -8
  141. package/src/__tests__/usage-attribution.test.ts +26 -0
  142. package/src/acp/__tests__/acp-auth-marker-store.test.ts +191 -0
  143. package/src/acp/__tests__/acp-claude-oauth.test.ts +262 -0
  144. package/src/acp/__tests__/acp-connect-card-state.test.ts +17 -0
  145. package/src/acp/__tests__/helpers/acp-history-db.ts +10 -3
  146. package/src/acp/acp-auth-marker-store.ts +196 -0
  147. package/src/acp/acp-claude-oauth.ts +154 -4
  148. package/src/acp/acp-connect-card-state.ts +49 -8
  149. package/src/acp/agent-process.ts +1 -0
  150. package/src/acp/auto-install.ts +1 -0
  151. package/src/acp/client-handler.ts +1 -0
  152. package/src/acp/prepare-agent-env.ts +79 -1
  153. package/src/acp/resolve-agent.ts +12 -0
  154. package/src/acp/session-manager.test.ts +183 -0
  155. package/src/acp/session-manager.ts +110 -6
  156. package/src/acp/types.ts +17 -0
  157. package/src/agent/attachments.ts +7 -2
  158. package/src/agent/loop.ts +59 -1
  159. package/src/api/events/contact-form-closed.ts +32 -0
  160. package/src/api/events/contact-record-request.ts +63 -0
  161. package/src/api/events/contact-request.ts +10 -3
  162. package/src/api/index.ts +20 -0
  163. package/src/api/responses/conversation-message.ts +20 -0
  164. package/src/api/responses/home.ts +101 -0
  165. package/src/approvals/AGENTS.md +1 -1
  166. package/src/approvals/guardian-card-withdrawal.ts +127 -30
  167. package/src/approvals/guardian-decision-primitive.ts +9 -0
  168. package/src/approvals/guardian-expiry-notifier.ts +22 -16
  169. package/src/approvals/guardian-request-status-sync.ts +98 -0
  170. package/src/avatar/svg-compositor.ts +1 -1
  171. package/src/avatar/traits-png-sync.ts +1 -1
  172. package/src/bundler/app-compiler.ts +1 -0
  173. package/src/bundler/compiler-tools.ts +1 -0
  174. package/src/bundler/package-resolver.ts +1 -0
  175. package/src/calls/__tests__/voice-session-bridge.test.ts +183 -3
  176. package/src/calls/guardian-action-sweep.ts +6 -1
  177. package/src/calls/voice-session-bridge.ts +46 -1
  178. package/src/channels/__tests__/gateway-guardian-requests.test.ts +13 -18
  179. package/src/channels/gateway-guardian-requests.ts +37 -18
  180. package/src/cli/__tests__/contacts-record-prompt-cli.test.ts +329 -0
  181. package/src/cli/commands/__tests__/browser.test.ts +65 -1
  182. package/src/cli/commands/browser.ts +30 -10
  183. package/src/cli/commands/contacts.help.ts +173 -11
  184. package/src/cli/commands/contacts.ts +343 -4
  185. package/src/cli/commands/db/refresh.ts +1 -0
  186. package/src/cli/commands/oauth/index.help.ts +6 -3
  187. package/src/cli/commands/oauth/request.test.ts +37 -0
  188. package/src/cli/commands/oauth/request.ts +7 -6
  189. package/src/cli/lib/bundled-marketplace.json +1 -1
  190. package/src/cli/lib/install-from-github.ts +20 -19
  191. package/src/cli/lib/install-plugin-dependencies.ts +3 -5
  192. package/src/cli/lib/merge-plugin-tree.ts +1 -0
  193. package/src/cli/lib/publish-plugin.ts +4 -1
  194. package/src/config/__tests__/balanced-model-experiment.test.ts +63 -31
  195. package/src/config/__tests__/default-profile-catalog.test.ts +2 -2
  196. package/src/config/balanced-model-experiment.ts +15 -9
  197. package/src/config/bundled-skills/contacts/SKILL.md +61 -18
  198. package/src/config/default-profile-catalog.ts +12 -11
  199. package/src/config/feature-flag-registry.json +22 -13
  200. package/src/config/schema.ts +2 -0
  201. package/src/config/schemas/__tests__/live-voice.test.ts +13 -0
  202. package/src/config/schemas/live-voice.ts +7 -0
  203. package/src/config/schemas/sight.ts +45 -0
  204. package/src/config/skills.ts +2 -1
  205. package/src/contacts/__tests__/mirror-reconciler.test.ts +352 -0
  206. package/src/contacts/contact-store.ts +47 -4
  207. package/src/contacts/contacts-write.ts +5 -2
  208. package/src/contacts/mirror-reconciler.ts +210 -0
  209. package/src/contacts/mirror-tombstones.ts +42 -0
  210. package/src/contacts/notify-contacts-changed.ts +11 -2
  211. package/src/context/compactor.ts +47 -19
  212. package/src/context/tool-result-spool.ts +21 -14
  213. package/src/conversations/message-consolidation.ts +2 -2
  214. package/src/credential-health/credential-health-service.ts +37 -6
  215. package/src/daemon/conversation-agent-loop-handlers.ts +19 -2
  216. package/src/daemon/conversation-agent-loop.ts +100 -1
  217. package/src/daemon/conversation-media-retry.ts +11 -6
  218. package/src/daemon/conversation-messaging.ts +293 -30
  219. package/src/daemon/conversation-runtime-assembly.ts +67 -0
  220. package/src/daemon/conversation-sight-frames.ts +187 -0
  221. package/src/daemon/conversation-store.ts +18 -28
  222. package/src/daemon/conversation-surfaces.ts +12 -2
  223. package/src/daemon/conversation-tool-setup.ts +39 -10
  224. package/src/daemon/conversation.ts +159 -16
  225. package/src/daemon/daemon-control.ts +6 -17
  226. package/src/daemon/handlers/config-model.test.ts +1 -0
  227. package/src/daemon/handlers/conversations.ts +10 -11
  228. package/src/daemon/install-assistant-command.ts +1 -0
  229. package/src/daemon/lifecycle.ts +10 -13
  230. package/src/daemon/message-types/sync.ts +4 -0
  231. package/src/daemon/process-message.ts +2 -17
  232. package/src/daemon/reaction-history-render.test.ts +162 -0
  233. package/src/daemon/reaction-history-render.ts +115 -0
  234. package/src/daemon/reaction-record.test.ts +162 -0
  235. package/src/daemon/reaction-record.ts +83 -0
  236. package/src/daemon/shutdown-handlers.ts +0 -2
  237. package/src/daemon/summarize-boundary.ts +12 -0
  238. package/src/daemon/video-thumbnail.ts +1 -1
  239. package/src/home/feed-types.ts +4 -0
  240. package/src/home/feed-writer.ts +25 -0
  241. package/src/live-voice/__tests__/live-voice-attach-frame.test.ts +679 -0
  242. package/src/live-voice/__tests__/live-voice-attach-image.test.ts +8 -5
  243. package/src/live-voice/__tests__/live-voice-photo.test.ts +637 -1
  244. package/src/live-voice/__tests__/live-voice-sight-frame-inline.test.ts +787 -0
  245. package/src/live-voice/__tests__/live-voice-sight-frame.test.ts +347 -0
  246. package/src/live-voice/__tests__/live-voice-vad.test.ts +109 -0
  247. package/src/live-voice/live-voice-photo.ts +420 -65
  248. package/src/live-voice/live-voice-session.ts +311 -27
  249. package/src/live-voice/protocol.ts +147 -11
  250. package/src/messaging/provider-message-metadata.ts +7 -0
  251. package/src/messaging/providers/__tests__/transport-dispatch.test.ts +113 -1
  252. package/src/messaging/providers/channel-transport.ts +37 -0
  253. package/src/messaging/providers/discord/api.ts +4 -4
  254. package/src/messaging/providers/discord/send.test.ts +177 -2
  255. package/src/messaging/providers/discord/send.ts +197 -10
  256. package/src/messaging/providers/discord/transport.ts +10 -0
  257. package/src/messaging/providers/index.ts +42 -0
  258. package/src/messaging/providers/slack/__tests__/adapter-mention-rendering.test.ts +129 -3
  259. package/src/messaging/providers/slack/__tests__/auth.test.ts +35 -0
  260. package/src/messaging/providers/slack/__tests__/message-content.test.ts +152 -0
  261. package/src/messaging/providers/slack/adapter.ts +112 -27
  262. package/src/messaging/providers/slack/api.test.ts +10 -0
  263. package/src/messaging/providers/slack/auth.ts +32 -6
  264. package/src/messaging/providers/slack/client.ts +0 -15
  265. package/src/messaging/providers/slack/message-content.ts +137 -0
  266. package/src/messaging/providers/slack/send.test.ts +43 -2
  267. package/src/messaging/providers/slack/send.ts +18 -5
  268. package/src/messaging/providers/slack/transport.ts +10 -0
  269. package/src/messaging/providers/slack/types.ts +21 -2
  270. package/src/messaging/providers/telegram-bot/adapter.ts +5 -1
  271. package/src/messaging/providers/telegram-bot/send.test.ts +49 -2
  272. package/src/messaging/providers/telegram-bot/send.ts +41 -1
  273. package/src/messaging/providers/telegram-bot/transport.ts +10 -0
  274. package/src/messaging/providers/telegram-bot/withdraw.ts +13 -5
  275. package/src/messaging/reaction-envelopes.ts +79 -0
  276. package/src/monitoring/control.ts +2 -2
  277. package/src/monitoring/page-cache.ts +5 -1
  278. package/src/notifications/AGENTS.md +1 -1
  279. package/src/notifications/__tests__/guardian-feed-projection.test.ts +360 -0
  280. package/src/notifications/__tests__/home-feed-side-effect.test.ts +8 -0
  281. package/src/notifications/adapters/discord.ts +62 -9
  282. package/src/notifications/conversation-pairing.ts +31 -4
  283. package/src/notifications/emit-signal.ts +8 -5
  284. package/src/notifications/guardian-delivery-recorder.ts +47 -6
  285. package/src/notifications/guardian-feed-projection.ts +492 -0
  286. package/src/notifications/home-feed-side-effect.ts +59 -11
  287. package/src/oauth/byo-connection.test.ts +22 -0
  288. package/src/oauth/byo-connection.ts +11 -5
  289. package/src/oauth/connection.ts +3 -2
  290. package/src/oauth/platform-connection.test.ts +35 -0
  291. package/src/oauth/platform-connection.ts +18 -13
  292. package/src/oauth/scope-utils.ts +48 -0
  293. package/src/oauth/seed-providers.ts +12 -0
  294. package/src/outbound-proxy/certs.ts +5 -4
  295. package/src/permissions/question-guardian-request.test.ts +7 -1
  296. package/src/persistence/attachments-store.ts +134 -31
  297. package/src/persistence/conversation-crud.ts +137 -4
  298. package/src/persistence/conversation-types.ts +109 -0
  299. package/src/persistence/db-connection.ts +3 -21
  300. package/src/persistence/delivery-crud.ts +42 -12
  301. package/src/persistence/embeddings/__tests__/embedding-local-lifecycle.test.ts +15 -2
  302. package/src/persistence/embeddings/embedding-local.ts +7 -112
  303. package/src/persistence/embeddings/qdrant-manager.ts +4 -1
  304. package/src/persistence/llm-request-log-store.ts +1 -1
  305. package/src/persistence/migrations/370-acp-session-history-auth-error-code.ts +36 -0
  306. package/src/persistence/migrations/371-acp-session-history-auth-error-credential.ts +40 -0
  307. package/src/persistence/migrations/372-create-acp-refused-credentials.ts +34 -0
  308. package/src/persistence/migrations/373-acp-auth-marker-index.ts +27 -0
  309. package/src/persistence/migrations/__tests__/370-acp-session-history-auth-error-code.test.ts +65 -0
  310. package/src/persistence/migrations/__tests__/371-acp-session-history-auth-error-credential.test.ts +68 -0
  311. package/src/persistence/migrations/__tests__/372-create-acp-refused-credentials.test.ts +64 -0
  312. package/src/persistence/migrations/__tests__/373-acp-auth-marker-index.test.ts +82 -0
  313. package/src/persistence/schema/acp.ts +25 -0
  314. package/src/persistence/steps.ts +8 -0
  315. package/src/platform/platform-patch-queue.test.ts +43 -41
  316. package/src/plugin-api/index.ts +12 -0
  317. package/src/plugins/defaults/compaction/window-manager.ts +2 -0
  318. package/src/plugins/defaults/image-recovery/recover.ts +12 -1
  319. package/src/plugins/defaults/memory/__tests__/memory-retrospective-accounting.test.ts +166 -1
  320. package/src/plugins/defaults/memory/__tests__/memory-retrospective-provider-path.test.ts +3 -0
  321. package/src/plugins/defaults/memory/__tests__/memory-retrospective-wake-chain.test.ts +3 -0
  322. package/src/plugins/defaults/memory/jobs-worker.ts +1 -2
  323. package/src/plugins/defaults/memory/memory-retrospective-accounting.ts +53 -9
  324. package/src/plugins/defaults/memory/src/__tests__/memory-worker-routes.test.ts +4 -7
  325. package/src/plugins/defaults/memory/src/memory-worker-routes.ts +1 -1
  326. package/src/plugins/defaults/memory/v1/job-handlers/index-maintenance.ts +15 -1
  327. package/src/plugins/defaults/memory/worker-control.ts +1 -2
  328. package/src/providers/content-block-schema.ts +1 -0
  329. package/src/providers/fireworks/client.ts +9 -1
  330. package/src/providers/gemini/client.ts +22 -15
  331. package/src/providers/media-resolve.ts +24 -10
  332. package/src/providers/model-catalog.ts +91 -0
  333. package/src/providers/openai/chat-completions-provider.ts +44 -5
  334. package/src/providers/retry.ts +16 -11
  335. package/src/providers/types.ts +56 -0
  336. package/src/routes/__tests__/control.test.ts +4 -3
  337. package/src/routes/control.ts +1 -1
  338. package/src/routes/route-host-client.ts +1 -1
  339. package/src/runtime/AGENTS.md +30 -12
  340. package/src/runtime/__tests__/agent-wake.test.ts +71 -6
  341. package/src/runtime/__tests__/guardian-form-registry.test.ts +242 -0
  342. package/src/runtime/access-request-helper.ts +15 -7
  343. package/src/runtime/agent-wake.ts +16 -4
  344. package/src/runtime/channel-approval-types.ts +8 -5
  345. package/src/runtime/channel-invite-transport.ts +2 -0
  346. package/src/runtime/channel-invite-transports/discord.ts +27 -0
  347. package/src/runtime/channel-readiness-service.ts +4 -4
  348. package/src/runtime/channel-reply-delivery.ts +72 -28
  349. package/src/runtime/guardian-action-service.ts +9 -5
  350. package/src/runtime/guardian-form-registry.ts +256 -0
  351. package/src/runtime/guardian-reply-router.ts +33 -14
  352. package/src/runtime/invite-instruction-generator.ts +14 -2
  353. package/src/runtime/migrations/vbundle-metadata-merge.ts +4 -7
  354. package/src/runtime/no-response.ts +13 -74
  355. package/src/runtime/routes/__tests__/acp-routes.test.ts +527 -0
  356. package/src/runtime/routes/__tests__/app-management-shared-apps.test.ts +74 -0
  357. package/src/runtime/routes/__tests__/contact-form-kinds.test.ts +22 -0
  358. package/src/runtime/routes/__tests__/contact-record-prompt-route.test.ts +303 -0
  359. package/src/runtime/routes/__tests__/monitoring-routes.test.ts +3 -4
  360. package/src/runtime/routes/__tests__/pending-interactions-route.test.ts +6 -0
  361. package/src/runtime/routes/__tests__/route-host-worker-routes.test.ts +3 -4
  362. package/src/runtime/routes/__tests__/schedule-worker-routes.test.ts +5 -6
  363. package/src/runtime/routes/acp-routes-list.test.ts +58 -0
  364. package/src/runtime/routes/acp-routes.ts +253 -47
  365. package/src/runtime/routes/app-management-routes.ts +80 -47
  366. package/src/runtime/routes/approval-routes.ts +9 -0
  367. package/src/runtime/routes/archive-utils.ts +6 -1
  368. package/src/runtime/routes/avatar-routes.ts +1 -1
  369. package/src/runtime/routes/channel-route-shared.ts +18 -20
  370. package/src/runtime/routes/contact-prompt-routes.ts +271 -105
  371. package/src/runtime/routes/conversation-routes.ts +41 -32
  372. package/src/runtime/routes/credential-prompt-routes.ts +10 -1
  373. package/src/runtime/routes/debug-bash-routes.ts +1 -0
  374. package/src/runtime/routes/guardian-expiry-sweep.ts +89 -31
  375. package/src/runtime/routes/guardian-form-routes.ts +115 -0
  376. package/src/runtime/routes/inbound-message-handler.ts +82 -29
  377. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +16 -3
  378. package/src/runtime/routes/inbound-stages/edit-intercept.ts +6 -0
  379. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.test.ts +6 -0
  380. package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +6 -1
  381. package/src/runtime/routes/inbound-stages/reaction-intercept.test.ts +50 -17
  382. package/src/runtime/routes/inbound-stages/reaction-intercept.ts +97 -94
  383. package/src/runtime/routes/index.ts +2 -0
  384. package/src/runtime/routes/integrations/slack/channel.ts +39 -18
  385. package/src/runtime/routes/migration-routes.ts +1 -3
  386. package/src/runtime/routes/monitoring-routes.ts +0 -3
  387. package/src/runtime/routes/oauth-commands-routes.ts +18 -5
  388. package/src/runtime/routes/route-host-worker-routes.ts +1 -1
  389. package/src/runtime/routes/schedule-worker-routes.ts +1 -1
  390. package/src/runtime/tool-grant-request-helper.ts +4 -0
  391. package/src/schedule/run-script.ts +1 -0
  392. package/src/schedule/scheduler.ts +1 -1
  393. package/src/schedule/worker-control.ts +2 -2
  394. package/src/security/oauth2.ts +2 -11
  395. package/src/security/secure-keys.ts +55 -8
  396. package/src/skills/catalog-install.ts +7 -4
  397. package/src/skills/clawhub.ts +1 -0
  398. package/src/skills/inline-command-runner.ts +1 -0
  399. package/src/stt/__tests__/room-noise-floor.test.ts +108 -0
  400. package/src/stt/room-noise-floor.ts +140 -0
  401. package/src/subagent/notify.ts +49 -3
  402. package/src/tools/__tests__/tool-input-schemas.test.ts +2 -0
  403. package/src/tools/acp/spawn.ts +2 -2
  404. package/src/tools/browser/__tests__/browser-status.test.ts +9 -0
  405. package/src/tools/browser/browser-execution.ts +10 -43
  406. package/src/tools/browser/browser-manager.ts +1 -0
  407. package/src/tools/browser/browser-status-constants.ts +6 -5
  408. package/src/tools/browser/runtime-check.ts +2 -0
  409. package/src/tools/channel/react-to-message.test.ts +193 -0
  410. package/src/tools/channel/react-to-message.ts +159 -0
  411. package/src/tools/credentials/metadata-store.ts +115 -19
  412. package/src/tools/credentials/store.ts +0 -2
  413. package/src/tools/filesystem/read.ts +2 -2
  414. package/src/tools/host-filesystem/read.ts +2 -2
  415. package/src/tools/host-terminal/host-shell.test.ts +24 -0
  416. package/src/tools/host-terminal/host-shell.ts +11 -38
  417. package/src/tools/shared/filesystem/file-ops-service.ts +10 -7
  418. package/src/tools/shared/shell-output.test.ts +104 -0
  419. package/src/tools/shared/shell-output.ts +112 -39
  420. package/src/tools/skills/sandbox-runner.ts +1 -0
  421. package/src/tools/system/request-permission.test.ts +34 -0
  422. package/src/tools/system/request-permission.ts +29 -5
  423. package/src/tools/terminal/shell.test.ts +63 -0
  424. package/src/tools/terminal/shell.ts +12 -44
  425. package/src/tools/tool-input-schemas.ts +2 -0
  426. package/src/tools/tool-manifest.ts +2 -0
  427. package/src/usage/attribution.ts +12 -4
  428. package/src/util/__tests__/worker-ownership.test.ts +77 -0
  429. package/src/util/__tests__/worker-process-command.test.ts +246 -1
  430. package/src/util/bun-runtime.ts +44 -13
  431. package/src/util/bundled-asset.ts +9 -1
  432. package/src/util/disk-usage.ts +1 -0
  433. package/src/util/file-use.ts +4 -1
  434. package/src/util/guardian-form-timeouts.ts +50 -0
  435. package/src/util/image-conversion.ts +1 -0
  436. package/src/util/oauth-request-body.test.ts +59 -0
  437. package/src/util/oauth-request-body.ts +56 -5
  438. package/src/util/platform.ts +69 -73
  439. package/src/util/process-table.ts +16 -5
  440. package/src/util/read-stdin.ts +5 -0
  441. package/src/util/spawn.ts +1 -0
  442. package/src/util/sqlite3-runtime.ts +25 -5
  443. package/src/util/worker-ownership.ts +193 -0
  444. package/src/util/worker-process.ts +375 -37
  445. package/src/workspace/git-service.ts +4 -12
  446. package/src/workspace/migrations/010-app-dir-rename.ts +1 -0
@@ -182,6 +182,43 @@ describe("oauth request body encoding", () => {
182
182
  );
183
183
  });
184
184
 
185
+ test("reads a binary @file as a Buffer without UTF-8 replacement", async () => {
186
+ const filePath = join(tempDir, "upload.bin");
187
+ writeFileSync(filePath, PNG_MAGIC);
188
+
189
+ const parsed = readBodyData(`@${filePath}`, {
190
+ "Content-Type": "application/octet-stream",
191
+ });
192
+ expect(Buffer.isBuffer(parsed)).toBe(true);
193
+ expect(Buffer.from(parsed as Uint8Array).equals(PNG_MAGIC)).toBe(true);
194
+ });
195
+
196
+ test("forwards a binary @file to the route handler as a Buffer", async () => {
197
+ const filePath = join(tempDir, "report.pdf");
198
+ writeFileSync(filePath, PNG_MAGIC);
199
+
200
+ const { exitCode } = await runRequestCommand([
201
+ "--provider",
202
+ "google",
203
+ "-s",
204
+ "-X",
205
+ "POST",
206
+ "-H",
207
+ "Content-Type: application/pdf",
208
+ "-d",
209
+ `@${filePath}`,
210
+ "https://www.googleapis.com/upload/drive/v3/files?uploadType=media",
211
+ ]);
212
+
213
+ expect(exitCode).toBe(0);
214
+ expect(handleRequestCalls).toHaveLength(1);
215
+ const call = handleRequestCalls[0] as { body: { parsed_data: unknown } };
216
+ expect(Buffer.isBuffer(call.body.parsed_data)).toBe(true);
217
+ expect(
218
+ Buffer.from(call.body.parsed_data as Uint8Array).equals(PNG_MAGIC),
219
+ ).toBe(true);
220
+ });
221
+
185
222
  test("forwards a multipart body to the route handler as a string", async () => {
186
223
  const { exitCode } = await runRequestCommand([
187
224
  "--provider",
@@ -5,9 +5,10 @@ import type { Command } from "commander";
5
5
  import { exitFromIpcResult } from "../../../ipc/cli-client.js";
6
6
  import {
7
7
  findContentTypeHeader,
8
+ parseRequestBodyBytes,
8
9
  parseRequestBodyData,
9
10
  } from "../../../util/oauth-request-body.js";
10
- import { readStdinSync } from "../../../util/read-stdin.js";
11
+ import { readStdinBytesSync } from "../../../util/read-stdin.js";
11
12
  import { subcommand } from "../../lib/cli-command-help.js";
12
13
  import { shouldOutputJson, writeOutput } from "../../output.js";
13
14
 
@@ -49,8 +50,8 @@ function parseHeader(raw: string): [string, string] {
49
50
  *
50
51
  * A non-JSON `Content-Type` keeps the payload as the exact string the caller
51
52
  * gave, so multipart, XML, and form-encoded bodies reach the provider
52
- * unchanged. Files are read as UTF-8 text, so a body that is not valid UTF-8
53
- * (a raw image, an archive) cannot travel through this flag.
53
+ * unchanged. Files and stdin are read as raw bytes: valid UTF-8 stays text
54
+ * (or JSON), and anything else travels as a Buffer.
54
55
  */
55
56
  export function readBodyData(
56
57
  data: string,
@@ -59,12 +60,12 @@ export function readBodyData(
59
60
  const contentType = findContentTypeHeader(headers);
60
61
 
61
62
  if (data === "@-") {
62
- return parseRequestBodyData(readStdinSync(), contentType);
63
+ return parseRequestBodyBytes(readStdinBytesSync(), contentType);
63
64
  }
64
65
 
65
66
  if (data.startsWith("@")) {
66
67
  const filePath = data.slice(1);
67
- return parseRequestBodyData(readFileSync(filePath, "utf-8"), contentType);
68
+ return parseRequestBodyBytes(readFileSync(filePath), contentType);
68
69
  }
69
70
 
70
71
  return parseRequestBodyData(data, contentType);
@@ -90,7 +91,7 @@ export function registerRequestCommand(oauth: Command): void {
90
91
  )
91
92
  .option(
92
93
  "-d, --data <data>",
93
- "Request body: inline JSON, @filename, or @- for stdin. Sent raw when Content-Type is not JSON",
94
+ "Request body: inline JSON, @filename, or @- for stdin. Sent raw when Content-Type is not JSON. Files keep their original bytes, including binary",
94
95
  )
95
96
  .option("-G, --get", "Force GET; body data becomes query params")
96
97
  .option("-I, --head", "Send a HEAD request")
@@ -219,7 +219,7 @@
219
219
  "source": {
220
220
  "source": "github",
221
221
  "repo": "vellum-ai/imessage",
222
- "ref": "069a8375e2a6ed069550866114a653218864bc91"
222
+ "ref": "01701eb1be4074f99371af90fc4340fdd1674efb"
223
223
  },
224
224
  "description": "iMessage and SMS channel for the Vellum assistant, backed by a Photon line.",
225
225
  "category": "productivity",
@@ -36,7 +36,7 @@ import {
36
36
  writeFileSync,
37
37
  } from "node:fs";
38
38
  import { tmpdir } from "node:os";
39
- import { dirname, join, resolve, sep } from "node:path";
39
+ import { delimiter, dirname, join, resolve, sep } from "node:path";
40
40
  import { promisify } from "node:util";
41
41
 
42
42
  import {
@@ -46,7 +46,12 @@ import {
46
46
  stripPreservedUserState,
47
47
  } from "../../plugins/plugin-tree-walk.js";
48
48
  import { ensureBun } from "../../util/bun-runtime.js";
49
- import { getWorkspacePluginsDir } from "../../util/platform.js";
49
+ import {
50
+ addToPathEnv,
51
+ getExtraToolPathDirs,
52
+ getWorkspacePluginsDir,
53
+ isWindows,
54
+ } from "../../util/platform.js";
50
55
  import type { FetchLike } from "./fetch-like.js";
51
56
  import {
52
57
  type DependencyInstaller,
@@ -1254,23 +1259,23 @@ export const defaultPostinstallRunner: PostinstallRunner = async ({
1254
1259
  timeout: POSTINSTALL_TIMEOUT_MS,
1255
1260
  maxBuffer: 16 * 1024 * 1024,
1256
1261
  env: pluginPostinstallEnv(bun),
1262
+ windowsHide: true,
1257
1263
  });
1258
1264
  };
1259
1265
 
1260
1266
  function pluginPostinstallEnv(bun: string): NodeJS.ProcessEnv {
1267
+ const systemDirs = isWindows()
1268
+ ? [process.env.SystemRoot ?? "C:\\Windows"]
1269
+ : [...getExtraToolPathDirs(), "/usr/bin", "/bin"];
1261
1270
  const env: NodeJS.ProcessEnv = {
1262
- PATH: [
1263
- dirname(bun),
1264
- "/opt/homebrew/bin",
1265
- "/usr/local/bin",
1266
- "/usr/bin",
1267
- "/bin",
1268
- ]
1269
- .filter(Boolean)
1270
- .join(":"),
1271
+ PATH: [dirname(bun), ...systemDirs].join(delimiter),
1271
1272
  };
1272
- if (process.env.HOME) {
1273
- env.HOME = process.env.HOME;
1273
+ for (const key of isWindows()
1274
+ ? ["USERPROFILE", "APPDATA", "LOCALAPPDATA", "TEMP", "SystemRoot"]
1275
+ : ["HOME"]) {
1276
+ if (process.env[key]) {
1277
+ env[key] = process.env[key];
1278
+ }
1274
1279
  }
1275
1280
  return env;
1276
1281
  }
@@ -1373,6 +1378,7 @@ export const defaultGitRunner: GitRunner = async (args, opts) => {
1373
1378
  timeout: GIT_TIMEOUT_MS,
1374
1379
  maxBuffer: 64 * 1024 * 1024,
1375
1380
  env: pluginGitEnv(),
1381
+ windowsHide: true,
1376
1382
  });
1377
1383
  return { stdout };
1378
1384
  };
@@ -1385,12 +1391,7 @@ function pluginGitEnv(): NodeJS.ProcessEnv {
1385
1391
  }
1386
1392
  }
1387
1393
  env.GIT_TERMINAL_PROMPT = "0";
1388
- const extraPaths = ["/opt/homebrew/bin", "/usr/local/bin"];
1389
- const current = (env.PATH ?? "").split(":").filter(Boolean);
1390
- const missing = extraPaths.filter((p) => !current.includes(p));
1391
- if (missing.length > 0) {
1392
- env.PATH = [...current, ...missing].join(":");
1393
- }
1394
+ addToPathEnv(env, getExtraToolPathDirs(), "back");
1394
1395
  return env;
1395
1396
  }
1396
1397
 
@@ -59,6 +59,7 @@ import { promisify } from "node:util";
59
59
 
60
60
  import { ensureBun } from "../../util/bun-runtime.js";
61
61
  import { getLogger } from "../../util/logger.js";
62
+ import { addToPathEnv } from "../../util/platform.js";
62
63
 
63
64
  const execFileAsync = promisify(execFile);
64
65
 
@@ -318,6 +319,7 @@ export const defaultDependencyInstaller: DependencyInstaller = async ({
318
319
  timeout: DEPENDENCY_INSTALL_TIMEOUT_MS,
319
320
  maxBuffer: 32 * 1024 * 1024,
320
321
  env: dependencyInstallEnv(bun),
322
+ windowsHide: true,
321
323
  });
322
324
  };
323
325
 
@@ -331,10 +333,6 @@ export const defaultDependencyInstaller: DependencyInstaller = async ({
331
333
  */
332
334
  function dependencyInstallEnv(bun: string): NodeJS.ProcessEnv {
333
335
  const env: NodeJS.ProcessEnv = { ...process.env };
334
- const bunDir = dirname(bun);
335
- const current = (env.PATH ?? "").split(":").filter(Boolean);
336
- if (!current.includes(bunDir)) {
337
- env.PATH = [bunDir, ...current].join(":");
338
- }
336
+ addToPathEnv(env, [dirname(bun)]);
339
337
  return env;
340
338
  }
@@ -205,6 +205,7 @@ async function threeWayMergeFile(
205
205
  encoding: "buffer",
206
206
  timeout: MERGE_TIMEOUT_MS,
207
207
  maxBuffer: 64 * 1024 * 1024,
208
+ windowsHide: true,
208
209
  });
209
210
  return { content: stdout, conflicted: false, binaryConflicted: false };
210
211
  } catch (err) {
@@ -199,7 +199,10 @@ export function validatePluginForPublish(dir: string): PublishValidation {
199
199
  // ---------------------------------------------------------------------------
200
200
 
201
201
  async function runGit(dir: string, args: string[]): Promise<string> {
202
- const { stdout } = await execFileAsync("git", args, { cwd: dir });
202
+ const { stdout } = await execFileAsync("git", args, {
203
+ cwd: dir,
204
+ windowsHide: true,
205
+ });
203
206
  return stdout.trim();
204
207
  }
205
208
 
@@ -24,15 +24,16 @@ import {
24
24
  type ProfileEntry,
25
25
  } from "../schemas/llm.js";
26
26
 
27
- // The `experiment-balanced-model-2026-08-06` arm repoints the model the managed
27
+ // The `experiment-balanced-model-2026-08-31` arm repoints the model the managed
28
28
  // Balanced profile resolves to. Both the runtime resolver and the client-facing
29
29
  // profile listing go through `defaultProfileBodyForProvider`, so both are
30
30
  // exercised here: a disagreement between them shows the user one model beside a
31
31
  // profile that runs another.
32
32
 
33
33
  const FLAG = BALANCED_MODEL_EXPERIMENT_FLAG_KEY;
34
- const SHIPPED_MODEL = "accounts/fireworks/models/glm-5p2";
35
- const GLM_MODEL = SHIPPED_MODEL;
34
+ const SHIPPED_MODEL = "accounts/fireworks/models/glm-5p3-flash";
35
+ const GLM_53_MODEL = "accounts/fireworks/models/glm-5p3";
36
+ const GLM_52_MODEL = "accounts/fireworks/models/glm-5p2";
36
37
 
37
38
  function setArm(value: boolean | string): void {
38
39
  setOverridesForTesting({ [FLAG]: value });
@@ -57,22 +58,22 @@ afterEach(() => {
57
58
  });
58
59
 
59
60
  describe("balanced-model experiment arms", () => {
60
- test("terra repoints mainAgent at gpt-5.6-terra on the managed connection", () => {
61
- setArm("terra");
61
+ test("glm-5p3 repoints mainAgent at GLM 5.3 on the managed connection", () => {
62
+ setArm("glm-5p3");
62
63
  const resolved = resolveCallSiteConfig(
63
64
  "mainAgent",
64
65
  llmWithActiveBalanced(),
65
66
  );
66
- expect(resolved.model).toBe("gpt-5.6-terra");
67
+ expect(resolved.model).toBe(GLM_53_MODEL);
67
68
  expect(resolved.provider).toBe("vellum");
68
- expect(getManagedUpstream("gpt-5.6-terra")).toBe("openai");
69
+ expect(getManagedUpstream(GLM_53_MODEL)).toBe("fireworks");
69
70
 
70
71
  const entry = resolveDefaultProfileForProvider(
71
72
  undefined,
72
73
  "balanced",
73
74
  managed,
74
75
  );
75
- expect(entry?.model).toBe("gpt-5.6-terra");
76
+ expect(entry?.model).toBe(GLM_53_MODEL);
76
77
  expect(entry?.provider).toBe("vellum");
77
78
  // Routing-identity providers derive their connection per request.
78
79
  expect(entry?.provider_connection).toBeUndefined();
@@ -85,12 +86,12 @@ describe("balanced-model experiment arms", () => {
85
86
  "mainAgent",
86
87
  llmWithActiveBalanced(),
87
88
  );
88
- expect(resolved.model).toBe(GLM_MODEL);
89
+ expect(resolved.model).toBe(GLM_52_MODEL);
89
90
  expect(resolved.provider).toBe("vellum");
90
91
 
91
- const upstream = getManagedUpstream(GLM_MODEL);
92
+ const upstream = getManagedUpstream(GLM_52_MODEL);
92
93
  expect(upstream).toBe("fireworks");
93
- const cap = catalogMaxOutputTokens(upstream as string, GLM_MODEL);
94
+ const cap = catalogMaxOutputTokens(upstream as string, GLM_52_MODEL);
94
95
  expect(cap).toBeDefined();
95
96
  expect(resolved.maxTokens).toBeLessThanOrEqual(cap as number);
96
97
  // The shipped token budget stands.
@@ -99,19 +100,26 @@ describe("balanced-model experiment arms", () => {
99
100
  );
100
101
  });
101
102
 
103
+ test("the glm-5p3-flash arm names the shipped pin", () => {
104
+ setArm("glm-5p3-flash");
105
+ expect(
106
+ resolveDefaultProfileForProvider(undefined, "balanced", managed)?.model,
107
+ ).toBe(SHIPPED_MODEL);
108
+ });
109
+
102
110
  test("an arm changes only the model of the managed balanced body", () => {
103
- setArm("terra");
111
+ setArm("glm-5p2");
104
112
  const entry = resolveDefaultProfileForProvider(
105
113
  undefined,
106
114
  "balanced",
107
115
  managed,
108
116
  );
109
117
  const shipped = CODE_DEFAULT_PROFILE_ENTRIES.balanced;
110
- expect(entry).toEqual({ ...shipped, model: "gpt-5.6-terra" });
118
+ expect(entry).toEqual({ ...shipped, model: GLM_52_MODEL });
111
119
  });
112
120
 
113
121
  test("the other default profiles are untouched by an arm", () => {
114
- setArm("terra");
122
+ setArm("glm-5p2");
115
123
  for (const key of [
116
124
  "quality-optimized",
117
125
  "cost-optimized",
@@ -124,7 +132,7 @@ describe("balanced-model experiment arms", () => {
124
132
  });
125
133
 
126
134
  test("every pinned arm model is managed-routable and in its upstream catalog", () => {
127
- for (const model of ["gpt-5.6-terra", GLM_MODEL]) {
135
+ for (const model of [SHIPPED_MODEL, GLM_53_MODEL, GLM_52_MODEL]) {
128
136
  const upstream = getManagedUpstream(model);
129
137
  expect(upstream).not.toBeNull();
130
138
  expect(isModelInCatalog(upstream as string, model)).toBe(true);
@@ -136,7 +144,10 @@ describe("balanced-model experiment fallbacks", () => {
136
144
  const shippedCases: [string, () => void][] = [
137
145
  ["the flag is unset", () => clearCachedOverrides()],
138
146
  ["no override is present", () => setOverridesForTesting({})],
147
+ // Arms of the retired 2026-08-06 experiment are no longer pinned, so a
148
+ // stale value cannot strand an install on a model this test no longer runs.
139
149
  ["the arm is control", () => setArm("control")],
150
+ ["the arm is terra", () => setArm("terra")],
140
151
  ["the arm is an unknown string", () => setArm("gpt-9-does-not-exist")],
141
152
  ["the arm is the empty string", () => setArm("")],
142
153
  ["the value is boolean true", () => setArm(true)],
@@ -162,11 +173,11 @@ describe("balanced-model experiment fallbacks", () => {
162
173
  });
163
174
  }
164
175
 
165
- test("the shipped catalog body is the control arm", () => {
176
+ test("the shipped catalog body is the glm-5p3-flash arm", () => {
166
177
  expect(CODE_DEFAULT_PROFILE_ENTRIES.balanced.model).toBe(SHIPPED_MODEL);
167
178
  });
168
179
 
169
- test("the registry default is control, which the override-only flag read assumes", () => {
180
+ test("the registry default names the shipped pin, which the override-only flag read assumes", () => {
170
181
  const registryPath = join(
171
182
  import.meta.dirname,
172
183
  "..",
@@ -188,14 +199,35 @@ describe("balanced-model experiment fallbacks", () => {
188
199
  const flag = registry.flags.find((entry) => entry.key === FLAG);
189
200
  expect(flag).toBeDefined();
190
201
  expect(flag?.scope).toBe("assistant");
191
- expect(flag?.defaultEnabled).toBe("control");
192
- expect(flag?.values).toEqual(["control", "terra", "glm-5p2"]);
202
+ expect(flag?.defaultEnabled).toBe("glm-5p3-flash");
203
+ expect(flag?.values).toEqual(["glm-5p3-flash", "glm-5p3", "glm-5p2"]);
204
+ });
205
+
206
+ test("the retired experiment's flag is gone from the registry", () => {
207
+ const registryPath = join(
208
+ import.meta.dirname,
209
+ "..",
210
+ "..",
211
+ "..",
212
+ "..",
213
+ "meta",
214
+ "feature-flags",
215
+ "feature-flag-registry.json",
216
+ );
217
+ const registry = JSON.parse(readFileSync(registryPath, "utf-8")) as {
218
+ flags: { key: string }[];
219
+ };
220
+ expect(
221
+ registry.flags.some(
222
+ (entry) => entry.key === "experiment-balanced-model-2026-08-06",
223
+ ),
224
+ ).toBe(false);
193
225
  });
194
226
  });
195
227
 
196
228
  describe("balanced-model experiment boundaries", () => {
197
229
  test("a user-owned balanced shadow wins over the arm", () => {
198
- setArm("terra");
230
+ setArm("glm-5p2");
199
231
  const profiles: Record<string, ProfileEntry> = {
200
232
  balanced: { source: "user", provider: "openai", model: "gpt-5.5" },
201
233
  };
@@ -209,7 +241,7 @@ describe("balanced-model experiment boundaries", () => {
209
241
  });
210
242
 
211
243
  test("a managed-source stub still resolves to the arm's body", () => {
212
- setArm("terra");
244
+ setArm("glm-5p2");
213
245
  const profiles: Record<string, ProfileEntry> = {
214
246
  balanced: { source: "managed", label: "My Balanced" },
215
247
  };
@@ -218,12 +250,12 @@ describe("balanced-model experiment boundaries", () => {
218
250
  "balanced",
219
251
  managed,
220
252
  );
221
- expect(entry?.model).toBe("gpt-5.6-terra");
253
+ expect(entry?.model).toBe(GLM_52_MODEL);
222
254
  expect(entry?.label).toBe("My Balanced");
223
255
  });
224
256
 
225
257
  test("the chatgpt and BYOK columns stay out of the experiment", () => {
226
- setArm("terra");
258
+ setArm("glm-5p2");
227
259
  for (const provider of ["anthropic", "openai", "chatgpt"] as const) {
228
260
  const armed = resolveDefaultProfileForProvider(undefined, "balanced", {
229
261
  provider,
@@ -234,37 +266,37 @@ describe("balanced-model experiment boundaries", () => {
234
266
  });
235
267
  expect(armed).toEqual(shipped as ProfileEntry);
236
268
  expect(armed?.provider).toBe(provider);
237
- setArm("terra");
269
+ setArm("glm-5p2");
238
270
  }
239
271
  });
240
272
 
241
273
  test("an install with no defaultProvider still picks up the arm", () => {
242
- setArm("terra");
274
+ setArm("glm-5p2");
243
275
  expect(
244
276
  resolveDefaultProfileForProvider(undefined, "balanced", null)?.model,
245
- ).toBe("gpt-5.6-terra");
277
+ ).toBe(GLM_52_MODEL);
246
278
  });
247
279
  });
248
280
 
249
281
  describe("client-facing profile listing", () => {
250
282
  test("reports the arm's model so the UI matches what runs", () => {
251
- setArm("terra");
283
+ setArm("glm-5p2");
252
284
  const profiles = getEffectiveProfilesForProvider(undefined, managed);
253
- expect(profiles.balanced?.model).toBe("gpt-5.6-terra");
285
+ expect(profiles.balanced?.model).toBe(GLM_52_MODEL);
254
286
  expect(profiles["quality-optimized"]?.model).toBe(
255
287
  CODE_DEFAULT_PROFILE_ENTRIES["quality-optimized"].model,
256
288
  );
257
289
  });
258
290
 
259
- test("reports the shipped model on the control arm", () => {
260
- setArm("control");
291
+ test("reports the shipped model on an arm this build does not know", () => {
292
+ setArm("terra");
261
293
  expect(
262
294
  getEffectiveProfilesForProvider(undefined, managed).balanced?.model,
263
295
  ).toBe(SHIPPED_MODEL);
264
296
  });
265
297
 
266
298
  test("agrees with the runtime resolver on every arm", () => {
267
- for (const arm of ["control", "terra", "glm-5p2", "nonsense"]) {
299
+ for (const arm of ["glm-5p3-flash", "glm-5p3", "glm-5p2", "nonsense"]) {
268
300
  setArm(arm);
269
301
  const listed = getEffectiveProfilesForProvider(undefined, managed)
270
302
  .balanced?.model;
@@ -59,9 +59,9 @@ describe("getEffectiveProfiles", () => {
59
59
  }
60
60
  });
61
61
 
62
- test("the managed Balanced profile routes GLM 5.2 through Fireworks", () => {
62
+ test("the managed Balanced profile routes GLM 5.3 Flash through Fireworks", () => {
63
63
  const balanced = CODE_DEFAULT_PROFILE_ENTRIES.balanced;
64
- expect(balanced.model).toBe("accounts/fireworks/models/glm-5p2");
64
+ expect(balanced.model).toBe("accounts/fireworks/models/glm-5p3-flash");
65
65
  expect(resolveRoutingIdentity(balanced.provider, balanced.model)).toEqual({
66
66
  connectionName: "vellum",
67
67
  expectedProvider: "fireworks",
@@ -1,33 +1,39 @@
1
1
  /**
2
2
  * Flag read for the managed Balanced profile's model A/B test.
3
3
  *
4
- * `experiment-balanced-model-2026-08-06` is a multivariate LaunchDarkly flag
4
+ * `experiment-balanced-model-2026-08-31` is a multivariate LaunchDarkly flag
5
5
  * whose arm repoints the model the managed (`vellum`) implementation of the
6
6
  * `balanced` default profile resolves to. The arm to model pins live beside
7
7
  * the profile bodies in `default-profile-catalog.ts`, which validates their
8
8
  * managed routability at load time; this module owns only the flag read.
9
9
  *
10
+ * A flag key per experiment, rather than new variations on the previous one,
11
+ * is what keeps a readout honest: the activation cohort query keys a user on
12
+ * their first-ever assignment for a flag, so every user the 2026-08-06 test
13
+ * already stamped would stay pinned to that arm forever and never enter a new
14
+ * arm on the same key.
15
+ *
10
16
  * The read goes straight to the override cache rather than through
11
17
  * `assistant-feature-flags.ts`. `feature-flag-cache.ts` is a stdlib-only leaf,
12
18
  * so the profile catalog stays free of the pino logger and the gateway IPC
13
19
  * client that resolver would pull into its import graph, and the registry
14
- * declares `defaultEnabled: "control"` for this flag, which is the same
15
- * shipped body an absent override already resolves to. That equivalence is
16
- * pinned by `__tests__/balanced-model-experiment.test.ts` so it cannot drift.
20
+ * declares `defaultEnabled: "glm-5p3-flash"` for this flag, which pins the same
21
+ * model the shipped body an absent override resolves to already carries. That
22
+ * equivalence is pinned by `__tests__/balanced-model-experiment.test.ts` so it
23
+ * cannot drift.
17
24
  */
18
25
 
19
26
  import { getCachedOverrides } from "./feature-flag-cache.js";
20
27
 
21
28
  export const BALANCED_MODEL_EXPERIMENT_FLAG_KEY =
22
- "experiment-balanced-model-2026-08-06";
29
+ "experiment-balanced-model-2026-08-31";
23
30
 
24
31
  /**
25
32
  * The experiment arm in force, or `undefined` when the flag resolves to
26
33
  * anything that is not an arm name: unset, a boolean, or the empty string.
27
- * `control` and any arm this build does not know are returned as-is and miss
28
- * the pin table, which is what keeps a stale or malformed LaunchDarkly value
29
- * on the shipped model rather than stranding an install on one that does not
30
- * exist.
34
+ * Any arm this build does not know is returned as-is and misses the pin table,
35
+ * which is what keeps a stale or malformed LaunchDarkly value on the shipped
36
+ * model rather than stranding an install on one that does not exist.
31
37
  */
32
38
  export function getBalancedModelExperimentArm(): string | undefined {
33
39
  const value = getCachedOverrides()?.[BALANCED_MODEL_EXPERIMENT_FLAG_KEY];