Skip to content

feat: OpenCode runner support#396

Open
lukeinglis wants to merge 3 commits into
mainfrom
qwen-runner-support
Open

feat: OpenCode runner support#396
lukeinglis wants to merge 3 commits into
mainfrom
qwen-runner-support

Conversation

@lukeinglis
Copy link
Copy Markdown
Collaborator

@lukeinglis lukeinglis commented May 27, 2026

Summary

Closes #394

  • Add factory/runners/opencode.py implementing the Runner protocol
  • Uses 'opencode run' subcommand pattern with prompt prepended to task message
  • --dangerously-skip-permissions gated on parameter (not unconditional)
  • Dry-run mode via FACTORY_OPENCODE_DRY_RUN=1
  • Register 'opencode' in RunnerName and _RUNNERS registry
  • Update CLAUDE.md with OpenCode runner docs and config profile example
  • Tests in tests/test_opencode_runner.py

Usage

factory ceo /path --runner opencode
factory agent researcher --task '...' --runner opencode
FACTORY_RUNNER=opencode factory ceo /path

Config profile:

[credentials.opencode]
FACTORY_RUNNER = "opencode"

Test plan

  • uv run pytest tests/test_opencode_runner.py -v passes
  • factory ceo /path --runner opencode resolves to OpenCodeRunner
  • Dry-run mode returns stub without spending tokens

Add QwenRunner as a fourth CLI backend alongside Claude Code, Bob Shell,
and Codex. Qwen Code's CLI flags mirror Claude Code (--append-system-prompt,
-p, --model), making it the simplest runner addition.

Key details:
- Auth: warns if DASHSCOPE_API_KEY/QWEN_API_KEY missing (non-blocking)
- Headless: uses --yolo and --output-format text
- Dry-run: FACTORY_QWEN_DRY_RUN=1 returns stub responses
- Env: strips VIRTUAL_ENV from subprocess environment
- 22 tests covering command construction, auth, dry-run, env, and errors

Signed-off-by: Luke Inglis <lukeinglis21@yahoo.com>
Signed-off-by: Luke Inglis <lukeinglis21@yahoo.com>
@codecov
Copy link
Copy Markdown

codecov Bot commented May 27, 2026

Codecov Report

❌ Patch coverage is 92.85714% with 5 lines in your changes missing coverage. Please review.
✅ Project coverage is 87.43%. Comparing base (8097461) to head (641dc58).
⚠️ Report is 17 commits behind head on main.

Files with missing lines Patch % Lines
factory/runners/opencode.py 92.64% 5 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #396      +/-   ##
==========================================
+ Coverage   87.31%   87.43%   +0.12%     
==========================================
  Files          61       62       +1     
  Lines        9339     9472     +133     
==========================================
+ Hits         8154     8282     +128     
- Misses       1185     1190       +5     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link
Copy Markdown
Collaborator

@xukai92 xukai92 left a comment

Choose a reason for hiding this comment

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

Clean addition that follows the existing Claude/Codex runner pattern with dry-run + auth-warn parity and good test coverage. One small contract bug noted below — happy to approve once gated (or as a quick follow-up PR).

[P2] dangerously_skip_permissions is silently ignored in QwenRunner.headless

factory/runners/qwen.py around the headless method:

        dangerously_skip_permissions: bool = True,
        ...
        cmd = [
            "qwen",
            "--append-system-prompt", prompt,
            "-p", task,
            "--yolo",
            "--output-format", "text",
        ]

The signature accepts dangerously_skip_permissions: bool = True to match the Runner protocol used by Claude/Codex/Bob, but --yolo is appended unconditionally. interactive_run in the same file gates this flag correctly, and the sibling runners all gate their equivalents. A caller that explicitly passes dangerously_skip_permissions=False to disable auto-approval will still get --yolo, bypassing all permission prompts.

The default is True so current call sites are unaffected, but the contract is broken for any future caller.

Suggested fix:

        cmd = [
            "qwen",
            "--append-system-prompt", prompt,
            "-p", task,
            "--output-format", "text",
        ]
        if dangerously_skip_permissions:
            cmd.append("--yolo")

Worth adding a dangerously_skip_permissions=False test in the headless suite — there's already an equivalent for interactive_run.

- Add factory/runners/opencode.py implementing the Runner protocol
- Uses 'opencode run' subcommand pattern with prompt prepended to task
- --dangerously-skip-permissions gated on parameter (not unconditional)
- Dry-run mode via FACTORY_OPENCODE_DRY_RUN=1
- Register 'opencode' in RunnerName and _RUNNERS registry
- Update CLAUDE.md with OpenCode runner docs and config profile
- Tests in tests/test_opencode_runner.py (20 tests)
- Remove factory/runners/qwen.py and tests/test_qwen_runner.py

Signed-off-by: Luke Inglis <linglis@redhat.com>
Signed-off-by: Luke Inglis <lukeinglis21@yahoo.com>
@lukeinglis lukeinglis changed the title feat: Qwen Code runner support feat: OpenCode runner support May 28, 2026
@lukeinglis
Copy link
Copy Markdown
Collaborator Author

Thanks for the review @xukai92. Heads up: this PR has been significantly reworked since your review.

Scope change: Replaced the Qwen Code runner with an OpenCode runner (opencode.ai). The runner pattern and test structure remain the same, but the underlying CLI mapping is different.

Your P2 is addressed: --dangerously-skip-permissions is now gated on the parameter in both headless() and interactive_run(), matching how ClaudeRunner handles it. A dangerously_skip_permissions=False test is included for both methods.

Key implementation details:

  • OpenCode has no --append-system-prompt, so the role prompt is prepended to the task message
  • Headless uses opencode run --dir --format default
  • Interactive uses opencode run --interactive --dir
  • No auth warning needed since OpenCode handles provider auth internally via opencode auth login

Files changed: factory/runners/opencode.py (new), tests/test_opencode_runner.py (new, 20 tests passing), factory/runners/init.py, CLAUDE.md. Old qwen.py and test_qwen_runner.py are deleted.

Ready for re-review when you get a chance.

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.

feat: Qwen Code runner support

2 participants