Skip to content

fix: version-gate Claude Code/Codex agents to BrowserOS 0.46.0.0#1066

Merged
Nikhil (shadowfax92) merged 2 commits into
mainfrom
fix/capabilties-gating
May 28, 2026
Merged

fix: version-gate Claude Code/Codex agents to BrowserOS 0.46.0.0#1066
Nikhil (shadowfax92) merged 2 commits into
mainfrom
fix/capabilties-gating

Conversation

@shadowfax92
Copy link
Copy Markdown
Contributor

Summary

  • The unified picker (feat: unify agents & BrowserOS AI (one picker, merged settings) #1065) surfaced the agent-harness adapters (Claude Code, Codex) on every BrowserOS version the auto-updating extension runs on — but the harness/ACP backend that runs them only ships in BrowserOS 0.46.0.0+. On older builds the agents appeared but could not run.
  • Adds Feature.AGENT_HARNESS_SUPPORT (minBrowserOSVersion: 0.46.0.0) and gates the two data hooks that feed every surface: useAgentAdapters (AI & Agents settings tabs) and useHarnessAgents (picker ACP targets).
  • When unsupported, both hooks disable their query and return empty — so the home + sidepanel pickers, the settings tabs, and the recent-agents dock all hide the agents, with no requests to harness endpoints that may not exist on older BrowserOS.

Design

The gate is applied at the single data-source choke point. useAgentAdapters and useHarnessAgents (apps/agent/entrypoints/app/agents/useAgents.ts) are the only readers of the harness /adapters and agents-list endpoints, and every UI surface reads through them. Each hook consults useCapabilities(); when AGENT_HARNESS_SUPPORT is unsupported it sets react-query enabled: false and returns [] (non-loading).

The picker builds ACP entries from agents and the settings tabs from adapters, so gating both hooks covers both axes. The pure helpers (adapter-visibility.ts, buildSidepanelChatTargets) stay untouched and pure. Dev mode and BrowserOS ≥ 0.46.0.0 are unchanged. ChatGPT / GitHub Copilot LLM providers are out of scope (already gated by their own server-version features).

Test plan

  • bun run typecheck — all workspaces green (the TS mapped type { [K in Feature]: FeatureConfig } enforces the new config entry at compile time).
  • bun test apps/agent/lib/browseros/capabilities.test.ts — new gate-boundary test: hidden below 0.46.0.0 and when the version is unknown, shown at/above.
  • bun test apps/agent/entrypoints/sidepanel/index/sidepanel-chat-targets.test.ts apps/agent/lib/chat/adapter-visibility.test.ts — pure-function tests unchanged (no regressions).
  • bunx biome check — clean.

The unified picker (#1065) surfaced the agent-harness adapters (Claude
Code, Codex) on every BrowserOS version the auto-updating extension runs
on, but the harness/ACP backend only ships in BrowserOS 0.46.0.0+. On
older builds the agents appeared but could not run.

Add Feature.AGENT_HARNESS_SUPPORT (minBrowserOSVersion 0.46.0.0) and gate
the two data hooks that feed every surface: useAgentAdapters (AI & Agents
settings tabs) and useHarnessAgents (picker ACP targets). When
unsupported, both disable their query and return empty — so the home and
sidepanel pickers, settings tabs, and recent-agents dock all hide the
agents, with no requests to harness endpoints that may not exist.
@github-actions github-actions Bot added the fix label May 28, 2026
@shadowfax92
Copy link
Copy Markdown
Contributor Author

Greptile (@greptileai) review

@shadowfax92
Copy link
Copy Markdown
Contributor Author

Claude (@claude) review

@claude
Copy link
Copy Markdown

claude Bot commented May 28, 2026

Claude finished Nikhil (@shadowfax92)'s task in 1m 20s —— View job


Code Review

  • Gather context and read changed files
  • Review capabilities.ts — new feature flag
  • Review useAgents.ts — gating hooks
  • Review capabilities.test.ts — test coverage
  • Post final review

Overall this is a clean, well-scoped change. The "single choke-point" design — gating at the two data-source hooks rather than in individual UI surfaces — is the right approach, and the compile-time enforcement via the mapped type is a nice safety net. A few things worth looking at before merging:

Potential bug: urlError leaks through the gate

In both useAgentAdapters and useHarnessAgents, the error field is not guarded by agentsSupported:

// useAgents.ts:69 (useAgentAdapters)
error: query.error ?? urlError,

// useAgents.ts:109 (useHarnessAgents)
error: query.error ?? urlError,

useAgentServerUrl() is called unconditionally and may itself fail or produce urlError on older BrowserOS builds where the harness port doesn't exist. A consumer that renders an error banner when the hook returns a non-null error would show it even though adapters/agents are empty and loading is false. The intent (per the PR description) is that older builds see nothing — no requests, no UI, no errors.

Suggested fix:

error: agentsSupported ? (query.error ?? urlError) : null,

Fix this →


Minor: checkFeatureSupport exported solely for test access

checkFeatureSupport was private before this PR and is now exported purely to allow the new test to call it directly (capabilities.test.ts:50). This is a reasonable pragmatic choice — the alternative would be mocking the BrowserOSAdapter and testing through Capabilities.supports() which is async and harder to set up. Worth a comment or a // exported for testing annotation if the team has a convention for that, but not a blocker.


Tests look solid

The boundary cases in capabilities.test.ts cover the important paths:

Input Expected
[0, 45, 9, 9] false
[0, 45, 0, 0] false
null (unknown version) false
[0, 46, 0, 0] (exact boundary) true
[0, 47, 0, 0] (above) true

One missing edge case: dev mode. resolveStaticFeatureSupport returns true in dev mode (bypassing version checks), but the new test suite uses checkFeatureSupport directly, which skips the static-support path. There's no test asserting that in a dev environment Capabilities.supports(Feature.AGENT_HARNESS_SUPPORT) returns true regardless of version. This is covered implicitly by the existing resolveStaticFeatureSupport tests, so it's not a gap in the logic — just a potential future readability issue if someone looks at only the new checkFeatureSupport tests.


Nit: duplicate capability check pattern

Both hooks repeat the same two lines:

const { supports } = useCapabilities()
const agentsSupported = supports(Feature.AGENT_HARNESS_SUPPORT)

This is fine for now (two hooks is not an abstraction threshold), but if a third harness hook is added, consider a shared useAgentHarnessSupported() hook to keep the pattern DRY. Not a blocker.


Summary: The gating logic is correct and the design is sound. The one concrete issue is the error field leaking through the gate on unsupported builds. Everything else is minor.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 28, 2026

✅ Tests passed — 1058/1063

Suite Passed Failed Skipped
agent 91/91 0 0
build 10/10 0 0
eval 95/95 0 0
server-agent 246/246 0 0
server-api 63/63 0 0
server-browser 4/4 0 0
server-integration 9/10 0 1
server-lib 252/253 0 1
server-root 59/62 0 3
server-tools 229/229 0 0

View workflow run

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 28, 2026

Greptile Summary

This PR adds a Feature.AGENT_HARNESS_SUPPORT version gate (minBrowserOSVersion: 0.46.0.0) and applies it to the two data-source hooks that feed the unified picker and settings tabs for Claude Code/Codex agents, preventing harness endpoint requests on older BrowserOS builds.

  • capabilities.ts: Adds AGENT_HARNESS_SUPPORT to the Feature enum and FEATURE_CONFIG; exports checkFeatureSupport for test use. The TypeScript mapped-type constraint ensures the config stays complete at compile time.
  • useAgents.ts: Both useAgentAdapters and useHarnessAgents now consult useCapabilities() before enabling their queries, returning empty arrays when unsupported instead of firing requests at missing endpoints.
  • capabilities.test.ts: Adds boundary tests covering null, below-, and at/above-0.46.0.0 cases for the new feature.

Confidence Score: 4/5

Safe to merge; the gating logic is correct and no harness requests will fire on unsupported BrowserOS versions.

The feature gate and TypeScript enforcement are solid. Both hooks return loading: false, data: [] during the async capabilities initialization window on BrowserOS ≥ 0.46 because useCapabilities.isLoading isn't threaded into the loading field, which can cause a transient empty-state flash.

useAgents.ts — the loading computation in both useAgentAdapters and useHarnessAgents should incorporate capabilitiesLoading from useCapabilities.

Important Files Changed

Filename Overview
packages/browseros-agent/apps/agent/lib/browseros/capabilities.ts Adds AGENT_HARNESS_SUPPORT feature enum entry and its BrowserOS 0.46.0.0 version gate to FEATURE_CONFIG; exports checkFeatureSupport for testability.
packages/browseros-agent/apps/agent/lib/browseros/capabilities.test.ts Adds boundary tests for AGENT_HARNESS_SUPPORT: hidden below 0.46.0.0 and when version is null, shown at/above 0.46.0.0. Coverage is correct for the exact boundary.
packages/browseros-agent/apps/agent/entrypoints/app/agents/useAgents.ts Gates useAgentAdapters and useHarnessAgents behind AGENT_HARNESS_SUPPORT using useCapabilities(); the loading field does not account for the capabilities initialization window, potentially flashing a false empty state on supported BrowserOS versions.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Component mounts] --> B[useCapabilities]
    B --> C{isLoading?}
    C -- yes --> D[agentsSupported = false\nloading = false ⚠️\ndata = empty]
    C -- no --> E{BrowserOS >= 0.46.0.0?}
    E -- no --> F[agentsSupported = false\nloading = false\ndata = empty — correct]
    E -- yes --> G[agentsSupported = true]
    G --> H[react-query enabled\nfetch /adapters or /agents]
    H --> I{Query done?}
    I -- loading --> J[loading = true\ndata = empty]
    I -- done --> K[loading = false\ndata = adapters / agents]
Loading

Comments Outside Diff (1)

  1. packages/browseros-agent/apps/agent/entrypoints/app/agents/useAgents.ts, line 75-83 (link)

    P2 Same transient loading gap applies to useHarnessAgents. During capabilities initialization on BrowserOS ≥ 0.46, loading will be false while agents and harnessAgents are both [], which looks identical to the "no agents configured" state to callers.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: packages/browseros-agent/apps/agent/entrypoints/app/agents/useAgents.ts
    Line: 75-83
    
    Comment:
    Same transient loading gap applies to `useHarnessAgents`. During capabilities initialization on BrowserOS ≥ 0.46, `loading` will be `false` while `agents` and `harnessAgents` are both `[]`, which looks identical to the "no agents configured" state to callers.
    
    
    
    How can I resolve this? If you propose a fix, please make it concise.
Prompt To Fix All With AI
Fix the following 3 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 3
packages/browseros-agent/apps/agent/entrypoints/app/agents/useAgents.ts:46-68
The `loading` field doesn't account for the `useCapabilities` initialization window. Before the async capabilities check resolves, `supports(Feature.AGENT_HARNESS_SUPPORT)` returns `false` (the feature is not in `getInitialSupportedFeatures()` for BrowserOS-gated features in production), so `loading` evaluates to `false && ... = false`. On BrowserOS ≥ 0.46, callers will briefly see `loading: false, adapters: []` — identical to the fully-loaded empty state — before capabilities resolve and the real fetch begins. Any consumer that conditionally renders an empty-state UI based on `!loading && adapters.length === 0` will flash it on every mount.

```suggestion
  const { supports, isLoading: capabilitiesLoading } = useCapabilities()
  const agentsSupported = supports(Feature.AGENT_HARNESS_SUPPORT)
  const {
    baseUrl,
    isLoading: urlLoading,
    error: urlError,
  } = useAgentServerUrl()

  const query = useQuery<HarnessAdapterDescriptor[], Error>({
    queryKey: [AGENT_QUERY_KEYS.adapters, baseUrl],
    queryFn: async () => {
      const data = await agentsFetch<{ adapters: HarnessAdapterDescriptor[] }>(
        baseUrl as string,
        '/adapters',
      )
      return data.adapters ?? []
    },
    enabled: Boolean(baseUrl) && !urlLoading && enabled && agentsSupported,
  })

  return {
    adapters: agentsSupported ? (query.data ?? []) : [],
    loading: capabilitiesLoading || (agentsSupported && (query.isLoading || urlLoading)),
```

### Issue 2 of 3
packages/browseros-agent/apps/agent/entrypoints/app/agents/useAgents.ts:75-83
Same transient loading gap applies to `useHarnessAgents`. During capabilities initialization on BrowserOS ≥ 0.46, `loading` will be `false` while `agents` and `harnessAgents` are both `[]`, which looks identical to the "no agents configured" state to callers.

```suggestion
  const { supports, isLoading: capabilitiesLoading } = useCapabilities()
  const agentsSupported = supports(Feature.AGENT_HARNESS_SUPPORT)
  const {
    baseUrl,
    isLoading: urlLoading,
    error: urlError,
  } = useAgentServerUrl()

  const query = useQuery<HarnessAgentsResponse, Error>({
```

### Issue 3 of 3
packages/browseros-agent/apps/agent/entrypoints/app/agents/useAgents.ts:104-108
The `loading` return for `useHarnessAgents` should also include `capabilitiesLoading` so the return value stays consistent with the `useAgentAdapters` fix.

```suggestion
    agents: agentsSupported
      ? (query.data?.agents ?? []).map(mapHarnessAgentToEntry)
      : [],
    harnessAgents: agentsSupported ? (query.data?.agents ?? []) : [],
    loading: capabilitiesLoading || (agentsSupported && (query.isLoading || urlLoading)),
```

Reviews (1): Last reviewed commit: "fix: version-gate Claude Code/Codex agen..." | Re-trigger Greptile

Comment thread packages/browseros-agent/apps/agent/entrypoints/app/agents/useAgents.ts Outdated
Comment thread packages/browseros-agent/apps/agent/entrypoints/app/agents/useAgents.ts Outdated
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 28, 2026

Greptile Summary

This PR version-gates the Claude Code and Codex agent-harness adapters behind a new Feature.AGENT_HARNESS_SUPPORT capability (minBrowserOSVersion: 0.46.0.0), preventing the agents from appearing — and the harness endpoints from being called — on older BrowserOS builds that don't ship the harness backend.

  • capabilities.ts: Adds AGENT_HARNESS_SUPPORT to the Feature enum and FEATURE_CONFIG map; exports checkFeatureSupport for direct unit-testing.
  • capabilities.test.ts: New checkFeatureSupport test suite validates the exact version boundary (hidden below 0.46.0.0 / when version is unknown, shown at or above).
  • useAgents.ts: Both useAgentAdapters and useHarnessAgents read supports(Feature.AGENT_HARNESS_SUPPORT) from useCapabilities(), set enabled: false when unsupported, and return [] / loading: false so every downstream UI surface goes dark cleanly.

Confidence Score: 4/5

Safe to merge. The version gate is wired at the single data-source layer and the TS exhaustive-map enforces the config entry at compile time; older BrowserOS builds will correctly see no harness agents and make no harness requests.

The gating logic and feature config are correct. The one gap is that useCapabilities().isLoading is not factored into the loading return value, which means callers on supported versions see a brief loading: false, data: [] state while capabilities resolve — a potential flash of empty content before agents appear.

The loading expression in useAgents.ts (both hooks) warrants a second look to decide whether the capabilities-initialization window needs to be reflected in the returned loading flag.

Important Files Changed

Filename Overview
packages/browseros-agent/apps/agent/lib/browseros/capabilities.ts Adds AGENT_HARNESS_SUPPORT feature with minBrowserOSVersion: 0.46.0.0; exports checkFeatureSupport for direct testing. TypeScript exhaustive-map pattern enforces the new entry at compile time. Logic is correct.
packages/browseros-agent/apps/agent/lib/browseros/capabilities.test.ts New checkFeatureSupport test suite covers the exact boundary (0.45.x → false, 0.46.0.0 → true, null → false). Boundary logic is correct.
packages/browseros-agent/apps/agent/entrypoints/app/agents/useAgents.ts Both useAgentAdapters and useHarnessAgents now gate on AGENT_HARNESS_SUPPORT; queries are disabled and empty arrays returned when unsupported. The loading field doesn't account for the capabilities initialization window, which can briefly surface loading: false, data: [] on supported versions.

Sequence Diagram

sequenceDiagram
    participant UI as UI Surface (picker / settings)
    participant HA as useAgentAdapters / useHarnessAgents
    participant UC as useCapabilities
    participant Cap as Capabilities (singleton)
    participant HE as Harness Endpoint

    UI->>HA: render
    HA->>UC: supports(AGENT_HARNESS_SUPPORT)
    UC->>Cap: getStaticSupport / async init
    Cap-->>UC: browserOSVersion resolved
    alt "BrowserOS < 0.46.0.0 or unknown"
        UC-->>HA: false
        HA-->>UI: "data=[], loading=false (query disabled)"
        note over HE: No request made
    else "BrowserOS >= 0.46.0.0"
        UC-->>HA: true
        HA->>HE: GET /adapters or GET /
        HE-->>HA: adapters / agents
        HA-->>UI: "data=[...], loading=false"
    end
Loading
Prompt To Fix All With AI
Fix the following 1 code review issue. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 1
packages/browseros-agent/apps/agent/entrypoints/app/agents/useAgents.ts:45-71
**`loading` hides the capabilities-initializing window**

`useCapabilities().isLoading` is destructured away — only `supports` is taken. While capabilities are initializing (the async `init()` in `useCapabilities` hasn't settled yet), `supports(Feature.AGENT_HARNESS_SUPPORT)` returns `false` (the map's default), so `agentsSupported` is `false` and `loading` evaluates to `false && …` = `false`. On a supported BrowserOS (≥ 0.46.0.0) this produces a brief window where the hook returns `loading: false, data: []`, which callers may interpret as "no agents" and render an empty state, before capabilities resolve and the query finally fires. The same pattern applies to `useHarnessAgents`. Including `capabilitiesLoading` in the loading expression would close the gap: `loading: capabilitiesLoading || (agentsSupported && (query.isLoading || urlLoading))`

Reviews (2): Last reviewed commit: "fix: version-gate Claude Code/Codex agen..." | Re-trigger Greptile

…ies init

- loading now folds in useCapabilities().isLoading so consumers don't see a
  premature 'no agents' empty state during the capabilities-init window on
  supported BrowserOS (Greptile P2, both hooks).
- error is now gated by agentsSupported, so urlError from useAgentServerUrl
  can't leak an error banner on older builds where harness endpoints are
  absent — older builds see nothing, per intent (Claude review).
- Note why checkFeatureSupport is exported (tests only).
@shadowfax92
Copy link
Copy Markdown
Contributor Author

Thanks for the review — addressed in d87f3a1:

  • urlError leaks through the gate (potential bug): good catch. Both hooks now return error: agentsSupported ? (query.error ?? urlError) : null, so an older build with no harness endpoint surfaces no error banner — it sees nothing, per the intent.
  • checkFeatureSupport exported for tests: added a // Exported for unit tests… comment explaining why.
  • Duplicate capability-check pattern (nit): left inline for now — two hooks is below the abstraction threshold, and it matches the repo's existing inline supports(Feature.X) convention. Will extract a useAgentHarnessSupported() if a third harness hook appears.
  • Dev-mode test edge: intentionally not re-tested via checkFeatureSupport (it bypasses the static/dev short-circuit by design); the dev-mode path stays covered by the existing resolveStaticFeatureSupport tests.

Also folded useCapabilities().isLoading into loading for both hooks (Greptile P2) so consumers don't flash an empty state during the capabilities-init window.

@shadowfax92 Nikhil (shadowfax92) merged commit 0d2ae82 into main May 28, 2026
22 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant