chore: sync workspace changes

This commit is contained in:
Nathan Flurry 2026-01-27 05:06:33 -08:00
parent d24f983e2c
commit bf58891edf
139 changed files with 5454 additions and 8986 deletions

View file

@ -1,5 +1,5 @@
import { describe, it, expect, vi, type Mock } from "vitest";
import { SandboxDaemonClient, SandboxDaemonError } from "../src/client.ts";
import { SandboxAgent, SandboxAgentError } from "../src/client.ts";
function createMockFetch(
response: unknown,
@ -23,18 +23,18 @@ function createMockFetchError(status: number, problem: unknown): Mock<typeof fet
);
}
describe("SandboxDaemonClient", () => {
describe("constructor", () => {
it("creates client with baseUrl", () => {
const client = new SandboxDaemonClient({
describe("SandboxAgent", () => {
describe("connect", () => {
it("creates client with baseUrl", async () => {
const client = await SandboxAgent.connect({
baseUrl: "http://localhost:8080",
});
expect(client).toBeInstanceOf(SandboxDaemonClient);
expect(client).toBeInstanceOf(SandboxAgent);
});
it("strips trailing slash from baseUrl", async () => {
const mockFetch = createMockFetch({ status: "ok" });
const client = new SandboxDaemonClient({
const client = await SandboxAgent.connect({
baseUrl: "http://localhost:8080/",
fetch: mockFetch,
});
@ -47,41 +47,33 @@ describe("SandboxDaemonClient", () => {
);
});
it("throws if fetch is not available", () => {
it("throws if fetch is not available", async () => {
const originalFetch = globalThis.fetch;
// @ts-expect-error - testing missing fetch
globalThis.fetch = undefined;
expect(() => {
new SandboxDaemonClient({
await expect(
SandboxAgent.connect({
baseUrl: "http://localhost:8080",
});
}).toThrow("Fetch API is not available");
})
).rejects.toThrow("Fetch API is not available");
globalThis.fetch = originalFetch;
});
});
describe("connect", () => {
it("creates client without spawn when baseUrl provided", async () => {
const client = await SandboxDaemonClient.connect({
baseUrl: "http://localhost:8080",
spawn: false,
});
expect(client).toBeInstanceOf(SandboxDaemonClient);
});
it("throws when no baseUrl and spawn disabled", async () => {
await expect(
SandboxDaemonClient.connect({ spawn: false })
).rejects.toThrow("baseUrl is required when autospawn is disabled");
describe("start", () => {
it("rejects when spawn disabled", async () => {
await expect(SandboxAgent.start({ spawn: false })).rejects.toThrow(
"SandboxAgent.start requires spawn to be enabled."
);
});
});
describe("getHealth", () => {
it("returns health response", async () => {
const mockFetch = createMockFetch({ status: "ok" });
const client = new SandboxDaemonClient({
const client = await SandboxAgent.connect({
baseUrl: "http://localhost:8080",
fetch: mockFetch,
});
@ -100,7 +92,7 @@ describe("SandboxDaemonClient", () => {
it("returns agent list", async () => {
const agents = { agents: [{ id: "claude", installed: true }] };
const mockFetch = createMockFetch(agents);
const client = new SandboxDaemonClient({
const client = await SandboxAgent.connect({
baseUrl: "http://localhost:8080",
fetch: mockFetch,
});
@ -115,7 +107,7 @@ describe("SandboxDaemonClient", () => {
it("creates session with agent", async () => {
const response = { healthy: true, agentSessionId: "abc123" };
const mockFetch = createMockFetch(response);
const client = new SandboxDaemonClient({
const client = await SandboxAgent.connect({
baseUrl: "http://localhost:8080",
fetch: mockFetch,
});
@ -136,7 +128,7 @@ describe("SandboxDaemonClient", () => {
it("encodes session ID in URL", async () => {
const mockFetch = createMockFetch({ healthy: true });
const client = new SandboxDaemonClient({
const client = await SandboxAgent.connect({
baseUrl: "http://localhost:8080",
fetch: mockFetch,
});
@ -155,7 +147,7 @@ describe("SandboxDaemonClient", () => {
const mockFetch = vi.fn().mockResolvedValue(
new Response(null, { status: 204 })
);
const client = new SandboxDaemonClient({
const client = await SandboxAgent.connect({
baseUrl: "http://localhost:8080",
fetch: mockFetch,
});
@ -176,7 +168,7 @@ describe("SandboxDaemonClient", () => {
it("returns events", async () => {
const events = { events: [], hasMore: false };
const mockFetch = createMockFetch(events);
const client = new SandboxDaemonClient({
const client = await SandboxAgent.connect({
baseUrl: "http://localhost:8080",
fetch: mockFetch,
});
@ -188,7 +180,7 @@ describe("SandboxDaemonClient", () => {
it("passes query parameters", async () => {
const mockFetch = createMockFetch({ events: [], hasMore: false });
const client = new SandboxDaemonClient({
const client = await SandboxAgent.connect({
baseUrl: "http://localhost:8080",
fetch: mockFetch,
});
@ -205,7 +197,7 @@ describe("SandboxDaemonClient", () => {
describe("authentication", () => {
it("includes authorization header when token provided", async () => {
const mockFetch = createMockFetch({ status: "ok" });
const client = new SandboxDaemonClient({
const client = await SandboxAgent.connect({
baseUrl: "http://localhost:8080",
token: "test-token",
fetch: mockFetch,
@ -227,7 +219,7 @@ describe("SandboxDaemonClient", () => {
});
describe("error handling", () => {
it("throws SandboxDaemonError on non-ok response", async () => {
it("throws SandboxAgentError on non-ok response", async () => {
const problem = {
type: "error",
title: "Not Found",
@ -235,20 +227,20 @@ describe("SandboxDaemonClient", () => {
detail: "Session not found",
};
const mockFetch = createMockFetchError(404, problem);
const client = new SandboxDaemonClient({
const client = await SandboxAgent.connect({
baseUrl: "http://localhost:8080",
fetch: mockFetch,
});
await expect(client.getEvents("nonexistent")).rejects.toThrow(
SandboxDaemonError
SandboxAgentError
);
try {
await client.getEvents("nonexistent");
} catch (e) {
expect(e).toBeInstanceOf(SandboxDaemonError);
const error = e as SandboxDaemonError;
expect(e).toBeInstanceOf(SandboxAgentError);
const error = e as SandboxAgentError;
expect(error.status).toBe(404);
expect(error.problem?.title).toBe("Not Found");
}
@ -260,7 +252,7 @@ describe("SandboxDaemonClient", () => {
const mockFetch = vi.fn().mockResolvedValue(
new Response(null, { status: 204 })
);
const client = new SandboxDaemonClient({
const client = await SandboxAgent.connect({
baseUrl: "http://localhost:8080",
fetch: mockFetch,
});
@ -284,7 +276,7 @@ describe("SandboxDaemonClient", () => {
const mockFetch = vi.fn().mockResolvedValue(
new Response(null, { status: 204 })
);
const client = new SandboxDaemonClient({
const client = await SandboxAgent.connect({
baseUrl: "http://localhost:8080",
fetch: mockFetch,
});

View file

@ -3,8 +3,8 @@ import { existsSync } from "node:fs";
import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { type ChildProcess } from "node:child_process";
import { SandboxDaemonClient } from "../src/client.ts";
import { spawnSandboxDaemon, isNodeRuntime } from "../src/spawn.ts";
import { SandboxAgent } from "../src/client.ts";
import { spawnSandboxAgent, isNodeRuntime } from "../src/spawn.ts";
const __dirname = dirname(fileURLToPath(import.meta.url));
@ -38,8 +38,8 @@ if (BINARY_PATH && !process.env.SANDBOX_AGENT_BIN) {
}
describe.skipIf(SKIP_INTEGRATION)("Integration: spawn (local mode)", () => {
it("spawns daemon and connects", async () => {
const handle = await spawnSandboxDaemon({
it("spawns server and connects", async () => {
const handle = await spawnSandboxAgent({
enabled: true,
log: "silent",
timeoutMs: 30000,
@ -49,7 +49,7 @@ describe.skipIf(SKIP_INTEGRATION)("Integration: spawn (local mode)", () => {
expect(handle.baseUrl).toMatch(/^http:\/\/127\.0\.0\.1:\d+$/);
expect(handle.token).toBeTruthy();
const client = new SandboxDaemonClient({
const client = await SandboxAgent.connect({
baseUrl: handle.baseUrl,
token: handle.token,
});
@ -61,8 +61,8 @@ describe.skipIf(SKIP_INTEGRATION)("Integration: spawn (local mode)", () => {
}
});
it("SandboxDaemonClient.connect spawns automatically", async () => {
const client = await SandboxDaemonClient.connect({
it("SandboxAgent.start spawns automatically", async () => {
const client = await SandboxAgent.start({
spawn: { log: "silent", timeoutMs: 30000 },
});
@ -79,7 +79,7 @@ describe.skipIf(SKIP_INTEGRATION)("Integration: spawn (local mode)", () => {
});
it("lists available agents", async () => {
const client = await SandboxDaemonClient.connect({
const client = await SandboxAgent.start({
spawn: { log: "silent", timeoutMs: 30000 },
});
@ -95,31 +95,31 @@ describe.skipIf(SKIP_INTEGRATION)("Integration: spawn (local mode)", () => {
});
describe.skipIf(SKIP_INTEGRATION)("Integration: connect (remote mode)", () => {
let daemonProcess: ChildProcess;
let serverProcess: ChildProcess;
let baseUrl: string;
let token: string;
beforeAll(async () => {
// Start daemon manually to simulate remote server
const handle = await spawnSandboxDaemon({
// Start server manually to simulate remote server
const handle = await spawnSandboxAgent({
enabled: true,
log: "silent",
timeoutMs: 30000,
});
daemonProcess = handle.child;
serverProcess = handle.child;
baseUrl = handle.baseUrl;
token = handle.token;
});
afterAll(async () => {
if (daemonProcess && daemonProcess.exitCode === null) {
daemonProcess.kill("SIGTERM");
if (serverProcess && serverProcess.exitCode === null) {
serverProcess.kill("SIGTERM");
await new Promise<void>((resolve) => {
const timeout = setTimeout(() => {
daemonProcess.kill("SIGKILL");
serverProcess.kill("SIGKILL");
resolve();
}, 5000);
daemonProcess.once("exit", () => {
serverProcess.once("exit", () => {
clearTimeout(timeout);
resolve();
});
@ -128,26 +128,17 @@ describe.skipIf(SKIP_INTEGRATION)("Integration: connect (remote mode)", () => {
});
it("connects to remote server", async () => {
const client = await SandboxDaemonClient.connect({
const client = await SandboxAgent.connect({
baseUrl,
token,
spawn: false,
});
const health = await client.getHealth();
expect(health.status).toBe("ok");
});
it("creates client directly without spawn", () => {
const client = new SandboxDaemonClient({
baseUrl,
token,
});
expect(client).toBeInstanceOf(SandboxDaemonClient);
});
it("handles authentication", async () => {
const client = new SandboxDaemonClient({
const client = await SandboxAgent.connect({
baseUrl,
token,
});
@ -157,7 +148,7 @@ describe.skipIf(SKIP_INTEGRATION)("Integration: connect (remote mode)", () => {
});
it("rejects invalid token on protected endpoints", async () => {
const client = new SandboxDaemonClient({
const client = await SandboxAgent.connect({
baseUrl,
token: "invalid-token",
});

View file

@ -1,5 +1,5 @@
import { describe, it, expect, vi, type Mock } from "vitest";
import { SandboxDaemonClient } from "../src/client.ts";
import { SandboxAgent } from "../src/client.ts";
import type { UniversalEvent } from "../src/types.ts";
function createMockResponse(chunks: string[]): Response {
@ -51,7 +51,7 @@ describe("SSE Parser", () => {
const event = createEvent(1);
const mockFetch = createMockFetch([`data: ${JSON.stringify(event)}\n\n`]);
const client = new SandboxDaemonClient({
const client = await SandboxAgent.connect({
baseUrl: "http://localhost:8080",
fetch: mockFetch,
});
@ -73,7 +73,7 @@ describe("SSE Parser", () => {
`data: ${JSON.stringify(event2)}\n\n`,
]);
const client = new SandboxDaemonClient({
const client = await SandboxAgent.connect({
baseUrl: "http://localhost:8080",
fetch: mockFetch,
});
@ -97,7 +97,7 @@ describe("SSE Parser", () => {
fullMessage.slice(10),
]);
const client = new SandboxDaemonClient({
const client = await SandboxAgent.connect({
baseUrl: "http://localhost:8080",
fetch: mockFetch,
});
@ -118,7 +118,7 @@ describe("SSE Parser", () => {
`data: ${JSON.stringify(event1)}\n\ndata: ${JSON.stringify(event2)}\n\n`,
]);
const client = new SandboxDaemonClient({
const client = await SandboxAgent.connect({
baseUrl: "http://localhost:8080",
fetch: mockFetch,
});
@ -139,7 +139,7 @@ describe("SSE Parser", () => {
`data: ${JSON.stringify(event)}\n\n`,
]);
const client = new SandboxDaemonClient({
const client = await SandboxAgent.connect({
baseUrl: "http://localhost:8080",
fetch: mockFetch,
});
@ -158,7 +158,7 @@ describe("SSE Parser", () => {
`data: ${JSON.stringify(event)}\r\n\r\n`,
]);
const client = new SandboxDaemonClient({
const client = await SandboxAgent.connect({
baseUrl: "http://localhost:8080",
fetch: mockFetch,
});
@ -174,7 +174,7 @@ describe("SSE Parser", () => {
it("handles empty stream", async () => {
const mockFetch = createMockFetch([]);
const client = new SandboxDaemonClient({
const client = await SandboxAgent.connect({
baseUrl: "http://localhost:8080",
fetch: mockFetch,
});
@ -190,7 +190,7 @@ describe("SSE Parser", () => {
it("passes query parameters", async () => {
const mockFetch = createMockFetch([]);
const client = new SandboxDaemonClient({
const client = await SandboxAgent.connect({
baseUrl: "http://localhost:8080",
fetch: mockFetch,
});