Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions backend/internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 <state-dir>/running.json)
// AO_DATA_DIR durable state dir (default <state-dir>/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)
//
Expand Down Expand Up @@ -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 != "" {
Expand All @@ -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 != "" {
Expand All @@ -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
}
8 changes: 4 additions & 4 deletions backend/internal/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
14 changes: 7 additions & 7 deletions docs/cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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` | `<UserConfigDir>/agent-orchestrator/running.json` | PID/port handshake. |
| `AO_DATA_DIR` | `<UserConfigDir>/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`.

Expand Down
2 changes: 1 addition & 1 deletion frontend/src/landing/content/docs/cli.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
Expand Down
3 changes: 0 additions & 3 deletions frontend/src/renderer/components/SessionsBoard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<AttentionZone, WorkspaceSession[]>();
Expand Down
25 changes: 11 additions & 14 deletions frontend/src/shared/daemon-discovery.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
});
});
24 changes: 8 additions & 16 deletions frontend/src/shared/daemon-discovery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, string | undefined>,
_env: Record<string, string | undefined>,
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");
}