From e2559809679c9b5601d9f369abe049a211cd5d12 Mon Sep 17 00:00:00 2001 From: Sebastian Maniak Date: Fri, 18 Sep 2026 17:13:35 -0400 Subject: [PATCH 01/11] Add Pi discovery and managed gateway configuration Detect the pi.dev coding-agent harness (@earendil-works/pi-coding-agent), inventory MCP servers and skills, and reconcile user-level models.json and settings.json so Pi can use the LLM gateway with a request-time credential helper. --- README.md | 1 + crates/agent/src/daemon.rs | 57 ++++ crates/agent/src/provider/README.md | 31 +- crates/agent/src/provider/mod.rs | 1 + crates/agent/src/provider/pi/discovery.rs | 249 ++++++++++++++++ crates/agent/src/provider/pi/mod.rs | 131 +++++++++ crates/agent/src/provider/pi/reconcile.rs | 275 ++++++++++++++++++ crates/agent/src/reconcile/mod.rs | 69 ++++- crates/core/src/config.rs | 149 ++++++++++ examples/claude/claude-code.yaml | 2 +- examples/standalone/config.yaml | 3 + frontend/controller/src/types.ts | 3 +- .../src/views/ConfigurationView.tsx | 10 +- frontend/desktop/src/views/ToolsView.tsx | 2 +- frontend/ui/src/assets/pi.svg | 1 + frontend/ui/src/tools.tsx | 3 + schema/daemon-config.json | 72 +++++ schema/daemon-config.md | 9 + 18 files changed, 1050 insertions(+), 18 deletions(-) create mode 100644 crates/agent/src/provider/pi/discovery.rs create mode 100644 crates/agent/src/provider/pi/mod.rs create mode 100644 crates/agent/src/provider/pi/reconcile.rs create mode 100644 frontend/ui/src/assets/pi.svg diff --git a/README.md b/README.md index 83fcc226..749dff13 100644 --- a/README.md +++ b/README.md @@ -137,6 +137,7 @@ stage. | OpenCode | Yes | Yes | MCP | — | | VS Code | Yes | — | MCP and skills | — | | Grok Build | Yes | System mode | MCP and skills | — | +| Pi | Yes | User mode | MCP and skills | — | > **Don't see your tool?** We're actively expanding this list and would love > your help. [Open an integration request](https://github.com/agentdesktop-dev/agentdesktop/issues/new) diff --git a/crates/agent/src/daemon.rs b/crates/agent/src/daemon.rs index c8cc4262..c5db9816 100644 --- a/crates/agent/src/daemon.rs +++ b/crates/agent/src/daemon.rs @@ -76,6 +76,7 @@ struct ResolvedDaemonArgs { codex: ResolvedToolConfigPath, open_code: ResolvedOpenCodeStartupConfig, grok: ResolvedToolConfigPath, + pi: ResolvedPiStartupConfig, once: bool, dry_run: bool, } @@ -94,6 +95,11 @@ struct ResolvedOpenCodeStartupConfig { plugin: PathBuf, } +struct ResolvedPiStartupConfig { + models: PathBuf, + settings: PathBuf, +} + impl DaemonArgs { fn resolve( self, @@ -150,6 +156,16 @@ impl DaemonArgs { .config .unwrap_or_else(reconcile::default_grok_managed_config_path), }, + pi: ResolvedPiStartupConfig { + models: startup + .pi + .models + .unwrap_or_else(reconcile::default_pi_models_path), + settings: startup + .pi + .settings + .unwrap_or_else(reconcile::default_pi_settings_path), + }, once: self.once || self.dry_run, dry_run: self.dry_run, }); @@ -218,6 +234,14 @@ impl DaemonArgs { .join("managed_config.toml") }), }, + pi: ResolvedPiStartupConfig { + models: startup.pi.models.unwrap_or_else(|| { + crate::provider::pi::user_pi_agent_dir(&home).join("models.json") + }), + settings: startup.pi.settings.unwrap_or_else(|| { + crate::provider::pi::user_pi_agent_dir(&home).join("settings.json") + }), + }, once: self.once || self.dry_run, dry_run: self.dry_run, }) @@ -299,6 +323,8 @@ where args.open_code.config.clone(), args.open_code.plugin.clone(), args.grok.config.clone(), + args.pi.models.clone(), + args.pi.settings.clone(), agentdesktop_client_executable()?, socket.clone(), ); @@ -911,6 +937,37 @@ mod tests { use agentdesktop_core::model::{Agent, Discovery}; use tokio::sync::watch; + #[test] + fn pi_startup_paths_override_defaults_in_both_scopes() { + for user in [false, true] { + let file = tempfile::NamedTempFile::new().unwrap(); + std::fs::write( + file.path(), + format!( + "daemon:\n user: {user}\n pi:\n models: custom/pi-models.json\n settings: custom/pi-settings.json\n" + ), + ) + .unwrap(); + let config = agentdesktop_core::config::load_daemon(file.path()).unwrap(); + let args = super::DaemonArgs { + user: false, + once: false, + dry_run: false, + config: None, + }; + let resolved = args + .resolve( + config.daemon.unwrap(), + file.path().to_path_buf(), + agentdesktop_core::DEFAULT_SOCKET_PATH.into(), + ) + .unwrap(); + assert_eq!(resolved.user, user); + assert_eq!(resolved.pi.models, Path::new("custom/pi-models.json")); + assert_eq!(resolved.pi.settings, Path::new("custom/pi-settings.json")); + } + } + fn discovery(kinds: &[&str]) -> Discovery { Discovery { agents: kinds diff --git a/crates/agent/src/provider/README.md b/crates/agent/src/provider/README.md index e2417f50..14b3358c 100644 --- a/crates/agent/src/provider/README.md +++ b/crates/agent/src/provider/README.md @@ -8,20 +8,27 @@ Grok Build managed configuration requires system mode. Linux and macOS use Grok's own configuration sync can delete or replace the user-level managed file. +Pi stores configuration in `~/.pi/agent` (or `PI_CODING_AGENT_DIR`). `--user` +merges `providers.agentdesktop` into `models.json` and sets the default model +in `settings.json`. MCP inventory comes from `pi-mcp-adapter` config files +(`~/.pi/agent/mcp.json`, `.pi/mcp.json`, `.mcp.json`, and shared +`~/.config/mcp/mcp.json` / `~/.agents/mcp.json`). Host-specific Cursor/Claude +files are not scanned until the adapter imports them into a Pi-owned file. + ✅ Implemented · ◯ Not implemented · — Not applicable -| Feature | [Claude Code](claude_code/) | [Claude Desktop](claude_desktop/) | [Codex](codex/) | [OpenCode](opencode/) | [VS Code](vscode/) | [Ollama](ollama/) | [Cursor](cursor/) | [Grok Build](grok/) | -| --- | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | -| Installation and version discovery | ✅ | ✅ | ✅ | ✅ | ✅ | — | ✅ | ✅ | -| Local model discovery | — | — | — | — | — | ✅ | — | — | -| MCP server discovery | ✅ | ✅ | ✅ | ◯ | ✅ | — | ✅ | ✅ | -| Skill discovery | ✅ | ◯ | ✅ | ◯ | ✅ | — | ✅ | ✅ | -| Managed configuration | ✅ | ✅ | ✅ | ✅ | ◯ | ◯ | ◯ | ✅ | -| LLM gateway routing | ✅ | ✅ | ✅ | ✅ | ◯ | — | ◯ | ✅ | -| Gateway credentials | ✅ | ✅ | ✅ | ✅ | ◯ | — | ◯ | ✅ | -| Sandbox configuration | ✅ | ◯ | ✅ | ◯ | ◯ | — | ◯ | ◯ | -| Tool-use telemetry | ✅ | ◯ | ◯ | ◯ | ◯ | — | ◯ | ◯ | -| Session-start telemetry | ✅ | ◯ | ◯ | ◯ | ◯ | — | ◯ | ◯ | +| Feature | [Claude Code](claude_code/) | [Claude Desktop](claude_desktop/) | [Codex](codex/) | [OpenCode](opencode/) | [VS Code](vscode/) | [Ollama](ollama/) | [Cursor](cursor/) | [Grok Build](grok/) | [Pi](pi/) | +| --- | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | +| Installation and version discovery | ✅ | ✅ | ✅ | ✅ | ✅ | — | ✅ | ✅ | ✅ | +| Local model discovery | — | — | — | — | — | ✅ | — | — | — | +| MCP server discovery | ✅ | ✅ | ✅ | ◯ | ✅ | — | ✅ | ✅ | ✅ | +| Skill discovery | ✅ | ◯ | ✅ | ◯ | ✅ | — | ✅ | ✅ | ✅ | +| Managed configuration | ✅ | ✅ | ✅ | ✅ | ◯ | ◯ | ◯ | ✅ | ✅ | +| LLM gateway routing | ✅ | ✅ | ✅ | ✅ | ◯ | — | ◯ | ✅ | ✅ | +| Gateway credentials | ✅ | ✅ | ✅ | ✅ | ◯ | — | ◯ | ✅ | ✅ | +| Sandbox configuration | ✅ | ◯ | ✅ | ◯ | ◯ | — | ◯ | ◯ | ◯ | +| Tool-use telemetry | ✅ | ◯ | ◯ | ◯ | ◯ | — | ◯ | ◯ | ◯ | +| Session-start telemetry | ✅ | ◯ | ◯ | ◯ | ◯ | — | ◯ | ◯ | ◯ | - ◯ describes a gap in Agentdesktop; it does not mean the upstream provider cannot support the feature. diff --git a/crates/agent/src/provider/mod.rs b/crates/agent/src/provider/mod.rs index ebd28c4c..a1e6af30 100644 --- a/crates/agent/src/provider/mod.rs +++ b/crates/agent/src/provider/mod.rs @@ -17,6 +17,7 @@ mod json_merge; mod metadata; pub mod ollama; pub mod opencode; +pub mod pi; pub(crate) mod shared; pub mod vscode; diff --git a/crates/agent/src/provider/pi/discovery.rs b/crates/agent/src/provider/pi/discovery.rs new file mode 100644 index 00000000..91932b3b --- /dev/null +++ b/crates/agent/src/provider/pi/discovery.rs @@ -0,0 +1,249 @@ +use std::{ + collections::BTreeSet, + path::{Path, PathBuf}, +}; + +use agentdesktop_core::model::{Agent, McpServer}; + +use crate::provider::{metadata, vscode::discovery as vscode}; + +use super::Pi; + +pub(super) fn discover() -> Option { + let executable = metadata::find_all_in_path("pi") + .into_iter() + .chain( + executable_candidates() + .into_iter() + .filter(|candidate| candidate.is_file()), + ) + .find(|candidate| is_pi(candidate))?; + Some(Agent { + version: package_version(&executable), + executable, + kind: Pi::ID.to_owned(), + mcp_servers: discover_mcp_servers(), + skills: metadata::discover_skills(skill_roots()), + }) +} + +fn executable_candidates() -> Vec { + let mut candidates = BTreeSet::new(); + for home in metadata::user_home_dirs() { + candidates.insert(home.join(".local/bin/pi")); + candidates.insert(home.join(".npm-global/bin/pi")); + #[cfg(windows)] + { + candidates.insert(home.join(".local/bin/pi.exe")); + candidates.insert(home.join("AppData/Roaming/npm/pi.cmd")); + candidates.insert(home.join("AppData/Roaming/npm/pi.exe")); + candidates.insert(home.join("AppData/Local/pnpm/pi.exe")); + } + } + #[cfg(target_os = "macos")] + candidates.extend([ + PathBuf::from("/opt/homebrew/bin/pi"), + PathBuf::from("/usr/local/bin/pi"), + ]); + #[cfg(target_os = "linux")] + candidates.extend([ + PathBuf::from("/usr/bin/pi"), + PathBuf::from("/usr/local/bin/pi"), + ]); + candidates.into_iter().collect() +} + +fn is_pi(executable: &Path) -> bool { + package_manifest(executable).is_some() +} + +fn package_version(executable: &Path) -> Option { + let manifest = package_manifest(executable)?; + metadata::json_package_version(&manifest, Pi::PACKAGE_NAME) +} + +fn package_manifest(executable: &Path) -> Option { + let mut seen = BTreeSet::new(); + for start in [ + Some(executable.to_path_buf()), + executable.canonicalize().ok(), + ] + .into_iter() + .flatten() + { + let mut directory = start.parent()?.to_path_buf(); + for _ in 0..8 { + let manifest = directory.join("package.json"); + if seen.insert(manifest.clone()) + && metadata::json_package_version(&manifest, Pi::PACKAGE_NAME).is_some() + { + return Some(manifest); + } + if !directory.pop() { + break; + } + } + } + None +} + +fn discover_mcp_servers() -> Vec { + mcp_config_paths() + .into_iter() + .flat_map(|path| vscode::mcp_servers_from_json(&path)) + .collect() +} + +/// Files `pi-mcp-adapter` loads as normal MCP config. +/// +/// Host-specific Cursor/Claude/Codex files are not included: the adapter only +/// imports those after `/mcp setup` writes them into a Pi-owned file. +fn mcp_config_paths() -> Vec { + let mut paths = BTreeSet::new(); + for home in metadata::user_home_dirs() { + let agent_dir = std::env::var_os("PI_CODING_AGENT_DIR") + .filter(|value| !value.is_empty()) + .map(PathBuf::from) + .unwrap_or_else(|| home.join(".pi/agent")); + paths.insert(agent_dir.join("mcp.json")); + paths.insert(home.join(".config/mcp/mcp.json")); + paths.insert(home.join(".agents/mcp.json")); + paths.insert(home.join(".agents/mcp/mcp.json")); + } + paths.extend(metadata::current_dir_ancestors(Path::new(".mcp.json"))); + paths.extend(metadata::current_dir_ancestors(Path::new(".pi/mcp.json"))); + paths.into_iter().collect() +} + +fn skill_roots() -> Vec { + let mut roots = BTreeSet::new(); + for home in metadata::user_home_dirs() { + let agent_dir = std::env::var_os("PI_CODING_AGENT_DIR") + .filter(|value| !value.is_empty()) + .map(PathBuf::from) + .unwrap_or_else(|| home.join(".pi/agent")); + roots.insert(agent_dir.join("skills")); + roots.insert(home.join(".agents/skills")); + } + roots.extend(metadata::current_dir_ancestors(Path::new(".pi/skills"))); + roots.extend(metadata::current_dir_ancestors(Path::new(".agents/skills"))); + roots.into_iter().collect() +} + +#[cfg(test)] +mod tests { + use std::{ + fs, + path::{Path, PathBuf}, + }; + + use serde_json::json; + + use super::{is_pi, mcp_config_paths, package_version}; + use crate::provider::vscode::discovery::mcp_servers_from_value; + + #[test] + fn accepts_earendil_pi_package_and_reads_version() { + let root = temporary("pi-package"); + let package = root.join("node_modules/@earendil-works/pi-coding-agent"); + let bundle = package.join("dist/bundle"); + fs::create_dir_all(&bundle).unwrap(); + fs::write( + package.join("package.json"), + r#"{"name":"@earendil-works/pi-coding-agent","version":"0.85.1"}"#, + ) + .unwrap(); + let executable = bundle.join("cli.js"); + fs::write(&executable, "#!/usr/bin/env node\n").unwrap(); + + assert!(is_pi(&executable)); + assert_eq!(package_version(&executable).as_deref(), Some("0.85.1")); + + let _ = fs::remove_dir_all(root); + } + + #[test] + fn rejects_an_unrelated_pi_binary() { + let root = temporary("pi-unrelated"); + fs::create_dir_all(&root).unwrap(); + fs::write( + root.join("package.json"), + r#"{"name":"raspberry-pi-tools","version":"1.0.0"}"#, + ) + .unwrap(); + let executable = root.join("pi"); + fs::write(&executable, "#!/bin/sh\n").unwrap(); + + assert!(!is_pi(&executable)); + assert_eq!(package_version(&executable), None); + + let _ = fs::remove_dir_all(root); + } + + #[test] + fn reads_adapter_servers_without_secrets_or_arguments() { + let servers = mcp_servers_from_value( + &json!({ + "mcpServers": { + "github": { + "url": "https://api.githubcopilot.com/mcp", + "headers": { "Authorization": "secret" } + }, + "chrome": { + "command": "npx", + "args": ["-y", "chrome-devtools-mcp@1.6.0"], + "env": { "TOKEN": "secret" } + }, + "skipped": { + "command": "npx", + "disabled": true + } + } + }), + Path::new("/home/tester/.pi/agent/mcp.json"), + ); + + assert_eq!(servers.len(), 3); + assert_eq!(servers[0].name, "chrome"); + assert_eq!(servers[0].transport, "stdio"); + assert_eq!(servers[0].command.as_deref(), Some("npx")); + assert!(servers[0].enabled); + assert_eq!(servers[1].name, "github"); + assert_eq!(servers[1].transport, "http"); + assert_eq!( + servers[1].url.as_deref(), + Some("https://api.githubcopilot.com/mcp") + ); + assert!(!servers[2].enabled); + assert!( + servers + .iter() + .all(|server| server.source.ends_with("mcp.json")) + ); + } + + #[test] + fn scans_pi_owned_and_shared_mcp_files() { + let paths = mcp_config_paths(); + assert!( + paths + .iter() + .any(|path| path.ends_with(std::path::Path::new(".pi/agent/mcp.json"))) + ); + assert!( + paths + .iter() + .any(|path| path.ends_with(std::path::Path::new(".config/mcp/mcp.json"))) + ); + assert!(paths.iter().any(|path| path.ends_with(".mcp.json"))); + assert!( + paths + .iter() + .any(|path| path.ends_with(std::path::Path::new(".pi/mcp.json"))) + ); + } + + fn temporary(name: &str) -> PathBuf { + std::env::temp_dir().join(format!("agentdesktop-{name}-{}", std::process::id())) + } +} diff --git a/crates/agent/src/provider/pi/mod.rs b/crates/agent/src/provider/pi/mod.rs new file mode 100644 index 00000000..74686560 --- /dev/null +++ b/crates/agent/src/provider/pi/mod.rs @@ -0,0 +1,131 @@ +use std::path::PathBuf; + +use agentdesktop_core::config::DaemonConfig; +use agentdesktop_core::model::Discovery; + +use super::{Provider, ReconcileContext}; +use crate::reconcile::ReconcilePlan; + +pub(super) mod discovery; +mod reconcile; + +#[derive(Clone)] +pub struct Pi { + pub models_path: PathBuf, + pub settings_path: PathBuf, +} + +impl Default for Pi { + fn default() -> Self { + Self { + models_path: default_pi_models_path(), + settings_path: default_pi_settings_path(), + } + } +} + +impl Pi { + pub const ID: &'static str = "pi"; + pub const DISPLAY_NAME: &'static str = "Pi"; + pub const PACKAGE_NAME: &'static str = "@earendil-works/pi-coding-agent"; +} + +#[async_trait::async_trait] +impl Provider for Pi { + async fn discover(&self) -> Discovery { + Discovery { + agents: discovery::discover().into_iter().collect(), + model_runtimes: Vec::new(), + } + } + + fn plan(&self, ctx: &ReconcileContext, config: &DaemonConfig) -> anyhow::Result { + let configured = config.programs.pi.as_ref().map(|provider| { + let gateway = config + .llm_gateway + .as_ref() + .filter(|_| provider.use_llm_gateway); + (provider, gateway) + }); + let plan = ReconcilePlan::default(); + reconcile::plan( + &self.models_path, + &self.settings_path, + &ctx.credential_helper, + &ctx.socket, + configured, + &plan, + )?; + Ok(plan) + } +} + +/// Returns the system-wide Pi models.json path. +pub fn default_pi_models_path() -> PathBuf { + pi_agent_dir(None).join("models.json") +} + +/// Returns the system-wide Pi settings.json path. +pub fn default_pi_settings_path() -> PathBuf { + pi_agent_dir(None).join("settings.json") +} + +pub fn user_pi_agent_dir(home: &std::path::Path) -> PathBuf { + std::env::var_os("PI_CODING_AGENT_DIR") + .filter(|value| !value.is_empty()) + .map(PathBuf::from) + .unwrap_or_else(|| home.join(".pi/agent")) +} + +fn pi_agent_dir(home: Option<&std::path::Path>) -> PathBuf { + if let Some(home) = home { + return user_pi_agent_dir(home); + } + + #[cfg(windows)] + return windows_system_pi_agent_dir( + std::env::var_os("SystemDrive").or_else(|| std::env::var_os("SYSTEMDRIVE")), + ); + + #[cfg(not(windows))] + PathBuf::from("/etc/pi/agent") +} + +#[cfg(any(windows, test))] +fn windows_system_pi_agent_dir(system_drive: Option) -> PathBuf { + let drive = system_drive + .filter(|value| !value.is_empty()) + .unwrap_or_else(|| "C:".into()); + let mut drive = drive + .to_string_lossy() + .trim_end_matches(['\\', '/']) + .to_owned(); + if drive.len() == 1 && drive.as_bytes()[0].is_ascii_alphabetic() { + drive.push(':'); + } + if drive.is_empty() || !drive.ends_with(':') { + drive = "C:".to_owned(); + } + PathBuf::from(format!(r"{drive}\etc\pi\agent")) +} + +#[cfg(test)] +mod tests { + use super::windows_system_pi_agent_dir; + + #[test] + fn windows_system_pi_agent_dir_uses_system_drive_root() { + assert_eq!( + windows_system_pi_agent_dir(Some("D:".into())), + std::path::PathBuf::from(r"D:\etc\pi\agent") + ); + assert_eq!( + windows_system_pi_agent_dir(Some(r"E:\".into())), + std::path::PathBuf::from(r"E:\etc\pi\agent") + ); + assert_eq!( + windows_system_pi_agent_dir(None), + std::path::PathBuf::from(r"C:\etc\pi\agent") + ); + } +} diff --git a/crates/agent/src/provider/pi/reconcile.rs b/crates/agent/src/provider/pi/reconcile.rs new file mode 100644 index 00000000..ad7141ac --- /dev/null +++ b/crates/agent/src/provider/pi/reconcile.rs @@ -0,0 +1,275 @@ +use super::Pi; + +use std::path::Path; + +use agentdesktop_core::config::{LlmGatewayAuthentication, LlmGatewayConfig, PiConfig}; +use anyhow::Context; +use serde_json::{Map, Value, json}; + +use crate::provider::{ + json_merge, + shared::{CommandSpec, deep_merge, render_command, responses_base_url}, +}; +use crate::reconcile::ReconcilePlan; + +const PROVIDER_NAME: &str = "agentdesktop"; +const DEFAULT_API: &str = "anthropic-messages"; + +pub(super) fn plan( + models_path: &Path, + settings_path: &Path, + credential_helper: &Path, + socket: &Path, + config: Option<(&PiConfig, Option<&LlmGatewayConfig>)>, + plan: &ReconcilePlan, +) -> anyhow::Result<()> { + let models_state = json_merge::state_path(models_path); + let settings_state = json_merge::state_path(settings_path); + let Some((config, gateway)) = config else { + json_merge::plan_remove(models_path, &models_state, "models", Pi::DISPLAY_NAME, plan)?; + json_merge::plan_remove( + settings_path, + &settings_state, + "settings", + Pi::DISPLAY_NAME, + plan, + )?; + return Ok(()); + }; + + json_merge::plan_merge( + models_path, + &models_state, + managed_models(config, gateway, credential_helper, socket)?, + false, + "models", + Pi::DISPLAY_NAME, + plan, + )?; + json_merge::plan_merge( + settings_path, + &settings_state, + managed_settings(config, gateway)?, + false, + "settings", + Pi::DISPLAY_NAME, + plan, + )?; + Ok(()) +} + +fn managed_models( + config: &PiConfig, + gateway: Option<&LlmGatewayConfig>, + credential_helper: &Path, + socket: &Path, +) -> anyhow::Result { + let mut settings = serde_json::to_value(&config.managed_config) + .context("serialize Pi pass-through managed configuration")?; + let Some(gateway) = gateway else { + return Ok(settings); + }; + + let default_model = config + .model + .as_deref() + .filter(|model| !model.trim().is_empty()) + .context("Pi gateway configuration has no model")?; + let api = config + .api + .as_deref() + .map(str::trim) + .filter(|api| !api.is_empty()) + .unwrap_or(DEFAULT_API); + let uses_credential_helper = gateway + .authentication + .as_ref() + .is_some_and(LlmGatewayAuthentication::uses_credential_helper); + let api_key = if uses_credential_helper { + format!( + "!{}", + render_command(&CommandSpec::new( + credential_helper, + [ + "--socket", + &socket.to_string_lossy(), + "credential", + "--client-id", + Pi::ID, + ], + )) + ) + } else { + "agentdesktop-managed".to_owned() + }; + + let mut models = Vec::new(); + for (id, mut entry) in catalog_entries(config, default_model) { + if entry.get("id").and_then(Value::as_str).is_none() { + entry.insert("id".to_owned(), json!(id)); + } + models.push(Value::Object(entry)); + } + + let generated = json!({ + "providers": { + (PROVIDER_NAME): { + "baseUrl": gateway_base_url(gateway, api), + "api": api, + "apiKey": api_key, + "authHeader": true, + "models": models, + } + } + }); + deep_merge(&mut settings, generated); + Ok(settings) +} + +fn managed_settings( + config: &PiConfig, + gateway: Option<&LlmGatewayConfig>, +) -> anyhow::Result { + if gateway.is_none() { + return Ok(json!({})); + } + let model = config + .model + .as_deref() + .filter(|model| !model.trim().is_empty()) + .context("Pi gateway configuration has no model")?; + Ok(json!({ + "defaultProvider": PROVIDER_NAME, + "defaultModel": model, + })) +} + +fn catalog_entries(config: &PiConfig, default_model: &str) -> Vec<(String, Map)> { + if config.models.is_empty() { + return vec![(default_model.to_owned(), Map::new())]; + } + config + .models + .iter() + .map(|(id, value)| { + let object = match value { + Value::Object(object) => object.clone(), + _ => Map::new(), + }; + (id.clone(), object) + }) + .collect() +} + +fn gateway_base_url(gateway: &LlmGatewayConfig, api: &str) -> String { + match api { + "openai-completions" | "openai-responses" => responses_base_url(gateway), + _ => gateway.url.as_str().trim_end_matches('/').to_owned(), + } +} + +#[cfg(test)] +mod tests { + use std::path::Path; + + use agentdesktop_core::config::parse_daemon; + + use super::{managed_models, managed_settings}; + + #[test] + fn pass_through_settings_are_merged_with_managed_gateway_values() { + let config = parse_daemon( + r#" +llmGateway: + url: https://gateway.example.com/proxy + authentication: + type: controllerJwt + audience: agentgateway + allowedClientIds: [pi] +programs: + pi: + model: claude-sonnet-4-5 + models: + claude-sonnet-4-5: + name: Company Claude + contextWindow: 200000 + claude-haiku-4-5: + name: Haiku + managedConfig: + providers: + ollama: + baseUrl: http://127.0.0.1:11434/v1 +"#, + ) + .expect("valid daemon configuration"); + let pi = config.programs.pi.as_ref().unwrap(); + let gateway = config.llm_gateway.as_ref().unwrap(); + + let settings = managed_models( + pi, + Some(gateway), + Path::new("/usr/local/bin/agentdesktop"), + Path::new("/run/agentdesktop/agentdesktop.sock"), + ) + .expect("merged settings"); + + assert_eq!( + settings["providers"]["ollama"]["baseUrl"], + "http://127.0.0.1:11434/v1" + ); + let provider = &settings["providers"]["agentdesktop"]; + assert_eq!(provider["baseUrl"], "https://gateway.example.com/proxy"); + assert_eq!(provider["api"], "anthropic-messages"); + assert_eq!(provider["authHeader"], true); + let api_key = provider["apiKey"].as_str().unwrap(); + assert!(api_key.starts_with('!'), "{api_key}"); + #[cfg(unix)] + { + assert!(api_key.contains("credential"), "{api_key}"); + assert!(api_key.contains("--client-id"), "{api_key}"); + assert!(api_key.contains("/usr/local/bin/agentdesktop"), "{api_key}"); + } + let models = provider["models"].as_array().unwrap(); + assert_eq!(models.len(), 2); + assert_eq!(models[0]["id"], "claude-haiku-4-5"); + assert_eq!(models[1]["id"], "claude-sonnet-4-5"); + assert_eq!(models[1]["name"], "Company Claude"); + assert_eq!(models[1]["contextWindow"], 200000); + + let startup = managed_settings(pi, Some(gateway)).expect("startup settings"); + assert_eq!(startup["defaultProvider"], "agentdesktop"); + assert_eq!(startup["defaultModel"], "claude-sonnet-4-5"); + } + + #[test] + fn openai_compatible_gateways_append_v1() { + let config = parse_daemon( + r#" +llmGateway: + url: https://gateway.example.com +programs: + pi: + model: local-model + api: openai-completions +"#, + ) + .expect("valid daemon configuration"); + let pi = config.programs.pi.as_ref().unwrap(); + let gateway = config.llm_gateway.as_ref().unwrap(); + let settings = managed_models( + pi, + Some(gateway), + Path::new("agentdesktop"), + Path::new("/tmp/agentdesktop.sock"), + ) + .expect("merged settings"); + assert_eq!( + settings["providers"]["agentdesktop"]["baseUrl"], + "https://gateway.example.com/v1" + ); + assert_eq!( + settings["providers"]["agentdesktop"]["api"], + "openai-completions" + ); + } +} diff --git a/crates/agent/src/reconcile/mod.rs b/crates/agent/src/reconcile/mod.rs index 64e71621..0205eba2 100644 --- a/crates/agent/src/reconcile/mod.rs +++ b/crates/agent/src/reconcile/mod.rs @@ -4,7 +4,8 @@ pub use plan::ReconcilePlan; use crate::provider::{ Provider, ReconcileContext, claude_code::ClaudeCode, claude_desktop::ClaudeDesktop, - codex::Codex, cursor::Cursor, grok::Grok, ollama::Ollama, opencode::OpenCode, vscode::VsCode, + codex::Codex, cursor::Cursor, grok::Grok, ollama::Ollama, opencode::OpenCode, pi::Pi, + vscode::VsCode, }; use agentdesktop_core::{config::DaemonConfig, model::Discovery}; use serde_json::Value; @@ -25,6 +26,7 @@ pub use crate::provider::{ codex::default_codex_managed_config_path, grok::default_grok_managed_config_path, opencode::{default_open_code_managed_config_path, default_open_code_plugin_path}, + pi::{default_pi_models_path, default_pi_settings_path}, }; #[derive(Clone)] @@ -44,6 +46,8 @@ impl Reconciler { open_code_managed_config_path: PathBuf, open_code_plugin_path: PathBuf, grok_managed_config_path: PathBuf, + pi_models_path: PathBuf, + pi_settings_path: PathBuf, credential_helper: PathBuf, socket: PathBuf, ) -> Self { @@ -73,6 +77,10 @@ impl Reconciler { Box::new(Grok { managed_config_path: grok_managed_config_path, }), + Box::new(Pi { + models_path: pi_models_path, + settings_path: pi_settings_path, + }), Box::new(Ollama), ]), } @@ -274,6 +282,8 @@ programs: root.join("opencode/config.json"), root.join("opencode/plugin.js"), root.join("grok/managed_config.toml"), + root.join("pi/models.json"), + root.join("pi/settings.json"), root.join("bin/agentdesktop"), root.join("agentdesktop.sock"), ); @@ -312,6 +322,8 @@ programs: root.join("opencode/config.json"), root.join("opencode/plugin.js"), root.join("grok/managed_config.toml"), + root.join("pi/models.json"), + root.join("pi/settings.json"), root.join("bin/agentdesktop"), root.join("agentdesktop.sock"), ); @@ -321,6 +333,49 @@ programs: assert!(!root.exists(), "preflight failure must not write any files"); } + #[test] + fn user_mode_reconciles_pi_models_and_settings() { + let root = std::env::temp_dir().join(format!( + "agentdesktop-reconcile-user-pi-{}-{}", + std::process::id(), + rand::random::() + )); + let config = parse_daemon( + r#" +llmGateway: + url: https://gateway.example.com +programs: + pi: + model: claude-sonnet-4-5 +"#, + ) + .unwrap(); + let reconciler = Reconciler::new( + true, + root.join("claude/settings.json"), + root.join("claude-desktop/settings.json"), + root.join("claude-desktop/helper"), + root.join("codex/config.toml"), + root.join("opencode/config.json"), + root.join("opencode/plugin.js"), + root.join("grok/managed_config.toml"), + root.join("pi/models.json"), + root.join("pi/settings.json"), + root.join("bin/agentdesktop"), + root.join("agentdesktop.sock"), + ); + + reconciler + .apply(&config) + .expect("Pi is supported in user mode"); + let models = String::from_utf8(fs::read(root.join("pi/models.json")).unwrap()).unwrap(); + assert!(models.contains("agentdesktop")); + assert!(models.contains("claude-sonnet-4-5")); + let settings = String::from_utf8(fs::read(root.join("pi/settings.json")).unwrap()).unwrap(); + assert!(settings.contains("defaultProvider")); + fs::remove_dir_all(root).unwrap(); + } + #[cfg(windows)] #[test] fn windows_reconciles_system_grok_managed_config() { @@ -367,6 +422,8 @@ programs: root.join("opencode/config.json"), root.join("opencode/plugin.js"), root.join("grok/managed_config.toml"), + root.join("pi/models.json"), + root.join("pi/settings.json"), root.join("bin/agentdesktop"), root.join("agentdesktop.sock"), ); @@ -408,6 +465,8 @@ programs: root.join("opencode/config.json"), root.join("opencode/plugin.js"), root.join("grok/managed_config.toml"), + root.join("pi/models.json"), + root.join("pi/settings.json"), root.join("bin/agentdesktop"), root.join("agentdesktop.sock"), ); @@ -431,7 +490,7 @@ llmGateway: authentication: type: controllerJwt audience: agentgateway - allowedClientIds: [claude-code, claude-desktop, codex, opencode, grok] + allowedClientIds: [claude-code, claude-desktop, codex, opencode, grok, pi] programs: claudeCode: {} claudeDesktop: {} @@ -442,6 +501,8 @@ programs: company-model: {} grok: model: grok-4.6 + pi: + model: claude-sonnet-4-5 "#, ) .unwrap(); @@ -464,6 +525,10 @@ programs: "opencode/config.json", "opencode/plugin.js", "grok/managed_config.toml", + "pi/models.json", + "pi/.models.json.agentdesktop", + "pi/settings.json", + "pi/.settings.json.agentdesktop", ]; let contents: Vec<_> = paths .iter() diff --git a/crates/core/src/config.rs b/crates/core/src/config.rs index 1ff9cb90..e99ba56c 100644 --- a/crates/core/src/config.rs +++ b/crates/core/src/config.rs @@ -76,6 +76,9 @@ pub struct DaemonStartupConfig { /// Grok Build paths. #[serde(default)] pub grok: ToolConfigPath, + /// Pi paths. + #[serde(default)] + pub pi: PiStartupConfig, } /// Local Claude Desktop paths. @@ -114,6 +117,19 @@ pub struct OpenCodeStartupConfig { pub plugin: Option, } +/// Local Pi paths. +#[derive(Clone, Debug, Default, Deserialize, Serialize)] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub struct PiStartupConfig { + /// Model catalog file. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub models: Option, + /// Settings file. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub settings: Option, +} + impl Default for DaemonConfig { fn default() -> Self { Self { @@ -481,6 +497,9 @@ pub struct ProgramsConfig { /// Grok Build managed configuration. #[serde(default, skip_serializing_if = "Option::is_none")] pub grok: Option, + /// Pi coding-agent harness managed configuration. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub pi: Option, } impl ProgramsConfig { @@ -490,6 +509,7 @@ impl ProgramsConfig { && self.codex.is_none() && self.open_code.is_none() && self.grok.is_none() + && self.pi.is_none() } } @@ -616,6 +636,39 @@ pub struct GrokConfig { pub managed_config: BTreeMap, } +/// Settings reconciled into Pi's user `models.json` and `settings.json`. +/// +/// Generated `providers.agentdesktop` values take precedence over overlapping +/// `managedConfig` keys. Pi stores configuration under `~/.pi/agent` (or +/// `PI_CODING_AGENT_DIR`), so `--user` is the supported management mode. +#[derive(Clone, Debug, Deserialize, Serialize)] +#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub struct PiConfig { + /// Whether this program uses the top-level LLM gateway. + #[serde(default = "default_true", skip_serializing_if = "is_true")] + pub use_llm_gateway: bool, + /// Model ID selected when pointing Pi at the LLM gateway. + /// + /// This is required when a top-level `llmGateway` is configured. If `models` + /// is empty, agentdesktop creates a catalog entry with this ID. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub model: Option, + /// Extra Pi model objects, keyed by model ID. + /// + /// Each value is an arbitrary Pi `models.json` model object. Generated + /// gateway `baseUrl` and `apiKey` values take precedence. When this map is + /// non-empty, `model` must name one of its keys. + #[serde(default, skip_serializing_if = "BTreeMap::is_empty")] + pub models: BTreeMap, + /// Pi API dialect for the managed provider. Defaults to `anthropic-messages`. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub api: Option, + /// Arbitrary values merged into Pi's `models.json`. + #[serde(default, skip_serializing_if = "BTreeMap::is_empty")] + pub managed_config: BTreeMap, +} + /// Upstream authentication selected by a managed agent. #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] @@ -758,6 +811,9 @@ fn validate_daemon( if programs.grok.is_some() { anyhow::bail!("sandbox is not supported for Grok Build"); } + if programs.pi.is_some() { + anyhow::bail!("sandbox is not supported for Pi"); + } } if let Some(gateway) = llm_gateway { if !matches!(gateway.url.scheme(), "http" | "https") { @@ -901,6 +957,19 @@ fn validate_daemon( anyhow::bail!("Grok Build model {model} is not declared in models"); } } + if let Some(pi) = &programs.pi + && llm_gateway.is_some() + && pi.use_llm_gateway + { + let model = pi + .model + .as_deref() + .filter(|model| !model.trim().is_empty()) + .context("Pi requires model when llmGateway is configured")?; + if !pi.models.is_empty() && !pi.models.contains_key(model) { + anyhow::bail!("Pi model {model} is not declared in models"); + } + } Ok(()) } @@ -952,6 +1021,22 @@ mod tests { assert!(super::parse_local_daemon("daemon: { stateDr: /tmp/device }").is_err()); } + #[test] + fn pi_startup_paths_are_local_only() { + let yaml = "daemon:\n pi:\n models: custom/pi-models.json\n settings: custom/pi-settings.json\n"; + let config = super::parse_local_daemon(yaml).expect("local Pi paths are supported"); + let value = serde_json::to_value(config).unwrap(); + assert_eq!(value["daemon"]["pi"]["models"], "custom/pi-models.json"); + assert_eq!(value["daemon"]["pi"]["settings"], "custom/pi-settings.json"); + assert!( + parse_daemon(yaml) + .unwrap_err() + .to_string() + .contains("only allowed in the local") + ); + assert!(super::parse_local_daemon("daemon: { pi: { model: wrong } }").is_err()); + } + #[test] fn daemon_configuration_supports_local_and_managed_options() { let document = r#" @@ -1301,4 +1386,68 @@ programs: .contains("Grok Build model missing is not declared in models") ); } + + #[test] + fn pi_parses_gateway_catalog_settings() { + let daemon = parse_daemon( + r#" +llmGateway: + url: https://gateway.example.com +programs: + pi: + model: claude-sonnet-4-5 + api: anthropic-messages + models: + claude-sonnet-4-5: + name: Claude Sonnet +"#, + ) + .expect("valid Pi configuration"); + let pi = daemon.programs.pi.expect("Pi program"); + assert!(pi.use_llm_gateway); + assert_eq!(pi.model.as_deref(), Some("claude-sonnet-4-5")); + assert_eq!(pi.api.as_deref(), Some("anthropic-messages")); + assert!(pi.models.contains_key("claude-sonnet-4-5")); + } + + #[test] + fn pi_requires_a_model_when_using_the_gateway() { + let error = parse_daemon( + r#" +llmGateway: + url: https://gateway.example.com +programs: + pi: {} +"#, + ) + .expect_err("Pi without a model should fail"); + + assert!( + error + .to_string() + .contains("Pi requires model when llmGateway is configured") + ); + } + + #[test] + fn pi_requires_a_declared_gateway_model_when_models_are_listed() { + let error = parse_daemon( + r#" +llmGateway: + url: https://gateway.example.com +programs: + pi: + model: missing + models: + available: {} +"#, + ) + .expect_err("undeclared Pi model should fail"); + + assert!( + error + .to_string() + .contains("Pi model missing is not declared in models") + ); + } } diff --git a/examples/claude/claude-code.yaml b/examples/claude/claude-code.yaml index b43aa366..4f504e58 100644 --- a/examples/claude/claude-code.yaml +++ b/examples/claude/claude-code.yaml @@ -4,7 +4,7 @@ llmGateway: authentication: type: controllerJwt audience: "agentgateway" - allowedClientIds: [claude-code, claude-desktop, codex, opencode] + allowedClientIds: [claude-code, claude-desktop, codex, opencode, pi] telemetry: events: diff --git a/examples/standalone/config.yaml b/examples/standalone/config.yaml index dcbad869..351ffa1f 100644 --- a/examples/standalone/config.yaml +++ b/examples/standalone/config.yaml @@ -12,6 +12,9 @@ programs: claudeCode: useLlmGateway: true companyAnnouncements: ["Using the local Agentgateway through Agentdesktop"] + pi: + useLlmGateway: true + model: claude-sonnet-4-5 # Claude desktop cannot run with --user. Uncomment to enable # claudeDesktop: # useLlmGateway: true diff --git a/frontend/controller/src/types.ts b/frontend/controller/src/types.ts index d3bb0aeb..94625be7 100644 --- a/frontend/controller/src/types.ts +++ b/frontend/controller/src/types.ts @@ -74,7 +74,8 @@ export type AgentKind = | "claudeDesktop" | "codex" | "openCode" - | "grok"; + | "grok" + | "pi"; export type AgentDraft = { kind: AgentKind; diff --git a/frontend/controller/src/views/ConfigurationView.tsx b/frontend/controller/src/views/ConfigurationView.tsx index ad4ad3f3..9ef67768 100644 --- a/frontend/controller/src/views/ConfigurationView.tsx +++ b/frontend/controller/src/views/ConfigurationView.tsx @@ -528,12 +528,20 @@ const configurableAgents: Array<{ placeholder: "model: grok-4.6", initialSettings: "model: grok-4.6", }, + { + kind: "pi", + label: "Pi", + iconKind: "pi", + placeholder: "model: claude-sonnet-4-5", + initialSettings: "model: claude-sonnet-4-5", + }, ]; const sandboxUnsupportedAgents = new Set([ "claudeDesktop", "openCode", "grok", + "pi", ]); function daemonConfigYaml(options: { @@ -558,7 +566,7 @@ function daemonConfigYaml(options: { " authentication:", " type: controllerJwt", ` audience: ${yamlString(options.audience)}`, - " allowedClientIds: [claude-code, claude-desktop, codex, opencode, grok]", + " allowedClientIds: [claude-code, claude-desktop, codex, opencode, grok, pi]", ); } lines.push(""); diff --git a/frontend/desktop/src/views/ToolsView.tsx b/frontend/desktop/src/views/ToolsView.tsx index 0f1c702c..99f3f531 100644 --- a/frontend/desktop/src/views/ToolsView.tsx +++ b/frontend/desktop/src/views/ToolsView.tsx @@ -97,7 +97,7 @@ export function ToolsView({ discovery, unavailable }: ToolsViewProps) { No supported tools found Agent Desktop can inventory VS Code, Claude Code, Claude - Desktop, Codex, OpenCode, and Grok Build. + Desktop, Codex, OpenCode, Grok Build, and Pi. diff --git a/frontend/ui/src/assets/pi.svg b/frontend/ui/src/assets/pi.svg new file mode 100644 index 00000000..046fd463 --- /dev/null +++ b/frontend/ui/src/assets/pi.svg @@ -0,0 +1 @@ +Pi diff --git a/frontend/ui/src/tools.tsx b/frontend/ui/src/tools.tsx index 1fbda87e..71292322 100644 --- a/frontend/ui/src/tools.tsx +++ b/frontend/ui/src/tools.tsx @@ -15,6 +15,7 @@ import cursorIcon from "./assets/cursor.svg"; import grokIcon from "./assets/grok.svg"; import ollamaIcon from "./assets/ollama.svg"; import openCodeIcon from "./assets/opencode.svg"; +import piIcon from "./assets/pi.svg"; export interface ToolMcpServer { name: string; @@ -53,6 +54,7 @@ const toolIcons: Record = { "grok-build": grokIcon, cursor: cursorIcon, opencode: openCodeIcon, + pi: piIcon, vscode: copilotIcon, }; @@ -66,6 +68,7 @@ export function friendlyTool(kind: string) { grok: "Grok Build", "grok-build": "Grok Build", opencode: "OpenCode", + pi: "Pi", vscode: "VS Code", cursor: "Cursor", }; diff --git a/schema/daemon-config.json b/schema/daemon-config.json index 7f7bd3e9..bcb49067 100644 --- a/schema/daemon-config.json +++ b/schema/daemon-config.json @@ -208,6 +208,11 @@ "$ref": "#/$defs/OpenCodeStartupConfig", "default": {} }, + "pi": { + "description": "Pi paths.", + "$ref": "#/$defs/PiStartupConfig", + "default": {} + }, "socket": { "description": "Local API Unix socket or Windows named pipe.", "type": [ @@ -412,6 +417,62 @@ }, "additionalProperties": false }, + "PiConfig": { + "description": "Settings reconciled into Pi's user `models.json` and `settings.json`.\n\nGenerated `providers.agentdesktop` values take precedence over overlapping\n`managedConfig` keys. Pi stores configuration under `~/.pi/agent` (or\n`PI_CODING_AGENT_DIR`), so `--user` is the supported management mode.", + "type": "object", + "properties": { + "api": { + "description": "Pi API dialect for the managed provider. Defaults to `anthropic-messages`.", + "type": [ + "string", + "null" + ] + }, + "managedConfig": { + "description": "Arbitrary values merged into Pi's `models.json`.", + "type": "object", + "additionalProperties": true + }, + "model": { + "description": "Model ID selected when pointing Pi at the LLM gateway.\n\nThis is required when a top-level `llmGateway` is configured. If `models`\nis empty, agentdesktop creates a catalog entry with this ID.", + "type": [ + "string", + "null" + ] + }, + "models": { + "description": "Extra Pi model objects, keyed by model ID.\n\nEach value is an arbitrary Pi `models.json` model object. Generated\ngateway `baseUrl` and `apiKey` values take precedence. When this map is\nnon-empty, `model` must name one of its keys.", + "type": "object", + "additionalProperties": true + }, + "useLlmGateway": { + "description": "Whether this program uses the top-level LLM gateway.", + "type": "boolean" + } + }, + "additionalProperties": false + }, + "PiStartupConfig": { + "description": "Local Pi paths.", + "type": "object", + "properties": { + "models": { + "description": "Model catalog file.", + "type": [ + "string", + "null" + ] + }, + "settings": { + "description": "Settings file.", + "type": [ + "string", + "null" + ] + } + }, + "additionalProperties": false + }, "ProgramAuthentication": { "description": "Upstream authentication selected by a managed agent.", "oneOf": [ @@ -480,6 +541,17 @@ "type": "null" } ] + }, + "pi": { + "description": "Pi coding-agent harness managed configuration.", + "anyOf": [ + { + "$ref": "#/$defs/PiConfig" + }, + { + "type": "null" + } + ] } }, "additionalProperties": false diff --git a/schema/daemon-config.md b/schema/daemon-config.md index bd98ee58..a0baf07c 100644 --- a/schema/daemon-config.md +++ b/schema/daemon-config.md @@ -20,6 +20,9 @@ |`daemon.openCode`|object|OpenCode paths.| |`daemon.openCode.config`|string|Configuration file.| |`daemon.openCode.plugin`|string|Credential plugin path.| +|`daemon.pi`|object|Pi paths.| +|`daemon.pi.models`|string|Model catalog file.| +|`daemon.pi.settings`|string|Settings file.| |`daemon.socket`|string|Local API Unix socket or Windows named pipe.| |`daemon.stateDir`|string|Persistent daemon state directory.| |`daemon.user`|boolean|Manage the current user’s tool settings instead of system settings.| @@ -56,6 +59,12 @@ |`programs.openCode.model`|string|Model ID selected from `models` when using the LLM gateway.

This is required when a top-level `llmGateway` is configured.| |`programs.openCode.models`|object|Models exposed by the managed LLM gateway provider, keyed by model ID.

Each value is an arbitrary OpenCode model configuration object. At least
one model is required when a top-level `llmGateway` is configured.| |`programs.openCode.useLlmGateway`|boolean|Whether this program uses the top-level LLM gateway.| +|`programs.pi`|object|Pi coding-agent harness managed configuration.| +|`programs.pi.api`|string|Pi API dialect for the managed provider. Defaults to `anthropic-messages`.| +|`programs.pi.managedConfig`|object|Arbitrary values merged into Pi's `models.json`.| +|`programs.pi.model`|string|Model ID selected when pointing Pi at the LLM gateway.

This is required when a top-level `llmGateway` is configured. If `models`
is empty, agentdesktop creates a catalog entry with this ID.| +|`programs.pi.models`|object|Extra Pi model objects, keyed by model ID.

Each value is an arbitrary Pi `models.json` model object. Generated
gateway `baseUrl` and `apiKey` values take precedence. When this map is
non-empty, `model` must name one of its keys.| +|`programs.pi.useLlmGateway`|boolean|Whether this program uses the top-level LLM gateway.| |`sandbox`|object|Local execution sandbox required for managed developer tools.| |`sandbox.filesystem`|object|Filesystem access available to sandboxed commands.| |`sandbox.filesystem.denied`|[]string|Paths sandboxed commands may neither read nor modify.| From 73f8d2f3446c9e7f7f7b604e73a98782bbb3f9a5 Mon Sep 17 00:00:00 2001 From: Sebastian Maniak Date: Fri, 18 Sep 2026 17:16:12 -0400 Subject: [PATCH 02/11] docs: add Pi inventory screenshots for review Local Agent Desktop Tools view of Pi 0.85.1 with MCP and skills. --- docs/screenshots/pi-inventory.png | Bin 0 -> 20661 bytes docs/screenshots/pi-mcp.png | Bin 0 -> 29517 bytes docs/screenshots/pi-skills.png | Bin 0 -> 55953 bytes 3 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 docs/screenshots/pi-inventory.png create mode 100644 docs/screenshots/pi-mcp.png create mode 100644 docs/screenshots/pi-skills.png diff --git a/docs/screenshots/pi-inventory.png b/docs/screenshots/pi-inventory.png new file mode 100644 index 0000000000000000000000000000000000000000..637a82ddf2e60b7b65c5f148fc83b2a5f3e2c9e6 GIT binary patch literal 20661 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 z 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 L>gTe~DWM4fHtd$ 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®Y6 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®>(7gf@CHdbj~2N)-mH!;15*l3k9US-hlCvm^}0 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

@gCb6`%-L; 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 literal 0 HcmV?d00001 From f2953e1838d4458a6fc70b816ed62ce0027bb1e5 Mon Sep 17 00:00:00 2001 From: Sebastian Maniak Date: Fri, 18 Sep 2026 17:21:50 -0400 Subject: [PATCH 03/11] Include Pi in the controller-managed example config The controller watches examples/claude/claude-code.yaml. Add programs.pi so the Configuration UI shows Pi next to Claude Code/Desktop. --- examples/claude/claude-code.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/examples/claude/claude-code.yaml b/examples/claude/claude-code.yaml index 4f504e58..b37a4a26 100644 --- a/examples/claude/claude-code.yaml +++ b/examples/claude/claude-code.yaml @@ -16,3 +16,6 @@ programs: companyAnnouncements: ["Managed by Agentdesktop"] claudeDesktop: isLocalDevMcpEnabled: true + pi: + useLlmGateway: true + model: claude-sonnet-4-5 From 30d50680d83181b14ff0e323080ee3ddec778757 Mon Sep 17 00:00:00 2001 From: Sebastian Maniak Date: Fri, 18 Sep 2026 17:25:10 -0400 Subject: [PATCH 04/11] docs: add controller Pi configuration screenshot --- docs/screenshots/pi-controller-config.png | Bin 0 -> 28101 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 docs/screenshots/pi-controller-config.png diff --git a/docs/screenshots/pi-controller-config.png b/docs/screenshots/pi-controller-config.png new file mode 100644 index 0000000000000000000000000000000000000000..34bf6170bf9549fcc35196a13f36aef68f7bdcb2 GIT binary patch literal 28101 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 Q#Osizs_w0#o3_FK2jlhr)Bpeg literal 0 HcmV?d00001 From 5f2c50df3bd689cd5a776f6bb692441ac25983d9 Mon Sep 17 00:00:00 2001 From: Sebastian Maniak Date: Fri, 18 Sep 2026 17:36:01 -0400 Subject: [PATCH 05/11] style: keep agentdesktop lowercase in Pi copy --- crates/agent/src/provider/README.md | 6 +++--- frontend/desktop/src/views/ToolsView.tsx | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/agent/src/provider/README.md b/crates/agent/src/provider/README.md index 14b3358c..1ba2b6ce 100644 --- a/crates/agent/src/provider/README.md +++ b/crates/agent/src/provider/README.md @@ -1,6 +1,6 @@ # Provider support -Features currently implemented by Agentdesktop. +Features currently implemented by agentdesktop. Grok Build managed configuration requires system mode. Linux and macOS use `/etc/grok/managed_config.toml`; Windows uses the system drive's @@ -30,9 +30,9 @@ files are not scanned until the adapter imports them into a Pi-owned file. | Tool-use telemetry | ✅ | ◯ | ◯ | ◯ | ◯ | — | ◯ | ◯ | ◯ | | Session-start telemetry | ✅ | ◯ | ◯ | ◯ | ◯ | — | ◯ | ◯ | ◯ | -- ◯ describes a gap in Agentdesktop; it does not mean the upstream provider +- ◯ describes a gap in agentdesktop; it does not mean the upstream provider cannot support the feature. -- Managed configuration includes dry runs and removal of Agentdesktop-owned +- Managed configuration includes dry runs and removal of agentdesktop-owned settings. Claude Code also supports merging into user settings; Claude Desktop requires system-managed settings. - Discovery is best effort; versions may be unavailable. Ollama discovers models diff --git a/frontend/desktop/src/views/ToolsView.tsx b/frontend/desktop/src/views/ToolsView.tsx index 99f3f531..8d6d78ab 100644 --- a/frontend/desktop/src/views/ToolsView.tsx +++ b/frontend/desktop/src/views/ToolsView.tsx @@ -96,7 +96,7 @@ export function ToolsView({ discovery, unavailable }: ToolsViewProps) {

No supported tools found - Agent Desktop can inventory VS Code, Claude Code, Claude + agentdesktop can inventory VS Code, Claude Code, Claude Desktop, Codex, OpenCode, Grok Build, and Pi.
From f45e0eb938bb9eb73eeae22f7563a36d70c3e783 Mon Sep 17 00:00:00 2001 From: Sebastian Maniak Date: Fri, 18 Sep 2026 20:12:15 -0400 Subject: [PATCH 06/11] style: wrap ToolsView empty-state copy for biome --- frontend/desktop/src/views/ToolsView.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/desktop/src/views/ToolsView.tsx b/frontend/desktop/src/views/ToolsView.tsx index 8d6d78ab..48f2586c 100644 --- a/frontend/desktop/src/views/ToolsView.tsx +++ b/frontend/desktop/src/views/ToolsView.tsx @@ -96,8 +96,8 @@ export function ToolsView({ discovery, unavailable }: ToolsViewProps) {
No supported tools found - agentdesktop can inventory VS Code, Claude Code, Claude - Desktop, Codex, OpenCode, Grok Build, and Pi. + agentdesktop can inventory VS Code, Claude Code, Claude Desktop, + Codex, OpenCode, Grok Build, and Pi.
From acd219cb3f88dc42f2d042223a2f12cf9feefc66 Mon Sep 17 00:00:00 2001 From: Sebastian Maniak Date: Fri, 18 Sep 2026 22:38:51 -0400 Subject: [PATCH 07/11] fix: address Pi gateway configuration and discovery issues --- crates/agent/src/daemon.rs | 31 ++ crates/agent/src/provider/README.md | 15 +- .../src/provider/claude_code/reconcile.rs | 2 + .../src/provider/claude_desktop/reconcile.rs | 1 + crates/agent/src/provider/json_merge.rs | 24 +- crates/agent/src/provider/pi/discovery.rs | 299 +++++++++++++++++- crates/agent/src/provider/pi/mod.rs | 66 +++- crates/agent/src/provider/pi/reconcile.rs | 206 +++++++++++- crates/agent/src/reconcile/mod.rs | 45 ++- crates/core/src/config.rs | 9 +- examples/claude/README.md | 13 + examples/claude/claude-code.yaml | 7 +- examples/standalone/README.md | 9 +- examples/standalone/config.yaml | 2 +- schema/daemon-config.json | 4 +- schema/daemon-config.md | 2 +- 16 files changed, 690 insertions(+), 45 deletions(-) diff --git a/crates/agent/src/daemon.rs b/crates/agent/src/daemon.rs index c5db9816..f44d5a57 100644 --- a/crates/agent/src/daemon.rs +++ b/crates/agent/src/daemon.rs @@ -686,6 +686,11 @@ fn validate_one_shot(config: &agentdesktop_core::config::DaemonConfig) -> anyhow .grok .as_ref() .is_some_and(|program| program.use_llm_gateway), + config + .programs + .pi + .as_ref() + .is_some_and(|program| program.use_llm_gateway), ] .into_iter() .any(|used| used); @@ -1374,6 +1379,32 @@ programs: ); } + #[test] + fn one_shot_rejects_authenticated_pi_gateway() { + for authentication in [ + " type: oidc\n issuer: https://login.example.com\n clientId: agentdesktop\n", + " type: controllerJwt\n audience: agentgateway\n allowedClientIds: [pi]\n", + ] { + let config = parse_daemon(&format!( + "llmGateway:\n url: https://gateway.example.com\n authentication:\n{authentication}programs:\n pi:\n model: selected\n" + )).unwrap(); + let error = + validate_one_shot(&config).expect_err("Pi credentials require a running daemon"); + assert!(error.to_string().contains("credential helpers")); + } + } + + #[test] + fn one_shot_accepts_pi_without_runtime_credentials() { + for yaml in [ + "programs:\n pi: {}\n", + "llmGateway:\n url: https://gateway.example.com\nprograms:\n pi:\n model: selected\n", + "llmGateway:\n url: https://gateway.example.com\n authentication:\n type: oidc\n issuer: https://login.example.com\n clientId: agentdesktop\nprograms:\n pi:\n useLlmGateway: false\n", + ] { + validate_one_shot(&parse_daemon(yaml).unwrap()).unwrap(); + } + } + #[test] fn one_shot_accepts_static_settings_and_rejects_runtime_services() { let static_config = parse_daemon( diff --git a/crates/agent/src/provider/README.md b/crates/agent/src/provider/README.md index 1ba2b6ce..a432cecb 100644 --- a/crates/agent/src/provider/README.md +++ b/crates/agent/src/provider/README.md @@ -8,9 +8,18 @@ Grok Build managed configuration requires system mode. Linux and macOS use Grok's own configuration sync can delete or replace the user-level managed file. -Pi stores configuration in `~/.pi/agent` (or `PI_CODING_AGENT_DIR`). `--user` -merges `providers.agentdesktop` into `models.json` and sets the default model -in `settings.json`. MCP inventory comes from `pi-mcp-adapter` config files +Pi stores configuration in `~/.pi/agent` (or `PI_CODING_AGENT_DIR`, with `~` +expanded to the user's home). Management requires `--user`; system mode +rejects `programs.pi` because Pi does not read `/etc/pi/agent`. Authenticated +gateway configuration also requires a running daemon and rejects `--once`. +User mode merges `providers.agentdesktop` into `models.json` and sets the +default model in `settings.json`. Model map keys determine catalog IDs, and +gateway URLs override per-model endpoints, using each model's API dialect. +Commented `models.json` files are accepted and rewritten as standard JSON, +preserving user configuration values. `settings.json` requires standard JSON. +Discovery recognizes npm's Windows command shims as well as symlinked launchers, +verifying the Pi package manifest without executing the launcher. +MCP inventory comes from `pi-mcp-adapter` config files (`~/.pi/agent/mcp.json`, `.pi/mcp.json`, `.mcp.json`, and shared `~/.config/mcp/mcp.json` / `~/.agents/mcp.json`). Host-specific Cursor/Claude files are not scanned until the adapter imports them into a Pi-owned file. diff --git a/crates/agent/src/provider/claude_code/reconcile.rs b/crates/agent/src/provider/claude_code/reconcile.rs index 54727276..19b92f8e 100644 --- a/crates/agent/src/provider/claude_code/reconcile.rs +++ b/crates/agent/src/provider/claude_code/reconcile.rs @@ -52,6 +52,7 @@ pub(super) fn plan( && json_merge::plan_remove( path, &merge_state_path, + json_merge::JsonFormat::Json, "settings", ClaudeCode::DISPLAY_NAME, plan, @@ -68,6 +69,7 @@ pub(super) fn plan( json_merge::plan_merge( path, &merge_state_path, + json_merge::JsonFormat::Json, settings, is_owned(&owner_path, plan)?, "settings", diff --git a/crates/agent/src/provider/claude_desktop/reconcile.rs b/crates/agent/src/provider/claude_desktop/reconcile.rs index 29477c66..41788ed4 100644 --- a/crates/agent/src/provider/claude_desktop/reconcile.rs +++ b/crates/agent/src/provider/claude_desktop/reconcile.rs @@ -32,6 +32,7 @@ pub(super) fn plan( || !json_merge::plan_remove( settings_path, &settings_state, + json_merge::JsonFormat::Json, "managed settings", ClaudeDesktop::DISPLAY_NAME, plan, diff --git a/crates/agent/src/provider/json_merge.rs b/crates/agent/src/provider/json_merge.rs index d9e39c35..38ecd09c 100644 --- a/crates/agent/src/provider/json_merge.rs +++ b/crates/agent/src/provider/json_merge.rs @@ -17,6 +17,24 @@ struct MergeState { after: Value, } +pub(super) enum JsonFormat { + Json, + Jsonc, +} + +impl JsonFormat { + fn parse(&self, contents: &[u8]) -> anyhow::Result { + match self { + Self::Json => Ok(serde_json::from_slice(contents)?), + Self::Jsonc => { + let contents = std::str::from_utf8(contents)?; + // Normalize commented input to ordinary JSON when writing it. + Ok(json5::from_str(contents.trim_start_matches('\u{feff}'))?) + } + } + } +} + pub(super) fn state_path(path: &Path) -> PathBuf { let name = path .file_name() @@ -29,6 +47,7 @@ pub(super) fn state_path(path: &Path) -> PathBuf { pub(super) fn plan_merge( path: &Path, state_path: &Path, + format: JsonFormat, managed: Value, legacy_owned: bool, description: &str, @@ -50,7 +69,7 @@ pub(super) fn plan_merge( .unwrap_or(existing.is_none() || legacy_owned); let mut combined = match existing.as_deref() { - Some(contents) => match serde_json::from_slice::(contents) { + Some(contents) => match format.parse(contents) { Ok(Value::Object(object)) => Value::Object(object), Ok(_) | Err(_) => { plan.record(display_name, description, "conflict", path); @@ -103,6 +122,7 @@ pub(super) fn plan_merge( pub(super) fn plan_remove( path: &Path, state_path: &Path, + format: JsonFormat, description: &str, display_name: &str, plan: &ReconcilePlan, @@ -122,7 +142,7 @@ pub(super) fn plan_remove( .with_context(|| format!("read {display_name} from {}", path.display())); } }; - let settings = match serde_json::from_slice::(&existing) { + let settings = match format.parse(&existing) { Ok(Value::Object(object)) => Value::Object(object), Ok(_) | Err(_) => { plan.record(display_name, description, "conflict", path); diff --git a/crates/agent/src/provider/pi/discovery.rs b/crates/agent/src/provider/pi/discovery.rs index 91932b3b..9c500010 100644 --- a/crates/agent/src/provider/pi/discovery.rs +++ b/crates/agent/src/provider/pi/discovery.rs @@ -1,5 +1,6 @@ use std::{ collections::BTreeSet, + io::Read, path::{Path, PathBuf}, }; @@ -7,7 +8,7 @@ use agentdesktop_core::model::{Agent, McpServer}; use crate::provider::{metadata, vscode::discovery as vscode}; -use super::Pi; +use super::{Pi, user_pi_agent_dir}; pub(super) fn discover() -> Option { let executable = metadata::find_all_in_path("pi") @@ -71,6 +72,9 @@ fn package_manifest(executable: &Path) -> Option { .into_iter() .flatten() { + if let Some(manifest) = npm_shim_manifest(&start) { + return Some(manifest); + } let mut directory = start.parent()?.to_path_buf(); for _ in 0..8 { let manifest = directory.join("package.json"); @@ -87,6 +91,47 @@ fn package_manifest(executable: &Path) -> Option { None } +fn npm_shim_manifest(executable: &Path) -> Option { + let manifest = executable + .parent()? + .join("node_modules") + .join(Pi::PACKAGE_NAME) + .join("package.json"); + metadata::json_package_version(&manifest, Pi::PACKAGE_NAME)?; + + // npm's Windows global launchers point to a sibling package. Verify that + // relationship from the small launcher script without executing it. + const MAX_SHIM_BYTES: usize = 16 * 1024; + let file = std::fs::File::open(executable).ok()?; + if !file.metadata().ok()?.is_file() { + return None; + } + let mut source = String::new(); + file.take((MAX_SHIM_BYTES + 1) as u64) + .read_to_string(&mut source) + .ok()?; + if source.len() > MAX_SHIM_BYTES { + return None; + } + + let target = format!("node_modules/{}/dist/bundle/cli.js\"", Pi::PACKAGE_NAME); + let basedir_target = format!("\"$basedir/{target}"); + let dp0_target = format!("\"%dp0%/{target}"); + source + .lines() + .any(|line| { + let line = line.trim_start().replace('\\', "/"); + ((line.starts_with("exec ") + || line.starts_with("& ") + || line.starts_with("$input | & ")) + && line.contains(&basedir_target)) + || (line.starts_with("endLocal & ") + && line.contains("\"%_prog%\"") + && line.contains(&dp0_target)) + }) + .then_some(manifest) +} + fn discover_mcp_servers() -> Vec { mcp_config_paths() .into_iter() @@ -101,10 +146,7 @@ fn discover_mcp_servers() -> Vec { fn mcp_config_paths() -> Vec { let mut paths = BTreeSet::new(); for home in metadata::user_home_dirs() { - let agent_dir = std::env::var_os("PI_CODING_AGENT_DIR") - .filter(|value| !value.is_empty()) - .map(PathBuf::from) - .unwrap_or_else(|| home.join(".pi/agent")); + let agent_dir = user_pi_agent_dir(&home); paths.insert(agent_dir.join("mcp.json")); paths.insert(home.join(".config/mcp/mcp.json")); paths.insert(home.join(".agents/mcp.json")); @@ -118,10 +160,7 @@ fn mcp_config_paths() -> Vec { fn skill_roots() -> Vec { let mut roots = BTreeSet::new(); for home in metadata::user_home_dirs() { - let agent_dir = std::env::var_os("PI_CODING_AGENT_DIR") - .filter(|value| !value.is_empty()) - .map(PathBuf::from) - .unwrap_or_else(|| home.join(".pi/agent")); + let agent_dir = user_pi_agent_dir(&home); roots.insert(agent_dir.join("skills")); roots.insert(home.join(".agents/skills")); } @@ -139,7 +178,8 @@ mod tests { use serde_json::json; - use super::{is_pi, mcp_config_paths, package_version}; + use super::{is_pi, mcp_config_paths, package_version, skill_roots}; + use crate::provider::pi::user_pi_agent_dir; use crate::provider::vscode::discovery::mcp_servers_from_value; #[test] @@ -180,6 +220,165 @@ mod tests { let _ = fs::remove_dir_all(root); } + // npm-generated Windows global shims: package.json is beside these launchers + // under node_modules, rather than in an ancestor of the launcher itself. + const NPM_CMD_SHIM: &str = r#"@ECHO off +GOTO start +:find_dp0 +SET dp0=%~dp0 +EXIT /b +:start +SETLOCAL +CALL :find_dp0 + +IF EXIST "%dp0%\node.exe" ( + SET "_prog=%dp0%\node.exe" +) ELSE ( + SET "_prog=node" + SET PATHEXT=%PATHEXT:;.JS;=;% +) + +endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\node_modules\@earendil-works\pi-coding-agent\dist\bundle\cli.js" %* +"#; + + const NPM_POWERSHELL_SHIM: &str = r#"#!/usr/bin/env pwsh +$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent + +$exe="" +if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) { + # Fix case when both the Windows and Linux builds of Node + # are installed in the same directory + $exe=".exe" +} +$ret=0 +if (Test-Path "$basedir/node$exe") { + # Support pipeline input + if ($MyInvocation.ExpectingInput) { + $input | & "$basedir/node$exe" "$basedir/node_modules/@earendil-works/pi-coding-agent/dist/bundle/cli.js" $args + } else { + & "$basedir/node$exe" "$basedir/node_modules/@earendil-works/pi-coding-agent/dist/bundle/cli.js" $args + } + $ret=$LASTEXITCODE +} else { + # Support pipeline input + if ($MyInvocation.ExpectingInput) { + $input | & "node$exe" "$basedir/node_modules/@earendil-works/pi-coding-agent/dist/bundle/cli.js" $args + } else { + & "node$exe" "$basedir/node_modules/@earendil-works/pi-coding-agent/dist/bundle/cli.js" $args + } + $ret=$LASTEXITCODE +} +exit $ret +"#; + + const NPM_SHELL_SHIM: &str = r#"#!/bin/sh +basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") + +case `uname` in + *CYGWIN*|*MINGW*|*MSYS*) + if command -v cygpath > /dev/null 2>&1; then + basedir=`cygpath -w "$basedir"` + fi + ;; +esac + +if [ -x "$basedir/node" ]; then + exec "$basedir/node" "$basedir/node_modules/@earendil-works/pi-coding-agent/dist/bundle/cli.js" "$@" +else + exec node "$basedir/node_modules/@earendil-works/pi-coding-agent/dist/bundle/cli.js" "$@" +fi +"#; + + fn npm_shim_fixture(name: &str, source: &str) -> (tempfile::TempDir, PathBuf) { + let root = tempfile::tempdir().unwrap(); + let package = root + .path() + .join("node_modules/@earendil-works/pi-coding-agent"); + fs::create_dir_all(package.join("dist/bundle")).unwrap(); + fs::write( + package.join("package.json"), + r#"{"name":"@earendil-works/pi-coding-agent","version":"0.85.1"}"#, + ) + .unwrap(); + fs::write(package.join("dist/bundle/cli.js"), "#!/usr/bin/env node\n").unwrap(); + let shim = root.path().join(name); + fs::write(&shim, source).unwrap(); + (root, shim) + } + + #[test] + fn accepts_npm_cmd_shim_and_reads_version() { + let (_root, shim) = npm_shim_fixture("pi.cmd", NPM_CMD_SHIM); + assert!(is_pi(&shim)); + assert_eq!(package_version(&shim).as_deref(), Some("0.85.1")); + } + + #[test] + fn accepts_npm_powershell_shim_and_reads_version() { + let (_root, shim) = npm_shim_fixture("pi.ps1", NPM_POWERSHELL_SHIM); + assert!(is_pi(&shim)); + assert_eq!(package_version(&shim).as_deref(), Some("0.85.1")); + } + + #[test] + fn accepts_npm_shell_shim_and_reads_version() { + let (_root, shim) = npm_shim_fixture("pi", NPM_SHELL_SHIM); + assert!(is_pi(&shim)); + assert_eq!(package_version(&shim).as_deref(), Some("0.85.1")); + } + + #[test] + fn rejects_unrelated_launchers_beside_pi_package() { + for (name, source) in [ + ("pi.cmd", NPM_CMD_SHIM), + ("pi.ps1", NPM_POWERSHELL_SHIM), + ("pi", NPM_SHELL_SHIM), + ] { + let unrelated = source + .replace("@earendil-works/pi-coding-agent", "unrelated-pi") + .replace(r"@earendil-works\pi-coding-agent", "unrelated-pi"); + let (_root, shim) = npm_shim_fixture(name, &unrelated); + assert!(!is_pi(&shim)); + assert_eq!(package_version(&shim), None); + } + } + + #[test] + fn rejects_npm_shim_without_matching_package_metadata() { + let (root, shim) = npm_shim_fixture("pi.cmd", NPM_CMD_SHIM); + let manifest = root + .path() + .join("node_modules/@earendil-works/pi-coding-agent/package.json"); + for contents in [ + r#"{"name":"unrelated-pi","version":"0.85.1"}"#, + r#"{"name":"@earendil-works/pi-coding-agent"}"#, + ] { + fs::write(&manifest, contents).unwrap(); + assert!(!is_pi(&shim)); + assert_eq!(package_version(&shim), None); + } + } + + #[test] + fn rejects_commented_npm_shim_references() { + let source = format!( + "{}\nexec unrelated-pi\n", + NPM_SHELL_SHIM + .lines() + .map(|line| format!("# {line}\n")) + .collect::() + ); + let (_root, shim) = npm_shim_fixture("pi", &source); + assert!(!is_pi(&shim)); + } + + #[test] + fn rejects_oversized_npm_shims() { + let source = format!("{NPM_CMD_SHIM}{}", " ".repeat(16 * 1024)); + let (_root, shim) = npm_shim_fixture("pi.cmd", &source); + assert!(!is_pi(&shim)); + } + #[test] fn reads_adapter_servers_without_secrets_or_arguments() { let servers = mcp_servers_from_value( @@ -225,11 +424,9 @@ mod tests { #[test] fn scans_pi_owned_and_shared_mcp_files() { let paths = mcp_config_paths(); - assert!( - paths - .iter() - .any(|path| path.ends_with(std::path::Path::new(".pi/agent/mcp.json"))) - ); + for home in crate::provider::metadata::user_home_dirs() { + assert!(paths.contains(&user_pi_agent_dir(&home).join("mcp.json"))); + } assert!( paths .iter() @@ -243,6 +440,78 @@ mod tests { ); } + #[test] + fn pi_agent_dir_overrides_match_all_consumers() { + const CHILD_HOME: &str = "AGENTDESKTOP_PI_PATH_TEST_HOME"; + const CHILD_EXPECTED: &str = "AGENTDESKTOP_PI_PATH_TEST_EXPECTED"; + if let Some(home) = std::env::var_os(CHILD_HOME) { + let home = PathBuf::from(home); + let expected = PathBuf::from(std::env::var_os(CHILD_EXPECTED).unwrap()); + assert_eq!(user_pi_agent_dir(&home), expected); + assert!(mcp_config_paths().contains(&expected.join("mcp.json"))); + assert!(skill_roots().contains(&expected.join("skills"))); + return; + } + + // Isolate environment overrides in a subprocess so parallel tests never + // observe changes to HOME, USERPROFILE, or PI_CODING_AGENT_DIR. + let home = tempfile::tempdir().unwrap(); + let absolute = home.path().join("absolute-agent"); + let cases = [ + ( + Some(Path::new("~/.pi/custom-agent")), + home.path().join(".pi/custom-agent"), + ), + (Some(Path::new("~")), home.path().to_owned()), + ( + Some(Path::new("relative-agent")), + PathBuf::from("relative-agent"), + ), + (Some(absolute.as_path()), absolute.clone()), + (Some(Path::new("")), home.path().join(".pi/agent")), + (None, home.path().join(".pi/agent")), + ( + Some(Path::new("~another-user/agent")), + PathBuf::from("~another-user/agent"), + ), + #[cfg(windows)] + ( + Some(Path::new(r"~\.pi\custom-agent")), + home.path().join(".pi/custom-agent"), + ), + #[cfg(unix)] + ( + Some(Path::new(r"~\.pi\custom-agent")), + PathBuf::from(r"~\.pi\custom-agent"), + ), + ]; + for (configured, expected) in cases { + let mut command = std::process::Command::new(std::env::current_exe().unwrap()); + command + .args([ + "--exact", + "provider::pi::discovery::tests::pi_agent_dir_overrides_match_all_consumers", + "--nocapture", + ]) + .env(CHILD_HOME, home.path()) + .env(CHILD_EXPECTED, expected) + .env("HOME", home.path()) + .env("USERPROFILE", home.path()); + if let Some(configured) = configured { + command.env("PI_CODING_AGENT_DIR", configured); + } else { + command.env_remove("PI_CODING_AGENT_DIR"); + } + let output = command.output().unwrap(); + assert!( + output.status.success(), + "PI_CODING_AGENT_DIR={configured:?}:\n{}\n{}", + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr), + ); + } + } + fn temporary(name: &str) -> PathBuf { std::env::temp_dir().join(format!("agentdesktop-{name}-{}", std::process::id())) } diff --git a/crates/agent/src/provider/pi/mod.rs b/crates/agent/src/provider/pi/mod.rs index 74686560..0909e41a 100644 --- a/crates/agent/src/provider/pi/mod.rs +++ b/crates/agent/src/provider/pi/mod.rs @@ -40,6 +40,11 @@ impl Provider for Pi { } fn plan(&self, ctx: &ReconcileContext, config: &DaemonConfig) -> anyhow::Result { + if !ctx.merge_user_settings && config.programs.pi.is_some() { + anyhow::bail!( + "Pi only reads user configuration; run agentdesktop with --user to manage programs.pi, or remove programs.pi from the system configuration" + ); + } let configured = config.programs.pi.as_ref().map(|provider| { let gateway = config .llm_gateway @@ -73,7 +78,15 @@ pub fn default_pi_settings_path() -> PathBuf { pub fn user_pi_agent_dir(home: &std::path::Path) -> PathBuf { std::env::var_os("PI_CODING_AGENT_DIR") .filter(|value| !value.is_empty()) - .map(PathBuf::from) + .map(|value| { + let path = PathBuf::from(value); + // Path components handle ~/ on every platform and ~\ on Windows, + // matching Pi's platform-specific tilde expansion. + match path.strip_prefix("~") { + Ok(relative) => home.join(relative), + Err(_) => path, + } + }) .unwrap_or_else(|| home.join(".pi/agent")) } @@ -111,7 +124,56 @@ fn windows_system_pi_agent_dir(system_drive: Option) -> Path #[cfg(test)] mod tests { - use super::windows_system_pi_agent_dir; + use agentdesktop_core::config::{DaemonConfig, parse_daemon}; + + use super::{Pi, Provider, ReconcileContext, windows_system_pi_agent_dir}; + + #[test] + fn rejects_configured_pi_in_system_mode() { + let root = tempfile::tempdir().unwrap(); + let pi = Pi { + models_path: root.path().join("models.json"), + settings_path: root.path().join("settings.json"), + }; + let ctx = ReconcileContext { + merge_user_settings: false, + credential_helper: root.path().join("agentdesktop"), + socket: root.path().join("agentdesktop.sock"), + }; + let config = + parse_daemon("programs:\n pi:\n managedConfig:\n providers: {}\n").unwrap(); + + let error = pi.plan(&ctx, &config).err().expect("Pi needs user mode"); + assert!(error.to_string().contains("--user")); + assert!(error.to_string().contains("programs.pi")); + assert!(!pi.models_path.exists()); + assert!(!pi.settings_path.exists()); + } + + #[test] + fn allows_absent_pi_cleanup_in_system_mode() { + let root = tempfile::tempdir().unwrap(); + let pi = Pi { + models_path: root.path().join("models.json"), + settings_path: root.path().join("settings.json"), + }; + let mut ctx = ReconcileContext { + merge_user_settings: true, + credential_helper: root.path().join("agentdesktop"), + socket: root.path().join("agentdesktop.sock"), + }; + let config = + parse_daemon("programs:\n pi:\n managedConfig:\n providers: {}\n").unwrap(); + pi.plan(&ctx, &config).unwrap().apply().unwrap(); + assert!(pi.models_path.exists()); + + ctx.merge_user_settings = false; + pi.plan(&ctx, &DaemonConfig::default()) + .unwrap() + .apply() + .unwrap(); + assert!(!pi.models_path.exists()); + } #[test] fn windows_system_pi_agent_dir_uses_system_drive_root() { diff --git a/crates/agent/src/provider/pi/reconcile.rs b/crates/agent/src/provider/pi/reconcile.rs index ad7141ac..f7aeea69 100644 --- a/crates/agent/src/provider/pi/reconcile.rs +++ b/crates/agent/src/provider/pi/reconcile.rs @@ -26,10 +26,18 @@ pub(super) fn plan( let models_state = json_merge::state_path(models_path); let settings_state = json_merge::state_path(settings_path); let Some((config, gateway)) = config else { - json_merge::plan_remove(models_path, &models_state, "models", Pi::DISPLAY_NAME, plan)?; + json_merge::plan_remove( + models_path, + &models_state, + json_merge::JsonFormat::Jsonc, + "models", + Pi::DISPLAY_NAME, + plan, + )?; json_merge::plan_remove( settings_path, &settings_state, + json_merge::JsonFormat::Json, "settings", Pi::DISPLAY_NAME, plan, @@ -40,6 +48,7 @@ pub(super) fn plan( json_merge::plan_merge( models_path, &models_state, + json_merge::JsonFormat::Jsonc, managed_models(config, gateway, credential_helper, socket)?, false, "models", @@ -49,6 +58,7 @@ pub(super) fn plan( json_merge::plan_merge( settings_path, &settings_state, + json_merge::JsonFormat::Json, managed_settings(config, gateway)?, false, "settings", @@ -105,9 +115,10 @@ fn managed_models( let mut models = Vec::new(); for (id, mut entry) in catalog_entries(config, default_model) { - if entry.get("id").and_then(Value::as_str).is_none() { - entry.insert("id".to_owned(), json!(id)); - } + entry.insert("id".to_owned(), json!(id)); + let model_api = entry.get("api").and_then(Value::as_str).unwrap_or(api); + let base_url = gateway_base_url(gateway, model_api); + entry.insert("baseUrl".to_owned(), json!(base_url)); models.push(Value::Object(entry)); } @@ -170,11 +181,196 @@ fn gateway_base_url(gateway: &LlmGatewayConfig, api: &str) -> String { #[cfg(test)] mod tests { + use std::fs; use std::path::Path; use agentdesktop_core::config::parse_daemon; + use serde_json::{Value, json}; + + use super::{managed_models, managed_settings, plan}; + use crate::reconcile::ReconcilePlan; + + #[test] + fn gateway_overrides_per_model_endpoints() { + let config = parse_daemon( + r#" +llmGateway: + url: https://gateway.example.com/proxy +programs: + pi: + model: claude + models: + claude: + baseUrl: https://outside.example.com + chat: + api: openai-completions + baseUrl: https://outside.example.com/v1 + responses: + api: openai-responses +"#, + ) + .unwrap(); + let managed = managed_models( + config.programs.pi.as_ref().unwrap(), + config.llm_gateway.as_ref(), + Path::new("agentdesktop"), + Path::new("agentdesktop.sock"), + ) + .unwrap(); + for model in managed["providers"]["agentdesktop"]["models"] + .as_array() + .unwrap() + { + let expected = if model["id"] == "claude" { + "https://gateway.example.com/proxy" + } else { + "https://gateway.example.com/proxy/v1" + }; + assert_eq!(model["baseUrl"], expected, "model {}", model["id"]); + } + } - use super::{managed_models, managed_settings}; + #[test] + fn model_map_keys_define_catalog_ids() { + let config = parse_daemon( + r#" +llmGateway: + url: https://gateway.example.com +programs: + pi: + model: selected + models: + selected: + id: different + name: Selected Model +"#, + ) + .unwrap(); + let pi = config.programs.pi.as_ref().unwrap(); + let gateway = config.llm_gateway.as_ref(); + let managed = managed_models( + pi, + gateway, + Path::new("agentdesktop"), + Path::new("agentdesktop.sock"), + ) + .unwrap(); + let settings = managed_settings(pi, gateway).unwrap(); + let model = &managed["providers"]["agentdesktop"]["models"][0]; + assert_eq!(model["id"], settings["defaultModel"]); + assert_eq!(model["name"], "Selected Model"); + } + + #[test] + fn commented_models_survive_merge_repeat_and_removal() { + let root = tempfile::tempdir().unwrap(); + let models = root.path().join("models.json"); + let settings = root.path().join("settings.json"); + fs::write( + &models, + "\u{feff}{\n // Personal provider\n \"providers\": {\"local\": {\"baseUrl\": \"http://localhost:11434/v1\",},},\n}\n", + ) + .unwrap(); + fs::write(&settings, r#"{"theme":"dark"}"#).unwrap(); + let config = parse_daemon( + "llmGateway:\n url: https://gateway.example.com\nprograms:\n pi:\n model: selected\n", + ) + .unwrap(); + let configured = Some(( + config.programs.pi.as_ref().unwrap(), + config.llm_gateway.as_ref(), + )); + let helper = root.path().join("agentdesktop"); + let socket = root.path().join("agentdesktop.sock"); + let changes = ReconcilePlan::default(); + plan(&models, &settings, &helper, &socket, configured, &changes).unwrap(); + assert!(!changes.has_conflicts(), "Pi accepts commented models.json"); + changes.apply().unwrap(); + let merged: Value = serde_json::from_slice(&fs::read(&models).unwrap()).unwrap(); + assert_eq!( + merged["providers"]["local"]["baseUrl"], + "http://localhost:11434/v1" + ); + assert_eq!( + merged["providers"]["agentdesktop"]["models"][0]["id"], + "selected" + ); + + let before = fs::read(&models).unwrap(); + let repeated = ReconcilePlan::default(); + plan(&models, &settings, &helper, &socket, configured, &repeated).unwrap(); + assert!(repeated.render().contains("Summary: 0 changes")); + repeated.apply().unwrap(); + assert_eq!(fs::read(&models).unwrap(), before); + + // A user can add comments while agentdesktop manages the file. + fs::write( + &models, + format!("// Updated comment\n{}", String::from_utf8(before).unwrap()), + ) + .unwrap(); + let removal = ReconcilePlan::default(); + plan(&models, &settings, &helper, &socket, None, &removal).unwrap(); + removal.apply().unwrap(); + let restored: Value = serde_json::from_slice(&fs::read(&models).unwrap()).unwrap(); + assert_eq!( + restored, + json!({"providers":{"local":{"baseUrl":"http://localhost:11434/v1"}}}) + ); + let restored_settings: Value = + serde_json::from_slice(&fs::read(&settings).unwrap()).unwrap(); + assert_eq!(restored_settings, json!({"theme":"dark"})); + assert!(!super::json_merge::state_path(&models).exists()); + assert!(!super::json_merge::state_path(&settings).exists()); + } + + #[test] + fn commented_settings_remain_a_conflict() { + let root = tempfile::tempdir().unwrap(); + let models = root.path().join("models.json"); + let settings = root.path().join("settings.json"); + let existing = b"// Pi settings require strict JSON\n{\"theme\":\"dark\"}"; + fs::write(&settings, existing).unwrap(); + let config = parse_daemon("programs:\n pi: {}\n").unwrap(); + let changes = ReconcilePlan::default(); + plan( + &models, + &settings, + Path::new("agentdesktop"), + Path::new("agentdesktop.sock"), + Some((config.programs.pi.as_ref().unwrap(), None)), + &changes, + ) + .unwrap(); + assert!(changes.has_conflicts()); + assert!(changes.apply().is_err()); + assert_eq!(fs::read(&settings).unwrap(), existing); + assert!(!models.exists()); + } + + #[test] + fn malformed_models_prevent_settings_writes() { + let root = tempfile::tempdir().unwrap(); + let models = root.path().join("models.json"); + let settings = root.path().join("settings.json"); + let invalid = b"{\"providers\":"; + fs::write(&models, invalid).unwrap(); + let config = parse_daemon("programs:\n pi: {}\n").unwrap(); + let changes = ReconcilePlan::default(); + plan( + &models, + &settings, + Path::new("agentdesktop"), + Path::new("agentdesktop.sock"), + Some((config.programs.pi.as_ref().unwrap(), None)), + &changes, + ) + .unwrap(); + assert!(changes.has_conflicts()); + assert!(changes.apply().is_err()); + assert_eq!(fs::read(&models).unwrap(), invalid); + assert!(!settings.exists()); + } #[test] fn pass_through_settings_are_merged_with_managed_gateway_values() { diff --git a/crates/agent/src/reconcile/mod.rs b/crates/agent/src/reconcile/mod.rs index 0205eba2..09e355ce 100644 --- a/crates/agent/src/reconcile/mod.rs +++ b/crates/agent/src/reconcile/mod.rs @@ -333,6 +333,21 @@ programs: assert!(!root.exists(), "preflight failure must not write any files"); } + #[test] + fn system_mode_rejects_pi_before_writing_other_settings() { + let fixture = Fixture::new(); + let config = parse_daemon("programs:\n claudeCode: {}\n pi: {}\n").unwrap(); + let error = fixture + .reconciler + .apply(&config) + .expect_err("Pi requires user mode"); + assert!(error.to_string().contains("--user")); + assert!( + !fixture.root.exists(), + "preflight failure must not write any files" + ); + } + #[test] fn user_mode_reconciles_pi_models_and_settings() { let root = std::env::temp_dir().join(format!( @@ -373,6 +388,30 @@ programs: assert!(models.contains("claude-sonnet-4-5")); let settings = String::from_utf8(fs::read(root.join("pi/settings.json")).unwrap()).unwrap(); assert!(settings.contains("defaultProvider")); + + let repeated = reconciler.plan(&config).unwrap(); + assert!(repeated.render().contains("Summary: 0 changes")); + repeated.apply().unwrap(); + assert_eq!( + fs::read_to_string(root.join("pi/models.json")).unwrap(), + models + ); + assert_eq!( + fs::read_to_string(root.join("pi/settings.json")).unwrap(), + settings + ); + + reconciler + .apply(&parse_daemon("programs: {}").unwrap()) + .unwrap(); + for path in [ + "models.json", + ".models.json.agentdesktop", + "settings.json", + ".settings.json.agentdesktop", + ] { + assert!(!root.join("pi").join(path).exists()); + } fs::remove_dir_all(root).unwrap(); } @@ -501,8 +540,6 @@ programs: company-model: {} grok: model: grok-4.6 - pi: - model: claude-sonnet-4-5 "#, ) .unwrap(); @@ -525,10 +562,6 @@ programs: "opencode/config.json", "opencode/plugin.js", "grok/managed_config.toml", - "pi/models.json", - "pi/.models.json.agentdesktop", - "pi/settings.json", - "pi/.settings.json.agentdesktop", ]; let contents: Vec<_> = paths .iter() diff --git a/crates/core/src/config.rs b/crates/core/src/config.rs index e99ba56c..ab7d350f 100644 --- a/crates/core/src/config.rs +++ b/crates/core/src/config.rs @@ -640,7 +640,7 @@ pub struct GrokConfig { /// /// Generated `providers.agentdesktop` values take precedence over overlapping /// `managedConfig` keys. Pi stores configuration under `~/.pi/agent` (or -/// `PI_CODING_AGENT_DIR`), so `--user` is the supported management mode. +/// `PI_CODING_AGENT_DIR`), so management requires `--user`. #[derive(Clone, Debug, Deserialize, Serialize)] #[cfg_attr(feature = "schema", derive(schemars::JsonSchema))] #[serde(rename_all = "camelCase", deny_unknown_fields)] @@ -656,9 +656,10 @@ pub struct PiConfig { pub model: Option, /// Extra Pi model objects, keyed by model ID. /// - /// Each value is an arbitrary Pi `models.json` model object. Generated - /// gateway `baseUrl` and `apiKey` values take precedence. When this map is - /// non-empty, `model` must name one of its keys. + /// Each value is an arbitrary Pi `models.json` model object. The map key + /// sets its `id`. Generated gateway `baseUrl` and `apiKey` values take + /// precedence, including per-model URLs. When this map is non-empty, + /// `model` must name one of its keys. #[serde(default, skip_serializing_if = "BTreeMap::is_empty")] pub models: BTreeMap, /// Pi API dialect for the managed provider. Defaults to `anthropic-messages`. diff --git a/examples/claude/README.md b/examples/claude/README.md index cbc03bfb..97dcdc9b 100644 --- a/examples/claude/README.md +++ b/examples/claude/README.md @@ -70,6 +70,19 @@ agentdesktop Now that agentdesktop is running, Claude Code can be run. It will show the configured `Managed by Agentdesktop` and direct traffic through the gateway. +## Pi + +Pi requires user-mode configuration. To include Pi, remove `programs.claudeDesktop` +from `claude-code.yaml` and uncomment `programs.pi`. Run the daemon as the user +who runs Pi: + +```console +agentdesktop daemon --user --config examples/claude/agentdesktop.yaml +``` + +After enrollment, run `pi`; the managed default model uses the gateway. Keep the +daemon running so Pi can request gateway credentials. + ## Stop the scenario Stop the foreground daemon and controller with Ctrl-C, then stop Dex and diff --git a/examples/claude/claude-code.yaml b/examples/claude/claude-code.yaml index b37a4a26..d5e9a37c 100644 --- a/examples/claude/claude-code.yaml +++ b/examples/claude/claude-code.yaml @@ -16,6 +16,7 @@ programs: companyAnnouncements: ["Managed by Agentdesktop"] claudeDesktop: isLocalDevMcpEnabled: true - pi: - useLlmGateway: true - model: claude-sonnet-4-5 + # Pi requires --user. Remove claudeDesktop before enabling Pi in user mode. + # pi: + # useLlmGateway: true + # model: claude-sonnet-4-5 diff --git a/examples/standalone/README.md b/examples/standalone/README.md index 6198fdcd..120162e4 100644 --- a/examples/standalone/README.md +++ b/examples/standalone/README.md @@ -62,13 +62,20 @@ $ agentdesktop daemon --config examples/standalone/config.yaml --user When we run `claude`, we can now see the message we programmed (`Using the local Agentgateway through Agentdesktop`), and messages we send will traverse Agentgateway. +## Pi + +The user-mode configuration also enables Pi with `claude-sonnet-4-5`. Run `pi` +while the daemon is running to use that model through the gateway. Pi requires +`--user`; authenticated gateway configuration cannot be applied with `--once`. + ## Claude Desktop In the Claude Code example, we ran with `--user`. This runs the daemon as an unprivileged user. Claude Desktop, however, requires root configuration. -Uncomment the Claude Desktop configuration in `config.yaml` and save the file. +Remove `programs.pi` and uncomment the Claude Desktop configuration in +`config.yaml`, then save the file. Then run the daemon as root: ```sh diff --git a/examples/standalone/config.yaml b/examples/standalone/config.yaml index 351ffa1f..2a9316b6 100644 --- a/examples/standalone/config.yaml +++ b/examples/standalone/config.yaml @@ -15,6 +15,6 @@ programs: pi: useLlmGateway: true model: claude-sonnet-4-5 -# Claude desktop cannot run with --user. Uncomment to enable +# Claude Desktop requires system mode. Remove pi above before enabling it. # claudeDesktop: # useLlmGateway: true diff --git a/schema/daemon-config.json b/schema/daemon-config.json index bcb49067..3756ca34 100644 --- a/schema/daemon-config.json +++ b/schema/daemon-config.json @@ -418,7 +418,7 @@ "additionalProperties": false }, "PiConfig": { - "description": "Settings reconciled into Pi's user `models.json` and `settings.json`.\n\nGenerated `providers.agentdesktop` values take precedence over overlapping\n`managedConfig` keys. Pi stores configuration under `~/.pi/agent` (or\n`PI_CODING_AGENT_DIR`), so `--user` is the supported management mode.", + "description": "Settings reconciled into Pi's user `models.json` and `settings.json`.\n\nGenerated `providers.agentdesktop` values take precedence over overlapping\n`managedConfig` keys. Pi stores configuration under `~/.pi/agent` (or\n`PI_CODING_AGENT_DIR`), so management requires `--user`.", "type": "object", "properties": { "api": { @@ -441,7 +441,7 @@ ] }, "models": { - "description": "Extra Pi model objects, keyed by model ID.\n\nEach value is an arbitrary Pi `models.json` model object. Generated\ngateway `baseUrl` and `apiKey` values take precedence. When this map is\nnon-empty, `model` must name one of its keys.", + "description": "Extra Pi model objects, keyed by model ID.\n\nEach value is an arbitrary Pi `models.json` model object. The map key\nsets its `id`. Generated gateway `baseUrl` and `apiKey` values take\nprecedence, including per-model URLs. When this map is non-empty,\n`model` must name one of its keys.", "type": "object", "additionalProperties": true }, diff --git a/schema/daemon-config.md b/schema/daemon-config.md index a0baf07c..20a699c4 100644 --- a/schema/daemon-config.md +++ b/schema/daemon-config.md @@ -63,7 +63,7 @@ |`programs.pi.api`|string|Pi API dialect for the managed provider. Defaults to `anthropic-messages`.| |`programs.pi.managedConfig`|object|Arbitrary values merged into Pi's `models.json`.| |`programs.pi.model`|string|Model ID selected when pointing Pi at the LLM gateway.

This is required when a top-level `llmGateway` is configured. If `models`
is empty, agentdesktop creates a catalog entry with this ID.| -|`programs.pi.models`|object|Extra Pi model objects, keyed by model ID.

Each value is an arbitrary Pi `models.json` model object. Generated
gateway `baseUrl` and `apiKey` values take precedence. When this map is
non-empty, `model` must name one of its keys.| +|`programs.pi.models`|object|Extra Pi model objects, keyed by model ID.

Each value is an arbitrary Pi `models.json` model object. The map key
sets its `id`. Generated gateway `baseUrl` and `apiKey` values take
precedence, including per-model URLs. When this map is non-empty,
`model` must name one of its keys.| |`programs.pi.useLlmGateway`|boolean|Whether this program uses the top-level LLM gateway.| |`sandbox`|object|Local execution sandbox required for managed developer tools.| |`sandbox.filesystem`|object|Filesystem access available to sandboxed commands.| From a248ec50a3694b447f659d2f3fb3eadc05edc321 Mon Sep 17 00:00:00 2001 From: Sebastian Maniak Date: Fri, 18 Sep 2026 22:57:04 -0400 Subject: [PATCH 08/11] fix: discover Pi PowerShell shims on Windows --- crates/agent/src/provider/pi/discovery.rs | 105 +++++++++++++++++++--- 1 file changed, 93 insertions(+), 12 deletions(-) diff --git a/crates/agent/src/provider/pi/discovery.rs b/crates/agent/src/provider/pi/discovery.rs index 9c500010..92a10c98 100644 --- a/crates/agent/src/provider/pi/discovery.rs +++ b/crates/agent/src/provider/pi/discovery.rs @@ -33,13 +33,6 @@ fn executable_candidates() -> Vec { for home in metadata::user_home_dirs() { candidates.insert(home.join(".local/bin/pi")); candidates.insert(home.join(".npm-global/bin/pi")); - #[cfg(windows)] - { - candidates.insert(home.join(".local/bin/pi.exe")); - candidates.insert(home.join("AppData/Roaming/npm/pi.cmd")); - candidates.insert(home.join("AppData/Roaming/npm/pi.exe")); - candidates.insert(home.join("AppData/Local/pnpm/pi.exe")); - } } #[cfg(target_os = "macos")] candidates.extend([ @@ -51,9 +44,32 @@ fn executable_candidates() -> Vec { PathBuf::from("/usr/bin/pi"), PathBuf::from("/usr/local/bin/pi"), ]); + #[cfg(windows)] + { + windows_executable_candidates(candidates) + } + #[cfg(not(windows))] candidates.into_iter().collect() } +#[cfg(any(windows, test))] +fn windows_executable_candidates(mut candidates: BTreeSet) -> Vec { + for home in metadata::user_home_dirs() { + candidates.extend([ + home.join(".local/bin/pi.exe"), + home.join("AppData/Roaming/npm/pi.cmd"), + home.join("AppData/Roaming/npm/pi.exe"), + home.join("AppData/Roaming/npm/pi.ps1"), + home.join("AppData/Local/pnpm/pi.exe"), + ]); + } + // PowerShell shims may be absent from PATHEXT; keep PATH order before fallbacks. + metadata::find_all_in_path("pi.ps1") + .into_iter() + .chain(candidates) + .collect() +} + fn is_pi(executable: &Path) -> bool { package_manifest(executable).is_some() } @@ -291,9 +307,12 @@ fi fn npm_shim_fixture(name: &str, source: &str) -> (tempfile::TempDir, PathBuf) { let root = tempfile::tempdir().unwrap(); - let package = root - .path() - .join("node_modules/@earendil-works/pi-coding-agent"); + let shim = write_npm_shim(root.path(), name, source); + (root, shim) + } + + fn write_npm_shim(prefix: &Path, name: &str, source: &str) -> PathBuf { + let package = prefix.join("node_modules/@earendil-works/pi-coding-agent"); fs::create_dir_all(package.join("dist/bundle")).unwrap(); fs::write( package.join("package.json"), @@ -301,9 +320,71 @@ fi ) .unwrap(); fs::write(package.join("dist/bundle/cli.js"), "#!/usr/bin/env node\n").unwrap(); - let shim = root.path().join(name); + let shim = prefix.join(name); fs::write(&shim, source).unwrap(); - (root, shim) + shim + } + + #[test] + fn discovers_powershell_only_npm_installs_without_pathext() { + const EXPECTED_SHIM: &str = "AGENTDESKTOP_PI_PS1_TEST_SHIM"; + if let Some(expected) = std::env::var_os(EXPECTED_SHIM) { + #[cfg(windows)] + let executable = super::discover() + .expect("Pi should be discovered") + .executable; + // Exercise Windows candidate discovery on other platforms too. + #[cfg(not(windows))] + let executable = super::windows_executable_candidates(Default::default()) + .into_iter() + .filter(|path| path.is_file()) + .find(|path| is_pi(path)) + .expect("PowerShell-only Pi should be discovered"); + assert_eq!(executable, PathBuf::from(expected)); + return; + } + + for use_path in [false, true] { + let home = tempfile::tempdir().unwrap(); + let prefix = home.path().join(if use_path { + "z-custom-npm-prefix" + } else { + "AppData/Roaming/npm" + }); + let shim = write_npm_shim(&prefix, "pi.ps1", NPM_POWERSHELL_SHIM); + let path = if use_path { + // The first PATH match wins over later entries and fallback locations. + let later_prefix = home.path().join("a-older-npm-prefix"); + write_npm_shim(&later_prefix, "pi.ps1", NPM_POWERSHELL_SHIM); + write_npm_shim( + &home.path().join("AppData/Roaming/npm"), + "pi.ps1", + NPM_POWERSHELL_SHIM, + ); + std::env::join_paths([&prefix, &later_prefix]).unwrap() + } else { + Default::default() + }; + let output = std::process::Command::new(std::env::current_exe().unwrap()) + .args([ + "--exact", + "provider::pi::discovery::tests::discovers_powershell_only_npm_installs_without_pathext", + "--nocapture", + ]) + .env(EXPECTED_SHIM, &shim) + .env("HOME", home.path()) + .env("USERPROFILE", home.path()) + .env("PATH", path) + .env("PATHEXT", ".COM;.EXE;.BAT;.CMD") + .output() + .unwrap(); + assert!( + output.status.success(), + "Pi with only pi.ps1 (on PATH: {use_path}):\n{}\n{}", + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr), + ); + } } #[test] From effafbddf7b8e10fe5b7b9b40f9de911862a409b Mon Sep 17 00:00:00 2001 From: Sebastian Maniak Date: Sat, 19 Sep 2026 08:00:24 -0400 Subject: [PATCH 09/11] fix: protect Pi credentials and replace managed model catalogs --- crates/agent/src/provider/README.md | 9 +- .../src/provider/claude_code/reconcile.rs | 4 +- .../src/provider/claude_desktop/reconcile.rs | 2 +- crates/agent/src/provider/json_merge.rs | 94 +++++- crates/agent/src/provider/pi/reconcile.rs | 289 +++++++++++++++++- 5 files changed, 382 insertions(+), 16 deletions(-) diff --git a/crates/agent/src/provider/README.md b/crates/agent/src/provider/README.md index a432cecb..89c1b193 100644 --- a/crates/agent/src/provider/README.md +++ b/crates/agent/src/provider/README.md @@ -15,13 +15,20 @@ gateway configuration also requires a running daemon and rejects `--once`. User mode merges `providers.agentdesktop` into `models.json` and sets the default model in `settings.json`. Model map keys determine catalog IDs, and gateway URLs override per-model endpoints, using each model's API dialect. +The gateway owns the entire `providers.agentdesktop.models` catalog while +enabled; stale entries are replaced, and the prior catalog is restored on +cleanup. Other providers and user additions are preserved. On Unix, +`models.json` is written with owner-only permissions (`0600`), including +cleanup, and reconciliation repairs permissions on existing managed files. Commented `models.json` files are accepted and rewritten as standard JSON, preserving user configuration values. `settings.json` requires standard JSON. Discovery recognizes npm's Windows command shims as well as symlinked launchers, verifying the Pi package manifest without executing the launcher. MCP inventory comes from `pi-mcp-adapter` config files (`~/.pi/agent/mcp.json`, `.pi/mcp.json`, `.mcp.json`, and shared -`~/.config/mcp/mcp.json` / `~/.agents/mcp.json`). Host-specific Cursor/Claude +`~/.config/mcp/mcp.json` / `~/.agents/mcp.json` / `~/.agents/mcp/mcp.json`). +Both `.agents` paths are supported by the [adapter's configuration loader](https://github.com/nicobailon/pi-mcp-adapter/blob/33bdc38d8dd3802f2b51ba1fe37e30ed7ae6a29a/config.ts#L15-L19). +Host-specific Cursor/Claude files are not scanned until the adapter imports them into a Pi-owned file. ✅ Implemented · ◯ Not implemented · — Not applicable diff --git a/crates/agent/src/provider/claude_code/reconcile.rs b/crates/agent/src/provider/claude_code/reconcile.rs index 19b92f8e..6935e6ce 100644 --- a/crates/agent/src/provider/claude_code/reconcile.rs +++ b/crates/agent/src/provider/claude_code/reconcile.rs @@ -52,7 +52,7 @@ pub(super) fn plan( && json_merge::plan_remove( path, &merge_state_path, - json_merge::JsonFormat::Json, + json_merge::MergeOptions::default(), "settings", ClaudeCode::DISPLAY_NAME, plan, @@ -69,7 +69,7 @@ pub(super) fn plan( json_merge::plan_merge( path, &merge_state_path, - json_merge::JsonFormat::Json, + json_merge::MergeOptions::default(), settings, is_owned(&owner_path, plan)?, "settings", diff --git a/crates/agent/src/provider/claude_desktop/reconcile.rs b/crates/agent/src/provider/claude_desktop/reconcile.rs index 41788ed4..3d7ce9cd 100644 --- a/crates/agent/src/provider/claude_desktop/reconcile.rs +++ b/crates/agent/src/provider/claude_desktop/reconcile.rs @@ -32,7 +32,7 @@ pub(super) fn plan( || !json_merge::plan_remove( settings_path, &settings_state, - json_merge::JsonFormat::Json, + json_merge::MergeOptions::default(), "managed settings", ClaudeDesktop::DISPLAY_NAME, plan, diff --git a/crates/agent/src/provider/json_merge.rs b/crates/agent/src/provider/json_merge.rs index 38ecd09c..8ffa4a96 100644 --- a/crates/agent/src/provider/json_merge.rs +++ b/crates/agent/src/provider/json_merge.rs @@ -22,6 +22,23 @@ pub(super) enum JsonFormat { Jsonc, } +pub(super) struct MergeOptions { + pub format: JsonFormat, + pub permissions: u32, + /// JSON pointers to values managed as a whole, rather than merged additively. + pub replace_paths: &'static [&'static str], +} + +impl Default for MergeOptions { + fn default() -> Self { + Self { + format: JsonFormat::Json, + permissions: 0o644, + replace_paths: &[], + } + } +} + impl JsonFormat { fn parse(&self, contents: &[u8]) -> anyhow::Result { match self { @@ -47,7 +64,7 @@ pub(super) fn state_path(path: &Path) -> PathBuf { pub(super) fn plan_merge( path: &Path, state_path: &Path, - format: JsonFormat, + options: MergeOptions, managed: Value, legacy_owned: bool, description: &str, @@ -69,7 +86,7 @@ pub(super) fn plan_merge( .unwrap_or(existing.is_none() || legacy_owned); let mut combined = match existing.as_deref() { - Some(contents) => match format.parse(contents) { + Some(contents) => match options.format.parse(contents) { Ok(Value::Object(object)) => Value::Object(object), Ok(_) | Err(_) => { plan.record(display_name, description, "conflict", path); @@ -85,13 +102,25 @@ pub(super) fn plan_merge( combined = json!({}); } let before = combined.clone(); + // Snapshot the user's original values before replacing any owned subtree. + for pointer in options.replace_paths { + if managed.pointer(pointer).is_some() + && let Some(existing) = combined.pointer_mut(pointer) + { + *existing = Value::Null; + } + } merge_overlay(&mut combined, managed); let mut contents = serde_json::to_vec_pretty(&combined) .with_context(|| format!("serialize merged {display_name}"))?; contents.push(b'\n'); let action = match existing.as_deref() { - Some(existing) if existing == contents => "unchanged", + Some(existing) + if existing == contents && !needs_permission_update(path, options.permissions)? => + { + "unchanged" + } Some(_) => "update", None => "create", }; @@ -105,7 +134,7 @@ pub(super) fn plan_merge( ); if action != "unchanged" { - plan.write_file(path, &contents, 0o644)?; + plan.write_file(path, &contents, options.permissions)?; } let mut state = serde_json::to_vec_pretty(&MergeState { created, @@ -122,7 +151,7 @@ pub(super) fn plan_merge( pub(super) fn plan_remove( path: &Path, state_path: &Path, - format: JsonFormat, + options: MergeOptions, description: &str, display_name: &str, plan: &ReconcilePlan, @@ -142,7 +171,7 @@ pub(super) fn plan_remove( .with_context(|| format!("read {display_name} from {}", path.display())); } }; - let settings = match format.parse(&existing) { + let settings = match options.format.parse(&existing) { Ok(Value::Object(object)) => Value::Object(object), Ok(_) | Err(_) => { plan.record(display_name, description, "conflict", path); @@ -161,7 +190,11 @@ pub(super) fn plan_remove( }; let action = match proposed.as_deref() { None => "remove", - Some(contents) if contents == existing => "unchanged", + Some(contents) + if contents == existing && !needs_permission_update(path, options.permissions)? => + { + "unchanged" + } Some(_) => "update", }; plan.record_diff( @@ -178,13 +211,31 @@ pub(super) fn plan_remove( } else if let Some(contents) = proposed && action == "update" { - plan.write_file(path, &contents, 0o644)?; + plan.write_file(path, &contents, options.permissions)?; } remove_file(state_path, display_name, plan)?; debug!(provider = display_name, action, path = %path.display(), "planned removal of managed values from user settings"); Ok(true) } +fn needs_permission_update(path: &Path, permissions: u32) -> anyhow::Result { + #[cfg(unix)] + { + use std::os::unix::fs::PermissionsExt; + + let existing = std::fs::metadata(path) + .with_context(|| format!("inspect permissions at {}", path.display()))? + .permissions() + .mode(); + Ok(existing & 0o777 & !permissions != 0) + } + #[cfg(not(unix))] + { + let _ = (path, permissions); + Ok(false) + } +} + fn read_state( path: &Path, display_name: &str, @@ -292,6 +343,33 @@ fn rollback_value( let after = after .and_then(Value::as_array) .map_or(&[][..], Vec::as_slice); + // Additive merges keep the original array as a prefix. A replacement + // must restore its order and duplicate entries, then retain user edits. + if !after.starts_with(before) { + let mut result = before.to_vec(); + let mut restored = before.to_vec(); + let mut added = current_array.clone(); + for value in after { + if let Some(index) = restored.iter().position(|entry| entry == value) { + restored.remove(index); + } + if let Some(index) = added.iter().position(|entry| entry == value) { + added.remove(index); + } else if let Some(index) = result.iter().position(|entry| entry == value) { + // Respect a user's removal of a retained original entry. + result.remove(index); + } + } + for value in added { + if let Some(index) = restored.iter().position(|entry| entry == &value) { + // An original entry restored by the user is already present. + restored.remove(index); + } else { + result.push(value); + } + } + return Some(Value::Array(result)); + } let mut result = current_array.clone(); for added in after.iter().filter(|value| !before.contains(value)) { if let Some(index) = result.iter().position(|value| value == added) { diff --git a/crates/agent/src/provider/pi/reconcile.rs b/crates/agent/src/provider/pi/reconcile.rs index f7aeea69..a0e3065c 100644 --- a/crates/agent/src/provider/pi/reconcile.rs +++ b/crates/agent/src/provider/pi/reconcile.rs @@ -14,6 +14,11 @@ use crate::reconcile::ReconcilePlan; const PROVIDER_NAME: &str = "agentdesktop"; const DEFAULT_API: &str = "anthropic-messages"; +const MODELS_OPTIONS: json_merge::MergeOptions = json_merge::MergeOptions { + format: json_merge::JsonFormat::Jsonc, + permissions: 0o600, + replace_paths: &[], +}; pub(super) fn plan( models_path: &Path, @@ -29,7 +34,7 @@ pub(super) fn plan( json_merge::plan_remove( models_path, &models_state, - json_merge::JsonFormat::Jsonc, + MODELS_OPTIONS, "models", Pi::DISPLAY_NAME, plan, @@ -37,7 +42,7 @@ pub(super) fn plan( json_merge::plan_remove( settings_path, &settings_state, - json_merge::JsonFormat::Json, + json_merge::MergeOptions::default(), "settings", Pi::DISPLAY_NAME, plan, @@ -48,7 +53,14 @@ pub(super) fn plan( json_merge::plan_merge( models_path, &models_state, - json_merge::JsonFormat::Jsonc, + json_merge::MergeOptions { + replace_paths: if gateway.is_some() { + &["/providers/agentdesktop/models"] + } else { + &[] + }, + ..MODELS_OPTIONS + }, managed_models(config, gateway, credential_helper, socket)?, false, "models", @@ -58,7 +70,7 @@ pub(super) fn plan( json_merge::plan_merge( settings_path, &settings_state, - json_merge::JsonFormat::Json, + json_merge::MergeOptions::default(), managed_settings(config, gateway)?, false, "settings", @@ -190,6 +202,275 @@ mod tests { use super::{managed_models, managed_settings, plan}; use crate::reconcile::ReconcilePlan; + fn reconcile_fixture(models: &Path, config: Option<&str>) -> ReconcilePlan { + let config = config.map(|yaml| parse_daemon(yaml).unwrap()); + let changes = ReconcilePlan::default(); + plan( + models, + &models.with_file_name("settings.json"), + Path::new("agentdesktop"), + Path::new("agentdesktop.sock"), + config.as_ref().map(|config| { + ( + config.programs.pi.as_ref().unwrap(), + config.llm_gateway.as_ref(), + ) + }), + &changes, + ) + .unwrap(); + changes + } + + #[cfg(unix)] + #[test] + fn models_create_and_merge_use_private_permissions() { + use std::os::unix::fs::PermissionsExt; + + for existing_mode in [None, Some(0o600), Some(0o644)] { + let root = tempfile::tempdir().unwrap(); + let models = root.path().join("models.json"); + if let Some(mode) = existing_mode { + fs::write( + &models, + r#"{"providers":{"personal":{"apiKey":"test-secret"}}}"#, + ) + .unwrap(); + fs::set_permissions(&models, fs::Permissions::from_mode(mode)).unwrap(); + } + let changes = reconcile_fixture(&models, Some("programs:\n pi: {}\n")); + if let Some(mode) = existing_mode { + assert_eq!( + fs::metadata(&models).unwrap().permissions().mode() & 0o777, + mode + ); + } else { + assert!(!models.exists(), "planning must not write files"); + } + changes.apply().unwrap(); + assert_eq!( + fs::metadata(&models).unwrap().permissions().mode() & 0o077, + 0 + ); + if existing_mode.is_some() { + let merged: Value = serde_json::from_slice(&fs::read(&models).unwrap()).unwrap(); + assert_eq!(merged["providers"]["personal"]["apiKey"], "test-secret"); + } + } + } + + #[cfg(unix)] + #[test] + fn models_cleanup_keeps_personal_credentials_private() { + use std::os::unix::fs::PermissionsExt; + + let root = tempfile::tempdir().unwrap(); + let models = root.path().join("models.json"); + let original = json!({"providers":{"personal":{"apiKey":"test-secret"}}}); + fs::write(&models, serde_json::to_vec(&original).unwrap()).unwrap(); + reconcile_fixture(&models, Some("llmGateway:\n url: https://gateway.example.com\nprograms:\n pi:\n model: selected\n")) + .apply().unwrap(); + fs::set_permissions(&models, fs::Permissions::from_mode(0o600)).unwrap(); + + reconcile_fixture(&models, None).apply().unwrap(); + assert_eq!( + fs::metadata(&models).unwrap().permissions().mode() & 0o077, + 0 + ); + let restored: Value = serde_json::from_slice(&fs::read(&models).unwrap()).unwrap(); + assert_eq!(restored, original); + } + + #[cfg(unix)] + #[test] + fn models_reconcile_repairs_permissions_without_content_changes() { + use std::os::unix::fs::PermissionsExt; + + // Cover both an active provider and cleanup of a previous merge. + for cleanup in [false, true] { + let root = tempfile::tempdir().unwrap(); + let models = root.path().join("models.json"); + fs::write( + &models, + r#"{"providers":{"personal":{"apiKey":"test-secret"}}}"#, + ) + .unwrap(); + let config = Some("programs:\n pi: {}\n"); + reconcile_fixture(&models, config).apply().unwrap(); + let before = fs::read(&models).unwrap(); + // Simulate a file written by the old 0644 merge implementation. + fs::set_permissions(&models, fs::Permissions::from_mode(0o644)).unwrap(); + let changes = reconcile_fixture(&models, if cleanup { None } else { config }); + assert!( + changes.render().contains("UPDATE Pi models"), + "{}", + changes.render() + ); + changes.apply().unwrap(); + assert_eq!(fs::read(&models).unwrap(), before); + assert_eq!( + fs::metadata(&models).unwrap().permissions().mode() & 0o077, + 0 + ); + if !cleanup { + let repeated = reconcile_fixture(&models, config); + assert!(repeated.render().contains("Summary: 0 changes")); + repeated.apply().unwrap(); + } + } + } + + #[test] + fn gateway_catalog_replaces_existing_models_and_restores_on_removal() { + let root = tempfile::tempdir().unwrap(); + let models = root.path().join("models.json"); + let original = json!({"providers":{ + "agentdesktop": { + "baseUrl":"https://old.example.com", + "apiKey":"test-secret", + "models":[ + {"id":"selected","baseUrl":"https://gateway.example.com"}, + {"id":"selected","baseUrl":"https://outside.example.com"}, + {"id":"stale","baseUrl":"https://outside.example.com"} + ] + }, + "personal":{"models":[{"id":"personal-model"}],"apiKey":"personal-secret"} + }}); + fs::write(&models, serde_json::to_vec(&original).unwrap()).unwrap(); + + for selected in ["selected", "next"] { + let config = format!( + "llmGateway:\n url: https://gateway.example.com\nprograms:\n pi:\n model: {selected}\n" + ); + reconcile_fixture(&models, Some(&config)).apply().unwrap(); + let merged: Value = serde_json::from_slice(&fs::read(&models).unwrap()).unwrap(); + assert_eq!( + merged["providers"]["agentdesktop"]["models"], + json!([ + {"id":selected,"baseUrl":"https://gateway.example.com"} + ]) + ); + assert_eq!( + merged["providers"]["personal"], + original["providers"]["personal"] + ); + let repeated = reconcile_fixture(&models, Some(&config)); + assert!(repeated.render().contains("Summary: 0 changes")); + repeated.apply().unwrap(); + } + + // Unrelated user edits made while managed must survive cleanup. + let mut edited: Value = serde_json::from_slice(&fs::read(&models).unwrap()).unwrap(); + edited["providers"]["personal"]["apiKey"] = json!("updated-personal-secret"); + fs::write(&models, serde_json::to_vec(&edited).unwrap()).unwrap(); + reconcile_fixture(&models, None).apply().unwrap(); + let restored: Value = serde_json::from_slice(&fs::read(&models).unwrap()).unwrap(); + assert_eq!( + restored["providers"]["agentdesktop"], + original["providers"]["agentdesktop"] + ); + assert_eq!( + restored["providers"]["personal"]["apiKey"], + "updated-personal-secret" + ); + assert!(!super::json_merge::state_path(&models).exists()); + } + + #[test] + fn cleanup_restores_catalog_order_after_user_additions() { + for restore_original in [false, true] { + let root = tempfile::tempdir().unwrap(); + let models = root.path().join("models.json"); + // Pi resolves duplicate IDs using the last entry. The original catalog + // must keep that ordering even if one entry matches the managed model. + let original_catalog = json!([ + {"id":"selected","baseUrl":"https://outside.example.com"}, + {"id":"selected","baseUrl":"https://gateway.example.com"}, + {"id":"selected","baseUrl":"https://gateway.example.com"} + ]); + fs::write( + &models, + serde_json::to_vec(&json!({"providers":{"agentdesktop":{ + "models":original_catalog + }}})) + .unwrap(), + ) + .unwrap(); + reconcile_fixture(&models, Some("llmGateway:\n url: https://gateway.example.com\nprograms:\n pi:\n model: selected\n")) + .apply().unwrap(); + let mut edited: Value = serde_json::from_slice(&fs::read(&models).unwrap()).unwrap(); + let addition = json!({"id":"added","baseUrl":"https://added.example.com"}); + if restore_original { + // A user may restore an original entry before disabling management. + edited["providers"]["agentdesktop"]["models"] + .as_array_mut() + .unwrap() + .push(original_catalog[0].clone()); + } + edited["providers"]["agentdesktop"]["models"] + .as_array_mut() + .unwrap() + .push(addition.clone()); + fs::write(&models, serde_json::to_vec(&edited).unwrap()).unwrap(); + + reconcile_fixture(&models, None).apply().unwrap(); + let restored: Value = serde_json::from_slice(&fs::read(&models).unwrap()).unwrap(); + let mut expected = original_catalog; + expected.as_array_mut().unwrap().push(addition); + assert_eq!(restored["providers"]["agentdesktop"]["models"], expected); + } + } + + #[test] + fn disabling_gateway_restores_catalog_and_preserves_user_additions() { + let root = tempfile::tempdir().unwrap(); + let models = root.path().join("models.json"); + let original_model = json!({"id":"original","baseUrl":"https://original.example.com"}); + fs::write( + &models, + serde_json::to_vec(&json!({"providers":{"agentdesktop":{ + "models":[original_model.clone()] + }}})) + .unwrap(), + ) + .unwrap(); + reconcile_fixture(&models, Some("llmGateway:\n url: https://gateway.example.com\nprograms:\n pi:\n model: selected\n")) + .apply().unwrap(); + let mut edited: Value = serde_json::from_slice(&fs::read(&models).unwrap()).unwrap(); + assert_eq!( + edited["providers"]["agentdesktop"]["models"] + .as_array() + .unwrap() + .len(), + 1 + ); + let added_model = json!({"id":"added","baseUrl":"https://added.example.com"}); + edited["providers"]["agentdesktop"]["models"] + .as_array_mut() + .unwrap() + .push(added_model.clone()); + fs::write(&models, serde_json::to_vec(&edited).unwrap()).unwrap(); + + reconcile_fixture( + &models, + Some("programs:\n pi:\n useLlmGateway: false\n"), + ) + .apply() + .unwrap(); + let restored: Value = serde_json::from_slice(&fs::read(&models).unwrap()).unwrap(); + let catalog = restored["providers"]["agentdesktop"]["models"] + .as_array() + .unwrap(); + assert_eq!(catalog.len(), 2); + assert!(catalog.contains(&original_model)); + assert!(catalog.contains(&added_model)); + reconcile_fixture(&models, None).apply().unwrap(); + assert_eq!( + serde_json::from_slice::(&fs::read(&models).unwrap()).unwrap(), + restored + ); + } + #[test] fn gateway_overrides_per_model_endpoints() { let config = parse_daemon( From b201f7d4d9395b2183682ca54d26f0274a1adac0 Mon Sep 17 00:00:00 2001 From: Sebastian Maniak Date: Tue, 22 Sep 2026 11:48:56 -0400 Subject: [PATCH 10/11] Remove Pi screenshots and format rebased tool icons --- docs/screenshots/pi-controller-config.png | Bin 28101 -> 0 bytes docs/screenshots/pi-inventory.png | Bin 20661 -> 0 bytes docs/screenshots/pi-mcp.png | Bin 29517 -> 0 bytes docs/screenshots/pi-skills.png | Bin 55953 -> 0 bytes frontend/ui/src/tools.tsx | 4 +++- 5 files changed, 3 insertions(+), 1 deletion(-) delete mode 100644 docs/screenshots/pi-controller-config.png delete mode 100644 docs/screenshots/pi-inventory.png delete mode 100644 docs/screenshots/pi-mcp.png delete mode 100644 docs/screenshots/pi-skills.png diff --git a/docs/screenshots/pi-controller-config.png b/docs/screenshots/pi-controller-config.png deleted file mode 100644 index 34bf6170bf9549fcc35196a13f36aef68f7bdcb2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 28101 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 Q#Osizs_w0#o3_FK2jlhr)Bpeg diff --git a/docs/screenshots/pi-inventory.png b/docs/screenshots/pi-inventory.png deleted file mode 100644 index 637a82ddf2e60b7b65c5f148fc83b2a5f3e2c9e6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 20661 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 z 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 L>gTe~DWM4fHtd$ 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®Y6 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®>(7gf@CHdbj~2N)-mH!;15*l3k9US-hlCvm^}0 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

@gCb6`%-L; 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 diff --git a/frontend/ui/src/tools.tsx b/frontend/ui/src/tools.tsx index 71292322..945743c0 100644 --- a/frontend/ui/src/tools.tsx +++ b/frontend/ui/src/tools.tsx @@ -126,7 +126,9 @@ export function ModelRuntimeInventory({ export function ToolIcon({ kind }: { kind: string }) { const icon = toolIcons[kind.toLowerCase()]; - const monochrome = ["opencode", "vscode", "cursor"].includes(kind.toLowerCase()); + const monochrome = ["opencode", "vscode", "cursor"].includes( + kind.toLowerCase(), + ); return icon ? ( Date: Thu, 1 Oct 2026 20:02:18 -0400 Subject: [PATCH 11/11] fix: align Pi inventory with the files Pi loads Project MCP files stay in the working directory unless a user-global adapter config opts into ancestorConfigRoots. Skill discovery keeps .pi/skills in the working directory and stops .agents/skills at the git root. Validation rejects Pi combined with Claude Desktop or Grok Build, and rejects empty Pi model IDs before models.json is written. --- crates/agent/src/provider/README.md | 17 +- crates/agent/src/provider/pi/discovery.rs | 402 +++++++++++++++++++++- crates/core/src/config.rs | 55 ++- schema/daemon-config.json | 4 +- schema/daemon-config.md | 4 +- 5 files changed, 462 insertions(+), 20 deletions(-) diff --git a/crates/agent/src/provider/README.md b/crates/agent/src/provider/README.md index 89c1b193..61148aa5 100644 --- a/crates/agent/src/provider/README.md +++ b/crates/agent/src/provider/README.md @@ -10,7 +10,9 @@ file. Pi stores configuration in `~/.pi/agent` (or `PI_CODING_AGENT_DIR`, with `~` expanded to the user's home). Management requires `--user`; system mode -rejects `programs.pi` because Pi does not read `/etc/pi/agent`. Authenticated +rejects `programs.pi` because Pi does not read `/etc/pi/agent`. Pi cannot be +combined with Claude Desktop or Grok Build, because those programs require +system mode. Authenticated gateway configuration also requires a running daemon and rejects `--once`. User mode merges `providers.agentdesktop` into `models.json` and sets the default model in `settings.json`. Model map keys determine catalog IDs, and @@ -25,9 +27,16 @@ preserving user configuration values. `settings.json` requires standard JSON. Discovery recognizes npm's Windows command shims as well as symlinked launchers, verifying the Pi package manifest without executing the launcher. MCP inventory comes from `pi-mcp-adapter` config files -(`~/.pi/agent/mcp.json`, `.pi/mcp.json`, `.mcp.json`, and shared -`~/.config/mcp/mcp.json` / `~/.agents/mcp.json` / `~/.agents/mcp/mcp.json`). -Both `.agents` paths are supported by the [adapter's configuration loader](https://github.com/nicobailon/pi-mcp-adapter/blob/33bdc38d8dd3802f2b51ba1fe37e30ed7ae6a29a/config.ts#L15-L19). +(`~/.pi/agent/mcp.json`, the working directory's `.pi/mcp.json` and +`.mcp.json`, and shared `~/.config/mcp/mcp.json` / `~/.agents/mcp.json` / +`~/.agents/mcp/mcp.json`). Ancestor project files are included only when a +user-global config sets `settings.ancestorConfigRoots` to an existing +directory under the home that contains the working directory. Both `.agents` +paths are supported by the [adapter's configuration loader](https://github.com/nicobailon/pi-mcp-adapter/blob/33bdc38d8dd3802f2b51ba1fe37e30ed7ae6a29a/config.ts#L15-L19). +`PI_MCP_CONFIG_MODE=exclusive` inventories only the Pi agent `mcp.json`. +Skill inventory reads `~/.pi/agent/skills`, `~/.agents/skills`, the working +directory's `.pi/skills`, and `.agents/skills` from the working directory +through the git repository root. Host-specific Cursor/Claude files are not scanned until the adapter imports them into a Pi-owned file. diff --git a/crates/agent/src/provider/pi/discovery.rs b/crates/agent/src/provider/pi/discovery.rs index 92a10c98..cf0ae54f 100644 --- a/crates/agent/src/provider/pi/discovery.rs +++ b/crates/agent/src/provider/pi/discovery.rs @@ -1,10 +1,12 @@ use std::{ collections::BTreeSet, + env, fs, io::Read, path::{Path, PathBuf}, }; use agentdesktop_core::model::{Agent, McpServer}; +use serde_json::Value; use crate::provider::{metadata, vscode::discovery as vscode}; @@ -157,34 +159,268 @@ fn discover_mcp_servers() -> Vec { /// Files `pi-mcp-adapter` loads as normal MCP config. /// +/// Global files are always included. Project files are only the working +/// directory's `.mcp.json` and `.pi/mcp.json`, plus ancestors when a +/// user-global config opts in through `settings.ancestorConfigRoots`. +/// `PI_MCP_CONFIG_MODE=exclusive` keeps only the Pi agent `mcp.json`. /// Host-specific Cursor/Claude/Codex files are not included: the adapter only /// imports those after `/mcp setup` writes them into a Pi-owned file. fn mcp_config_paths() -> Vec { + let current = env::current_dir().ok(); + mcp_config_paths_with( + &metadata::user_home_dirs(), + current.as_deref(), + pi_mcp_exclusive_mode(), + user_pi_agent_dir, + ) +} + +fn mcp_config_paths_with( + homes: &[PathBuf], + cwd: Option<&Path>, + exclusive: bool, + agent_dir: impl Fn(&Path) -> PathBuf, +) -> Vec { let mut paths = BTreeSet::new(); - for home in metadata::user_home_dirs() { - let agent_dir = user_pi_agent_dir(&home); - paths.insert(agent_dir.join("mcp.json")); + for home in homes { + let agent_mcp = agent_dir(home).join("mcp.json"); + if exclusive { + paths.insert(agent_mcp); + continue; + } paths.insert(home.join(".config/mcp/mcp.json")); paths.insert(home.join(".agents/mcp.json")); paths.insert(home.join(".agents/mcp/mcp.json")); + paths.insert(agent_mcp); + } + if exclusive { + return paths.into_iter().collect(); + } + if let Some(cwd) = cwd { + let cwd = canonicalize_dir(cwd).unwrap_or_else(|| cwd.to_path_buf()); + paths.insert(cwd.join(".mcp.json")); + paths.insert(cwd.join(".pi/mcp.json")); + for home in homes { + let Some(root) = configured_ancestor_root(home, &cwd, &agent_dir) else { + continue; + }; + for directory in ancestor_project_dirs(&cwd, &root) { + paths.insert(directory.join(".mcp.json")); + paths.insert(directory.join(".pi/mcp.json")); + } + } } - paths.extend(metadata::current_dir_ancestors(Path::new(".mcp.json"))); - paths.extend(metadata::current_dir_ancestors(Path::new(".pi/mcp.json"))); paths.into_iter().collect() } +fn pi_mcp_exclusive_mode() -> bool { + env::var("PI_MCP_CONFIG_MODE").is_ok_and(|value| value.trim().eq_ignore_ascii_case("exclusive")) +} + +/// Last user-global `settings.ancestorConfigRoots` that pi-mcp-adapter would honor. +/// +/// Later global files replace earlier ones. An empty or invalid value disables +/// ancestor loading. Project files cannot enable it. +fn configured_ancestor_root( + home: &Path, + cwd: &Path, + agent_dir: &impl Fn(&Path) -> PathBuf, +) -> Option { + let mut configured = None; + for path in global_mcp_sources(home, agent_dir) { + match read_ancestor_setting(&path) { + AncestorSetting::Unset => {} + AncestorSetting::Clear => configured = Some(Vec::new()), + AncestorSetting::Roots(roots) => configured = Some(roots), + } + } + let roots = configured.filter(|roots| !roots.is_empty())?; + let home_id = canonicalize_dir(home)?; + let mut valid = Vec::new(); + for entry in roots { + let Some(expanded) = expand_ancestor_root(&entry, home) else { + continue; + }; + let Some(root) = canonicalize_dir(&expanded) else { + continue; + }; + // The deepest existing directory under the user's home that contains + // the working directory wins. A root equal to the working directory + // adds no parent files. + if root.starts_with(&home_id) && root_contains_cwd(&root, cwd) { + valid.push(root); + } + } + valid.sort_by_key(|path| std::cmp::Reverse(path.as_os_str().len())); + valid.into_iter().next() +} + +fn global_mcp_sources(home: &Path, agent_dir: &impl Fn(&Path) -> PathBuf) -> Vec { + let user = agent_dir(home).join("mcp.json"); + let generic = home.join(".config/mcp/mcp.json"); + let mut sources = Vec::new(); + if generic != user { + sources.push(generic.clone()); + } + for relative in [ + Path::new(".agents/mcp.json"), + Path::new(".agents/mcp/mcp.json"), + ] { + let path = home.join(relative); + if path != user && path != generic { + sources.push(path); + } + } + sources.push(user); + sources +} + +enum AncestorSetting { + Unset, + Clear, + Roots(Vec), +} + +fn read_ancestor_setting(path: &Path) -> AncestorSetting { + let Ok(bytes) = fs::read(path) else { + return AncestorSetting::Unset; + }; + let Ok(text) = str::from_utf8(&bytes) else { + return AncestorSetting::Unset; + }; + let Ok(value) = json5::from_str::(text.trim_start_matches('\u{feff}')) else { + return AncestorSetting::Unset; + }; + let Some(roots) = value.pointer("/settings/ancestorConfigRoots") else { + return AncestorSetting::Unset; + }; + let Some(entries) = roots.as_array() else { + return AncestorSetting::Clear; + }; + if entries.is_empty() { + return AncestorSetting::Clear; + } + AncestorSetting::Roots( + entries + .iter() + .filter_map(Value::as_str) + .map(str::to_owned) + .collect(), + ) +} + +fn expand_ancestor_root(entry: &str, home: &Path) -> Option { + entry + .strip_prefix("~/") + .map(|rest| home.join(rest)) + .or_else(|| { + let path = PathBuf::from(entry); + path.is_absolute().then_some(path) + }) +} + +fn ancestor_project_dirs(cwd: &Path, root: &Path) -> Vec { + let Some(cwd) = canonicalize_dir(cwd) else { + return Vec::new(); + }; + let Some(root) = canonicalize_dir(root) else { + return Vec::new(); + }; + let mut dirs = Vec::new(); + let Some(mut current) = cwd.parent().map(Path::to_path_buf) else { + return dirs; + }; + while current.starts_with(&root) { + dirs.insert(0, current.clone()); + if current == root { + break; + } + let Some(parent) = current.parent().map(Path::to_path_buf) else { + break; + }; + if parent == current { + break; + } + current = parent; + } + dirs +} + +fn root_contains_cwd(root: &Path, cwd: &Path) -> bool { + let Some(cwd) = canonicalize_dir(cwd) else { + return false; + }; + let Some(root) = canonicalize_dir(root) else { + return false; + }; + cwd.starts_with(root) +} + +fn canonicalize_dir(path: &Path) -> Option { + let canonical = fs::canonicalize(path).ok()?; + canonical.is_dir().then_some(canonical) +} + +/// Skill directories Pi loads. +/// +/// User skills come from the Pi agent directory and `~/.agents/skills`. +/// Project `.pi/skills` is only the working directory. Project `.agents/skills` +/// walks from the working directory through the git repository root, or the +/// filesystem root when the working directory is not in a repository. fn skill_roots() -> Vec { + let current = env::current_dir().ok(); + skill_roots_in(&metadata::user_home_dirs(), current.as_deref()) +} + +fn skill_roots_in(homes: &[PathBuf], cwd: Option<&Path>) -> Vec { let mut roots = BTreeSet::new(); - for home in metadata::user_home_dirs() { - let agent_dir = user_pi_agent_dir(&home); - roots.insert(agent_dir.join("skills")); + for home in homes { + roots.insert(user_pi_agent_dir(home).join("skills")); roots.insert(home.join(".agents/skills")); } - roots.extend(metadata::current_dir_ancestors(Path::new(".pi/skills"))); - roots.extend(metadata::current_dir_ancestors(Path::new(".agents/skills"))); + if let Some(cwd) = cwd { + let cwd = canonicalize_dir(cwd).unwrap_or_else(|| cwd.to_path_buf()); + roots.insert(cwd.join(".pi/skills")); + roots.extend(agents_skill_dirs(&cwd)); + } roots.into_iter().collect() } +fn agents_skill_dirs(start: &Path) -> Vec { + let start = canonicalize_dir(start).unwrap_or_else(|| start.to_path_buf()); + let git_root = git_repo_root(&start); + let mut dir = start; + let mut dirs = Vec::new(); + loop { + dirs.push(dir.join(".agents/skills")); + if git_root.as_ref().is_some_and(|root| &dir == root) { + break; + } + let Some(parent) = dir.parent().map(Path::to_path_buf) else { + break; + }; + if parent == dir { + break; + } + dir = parent; + } + dirs +} + +fn git_repo_root(start: &Path) -> Option { + let mut dir = start.to_path_buf(); + loop { + if dir.join(".git").exists() { + return Some(dir); + } + let parent = dir.parent()?.to_path_buf(); + if parent == dir { + return None; + } + dir = parent; + } +} + #[cfg(test)] mod tests { use std::{ @@ -194,7 +430,10 @@ mod tests { use serde_json::json; - use super::{is_pi, mcp_config_paths, package_version, skill_roots}; + use super::{ + is_pi, mcp_config_paths, mcp_config_paths_with, package_version, skill_roots, + skill_roots_in, + }; use crate::provider::pi::user_pi_agent_dir; use crate::provider::vscode::discovery::mcp_servers_from_value; @@ -502,6 +741,147 @@ fi ); } + #[test] + fn project_mcp_stays_in_the_working_directory_unless_opted_in() { + let root = tempfile::tempdir().unwrap(); + let home = root.path().join("home"); + let repo = home.join("repo"); + let project = repo.join("child"); + let outside = home.join("outside"); + fs::create_dir_all(project.join(".pi")).unwrap(); + fs::create_dir_all(&outside).unwrap(); + fs::create_dir_all(repo.join(".pi")).unwrap(); + let project_id = fs::canonicalize(&project).unwrap(); + let repo_id = fs::canonicalize(&repo).unwrap(); + let outside_id = fs::canonicalize(&outside).unwrap(); + let home_id = fs::canonicalize(&home).unwrap(); + let agent = |home: &Path| home.join(".pi/agent"); + let paths = + mcp_config_paths_with(std::slice::from_ref(&home), Some(&project), false, agent); + assert!(paths.contains(&project_id.join(".mcp.json"))); + assert!(paths.contains(&project_id.join(".pi/mcp.json"))); + assert!(!paths.contains(&repo_id.join(".mcp.json"))); + assert!(!paths.contains(&repo_id.join(".pi/mcp.json"))); + assert!(!paths.contains(&outside_id.join(".mcp.json"))); + assert!(paths.contains(&home.join(".config/mcp/mcp.json"))); + assert!(paths.contains(&home.join(".agents/mcp.json"))); + assert!(paths.contains(&home.join(".agents/mcp/mcp.json"))); + assert!(paths.contains(&agent(&home).join("mcp.json"))); + + // A project file cannot widen discovery. + fs::write( + project.join(".mcp.json"), + r#"{"settings":{"ancestorConfigRoots":["~/"]}}"#, + ) + .unwrap(); + let paths = + mcp_config_paths_with(std::slice::from_ref(&home), Some(&project), false, agent); + assert!(!paths.contains(&repo_id.join(".mcp.json"))); + + fs::create_dir_all(home.join(".config/mcp")).unwrap(); + fs::write( + home.join(".config/mcp/mcp.json"), + r#"{"settings":{"ancestorConfigRoots":["~/repo"]}}"#, + ) + .unwrap(); + let paths = + mcp_config_paths_with(std::slice::from_ref(&home), Some(&project), false, agent); + assert!(paths.contains(&repo_id.join(".mcp.json"))); + assert!(paths.contains(&repo_id.join(".pi/mcp.json"))); + assert!(!paths.contains(&outside_id.join(".mcp.json"))); + assert!(!paths.contains(&home_id.join(".mcp.json"))); + + // The Pi agent file is the last global source, so it can turn the opt-in off. + fs::create_dir_all(home.join(".pi/agent")).unwrap(); + fs::write( + agent(&home).join("mcp.json"), + r#"{"settings":{"ancestorConfigRoots":[]}}"#, + ) + .unwrap(); + let paths = + mcp_config_paths_with(std::slice::from_ref(&home), Some(&project), false, agent); + assert!(!paths.contains(&repo_id.join(".mcp.json"))); + + let paths = mcp_config_paths_with(std::slice::from_ref(&home), Some(&project), true, agent); + assert_eq!(paths, vec![agent(&home).join("mcp.json")]); + } + + #[test] + fn ancestor_mcp_ignores_roots_outside_the_home_or_working_directory() { + let root = tempfile::tempdir().unwrap(); + let home = root.path().join("home"); + let project = home.join("work/child"); + let elsewhere = home.join("elsewhere"); + fs::create_dir_all(&project).unwrap(); + fs::create_dir_all(&elsewhere).unwrap(); + fs::create_dir_all(home.join(".pi/agent")).unwrap(); + fs::write( + home.join(".pi/agent/mcp.json"), + format!( + r#"{{"settings":{{"ancestorConfigRoots":["{}", "~/elsewhere"]}}}}"#, + root.path().display() + ), + ) + .unwrap(); + let paths = + mcp_config_paths_with(std::slice::from_ref(&home), Some(&project), false, |home| { + home.join(".pi/agent") + }); + let root_id = fs::canonicalize(root.path()).unwrap(); + let elsewhere_id = fs::canonicalize(&elsewhere).unwrap(); + let work_id = fs::canonicalize(home.join("work")).unwrap(); + assert!(!paths.contains(&root_id.join(".mcp.json"))); + assert!(!paths.contains(&elsewhere_id.join(".mcp.json"))); + assert!(!paths.contains(&work_id.join(".mcp.json"))); + } + + #[test] + fn skill_roots_follow_pi_project_scopes() { + let root = tempfile::tempdir().unwrap(); + let home = root.path().join("home"); + let repo = root.path().join("repo"); + let project = repo.join("child"); + fs::create_dir_all(&home).unwrap(); + fs::create_dir_all(&project).unwrap(); + fs::write(repo.join(".git"), "gitdir: /unused").unwrap(); + + let project = fs::canonicalize(&project).unwrap(); + let repo = fs::canonicalize(&repo).unwrap(); + let paths = skill_roots_in(std::slice::from_ref(&home), Some(&project)); + assert!(paths.contains(&project.join(".pi/skills"))); + assert!(paths.contains(&project.join(".agents/skills"))); + assert!(paths.contains(&repo.join(".agents/skills"))); + assert!(!paths.contains(&repo.join(".pi/skills"))); + assert!( + !paths.contains( + &fs::canonicalize(root.path()) + .unwrap() + .join(".agents/skills") + ) + ); + assert!(!paths.contains(&fs::canonicalize(root.path()).unwrap().join(".pi/skills"))); + assert!(paths.contains(&home.join(".agents/skills"))); + assert!(paths.contains(&user_pi_agent_dir(&home).join("skills"))); + + let loose = root.path().join("loose/child"); + fs::create_dir_all(&loose).unwrap(); + let loose = fs::canonicalize(&loose).unwrap(); + let paths = skill_roots_in(&[home], Some(&loose)); + assert!(paths.contains(&loose.join(".pi/skills"))); + assert!(!paths.contains(&loose.parent().unwrap().join(".pi/skills"))); + assert!(paths.contains(&loose.parent().unwrap().join(".agents/skills"))); + assert!( + paths.contains( + &loose + .parent() + .unwrap() + .parent() + .unwrap() + .join(".agents/skills") + ) + ); + } + #[test] fn scans_pi_owned_and_shared_mcp_files() { let paths = mcp_config_paths(); diff --git a/crates/core/src/config.rs b/crates/core/src/config.rs index ab7d350f..84c8787c 100644 --- a/crates/core/src/config.rs +++ b/crates/core/src/config.rs @@ -498,6 +498,9 @@ pub struct ProgramsConfig { #[serde(default, skip_serializing_if = "Option::is_none")] pub grok: Option, /// Pi coding-agent harness managed configuration. + /// + /// Requires `--user`. Cannot be combined with Claude Desktop or Grok Build, + /// which require system mode. #[serde(default, skip_serializing_if = "Option::is_none")] pub pi: Option, } @@ -659,7 +662,8 @@ pub struct PiConfig { /// Each value is an arbitrary Pi `models.json` model object. The map key /// sets its `id`. Generated gateway `baseUrl` and `apiKey` values take /// precedence, including per-model URLs. When this map is non-empty, - /// `model` must name one of its keys. + /// `model` must name one of its keys. Every key must contain a + /// non-whitespace character. #[serde(default, skip_serializing_if = "BTreeMap::is_empty")] pub models: BTreeMap, /// Pi API dialect for the managed provider. Defaults to `anthropic-messages`. @@ -958,6 +962,12 @@ fn validate_daemon( anyhow::bail!("Grok Build model {model} is not declared in models"); } } + if programs.pi.is_some() && programs.claude_desktop.is_some() { + anyhow::bail!("programs.pi requires --user, which cannot apply programs.claudeDesktop"); + } + if programs.pi.is_some() && programs.grok.is_some() { + anyhow::bail!("programs.pi requires --user, which cannot apply programs.grok"); + } if let Some(pi) = &programs.pi && llm_gateway.is_some() && pi.use_llm_gateway @@ -967,6 +977,9 @@ fn validate_daemon( .as_deref() .filter(|model| !model.trim().is_empty()) .context("Pi requires model when llmGateway is configured")?; + if pi.models.keys().any(|model| model.trim().is_empty()) { + anyhow::bail!("Pi model IDs cannot be empty"); + } if !pi.models.is_empty() && !pi.models.contains_key(model) { anyhow::bail!("Pi model {model} is not declared in models"); } @@ -1451,4 +1464,44 @@ programs: .contains("Pi model missing is not declared in models") ); } + + #[test] + fn pi_rejects_empty_model_ids() { + let error = parse_daemon( + r#" +llmGateway: + url: https://gateway.example.com +programs: + pi: + model: claude-sonnet-4-5 + models: + claude-sonnet-4-5: {} + "": {} +"#, + ) + .expect_err("empty Pi model id should fail"); + + assert!(error.to_string().contains("Pi model IDs cannot be empty")); + } + + #[test] + fn pi_rejects_system_only_program_combinations() { + for (name, message) in [ + ( + "claudeDesktop", + "programs.pi requires --user, which cannot apply programs.claudeDesktop", + ), + ( + "grok", + "programs.pi requires --user, which cannot apply programs.grok", + ), + ] { + let error = parse_daemon(&format!("programs:\n pi: {{}}\n {name}: {{}}\n")) + .expect_err("Pi combined with a system-only program should fail"); + assert!( + error.to_string().contains(message), + "missing {message} in {error}" + ); + } + } } diff --git a/schema/daemon-config.json b/schema/daemon-config.json index 3756ca34..68872d89 100644 --- a/schema/daemon-config.json +++ b/schema/daemon-config.json @@ -441,7 +441,7 @@ ] }, "models": { - "description": "Extra Pi model objects, keyed by model ID.\n\nEach value is an arbitrary Pi `models.json` model object. The map key\nsets its `id`. Generated gateway `baseUrl` and `apiKey` values take\nprecedence, including per-model URLs. When this map is non-empty,\n`model` must name one of its keys.", + "description": "Extra Pi model objects, keyed by model ID.\n\nEach value is an arbitrary Pi `models.json` model object. The map key\nsets its `id`. Generated gateway `baseUrl` and `apiKey` values take\nprecedence, including per-model URLs. When this map is non-empty,\n`model` must name one of its keys. Every key must contain a\nnon-whitespace character.", "type": "object", "additionalProperties": true }, @@ -543,7 +543,7 @@ ] }, "pi": { - "description": "Pi coding-agent harness managed configuration.", + "description": "Pi coding-agent harness managed configuration.\n\nRequires `--user`. Cannot be combined with Claude Desktop or Grok Build,\nwhich require system mode.", "anyOf": [ { "$ref": "#/$defs/PiConfig" diff --git a/schema/daemon-config.md b/schema/daemon-config.md index 20a699c4..c23231c8 100644 --- a/schema/daemon-config.md +++ b/schema/daemon-config.md @@ -59,11 +59,11 @@ |`programs.openCode.model`|string|Model ID selected from `models` when using the LLM gateway.

This is required when a top-level `llmGateway` is configured.| |`programs.openCode.models`|object|Models exposed by the managed LLM gateway provider, keyed by model ID.

Each value is an arbitrary OpenCode model configuration object. At least
one model is required when a top-level `llmGateway` is configured.| |`programs.openCode.useLlmGateway`|boolean|Whether this program uses the top-level LLM gateway.| -|`programs.pi`|object|Pi coding-agent harness managed configuration.| +|`programs.pi`|object|Pi coding-agent harness managed configuration.

Requires `--user`. Cannot be combined with Claude Desktop or Grok Build,
which require system mode.| |`programs.pi.api`|string|Pi API dialect for the managed provider. Defaults to `anthropic-messages`.| |`programs.pi.managedConfig`|object|Arbitrary values merged into Pi's `models.json`.| |`programs.pi.model`|string|Model ID selected when pointing Pi at the LLM gateway.

This is required when a top-level `llmGateway` is configured. If `models`
is empty, agentdesktop creates a catalog entry with this ID.| -|`programs.pi.models`|object|Extra Pi model objects, keyed by model ID.

Each value is an arbitrary Pi `models.json` model object. The map key
sets its `id`. Generated gateway `baseUrl` and `apiKey` values take
precedence, including per-model URLs. When this map is non-empty,
`model` must name one of its keys.| +|`programs.pi.models`|object|Extra Pi model objects, keyed by model ID.

Each value is an arbitrary Pi `models.json` model object. The map key
sets its `id`. Generated gateway `baseUrl` and `apiKey` values take
precedence, including per-model URLs. When this map is non-empty,
`model` must name one of its keys. Every key must contain a
non-whitespace character.| |`programs.pi.useLlmGateway`|boolean|Whether this program uses the top-level LLM gateway.| |`sandbox`|object|Local execution sandbox required for managed developer tools.| |`sandbox.filesystem`|object|Filesystem access available to sandboxed commands.|