diff --git a/backend/internal/config/config.go b/backend/internal/config/config.go index d1318789..e2a9386c 100644 --- a/backend/internal/config/config.go +++ b/backend/internal/config/config.go @@ -87,8 +87,8 @@ func (c Config) Addr() string { // AO_PORT bind port (default 3001) // AO_REQUEST_TIMEOUT per-request timeout (Go duration > 0, default 60s) // AO_SHUTDOWN_TIMEOUT shutdown deadline (Go duration > 0, default 10s) -// AO_RUN_FILE running.json path (default /running.json) -// AO_DATA_DIR durable state dir (default /data) +// AO_RUN_FILE running.json path (default ~/.ao/running.json) +// AO_DATA_DIR durable state dir (default ~/.ao/data) // AO_AGENT agent adapter id (default claude-code) // AO_ALLOWED_ORIGINS CORS origins, comma-separated (default DefaultAllowedOrigins) // @@ -183,7 +183,7 @@ func parsePositiveDuration(name, raw string) (time.Duration, error) { } // resolveRunFilePath picks where running.json lives. An explicit AO_RUN_FILE -// wins; otherwise it sits under the per-user config directory so the CLI and +// wins; otherwise it sits under the canonical AO home directory so the CLI and // Electron supervisor share one handshake location. func resolveRunFilePath() (string, error) { if p, ok := os.LookupEnv("AO_RUN_FILE"); ok && p != "" { @@ -197,7 +197,7 @@ func resolveRunFilePath() (string, error) { } // resolveDataDir picks where durable state (the SQLite DB) lives. An explicit -// AO_DATA_DIR wins; otherwise it defaults under the same per-user config +// AO_DATA_DIR wins; otherwise it defaults under the same canonical AO home // directory as the run-file. func resolveDataDir() (string, error) { if p, ok := os.LookupEnv("AO_DATA_DIR"); ok && p != "" { @@ -211,9 +211,9 @@ func resolveDataDir() (string, error) { } func defaultStateDir() (string, error) { - configDir, err := os.UserConfigDir() + homeDir, err := os.UserHomeDir() if err != nil { return "", fmt.Errorf("resolve state dir: %w", err) } - return filepath.Join(configDir, "agent-orchestrator"), nil + return filepath.Join(homeDir, ".ao"), nil } diff --git a/backend/internal/config/config_test.go b/backend/internal/config/config_test.go index c85cc931..4ce22512 100644 --- a/backend/internal/config/config_test.go +++ b/backend/internal/config/config_test.go @@ -33,18 +33,18 @@ func TestLoadDefaults(t *testing.T) { if cfg.RunFilePath == "" { t.Error("RunFilePath is empty, want a resolved default path") } - configDir, err := os.UserConfigDir() + homeDir, err := os.UserHomeDir() if err != nil { - t.Fatalf("UserConfigDir: %v", err) + t.Fatalf("UserHomeDir: %v", err) } - wantRunFilePath := filepath.Join(configDir, "agent-orchestrator", "running.json") + wantRunFilePath := filepath.Join(homeDir, ".ao", "running.json") if cfg.RunFilePath != wantRunFilePath { t.Errorf("RunFilePath = %q, want %q", cfg.RunFilePath, wantRunFilePath) } if cfg.DataDir == "" { t.Error("DataDir is empty, want a resolved default path") } - wantDataDir := filepath.Join(configDir, "agent-orchestrator", "data") + wantDataDir := filepath.Join(homeDir, ".ao", "data") if cfg.DataDir != wantDataDir { t.Errorf("DataDir = %q, want %q", cfg.DataDir, wantDataDir) } diff --git a/docs/cli/README.md b/docs/cli/README.md index 6d21fa56..f874e33a 100644 --- a/docs/cli/README.md +++ b/docs/cli/README.md @@ -53,13 +53,13 @@ commands yet. The CLI and daemon share the same environment-driven config: -| Var | Default | Purpose | -| --------------------- | ------------------------------------------------- | ---------------------- | -| `AO_PORT` | `3001` | Loopback daemon port. | -| `AO_RUN_FILE` | `/agent-orchestrator/running.json` | PID/port handshake. | -| `AO_DATA_DIR` | `/agent-orchestrator/data` | SQLite data directory. | -| `AO_REQUEST_TIMEOUT` | `60s` | REST request timeout. | -| `AO_SHUTDOWN_TIMEOUT` | `10s` | Graceful shutdown cap. | +| Var | Default | Purpose | +| --------------------- | -------------------- | ---------------------- | +| `AO_PORT` | `3001` | Loopback daemon port. | +| `AO_RUN_FILE` | `~/.ao/running.json` | PID/port handshake. | +| `AO_DATA_DIR` | `~/.ao/data` | SQLite data directory. | +| `AO_REQUEST_TIMEOUT` | `60s` | REST request timeout. | +| `AO_SHUTDOWN_TIMEOUT` | `10s` | Graceful shutdown cap. | The daemon always binds `127.0.0.1`. diff --git a/frontend/src/landing/content/docs/cli.mdx b/frontend/src/landing/content/docs/cli.mdx index e18b0aca..ae1c674b 100644 --- a/frontend/src/landing/content/docs/cli.mdx +++ b/frontend/src/landing/content/docs/cli.mdx @@ -367,7 +367,7 @@ A few env vars affect every command: | Variable | Purpose | | ----------------------------------- | ------------------------------------------------------------------------------------ | -| `AO_DATA_DIR` | Override the `~/.agent-orchestrator` base directory. | +| `AO_DATA_DIR` | Override the `~/.ao` base directory. | | `AO_LOG_LEVEL` | `error` · `warn` · `info` · `debug` · `trace` | | `AO_CONFIG_PATH` | Absolute path to a specific `agent-orchestrator.yaml`. Overrides CWD-based search. | | `AO_SESSION_ID` · `AO_SESSION_NAME` | Auto-set inside spawned sessions; used by `claim-pr` default | diff --git a/frontend/src/renderer/components/SessionsBoard.tsx b/frontend/src/renderer/components/SessionsBoard.tsx index 6dcd622c..6649e1d5 100644 --- a/frontend/src/renderer/components/SessionsBoard.tsx +++ b/frontend/src/renderer/components/SessionsBoard.tsx @@ -76,9 +76,6 @@ export function SessionsBoard({ projectId }: SessionsBoardProps) { const workspaceQuery = useWorkspaceQuery(); const all = workspaceQuery.data ?? []; const workspaces = projectId ? all.filter((w) => w.id === projectId) : all; - // Project board leads with the project's name; the cross-project board - // (home) keeps the generic title. - const boardTitle = (projectId && workspaces[0]?.name) || "Board"; const sessions = workspaces.flatMap((w) => workerSessions(w.sessions)); const byZone = new Map(); diff --git a/frontend/src/shared/daemon-discovery.test.ts b/frontend/src/shared/daemon-discovery.test.ts index 4a439d08..c0939bfc 100644 --- a/frontend/src/shared/daemon-discovery.test.ts +++ b/frontend/src/shared/daemon-discovery.test.ts @@ -91,28 +91,25 @@ describe("parseRunFile", () => { }); describe("defaultRunFilePath", () => { - it("mirrors Go os.UserConfigDir on macOS", () => { - expect(defaultRunFilePath("darwin", {}, "/Users/me")).toBe( - "/Users/me/Library/Application Support/agent-orchestrator/running.json", - ); + it("matches Go's canonical AO home default on macOS", () => { + expect(defaultRunFilePath("darwin", {}, "/Users/me")).toBe("/Users/me/.ao/running.json"); }); - it("prefers XDG_CONFIG_HOME on linux, falling back to ~/.config", () => { - expect(defaultRunFilePath("linux", { XDG_CONFIG_HOME: "/xdg" }, "/home/me")).toBe( - "/xdg/agent-orchestrator/running.json", - ); - expect(defaultRunFilePath("linux", {}, "/home/me")).toBe("/home/me/.config/agent-orchestrator/running.json"); + it("ignores XDG_CONFIG_HOME on linux", () => { + expect(defaultRunFilePath("linux", { XDG_CONFIG_HOME: "/xdg" }, "/home/me")).toBe("/home/me/.ao/running.json"); + expect(defaultRunFilePath("linux", {}, "/home/me")).toBe("/home/me/.ao/running.json"); }); - it("uses APPDATA on windows and returns null when it is unset", () => { - expect(defaultRunFilePath("win32", { APPDATA: "C:\\Users\\me\\AppData\\Roaming" }, "C:\\Users\\me")).toContain( - "agent-orchestrator", + it("ignores APPDATA on windows", () => { + expect(defaultRunFilePath("win32", { APPDATA: "C:\\Users\\me\\AppData\\Roaming" }, "C:\\Users\\me")).toBe( + "C:\\Users\\me/.ao/running.json", ); - expect(defaultRunFilePath("win32", {}, "C:\\Users\\me")).toBeNull(); + expect(defaultRunFilePath("win32", {}, "C:\\Users\\me")).toBe("C:\\Users\\me/.ao/running.json"); }); - it("returns null when no config root can be resolved", () => { + it("returns null when no home directory can be resolved", () => { expect(defaultRunFilePath("linux", {}, "")).toBeNull(); expect(defaultRunFilePath("darwin", {}, "")).toBeNull(); + expect(defaultRunFilePath("win32", {}, "")).toBeNull(); }); }); diff --git a/frontend/src/shared/daemon-discovery.ts b/frontend/src/shared/daemon-discovery.ts index 79cddeb0..16f407cb 100644 --- a/frontend/src/shared/daemon-discovery.ts +++ b/frontend/src/shared/daemon-discovery.ts @@ -88,25 +88,17 @@ export function parseRunFile(contents: string): RunFileInfo | null { } /** - * Where the daemon writes running.json when AO_RUN_FILE is unset. Mirrors Go's - * os.UserConfigDir() + "agent-orchestrator/running.json" resolution in - * backend/internal/config so the supervisor reads the same file the daemon - * writes. Returns null when the platform's config root cannot be resolved. + * Where the daemon writes running.json when AO_RUN_FILE is unset. Matches + * backend/internal/config's canonical AO home default so the supervisor reads + * the same file the daemon writes. Returns null when the user home directory + * cannot be resolved. */ export function defaultRunFilePath( platform: NodeJS.Platform, - env: Record, + _env: Record, homeDir: string, ): string | null { - if (platform === "darwin") { - if (!homeDir) return null; - return joinPath(homeDir, "Library", "Application Support", "agent-orchestrator", "running.json"); - } - if (platform === "win32") { - if (!env.APPDATA) return null; - return joinPath(env.APPDATA, "agent-orchestrator", "running.json"); - } - const configRoot = env.XDG_CONFIG_HOME || (homeDir ? joinPath(homeDir, ".config") : ""); - if (!configRoot) return null; - return joinPath(configRoot, "agent-orchestrator", "running.json"); + void platform; + if (!homeDir) return null; + return joinPath(homeDir, ".ao", "running.json"); }