Skip to content
Merged
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: 4 additions & 19 deletions codi-rs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

Rust implementation of Codi - Your AI coding wingman.

## 🚨 **ALL PHASES COMPLETE!** 🚨
## Status

The entire Rust implementation of Codi is now **feature-complete**! All roadmap phases have been successfully implemented and integrated.
Core feature parity with the TypeScript CLI is in place, and ongoing work is tracked in `docs/ROADMAP.md`.

### What's Now Complete:

Expand All @@ -24,24 +24,9 @@ The entire Rust implementation of Codi is now **feature-complete**! All roadmap

✅ **Multi-Agent Orchestration** - Git worktree-based parallel workers with IPC permission bubbling

✅ **Test Suite** - Comprehensive 440+ test suite ensuring reliability across all components
✅ **Test Suite** - Comprehensive 500+ test suite ensuring reliability across all components

## Status: All Phases Complete ✅

The migration roadmap has been successfully completed:

| Phase | Description | Status |
|-------|-------------|--------|
| **0** | Foundation - types, errors, config, CLI shell | ✅ Complete |
| **1** | Tool layer - file tools, grep, glob, bash | ✅ Complete |
| **2** | Provider layer - Anthropic, OpenAI, Ollama | ✅ Complete |
| **3** | Agent loop - core agentic orchestration | ✅ Complete |
| **4** | Symbol index - tree-sitter based code navigation | ✅ Complete |
| **5** | RAG system - vector search with embeddings | ✅ Complete |
| **6** | Terminal UI - ratatui based interface | ✅ Complete |
| **7** | Multi-agent - IPC-based worker orchestration | ✅ Complete |

This release marks full feature parity with the TypeScript implementation, ending the migration period.
## Phase Status

| Phase | Description | Status |
|-------|-------------|--------|
Expand Down
64 changes: 64 additions & 0 deletions codi-rs/docs/ROADMAP.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# Codi-RS Roadmap

This roadmap focuses on the Rust CLI (`codi-rs`) and its TUI/orchestration stack. It complements the broader Codi roadmap in `docs/ROADMAP.md`.

## Status (2026-02-06)

- Core parity with the TypeScript CLI is in place (agent loop, tools, providers, symbol index, RAG, TUI, multi-agent).
- Remaining work clusters around cross-platform support, orchestration robustness, and TUI workflow polish.

## P0: Stability and Cross-Platform Foundations

1) Cross-platform IPC for multi-agent
- Replace Unix domain sockets with an IPC abstraction.
- Implement Windows named pipes (or a transport-agnostic layer).
- Ensure commander/worker handshake is deterministic with explicit timeouts.

2) Cancellation and lifecycle correctness
- Wire the TUI cancel flow to actual worker cancellation.
- Track tool_count and token usage for child agents.
- Add tests for cancellation and reconnection scenarios.

3) Windows support parity
- Audit file/path handling and shell execution behavior.
- Add Windows-specific tests for tool execution and config loading.
- Ensure multi-agent mode degrades gracefully when unsupported.

## P1: Workflow and Model UX

1) TUI workflow improvements
- Context summarization for long sessions.
- Model listing and switching from the TUI.
- Display active provider/model in session header.
- Worktree list/explorer surfaced in the TUI.

2) Model map integration
- Connect embeddings selection to model_map configuration.
- Expose errors and misconfigurations in `codi models` output.

## P2: Indexing and Retrieval Quality

1) Symbol index maintenance
- Cleanup of deleted/renamed files in the index.
- Usage detection and dependency graph traversal.

2) RAG reliability and performance
- Safer incremental index updates.
- Caching and pooling of embedding providers.

3) Syntax highlighting polish
- Upgrade tree-sitter-markdown when dependency compatibility allows.

## P3: Security and Observability

1) Execution policy improvements
- Extend dangerous pattern handling to a configurable policy engine.
- Safer defaults in multi-agent auto-approve scenarios.

2) Telemetry and diagnostics
- Surface per-worker metrics and error summaries in the TUI.

## Notes

- This roadmap prioritizes correctness and portability; features should not regress Windows support.
- Items are grouped by priority, not by release version.
8 changes: 6 additions & 2 deletions codi-rs/src/orchestrate/child_agent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ pub struct ChildAgent {
workspace: WorkspaceInfo,
/// Auto-approved tools from handshake.
auto_approve: Vec<String>,
/// Dangerous patterns from handshake.
dangerous_patterns: Vec<String>,
/// Timeout from handshake.
timeout_ms: u64,
}
Expand Down Expand Up @@ -107,13 +109,15 @@ impl ChildAgent {

let ipc = Arc::new(Mutex::new(ipc));
let auto_approve = ack.auto_approve.clone();
let dangerous_patterns = ack.dangerous_patterns.clone();

// Create agent
let mut child_agent = Self {
ipc: Arc::clone(&ipc),
config,
workspace,
auto_approve,
dangerous_patterns,
timeout_ms: ack.timeout_ms,
};

Expand Down Expand Up @@ -183,7 +187,7 @@ impl ChildAgent {
let callbacks = AgentCallbacks {
on_confirm: Some(Arc::new(move |confirmation: ToolConfirmation| {
// Check auto-approve list
if auto_approve.contains(&confirmation.tool_name) {
if !confirmation.is_dangerous && auto_approve.contains(&confirmation.tool_name) {
return ConfirmationResult::Approve;
}

Expand Down Expand Up @@ -241,7 +245,7 @@ impl ChildAgent {
extract_tools_from_text: true,
auto_approve_all: false,
auto_approve_tools: self.auto_approve.clone(),
dangerous_patterns: Vec::new(),
dangerous_patterns: self.dangerous_patterns.clone(),
};

let mut agent = Agent::new(AgentOptions {
Expand Down
15 changes: 14 additions & 1 deletion codi-rs/src/orchestrate/commander.rs
Original file line number Diff line number Diff line change
Expand Up @@ -271,8 +271,21 @@ impl Commander {
.unwrap_or(300_000)
};

let dangerous_patterns = {
let workers = workers.read().await;
workers
.get(&worker_id)
.map(|w| w.config.dangerous_patterns.clone())
.unwrap_or_default()
};

// Send ack
let ack = CommanderMessage::handshake_ack(true, auto_approve, timeout_ms);
let ack = CommanderMessage::handshake_ack(
true,
auto_approve,
dangerous_patterns,
timeout_ms
);
if let Err(e) = self.server.send(&worker_id, &ack).await {
error!("Failed to send handshake ack: {}", e);
}
Expand Down
98 changes: 91 additions & 7 deletions codi-rs/src/orchestrate/ipc/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,16 @@ pub enum IpcClientError {
/// Handshake acknowledgment from commander.
#[derive(Debug, Clone)]
pub struct HandshakeAck {
/// Whether the handshake was accepted.
pub accepted: bool,
/// Tools that can be auto-approved.
pub auto_approve: Vec<String>,
/// Dangerous patterns for tool inputs.
pub dangerous_patterns: Vec<String>,
/// Timeout in milliseconds.
pub timeout_ms: u64,
/// Optional rejection reason.
pub reason: Option<String>,
}

/// Pending permission request.
Expand All @@ -81,6 +87,8 @@ pub struct IpcClient {
cancel_tx: Option<mpsc::Sender<()>>,
/// Whether we've been cancelled.
cancelled: Arc<Mutex<bool>>,
/// Latest handshake acknowledgement.
handshake_ack: Arc<Mutex<Option<HandshakeAck>>>,
}

impl IpcClient {
Expand All @@ -93,6 +101,7 @@ impl IpcClient {
pending_permissions: Arc::new(Mutex::new(HashMap::new())),
cancel_tx: None,
cancelled: Arc::new(Mutex::new(false)),
handshake_ack: Arc::new(Mutex::new(None)),
}
}

Expand All @@ -109,6 +118,8 @@ impl IpcClient {
let (cancel_tx, mut cancel_rx) = mpsc::channel::<()>(1);
self.cancel_tx = Some(cancel_tx);

let handshake_ack = Arc::clone(&self.handshake_ack);

tokio::spawn(async move {
let mut reader = BufReader::new(read_half);
let mut line = String::new();
Expand All @@ -123,7 +134,12 @@ impl IpcClient {
}
Ok(_) => {
if let Ok(msg) = decode::<CommanderMessage>(&line) {
Self::handle_commander_message(msg, &pending, &cancelled).await;
Self::handle_commander_message(
msg,
&pending,
&cancelled,
&handshake_ack
).await;
}
line.clear();
}
Expand All @@ -150,8 +166,26 @@ impl IpcClient {
msg: CommanderMessage,
pending: &Arc<Mutex<HashMap<String, PendingPermission>>>,
cancelled: &Arc<Mutex<bool>>,
handshake_ack: &Arc<Mutex<Option<HandshakeAck>>>,
) {
match msg {
CommanderMessage::HandshakeAck {
accepted,
auto_approve,
dangerous_patterns,
timeout_ms,
reason,
..
} => {
let mut ack = handshake_ack.lock().await;
*ack = Some(HandshakeAck {
accepted,
auto_approve,
dangerous_patterns,
timeout_ms,
reason,
});
}
CommanderMessage::PermissionResponse { request_id, result, .. } => {
let mut pending = pending.lock().await;
if let Some(req) = pending.remove(&request_id) {
Expand Down Expand Up @@ -202,13 +236,63 @@ impl IpcClient {
writer.write_all(encoded.as_bytes()).await?;
writer.flush().await?;

// Wait for handshake ack (with timeout)
// Note: The actual ack comes through the reader task, but for simplicity
// we'll just return the config values
Ok(HandshakeAck {
auto_approve: config.auto_approve.clone(),
timeout_ms: config.timeout_ms,
let ack = self
.wait_for_handshake_ack(Duration::from_secs(2))
.await;

if let Some(ack) = ack {
if !ack.accepted {
return Err(IpcClientError::HandshakeFailed(
ack.reason.unwrap_or_else(|| "Handshake rejected".to_string())
));
}

// If commander didn't provide values, fall back to local config
let auto_approve = if ack.auto_approve.is_empty() {
config.auto_approve.clone()
} else {
ack.auto_approve
};
let dangerous_patterns = if ack.dangerous_patterns.is_empty() {
config.dangerous_patterns.clone()
} else {
ack.dangerous_patterns
};
let timeout_ms = if ack.timeout_ms == 0 { config.timeout_ms } else { ack.timeout_ms };

Ok(HandshakeAck {
accepted: true,
auto_approve,
dangerous_patterns,
timeout_ms,
reason: None,
})
} else {
warn!("Handshake ack not received; using local config defaults");
Ok(HandshakeAck {
accepted: true,
auto_approve: config.auto_approve.clone(),
dangerous_patterns: config.dangerous_patterns.clone(),
timeout_ms: config.timeout_ms,
reason: None,
})
}
}

async fn wait_for_handshake_ack(&self, timeout: Duration) -> Option<HandshakeAck> {
match tokio::time::timeout(timeout, async {
loop {
if let Some(ack) = self.handshake_ack.lock().await.take() {
return ack;
}
tokio::time::sleep(Duration::from_millis(10)).await;
}
})
.await
{
Ok(ack) => Some(ack),
Err(_) => None,
}
}

/// Request permission for a tool operation.
Expand Down
18 changes: 16 additions & 2 deletions codi-rs/src/orchestrate/ipc/protocol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,8 @@ pub enum CommanderMessage {
accepted: bool,
/// Tools to auto-approve.
auto_approve: Vec<String>,
/// Dangerous patterns for tool inputs.
dangerous_patterns: Vec<String>,
/// Timeout in milliseconds.
timeout_ms: u64,
/// Rejection reason (if not accepted).
Expand Down Expand Up @@ -377,12 +379,18 @@ pub enum PermissionResult {

impl CommanderMessage {
/// Create a handshake acknowledgment.
pub fn handshake_ack(accepted: bool, auto_approve: Vec<String>, timeout_ms: u64) -> Self {
pub fn handshake_ack(
accepted: bool,
auto_approve: Vec<String>,
dangerous_patterns: Vec<String>,
timeout_ms: u64
) -> Self {
Self::HandshakeAck {
id: generate_message_id(),
timestamp: now(),
accepted,
auto_approve,
dangerous_patterns,
timeout_ms,
reason: None,
}
Expand All @@ -395,6 +403,7 @@ impl CommanderMessage {
timestamp: now(),
accepted: false,
auto_approve: Vec::new(),
dangerous_patterns: Vec::new(),
timeout_ms: 0,
reason: Some(reason.into()),
}
Expand Down Expand Up @@ -577,7 +586,12 @@ mod tests {

#[test]
fn test_commander_messages() {
let ack = CommanderMessage::handshake_ack(true, vec!["read_file".to_string()], 60000);
let ack = CommanderMessage::handshake_ack(
true,
vec!["read_file".to_string()],
vec![],
60000
);
assert!(ack.is_handshake_ack());

let cancel = CommanderMessage::cancel(Some("User requested".to_string()));
Expand Down
Loading