Skip to content

Commit 41087da

Browse files
authored
fix(workbench): skip the catalog of an uninstalled harness (#527)
1 parent da9c067 commit 41087da

2 files changed

Lines changed: 38 additions & 23 deletions

File tree

‎packages/client/workbench/src/surface/__tests__/use-agent-catalogs.test.ts‎

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,24 @@ afterEach(() => {
2323
runtimeMock.runtimes = undefined;
2424
});
2525

26+
const ALL_AVAILABLE: AgentRuntimes = {
27+
'claude-code': { status: 'available', source: 'detected' },
28+
codex: { status: 'available', source: 'detected' },
29+
opencode: { status: 'available', source: 'detected' },
30+
pi: { status: 'available', source: 'managed' },
31+
'grok-build': { status: 'available', source: 'detected' },
32+
};
33+
2634
describe('useAgentStartCatalogs', () => {
27-
it('pauses only the Pi catalog while runtime availability is loading', () => {
35+
it('pauses every catalog while runtime availability is loading', () => {
36+
renderHook(() => useAgentStartCatalogs('/repo/app'));
37+
38+
expect(tayoriMock.params).toEqual([null, null, null, null, null]);
39+
});
40+
41+
it('requests every catalog once the runtimes are available', () => {
42+
runtimeMock.runtimes = ALL_AVAILABLE;
43+
2844
renderHook(() => useAgentStartCatalogs('/repo/app'));
2945

3046
// The cwd is what lets an adapter resolve the tier a session would really start under —
@@ -33,26 +49,23 @@ describe('useAgentStartCatalogs', () => {
3349
{ agentKind: 'claude-code', cwd: '/repo/app' },
3450
{ agentKind: 'codex', cwd: '/repo/app' },
3551
{ agentKind: 'opencode', cwd: '/repo/app' },
36-
null,
52+
{ agentKind: 'pi', cwd: '/repo/app' },
3753
{ agentKind: 'grok-build', cwd: '/repo/app' },
3854
]);
3955
});
4056

41-
it('keeps the Pi catalog paused while its managed runtime is missing', () => {
42-
runtimeMock.runtimes = { pi: { status: 'missing' } };
43-
44-
renderHook(() => useAgentStartCatalogs('/repo/app'));
45-
46-
expect(tayoriMock.params[3]).toBeNull();
47-
});
48-
49-
it('requests the Pi catalog once its runtime is available', () => {
50-
runtimeMock.runtimes = { pi: { status: 'available', source: 'managed' } };
57+
it('skips a missing runtime but keeps out-of-range and unevaluated kinds', () => {
58+
// The dev mock host's onboarding fixture: one kind per runtime state.
59+
runtimeMock.runtimes = {
60+
'claude-code': { status: 'missing' },
61+
codex: { status: 'out-of-range', source: 'detected', version: '0.99.0' },
62+
pi: { status: 'available', source: 'builtin' },
63+
};
5164

5265
renderHook(() => useAgentStartCatalogs('/repo/app'));
5366

5467
expect(tayoriMock.params).toEqual([
55-
{ agentKind: 'claude-code', cwd: '/repo/app' },
68+
null,
5669
{ agentKind: 'codex', cwd: '/repo/app' },
5770
{ agentKind: 'opencode', cwd: '/repo/app' },
5871
{ agentKind: 'pi', cwd: '/repo/app' },
@@ -61,7 +74,7 @@ describe('useAgentStartCatalogs', () => {
6174
});
6275

6376
it('follows a workspace switch rather than capturing the first cwd', () => {
64-
runtimeMock.runtimes = { pi: { status: 'available', source: 'managed' } };
77+
runtimeMock.runtimes = ALL_AVAILABLE;
6578
const { rerender } = renderHook(({ cwd }: { cwd: string }) => useAgentStartCatalogs(cwd), {
6679
initialProps: { cwd: '/repo/app' },
6780
});

‎packages/client/workbench/src/surface/use-agent-catalogs.ts‎

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,17 @@ const SCOPED = { keepPreviousData: false } as const;
1919

2020
export function useAgentStartCatalogs(cwd?: string): Partial<Record<AgentKind, AgentStartCatalog>> {
2121
const { data: runtimes } = useAgentRuntimes();
22-
const claude = useData(getAgentCatalog, { agentKind: 'claude-code', cwd }, SCOPED);
23-
const codex = useData(getAgentCatalog, { agentKind: 'codex', cwd }, SCOPED);
24-
const opencode = useData(getAgentCatalog, { agentKind: 'opencode', cwd }, SCOPED);
25-
const pi = useData(
26-
getAgentCatalog,
27-
runtimes?.pi?.status === 'available' ? { agentKind: 'pi', cwd } : null,
28-
SCOPED,
29-
);
30-
const grok = useData(getAgentCatalog, { agentKind: 'grok-build', cwd }, SCOPED);
22+
// A runtime the host cannot spawn has no catalog to serve — the adapter throws and SWR retries
23+
// the failure indefinitely — so a `missing` kind is not requested (the harness picker already
24+
// badges it "Not installed"). Loading pauses every request; a kind the host never evaluated is
25+
// absent from the snapshot and stays fail-open, like `deriveAgentRuntimeCues`.
26+
const request = (agentKind: AgentKind) =>
27+
runtimes !== undefined && runtimes[agentKind]?.status !== 'missing' ? { agentKind, cwd } : null;
28+
const claude = useData(getAgentCatalog, request('claude-code'), SCOPED);
29+
const codex = useData(getAgentCatalog, request('codex'), SCOPED);
30+
const opencode = useData(getAgentCatalog, request('opencode'), SCOPED);
31+
const pi = useData(getAgentCatalog, request('pi'), SCOPED);
32+
const grok = useData(getAgentCatalog, request('grok-build'), SCOPED);
3133
return {
3234
...(claude.data && { 'claude-code': claude.data }),
3335
...(codex.data && { codex: codex.data }),

0 commit comments

Comments
 (0)