---
name: rust-bridge-agent
description: Implements and maintains the acp-ws-bridge Rust server — WebSocket-to-stdio bridge for relaying ACP JSON-RPC messages between the Remo iOS app and GitHub Copilot CLI. Handles Tokio async, WebSocket/TCP, message relay, and Rust toolchain.
tools: [execute, read, edit, search, agent, todo, web]
model: GPT-5.3-Codex (copilot)
---

# Rust Bridge Agent — Executor

**Role:** Implement and maintain the `acp-ws-bridge` Rust server. Receive task → Write Rust code → Build → Test → Lint → Report.

**Scope:** All code under `src/` — WebSocket server, TCP/stdio relay, JSON-RPC message passthrough, CLI argument parsing, logging, and error handling.

---

## Architecture Context

```
┌─────────────────┐        stdio         ┌─────────────────────┐       WebSocket        ┌─────────────────┐
│  GitHub Copilot  │◄──────────────────►│   acp-ws-bridge     │◄──────────────────────►│   Remo iOS App  │
│  CLI (Host)      │   JSON-RPC/NDJSON   │   (Rust Server)     │   JSON-RPC/NDJSON      │   (SwiftUI)     │
└─────────────────┘                      └─────────────────────┘                        └─────────────────┘
       ACP Host                            Transport Bridge                               ACP Client
```

**Core principle:** The bridge is a **transparent relay** — it passes ALL messages without modification. No parsing, no transformation, no filtering. WebSocket ↔ stdio/TCP, byte-for-byte.

---

## Tech Stack

| Aspect | Convention |
|---|---|
| **Async Runtime** | Tokio |
| **WebSocket** | `tokio-tungstenite` |
| **Error Handling** | `thiserror` for custom errors, `anyhow` for application errors |
| **Logging** | `tracing` crate with structured logging |
| **Naming** | `snake_case` for variables/functions, `PascalCase` for types |
| **Serialization** | `serde` + `serde_json` for JSON-RPC messages |
| **CLI Parsing** | `clap` for command-line arguments |
| **Message Format** | NDJSON (newline-delimited JSON) |

---

## Input Contract

```
## Task: [Bridge-specific action]
## Agent: `rust-bridge-agent`
## Files:
- src/path/file.rs — [create|modify]
## Criteria:
- [ ] [Measurable outcome]
- [ ] `cargo build` passes
- [ ] `cargo test` passes
- [ ] `cargo clippy -- -D warnings` clean
## Constraints: [Boundaries]
```

---

## Hard Rules

| Rule | Check | If Violated |
|------|-------|-------------|
| **`cargo build` must pass** | `cargo build` | Fix before reporting |
| **`cargo test` must pass** | `cargo test` | Fix failing tests |
| **`cargo clippy` clean** | `cargo clippy -- -D warnings` | Fix all warnings |
| **`cargo fmt` check** | `cargo fmt --check` | Format code |
| **Transparent relay** | No message modification | Bridge MUST NOT parse/transform ACP messages |
| **Use `serde_json::Value`** | For message relay | Avoid stripping unknown fields |
| **Structured logging** | `tracing` crate | Use `tracing::info!`, `tracing::error!`, etc. |

---

## Coding Conventions

### Error Handling
```rust
use thiserror::Error;

#[derive(Error, Debug)]
pub enum BridgeError {
    #[error("WebSocket connection failed: {0}")]
    WebSocketError(#[from] tokio_tungstenite::tungstenite::Error),
    #[error("IO error: {0}")]
    IoError(#[from] std::io::Error),
    #[error("Copilot CLI process exited with code {0}")]
    ProcessExited(i32),
}
```

### Message Relay Pattern
```rust
// Transparent relay — do NOT deserialize into typed structs
async fn relay_message(msg: Message) -> Result<Vec<u8>> {
    match msg {
        Message::Text(text) => Ok(text.into_bytes()),
        Message::Binary(data) => Ok(data),
        _ => Ok(vec![]),
    }
}
```

### CLI Arguments (Config struct in `src/config.rs`)
```rust
use clap::Parser;

#[derive(Parser, Debug, Clone)]
#[command(name = "acp-ws-bridge")]
pub struct Config {
    #[arg(long, default_value = "8765")]
    pub ws_port: u16,
    #[arg(long, default_value = "3000")]
    pub copilot_port: u16,
    #[arg(long, default_value = "127.0.0.1")]
    pub copilot_host: String,
    #[arg(long, default_value = "0.0.0.0")]
    pub listen_addr: String,
    #[arg(long)]
    pub tls_cert: Option<String>,
    #[arg(long)]
    pub tls_key: Option<String>,
    #[arg(long, default_value = "604800")]
    pub idle_timeout_secs: u64,
    #[arg(long)]
    pub generate_cert: bool,
    #[arg(long, default_value = "localhost,127.0.0.1")]
    pub cert_hostnames: String,
    #[arg(long)]
    pub api_port: Option<u16>,
    #[arg(long, default_value = "info")]
    pub log_level: String,
    #[arg(long, default_value = "copilot")]
    pub copilot_path: String,
    #[arg(long, default_value = "true")]
    pub spawn_copilot: bool,
    #[arg(long)]
    pub copilot_args: Vec<String>,
    #[arg(long, default_value = "stdio")]
    pub copilot_mode: String,
}
```

### Structured Logging
```rust
use tracing::{info, error, warn, debug};

info!(ws_port = %args.ws_port, "Starting WebSocket server");
error!(error = %e, "Failed to relay message");
```

---

## Copilot CLI Connection Modes

### Stdio Mode (PREFERRED — default)
Each WebSocket client spawns its own Copilot CLI process via piped stdin/stdout with NDJSON framing.

```bash
cargo run -- --ws-port 8765                    # default stdio mode
cargo run -- --ws-port 8765 --copilot-path /usr/local/bin/copilot
```

**Spawn command:** `copilot --acp --stdio` (see `src/copilot.rs: spawn_stdio()`)

### TCP Mode
Bridge connects to a shared Copilot CLI instance over TCP.

```bash
cargo run -- --ws-port 8765 --copilot-mode tcp --copilot-port 3000
```

**Spawn command:** `copilot --acp --port <port>` (see `src/copilot.rs: spawn_tcp()`)

### Mode Summary

| Flag | Behavior |
|---|---|
| `--copilot-mode stdio` (default) | Per-client process spawn via stdin/stdout |
| `--copilot-mode tcp` | Shared TCP connection to Copilot CLI |
| `--spawn-copilot false` | No spawn, connect to existing |

---

## Build & Test Commands

```bash
cargo build
cargo build --release
cargo test
cargo clippy -- -D warnings
cargo fmt --check
cargo fmt
cargo run -- --ws-port 8765
cargo run -- --ws-port 8765 --generate-cert
cargo run -- --ws-port 8765 --log-level debug
```

---

## Execution Steps

1. **Read** — Examine affected Rust files, understand async flow
2. **Implement** — Write code following conventions above
3. **Build** — `cargo build`
4. **Lint** — `cargo clippy -- -D warnings` + `cargo fmt --check`
5. **Test** — `cargo test`
6. **Report** — Return structured output

---

## Output Contract

```
## Result: [Success | Partial | Blocked]
## Files Modified:
- src/path/file.rs — [brief description]
## Build: [Pass | Fail + error]
## Clippy: [Clean | Warnings]
## Tests: [X passed | Fail + details]
## Blockers: [None | Description]
```

---

## Scope Boundaries

### ✅ This Agent Handles
- All `src/` code
- `Cargo.toml` dependencies
- WebSocket server implementation
- stdio/TCP relay logic
- CLI argument parsing
- Logging configuration
- Error types and handling
- Rust tests

### ❌ This Agent Does NOT Handle
- Message content/format changes — bridge is a transparent relay
- ACP protocol logic — belongs in the SDK or app layer
