new gateway

This commit is contained in:
Harivansh Rathi 2026-03-05 18:58:27 -08:00
parent 01958298e0
commit 9a0b848789
34 changed files with 1632 additions and 290 deletions

View file

@ -6,7 +6,7 @@
*/
import { spawnSync } from "node:child_process";
import { execCommand, type SpawnOptions, type TerminalAdapter } from "../utils/terminal-adapter";
import type { SpawnOptions, TerminalAdapter } from "../utils/terminal-adapter";
/**
* Context needed for iTerm2 spawning (tracks last pane for layout)

View file

@ -39,7 +39,7 @@ describe("WezTermAdapter", () => {
describe("spawn", () => {
it("should spawn first pane to the right with 50%", () => {
// Mock getPanes finding only current pane
mockExecCommand.mockImplementation((bin, args) => {
mockExecCommand.mockImplementation((_bin: string, args: string[]) => {
if (args.includes("list")) {
return {
stdout: JSON.stringify([{ pane_id: 0, tab_id: 0 }]),
@ -69,7 +69,7 @@ describe("WezTermAdapter", () => {
it("should spawn subsequent panes by splitting the sidebar", () => {
// Mock getPanes finding current pane (0) and sidebar pane (1)
mockExecCommand.mockImplementation((bin, args) => {
mockExecCommand.mockImplementation((_bin: string, args: string[]) => {
if (args.includes("list")) {
return {
stdout: JSON.stringify([

View file

@ -1,13 +1,12 @@
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { afterEach, beforeEach, describe, expect, it } from "vitest";
import { withLock } from "./lock";
describe("withLock race conditions", () => {
const testDir = path.join(os.tmpdir(), "pi-lock-race-test-" + Date.now());
const testDir = path.join(os.tmpdir(), `pi-lock-race-test-${Date.now()}`);
const lockPath = path.join(testDir, "test");
const lockFile = `${lockPath}.lock`;
beforeEach(() => {
if (!fs.existsSync(testDir)) fs.mkdirSync(testDir, { recursive: true });

View file

@ -7,7 +7,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { withLock } from "./lock";
describe("withLock", () => {
const testDir = path.join(os.tmpdir(), "pi-lock-test-" + Date.now());
const testDir = path.join(os.tmpdir(), `pi-lock-test-${Date.now()}`);
const lockPath = path.join(testDir, "test");
const lockFile = `${lockPath}.lock`;

View file

@ -1,8 +1,6 @@
// Project: pi-teams
import fs from "node:fs";
import path from "node:path";
const LOCK_TIMEOUT = 5000; // 5 seconds of retrying
const STALE_LOCK_TIMEOUT = 30000; // 30 seconds for a lock to be considered stale
export async function withLock<T>(lockPath: string, fn: () => Promise<T>, retries: number = 50): Promise<T> {
@ -18,7 +16,7 @@ export async function withLock<T>(lockPath: string, fn: () => Promise<T>, retrie
// Attempt to remove stale lock
try {
fs.unlinkSync(lockFile);
} catch (e) {
} catch (_error) {
// ignore, another process might have already removed it
}
}
@ -26,7 +24,7 @@ export async function withLock<T>(lockPath: string, fn: () => Promise<T>, retrie
fs.writeFileSync(lockFile, process.pid.toString(), { flag: "wx" });
break;
} catch (e) {
} catch (_error) {
retries--;
await new Promise((resolve) => setTimeout(resolve, 100));
}
@ -41,7 +39,7 @@ export async function withLock<T>(lockPath: string, fn: () => Promise<T>, retrie
} finally {
try {
fs.unlinkSync(lockFile);
} catch (e) {
} catch (_error) {
// ignore
}
}

View file

@ -6,7 +6,7 @@ import { appendMessage, broadcastMessage, readInbox, sendPlainMessage } from "./
import * as paths from "./paths";
// Mock the paths to use a temporary directory
const testDir = path.join(os.tmpdir(), "pi-teams-test-" + Date.now());
const testDir = path.join(os.tmpdir(), `pi-teams-test-${Date.now()}`);
describe("Messaging Utilities", () => {
beforeEach(() => {
@ -14,11 +14,11 @@ describe("Messaging Utilities", () => {
fs.mkdirSync(testDir, { recursive: true });
// Override paths to use testDir
vi.spyOn(paths, "inboxPath").mockImplementation((teamName, agentName) => {
vi.spyOn(paths, "inboxPath").mockImplementation((_teamName, agentName) => {
return path.join(testDir, "inboxes", `${agentName}.json`);
});
vi.spyOn(paths, "teamDir").mockReturnValue(testDir);
vi.spyOn(paths, "configPath").mockImplementation((teamName) => {
vi.spyOn(paths, "configPath").mockImplementation((_teamName) => {
return path.join(testDir, "config.json");
});
});

View file

@ -103,6 +103,8 @@ export async function broadcastMessage(
if (failures.length > 0) {
console.error(`Broadcast partially failed: ${failures.length} messages could not be delivered.`);
// Optionally log individual errors
failures.forEach((f) => console.error(`- Delivery error:`, f.reason));
for (const failure of failures) {
console.error("- Delivery error:", failure.reason);
}
}
}

View file

@ -1,6 +1,3 @@
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { describe, expect, it } from "vitest";
import { inboxPath, sanitizeName, teamDir } from "./paths";
@ -17,7 +14,6 @@ describe("Security Audit - Path Traversal (Prevention Check)", () => {
});
it("should throw an error for path traversal via taskId", () => {
const teamName = "audit-team";
const maliciousTaskId = "../../../etc/passwd";
// We need to import readTask/updateTask or just sanitizeName directly if we want to test the logic
// But since we already tested sanitizeName via other paths, this is just for completeness.

View file

@ -3,9 +3,9 @@ import os from "node:os";
import path from "node:path";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import * as paths from "./paths";
import { createTask, listTasks } from "./tasks";
import { createTask } from "./tasks";
const testDir = path.join(os.tmpdir(), "pi-tasks-race-test-" + Date.now());
const testDir = path.join(os.tmpdir(), `pi-tasks-race-test-${Date.now()}`);
describe("Tasks Race Condition Bug", () => {
beforeEach(() => {

View file

@ -6,10 +6,9 @@ import path from "node:path";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import * as paths from "./paths";
import { createTask, evaluatePlan, listTasks, readTask, submitPlan, updateTask } from "./tasks";
import * as teams from "./teams";
// Mock the paths to use a temporary directory
const testDir = path.join(os.tmpdir(), "pi-teams-test-" + Date.now());
const testDir = path.join(os.tmpdir(), `pi-teams-test-${Date.now()}`);
describe("Tasks Utilities", () => {
beforeEach(() => {

View file

@ -10,7 +10,7 @@ import { teamExists } from "./teams";
export function getTaskId(teamName: string): string {
const dir = taskDir(teamName);
const files = fs.readdirSync(dir).filter((f) => f.endsWith(".json"));
const ids = files.map((f) => parseInt(path.parse(f).name, 10)).filter((id) => !isNaN(id));
const ids = files.map((f) => parseInt(path.parse(f).name, 10)).filter((id) => !Number.isNaN(id));
return ids.length > 0 ? (Math.max(...ids) + 1).toString() : "1";
}
@ -169,7 +169,7 @@ export async function listTasks(teamName: string): Promise<TaskFile[]> {
const tasks: TaskFile[] = files
.map((f) => {
const id = parseInt(path.parse(f).name, 10);
if (isNaN(id)) return null;
if (Number.isNaN(id)) return null;
return JSON.parse(fs.readFileSync(path.join(dir, f), "utf-8"));
})
.filter((t) => t !== null);

View file

@ -1,5 +1,4 @@
import fs from "node:fs";
import path from "node:path";
import { withLock } from "./lock";
import type { Member, TeamConfig } from "./models";
import { configPath, taskDir, teamDir } from "./paths";