Skip to content

feat(macos): inference providers UI (Phase 2 PR-E)#30231

Merged
noanflaherty merged 5 commits into
mainfrom
credence/macos-inference-providers-ui
May 11, 2026
Merged

feat(macos): inference providers UI (Phase 2 PR-E)#30231
noanflaherty merged 5 commits into
mainfrom
credence/macos-inference-providers-ui

Conversation

@credence-the-bot
Copy link
Copy Markdown
Contributor

@credence-the-bot credence-the-bot Bot commented May 10, 2026

Goal

Replace the inference mode toggle (Managed / Your Own) and API Keys sheet in InferenceServiceCard with a new Providers sheet that lets users create, edit, and delete named provider connections (ProviderConnection rows backed by PR-B's HTTP routes).

Depends on:


Changes

New: clients/shared/Network/ProviderConnectionClient.swift

  • ProviderConnectionClientProtocol + ProviderConnectionClient covering all five CRUD routes
  • Typed ProviderConnectionDeleteResult enum (deleted / notFound / conflict(referencedBy:) / error)
  • Path components percent-encoded via the same addingPercentEncoding helper as SettingsClient

New: clients/macos/…/Features/Settings/ProvidersSheet.swift

  • Inline-editor sheet modelled after InferenceProfilesSheet:
    • Lazy-fetched connection list (local @State; no SettingsStore extension)
    • Per-row: name, provider chip, auth-type badge
    • Per-row Edit / Delete actions
    • "+ New Connection" → inline editor (name, provider dropdown, auth type, credential)
    • Empty state, 409 conflict sheet with referencedBy list, 404 refresh

Modified: InferenceServiceCard.swift

  • Delete mode toggle, API keys sheet, managed-login prompt, auth-related handlers
  • Remove authManager parameter
  • Replace ServiceModeCard → flat SettingsCard with provider picker + profile picker
  • Replace "API Keys" button → "Providers" button → ProvidersSheet
  • hasChanges / performSaveCore are now provider-change-only

Modified: SettingsStore.swift

  • Delete @Published var inferenceMode and loadServiceModes block that read it
  • Delete func setInferenceMode(_:)
  • Inject providerConnectionClient through init (default ProviderConnectionClient())

Collateral

  • WebSearchServiceCard: drop store.inferenceMode guards and handler
  • CallSiteOverridesSheet + InferenceProfileEditor: always use store.dynamicProviderIds
  • SettingsPanel: remove authManager: arg from InferenceServiceCard call site

Test Plan

File Status Coverage
Network/ProviderConnectionClientTests.swift ✅ new Mock spy; list/get/create/update/delete with all result variants
Features/Settings/ProvidersSheetTests.swift ✅ new Construction, create args, 409 conflict, 404 refresh signal
Features/Settings/InferenceServiceCardTests.swift ✅ updated Remove authManager/regression guard; add Providers sheet construction test
SettingsStoreInferenceTests.swift ✅ updated Drop mode fixture entries; remove setInferenceMode test
SettingsStoreManagedInferenceSelectionTests.swift ✅ updated Remove inferenceMode assignments

Out of Scope

  • APIKeyManager + APIKeysSheet deletion (still used from other cards)
  • ImageGenerationServiceCard mode toggle cleanup
  • OAuth service mode handlers
  • Daemon-side services.inference.mode synthesis removal (follow-up after soak)

Open in Devin Review

Credence and others added 4 commits May 10, 2026 23:49
Add ProviderConnectionClient + ProviderConnectionClientProtocol in the
shared Network package. Covers all five CRUD routes from PR-B (#30228):
  GET    /v1/inference/provider-connections (+ ?provider= filter)
  GET    /v1/inference/provider-connections/:name
  POST   /v1/inference/provider-connections
  PATCH  /v1/inference/provider-connections/:name
  DELETE /v1/inference/provider-connections/:name

Delete returns a typed ProviderConnectionDeleteResult that surfaces the
409 conflict case (referencedBy list), 404, and generic error separately
so callers can render appropriate UX without status-code inspection.

Modelled after ConversationInferenceProfileClient (same error-handling
style) and SettingsClient (same addingPercentEncoding path-escape helper).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add ProvidersSheet (models after InferenceProfilesSheet) with:
- Lazily-fetched connection list (local @State cache, no SettingsStore extension)
- Per-row: name, provider chip (VBadge), auth-type badge (API Key / Platform / …)
- Per-row Edit and Delete actions
- Inline editor for create / edit with:
    - name text field (create only: provider dropdown from store.providerCatalog)
    - auth-type dropdown (api_key / platform)
    - credential reference field (api_key only)
    - "Managed by Vellum" info row (platform)
- Empty state when no connections exist
- Error surface: actionError inline text in footer / editor
- 409 conflict sheet with referencedBy list (no re-target affordance in this PR)
- 404 on edit/delete triggers refresh + inline error

ProviderConnectionClientProtocol is injected via init (default:
ProviderConnectionClient()) so tests can pass a mock.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… + collateral

**InferenceServiceCard**
- Delete draftMode, showAPIKeysSheet, providerKeyStatuses, apiKeysRefreshToken state
- Delete wouldInvalidateWebSearch (entire property — web-search half lives in WebSearchServiceCard)
- Delete apiKeysSection, apiKeysEmptyState, loadProviderKeyStatuses, managedLoginPrompt helpers
- Delete all onChange(of: store.inferenceMode), onChange(of: authManager.isAuthenticated/isLoading), onChange(of: draftMode) handlers
- Delete authManager parameter (no managed-mode login prompt remains)
- Replace ServiceModeCard with flat SettingsCard + providerPicker + activeProfilePicker
- Replace "API Keys" button in secondaryActionsRow with new "Providers" button → showProvidersSheet
- Add .sheet(isPresented: $showProvidersSheet) { ProvidersSheet(store:isPresented:) }
- hasChanges and performSaveCore now provider-change-only (no mode PATCH path)

**SettingsStore**
- Delete @published var inferenceMode and the loadServiceModes block that read it
- Delete func setInferenceMode(_:) — services.inference.mode synthesis removed in next PR
- Inject ProviderConnectionClient: add providerConnectionClient parameter to init (default
  ProviderConnectionClient()), store as private let for PR-D follow-up wiring

**WebSearchServiceCard** (collateral — inferenceMode reads would no longer compile)
- Drop store.inferenceMode == "your-own" guard from hasChanges and managedContent body
- Delete onChange(of: store.inferenceMode) handler (auto-correct to managed now unconditional)
- Delete dead managedUnavailableMessage helper

**CallSiteOverridesSheet**
- providerIds computed property now always returns store.dynamicProviderIds (drop managed-only branch)

**InferenceProfileEditor**
- availableProviderIds computed property now always returns store.dynamicProviderIds (drop managed-only branch)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…tests

**New: ProviderConnectionClientTests.swift** (Network/)
Create MockProviderConnectionClient (spy pattern, mirrors MockSettingsClient).
Cover list (empty, populated, ?provider= filter), get (success, 404),
create (success, nil→409/400), update (success, nil→404), delete (deleted,
notFound, conflict with referencedBy).

**New: ProvidersSheetTests.swift** (Features/Settings/)
Cover: sheet constructs for empty list, populated list, nil response;
create happy-path call args; delete 409 conflict surfaces referencedBy;
404 on edit returns nil and triggers refresh; default-client init builds.

**Updated: InferenceServiceCardTests.swift**
- Remove authManager parameter from setUp + makeCard (no longer on the card)
- Remove regression guard test for apiKeysRefreshToken (state deleted)
- Add testProvidersSheetIsConstructible asserting Providers sheet builds

**Updated: SettingsStoreInferenceTests.swift**
- Drop "mode": "your-own"/"managed" fixture entries from inline config dicts
- Remove testSetInferenceModeStillWritesToServicesInference (setInferenceMode deleted)
- Remove store.inferenceMode assertions from read-path tests

**Updated: SettingsStoreManagedInferenceSelectionTests.swift**
- Remove testStore.inferenceMode = "managed" assignments from managed-provider
  persistence and web-search tests (inferenceMode property no longer exists)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@credence-the-bot
Copy link
Copy Markdown
Contributor Author

@codex review
@devin review

Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 4 potential issues.

View 2 additional findings in Devin Review.

Open in Devin Review

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚩 APIKeysSheet is now dead code after InferenceServiceCard refactor

APIKeysSheet at clients/macos/vellum-assistant/Features/Settings/APIKeysSheet.swift was previously presented from InferenceServiceCard via the showAPIKeysSheet state variable. This PR removed all references to it from InferenceServiceCard (replaced by the new ProvidersSheet). A grep shows APIKeysSheet is only defined but never instantiated anywhere in the app. Per AGENTS.md Dead Code Removal rules, this file should be removed if no other surface uses it.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

SettingsDivider()
ScrollView {
VStack(alignment: .leading, spacing: VSpacing.md) {
editorNameField
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Name field editable in edit mode but changes are silently discarded

In ProvidersSheet, the editorNameField is displayed in both create and edit modes (ProvidersSheet.swift:226). When editing, the user can modify the connection name and validation passes (ProvidersSheet.swift:421-424), but commitEditor() sends originalName (from EditorState.edit(name:)) to updateProviderConnection, not the edited draft name (ProvidersSheet.swift:448-450). The updateProviderConnection API body only contains auth — it never sends a new name (ProviderConnectionClient.swift:101). The result: the user edits the name, clicks Save, the save succeeds, but the name change is silently dropped and the list shows the old name.

Prompt for agents
In ProvidersSheet.swift, the editorNameField (line 226) is shown in both create and edit modes. However, the updateProviderConnection API (ProviderConnectionClient.swift:99-116) only sends auth in the PATCH body — it does not support renaming. When the user edits the name and saves, commitEditor() (line 447-460) uses originalName for the API call, silently discarding any name change.

The simplest fix is to make the name field read-only (disabled) when editorState is .edit. In the editorInline view builder (around line 226), conditionally disable the name field:

  editorNameField
      .disabled(editorState != .create)  // or wrap with an if/else showing a read-only Text instead

Alternatively, if rename support is desired, the updateProviderConnection protocol and client would need to accept an optional newName parameter and include it in the PATCH body.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

private let channelClient: ChannelClientProtocol
private let integrationClient: IntegrationClientProtocol
private let settingsClient: SettingsClientProtocol
private let providerConnectionClient: ProviderConnectionClientProtocol
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Unused providerConnectionClient added to SettingsStore violates Dead Code Removal rule

The PR adds providerConnectionClient as a stored property (SettingsStore.swift:400), init parameter (SettingsStore.swift:471), and assignment (SettingsStore.swift:483), but it is never called anywhere in SettingsStore. The ProvidersSheet creates its own client via its init default parameter (ProvidersSheet.swift:49), and InferenceServiceCard instantiates ProvidersSheet without passing the store's client (InferenceServiceCard.swift:69). This violates the AGENTS.md Dead Code Removal rule: "Proactively remove unused code during every change. Remove code your change makes unused, clean up adjacent dead code."

Prompt for agents
SettingsStore.swift has a new private let providerConnectionClient (line 400), init parameter (line 471), and assignment (line 483) that are never used. ProvidersSheet creates its own ProviderConnectionClient via its init default. Either remove the property/parameter/assignment from SettingsStore entirely, or wire it through to ProvidersSheet by passing store.providerConnectionClient when constructing the sheet in InferenceServiceCard.swift:69.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines 94 to 98
managedContent: {
if store.inferenceMode == "your-own" {
managedUnavailableMessage
} else if isLoggedIn {
if isLoggedIn {
VStack(alignment: .leading, spacing: VSpacing.md) {
managedIncludedMessage
if hasChanges {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚩 WebSearchServiceCard managed mode no longer checks inference mode dependency

Previously, WebSearchServiceCard blocked managed web search when inference was in your-own mode (showing "Managed web search requires managed inference"). With the removal of store.inferenceMode, this guard is gone — users can now select managed web search regardless of their inference setup. This is intentional given the PR's scope, but worth noting that if the server-side still enforces this dependency (managed web search requiring managed inference routing), users could save a configuration that the daemon cannot honor. The daemon would need to handle this gracefully.

(Refers to lines 94-104)

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: cc701c1402

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +447 to +450
case .edit(let originalName):
guard let updated = await client.updateProviderConnection(
name: originalName,
auth: auth
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Honor edited connection name or make field read-only

The edit form allows users to modify the Name field, but the save path always patches originalName and never uses the edited name. That means rename attempts are silently ignored, so users can click Save and believe the rename applied when it did not. Either persist renames explicitly (for example via rename/create+delete flow) or make the name non-editable in edit mode.

Useful? React with 👍 / 👎.

Comment on lines +141 to +144
private func parseConflictRefs(from data: Data) -> [String] {
guard let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
let refs = json["referencedBy"] as? [String] else {
return []
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Parse conflict refs from the API error envelope

The 409 parser only looks for a top-level referencedBy, but runtime /v1/* errors are returned in the standard envelope shape (error.message / optional error.details). This drops structured conflict references when they are sent in error.details, so the conflict sheet can show empty/incorrect dependency context and make deletion remediation harder.

Useful? React with 👍 / 👎.

- Make name field read-only in edit mode since the API doesn't support
  renaming (flagged by both Devin and Codex)
- Remove unused providerConnectionClient from SettingsStore
- Fix 409 conflict parser to handle standard error envelope format

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@noanflaherty noanflaherty merged commit 79a3d16 into main May 11, 2026
7 checks passed
@noanflaherty noanflaherty deleted the credence/macos-inference-providers-ui branch May 11, 2026 01:11
siddseethepalli added a commit that referenced this pull request May 11, 2026
…sert (#30237)

PR #30231 removed `SettingsStore.inferenceMode` and the managed-only
branch of `InferenceProfileEditor.availableProviderIds`, but two
tests in `InferenceProfileEditorTests.swift` still referenced the
deleted field. Delete them.

`ProviderConnectionClientTests.testListReturnsEmptyWhenClientReturnsEmpty`
called `XCTAssertEqual(result, [])`, but `ProviderConnection` is a
generated Codable/Sendable struct without an `Equatable` conformance.
Switch to `XCTAssertEqual(result?.count, 0)` to match the style used
by the surrounding tests.

Co-authored-by: Vellum Assistant <assistant@vellum.ai>
dvargasfuertes pushed a commit that referenced this pull request May 12, 2026
…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>
dvargasfuertes pushed a commit that referenced this pull request May 12, 2026
* 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>
noanflaherty added a commit that referenced this pull request May 13, 2026
* 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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant