Files
paseo/packages/server/src/server/hub/execution-session.websocket.test.ts
T
Mohamed Boudra b2ff359b60 Always create a fresh worktree (#3224)
* fix(server): always create requested worktrees

A slug is a preferred name, not workspace identity. Let the worktree creator suffix occupied paths and branches so duplicate and detached-slug requests still create fresh worktrees.

* fix(server): suffix occupied checkout branches

Fresh creation also applies when checkout targets are already in use. Create a suffixed branch from the requested branch instead of rejecting the request.

* test(server): align occupied branch coverage

* test(server): remove stale worktree reuse expectations
2026-08-11 17:54:59 +02:00

332 lines
15 KiB
TypeScript

import { afterEach, expect, test } from "vitest";
import { HubRelationshipHarness } from "./test-utils/relationship-harness.js";
let relationship: HubRelationshipHarness | null = null;
afterEach(async () => {
await relationship?.close();
relationship = null;
});
async function launchRelationship(): Promise<HubRelationshipHarness> {
const launched = await HubRelationshipHarness.start();
await launched.beginConnect().result;
launched.connectLatestSocket();
relationship = launched;
return launched;
}
test("Hub retries one durable daemon execution across concurrency and reconstruction", async () => {
const hub = await launchRelationship();
const created = await hub.createOwnedConcurrently();
const update = await hub.ownedUpdate(created.first.agentId);
const stream = await hub.ownedStream(created.first.agentId);
const reconstructed = await hub.reconstructAndReplay();
expect(created.duplicate.agentId).toBe(created.first.agentId);
expect(update).toMatchObject({
executionId: "execution-1",
agentId: created.first.agentId,
agent: { id: created.first.agentId },
});
expect(stream).toMatchObject({ executionId: "execution-1", agentId: created.first.agentId });
expect(reconstructed.replay.agent.id).toBe(created.first.agentId);
expect(reconstructed.durableAgentCount).toBe(1);
});
test("Hub denies trusted steering and browser dispatch", async () => {
const hub = await launchRelationship();
const localAgentId = await hub.createUnrelatedLocalAgent();
const steeringDenial = await hub.deniedSteering(localAgentId);
const browserDenial = await hub.deniedBrowserDispatch();
expect(steeringDenial).toEqual({
requestId: "denied-steer",
requestType: "send_agent_message_request",
error: "Session is not authorized for send_agent_message_request",
code: "access_denied",
});
expect(browserDenial).toEqual({
requestId: "browser-1",
requestType: "browser.automation.execute.response",
error: "Session is not authorized for browser.automation.execute.response",
code: "access_denied",
});
expect(hub.observedAgentIds()).not.toContain(localAgentId);
expect(hub.observedTrustedLifecycleMessages()).toEqual([]);
});
test("Hub sockets reject trusted hello and capabilities", async () => {
const hub = await launchRelationship();
expect(hub.probeTrustedHello()).toBe(4002);
});
test("Hub sockets reject trusted binary frames", async () => {
const hub = await launchRelationship();
expect(hub.probeBinaryFrame()).toBe(4002);
});
test("Hub does not receive trusted broadcasts", async () => {
const hub = await launchRelationship();
const trustedBroadcasts = await hub.trustedBroadcastCount();
const trustedStatus = await hub.trustedDaemonStatus();
expect(trustedBroadcasts).toBe(0);
expect(trustedStatus).toMatchObject({ pid: process.pid, relay: { enabled: false } });
expect(hub.observedTrustedLifecycleMessages()).toEqual([]);
});
test("Hub reconnects without retaining trusted session state", async () => {
const hub = await launchRelationship();
const created = await hub.createOwnedConcurrently();
const reconnected = await hub.reconnectAndRetry();
expect(reconnected).toMatchObject({
executionId: "execution-1",
agentId: created.first.agentId,
});
expect(hub.observedTrustedLifecycleMessages()).toEqual([]);
});
test("Hub interrupts an owned running execution idempotently", async () => {
const hub = await launchRelationship();
hub.beginOwnedCreate("interrupt-create", "execution-interrupt", { prompt: "sleep 30" });
const created = await hub.ownedCreateResult("interrupt-create");
await hub.ownedRunningUpdate(created.payload.agentId!);
const interrupted = await hub.interruptExecution("execution-interrupt", "interrupt-first");
const duplicate = await hub.interruptExecution("execution-interrupt", "interrupt-duplicate");
expect(interrupted).toEqual({
requestId: "interrupt-first",
executionId: "execution-interrupt",
action: "interrupt",
success: true,
error: null,
});
expect(duplicate).toEqual({
requestId: "interrupt-duplicate",
executionId: "execution-interrupt",
action: "interrupt",
success: true,
error: null,
});
expect(hub.ownedAgentIsRunning(created.payload.agentId!)).toBe(false);
});
test("Hub control waits for an in-flight create of the same execution", async () => {
const hub = await launchRelationship();
hub.holdAgentCreation();
hub.beginOwnedCreate("pending-control-create", "execution-pending-control", {
prompt: "sleep 30",
});
await hub.agentCreationAttempts(1);
hub.beginExecutionControl("pending-control-archive", "execution-pending-control", "archive");
hub.finishAgentCreation();
const created = await hub.ownedCreateResult("pending-control-create");
const archived = await hub.executionControlResult("pending-control-archive");
expect(created).toMatchObject({ payload: { success: true, agentId: expect.any(String) } });
expect(archived).toMatchObject({ success: true, error: null, action: "archive" });
expect(created.payload.agent?.workspaceId).toEqual(expect.any(String));
expect(await hub.ownedAgentArchivedAt(created.payload.agentId!)).toEqual(expect.any(String));
});
test("Hub archives an execution workspace on a local checkout", async () => {
const hub = await launchRelationship();
const siblingWorkspaceId = await hub.createSiblingWorkspace(hub.repoRoot());
hub.beginOwnedCreate("local-create", "execution-local", {
workspaceId: siblingWorkspaceId,
prompt: "sleep 30",
});
const created = await hub.ownedCreateResult("local-create");
const executionWorkspaceId = created.payload.agent?.workspaceId;
expect(executionWorkspaceId).toEqual(expect.any(String));
const terminalId = await hub.createWorkspaceTerminal(executionWorkspaceId!);
await hub.ownedRunningUpdate(created.payload.agentId!);
const archived = await hub.archiveExecution("execution-local", "archive-local");
const duplicate = await hub.archiveExecution("execution-local", "archive-local-duplicate");
expect(archived).toMatchObject({ success: true, error: null, action: "archive" });
expect(duplicate).toMatchObject({ success: true, error: null, action: "archive" });
expect(executionWorkspaceId).not.toBe(siblingWorkspaceId);
expect(await hub.ownedAgentArchivedAt(created.payload.agentId!)).toEqual(expect.any(String));
expect(await hub.ownedWorkspaceArchivedAt(created.payload.agentId!)).toEqual(expect.any(String));
expect(await hub.archivedWorkspaceAt(siblingWorkspaceId)).toBeNull();
expect(hub.terminalExists(terminalId)).toBe(false);
expect(hub.ownedAgentIsRunning(created.payload.agentId!)).toBe(false);
expect(hub.repoExists()).toBe(true);
});
test("Hub creates and archives distinct local workspaces for classifier and worker executions", async () => {
const hub = await launchRelationship();
hub.beginOwnedCreate("classifier-create", "execution-classifier", {
prompt: "Classify the request",
providerOptions: { sandbox_mode: "read-only" },
});
const classifier = await hub.ownedCreateResult("classifier-create");
hub.beginOwnedCreate("worker-create", "execution-worker", {
prompt: "Implement the request",
providerOptions: { sandbox_mode: "workspace-write" },
});
const worker = await hub.ownedCreateResult("worker-create");
const classifierWorkspaceId = classifier.payload.agent?.workspaceId;
const workerWorkspaceId = worker.payload.agent?.workspaceId;
expect(classifierWorkspaceId).toEqual(expect.any(String));
expect(workerWorkspaceId).toEqual(expect.any(String));
expect(classifierWorkspaceId).not.toBe(workerWorkspaceId);
expect(classifier.payload.agent?.cwd).toBe(hub.repoRoot());
expect(worker.payload.agent?.cwd).toBe(hub.repoRoot());
await hub.archiveExecution("execution-classifier", "archive-classifier");
expect(await hub.archivedWorkspaceAt(classifierWorkspaceId!)).toEqual(expect.any(String));
expect(await hub.archivedWorkspaceAt(workerWorkspaceId!)).toBeNull();
await hub.archiveExecution("execution-worker", "archive-worker");
expect(await hub.archivedWorkspaceAt(workerWorkspaceId!)).toEqual(expect.any(String));
});
test("Hub archives a running execution's Paseo-created worktree", async () => {
const hub = await launchRelationship();
hub.beginOwnedCreate("worktree-create", "execution-worktree", {
worktree: { mode: "branch-off", newBranch: "hub-created-worktree", base: "main" },
prompt: "sleep 30",
});
const worktreeCreated = await hub.ownedCreateResult("worktree-create");
const workspaceId = worktreeCreated.payload.agent?.workspaceId;
const worktreeCwd = hub.latestCreatedCwd();
await hub.ownedRunningUpdate(worktreeCreated.payload.agentId!);
const duringRun = await hub.worktreeState(worktreeCwd!);
const response = await hub.archiveExecution("execution-worktree", "archive-worktree");
const afterArchive = await hub.worktreeState(worktreeCwd!);
expect(worktreeCreated).toMatchObject({
type: "hub.execution.agent.create.response",
payload: { success: true, agent: { cwd: worktreeCwd } },
});
expect(worktreeCwd).not.toBe(hub.repoRoot());
expect(duringRun).toEqual({ exists: true, listed: true });
expect(response).toMatchObject({ success: true, error: null, action: "archive" });
expect(afterArchive).toEqual({ exists: false, listed: false });
expect(workspaceId).toEqual(expect.any(String));
expect(await hub.archivedWorkspaceAt(workspaceId!)).toEqual(expect.any(String));
expect(await hub.ownedAgentArchivedAt(worktreeCreated.payload.agentId!)).toEqual(
expect.any(String),
);
});
test("a sibling workspace keeps an archived execution's worktree directory alive", async () => {
const hub = await launchRelationship();
hub.beginOwnedCreate("sibling-create", "execution-sibling", {
worktree: { mode: "branch-off", newBranch: "hub-sibling-worktree", base: "main" },
prompt: "sleep 30",
});
const created = await hub.ownedCreateResult("sibling-create");
const targetWorkspaceId = created.payload.agent?.workspaceId;
const worktreeCwd = hub.latestCreatedCwd()!;
await hub.ownedRunningUpdate(created.payload.agentId!);
const siblingWorkspaceId = await hub.createSiblingWorkspace(worktreeCwd);
const response = await hub.archiveExecution("execution-sibling", "archive-sibling");
expect(response).toMatchObject({ success: true, error: null });
expect(targetWorkspaceId).toEqual(expect.any(String));
expect(await hub.archivedWorkspaceAt(targetWorkspaceId!)).toEqual(expect.any(String));
expect(await hub.archivedWorkspaceAt(siblingWorkspaceId)).toBeNull();
expect(await hub.worktreeState(worktreeCwd)).toEqual({ exists: true, listed: true });
expect(await hub.ownedAgentArchivedAt(created.payload.agentId!)).toEqual(expect.any(String));
});
test("archiving a second same-slug execution leaves the first worktree intact", async () => {
const hub = await launchRelationship();
const worktree = {
mode: "branch-off" as const,
newBranch: "hub-reused-worktree",
base: "main",
};
hub.beginOwnedCreate("original-worktree-create", "execution-original-worktree", {
worktree,
prompt: "respond with exactly: original complete",
});
const original = await hub.ownedCreateResult("original-worktree-create");
const worktreeCwd = hub.latestCreatedCwd()!;
const originalWorkspaceId = original.payload.agent?.workspaceId;
await hub.ownedTurnCompletion(original.payload.agentId!);
hub.beginOwnedCreate("reused-worktree-create", "execution-reused-worktree", {
worktree,
prompt: "sleep 30",
});
const reused = await hub.ownedCreateResult("reused-worktree-create");
const secondWorktreeCwd = hub.latestCreatedCwd()!;
const reusedWorkspaceId = reused.payload.agent?.workspaceId;
await hub.ownedRunningUpdate(reused.payload.agentId!);
const response = await hub.archiveExecution(
"execution-reused-worktree",
"archive-reused-worktree",
);
expect(response).toMatchObject({ success: true, error: null });
expect(reusedWorkspaceId).toEqual(expect.any(String));
expect(reusedWorkspaceId).not.toBe(originalWorkspaceId);
expect(hub.pathsReferToSameLocation(reused.payload.agent!.cwd, worktreeCwd)).toBe(false);
expect(hub.pathsReferToSameLocation(reused.payload.agent!.cwd, secondWorktreeCwd)).toBe(true);
expect(await hub.worktreeState(worktreeCwd)).toEqual({ exists: true, listed: true });
expect(await hub.worktreeState(secondWorktreeCwd)).toEqual({ exists: false, listed: false });
expect(await hub.agentRemainsAvailable(original.payload.agentId!)).toBe(true);
expect(await hub.ownedAgentArchivedAt(reused.payload.agentId!)).toEqual(expect.any(String));
expect(await hub.ownedWorkspaceArchivedAt(reused.payload.agentId!)).toEqual(expect.any(String));
expect(await hub.ownedWorkspaceArchivedAt(original.payload.agentId!)).toBeNull();
});
test("Hub resolves persisted execution ownership after daemon restart", async () => {
const hub = await launchRelationship();
hub.beginOwnedCreate("restart-create", "execution-restart", {
worktree: { mode: "branch-off", newBranch: "hub-restart-worktree", base: "main" },
prompt: "sleep 30",
});
const created = await hub.ownedCreateResult("restart-create");
const workspaceId = created.payload.agent?.workspaceId;
const worktreeCwd = hub.latestCreatedCwd()!;
await hub.ownedRunningUpdate(created.payload.agentId!);
await hub.restartDaemon();
await hub.socketDialed();
hub.connectLatestSocket();
const response = await hub.archiveExecution("execution-restart", "archive-after-restart");
expect(response).toMatchObject({ success: true, error: null });
expect(await hub.ownedAgentArchivedAt(created.payload.agentId!)).toEqual(expect.any(String));
expect(workspaceId).toEqual(expect.any(String));
expect(await hub.archivedWorkspaceAt(workspaceId!)).toEqual(expect.any(String));
expect(await hub.worktreeState(worktreeCwd)).toEqual({ exists: false, listed: false });
}, 20_000);
test("Hub treats missing and foreign executions as already controlled without exposing ownership", async () => {
const hub = await launchRelationship();
const foreignAgentId = await hub.createForeignExecution("execution-foreign");
const missingInterrupt = await hub.interruptExecution("execution-missing", "interrupt-missing");
const missingArchive = await hub.archiveExecution("execution-missing", "archive-missing");
const foreignInterrupt = await hub.interruptExecution("execution-foreign", "interrupt-foreign");
const foreignArchive = await hub.archiveExecution("execution-foreign", "archive-foreign");
expect([missingInterrupt, missingArchive, foreignInterrupt, foreignArchive]).toEqual([
expect.objectContaining({ success: true, error: null }),
expect.objectContaining({ success: true, error: null }),
expect.objectContaining({ success: true, error: null }),
expect.objectContaining({ success: true, error: null }),
]);
expect(await hub.agentRemainsAvailable(foreignAgentId)).toBe(true);
});