diff --git a/src/acp_client.rs b/src/acp_client.rs index 215b610..a665794 100644 --- a/src/acp_client.rs +++ b/src/acp_client.rs @@ -121,6 +121,7 @@ struct AcpProfileAgentsResponse { #[derive(Debug, Clone, Deserialize)] pub(crate) struct DelegateModelOverrideInfo { pub model_id: String, + #[allow(dead_code)] // Preserved from the ACP response for remote model identity. #[serde(default)] pub node_id: Option, } diff --git a/src/app.rs b/src/app.rs index 3d50193..db4fbde 100644 --- a/src/app.rs +++ b/src/app.rs @@ -261,6 +261,7 @@ pub struct AppLogEntry { // ── Delegation tracking ─────────────────────────────────────────────────────── /// Update per-delegation stats from a single event arriving on a child session. +#[cfg_attr(not(test), allow(dead_code))] pub(crate) fn accumulate_delegate_stats(stats: &mut DelegateStats, kind: &EventKind) { match kind { EventKind::ToolCallStart { .. } => { @@ -301,6 +302,7 @@ pub(crate) fn accumulate_delegate_stats(stats: &mut DelegateStats, kind: &EventK } } +#[cfg_attr(not(test), allow(dead_code))] pub(crate) fn update_delegate_child_state(state: &mut DelegateChildState, kind: &EventKind) { match kind { EventKind::ElicitationRequested { @@ -361,6 +363,7 @@ pub(crate) fn update_delegate_child_state(state: &mut DelegateChildState, kind: } } +#[cfg_attr(not(test), allow(dead_code))] pub(crate) fn backfill_elicitation_outcomes(messages: &mut [ChatEntry], result_str: &str) { let Ok(val) = serde_json::from_str::(result_str) else { return; @@ -764,9 +767,6 @@ pub struct App { pub parent_session_id: Option, /// Staging field: set by delegate popup before LoadSession, consumed by session_loaded. pub pending_parent_session_id: Option, - /// Set after DelegationCompleted/DelegationFailed; consumed by the next - /// UserMessageStored to suppress the noisy batch-result message. - pub suppress_delegation_result: bool, /// Child-session state observed before a delegation entry can be linked. pub pending_delegate_child_states: HashMap, pub pending_delegate_child_stats: HashMap, @@ -982,7 +982,6 @@ impl App { delegate_filter: String::new(), parent_session_id: None, pending_parent_session_id: None, - suppress_delegation_result: false, pending_delegate_child_states: HashMap::new(), pending_delegate_child_stats: HashMap::new(), delegate_child_message_ids: HashMap::new(), @@ -1063,9 +1062,6 @@ impl App { self.reasoning_effort.as_deref().unwrap_or("auto") } - /// Valid reasoning effort levels (excluding "auto" which maps to `None`). - pub const EFFORT_LEVELS: &[&str] = &["low", "medium", "high", "max"]; - /// Cycle through `[auto, low, medium, high, max]` (wraps around). /// Updates `self.reasoning_effort` optimistically, saves the new value as /// the preference for the current `(mode, provider, model)` context, and @@ -1183,23 +1179,12 @@ impl App { .find(|profile| profile.id == profile_id) } - pub fn active_profile(&self) -> Option<&ProfileInfo> { - self.active_profile_id - .as_deref() - .and_then(|profile_id| self.profile_by_id(profile_id)) - } - pub fn current_session_profile_id(&self) -> Option<&str> { self.session_id .as_deref() .and_then(|session_id| self.session_profiles.get(session_id).map(String::as_str)) } - pub fn current_session_profile(&self) -> Option<&ProfileInfo> { - self.current_session_profile_id() - .and_then(|profile_id| self.profile_by_id(profile_id)) - } - pub fn profile_display_name(&self, profile_id: &str) -> String { self.profile_by_id(profile_id) .map(|profile| profile.name.clone()) @@ -1728,6 +1713,7 @@ impl App { self.session_stats.open_llm_request_instant = None; } + #[cfg_attr(not(test), allow(dead_code))] pub fn apply_event_stats(&mut self, kind: &EventKind, timestamp: Option) { match kind { EventKind::ToolCallStart { .. } => { @@ -1816,6 +1802,7 @@ impl App { .unwrap_or(false) } + #[cfg_attr(not(test), allow(dead_code))] pub fn pending_session_label(&self) -> Option<&'static str> { match self.activity { ActivityState::SessionOp(SessionOp::Undo) => Some("undoing"), @@ -1978,6 +1965,7 @@ impl App { // ── delegate model preferences ─────────────────────────────────────────── /// Whether there are multiple agents (multi-agent / delegation mode). + #[cfg_attr(not(test), allow(dead_code))] pub fn is_multi_agent(&self) -> bool { self.agents.len() > 1 } @@ -2875,7 +2863,16 @@ mod session_mode_tests { mod tests { use super::*; use crate::domain::chat::OUTCOME_BULLET; - use crate::protocol::{AgentEvent, ProgressKind}; + use crate::protocol::ProgressKind; + use serde::Deserialize; + + #[derive(Deserialize)] + struct TestAgentEvent { + #[serde(default)] + timestamp: Option, + #[serde(flatten)] + kind: EventKind, + } fn make_turn(message_id: &str) -> UndoableTurn { UndoableTurn { @@ -2894,76 +2891,69 @@ mod tests { #[test] fn backend_next_protocol_events_deserialize() { let session_queued = serde_json::json!({ - "kind": { - "type": "session_queued", - "data": { "reason": "waiting for previous operation to complete" } - }, + "type": "session_queued", + "data": { "reason": "waiting for previous operation to complete" }, "timestamp": null }); let session_configured = serde_json::json!({ - "kind": { - "type": "session_configured", - "data": { - "cwd": "/workspace/project", - "mcp_servers": [], - "limits": { - "max_steps": 200, - "max_turns": 50, - "max_cost_usd": null - } + "type": "session_configured", + "data": { + "cwd": "/workspace/project", + "mcp_servers": [], + "limits": { + "max_steps": 200, + "max_turns": 50, + "max_cost_usd": null } }, "timestamp": null }); let tools_available = serde_json::json!({ - "kind": { - "type": "tools_available", - "data": { - "tools": [{ - "type": "function", - "function": { - "name": "search_text", - "description": "Search file contents", - "parameters": { "type": "object" } - } - }], - "tools_hash": "123456789" - } + "type": "tools_available", + "data": { + "tools": [{ + "type": "function", + "function": { + "name": "search_text", + "description": "Search file contents", + "parameters": { "type": "object" } + } + }], + "tools_hash": "123456789" }, "timestamp": null }); let artifact_recorded = serde_json::json!({ - "kind": { - "type": "artifact_recorded", - "data": { - "artifact": { - "kind": "file", - "uri": null, - "path": "src/generated.txt", - "summary": "Produced by write_file", - "created_at": "2026-04-29T14:25:09Z" - } + "type": "artifact_recorded", + "data": { + "artifact": { + "kind": "file", + "uri": null, + "path": "src/generated.txt", + "summary": "Produced by write_file", + "created_at": "2026-04-29T14:25:09Z" } }, "timestamp": null }); - let queued: AgentEvent = serde_json::from_value(session_queued).unwrap(); + let queued: TestAgentEvent = serde_json::from_value(session_queued).unwrap(); + assert!(queued.timestamp.is_none()); assert!( matches!(queued.kind, EventKind::SessionQueued { reason } if reason == "waiting for previous operation to complete") ); - let configured: AgentEvent = serde_json::from_value(session_configured).unwrap(); + let configured: TestAgentEvent = serde_json::from_value(session_configured).unwrap(); assert!( matches!(configured.kind, EventKind::SessionConfigured { cwd, mcp_servers, limits } if cwd.as_deref() == Some("/workspace/project") && mcp_servers.is_empty() && limits.as_ref().and_then(|l| l.max_steps) == Some(200)) ); - let available: AgentEvent = serde_json::from_value(tools_available).unwrap(); + let available: TestAgentEvent = serde_json::from_value(tools_available).unwrap(); assert!( matches!(available.kind, EventKind::ToolsAvailable { tools, tools_hash } if tools.first().and_then(|tool| tool.function.as_ref()).map(|function| function.name.as_str()) == Some("search_text") && tools_hash.is_some()) ); - let artifact: AgentEvent = serde_json::from_value(artifact_recorded).unwrap(); + let artifact: TestAgentEvent = serde_json::from_value(artifact_recorded).unwrap(); assert!( matches!(artifact.kind, EventKind::ArtifactRecorded { artifact } if artifact.kind == "file" && artifact.path.as_deref() == Some("src/generated.txt") && artifact.summary.as_deref() == Some("Produced by write_file")) ); @@ -2972,43 +2962,36 @@ mod tests { #[test] fn backend_snapshot_and_progress_events_deserialize() { let snapshot_start = serde_json::json!({ - "kind": { - "type": "snapshot_start", - "data": { "policy": "diff" } - }, + "type": "snapshot_start", + "data": { "policy": "diff" }, "timestamp": null }); let snapshot_end = serde_json::json!({ - "kind": { - "type": "snapshot_end", - "data": { "summary": "1 modified" } - }, + "type": "snapshot_end", + "data": { "summary": "1 modified" }, "timestamp": null }); let progress_recorded = serde_json::json!({ - "kind": { - "type": "progress_recorded", - "data": { - "progress_entry": { - "kind": "tool_call", - "content": "Calling tool: shell", - "metadata": "{\"tool\":\"shell\"}", - "created_at": "2026-04-13T00:00:00Z" - } + "type": "progress_recorded", + "data": { + "progress_entry": { + "kind": "tool_call", + "content": "Calling tool: shell", + "metadata": "{\"tool\":\"shell\"}", + "created_at": "2026-04-13T00:00:00Z" } }, "timestamp": null }); - let start: AgentEvent = serde_json::from_value(snapshot_start).unwrap(); - assert!(matches!(start.kind, EventKind::SnapshotStart { policy } if policy == "diff")); + let start: TestAgentEvent = serde_json::from_value(snapshot_start).unwrap(); + let end: TestAgentEvent = serde_json::from_value(snapshot_end).unwrap(); + let progress: TestAgentEvent = serde_json::from_value(progress_recorded).unwrap(); - let end: AgentEvent = serde_json::from_value(snapshot_end).unwrap(); + assert!(matches!(start.kind, EventKind::SnapshotStart { policy } if policy == "diff")); assert!( matches!(end.kind, EventKind::SnapshotEnd { summary } if summary.as_deref() == Some("1 modified")) ); - - let progress: AgentEvent = serde_json::from_value(progress_recorded).unwrap(); assert!( matches!(progress.kind, EventKind::ProgressRecorded { progress_entry } if progress_entry.kind == ProgressKind::ToolCall && progress_entry.content == "Calling tool: shell") ); diff --git a/src/config.rs b/src/config.rs index 156e007..b416014 100644 --- a/src/config.rs +++ b/src/config.rs @@ -13,18 +13,21 @@ use crate::domain::model::DelegateModelPreference; // ── path overrides for tests ───────────────────────────────────────────────── static CONFIG_PATH_OVERRIDE: OnceLock>> = OnceLock::new(); +#[cfg(test)] static TEST_PERSISTENCE_LOCK: OnceLock> = OnceLock::new(); fn config_path_override() -> &'static Mutex> { CONFIG_PATH_OVERRIDE.get_or_init(|| Mutex::new(None)) } +#[cfg(test)] fn test_persistence_lock() -> &'static Mutex<()> { TEST_PERSISTENCE_LOCK.get_or_init(|| Mutex::new(())) } /// Override the config path used by `TuiConfig::load()` / `save()`. /// Intended for tests only; production code should not call this. +#[cfg(test)] pub fn test_set_config_path_override(path: Option) { *config_path_override().lock().unwrap() = path; } @@ -196,11 +199,15 @@ mod tests { dir } - struct TestPathGuard(TestPersistenceGuard); + struct TestPathGuard { + _guard: TestPersistenceGuard, + } impl TestPathGuard { fn new(label: &str) -> Self { - Self(TestPersistenceGuard::new(label)) + Self { + _guard: TestPersistenceGuard::new(label), + } } } diff --git a/src/domain/activity.rs b/src/domain/activity.rs index 027567c..5e9d99c 100644 --- a/src/domain/activity.rs +++ b/src/domain/activity.rs @@ -70,6 +70,7 @@ impl DelegateEntry { ) } + #[cfg_attr(not(test), allow(dead_code))] pub fn pending_elicitation(&self) -> Option<(&str, &str, &str)> { match &self.child_state { DelegateChildState::PendingElicitation { @@ -123,8 +124,13 @@ pub enum ActivityState { Idle, Thinking, Streaming, - RunningTool { name: String }, - Compacting { token_estimate: u32 }, + RunningTool { + name: String, + }, + #[allow(dead_code)] // Retained for compaction rendering and replay state. + Compacting { + token_estimate: u32, + }, SessionOp(SessionOp), } diff --git a/src/domain/auth.rs b/src/domain/auth.rs index cc3185c..4c52a89 100644 --- a/src/domain/auth.rs +++ b/src/domain/auth.rs @@ -53,6 +53,7 @@ impl AuthProviderEntry { } /// Provider supports multiple auth methods (both OAuth and API key). + #[cfg_attr(not(test), allow(dead_code))] pub fn has_multiple_auth_methods(&self) -> bool { self.supports_oauth && self.env_var_name.is_some() } diff --git a/src/domain/chat.rs b/src/domain/chat.rs index 948cdb2..4c73de4 100644 --- a/src/domain/chat.rs +++ b/src/domain/chat.rs @@ -23,19 +23,23 @@ pub enum ChatEntry { is_error: bool, detail: ToolDetail, }, + #[allow(dead_code)] // Retained for compaction event rendering. CompactionStart { token_estimate: u32, }, + #[allow(dead_code)] // Retained for compaction event rendering. CompactionEnd { token_estimate: Option, summary: String, summary_len: u32, }, + #[allow(dead_code)] // Retained for informational replay entries. Info(String), Error(String), Elicitation { elicitation_id: String, message: String, + #[allow(dead_code)] // Retained to preserve elicitation origin metadata. source: String, /// None = pending; Some = responded with this outcome label. outcome: Option, diff --git a/src/domain/elicitation.rs b/src/domain/elicitation.rs index 16d29fe..2877c93 100644 --- a/src/domain/elicitation.rs +++ b/src/domain/elicitation.rs @@ -29,6 +29,7 @@ pub struct ElicitationField { pub struct ElicitationState { pub elicitation_id: String, pub message: String, + #[allow(dead_code)] // Retained so responses can preserve their ACP source metadata. pub source: String, pub fields: Vec, /// Accumulated schema values (field name -> value). diff --git a/src/domain/model.rs b/src/domain/model.rs index 77d88a4..58a9bdf 100644 --- a/src/domain/model.rs +++ b/src/domain/model.rs @@ -18,6 +18,8 @@ pub struct ModelEntry { pub node_id: Option, #[serde(default)] pub node_label: Option, + #[allow(dead_code)] // Preserved from ACP model metadata for future presentation. pub family: Option, + #[allow(dead_code)] // Preserved from ACP model metadata for future presentation. pub quant: Option, } diff --git a/src/domain/profile.rs b/src/domain/profile.rs index 1fcf471..96b0f6e 100644 --- a/src/domain/profile.rs +++ b/src/domain/profile.rs @@ -6,12 +6,14 @@ pub struct ProfileInfo { pub name: String, #[serde(default)] pub description: Option, + #[allow(dead_code)] // Preserved from the profile catalog wire contract. #[serde(default)] pub tags: Vec, #[serde(default)] pub source: Option, #[serde(default)] pub config_kind: Option, + #[allow(dead_code)] // Preserved from the profile catalog wire contract. #[serde(default)] pub fingerprint: Option, } @@ -20,8 +22,10 @@ pub struct ProfileInfo { pub struct AgentInfo { pub id: String, pub name: String, + #[allow(dead_code)] // Preserved from the profile-agent wire contract. #[serde(default)] pub description: Option, + #[allow(dead_code)] // Preserved from the profile-agent wire contract. #[serde(default)] pub capabilities: Vec, } diff --git a/src/domain/session.rs b/src/domain/session.rs index dc56a31..bfebcc1 100644 --- a/src/domain/session.rs +++ b/src/domain/session.rs @@ -11,26 +11,33 @@ pub struct SessionGroup { #[derive(Debug, Clone, Default)] pub struct SessionSummary { pub session_id: String, + #[allow(dead_code)] // Preserved from ACP session metadata. pub name: Option, pub title: Option, /// Working directory for this session (may differ from group cwd for remote sessions). pub cwd: Option, + #[allow(dead_code)] // Preserved from ACP session metadata. pub created_at: Option, pub updated_at: Option, /// Parent session ID if this is a forked session. pub parent_session_id: Option, pub fork_origin: Option, + #[allow(dead_code)] // Preserved from ACP session metadata. pub session_kind: Option, /// Whether this session has child (forked) sessions. + #[allow(dead_code)] // Preserved for native child-page merging and UI expansion. pub has_children: bool, /// Number of direct forked child sessions. pub fork_count: u64, pub children: Vec, pub children_next_cursor: Option, + #[allow(dead_code)] // Preserved for native child-page pagination. pub children_total_count: Option, pub node: Option, pub node_id: Option, + #[allow(dead_code)] // Preserved from remote session metadata. pub attached: Option, + #[allow(dead_code)] // Preserved from remote session metadata. pub runtime_state: Option, } @@ -38,9 +45,11 @@ pub struct SessionSummary { pub struct SessionListPage { pub groups: Vec, pub next_cursor: Option, + #[allow(dead_code)] // Preserved from the native ACP page response. pub total_count: Option, } +#[cfg_attr(not(test), allow(dead_code))] #[derive(Debug, Clone, Default)] pub struct SessionChildrenPage { pub parent_session_id: String, diff --git a/src/domain/tool.rs b/src/domain/tool.rs index 105f939..762bb78 100644 --- a/src/domain/tool.rs +++ b/src/domain/tool.rs @@ -18,6 +18,7 @@ pub enum ToolDetail { /// Compact one-liner info for display after the tool name. Summary(String), /// One-liner header with output displayed below it. + #[allow(dead_code)] // Retained for tool-result rendering. SummaryWithOutput { header: String, output: String, @@ -33,6 +34,7 @@ pub enum ToolDetail { edit_count: usize, sections: Vec, }, + #[allow(dead_code)] // Retained for replace-symbol rendering and UI fixtures. ReplaceSymbol { title: String, sections: Vec, diff --git a/src/lib.rs b/src/lib.rs index d2a0d52..0eeac9f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,3 @@ -#![allow(dead_code)] - mod acp_client; mod acp_state; mod app; diff --git a/src/protocol.rs b/src/protocol.rs index 4e7c1f1..7457621 100644 --- a/src/protocol.rs +++ b/src/protocol.rs @@ -1,743 +1,6 @@ -use serde::{Deserialize, Serialize}; +use serde::Deserialize; -use crate::command::SessionListRequest; -use crate::domain::auth::{AuthMethod, AuthProviderEntry, OAuthFlowKind}; - -// --- Client → Server messages --- - -#[derive(Debug, Serialize)] -#[serde(rename_all = "snake_case")] -pub enum SessionScope { - Root, - Forks, -} - -#[derive(Debug, Serialize)] -#[serde(tag = "type", content = "data", rename_all = "snake_case")] -pub enum ClientMsg { - Init, - ListSessions { - #[serde(skip)] - request: SessionListRequest, - #[serde(skip_serializing_if = "Option::is_none")] - mode: Option, - #[serde(skip_serializing_if = "Option::is_none")] - cursor: Option, - #[serde(skip_serializing_if = "Option::is_none")] - limit: Option, - #[serde(skip_serializing_if = "Option::is_none")] - cwd: Option, - #[serde(skip_serializing_if = "Option::is_none")] - query: Option, - #[serde(skip_serializing_if = "Option::is_none")] - include_remote: Option, - session_scope: SessionScope, - }, - ListRemoteNodes, - ListRemoteSessions { - node_id: String, - #[serde(skip_serializing_if = "Option::is_none")] - offset: Option, - #[serde(skip_serializing_if = "Option::is_none")] - limit: Option, - }, - CreateRemoteSession { - node_id: String, - cwd: Option, - request_id: Option, - }, - AttachRemoteSession { - node_id: String, - session_id: String, - }, - DismissRemoteSession { - session_id: String, - }, - CreateMeshInvite { - #[serde(skip_serializing_if = "Option::is_none")] - mesh_name: Option, - #[serde(skip_serializing_if = "Option::is_none")] - ttl: Option, - #[serde(skip_serializing_if = "Option::is_none")] - max_uses: Option, - }, - ListSessionChildren { - parent_session_id: String, - #[serde(skip_serializing_if = "Option::is_none")] - cursor: Option, - #[serde(skip_serializing_if = "Option::is_none")] - limit: Option, - session_scope: SessionScope, - }, - SetReasoningEffort { - reasoning_effort: String, - }, - ListProfiles, - ListProfileAgents { - profile_id: String, - }, - SetDelegateModel { - session_id: String, - agent_id: String, - model_id: Option, - node_id: Option, - }, - NewSession { - cwd: Option, - request_id: Option, - #[serde(skip_serializing_if = "Option::is_none")] - profile_id: Option, - }, - LoadSession { - session_id: String, - #[serde(skip_serializing_if = "Option::is_none")] - cwd: Option, - }, - Prompt { - prompt: Vec, - #[serde(skip)] - local_id: String, - }, - CancelSession, - ListAllModels { - refresh: bool, - }, - SetSessionModel { - session_id: String, - model_id: String, - node_id: Option, - }, - SubscribeSession { - session_id: String, - agent_id: Option, - }, - DeleteSession { - session_id: String, - }, - ForkSession { - message_id: String, - }, - Undo { - message_id: String, - }, - Redo, - GetFileIndex, - SetAgentMode { - mode: String, - }, - GetAgentMode, - ElicitationResponse { - elicitation_id: String, - action: String, // "accept", "decline", "cancel" - content: Option, - }, - ListAuthProviders, - #[serde(rename = "start_oauth_login")] - StartOAuthLogin { - provider: String, - }, - #[serde(rename = "complete_oauth_login")] - CompleteOAuthLogin { - flow_id: String, - response: String, - }, - #[serde(rename = "disconnect_oauth")] - DisconnectOAuth { - provider: String, - }, - SetApiToken { - provider: String, - api_key: String, - }, - ClearApiToken { - provider: String, - }, - SetAuthMethod { - provider: String, - method: AuthMethod, - }, -} - -impl ClientMsg { - pub fn list_sessions_browse() -> Self { - Self::list_sessions_discovery(None) - } - - pub fn list_sessions_discovery(cursor: Option) -> Self { - Self::ListSessions { - request: SessionListRequest::Discovery, - mode: None, - cursor, - limit: None, - cwd: None, - query: None, - include_remote: Some(true), - session_scope: SessionScope::Root, - } - } - - pub fn list_sessions_workspace(cwd: String) -> Self { - Self::ListSessions { - request: SessionListRequest::WorkspaceFirstPage { cwd: cwd.clone() }, - mode: Some("group".to_string()), - cursor: None, - limit: Some(10), - cwd: Some(cwd), - query: None, - include_remote: None, - session_scope: SessionScope::Root, - } - } - - pub fn list_sessions_group(cwd: String, cursor: String) -> Self { - Self::ListSessions { - request: SessionListRequest::WorkspaceContinuation { cwd: cwd.clone() }, - mode: Some("group".to_string()), - cursor: Some(cursor), - limit: Some(10), - cwd: Some(cwd), - query: None, - include_remote: None, - session_scope: SessionScope::Root, - } - } - - pub fn list_session_children( - parent_session_id: String, - cursor: Option, - limit: u32, - ) -> Self { - Self::ListSessionChildren { - parent_session_id, - cursor, - limit: Some(limit), - session_scope: SessionScope::Forks, - } - } -} - -#[cfg(test)] -mod client_msg_tests { - use super::ClientMsg; - use crate::domain::auth::AuthMethod; - use serde_json::json; - - #[test] - fn list_sessions_browse_serializes_root_session_scope() { - let value = serde_json::to_value(ClientMsg::list_sessions_browse()).unwrap(); - assert_eq!( - value, - json!({ - "type": "list_sessions", - "data": { - "include_remote": true, - "session_scope": "root" - } - }) - ); - } - - #[test] - fn list_sessions_workspace_serializes_cwd_without_cursor() { - let value = serde_json::to_value(ClientMsg::list_sessions_workspace( - "/workspace/project".to_string(), - )) - .unwrap(); - assert_eq!( - value, - json!({ - "type": "list_sessions", - "data": { - "mode": "group", - "limit": 10, - "cwd": "/workspace/project", - "session_scope": "root" - } - }) - ); - } - - #[test] - fn list_sessions_group_omits_include_remote_for_pagination() { - let value = serde_json::to_value(ClientMsg::list_sessions_group( - "/workspace/project".to_string(), - "cursor-1".to_string(), - )) - .unwrap(); - assert!(value["data"].get("include_remote").is_none()); - } - - #[test] - fn list_sessions_discovery_serializes_opaque_cursor() { - let value = serde_json::to_value(ClientMsg::list_sessions_discovery(Some( - "opaque-root-2".to_string(), - ))) - .unwrap(); - assert_eq!( - value, - json!({ - "type": "list_sessions", - "data": { - "cursor": "opaque-root-2", - "include_remote": true, - "session_scope": "root" - } - }) - ); - } - - #[test] - fn remote_session_messages_serialize() { - assert_eq!( - serde_json::to_value(ClientMsg::ListRemoteNodes).unwrap(), - json!({ "type": "list_remote_nodes" }) - ); - assert_eq!( - serde_json::to_value(ClientMsg::ListRemoteSessions { - node_id: "node-1".to_string(), - offset: Some(20), - limit: Some(10), - }) - .unwrap(), - json!({ - "type": "list_remote_sessions", - "data": { "node_id": "node-1", "offset": 20, "limit": 10 } - }) - ); - assert_eq!( - serde_json::to_value(ClientMsg::CreateRemoteSession { - node_id: "node-1".to_string(), - cwd: Some("/repo".to_string()), - request_id: Some("req-1".to_string()), - }) - .unwrap(), - json!({ - "type": "create_remote_session", - "data": { "node_id": "node-1", "cwd": "/repo", "request_id": "req-1" } - }) - ); - assert_eq!( - serde_json::to_value(ClientMsg::AttachRemoteSession { - node_id: "node-1".to_string(), - session_id: "s1".to_string(), - }) - .unwrap(), - json!({ - "type": "attach_remote_session", - "data": { "node_id": "node-1", "session_id": "s1" } - }) - ); - assert_eq!( - serde_json::to_value(ClientMsg::DismissRemoteSession { - session_id: "s1".to_string(), - }) - .unwrap(), - json!({ - "type": "dismiss_remote_session", - "data": { "session_id": "s1" } - }) - ); - } - - #[test] - fn list_sessions_group_serializes_backend_pagination_fields() { - let value = serde_json::to_value(ClientMsg::list_sessions_group( - "/workspace/project".to_string(), - "cursor-1".to_string(), - )) - .unwrap(); - assert_eq!( - value, - json!({ - "type": "list_sessions", - "data": { - "mode": "group", - "cursor": "cursor-1", - "limit": 10, - "cwd": "/workspace/project", - "session_scope": "root" - } - }) - ); - } - - #[test] - fn list_session_children_serializes_forks_scope() { - let value = serde_json::to_value(ClientMsg::list_session_children( - "root-1".to_string(), - Some("child-cursor".to_string()), - 10, - )) - .unwrap(); - assert_eq!( - value, - json!({ - "type": "list_session_children", - "data": { - "parent_session_id": "root-1", - "cursor": "child-cursor", - "limit": 10, - "session_scope": "forks" - } - }) - ); - } - - #[test] - fn fork_session_serializes_message_id() { - let value = serde_json::to_value(ClientMsg::ForkSession { - message_id: "msg-123".to_string(), - }) - .unwrap(); - assert_eq!( - value, - json!({ - "type": "fork_session", - "data": { - "message_id": "msg-123" - } - }) - ); - } - - #[test] - fn list_profiles_serializes() { - let list = serde_json::to_value(ClientMsg::ListProfiles).unwrap(); - assert_eq!(list, json!({ "type": "list_profiles" })); - } - - #[test] - fn list_auth_providers_serializes_exact_shape() { - assert_eq!( - serde_json::to_value(ClientMsg::ListAuthProviders).unwrap(), - json!({ "type": "list_auth_providers" }) - ); - } - - #[test] - fn start_oauth_login_serializes_exact_shape() { - assert_eq!( - serde_json::to_value(ClientMsg::StartOAuthLogin { - provider: "codex".into(), - }) - .unwrap(), - json!({ - "type": "start_oauth_login", - "data": { "provider": "codex" } - }) - ); - } - - #[test] - fn complete_oauth_login_serializes_exact_shape() { - assert_eq!( - serde_json::to_value(ClientMsg::CompleteOAuthLogin { - flow_id: "flow-1".into(), - response: "code-123".into(), - }) - .unwrap(), - json!({ - "type": "complete_oauth_login", - "data": { "flow_id": "flow-1", "response": "code-123" } - }) - ); - } - - #[test] - fn disconnect_oauth_serializes_exact_shape() { - assert_eq!( - serde_json::to_value(ClientMsg::DisconnectOAuth { - provider: "openai".into(), - }) - .unwrap(), - json!({ - "type": "disconnect_oauth", - "data": { "provider": "openai" } - }) - ); - } - - #[test] - fn set_api_token_serializes_exact_shape() { - assert_eq!( - serde_json::to_value(ClientMsg::SetApiToken { - provider: "openai".into(), - api_key: "sk-123".into(), - }) - .unwrap(), - json!({ - "type": "set_api_token", - "data": { "provider": "openai", "api_key": "sk-123" } - }) - ); - } - - #[test] - fn clear_api_token_serializes_exact_shape() { - assert_eq!( - serde_json::to_value(ClientMsg::ClearApiToken { - provider: "openai".into(), - }) - .unwrap(), - json!({ - "type": "clear_api_token", - "data": { "provider": "openai" } - }) - ); - } - - #[test] - fn set_auth_method_serializes_exact_shape() { - assert_eq!( - serde_json::to_value(ClientMsg::SetAuthMethod { - provider: "openai".into(), - method: AuthMethod::ApiKey, - }) - .unwrap(), - json!({ - "type": "set_auth_method", - "data": { "provider": "openai", "method": "api_key" } - }) - ); - } - - #[test] - fn set_oauth_auth_method_serializes_exact_shape() { - assert_eq!( - serde_json::to_value(ClientMsg::SetAuthMethod { - provider: "openai".into(), - method: AuthMethod::OAuth, - }) - .unwrap(), - json!({ - "type": "set_auth_method", - "data": { "provider": "openai", "method": "oauth" } - }) - ); - } - - #[test] - fn delegate_profile_messages_serialize() { - let agents = serde_json::to_value(ClientMsg::ListProfileAgents { - profile_id: "quorum".into(), - }) - .unwrap(); - assert_eq!( - agents, - json!({ - "type": "list_profile_agents", - "data": { "profile_id": "quorum" } - }) - ); - - let set = serde_json::to_value(ClientMsg::SetDelegateModel { - session_id: "parent".into(), - agent_id: "coder".into(), - model_id: Some("openai/gpt-5".into()), - node_id: Some("node-1".into()), - }) - .unwrap(); - assert_eq!( - set, - json!({ - "type": "set_delegate_model", - "data": { - "session_id": "parent", - "agent_id": "coder", - "model_id": "openai/gpt-5", - "node_id": "node-1" - } - }) - ); - } - - #[test] - fn prompt_serializes_exact_shape() { - let value = serde_json::to_value(ClientMsg::Prompt { - prompt: vec![ - super::PromptBlock::Text { - text: "inspect this".into(), - }, - super::PromptBlock::ResourceLink { - name: "main.rs".into(), - uri: "file:///repo/src/main.rs".into(), - }, - ], - local_id: "local-1".into(), - }) - .unwrap(); - - assert_eq!( - value, - json!({ - "type": "prompt", - "data": { - "prompt": [ - { "type": "text", "data": { "text": "inspect this" } }, - { - "type": "resource_link", - "data": { "name": "main.rs", "uri": "file:///repo/src/main.rs" } - } - ] - } - }) - ); - } - - #[test] - fn new_session_serializes_optional_profile_id() { - let without_profile = serde_json::to_value(ClientMsg::NewSession { - cwd: None, - request_id: None, - profile_id: None, - }) - .unwrap(); - assert_eq!( - without_profile, - json!({ - "type": "new_session", - "data": { "cwd": null, "request_id": null } - }) - ); - - let with_profile = serde_json::to_value(ClientMsg::NewSession { - cwd: Some("/repo".to_string()), - request_id: None, - profile_id: Some("fast".to_string()), - }) - .unwrap(); - assert_eq!( - with_profile, - json!({ - "type": "new_session", - "data": { "cwd": "/repo", "request_id": null, "profile_id": "fast" } - }) - ); - } -} - -#[derive(Debug, Serialize)] -#[serde(tag = "type", content = "data", rename_all = "snake_case")] -pub enum PromptBlock { - Text { text: String }, - ResourceLink { name: String, uri: String }, -} - -#[derive(Debug, Deserialize)] -pub struct ReasoningEffortData { - /// `None` or `"auto"` both map to the "auto" (no effort override) state. - pub reasoning_effort: Option, -} - -#[derive(Debug, Deserialize)] -pub struct SessionCreatedData { - pub agent_id: String, - pub session_id: String, - pub request_id: Option, - #[serde(default)] - pub profile_id: Option, -} - -#[derive(Debug, Default, Deserialize)] -pub struct SessionListData { - #[serde(default)] - pub groups: Vec, - #[serde(default)] - pub next_cursor: Option, - #[serde(default)] - pub total_count: Option, -} - -#[derive(Debug, Default, Deserialize)] -pub struct SessionGroupData { - pub cwd: Option, - #[serde(default)] - pub sessions: Vec, - /// ISO 8601 timestamp of the most recent activity in this group. - #[serde(default)] - pub latest_activity: Option, - #[serde(default)] - pub total_count: Option, - #[serde(default)] - pub next_cursor: Option, -} - -#[derive(Debug, Default, Deserialize)] -pub struct SessionSummaryData { - pub session_id: String, - #[serde(default)] - pub name: Option, - #[serde(default)] - pub title: Option, - /// Working directory for this session (may differ from group cwd for remote sessions). - #[serde(default)] - pub cwd: Option, - #[serde(default)] - pub created_at: Option, - #[serde(default)] - pub updated_at: Option, - /// Parent session ID if this is a forked session. - #[serde(default)] - pub parent_session_id: Option, - #[serde(default)] - pub fork_origin: Option, - #[serde(default)] - pub session_kind: Option, - /// Whether this session has child (forked) sessions. - #[serde(default)] - pub has_children: bool, - /// Number of direct forked child sessions. - #[serde(default)] - pub fork_count: u64, - #[serde(default)] - pub children: Vec, - #[serde(default)] - pub children_next_cursor: Option, - #[serde(default)] - pub children_total_count: Option, - #[serde(default)] - pub node: Option, - #[serde(default)] - pub node_id: Option, - #[serde(default)] - pub attached: Option, - #[serde(default)] - pub runtime_state: Option, -} - -#[derive(Debug, Default, Deserialize)] -pub struct SessionChildrenData { - pub parent_session_id: String, - #[serde(default)] - pub sessions: Vec, - #[serde(default)] - pub next_cursor: Option, - #[serde(default)] - pub total_count: Option, -} - -#[derive(Debug, Deserialize)] -pub struct SessionLoadedData { - pub session_id: String, - pub agent_id: String, - pub audit: serde_json::Value, - #[serde(default)] - pub undo_stack: Vec, - #[serde(default)] - pub profile_id: Option, -} - -#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] -pub struct FileIndexEntry { - pub path: String, - pub is_dir: bool, -} - -#[derive(Debug, Deserialize)] -pub struct FileIndexData { - pub files: Vec, - pub generated_at: u64, -} +use crate::domain::auth::{AuthProviderEntry, OAuthFlowKind}; #[derive(Debug, Clone, Deserialize)] pub struct UndoStackFrame { @@ -763,156 +26,9 @@ pub struct RedoResultData { pub undo_stack: Vec, } -#[derive(Debug, Clone, Deserialize)] -pub struct ForkResultData { - pub success: bool, - #[serde(default)] - pub source_session_id: Option, - #[serde(default)] - pub forked_session_id: Option, - #[serde(default)] - pub message: Option, -} - -#[cfg(test)] -mod session_page_data_tests { - use super::{SessionChildrenData, SessionListData}; - use serde_json::json; - - #[test] - fn session_pages_deserialize_recursive_wire_fields_and_defaults() { - let list: SessionListData = serde_json::from_value(json!({ - "groups": [{ - "cwd": "/workspace/project", - "latest_activity": "2024-02-01T00:00:00Z", - "total_count": 4, - "next_cursor": "group-next", - "unknown_group_field": true, - "sessions": [{ - "session_id": "root", - "name": "Session One", - "title": "Root", - "cwd": "/workspace/project", - "created_at": "2024-01-01T00:00:00Z", - "updated_at": "2024-02-01T00:00:00Z", - "parent_session_id": null, - "fork_origin": "manual", - "session_kind": "interactive", - "has_children": true, - "fork_count": 1, - "children_next_cursor": "child-next", - "children_total_count": 2, - "node": "remote", - "node_id": "node-1", - "attached": true, - "runtime_state": "running", - "unknown_session_field": "ignored", - "children": [{ - "session_id": "child", - "parent_session_id": "root", - "fork_origin": "manual", - "children_next_cursor": "grandchild-next", - "children_total_count": 1, - "node_id": "node-2", - "children": [{ "session_id": "grandchild" }] - }] - }] - }], - "next_cursor": "list-next", - "total_count": 9, - "unknown_page_field": null - })) - .expect("session list wire shape should deserialize"); - - assert_eq!(list.next_cursor.as_deref(), Some("list-next")); - assert_eq!(list.total_count, Some(9)); - let group = &list.groups[0]; - assert_eq!(group.cwd.as_deref(), Some("/workspace/project")); - assert_eq!( - group.latest_activity.as_deref(), - Some("2024-02-01T00:00:00Z") - ); - assert_eq!(group.total_count, Some(4)); - assert_eq!(group.next_cursor.as_deref(), Some("group-next")); - let root = &group.sessions[0]; - assert_eq!(root.session_id, "root"); - assert_eq!(root.name.as_deref(), Some("Session One")); - assert_eq!(root.title.as_deref(), Some("Root")); - assert_eq!(root.cwd.as_deref(), Some("/workspace/project")); - assert_eq!(root.created_at.as_deref(), Some("2024-01-01T00:00:00Z")); - assert_eq!(root.updated_at.as_deref(), Some("2024-02-01T00:00:00Z")); - assert_eq!(root.parent_session_id, None); - assert_eq!(root.fork_origin.as_deref(), Some("manual")); - assert_eq!(root.session_kind.as_deref(), Some("interactive")); - assert!(root.has_children); - assert_eq!(root.fork_count, 1); - assert_eq!(root.children_next_cursor.as_deref(), Some("child-next")); - assert_eq!(root.children_total_count, Some(2)); - assert_eq!(root.node.as_deref(), Some("remote")); - assert_eq!(root.node_id.as_deref(), Some("node-1")); - assert_eq!(root.attached, Some(true)); - assert_eq!(root.runtime_state.as_deref(), Some("running")); - - let child = &root.children[0]; - assert_eq!(child.session_id, "child"); - assert_eq!(child.parent_session_id.as_deref(), Some("root")); - assert_eq!(child.fork_origin.as_deref(), Some("manual")); - assert_eq!( - child.children_next_cursor.as_deref(), - Some("grandchild-next") - ); - assert_eq!(child.children_total_count, Some(1)); - assert_eq!(child.node_id.as_deref(), Some("node-2")); - assert_eq!(child.children[0].session_id, "grandchild"); - assert_eq!(child.children[0].fork_count, 0); - assert!(child.children[0].children.is_empty()); - - let children: SessionChildrenData = serde_json::from_value(json!({ - "parent_session_id": "root", - "sessions": [{ - "session_id": "child", - "node_id": "node-2", - "children_next_cursor": "nested-next", - "children_total_count": 1, - "children": [{ "session_id": "grandchild" }] - }], - "next_cursor": "children-next", - "total_count": 3 - })) - .expect("session children wire shape should deserialize"); - assert_eq!(children.parent_session_id, "root"); - assert_eq!(children.next_cursor.as_deref(), Some("children-next")); - assert_eq!(children.total_count, Some(3)); - assert_eq!(children.sessions[0].node_id.as_deref(), Some("node-2")); - assert_eq!( - children.sessions[0].children_next_cursor.as_deref(), - Some("nested-next") - ); - assert_eq!(children.sessions[0].children_total_count, Some(1)); - assert_eq!(children.sessions[0].children[0].session_id, "grandchild"); - - let defaulted_children: SessionChildrenData = serde_json::from_value(json!({ - "parent_session_id": "root", - "sessions": [{ "session_id": "child" }] - })) - .expect("session children defaults should deserialize"); - assert_eq!(defaulted_children.sessions[0].fork_count, 0); - assert!(!defaulted_children.sessions[0].has_children); - assert!(defaulted_children.sessions[0].children.is_empty()); - assert_eq!(defaulted_children.next_cursor, None); - assert_eq!(defaulted_children.total_count, None); - - let empty_list: SessionListData = - serde_json::from_value(json!({})).expect("session list defaults should deserialize"); - assert!(empty_list.groups.is_empty()); - assert_eq!(empty_list.next_cursor, None); - assert_eq!(empty_list.total_count, None); - } -} - #[cfg(test)] mod session_mutation_data_tests { - use super::{ForkResultData, RedoResultData, UndoResultData}; + use super::{RedoResultData, UndoResultData}; use serde_json::json; #[test] @@ -982,103 +98,9 @@ mod session_mutation_data_tests { assert!(result.undo_stack.is_empty()); assert_eq!(result.message.as_deref(), Some("redo rejected")); } - - #[test] - fn fork_result_deserializes_present_and_missing_optional_fields() { - let succeeded: ForkResultData = serde_json::from_value(json!({ - "success": true, - "source_session_id": "source-1", - "forked_session_id": "fork-1", - "message": "forked" - })) - .unwrap(); - assert!(succeeded.success); - assert_eq!(succeeded.source_session_id.as_deref(), Some("source-1")); - assert_eq!(succeeded.forked_session_id.as_deref(), Some("fork-1")); - assert_eq!(succeeded.message.as_deref(), Some("forked")); - - let failed: ForkResultData = serde_json::from_value(json!({ "success": false })).unwrap(); - assert!(!failed.success); - assert_eq!(failed.source_session_id, None); - assert_eq!(failed.forked_session_id, None); - assert_eq!(failed.message, None); - } -} - -#[derive(Debug, Deserialize)] -pub struct EventData { - pub agent_id: String, - pub session_id: String, - #[serde(default)] - pub profile_id: Option, - pub event: EventEnvelope, -} - -/// Like [`EventData`] but keeps the event as raw JSON so an unknown -/// event kind doesn't prevent routing the message entirely. -#[derive(Debug, Deserialize)] -pub struct EventDataRaw { - pub agent_id: String, - pub session_id: String, - #[serde(default)] - pub profile_id: Option, - pub event: serde_json::Value, -} - -#[derive(Debug, Deserialize)] -pub struct SessionEventsData { - pub session_id: String, - pub agent_id: String, - #[serde(default)] - pub profile_id: Option, - pub events: Vec, -} - -/// Like [`SessionEventsData`] but with raw JSON values for events so unknown -/// event kinds don't blow up deserialization of the whole batch. -#[derive(Debug, Deserialize)] -pub struct SessionEventsDataRaw { - pub session_id: String, - pub agent_id: String, - #[serde(default)] - pub profile_id: Option, - pub events: Vec, -} - -#[derive(Debug, Deserialize)] -#[serde(tag = "type", content = "data", rename_all = "snake_case")] -pub enum EventEnvelope { - Durable(InnerEvent), - Ephemeral(InnerEvent), -} - -impl EventEnvelope { - pub fn kind(&self) -> &EventKind { - match self { - Self::Durable(e) | Self::Ephemeral(e) => &e.kind, - } - } - - pub fn timestamp(&self) -> Option { - match self { - Self::Durable(e) | Self::Ephemeral(e) => e.timestamp, - } - } -} - -#[derive(Debug, Deserialize)] -pub struct InnerEvent { - pub kind: EventKind, - pub timestamp: Option, -} - -/// Flat event shape used in AuditView.events (not wrapped in EventEnvelope). -#[derive(Debug, Deserialize)] -pub struct AgentEvent { - pub kind: EventKind, - pub timestamp: Option, } +#[allow(dead_code)] // Active event wire contract retains fields consumed across targets. #[derive(Debug, Clone, Deserialize)] #[serde(tag = "type", content = "data", rename_all = "snake_case")] pub enum EventKind { @@ -1218,6 +240,7 @@ pub enum EventKind { Unknown, } +#[allow(dead_code)] // Nested active EventKind payload. #[derive(Debug, Clone, Deserialize)] pub struct ProgressEntry { pub kind: ProgressKind, @@ -1226,6 +249,7 @@ pub struct ProgressEntry { pub created_at: String, } +#[allow(dead_code)] // Nested active EventKind payload. #[derive(Debug, Clone, Deserialize)] pub struct ArtifactInfo { pub kind: String, @@ -1235,6 +259,7 @@ pub struct ArtifactInfo { pub created_at: String, } +#[allow(dead_code)] // Nested active EventKind payload. #[derive(Debug, Clone, Deserialize, PartialEq, Eq)] #[serde(rename_all = "snake_case")] pub enum ProgressKind { @@ -1244,6 +269,7 @@ pub enum ProgressKind { Checkpoint, } +#[allow(dead_code)] // Nested active EventKind payload. #[derive(Debug, Clone, Deserialize)] pub struct SessionLimits { pub max_steps: Option, @@ -1251,6 +277,7 @@ pub struct SessionLimits { pub max_cost_usd: Option, } +#[allow(dead_code)] // Nested active EventKind payload. #[derive(Debug, Clone, Deserialize)] pub struct ToolInfo { #[serde(rename = "type", default)] @@ -1259,6 +286,7 @@ pub struct ToolInfo { pub function: Option, } +#[allow(dead_code)] // Nested active EventKind payload. #[derive(Debug, Clone, Deserialize)] pub struct FunctionToolInfo { #[serde(default)] @@ -1270,6 +298,7 @@ pub struct FunctionToolInfo { } /// Subset of the server-side `Delegation` struct that we care about. +#[allow(dead_code)] // Nested active EventKind payload. #[derive(Debug, Clone, Deserialize)] pub struct DelegationData { pub public_id: String, @@ -1279,6 +308,7 @@ pub struct DelegationData { pub objective: Option, } +#[allow(dead_code)] // Mesh status preserves server fields not currently rendered. #[derive(Debug, Clone, Deserialize, Default)] pub struct MeshStatusInfo { pub enabled: bool, @@ -1296,12 +326,14 @@ pub struct MeshStatusInfo { pub scopes: Vec, } +#[allow(dead_code)] // Nested active mesh status payload. #[derive(Debug, Clone, Deserialize, Default)] pub struct MeshScopeInfo { pub kind: String, pub id: String, } +#[allow(dead_code)] // Remote node DTO preserves active mesh metadata. #[derive(Debug, Clone, Deserialize, Default)] pub struct RemoteNodeInfo { pub id: String, @@ -1322,6 +354,7 @@ pub struct MeshNodesInfo { pub nodes: Vec, } +#[allow(dead_code)] // Remote session DTO preserves active mesh metadata. #[derive(Debug, Clone, Deserialize, Default)] pub struct RemoteSessionInfo { pub id: String, @@ -1340,6 +373,7 @@ pub struct RemoteSessionInfo { pub model_id: Option, } +#[allow(dead_code)] // Active paged remote-session response boundary. #[derive(Debug, Clone, Deserialize, Default)] pub struct RemoteSessionListInfo { pub node_id: String, @@ -1351,6 +385,7 @@ pub struct RemoteSessionListInfo { pub total_count: u32, } +#[allow(dead_code)] // Active attach response preserves snapshot and config metadata. #[derive(Debug, Clone, Deserialize)] pub struct RemoteSessionAttachInfo { pub session_id: String, @@ -1363,6 +398,7 @@ pub struct RemoteSessionAttachInfo { pub snapshot: serde_json::Value, } +#[allow(dead_code)] // Active invite response preserves server metadata. #[derive(Debug, Clone, Deserialize, Default)] pub struct MeshInviteCreatedInfo { pub invite_id: String, @@ -1377,41 +413,6 @@ pub struct MeshInviteCreatedInfo { pub mesh_name: Option, } -#[derive(Debug, Clone, Deserialize, Default)] -pub struct MeshInviteInfo { - pub invite_id: String, - #[serde(default)] - pub mesh_name: Option, - #[serde(default)] - pub expires_at: u64, - #[serde(default)] - pub max_uses: u32, - #[serde(default)] - pub uses_remaining: u32, - #[serde(default)] - pub status: String, - #[serde(default)] - pub used_by: Vec, - #[serde(default)] - pub created_at: u64, -} - -#[derive(Debug, Clone, Deserialize, Default)] -pub struct MeshInviteListInfo { - #[serde(default)] - pub invites: Vec, -} - -#[derive(Debug, Deserialize)] -pub struct AgentModeData { - pub mode: String, -} - -#[derive(Debug, Deserialize)] -pub struct ErrorData { - pub message: String, -} - // ── Auth / token types ──────────────────────────────────────────────────────── #[derive(Debug, Clone, PartialEq, Eq, Deserialize)] diff --git a/src/runtime/endpoint.rs b/src/runtime/endpoint.rs index 7f66aad..04a8b0c 100644 --- a/src/runtime/endpoint.rs +++ b/src/runtime/endpoint.rs @@ -38,7 +38,6 @@ pub(super) enum EndpointSelection { discovered_ws: Option, missing_binary_fallback: bool, }, - BinaryNotFound, Disabled, } diff --git a/src/runtime/event_loop.rs b/src/runtime/event_loop.rs index 853a1e1..019ef22 100644 --- a/src/runtime/event_loop.rs +++ b/src/runtime/event_loop.rs @@ -105,16 +105,6 @@ pub(super) async fn run_loop( app.set_status(app::LogLevel::Info, "acp", "qmtcode ACP agent started"); } } - ServerEvent::BinaryNotFound => { - app.server_state = ServerState::BinaryNotFound; - if app.conn != app::ConnState::Connected { - app.set_status( - app::LogLevel::Warn, - "acp", - "qmtcode not found; install it or set acp.binary_path in ~/.qmt/qmtui.toml", - ); - } - } ServerEvent::StartFailed { error } => { app.server_state = ServerState::StartFailed { error: error.clone() }; app.set_status( diff --git a/src/runtime/mod.rs b/src/runtime/mod.rs index 898d54f..46d11b2 100644 --- a/src/runtime/mod.rs +++ b/src/runtime/mod.rs @@ -1532,7 +1532,7 @@ pub async fn run() -> anyhow::Result<()> { } | EndpointSelection::Endpoint { missing_binary_fallback: true, .. - } | EndpointSelection::BinaryNotFound + } ) { log_server_binary_discovery(&mut app, &cfg, &discovery); } @@ -1557,10 +1557,6 @@ pub async fn run() -> anyhow::Result<()> { discovered_ws: _, missing_binary_fallback: _, } => (Some(endpoint), state), - EndpointSelection::BinaryNotFound => { - let _ = sup_event_tx.send(server_manager::ServerEvent::BinaryNotFound); - (None, server_manager::ServerState::BinaryNotFound) - } EndpointSelection::Disabled => (None, server_manager::ServerState::Disabled), }; @@ -1604,12 +1600,16 @@ fn restore_hint(session_id: &str) -> String { } #[cfg(test)] -struct PersistenceGuard(config::TestPersistenceGuard); +struct PersistenceGuard { + _guard: config::TestPersistenceGuard, +} #[cfg(test)] impl PersistenceGuard { fn new(label: &str) -> Self { - Self(config::TestPersistenceGuard::new(label)) + Self { + _guard: config::TestPersistenceGuard::new(label), + } } } @@ -3128,10 +3128,6 @@ mod reasoning_effort_integration_tests { } } - fn chord_key(c: char) -> KeyEvent { - KeyEvent::new(KeyCode::Char(c), KeyModifiers::NONE) - } - fn tab_key() -> KeyEvent { KeyEvent::new(KeyCode::Tab, KeyModifiers::NONE) } diff --git a/src/server_manager.rs b/src/server_manager.rs index d862c3b..c880a56 100644 --- a/src/server_manager.rs +++ b/src/server_manager.rs @@ -14,8 +14,6 @@ pub enum ServerEvent { Starting, /// ACP process is running and the client connection is active. Started, - /// No `qmtcode` binary could be found. - BinaryNotFound, /// ACP process failed to start or connect. StartFailed { error: String }, /// ACP process exited. @@ -27,7 +25,6 @@ pub enum ServerEvent { pub enum ServerState { #[default] Disabled, - BinaryNotFound, Starting, Running, StartFailed { @@ -79,10 +76,6 @@ pub fn find_binary_info(configured_path: Option<&str>) -> BinaryDiscovery { } } -pub fn find_binary(configured_path: Option<&str>) -> Option { - find_binary_info(configured_path).binary -} - pub fn build_acp_argv(binary: OsString, args: Vec) -> Vec { let mut argv = vec![binary.to_string_lossy().to_string()]; if args.is_empty() { diff --git a/src/session.rs b/src/session.rs index 3d7fdf9..f26a51e 100644 --- a/src/session.rs +++ b/src/session.rs @@ -50,6 +50,7 @@ fn matching_session_indices(group: &SessionGroup, q: &str) -> Vec { scored.into_iter().map(|(_, i)| i).collect() } +#[cfg_attr(not(test), allow(dead_code))] fn session_by_id_mut<'a>( sessions: &'a mut [SessionSummary], session_id: &str, @@ -65,6 +66,7 @@ fn session_by_id_mut<'a>( None } +#[cfg_attr(not(test), allow(dead_code))] fn fork_browsing_child(session: &SessionSummary) -> bool { session.fork_origin.as_deref() != Some("delegation") } @@ -193,6 +195,7 @@ impl App { false } + #[cfg_attr(not(test), allow(dead_code))] pub fn merge_session_children(&mut self, data: SessionChildrenPage) { let had_pending_request = self .pending_session_child_loads @@ -250,6 +253,7 @@ impl App { /// Flat list of sessions that match the current filter, across all groups. /// /// Used by the session popup (which shows a flat list) for backward compatibility. + #[cfg_attr(not(test), allow(dead_code))] pub fn filtered_sessions(&self) -> Vec<&SessionSummary> { let q = self.session_filter.to_lowercase(); self.session_groups @@ -697,6 +701,7 @@ impl App { ); } + #[cfg_attr(not(test), allow(dead_code))] pub fn active_session_count(&self) -> usize { const ACTIVE_SESSION_WINDOW: Duration = Duration::from_secs(5); let now = Instant::now(); diff --git a/src/theme.rs b/src/theme.rs index b66d2c1..175bf9b 100644 --- a/src/theme.rs +++ b/src/theme.rs @@ -25,6 +25,7 @@ thread_local! { static FRAME_THEME_IDX: std::cell::Cell = const { std::cell::Cell::new(0) }; } +#[allow(dead_code)] // Style helpers form the internal rendering palette, including reserved states. impl Theme { pub fn init(id: &str) { let idx = DARK_THEMES.iter().position(|t| t.id == id).unwrap_or(0); diff --git a/src/ui/chat.rs b/src/ui/chat.rs index 4df8502..131f9c2 100644 --- a/src/ui/chat.rs +++ b/src/ui/chat.rs @@ -27,7 +27,9 @@ const BRAILLE_SPINNER: &[&str] = &["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", " #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub(crate) enum SpinnerKind { Braille, + #[allow(dead_code)] // Retained for alternate spinner fixtures. Line, + #[allow(dead_code)] // Retained for alternate spinner fixtures. Dots, } diff --git a/src/ui/popups.rs b/src/ui/popups.rs index e89a825..5147f7a 100644 --- a/src/ui/popups.rs +++ b/src/ui/popups.rs @@ -595,8 +595,6 @@ fn draw_session_tab_content(f: &mut Frame, app: &mut App, chunks: &std::rc::Rc<[ // ── Delegate session popup ───────────────────────────────────────────────────── -const DELEGATE_POPUP_MAX_W: u16 = 72; -const DELEGATE_POPUP_MIN_W: u16 = 36; const DELEGATE_STATUS_COL_W: usize = 1; const DELEGATE_ICON_TOOLS: &str = "\u{2692}"; // ⚒ const DELEGATE_ICON_MSG: &str = "\u{1F5E9}"; // 🗩 diff --git a/src/ui/start.rs b/src/ui/start.rs index db60352..6f2e346 100644 --- a/src/ui/start.rs +++ b/src/ui/start.rs @@ -19,7 +19,8 @@ pub(super) const COLLAPSE_CLOSED: &str = "\u{25B8}"; // ▸ collapsed group /// /// Returned by [`build_start_page_rows`] and consumed by [`draw_start`]. pub(crate) struct StartPageRow { - /// The logical item this row represents. + /// The logical item this row represents in rendering fixtures. + #[cfg(test)] pub(crate) item: crate::app::StartPageItem, /// Pre-rendered line (spans already styled). pub(crate) line: Line<'static>, @@ -137,6 +138,7 @@ pub(crate) fn build_start_page_rows(app: &App, area_width: usize) -> Vec