Some checks failed
Update All Top Ranking Issues / update_top_ranking_issues (push) Has been cancelled
Triage Project Sync (#84) / Sync triage project (push) Has been cancelled
release_nightly / notify_on_failure (push) Has been cancelled
release_nightly / check_style (push) Has been cancelled
release_nightly / run_tests_windows (push) Has been cancelled
release_nightly / clippy_windows (push) Has been cancelled
release_nightly / bundle_linux_aarch64 (push) Has been cancelled
release_nightly / bundle_linux_x86_64 (push) Has been cancelled
release_nightly / bundle_mac_aarch64 (push) Has been cancelled
release_nightly / bundle_mac_x86_64 (push) Has been cancelled
release_nightly / bundle_windows_aarch64 (push) Has been cancelled
release_nightly / bundle_windows_x86_64 (push) Has been cancelled
release_nightly / build_nix_linux_x86_64 (push) Has been cancelled
release_nightly / build_nix_mac_aarch64 (push) Has been cancelled
release_nightly / update_nightly_tag (push) Has been cancelled
Hotfix Review Monitor / check-hotfix-reviews (push) Has been cancelled
Stale PR Review Reminder / check-stale-prs (push) Has been cancelled
Update Weekly Top Ranking Issues / update_top_ranking_issues (push) Has been cancelled
Bump collab-staging Tag / update-collab-staging-tag (push) Has been cancelled
compliance_check / scheduled_compliance_check (push) Has been cancelled
Single-commit orphan branch: full zed-industries/zed @ 8c74db0 source tree with a 3-file patch applied (no upstream history). Patch (crates/gpui_linux/src/linux/wayland/): - serial.rs: add SerialKind::KeyboardEnter - client.rs: store wl_keyboard.enter serial; add latest_serial_of() - window.rs: activate() uses keyboard-enter serial (Mutter focus gate) Mutter honors window activation only when the token carries the keyboard- focus serial from wl_keyboard.enter; GPUI used a stale mouse-press serial. See docs/tray-window-focus-wayland.md in logiguard. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
333 lines
11 KiB
Rust
333 lines
11 KiB
Rust
use anyhow::Result;
|
|
use collections::HashMap;
|
|
use gpui::{AppContext, AsyncApp, SharedString, Task, TestAppContext};
|
|
use node_runtime::NodeRuntime;
|
|
use project::worktree_store::WorktreeStore;
|
|
use project::{agent_server_store::*, worktree_store::WorktreeIdCounter};
|
|
use std::{any::Any, path::PathBuf, sync::Arc};
|
|
|
|
#[test]
|
|
fn extension_agent_constructs_proper_display_names() {
|
|
// Verify the display name format for extension-provided agents
|
|
let name1 = AgentId(SharedString::from("Extension: Agent"));
|
|
assert!(name1.0.contains(": "));
|
|
|
|
let name2 = AgentId(SharedString::from("MyExt: MyAgent"));
|
|
assert_eq!(name2.0, "MyExt: MyAgent");
|
|
|
|
// Non-extension agents shouldn't have the separator
|
|
let custom = AgentId(SharedString::from("custom"));
|
|
assert!(!custom.0.contains(": "));
|
|
}
|
|
|
|
struct NoopExternalAgent;
|
|
|
|
impl ExternalAgentServer for NoopExternalAgent {
|
|
fn get_command(
|
|
&self,
|
|
_extra_args: Vec<String>,
|
|
_extra_env: HashMap<String, String>,
|
|
_cx: &mut AsyncApp,
|
|
) -> Task<Result<AgentServerCommand>> {
|
|
Task::ready(Ok(AgentServerCommand {
|
|
path: PathBuf::from("noop"),
|
|
args: Vec::new(),
|
|
env: None,
|
|
}))
|
|
}
|
|
|
|
fn as_any(&self) -> &dyn Any {
|
|
self
|
|
}
|
|
|
|
fn as_any_mut(&mut self) -> &mut dyn Any {
|
|
self
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn sync_removes_only_extension_provided_agents() {
|
|
let mut store = AgentServerStore::collab();
|
|
|
|
// Seed with extension agents (contain ": ") and custom agents (don't contain ": ")
|
|
store.external_agents.insert(
|
|
AgentId(SharedString::from("Ext1: Agent1")),
|
|
ExternalAgentEntry::new(
|
|
Box::new(NoopExternalAgent) as Box<dyn ExternalAgentServer>,
|
|
ExternalAgentSource::Extension,
|
|
None,
|
|
None,
|
|
),
|
|
);
|
|
store.external_agents.insert(
|
|
AgentId(SharedString::from("Ext2: Agent2")),
|
|
ExternalAgentEntry::new(
|
|
Box::new(NoopExternalAgent) as Box<dyn ExternalAgentServer>,
|
|
ExternalAgentSource::Extension,
|
|
None,
|
|
None,
|
|
),
|
|
);
|
|
store.external_agents.insert(
|
|
AgentId(SharedString::from("custom-agent")),
|
|
ExternalAgentEntry::new(
|
|
Box::new(NoopExternalAgent) as Box<dyn ExternalAgentServer>,
|
|
ExternalAgentSource::Custom,
|
|
None,
|
|
None,
|
|
),
|
|
);
|
|
|
|
// Simulate removal phase
|
|
store
|
|
.external_agents
|
|
.retain(|_, entry| entry.source != ExternalAgentSource::Extension);
|
|
|
|
// Only custom-agent should remain
|
|
assert_eq!(store.external_agents.len(), 1);
|
|
assert!(
|
|
store
|
|
.external_agents
|
|
.contains_key(&AgentId(SharedString::from("custom-agent")))
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn archive_launcher_constructs_with_all_fields() {
|
|
use extension::AgentServerManifestEntry;
|
|
|
|
let mut env = HashMap::default();
|
|
env.insert("GITHUB_TOKEN".into(), "secret".into());
|
|
|
|
let mut targets = HashMap::default();
|
|
targets.insert(
|
|
"darwin-aarch64".to_string(),
|
|
extension::TargetConfig {
|
|
archive:
|
|
"https://github.com/owner/repo/releases/download/v1.0.0/agent-darwin-arm64.zip"
|
|
.into(),
|
|
cmd: "./agent".into(),
|
|
args: vec![],
|
|
sha256: None,
|
|
env: Default::default(),
|
|
},
|
|
);
|
|
|
|
let _entry = AgentServerManifestEntry {
|
|
name: "GitHub Agent".into(),
|
|
targets,
|
|
env,
|
|
icon: None,
|
|
};
|
|
|
|
// Verify display name construction
|
|
let expected_name = AgentId(SharedString::from("GitHub Agent"));
|
|
assert_eq!(expected_name.0, "GitHub Agent");
|
|
}
|
|
|
|
#[gpui::test]
|
|
async fn archive_agent_uses_extension_and_agent_id_for_cache_key(cx: &mut TestAppContext) {
|
|
let fs = fs::FakeFs::new(cx.background_executor.clone());
|
|
let http_client = http_client::FakeHttpClient::with_404_response();
|
|
let worktree_store =
|
|
cx.new(|cx| WorktreeStore::local(false, fs.clone(), WorktreeIdCounter::get(cx)));
|
|
let project_environment = cx.new(|cx| {
|
|
crate::ProjectEnvironment::new(None, worktree_store.downgrade(), None, false, cx)
|
|
});
|
|
|
|
let agent = LocalExtensionArchiveAgent {
|
|
fs,
|
|
http_client,
|
|
node_runtime: node_runtime::NodeRuntime::unavailable(),
|
|
project_environment,
|
|
extension_id: Arc::from("my-extension"),
|
|
agent_id: Arc::from("my-agent"),
|
|
version: Some(SharedString::from("1.0.0")),
|
|
targets: {
|
|
let mut map = HashMap::default();
|
|
map.insert(
|
|
"darwin-aarch64".to_string(),
|
|
extension::TargetConfig {
|
|
archive: "https://example.com/my-agent-darwin-arm64.zip".into(),
|
|
cmd: "./my-agent".into(),
|
|
args: vec!["--serve".into()],
|
|
sha256: None,
|
|
env: Default::default(),
|
|
},
|
|
);
|
|
map
|
|
},
|
|
env: {
|
|
let mut map = HashMap::default();
|
|
map.insert("PORT".into(), "8080".into());
|
|
map
|
|
},
|
|
new_version_available_tx: None,
|
|
};
|
|
|
|
// Verify agent is properly constructed
|
|
assert_eq!(agent.extension_id.as_ref(), "my-extension");
|
|
assert_eq!(agent.agent_id.as_ref(), "my-agent");
|
|
assert_eq!(agent.env.get("PORT"), Some(&"8080".to_string()));
|
|
assert!(agent.targets.contains_key("darwin-aarch64"));
|
|
}
|
|
|
|
#[test]
|
|
fn sync_extension_agents_registers_archive_launcher() {
|
|
use extension::AgentServerManifestEntry;
|
|
|
|
let expected_name = AgentId(SharedString::from("Release Agent"));
|
|
assert_eq!(expected_name.0, "Release Agent");
|
|
|
|
// Verify the manifest entry structure for archive-based installation
|
|
let mut env = HashMap::default();
|
|
env.insert("API_KEY".into(), "secret".into());
|
|
|
|
let mut targets = HashMap::default();
|
|
targets.insert(
|
|
"linux-x86_64".to_string(),
|
|
extension::TargetConfig {
|
|
archive: "https://github.com/org/project/releases/download/v2.1.0/release-agent-linux-x64.tar.gz".into(),
|
|
cmd: "./release-agent".into(),
|
|
args: vec!["serve".into()],
|
|
sha256: None,
|
|
env: Default::default(),
|
|
},
|
|
);
|
|
|
|
let manifest_entry = AgentServerManifestEntry {
|
|
name: "Release Agent".into(),
|
|
targets: targets.clone(),
|
|
env,
|
|
icon: None,
|
|
};
|
|
|
|
// Verify target config is present
|
|
assert!(manifest_entry.targets.contains_key("linux-x86_64"));
|
|
let target = manifest_entry.targets.get("linux-x86_64").unwrap();
|
|
assert_eq!(target.cmd, "./release-agent");
|
|
}
|
|
|
|
#[gpui::test]
|
|
async fn test_node_command_uses_managed_runtime(cx: &mut TestAppContext) {
|
|
let fs = fs::FakeFs::new(cx.background_executor.clone());
|
|
let http_client = http_client::FakeHttpClient::with_404_response();
|
|
let node_runtime = NodeRuntime::unavailable();
|
|
let worktree_store =
|
|
cx.new(|cx| WorktreeStore::local(false, fs.clone(), WorktreeIdCounter::get(cx)));
|
|
let project_environment = cx.new(|cx| {
|
|
crate::ProjectEnvironment::new(None, worktree_store.downgrade(), None, false, cx)
|
|
});
|
|
|
|
let agent = LocalExtensionArchiveAgent {
|
|
fs: fs.clone(),
|
|
http_client,
|
|
node_runtime,
|
|
project_environment,
|
|
extension_id: Arc::from("node-extension"),
|
|
agent_id: Arc::from("node-agent"),
|
|
version: Some(SharedString::from("1.0.0")),
|
|
targets: {
|
|
let mut map = HashMap::default();
|
|
map.insert(
|
|
"darwin-aarch64".to_string(),
|
|
extension::TargetConfig {
|
|
archive: "https://example.com/node-agent.zip".into(),
|
|
cmd: "node".into(),
|
|
args: vec!["index.js".into()],
|
|
sha256: None,
|
|
env: Default::default(),
|
|
},
|
|
);
|
|
map
|
|
},
|
|
env: HashMap::default(),
|
|
new_version_available_tx: None,
|
|
};
|
|
|
|
// Verify that when cmd is "node", it attempts to use the node runtime
|
|
assert_eq!(agent.extension_id.as_ref(), "node-extension");
|
|
assert_eq!(agent.agent_id.as_ref(), "node-agent");
|
|
|
|
let target = agent.targets.get("darwin-aarch64").unwrap();
|
|
assert_eq!(target.cmd, "node");
|
|
assert_eq!(target.args, vec!["index.js"]);
|
|
}
|
|
|
|
#[gpui::test]
|
|
async fn test_commands_run_in_extraction_directory(cx: &mut TestAppContext) {
|
|
let fs = fs::FakeFs::new(cx.background_executor.clone());
|
|
let http_client = http_client::FakeHttpClient::with_404_response();
|
|
let node_runtime = NodeRuntime::unavailable();
|
|
let worktree_store =
|
|
cx.new(|cx| WorktreeStore::local(false, fs.clone(), WorktreeIdCounter::get(cx)));
|
|
let project_environment = cx.new(|cx| {
|
|
crate::ProjectEnvironment::new(None, worktree_store.downgrade(), None, false, cx)
|
|
});
|
|
|
|
let agent = LocalExtensionArchiveAgent {
|
|
fs: fs.clone(),
|
|
http_client,
|
|
node_runtime,
|
|
project_environment,
|
|
extension_id: Arc::from("test-ext"),
|
|
agent_id: Arc::from("test-agent"),
|
|
version: Some(SharedString::from("1.0.0")),
|
|
targets: {
|
|
let mut map = HashMap::default();
|
|
map.insert(
|
|
"darwin-aarch64".to_string(),
|
|
extension::TargetConfig {
|
|
archive: "https://example.com/test.zip".into(),
|
|
cmd: "node".into(),
|
|
args: vec![
|
|
"server.js".into(),
|
|
"--config".into(),
|
|
"./config.json".into(),
|
|
],
|
|
sha256: None,
|
|
env: Default::default(),
|
|
},
|
|
);
|
|
map
|
|
},
|
|
env: Default::default(),
|
|
new_version_available_tx: None,
|
|
};
|
|
|
|
// Verify the agent is configured with relative paths in args
|
|
let target = agent.targets.get("darwin-aarch64").unwrap();
|
|
assert_eq!(target.args[0], "server.js");
|
|
assert_eq!(target.args[2], "./config.json");
|
|
// These relative paths will resolve relative to the extraction directory
|
|
// when the command is executed
|
|
}
|
|
|
|
#[test]
|
|
fn test_tilde_expansion_in_settings() {
|
|
let settings = settings::CustomAgentServerSettings::Custom {
|
|
path: PathBuf::from("~/custom/agent"),
|
|
args: vec!["serve".into()],
|
|
env: Default::default(),
|
|
default_mode: None,
|
|
default_model: None,
|
|
favorite_models: vec![],
|
|
default_config_options: Default::default(),
|
|
favorite_config_option_values: Default::default(),
|
|
};
|
|
|
|
let converted: CustomAgentServerSettings = settings.into();
|
|
let CustomAgentServerSettings::Custom {
|
|
command: AgentServerCommand { path, .. },
|
|
..
|
|
} = converted
|
|
else {
|
|
panic!("Expected Custom variant");
|
|
};
|
|
|
|
assert!(
|
|
!path.to_string_lossy().starts_with("~"),
|
|
"Tilde should be expanded for custom agent path"
|
|
);
|
|
}
|