Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 18 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,17 +93,30 @@ To check your configured providers:
| `/codex:status` | `/opencode:status` | Show running/recent jobs |
| `/codex:result` | `/opencode:result` | Show finished job output |
| `/codex:cancel` | `/opencode:cancel` | Cancel active background job |
| `/codex:setup` | `/opencode:setup` | Check install/auth, toggle review gate |
| `/codex:setup` | `/opencode:setup` | Check install/auth, configure defaults, toggle review gate |

## Slash Commands

- `/opencode:review` -- Normal OpenCode code review (read-only). Supports `--base <ref>`, `--pr <number>`, `--model <provider/model>`, `--free`, `--wait`, and `--background`.
- `/opencode:adversarial-review` -- Steerable review that challenges implementation and design decisions. Supports `--base <ref>`, `--pr <number>`, `--model <provider/model>`, `--free`, `--wait`, `--background`, and custom focus text.
- `/opencode:rescue` -- Delegates a task to OpenCode via the `opencode:opencode-rescue` subagent. Supports `--model`, `--free`, `--agent`, `--resume`, `--fresh`, `--worktree`, `--wait`, and `--background`.
- `/opencode:review` -- Normal OpenCode code review (read-only). Supports `--base <ref>`, `--pr <number>`, `--model <provider/model>`, `--free`, `--wait`, and `--background`. Uses the saved default model when configured and no runtime model flag is supplied.
- `/opencode:adversarial-review` -- Steerable review that challenges implementation and design decisions. Supports `--base <ref>`, `--pr <number>`, `--model <provider/model>`, `--free`, `--wait`, `--background`, and custom focus text. Uses the saved default model when configured and no runtime model flag is supplied.
- `/opencode:rescue` -- Delegates a task to OpenCode via the `opencode:opencode-rescue` subagent. Supports `--model`, `--free`, `--agent`, `--resume`, `--fresh`, `--worktree`, `--wait`, and `--background`. Uses saved default model/agent values when configured and no runtime flag is supplied.
- `/opencode:status` -- Shows running/recent OpenCode jobs for the current repo.
- `/opencode:result` -- Shows final output for a finished job, including OpenCode session ID for resuming.
- `/opencode:cancel` -- Cancels an active OpenCode job.
- `/opencode:setup` -- Checks OpenCode install/auth, can enable/disable the review gate hook, and can configure review-gate throttles.
- `/opencode:setup` -- Checks OpenCode install/auth, can configure default model/agent values, can enable/disable the review gate hook, and can configure review-gate throttles.

## Command Defaults

Persist model and rescue-agent defaults with setup:

```
/opencode:setup --default-model anthropic/claude-opus-4-6 --default-agent build
/opencode:setup --default-model off
/opencode:setup --default-agent off
```

- `--default-model <provider/model>` applies to `/opencode:review`, `/opencode:adversarial-review`, and `/opencode:rescue` unless a command includes `--model` or `--free`.
- `--default-agent <build|plan>` applies to `/opencode:rescue` unless the command includes `--agent`. Review commands keep using the bundled read-only review agent.

## Review Gate

Expand Down
4 changes: 2 additions & 2 deletions plugins/opencode/agents/opencode-rescue.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ Forwarding rules:
- Do not use that skill to inspect the repository, reason through the problem yourself, draft a solution, or do any independent work beyond shaping the forwarded prompt text.
- Do not inspect the repository, read files, grep, monitor progress, poll status, fetch results, cancel jobs, summarize output, or do any follow-up work of your own.
- Do not call `review`, `adversarial-review`, `status`, `result`, or `cancel`. This subagent only forwards to `task`.
- Leave `--agent` unset unless the user explicitly requests a specific agent (build or plan).
- Leave model unset by default. Only add `--model` or `--free` when the user explicitly asks for a specific model or a free-tier pick. `--free` and `--model` are mutually exclusive.
- Leave `--agent` unset unless the user explicitly requests a specific agent (build or plan). The companion may apply a saved setup default when `--agent` is omitted.
- Leave model unset by default. Only add `--model` or `--free` when the user explicitly asks for a specific model or a free-tier pick. The companion may apply a saved setup default when both flags are omitted. `--free` and `--model` are mutually exclusive.
- Treat `--agent <value>`, `--model <value>`, and `--free` as runtime controls and do not include them in the task text you pass through.
- If the request includes `--worktree`, pass `--worktree` through to `task`. This runs OpenCode in an isolated git worktree instead of editing the working directory in-place.
- Default to a write-capable OpenCode run by adding `--write` unless the user explicitly asks for read-only behavior or only wants review, diagnosis, or research without edits.
Expand Down
2 changes: 1 addition & 1 deletion plugins/opencode/commands/adversarial-review.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ Argument handling:
- Do not strip `--wait`, `--background`, `--model`, `--free`, or `--pr` yourself.
- Adversarial reviews support custom focus text. Any text after flags is treated as a focus area.
- The companion script handles `--adversarial` internally.
- `--model <id>` overrides OpenCode's default model for this single review (e.g. `--model openrouter/anthropic/claude-opus-4-6`). Pass it through verbatim if the user supplied it.
- `--model <id>` overrides the saved setup default model and OpenCode's own default model for this single review (e.g. `--model openrouter/anthropic/claude-opus-4-6`). Pass it through verbatim if the user supplied it.
- `--free` tells the companion script to shell out to `opencode models`, filter for first-party `opencode/*` free-tier models (those ending in `:free` or `-free`), and pick one at random for this review. Restricted to the `opencode/*` provider because OpenRouter free-tier models have inconsistent tool-use support, and the review agent needs `read`/`grep`/`glob`/`list`. Pass it through verbatim if the user supplied it. `--free` and `--model` are mutually exclusive — the companion will error if both are given.
- `--pr <number>` reviews a GitHub pull request via `gh pr diff` instead of the local working tree. The cwd must be a git repo whose remote points at the PR's repository, and `gh` must be installed and authenticated.

Expand Down
2 changes: 1 addition & 1 deletion plugins/opencode/commands/rescue.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ Execution mode:
- If the request includes `--wait`, run the `opencode:opencode-rescue` subagent in the foreground.
- If neither flag is present, default to foreground.
- `--background` and `--wait` are execution flags for Claude Code. Do not forward them to `task`, and do not treat them as part of the natural-language task text.
- `--model`, `--free`, and `--agent` are runtime-selection flags. Preserve them for the forwarded `task` call, but do not treat them as part of the natural-language task text. `--free` tells the companion to pick a random first-party `opencode/*` free-tier model from `opencode models`; it is restricted to `opencode/*` because OpenRouter free models have inconsistent tool-use support. `--free` is mutually exclusive with `--model`.
- `--model`, `--free`, and `--agent` are runtime-selection flags. Preserve them for the forwarded `task` call, but do not treat them as part of the natural-language task text. If `--model`/`--free` or `--agent` are omitted, companion-level defaults configured by `/opencode:setup` may apply. `--free` tells the companion to pick a random first-party `opencode/*` free-tier model from `opencode models`; it is restricted to `opencode/*` because OpenRouter free models have inconsistent tool-use support. `--free` is mutually exclusive with `--model`.
- `--worktree` is an isolation flag. Preserve it for the forwarded `task` call, but do not treat it as part of the natural-language task text. When present, OpenCode runs in an isolated git worktree instead of editing the working directory in-place.
- If the request includes `--resume`, do not ask whether to continue. The user already chose.
- If the request includes `--fresh`, do not ask whether to continue. The user already chose.
Expand Down
2 changes: 1 addition & 1 deletion plugins/opencode/commands/review.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ Argument handling:
- The companion script parses `--wait` and `--background`, but Claude Code's `Bash(..., run_in_background: true)` is what actually detaches the run.
- `/opencode:review` is native-review only. It does not support staged-only review, unstaged-only review, or extra focus text.
- If the user needs custom review instructions or more adversarial framing, they should use `/opencode:adversarial-review`.
- `--model <id>` overrides OpenCode's default model for this single review (e.g. `--model openrouter/anthropic/claude-opus-4-6`). Pass it through verbatim if the user supplied it.
- `--model <id>` overrides the saved setup default model and OpenCode's own default model for this single review (e.g. `--model openrouter/anthropic/claude-opus-4-6`). Pass it through verbatim if the user supplied it.
- `--free` tells the companion script to shell out to `opencode models`, filter for first-party `opencode/*` free-tier models (those ending in `:free` or `-free`), and pick one at random for this review. Restricted to the `opencode/*` provider because OpenRouter free-tier models have inconsistent tool-use support, and the review agent needs `read`/`grep`/`glob`/`list`. Pass it through verbatim if the user supplied it. `--free` and `--model` are mutually exclusive — the companion will error if both are given.
- `--pr <number>` reviews a GitHub pull request via `gh pr diff` instead of the local working tree. The cwd must be a git repo whose remote points at the PR's repository, and `gh` must be installed and authenticated. Pass it through verbatim if the user supplied it.
- **PR reference extraction (REQUIRED)**: if the user's input contains a PR reference like `PR #390`, `pr #390`, `PR 390`, or `pr 390` (e.g. `/opencode:review on PR #390`), you MUST extract the number yourself and pass it as `--pr 390`. Do not pass `PR #390` literally to bash — bash strips unquoted `#NNN` tokens as comments before they reach the companion script. Example: `node ... review --pr 390`, NOT `node ... review on PR #390`.
Expand Down
6 changes: 4 additions & 2 deletions plugins/opencode/commands/setup.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
description: Check whether the local OpenCode CLI is ready and optionally toggle the stop-time review gate
argument-hint: '[--enable-review-gate|--disable-review-gate] [--review-gate-max <n|off>] [--review-gate-cooldown <minutes|off>]'
description: Check whether the local OpenCode CLI is ready, configure defaults, and optionally toggle the stop-time review gate
argument-hint: '[--default-model <provider/model|off>] [--default-agent <build|plan|off>] [--enable-review-gate|--disable-review-gate] [--review-gate-max <n|off>] [--review-gate-cooldown <minutes|off>]'
allowed-tools: Bash(node:*), Bash(npm:*), Bash(brew:*), Bash(curl:*), AskUserQuestion
---

Expand Down Expand Up @@ -39,3 +39,5 @@ Output rules:
- Present the final setup output to the user.
- If installation was skipped, present the original setup output.
- If OpenCode is installed but no provider is configured, guide the user to run `!opencode providers` to set up authentication.
- `--default-model <provider/model>` sets the model used by `/opencode:review`, `/opencode:adversarial-review`, and `/opencode:rescue` when no `--model` or `--free` flag is supplied. Use `--default-model off` to clear it.
- `--default-agent <build|plan>` sets the rescue/task agent used when no `--agent` flag is supplied. Review commands keep using the bundled read-only review agent. Use `--default-agent off` to clear it.
94 changes: 94 additions & 0 deletions plugins/opencode/scripts/lib/defaults.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// Persistent command-default helpers for the OpenCode companion.
//
// `/opencode:setup` can persist defaults in `state.config.defaults`.
// These helpers keep precedence rules centralized and testable:
// explicit runtime flags win, otherwise saved defaults apply.

import { parseModelString } from "./model.mjs";

const SUPPORTED_DEFAULT_AGENTS = new Set(["build", "plan"]);

/**
* @param {Record<string, unknown>|undefined|null} options
* @param {string} key
* @returns {boolean}
*/
export function hasOwnOption(options, key) {
return Object.prototype.hasOwnProperty.call(options ?? {}, key);
}

/**
* Normalize persisted defaults read from state. Invalid or missing values are
* ignored so a hand-edited state file cannot break every command invocation.
* @param {unknown} raw
* @returns {{ model: string | null, agent: string | null }}
*/
export function normalizeDefaults(raw) {
const defaults = raw && typeof raw === "object" ? raw : {};

const modelRaw = typeof defaults.model === "string" ? defaults.model.trim() : "";
const model = modelRaw && parseModelString(modelRaw) ? modelRaw : null;

const agentRaw = typeof defaults.agent === "string" ? defaults.agent.trim() : "";
const agent = SUPPORTED_DEFAULT_AGENTS.has(agentRaw) ? agentRaw : null;

return { model, agent };
}

/**
* Parse a `/opencode:setup --default-model` value. Returns null for "off".
* @param {unknown} value
* @returns {string | null}
*/
export function parseDefaultModelSetting(value) {
const raw = typeof value === "string" ? value.trim() : "";
if (raw === "off") return null;
if (!raw || !parseModelString(raw)) {
throw new Error(
`--default-model must be "off" or a provider/model-id value ` +
`(e.g. anthropic/claude-opus-4-6).`
);
}
return raw;
}

/**
* Parse a `/opencode:setup --default-agent` value. Returns null for "off".
* @param {unknown} value
* @returns {"build" | "plan" | null}
*/
export function parseDefaultAgentSetting(value) {
const raw = typeof value === "string" ? value.trim() : "";
if (raw === "off") return null;
if (!SUPPORTED_DEFAULT_AGENTS.has(raw)) {
throw new Error(`--default-agent must be "build", "plan", or "off".`);
}
return raw;
}

/**
* Apply a persisted model default when the user did not explicitly supply
* either `--model` or `--free`.
* @param {Record<string, unknown>} options
* @param {{ model?: string | null }} defaults
* @returns {Record<string, unknown>}
*/
export function applyDefaultModelOptions(options, defaults) {
if (hasOwnOption(options, "model") || options?.free) return options;
if (!defaults?.model) return options;
return { ...options, model: defaults.model };
}

/**
* Resolve the task agent using explicit CLI args first, then persisted
* defaults, then the existing write/read-only fallback.
* @param {Record<string, unknown>} options
* @param {{ agent?: string | null }} defaults
* @param {boolean} isWrite
* @returns {string}
*/
export function resolveTaskAgentName(options, defaults, isWrite) {
if (hasOwnOption(options, "agent")) return options.agent;
if (defaults?.agent) return defaults.agent;
return isWrite ? "build" : "plan";
}
4 changes: 4 additions & 0 deletions plugins/opencode/scripts/lib/render.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,10 @@ export function renderSetup(status) {
} else if (status.installed) {
lines.push(`- **Providers**: None configured. Run \`!opencode providers\` to set up.`);
}
if (status.defaults) {
lines.push(`- **Default Model**: ${status.defaults.model ? `\`${status.defaults.model}\`` : "Unset"}`);
lines.push(`- **Default Agent**: ${status.defaults.agent ? `\`${status.defaults.agent}\`` : "Unset"}`);
}
if (status.reviewGate !== undefined) {
const parts = [status.reviewGate ? "Enabled" : "Disabled"];
if (status.reviewGateMaxPerSession != null) {
Expand Down
Loading