fix(ai): expand agent vault paths before spawn
This commit is contained in:
@@ -249,7 +249,7 @@ Full agent mode — spawns the selected local CLI agent as a subprocess with too
|
||||
4. **Agent adapters** — Shared prompts are mode-aware on every turn, including turns with note context snapshots: Vault Safe tells agents not to use or advertise shell, while Power User tells shell-capable agents to keep local commands scoped to the active vault. Claude Code still uses `claude_cli.rs` with `acceptEdits`, strict Tolaria MCP config, and a scoped tool list: Safe enables file/search/edit tools only, while Power User adds Bash to the available tools and pre-approves Bash with `--allowedTools` without using dangerous bypass flags. Codex runtime specifics live in `codex_cli.rs`; Safe runs `codex --sandbox read-only --ask-for-approval untrusted exec --json`, while Power User runs `codex --sandbox workspace-write --ask-for-approval never exec --json` so shell execution stays enabled across repeated turns. OpenCode runs through `opencode run --format json` with transient permissions: Safe denies bash and external directories, while Power User allows bash but still denies external directories. Pi runs through `pi --mode json --no-session` with `npm:pi-mcp-adapter`; both modes currently share the same transient MCP config and the prompt does not promise shell for Pi Power User. Gemini runs through `gemini --output-format stream-json --prompt` so assistant message chunks, tool calls, and final errors are mapped from the CLI event stream instead of relying on a buffered `response` field. Gemini Safe uses `auto_edit` plus `tools.exclude=["run_shell_command"]`; Power User intentionally uses `yolo` against a trusted transient Tolaria MCP entry. Codex, OpenCode, Pi, and Gemini all launch from the active vault cwd with closed stdin and transient MCP config. All app-launched paths use hidden Windows launches and avoid dangerous permission-bypass flags.
|
||||
5. **MCP Integration** — Claude receives the generated MCP config file path, Codex receives the same Tolaria MCP server via transient `-c mcp_servers.tolaria.*` config overrides using Tolaria's resolved Node path plus `VAULT_PATH` and `WS_UI_PORT`, OpenCode receives it through `OPENCODE_CONFIG_CONTENT`, Pi receives it through a temporary `PI_CODING_AGENT_DIR/mcp.json` consumed by `pi-mcp-adapter`, and Gemini receives it through a temporary settings file pointed at by `GEMINI_CLI_SYSTEM_SETTINGS_PATH`
|
||||
|
||||
CLI-agent availability intentionally does not depend only on the desktop app's inherited `PATH`. The detectors check the current process path, the user's login shell, and supported local/toolchain install locations such as native `~/.local/bin`, local `~/.claude/local`, Mise/asdf shims, nvm-managed Node installs, npm-global, Homebrew, Windows `%APPDATA%\npm`/pnpm/Scoop shims, Windows `.exe` launchers, and the macOS Codex app resource path so first-run onboarding works on fresh macOS and Windows installs. App-managed CLI spawns also extend the child process `PATH` with the resolved binary directory plus those common toolchain directories, which lets GUI-launched macOS sessions run Homebrew/npm shims and their `node`-backed MCP subprocesses even when Finder/Dock did not inherit a terminal shell path.
|
||||
CLI-agent availability intentionally does not depend only on the desktop app's inherited `PATH`. The detectors check the current process path, the user's login shell, and supported local/toolchain install locations such as native `~/.local/bin`, local `~/.claude/local`, Mise/asdf shims, nvm-managed Node installs, npm-global, Homebrew, Windows `%APPDATA%\npm`/pnpm/Scoop shims, Windows `.exe` launchers, and the macOS Codex app resource path so first-run onboarding works on fresh macOS and Windows installs. App-managed CLI spawns also expand the active vault path before using it as the subprocess working directory, then extend the child process `PATH` with the resolved binary directory plus those common toolchain directories, which lets GUI-launched macOS sessions run Homebrew/npm shims and their `node`-backed MCP subprocesses even when Finder/Dock did not inherit a terminal shell path.
|
||||
|
||||
#### Agent Event Flow
|
||||
|
||||
|
||||
@@ -82,12 +82,25 @@ define_desktop_stream_command!(
|
||||
);
|
||||
|
||||
#[cfg(desktop)]
|
||||
define_desktop_stream_command!(
|
||||
stream_ai_agent,
|
||||
AiAgentStreamRequest,
|
||||
"ai-agent-stream",
|
||||
crate::ai_agents::run_ai_agent_stream
|
||||
);
|
||||
fn normalize_agent_request(mut request: AiAgentStreamRequest) -> AiAgentStreamRequest {
|
||||
request.vault_path = expand_tilde(&request.vault_path).into_owned();
|
||||
request
|
||||
}
|
||||
|
||||
#[cfg(desktop)]
|
||||
#[tauri::command]
|
||||
pub async fn stream_ai_agent(
|
||||
app_handle: tauri::AppHandle,
|
||||
request: AiAgentStreamRequest,
|
||||
) -> Result<String, String> {
|
||||
run_desktop_stream(
|
||||
app_handle,
|
||||
"ai-agent-stream",
|
||||
normalize_agent_request(request),
|
||||
crate::ai_agents::run_ai_agent_stream,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
// ── Claude CLI (mobile stubs) ───────────────────────────────────────────────
|
||||
|
||||
@@ -150,6 +163,47 @@ mod tests {
|
||||
use super::*;
|
||||
use crate::vault::AiGuidanceFileState;
|
||||
|
||||
#[cfg(desktop)]
|
||||
#[test]
|
||||
fn normalize_agent_request_expands_tilde_in_vault_path() {
|
||||
use crate::ai_agents::AiAgentId;
|
||||
|
||||
let home = dirs::home_dir().unwrap();
|
||||
let request = AiAgentStreamRequest {
|
||||
agent: AiAgentId::ClaudeCode,
|
||||
message: "hi".into(),
|
||||
system_prompt: None,
|
||||
vault_path: "~/Vaults/content".into(),
|
||||
permission_mode: None,
|
||||
};
|
||||
|
||||
let normalized = normalize_agent_request(request);
|
||||
|
||||
assert_eq!(
|
||||
normalized.vault_path,
|
||||
format!("{}/Vaults/content", home.display()),
|
||||
"vault_path must be tilde-expanded so spawned agents can chdir into it",
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(desktop)]
|
||||
#[test]
|
||||
fn normalize_agent_request_leaves_absolute_vault_path_untouched() {
|
||||
use crate::ai_agents::AiAgentId;
|
||||
|
||||
let request = AiAgentStreamRequest {
|
||||
agent: AiAgentId::Codex,
|
||||
message: "hi".into(),
|
||||
system_prompt: None,
|
||||
vault_path: "/Users/example/vault".into(),
|
||||
permission_mode: None,
|
||||
};
|
||||
|
||||
let normalized = normalize_agent_request(request);
|
||||
|
||||
assert_eq!(normalized.vault_path, "/Users/example/vault");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn guidance_commands_report_and_restore_vault_guidance_files() {
|
||||
let dir = tempfile::TempDir::new().unwrap();
|
||||
|
||||
Reference in New Issue
Block a user