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
124 changes: 123 additions & 1 deletion crates/protocol/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,125 @@
# devo-protocol

This crate keep the types utilized between client and server.
This crate defines the protocol types shared by Devo clients and the Devo
server.

## ACP and Devo extension methods

Devo uses ACP JSON-RPC methods for the portable protocol surface. The current
client-to-server ACP methods are:

- `initialize`: negotiate protocol version, client capabilities, and server
metadata.
- `session/new`: create a new session for a working directory.
- `session/list`: list persisted sessions.
- `session/resume`: load a persisted session.
- `session/prompt`: submit a prompt to an active session.
- `session/cancel`: cancel the active session turn.

The current server-to-client ACP notification method is:

- `session/update`: stream session lifecycle, item, plan, usage, and turn-status
updates to subscribed clients. The payload is an `AcpSessionNotification`
whose `update.sessionUpdate` discriminator can include:
- `session_info_update`: session title and update timestamp changes.
- `user_message_chunk`: streamed user message content.
- `agent_message_chunk`: streamed assistant message content.
- `agent_thought_chunk`: streamed assistant reasoning or reasoning-summary
content.
- `tool_call`: initial tool or command-execution call metadata, including
tool call id, title, kind, status, raw input, content, and locations.
- `tool_call_update`: status, output, content, terminal, diff, or location
updates for an existing tool call.
- `plan`: current plan entries and their statuses.
- `available_commands_update`: slash commands currently available to the
client, including command descriptions and optional input hints.
- `current_mode_update`: the current ACP session mode id.
- `config_option_update`: configurable ACP session options currently exposed
by the server.
- `usage_update`: context-window usage and optional cost information.

The current server-to-client ACP request methods are:

- `session/request_permission`: ask the client to approve or reject a tool or
runtime action.
- `fs/read_text_file`: ask the client to read an absolute text-file path.
- `fs/write_text_file`: ask the client to write text to an absolute file path.
- `terminal/create`: ask the client to create a terminal-backed process.
- `terminal/output`: ask the client for a terminal output snapshot.
- `terminal/wait_for_exit`: ask the client to wait for a terminal process to
exit.
- `terminal/kill`: ask the client to kill a terminal process.
- `terminal/release`: ask the client to release a terminal process and clean up
associated state.

Devo-specific client-to-server APIs are sent with the `_devo/` method prefix.
The prefix is applied by the client transport, then removed by the server before
dispatching to `ClientMethod`. These methods remain non-standard ACP extension
points because they expose Devo-specific TUI, runtime, or local workflow
behavior that is not represented by the portable ACP method set.

### Session extensions

- `_devo/session/title/update`: rename a session from the client.
- `_devo/session/metadata/update`: update session metadata such as the active
model or reasoning-effort selection.
- `_devo/session/permissions/update`: update the current permission preset.
- `_devo/session/compact`: proactively compact a session context.
- `_devo/session/fork`: fork a new session from an existing turn.
- `_devo/session/rollback`: roll back a session to a selected user turn.

### Turn extensions

- `_devo/turn/start`: start a Devo turn with the full Devo turn request shape.
If an older server does not support it, the client falls back to ACP
`session/prompt`.
- `_devo/turn/shell_command`: run a user shell command through the server
runtime.
- `_devo/turn/interrupt`: interrupt the active Devo turn.
- `_devo/turn/steer`: send steering input into a running turn.

### Provider and model extensions

- `_devo/provider/list`: list configured provider vendors.
- `_devo/provider/upsert`: add or update a provider vendor and optional model
binding.
- `_devo/provider/validate`: validate provider credentials and model settings.
- `_devo/model/catalog`: read the effective model catalog.
- `_devo/model/saved`: notify the server that model configuration was saved.

### Skills extensions

- `_devo/skills/list`: list available skills for a working directory.
- `_devo/skills/changed`: notify the server that skill files changed.
- `_devo/skills/set_enabled`: persistently enable or disable a skill.

### Command execution extensions

- `_devo/command/exec`: launch a command execution request.
- `_devo/command/exec/write`: write input to a running command.
- `_devo/command/exec/resize`: resize a running command terminal.
- `_devo/command/exec/terminate`: terminate a running command.

### Goal extensions

- `_devo/goal/create`: create a goal for the active thread.
- `_devo/goal/set`: update the current goal objective.
- `_devo/goal/status`: read the current goal state.
- `_devo/goal/pause`: pause goal continuation.
- `_devo/goal/resume`: resume goal continuation.
- `_devo/goal/complete`: mark the goal complete.
- `_devo/goal/clear`: clear the current goal.

### Agent extensions

- `_devo/agent/list`: list subagents associated with a session.
- `_devo/agent/spawn`: spawn a subagent.
- `_devo/agent/close`: close a subagent.

### Reference search and user-input extensions

- `_devo/search/start`: start a server-backed composer reference search.
- `_devo/search/update`: update the active reference-search query.
- `_devo/search/cancel`: cancel the active reference search.
- `_devo/request_user_input/respond`: answer a pending structured user-input
request.
33 changes: 18 additions & 15 deletions crates/server/src/runtime/handlers/acp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@ impl ServerRuntime {
}

pub(crate) async fn handle_acp_session_load(
&self,
self: &Arc<Self>,
connection_id: u64,
request_id: serde_json::Value,
params: serde_json::Value,
Expand Down Expand Up @@ -317,24 +317,24 @@ impl ServerRuntime {
&legacy.result.history_items,
)
.await;
self.send_acp_available_commands_update(connection_id, params.session_id)
.await;
let config_options = match self.acp_session_config_options(params.session_id).await {
Ok(config_options) => config_options,
Err(error) => return acp_error_response(request_id, AcpErrorCode::ServerError, error),
};
acp_success_response(
let response = acp_success_response(
request_id,
AcpLoadSessionResult {
modes: None,
config_options: Some(config_options),
meta: None,
},
)
);
self.schedule_acp_session_state_snapshot_after_response(connection_id, params.session_id);
response
}

pub(crate) async fn handle_acp_session_new(
&self,
self: &Arc<Self>,
connection_id: u64,
request_id: serde_json::Value,
params: serde_json::Value,
Expand Down Expand Up @@ -390,24 +390,27 @@ impl ServerRuntime {
);
self.subscribe_connection_to_session(connection_id, legacy.result.session.session_id, None)
.await;
self.send_acp_available_commands_update(connection_id, legacy.result.session.session_id)
.await;
let config_options = match self
.acp_session_config_options(legacy.result.session.session_id)
.await
{
Ok(config_options) => config_options,
Err(error) => return acp_error_response(request_id, AcpErrorCode::ServerError, error),
};
acp_success_response(
let response = acp_success_response(
request_id,
AcpNewSessionResult {
session_id: legacy.result.session.session_id,
modes: None,
config_options: Some(config_options),
meta: Some(meta),
},
)
);
self.schedule_acp_session_state_snapshot_after_response(
connection_id,
legacy.result.session.session_id,
);
response
}

async fn validate_acp_existing_session_cwd(
Expand Down Expand Up @@ -468,7 +471,7 @@ impl ServerRuntime {
}

pub(crate) async fn handle_acp_session_resume(
&self,
self: &Arc<Self>,
connection_id: u64,
request_id: serde_json::Value,
params: serde_json::Value,
Expand Down Expand Up @@ -545,20 +548,20 @@ impl ServerRuntime {
);
self.subscribe_connection_to_session(connection_id, params.session_id, None)
.await;
self.send_acp_available_commands_update(connection_id, params.session_id)
.await;
let config_options = match self.acp_session_config_options(params.session_id).await {
Ok(config_options) => config_options,
Err(error) => return acp_error_response(request_id, AcpErrorCode::ServerError, error),
};
acp_success_response(
let response = acp_success_response(
request_id,
AcpResumeSessionResult {
modes: None,
config_options: Some(config_options),
meta: Some(meta),
},
)
);
self.schedule_acp_session_state_snapshot_after_response(connection_id, params.session_id);
response
}

async fn apply_acp_session_additional_directories(
Expand Down
15 changes: 13 additions & 2 deletions crates/server/src/runtime/handlers/acp_slash_commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,22 @@ pub(super) enum AcpSlashCommandPromptResult {
}

impl ServerRuntime {
pub(super) async fn send_acp_available_commands_update(
&self,
pub(super) fn schedule_acp_session_state_snapshot_after_response(
self: &Arc<Self>,
connection_id: u64,
session_id: SessionId,
) {
let runtime = Arc::clone(self);
tokio::spawn(async move {
// Let the request handler return and the transport enqueue the JSON-RPC response first.
tokio::time::sleep(std::time::Duration::from_millis(1)).await;
runtime
.send_acp_session_state_snapshot(connection_id, session_id)
.await;
});
}

async fn send_acp_session_state_snapshot(&self, connection_id: u64, session_id: SessionId) {
self.send_acp_session_update(
connection_id,
session_id,
Expand Down
Loading
Loading