Fresh history cut from cynarAI/evoo antigravity-handoff/project-timeline (Voice First + project timeline). Brand docs/titles as evOo; keep @rakazo/* packages as tech debt. Excludes node_modules, caches, secrets, voice blobs.
460 lines
16 KiB
TypeScript
460 lines
16 KiB
TypeScript
import type { Sandbox } from "@daytona/sdk";
|
|
import { describe, expect, it, vi } from "vitest";
|
|
import { DaytonaSandboxProvider, type DaytonaSandboxSdk } from "./daytona-sandbox.js";
|
|
import { desktopCommandResponder } from "./linux-desktop.test-support.js";
|
|
|
|
const context = {
|
|
operationId: "test",
|
|
traceId: "test",
|
|
spaceId: "workspace",
|
|
userId: "user",
|
|
signal: new AbortController().signal,
|
|
};
|
|
|
|
describe("DaytonaSandboxProvider", () => {
|
|
it("preserves desktop command output in failures", async () => {
|
|
const fixture = daytonaFixture();
|
|
const provider = new DaytonaSandboxProvider({ apiKey: "test-key" }, fixture.client);
|
|
const computer = await provider.provision({ botId: "bot-a", homePath: "/unused" }, context);
|
|
await provider.prepare(computer, context);
|
|
fixture.executeCommand.mockResolvedValueOnce({ exitCode: 1, result: "could not start Xvfb" });
|
|
|
|
await expect(provider.observe(computer, context)).rejects.toThrow("could not start Xvfb");
|
|
});
|
|
|
|
it("implements the portable command, file, screen, and computer-use contract", async () => {
|
|
const fixture = daytonaFixture();
|
|
const provider = new DaytonaSandboxProvider({ apiKey: "test-key" }, fixture.client);
|
|
const computer = await provider.provision({ botId: "bot-a", homePath: "/unused" }, context);
|
|
await provider.prepare(computer, context);
|
|
|
|
expect(computer).toMatchObject({
|
|
id: "daytona-box",
|
|
kind: "daytona",
|
|
providerRef: "daytona-box",
|
|
fresh: true,
|
|
});
|
|
expect(fixture.create).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
labels: { botId: "bot-a", rakazo: "computer" },
|
|
envVars: { VNC_RESOLUTION: "1280x800" },
|
|
}),
|
|
{ timeout: 120 },
|
|
);
|
|
|
|
const events = [];
|
|
for await (const event of provider.execute(computer, { argv: ["echo", "hello"] }, context)) {
|
|
events.push(event);
|
|
}
|
|
expect(events).toEqual([
|
|
{ type: "stdout", data: "hello\n" },
|
|
{ type: "exit", code: 0 },
|
|
]);
|
|
expect(fixture.executeCommand).toHaveBeenCalledWith(
|
|
"'echo' 'hello'",
|
|
"/home/daytona/rakazo-home",
|
|
undefined,
|
|
300,
|
|
);
|
|
|
|
const binary = Uint8Array.from([0, 255, 1, 128]);
|
|
await provider.writeFile(
|
|
computer,
|
|
{ path: "bin/tool", content: binary, executable: true },
|
|
context,
|
|
);
|
|
expect(await provider.readFile(computer, "bin/tool", context)).toEqual(binary);
|
|
expect(await provider.listFiles(computer, "bin", context)).toEqual([
|
|
{ path: "bin/tool", kind: "file", size: 4, executable: true },
|
|
]);
|
|
const exported = [];
|
|
for await (const file of provider.exportWorkspace(computer, context)) exported.push(file);
|
|
expect(exported).toContainEqual({
|
|
path: "bin/tool",
|
|
content: binary,
|
|
executable: true,
|
|
});
|
|
|
|
const observation = await provider.observe(computer, context);
|
|
expect(observation).toMatchObject({
|
|
mimeType: "image/png",
|
|
width: 1280,
|
|
height: 800,
|
|
cursor: { x: 10, y: 20 },
|
|
});
|
|
expect(observation.image).toEqual(Uint8Array.from([1, 2, 3]));
|
|
|
|
const result = await provider.act(
|
|
computer,
|
|
{
|
|
actions: [
|
|
{ kind: "pointer", type: "down", x: 10, y: 20 },
|
|
{ kind: "pointer", type: "up", x: 30, y: 40 },
|
|
{ kind: "key", key: "enter" },
|
|
{ kind: "clipboard", text: "hello" },
|
|
{ kind: "scroll", direction: "down", amount: 2 },
|
|
],
|
|
observe: false,
|
|
},
|
|
context,
|
|
);
|
|
expect(result).toEqual({ completed: 5 });
|
|
expect(fixture.executeCommand).toHaveBeenCalledWith(
|
|
expect.stringContaining("mousemove 10 20 mousedown 1"),
|
|
undefined,
|
|
undefined,
|
|
300,
|
|
);
|
|
expect(fixture.executeCommand).toHaveBeenCalledWith(
|
|
expect.stringContaining("mousemove 30 40 mouseup 1"),
|
|
undefined,
|
|
undefined,
|
|
300,
|
|
);
|
|
const [screen, interactiveScreen] = await Promise.all([
|
|
provider.connectScreen(computer, { view: "stream", interactive: false }, context),
|
|
provider.connectScreen(
|
|
computer,
|
|
{ view: "stream", interactive: true, controlToken: "control-1" },
|
|
context,
|
|
),
|
|
]);
|
|
expect(screen.url).toContain("/vnc.html");
|
|
expect(new URL(screen.url!).searchParams.get("path")).toMatch(/^websockify\?token=view-/);
|
|
await screen.close();
|
|
expect(new URL(interactiveScreen.url!).searchParams.get("path")).toBe(
|
|
"websockify?token=control-1",
|
|
);
|
|
expect(fixture.getSignedPreviewUrl).toHaveBeenCalledOnce();
|
|
|
|
await provider.stop(computer, context);
|
|
expect(fixture.expireSignedPreviewUrl).toHaveBeenCalledWith(6100, "preview-token");
|
|
expect(fixture.stop).toHaveBeenCalledWith(120);
|
|
});
|
|
|
|
it("reconnects stopped sandboxes and replaces missing ones", async () => {
|
|
const existing = daytonaFixture({ id: "existing", state: "stopped" });
|
|
const provider = new DaytonaSandboxProvider({ apiKey: "test-key" }, existing.client);
|
|
const reconnected = await provider.provision(
|
|
{
|
|
botId: "bot-a",
|
|
homePath: "/unused",
|
|
providerRef: "existing",
|
|
providerKind: "daytona",
|
|
},
|
|
context,
|
|
);
|
|
expect(existing.start).toHaveBeenCalledWith(120);
|
|
expect(reconnected).toMatchObject({
|
|
providerRef: "existing",
|
|
fresh: false,
|
|
});
|
|
|
|
const replacement = daytonaFixture({ id: "replacement" });
|
|
replacement.get.mockRejectedValueOnce({ statusCode: 404 });
|
|
const replacingProvider = new DaytonaSandboxProvider(
|
|
{ apiKey: "test-key" },
|
|
replacement.client,
|
|
);
|
|
const replaced = await replacingProvider.provision(
|
|
{
|
|
botId: "bot-b",
|
|
homePath: "/unused",
|
|
providerRef: "missing",
|
|
providerKind: "daytona",
|
|
},
|
|
context,
|
|
);
|
|
expect(replaced).toMatchObject({ providerRef: "replacement", fresh: true });
|
|
});
|
|
|
|
it("coalesces concurrent reconnect and workspace preparation", async () => {
|
|
const fixture = daytonaFixture({ id: "shared", state: "stopped" });
|
|
const provider = new DaytonaSandboxProvider({ apiKey: "test-key" }, fixture.client);
|
|
const request = {
|
|
botId: "bot-a",
|
|
homePath: "/unused",
|
|
providerRef: "shared",
|
|
providerKind: "daytona" as const,
|
|
};
|
|
|
|
const computers = await Promise.all([
|
|
provider.provision(request, context),
|
|
provider.provision(request, context),
|
|
]);
|
|
await Promise.all(computers.map((computer) => provider.prepare(computer, context)));
|
|
|
|
expect(fixture.get).toHaveBeenCalledTimes(1);
|
|
expect(fixture.start).toHaveBeenCalledTimes(1);
|
|
expect(
|
|
fixture.executeCommand.mock.calls.filter(([command]) =>
|
|
String(command).startsWith("mkdir -p -- "),
|
|
),
|
|
).toHaveLength(1);
|
|
});
|
|
|
|
it("returns a fresh reference before workspace preparation fails", async () => {
|
|
const fixture = daytonaFixture({ prepareFails: true });
|
|
const provider = new DaytonaSandboxProvider({ apiKey: "test-key" }, fixture.client);
|
|
const computer = await provider.provision({ botId: "bot-a", homePath: "/unused" }, context);
|
|
|
|
expect(computer).toMatchObject({ providerRef: "daytona-box", fresh: true });
|
|
await expect(provider.prepare(computer, context)).rejects.toThrow(
|
|
/could not create Daytona workspace/,
|
|
);
|
|
expect(fixture.deleteSandbox).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("surfaces transient stop failures so cleanup can retry", async () => {
|
|
const fixture = daytonaFixture();
|
|
const provider = new DaytonaSandboxProvider({ apiKey: "test-key" }, fixture.client);
|
|
const computer = await provider.provision({ botId: "bot-a", homePath: "/unused" }, context);
|
|
await provider.prepare(computer, context);
|
|
fixture.stop.mockRejectedValueOnce(new Error("temporary Daytona outage"));
|
|
|
|
await expect(provider.stop(computer, context)).rejects.toThrow("temporary Daytona outage");
|
|
await expect(provider.stop(computer, context)).resolves.toBeUndefined();
|
|
expect(fixture.get).toHaveBeenCalledTimes(1);
|
|
expect(fixture.stop).toHaveBeenCalledTimes(2);
|
|
});
|
|
|
|
it("distinguishes transient lookup failures from an already deleted sandbox", async () => {
|
|
const fixture = daytonaFixture({ id: "remote" });
|
|
const provider = new DaytonaSandboxProvider({ apiKey: "test-key" }, fixture.client);
|
|
const computer = {
|
|
id: "remote",
|
|
botId: "bot-a",
|
|
kind: "daytona" as const,
|
|
providerRef: "remote",
|
|
fresh: false,
|
|
};
|
|
fixture.get.mockRejectedValueOnce(new Error("temporary Daytona outage"));
|
|
fixture.get.mockRejectedValueOnce({ statusCode: 404 });
|
|
|
|
await expect(provider.stop(computer, context)).rejects.toThrow("temporary Daytona outage");
|
|
await expect(provider.stop(computer, context)).resolves.toBeUndefined();
|
|
});
|
|
|
|
it("accepts definitive deletion while preserving transient teardown failures", async () => {
|
|
const fixture = daytonaFixture();
|
|
const provider = new DaytonaSandboxProvider({ apiKey: "test-key" }, fixture.client);
|
|
const computer = await provider.provision({ botId: "bot-a", homePath: "/unused" }, context);
|
|
const failure = new Error("temporary Daytona outage");
|
|
fixture.deleteSandbox.mockRejectedValueOnce(failure).mockRejectedValueOnce({ statusCode: 404 });
|
|
|
|
await expect(provider.destroy(computer, context)).rejects.toBe(failure);
|
|
await expect(provider.destroy(computer, context)).resolves.toBeUndefined();
|
|
expect(fixture.deleteSandbox).toHaveBeenCalledTimes(2);
|
|
// Subsequent teardown can also observe the missing resource during lookup.
|
|
fixture.get.mockRejectedValueOnce({ statusCode: 404 });
|
|
await expect(provider.destroy(computer, context)).resolves.toBeUndefined();
|
|
expect(fixture.deleteSandbox).toHaveBeenCalledTimes(2);
|
|
});
|
|
|
|
it("gives Team bots distinct Daytona previews using the shared runtime", async () => {
|
|
const fixture = daytonaFixture();
|
|
const provider = new DaytonaSandboxProvider({ apiKey: "test-key" }, fixture.client);
|
|
const computer = await provider.provision({ botId: "team-home", homePath: "/unused" }, context);
|
|
await provider.prepare(computer, context);
|
|
const writer = { ...context, botId: "writer" };
|
|
const researcher = { ...context, botId: "researcher" };
|
|
const _screenRegistry = desktopCommandResponder();
|
|
|
|
fixture.getSignedPreviewUrl.mockImplementation(async (port: number) => ({
|
|
url: `https://${port}-preview.proxy.daytona.test`,
|
|
token: `token-${port}`,
|
|
}));
|
|
await provider.observe(computer, writer);
|
|
await provider.observe(computer, researcher);
|
|
const writerView = await provider.connectScreen(
|
|
computer,
|
|
{ view: "stream", interactive: false },
|
|
writer,
|
|
);
|
|
const researcherView = await provider.connectScreen(
|
|
computer,
|
|
{ view: "stream", interactive: false },
|
|
researcher,
|
|
);
|
|
expect(writerView.url).toContain("6100-preview");
|
|
expect(researcherView.url).not.toBe(writerView.url);
|
|
expect(researcherView.url).toContain("6100-preview");
|
|
expect(new URL(researcherView.url!).searchParams.get("path")).toMatch(
|
|
/^websockify\?token=view-/,
|
|
);
|
|
expect(fixture.computerUse.start).not.toHaveBeenCalled();
|
|
expect(fixture.click).not.toHaveBeenCalled();
|
|
|
|
await provider.act(
|
|
computer,
|
|
{
|
|
actions: [{ kind: "pointer", type: "click", x: 4, y: 5 }],
|
|
observe: false,
|
|
},
|
|
researcher,
|
|
);
|
|
expect(
|
|
fixture.executeCommand.mock.calls.some(([command]) => command.includes("DISPLAY=:21")),
|
|
).toBe(true);
|
|
expect(fixture.click).not.toHaveBeenCalled();
|
|
|
|
await provider.releaseScreen(computer, writer);
|
|
await expect(provider.observe(computer, researcher)).resolves.toMatchObject({
|
|
width: 1280,
|
|
height: 800,
|
|
});
|
|
|
|
const researcherControl = await provider.connectScreen(
|
|
computer,
|
|
{ view: "stream", interactive: true, controlToken: "lease-1" },
|
|
researcher,
|
|
);
|
|
expect(researcherControl.url).toContain("6100-preview");
|
|
expect(new URL(researcherControl.url!).searchParams.get("path")).toBe(
|
|
"websockify?token=lease-1",
|
|
);
|
|
expect(
|
|
fixture.executeCommand.mock.calls.some(([command]) =>
|
|
String(command).includes("sockets/control-"),
|
|
),
|
|
).toBe(true);
|
|
|
|
expect(provider.describe().capabilities.multiScreen).toBe(true);
|
|
});
|
|
});
|
|
|
|
function daytonaFixture(options: { id?: string; state?: string; prepareFails?: boolean } = {}) {
|
|
const files = new Map<string, Buffer>();
|
|
const modes = new Map<string, string>();
|
|
const id = options.id ?? "daytona-box";
|
|
const screenRegistry = desktopCommandResponder();
|
|
const executeCommand = vi.fn(
|
|
async (command: string, _cwd?: string, _env?: Record<string, string>, _timeout?: number) => {
|
|
const registryResult = screenRegistry(command);
|
|
if (registryResult)
|
|
return { exitCode: registryResult.exitCode, result: registryResult.stdout };
|
|
if (command === "'echo' 'hello'") return { exitCode: 0, result: "hello\n" };
|
|
if (options.prepareFails && command.startsWith("mkdir -p -- ")) {
|
|
return { exitCode: 1, result: "could not create Daytona workspace" };
|
|
}
|
|
if (command.startsWith("chmod 700 -- ")) {
|
|
for (const match of command.matchAll(/'([^']+)'/g)) modes.set(match[1]!, "0755");
|
|
}
|
|
return { exitCode: 0, result: "" };
|
|
},
|
|
);
|
|
const uploadFile = vi.fn(async (content: Buffer, target: string) => {
|
|
files.set(target, Buffer.from(content));
|
|
modes.set(target, modes.get(target) ?? "0644");
|
|
});
|
|
const downloadFile = vi.fn(async (target: string) => Buffer.from(files.get(target) ?? []));
|
|
const listFiles = vi.fn(async (directory: string) => {
|
|
const prefix = `${directory.replace(/\/$/, "")}/`;
|
|
const entries = new Map<string, ReturnType<typeof fileInfo>>();
|
|
for (const [target, content] of files) {
|
|
if (!target.startsWith(prefix)) continue;
|
|
const remainder = target.slice(prefix.length);
|
|
const [name, ...rest] = remainder.split("/");
|
|
if (!name) continue;
|
|
entries.set(
|
|
name,
|
|
fileInfo(name, rest.length > 0, rest.length ? 0 : content.byteLength, modes.get(target)),
|
|
);
|
|
}
|
|
return [...entries.values()];
|
|
});
|
|
const start = vi.fn(async () => undefined);
|
|
const stop = vi.fn(async () => undefined);
|
|
const deleteSandbox = vi.fn(async () => undefined);
|
|
const expireSignedPreviewUrl = vi.fn(async () => undefined);
|
|
const drag = vi.fn(async () => ({}));
|
|
const click = vi.fn(async () => ({}));
|
|
const press = vi.fn(async () => undefined);
|
|
const type = vi.fn(async () => undefined);
|
|
const scroll = vi.fn(async () => true);
|
|
const getSignedPreviewUrl = vi.fn(async (_port: number) => ({
|
|
url: `https://${_port}-preview.proxy.daytona.test`,
|
|
token: "preview-token",
|
|
}));
|
|
const sandbox = {
|
|
id,
|
|
state: options.state ?? "started",
|
|
process: { executeCommand },
|
|
fs: {
|
|
uploadFile,
|
|
downloadFile,
|
|
listFiles,
|
|
getFileDetails: vi.fn(async (target: string) =>
|
|
fileInfo(target.split("/").at(-1) ?? target, false, files.get(target)?.byteLength ?? 0),
|
|
),
|
|
},
|
|
computerUse: {
|
|
start: vi.fn(async () => ({ message: "started" })),
|
|
stop: vi.fn(async () => ({ message: "stopped" })),
|
|
screenshot: {
|
|
takeFullScreen: vi.fn(async () => ({
|
|
screenshot: Buffer.from([1, 2, 3]).toString("base64"),
|
|
})),
|
|
},
|
|
display: {
|
|
getInfo: vi.fn(async () => ({
|
|
displays: [{ width: 1280, height: 800, isActive: true }],
|
|
})),
|
|
getWindows: vi.fn(async () => ({
|
|
windows: [{ id: 7, title: "Browser", isActive: true }],
|
|
})),
|
|
},
|
|
mouse: {
|
|
getPosition: vi.fn(async () => ({ x: 10, y: 20 })),
|
|
move: vi.fn(async () => ({ x: 10, y: 20 })),
|
|
click,
|
|
drag,
|
|
scroll,
|
|
},
|
|
keyboard: { press, type },
|
|
},
|
|
getUserHomeDir: vi.fn(async () => "/home/daytona"),
|
|
getWorkDir: vi.fn(async () => "/home/daytona"),
|
|
getSignedPreviewUrl,
|
|
expireSignedPreviewUrl,
|
|
refreshActivity: vi.fn(async () => undefined),
|
|
start,
|
|
stop,
|
|
delete: deleteSandbox,
|
|
} as unknown as Sandbox;
|
|
const create = vi.fn(async () => sandbox);
|
|
const get = vi.fn(async () => sandbox);
|
|
return {
|
|
sandbox,
|
|
client: { create, get } satisfies DaytonaSandboxSdk,
|
|
create,
|
|
get,
|
|
executeCommand,
|
|
computerUse: sandbox.computerUse,
|
|
start,
|
|
stop,
|
|
deleteSandbox,
|
|
getSignedPreviewUrl,
|
|
expireSignedPreviewUrl,
|
|
drag,
|
|
click,
|
|
press,
|
|
type,
|
|
scroll,
|
|
};
|
|
}
|
|
|
|
function fileInfo(name: string, isDir: boolean, size: number, mode = "0644") {
|
|
return {
|
|
group: "daytona",
|
|
isDir,
|
|
modTime: "",
|
|
modifiedAt: "2026-01-01T00:00:00Z",
|
|
mode,
|
|
name,
|
|
owner: "daytona",
|
|
permissions: mode === "0755" ? "rwxr-xr-x" : "rw-r--r--",
|
|
size,
|
|
};
|
|
}
|