Skip to content

Feat: cli opencode & droid support#524

Open
chr1syy wants to merge 2 commits intoRunMaestro:mainfrom
chr1syy:feat/cli-opencode-droid-minimal
Open

Feat: cli opencode & droid support#524
chr1syy wants to merge 2 commits intoRunMaestro:mainfrom
chr1syy:feat/cli-opencode-droid-minimal

Conversation

@chr1syy
Copy link
Contributor

@chr1syy chr1syy commented Mar 6, 2026

This pull request adds support for two new agent types, OpenCode and Factory Droid, to the CLI. The changes include detection of their CLI binaries, validation logic, and agent spawning and output parsing. The implementation closely follows the existing structure for Claude and Codex agents, ensuring consistent error handling and session management.

Agent support and detection enhancements:

  • Added detection and validation for OpenCode and Factory Droid CLI binaries in both send and run-playbook commands, including clear error messages if the binaries are not found. [1] [2] [3] [4] [5]
  • Implemented generic command detection logic (findCommandInPath) to support flexible binary lookup for new agent types.

Agent spawning and output parsing:

  • Added spawnOpenCodeAgent and spawnDroidAgent functions to handle agent process management, output parsing, error handling, and usage statistics aggregation for OpenCode and Factory Droid agents.
  • Integrated new output parsers (OpenCodeOutputParser, FactoryDroidOutputParser) for structured JSON event parsing from agent responses.

CLI integration and agent selection:

  • Updated spawnAgent function to route requests to the appropriate agent-spawning logic based on the agent type, supporting OpenCode and Factory Droid alongside existing Claude and Codex agents.

replacement for #520 which escalated through review, this adds minimal support for opencode/droid support for CLI for use with Maestro-Discord

Summary by CodeRabbit

  • New Features
    • Added support for OpenCode and Factory Droid as new agent tool types.
    • Implemented CLI detection and validation for these agents when running playbooks and sending commands.
    • Enhanced error reporting with specific messages when required tools are unavailable.

@coderabbitai
Copy link

coderabbitai bot commented Mar 6, 2026

📝 Walkthrough

Walkthrough

The changes extend CLI agent capability handling to support two new tool types: opencode and factory-droid. This includes detection logic across command files, spawning implementations, output parsing, and updated type definitions in the agent spawner service.

Changes

Cohort / File(s) Summary
CLI Command Handlers
src/cli/commands/run-playbook.ts, src/cli/commands/send.ts
Added conditional detection branches for opencode and factory-droid tool types. When these tools are specified, the commands verify CLI availability via detectOpenCode and detectDroid, emitting structured errors and exiting if unavailable. Maintains existing control flow for codex and claude-code agents.
Agent Spawner Service
src/cli/services/agent-spawner.ts
Implemented comprehensive support for opencode and factory-droid agents: added detectOpenCode and detectDroid detection functions with path caching and validation; introduced spawnOpenCodeAgent and spawnDroidAgent implementations with session management; added OpenCodeOutputParser and FactoryDroidOutputParser for JSON-line event processing; extended AgentResult type with response, agentSessionId, usageStats, and error fields for richer result reporting; integrated getAgentDefinition for tool-specific configuration (environment variables, arguments, resume support).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 69.23% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title clearly and concisely describes the main change: adding CLI support for two new agent types (OpenCode and Factory Droid), which aligns with the primary objectives and changes across all three modified files.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

@greptile-apps
Copy link

greptile-apps bot commented Mar 6, 2026

Greptile Summary

This PR adds support for OpenCode and Factory Droid agent types to the Maestro CLI, following the established Claude/Codex integration pattern. The implementation includes binary detection (detectOpenCode, detectDroid), process spawning (spawnOpenCodeAgent, spawnDroidAgent), structured JSON output parsing via new parsers, and integration into spawnAgent, send, and run-playbook.

The core architecture is sound and closely mirrors Codex's approach. Minor issues identified:

  • Type safety: Both new spawn functions use unnecessary as any casts on extractUsage() calls, unlike the Codex implementation.
  • Cache labelling: Detection functions mislabel cached paths—they always report source: 'settings' on cache hits, even when the path was originally resolved via PATH detection. This is a pre-existing pattern in all detection functions but affects correctness of returned metadata.

Confidence Score: 4/5

  • Safe to merge. Core logic is sound and follows established patterns. Minor code-quality issues identified.
  • The implementation is well-structured and mirrors the Codex integration pattern successfully. Both new spawn functions and detection logic are implemented correctly. The two flagged issues are style/hygiene matters: unnecessary type casts that reduce clarity, and metadata mislabelling that doesn't affect behavior but indicates incomplete precision. Neither blocks functionality. The PR extends a working pattern to two new agent types with no functional gaps.
  • src/cli/services/agent-spawner.ts — remove unnecessary as any casts and consider caching detection source alongside path.

Last reviewed commit: f8477a9

Comment on lines +676 to +686
const usage = parser.extractUsage(event as any);
if (usage) {
usageStats = mergeUsageStats(usageStats, {
inputTokens: usage.inputTokens || 0,
outputTokens: usage.outputTokens || 0,
cacheReadTokens: usage.cacheReadTokens || 0,
cacheCreationTokens: usage.cacheCreationTokens || 0,
costUsd: usage.costUsd || 0,
contextWindow: usage.contextWindow || 0,
reasoningTokens: usage.reasoningTokens || 0,
});
Copy link

Choose a reason for hiding this comment

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

Unnecessary as any casts bypass type safety. Both spawnOpenCodeAgent (line 676) and spawnDroidAgent (line 780) cast the event to any before calling parser.extractUsage(), but the equivalent spawnCodexAgent at line 566 calls parser.extractUsage(event) without a cast. Since OpenCodeOutputParser.extractUsage() and FactoryDroidOutputParser.extractUsage() both accept the same ParsedEvent type returned by parseJsonLine(), the casts are unnecessary and hide type mismatches.

Suggested change
const usage = parser.extractUsage(event as any);
if (usage) {
usageStats = mergeUsageStats(usageStats, {
inputTokens: usage.inputTokens || 0,
outputTokens: usage.outputTokens || 0,
cacheReadTokens: usage.cacheReadTokens || 0,
cacheCreationTokens: usage.cacheCreationTokens || 0,
costUsd: usage.costUsd || 0,
contextWindow: usage.contextWindow || 0,
reasoningTokens: usage.reasoningTokens || 0,
});
const usage = parser.extractUsage(event);

Apply the same fix at line 780 in spawnDroidAgent.

Comment on lines +256 to +258
if (cachedOpenCodePath) {
return { available: true, path: cachedOpenCodePath, source: 'settings' };
}
Copy link

Choose a reason for hiding this comment

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

Cached detection results always report source: 'settings' regardless of how the path was originally resolved. When cachedOpenCodePath is first populated via PATH detection (returning source: 'path'), subsequent cache hits still report source: 'settings'. The same issue exists in detectDroid() (line ~289) and mirrors a pre-existing pattern in detectClaude() and detectCodex().

To fix, cache the source alongside the path:

Suggested change
if (cachedOpenCodePath) {
return { available: true, path: cachedOpenCodePath, source: 'settings' };
}
let cachedOpenCodePath: string | null = null;
let cachedOpenCodeSource: 'settings' | 'path' | null = null;

Then update cache returns to use the cached source. Apply the same pattern to detectDroid().

Copy link

@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/cli/services/agent-spawner.ts (2)

148-175: Good addition of generic command lookup helper.

findCommandInPath nicely consolidates the pattern used by findClaudeInPath and findCodexInPath. Consider refactoring those two functions to use this helper in a future cleanup.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/cli/services/agent-spawner.ts` around lines 148 - 175, Add a one-line
summary: the new helper findCommandInPath centralizes command lookup and should
be reused by existing findClaudeInPath and findCodexInPath; refactor those two
functions to call findCommandInPath(commandName) instead of duplicating spawn
logic, passing the appropriate command names (e.g., "claude" and "codex" or
whatever they currently use), and preserve any special environment handling by
forwarding getExpandedPath via the helper; ensure error/close handling remains
the same and remove the duplicated spawn implementations from findClaudeInPath
and findCodexInPath.

676-687: Type cast as any suggests type mismatch with parser.

The event as any cast indicates the parsed event type doesn't match what extractUsage expects. Consider aligning the types in OpenCodeOutputParser to avoid the cast, or add a type guard.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/cli/services/agent-spawner.ts` around lines 676 - 687, The call to
parser.extractUsage(event as any) masks a type mismatch between the event
variable and the parser's expected input; update the OpenCodeOutputParser type
signature (or the parser instance type) so extractUsage accepts the actual event
type, or add a narrow type guard before calling extractUsage that
validates/casts event to the parser-expected interface; locate the parser
instance and the extractUsage method on OpenCodeOutputParser and either align
their parameter types or implement a function like isOpenCodeOutputEvent(event):
event is OpenCodeOutputEvent and use it before calling parser.extractUsage, then
pass the correctly typed value into mergeUsageStats (usageStats) without using
any casts.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/cli/services/agent-spawner.ts`:
- Around line 148-175: Add a one-line summary: the new helper findCommandInPath
centralizes command lookup and should be reused by existing findClaudeInPath and
findCodexInPath; refactor those two functions to call
findCommandInPath(commandName) instead of duplicating spawn logic, passing the
appropriate command names (e.g., "claude" and "codex" or whatever they currently
use), and preserve any special environment handling by forwarding
getExpandedPath via the helper; ensure error/close handling remains the same and
remove the duplicated spawn implementations from findClaudeInPath and
findCodexInPath.
- Around line 676-687: The call to parser.extractUsage(event as any) masks a
type mismatch between the event variable and the parser's expected input; update
the OpenCodeOutputParser type signature (or the parser instance type) so
extractUsage accepts the actual event type, or add a narrow type guard before
calling extractUsage that validates/casts event to the parser-expected
interface; locate the parser instance and the extractUsage method on
OpenCodeOutputParser and either align their parameter types or implement a
function like isOpenCodeOutputEvent(event): event is OpenCodeOutputEvent and use
it before calling parser.extractUsage, then pass the correctly typed value into
mergeUsageStats (usageStats) without using any casts.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 54d7a12c-fd01-4468-987e-c6aa080e3501

📥 Commits

Reviewing files that changed from the base of the PR and between 58ec09a and f8477a9.

📒 Files selected for processing (3)
  • src/cli/commands/run-playbook.ts
  • src/cli/commands/send.ts
  • src/cli/services/agent-spawner.ts

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.

1 participant