fix(workbench): hide disabled harnesses from new tasks - #460
Conversation
There was a problem hiding this comment.
Important
The filtering itself is correct and well-tested, but "config still loading" and "every harness disabled" collapse into the same silent state: the composer's harness/model selector unmounts entirely and Enter stops working, with nothing on screen to explain either.
Reviewed changes — full diff of 96f69f2 (7 files, 1 commit), plus the surrounding composer/settings/daemon code the change depends on.
- Enablement filter —
selectableHarnessKinds(providers)filtersAgentKindSchema.optionsbyproviders[kind]?.enabled ?? true. This matchesProviderConfigSchema.enabled's documented intent ("whether the agent is offered in the client's agent picker"), a flag the new-thread page simply never read before. - Harness becomes derived, not stored —
NewSessionSurfacesplits state intopreferredHarnessplus an effectiveharnessthat falls back toavailableHarnesses.at(0)when the remembered one is disabled, so the surface'sharnessis nowAgentKind | undefinedand ~15 call sites gain guards. - Prop plumbed as required —
ShellFrameProps.selectableHarnessesis non-optional, so desktop-shell and webview (which spreads{...props}) are both covered by typecheck; no call site was missed. - Send gating widened —
sendBlockednow also fires when no harness resolves, andsubmit()throws rather than postingkind: undefined. - Coverage — two new UI tests (fallback from a disabled remembered harness, blocked submit with none enabled) plus a unit test for the filter. Both UI tests genuinely fail with the bug present.
Two things I checked and cleared, so they don't need re-deriving: Object.keys(AGENT_LABELS) and AgentKindSchema.options are the same kinds in the same order, so swapping the static list for the schema-derived one changes neither picker order nor the at(0) default; and the rewritten effort ternary is behavior-preserving for every defined-harness case, including the explicit-null reset.
ℹ️ Mobile's new-thread sheet still offers every harness
apps/mobile/src/components/host/new-thread-sheet.tsx:83 builds its segmented picker straight from AgentKindSchema.options and never reads getProviderConfig, so a harness disabled in Settings → Agents stays on offer there. The daemon doesn't enforce enabled at session start either — it is a client-picker flag only — so mobile will happily start a thread on an agent the user switched off. Not a defect in this diff, but the issue title says "new tasks" rather than "desktop new tasks", so it's worth deciding explicitly whether mobile is in scope or gets a follow-up.
Technical details
# Mobile new-thread sheet ignores per-agent enablement
## Affected sites
- `apps/mobile/src/components/host/new-thread-sheet.tsx:49,83` — `useState<AgentKind>(AgentKindSchema.options[0])` and the `Picker` body map the full schema enum; no `getProviderConfig` read.
- `packages/foundation/schema/src/model/provider-config.ts:8-9` — `enabled` is documented as a client-picker flag, and no host adapter gates on it, so nothing downstream catches a disabled pick.
## Required outcome
- A decision (not necessarily code in this PR): either mobile applies the same filter, or the enablement flag is documented as desktop/web-only and a follow-up issue tracks mobile.
## Open questions for the human
- Does CODE-597 cover mobile, or is that a separate ticket?ℹ️ Nitpicks
selectableHarnessKindsis a pureProvidersConfig → AgentKind[]derivation, which is exactly whatsettings/providers/view.tsis for ("Pure view helpers … no hooks, unit-testable"). Its siblingwithEnabledwrites the very flag this reads,AGENT_KINDSthere is alreadyAgentKindSchema.options, and that module has its own__tests__/view.test.ts.model-options.tsis otherwise about models and the hook that builds them.surface/workbench.tsx:249repeats theuseData(getProviderConfig, {})call thatuseAccountModelOptionsalready makes internally. SWR dedupes so there's no extra request, but auseSelectableHarnesses()besideuseAccountModelOptionswould keep the "wait for daemon config, returnnulluntil then" rule in one place instead of two.
Claude Opus | 𝕏
PeronGH
left a comment
There was a problem hiding this comment.
Reviewed: filtering, fallback, and blocked-submission behavior are correct and well-scoped. Wired through all three shell render sites; empty/undefined harness handled safely in Composer and ModelSelectorMenu. Approving.

Summary
Fixes CODE-597.
Verification
pnpm check:cipnpm test— 2973 passed, 1 skippedChecklist
pnpm check:ciandpnpm testboth pass (pluscargo fmt/clippy/testfor Rust changes)WIRE_PROTOCOL_VERSIONis bumped