@vellumai/assistant 0.11.1 → 0.11.2-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 (318) hide show
  1. package/Dockerfile +1 -3
  2. package/README.md +1 -1
  3. package/eslint.config.mjs +28 -8
  4. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/package.json +1 -0
  5. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/__tests__/stripe-currency.test.ts +37 -0
  6. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/stripe-currency.ts +55 -0
  7. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/package.json +1 -0
  8. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/stripe-currency.test.ts +37 -0
  9. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/stripe-currency.ts +55 -0
  10. package/node_modules/@vellumai/service-contracts/package.json +1 -0
  11. package/node_modules/@vellumai/service-contracts/src/__tests__/stripe-currency.test.ts +37 -0
  12. package/node_modules/@vellumai/service-contracts/src/stripe-currency.ts +55 -0
  13. package/node_modules/@vellumai/slack-text/src/index.test.ts +48 -0
  14. package/node_modules/@vellumai/slack-text/src/index.ts +25 -6
  15. package/openapi.yaml +292 -5
  16. package/package.json +1 -1
  17. package/src/__tests__/agent-loop-resume-interrupted.test.ts +223 -0
  18. package/src/__tests__/byok-default-profile-ensure.test.ts +43 -0
  19. package/src/__tests__/cli-logger-boundary-guard.test.ts +87 -0
  20. package/src/__tests__/compaction-events.test.ts +139 -2
  21. package/src/__tests__/config-loader-backfill.test.ts +35 -3
  22. package/src/__tests__/config-schema.test.ts +1 -0
  23. package/src/__tests__/conversation-agent-loop-fatal-cleanup.test.ts +30 -0
  24. package/src/__tests__/conversation-agent-loop.test.ts +180 -7
  25. package/src/__tests__/conversation-error.test.ts +211 -4
  26. package/src/__tests__/conversation-options-turn-scoped-transport.test.ts +64 -0
  27. package/src/__tests__/conversation-queue.test.ts +126 -0
  28. package/src/__tests__/conversation-retry-route.test.ts +46 -7
  29. package/src/__tests__/conversation-slash.test.ts +4 -2
  30. package/src/__tests__/conversation-summarize-route.test.ts +7 -1
  31. package/src/__tests__/conversation-summarize-up-to.test.ts +71 -5
  32. package/src/__tests__/document-create-dedupe.test.ts +2 -1
  33. package/src/__tests__/document-find-replace.test.ts +2 -1
  34. package/src/__tests__/document-tool-security.test.ts +2 -1
  35. package/src/__tests__/document-update-default-surface.test.ts +2 -1
  36. package/src/__tests__/document-workspace-file.test.ts +467 -0
  37. package/src/__tests__/emit-signal-routing-intent.test.ts +290 -49
  38. package/src/__tests__/guardian-card-withdrawal.test.ts +91 -3
  39. package/src/__tests__/http-user-message-parity.test.ts +66 -0
  40. package/src/__tests__/list-messages-attachments.test.ts +87 -0
  41. package/src/__tests__/list-messages-provider-error.test.ts +143 -0
  42. package/src/__tests__/list-messages-system-card.test.ts +100 -0
  43. package/src/__tests__/llm-resolver.test.ts +19 -9
  44. package/src/__tests__/managed-profile-guard.test.ts +23 -0
  45. package/src/__tests__/notification-platform-adapter.test.ts +130 -2
  46. package/src/__tests__/notification-telegram-adapter.test.ts +6 -0
  47. package/src/__tests__/notification-vellum-adapter.test.ts +45 -0
  48. package/src/__tests__/plugin-api-model-profiles.test.ts +10 -1
  49. package/src/__tests__/plugin-import-boundary-guard.test.ts +3 -0
  50. package/src/__tests__/provider-error-scenarios.test.ts +140 -0
  51. package/src/__tests__/provider-send-message-override-profile.test.ts +95 -0
  52. package/src/__tests__/run-conversation-turn-persistence.test.ts +66 -3
  53. package/src/__tests__/scripted-turn-metadata-persistence.test.ts +209 -0
  54. package/src/__tests__/skills.test.ts +27 -0
  55. package/src/__tests__/slack-channels-routes.test.ts +0 -2
  56. package/src/__tests__/slack-share-routes.test.ts +0 -3
  57. package/src/__tests__/slack-users-routes.test.ts +0 -2
  58. package/src/__tests__/subagent-tools.test.ts +11 -0
  59. package/src/__tests__/tool-preview-lifecycle.test.ts +58 -0
  60. package/src/__tests__/tool-result-spool.test.ts +5 -4
  61. package/src/__tests__/turn-boundary-resolution.test.ts +53 -0
  62. package/src/__tests__/turn-events-store.test.ts +26 -0
  63. package/src/__tests__/ui-visual-surface.test.ts +695 -0
  64. package/src/__tests__/unified-turn-context-visible-app.test.ts +99 -0
  65. package/src/__tests__/visible-app-context.test.ts +189 -0
  66. package/src/__tests__/workspace-git-service.test.ts +173 -33
  67. package/src/__tests__/workspace-migration-137-repair-retired-fireworks-minimax-model-id.test.ts +157 -0
  68. package/src/__tests__/workspace-migration-138-backfill-home-feed-titles.test.ts +373 -0
  69. package/src/__tests__/workspace-migration-139-clear-renamed-cost-profile-label.test.ts +137 -0
  70. package/src/agent/loop.ts +36 -7
  71. package/src/api/events/context-window-usage.ts +31 -0
  72. package/src/api/events/notification-intent.ts +8 -0
  73. package/src/api/events/ui-surface-pending.ts +35 -0
  74. package/src/api/index.ts +14 -0
  75. package/src/api/responses/conversation-message.ts +25 -4
  76. package/src/api/surfaces.ts +90 -1
  77. package/src/approvals/guardian-card-withdrawal.ts +66 -31
  78. package/src/approvals/guardian-decision-primitive.ts +4 -0
  79. package/src/calls/__tests__/call-setup-router.test.ts +156 -26
  80. package/src/calls/__tests__/voice-session-bridge.test.ts +201 -12
  81. package/src/calls/call-setup-router.ts +80 -36
  82. package/src/calls/voice-session-bridge.ts +173 -26
  83. package/src/cli/commands/inference.help.ts +3 -3
  84. package/src/cli/commands/notifications.help.ts +3 -2
  85. package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +42 -128
  86. package/src/cli/commands/platform/__tests__/credits.test.ts +9 -78
  87. package/src/cli/commands/platform/__tests__/helpers.ts +90 -0
  88. package/src/cli/commands/platform/__tests__/invoices.test.ts +238 -0
  89. package/src/cli/commands/platform/__tests__/plans.test.ts +9 -88
  90. package/src/cli/commands/platform/__tests__/status.test.ts +12 -87
  91. package/src/cli/commands/platform/__tests__/subscription.test.ts +9 -86
  92. package/src/cli/commands/platform/index.help.ts +92 -0
  93. package/src/cli/commands/platform/index.ts +7 -0
  94. package/src/cli/commands/platform/invoices.ts +132 -0
  95. package/src/cli/commands/usage.help.ts +1 -1
  96. package/src/cli/lib/list-installed-plugins.ts +2 -1
  97. package/src/config/__tests__/default-profile-catalog.test.ts +7 -10
  98. package/src/config/__tests__/deployment-context-defaults.test.ts +27 -5
  99. package/src/config/assistant-feature-flags.ts +7 -2
  100. package/src/config/bundled-skills/app-builder/SKILL.md +3 -2
  101. package/src/config/bundled-skills/subagent/SKILL.md +3 -1
  102. package/src/config/bundled-skills/subagent/TOOLS.json +3 -3
  103. package/src/config/bundled-skills/visualize/SKILL.md +163 -0
  104. package/src/config/call-site-defaults.ts +3 -2
  105. package/src/config/default-profile-catalog.ts +75 -70
  106. package/src/config/default-profile-names.ts +13 -28
  107. package/src/config/env-registry.ts +1 -0
  108. package/src/config/feature-flag-registry.json +16 -0
  109. package/src/config/llm-resolver.ts +21 -5
  110. package/src/config/loader.ts +18 -11
  111. package/src/config/schemas/llm.ts +1 -9
  112. package/src/config/schemas/memory-retrospective.ts +9 -0
  113. package/src/config/schemas/monitoring.ts +28 -2
  114. package/src/config/schemas/workspace-git.ts +15 -0
  115. package/src/config/seed-inference-profiles.ts +19 -5
  116. package/src/context/post-turn-tool-result-truncation.ts +2 -2
  117. package/src/conversations/__tests__/message-consolidation.test.ts +54 -0
  118. package/src/conversations/message-consolidation.ts +17 -15
  119. package/src/daemon/__tests__/turn-tail-assistant-reply-notify.test.ts +182 -0
  120. package/src/daemon/__tests__/turn-tail-deleted-conversation.test.ts +181 -0
  121. package/src/daemon/conversation-agent-loop-handlers.ts +90 -7
  122. package/src/daemon/conversation-agent-loop.ts +107 -22
  123. package/src/daemon/conversation-error.ts +169 -48
  124. package/src/daemon/conversation-messaging.ts +87 -4
  125. package/src/daemon/conversation-process.ts +57 -45
  126. package/src/daemon/conversation-runtime-assembly.ts +57 -0
  127. package/src/daemon/conversation-store.ts +36 -1
  128. package/src/daemon/conversation-surfaces.ts +68 -6
  129. package/src/daemon/conversation-turn-finalize.ts +76 -18
  130. package/src/daemon/conversation.ts +122 -27
  131. package/src/daemon/lifecycle.ts +9 -0
  132. package/src/daemon/message-types/conversations.ts +8 -0
  133. package/src/daemon/message-types/surfaces.ts +3 -0
  134. package/src/documents/document-store.ts +247 -10
  135. package/src/home/__tests__/feed-types.test.ts +43 -8
  136. package/src/home/__tests__/feed-writer.test.ts +59 -0
  137. package/src/home/feed-types.ts +34 -7
  138. package/src/home/feed-writer.ts +15 -6
  139. package/src/live-voice/__tests__/activity-label.test.ts +95 -0
  140. package/src/live-voice/__tests__/live-activity-reporter.test.ts +86 -5
  141. package/src/live-voice/__tests__/live-voice-agent-turn.test.ts +310 -25
  142. package/src/live-voice/__tests__/live-voice-events.test.ts +4 -4
  143. package/src/live-voice/__tests__/live-voice-triage-escalate.test.ts +8 -4
  144. package/src/live-voice/__tests__/live-voice-vad.test.ts +9 -1
  145. package/src/live-voice/activity-label.ts +169 -0
  146. package/src/live-voice/live-activity-reporter.ts +35 -6
  147. package/src/live-voice/live-voice-session.ts +282 -41
  148. package/src/live-voice/protocol.ts +35 -0
  149. package/src/messaging/providers/slack/__tests__/adapter-mention-rendering.test.ts +84 -2
  150. package/src/messaging/providers/slack/adapter.ts +86 -19
  151. package/src/messaging/providers/slack/api.test.ts +85 -1
  152. package/src/messaging/providers/slack/api.ts +121 -288
  153. package/src/messaging/providers/slack/client.ts +20 -251
  154. package/src/messaging/providers/slack/send.test.ts +4 -9
  155. package/src/messaging/providers/slack/send.ts +1 -1
  156. package/src/messaging/providers/slack/types.ts +5 -0
  157. package/src/messaging/providers/slack/web-api-transport.test.ts +181 -0
  158. package/src/messaging/providers/slack/web-api-transport.ts +367 -0
  159. package/src/messaging/providers/slack/withdraw.ts +6 -14
  160. package/src/messaging/providers/telegram-bot/send.test.ts +31 -1
  161. package/src/messaging/providers/telegram-bot/send.ts +23 -2
  162. package/src/messaging/providers/telegram-bot/withdraw.test.ts +152 -0
  163. package/src/messaging/providers/telegram-bot/withdraw.ts +166 -0
  164. package/src/monitoring/__tests__/db-integrity-sample.test.ts +18 -4
  165. package/src/monitoring/__tests__/file-descriptors.test.ts +144 -0
  166. package/src/monitoring/file-descriptors.ts +262 -0
  167. package/src/monitoring/process-memory.ts +4 -11
  168. package/src/monitoring/resource-sampler.ts +5 -0
  169. package/src/monitoring/worker.ts +12 -0
  170. package/src/notifications/__tests__/assistant-reply-producer.test.ts +740 -0
  171. package/src/notifications/__tests__/broadcaster.test.ts +347 -7
  172. package/src/notifications/__tests__/copy-composer.test.ts +53 -3
  173. package/src/notifications/__tests__/decision-engine.test.ts +349 -29
  174. package/src/notifications/__tests__/deterministic-checks.test.ts +21 -0
  175. package/src/notifications/__tests__/edit-notification.test.ts +330 -0
  176. package/src/notifications/__tests__/guardian-delivery-recorder.test.ts +71 -0
  177. package/src/notifications/__tests__/home-feed-side-effect.test.ts +170 -12
  178. package/src/notifications/adapters/macos.ts +3 -0
  179. package/src/notifications/adapters/platform.ts +90 -14
  180. package/src/notifications/adapters/telegram.ts +6 -4
  181. package/src/notifications/assistant-reply-producer.ts +219 -0
  182. package/src/notifications/broadcaster.ts +523 -317
  183. package/src/notifications/copy-composer.ts +27 -6
  184. package/src/notifications/decision-engine.ts +154 -77
  185. package/src/notifications/deterministic-checks.ts +6 -7
  186. package/src/notifications/edit-notification.ts +8 -4
  187. package/src/notifications/emit-signal.ts +49 -16
  188. package/src/notifications/guardian-delivery-recorder.ts +8 -2
  189. package/src/notifications/home-feed-side-effect.ts +43 -6
  190. package/src/notifications/notification-utils.ts +39 -4
  191. package/src/notifications/signal.ts +10 -0
  192. package/src/notifications/types.ts +17 -0
  193. package/src/persistence/bookmark-crud.ts +18 -9
  194. package/src/persistence/conversation-attention-store.ts +26 -0
  195. package/src/persistence/conversation-crud.ts +79 -32
  196. package/src/persistence/conversation-queries.ts +30 -15
  197. package/src/persistence/conversation-title-service.ts +5 -170
  198. package/src/persistence/conversation-types.ts +192 -1
  199. package/src/persistence/db-init.ts +14 -1
  200. package/src/persistence/db-maintenance.ts +5 -4
  201. package/src/persistence/embeddings/__tests__/plugin-index-qdrant-init.test.ts +219 -0
  202. package/src/persistence/embeddings/__tests__/plugin-index.test.ts +22 -0
  203. package/src/persistence/embeddings/__tests__/worker-script-version.test.ts +76 -0
  204. package/src/persistence/embeddings/embedding-local.ts +2 -1
  205. package/src/persistence/embeddings/embedding-runtime-manager.ts +29 -5
  206. package/src/persistence/embeddings/plugin-index.ts +84 -12
  207. package/src/persistence/migrations/360-add-document-workspace-path.test.ts +110 -0
  208. package/src/persistence/migrations/360-add-document-workspace-path.ts +39 -0
  209. package/src/persistence/planner-statistics.ts +14 -0
  210. package/src/persistence/schema/documents.ts +26 -11
  211. package/src/persistence/steps.ts +2 -0
  212. package/src/platform/client.test.ts +172 -2
  213. package/src/platform/client.ts +178 -55
  214. package/src/plugin-api/conversation-turn.ts +13 -0
  215. package/src/plugin-api/model-profiles.test.ts +6 -2
  216. package/src/plugins/defaults/memory/__tests__/bookmark-crud.test.ts +34 -0
  217. package/src/plugins/defaults/memory/__tests__/conversation-queries.test.ts +22 -0
  218. package/src/plugins/defaults/memory/__tests__/jobs-store-enqueue-gate.test.ts +11 -0
  219. package/src/plugins/defaults/memory/__tests__/memory-retrospective-accounting.test.ts +199 -0
  220. package/src/plugins/defaults/memory/__tests__/memory-retrospective-enqueue.test.ts +98 -1
  221. package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +105 -1
  222. package/src/plugins/defaults/memory/__tests__/memory-retrospective-sweep.test.ts +24 -2
  223. package/src/plugins/defaults/memory/__tests__/memory-retrospective-wake-chain.test.ts +524 -0
  224. package/src/plugins/defaults/memory/__tests__/memory-tier-boundary-guard.test.ts +1 -1
  225. package/src/plugins/defaults/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +9 -4
  226. package/src/plugins/defaults/memory/host-utils.ts +5 -0
  227. package/src/plugins/defaults/memory/memory-retrospective-accounting.ts +105 -1
  228. package/src/plugins/defaults/memory/memory-retrospective-enqueue.ts +64 -5
  229. package/src/plugins/defaults/memory/memory-retrospective-job.ts +36 -1
  230. package/src/plugins/defaults/memory/memory-retrospective-sweep.ts +12 -3
  231. package/src/plugins/defaults/memory/src/__tests__/memory-v2-simulate-route.test.ts +1 -0
  232. package/src/plugins/defaults/memory/substrate/__tests__/skill-store.test.ts +195 -1
  233. package/src/plugins/defaults/memory/substrate/consolidation-job.ts +1 -1
  234. package/src/plugins/defaults/memory/substrate/skill-store.ts +167 -29
  235. package/src/plugins/defaults/memory/v2/__tests__/injection.test.ts +131 -0
  236. package/src/plugins/defaults/memory/v2/__tests__/router.test.ts +1 -0
  237. package/src/plugins/defaults/memory/v2/activation-log-store.ts +4 -0
  238. package/src/plugins/defaults/memory/v2/injection.ts +48 -3
  239. package/src/plugins/defaults/memory/v2/rerank-local.ts +6 -2
  240. package/src/plugins/defaults/memory/v3/__tests__/pool-select.test.ts +1 -1
  241. package/src/plugins/defaults/platform-hosted/routes/reengage.ts +1 -1
  242. package/src/plugins/defaults/turn-context/injectors.ts +1 -0
  243. package/src/plugins/defaults/turn-context/unified-turn-context.ts +26 -0
  244. package/src/plugins/types.ts +17 -0
  245. package/src/prompts/templates/system-sections.ts +2 -2
  246. package/src/providers/__tests__/dispatch-connection-routing.test.ts +43 -2
  247. package/src/providers/__tests__/vellum-mismatch-routing.test.ts +8 -0
  248. package/src/providers/call-site-routing.ts +77 -14
  249. package/src/providers/connection-resolution.ts +42 -2
  250. package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +127 -1
  251. package/src/providers/inference/adapter-factory.ts +70 -12
  252. package/src/providers/model-catalog.ts +0 -11
  253. package/src/providers/model-intents.ts +11 -0
  254. package/src/providers/registry.ts +8 -0
  255. package/src/providers/retry.ts +85 -17
  256. package/src/providers/types.ts +8 -1
  257. package/src/runtime/__tests__/agent-wake.test.ts +181 -0
  258. package/src/runtime/agent-wake.ts +132 -9
  259. package/src/runtime/channel-approval-types.ts +25 -0
  260. package/src/runtime/routes/__tests__/connection-routes-vs-cli-parity.test.ts +2 -2
  261. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +11 -0
  262. package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +121 -9
  263. package/src/runtime/routes/__tests__/platform-invoice-routes.test.ts +392 -0
  264. package/src/runtime/routes/__tests__/slack-channel-routes.test.ts +7 -0
  265. package/src/runtime/routes/__tests__/workspace-commit-routes.test.ts +63 -0
  266. package/src/runtime/routes/consolidation-routes.ts +1 -1
  267. package/src/runtime/routes/conversation-management-routes.ts +18 -6
  268. package/src/runtime/routes/conversation-query-routes.ts +14 -23
  269. package/src/runtime/routes/conversation-routes.ts +113 -8
  270. package/src/runtime/routes/credential-routes.ts +1 -5
  271. package/src/runtime/routes/documents-routes.ts +207 -12
  272. package/src/runtime/routes/identity-routes.ts +3 -93
  273. package/src/runtime/routes/inbound-stages/admission-policy.test.ts +31 -1
  274. package/src/runtime/routes/inbound-stages/admission-policy.ts +18 -0
  275. package/src/runtime/routes/inference-provider-connection-routes.ts +52 -3
  276. package/src/runtime/routes/notification-routes.ts +4 -1
  277. package/src/runtime/routes/platform-routes.ts +238 -3
  278. package/src/runtime/routes/playground/guard.ts +1 -2
  279. package/src/runtime/routes/slack-channel-routes.ts +2 -4
  280. package/src/runtime/routes/workspace-commit-routes.ts +49 -2
  281. package/src/runtime/routes/workspace-routes.ts +11 -12
  282. package/src/runtime/routes/workspace-utils.ts +49 -2
  283. package/src/runtime/services/conversation-serializer.ts +2 -4
  284. package/src/subagent/__tests__/consult-context-gating.test.ts +98 -0
  285. package/src/subagent/__tests__/consult-context-skills.test.ts +82 -0
  286. package/src/subagent/__tests__/consult-context.test.ts +84 -0
  287. package/src/subagent/__tests__/consult-prompt.test.ts +36 -0
  288. package/src/subagent/consult-context.ts +410 -0
  289. package/src/subagent/consult-prompt.ts +38 -5
  290. package/src/subagent/manager.ts +9 -4
  291. package/src/subagent/types.ts +8 -0
  292. package/src/telemetry/telemetry-event-sources.ts +7 -0
  293. package/src/telemetry/telemetry-wire-source.json +1 -1
  294. package/src/telemetry/telemetry-wire.generated.ts +1 -0
  295. package/src/telemetry/turn-events-store.ts +30 -1
  296. package/src/telemetry/types.ts +24 -0
  297. package/src/telemetry/usage-telemetry-reporter.test.ts +48 -0
  298. package/src/tools/browser/pinned-tabs.ts +3 -1
  299. package/src/tools/subagent/spawn.ts +23 -1
  300. package/src/tools/terminal/safe-env.ts +1 -0
  301. package/src/tools/ui-surface/definitions.ts +39 -1
  302. package/src/tools/ui-surface/surface-shape-docs.ts +38 -4
  303. package/src/tools/ui-surface/visual-validation.ts +787 -0
  304. package/src/tools/workflows/run-workflow.test.ts +1 -0
  305. package/src/util/__tests__/short-title.test.ts +229 -0
  306. package/src/util/__tests__/worker-compute.test.ts +65 -0
  307. package/src/util/cgroup-cpu.ts +93 -0
  308. package/src/util/errors.ts +27 -0
  309. package/src/util/process-tree.ts +19 -0
  310. package/src/util/short-title.ts +189 -0
  311. package/src/util/worker-compute.ts +83 -0
  312. package/src/workspace/byok-default-profile-ensure.ts +25 -15
  313. package/src/workspace/git-service.ts +79 -30
  314. package/src/workspace/migrations/137-repair-retired-fireworks-minimax-model-id.ts +131 -0
  315. package/src/workspace/migrations/138-backfill-home-feed-titles.ts +179 -0
  316. package/src/workspace/migrations/139-clear-renamed-cost-profile-label.ts +92 -0
  317. package/src/workspace/migrations/registry.ts +6 -0
  318. package/src/plugins/defaults/memory/substrate/constants.ts +0 -8
@@ -0,0 +1,83 @@
1
+ /**
2
+ * ONNX Runtime thread tuning for the local ML worker subprocesses (embed,
3
+ * rerank).
4
+ *
5
+ * ONNX Runtime sizes its intra-op thread pool from the machine's physical core
6
+ * count when nothing says otherwise. An unhinted worker therefore takes every
7
+ * core for the duration of an inference batch. On a developer laptop that pins
8
+ * the machine and spins the fans; in a container it oversubscribes a CPU quota
9
+ * the scheduler then throttles.
10
+ *
11
+ * The work is background indexing the user neither initiated nor can see, so
12
+ * it should not preempt foreground work. Capping the pool trades batch latency
13
+ * for leaving the rest of the system responsive, the same trade as spawning
14
+ * these workers with `--smol`.
15
+ *
16
+ * Unlike the memory hint in worker-memory.ts, this is a hard cap: ONNX will
17
+ * not exceed the configured thread count.
18
+ */
19
+
20
+ import { getContainerCpuCores } from "./cgroup-cpu.js";
21
+
22
+ /**
23
+ * Environment variable carrying the computed cap to the worker script. The
24
+ * worker is a standalone bun process that reads this and passes it to ONNX as
25
+ * `session_options.intraOpNumThreads`.
26
+ */
27
+ export const ONNX_INTRA_OP_THREADS_ENV = "VELLUM_ONNX_INTRA_OP_THREADS";
28
+
29
+ /**
30
+ * Fraction of available cores one ML worker's intra-op pool may use. The
31
+ * machine is shared with the daemon, the gateway, tool subprocesses, and
32
+ * (because embed and rerank are separate processes) potentially a second ML
33
+ * worker, so one of them gets a quarter.
34
+ */
35
+ const WORKER_CPU_FRACTION = 0.25;
36
+
37
+ /**
38
+ * Lower clamp. ONNX treats a non-positive thread count as "pick for me", which
39
+ * is the unbounded default this module exists to prevent, so the floor is 1.
40
+ */
41
+ export const WORKER_MIN_INTRA_OP_THREADS = 1;
42
+
43
+ /**
44
+ * Upper clamp. Intra-op scaling for models this size flattens well before this
45
+ * point; past it, extra threads add synchronization overhead and heat without
46
+ * shortening the batch.
47
+ */
48
+ export const WORKER_MAX_INTRA_OP_THREADS = 4;
49
+
50
+ /**
51
+ * Intra-op thread cap for an ML worker, derived from the container CPU limit
52
+ * (the visible core count when no container limit applies, e.g. local dev),
53
+ * clamped to [{@link WORKER_MIN_INTRA_OP_THREADS}, {@link WORKER_MAX_INTRA_OP_THREADS}].
54
+ *
55
+ * A zero or negative core count means detection failed entirely; fall back to
56
+ * the floor rather than handing ONNX a value it would read as "unbounded".
57
+ */
58
+ export function computeWorkerIntraOpThreads(cores: number): number {
59
+ if (!Number.isFinite(cores) || cores <= 0) {
60
+ return WORKER_MIN_INTRA_OP_THREADS;
61
+ }
62
+ const target = Math.floor(cores * WORKER_CPU_FRACTION);
63
+ return Math.min(
64
+ WORKER_MAX_INTRA_OP_THREADS,
65
+ Math.max(WORKER_MIN_INTRA_OP_THREADS, target),
66
+ );
67
+ }
68
+
69
+ /**
70
+ * Environment additions for spawning an ONNX worker: the intra-op thread cap.
71
+ * An operator-provided value in the parent environment wins over the computed
72
+ * one, matching how `workerMemoryEnv` treats `BUN_JSC_forceRAMSize`.
73
+ *
74
+ * Returns only the compute keys, so spread this over `workerMemoryEnv()`,
75
+ * which carries the full parent environment.
76
+ */
77
+ export function workerComputeEnv(): Record<string, string> {
78
+ return {
79
+ [ONNX_INTRA_OP_THREADS_ENV]:
80
+ process.env[ONNX_INTRA_OP_THREADS_ENV] ??
81
+ String(computeWorkerIntraOpThreads(getContainerCpuCores())),
82
+ };
83
+ }
@@ -7,10 +7,7 @@ import {
7
7
  resolveDefaultProfileForProvider,
8
8
  USER_PROFILE_TEMPLATES,
9
9
  } from "../config/default-profile-catalog.js";
10
- import {
11
- DEFAULT_PROFILE_KEYS,
12
- type DefaultProfileKey,
13
- } from "../config/default-profile-names.js";
10
+ import type { DefaultProfileKey } from "../config/default-profile-names.js";
14
11
  import { getIsPlatform } from "../config/env-registry.js";
15
12
  import { invalidateConfigCache } from "../config/loader.js";
16
13
  import {
@@ -65,6 +62,19 @@ const log = getLogger("byok-default-profile-ensure");
65
62
  // Idempotent and write-avoidant: the file is rewritten only when at least one
66
63
  // stub or copy was removed.
67
64
 
65
+ /**
66
+ * The default keys BYOK hatching writes to disk. `latency-optimized` is absent
67
+ * by construction: no install carries a hatch stub or a
68
+ * `custom-latency-optimized` copy for it, and listing it here would make
69
+ * `uniformCopyProvider` demand a copy that cannot exist and convert nothing.
70
+ */
71
+ const HATCH_ERA_PROFILE_KEYS = [
72
+ "balanced",
73
+ "quality-optimized",
74
+ "cost-optimized",
75
+ ] as const satisfies readonly DefaultProfileKey[];
76
+ type HatchEraProfileKey = (typeof HATCH_ERA_PROFILE_KEYS)[number];
77
+
68
78
  /**
69
79
  * The exact stub shapes BYOK hatching left on each default key: thin (only
70
80
  * the workspace-owned overlay fields), `source: "managed"`, the frozen
@@ -82,7 +92,7 @@ const log = getLogger("byok-default-profile-ensure");
82
92
  * alone.
83
93
  */
84
94
  const STUB_ONLY_KEYS = new Set(["source", "status", "label", "thinking"]);
85
- const HATCH_STUB_LABELS: Record<DefaultProfileKey, string> = {
95
+ const HATCH_STUB_LABELS: Record<HatchEraProfileKey, string> = {
86
96
  balanced: "Balanced (Managed)",
87
97
  "quality-optimized": "Quality (Managed)",
88
98
  "cost-optimized": "Speed (Managed)",
@@ -100,7 +110,7 @@ const HATCH_STUB_LABELS: Record<DefaultProfileKey, string> = {
100
110
  const REPAIR_WRITTEN_THINKING = { enabled: true, streamThinking: true };
101
111
 
102
112
  function isHatchStub(
103
- key: DefaultProfileKey,
113
+ key: HatchEraProfileKey,
104
114
  entry: Record<string, unknown>,
105
115
  ): boolean {
106
116
  return (
@@ -142,7 +152,7 @@ const IGNORED_COMPARISON_KEYS = new Set(["label", "status", "model"]);
142
152
  * field still matches the template.
143
153
  */
144
154
  const HISTORICAL_INTENT_MODELS: Record<
145
- DefaultProfileKey,
155
+ HatchEraProfileKey,
146
156
  Partial<Record<string, readonly string[]>>
147
157
  > = {
148
158
  balanced: {
@@ -231,7 +241,7 @@ export function ensureByokDefaultProfiles(workspaceDir: string): void {
231
241
 
232
242
  // Deleting a hatch stub makes the default key resolve active from the
233
243
  // default provider's catalog column (and drops the stub's suffixed label).
234
- for (const key of DEFAULT_PROFILE_KEYS) {
244
+ for (const key of HATCH_ERA_PROFILE_KEYS) {
235
245
  const entry = readObject(profiles[key]);
236
246
  if (entry === null || !isHatchStub(key, entry)) {
237
247
  continue;
@@ -260,8 +270,8 @@ export function ensureByokDefaultProfiles(workspaceDir: string): void {
260
270
  : null;
261
271
 
262
272
  const retired = new Map<string, string>();
263
- const carriedDisables = new Set<DefaultProfileKey>();
264
- for (const key of DEFAULT_PROFILE_KEYS) {
273
+ const carriedDisables = new Set<HatchEraProfileKey>();
274
+ for (const key of HATCH_ERA_PROFILE_KEYS) {
265
275
  const name = `custom-${key}`;
266
276
  const entry = readObject(profiles[name]);
267
277
  if (
@@ -318,7 +328,7 @@ export function ensureByokDefaultProfiles(workspaceDir: string): void {
318
328
 
319
329
  function isKnownUneditedBody(
320
330
  entry: Record<string, unknown>,
321
- key: DefaultProfileKey,
331
+ key: HatchEraProfileKey,
322
332
  convertibleProvider: LLMProvider | null,
323
333
  completionBase: LLMConfigBase | undefined,
324
334
  ): boolean {
@@ -374,7 +384,7 @@ function isKnownUneditedBody(
374
384
  */
375
385
  function uniformCopyProvider(profiles: Record<string, unknown>): string | null {
376
386
  let provider: string | null = null;
377
- for (const key of DEFAULT_PROFILE_KEYS) {
387
+ for (const key of HATCH_ERA_PROFILE_KEYS) {
378
388
  const entry = readObject(profiles[`custom-${key}`]);
379
389
  if (entry === null) {
380
390
  return null;
@@ -415,7 +425,7 @@ function uniformCopyProvider(profiles: Record<string, unknown>): string | null {
415
425
  */
416
426
  function hatchBodyVariants(
417
427
  known: Record<string, unknown>,
418
- key: DefaultProfileKey,
428
+ key: HatchEraProfileKey,
419
429
  copyProvider: string,
420
430
  ): Record<string, unknown>[] {
421
431
  let variants = [known];
@@ -460,7 +470,7 @@ function withCompletionBaked(
460
470
  */
461
471
  function userOverlayState(
462
472
  entry: Record<string, unknown>,
463
- key: DefaultProfileKey,
473
+ key: HatchEraProfileKey,
464
474
  ): Record<string, unknown> | null {
465
475
  const overlay: Record<string, unknown> = {};
466
476
  const templateLabel = USER_PROFILE_TEMPLATES[`custom-${key}`]?.label;
@@ -566,7 +576,7 @@ function repairProfileSelections(
566
576
  llm: Record<string, unknown>,
567
577
  profiles: Record<string, unknown>,
568
578
  defaultProvider: DefaultProviderConfig,
569
- carriedDisables: ReadonlySet<DefaultProfileKey>,
579
+ carriedDisables: ReadonlySet<HatchEraProfileKey>,
570
580
  ): void {
571
581
  const effectiveEntry = (name: string): ProfileEntry | undefined =>
572
582
  resolveDefaultProfileForProvider(
@@ -915,6 +915,15 @@ export class WorkspaceGitService {
915
915
  );
916
916
  }
917
917
 
918
+ /**
919
+ * Whether background history compaction is enabled for this workspace.
920
+ * Gates only the scheduler — {@link compactHistoryNow} stays callable so
921
+ * operators can compact manually while automatic runs are off.
922
+ */
923
+ private isHistoryCompactionEnabled(): boolean {
924
+ return getConfig().workspaceGit?.historyCompaction?.enabled ?? true;
925
+ }
926
+
918
927
  /**
919
928
  * Working-tree size check for a repo-relative path. Uses lstat so a
920
929
  * symlink is measured by the link itself, not its target. Missing or
@@ -1003,10 +1012,21 @@ export class WorkspaceGitService {
1003
1012
  * history is still within retention, the attempt reschedules itself for
1004
1013
  * when the oldest commit ages past the cutoff. Best-effort like the
1005
1014
  * untrack sweeps: failures are logged and never affect commits.
1015
+ *
1016
+ * Gated by workspaceGit.historyCompaction.enabled, checked both here and
1017
+ * when the timer fires (config hot-reloads, so a pending timer must honor
1018
+ * a flip to disabled).
1006
1019
  */
1007
1020
  private scheduleHistoryCompaction(
1008
1021
  delayMs = HISTORY_COMPACTION_INITIAL_DELAY_MS,
1009
1022
  ): void {
1023
+ if (!this.isHistoryCompactionEnabled()) {
1024
+ log.debug(
1025
+ { workspaceDir: this.workspaceDir },
1026
+ "Background history compaction disabled by config; not scheduling",
1027
+ );
1028
+ return;
1029
+ }
1010
1030
  const dueAtMs = Date.now() + delayMs;
1011
1031
  if (this.historyCompactionTimer) {
1012
1032
  if (dueAtMs >= this.historyCompactionDueAtMs) {
@@ -1020,6 +1040,14 @@ export class WorkspaceGitService {
1020
1040
  }
1021
1041
  const timer = setTimeout(() => {
1022
1042
  void (async () => {
1043
+ if (!this.isHistoryCompactionEnabled()) {
1044
+ log.debug(
1045
+ { workspaceDir: this.workspaceDir },
1046
+ "Background history compaction disabled by config; skipping scheduled run",
1047
+ );
1048
+ this.historyCompactionTimer = null;
1049
+ return;
1050
+ }
1023
1051
  let retryAfterMs: number | undefined;
1024
1052
  try {
1025
1053
  const result = await this.compactHistoryNow();
@@ -1044,14 +1072,19 @@ export class WorkspaceGitService {
1044
1072
 
1045
1073
  /**
1046
1074
  * Rewrite workspace history so blobs over workspaceGit.maxFileSizeBytes
1047
- * stop occupying .git. Commits older than HISTORY_RETENTION_DAYS are
1075
+ * stop occupying .git. Commits older than the retention window
1076
+ * (HISTORY_RETENTION_DAYS unless `retentionDays` overrides it) are
1048
1077
  * squashed into a single base commit whose tree is scrubbed of oversized
1049
1078
  * entries; younger commits are replayed verbatim (trees, messages,
1050
1079
  * authors, and dates preserved); reflogs are then expired and unreachable
1051
- * objects pruned. Runs only when the object store contains an oversized
1052
- * blob that is actionable — unreachable, or reachable at a non-exempt
1053
- * path (see SIZE_GUARD_EXEMPT_PATTERNS) — so the steady state is a cheap
1054
- * detection scan and exempt canonical state never triggers rewrites.
1080
+ * objects pruned. By default runs only when the object store contains an
1081
+ * oversized blob that is actionable — unreachable, or reachable at a
1082
+ * non-exempt path (see SIZE_GUARD_EXEMPT_PATTERNS) — so the steady state
1083
+ * is a cheap detection scan and exempt canonical state never triggers
1084
+ * rewrites. With `force`, the squash runs unconditionally whenever any
1085
+ * commit is older than the cutoff — the manual path for reclaiming bulk
1086
+ * history (including old versions of exempt canonical state referenced
1087
+ * only by squashed commits).
1055
1088
  *
1056
1089
  * Oversized blobs still referenced by replayed recent commits survive
1057
1090
  * until those commits age past retention — the result carries
@@ -1061,7 +1094,12 @@ export class WorkspaceGitService {
1061
1094
  * Git notes attached to rewritten commits are orphaned; enrichment only
1062
1095
  * targets commits created after the rewrite, so this is cosmetic.
1063
1096
  */
1064
- async compactHistoryNow(): Promise<{
1097
+ async compactHistoryNow(options?: {
1098
+ /** Squash aged history even when no oversized blob is actionable. */
1099
+ force?: boolean;
1100
+ /** Retention window override in days for this run. */
1101
+ retentionDays?: number;
1102
+ }): Promise<{
1065
1103
  rewrote: boolean;
1066
1104
  squashedCommits: number;
1067
1105
  keptCommits: number;
@@ -1069,7 +1107,7 @@ export class WorkspaceGitService {
1069
1107
  retryAfterMs?: number;
1070
1108
  }> {
1071
1109
  await this.ensureInitialized();
1072
- return this.mutex.withLock(() => this.compactHistoryLocked());
1110
+ return this.mutex.withLock(() => this.compactHistoryLocked(options));
1073
1111
  }
1074
1112
 
1075
1113
  /**
@@ -1180,7 +1218,10 @@ export class WorkspaceGitService {
1180
1218
  return remaining.size > 0 ? "prunable" : "none";
1181
1219
  }
1182
1220
 
1183
- private async compactHistoryLocked(): Promise<{
1221
+ private async compactHistoryLocked(options?: {
1222
+ force?: boolean;
1223
+ retentionDays?: number;
1224
+ }): Promise<{
1184
1225
  rewrote: boolean;
1185
1226
  squashedCommits: number;
1186
1227
  keptCommits: number;
@@ -1188,11 +1229,13 @@ export class WorkspaceGitService {
1188
1229
  }> {
1189
1230
  const noop = { rewrote: false, squashedCommits: 0, keptCommits: 0 };
1190
1231
  const limit = this.maxFileSizeBytes();
1232
+ const force = options?.force === true;
1233
+ const retentionDays = options?.retentionDays ?? HISTORY_RETENTION_DAYS;
1191
1234
 
1192
1235
  // Any oversized blobs at all? Bounds the cost of every boot where there
1193
- // is nothing to do.
1236
+ // is nothing to do. Forced runs squash regardless.
1194
1237
  const oversizedOids = await this.collectOversizedBlobOidsLocked(limit);
1195
- if (oversizedOids.size === 0) {
1238
+ if (oversizedOids.size === 0 && !force) {
1196
1239
  return noop;
1197
1240
  }
1198
1241
 
@@ -1236,31 +1279,39 @@ export class WorkspaceGitService {
1236
1279
  return noop;
1237
1280
  }
1238
1281
 
1239
- const verdict = await this.classifyOversizedBlobsLocked(oversizedOids);
1240
- if (verdict === "none") {
1241
- // Exempt canonical state or ref-retained only — nothing to reclaim.
1242
- return { ...noop, keptCommits: commits.length };
1243
- }
1244
- if (verdict === "prunable") {
1245
- // Only unreachable blobs (e.g. an external add that stageAllLocked
1246
- // reset) — prune reclaims them without rewriting any history.
1247
- await this.expireReflogsAndPruneLocked();
1248
- log.info(
1249
- { workspaceDir: this.workspaceDir },
1250
- "Pruned unreachable oversized blobs from workspace git",
1251
- );
1252
- return { ...noop, keptCommits: commits.length };
1282
+ // Forced runs skip classification: they squash whenever aged history
1283
+ // exists, so the reachable/exempt distinction does not short-circuit.
1284
+ if (!force) {
1285
+ const verdict = await this.classifyOversizedBlobsLocked(oversizedOids);
1286
+ if (verdict === "none") {
1287
+ // Exempt canonical state or ref-retained only — nothing to reclaim.
1288
+ return { ...noop, keptCommits: commits.length };
1289
+ }
1290
+ if (verdict === "prunable") {
1291
+ // Only unreachable blobs (e.g. an external add that stageAllLocked
1292
+ // reset) — prune reclaims them without rewriting any history.
1293
+ await this.expireReflogsAndPruneLocked();
1294
+ log.info(
1295
+ { workspaceDir: this.workspaceDir },
1296
+ "Pruned unreachable oversized blobs from workspace git",
1297
+ );
1298
+ return { ...noop, keptCommits: commits.length };
1299
+ }
1253
1300
  }
1254
1301
 
1255
1302
  // Squash a PREFIX of the chain so replay order stays consistent even if
1256
1303
  // commit timestamps are not monotonic.
1257
- const cutoffSec =
1258
- Math.floor(Date.now() / 1000) - HISTORY_RETENTION_DAYS * 86400;
1304
+ const cutoffSec = Math.floor(Date.now() / 1000) - retentionDays * 86400;
1259
1305
  let splitIdx = commits.findIndex((c) => c.committedAtSec >= cutoffSec);
1260
1306
  if (splitIdx === -1) {
1261
1307
  splitIdx = commits.length;
1262
1308
  }
1263
1309
  if (splitIdx === 0) {
1310
+ if (oversizedOids.size === 0) {
1311
+ // Forced run, but every commit is inside retention and there is
1312
+ // nothing to reclaim — no rewrite.
1313
+ return { ...noop, keptCommits: commits.length };
1314
+ }
1264
1315
  // Main holds the blobs, but only in commits still within retention.
1265
1316
  // A mixed case can also carry unreachable blobs — prune those now,
1266
1317
  // and re-check in case they were all that remained actionable.
@@ -1279,7 +1330,7 @@ export class WorkspaceGitService {
1279
1330
  // bloat until restart.
1280
1331
  const oldestCommittedAtSec = commits[0]?.committedAtSec ?? cutoffSec;
1281
1332
  const oldestAgesOutMs =
1282
- (oldestCommittedAtSec + HISTORY_RETENTION_DAYS * 86400) * 1000 -
1333
+ (oldestCommittedAtSec + retentionDays * 86400) * 1000 -
1283
1334
  Date.now() +
1284
1335
  60_000;
1285
1336
  const retryAfterMs = Math.max(
@@ -1358,9 +1409,7 @@ export class WorkspaceGitService {
1358
1409
  const oldestKeptSec =
1359
1410
  kept[0]?.committedAtSec ?? Math.floor(Date.now() / 1000);
1360
1411
  retryAfterMs = Math.max(
1361
- (oldestKeptSec + HISTORY_RETENTION_DAYS * 86400) * 1000 -
1362
- Date.now() +
1363
- 60_000,
1412
+ (oldestKeptSec + retentionDays * 86400) * 1000 - Date.now() + 60_000,
1364
1413
  HISTORY_COMPACTION_MIN_RETRY_MS,
1365
1414
  );
1366
1415
  }
@@ -0,0 +1,131 @@
1
+ import { existsSync, readFileSync, renameSync, writeFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+
4
+ import type { WorkspaceMigration } from "./types.js";
5
+
6
+ /**
7
+ * Repair the retired Fireworks MiniMax M2.5 model ID in workspace LLM config.
8
+ *
9
+ * Fireworks no longer serves `accounts/fireworks/models/minimax-m2p5`, so
10
+ * chat/completions calls fail with a 404 "Model not found, inaccessible,
11
+ * and/or not deployed". The ID was never an intent default — it was only
12
+ * reachable by picking it — but a config that picked it still pins it in
13
+ * `llm.default`, `llm.callSites.*`, and `llm.profiles.*`.
14
+ *
15
+ * Repair those leaves only on an exact match, replacing with
16
+ * `accounts/fireworks/models/minimax-m2p7`: the nearest live MiniMax entry
17
+ * (same 196K window, same 25K output cap, same per-token pricing), so the
18
+ * repaired pin keeps the model family the user chose. Deliberately not
19
+ * `minimax-m3`, which is the current Fireworks balanced-intent model —
20
+ * rewriting a hand-edited `custom-*` profile onto the template's model
21
+ * would make it read as unedited to `ensureByokDefaultProfiles`.
22
+ *
23
+ * Provider guard: the retired ID belongs to the `fireworks` provider and
24
+ * also appears in managed profiles stamped `provider: "vellum"` (which
25
+ * route Fireworks-account model IDs through the managed proxy). A fragment
26
+ * is repaired when its `provider` is `"fireworks"`, `"vellum"`, or absent;
27
+ * an explicit other provider (e.g. an `openai-compatible` endpoint serving
28
+ * a model by the same name) is left untouched.
29
+ */
30
+ export const repairRetiredFireworksMinimaxModelIdMigration: WorkspaceMigration =
31
+ {
32
+ id: "137-repair-retired-fireworks-minimax-model-id",
33
+ description:
34
+ "Repair retired Fireworks accounts/fireworks/models/minimax-m2p5 model ID in workspace LLM config",
35
+ run(workspaceDir: string): void {
36
+ const configPath = join(workspaceDir, "config.json");
37
+ if (!existsSync(configPath)) {
38
+ return;
39
+ }
40
+
41
+ // Read outside the parse catch: a transient filesystem error (EIO,
42
+ // EACCES) must reach the runner so the migration retries, while
43
+ // malformed JSON is a permanent state this migration cannot repair.
44
+ const rawText = readFileSync(configPath, "utf-8");
45
+
46
+ let config: Record<string, unknown>;
47
+ try {
48
+ const raw = JSON.parse(rawText);
49
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
50
+ return;
51
+ }
52
+ config = raw as Record<string, unknown>;
53
+ } catch {
54
+ return;
55
+ }
56
+
57
+ const llm = readObject(config.llm);
58
+ if (llm === null) {
59
+ return;
60
+ }
61
+
62
+ let changed = false;
63
+
64
+ changed = repairFragment(readObject(llm.default)) || changed;
65
+
66
+ const callSites = readObject(llm.callSites);
67
+ if (callSites !== null) {
68
+ for (const rawConfig of Object.values(callSites)) {
69
+ changed = repairFragment(readObject(rawConfig)) || changed;
70
+ }
71
+ }
72
+
73
+ const profiles = readObject(llm.profiles);
74
+ if (profiles !== null) {
75
+ for (const rawProfile of Object.values(profiles)) {
76
+ changed = repairFragment(readObject(rawProfile)) || changed;
77
+ }
78
+ }
79
+
80
+ if (!changed) {
81
+ return;
82
+ }
83
+
84
+ // Write-then-rename so an interrupted write cannot leave config.json
85
+ // truncated: a torn in-place write would parse as invalid JSON on the
86
+ // retry, which the catch above treats as "nothing to do", letting the
87
+ // runner checkpoint the migration as completed against a corrupt file.
88
+ const tmpPath = `${configPath}.migration-137.tmp`;
89
+ writeFileSync(tmpPath, JSON.stringify(config, null, 2) + "\n");
90
+ renameSync(tmpPath, configPath);
91
+ },
92
+ // The exact-match rewrite is idempotent, so a transient failure (full
93
+ // disk, I/O error) is safe to retry on later startups.
94
+ retryFailedCheckpoint: true,
95
+ down(_workspaceDir: string): void {
96
+ // Forward-only: reintroducing the retired model ID would break
97
+ // Fireworks calls.
98
+ },
99
+ };
100
+
101
+ // ---------------------------------------------------------------------------
102
+ // Helpers: self-contained per workspace migrations AGENTS.md
103
+ // ---------------------------------------------------------------------------
104
+
105
+ const RETIRED_MODEL_ID = "accounts/fireworks/models/minimax-m2p5";
106
+ const REPLACEMENT_MODEL_ID = "accounts/fireworks/models/minimax-m2p7";
107
+ const REPAIRABLE_PROVIDERS = new Set(["fireworks", "vellum"]);
108
+
109
+ function repairFragment(fragment: Record<string, unknown> | null): boolean {
110
+ if (fragment === null) {
111
+ return false;
112
+ }
113
+ if (fragment.model !== RETIRED_MODEL_ID) {
114
+ return false;
115
+ }
116
+ if (
117
+ fragment.provider !== undefined &&
118
+ !REPAIRABLE_PROVIDERS.has(fragment.provider as string)
119
+ ) {
120
+ return false;
121
+ }
122
+ fragment.model = REPLACEMENT_MODEL_ID;
123
+ return true;
124
+ }
125
+
126
+ function readObject(value: unknown): Record<string, unknown> | null {
127
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
128
+ return null;
129
+ }
130
+ return value as Record<string, unknown>;
131
+ }