From 946d236d9f8ae66e6994f0f9824b54c6036c802f Mon Sep 17 00:00:00 2001 From: mmhiyoko Date: Thu, 22 Jan 2026 16:15:25 +0900 Subject: [PATCH] fix: use ~/.agent-browser for socket files instead of TMPDIR (#180) * fix: use ~/.agent-browser for socket files instead of TMPDIR This fixes issue #163 where different TMPDIR values (common with tmux/screen/VSCode/IntelliJ) caused the CLI and daemon to use different socket paths. Socket directory priority: 1. AGENT_BROWSER_SOCKET_DIR (explicit override) 2. $XDG_RUNTIME_DIR/agent-browser (Linux standard) 3. ~/.agent-browser (fallback, like Docker Desktop) Both CLI (Rust) and daemon (Node.js) now use the same logic. Co-Authored-By: Claude Opus 4.5 * fix: session list now looks in correct socket directory - Make get_socket_dir() public in connection.rs - Update session list to use get_socket_dir() instead of temp_dir() - Update pid file pattern from agent-browser-{session}.pid to {session}.pid - Add tmpdir fallback to daemon.ts when homedir is unavailable Co-Authored-By: Claude Opus 4.5 * test: add unit tests for socket directory resolution Add comprehensive tests for get_socket_dir/getSocketDir to verify: - AGENT_BROWSER_SOCKET_DIR takes priority - Empty strings are ignored (fixes Rust/TypeScript consistency) - XDG_RUNTIME_DIR fallback works correctly - Home directory fallback when env vars unset Co-Authored-By: Claude Opus 4.5 --------- Co-authored-by: Claude Opus 4.5 --- cli/Cargo.lock | 182 +++++++++++++++++++++++++++++++++++++++--- cli/Cargo.toml | 1 + cli/src/connection.rs | 130 ++++++++++++++++++++++++++++-- cli/src/main.rs | 17 ++-- src/daemon.test.ts | 60 ++++++++++++++ src/daemon.ts | 41 ++++++++-- 6 files changed, 401 insertions(+), 30 deletions(-) create mode 100644 src/daemon.test.ts diff --git a/cli/Cargo.lock b/cli/Cargo.lock index e0b3c02..b4230b9 100644 --- a/cli/Cargo.lock +++ b/cli/Cargo.lock @@ -6,10 +6,55 @@ version = 4 name = "agent-browser" version = "0.6.0" dependencies = [ + "dirs", "libc", "serde", "serde_json", - "windows-sys", + "windows-sys 0.52.0", +] + +[[package]] +name = "bitflags" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "dirs" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.48.0", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "libc", + "wasi", ] [[package]] @@ -24,12 +69,28 @@ version = "0.2.180" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" +[[package]] +name = "libredox" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" +dependencies = [ + "bitflags", + "libc", +] + [[package]] name = "memchr" version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + [[package]] name = "proc-macro2" version = "1.0.105" @@ -48,6 +109,17 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "redox_users" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +dependencies = [ + "getrandom", + "libredox", + "thiserror", +] + [[package]] name = "serde" version = "1.0.228" @@ -102,19 +174,69 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "unicode-ident" version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", ] [[package]] @@ -123,28 +245,46 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -157,24 +297,48 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + [[package]] name = "windows_x86_64_msvc" version = "0.52.6" diff --git a/cli/Cargo.toml b/cli/Cargo.toml index f49de10..d3cde4d 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -8,6 +8,7 @@ license = "Apache-2.0" [dependencies] serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" +dirs = "5.0" [target.'cfg(unix)'.dependencies] libc = "0.2" diff --git a/cli/src/connection.rs b/cli/src/connection.rs index e8dae1f..7221335 100644 --- a/cli/src/connection.rs +++ b/cli/src/connection.rs @@ -81,21 +81,44 @@ impl Connection { } } +/// Get the base directory for socket/pid files. +/// Priority: AGENT_BROWSER_SOCKET_DIR > XDG_RUNTIME_DIR > ~/.agent-browser > tmpdir +pub fn get_socket_dir() -> PathBuf { + // 1. Explicit override (ignore empty string) + if let Ok(dir) = env::var("AGENT_BROWSER_SOCKET_DIR") { + if !dir.is_empty() { + return PathBuf::from(dir); + } + } + + // 2. XDG_RUNTIME_DIR (Linux standard, ignore empty string) + if let Ok(runtime_dir) = env::var("XDG_RUNTIME_DIR") { + if !runtime_dir.is_empty() { + return PathBuf::from(runtime_dir).join("agent-browser"); + } + } + + // 3. Home directory fallback (like Docker Desktop's ~/.docker/run/) + if let Some(home) = dirs::home_dir() { + return home.join(".agent-browser"); + } + + // 4. Last resort: temp dir + env::temp_dir().join("agent-browser") +} + #[cfg(unix)] fn get_socket_path(session: &str) -> PathBuf { - let tmp = env::temp_dir(); - tmp.join(format!("agent-browser-{}.sock", session)) + get_socket_dir().join(format!("{}.sock", session)) } fn get_pid_path(session: &str) -> PathBuf { - let tmp = env::temp_dir(); - tmp.join(format!("agent-browser-{}.pid", session)) + get_socket_dir().join(format!("{}.pid", session)) } #[cfg(windows)] fn get_port_path(session: &str) -> PathBuf { - let tmp = env::temp_dir(); - tmp.join(format!("agent-browser-{}.port", session)) + get_socket_dir().join(format!("{}.port", session)) } #[cfg(windows)] @@ -178,6 +201,12 @@ pub fn ensure_daemon( }); } + // Ensure socket directory exists + let socket_dir = get_socket_dir(); + if !socket_dir.exists() { + fs::create_dir_all(&socket_dir).map_err(|e| format!("Failed to create socket directory: {}", e))?; + } + let exe_path = env::current_exe().map_err(|e| e.to_string())?; let exe_dir = exe_path.parent().unwrap(); @@ -354,3 +383,92 @@ pub fn send_command(cmd: Value, session: &str) -> Result { serde_json::from_str(&response_line).map_err(|e| format!("Invalid response: {}", e)) } + +#[cfg(test)] +mod tests { + use super::*; + use std::sync::{Mutex, MutexGuard}; + + // Mutex to prevent parallel tests from interfering with env vars + static ENV_MUTEX: Mutex<()> = Mutex::new(()); + + /// RAII guard that locks env mutex and restores env vars on drop + struct EnvGuard<'a> { + _lock: MutexGuard<'a, ()>, + vars: Vec<(String, Option)>, + } + + impl<'a> EnvGuard<'a> { + fn new(var_names: &[&str]) -> Self { + let lock = ENV_MUTEX.lock().unwrap(); + let vars = var_names + .iter() + .map(|&name| (name.to_string(), env::var(name).ok())) + .collect(); + Self { _lock: lock, vars } + } + } + + impl Drop for EnvGuard<'_> { + fn drop(&mut self) { + for (name, value) in &self.vars { + match value { + Some(v) => env::set_var(name, v), + None => env::remove_var(name), + } + } + } + } + + #[test] + fn test_get_socket_dir_explicit_override() { + let _guard = EnvGuard::new(&["AGENT_BROWSER_SOCKET_DIR", "XDG_RUNTIME_DIR"]); + + env::set_var("AGENT_BROWSER_SOCKET_DIR", "/custom/socket/path"); + env::remove_var("XDG_RUNTIME_DIR"); + + assert_eq!(get_socket_dir(), PathBuf::from("/custom/socket/path")); + } + + #[test] + fn test_get_socket_dir_ignores_empty_socket_dir() { + let _guard = EnvGuard::new(&["AGENT_BROWSER_SOCKET_DIR", "XDG_RUNTIME_DIR"]); + + env::set_var("AGENT_BROWSER_SOCKET_DIR", ""); + env::remove_var("XDG_RUNTIME_DIR"); + + assert!(get_socket_dir().to_string_lossy().ends_with(".agent-browser")); + } + + #[test] + fn test_get_socket_dir_xdg_runtime() { + let _guard = EnvGuard::new(&["AGENT_BROWSER_SOCKET_DIR", "XDG_RUNTIME_DIR"]); + + env::remove_var("AGENT_BROWSER_SOCKET_DIR"); + env::set_var("XDG_RUNTIME_DIR", "/run/user/1000"); + + assert_eq!(get_socket_dir(), PathBuf::from("/run/user/1000/agent-browser")); + } + + #[test] + fn test_get_socket_dir_ignores_empty_xdg_runtime() { + let _guard = EnvGuard::new(&["AGENT_BROWSER_SOCKET_DIR", "XDG_RUNTIME_DIR"]); + + env::set_var("AGENT_BROWSER_SOCKET_DIR", ""); + env::set_var("XDG_RUNTIME_DIR", ""); + + assert!(get_socket_dir().to_string_lossy().ends_with(".agent-browser")); + } + + #[test] + fn test_get_socket_dir_home_fallback() { + let _guard = EnvGuard::new(&["AGENT_BROWSER_SOCKET_DIR", "XDG_RUNTIME_DIR"]); + + env::remove_var("AGENT_BROWSER_SOCKET_DIR"); + env::remove_var("XDG_RUNTIME_DIR"); + + let result = get_socket_dir(); + assert!(result.to_string_lossy().ends_with(".agent-browser")); + assert!(result.to_string_lossy().contains("home") || result.to_string_lossy().contains("Users")); + } +} diff --git a/cli/src/main.rs b/cli/src/main.rs index 52f2b68..e774d20 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -19,7 +19,7 @@ use windows_sys::Win32::Foundation::CloseHandle; use windows_sys::Win32::System::Threading::{OpenProcess, PROCESS_QUERY_LIMITED_INFORMATION}; use commands::{gen_id, parse_command, ParseError}; -use connection::{ensure_daemon, send_command}; +use connection::{ensure_daemon, get_socket_dir, send_command}; use flags::{clean_args, parse_flags}; use install::run_install; use output::{print_command_help, print_help, print_response, print_version}; @@ -59,21 +59,18 @@ fn run_session(args: &[String], session: &str, json_mode: bool) { match subcommand { Some("list") => { - let tmp = env::temp_dir(); + let socket_dir = get_socket_dir(); let mut sessions: Vec = Vec::new(); - if let Ok(entries) = fs::read_dir(&tmp) { + if let Ok(entries) = fs::read_dir(&socket_dir) { for entry in entries.flatten() { let name = entry.file_name().to_string_lossy().to_string(); - // Look for socket files (Unix) or pid files - if name.starts_with("agent-browser-") && name.ends_with(".pid") { - let session_name = name - .strip_prefix("agent-browser-") - .and_then(|s| s.strip_suffix(".pid")) - .unwrap_or(""); + // Look for pid files in socket directory + if name.ends_with(".pid") { + let session_name = name.strip_suffix(".pid").unwrap_or(""); if !session_name.is_empty() { // Check if session is actually running - let pid_path = tmp.join(&name); + let pid_path = socket_dir.join(&name); if let Ok(pid_str) = fs::read_to_string(&pid_path) { if let Ok(pid) = pid_str.trim().parse::() { #[cfg(unix)] diff --git a/src/daemon.test.ts b/src/daemon.test.ts new file mode 100644 index 0000000..8825b65 --- /dev/null +++ b/src/daemon.test.ts @@ -0,0 +1,60 @@ +import { describe, it, expect, beforeEach, afterEach } from 'vitest'; +import * as os from 'os'; +import * as path from 'path'; +import { getSocketDir } from './daemon.js'; + +describe('getSocketDir', () => { + const originalEnv = { ...process.env }; + + beforeEach(() => { + // Clear relevant env vars before each test + delete process.env.AGENT_BROWSER_SOCKET_DIR; + delete process.env.XDG_RUNTIME_DIR; + }); + + afterEach(() => { + // Restore original env + process.env = { ...originalEnv }; + }); + + describe('AGENT_BROWSER_SOCKET_DIR', () => { + it('should use custom path when set', () => { + process.env.AGENT_BROWSER_SOCKET_DIR = '/custom/socket/path'; + expect(getSocketDir()).toBe('/custom/socket/path'); + }); + + it('should ignore empty string', () => { + process.env.AGENT_BROWSER_SOCKET_DIR = ''; + const result = getSocketDir(); + expect(result).toContain('.agent-browser'); + }); + + it('should take priority over XDG_RUNTIME_DIR', () => { + process.env.AGENT_BROWSER_SOCKET_DIR = '/custom/path'; + process.env.XDG_RUNTIME_DIR = '/run/user/1000'; + expect(getSocketDir()).toBe('/custom/path'); + }); + }); + + describe('XDG_RUNTIME_DIR', () => { + it('should use when AGENT_BROWSER_SOCKET_DIR is not set', () => { + process.env.XDG_RUNTIME_DIR = '/run/user/1000'; + expect(getSocketDir()).toBe('/run/user/1000/agent-browser'); + }); + + it('should ignore empty string', () => { + process.env.AGENT_BROWSER_SOCKET_DIR = ''; + process.env.XDG_RUNTIME_DIR = ''; + const result = getSocketDir(); + expect(result).toContain('.agent-browser'); + }); + }); + + describe('fallback', () => { + it('should use home directory when env vars are not set', () => { + const result = getSocketDir(); + const expected = path.join(os.homedir(), '.agent-browser'); + expect(result).toBe(expected); + }); + }); +}); diff --git a/src/daemon.ts b/src/daemon.ts index d65959b..0f0fb08 100644 --- a/src/daemon.ts +++ b/src/daemon.ts @@ -47,6 +47,31 @@ function getPortForSession(session: string): number { return 49152 + (Math.abs(hash) % 16383); } +/** + * Get the base directory for socket/pid files. + * Priority: AGENT_BROWSER_SOCKET_DIR > XDG_RUNTIME_DIR > ~/.agent-browser > tmpdir + */ +export function getSocketDir(): string { + // 1. Explicit override + if (process.env.AGENT_BROWSER_SOCKET_DIR) { + return process.env.AGENT_BROWSER_SOCKET_DIR; + } + + // 2. XDG_RUNTIME_DIR (Linux standard) + if (process.env.XDG_RUNTIME_DIR) { + return path.join(process.env.XDG_RUNTIME_DIR, 'agent-browser'); + } + + // 3. Home directory fallback (like Docker Desktop's ~/.docker/run/) + const homeDir = os.homedir(); + if (homeDir) { + return path.join(homeDir, '.agent-browser'); + } + + // 4. Last resort: temp dir + return path.join(os.tmpdir(), 'agent-browser'); +} + /** * Get the socket path for the current session (Unix) or port (Windows) */ @@ -55,7 +80,7 @@ export function getSocketPath(session?: string): string { if (isWindows) { return String(getPortForSession(sess)); } - return path.join(os.tmpdir(), `agent-browser-${sess}.sock`); + return path.join(getSocketDir(), `${sess}.sock`); } /** @@ -63,7 +88,7 @@ export function getSocketPath(session?: string): string { */ export function getPortFile(session?: string): string { const sess = session ?? currentSession; - return path.join(os.tmpdir(), `agent-browser-${sess}.port`); + return path.join(getSocketDir(), `${sess}.port`); } /** @@ -71,7 +96,7 @@ export function getPortFile(session?: string): string { */ export function getPidFile(session?: string): string { const sess = session ?? currentSession; - return path.join(os.tmpdir(), `agent-browser-${sess}.pid`); + return path.join(getSocketDir(), `${sess}.pid`); } /** @@ -104,7 +129,7 @@ export function getConnectionInfo( if (isWindows) { return { type: 'tcp', port: getPortForSession(sess) }; } - return { type: 'unix', path: path.join(os.tmpdir(), `agent-browser-${sess}.sock`) }; + return { type: 'unix', path: path.join(getSocketDir(), `${sess}.sock`) }; } /** @@ -133,7 +158,7 @@ export function cleanupSocket(session?: string): void { */ export function getStreamPortFile(session?: string): string { const sess = session ?? currentSession; - return path.join(os.tmpdir(), `agent-browser-${sess}.stream`); + return path.join(getSocketDir(), `${sess}.stream`); } /** @@ -141,6 +166,12 @@ export function getStreamPortFile(session?: string): string { * @param options.streamPort Port for WebSocket stream server (0 to disable) */ export async function startDaemon(options?: { streamPort?: number }): Promise { + // Ensure socket directory exists + const socketDir = getSocketDir(); + if (!fs.existsSync(socketDir)) { + fs.mkdirSync(socketDir, { recursive: true }); + } + // Clean up any stale socket cleanupSocket();