Skip to content
Closed
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
11 changes: 8 additions & 3 deletions src/plugins/pi/content/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,9 +74,14 @@ package name when needed.
Settings load in this order, with later values taking precedence:

1. Defaults
2. `~/.pi/agent/braintrust.json`
3. `.pi/braintrust.json` in the project (or Pi's configured project config directory)
4. `bt trace run` settings for that invocation
2. `~/.pi/agent/braintrust.json`, the file `bt trace enable pi` writes
3. `braintrust.json` in the running host's agent directory, when it differs from `~/.pi/agent`
4. `.pi/braintrust.json` in the project (or the running host's configured config directory)
5. `bt trace run` settings for that invocation

A host package's `piConfig.configDir` replaces `.pi` in steps 3 and 4. A host that
sets `PI_CODING_AGENT_DIR` (for example, for named profiles) uses that directory in
step 3.

Example:

Expand Down
56 changes: 55 additions & 1 deletion src/plugins/pi/content/src/config.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { dirname, join } from "node:path";
import { afterEach, beforeEach, describe, expect, it } from "vitest";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { loadConfig } from "./config.ts";

const ENVIRONMENT_KEYS = [
Expand All @@ -13,6 +13,7 @@ const ENVIRONMENT_KEYS = [
"BRAINTRUST_SHOW_TRACE_LINK",
"TRACE_TO_BRAINTRUST",
"BT_TRACE_INVOCATION_SETTINGS",
"PI_CODING_AGENT_DIR",
] as const;

const originalEnvironment = new Map<string, string | undefined>();
Expand Down Expand Up @@ -105,6 +106,59 @@ describe("loadConfig", () => {
});
});

it("honors the host's explicit agent directory without changing project precedence", () => {
process.env.PI_CODING_AGENT_DIR = join(home, ".omp", "profiles", "work", "agent");
writeJson(join(process.env.PI_CODING_AGENT_DIR, "braintrust.json"), {
trace_to_braintrust: true,
profile: "host-profile",
project: "profile-project",
});
expect(loadConfig(cwd)).toMatchObject({
enabled: true,
profile: "host-profile",
projectName: "profile-project",
});
writeJson(join(cwd, ".pi", "braintrust.json"), { project: "project-override" });
expect(loadConfig(cwd)).toMatchObject({
profile: "host-profile",
projectName: "project-override",
});
});

it("uses the running host's config directory for global and project settings", async () => {
vi.doMock("./pi-package.ts", () => ({
runningPiPackage: () => ({ configDir: ".omp", version: "18.12.6" }),
}));
vi.resetModules();
try {
// Reload the module boundary so its cached config directory sees this host.
const { loadConfig: loadHostConfig } = await import("./config.ts");
// `bt trace enable pi` writes Pi's global file; hosts still honor it.
writeJson(join(home, ".pi", "agent", "braintrust.json"), {
trace_to_braintrust: true,
project: "bt-written",
});
expect(loadHostConfig(cwd)).toMatchObject({ enabled: true, projectName: "bt-written" });
writeJson(join(home, ".omp", "agent", "braintrust.json"), {
profile: "host-profile",
project: "host-project",
});
expect(loadHostConfig(cwd)).toMatchObject({
enabled: true,
profile: "host-profile",
projectName: "host-project",
});
writeJson(join(cwd, ".omp", "braintrust.json"), { project: "project-override" });
expect(loadHostConfig(cwd)).toMatchObject({
profile: "host-profile",
projectName: "project-override",
});
} finally {
vi.doUnmock("./pi-package.ts");
vi.resetModules();
}
});

it("prefers a canonical route over stale top-level routing", () => {
writeJson(join(home, ".pi", "agent", "braintrust.json"), {
trace_to_braintrust: true,
Expand Down
15 changes: 11 additions & 4 deletions src/plugins/pi/content/src/config.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { existsSync, readFileSync } from "node:fs";
import { homedir } from "node:os";
import { join } from "node:path";
import { join, resolve } from "node:path";
import { type DaemonSessionRoute, resolveDaemonTraceSettings } from "./runtime/daemon-client.ts";
import { runningPiPackage } from "./pi-package.ts";

Expand All @@ -17,7 +17,7 @@ export interface PiConfig {

type ConfigRecord = Record<string, unknown>;

const PROJECT_CONFIG_DIR_NAME = runningPiPackage().configDir;
const CONFIG_DIR_NAME = runningPiPackage().configDir;

function record(value: unknown): ConfigRecord | undefined {
return value !== null && typeof value === "object" && !Array.isArray(value)
Expand Down Expand Up @@ -101,8 +101,15 @@ export function loadConfig(cwd = process.cwd()): PiConfig {
},
};

applyConfig(config, readConfig(join(homedir(), ".pi", "agent", "braintrust.json")));
applyConfig(config, readConfig(join(cwd, PROJECT_CONFIG_DIR_NAME, "braintrust.json")));
// `bt trace enable pi` writes Pi's global file, so it stays the base layer. A host
// with its own agent directory layers its settings on top.
const piAgentDir = join(homedir(), ".pi", "agent");
const hostAgentDir = process.env.PI_CODING_AGENT_DIR || join(homedir(), CONFIG_DIR_NAME, "agent");
applyConfig(config, readConfig(join(piAgentDir, "braintrust.json")));
if (resolve(hostAgentDir) !== resolve(piAgentDir)) {
applyConfig(config, readConfig(join(hostAgentDir, "braintrust.json")));
}
applyConfig(config, readConfig(join(cwd, CONFIG_DIR_NAME, "braintrust.json")));

config.showUi = boolean(process.env.BRAINTRUST_SHOW_UI) ?? config.showUi;
config.showTraceLink = boolean(process.env.BRAINTRUST_SHOW_TRACE_LINK) ?? config.showTraceLink;
Expand Down