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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).

### Added

- feat(acp): bump `agent-client-protocol` 0.10.2→0.10.3, `agent-client-protocol-schema` 0.11.2→0.11.3; add `unstable-logout` feature with no-op logout handler and `auth.logout` capability advertisement; add `unstable-elicitation` feature gate (exposes schema types; SDK methods not yet available upstream); fix discovery endpoint `protocol_version` to use `ProtocolVersion::LATEST`; fix double-feature-activation antipattern in `zeph-acp` feature flags (#2411)
- feat(skills): add `category` field to SKILL.md frontmatter — optional grouping for skill library organisation; all 26 bundled skills annotated with categories (`web`, `data`, `dev`, `system`) (#2268)
- feat(skills): `/skills` command now groups output by category when skills have `category` set; uncategorised skills appear under `[other]` (#2268)
- feat(skills): `/skills confusability` — new command showing all skill pairs whose cosine similarity exceeds `[skills] confusability_threshold`; identifies disambiguation risk before library phase transition (#2268)
Expand Down
8 changes: 4 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ publish = true

[workspace.dependencies]
age = { version = "0.11.2", default-features = false }
agent-client-protocol = "0.10.2"
agent-client-protocol = "0.10.3"
anyhow = "1.0"
async-stream = "0.3"
async-trait = "0.1"
Expand Down Expand Up @@ -179,7 +179,7 @@ a2a = ["dep:zeph-a2a", "zeph-a2a?/server"]
compression-guidelines = ["zeph-core/compression-guidelines", "zeph-memory/compression-guidelines"]
acp = ["dep:zeph-acp"]
acp-http = ["acp", "dep:axum", "zeph-acp/acp-http"]
acp-unstable = ["acp", "zeph-acp/unstable-session-fork", "zeph-acp/unstable-session-resume"]
acp-unstable = ["acp", "zeph-acp/unstable-session-fork", "zeph-acp/unstable-session-resume", "zeph-acp/unstable-elicitation", "zeph-acp/unstable-logout"]
candle = ["zeph-llm/candle", "zeph-core/candle"]
classifiers = ["candle", "zeph-llm/classifiers", "zeph-sanitizer/classifiers", "zeph-core/classifiers"]
metal = ["candle", "zeph-llm/metal", "zeph-core/metal"]
Expand Down
14 changes: 7 additions & 7 deletions crates/zeph-acp/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,20 @@ default = [
"unstable-session-resume",
"unstable-session-usage",
"unstable-session-model",
"unstable-elicitation",
"unstable-logout",
]
acp-http = ["dep:axum", "dep:blake3", "dep:dashmap", "dep:async-stream", "dep:tower-http", "dep:subtle", "dep:tower"]
unstable-session-fork = ["agent-client-protocol/unstable_session_fork"]
unstable-session-resume = ["agent-client-protocol/unstable_session_resume"]
unstable-session-usage = ["agent-client-protocol/unstable_session_usage"]
unstable-session-model = ["agent-client-protocol/unstable_session_model"]
# Exposes schema types (ElicitationRequest, etc.); SDK methods not yet wired in agent-client-protocol 0.10.3.
unstable-elicitation = ["agent-client-protocol/unstable_elicitation"]
unstable-logout = ["agent-client-protocol/unstable_logout"]

[dependencies]
agent-client-protocol = { workspace = true, features = [
"unstable_session_fork",
"unstable_session_resume",
"unstable_session_usage",
"unstable_session_model",
] }
agent-client-protocol = { workspace = true }
async-stream = { workspace = true, optional = true }
async-trait.workspace = true
axum = { workspace = true, features = ["ws"], optional = true }
Expand Down Expand Up @@ -68,7 +68,7 @@ zeph-tools.workspace = true
tempfile.workspace = true
tower = { workspace = true, features = ["util"] }
axum = { workspace = true, features = ["ws"] }
tokio-tungstenite.workspace = true
tokio-tungstenite = { workspace = true, features = ["connect"] }

[lints]
workspace = true
12 changes: 7 additions & 5 deletions crates/zeph-acp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -360,12 +360,13 @@ The `initialize` response includes an `auth_hint` key in its metadata map. For s
| Feature | Status | Description |
|---------|--------|-------------|
| `acp-http` | stable | Enables the HTTP+SSE and WebSocket transports (axum-based). Required for `post_handler`, `get_handler`, `ws_upgrade_handler`, and `router`. |
| `unstable-session-list` | unstable | Enables the `list_sessions` ACP method. See below. |
| `unstable-session-fork` | unstable | Enables the `fork_session` ACP method. See below. |
| `unstable-session-resume` | unstable | Enables the `resume_session` ACP method. See below. |
| `unstable-session-usage` | unstable | Enables `UsageUpdate` events — token counts (input, output, cache) sent to the IDE after each turn. See below. |
| `unstable-session-model` | unstable | Enables `SetSessionModel` — IDE-driven model switching via a native picker without `session/configure`. See below. |
| `unstable-session-info-update` | unstable | Enables `SessionInfoUpdate` — agent-generated session title emitted to the IDE after the first turn. See below. |
| `unstable-elicitation` | unstable | Exposes elicitation schema types (`ElicitationRequest`, etc.) for future agent-loop integration. SDK methods not yet available in 0.10.3. |
| `unstable-logout` | unstable | Enables the `logout` ACP method and advertises `auth.logout` capability. Zeph logout is a no-op (vault-based auth). |

> [!WARNING]
> All `unstable-*` features have wire protocol that is not yet finalized. Expect breaking changes before these features graduate to stable.
Expand All @@ -375,22 +376,23 @@ To opt in, add the desired features in your `Cargo.toml`:
```toml
[dependencies]
zeph-acp = { version = "*", features = [
"unstable-session-list",
"unstable-session-fork",
"unstable-session-resume",
"unstable-session-usage",
"unstable-session-model",
"unstable-session-info-update",
"unstable-elicitation",
"unstable-logout",
] }
```

All flags are independent and can be combined freely.

### `unstable-session-list`
### `session/list` (stable)

Enables the `list_sessions` method on `ZephAcpAgent`. Returns a snapshot of all active in-memory sessions as `SessionInfo` records (session ID, working directory, last-updated timestamp). Supports an optional `cwd` filter — when provided, only sessions whose working directory matches the given path are returned.
The `list_sessions` method on `ZephAcpAgent` is stable as of `agent-client-protocol` 0.10.3 — no feature flag required. Returns a snapshot of all active in-memory sessions as `SessionInfo` records (session ID, working directory, last-updated timestamp). Supports an optional `cwd` filter — when provided, only sessions whose working directory matches the given path are returned.

When this feature is active, `initialize` advertises `SessionListCapabilities` in the `session` capabilities block, signalling to the IDE that the server supports session enumeration.
The `initialize` response always advertises `SessionListCapabilities` in the `session` capabilities block, signalling to the IDE that the server supports session enumeration.

### `unstable-session-fork`

Expand Down
12 changes: 12 additions & 0 deletions crates/zeph-acp/src/agent/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -743,6 +743,10 @@ impl acp::Agent for ZephAcpAgent {
caps.session_capabilities(session_caps)
};

#[cfg(feature = "unstable-logout")]
let caps = caps
.auth(acp::AgentAuthCapabilities::default().logout(acp::LogoutCapabilities::default()));

Ok(acp::InitializeResponse::new(acp::ProtocolVersion::LATEST)
.agent_info(
acp::Implementation::new(&self.agent_name, &self.agent_version).title(title),
Expand Down Expand Up @@ -782,6 +786,14 @@ impl acp::Agent for ZephAcpAgent {
Ok(acp::AuthenticateResponse::default())
}

#[cfg(feature = "unstable-logout")]
async fn logout(&self, _args: acp::LogoutRequest) -> acp::Result<acp::LogoutResponse> {
// Zeph uses vault-based authentication, not session-based auth.
// Logout is a no-op but we advertise the capability for protocol compliance.
tracing::debug!("ACP logout (no-op: vault-based auth)");
Ok(acp::LogoutResponse::default())
}

async fn new_session(
&self,
args: acp::NewSessionRequest,
Expand Down
3 changes: 2 additions & 1 deletion crates/zeph-acp/src/transport/discovery.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// SPDX-FileCopyrightText: 2026 Andrei G <bug-ops>
// SPDX-License-Identifier: MIT OR Apache-2.0

use agent_client_protocol as acp;
use axum::Json;
use axum::extract::State;
use axum::response::IntoResponse;
Expand All @@ -23,7 +24,7 @@ pub async fn discovery_handler(State(state): State<AcpHttpState>) -> impl IntoRe
"name": state.server_config.agent_name,
"version": state.server_config.agent_version,
"protocol": "acp",
"protocol_version": "0.9",
"protocol_version": acp::ProtocolVersion::LATEST,
"transports": {
"http_sse": { "url": "/acp" },
"websocket": { "url": "/acp/ws" },
Expand Down
6 changes: 6 additions & 0 deletions crates/zeph-acp/src/transport/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use std::pin::Pin;
use std::sync::Arc;
use std::sync::atomic::AtomicU64;

use agent_client_protocol;
use axum::body::Body;
use axum::http::{Request, StatusCode};
use tower::ServiceExt as _;
Expand Down Expand Up @@ -557,6 +558,11 @@ async fn discovery_returns_expected_json_fields() {
);
assert_eq!(json["readiness"]["stdio_notification"], "zeph/ready");
assert_eq!(json["readiness"]["http_health_endpoint"], "/health");
// protocol_version must be the integer value of ProtocolVersion::LATEST (1).
assert_eq!(
json["protocol_version"],
serde_json::json!(agent_client_protocol::ProtocolVersion::LATEST)
);
}

#[tokio::test]
Expand Down
60 changes: 60 additions & 0 deletions crates/zeph-acp/tests/integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -613,3 +613,63 @@ async fn e2e_set_session_mode_rejects_unknown_session() {
})
.await;
}

#[cfg(feature = "unstable-logout")]
#[tokio::test]
async fn initialize_advertises_logout_capability() {
let (client_stream, server_stream) = duplex(65536);
let (client_read, client_write) = tokio::io::split(client_stream);
let (server_read, server_write) = tokio::io::split(server_stream);

let server_fut = serve_connection(
make_echo_spawner(),
make_server_config(),
server_write.compat_write(),
server_read.compat(),
);

let client_fut = async {
let local = tokio::task::LocalSet::new();
local
.run_until(async {
let (client_conn, io_fut) = acp::ClientSideConnection::new(
NoopClient,
client_write.compat_write(),
client_read.compat(),
|fut| {
tokio::task::spawn_local(fut);
},
);
tokio::task::spawn_local(async move {
let _ = io_fut.await;
});

let resp = client_conn
.initialize(acp::InitializeRequest::new(acp::ProtocolVersion::LATEST))
.await
.expect("initialize failed");

assert!(
resp.agent_capabilities.auth.logout.is_some(),
"logout capability must be advertised"
);
})
.await;
};

tokio::select! {
res = server_fut => { let _ = res; }
() = client_fut => {}
}
}

#[cfg(feature = "unstable-logout")]
#[tokio::test]
async fn logout_returns_ok() {
with_initialized_client(|conn| async move {
conn.logout(acp::LogoutRequest::default())
.await
.expect("logout must return Ok");
})
.await;
}
Loading