Skip to content

feat(opencode): per-agent OpenCode agent selection (--agent) - #1308

Open
pedramamini wants to merge 1 commit into
mainfrom
feat/284-opencode-agent-selection
Open

feat(opencode): per-agent OpenCode agent selection (--agent)#1308
pedramamini wants to merge 1 commit into
mainfrom
feat/284-opencode-agent-selection

Conversation

@pedramamini

@pedramamini pedramamini commented Jul 25, 2026

Copy link
Copy Markdown
Collaborator

Closes #284

Problem

Maestro spawns OpenCode with opencode run ... and never passes --agent, so every Maestro agent runs OpenCode's default primary agent. There was no way to pin a Maestro agent to a specific OpenCode agent (build, plan, or the ones plugins like oh-my-opencode register), which is what #284 asks for: reuse the agents OpenCode already defines instead of recreating personas as Maestro nudge instructions.

What this adds

An OpenCode Agent field in the agent configuration panel (New Agent, Edit Agent, Wizard, Group Chat, Encore). Setting it runs that Maestro agent as opencode run --agent <name>, so it keeps the OpenCode agent's persona, model, and instructions.

  • Free text, not a dropdown, on purpose: plugin-contributed agents are resolvable at run time but do not show up in opencode agent list (per the OpenCode bug linked in the issue thread), so a discovery-driven picker would hide exactly the agents this issue is about.
  • Per-agent, not per-provider: the value is stored in that agent's Custom Arguments (session.customArgs), which is per-agent, while agent config options are shared by every agent on the provider. Two-way bound, so --agent foo typed directly into Custom Arguments shows up in the field and vice versa.
  • Because it lives in customArgs, it flows through every existing spawn path (desktop, CLI, Cue, group chat, SSH) with no new plumbing.

The workflow from the issue thread now works end to end: create "Project X: Prometheus" and "Project X: Sisyphus", point each at its OpenCode agent, and group chat across them.

Bug this exposed

Config options and custom args are appended after buildAgentArgs() has already emitted readOnlyArgs. For OpenCode, plan mode is --agent plan, so a user's --agent prometheus landed after it and silently won: read-only mode was defeated with no warning. This is reachable today by anyone who puts --agent in Custom Arguments.

applyAgentConfigOverrides() now accepts readOnlyMode and drops any config-option or custom arg that repeats a flag the agent's readOnlyArgs pin (both --flag value and --flag=value spellings). Callers thread their existing read-only state through. Nothing else changes: no other agent's config option or readOnlyArgs overlap today, so this only bites the conflict case.

Testing

  • New src/__tests__/shared/opencodeAgentArg.test.ts (read/write/round-trip, quoting, duplicates, removal).
  • New read-only pinning cases in agent-args.test.ts.
  • New OpenCode field cases in AgentConfigPanel.test.tsx.
  • Full suite green locally: 31682 passed, 1107 files.
  • npm run lint and npm run lint:eslint clean.

Notes for review

  • Per-message agent switching (a picker above the input, like the model/effort pills) is deliberately out of scope here: that needs a new per-session/per-tab field threaded through ~30 files. This PR gets the per-agent binding working first. Happy to follow up if you want the pill.
  • Tab naming and group chat moderator spawns declare readOnlyMode: true, so they now ignore a configured --agent rather than running the user's persona agent for a title. That seemed clearly right, flagging it in case you disagree.

Summary by CodeRabbit

  • New Features

    • Added an optional OpenCode Agent field for selecting a named OpenCode agent directly from the configuration panel.
    • Supports preserving other custom arguments while adding, updating, or removing the selected agent.
  • Bug Fixes

    • Read-only mode now consistently enforces its pinned agent and command-line settings across launches, group chats, tab naming, and session recovery.
    • Conflicting custom arguments are automatically ignored in read-only mode.
  • Documentation

    • Documented OpenCode agent selection, including plan-mode behavior and plugin-provided agents.

Adds an "OpenCode Agent" field to the agent configuration panel that runs a
Maestro agent as `opencode run --agent <name>`, so it keeps that OpenCode
agent's persona, model, and instructions instead of falling back to the
provider default. Plugin-contributed agents (oh-my-opencode and friends) work
too: OpenCode resolves the name at run time even though `opencode agent list`
does not print them.

The value is stored in the agent's Custom Arguments rather than a provider
config option, because Custom Arguments are per-agent (`session.customArgs`)
while config options are shared by every agent on that provider. Living in
customArgs also means it flows through every spawn path already (desktop, CLI,
Cue, group chat) with no extra plumbing.

Also fixes a latent conflict this exposes: config-option and custom args are
appended after `buildAgentArgs()` has emitted `readOnlyArgs`, so a user's
`--agent <name>` landed after plan mode's `--agent plan` and silently won,
defeating read-only enforcement. `applyAgentConfigOverrides()` now takes
`readOnlyMode` and drops args that repeat a flag the agent's readOnlyArgs pin
(both `--flag value` and `--flag=value` spellings); callers thread their
existing read-only state through.

Closes #284
@coderabbitai

coderabbitai Bot commented Jul 25, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The PR adds an OpenCode agent selector backed by --agent argument helpers and documentation. It also propagates read-only mode through agent resolution so pinned flags cannot be overridden by config or session arguments.

Changes

OpenCode agent selection and read-only execution flow

Layer / File(s) Summary
OpenCode agent selection
src/shared/opencodeAgentArg.ts, src/renderer/components/shared/AgentConfigPanel.tsx, src/__tests__/shared/*, src/__tests__/renderer/components/shared/*, docs/provider-notes.md
Adds parsing and rewriting for OpenCode --agent arguments, exposes an OpenCode-only configuration field, tests the behavior, and documents agent selection and plan-mode interaction.
Read-only flag filtering
src/main/utils/agent-args.ts, src/__tests__/main/utils/agent-args.test.ts
Adds readOnlyMode override handling, derives pinned flags from readOnlyArgs, and removes conflicting config and custom arguments.
Read-only mode propagation
src/cli/services/agent-spawner.ts, src/main/group-chat/group-chat-router.ts, src/main/ipc/handlers/{process,tabNaming}.ts, src/main/utils/context-groomer.ts, src/__tests__/main/utils/context-groomer.test.ts
Passes read-only mode through spawning, group-chat, IPC, and context-grooming override paths.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant AgentConfigPanel
  participant opencodeAgentArg
  participant applyAgentConfigOverrides
  participant OpenCode
  AgentConfigPanel->>opencodeAgentArg: writeOpenCodeAgentArg(customArgs, agentName)
  opencodeAgentArg->>applyAgentConfigOverrides: provide customArgs
  applyAgentConfigOverrides->>applyAgentConfigOverrides: strip read-only pinned flags
  applyAgentConfigOverrides->>OpenCode: resolved CLI arguments
Loading

Possibly related PRs

Suggested labels: approved, ready to merge

Suggested reviewers: copilot, reachrazamair

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately names the main feature: per-agent OpenCode agent selection via --agent.
Linked Issues check ✅ Passed The PR satisfies #284 by letting Maestro agents select existing OpenCode agents by name, preserving OpenCode personas and plugin-provided agents.
Out of Scope Changes check ✅ Passed The added read-only pinning, docs, and tests support the same feature set and do not appear unrelated.
Docstring Coverage ✅ Passed Docstring coverage is 95.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/284-opencode-agent-selection

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.

@greptile-apps

greptile-apps Bot commented Jul 25, 2026

Copy link
Copy Markdown

Greptile Summary

Adds per-agent OpenCode agent selection and protects read-only arguments from later overrides.

  • Adds an OpenCode Agent field backed by each session's custom arguments.
  • Introduces helpers for reading and rewriting --agent.
  • Propagates read-only state through desktop, CLI, context-grooming, tab-naming, and group-chat spawn paths.
  • Filters conflicting config and custom arguments when read-only mode is active.
  • Adds focused tests and provider documentation.

Confidence Score: 3/5

The PR should not merge until Cue read-only spawns also enforce the new conflict filtering.

Cue continues appending configured OpenCode agent arguments without enabling read-only pinning, so a later --agent can override plan mode; the generic filter can also remove positional tokens after duplicated boolean switches.

Files Needing Attention: src/main/utils/agent-args.ts and src/main/cue/cue-spawn-builder.ts

Important Files Changed

Filename Overview
src/main/utils/agent-args.ts Adds read-only conflict filtering, but relies on complete caller propagation and incorrectly treats boolean flags as value-taking.
src/shared/opencodeAgentArg.ts Adds dedicated parsing and rewriting helpers for the OpenCode agent custom argument.
src/renderer/components/shared/AgentConfigPanel.tsx Adds a two-way-bound OpenCode Agent field backed by custom arguments.
src/main/group-chat/group-chat-router.ts Threads moderator and participant read-only state into override resolution.
src/cli/services/agent-spawner.ts Threads CLI spawn read-only state into override resolution.

Sequence Diagram

sequenceDiagram
    participant UI as Agent configuration
    participant Session as Session customArgs
    participant Builder as buildAgentArgs
    participant Overrides as applyAgentConfigOverrides
    participant CLI as OpenCode
    UI->>Session: "Store --agent <name>"
    Builder->>Builder: Add --agent plan when read-only
    Builder->>Overrides: Base args and session overrides
    Overrides->>Overrides: Remove flags pinned by readOnlyArgs
    Overrides->>CLI: Spawn final argv
Loading

Reviews (1): Last reviewed commit: "feat(opencode): per-agent OpenCode agent..." | Re-trigger Greptile

Comment on lines +233 to +235
const readOnlyPinnedFlags = new Set<string>(
overrides.readOnlyMode ? (agent?.readOnlyArgs ?? []).filter((arg) => arg.startsWith('-')) : []
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Cue skips read-only pinning

When a read-only Cue step runs OpenCode with a configured --agent <name>, the Cue override call omits readOnlyMode, so the custom agent argument remains after --agent plan and defeats plan mode.

Comment on lines +96 to +98
// `--flag=value` carries its value inline; `--flag value` eats the next
// token too, as long as that token isn't itself a flag.
if (equalsIndex === -1 && i + 1 < args.length && !args[i + 1].startsWith('-')) {

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 Boolean flags consume following arguments

For read-only Codex or Gemini runs, stripping a repeated boolean switch such as --skip-git-repo-check or -y also removes the next non-flag token, silently changing unrelated custom arguments.

Copilot AI left a comment

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.

Pull request overview

This PR adds per-agent OpenCode primary agent selection by exposing an "OpenCode Agent" field in the agent configuration UI that round-trips to session.customArgs as opencode run --agent <name>. It also hardens read-only (plan) mode by preventing later config/custom-args from overriding flags pinned by an agent's readOnlyArgs (notably OpenCode's --agent plan).

Changes:

  • Add shared helpers to read and write OpenCode --agent inside Custom Arguments, plus UI support in AgentConfigPanel.
  • Prevent read-only pinned flags from being overridden by later config options or custom args via applyAgentConfigOverrides(..., readOnlyMode).
  • Add tests and docs coverage for OpenCode agent selection and read-only pinning behavior.

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
src/shared/opencodeAgentArg.ts New helper utilities to parse and rewrite OpenCode --agent within per-agent Custom Arguments.
src/renderer/components/shared/AgentConfigPanel.tsx Adds the "OpenCode Agent (optional)" UI field and binds it to Custom Arguments.
src/main/utils/context-groomer.ts Threads readOnlyMode into override application so pinned flags cannot be overridden.
src/main/utils/agent-args.ts Adds readOnlyMode support and strips pinned flags from later overrides to enforce read-only args.
src/main/ipc/handlers/tabNaming.ts Marks tab-naming spawns as read-only for override stripping.
src/main/ipc/handlers/process.ts Forwards readOnlyMode into override resolution for process spawns.
src/main/group-chat/group-chat-router.ts Marks moderator/participant spawns as read-only when applicable so pinned flags are enforced.
src/cli/services/agent-spawner.ts Threads readOnlyMode into CLI override resolution to match desktop behavior.
src/tests/shared/opencodeAgentArg.test.ts New unit tests for reading/writing OpenCode --agent and round-tripping behavior.
src/tests/renderer/components/shared/AgentConfigPanel.test.tsx Adds UI tests for the OpenCode Agent field visibility and behavior.
src/tests/main/utils/context-groomer.test.ts Updates coverage to ensure readOnlyMode is forwarded into override resolution.
src/tests/main/utils/agent-args.test.ts Adds tests validating read-only pinning behavior for conflicting flags.
docs/provider-notes.md Documents OpenCode --agent support and how it behaves with plan mode.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +35 to +38
/** Wrap a value in double quotes when it contains whitespace. */
function quoteIfNeeded(value: string): string {
return /\s/.test(value) ? `"${value}"` : value;
}
Comment on lines +9 to +14
* Maestro stores the flag inside the per-agent Custom CLI Args string rather
* than in the provider-level agent config, because Custom CLI Args are
* per-agent (`session.customArgs`) while config options are shared by every
* agent using that provider. These helpers let the UI expose a dedicated
* "OpenCode Agent" field that reads and rewrites just that one token, leaving
* everything else in the string untouched.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 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.

Inline comments:
In `@src/renderer/components/shared/AgentConfigPanel.tsx`:
- Around line 679-689: Associate the visible “OpenCode Agent (optional)” label
with the input in the relevant AgentConfigPanel field by adding a programmatic
accessible name, using either aria-label or matching id/htmlFor attributes.
Update the component test to locate this input by role and its accessible name.

In `@src/shared/opencodeAgentArg.ts`:
- Around line 35-38: Update quoteIfNeeded to safely serialize values containing
whitespace or embedded quote characters for compatibility with parseCustomArgs.
Escape or otherwise encode internal quotes while preserving the entire value as
one token, and keep unquoted output for values that require no quoting.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 2ea3562b-6196-4721-9679-53a425a8e333

📥 Commits

Reviewing files that changed from the base of the PR and between d2085a0 and 7776067.

📒 Files selected for processing (13)
  • docs/provider-notes.md
  • src/__tests__/main/utils/agent-args.test.ts
  • src/__tests__/main/utils/context-groomer.test.ts
  • src/__tests__/renderer/components/shared/AgentConfigPanel.test.tsx
  • src/__tests__/shared/opencodeAgentArg.test.ts
  • src/cli/services/agent-spawner.ts
  • src/main/group-chat/group-chat-router.ts
  • src/main/ipc/handlers/process.ts
  • src/main/ipc/handlers/tabNaming.ts
  • src/main/utils/agent-args.ts
  • src/main/utils/context-groomer.ts
  • src/renderer/components/shared/AgentConfigPanel.tsx
  • src/shared/opencodeAgentArg.ts

Comment on lines +679 to +689
<label className="block text-xs font-medium mb-2" style={{ color: theme.colors.textDim }}>
OpenCode Agent (optional)
</label>
<input
type="text"
value={readOpenCodeAgentArg(customArgs)}
onChange={(e) => onCustomArgsChange(writeOpenCodeAgentArg(customArgs, e.target.value))}
onBlur={onCustomArgsBlur}
onClick={(e) => e.stopPropagation()}
placeholder="build"
className="w-full p-2 rounded border bg-transparent outline-none text-xs font-mono"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Give the input a programmatic accessible name.

The visible label is not associated with this input. Add aria-label="OpenCode Agent (optional)" or an id and matching htmlFor, then query it by role and name in the component test.

🤖 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/components/shared/AgentConfigPanel.tsx` around lines 679 - 689,
Associate the visible “OpenCode Agent (optional)” label with the input in the
relevant AgentConfigPanel field by adding a programmatic accessible name, using
either aria-label or matching id/htmlFor attributes. Update the component test
to locate this input by role and its accessible name.

Comment on lines +35 to +38
/** Wrap a value in double quotes when it contains whitespace. */
function quoteIfNeeded(value: string): string {
return /\s/.test(value) ? `"${value}"` : value;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -C3 --type ts '(customArgs|shell-quote|parse.*Args|split.*Args)' src/main src/cli src/shared

Repository: RunMaestro/Maestro

Length of output: 37775


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- opencodeAgentArg.ts ---'
cat -n src/shared/opencodeAgentArg.ts

echo '--- agent-args.ts parseCustomArgs section ---'
sed -n '1,120p' src/main/utils/agent-args.ts | cat -n

echo '--- opencodeAgentArg tests ---'
rg -n -C4 --type ts 'readOpenCodeAgentArg|writeOpenCodeAgentArg|quoteIfNeeded|opencodeAgentArg'

Repository: RunMaestro/Maestro

Length of output: 9126


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- tests mentioning OpenCode/opencode/agent arg ---'
rg -n -C4 --type ts 'opencode|OpenCode|open-code|--agent|agent name|readOpenCodeAgentArg|writeOpenCodeAgentArg' src | head -n 300

echo '--- custom args write entrypoints ---'
rg -n -C5 --type ts 'setCustomArgs|getOpenCodeAgentArg|writeOpenCodeAgentArg|customArgs.*quoteIfNeeded|quoteIfNeeded' src | head -n 300

echo '--- behavioral probe of matching tokenization semantics ---'
node - <<'JS'
function parseCustomArgs(customArgs) {
  if (!customArgs || typeof customArgs !== 'string') return [];
  const customArgsArray = customArgs.match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g) || [];
  return customArgsArray.map((arg) => {
    if ((arg.startsWith('"') && arg.endsWith('"')) || (arg.startsWith("'") && arg.endsWith("'"))) {
      return arg.slice(1, -1);
    }
    return arg;
  });
}
function quoteIfNeeded(value) {
  return /\s/.test(value) ? `"${value}"` : value;
}
for (const name of ['foo"', 'my "agent"', 'name with "quotes"', 'foo"bar']) {
  const serialized = ['--agent', quoteIfNeeded(name)].join(' ');
  const parsed = parseCustomArgs(serialized);
  console.log(JSON.stringify({ name, serialized, parsed: parsed.slice(parsed.indexOf('--agent') + 1) }));
}
JS

Repository: RunMaestro/Maestro

Length of output: 50376


Handle embedded quotes and unpaired whitespace in the OpenCode agent serializer.

quoteIfNeeded still serializes names such as foo"bar as --agent foo"bar and names with embedded quotes like my "agent" as --agent "my "agent", which this custom-args parser would break into tokens or strip quotes for. Use a serializer matching parseCustomArgs or escape/split token values that contain quote or space characters.

🤖 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/shared/opencodeAgentArg.ts` around lines 35 - 38, Update quoteIfNeeded to
safely serialize values containing whitespace or embedded quote characters for
compatibility with parseCustomArgs. Escape or otherwise encode internal quotes
while preserving the entire value as one token, and keep unquoted output for
values that require no quoting.

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.

Ability to utilize existing agents on Opencode (oh-my-opencode support)

2 participants