Skip to content
Open
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
5 changes: 2 additions & 3 deletions broker/broker.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import net from "net";
import { writeFileSync, unlinkSync, mkdirSync } from "fs";
import { join } from "path";
import { homedir } from "os";
import { randomUUID } from "crypto";
import { writeMessage, createMessageReader } from "./framing.js";
import { getBrokerSocketPath } from "./paths.js";
import { getBrokerSocketPath, getIntercomDir } from "./paths.js";
import type { SessionInfo, Message, Attachment, BrokerMessage } from "../types.js";

const INTERCOM_DIR = join(homedir(), ".pi/agent/intercom");
const INTERCOM_DIR = getIntercomDir();
const SOCKET_PATH = getBrokerSocketPath();
const PID_PATH = join(INTERCOM_DIR, "broker.pid");

Expand Down
38 changes: 36 additions & 2 deletions broker/paths.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,18 @@
import test from "node:test";
import assert from "node:assert/strict";
import { getBrokerSocketPath } from "./paths.js";
import { getAgentDir, getBrokerSocketPath, getIntercomDir } from "./paths.js";

function withAgentDirEnv<T>(value: string | undefined, fn: () => T): T {
const prev = process.env.PI_CODING_AGENT_DIR;
if (value === undefined) delete process.env.PI_CODING_AGENT_DIR;
else process.env.PI_CODING_AGENT_DIR = value;
try {
return fn();
} finally {
if (prev === undefined) delete process.env.PI_CODING_AGENT_DIR;
else process.env.PI_CODING_AGENT_DIR = prev;
}
}

test("getBrokerSocketPath uses named pipe on Windows", () => {
const pipePath = getBrokerSocketPath("win32", "C:/Users/rcroh");
Expand All @@ -9,7 +21,29 @@ test("getBrokerSocketPath uses named pipe on Windows", () => {
});

test("getBrokerSocketPath uses broker.sock on non-Windows", () => {
const socketPath = getBrokerSocketPath("linux", "/home/rcroh");
const socketPath = withAgentDirEnv(undefined, () => getBrokerSocketPath("linux", "/home/rcroh"));
assert.match(socketPath, /broker\.sock$/);
assert.match(socketPath, /rcroh/);
});

test("getAgentDir falls back to ~/.pi/agent and expands ~", () => {
withAgentDirEnv(undefined, () => {
assert.equal(getAgentDir("/home/rcroh"), "/home/rcroh/.pi/agent");
});
withAgentDirEnv("~", () => {
assert.equal(getAgentDir("/home/rcroh"), "/home/rcroh");
});
withAgentDirEnv("~/relocated", () => {
assert.equal(getAgentDir("/home/rcroh"), "/home/rcroh/relocated");
});
withAgentDirEnv("/abs/agent", () => {
assert.equal(getAgentDir("/home/rcroh"), "/abs/agent");
});
});

test("getIntercomDir and broker socket honor PI_CODING_AGENT_DIR", () => {
withAgentDirEnv("/abs/agent", () => {
assert.equal(getIntercomDir("/home/rcroh"), "/abs/agent/intercom");
assert.equal(getBrokerSocketPath("linux", "/home/rcroh"), "/abs/agent/intercom/broker.sock");
});
});
25 changes: 24 additions & 1 deletion broker/paths.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,29 @@ function sanitizePipeSegment(value: string): string {
.toLowerCase() || "default";
}

function expandAgentDir(value: string, homeDir: string): string {
if (value === "~") return homeDir;
if (value.startsWith("~/")) return join(homeDir, value.slice(2));
return value;
}

/**
* Resolve the Pi agent directory. Honors PI_CODING_AGENT_DIR (with `~` / `~/`
* expansion) to stay consistent with pi-subagents' getAgentDir, so a relocated
* agent dir keeps the intercom broker socket, pid, and config co-located with
* the rest of Pi's writable state. Falls back to `~/.pi/agent`.
*/
export function getAgentDir(homeDir: string = homedir()): string {
const configured = process.env.PI_CODING_AGENT_DIR;
if (configured) return expandAgentDir(configured, homeDir);
return join(homeDir, ".pi", "agent");
}

/** Resolve the intercom state directory (broker socket/pid/config) under the agent dir. */
export function getIntercomDir(homeDir: string = homedir()): string {
return join(getAgentDir(homeDir), "intercom");
}

export function getBrokerSocketPath(
platform: NodeJS.Platform = process.platform,
homeDir: string = homedir(),
Expand All @@ -16,5 +39,5 @@ export function getBrokerSocketPath(
return `\\\\.\\pipe\\pi-intercom-${sanitizePipeSegment(homeDir)}`;
}

return join(homeDir, ".pi/agent/intercom/broker.sock");
return join(getIntercomDir(homeDir), "broker.sock");
}
5 changes: 2 additions & 3 deletions broker/spawn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,10 @@ import { spawn } from "child_process";
import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "fs";
import { join, dirname } from "path";
import { fileURLToPath } from "url";
import { homedir } from "os";
import net from "net";
import { getBrokerSocketPath } from "./paths.js";
import { getBrokerSocketPath, getIntercomDir } from "./paths.js";

const INTERCOM_DIR = join(homedir(), ".pi/agent/intercom");
const INTERCOM_DIR = getIntercomDir();
const EXTENSION_DIR = join(dirname(fileURLToPath(import.meta.url)), "..");
const BROKER_SOCKET = getBrokerSocketPath();
const BROKER_PID = join(INTERCOM_DIR, "broker.pid");
Expand Down
15 changes: 9 additions & 6 deletions config.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { existsSync, readFileSync } from "fs";
import { join } from "path";
import { homedir } from "os";
import { getIntercomDir } from "./broker/paths.js";

export interface IntercomConfig {
/** Broker command used to spawn the broker process (e.g. "npx" or "bun") */
Expand All @@ -22,7 +22,9 @@ export interface IntercomConfig {
replyHint: boolean;
}

const CONFIG_PATH = join(homedir(), ".pi/agent/intercom/config.json");
function getConfigPath(): string {
return join(getIntercomDir(), "config.json");
}

const defaults: IntercomConfig = {
brokerCommand: "npx",
Expand All @@ -33,12 +35,13 @@ const defaults: IntercomConfig = {
};

export function loadConfig(): IntercomConfig {
if (!existsSync(CONFIG_PATH)) {
const configPath = getConfigPath();
if (!existsSync(configPath)) {
return { ...defaults };
}

try {
const raw = readFileSync(CONFIG_PATH, "utf-8");
const raw = readFileSync(configPath, "utf-8");
const parsed: unknown = JSON.parse(raw);
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
throw new Error("Config must be a JSON object");
Expand Down Expand Up @@ -102,7 +105,7 @@ export function loadConfig(): IntercomConfig {

return config;
} catch (error) {
console.error(`Failed to load intercom config at ${CONFIG_PATH}:`, error);
console.error(`Failed to load intercom config at ${configPath}:`, error);
return { ...defaults };
}
}