ai-dispatch 2.8.0

Multi-AI CLI team orchestrator
# v2.6 — Efficiency
# Auto quota detection + read-only mode

[[task]]
agent = "codex"
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" (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, after `pub mod pty_watch;` area).

### 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
- `test_expired_marker_not_rate_limited` — verify old markers are ignored (set file mtime to past)

IMPORTANT: Only modify the 5 files listed above. Do NOT touch any other files."""
worktree = "v26-rate-limit"
verify = "auto"
context = ["src/agent/codex.rs:parse_error_event", "src/agent/selection.rs:score_codex,select_agent_from", "src/watcher.rs:watch_streaming", "src/paths.rs", "src/main.rs"]

[[task]]
agent = "codex"
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:
```rust
/// Run in read-only mode (no file writes)
#[arg(long)]
read_only: bool,
```
Pass it through to RunOpts via RunArgs. In the `Commands::Run` handler, set `opts.read_only = read_only` (or pass through RunArgs).

### 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` (there are usages in main.rs Run handler, background.rs, and cmd/run.rs — search for `RunOpts {`).

### 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, add `--mode plan` to the args (before prompt). Remove `--trust` when in read-only mode since plan mode doesn't need it:
```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 to the prompt (same as codex):
```
"IMPORTANT: READ-ONLY MODE. Do NOT modify, create, or delete any files. Only read and analyze.\n\n"
```

### Edit src/background.rs
In `run_task_inner`, add `read_only: false` to the RunOpts construction.

### Edit src/cmd/run.rs
In the RunArgs struct, add `pub read_only: bool`.
In the `run()` function where RunOpts is constructed, set `read_only: args.read_only`.
Also update the `run_args()` helper in tests to include `read_only: false`.

### Tests
Add a test in src/agent/codex.rs that verifies `build_command` prepends the read-only guard when read_only is true.
Add a test in src/agent/cursor.rs that verifies `--mode plan` is used when read_only is true.

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