🛰️ export from upstream (56c5a2dd)
This commit is contained in:
@@ -6,6 +6,7 @@
|
||||
|
||||
<p align="center">Core libraries for the Dirigent agent orchestration platform.</p>
|
||||
|
||||
> [!CAUTION]
|
||||
> **Alpha software.** Dirigent is in early active development and not fully battle-tested. Most crates are experimental — APIs will change without notice. There is nothing to install from this repository yet. The standalone tools listed below have their own repositories and maturity levels.
|
||||
|
||||
---
|
||||
@@ -79,6 +80,7 @@ dirigent_core = { git = "https://git.g4b.org/dirigence/dirigent", path = "cr
|
||||
|
||||
Replace the crate name and path with the one you need. All crates follow the same pattern.
|
||||
|
||||
> [!CAUTION]
|
||||
> **Expect breakage.** These are internal library crates under active development. Pin to a specific commit if you depend on stability.
|
||||
|
||||
---
|
||||
|
||||
@@ -1,124 +0,0 @@
|
||||
# Package: dirigent_acp_api
|
||||
|
||||
ACP Server implementation for accepting incoming ACP connections from external agents.
|
||||
|
||||
## Quick Facts
|
||||
- **Type**: Library
|
||||
- **Main Entry**: src/lib.rs
|
||||
- **Dependencies**: axum, tokio, serde, tracing, uuid, async-trait, dirigent_protocol
|
||||
- **Status**: Core structure complete, integration with CoreRuntime pending
|
||||
|
||||
## Overview
|
||||
|
||||
The `dirigent_acp_api` package implements an ACP (Agent-Client Protocol) server that allows Dirigent to accept incoming connections from external ACP clients like Claude Code or custom agents. This enables session sharing, remote orchestration, and multi-client collaboration.
|
||||
|
||||
## Architecture
|
||||
|
||||
### Core Components
|
||||
|
||||
- **config.rs** - Server configuration types (`AcpServerConfig`)
|
||||
- **error.rs** - Error types (`AcpServerError`, `JsonRpcErrorObject`)
|
||||
- **jsonrpc.rs** - JSON-RPC 2.0 types and parsing
|
||||
- **rpc.rs** - RPC handler and method dispatch
|
||||
- **session_manager.rs** - Session/client tracking (TODO)
|
||||
- **sse.rs** - SSE notification system (TODO)
|
||||
- **event_bridge.rs** - Event translation (TODO)
|
||||
|
||||
### Key Types
|
||||
|
||||
```rust
|
||||
pub struct AcpServerConfig {
|
||||
pub enabled: bool, // Enable/disable server
|
||||
pub port: u16, // Listen port (default: 3001)
|
||||
pub allowed_origins: Option<Vec<String>>, // CORS origins
|
||||
pub max_connections: usize, // Connection limit (default: 100)
|
||||
}
|
||||
```
|
||||
|
||||
### ConnectorOperations Trait
|
||||
|
||||
The RPC handler uses a trait abstraction to avoid circular dependencies with dirigent_core:
|
||||
|
||||
```rust
|
||||
#[async_trait]
|
||||
pub trait ConnectorOperations: Send + Sync {
|
||||
async fn create_session(&self, connector_id: &str) -> Result<String>;
|
||||
async fn load_session(&self, connector_id: &str, session_id: &str) -> Result<Session>;
|
||||
async fn send_prompt(&self, connector_id: &str, session_id: &str, prompt: &str) -> Result<String>;
|
||||
// ... more methods
|
||||
}
|
||||
```
|
||||
|
||||
## API Endpoints
|
||||
|
||||
### POST `/rpc`
|
||||
|
||||
JSON-RPC 2.0 endpoint supporting:
|
||||
- `initialize` - Client handshake
|
||||
- `session/new` - Create session
|
||||
- `session/load` - Load existing session
|
||||
- `session/prompt` - Send prompt
|
||||
- `session/cancel` - Cancel generation
|
||||
- `session/close` - Close session
|
||||
|
||||
### GET `/events`
|
||||
|
||||
Server-Sent Events for streaming notifications:
|
||||
- `acp/messageChunk` - Streaming content
|
||||
- `acp/messageComplete` - Generation complete
|
||||
- `acp/sessionIdle` - Ready for input
|
||||
|
||||
### GET `/health`
|
||||
|
||||
Health check endpoint.
|
||||
|
||||
## Configuration UI
|
||||
|
||||
The ACP Server is configured via the web UI at **Configuration > ACP Server**:
|
||||
|
||||
- Enable/disable toggle
|
||||
- Port configuration
|
||||
- Max connections limit
|
||||
- Allowed origins (CORS)
|
||||
- Default connector selection
|
||||
- Connected clients management
|
||||
|
||||
Server functions in `crates/api/src/acp_server.rs` bridge the UI and this package.
|
||||
|
||||
## Implementation Status
|
||||
|
||||
**Completed:**
|
||||
- Configuration types (`AcpServerConfig`)
|
||||
- Error types (`AcpServerError`)
|
||||
- JSON-RPC types and parsing
|
||||
- RPC handler structure with ConnectorOperations trait
|
||||
|
||||
**Pending:**
|
||||
- Session Manager implementation
|
||||
- SSE Notifier implementation
|
||||
- Event Bridge implementation
|
||||
- Axum router integration
|
||||
- Web server integration
|
||||
|
||||
## Key Files
|
||||
|
||||
| File | Description |
|
||||
|------|-------------|
|
||||
| `src/lib.rs` | Module exports and router creation |
|
||||
| `src/config.rs` | AcpServerConfig with validation |
|
||||
| `src/error.rs` | Error types and codes |
|
||||
| `src/jsonrpc.rs` | JSON-RPC 2.0 implementation |
|
||||
| `src/rpc.rs` | RPC handler and method dispatch |
|
||||
|
||||
## Related Packages
|
||||
|
||||
- **dirigent_core** - Provides CoreHandle implementation of ConnectorOperations
|
||||
- **dirigent_protocol** - Shared event and message types
|
||||
- **api** - Server functions for UI configuration
|
||||
- **web** - Configuration UI components
|
||||
|
||||
## Documentation
|
||||
|
||||
- **Architecture**: `docs/architecture/acp_server.md`
|
||||
- **Configuration**: `docs/configuration/acp-connectors.md`
|
||||
- **Tasks**: `docs/building/07_acp_serve/02_acp_server_tasks.md`
|
||||
@@ -1,22 +0,0 @@
|
||||
# dirigent_config
|
||||
|
||||
Platform-native configuration and data path resolution.
|
||||
|
||||
## Purpose
|
||||
Provides `DirigentPaths` for resolving config/data directories across Linux, macOS, Windows.
|
||||
Creates a symlink on Linux/macOS from config_dir/data -> data_dir for discoverability.
|
||||
|
||||
## Key Types
|
||||
- `DirigentPaths` -- resolved config_dir + data_dir with convenience methods
|
||||
- `ConfigPathError` -- error enum for path resolution failures
|
||||
|
||||
## Usage
|
||||
```rust
|
||||
let paths = DirigentPaths::resolve()?;
|
||||
paths.ensure_dirs()?; // creates dirs + symlink
|
||||
let config = paths.config_file(); // ~/.config/dirigent/dirigent.toml
|
||||
```
|
||||
|
||||
## Dependencies
|
||||
- `dirs` -- cross-platform directory resolution
|
||||
- Zero UI dependency -- used by core, archivist, zed crates
|
||||
@@ -1,616 +0,0 @@
|
||||
# Package: dirigent_core
|
||||
|
||||
Core orchestration engine for multi-connector agent system management.
|
||||
|
||||
## Quick Facts
|
||||
- **Type**: Library
|
||||
- **Main Entry**: src/lib.rs
|
||||
- **Dependencies**: dirigent_protocol, tokio, axum, serde, uuid
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
The dirigent_core package provides a **runtime-based architecture** for managing long-lived connections to external agent systems (OpenCode.ai, ACP agents, etc.). The core abstraction is the **Connector**, which represents a bidirectional communication channel to an agent system.
|
||||
|
||||
### Core Components
|
||||
|
||||
#### CoreRuntime
|
||||
The central orchestrator for managing connectors. It maintains:
|
||||
- Registry of active connectors (keyed by ConnectorId)
|
||||
- Global event broadcast channel for system-wide events
|
||||
- User registry for ownership and authorization
|
||||
- Configuration state (with persistence support)
|
||||
|
||||
#### CoreHandle
|
||||
Lightweight, cloneable wrapper around CoreRuntime. Uses Arc internally for cheap cloning across async tasks and server functions.
|
||||
|
||||
#### Connector Trait
|
||||
Defines the interface for connector implementations:
|
||||
- Command channel (mpsc) for control operations
|
||||
- Event broadcast channel for publishing events
|
||||
- State tracking (Initializing, Connecting, Ready, Error, Stopped)
|
||||
- User ownership for authorization
|
||||
|
||||
#### ConnectorHandle
|
||||
Concrete implementation of the Connector trait that wraps:
|
||||
- Metadata (id, kind, owner, title)
|
||||
- Shared state (protected by RwLock)
|
||||
- Command and event channels
|
||||
- Optional task handle for lifecycle management
|
||||
|
||||
### Connector Implementations
|
||||
|
||||
#### OpenCodeConnector
|
||||
Connector for OpenCode.ai REST + SSE API:
|
||||
- HTTP client for session/message operations
|
||||
- SSE event stream for real-time updates
|
||||
- Background task loop for command processing
|
||||
- State machine for connection lifecycle
|
||||
- **TurnComplete emission**: Uses `TurnCompleteTrigger::ExplicitSignal` after `MessageCompleted` events (based on upstream session.idle signals)
|
||||
|
||||
#### AcpConnector (Future)
|
||||
Connector for Agent-Client Protocol:
|
||||
- WebSocket or HTTP/2 transport
|
||||
- ACP message protocol handling
|
||||
- Tool execution and streaming support
|
||||
|
||||
## Key Files
|
||||
|
||||
### Core Runtime
|
||||
- `src/runtime.rs` - CoreRuntime and CoreHandle implementation
|
||||
- `src/types.rs` - Core types (ConnectorId, ConnectorState, User, etc.)
|
||||
- `src/error.rs` - Error types for the runtime
|
||||
- `src/config.rs` - Configuration types and template system
|
||||
|
||||
### Connectors
|
||||
- `src/connectors/mod.rs` - Connector trait and ConnectorHandle
|
||||
- `src/connectors/opencode/mod.rs` - OpenCode connector implementation
|
||||
- `src/connectors/opencode/config.rs` - OpenCode-specific configuration
|
||||
- `src/connectors/acp/mod.rs` - ACP connector implementation (in progress)
|
||||
|
||||
### ACP Protocol Implementation
|
||||
- `src/acp/protocol/initialize.rs` - Protocol initialization and capability negotiation
|
||||
- `src/acp/protocol/authenticate.rs` - Optional authentication flow
|
||||
- `src/acp/protocol/session.rs` - Session lifecycle (new, load, set_mode, cancel)
|
||||
- `src/acp/protocol/prompt.rs` - Prompt requests with content blocks (Phases 3-7 complete)
|
||||
- `src/acp/protocol/streaming.rs` - Session update notifications and handlers
|
||||
- `src/acp/protocol/stop_reason.rs` - Stop reason interpretation and actions
|
||||
- `src/acp/protocol/cancellation.rs` - Cancellation and disconnect handling
|
||||
- `src/acp/protocol/error.rs` - Error classification and retry logic
|
||||
- `src/acp/connector_state.rs` - Connection and session state management
|
||||
- `src/acp/transport/mod.rs` - Transport abstraction layer
|
||||
- `src/acp/transport/stdio.rs` - Stdio transport (process spawning)
|
||||
- `src/acp/transport/http.rs` - HTTP+SSE transport
|
||||
|
||||
### Bidirectional Request Handling Pattern
|
||||
|
||||
The ACP connector implements **true bidirectional communication** where both client and agent can send JSON-RPC requests at any time. This creates an architectural challenge: how to maintain synchronous request/response semantics (async/await) while handling incoming agent requests during outgoing client requests.
|
||||
|
||||
**The Challenge:**
|
||||
|
||||
When the connector sends `session/prompt` to the agent:
|
||||
1. It calls `send_request()` which awaits the JSON-RPC response
|
||||
2. The agent may send permission requests (e.g., `tools/write`) before responding
|
||||
3. These agent requests arrive as `ConnectorCommand::AgentResponse` via the command channel
|
||||
4. If `send_request()` only polls transport and response channels, the command channel isn't polled
|
||||
5. **Result**: Deadlock - agent waits for permission → permission stuck in channel → client waits for prompt response
|
||||
|
||||
**The Solution (src/connectors/acp/connector.rs:1253-1523):**
|
||||
|
||||
The `send_request()` method uses `tokio::select!` to poll **three** sources simultaneously:
|
||||
- **Response channel** (`response_rx`) - Waiting for the correlated JSON-RPC response
|
||||
- **Transport channel** - Receiving messages/notifications from agent (may trigger response)
|
||||
- **Command channel** (`cmd_rx`) - Receiving commands from event bridge (e.g., `AgentResponse`)
|
||||
|
||||
When an `AgentResponse` command arrives during `send_request()`:
|
||||
1. Extract the response payload from the command
|
||||
2. Send it to the agent via transport immediately
|
||||
3. Remove from pending requests map
|
||||
4. Continue waiting for the original prompt response
|
||||
|
||||
This pattern is **idiomatic async Rust** for implementing synchronous abstractions over bidirectional transports. It's similar to:
|
||||
- gRPC bidirectional streaming with request/response correlation
|
||||
- WebSocket clients with RPC-style method calls
|
||||
- HTTP/2 multiplexing with concurrent streams
|
||||
|
||||
**Why Not Separate Tasks?**
|
||||
|
||||
Alternative architectures (separate command processing task, full actor model) introduce:
|
||||
- Complex synchronization between tasks
|
||||
- Race conditions on shared state
|
||||
- Message ordering guarantees across channels
|
||||
- Significantly more code and cognitive overhead
|
||||
|
||||
The single-task, multi-channel select pattern keeps all state local and eliminates these issues.
|
||||
|
||||
**Key Invariant:**
|
||||
|
||||
Any method that blocks waiting for a response MUST also poll the command channel to process `AgentResponse` commands, otherwise bidirectional flows deadlock.
|
||||
|
||||
## Main Exports
|
||||
|
||||
### Runtime
|
||||
- `CoreRuntime` - Main orchestrator
|
||||
- `CoreHandle` - Cloneable runtime handle
|
||||
- `CoreConfig` - Runtime configuration
|
||||
|
||||
### Types
|
||||
- `ConnectorId`, `UserId` - Type aliases for IDs
|
||||
- `ConnectorKind` - Enum of connector types (OpenCode, Acp, Mock)
|
||||
- `ConnectorState` - Lifecycle state enum
|
||||
- `ConnectorSummary` - Lightweight connector view
|
||||
- `User` - User information
|
||||
|
||||
### Connectors
|
||||
- `Connector` - Trait for connector implementations
|
||||
- `ConnectorHandle` - Handle to a running connector
|
||||
- `ConnectorCommand` - Commands sent to connectors
|
||||
- `OpenCodeConnector` - OpenCode.ai integration
|
||||
- `OpenCodeConfig` - OpenCode connector configuration
|
||||
|
||||
### Configuration
|
||||
- `ConnectorConfig` - Configuration for creating connectors
|
||||
- `apply_template()` - Apply connector templates with patches
|
||||
|
||||
### ACP Protocol Types (Phases 3-7)
|
||||
- **Prompt Turn**:
|
||||
- `SessionPromptRequest`, `SessionPromptResponse` - Prompt requests/responses
|
||||
- `ContentBlock` - Text, Image, Audio, Resource, ResourceLink content
|
||||
- `EmbeddedResource` - Text or Blob embedded resources
|
||||
- `StopReason` - EndTurn, MaxTokens, MaxTurnRequests, Refusal, Cancelled
|
||||
- `PromptError` - Timeout, JsonRpcError, TransportError, Cancelled, ValidationError
|
||||
|
||||
- **Streaming Updates**:
|
||||
- `SessionUpdate` - All update types (agent_message_chunk, tool_call, plan, etc.)
|
||||
- `SessionUpdateNotification` - Notification wrapper with session_id
|
||||
- `ToolCallInfo` - Tool call tracking with status and content
|
||||
- `ToolKind` - Read, Edit, Search, Execute, Think, Other
|
||||
- `ToolCallStatus` - Pending, InProgress, Completed, Failed, Cancelled
|
||||
- `ToolCallContent` - Content, Diff, Terminal output
|
||||
- `MessageAccumulator` - Message chunk accumulation helper
|
||||
- `PlanEntry`, `Command` - Plan and command structures
|
||||
|
||||
- **Stop Reason Handling**:
|
||||
- `StopReasonAction` - Complete, ShowWarning, ShowError, ShowInfo
|
||||
- `handle_stop_reason()` - Interpret stop reasons
|
||||
- `is_continuable()`, `is_error()` - Stop reason classification
|
||||
|
||||
- **Cancellation**:
|
||||
- `handle_cancellation()` - Cancel pending operations
|
||||
- `cancel_pending_tool_calls()` - Mark tool calls as cancelled
|
||||
- `handle_disconnect()` - Handle transport disconnect
|
||||
|
||||
- **Error Classification**:
|
||||
- `ClassifiedError` - Error with class, message, details, retry_after
|
||||
- `ErrorClass` - Transient, Terminal, User
|
||||
- `ErrorSeverity` - Info, Warning, Error, Fatal
|
||||
- `ErrorAction` - Retry, Reconnect, CheckConfig, ContactSupport, Dismiss
|
||||
- `classify_jsonrpc_error()`, `classify_transport_error()` - Classify errors
|
||||
- `exponential_backoff()` - Calculate retry delays
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Creating a Runtime
|
||||
|
||||
```rust
|
||||
use dirigent_core::{CoreRuntime, CoreConfig, CoreHandle};
|
||||
|
||||
// Load config from file or use default
|
||||
let config = CoreConfig::load_config(None)?;
|
||||
|
||||
// Create runtime
|
||||
let runtime = CoreRuntime::new(config);
|
||||
|
||||
// Wrap in handle for cheap cloning
|
||||
let handle = CoreHandle::new(runtime);
|
||||
```
|
||||
|
||||
### Creating a Connector
|
||||
|
||||
```rust
|
||||
use dirigent_core::{ConnectorConfig, ConnectorKind, OpenCodeConfig};
|
||||
use serde_json::json;
|
||||
|
||||
// Build OpenCode connector config
|
||||
let config = OpenCodeConfig {
|
||||
base_url: "http://localhost:12225".to_string(),
|
||||
title: "My OpenCode".to_string(),
|
||||
initial_session: None,
|
||||
};
|
||||
|
||||
// Serialize to JSON for ConnectorConfig
|
||||
let params = serde_json::to_value(&config)?;
|
||||
|
||||
let connector_config = ConnectorConfig {
|
||||
id: None, // Runtime generates ID
|
||||
kind: ConnectorKind::OpenCode,
|
||||
owner: Some("user-123".to_string()),
|
||||
title: Some("My OpenCode".to_string()),
|
||||
params,
|
||||
};
|
||||
|
||||
// Create connector via runtime
|
||||
let connector_id = handle.create_connector(
|
||||
"user-123".to_string(),
|
||||
connector_config
|
||||
).await?;
|
||||
```
|
||||
|
||||
### Using Templates
|
||||
|
||||
```rust
|
||||
use dirigent_core::{apply_template, ConnectorKind};
|
||||
use serde_json::json;
|
||||
|
||||
// Use default template with custom URL
|
||||
let connector_config = apply_template(
|
||||
ConnectorKind::OpenCode,
|
||||
"default",
|
||||
json!({
|
||||
"base_url": "http://localhost:8080",
|
||||
"title": "Custom OpenCode"
|
||||
})
|
||||
)?;
|
||||
|
||||
let connector_id = handle.create_connector(
|
||||
"user-123".to_string(),
|
||||
connector_config
|
||||
).await?;
|
||||
```
|
||||
|
||||
### Managing Connectors
|
||||
|
||||
```rust
|
||||
// List all connectors
|
||||
let all_connectors = handle.list_connectors(None).await;
|
||||
|
||||
// List connectors for a specific user
|
||||
let user_connectors = handle.list_connectors(Some("user-123".to_string())).await;
|
||||
|
||||
// Get a specific connector
|
||||
let connector = handle.get_connector(&connector_id).await;
|
||||
|
||||
// Stop a connector
|
||||
handle.stop_connector(&connector_id).await?;
|
||||
|
||||
// Restart a stopped connector
|
||||
handle.restart_connector(&connector_id).await?;
|
||||
|
||||
// Remove a connector
|
||||
handle.remove_connector(&connector_id).await?;
|
||||
```
|
||||
|
||||
### Connector Lifecycle with Restart
|
||||
|
||||
Connectors can be restarted after being stopped or entering an error state:
|
||||
|
||||
```rust
|
||||
// Create and start a connector
|
||||
let connector_id = handle.create_connector(
|
||||
"user-123".to_string(),
|
||||
connector_config
|
||||
).await?;
|
||||
// Connector is now in Ready state
|
||||
|
||||
// Stop the connector
|
||||
handle.stop_connector(&connector_id).await?;
|
||||
// Connector is now in Stopped state
|
||||
|
||||
// Restart the connector (recreates background task with fresh channels)
|
||||
handle.restart_connector(&connector_id).await?;
|
||||
// Connector transitions: Stopped → Initializing → Connecting → Ready
|
||||
|
||||
// Restart preserves:
|
||||
// - Connector ID (same instance)
|
||||
// - Configuration (base_url, title, etc.)
|
||||
// - Event broadcast channel (subscribers continue receiving)
|
||||
// - State Arc (observers see real-time updates)
|
||||
|
||||
// Restart recreates:
|
||||
// - Command channel (new sender/receiver pair)
|
||||
// - Connector instance (fresh OpenCodeConnector)
|
||||
// - Background task (new spawn)
|
||||
// - Task handle (new JoinHandle)
|
||||
```
|
||||
|
||||
### Sending Commands
|
||||
|
||||
```rust
|
||||
use dirigent_core::connectors::ConnectorCommand;
|
||||
|
||||
// Get connector handle
|
||||
let connector = handle.get_connector(&connector_id).await.unwrap();
|
||||
|
||||
// Subscribe to events
|
||||
let mut events = connector.subscribe();
|
||||
|
||||
// Send a command
|
||||
let cmd_tx = connector.command_tx();
|
||||
cmd_tx.send(ConnectorCommand::ListSessions).await?;
|
||||
|
||||
// Receive events
|
||||
while let Ok(event) = events.recv().await {
|
||||
match event {
|
||||
Event::SessionsListed { sessions } => {
|
||||
println!("Got {} sessions", sessions.len());
|
||||
break;
|
||||
}
|
||||
Event::Error { message } => {
|
||||
eprintln!("Error: {}", message);
|
||||
break;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Global Event Stream
|
||||
|
||||
```rust
|
||||
// Subscribe to every event on the SharingBus. Callers can also pick an
|
||||
// `EventFilter` via `subscribe_filtered()` to receive only the events
|
||||
// they care about.
|
||||
let mut bus_rx = handle.sharing_bus().subscribe_all().await;
|
||||
|
||||
tokio::spawn(async move {
|
||||
while let Some(bus_event) = bus_rx.rx.recv().await {
|
||||
println!("Bus event: {:?}", bus_event.event);
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
### Runtime Configuration (dirigent.toml or dirigent.json)
|
||||
|
||||
```toml
|
||||
port = 3000
|
||||
project_dir = "."
|
||||
project_name = "my_project"
|
||||
templates_enabled = true
|
||||
|
||||
[[connectors]]
|
||||
id = "opencode-1"
|
||||
kind = "OpenCode"
|
||||
owner = "user-123"
|
||||
title = "OpenCode Local"
|
||||
|
||||
[connectors.params]
|
||||
base_url = "http://localhost:12225"
|
||||
title = "OpenCode Local"
|
||||
initial_session = null
|
||||
```
|
||||
|
||||
### Templates
|
||||
|
||||
Available templates:
|
||||
- `opencode/default` - Standard localhost OpenCode connector
|
||||
- `acp/claude-default` - Claude API connector (stub for future)
|
||||
|
||||
## Connector Event Emission Patterns
|
||||
|
||||
### TurnComplete Event Semantics
|
||||
|
||||
All connectors emit `Event::TurnComplete` to signal that a turn/message is finalized. This is the **primary signal** for:
|
||||
- Archivist to finalize and write message to disk
|
||||
- UI cache to lock message state as immutable
|
||||
- Conductor bridge to flush response to upstream
|
||||
|
||||
**Event ordering guarantee**:
|
||||
```text
|
||||
MessageCompleted → TurnComplete → SessionIdle
|
||||
```
|
||||
|
||||
### Connector-Specific TurnComplete Strategies
|
||||
|
||||
Different connectors use different strategies to determine when a turn is complete:
|
||||
|
||||
#### OpenCode Connector
|
||||
- **Trigger**: `TurnCompleteTrigger::ExplicitSignal`
|
||||
- **Strategy**: Relies on upstream `session.idle` events from OpenCode.ai
|
||||
- **Implementation**: After translating `MessageCompleted`, emits `TurnComplete` then `SessionIdle`
|
||||
- **Code location**: `crates/dirigent_core/src/connectors/opencode.rs:550-575`
|
||||
|
||||
#### ACP Connector (stdio transport)
|
||||
- **Trigger**: `TurnCompleteTrigger::ResponseReceived`
|
||||
- **Strategy**: JSON-RPC response message is the final message in a turn
|
||||
- **Implementation**: Emits `TurnComplete` after receiving the JSON-RPC response to `session/prompt`
|
||||
- **Code location**: `crates/dirigent_core/src/connectors/acp/connector.rs:328`
|
||||
|
||||
#### Gateway Connector
|
||||
- **Trigger**: `TurnCompleteTrigger::OperationsComplete`
|
||||
- **Strategy**: Tracks pending tool calls and emits when all operations resolve
|
||||
- **Implementation**: Monitors tool call status changes and emits when last pending call completes
|
||||
- **Code location**: `crates/dirigent_core/src/connectors/gateway/mod.rs:464,556,626,678`
|
||||
|
||||
**Important**: Connectors MUST emit `TurnComplete` exactly once per turn. Duplicate emissions can cause archiving issues and UI state corruption.
|
||||
|
||||
### Gateway Session Transfer Mechanics
|
||||
|
||||
The Gateway connector serves as an **entry point** for incoming ACP connections. Sessions can be transferred to real agent connectors (Claude, etc.) via `/select-connector` commands.
|
||||
|
||||
#### Key Principle: New Connector Is Authority
|
||||
|
||||
**After transfer, the target connector becomes the sole authority for session configuration.**
|
||||
|
||||
```text
|
||||
Before transfer:
|
||||
Gateway has placeholder modes: "ask", "write", "yolo"
|
||||
Gateway has placeholder models: "simple", "default", "high"
|
||||
|
||||
After transfer to Claude:
|
||||
Claude's actual modes/models become authoritative
|
||||
Gateway's placeholders are irrelevant
|
||||
Editor receives config_option_update with Claude's real options
|
||||
```
|
||||
|
||||
#### Transfer Flow
|
||||
|
||||
1. User sends `/select-connector claude` in Gateway session
|
||||
2. Gateway emits `SessionTransferRequest` to CoreRuntime
|
||||
3. CoreRuntime creates/loads session in target connector
|
||||
4. Target connector emits `SessionCreated` with its modes/models
|
||||
5. CoreRuntime extracts modes/models from `SessionCreated` event
|
||||
6. CoreRuntime emits `SessionTransferred` event with modes/models
|
||||
7. Event bridge sends `config_option_update` to editor with target's modes/models
|
||||
|
||||
#### What Does NOT Happen
|
||||
|
||||
- Gateway does NOT adjust or map its values to the target connector
|
||||
- Gateway does NOT remain involved after transfer completes
|
||||
- Target connector does NOT inherit Gateway's mode/model selections
|
||||
- No "mapping" between Gateway placeholders and real connector values
|
||||
|
||||
#### Code Locations
|
||||
|
||||
- Transfer request handling: `src/runtime.rs:execute_transfer()`
|
||||
- Gateway commands: `src/connectors/gateway/commands.rs`
|
||||
- SessionTransferred event: `dirigent_protocol/src/events/mod.rs`
|
||||
- config_option_update emission: `dirigent_acp_api/src/event_bridge.rs:handle_session_transferred_internal()`
|
||||
|
||||
## Architecture Patterns
|
||||
|
||||
### Request-Response Pattern
|
||||
For operations that need results (list_sessions, list_messages):
|
||||
1. Subscribe to connector events
|
||||
2. Send command via command channel
|
||||
3. Wait for corresponding response event (with timeout)
|
||||
4. Return result or error
|
||||
|
||||
### Fire-and-Forget Pattern
|
||||
For operations that stream results (send_message):
|
||||
1. Send command via command channel
|
||||
2. Return immediately
|
||||
3. Clients subscribe to event stream for updates
|
||||
|
||||
### Lifecycle Management
|
||||
Connectors progress through states:
|
||||
1. **Initializing** - Created but not connecting
|
||||
2. **Connecting** - Attempting to establish connection
|
||||
3. **Ready** - Connected and operational
|
||||
4. **Error** - Encountered failure (with error message)
|
||||
5. **Stopped** - Shutdown or unrecoverable error
|
||||
|
||||
#### Restart Support
|
||||
Connectors in `Stopped` or `Error` state can be restarted:
|
||||
- **restart_connector()** recreates the connector's background task with fresh channels
|
||||
- Preserves connector identity (ID, owner, configuration)
|
||||
- Preserves event broadcast channel (existing subscribers continue receiving)
|
||||
- Recreates command channel and background task
|
||||
- State transitions: `Stopped`/`Error` → `Initializing` → `Connecting` → `Ready`
|
||||
|
||||
### Configuration Persistence
|
||||
The runtime automatically saves configuration to disk when:
|
||||
- A connector is created
|
||||
- A connector is removed
|
||||
- Configuration is explicitly saved
|
||||
|
||||
This ensures connectors are restored on server restart.
|
||||
|
||||
## Session Tracking Responsibilities
|
||||
|
||||
**IMPORTANT**: CoreRuntime is a **stateless orchestrator** for session operations. It does NOT maintain session state or cache message history.
|
||||
|
||||
### What CoreRuntime Does
|
||||
|
||||
- **Route Commands**: Forward session/message commands to appropriate connectors
|
||||
- **Broadcast Events**: Relay connector events to global event stream
|
||||
- **Manage Connectors**: Track which connectors are active and available
|
||||
- **Persist Configuration**: Save/load connector configuration (not session data)
|
||||
|
||||
### What CoreRuntime Does NOT Do
|
||||
|
||||
- **Cache Sessions**: Does not maintain lists of sessions or session metadata
|
||||
- **Store Messages**: Does not retain message content or history
|
||||
- **Buffer Events**: Does not cache events for replay or historical access
|
||||
- **Track Session State**: Does not know which sessions exist or their current state
|
||||
|
||||
### Stateless Design Rationale
|
||||
|
||||
1. **Multi-Connector Support**: With multiple connectors (OpenCode, ACP, etc.), caching sessions would require complex invalidation
|
||||
2. **Memory Efficiency**: Long-running server should not accumulate unbounded session history
|
||||
3. **Single Source of Truth**: External APIs (OpenCode.ai, ACP agents) are authoritative for session state
|
||||
4. **Scalability**: Stateless design supports future horizontal scaling
|
||||
|
||||
### Data Flow for Session Operations
|
||||
|
||||
**List Sessions**:
|
||||
```
|
||||
Server Function → CoreRuntime.get_connector() → Send Command → Connector queries API
|
||||
↓ ↓
|
||||
Returns handle Broadcasts SessionsListed
|
||||
↓
|
||||
Server function returns to UI
|
||||
(CoreRuntime does NOT cache list)
|
||||
```
|
||||
|
||||
**Send Message**:
|
||||
```
|
||||
Server Function → CoreRuntime.get_connector() → Send Command → Connector sends to API
|
||||
↓ ↓
|
||||
Returns handle Broadcasts MessageSent
|
||||
↓
|
||||
SSE pushes to UI in real-time
|
||||
(CoreRuntime does NOT store message)
|
||||
```
|
||||
|
||||
All session data passes through CoreRuntime but is **never cached**. See `docs/architecture/session_tracking.md` for the complete three-layer architecture (CoreRuntime, UI Cache, Archivist).
|
||||
|
||||
## Phase 4: `SharingBus` + `StreamRegistry` (2026-04-21)
|
||||
|
||||
Every `Event` emitted by a connector or the runtime is published onto a
|
||||
single `SharingBus` that owns:
|
||||
|
||||
- A `broadcast::Sender<BusEvent>` as the internal multicast.
|
||||
- A worker task that receives from that broadcast and dispatches per-
|
||||
subscriber `mpsc::Sender<BusEvent>` pipes with filter matching.
|
||||
- A `HashMap<(connector_id, native_session_id), scroll_id>` cache that
|
||||
late-binds `routing.scroll_id` for events emitted before their
|
||||
`SessionRegistered` event arrived.
|
||||
|
||||
### Subscriber model
|
||||
|
||||
`SharingBus::subscribe_all()` and `subscribe_filtered(EventFilter, cap)`
|
||||
return a `BusReceiver { id, rx, lagged }`. Filters are applied by the
|
||||
worker — subscribers never allocate a closure for skipped events.
|
||||
|
||||
`EventFilter` variants: `All | ScrollId | ConnectorUid | Kinds | AnyOf
|
||||
| AllOf`.
|
||||
|
||||
### Stream registry
|
||||
|
||||
The runtime owns a `StreamRegistry` and a `StreamFactoryRegistry`.
|
||||
Streams are attached via `CoreRuntime::attach_stream(StreamConfig)`:
|
||||
|
||||
- Factory registry resolves the `kind` string → concrete
|
||||
`Arc<dyn SessionStream>`.
|
||||
- The new stream gets its own `BusReceiver` scoped via `scope_to_filter`
|
||||
(Session → ScrollId, Connector → ConnectorUid, ArchiveWide → All).
|
||||
- A worker task pumps events into `stream.on_event(&bus_event).await`.
|
||||
|
||||
Health drift: `record_failure` / `record_success` in `sharing/health.rs`
|
||||
transitions `HealthStatus` on 5 consecutive failures
|
||||
(`Healthy → Degraded → Unavailable`).
|
||||
|
||||
### Replay
|
||||
|
||||
`CoreRuntime::replay_session_to_stream(scroll_id, stream_id, opts)`
|
||||
loads archived messages via the archivist's read API and calls
|
||||
`stream.on_event` directly, bypassing the bus. Each replayed event has
|
||||
`origin = EventOrigin::Replay { replay_id }` and
|
||||
`routing.scroll_id = Some(scroll_id)`.
|
||||
|
||||
### Config
|
||||
|
||||
`[[streams]]` blocks in `dirigent.toml` are parsed into `StreamsConfig`
|
||||
and applied at boot (best-effort: failures log + continue).
|
||||
|
||||
## Related Packages
|
||||
- **api** - Server functions that wrap CoreRuntime operations
|
||||
- **web** - Dioxus UI that calls server functions
|
||||
- **dirigent_protocol** - Shared event and message types
|
||||
- **opencode_client** - Low-level OpenCode.ai HTTP client (used by OpenCodeConnector)
|
||||
|
||||
## Documentation
|
||||
- README: ./README.md
|
||||
- Architecture: ../../docs/architecture/overview.md
|
||||
- Migration Guide: ../../docs/migration/singleton_to_runtime.md
|
||||
@@ -1,268 +0,0 @@
|
||||
# dirigent_protocol
|
||||
|
||||
**Version:** 0.2.0
|
||||
**Status:** Active Development
|
||||
|
||||
ACP/MCP-aligned protocol library for agent-client interactions.
|
||||
|
||||
## Quick Links
|
||||
|
||||
- **Package README**: [README.md](README.md) - Main documentation
|
||||
- **Streaming Model**: [docs/streaming_model.md](docs/streaming_model.md) - Detailed SessionUpdate guide
|
||||
- **Migration Guide**: [docs/migration_from_0.1.md](docs/migration_from_0.1.md) - Upgrading from 0.1.x
|
||||
- **Architecture Doc**: [../../docs/architecture/protocol.md](../../docs/architecture/protocol.md) - System design
|
||||
|
||||
## Purpose
|
||||
|
||||
This package provides the core event protocol for Dirigent, enabling:
|
||||
- Real-time streaming of agent interactions
|
||||
- Provider-agnostic event representation
|
||||
- Tool lifecycle management
|
||||
- Structured content representation
|
||||
|
||||
## Key Types
|
||||
|
||||
```rust
|
||||
use dirigent_protocol::{
|
||||
Event, // Top-level event enum
|
||||
SessionUpdate, // Streaming content updates
|
||||
ContentBlock, // Structured content (Text, ResourceLink)
|
||||
ToolCall, // Tool execution state
|
||||
ToolCallStatus, // Pending → Running → Completed/Error
|
||||
TurnCompleteTrigger, // How turn completion was detected
|
||||
};
|
||||
```
|
||||
|
||||
## Event Semantics: MessageCompleted vs TurnComplete vs SessionIdle
|
||||
|
||||
Understanding the distinction between these three events is critical for correct system behavior:
|
||||
|
||||
### MessageCompleted - "Metadata is ready"
|
||||
- **Purpose**: Informational - signals that message metadata exists
|
||||
- **Timing**: Emitted when message record is created, content may still be streaming
|
||||
- **Consumer action**: Update UI status indicators ("Assistant is typing" → "Complete")
|
||||
- **Example**: Show message timestamp, update message count
|
||||
|
||||
### TurnComplete - "All content received" (ACTIONABLE)
|
||||
- **Purpose**: **Primary finalization signal** - all content for this turn is complete
|
||||
- **Timing**: Emitted AFTER all content chunks, tool calls, and metadata updates
|
||||
- **Consumer action**: **Finalize storage, lock state, trigger post-processing**
|
||||
- **Example**: Write message to disk, mark as immutable, generate summaries
|
||||
|
||||
### SessionIdle - "No recent activity"
|
||||
- **Purpose**: Informational - indicates session is quiet
|
||||
- **Timing**: Emitted AFTER TurnComplete as final safety signal
|
||||
- **Consumer action**: Hide spinners, update activity indicators
|
||||
- **Example**: Remove "typing" animation, update last activity timestamp
|
||||
|
||||
### Event Ordering Guarantee
|
||||
|
||||
```text
|
||||
1. MessageStarted (message created)
|
||||
2. SessionUpdate::*Chunk (content streaming)
|
||||
3. SessionUpdate::ToolCall* (tool execution)
|
||||
4. MessageCompleted (metadata ready) ← UI: "Complete"
|
||||
5. TurnComplete ← FINALIZE HERE!
|
||||
6. SessionIdle ← UI: hide spinner
|
||||
```
|
||||
|
||||
### Consumer Behavior Table
|
||||
|
||||
| Consumer | MessageCompleted | TurnComplete | SessionIdle |
|
||||
|----------|------------------|--------------|-------------|
|
||||
| **Archivist** | Ignore | **Finalize and write** | Safety net |
|
||||
| **UI Cache** | Update status | **Lock state** | Hide spinner |
|
||||
| **Conductor Bridge** | - | **Flush response** | Fallback flush |
|
||||
|
||||
### TurnCompleteTrigger Variants
|
||||
|
||||
The `TurnCompleteTrigger` enum indicates **how** the system determined completion:
|
||||
|
||||
- **`ExplicitSignal`**: Upstream provider sent explicit completion (e.g., OpenCode session.idle)
|
||||
- **`ResponseReceived`**: JSON-RPC response received (ACP stdio - response is last message)
|
||||
- **`OperationsComplete`**: All tracked operations finished (e.g., pending tool calls resolved)
|
||||
- **`IdleTimeout { duration_ms }`**: Timeout-based detection (fallback mechanism)
|
||||
|
||||
**For most consumers**, treat all triggers the same - the turn is complete. The trigger type is primarily for debugging and observability.
|
||||
|
||||
## Usage Pattern
|
||||
|
||||
```rust
|
||||
use dirigent_protocol::{Event, SessionUpdate, ContentBlock};
|
||||
|
||||
fn handle_event(event: Event) {
|
||||
match event {
|
||||
Event::SessionUpdate { session_id, update } => {
|
||||
match update {
|
||||
SessionUpdate::AgentMessageChunk { message_id, content, .. } => {
|
||||
if let ContentBlock::Text { text } = content {
|
||||
println!("Agent: {}", text);
|
||||
}
|
||||
}
|
||||
SessionUpdate::ToolCall { tool_call, .. } => {
|
||||
println!("Tool: {}", tool_call.tool_name);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
dirigent_protocol/
|
||||
├── src/
|
||||
│ ├── types/ # Core types
|
||||
│ │ ├── content.rs # ContentBlock definitions
|
||||
│ │ ├── updates.rs # SessionUpdate variants
|
||||
│ │ ├── tool.rs # ToolCall, ToolCallStatus
|
||||
│ │ └── meta.rs # Provider metadata
|
||||
│ ├── session.rs # Session types
|
||||
│ ├── conversation.rs # Message types
|
||||
│ ├── events.rs # Event enum
|
||||
│ └── adapters/ # Provider adapters
|
||||
│ ├── opencode.rs # OpenCode translation
|
||||
│ └── rest.rs # REST translation
|
||||
├── docs/ # Detailed documentation
|
||||
├── examples/ # Usage examples
|
||||
└── tests/ # Integration tests
|
||||
```
|
||||
|
||||
## Version 0.2.0 Changes
|
||||
|
||||
**Breaking:**
|
||||
- Removed `Event::MessagePartAdded`
|
||||
|
||||
**New:**
|
||||
- `SessionUpdate` event system (ACP-style)
|
||||
- `ContentBlock` types (MCP-compatible)
|
||||
- `ToolCall` with lifecycle tracking
|
||||
- Provider metadata via `_meta`
|
||||
|
||||
See [docs/migration_from_0.1.md](docs/migration_from_0.1.md) for migration guide.
|
||||
|
||||
## Development
|
||||
|
||||
### Running Tests
|
||||
```bash
|
||||
cargo test --package dirigent_protocol
|
||||
```
|
||||
|
||||
### Checking Code
|
||||
```bash
|
||||
cargo check --package dirigent_protocol
|
||||
```
|
||||
|
||||
### Running Examples
|
||||
```bash
|
||||
cargo run --package dirigent_protocol --example session_metadata_demo
|
||||
```
|
||||
|
||||
## Integration
|
||||
|
||||
This package is used by:
|
||||
- **api** package: Server functions consume protocol events
|
||||
- **web** package: UI renders protocol events
|
||||
- **dirigent_core** (future): Runtime emits protocol events
|
||||
|
||||
## Adapters
|
||||
|
||||
The adapter system translates provider-specific events to Dirigent Protocol:
|
||||
|
||||
- **OpenCodeAdapter**: Translates OpenCode.ai events
|
||||
- **RESTAdapter**: Converts REST API responses
|
||||
|
||||
Adapters preserve provider metadata in the `_meta` field for debugging and traceability.
|
||||
|
||||
## Current Scope
|
||||
|
||||
**Phase 1 (Implemented):**
|
||||
- User/Agent/Thought message streaming
|
||||
- Tool lifecycle (Pending → Running → Completed/Error)
|
||||
- Text and ResourceLink content types
|
||||
- Provider metadata support
|
||||
|
||||
**Deferred to Future Phases:**
|
||||
- Plans and mode switching
|
||||
- Permission system
|
||||
- Embedded resources (full content)
|
||||
- Rich media (images, audio)
|
||||
- Multi-agent communication
|
||||
|
||||
See [../../docs/building/03_acp_prep/04_first_order_refactor.md](../../docs/building/03_acp_prep/04_first_order_refactor.md) for the full plan.
|
||||
|
||||
## Standards Alignment
|
||||
|
||||
**Agent-Client Protocol (ACP):**
|
||||
- Session-centric streaming
|
||||
- Separate content types (user/agent/thought)
|
||||
- Tool status tracking
|
||||
|
||||
**Model Context Protocol (MCP):**
|
||||
- ContentBlock structure
|
||||
- Resource links
|
||||
- Extensible content types
|
||||
|
||||
Differences from standards are documented in [../../docs/architecture/protocol.md](../../docs/architecture/protocol.md).
|
||||
|
||||
## Anti-Patterns
|
||||
|
||||
### Timeout-Based Event Waiting (FORBIDDEN)
|
||||
|
||||
**Never use timeout-based waiting to receive events that should be available immediately.**
|
||||
|
||||
```rust
|
||||
// ❌ BAD - Race condition waiting for event
|
||||
async fn wait_for_metadata_event(
|
||||
events: &mut broadcast::Receiver<Event>,
|
||||
timeout: Duration,
|
||||
) -> Option<SessionMetadataReceived> {
|
||||
let start = Instant::now();
|
||||
while start.elapsed() < timeout {
|
||||
match tokio::time::timeout(Duration::from_millis(100), events.recv()).await {
|
||||
Ok(Ok(Event::SessionMetadataReceived { .. })) => return Some(...),
|
||||
_ => continue,
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
```
|
||||
|
||||
**Why this is wrong:**
|
||||
1. **Race condition**: The event may have been emitted before the receiver subscribed
|
||||
2. **Arbitrary delays**: 500ms waits add latency for no good reason
|
||||
3. **Silent failures**: Timeout expiring doesn't indicate the real problem
|
||||
4. **Fragile**: Works "most of the time" but fails under load or timing variations
|
||||
|
||||
**Instead, pass data directly:**
|
||||
```rust
|
||||
// ✅ GOOD - Extract data from existing events
|
||||
async fn create_session_in_connector(...) -> Result<(String, Option<Models>, Option<Modes>), String> {
|
||||
// The SessionCreated event already contains models/modes
|
||||
match event {
|
||||
Event::SessionCreated { session, .. } => {
|
||||
Ok((session.id, session.models, session.modes))
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Rule**: If you find yourself writing `timeout(Duration::from_millis(N), events.recv())` to wait for an event that "should" arrive, the architecture is wrong. Refactor to pass data directly through return values or existing event payloads.
|
||||
|
||||
## Contributing
|
||||
|
||||
When adding features:
|
||||
1. Update type definitions in `src/types/`
|
||||
2. Add comprehensive tests
|
||||
3. Update documentation in `docs/`
|
||||
4. Add examples if applicable
|
||||
5. Update CHANGELOG.md
|
||||
|
||||
## See Also
|
||||
|
||||
- Main project: [../../CLAUDE.md](../../CLAUDE.md)
|
||||
- Architecture docs: [../../docs/architecture/](../../docs/architecture/)
|
||||
- Building docs: [../../docs/building/](../../docs/building/)
|
||||
@@ -1,169 +0,0 @@
|
||||
# Package: dirigent_tools
|
||||
|
||||
Tool implementations for ACP client operations with sandboxing and security.
|
||||
|
||||
## Quick Facts
|
||||
- **Type**: Library
|
||||
- **Main Entry**: src/lib.rs
|
||||
- **Dependencies**: tokio, serde, anyhow, thiserror, tracing, regex, globset, similar, dunce
|
||||
- **Status**: Scaffolding phase - structure complete, implementation pending
|
||||
|
||||
## Purpose
|
||||
|
||||
This package provides the foundational tool operations for the Dirigent ACP client, implementing file operations, terminal execution, and search capabilities with strong security guarantees. It is designed to support ACP-compliant agents by providing safe, sandboxed tool handlers.
|
||||
|
||||
## Module Structure
|
||||
|
||||
### Core Modules
|
||||
- **error** (`src/error.rs`) - Error types for tool operations
|
||||
- **config** (`src/config.rs`) - Configuration types (sandbox, permissions, terminal, search, embedding)
|
||||
- **path** (`src/path.rs`) - Cross-platform path normalization and containment checking
|
||||
|
||||
### Tool Modules
|
||||
- **fs** (`src/fs.rs`) - File read/write/edit operations
|
||||
- **search** (`src/search.rs`) - Glob/grep/ls search operations
|
||||
- **terminal** (`src/terminal.rs`) - Command execution and output capture
|
||||
|
||||
### Security Modules
|
||||
- **permission** (`src/permission.rs`) - Permission prompt system and decision caching
|
||||
- **audit** (`src/audit.rs`) - Structured audit logging
|
||||
|
||||
### Integration Modules
|
||||
- **tool_call** (`src/tool_call.rs`) - ACP tool call metadata and helpers
|
||||
|
||||
## Security Model
|
||||
|
||||
All operations are subject to:
|
||||
|
||||
1. **Sandbox Containment**: Operations restricted to configured allowed roots
|
||||
2. **Blocklist Enforcement**: Sensitive paths explicitly denied
|
||||
3. **Permission Prompts**: Write/execute operations require user approval (configurable)
|
||||
4. **Resource Limits**: Bounded file sizes, search results, terminal output
|
||||
5. **Audit Logging**: All operations logged with structured context
|
||||
|
||||
## Platform Support
|
||||
|
||||
Windows is a first-class platform with explicit support for:
|
||||
- Backslash and forward slash separators
|
||||
- Drive letters (C:\, D:\)
|
||||
- UNC paths (\\server\share\...)
|
||||
- Long path prefixes (\\?\...)
|
||||
- MINGW-style paths (/c/Users/...)
|
||||
- Junctions and symlinks
|
||||
- cmd.exe and PowerShell
|
||||
|
||||
All path normalization and containment logic handles these cases.
|
||||
|
||||
## Configuration (Future)
|
||||
|
||||
Configuration types will be implemented in SCAFF-05:
|
||||
|
||||
```rust
|
||||
pub struct SandboxConfig {
|
||||
pub allowed_roots: Vec<PathBuf>,
|
||||
pub blocked_paths: Vec<String>, // glob patterns
|
||||
pub allow_symlink_escape: bool,
|
||||
pub follow_symlinks_within_roots: bool,
|
||||
pub read_enabled: bool,
|
||||
pub write_enabled: bool,
|
||||
pub max_read_bytes: u64,
|
||||
pub max_write_bytes: u64,
|
||||
// ... more fields
|
||||
}
|
||||
|
||||
pub struct PermissionConfig {
|
||||
pub mode: PermissionMode, // Ask | Whitelist | Yolo
|
||||
pub remember_decisions: bool,
|
||||
pub remember_ttl_secs: u64,
|
||||
pub scope: DecisionScope, // PerConnector | PerSession
|
||||
pub whitelist: WhitelistConfig,
|
||||
}
|
||||
|
||||
pub struct TerminalConfig {
|
||||
pub enabled: bool,
|
||||
pub default_cwd: PathBuf,
|
||||
pub env_allowlist: Vec<String>,
|
||||
pub command_blocklist: Vec<String>,
|
||||
pub output_byte_limit: u64,
|
||||
pub max_runtime_secs: u64,
|
||||
}
|
||||
|
||||
pub struct SearchConfig {
|
||||
pub max_results: u32,
|
||||
pub max_bytes: u64,
|
||||
pub default_include_globs: Vec<String>,
|
||||
pub default_exclude_globs: Vec<String>,
|
||||
}
|
||||
|
||||
pub struct EmbeddingConfig {
|
||||
pub max_embed_bytes: u64,
|
||||
pub allow_resource_link: bool,
|
||||
pub redact_patterns: Vec<String>, // regex patterns
|
||||
}
|
||||
```
|
||||
|
||||
## Implementation Status
|
||||
|
||||
**Current Phase**: SCAFF-01 (Scaffolding) - Complete
|
||||
|
||||
All modules are stubs with `unimplemented!()`. Implementation will proceed in phases:
|
||||
|
||||
### Phase 1: Path and Sandbox (Protocol tasks)
|
||||
- Path normalization (Windows, Linux, macOS)
|
||||
- Canonical path resolution (symlinks, junctions)
|
||||
- Containment checking
|
||||
- Blocklist matching
|
||||
|
||||
### Phase 2: Tool Operations (Tool tasks)
|
||||
- File read with line ranges
|
||||
- File write with atomic operations
|
||||
- File edit with diff generation
|
||||
- Glob search
|
||||
- Grep search with context
|
||||
- LS directory listing
|
||||
- Terminal creation and lifecycle
|
||||
- Terminal output capture with ring buffer
|
||||
|
||||
### Phase 3: Security and Integration (Integration tasks)
|
||||
- Permission prompt flow
|
||||
- Decision caching with TTL
|
||||
- Audit logging with structured fields
|
||||
- ACP tool call event generation
|
||||
- Tool title formatting
|
||||
|
||||
## Integration Points
|
||||
|
||||
### dirigent_core
|
||||
- Uses `dirigent_tools` for ACP client request handlers
|
||||
- Passes configuration from connector params
|
||||
- Routes tool operations through sandbox
|
||||
|
||||
### api
|
||||
- May use `dirigent_tools` for server function implementations
|
||||
- Exposes configuration endpoints
|
||||
- Handles permission prompts via UI
|
||||
|
||||
### web
|
||||
- Displays tool calls with titles, locations, diffs
|
||||
- Renders permission prompts
|
||||
- Shows audit logs
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
Test infrastructure will be set up in SCAFF-03:
|
||||
- Unit tests for each module
|
||||
- Integration tests with mocker
|
||||
- Cross-platform tests (Windows, Linux, macOS)
|
||||
- Golden transcript fixtures
|
||||
- Test utilities for temp directories, file comparison, sandboxed environments
|
||||
|
||||
## Related Packages
|
||||
- **dirigent_core** - Uses this package for tool operations
|
||||
- **api** - May use this package for server functions
|
||||
- **dirigent_protocol** - Shared event types for tool calls
|
||||
|
||||
## References
|
||||
- Task file: `docs/building/04_acp_client/04_tasks_00_scaffolding_and_finishing.md`
|
||||
- Tools research: `docs/building/04_acp_client/03_tools_research.md`
|
||||
- Sandbox spec: `docs/building/04_acp_client/03_fs_sandboxing_and_permissions_spec.md`
|
||||
- Roadmap: `docs/building/04_acp_client/roadmap.md`
|
||||
@@ -1,30 +0,0 @@
|
||||
# Package: opencode_client
|
||||
|
||||
Rust client library for interacting with the OpenCode.ai API.
|
||||
|
||||
## Quick Facts
|
||||
- **Type**: Library
|
||||
- **Main Entry**: src/lib.rs
|
||||
- **Dependencies**: reqwest, serde, serde_json, chrono
|
||||
|
||||
## Key Files
|
||||
- `src/lib.rs` - Public API exports
|
||||
- `src/types.rs` - OpenCode API type definitions (Session, Message, Part, etc.)
|
||||
- `src/client.rs` - HTTP client implementation with optional logging callbacks
|
||||
|
||||
## Main Exports
|
||||
- `OpenCodeClient` - Main API client with methods: list_sessions, list_messages, send_message
|
||||
- `Session` - Session metadata and configuration
|
||||
- `Message` - User or Assistant message (tagged enum)
|
||||
- `MessageWithParts` - Message info + content parts
|
||||
- `Part` - Text, Reasoning, or workflow parts (StepStart, StepFinish, Tool)
|
||||
- `ClientError` - Error types: Http, Request, Serialization
|
||||
- `LogCallback` - Type alias for logging callbacks
|
||||
|
||||
## Related
|
||||
- Used by: web, mobile (future), desktop (future)
|
||||
- Independent: Can be used in any Rust project
|
||||
|
||||
## Documentation
|
||||
- README: ./README.md
|
||||
- API spec: ../../docs/api/opencode.md
|
||||
Reference in New Issue
Block a user