evOo-local/packages/adapters/src/computer-screens.test.ts
cynarAI 6c7eed7580 Initial clean evOo-local from f00577b
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.
2026-09-20 17:15:16 +02:00

144 lines
5.9 KiB
TypeScript

import { describe, expect, it } from "vitest";
import {
ComputerScreenUnavailableError,
isComputerScreenUnavailable,
SingleScreenClaimTracker,
withComputerScreenAvailability,
} from "./computer-screens.js";
import { ManagedSandboxEmulator } from "./e2b-emulator.js";
import { FakeSandboxProvider } from "./fake-sandbox.js";
const writer = {
operationId: "1",
traceId: "1",
spaceId: "w",
userId: "u",
botId: "writer",
signal: new AbortController().signal,
};
const researcher = { ...writer, botId: "researcher" };
describe("Team Computer parallel screens", () => {
it("gives fake Team bots distinct observations without ComputerBusyError", async () => {
const fake = new FakeSandboxProvider();
expect(fake.describe().capabilities.multiScreen).toBe(true);
const computer = await fake.provision({ botId: "team-home", homePath: "/tmp/team" }, writer);
await fake.act(
computer,
{ actions: [{ kind: "clipboard", text: "writer-desk" }], observe: false },
writer,
);
await fake.act(
computer,
{ actions: [{ kind: "clipboard", text: "researcher-desk" }], observe: false },
researcher,
);
const [writerScreen, researcherScreen] = await Promise.all([
fake.observe(computer, writer),
fake.observe(computer, researcher),
]);
expect(writerScreen.activeWindow?.title).toBe("writer-desk");
expect(researcherScreen.activeWindow?.title).toBe("researcher-desk");
const writerView = await fake.connectScreen(computer, { view: "stream" }, writer);
const researcherView = await fake.connectScreen(computer, { view: "stream" }, researcher);
expect(writerView.url).toBe("fake://screen/fake-team-home/writer");
expect(researcherView.url).toBe("fake://screen/fake-team-home/researcher");
expect(writerView.url).not.toBe(researcherView.url);
await fake.writeFile(
computer,
{ path: "shared/note.txt", content: new TextEncoder().encode("office") },
writer,
);
expect(
new TextDecoder().decode(await fake.readFile(computer, "shared/note.txt", researcher)),
).toBe("office");
});
it("releases a single-screen claim only for the owning bot", () => {
const claims = new SingleScreenClaimTracker();
claims.claim("computer-1", writer);
claims.release("computer-1", researcher);
expect(() => claims.claim("computer-1", researcher)).toThrow(ComputerScreenUnavailableError);
claims.release("computer-1", writer);
expect(() => claims.claim("computer-1", researcher)).not.toThrow();
});
it("does not let stale cleanup release a replacement claim for the same bot", () => {
const claims = new SingleScreenClaimTracker();
const oldRun = { ...writer, screenLeaseId: "run-1:1" };
const newRun = { ...writer, screenLeaseId: "run-2:2" };
claims.claim("computer-1", oldRun);
claims.claim("computer-1", newRun);
expect(claims.release("computer-1", oldRun)).toBe(false);
expect(() => claims.claim("computer-1", researcher)).toThrow(ComputerScreenUnavailableError);
expect(claims.release("computer-1", newRun)).toBe(true);
expect(() => claims.claim("computer-1", researcher)).not.toThrow();
});
it("releases a claim retained across a same-run takeover resume", () => {
const claims = new SingleScreenClaimTracker();
const paused = { ...writer, screenLeaseId: "run-1:1" };
const resumed = { ...writer, screenLeaseId: "run-1:8" };
claims.claim("computer-1", paused);
claims.release("computer-1", resumed);
expect(() => claims.claim("computer-1", researcher)).not.toThrow();
});
it("rejects a delayed claim after a newer run reclaimed the screen", () => {
const claims = new SingleScreenClaimTracker();
claims.claim("computer-1", { ...writer, screenLeaseId: "run-2:2" });
expect(() => claims.claim("computer-1", { ...writer, screenLeaseId: "run-1:1" })).toThrow(
ComputerScreenUnavailableError,
);
claims.release("computer-1", { ...writer, screenLeaseId: "run-1:1" });
expect(() => claims.claim("computer-1", researcher)).toThrow(ComputerScreenUnavailableError);
claims.release("computer-1", { ...writer, screenLeaseId: "run-2:2" });
expect(() => claims.claim("computer-1", researcher)).not.toThrow();
});
it("turns a graphical refusal into a tool error object", async () => {
const claims = new SingleScreenClaimTracker();
claims.claim("computer-1", writer);
await expect(
withComputerScreenAvailability(async () => {
claims.claim("computer-1", researcher);
return "ok";
}),
).resolves.toEqual({ error: expect.stringMatching(/temporarily busy/) });
expect(isComputerScreenUnavailable(new Error("cannot allocate another screen"))).toBe(true);
});
it("lets Team bots use independent emulator screens concurrently", async () => {
const emulator = new ManagedSandboxEmulator();
expect(emulator.describe().capabilities.multiScreen).toBe(true);
const computer = await emulator.provision({ botId: "team-home", homePath: "/tmp" }, writer);
await emulator.observe(computer, writer);
await emulator.connectScreen(computer, { view: "stream" }, researcher);
await expect(emulator.observe(computer, writer)).resolves.toMatchObject({
activeWindow: expect.anything(),
});
await expect(emulator.observe(computer, researcher)).resolves.toMatchObject({
activeWindow: expect.anything(),
});
await emulator.releaseScreen(computer, writer);
await expect(emulator.observe(computer, researcher)).resolves.toMatchObject({
activeWindow: expect.anything(),
});
});
it("refuses a second graphical session on single-screen providers", () => {
const claims = new SingleScreenClaimTracker();
claims.claim("computer-1", writer);
expect(() => claims.claim("computer-1", researcher)).toThrow(ComputerScreenUnavailableError);
claims.claim("computer-1", writer);
claims.release("computer-1");
expect(() => claims.claim("computer-1", researcher)).not.toThrow();
});
});