@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
@@ -11,12 +11,8 @@ import {
11
11
  DEFAULT_PROFILE_PROVIDERS,
12
12
  type DefaultProfileKey,
13
13
  type DefaultProfileProvider,
14
- INTERNAL_PROFILE_KEYS,
15
- type InternalProfileKey,
16
14
  isDefaultProfileProvider,
17
15
  OS_BETA_PROFILE_KEY,
18
- PROFILE_MATRIX_KEYS,
19
- type ProfileMatrixKey,
20
16
  } from "./default-profile-names.js";
21
17
  import { resolveDefaultConnectionName } from "./default-provider-resolution.js";
22
18
  import {
@@ -28,7 +24,8 @@ import {
28
24
 
29
25
  /**
30
26
  * Code-defined catalog of the default inference profiles (`balanced`,
31
- * `quality-optimized`, `cost-optimized`, plus the flag-gated `os-beta`).
27
+ * `quality-optimized`, `cost-optimized`, `latency-optimized`, plus the
28
+ * flag-gated `os-beta`).
32
29
  *
33
30
  * The catalog is the single source of truth for default profile CONTENT,
34
31
  * structured as an intent × provider matrix: each default profile is an
@@ -60,6 +57,9 @@ export type DefaultProfileTemplate = Omit<
60
57
  provider: NonNullable<ProfileEntry["provider"]>;
61
58
  };
62
59
 
60
+ /** One implementation of every default profile, keyed by profile key. */
61
+ type ProfileImpls = Record<DefaultProfileKey, DefaultProfileTemplate>;
62
+
63
63
  /**
64
64
  * The `vellum` column: platform-managed implementations, stamped
65
65
  * `provider: "vellum"` — dispatch derives the upstream from the model.
@@ -68,7 +68,7 @@ export type DefaultProfileTemplate = Omit<
68
68
  * Overwritten in workspace config on every daemon boot so Vellum can push
69
69
  * model/config updates to customers in new releases.
70
70
  */
71
- const VELLUM_PROFILE_IMPLS: Record<ProfileMatrixKey, DefaultProfileTemplate> = {
71
+ const VELLUM_PROFILE_IMPLS: ProfileImpls = {
72
72
  balanced: {
73
73
  model: "gpt-5.6-luna",
74
74
  provider: "vellum",
@@ -99,11 +99,11 @@ const VELLUM_PROFILE_IMPLS: Record<ProfileMatrixKey, DefaultProfileTemplate> = {
99
99
  model: "accounts/fireworks/models/deepseek-v4-flash",
100
100
  provider: "vellum",
101
101
  source: "managed",
102
- label: "Speed",
102
+ label: "Cost",
103
103
  // Tier intent only - never name the concrete model here. Clients
104
104
  // surface the live model beside the description, so a model name in
105
105
  // this copy would go stale the moment the pin moves.
106
- description: "Fastest responses at lower cost",
106
+ description: "Cheapest responses, for high-volume work",
107
107
  maxTokens: 8192,
108
108
  // Explicit reasoning opt-out. OpenAI-compat APIs default reasoning to
109
109
  // "medium" when the field is omitted, and effort-driven providers encode
@@ -116,22 +116,24 @@ const VELLUM_PROFILE_IMPLS: Record<ProfileMatrixKey, DefaultProfileTemplate> = {
116
116
  },
117
117
  },
118
118
  "latency-optimized": {
119
- // The managed latency class. Its leading tokens are the live-voice
120
- // turn-taking verdict, so what this profile optimizes is the tail of
121
- // time-to-first-token rather than the median: a verdict slower than
122
- // `liveVoice.frontModel.endpointDecisionTimeoutMs` trips the speculative
123
- // fail-open commit in live-voice-session.ts, which is audible dead air.
119
+ // The managed latency class, also what the live-voice front model runs on.
120
+ // Its leading tokens are the turn-taking verdict, so what this profile
121
+ // optimizes is the tail of time-to-first-token rather than the median: a
122
+ // verdict slower than `liveVoice.frontModel.endpointDecisionTimeoutMs`
123
+ // trips the speculative fail-open commit in live-voice-session.ts, which
124
+ // is audible dead air.
124
125
  //
125
126
  // Two constraints bind the model id. Its managed credentials must be
126
127
  // provisioned in every environment, and it alone selects the upstream:
127
128
  // `provider` below is the provider-agnostic managed sentinel, so
128
129
  // `getManagedUpstream` resolves the real upstream from the model's catalog
129
- // owner.
130
+ // owner. Sharing `balanced`'s model is deliberate: the split is effort
131
+ // and thinking, and this model is the one live-voice TTFT drives validated.
130
132
  model: "gpt-5.6-luna",
131
133
  provider: "vellum",
132
134
  source: "managed",
133
- label: "Latency",
134
- description: "Lowest time-to-first-token, for real-time call sites",
135
+ label: "Speed",
136
+ description: "Fastest responses, with reasoning turned off",
135
137
  maxTokens: 8192,
136
138
  effort: "low",
137
139
  thinking: { enabled: false, streamThinking: false },
@@ -150,7 +152,7 @@ const VELLUM_PROFILE_IMPLS: Record<ProfileMatrixKey, DefaultProfileTemplate> = {
150
152
  * per-request for any other default-capable provider.
151
153
  */
152
154
  const BYOK_PROFILE_IMPLS: Record<
153
- ProfileMatrixKey,
155
+ DefaultProfileKey,
154
156
  Omit<DefaultProfileTemplate, "provider">
155
157
  > = {
156
158
  balanced: {
@@ -173,21 +175,24 @@ const BYOK_PROFILE_IMPLS: Record<
173
175
  thinking: { enabled: true, streamThinking: true },
174
176
  contextWindow: { maxInputTokens: DEFAULT_CONTEXT_WINDOW_MAX_INPUT_TOKENS },
175
177
  },
178
+ // On providers whose cheapest model is also their fastest (anthropic,
179
+ // ollama, fireworks) these two intents resolve the same model and the split
180
+ // is effort alone (see PROVIDER_MODEL_INTENTS).
176
181
  "cost-optimized": {
177
- intent: "latency-optimized",
182
+ intent: "cost-optimized",
178
183
  source: "user",
179
- label: "Speed",
180
- description: "Fastest responses at lower cost",
184
+ label: "Cost",
185
+ description: "Cheapest responses, for high-volume work",
181
186
  maxTokens: 8192,
182
- effort: "low",
187
+ effort: "none",
183
188
  thinking: { enabled: false, streamThinking: false },
184
189
  contextWindow: { maxInputTokens: DEFAULT_CONTEXT_WINDOW_MAX_INPUT_TOKENS },
185
190
  },
186
191
  "latency-optimized": {
187
192
  intent: "latency-optimized",
188
193
  source: "user",
189
- label: "Latency",
190
- description: "Lowest time-to-first-token, for real-time call sites",
194
+ label: "Speed",
195
+ description: "Fastest responses, with reasoning turned off",
191
196
  maxTokens: 8192,
192
197
  effort: "low",
193
198
  thinking: { enabled: false, streamThinking: false },
@@ -200,10 +205,10 @@ const BYOK_PROFILE_IMPLS: Record<
200
205
  * implementation of default profile `key` on `provider`.
201
206
  */
202
207
  export const PROFILE_IMPLS: Record<
203
- ProfileMatrixKey,
208
+ DefaultProfileKey,
204
209
  Record<DefaultProfileProvider, DefaultProfileTemplate>
205
210
  > = Object.fromEntries(
206
- PROFILE_MATRIX_KEYS.map((key) => [
211
+ DEFAULT_PROFILE_KEYS.map((key) => [
207
212
  key,
208
213
  Object.fromEntries(
209
214
  DEFAULT_PROFILE_PROVIDERS.map((provider) => [
@@ -215,7 +220,7 @@ export const PROFILE_IMPLS: Record<
215
220
  ) as Record<DefaultProfileProvider, DefaultProfileTemplate>,
216
221
  ]),
217
222
  ) as Record<
218
- ProfileMatrixKey,
223
+ DefaultProfileKey,
219
224
  Record<DefaultProfileProvider, DefaultProfileTemplate>
220
225
  >;
221
226
 
@@ -229,18 +234,45 @@ export const MANAGED_PROFILE_TEMPLATES: Record<string, DefaultProfileTemplate> =
229
234
  DEFAULT_PROFILE_KEYS.map((key) => [key, PROFILE_IMPLS[key].vellum]),
230
235
  );
231
236
 
237
+ /**
238
+ * The values BYOK hatch seeding wrote onto each `custom-*` copy, for the keys
239
+ * whose live template carries something else. The conversion pass recognizes
240
+ * an unedited copy by comparing it field-by-field against the frozen body, so
241
+ * every field here must describe what sits on disk, not what the profile
242
+ * resolves to today. A field the live template alone supplies makes every
243
+ * unedited copy read as user-edited and stop converting.
244
+ *
245
+ * `intent` selects the copy's model through `materializeProfile`, so it is the
246
+ * load-bearing pin. `label` is compared nowhere in the body, but
247
+ * `userOverlayState` reads it to tell a user rename from the hatch's own
248
+ * label, and a mismatch there carries a phantom rename onto the bare key.
249
+ */
250
+ const HATCH_ERA_TEMPLATE_FIELDS: Partial<
251
+ Record<DefaultProfileKey, Partial<DefaultProfileTemplate>>
252
+ > = {
253
+ "cost-optimized": {
254
+ intent: "latency-optimized",
255
+ label: "Speed",
256
+ description: "Fastest responses at lower cost",
257
+ effort: "low",
258
+ },
259
+ };
260
+
232
261
  /**
233
262
  * Frozen record of the `custom-*` profile bodies that pre-conversion BYOK
234
263
  * hatches wrote to workspace config (the anthropic column; provider and
235
264
  * connection were overridden per hatch). Consumed by the existing-install
236
265
  * conversion pass as the reference for recognizing unedited copies, which
237
266
  * are safe to remove in favor of the code-resolved defaults.
267
+ *
268
+ * The `latency-optimized` entry exists for shape only: hatch seeding writes no
269
+ * `custom-latency-optimized`, so no install holds one to recognize.
238
270
  */
239
271
  export const USER_PROFILE_TEMPLATES: Record<string, DefaultProfileTemplate> =
240
272
  Object.fromEntries(
241
273
  DEFAULT_PROFILE_KEYS.map((key) => [
242
274
  `custom-${key}`,
243
- PROFILE_IMPLS[key].anthropic,
275
+ { ...PROFILE_IMPLS[key].anthropic, ...HATCH_ERA_TEMPLATE_FIELDS[key] },
244
276
  ]),
245
277
  );
246
278
 
@@ -263,6 +295,17 @@ export const OS_BETA_PROFILE_TEMPLATE: DefaultProfileTemplate = {
263
295
  topP: 0.95,
264
296
  };
265
297
 
298
+ /**
299
+ * Profiles whose body is code-owned outright: no workspace overlay, and no
300
+ * user-owned shadow. The shadow rule below lets a user replace a default they
301
+ * can select, but `latency-optimized` fronts every live-voice turn through
302
+ * `voiceFrontDecision`/`voiceFrontDoor`, where a model outside the latency
303
+ * envelope is audible dead air rather than a slow reply. A same-named
304
+ * workspace entry stays on disk and stays listed; it just never governs what
305
+ * this name resolves to.
306
+ */
307
+ export const CODE_OWNED_PROFILE_NAMES = new Set<string>(["latency-optimized"]);
308
+
266
309
  // All managed profiles, including the flag-gated os-beta, are invariant:
267
310
  // their MANAGED-SOURCE entries are read-only to user-facing writes except
268
311
  // re-enabling a disabled one (enforced at commitConfigWrite). A user-owned
@@ -270,7 +313,6 @@ export const OS_BETA_PROFILE_TEMPLATE: DefaultProfileTemplate = {
270
313
  // the on-disk entry's `source` being `managed`.
271
314
  export const INVARIANT_PROFILE_NAMES = new Set<string>([
272
315
  ...DEFAULT_PROFILE_KEYS,
273
- ...INTERNAL_PROFILE_KEYS,
274
316
  OS_BETA_PROFILE_KEY,
275
317
  ]);
276
318
 
@@ -283,7 +325,6 @@ export const INVARIANT_PROFILE_NAMES = new Set<string>([
283
325
  // same-named user profile.
284
326
  export const MANAGED_PROFILE_NAMES = new Set<string>([
285
327
  ...DEFAULT_PROFILE_KEYS,
286
- ...INTERNAL_PROFILE_KEYS,
287
328
  OS_BETA_PROFILE_KEY,
288
329
  ]);
289
330
 
@@ -319,7 +360,7 @@ export function materializeProfile(
319
360
  // `PROVIDER_MODEL_INTENTS`' own check): exactly one of `intent`/`model` is
320
361
  // set, and every pinned model id exists in PROVIDER_CATALOG for its
321
362
  // underlying provider — catching drift when a model is renamed or removed.
322
- for (const key of PROFILE_MATRIX_KEYS) {
363
+ for (const key of DEFAULT_PROFILE_KEYS) {
323
364
  for (const provider of DEFAULT_PROFILE_PROVIDERS) {
324
365
  const impl = PROFILE_IMPLS[key][provider];
325
366
  if ((impl.model == null) === (impl.intent == null)) {
@@ -355,7 +396,7 @@ for (const provider of DEFAULT_PROVIDER_CHOICES) {
355
396
  if (isDefaultProfileProvider(provider)) {
356
397
  continue;
357
398
  }
358
- for (const key of PROFILE_MATRIX_KEYS) {
399
+ for (const key of DEFAULT_PROFILE_KEYS) {
359
400
  const { model } = materializeProfile(
360
401
  { ...BYOK_PROFILE_IMPLS[key], provider },
361
402
  provider,
@@ -372,7 +413,7 @@ for (const provider of DEFAULT_PROVIDER_CHOICES) {
372
413
 
373
414
  function buildDefaultProfileEntries(): Record<string, ProfileEntry> {
374
415
  const entries: Record<string, ProfileEntry> = {};
375
- for (const key of PROFILE_MATRIX_KEYS) {
416
+ for (const key of DEFAULT_PROFILE_KEYS) {
376
417
  const impl = PROFILE_IMPLS[key].vellum;
377
418
  entries[key] = materializeProfile(impl, impl.provider);
378
419
  }
@@ -445,15 +486,7 @@ function resolveAgainstBody(
445
486
  if (body == null) {
446
487
  return workspace;
447
488
  }
448
- // An internal profile's body is code-owned outright — no workspace overlay,
449
- // not even a user-owned shadow. The shadow rule below exists so a user can
450
- // deliberately replace a default they can see and select; an internal name
451
- // was never selectable, so a same-named workspace entry (legal before the
452
- // name was reserved) is unrelated state, not an override. Honoring it would
453
- // silently hand a latency-class call site an arbitrary user model. The
454
- // entry itself is untouched: it stays in `llm.profiles`, stays listed, and
455
- // stays valid as an `activeProfile` reference.
456
- if (isInternalProfileKey(name)) {
489
+ if (CODE_OWNED_PROFILE_NAMES.has(name)) {
457
490
  return { ...body };
458
491
  }
459
492
  if (workspace == null) {
@@ -506,27 +539,11 @@ export function isDefaultProfileKey(name: string): name is DefaultProfileKey {
506
539
  return (DEFAULT_PROFILE_KEYS as readonly string[]).includes(name);
507
540
  }
508
541
 
509
- /**
510
- * Whether a name is implemented by the intent × provider matrix — the
511
- * user-facing defaults plus the internal call-site-only profiles. This is the
512
- * predicate resolution uses: an internal profile must resolve through the
513
- * default provider's column exactly like a default, even though it is never
514
- * listed or seeded.
515
- */
516
- export function isMatrixProfileKey(name: string): name is ProfileMatrixKey {
517
- return (PROFILE_MATRIX_KEYS as readonly string[]).includes(name);
518
- }
519
-
520
- /** Whether a name is a code-owned profile that must never be listed to users. */
521
- export function isInternalProfileKey(name: string): name is InternalProfileKey {
522
- return (INTERNAL_PROFILE_KEYS as readonly string[]).includes(name);
523
- }
524
-
525
542
  function defaultProfileBodyForProvider(
526
543
  name: string,
527
544
  defaultProvider: DefaultProviderConfig | null,
528
545
  ): ProfileEntry | undefined {
529
- if (defaultProvider == null || !isMatrixProfileKey(name)) {
546
+ if (defaultProvider == null || !isDefaultProfileKey(name)) {
530
547
  return CODE_DEFAULT_PROFILE_ENTRIES[name];
531
548
  }
532
549
  const { provider } = defaultProvider;
@@ -569,12 +586,6 @@ function clampMaxTokensToModelCap(body: ProfileEntry): ProfileEntry {
569
586
  * available code default, merged per `getEffectiveProfile`. This is the
570
587
  * record all runtime readers of `llm.profiles` should consume; the raw
571
588
  * workspace record is a write-path concern.
572
- *
573
- * Internal profiles are omitted: they exist only to be named by a call-site
574
- * default, so listing them would offer them as selectable models and let
575
- * `activeProfile`/`overrideProfile` validation accept them. Resolution
576
- * reaches them by name through `resolveDefaultProfileForProvider`, which
577
- * does not go through this record.
578
589
  */
579
590
  export function getEffectiveProfiles(
580
591
  workspaceProfiles: Record<string, ProfileEntry> | undefined,
@@ -586,9 +597,6 @@ export function getEffectiveProfiles(
586
597
  ...(workspaceProfiles ?? {}),
587
598
  };
588
599
  for (const name of Object.keys(catalogEntries)) {
589
- if (isInternalProfileKey(name)) {
590
- continue;
591
- }
592
600
  const entry = getEffectiveProfile(workspaceProfiles, name, catalogEntries);
593
601
  if (entry != null) {
594
602
  effective[name] = entry;
@@ -613,9 +621,6 @@ export function getEffectiveProfilesForProvider(
613
621
  ...(workspaceProfiles ?? {}),
614
622
  };
615
623
  for (const name of Object.keys(CODE_DEFAULT_PROFILE_ENTRIES)) {
616
- if (isInternalProfileKey(name)) {
617
- continue;
618
- }
619
624
  const entry = resolveDefaultProfileForProvider(
620
625
  workspaceProfiles,
621
626
  name,
@@ -6,41 +6,26 @@
6
6
  * import cycles.
7
7
  */
8
8
 
9
- /** Stable keys of the always-available default profiles. */
9
+ /**
10
+ * Stable keys of the always-available default profiles, in picker order.
11
+ *
12
+ * A key is an on-disk contract: `llm.callSites.*.profile`, `activeProfile`,
13
+ * mix arms and schedule pins all reference it, so a key is fixed regardless of
14
+ * the label its profile carries. `cost-optimized` is labelled "Cost" and
15
+ * `latency-optimized` is labelled "Speed" (see `default-profile-catalog.ts`).
16
+ *
17
+ * `latency-optimized` also backs the live-voice front model, which is why it
18
+ * exists as a profile rather than a raw model pin on the call site: a pin
19
+ * resolves to a provider BYOK installs may hold no credential for.
20
+ */
10
21
  export const DEFAULT_PROFILE_KEYS = [
11
22
  "balanced",
12
23
  "quality-optimized",
13
24
  "cost-optimized",
25
+ "latency-optimized",
14
26
  ] as const;
15
27
  export type DefaultProfileKey = (typeof DEFAULT_PROFILE_KEYS)[number];
16
28
 
17
- /**
18
- * Code-owned profiles that exist only to be named by a call-site default —
19
- * never offered as a user-selectable model. They resolve through the same
20
- * intent × provider matrix as the defaults, but are deliberately excluded
21
- * from the workspace seed and from every profile listing, so the picker
22
- * still shows exactly Balanced / Speed / Quality.
23
- *
24
- * `latency-optimized` is the latency-class profile the live-voice front
25
- * model runs on: `cost-optimized`'s upstream cannot meet the turn-taking
26
- * latency envelope, and the alternative — a raw model pin on the call site —
27
- * resolves to a provider BYOK installs may hold no credential for.
28
- */
29
- export const INTERNAL_PROFILE_KEYS = ["latency-optimized"] as const;
30
- export type InternalProfileKey = (typeof INTERNAL_PROFILE_KEYS)[number];
31
-
32
- /**
33
- * Every key implemented by the intent × provider matrix: the user-selectable
34
- * defaults plus the internal call-site-only profiles. This is the set the
35
- * resolver dereferences against; `DEFAULT_PROFILE_KEYS` alone is the
36
- * user-facing subset.
37
- */
38
- export const PROFILE_MATRIX_KEYS = [
39
- ...DEFAULT_PROFILE_KEYS,
40
- ...INTERNAL_PROFILE_KEYS,
41
- ] as const;
42
- export type ProfileMatrixKey = DefaultProfileKey | InternalProfileKey;
43
-
44
29
  /**
45
30
  * Flag-gated default profile: only available while the `os-beta` feature
46
31
  * flag has reconciled it into the workspace (see `sync-gated-profiles.ts`).
@@ -237,6 +237,7 @@ const KNOWN_VELLUM_VARS = new Set([
237
237
  "VELLUM_CPU_LIMIT",
238
238
  "VELLUM_MEMORY_LIMIT",
239
239
  "VELLUM_MINIKUBE_STORAGE_SIZE",
240
+ "VELLUM_ONNX_INTRA_OP_THREADS",
240
241
  ]);
241
242
 
242
243
  /**
@@ -341,6 +341,14 @@
341
341
  "description": "Persist chat secret redactions as sentinel markers instead of the legacy HTML marker, enriched with the vault identity when the value byte-matches a credential revealed this turn. Also gates the `credentials reveal --for-chat` channel and the live-stream swap that replaces an echoed reveal plaintext with its sentinel before emission. Redaction itself is NOT gated: a route-proven reveal plaintext stays out of persisted rows in both modes. Client chip rendering is gated on daemon version, not on this flag.",
342
342
  "defaultEnabled": false
343
343
  },
344
+ {
345
+ "id": "assistant-switcher",
346
+ "scope": "client",
347
+ "key": "assistant-switcher",
348
+ "label": "Assistant Switcher",
349
+ "description": "Show the Switch Assistant section in General settings (local mode only), linking to the assistant chooser.",
350
+ "defaultEnabled": false
351
+ },
344
352
  {
345
353
  "id": "marketing-pricing-takeover",
346
354
  "scope": "client",
@@ -348,6 +356,14 @@
348
356
  "label": "Marketing Pricing Takeover",
349
357
  "description": "Gates the marketing pricing takeover page (www.vellum.ai/pricing) and the `/assistant/checkout?package=<slug>` deep link its package CTAs target. One flag covers both halves of the funnel so they can never be enabled independently and strand a CTA. When off, the checkout deep link redirects to the in-app plans takeover.",
350
358
  "defaultEnabled": false
359
+ },
360
+ {
361
+ "id": "assistant-reply-push",
362
+ "scope": "assistant",
363
+ "key": "assistant-reply-push",
364
+ "label": "Assistant Reply Push",
365
+ "description": "Gates the `chat.assistant_reply` push notification the assistant sends at the end of a user-initiated turn whose reply the user has not seen. On by default; turn it off to silence the push without disabling the rest of the notification pipeline.",
366
+ "defaultEnabled": true
351
367
  }
352
368
  ]
353
369
  }
@@ -11,7 +11,7 @@ import {
11
11
  } from "../providers/vellum-model-routing.js";
12
12
  import { CALL_SITE_DEFAULTS } from "./call-site-defaults.js";
13
13
  import {
14
- isMatrixProfileKey,
14
+ isDefaultProfileKey,
15
15
  resolveDefaultProfileForProvider,
16
16
  } from "./default-profile-catalog.js";
17
17
  import {
@@ -114,12 +114,29 @@ export interface ResolveCallSiteOpts {
114
114
 
115
115
  export type ResolutionFallbackReason = "missing" | "disabled" | "incomplete";
116
116
 
117
+ export interface ResolvedCallSiteConfig {
118
+ config: z.infer<typeof LLMConfigBase>;
119
+ profileName?: string;
120
+ }
121
+
122
+ export function resolveCallSiteConfigWithProfile(
123
+ callSite: LLMCallSite,
124
+ llm: z.infer<typeof LLMSchema>,
125
+ opts: ResolveCallSiteOpts = {},
126
+ ): ResolvedCallSiteConfig {
127
+ const selection = selectWinningProfile(callSite, llm, opts);
128
+ return {
129
+ config: resolveOverrideOrDefault(callSite, llm, selection),
130
+ ...(selection.profileName ? { profileName: selection.profileName } : {}),
131
+ };
132
+ }
133
+
117
134
  export function resolveCallSiteConfig(
118
135
  callSite: LLMCallSite,
119
136
  llm: z.infer<typeof LLMSchema>,
120
137
  opts: ResolveCallSiteOpts = {},
121
138
  ): z.infer<typeof LLMConfigBase> {
122
- return resolveOverrideOrDefault(callSite, llm, opts);
139
+ return resolveCallSiteConfigWithProfile(callSite, llm, opts).config;
123
140
  }
124
141
 
125
142
  // ─── Single-winner profile resolution ───────────────────────────────────────
@@ -234,7 +251,7 @@ function providerAwareEntry(
234
251
  name,
235
252
  defaultProvider,
236
253
  );
237
- if (!isMatrixProfileKey(name) || entry?.mix != null) {
254
+ if (!isDefaultProfileKey(name) || entry?.mix != null) {
238
255
  return entry;
239
256
  }
240
257
  if (
@@ -358,9 +375,8 @@ const CODE_DEFAULT_BASE: z.infer<typeof LLMConfigBase> = LLMConfigBase.parse(
358
375
  function resolveOverrideOrDefault(
359
376
  callSite: LLMCallSite,
360
377
  llm: z.infer<typeof LLMSchema>,
361
- opts: ResolveCallSiteOpts,
378
+ selection: ProfileWinnerSelection,
362
379
  ): z.infer<typeof LLMConfigBase> {
363
- const selection = selectWinningProfile(callSite, llm, opts);
364
380
  const winnerFragment: Mergeable =
365
381
  selection.entry == null ? {} : winnerConfigFragment(selection.entry);
366
382
 
@@ -1119,17 +1119,24 @@ export function loadConfig(): AssistantConfig {
1119
1119
  // Drop the deployment-context embedding provider so it is never
1120
1120
  // persisted (see above); the schema default re-applies in memory.
1121
1121
  delete (seed.memory.embeddings as { provider?: unknown }).provider;
1122
- // Memory-v3 tuning knobs are globally-shipped defaults, not per-assistant
1123
- // config: persist only `live` (genuine per-assistant state — some
1124
- // workspaces predate the v3 migration and must not be flipped on) and let
1125
- // every tuning knob resolve from the schema on load. This way a shipped
1126
- // schema-default change reaches all assistants (mirrors the
1127
- // embedding-provider strip above); migration
1128
- // 119-strip-persisted-memory-v3-tuning-defaults handles already-seeded
1129
- // configs.
1130
- seed.memory.v3 = {
1131
- live: seed.memory.v3.live,
1132
- } as (typeof seed.memory)["v3"];
1122
+ // Nothing under memory.v3 is persisted at seed time.
1123
+ //
1124
+ // The tuning knobs are globally-shipped defaults: freezing them to
1125
+ // disk would stop a shipped default change from reaching this
1126
+ // assistant (mirrors the embedding-provider strip above; migration 119
1127
+ // strips them from configs that already carry them).
1128
+ //
1129
+ // `live` stays absent because an absent leaf is the only way to
1130
+ // express "no decision recorded". This seed runs before workspace
1131
+ // migrations, and migration 105 defers to any value already present
1132
+ // (`if ("live" in v3Config) return`), unable to tell a deliberate
1133
+ // choice from a schema default. Writing the leaf here would pin a
1134
+ // brand-new workspace to the schema default; leaving it absent lets
1135
+ // 105 decide regardless of boot ordering. The schema default still
1136
+ // applies in memory on every load, and a hatch-time
1137
+ // `memory.v3.live=false` override still wins by merging after
1138
+ // migrations.
1139
+ seed.memory.v3 = {} as (typeof seed.memory)["v3"];
1133
1140
  // Strip dataDir (runtime-derived) from the persisted config
1134
1141
  const { dataDir: _, ...persistable } = seed;
1135
1142
  writeFileSync(configPath, JSON.stringify(persistable, null, 2) + "\n");
@@ -10,7 +10,6 @@ import {
10
10
  import {
11
11
  DEFAULT_PROFILE_KEYS,
12
12
  DEFAULT_PROFILE_PROVIDERS,
13
- INTERNAL_PROFILE_KEYS,
14
13
  } from "../default-profile-names.js";
15
14
 
16
15
  /**
@@ -714,18 +713,11 @@ export const LLMSchema = z
714
713
  ...Object.keys(config.profiles ?? {}),
715
714
  ...DEFAULT_PROFILE_KEYS,
716
715
  ]);
717
- // Internal profiles exist only to be named by a call site, so they are
718
- // valid reference targets there and nowhere else — never for
719
- // `activeProfile`/`advisorProfile`, which are user-facing selections.
720
- const callSiteProfileNames = new Set([
721
- ...profileNames,
722
- ...INTERNAL_PROFILE_KEYS,
723
- ]);
724
716
  for (const [siteId, siteConfig] of Object.entries(config.callSites ?? {})) {
725
717
  if (siteConfig?.profile == null) {
726
718
  continue;
727
719
  }
728
- if (!callSiteProfileNames.has(siteConfig.profile)) {
720
+ if (!profileNames.has(siteConfig.profile)) {
729
721
  ctx.addIssue({
730
722
  code: "custom",
731
723
  path: ["callSites", siteId, "profile"],
@@ -39,6 +39,15 @@ export const MemoryRetrospectiveConfigSchema = z
39
39
  "Minimum milliseconds between attempts (success or failure). Prevents tight retry loops across trigger types. Pre-compaction bypasses this gate.",
40
40
  ),
41
41
 
42
+ requireUserActivity: z
43
+ .boolean({
44
+ error: "memory.retrospective.requireUserActivity must be a boolean",
45
+ })
46
+ .default(true)
47
+ .describe(
48
+ "When true (default), a retrospective fires only when the unprocessed tail contains at least one user message carrying non-tool_result content. Tool results ride on user-role rows, so bare tool-result carriers do not count as user activity. Assistant-only stretches (proactive sends, broadcast recaps) are skipped with the cursor left in place, so the first retrospective after real user activity reviews the whole deferred stretch. Set false to run retrospectives over assistant-only activity as well.",
49
+ ),
50
+
42
51
  sweepIntervalMs: z
43
52
  .number({
44
53
  error: "memory.retrospective.sweepIntervalMs must be a number",
@@ -6,8 +6,9 @@ import { z } from "zod";
6
6
  * The resource monitor runs as a separate OS process (a child of the assistant)
7
7
  * that samples the container's own cgroup memory + workspace disk off the main
8
8
  * event loop, so it keeps recording during a main-thread freeze and its samples
9
- * survive an OOM SIGKILL. The assistant spawns it at every startup — it is
10
- * platform infrastructure, not an opt-in feature, and there is deliberately no
9
+ * survive an OOM SIGKILL. On a slower timer it also compares each process's
10
+ * open file-descriptor count against its limit. The assistant spawns it at every
11
+ * startup: it is platform infrastructure, not an opt-in feature, and there is no
11
12
  * config switch to turn it off. `assistant monitoring start`/`stop` control the
12
13
  * process at runtime only; a stopped monitor respawns on the next boot.
13
14
  */
@@ -49,6 +50,31 @@ export const MonitoringConfigSchema = z
49
50
  .describe(
50
51
  "Minimum interval between high-memory snapshots, in milliseconds, so a sustained spike does not write a snapshot on every sample.",
51
52
  ),
53
+ fdPollIntervalMs: z
54
+ .number({ error: "monitoring.fdPollIntervalMs must be a number" })
55
+ .int("monitoring.fdPollIntervalMs must be an integer")
56
+ .min(1_000, "monitoring.fdPollIntervalMs must be at least 1000ms")
57
+ .max(1_800_000, "monitoring.fdPollIntervalMs must be <= 1800000ms")
58
+ .default(300_000)
59
+ .describe(
60
+ "How often the monitor walks the container's processes to compare their open file-descriptor counts against their limits, in milliseconds. Far slower than the memory sampler (default 5 minutes) because the walk costs a readdir per process and descriptor counts climb over minutes, not milliseconds.",
61
+ ),
62
+ highFdThresholdRatio: z
63
+ .number({ error: "monitoring.highFdThresholdRatio must be a number" })
64
+ .min(0.1, "monitoring.highFdThresholdRatio must be >= 0.1")
65
+ .max(1, "monitoring.highFdThresholdRatio must be <= 1")
66
+ .default(0.8)
67
+ .describe(
68
+ "Fraction of a process's open-file soft limit (RLIMIT_NOFILE) at which the monitor logs a warning identifying the PIDs involved. Default 0.8, leaving headroom before open() starts failing with EMFILE.",
69
+ ),
70
+ fdWarnCooldownMs: z
71
+ .number({ error: "monitoring.fdWarnCooldownMs must be a number" })
72
+ .int("monitoring.fdWarnCooldownMs must be an integer")
73
+ .min(0, "monitoring.fdWarnCooldownMs must be non-negative")
74
+ .default(60_000)
75
+ .describe(
76
+ "Minimum interval between file-descriptor warnings while usage stays over the threshold, in milliseconds. Crossing the threshold always warns immediately; this only throttles the repeats.",
77
+ ),
52
78
  pluginSourceScanIntervalMs: z
53
79
  .number({
54
80
  error: "monitoring.pluginSourceScanIntervalMs must be a number",
@@ -34,6 +34,21 @@ export const WorkspaceGitConfigSchema = z
34
34
  .describe(
35
35
  "Files larger than this (bytes) are excluded from workspace auto-commits",
36
36
  ),
37
+ historyCompaction: z
38
+ .object({
39
+ enabled: z
40
+ .boolean({
41
+ error: "workspaceGit.historyCompaction.enabled must be a boolean",
42
+ })
43
+ .default(true)
44
+ .describe(
45
+ "Whether background history compaction runs automatically. Manual compaction via the admin route is unaffected.",
46
+ ),
47
+ })
48
+ .default({ enabled: true })
49
+ .describe(
50
+ "Background compaction of workspace git history (squashes commits older than the retention window to reclaim oversized blobs from .git)",
51
+ ),
37
52
  interactiveGitTimeoutMs: z
38
53
  .number({
39
54
  error: "workspaceGit.interactiveGitTimeoutMs must be a number",