Skip to content

feat: add OpenCode to spawn dialogs and empty-state UI#143

Open
khaliqgant wants to merge 4 commits into
mainfrom
claude/opencode-ui-only
Open

feat: add OpenCode to spawn dialogs and empty-state UI#143
khaliqgant wants to merge 4 commits into
mainfrom
claude/opencode-ui-only

Conversation

@khaliqgant
Copy link
Copy Markdown
Member

Summary

Adds OpenCode to the interactive spawn surfaces users touch directly. UI-only — no relay package bump, no headless routing, no architectural changes.

  • SpawnAgentCli type: 'claude' | 'codex''claude' | 'codex' | 'opencode'
  • SpawnAgentDialog: adds OpenCode card to the spawn grid (3-col: Claude / Codex / OpenCode)
  • TerminalPane empty state: adds OpenCode quick-spawn button alongside Claude and Codex

What happens when you spawn OpenCode

For now, OpenCode spawns via the existing PTY path (same as Claude/Codex today). The headless routing for OpenCode is in PR #140 (held pending V1 structured output parsing work).

Conflicts to expect at merge

SpawnAgentDialog, TerminalPane, and SpawnAgentCli are also touched by PR #138 (Grok). The conflicts are trivial — the result after both merge should be all four harnesses in each location.


Generated by Claude Code

- SpawnAgentCli type: 'claude' | 'codex' → 'claude' | 'codex' | 'opencode'
- SpawnAgentDialog: adds OpenCode card to the spawn grid (3-col)
- TerminalPane empty state: adds OpenCode quick-spawn button alongside Claude and Codex

UI-only change. No relay package bump, no headless routing.

https://claude.ai/code/session_01KXU1uAUwx3L82TMLnAmU4z
@codeant-ai
Copy link
Copy Markdown

codeant-ai Bot commented Jun 7, 2026

Your free trial PR review limit of 300 PRs has been reached. Please upgrade your plan to continue using CodeAnt AI.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 7, 2026

Review Change Stack

Warning

Review limit reached

@khaliqgant, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 46 minutes and 8 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: a0f98aff-230e-4d2c-9456-9c26858baafa

📥 Commits

Reviewing files that changed from the base of the PR and between ed6285c and daf84ba.

📒 Files selected for processing (3)
  • check_types.mjs
  • src/renderer/src/components/sidebar/SpawnAgentDialog.tsx
  • src/renderer/src/components/terminal/TerminalPane.tsx
📝 Walkthrough

Walkthrough

This PR adds CLI availability checking across the spawn agent UI. A new IPC handler queries whether CLIs (claude, codex, opencode) are resolvable on the system path, surfaced through preload API to renderer components. Both SpawnAgentDialog and TerminalPane now fetch availability on mount, disable spawn buttons when CLIs are unavailable, and show installation hints in tooltips. The UI layout expands from 2-column to 3-column to accommodate the new OpenCode option.

Changes

CLI Availability Checking Feature

Layer / File(s) Summary
IPC Contract and Infrastructure
src/shared/types/ipc.ts, src/main/broker.ts, src/main/ipc-handlers.ts, src/preload/index.ts
Adds checkCliAvailable(cli: string): Promise<boolean> to PearAPI type contract; exports resolveCommandWithAugmentedPath from broker.ts; registers broker:check-cli-available IPC handler that checks CLI resolution; wires preload bridge to expose the method to renderer.
Spawn Agent Type and Options
src/renderer/src/lib/spawn-agent.ts, src/renderer/src/components/sidebar/SpawnAgentDialog.tsx
Extends SpawnAgentCli type union to include 'opencode'; adds OpenCodeIcon import and OpenCode agent option to the options list.
SpawnAgentDialog CLI Availability
src/renderer/src/components/sidebar/SpawnAgentDialog.tsx
Adds cliAvailability state; on mount, fetches availability for claude, codex, and opencode via pear.broker.checkCliAvailable and caches results; updates button grid from 2 to 3 columns; disables buttons when path is missing, spawn is in progress, or CLI is unavailable; adjusts tooltips to show missing path, missing CLI with npm install -g <cli> hint, or spawn-ready message.
TerminalPane CLI Availability
src/renderer/src/components/terminal/TerminalPane.tsx
Adds OpenCodeIcon import and cliAvailability state; on mount, concurrently checks availability for claude, codex, and opencode and caches boolean map (treating failures as unavailable); renders empty-state spawn buttons via mapped array; disables buttons when root path is missing or CLI unavailable; adjusts button titles and content based on path presence, CLI availability, and current spawn progress.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 A rabbit hops through paths untread,
Checking CLIs to see what's spread—
Claude, Codex, OpenCode in line,
Now dialogs show when tools align! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: adding OpenCode to spawn dialogs and the empty-state UI, which aligns with the changeset's primary objective.
Description check ✅ Passed The description is directly related to the changeset, providing clear context about adding OpenCode to interactive spawn surfaces and explaining the scope and merge considerations.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/opencode-ui-only

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces support for a new agent type, "OpenCode". It updates the SpawnAgentCli type, integrates the OpenCodeIcon, and adjusts the UI layout in both SpawnAgentDialog and TerminalPane to support a three-column grid for spawning the new agent. The feedback suggests improving type safety and maintainability in SpawnAgentDialog by typing the Icon property in AGENT_OPTIONS as a generic React.ComponentType rather than hardcoding it to typeof ClaudeIcon.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines 10 to 14
const AGENT_OPTIONS: Array<{ cli: SpawnAgentCli; label: string; Icon: typeof ClaudeIcon }> = [
{ cli: 'claude', label: 'Claude', Icon: ClaudeIcon },
{ cli: 'codex', label: 'Codex', Icon: CodexIcon }
{ cli: 'codex', label: 'Codex', Icon: CodexIcon },
{ cli: 'opencode', label: 'OpenCode', Icon: OpenCodeIcon }
]
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The type of the Icon property in AGENT_OPTIONS is hardcoded to typeof ClaudeIcon. While this works because all agent icons share the same signature, it is more idiomatic and maintainable to use a generic React component type. This prevents potential type issues when adding new agent icons with different implementations in the future.

Suggested change
const AGENT_OPTIONS: Array<{ cli: SpawnAgentCli; label: string; Icon: typeof ClaudeIcon }> = [
{ cli: 'claude', label: 'Claude', Icon: ClaudeIcon },
{ cli: 'codex', label: 'Codex', Icon: CodexIcon }
{ cli: 'codex', label: 'Codex', Icon: CodexIcon },
{ cli: 'opencode', label: 'OpenCode', Icon: OpenCodeIcon }
]
const AGENT_OPTIONS: Array<{ cli: SpawnAgentCli; label: string; Icon: React.ComponentType<{ className?: string }> }> = [
{ cli: 'claude', label: 'Claude', Icon: ClaudeIcon },
{ cli: 'codex', label: 'Codex', Icon: CodexIcon },
{ cli: 'opencode', label: 'OpenCode', Icon: OpenCodeIcon }
]

Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

No issues found across 3 files

Re-trigger cubic

@agent-relay-code
Copy link
Copy Markdown
Contributor

ℹ️ pr-reviewer: review only — no file changes were applied to the PR (nothing to commit after review). The notes below are advisory and were not pushed.

Reviewed PR #143 against .workforce/pr.diff, changed files, and the checked-out code paths.

I did not find a reproducible breakage in the current checkout, so I left the PR source unchanged. opencode is already supported through the main broker/harness paths, shared IPC accepts the CLI string, and the renderer additions compile.

Verification run:

  • npm ci
  • npm run build
  • npm test
  • npm run verify:mcp-resources-drift

All passed locally. I cannot confirm GitHub CI status, mergeability, or pending checks from this sandbox, so I’m not marking this as READY.

Adds a broker:check-cli-available IPC that resolves each CLI binary
on PATH using the same augmented-path lookup as preflightSpawnCli.

Both SpawnAgentDialog and TerminalPane check availability on mount
and disable (opacity-40, not-allowed cursor) any spawn button whose
CLI is not found, with a tooltip explaining it is not installed.
Buttons remain enabled until the check resolves so there is no flash.

https://claude.ai/code/session_01KXU1uAUwx3L82TMLnAmU4z
@codeant-ai
Copy link
Copy Markdown

codeant-ai Bot commented Jun 7, 2026

Your free trial PR review limit of 300 PRs has been reached. Please upgrade your plan to continue using CodeAnt AI.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
src/main/ipc-handlers.ts (1)

354-356: ⚡ Quick win

Restrict broker:check-cli-available to supported harness IDs.

This IPC endpoint currently accepts any string and forwards it to command resolution. Normalize + whitelist (claude/codex/opencode) before resolving to keep the boundary tight and avoid unintended command probing.

♻️ Suggested hardening
+  const SUPPORTED_SPAWN_CLIS = new Set(['claude', 'codex', 'opencode'] as const)
+
   ipcMain.handle('broker:check-cli-available', (_, cli: string) => {
-    return Boolean(resolveCommandWithAugmentedPath(cli))
+    const normalized = typeof cli === 'string' ? cli.trim().toLowerCase() : ''
+    if (!SUPPORTED_SPAWN_CLIS.has(normalized as 'claude' | 'codex' | 'opencode')) {
+      return false
+    }
+    return Boolean(resolveCommandWithAugmentedPath(normalized))
   })
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/ipc-handlers.ts` around lines 354 - 356, The IPC handler
'broker:check-cli-available' currently accepts any cli string; restrict it by
normalizing the input and only allowing the whitelisted harness IDs "claude",
"codex", and "opencode" before calling resolveCommandWithAugmentedPath. Update
the handler function to map/normalize incoming cli to lowercase, check
membership against the allowed set, and return false immediately for anything
else; only call resolveCommandWithAugmentedPath(cli) when the value is in the
whitelist to avoid unintended command probing.
src/renderer/src/components/sidebar/SpawnAgentDialog.tsx (1)

50-58: ⚡ Quick win

Derive CLI checks from a single shared options source.

The CLI list is hardcoded here and again in other spawn surfaces. Centralizing options (and deriving availability checks from that list) will prevent drift during future harness additions.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/renderer/src/components/sidebar/SpawnAgentDialog.tsx` around lines 50 -
58, Replace the hardcoded clis array in the useEffect with the canonical shared
options list: import the centralized CLI options constant used across spawn
surfaces (the shared CLI/options array), map/convert those option entries to the
SpawnAgentCli values, then run the same availability checks with
pear.broker.checkCliAvailable and call setCliAvailability with
Object.fromEntries(results). Update the local variable referenced as clis and
keep the existing promise logic (Promise.all, pear.broker.checkCliAvailable,
setCliAvailability) and types (SpawnAgentCli) so availability checks are derived
from the single shared source rather than the hardcoded
['claude','codex','opencode'].
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/main/ipc-handlers.ts`:
- Around line 354-356: The IPC handler 'broker:check-cli-available' currently
accepts any cli string; restrict it by normalizing the input and only allowing
the whitelisted harness IDs "claude", "codex", and "opencode" before calling
resolveCommandWithAugmentedPath. Update the handler function to map/normalize
incoming cli to lowercase, check membership against the allowed set, and return
false immediately for anything else; only call
resolveCommandWithAugmentedPath(cli) when the value is in the whitelist to avoid
unintended command probing.

In `@src/renderer/src/components/sidebar/SpawnAgentDialog.tsx`:
- Around line 50-58: Replace the hardcoded clis array in the useEffect with the
canonical shared options list: import the centralized CLI options constant used
across spawn surfaces (the shared CLI/options array), map/convert those option
entries to the SpawnAgentCli values, then run the same availability checks with
pear.broker.checkCliAvailable and call setCliAvailability with
Object.fromEntries(results). Update the local variable referenced as clis and
keep the existing promise logic (Promise.all, pear.broker.checkCliAvailable,
setCliAvailability) and types (SpawnAgentCli) so availability checks are derived
from the single shared source rather than the hardcoded
['claude','codex','opencode'].

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: a0094217-ed35-45f4-bae6-3e6edb02acbc

📥 Commits

Reviewing files that changed from the base of the PR and between 6e66172 and 3b52bec.

📒 Files selected for processing (7)
  • src/main/broker.ts
  • src/main/ipc-handlers.ts
  • src/preload/index.ts
  • src/renderer/src/components/sidebar/SpawnAgentDialog.tsx
  • src/renderer/src/components/terminal/TerminalPane.tsx
  • src/renderer/src/lib/spawn-agent.ts
  • src/shared/types/ipc.ts

Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

2 issues found across 6 files (changes from recent commits).

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread src/renderer/src/components/terminal/TerminalPane.tsx
Comment thread src/renderer/src/components/sidebar/SpawnAgentDialog.tsx Outdated
@codeant-ai
Copy link
Copy Markdown

codeant-ai Bot commented Jun 7, 2026

Your free trial PR review limit of 300 PRs has been reached. Please upgrade your plan to continue using CodeAnt AI.

@agent-relay-code
Copy link
Copy Markdown
Contributor

pr-reviewer applied fixes — committed and pushed ed6285c to this PR. The notes below describe what changed.

Fixed the PR issues I could validate in the current checkout.

Changes made:

Validation run:

  • npm ci
  • npx vitest run src/main/broker.test.ts src/main/ipc-handlers.test.ts
  • npm test
  • npm run build
  • Also checked npm package metadata for the CLI install hints.

I’m not printing READY because I cannot verify remote CI/check status or GitHub mergeability from this sandbox under the “don’t use git or gh” constraint.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
src/main/ipc-handlers.test.ts (1)

203-232: ⚡ Quick win

Add test case for unavailable CLI.

The test suite covers string input with an available CLI and non-string input rejection, but misses the case where a valid string CLI is provided but the command is not available. This would ensure the handler correctly returns false in that scenario.

🧪 Suggested additional test case
   it('rejects non-string CLI values', () => {
     const handler = mock.handlers.get('broker:check-cli-available')
     expect(handler).toBeTypeOf('function')

     const result = handler?.({}, null)

     expect(result).toBe(false)
     expect(mock.isCommandAvailableWithAugmentedPath).not.toHaveBeenCalled()
   })
+
+  it('returns false when CLI is not available', () => {
+    const handler = mock.handlers.get('broker:check-cli-available')
+    expect(handler).toBeTypeOf('function')
+    mock.isCommandAvailableWithAugmentedPath.mockReturnValueOnce(false)
+
+    const result = handler?.({}, 'unavailable-cli')
+
+    expect(result).toBe(false)
+    expect(mock.isCommandAvailableWithAugmentedPath).toHaveBeenCalledWith('unavailable-cli')
+  })
 })
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/ipc-handlers.test.ts` around lines 203 - 232, Add a test asserting
that when a string CLI name is passed but
mock.isCommandAvailableWithAugmentedPath returns false the IPC handler for
'broker:check-cli-available' returns false; locate the handler via
mock.handlers.get('broker:check-cli-available') after calling
registerIpcHandlers(), set
mock.isCommandAvailableWithAugmentedPath.mockReturnValueOnce(false), invoke the
handler with a string like 'opencode', and assert the result is false and that
mock.isCommandAvailableWithAugmentedPath was called with the same string.
src/main/broker.test.ts (1)

1164-1187: ⚡ Quick win

Add test case for command not found.

The test suite covers the happy path (command exists) and blank input, but misses the negative case where a valid string command is provided but doesn't exist on PATH. This would improve coverage of the helper's behavior.

🧪 Suggested additional test case
   it('returns false for blank commands', () => {
     expect(isCommandAvailableWithAugmentedPath('   ')).toBe(false)
   })
+
+  it('returns false when command is not found', () => {
+    process.env.PATH = '/nonexistent/path'
+    expect(isCommandAvailableWithAugmentedPath('nonexistent-cli')).toBe(false)
+  })
 })
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/broker.test.ts` around lines 1164 - 1187, Add a negative test for
isCommandAvailableWithAugmentedPath that verifies a non-existing command returns
false and does not mutate PATH: create a tempDir (using mkdtemp like the other
test) with no executable inside (or set process.env.PATH to an empty dir), call
isCommandAvailableWithAugmentedPath('nonexistent-cmd'), assert the result is
false and assert process.env.PATH remains unchanged, and ensure the test uses
the same cleanup pattern (rm, tempDir reset) as the existing tests.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/main/broker.test.ts`:
- Around line 1164-1187: Add a negative test for
isCommandAvailableWithAugmentedPath that verifies a non-existing command returns
false and does not mutate PATH: create a tempDir (using mkdtemp like the other
test) with no executable inside (or set process.env.PATH to an empty dir), call
isCommandAvailableWithAugmentedPath('nonexistent-cmd'), assert the result is
false and assert process.env.PATH remains unchanged, and ensure the test uses
the same cleanup pattern (rm, tempDir reset) as the existing tests.

In `@src/main/ipc-handlers.test.ts`:
- Around line 203-232: Add a test asserting that when a string CLI name is
passed but mock.isCommandAvailableWithAugmentedPath returns false the IPC
handler for 'broker:check-cli-available' returns false; locate the handler via
mock.handlers.get('broker:check-cli-available') after calling
registerIpcHandlers(), set
mock.isCommandAvailableWithAugmentedPath.mockReturnValueOnce(false), invoke the
handler with a string like 'opencode', and assert the result is false and that
mock.isCommandAvailableWithAugmentedPath was called with the same string.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 2f7fd702-f506-469a-915b-f059d413e7ed

📥 Commits

Reviewing files that changed from the base of the PR and between 3b52bec and ed6285c.

📒 Files selected for processing (7)
  • src/main/broker.test.ts
  • src/main/broker.ts
  • src/main/ipc-handlers.test.ts
  • src/main/ipc-handlers.ts
  • src/renderer/src/components/sidebar/SpawnAgentDialog.tsx
  • src/renderer/src/components/terminal/TerminalPane.tsx
  • src/renderer/src/lib/spawn-agent.ts
🚧 Files skipped from review as they are similar to previous changes (5)
  • src/main/broker.ts
  • src/main/ipc-handlers.ts
  • src/renderer/src/lib/spawn-agent.ts
  • src/renderer/src/components/terminal/TerminalPane.tsx
  • src/renderer/src/components/sidebar/SpawnAgentDialog.tsx

Both SpawnAgentDialog and TerminalPane lacked a cancelled flag in the
CLI availability useEffect, allowing setCliAvailability to fire on an
unmounted component if the user closed the pane before the IPC round
trip completed. Pattern matches the existing loadPersonas/loadBurnSummaries
guards in the same files.

https://claude.ai/code/session_01KXU1uAUwx3L82TMLnAmU4z
@codeant-ai
Copy link
Copy Markdown

codeant-ai Bot commented Jun 7, 2026

Your free trial PR review limit of 300 PRs has been reached. Please upgrade your plan to continue using CodeAnt AI.

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.

2 participants