agent_servers: First-class Oh My Pi (omp) harness integration

Promotes the Oh My Pi (omp) agent harness to a first-class agent:
- Auto-detects `omp` in PATH when not explicitly configured
- Maps display name to "Oh My Pi" across agent panel, conversation views, and settings
- Defaults session mode to "task"
- Injects Zed context environment variables (`ZED=1`, `SURFACE=zed`, `ZED_VERSION`, `ZED_PROJECT_PATHS`)
- Adds documentation and example in default.json
- Preserves full compatibility with ACP subagent session info and task hierarchies

Release Notes:

- Added first-class integration and auto-detection for the Oh My Pi (omp) agent harness.
This commit is contained in:
yenru0 committed 2026-09-03 20:00:15 +09:00
1 parent a210572a9d
commit 05b31a44ce
4 files changed
+104 -3

No files matched your search

+2
View File
@@ -1,3 +1,5 @@
> [!IMPORTANT]
> Remove this line to confirm you've reviewed this PR before submitting.
# Zed
[![Zed](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/zed-industries/zed/main/assets/badge/v0.json)](https://zed.dev)
+8
View File
@@ -2851,6 +2851,14 @@
// Configures context servers for use by the agent.
"context_servers": {},
// Configures agent servers available in the agent panel.
// Example for Oh My Pi (omp) harness:
// "agent_servers": {
// "omp": {
// "type": "custom",
// "command": "omp",
// "args": ["acp"]
// }
// },
"agent_servers": {},
"debugger": {
"stepping_granularity": "line",
+44 -2
View File
@@ -18,6 +18,9 @@ pub const GEMINI_ID: &str = "gemini";
pub const CLAUDE_AGENT_ID: &str = "claude-acp";
pub const CODEX_ID: &str = "codex-acp";
pub const CURSOR_ID: &str = "cursor";
pub const OMP_ID: &str = "omp";
pub const OMP_NAME: &str = "Oh My Pi";
/// A generic agent server implementation for custom user-defined agents
pub struct CustomAgentServer {
@@ -37,7 +40,7 @@ impl AgentServer for CustomAgentServer {
fn logo(&self) -> IconName {
match self.agent_id.0.as_ref() {
"omp" => IconName::Omp,
OMP_ID => IconName::Omp,
_ => IconName::Terminal,
}
}
@@ -53,8 +56,14 @@ impl AgentServer for CustomAgentServer {
settings
.as_ref()
.and_then(|s| s.default_mode().map(acp::SessionModeId::new))
.or_else(|| {
if self.agent_id().0.as_ref() == OMP_ID {
Some(acp::SessionModeId::new("task"))
} else {
None
}
})
}
fn favorite_config_option_value_ids(
&self,
config_id: &acp::SessionConfigId,
@@ -248,6 +257,26 @@ impl AgentServer for CustomAgentServer {
_ => {}
}
}
if agent_id.as_ref() == OMP_ID {
extra_env.insert("ZED".to_owned(), "1".to_owned());
extra_env.insert("SURFACE".to_owned(), "zed".to_owned());
extra_env.insert(
"ZED_VERSION".to_owned(),
release_channel::AppVersion::global(cx).to_string(),
);
let project_paths: Vec<String> = project
.read(cx)
.worktrees(cx)
.filter_map(|w| {
w.read(cx)
.as_local()
.map(|l| l.abs_path().to_string_lossy().into_owned())
})
.collect();
if !project_paths.is_empty() {
extra_env.insert("ZED_PROJECT_PATHS".to_owned(), project_paths.join(":"));
}
}
let store = delegate.store.downgrade();
cx.spawn(async move |cx| {
if is_registry_agent && agent_id.as_ref() == GEMINI_ID {
@@ -431,4 +460,17 @@ mod tests {
assert!(is_registry_agent("agent-from-settings", cx));
});
}
#[gpui::test]
fn test_omp_agent_server_attributes(cx: &mut TestAppContext) {
init_test(cx);
let server = CustomAgentServer::new(AgentId::new(OMP_ID));
assert_eq!(server.logo(), IconName::Omp);
cx.update(|cx| {
assert_eq!(
server.default_mode(cx),
Some(acp::SessionModeId::new("task"))
);
});
}
}
+50 -1
View File
@@ -191,6 +191,9 @@ pub struct AgentServerStore {
pub struct AgentServersUpdated;
impl EventEmitter<AgentServersUpdated> for AgentServerStore {}
pub const OMP_ID: &str = "omp";
pub const OMP_NAME: &str = "Oh My Pi";
static EXTENSION_TO_REGISTRY_IDS: LazyLock<HashMap<&'static str, &'static str>> =
LazyLock::new(|| {
@@ -256,6 +259,10 @@ impl AgentServerStore {
self.external_agents
.get(name)
.and_then(|entry| entry.display_name.clone())
.or_else(|| match name.0.as_ref() {
OMP_ID => Some(SharedString::from(OMP_NAME)),
_ => None,
})
}
pub fn init_remote(session: &AnyProtoClient) {
@@ -360,6 +367,10 @@ impl AgentServerStore {
match settings {
CustomAgentServerSettings::Custom { command, .. } => {
let agent_name = AgentId(name.clone().into());
let display_name = match name.as_str() {
OMP_ID => Some(SharedString::from(OMP_NAME)),
_ => None,
};
self.external_agents.insert(
agent_name.clone(),
ExternalAgentEntry::new(
@@ -368,7 +379,7 @@ impl AgentServerStore {
project_environment: project_environment.clone(),
}) as Box<dyn ExternalAgentServer>,
ExternalAgentSource::Custom,
None,
display_name,
None,
),
);
@@ -444,6 +455,31 @@ impl AgentServerStore {
}
}
// Auto-detect Oh My Pi (omp) harness if not explicitly configured in settings
let omp_agent_id = AgentId(OMP_ID.into());
if !self.external_agents.contains_key(&omp_agent_id)
&& !new_settings.contains_key(OMP_ID)
{
if let Ok(omp_path) = which::which(OMP_ID) {
self.external_agents.insert(
omp_agent_id,
ExternalAgentEntry::new(
Box::new(LocalCustomAgent {
command: AgentServerCommand {
path: omp_path,
args: vec!["acp".to_string()],
env: None,
},
project_environment: project_environment.clone(),
}) as Box<dyn ExternalAgentServer>,
ExternalAgentSource::Custom,
Some(SharedString::from(OMP_NAME)),
None,
),
);
}
}
// For each rebuilt versioned agent, compare the version. If it
// changed, notify the active connection to reconnect. Otherwise,
// transfer the channel to the new entry so future updates can use it.
@@ -2337,4 +2373,17 @@ mod tests {
);
});
}
#[gpui::test]
fn test_omp_agent_display_name(_cx: &mut TestAppContext) {
let store = AgentServerStore::collab();
assert_eq!(
store.agent_display_name(&AgentId::new(OMP_ID)),
Some(SharedString::from(OMP_NAME))
);
assert_eq!(
store.agent_display_name(&AgentId::new("random_agent")),
None
);
}
}