feat(providers): make managed-auth capability catalog-driven#30411
Conversation
| private var providerSupportsManagedAuth: Bool { | ||
| LLMProviderRegistry.provider(id: editorDraft.provider)? | ||
| .supportsManagedAuth == true | ||
| } |
There was a problem hiding this comment.
🚩 Dual sources of truth for managed-provider capability
The PR introduces a data-driven supportsManagedAuth field derived from MANAGED_PROVIDER_META in assistant/src/providers/managed-proxy/constants.ts, used by ProvidersSheet to filter the auth-type dropdown. However, SettingsStore at clients/macos/vellum-assistant/Features/Settings/SettingsStore.swift:1192 maintains a separate hardcoded managedCapableProviderIds: Set<String> = ["anthropic", "openai", "gemini"] used by isManagedCapable() and managedCapableProviders. Both currently agree on the set of managed providers, but they're separate sources of truth. If a new provider is added to MANAGED_PROVIDER_META with managed: true, the catalog's supportsManagedAuth would automatically update, but managedCapableProviderIds would not — leading to inconsistency where the auth-type dropdown shows Platform but other managed-provider logic (e.g., managed inference selection) doesn't recognize the new provider. This is pre-existing technical debt that this PR partially addresses but doesn't fully resolve.
Was this helpful? React with 👍 or 👎 to provide feedback.
PR #30408 (Jason / LUM-1517) shipped the UX fix on macOS: hide the Platform auth-type option for providers without a managed proxy route. It does so via `store.isManagedCapable(provider)` which until now was a hardcoded `Set<String>("anthropic", "openai", "gemini")` on SettingsStore — with a docstring that explicitly calls out 'Mirrors the `MANAGED_PROVIDER_META` table in the backend.' That's a drift hazard: adding a managed provider in the proxy routing table doesn't propagate to the UI gate. This PR closes the loop by making the catalog the source of truth. Changes ------- * assistant/src/providers/model-catalog.ts — add `supportsManagedAuth?: boolean` to ProviderCatalogEntry, derived from `MANAGED_PROVIDER_META[entry.id]?.managed === true` at PROVIDER_CATALOG build time. The two can no longer drift. * assistant/scripts/sync-llm-catalog.ts — project the field through to the client-facing JSON. Re-generated both copies (meta/ + clients/shared/Resources/). * assistant/src/__tests__/llm-catalog-parity.test.ts — parity assertion + a focused invariant test guarding against future hand-edits drifting from MANAGED_PROVIDER_META. * clients/shared/Utilities/LLMProviderRegistry.swift — decode the field on `LLMProviderEntry` (optional, defaults to nil for forward-compat with older catalog versions). * clients/macos/.../SettingsStore.swift — refactor `isManagedCapable(_:)` and `managedCapableProviders` to read the flag from `LLMProviderRegistry` instead of the hardcoded set. The wire-protocol `ProviderCatalogEntry` doesn't carry capability flags, so we read from the static registry to keep the answer stable across daemon `model_info` refreshes. Behavior parity --------------- The existing 7 isManagedCapable tests + 2 managedCapableProviders tests in SettingsStoreManagedInferenceSelectionTests all continue to pass — anthropic/openai/gemini → true, ollama/fireworks/openrouter → false, unknown → false. Same truth table, different source. Not in this PR -------------- * macOS auth-type dropdown filtering — already shipped in #30408. * Web modal parity — separate vellum-assistant-platform PR. * Rename "managed" → "platform" terminology — Noa-tracked follow-up after this lands. Test ---- * `bun test src/__tests__/llm-catalog-parity.test.ts` — 14/14 pass. * Daemon lint clean (`bun run lint`).
f141bdb to
408a3f3
Compare
|
📦 Assistant artifact for this PR is ready for download: Download assistant-pr-30411 To run this build locally: |
|
🖥️ macOS app artifact for this PR is ready for download: Download vellum-assistant-pr-30411.dmg To run this build locally: |
Follow-up to the catalog-driven auth refactor (#30411 in this same branch + #6566 on the web side). The codebase has been using two words for the same concept: - User-facing UI: 'Platform (managed by Vellum)' - Auth-type wire value: 'platform' (AuthType.platform on Swift, "platform" in TS connection JSON) - Internal symbols: MANAGED_PROVIDER_META, supportsManagedAuth, isManagedCapable, managedCapableProviders, managed-proxy/ directory This commit aligns the internal symbols with the user-facing 'platform' terminology so future readers don't have to mentally translate. Renames ------- Symbols (word-boundary-safe sed across .ts/.swift/.tsx/.json): MANAGED_PROVIDER_META -> PLATFORM_PROVIDER_META supportsManagedAuth -> supportsPlatformAuth isManagedCapable -> isPlatformCapable managedCapableProviders -> platformCapableProviders Files / paths: assistant/src/providers/managed-proxy/ -> .../platform-proxy/ assistant/src/__tests__/managed-proxy-context.test.ts -> .../platform-proxy-context.test.ts assistant/src/__tests__/provider-managed-proxy-integration.test.ts -> .../provider-platform-proxy-integration.test.ts assistant/src/__tests__/secret-routes-managed-proxy.test.ts -> .../secret-routes-platform-proxy.test.ts Import-path string substitutions inside source files: './managed-proxy/...' -> './platform-proxy/...' What is NOT renamed (out of scope) ---------------------------------- * User-facing copy 'Platform (managed by Vellum)' — that's the UX label disambiguating what 'Platform' means; renaming would degrade clarity for end users. * Runtime routing-source discriminator string 'managed-proxy' — used in ProviderRoutingSource union type and several runtime checks. Leaving for a separate follow-up if Noa wants it tightened. * MANAGED_USAGE_LIMIT error code — wire-format contract with the macOS/iOS clients; out-of-scope structural rename. * ManagedProxyCredentials / ManagedProxyContext type names — left for a separate follow-up. Test ---- * bunx tsc --noEmit on assistant/ — clean. * bun test platform-proxy-context, provider-platform-proxy-integration, secret-routes-platform-proxy, llm-catalog-parity, satellite-connection-routing, credential-security-invariants — 110 tests pass. * bun run scripts/sync-llm-catalog.ts --check — clean.
PR #30408 (Jason / LUM-1517) shipped the UX fix on macOS: hide the Platform auth-type option for providers without a managed proxy route. It does so via `store.isManagedCapable(provider)` which until now was a hardcoded `Set<String>("anthropic", "openai", "gemini")` on SettingsStore — with a docstring that explicitly calls out 'Mirrors the `MANAGED_PROVIDER_META` table in the backend.' That's a drift hazard: adding a managed provider in the proxy routing table doesn't propagate to the UI gate. This PR closes the loop by making the catalog the source of truth. Changes ------- * assistant/src/providers/model-catalog.ts — add `supportsManagedAuth?: boolean` to ProviderCatalogEntry, derived from `MANAGED_PROVIDER_META[entry.id]?.managed === true` at PROVIDER_CATALOG build time. The two can no longer drift. * assistant/scripts/sync-llm-catalog.ts — project the field through to the client-facing JSON. Re-generated both copies (meta/ + clients/shared/Resources/). * assistant/src/__tests__/llm-catalog-parity.test.ts — parity assertion + a focused invariant test guarding against future hand-edits drifting from MANAGED_PROVIDER_META. * clients/shared/Utilities/LLMProviderRegistry.swift — decode the field on `LLMProviderEntry` (optional, defaults to nil for forward-compat with older catalog versions). * clients/macos/.../SettingsStore.swift — refactor `isManagedCapable(_:)` and `managedCapableProviders` to read the flag from `LLMProviderRegistry` instead of the hardcoded set. The wire-protocol `ProviderCatalogEntry` doesn't carry capability flags, so we read from the static registry to keep the answer stable across daemon `model_info` refreshes. Behavior parity --------------- The existing 7 isManagedCapable tests + 2 managedCapableProviders tests in SettingsStoreManagedInferenceSelectionTests all continue to pass — anthropic/openai/gemini → true, ollama/fireworks/openrouter → false, unknown → false. Same truth table, different source. Not in this PR -------------- * macOS auth-type dropdown filtering — already shipped in #30408. * Web modal parity — separate vellum-assistant-platform PR. * Rename "managed" → "platform" terminology — Noa-tracked follow-up after this lands. Test ---- * `bun test src/__tests__/llm-catalog-parity.test.ts` — 14/14 pass. * Daemon lint clean (`bun run lint`). Co-authored-by: credence-the-bot <credence-the-bot@users.noreply.github.com>
* fix(macos): hide redundant default Provider picker on Language Model card (#30413) * fix(macos): hide redundant default Provider picker on Language Model card Profiles already carry the provider they dispatch against — surfacing a separate "default Provider" picker on the Language Model card lets users land in nonsensical states (e.g. Provider: Anthropic + Profile: Kimi K2.5 on Fireworks). v0.8.0 hid the picker behind the inference-mode toggle; when that toggle was dropped in #30231 the picker became unconditionally visible. Drop the picker, its draft/Save state, and the override-confirmation flow that fired on default-provider change. Profile selection (which auto-persists) is the only thing the card surfaces; provider connections and per-call-site overrides remain reachable via the secondary actions row. Reported by Marina against v0.8.1 (Desktop Cloud hosted). Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * ci: retrigger workflow dispatch --------- Co-authored-by: ApolloBot <apollo@vellum.ai> Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com> * fix(warm-pool): gate LLM-touching background jobs on first user message (#30427) Warm-pool images run background services between daemon start and the moment a real user hatches the image. Provider credentials are not registered yet, so any LLM call fails and the failure is recorded as a 'Background job failed: <name>' conversation row that the inheriting user sees in their sidebar. Marina reported this in #releases (msg 1778610954.270769): 'Background job failed: update-bulletin. exception: Conversation: default provider anthropic is not registered'. This PR adds a single chokepoint gate so warm-pool images stay idle until they belong to a user. Changes: * New helper `runtime/pre-first-message-gate.ts` with `hasReceivedUserMessage()`: indexed `LIMIT 1` lookup against the messages/conversations tables, scoped to role='user' in conversation_type='standard'. Caches `true` (monotonic). Fails conservatively (returns false) on query error so background work stays paused. * `runtime/background-job-runner.ts` (the central chokepoint for every background producer — heartbeat, filing, scheduler, memory v2 consolidation, watcher, update-bulletin, sequence, subagent): early return with `{ ok: true, conversationId: '', skipReason: 'pre_first_user_message' }` when the gate is closed. No bootstrap, no processMessage, no `activity.failed` notification. Adds opt-out flag `allowPreFirstUserMessage` for any future job that legitimately needs to run pre-onboarding (none today). * `heartbeat/heartbeat-service.ts`: belt-and-suspenders skip inside `runOnce()`. Records `skipHeartbeatRun(runId, 'pre_first_user_message')` so the heartbeat_runs table shows why warm-pool beats were dropped. Forced runs (manual operator action) bypass the gate. The early-heartbeat counter is preserved because skipped beats never reach `completeHeartbeatRun`. * `prompts/update-bulletin-job.ts`: service-level guard at the top of `runUpdateBulletinJobIfNeeded()`. Checkpoint left unchanged so the job retries on the next daemon start or UPDATES.md change once the user has interacted — this is exactly the bug Marina hit. * `heartbeat/heartbeat-run-store.ts`: `HeartbeatSkipReason` union extended with `pre_first_user_message`. Tests: * New `pre-first-message-gate.test.ts` (5 tests): no-message → false, message exists → true, true result cached, false result re-queries (so gate opens when user interacts), rawGet throw → false. * `background-job-runner.test.ts` (+2): gate closed skips bootstrap; `allowPreFirstUserMessage` bypasses. * `heartbeat-service.test.ts` (+2): scheduled run skips with pre_first_user_message reason; forced runs bypass the gate. * `update-bulletin-job.test.ts` (+1): gate closed leaves checkpoint unchanged. Why gate inside `runBackgroundJob` and the heartbeat service? Defense-in-depth. The runner gate catches future producers automatically; the heartbeat service gate avoids the unnecessary heartbeat_runs CAS dance for skipped beats and keeps the early-heartbeat counter clean. Update-bulletin's checkpoint semantics similarly want service-level handling so a successful checkpoint write doesn't poison the retry path. Co-authored-by: ApolloBot <apollo@vellum.ai> * [LUM-1517] Widen VDropdown menu to match trigger width, auto-select auth type per provider (#30408) * [LUM-1517] Widen VDropdown menu to match trigger width, auto-select auth type per provider VDropdown menu now defaults to the trigger's rendered width instead of a fixed 180px, so labels like 'Platform (managed by Vellum)' are no longer truncated. Callers that pass an explicit menuWidth continue to work. In the New Connection form, selecting Ollama auto-sets auth to 'none' and hides other options. Fireworks and OpenRouter only show 'API Key' since they don't support managed keys. Anthropic, OpenAI, and Gemini show both 'API Key' and 'Platform'. Switching providers resets auth type when the current selection isn't valid for the new provider. Part of LUM-1517 Co-Authored-By: Jason Zhou <jasonczhou3@gmail.com> * Preserve current auth type in edit-mode dropdown options When editing an existing connection whose auth type is no longer offered for new connections (e.g. a non-ollama connection with 'none' auth), include it in the dropdown so the user sees the saved value instead of a confusing placeholder. Co-Authored-By: Jason Zhou <jasonczhou3@gmail.com> * Replace Cancel text with ghost close icon in editor header Uses VButton with iconOnly VIcon.x, matching the pattern in InferenceProfilesSheet and other panel headers. Co-Authored-By: Jason Zhou <jasonczhou3@gmail.com> --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * fix(macos): surface duplicate-name error on provider connection create (#30410) Marina QA flag (Slack 2026-05-12): creating a new provider connection with a duplicate name showed the generic "Couldn't create connection. Please try again." instead of telling the user the name is taken. Daemon returns a proper 409 ConflictError with a structured envelope, but the shared `createProviderConnection` returned `Optional<ProviderConnection>` so the status code (and the user-actionable reason) was being thrown away. This PR mirrors the existing `ProviderConnectionDeleteResult` pattern: * Introduce `ProviderConnectionCreateResult` with cases `.created/.duplicate/.invalid(message:)/.error`. * Switch on `response.statusCode` in `ProviderConnectionClient.create`: 200-299 → `.created`, 409 → `.duplicate`, 400 → `.invalid` (carrying the daemon's `error.message` from the standard envelope), otherwise `.error`. * Update `ProvidersSheet.commitEditor` to switch on the result and surface a precise message per branch — matches the web modal's existing wording (`A connection named "X" already exists.`) for cross-client parity. * Update the mock + happy-path / duplicate / invalid / error tests to the new enum surface. Web (`provider-editor-modal.tsx`) already handles 409 correctly; this is macOS-only catch-up. No daemon changes. Co-authored-by: credence-the-bot <credence-the-bot@users.noreply.github.com> * fix(providers): backfill missing label = name on existing connections (#30412) Migration 244 added the `label` column as nullable with no default, so every provider_connection that existed before 244 ran ended up with label = NULL. In 0.8.x the editor modal renders Display Name from `label` and shows the empty-state placeholder whenever it's null — users who created their connections pre-244 open Edit and see what looks like a wiped field, then conclude their data disappeared. Marina flagged this in QA against 0.8.1. The list view already falls back to `name` when label is empty (ProvidersSheet `connectionRow`), so this brings the editor into agreement with what's already on screen. New migration 246 backfills `label = name` once for any row where label is NULL or empty/whitespace, guarded by memory_checkpoints so a later user-cleared label isn't re-clobbered on subsequent boots. Belt-and-suspenders edge cases covered in tests: - provider_connections table absent (first-boot edge) → no-op, checkpoint set so we don't retry forever - label column absent (244 hasn't run yet) → no-op, NO checkpoint set so the backfill runs once the schema is provisioned - existing user-set labels never overwritten - second run after user clear → respects the clear Co-authored-by: credence-the-bot <credence-the-bot[bot]@users.noreply.github.com> * fix(profiles): backfill missing label on canonical managed profiles (#30419) Marina QA #5 (0.8.1 Desktop Cloud hosted): the profile picker on a freshly hatched assistant surfaces raw slugs — "balanced", "quality-optimized", "cost-optimized" — instead of the human labels "Balanced", "Quality", "Speed". Root cause: workspace migration 052 (`seed-default-inference-profiles`) seeds the canonical triplet with provider+model+maxTokens+effort+ thinking but no `label` field. The runtime profile seeder (`seedInferenceProfiles`) materializes labels on its second pass, but in platform mode (`IS_PLATFORM=true`) it defers to any existing on-disk entry to avoid clobbering platform-supplied overlay fragments. Net result: the labels never get written and `displayName: label ?? name` falls back to the slug. Fix: new workspace migration 082 backfills `label` on each of the three canonical names when (and only when) the key is absent on disk. Surgical — does not touch user-set strings or explicit nulls, does not touch non-canonical profile names, does not require relaxing the seeder's overlay-preserve invariant (which the existing "platform-provided profile fragments are not polluted by managed seeds" test guards). The seeder skip-path comment is updated to point at migration 082 so the next person to read it understands why it's safe to leave the on-platform defer in place. 10 new migration tests, all green. config-loader-backfill.test.ts (42 tests), workspace-migration-052 + managed-profile-guard (30 tests), config-loader-platform-defaults (19 tests), and migration-cross-version-compatibility (50 tests) all pass on the new branch. Co-authored-by: credence-the-bot[bot] <226614080+credence-the-bot[bot]@users.noreply.github.com> * feat(providers): make managed-auth capability catalog-driven (#30411) PR #30408 (Jason / LUM-1517) shipped the UX fix on macOS: hide the Platform auth-type option for providers without a managed proxy route. It does so via `store.isManagedCapable(provider)` which until now was a hardcoded `Set<String>("anthropic", "openai", "gemini")` on SettingsStore — with a docstring that explicitly calls out 'Mirrors the `MANAGED_PROVIDER_META` table in the backend.' That's a drift hazard: adding a managed provider in the proxy routing table doesn't propagate to the UI gate. This PR closes the loop by making the catalog the source of truth. Changes ------- * assistant/src/providers/model-catalog.ts — add `supportsManagedAuth?: boolean` to ProviderCatalogEntry, derived from `MANAGED_PROVIDER_META[entry.id]?.managed === true` at PROVIDER_CATALOG build time. The two can no longer drift. * assistant/scripts/sync-llm-catalog.ts — project the field through to the client-facing JSON. Re-generated both copies (meta/ + clients/shared/Resources/). * assistant/src/__tests__/llm-catalog-parity.test.ts — parity assertion + a focused invariant test guarding against future hand-edits drifting from MANAGED_PROVIDER_META. * clients/shared/Utilities/LLMProviderRegistry.swift — decode the field on `LLMProviderEntry` (optional, defaults to nil for forward-compat with older catalog versions). * clients/macos/.../SettingsStore.swift — refactor `isManagedCapable(_:)` and `managedCapableProviders` to read the flag from `LLMProviderRegistry` instead of the hardcoded set. The wire-protocol `ProviderCatalogEntry` doesn't carry capability flags, so we read from the static registry to keep the answer stable across daemon `model_info` refreshes. Behavior parity --------------- The existing 7 isManagedCapable tests + 2 managedCapableProviders tests in SettingsStoreManagedInferenceSelectionTests all continue to pass — anthropic/openai/gemini → true, ollama/fireworks/openrouter → false, unknown → false. Same truth table, different source. Not in this PR -------------- * macOS auth-type dropdown filtering — already shipped in #30408. * Web modal parity — separate vellum-assistant-platform PR. * Rename "managed" → "platform" terminology — Noa-tracked follow-up after this lands. Test ---- * `bun test src/__tests__/llm-catalog-parity.test.ts` — 14/14 pass. * Daemon lint clean (`bun run lint`). Co-authored-by: credence-the-bot <credence-the-bot@users.noreply.github.com> * ATL-539: Vercel credential injection hardening (#30401) * feat(security): escalate credentialed proxied bash to high risk classification (#30383) * Part of ATL-539: Restrict new Vercel token metadata to publish tools (#30381) * feat(security): restrict new Vercel token metadata to publish tools only * fix: explicitly clear injectionTemplates when storing Vercel metadata Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Part of ATL-539: Enforce credential policy before proxied bash sessions (#30384) * feat(security): enforce credential allowedTools policy before proxied bash sessions * fix: add credential metadata mocks to shell-tool-proxy-mode tests Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat(security): workspace migration to repair existing Vercel credential metadata (#30389) * Part of ATL-539: Remove Vercel credential proxy guidance from skills (#30388) * docs(security): remove Vercel credential proxy guidance from deployment skills * chore: regenerate skills catalog.json Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: reorder Vercel CLI install before login step Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * chore: regenerate skills catalog.json after step reorder Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: add explicit allowedDomains: [] to credential store examples Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * chore: regenerate skills catalog.json Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: backfill bash into allowedTools for credentials with injection templates (#30393) * fix: include bash in allowedTools for non-Vercel credential creation (#30396) * fix: add bash to allowedTools for all non-Vercel credential setup paths * chore: fix Prettier formatting and regenerate catalog * chore: regenerate catalog with correct updatedAt timestamp * fix: add bash to allowedTools for Slack credential setup (#30400) * chore: regenerate skills catalog.json from final feature branch state Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * chore: fix catalog.json updatedAt for stripe-app-setup Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * test(assistant): mock warm-pool gate open in pre-existing heartbeat/filing tests PR #30427 added the pre-first-message gate to runBackgroundJob and heartbeat-service.runOnce, then mocked hasReceivedUserMessage() to true in the test files it touched directly. The four pre-existing test files below also exercise those code paths but had no mock — their tests now short-circuit with 'pre_first_user_message' instead of running through the heartbeat / filing / disk-pressure logic they were written to check. Add the same mock.module("../runtime/pre-first-message-gate.js", ...) override at the top of each file so the gate defaults to OPEN for tests that predate it. Tests that specifically exercise the gate continue to live in pre-first-message-gate.test.ts and background-job-runner.test.ts. Verified locally: 79+19+3+4 = 105 tests previously red, now all green. Adjacent test files (scheduler, watcher, consolidation, workspace-*, commit-guarantee, heartbeat-routes) already pass without modification — they mock the surface higher up or don't reach runBackgroundJob. --------- Co-authored-by: vellum-apollo-bot[bot] <242025090+vellum-apollo-bot[bot]@users.noreply.github.com> Co-authored-by: ApolloBot <apollo@vellum.ai> Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com> Co-authored-by: Jason Zhou <jason@vellum.ai> Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: credence-the-bot[bot] <277301654+credence-the-bot[bot]@users.noreply.github.com> Co-authored-by: credence-the-bot <credence-the-bot@users.noreply.github.com> Co-authored-by: Noa Flaherty <noa@vellum.ai> Co-authored-by: credence-the-bot <credence-the-bot[bot]@users.noreply.github.com> Co-authored-by: credence-the-bot[bot] <226614080+credence-the-bot[bot]@users.noreply.github.com> Co-authored-by: Alex Nork <48630278+alex-nork@users.noreply.github.com>
* Release v0.8.1 * test(cdp-inspect): de-flake ws-transport event fan-out (#30397) (#30398) The 'fans out events (frames with no id) to listeners' test relied on a 5ms server-side setTimeout to push an unsolicited event after the client's open callback. On a busy CI runner the test body's addEventListener could lose the race against that timer — the event arrived, found no listeners, and was dropped, leaving received empty and the assertion failing. Push the event in response to a client trigger send instead, emitting the event frame BEFORE the response ack. WebSocket message ordering then guarantees the listener observes the event before the trigger promise resolves — fully deterministic, no sleeps. Co-authored-by: vellum-apollo-bot[bot] <242025090+vellum-apollo-bot[bot]@users.noreply.github.com> Co-authored-by: ApolloBot <apollo@vellum.ai> * Cherry-picks for release/v0.8.1 (#30415) * fix(macos): hide redundant default Provider picker on Language Model card (#30413) * fix(macos): hide redundant default Provider picker on Language Model card Profiles already carry the provider they dispatch against — surfacing a separate "default Provider" picker on the Language Model card lets users land in nonsensical states (e.g. Provider: Anthropic + Profile: Kimi K2.5 on Fireworks). v0.8.0 hid the picker behind the inference-mode toggle; when that toggle was dropped in #30231 the picker became unconditionally visible. Drop the picker, its draft/Save state, and the override-confirmation flow that fired on default-provider change. Profile selection (which auto-persists) is the only thing the card surfaces; provider connections and per-call-site overrides remain reachable via the secondary actions row. Reported by Marina against v0.8.1 (Desktop Cloud hosted). Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * ci: retrigger workflow dispatch --------- Co-authored-by: ApolloBot <apollo@vellum.ai> Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com> * fix(warm-pool): gate LLM-touching background jobs on first user message (#30427) Warm-pool images run background services between daemon start and the moment a real user hatches the image. Provider credentials are not registered yet, so any LLM call fails and the failure is recorded as a 'Background job failed: <name>' conversation row that the inheriting user sees in their sidebar. Marina reported this in #releases (msg 1778610954.270769): 'Background job failed: update-bulletin. exception: Conversation: default provider anthropic is not registered'. This PR adds a single chokepoint gate so warm-pool images stay idle until they belong to a user. Changes: * New helper `runtime/pre-first-message-gate.ts` with `hasReceivedUserMessage()`: indexed `LIMIT 1` lookup against the messages/conversations tables, scoped to role='user' in conversation_type='standard'. Caches `true` (monotonic). Fails conservatively (returns false) on query error so background work stays paused. * `runtime/background-job-runner.ts` (the central chokepoint for every background producer — heartbeat, filing, scheduler, memory v2 consolidation, watcher, update-bulletin, sequence, subagent): early return with `{ ok: true, conversationId: '', skipReason: 'pre_first_user_message' }` when the gate is closed. No bootstrap, no processMessage, no `activity.failed` notification. Adds opt-out flag `allowPreFirstUserMessage` for any future job that legitimately needs to run pre-onboarding (none today). * `heartbeat/heartbeat-service.ts`: belt-and-suspenders skip inside `runOnce()`. Records `skipHeartbeatRun(runId, 'pre_first_user_message')` so the heartbeat_runs table shows why warm-pool beats were dropped. Forced runs (manual operator action) bypass the gate. The early-heartbeat counter is preserved because skipped beats never reach `completeHeartbeatRun`. * `prompts/update-bulletin-job.ts`: service-level guard at the top of `runUpdateBulletinJobIfNeeded()`. Checkpoint left unchanged so the job retries on the next daemon start or UPDATES.md change once the user has interacted — this is exactly the bug Marina hit. * `heartbeat/heartbeat-run-store.ts`: `HeartbeatSkipReason` union extended with `pre_first_user_message`. Tests: * New `pre-first-message-gate.test.ts` (5 tests): no-message → false, message exists → true, true result cached, false result re-queries (so gate opens when user interacts), rawGet throw → false. * `background-job-runner.test.ts` (+2): gate closed skips bootstrap; `allowPreFirstUserMessage` bypasses. * `heartbeat-service.test.ts` (+2): scheduled run skips with pre_first_user_message reason; forced runs bypass the gate. * `update-bulletin-job.test.ts` (+1): gate closed leaves checkpoint unchanged. Why gate inside `runBackgroundJob` and the heartbeat service? Defense-in-depth. The runner gate catches future producers automatically; the heartbeat service gate avoids the unnecessary heartbeat_runs CAS dance for skipped beats and keeps the early-heartbeat counter clean. Update-bulletin's checkpoint semantics similarly want service-level handling so a successful checkpoint write doesn't poison the retry path. Co-authored-by: ApolloBot <apollo@vellum.ai> * [LUM-1517] Widen VDropdown menu to match trigger width, auto-select auth type per provider (#30408) * [LUM-1517] Widen VDropdown menu to match trigger width, auto-select auth type per provider VDropdown menu now defaults to the trigger's rendered width instead of a fixed 180px, so labels like 'Platform (managed by Vellum)' are no longer truncated. Callers that pass an explicit menuWidth continue to work. In the New Connection form, selecting Ollama auto-sets auth to 'none' and hides other options. Fireworks and OpenRouter only show 'API Key' since they don't support managed keys. Anthropic, OpenAI, and Gemini show both 'API Key' and 'Platform'. Switching providers resets auth type when the current selection isn't valid for the new provider. Part of LUM-1517 Co-Authored-By: Jason Zhou <jasonczhou3@gmail.com> * Preserve current auth type in edit-mode dropdown options When editing an existing connection whose auth type is no longer offered for new connections (e.g. a non-ollama connection with 'none' auth), include it in the dropdown so the user sees the saved value instead of a confusing placeholder. Co-Authored-By: Jason Zhou <jasonczhou3@gmail.com> * Replace Cancel text with ghost close icon in editor header Uses VButton with iconOnly VIcon.x, matching the pattern in InferenceProfilesSheet and other panel headers. Co-Authored-By: Jason Zhou <jasonczhou3@gmail.com> --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * fix(macos): surface duplicate-name error on provider connection create (#30410) Marina QA flag (Slack 2026-05-12): creating a new provider connection with a duplicate name showed the generic "Couldn't create connection. Please try again." instead of telling the user the name is taken. Daemon returns a proper 409 ConflictError with a structured envelope, but the shared `createProviderConnection` returned `Optional<ProviderConnection>` so the status code (and the user-actionable reason) was being thrown away. This PR mirrors the existing `ProviderConnectionDeleteResult` pattern: * Introduce `ProviderConnectionCreateResult` with cases `.created/.duplicate/.invalid(message:)/.error`. * Switch on `response.statusCode` in `ProviderConnectionClient.create`: 200-299 → `.created`, 409 → `.duplicate`, 400 → `.invalid` (carrying the daemon's `error.message` from the standard envelope), otherwise `.error`. * Update `ProvidersSheet.commitEditor` to switch on the result and surface a precise message per branch — matches the web modal's existing wording (`A connection named "X" already exists.`) for cross-client parity. * Update the mock + happy-path / duplicate / invalid / error tests to the new enum surface. Web (`provider-editor-modal.tsx`) already handles 409 correctly; this is macOS-only catch-up. No daemon changes. Co-authored-by: credence-the-bot <credence-the-bot@users.noreply.github.com> * fix(providers): backfill missing label = name on existing connections (#30412) Migration 244 added the `label` column as nullable with no default, so every provider_connection that existed before 244 ran ended up with label = NULL. In 0.8.x the editor modal renders Display Name from `label` and shows the empty-state placeholder whenever it's null — users who created their connections pre-244 open Edit and see what looks like a wiped field, then conclude their data disappeared. Marina flagged this in QA against 0.8.1. The list view already falls back to `name` when label is empty (ProvidersSheet `connectionRow`), so this brings the editor into agreement with what's already on screen. New migration 246 backfills `label = name` once for any row where label is NULL or empty/whitespace, guarded by memory_checkpoints so a later user-cleared label isn't re-clobbered on subsequent boots. Belt-and-suspenders edge cases covered in tests: - provider_connections table absent (first-boot edge) → no-op, checkpoint set so we don't retry forever - label column absent (244 hasn't run yet) → no-op, NO checkpoint set so the backfill runs once the schema is provisioned - existing user-set labels never overwritten - second run after user clear → respects the clear Co-authored-by: credence-the-bot <credence-the-bot[bot]@users.noreply.github.com> * fix(profiles): backfill missing label on canonical managed profiles (#30419) Marina QA #5 (0.8.1 Desktop Cloud hosted): the profile picker on a freshly hatched assistant surfaces raw slugs — "balanced", "quality-optimized", "cost-optimized" — instead of the human labels "Balanced", "Quality", "Speed". Root cause: workspace migration 052 (`seed-default-inference-profiles`) seeds the canonical triplet with provider+model+maxTokens+effort+ thinking but no `label` field. The runtime profile seeder (`seedInferenceProfiles`) materializes labels on its second pass, but in platform mode (`IS_PLATFORM=true`) it defers to any existing on-disk entry to avoid clobbering platform-supplied overlay fragments. Net result: the labels never get written and `displayName: label ?? name` falls back to the slug. Fix: new workspace migration 082 backfills `label` on each of the three canonical names when (and only when) the key is absent on disk. Surgical — does not touch user-set strings or explicit nulls, does not touch non-canonical profile names, does not require relaxing the seeder's overlay-preserve invariant (which the existing "platform-provided profile fragments are not polluted by managed seeds" test guards). The seeder skip-path comment is updated to point at migration 082 so the next person to read it understands why it's safe to leave the on-platform defer in place. 10 new migration tests, all green. config-loader-backfill.test.ts (42 tests), workspace-migration-052 + managed-profile-guard (30 tests), config-loader-platform-defaults (19 tests), and migration-cross-version-compatibility (50 tests) all pass on the new branch. Co-authored-by: credence-the-bot[bot] <226614080+credence-the-bot[bot]@users.noreply.github.com> * feat(providers): make managed-auth capability catalog-driven (#30411) PR #30408 (Jason / LUM-1517) shipped the UX fix on macOS: hide the Platform auth-type option for providers without a managed proxy route. It does so via `store.isManagedCapable(provider)` which until now was a hardcoded `Set<String>("anthropic", "openai", "gemini")` on SettingsStore — with a docstring that explicitly calls out 'Mirrors the `MANAGED_PROVIDER_META` table in the backend.' That's a drift hazard: adding a managed provider in the proxy routing table doesn't propagate to the UI gate. This PR closes the loop by making the catalog the source of truth. Changes ------- * assistant/src/providers/model-catalog.ts — add `supportsManagedAuth?: boolean` to ProviderCatalogEntry, derived from `MANAGED_PROVIDER_META[entry.id]?.managed === true` at PROVIDER_CATALOG build time. The two can no longer drift. * assistant/scripts/sync-llm-catalog.ts — project the field through to the client-facing JSON. Re-generated both copies (meta/ + clients/shared/Resources/). * assistant/src/__tests__/llm-catalog-parity.test.ts — parity assertion + a focused invariant test guarding against future hand-edits drifting from MANAGED_PROVIDER_META. * clients/shared/Utilities/LLMProviderRegistry.swift — decode the field on `LLMProviderEntry` (optional, defaults to nil for forward-compat with older catalog versions). * clients/macos/.../SettingsStore.swift — refactor `isManagedCapable(_:)` and `managedCapableProviders` to read the flag from `LLMProviderRegistry` instead of the hardcoded set. The wire-protocol `ProviderCatalogEntry` doesn't carry capability flags, so we read from the static registry to keep the answer stable across daemon `model_info` refreshes. Behavior parity --------------- The existing 7 isManagedCapable tests + 2 managedCapableProviders tests in SettingsStoreManagedInferenceSelectionTests all continue to pass — anthropic/openai/gemini → true, ollama/fireworks/openrouter → false, unknown → false. Same truth table, different source. Not in this PR -------------- * macOS auth-type dropdown filtering — already shipped in #30408. * Web modal parity — separate vellum-assistant-platform PR. * Rename "managed" → "platform" terminology — Noa-tracked follow-up after this lands. Test ---- * `bun test src/__tests__/llm-catalog-parity.test.ts` — 14/14 pass. * Daemon lint clean (`bun run lint`). Co-authored-by: credence-the-bot <credence-the-bot@users.noreply.github.com> * ATL-539: Vercel credential injection hardening (#30401) * feat(security): escalate credentialed proxied bash to high risk classification (#30383) * Part of ATL-539: Restrict new Vercel token metadata to publish tools (#30381) * feat(security): restrict new Vercel token metadata to publish tools only * fix: explicitly clear injectionTemplates when storing Vercel metadata Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Part of ATL-539: Enforce credential policy before proxied bash sessions (#30384) * feat(security): enforce credential allowedTools policy before proxied bash sessions * fix: add credential metadata mocks to shell-tool-proxy-mode tests Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat(security): workspace migration to repair existing Vercel credential metadata (#30389) * Part of ATL-539: Remove Vercel credential proxy guidance from skills (#30388) * docs(security): remove Vercel credential proxy guidance from deployment skills * chore: regenerate skills catalog.json Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: reorder Vercel CLI install before login step Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * chore: regenerate skills catalog.json after step reorder Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: add explicit allowedDomains: [] to credential store examples Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * chore: regenerate skills catalog.json Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: backfill bash into allowedTools for credentials with injection templates (#30393) * fix: include bash in allowedTools for non-Vercel credential creation (#30396) * fix: add bash to allowedTools for all non-Vercel credential setup paths * chore: fix Prettier formatting and regenerate catalog * chore: regenerate catalog with correct updatedAt timestamp * fix: add bash to allowedTools for Slack credential setup (#30400) * chore: regenerate skills catalog.json from final feature branch state Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * chore: fix catalog.json updatedAt for stripe-app-setup Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * test(assistant): mock warm-pool gate open in pre-existing heartbeat/filing tests PR #30427 added the pre-first-message gate to runBackgroundJob and heartbeat-service.runOnce, then mocked hasReceivedUserMessage() to true in the test files it touched directly. The four pre-existing test files below also exercise those code paths but had no mock — their tests now short-circuit with 'pre_first_user_message' instead of running through the heartbeat / filing / disk-pressure logic they were written to check. Add the same mock.module("../runtime/pre-first-message-gate.js", ...) override at the top of each file so the gate defaults to OPEN for tests that predate it. Tests that specifically exercise the gate continue to live in pre-first-message-gate.test.ts and background-job-runner.test.ts. Verified locally: 79+19+3+4 = 105 tests previously red, now all green. Adjacent test files (scheduler, watcher, consolidation, workspace-*, commit-guarantee, heartbeat-routes) already pass without modification — they mock the surface higher up or don't reach runBackgroundJob. --------- Co-authored-by: vellum-apollo-bot[bot] <242025090+vellum-apollo-bot[bot]@users.noreply.github.com> Co-authored-by: ApolloBot <apollo@vellum.ai> Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com> Co-authored-by: Jason Zhou <jason@vellum.ai> Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: credence-the-bot[bot] <277301654+credence-the-bot[bot]@users.noreply.github.com> Co-authored-by: credence-the-bot <credence-the-bot@users.noreply.github.com> Co-authored-by: Noa Flaherty <noa@vellum.ai> Co-authored-by: credence-the-bot <credence-the-bot[bot]@users.noreply.github.com> Co-authored-by: credence-the-bot[bot] <226614080+credence-the-bot[bot]@users.noreply.github.com> Co-authored-by: Alex Nork <48630278+alex-nork@users.noreply.github.com> --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: vellum-automation[bot] <192048195+vellum-automation[bot]@users.noreply.github.com> Co-authored-by: vellum-apollo-bot[bot] <242025090+vellum-apollo-bot[bot]@users.noreply.github.com> Co-authored-by: ApolloBot <apollo@vellum.ai> Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com> Co-authored-by: Jason Zhou <jason@vellum.ai> Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: credence-the-bot[bot] <277301654+credence-the-bot[bot]@users.noreply.github.com> Co-authored-by: credence-the-bot <credence-the-bot@users.noreply.github.com> Co-authored-by: Noa Flaherty <noa@vellum.ai> Co-authored-by: credence-the-bot <credence-the-bot[bot]@users.noreply.github.com> Co-authored-by: credence-the-bot[bot] <226614080+credence-the-bot[bot]@users.noreply.github.com> Co-authored-by: Alex Nork <48630278+alex-nork@users.noreply.github.com>
Follow-up to the catalog-driven auth refactor (#30411 in this same branch + #6566 on the web side). The codebase has been using two words for the same concept: - User-facing UI: 'Platform (managed by Vellum)' - Auth-type wire value: 'platform' (AuthType.platform on Swift, "platform" in TS connection JSON) - Internal symbols: MANAGED_PROVIDER_META, supportsManagedAuth, isManagedCapable, managedCapableProviders, managed-proxy/ directory This commit aligns the internal symbols with the user-facing 'platform' terminology so future readers don't have to mentally translate. Renames ------- Symbols (word-boundary-safe sed across .ts/.swift/.tsx/.json): MANAGED_PROVIDER_META -> PLATFORM_PROVIDER_META supportsManagedAuth -> supportsPlatformAuth isManagedCapable -> isPlatformCapable managedCapableProviders -> platformCapableProviders Files / paths: assistant/src/providers/managed-proxy/ -> .../platform-proxy/ assistant/src/__tests__/managed-proxy-context.test.ts -> .../platform-proxy-context.test.ts assistant/src/__tests__/provider-managed-proxy-integration.test.ts -> .../provider-platform-proxy-integration.test.ts assistant/src/__tests__/secret-routes-managed-proxy.test.ts -> .../secret-routes-platform-proxy.test.ts Import-path string substitutions inside source files: './managed-proxy/...' -> './platform-proxy/...' What is NOT renamed (out of scope) ---------------------------------- * User-facing copy 'Platform (managed by Vellum)' — that's the UX label disambiguating what 'Platform' means; renaming would degrade clarity for end users. * Runtime routing-source discriminator string 'managed-proxy' — used in ProviderRoutingSource union type and several runtime checks. Leaving for a separate follow-up if Noa wants it tightened. * MANAGED_USAGE_LIMIT error code — wire-format contract with the macOS/iOS clients; out-of-scope structural rename. * ManagedProxyCredentials / ManagedProxyContext type names — left for a separate follow-up. Test ---- * bunx tsc --noEmit on assistant/ — clean. * bun test platform-proxy-context, provider-platform-proxy-integration, secret-routes-platform-proxy, llm-catalog-parity, satellite-connection-routing, credential-security-invariants — 110 tests pass. * bun run scripts/sync-llm-catalog.ts --check — clean.
Follow-up to the catalog-driven auth refactor (#30411 in this same branch + #6566 on the web side). The codebase has been using two words for the same concept: - User-facing UI: 'Platform (managed by Vellum)' - Auth-type wire value: 'platform' (AuthType.platform on Swift, "platform" in TS connection JSON) - Internal symbols: MANAGED_PROVIDER_META, supportsManagedAuth, isManagedCapable, managedCapableProviders, managed-proxy/ directory This commit aligns the internal symbols with the user-facing 'platform' terminology so future readers don't have to mentally translate. Renames ------- Symbols (word-boundary-safe sed across .ts/.swift/.tsx/.json): MANAGED_PROVIDER_META -> PLATFORM_PROVIDER_META supportsManagedAuth -> supportsPlatformAuth isManagedCapable -> isPlatformCapable managedCapableProviders -> platformCapableProviders Files / paths: assistant/src/providers/managed-proxy/ -> .../platform-proxy/ assistant/src/__tests__/managed-proxy-context.test.ts -> .../platform-proxy-context.test.ts assistant/src/__tests__/provider-managed-proxy-integration.test.ts -> .../provider-platform-proxy-integration.test.ts assistant/src/__tests__/secret-routes-managed-proxy.test.ts -> .../secret-routes-platform-proxy.test.ts Import-path string substitutions inside source files: './managed-proxy/...' -> './platform-proxy/...' What is NOT renamed (out of scope) ---------------------------------- * User-facing copy 'Platform (managed by Vellum)' — that's the UX label disambiguating what 'Platform' means; renaming would degrade clarity for end users. * Runtime routing-source discriminator string 'managed-proxy' — used in ProviderRoutingSource union type and several runtime checks. Leaving for a separate follow-up if Noa wants it tightened. * MANAGED_USAGE_LIMIT error code — wire-format contract with the macOS/iOS clients; out-of-scope structural rename. * ManagedProxyCredentials / ManagedProxyContext type names — left for a separate follow-up. Test ---- * bunx tsc --noEmit on assistant/ — clean. * bun test platform-proxy-context, provider-platform-proxy-integration, secret-routes-platform-proxy, llm-catalog-parity, satellite-connection-routing, credential-security-invariants — 110 tests pass. * bun run scripts/sync-llm-catalog.ts --check — clean.
…#30432) * refactor(providers): rename managed→platform for catalog auth symbols Follow-up to the catalog-driven auth refactor (#30411 in this same branch + #6566 on the web side). The codebase has been using two words for the same concept: - User-facing UI: 'Platform (managed by Vellum)' - Auth-type wire value: 'platform' (AuthType.platform on Swift, "platform" in TS connection JSON) - Internal symbols: MANAGED_PROVIDER_META, supportsManagedAuth, isManagedCapable, managedCapableProviders, managed-proxy/ directory This commit aligns the internal symbols with the user-facing 'platform' terminology so future readers don't have to mentally translate. Renames ------- Symbols (word-boundary-safe sed across .ts/.swift/.tsx/.json): MANAGED_PROVIDER_META -> PLATFORM_PROVIDER_META supportsManagedAuth -> supportsPlatformAuth isManagedCapable -> isPlatformCapable managedCapableProviders -> platformCapableProviders Files / paths: assistant/src/providers/managed-proxy/ -> .../platform-proxy/ assistant/src/__tests__/managed-proxy-context.test.ts -> .../platform-proxy-context.test.ts assistant/src/__tests__/provider-managed-proxy-integration.test.ts -> .../provider-platform-proxy-integration.test.ts assistant/src/__tests__/secret-routes-managed-proxy.test.ts -> .../secret-routes-platform-proxy.test.ts Import-path string substitutions inside source files: './managed-proxy/...' -> './platform-proxy/...' What is NOT renamed (out of scope) ---------------------------------- * User-facing copy 'Platform (managed by Vellum)' — that's the UX label disambiguating what 'Platform' means; renaming would degrade clarity for end users. * Runtime routing-source discriminator string 'managed-proxy' — used in ProviderRoutingSource union type and several runtime checks. Leaving for a separate follow-up if Noa wants it tightened. * MANAGED_USAGE_LIMIT error code — wire-format contract with the macOS/iOS clients; out-of-scope structural rename. * ManagedProxyCredentials / ManagedProxyContext type names — left for a separate follow-up. Test ---- * bunx tsc --noEmit on assistant/ — clean. * bun test platform-proxy-context, provider-platform-proxy-integration, secret-routes-platform-proxy, llm-catalog-parity, satellite-connection-routing, credential-security-invariants — 110 tests pass. * bun run scripts/sync-llm-catalog.ts --check — clean. * style(providers): re-sort imports after managed→platform rename The rename in the parent commit shifted import path prefixes (`managed-proxy/*` → `platform-proxy/*`), which puts them later in the alphabetical order simple-import-sort enforces. * assistant/src/__tests__/llm-catalog-parity.test.ts: swap PROVIDER_CATALOG / PLATFORM_PROVIDER_META order so `model-catalog` (m) precedes `platform-proxy/constants` (p). * assistant/src/providers/registry.ts: move the `./platform-proxy/context.js` import block after `./model-intents.js` for the same reason. Test ---- * bunx tsc --noEmit clean. * No behavior change. * chore(openapi): regenerate spec to include /v1/skills/import The skills-import route handler shipped in #29915 but openapi.yaml was not regenerated as part of that merge. Rebasing #30432 onto main surfaces the staleness via the OpenAPI Spec Check job. Pure regen — no code change in this commit. --------- Co-authored-by: credence-the-bot[bot] <226841098+credence-the-bot[bot]@users.noreply.github.com>
Why
PR #30408 (Jason / LUM-1517, merged today) shipped the UX fix Marina flagged in 0.8.1: hide the Platform auth-type option for providers without a managed proxy route (Fireworks, OpenRouter, Ollama).
That PR's gate is
store.isManagedCapable(provider), which until now was a hardcodedSet<String>(["anthropic", "openai", "gemini"])onSettingsStore— with a docstring that explicitly calls out:That's a drift hazard. Adding a managed provider in the proxy routing table doesn't automatically propagate to the macOS UI gate; someone has to remember to hand-edit two files in sync.
This PR closes the loop by making the catalog the source of truth, so #30408's filter logic stays correct as the routing table evolves.
What
assistant/src/providers/model-catalog.ts— addsupportsManagedAuth?: booleantoProviderCatalogEntry, derived fromMANAGED_PROVIDER_META[entry.id]?.managed === trueatPROVIDER_CATALOGbuild time. The two can no longer drift by construction.assistant/scripts/sync-llm-catalog.ts— project the field into the client-facing JSON. Re-generated both copies (meta/+clients/shared/Resources/).assistant/src/__tests__/llm-catalog-parity.test.ts— JSON round-trip assertion + an invariant test guarding against future hand-edits drifting fromMANAGED_PROVIDER_META.clients/shared/Utilities/LLMProviderRegistry.swift— decode the field onLLMProviderEntry(optional, defaults tonilfor forward-compat with older catalog versions).clients/macos/.../SettingsStore.swift— refactorisManagedCapable(_:)andmanagedCapableProvidersto read the flag fromLLMProviderRegistryinstead of the hardcoded set. Reading from the static registry (notproviderCatalog) keeps the answer stable across daemonmodel_inforefreshes — the wire-protocolProviderCatalogEntrydoesn't carry capability flags.Behavior parity
The existing 7
isManagedCapabletests + 2managedCapableProviderstests inSettingsStoreManagedInferenceSelectionTestsall continue to pass:truefalsefalseSame truth table, different source.
What's NOT in this PR
["api_key", "platform", "none"]array exists inweb/src/app/(app)/assistant/settings/ai/provider-editor-modal.tsx).managed→platformterminology — Noa-tracked follow-up after this lands; the UI calls the auth type "Platform (managed by Vellum)" and uses a green "Platform" chip, so the catalog field name should follow. Mechanical rename acrossMANAGED_PROVIDER_META,supportsManagedAuth,isManagedCapable, etc.Test
bun test src/__tests__/llm-catalog-parity.test.ts→ 14/14 pass, including the new invariant.bun run lint→ clean.History
This PR was originally a fix for the underlying bug, but Jason's #30408 landed first with a different (UI-only) fix. Rescoped to the architectural cleanup the #30408 docstring asked for.