Skip to content

Commit 0db0109

Browse files
authored
Merge pull request #110 from 7df-lab/dev/acp_clean_0622
Document ACP extension methods and fix available commands ordering
2 parents 16d271a + d588127 commit 0db0109

4 files changed

Lines changed: 383 additions & 18 deletions

File tree

crates/protocol/README.md

Lines changed: 123 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,125 @@
11
# devo-protocol
22

3-
This crate keep the types utilized between client and server.
3+
This crate defines the protocol types shared by Devo clients and the Devo
4+
server.
5+
6+
## ACP and Devo extension methods
7+
8+
Devo uses ACP JSON-RPC methods for the portable protocol surface. The current
9+
client-to-server ACP methods are:
10+
11+
- `initialize`: negotiate protocol version, client capabilities, and server
12+
metadata.
13+
- `session/new`: create a new session for a working directory.
14+
- `session/list`: list persisted sessions.
15+
- `session/resume`: load a persisted session.
16+
- `session/prompt`: submit a prompt to an active session.
17+
- `session/cancel`: cancel the active session turn.
18+
19+
The current server-to-client ACP notification method is:
20+
21+
- `session/update`: stream session lifecycle, item, plan, usage, and turn-status
22+
updates to subscribed clients. The payload is an `AcpSessionNotification`
23+
whose `update.sessionUpdate` discriminator can include:
24+
- `session_info_update`: session title and update timestamp changes.
25+
- `user_message_chunk`: streamed user message content.
26+
- `agent_message_chunk`: streamed assistant message content.
27+
- `agent_thought_chunk`: streamed assistant reasoning or reasoning-summary
28+
content.
29+
- `tool_call`: initial tool or command-execution call metadata, including
30+
tool call id, title, kind, status, raw input, content, and locations.
31+
- `tool_call_update`: status, output, content, terminal, diff, or location
32+
updates for an existing tool call.
33+
- `plan`: current plan entries and their statuses.
34+
- `available_commands_update`: slash commands currently available to the
35+
client, including command descriptions and optional input hints.
36+
- `current_mode_update`: the current ACP session mode id.
37+
- `config_option_update`: configurable ACP session options currently exposed
38+
by the server.
39+
- `usage_update`: context-window usage and optional cost information.
40+
41+
The current server-to-client ACP request methods are:
42+
43+
- `session/request_permission`: ask the client to approve or reject a tool or
44+
runtime action.
45+
- `fs/read_text_file`: ask the client to read an absolute text-file path.
46+
- `fs/write_text_file`: ask the client to write text to an absolute file path.
47+
- `terminal/create`: ask the client to create a terminal-backed process.
48+
- `terminal/output`: ask the client for a terminal output snapshot.
49+
- `terminal/wait_for_exit`: ask the client to wait for a terminal process to
50+
exit.
51+
- `terminal/kill`: ask the client to kill a terminal process.
52+
- `terminal/release`: ask the client to release a terminal process and clean up
53+
associated state.
54+
55+
Devo-specific client-to-server APIs are sent with the `_devo/` method prefix.
56+
The prefix is applied by the client transport, then removed by the server before
57+
dispatching to `ClientMethod`. These methods remain non-standard ACP extension
58+
points because they expose Devo-specific TUI, runtime, or local workflow
59+
behavior that is not represented by the portable ACP method set.
60+
61+
### Session extensions
62+
63+
- `_devo/session/title/update`: rename a session from the client.
64+
- `_devo/session/metadata/update`: update session metadata such as the active
65+
model or reasoning-effort selection.
66+
- `_devo/session/permissions/update`: update the current permission preset.
67+
- `_devo/session/compact`: proactively compact a session context.
68+
- `_devo/session/fork`: fork a new session from an existing turn.
69+
- `_devo/session/rollback`: roll back a session to a selected user turn.
70+
71+
### Turn extensions
72+
73+
- `_devo/turn/start`: start a Devo turn with the full Devo turn request shape.
74+
If an older server does not support it, the client falls back to ACP
75+
`session/prompt`.
76+
- `_devo/turn/shell_command`: run a user shell command through the server
77+
runtime.
78+
- `_devo/turn/interrupt`: interrupt the active Devo turn.
79+
- `_devo/turn/steer`: send steering input into a running turn.
80+
81+
### Provider and model extensions
82+
83+
- `_devo/provider/list`: list configured provider vendors.
84+
- `_devo/provider/upsert`: add or update a provider vendor and optional model
85+
binding.
86+
- `_devo/provider/validate`: validate provider credentials and model settings.
87+
- `_devo/model/catalog`: read the effective model catalog.
88+
- `_devo/model/saved`: notify the server that model configuration was saved.
89+
90+
### Skills extensions
91+
92+
- `_devo/skills/list`: list available skills for a working directory.
93+
- `_devo/skills/changed`: notify the server that skill files changed.
94+
- `_devo/skills/set_enabled`: persistently enable or disable a skill.
95+
96+
### Command execution extensions
97+
98+
- `_devo/command/exec`: launch a command execution request.
99+
- `_devo/command/exec/write`: write input to a running command.
100+
- `_devo/command/exec/resize`: resize a running command terminal.
101+
- `_devo/command/exec/terminate`: terminate a running command.
102+
103+
### Goal extensions
104+
105+
- `_devo/goal/create`: create a goal for the active thread.
106+
- `_devo/goal/set`: update the current goal objective.
107+
- `_devo/goal/status`: read the current goal state.
108+
- `_devo/goal/pause`: pause goal continuation.
109+
- `_devo/goal/resume`: resume goal continuation.
110+
- `_devo/goal/complete`: mark the goal complete.
111+
- `_devo/goal/clear`: clear the current goal.
112+
113+
### Agent extensions
114+
115+
- `_devo/agent/list`: list subagents associated with a session.
116+
- `_devo/agent/spawn`: spawn a subagent.
117+
- `_devo/agent/close`: close a subagent.
118+
119+
### Reference search and user-input extensions
120+
121+
- `_devo/search/start`: start a server-backed composer reference search.
122+
- `_devo/search/update`: update the active reference-search query.
123+
- `_devo/search/cancel`: cancel the active reference search.
124+
- `_devo/request_user_input/respond`: answer a pending structured user-input
125+
request.

crates/server/src/runtime/handlers/acp.rs

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,7 @@ impl ServerRuntime {
245245
}
246246

247247
pub(crate) async fn handle_acp_session_load(
248-
&self,
248+
self: &Arc<Self>,
249249
connection_id: u64,
250250
request_id: serde_json::Value,
251251
params: serde_json::Value,
@@ -317,24 +317,24 @@ impl ServerRuntime {
317317
&legacy.result.history_items,
318318
)
319319
.await;
320-
self.send_acp_available_commands_update(connection_id, params.session_id)
321-
.await;
322320
let config_options = match self.acp_session_config_options(params.session_id).await {
323321
Ok(config_options) => config_options,
324322
Err(error) => return acp_error_response(request_id, AcpErrorCode::ServerError, error),
325323
};
326-
acp_success_response(
324+
let response = acp_success_response(
327325
request_id,
328326
AcpLoadSessionResult {
329327
modes: None,
330328
config_options: Some(config_options),
331329
meta: None,
332330
},
333-
)
331+
);
332+
self.schedule_acp_session_state_snapshot_after_response(connection_id, params.session_id);
333+
response
334334
}
335335

336336
pub(crate) async fn handle_acp_session_new(
337-
&self,
337+
self: &Arc<Self>,
338338
connection_id: u64,
339339
request_id: serde_json::Value,
340340
params: serde_json::Value,
@@ -390,24 +390,27 @@ impl ServerRuntime {
390390
);
391391
self.subscribe_connection_to_session(connection_id, legacy.result.session.session_id, None)
392392
.await;
393-
self.send_acp_available_commands_update(connection_id, legacy.result.session.session_id)
394-
.await;
395393
let config_options = match self
396394
.acp_session_config_options(legacy.result.session.session_id)
397395
.await
398396
{
399397
Ok(config_options) => config_options,
400398
Err(error) => return acp_error_response(request_id, AcpErrorCode::ServerError, error),
401399
};
402-
acp_success_response(
400+
let response = acp_success_response(
403401
request_id,
404402
AcpNewSessionResult {
405403
session_id: legacy.result.session.session_id,
406404
modes: None,
407405
config_options: Some(config_options),
408406
meta: Some(meta),
409407
},
410-
)
408+
);
409+
self.schedule_acp_session_state_snapshot_after_response(
410+
connection_id,
411+
legacy.result.session.session_id,
412+
);
413+
response
411414
}
412415

413416
async fn validate_acp_existing_session_cwd(
@@ -468,7 +471,7 @@ impl ServerRuntime {
468471
}
469472

470473
pub(crate) async fn handle_acp_session_resume(
471-
&self,
474+
self: &Arc<Self>,
472475
connection_id: u64,
473476
request_id: serde_json::Value,
474477
params: serde_json::Value,
@@ -545,20 +548,20 @@ impl ServerRuntime {
545548
);
546549
self.subscribe_connection_to_session(connection_id, params.session_id, None)
547550
.await;
548-
self.send_acp_available_commands_update(connection_id, params.session_id)
549-
.await;
550551
let config_options = match self.acp_session_config_options(params.session_id).await {
551552
Ok(config_options) => config_options,
552553
Err(error) => return acp_error_response(request_id, AcpErrorCode::ServerError, error),
553554
};
554-
acp_success_response(
555+
let response = acp_success_response(
555556
request_id,
556557
AcpResumeSessionResult {
557558
modes: None,
558559
config_options: Some(config_options),
559560
meta: Some(meta),
560561
},
561-
)
562+
);
563+
self.schedule_acp_session_state_snapshot_after_response(connection_id, params.session_id);
564+
response
562565
}
563566

564567
async fn apply_acp_session_additional_directories(

crates/server/src/runtime/handlers/acp_slash_commands.rs

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,22 @@ pub(super) enum AcpSlashCommandPromptResult {
4141
}
4242

4343
impl ServerRuntime {
44-
pub(super) async fn send_acp_available_commands_update(
45-
&self,
44+
pub(super) fn schedule_acp_session_state_snapshot_after_response(
45+
self: &Arc<Self>,
4646
connection_id: u64,
4747
session_id: SessionId,
4848
) {
49+
let runtime = Arc::clone(self);
50+
tokio::spawn(async move {
51+
// Let the request handler return and the transport enqueue the JSON-RPC response first.
52+
tokio::time::sleep(std::time::Duration::from_millis(1)).await;
53+
runtime
54+
.send_acp_session_state_snapshot(connection_id, session_id)
55+
.await;
56+
});
57+
}
58+
59+
async fn send_acp_session_state_snapshot(&self, connection_id: u64, session_id: SessionId) {
4960
self.send_acp_session_update(
5061
connection_id,
5162
session_id,

0 commit comments

Comments
 (0)