ai-dispatch 8.11.0

Multi-AI CLI team orchestrator
# v2.6 — Efficiency (opencode fallback)

[[task]]
agent = "opencode"
prompt = """Add auto rate-limit detection: when codex hits a quota error, automatically enable budget mode for subsequent tasks.

## Design

### New file: src/rate_limit.rs
Create a module with:
- `const RATE_LIMIT_WINDOW_SECS: u64 = 3600;` (1 hour cooldown)
- `fn mark_rate_limited(agent: &AgentKind)` — writes current timestamp to `~/.aid/rate-limit-{agent}` file
- `fn is_rate_limited(agent: &AgentKind) -> bool` — returns true if marker file exists and is less than RATE_LIMIT_WINDOW_SECS old
- `fn is_rate_limit_error(message: &str) -> bool` — returns true if message contains any of: "rate limit", "rate_limit", "429", "quota exceeded", "too many requests", "usage limit" (case insensitive)

Use `crate::paths::aid_dir()` for the marker file location. Use `std::fs::metadata().modified()` for timestamp check.

### Edit src/main.rs
Add `mod rate_limit;` to the module declarations (alphabetical order).

### Edit src/agent/codex.rs
In `parse_error_event`, after creating the error TaskEvent, check if the detail matches `rate_limit::is_rate_limit_error()`. If yes, call `rate_limit::mark_rate_limited(&AgentKind::Codex)`.

Import `use crate::rate_limit;` at the top.

### Edit src/agent/selection.rs
In `score_codex`, add a check: if `crate::rate_limit::is_rate_limited(&AgentKind::Codex)`, subtract 10 from score (same effect as budget mode).

In `select_agent_from`, after computing the reason string, if `crate::rate_limit::is_rate_limited(&AgentKind::Codex)` and the selected agent is not codex, append "; codex rate-limited" to reason.

### Edit src/watcher.rs
In `watch_streaming`, after recording the final error event (when status == TaskStatus::Failed), read the stderr file and check each line with `rate_limit::is_rate_limit_error()`. If found, call `rate_limit::mark_rate_limited(&agent.kind())`.

Import `use crate::rate_limit;` at the top.

### Tests in src/rate_limit.rs
- `test_is_rate_limit_error` — positive and negative cases
- `test_mark_and_check_rate_limited` — write marker, verify is_rate_limited returns true

IMPORTANT: Only modify the 5 files listed above plus the new rate_limit.rs."""
worktree = "v26-rate-limit"
verify = "auto"
context = ["src/agent/codex.rs", "src/agent/selection.rs", "src/watcher.rs", "src/paths.rs", "src/main.rs"]

[[task]]
agent = "opencode"
prompt = """Add --read-only flag to `aid run` for safe research/analysis dispatch.

When --read-only is set, agents should only read and analyze code, not modify it.

## Changes

### Edit src/main.rs
Add `--read-only` flag to the `Run` command variant:
```rust
/// Run in read-only mode (no file writes)
#[arg(long)]
read_only: bool,
```
Pass `read_only` through to RunArgs. In the Commands::Run match arm, add `read_only` to the RunArgs construction.

### Edit src/agent/mod.rs
Add `pub read_only: bool` to the `RunOpts` struct. Update all existing constructions of RunOpts to include `read_only: false`.

### Edit src/agent/codex.rs
In `build_command`, if `opts.read_only` is true, prepend to the prompt:
"IMPORTANT: READ-ONLY MODE. Do NOT modify, create, or delete any files. Only read and analyze.\n\n"

### Edit src/agent/cursor.rs
In `build_command`, if `opts.read_only` is true, use `--mode plan` instead of `--trust`:
```rust
if opts.read_only {
    cmd.args(["agent", "-p", prompt, "--mode", "plan", "--output-format", "stream-json"]);
} else {
    cmd.args(["agent", "-p", prompt, "--trust", "--output-format", "stream-json"]);
}
```

### Edit src/agent/opencode.rs
In `build_command`, if `opts.read_only` is true, prepend the same read-only guard to prompt.

### Edit src/background.rs
In `run_task_inner` where RunOpts is constructed, add `read_only: false`.

### Edit src/cmd/run.rs
Add `pub read_only: bool` to RunArgs struct.
In `run()` where RunOpts is constructed (~line 204), set `read_only: args.read_only`.
In tests `run_args()` helper, add `read_only: false`.

IMPORTANT: Search for ALL RunOpts constructions and update them all. Only modify the files listed above."""
worktree = "v26-read-only"
verify = "auto"
context = ["src/main.rs", "src/agent/mod.rs", "src/agent/codex.rs", "src/agent/cursor.rs", "src/agent/opencode.rs", "src/cmd/run.rs", "src/background.rs"]