Compare commits
10 Commits
git/job-qu
...
multi-auth
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
74e80993a3 | ||
|
|
70993b3ce3 | ||
|
|
a47a387186 | ||
|
|
5f6f7edc99 | ||
|
|
4f46ac8c2d | ||
|
|
946c41b4b3 | ||
|
|
7f7312ca8f | ||
|
|
4232b59a59 | ||
|
|
b00901c126 | ||
|
|
e1a83a5fe6 |
@@ -2,6 +2,7 @@ use std::{any::Any, path::Path, rc::Rc, sync::Arc};
|
|||||||
|
|
||||||
use agent_servers::{AgentServer, AgentServerDelegate};
|
use agent_servers::{AgentServer, AgentServerDelegate};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use collections::HashMap;
|
||||||
use fs::Fs;
|
use fs::Fs;
|
||||||
use gpui::{App, Entity, SharedString, Task};
|
use gpui::{App, Entity, SharedString, Task};
|
||||||
use prompt_store::PromptStore;
|
use prompt_store::PromptStore;
|
||||||
@@ -41,7 +42,7 @@ impl AgentServer for NativeAgentServer {
|
|||||||
) -> Task<
|
) -> Task<
|
||||||
Result<(
|
Result<(
|
||||||
Rc<dyn acp_thread::AgentConnection>,
|
Rc<dyn acp_thread::AgentConnection>,
|
||||||
Option<task::SpawnInTerminal>,
|
HashMap<String, task::SpawnInTerminal>,
|
||||||
)>,
|
)>,
|
||||||
> {
|
> {
|
||||||
log::debug!(
|
log::debug!(
|
||||||
@@ -67,7 +68,7 @@ impl AgentServer for NativeAgentServer {
|
|||||||
|
|
||||||
Ok((
|
Ok((
|
||||||
Rc::new(connection) as Rc<dyn acp_thread::AgentConnection>,
|
Rc::new(connection) as Rc<dyn acp_thread::AgentConnection>,
|
||||||
None,
|
HashMap::default(),
|
||||||
))
|
))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,7 +73,12 @@ pub trait AgentServer: Send {
|
|||||||
root_dir: Option<&Path>,
|
root_dir: Option<&Path>,
|
||||||
delegate: AgentServerDelegate,
|
delegate: AgentServerDelegate,
|
||||||
cx: &mut App,
|
cx: &mut App,
|
||||||
) -> Task<Result<(Rc<dyn AgentConnection>, Option<task::SpawnInTerminal>)>>;
|
) -> Task<
|
||||||
|
Result<(
|
||||||
|
Rc<dyn AgentConnection>,
|
||||||
|
HashMap<String, task::SpawnInTerminal>,
|
||||||
|
)>,
|
||||||
|
>;
|
||||||
|
|
||||||
fn into_any(self: Rc<Self>) -> Rc<dyn Any>;
|
fn into_any(self: Rc<Self>) -> Rc<dyn Any>;
|
||||||
}
|
}
|
||||||
@@ -84,6 +89,30 @@ impl dyn AgentServer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Extension trait for ACP-specific agent capabilities.
|
||||||
|
/// This trait is only implemented by agents that use the Agent Client Protocol (ACP).
|
||||||
|
pub trait AcpAgentServer: AgentServer {
|
||||||
|
/// Returns the list of slash commands that should trigger Zed's authentication UI
|
||||||
|
/// when running locally (e.g., "/login").
|
||||||
|
/// These commands will be intercepted by Zed to show the auth method selection UI.
|
||||||
|
fn local_login_commands(&self) -> Vec<String>;
|
||||||
|
|
||||||
|
/// Returns the list of slash commands that should trigger Zed's authentication UI
|
||||||
|
/// when running remotely (e.g., "/login").
|
||||||
|
/// These commands will be intercepted by Zed to show the auth method selection UI.
|
||||||
|
fn remote_login_commands(&self) -> Vec<String>;
|
||||||
|
|
||||||
|
/// Returns the list of logout-related slash commands that should be sent to the agent
|
||||||
|
/// when running locally to let it reset internal state (e.g., "/logout").
|
||||||
|
/// These commands will be added to available_commands and passed through to the agent.
|
||||||
|
fn local_logout_commands(&self) -> Vec<String>;
|
||||||
|
|
||||||
|
/// Returns the list of logout-related slash commands that should be sent to the agent
|
||||||
|
/// when running remotely to let it reset internal state (e.g., "/logout").
|
||||||
|
/// These commands will be added to available_commands and passed through to the agent.
|
||||||
|
fn remote_logout_commands(&self) -> Vec<String>;
|
||||||
|
}
|
||||||
|
|
||||||
/// Load the default proxy environment variables to pass through to the agent
|
/// Load the default proxy environment variables to pass through to the agent
|
||||||
pub fn load_proxy_env(cx: &mut App) -> HashMap<String, String> {
|
pub fn load_proxy_env(cx: &mut App) -> HashMap<String, String> {
|
||||||
let proxy_url = cx
|
let proxy_url = cx
|
||||||
|
|||||||
@@ -7,10 +7,11 @@ use std::sync::Arc;
|
|||||||
use std::{any::Any, path::PathBuf};
|
use std::{any::Any, path::PathBuf};
|
||||||
|
|
||||||
use anyhow::{Context as _, Result};
|
use anyhow::{Context as _, Result};
|
||||||
|
use collections::HashMap;
|
||||||
use gpui::{App, AppContext as _, SharedString, Task};
|
use gpui::{App, AppContext as _, SharedString, Task};
|
||||||
use project::agent_server_store::{AllAgentServersSettings, CLAUDE_CODE_NAME};
|
use project::agent_server_store::{AllAgentServersSettings, CLAUDE_CODE_NAME};
|
||||||
|
|
||||||
use crate::{AgentServer, AgentServerDelegate, load_proxy_env};
|
use crate::{AcpAgentServer, AgentServer, AgentServerDelegate, load_proxy_env};
|
||||||
use acp_thread::AgentConnection;
|
use acp_thread::AgentConnection;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@@ -60,7 +61,12 @@ impl AgentServer for ClaudeCode {
|
|||||||
root_dir: Option<&Path>,
|
root_dir: Option<&Path>,
|
||||||
delegate: AgentServerDelegate,
|
delegate: AgentServerDelegate,
|
||||||
cx: &mut App,
|
cx: &mut App,
|
||||||
) -> Task<Result<(Rc<dyn AgentConnection>, Option<task::SpawnInTerminal>)>> {
|
) -> Task<
|
||||||
|
Result<(
|
||||||
|
Rc<dyn AgentConnection>,
|
||||||
|
HashMap<String, task::SpawnInTerminal>,
|
||||||
|
)>,
|
||||||
|
> {
|
||||||
let name = self.name();
|
let name = self.name();
|
||||||
let root_dir = root_dir.map(|root_dir| root_dir.to_string_lossy().into_owned());
|
let root_dir = root_dir.map(|root_dir| root_dir.to_string_lossy().into_owned());
|
||||||
let is_remote = delegate.project.read(cx).is_via_remote_server();
|
let is_remote = delegate.project.read(cx).is_via_remote_server();
|
||||||
@@ -69,7 +75,7 @@ impl AgentServer for ClaudeCode {
|
|||||||
let default_mode = self.default_mode(cx);
|
let default_mode = self.default_mode(cx);
|
||||||
|
|
||||||
cx.spawn(async move |cx| {
|
cx.spawn(async move |cx| {
|
||||||
let (command, root_dir, login) = store
|
let (command, root_dir, auth_commands) = store
|
||||||
.update(cx, |store, cx| {
|
.update(cx, |store, cx| {
|
||||||
let agent = store
|
let agent = store
|
||||||
.get_external_agent(&CLAUDE_CODE_NAME.into())
|
.get_external_agent(&CLAUDE_CODE_NAME.into())
|
||||||
@@ -92,7 +98,7 @@ impl AgentServer for ClaudeCode {
|
|||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
Ok((connection, login))
|
Ok((connection, auth_commands))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,3 +106,21 @@ impl AgentServer for ClaudeCode {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl AcpAgentServer for ClaudeCode {
|
||||||
|
fn local_login_commands(&self) -> Vec<String> {
|
||||||
|
vec!["login".to_string()]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remote_login_commands(&self) -> Vec<String> {
|
||||||
|
vec!["login".to_string()]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn local_logout_commands(&self) -> Vec<String> {
|
||||||
|
vec!["logout".to_string()]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remote_logout_commands(&self) -> Vec<String> {
|
||||||
|
vec!["logout".to_string()]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,12 +5,13 @@ use std::{any::Any, path::Path};
|
|||||||
use acp_thread::AgentConnection;
|
use acp_thread::AgentConnection;
|
||||||
use agent_client_protocol as acp;
|
use agent_client_protocol as acp;
|
||||||
use anyhow::{Context as _, Result};
|
use anyhow::{Context as _, Result};
|
||||||
|
use collections::HashMap;
|
||||||
use fs::Fs;
|
use fs::Fs;
|
||||||
use gpui::{App, AppContext as _, SharedString, Task};
|
use gpui::{App, AppContext as _, SharedString, Task};
|
||||||
use project::agent_server_store::{AllAgentServersSettings, CODEX_NAME};
|
use project::agent_server_store::{AllAgentServersSettings, CODEX_NAME};
|
||||||
use settings::{SettingsStore, update_settings_file};
|
use settings::{SettingsStore, update_settings_file};
|
||||||
|
|
||||||
use crate::{AgentServer, AgentServerDelegate, load_proxy_env};
|
use crate::{AcpAgentServer, AgentServer, AgentServerDelegate, load_proxy_env};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Codex;
|
pub struct Codex;
|
||||||
@@ -61,7 +62,12 @@ impl AgentServer for Codex {
|
|||||||
root_dir: Option<&Path>,
|
root_dir: Option<&Path>,
|
||||||
delegate: AgentServerDelegate,
|
delegate: AgentServerDelegate,
|
||||||
cx: &mut App,
|
cx: &mut App,
|
||||||
) -> Task<Result<(Rc<dyn AgentConnection>, Option<task::SpawnInTerminal>)>> {
|
) -> Task<
|
||||||
|
Result<(
|
||||||
|
Rc<dyn AgentConnection>,
|
||||||
|
HashMap<String, task::SpawnInTerminal>,
|
||||||
|
)>,
|
||||||
|
> {
|
||||||
let name = self.name();
|
let name = self.name();
|
||||||
let root_dir = root_dir.map(|root_dir| root_dir.to_string_lossy().into_owned());
|
let root_dir = root_dir.map(|root_dir| root_dir.to_string_lossy().into_owned());
|
||||||
let is_remote = delegate.project.read(cx).is_via_remote_server();
|
let is_remote = delegate.project.read(cx).is_via_remote_server();
|
||||||
@@ -70,7 +76,7 @@ impl AgentServer for Codex {
|
|||||||
let default_mode = self.default_mode(cx);
|
let default_mode = self.default_mode(cx);
|
||||||
|
|
||||||
cx.spawn(async move |cx| {
|
cx.spawn(async move |cx| {
|
||||||
let (command, root_dir, login) = store
|
let (command, root_dir, auth_commands) = store
|
||||||
.update(cx, |store, cx| {
|
.update(cx, |store, cx| {
|
||||||
let agent = store
|
let agent = store
|
||||||
.get_external_agent(&CODEX_NAME.into())
|
.get_external_agent(&CODEX_NAME.into())
|
||||||
@@ -96,7 +102,7 @@ impl AgentServer for Codex {
|
|||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
Ok((connection, login))
|
Ok((connection, auth_commands))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,3 +110,21 @@ impl AgentServer for Codex {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl AcpAgentServer for Codex {
|
||||||
|
fn local_login_commands(&self) -> Vec<String> {
|
||||||
|
vec![]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remote_login_commands(&self) -> Vec<String> {
|
||||||
|
vec![]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn local_logout_commands(&self) -> Vec<String> {
|
||||||
|
vec![]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remote_logout_commands(&self) -> Vec<String> {
|
||||||
|
vec![]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
use crate::{AgentServerDelegate, load_proxy_env};
|
use crate::{AcpAgentServer, AgentServerDelegate, load_proxy_env};
|
||||||
use acp_thread::AgentConnection;
|
use acp_thread::AgentConnection;
|
||||||
use agent_client_protocol as acp;
|
use agent_client_protocol as acp;
|
||||||
use anyhow::{Context as _, Result};
|
use anyhow::{Context as _, Result};
|
||||||
|
use collections::HashMap;
|
||||||
use fs::Fs;
|
use fs::Fs;
|
||||||
use gpui::{App, AppContext as _, SharedString, Task};
|
use gpui::{App, AppContext as _, SharedString, Task};
|
||||||
use project::agent_server_store::{AllAgentServersSettings, ExternalAgentServerName};
|
use project::agent_server_store::{AllAgentServersSettings, ExternalAgentServerName};
|
||||||
use settings::{SettingsStore, update_settings_file};
|
use settings::{SettingsStore, update_settings_file};
|
||||||
use std::{path::Path, rc::Rc, sync::Arc};
|
use std::{any::Any, path::Path, rc::Rc, sync::Arc};
|
||||||
use ui::IconName;
|
use ui::IconName;
|
||||||
|
|
||||||
/// A generic agent server implementation for custom user-defined agents
|
/// A generic agent server implementation for custom user-defined agents
|
||||||
@@ -65,7 +66,12 @@ impl crate::AgentServer for CustomAgentServer {
|
|||||||
root_dir: Option<&Path>,
|
root_dir: Option<&Path>,
|
||||||
delegate: AgentServerDelegate,
|
delegate: AgentServerDelegate,
|
||||||
cx: &mut App,
|
cx: &mut App,
|
||||||
) -> Task<Result<(Rc<dyn AgentConnection>, Option<task::SpawnInTerminal>)>> {
|
) -> Task<
|
||||||
|
Result<(
|
||||||
|
Rc<dyn AgentConnection>,
|
||||||
|
HashMap<String, task::SpawnInTerminal>,
|
||||||
|
)>,
|
||||||
|
> {
|
||||||
let name = self.name();
|
let name = self.name();
|
||||||
let root_dir = root_dir.map(|root_dir| root_dir.to_string_lossy().into_owned());
|
let root_dir = root_dir.map(|root_dir| root_dir.to_string_lossy().into_owned());
|
||||||
let is_remote = delegate.project.read(cx).is_via_remote_server();
|
let is_remote = delegate.project.read(cx).is_via_remote_server();
|
||||||
@@ -74,7 +80,7 @@ impl crate::AgentServer for CustomAgentServer {
|
|||||||
let extra_env = load_proxy_env(cx);
|
let extra_env = load_proxy_env(cx);
|
||||||
|
|
||||||
cx.spawn(async move |cx| {
|
cx.spawn(async move |cx| {
|
||||||
let (command, root_dir, login) = store
|
let (command, root_dir, auth_commands) = store
|
||||||
.update(cx, |store, cx| {
|
.update(cx, |store, cx| {
|
||||||
let agent = store
|
let agent = store
|
||||||
.get_external_agent(&ExternalAgentServerName(name.clone()))
|
.get_external_agent(&ExternalAgentServerName(name.clone()))
|
||||||
@@ -99,11 +105,29 @@ impl crate::AgentServer for CustomAgentServer {
|
|||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
Ok((connection, login))
|
Ok((connection, auth_commands))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn into_any(self: Rc<Self>) -> Rc<dyn std::any::Any> {
|
fn into_any(self: Rc<Self>) -> Rc<dyn Any> {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl AcpAgentServer for CustomAgentServer {
|
||||||
|
fn local_login_commands(&self) -> Vec<String> {
|
||||||
|
vec![]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remote_login_commands(&self) -> Vec<String> {
|
||||||
|
vec![]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn local_logout_commands(&self) -> Vec<String> {
|
||||||
|
vec![]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remote_logout_commands(&self) -> Vec<String> {
|
||||||
|
vec![]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::{any::Any, path::Path};
|
use std::{any::Any, path::Path};
|
||||||
|
|
||||||
use crate::{AgentServer, AgentServerDelegate, load_proxy_env};
|
use crate::{AcpAgentServer, AgentServer, AgentServerDelegate, load_proxy_env};
|
||||||
use acp_thread::AgentConnection;
|
use acp_thread::AgentConnection;
|
||||||
use anyhow::{Context as _, Result};
|
use anyhow::{Context as _, Result};
|
||||||
|
use collections::HashMap;
|
||||||
use gpui::{App, SharedString, Task};
|
use gpui::{App, SharedString, Task};
|
||||||
use language_models::provider::google::GoogleLanguageModelProvider;
|
use language_models::provider::google::GoogleLanguageModelProvider;
|
||||||
use project::agent_server_store::GEMINI_NAME;
|
use project::agent_server_store::GEMINI_NAME;
|
||||||
@@ -29,7 +30,12 @@ impl AgentServer for Gemini {
|
|||||||
root_dir: Option<&Path>,
|
root_dir: Option<&Path>,
|
||||||
delegate: AgentServerDelegate,
|
delegate: AgentServerDelegate,
|
||||||
cx: &mut App,
|
cx: &mut App,
|
||||||
) -> Task<Result<(Rc<dyn AgentConnection>, Option<task::SpawnInTerminal>)>> {
|
) -> Task<
|
||||||
|
Result<(
|
||||||
|
Rc<dyn AgentConnection>,
|
||||||
|
HashMap<String, task::SpawnInTerminal>,
|
||||||
|
)>,
|
||||||
|
> {
|
||||||
let name = self.name();
|
let name = self.name();
|
||||||
let root_dir = root_dir.map(|root_dir| root_dir.to_string_lossy().into_owned());
|
let root_dir = root_dir.map(|root_dir| root_dir.to_string_lossy().into_owned());
|
||||||
let is_remote = delegate.project.read(cx).is_via_remote_server();
|
let is_remote = delegate.project.read(cx).is_via_remote_server();
|
||||||
@@ -47,7 +53,7 @@ impl AgentServer for Gemini {
|
|||||||
{
|
{
|
||||||
extra_env.insert("GEMINI_API_KEY".into(), api_key);
|
extra_env.insert("GEMINI_API_KEY".into(), api_key);
|
||||||
}
|
}
|
||||||
let (command, root_dir, login) = store
|
let (command, root_dir, auth_commands) = store
|
||||||
.update(cx, |store, cx| {
|
.update(cx, |store, cx| {
|
||||||
let agent = store
|
let agent = store
|
||||||
.get_external_agent(&GEMINI_NAME.into())
|
.get_external_agent(&GEMINI_NAME.into())
|
||||||
@@ -71,7 +77,7 @@ impl AgentServer for Gemini {
|
|||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
Ok((connection, login))
|
Ok((connection, auth_commands))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,6 +86,26 @@ impl AgentServer for Gemini {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl AcpAgentServer for Gemini {
|
||||||
|
fn local_login_commands(&self) -> Vec<String> {
|
||||||
|
vec!["login".to_string()]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remote_login_commands(&self) -> Vec<String> {
|
||||||
|
// When remote, OAuth doesn't work, so login is handled via the
|
||||||
|
// auth_commands mapping (oauth-personal -> spawn-gemini-cli)
|
||||||
|
vec![]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn local_logout_commands(&self) -> Vec<String> {
|
||||||
|
vec![]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remote_logout_commands(&self) -> Vec<String> {
|
||||||
|
vec![]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub(crate) mod tests {
|
pub(crate) mod tests {
|
||||||
use project::agent_server_store::AgentServerCommand;
|
use project::agent_server_store::AgentServerCommand;
|
||||||
|
|||||||
@@ -260,10 +260,11 @@ impl ThreadFeedbackState {
|
|||||||
|
|
||||||
pub struct AcpThreadView {
|
pub struct AcpThreadView {
|
||||||
agent: Rc<dyn AgentServer>,
|
agent: Rc<dyn AgentServer>,
|
||||||
|
acp_agent: Option<Rc<dyn agent_servers::AcpAgentServer>>,
|
||||||
workspace: WeakEntity<Workspace>,
|
workspace: WeakEntity<Workspace>,
|
||||||
project: Entity<Project>,
|
project: Entity<Project>,
|
||||||
thread_state: ThreadState,
|
thread_state: ThreadState,
|
||||||
login: Option<task::SpawnInTerminal>,
|
auth_commands: HashMap<String, task::SpawnInTerminal>,
|
||||||
history_store: Entity<HistoryStore>,
|
history_store: Entity<HistoryStore>,
|
||||||
hovered_recent_history_item: Option<usize>,
|
hovered_recent_history_item: Option<usize>,
|
||||||
entry_view_state: Entity<EntryViewState>,
|
entry_view_state: Entity<EntryViewState>,
|
||||||
@@ -403,8 +404,38 @@ impl AcpThreadView {
|
|||||||
let show_codex_windows_warning = crate::ExternalAgent::parse_built_in(agent.as_ref())
|
let show_codex_windows_warning = crate::ExternalAgent::parse_built_in(agent.as_ref())
|
||||||
== Some(crate::ExternalAgent::Codex);
|
== Some(crate::ExternalAgent::Codex);
|
||||||
|
|
||||||
|
// Try to downcast to AcpAgentServer for ACP-specific functionality
|
||||||
|
let acp_agent = agent
|
||||||
|
.clone()
|
||||||
|
.into_any()
|
||||||
|
.downcast::<agent_servers::Gemini>()
|
||||||
|
.map(|a| a as Rc<dyn agent_servers::AcpAgentServer>)
|
||||||
|
.or_else(|_| {
|
||||||
|
agent
|
||||||
|
.clone()
|
||||||
|
.into_any()
|
||||||
|
.downcast::<agent_servers::ClaudeCode>()
|
||||||
|
.map(|a| a as Rc<dyn agent_servers::AcpAgentServer>)
|
||||||
|
})
|
||||||
|
.or_else(|_| {
|
||||||
|
agent
|
||||||
|
.clone()
|
||||||
|
.into_any()
|
||||||
|
.downcast::<agent_servers::Codex>()
|
||||||
|
.map(|a| a as Rc<dyn agent_servers::AcpAgentServer>)
|
||||||
|
})
|
||||||
|
.or_else(|_| {
|
||||||
|
agent
|
||||||
|
.clone()
|
||||||
|
.into_any()
|
||||||
|
.downcast::<agent_servers::CustomAgentServer>()
|
||||||
|
.map(|a| a as Rc<dyn agent_servers::AcpAgentServer>)
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
agent: agent.clone(),
|
agent: agent.clone(),
|
||||||
|
acp_agent,
|
||||||
workspace: workspace.clone(),
|
workspace: workspace.clone(),
|
||||||
project: project.clone(),
|
project: project.clone(),
|
||||||
entry_view_state,
|
entry_view_state,
|
||||||
@@ -416,7 +447,7 @@ impl AcpThreadView {
|
|||||||
window,
|
window,
|
||||||
cx,
|
cx,
|
||||||
),
|
),
|
||||||
login: None,
|
auth_commands: HashMap::default(),
|
||||||
message_editor,
|
message_editor,
|
||||||
model_selector: None,
|
model_selector: None,
|
||||||
profile_selector: None,
|
profile_selector: None,
|
||||||
@@ -509,8 +540,9 @@ impl AcpThreadView {
|
|||||||
let connect_task = agent.connect(root_dir.as_deref(), delegate, cx);
|
let connect_task = agent.connect(root_dir.as_deref(), delegate, cx);
|
||||||
let load_task = cx.spawn_in(window, async move |this, cx| {
|
let load_task = cx.spawn_in(window, async move |this, cx| {
|
||||||
let connection = match connect_task.await {
|
let connection = match connect_task.await {
|
||||||
Ok((connection, login)) => {
|
Ok((connection, auth_commands)) => {
|
||||||
this.update(cx, |this, _| this.login = login).ok();
|
this.update(cx, |this, _| this.auth_commands = auth_commands)
|
||||||
|
.ok();
|
||||||
connection
|
connection
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
@@ -1051,20 +1083,52 @@ impl AcpThreadView {
|
|||||||
|
|
||||||
let text = self.message_editor.read(cx).text(cx);
|
let text = self.message_editor.read(cx).text(cx);
|
||||||
let text = text.trim();
|
let text = text.trim();
|
||||||
if text == "/login" || text == "/logout" {
|
|
||||||
|
// Check if this is a login or logout command (only for ACP agents)
|
||||||
|
let command_name = text.strip_prefix('/');
|
||||||
|
let is_remote = self.project.read(cx).is_via_remote_server();
|
||||||
|
let (login_commands, logout_commands) = if let Some(acp_agent) = &self.acp_agent {
|
||||||
|
let login = if is_remote {
|
||||||
|
acp_agent.remote_login_commands()
|
||||||
|
} else {
|
||||||
|
acp_agent.local_login_commands()
|
||||||
|
};
|
||||||
|
let logout = if is_remote {
|
||||||
|
acp_agent.remote_logout_commands()
|
||||||
|
} else {
|
||||||
|
acp_agent.local_logout_commands()
|
||||||
|
};
|
||||||
|
(login, logout)
|
||||||
|
} else {
|
||||||
|
(vec![], vec![])
|
||||||
|
};
|
||||||
|
let is_login_command = if let Some(cmd) = command_name {
|
||||||
|
login_commands.iter().any(|c| c == cmd)
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
let is_logout_command = if let Some(cmd) = command_name {
|
||||||
|
logout_commands.iter().any(|c| c == cmd)
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
|
||||||
|
if is_login_command || is_logout_command {
|
||||||
let ThreadState::Ready { thread, .. } = &self.thread_state else {
|
let ThreadState::Ready { thread, .. } = &self.thread_state else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let connection = thread.read(cx).connection().clone();
|
let connection = thread.read(cx).connection().clone();
|
||||||
let can_login = !connection.auth_methods().is_empty() || self.login.is_some();
|
let can_login = !connection.auth_methods().is_empty() || !self.auth_commands.is_empty();
|
||||||
|
|
||||||
// Does the agent have a specific logout command? Prefer that in case they need to reset internal state.
|
// Does the agent have a specific logout command? Prefer that in case they need to reset internal state.
|
||||||
let logout_supported = text == "/logout"
|
let logout_supported = is_logout_command
|
||||||
&& self
|
&& self
|
||||||
.available_commands
|
.available_commands
|
||||||
.borrow()
|
.borrow()
|
||||||
.iter()
|
.iter()
|
||||||
.any(|command| command.name == "logout");
|
.any(|command| command_name == Some(command.name.as_str()));
|
||||||
|
|
||||||
if can_login && !logout_supported {
|
if can_login && !logout_supported {
|
||||||
self.message_editor
|
self.message_editor
|
||||||
.update(cx, |editor, cx| editor.clear(window, cx));
|
.update(cx, |editor, cx| editor.clear(window, cx));
|
||||||
@@ -1421,25 +1485,39 @@ impl AcpThreadView {
|
|||||||
AcpThreadEvent::AvailableCommandsUpdated(available_commands) => {
|
AcpThreadEvent::AvailableCommandsUpdated(available_commands) => {
|
||||||
let mut available_commands = available_commands.clone();
|
let mut available_commands = available_commands.clone();
|
||||||
|
|
||||||
if thread
|
// Add auth commands only for ACP agents
|
||||||
.read(cx)
|
if let Some(acp_agent) = &self.acp_agent {
|
||||||
.connection()
|
let is_remote = self.project.read(cx).is_via_remote_server();
|
||||||
.auth_methods()
|
let login_commands = if is_remote {
|
||||||
.iter()
|
acp_agent.remote_login_commands()
|
||||||
.any(|method| method.id.0.as_ref() == "claude-login")
|
} else {
|
||||||
{
|
acp_agent.local_login_commands()
|
||||||
available_commands.push(acp::AvailableCommand {
|
};
|
||||||
name: "login".to_owned(),
|
let logout_commands = if is_remote {
|
||||||
description: "Authenticate".to_owned(),
|
acp_agent.remote_logout_commands()
|
||||||
input: None,
|
} else {
|
||||||
meta: None,
|
acp_agent.local_logout_commands()
|
||||||
});
|
};
|
||||||
available_commands.push(acp::AvailableCommand {
|
|
||||||
name: "logout".to_owned(),
|
// Add login commands from the agent
|
||||||
description: "Authenticate".to_owned(),
|
for command_name in login_commands {
|
||||||
input: None,
|
available_commands.push(acp::AvailableCommand {
|
||||||
meta: None,
|
name: command_name,
|
||||||
});
|
description: "Authenticate".to_owned(),
|
||||||
|
input: None,
|
||||||
|
meta: None,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add logout commands from the agent
|
||||||
|
for command_name in logout_commands {
|
||||||
|
available_commands.push(acp::AvailableCommand {
|
||||||
|
name: command_name,
|
||||||
|
description: "Authenticate".to_owned(),
|
||||||
|
input: None,
|
||||||
|
meta: None,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.available_commands.replace(available_commands);
|
self.available_commands.replace(available_commands);
|
||||||
@@ -1561,10 +1639,7 @@ impl AcpThreadView {
|
|||||||
self.thread_error.take();
|
self.thread_error.take();
|
||||||
configuration_view.take();
|
configuration_view.take();
|
||||||
pending_auth_method.replace(method.clone());
|
pending_auth_method.replace(method.clone());
|
||||||
let authenticate = if (method.0.as_ref() == "claude-login"
|
let authenticate = if let Some(login) = self.auth_commands.get(method.0.as_ref()).cloned() {
|
||||||
|| method.0.as_ref() == "spawn-gemini-cli")
|
|
||||||
&& let Some(login) = self.login.clone()
|
|
||||||
{
|
|
||||||
if let Some(workspace) = self.workspace.upgrade() {
|
if let Some(workspace) = self.workspace.upgrade() {
|
||||||
Self::spawn_external_agent_login(login, workspace, false, window, cx)
|
Self::spawn_external_agent_login(login, workspace, false, window, cx)
|
||||||
} else {
|
} else {
|
||||||
@@ -3270,48 +3345,34 @@ impl AcpThreadView {
|
|||||||
})
|
})
|
||||||
.children(connection.auth_methods().iter().enumerate().rev().map(
|
.children(connection.auth_methods().iter().enumerate().rev().map(
|
||||||
|(ix, method)| {
|
|(ix, method)| {
|
||||||
let (method_id, name) = if self
|
let method_id = method.id.clone();
|
||||||
.project
|
let method_id_str = method.id.0.to_string();
|
||||||
.read(cx)
|
Button::new(
|
||||||
.is_via_remote_server()
|
SharedString::from(method.id.0.clone()),
|
||||||
&& method.id.0.as_ref() == "oauth-personal"
|
method.name.clone(),
|
||||||
&& method.name == "Log in with Google"
|
)
|
||||||
{
|
.label_size(LabelSize::Small)
|
||||||
("spawn-gemini-cli".into(), "Log in with Gemini CLI".into())
|
.map(|this| {
|
||||||
} else {
|
if ix == 0 {
|
||||||
(method.id.0.clone(), method.name.clone())
|
this.style(ButtonStyle::Tinted(TintColor::Warning))
|
||||||
};
|
} else {
|
||||||
|
this.style(ButtonStyle::Outlined)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.when_some(method.description.clone(), |this, description| {
|
||||||
|
this.tooltip(Tooltip::text(description))
|
||||||
|
})
|
||||||
|
.on_click({
|
||||||
|
cx.listener(move |this, _, window, cx| {
|
||||||
|
telemetry::event!(
|
||||||
|
"Authenticate Agent Started",
|
||||||
|
agent = this.agent.telemetry_id(),
|
||||||
|
method = method_id_str
|
||||||
|
);
|
||||||
|
|
||||||
Button::new(SharedString::from(method_id.clone()), name)
|
this.authenticate(method_id.clone(), window, cx)
|
||||||
.label_size(LabelSize::Small)
|
|
||||||
.map(|this| {
|
|
||||||
if ix == 0 {
|
|
||||||
this.style(ButtonStyle::Tinted(TintColor::Warning))
|
|
||||||
} else {
|
|
||||||
this.style(ButtonStyle::Outlined)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.when_some(
|
|
||||||
method.description.clone(),
|
|
||||||
|this, description| {
|
|
||||||
this.tooltip(Tooltip::text(description))
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.on_click({
|
|
||||||
cx.listener(move |this, _, window, cx| {
|
|
||||||
telemetry::event!(
|
|
||||||
"Authenticate Agent Started",
|
|
||||||
agent = this.agent.telemetry_id(),
|
|
||||||
method = method_id
|
|
||||||
);
|
|
||||||
|
|
||||||
this.authenticate(
|
|
||||||
acp::AuthMethodId(method_id.clone()),
|
|
||||||
window,
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
})
|
||||||
},
|
},
|
||||||
)),
|
)),
|
||||||
)
|
)
|
||||||
@@ -6033,8 +6094,13 @@ pub(crate) mod tests {
|
|||||||
_root_dir: Option<&Path>,
|
_root_dir: Option<&Path>,
|
||||||
_delegate: AgentServerDelegate,
|
_delegate: AgentServerDelegate,
|
||||||
_cx: &mut App,
|
_cx: &mut App,
|
||||||
) -> Task<gpui::Result<(Rc<dyn AgentConnection>, Option<task::SpawnInTerminal>)>> {
|
) -> Task<
|
||||||
Task::ready(Ok((Rc::new(self.connection.clone()), None)))
|
gpui::Result<(
|
||||||
|
Rc<dyn AgentConnection>,
|
||||||
|
HashMap<String, task::SpawnInTerminal>,
|
||||||
|
)>,
|
||||||
|
> {
|
||||||
|
Task::ready(Ok((Rc::new(self.connection.clone()), HashMap::default())))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn into_any(self: Rc<Self>) -> Rc<dyn Any> {
|
fn into_any(self: Rc<Self>) -> Rc<dyn Any> {
|
||||||
|
|||||||
@@ -95,7 +95,13 @@ pub trait ExternalAgentServer {
|
|||||||
status_tx: Option<watch::Sender<SharedString>>,
|
status_tx: Option<watch::Sender<SharedString>>,
|
||||||
new_version_available_tx: Option<watch::Sender<Option<String>>>,
|
new_version_available_tx: Option<watch::Sender<Option<String>>>,
|
||||||
cx: &mut AsyncApp,
|
cx: &mut AsyncApp,
|
||||||
) -> Task<Result<(AgentServerCommand, String, Option<task::SpawnInTerminal>)>>;
|
) -> Task<
|
||||||
|
Result<(
|
||||||
|
AgentServerCommand,
|
||||||
|
String,
|
||||||
|
HashMap<String, task::SpawnInTerminal>,
|
||||||
|
)>,
|
||||||
|
>;
|
||||||
|
|
||||||
fn as_any_mut(&mut self) -> &mut dyn Any;
|
fn as_any_mut(&mut self) -> &mut dyn Any;
|
||||||
}
|
}
|
||||||
@@ -392,7 +398,7 @@ impl AgentServerStore {
|
|||||||
envelope: TypedEnvelope<proto::GetAgentServerCommand>,
|
envelope: TypedEnvelope<proto::GetAgentServerCommand>,
|
||||||
mut cx: AsyncApp,
|
mut cx: AsyncApp,
|
||||||
) -> Result<proto::AgentServerCommand> {
|
) -> Result<proto::AgentServerCommand> {
|
||||||
let (command, root_dir, login) = this
|
let (command, root_dir, _login) = this
|
||||||
.update(&mut cx, |this, cx| {
|
.update(&mut cx, |this, cx| {
|
||||||
let AgentServerStoreState::Local {
|
let AgentServerStoreState::Local {
|
||||||
downstream_client, ..
|
downstream_client, ..
|
||||||
@@ -466,7 +472,7 @@ impl AgentServerStore {
|
|||||||
.map(|env| env.into_iter().collect())
|
.map(|env| env.into_iter().collect())
|
||||||
.unwrap_or_default(),
|
.unwrap_or_default(),
|
||||||
root_dir: root_dir,
|
root_dir: root_dir,
|
||||||
login: login.map(|login| login.to_proto()),
|
login: None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -774,7 +780,13 @@ impl ExternalAgentServer for RemoteExternalAgentServer {
|
|||||||
status_tx: Option<watch::Sender<SharedString>>,
|
status_tx: Option<watch::Sender<SharedString>>,
|
||||||
new_version_available_tx: Option<watch::Sender<Option<String>>>,
|
new_version_available_tx: Option<watch::Sender<Option<String>>>,
|
||||||
cx: &mut AsyncApp,
|
cx: &mut AsyncApp,
|
||||||
) -> Task<Result<(AgentServerCommand, String, Option<task::SpawnInTerminal>)>> {
|
) -> Task<
|
||||||
|
Result<(
|
||||||
|
AgentServerCommand,
|
||||||
|
String,
|
||||||
|
HashMap<String, task::SpawnInTerminal>,
|
||||||
|
)>,
|
||||||
|
> {
|
||||||
let project_id = self.project_id;
|
let project_id = self.project_id;
|
||||||
let name = self.name.to_string();
|
let name = self.name.to_string();
|
||||||
let upstream_client = self.upstream_client.downgrade();
|
let upstream_client = self.upstream_client.downgrade();
|
||||||
@@ -811,9 +823,7 @@ impl ExternalAgentServer for RemoteExternalAgentServer {
|
|||||||
env: Some(command.env),
|
env: Some(command.env),
|
||||||
},
|
},
|
||||||
root_dir,
|
root_dir,
|
||||||
response
|
HashMap::default(),
|
||||||
.login
|
|
||||||
.map(|login| task::SpawnInTerminal::from_proto(login)),
|
|
||||||
))
|
))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -839,7 +849,13 @@ impl ExternalAgentServer for LocalGemini {
|
|||||||
status_tx: Option<watch::Sender<SharedString>>,
|
status_tx: Option<watch::Sender<SharedString>>,
|
||||||
new_version_available_tx: Option<watch::Sender<Option<String>>>,
|
new_version_available_tx: Option<watch::Sender<Option<String>>>,
|
||||||
cx: &mut AsyncApp,
|
cx: &mut AsyncApp,
|
||||||
) -> Task<Result<(AgentServerCommand, String, Option<task::SpawnInTerminal>)>> {
|
) -> Task<
|
||||||
|
Result<(
|
||||||
|
AgentServerCommand,
|
||||||
|
String,
|
||||||
|
HashMap<String, task::SpawnInTerminal>,
|
||||||
|
)>,
|
||||||
|
> {
|
||||||
let fs = self.fs.clone();
|
let fs = self.fs.clone();
|
||||||
let node_runtime = self.node_runtime.clone();
|
let node_runtime = self.node_runtime.clone();
|
||||||
let project_environment = self.project_environment.downgrade();
|
let project_environment = self.project_environment.downgrade();
|
||||||
@@ -906,12 +922,15 @@ impl ExternalAgentServer for LocalGemini {
|
|||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let mut auth_commands = HashMap::default();
|
||||||
|
auth_commands.insert("spawn-gemini-cli".to_string(), login);
|
||||||
|
|
||||||
command.env.get_or_insert_default().extend(extra_env);
|
command.env.get_or_insert_default().extend(extra_env);
|
||||||
command.args.push("--experimental-acp".into());
|
command.args.push("--experimental-acp".into());
|
||||||
Ok((
|
Ok((
|
||||||
command,
|
command,
|
||||||
root_dir.to_string_lossy().into_owned(),
|
root_dir.to_string_lossy().into_owned(),
|
||||||
Some(login),
|
auth_commands,
|
||||||
))
|
))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -936,7 +955,13 @@ impl ExternalAgentServer for LocalClaudeCode {
|
|||||||
status_tx: Option<watch::Sender<SharedString>>,
|
status_tx: Option<watch::Sender<SharedString>>,
|
||||||
new_version_available_tx: Option<watch::Sender<Option<String>>>,
|
new_version_available_tx: Option<watch::Sender<Option<String>>>,
|
||||||
cx: &mut AsyncApp,
|
cx: &mut AsyncApp,
|
||||||
) -> Task<Result<(AgentServerCommand, String, Option<task::SpawnInTerminal>)>> {
|
) -> Task<
|
||||||
|
Result<(
|
||||||
|
AgentServerCommand,
|
||||||
|
String,
|
||||||
|
HashMap<String, task::SpawnInTerminal>,
|
||||||
|
)>,
|
||||||
|
> {
|
||||||
let fs = self.fs.clone();
|
let fs = self.fs.clone();
|
||||||
let node_runtime = self.node_runtime.clone();
|
let node_runtime = self.node_runtime.clone();
|
||||||
let project_environment = self.project_environment.downgrade();
|
let project_environment = self.project_environment.downgrade();
|
||||||
@@ -999,8 +1024,17 @@ impl ExternalAgentServer for LocalClaudeCode {
|
|||||||
(command, login)
|
(command, login)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let mut auth_commands = HashMap::default();
|
||||||
|
if let Some(login) = login {
|
||||||
|
auth_commands.insert("claude-login".to_string(), login);
|
||||||
|
}
|
||||||
|
|
||||||
command.env.get_or_insert_default().extend(extra_env);
|
command.env.get_or_insert_default().extend(extra_env);
|
||||||
Ok((command, root_dir.to_string_lossy().into_owned(), login))
|
Ok((
|
||||||
|
command,
|
||||||
|
root_dir.to_string_lossy().into_owned(),
|
||||||
|
auth_commands,
|
||||||
|
))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1025,7 +1059,13 @@ impl ExternalAgentServer for LocalCodex {
|
|||||||
_status_tx: Option<watch::Sender<SharedString>>,
|
_status_tx: Option<watch::Sender<SharedString>>,
|
||||||
_new_version_available_tx: Option<watch::Sender<Option<String>>>,
|
_new_version_available_tx: Option<watch::Sender<Option<String>>>,
|
||||||
cx: &mut AsyncApp,
|
cx: &mut AsyncApp,
|
||||||
) -> Task<Result<(AgentServerCommand, String, Option<task::SpawnInTerminal>)>> {
|
) -> Task<
|
||||||
|
Result<(
|
||||||
|
AgentServerCommand,
|
||||||
|
String,
|
||||||
|
HashMap<String, task::SpawnInTerminal>,
|
||||||
|
)>,
|
||||||
|
> {
|
||||||
let fs = self.fs.clone();
|
let fs = self.fs.clone();
|
||||||
let project_environment = self.project_environment.downgrade();
|
let project_environment = self.project_environment.downgrade();
|
||||||
let http = self.http_client.clone();
|
let http = self.http_client.clone();
|
||||||
@@ -1116,7 +1156,11 @@ impl ExternalAgentServer for LocalCodex {
|
|||||||
};
|
};
|
||||||
|
|
||||||
command.env.get_or_insert_default().extend(extra_env);
|
command.env.get_or_insert_default().extend(extra_env);
|
||||||
Ok((command, root_dir.to_string_lossy().into_owned(), None))
|
Ok((
|
||||||
|
command,
|
||||||
|
root_dir.to_string_lossy().into_owned(),
|
||||||
|
HashMap::default(),
|
||||||
|
))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1173,7 +1217,13 @@ impl ExternalAgentServer for LocalCustomAgent {
|
|||||||
_status_tx: Option<watch::Sender<SharedString>>,
|
_status_tx: Option<watch::Sender<SharedString>>,
|
||||||
_new_version_available_tx: Option<watch::Sender<Option<String>>>,
|
_new_version_available_tx: Option<watch::Sender<Option<String>>>,
|
||||||
cx: &mut AsyncApp,
|
cx: &mut AsyncApp,
|
||||||
) -> Task<Result<(AgentServerCommand, String, Option<task::SpawnInTerminal>)>> {
|
) -> Task<
|
||||||
|
Result<(
|
||||||
|
AgentServerCommand,
|
||||||
|
String,
|
||||||
|
HashMap<String, task::SpawnInTerminal>,
|
||||||
|
)>,
|
||||||
|
> {
|
||||||
let mut command = self.command.clone();
|
let mut command = self.command.clone();
|
||||||
let root_dir: Arc<Path> = root_dir
|
let root_dir: Arc<Path> = root_dir
|
||||||
.map(|root_dir| Path::new(root_dir))
|
.map(|root_dir| Path::new(root_dir))
|
||||||
@@ -1194,7 +1244,11 @@ impl ExternalAgentServer for LocalCustomAgent {
|
|||||||
env.extend(command.env.unwrap_or_default());
|
env.extend(command.env.unwrap_or_default());
|
||||||
env.extend(extra_env);
|
env.extend(extra_env);
|
||||||
command.env = Some(env);
|
command.env = Some(env);
|
||||||
Ok((command, root_dir.to_string_lossy().into_owned(), None))
|
Ok((
|
||||||
|
command,
|
||||||
|
root_dir.to_string_lossy().into_owned(),
|
||||||
|
HashMap::default(),
|
||||||
|
))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1825,7 +1825,7 @@ async fn test_remote_external_agent_server(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
assert_eq!(&PathBuf::from(root), paths::home_dir());
|
assert_eq!(&PathBuf::from(root), paths::home_dir());
|
||||||
assert!(login.is_none());
|
assert!(login.is_empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn init_test(
|
pub async fn init_test(
|
||||||
|
|||||||
Reference in New Issue
Block a user