Use ~/.ao as canonical state home (#233)
* Use ~/.ao as canonical state home * chore: format with prettier [skip ci] --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
This commit is contained in:
parent
17df922652
commit
724b9a2d81
|
|
@ -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)
|
||||
//
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -54,10 +54,10 @@ 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_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. |
|
||||
|
||||
|
|
|
|||
|
|
@ -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 |
|
||||
|
|
|
|||
|
|
@ -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[]>();
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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") {
|
||||
void platform;
|
||||
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");
|
||||
return joinPath(homeDir, ".ao", "running.json");
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue