mirror of
https://github.com/getpaseo/paseo.git
synced 2026-10-14 13:22:37 +00:00
Pass workspace ownership when creating terminals
This commit is contained in:
@@ -53,7 +53,7 @@ export interface SeedDaemonClient {
|
||||
cwd: string,
|
||||
name?: string,
|
||||
requestId?: string,
|
||||
options?: { agentId?: string; command?: string; args?: string[] },
|
||||
options?: { agentId?: string; command?: string; args?: string[]; workspaceId?: string },
|
||||
): Promise<{
|
||||
terminal: { id: string; name: string; cwd: string; activity?: TerminalActivity | null } | null;
|
||||
error: string | null;
|
||||
|
||||
@@ -67,8 +67,9 @@ export class TerminalE2EHarness {
|
||||
? {
|
||||
command: input.command,
|
||||
args: input.args,
|
||||
workspaceId: this.workspaceId,
|
||||
}
|
||||
: undefined;
|
||||
: { workspaceId: this.workspaceId };
|
||||
const result = await this.client.createTerminal(
|
||||
this.tempRepo.path,
|
||||
input.name,
|
||||
@@ -90,7 +91,9 @@ export class TerminalE2EHarness {
|
||||
const timeoutMs = input.timeoutMs ?? 10_000;
|
||||
const deadline = Date.now() + timeoutMs;
|
||||
while (Date.now() < deadline) {
|
||||
const result = await this.client.listTerminals(this.tempRepo.path);
|
||||
const result = await this.client.listTerminals(this.tempRepo.path, undefined, {
|
||||
workspaceId: this.workspaceId,
|
||||
});
|
||||
const terminal = result.terminals.find((entry) => entry.id === input.terminalId);
|
||||
const activity = terminal?.activity ?? null;
|
||||
const attentionMatches =
|
||||
|
||||
@@ -108,7 +108,14 @@ test.describe("Terminal title propagation", () => {
|
||||
test.skip("terminal tab title updates from OSC title escape sequence", async ({ page }) => {
|
||||
test.setTimeout(60_000);
|
||||
|
||||
const result = await workspace.client.createTerminal(workspace.repoPath, "title-test");
|
||||
const result = await workspace.client.createTerminal(
|
||||
workspace.repoPath,
|
||||
"title-test",
|
||||
undefined,
|
||||
{
|
||||
workspaceId: workspace.workspaceId,
|
||||
},
|
||||
);
|
||||
if (!result.terminal) throw new Error(`Failed to create terminal: ${result.error}`);
|
||||
const terminalId = result.terminal.id;
|
||||
|
||||
@@ -138,7 +145,14 @@ test.describe("Terminal title propagation", () => {
|
||||
test.skip("title debouncing coalesces rapid changes", async ({ page }) => {
|
||||
test.setTimeout(60_000);
|
||||
|
||||
const result = await workspace.client.createTerminal(workspace.repoPath, "debounce-test");
|
||||
const result = await workspace.client.createTerminal(
|
||||
workspace.repoPath,
|
||||
"debounce-test",
|
||||
undefined,
|
||||
{
|
||||
workspaceId: workspace.workspaceId,
|
||||
},
|
||||
);
|
||||
if (!result.terminal) throw new Error(`Failed to create terminal: ${result.error}`);
|
||||
const terminalId = result.terminal.id;
|
||||
|
||||
|
||||
@@ -20,7 +20,18 @@ export async function runCreateCommand(
|
||||
const cwd = options.cwd ?? process.cwd();
|
||||
|
||||
try {
|
||||
const payload = await client.createTerminal(cwd, options.name);
|
||||
const opened = await client.openProject(cwd);
|
||||
if (!opened.workspace) {
|
||||
const error: CommandError = {
|
||||
code: "WORKSPACE_OPEN_FAILED",
|
||||
message: opened.error ?? "Failed to open workspace",
|
||||
};
|
||||
throw error;
|
||||
}
|
||||
|
||||
const payload = await client.createTerminal(cwd, options.name, undefined, {
|
||||
workspaceId: opened.workspace.id,
|
||||
});
|
||||
if (!payload.terminal) {
|
||||
const error: CommandError = {
|
||||
code: "TERMINAL_CREATE_FAILED",
|
||||
|
||||
@@ -519,7 +519,18 @@ test("bootstraps configured worktree terminals after setup succeeds", async () =
|
||||
const setupPort = readFileSync(path.join(agent.cwd, "setup-port.txt"), "utf8").trim();
|
||||
expect(setupPort.length).toBeGreaterThan(0);
|
||||
|
||||
const createdTerminal = await ctx.client.createTerminal(agent.cwd, "Manual Port Check");
|
||||
const openedWorktree = await ctx.client.openProject(agent.cwd);
|
||||
if (!openedWorktree.workspace) {
|
||||
throw new Error(openedWorktree.error ?? `Failed to open worktree workspace for ${agent.cwd}`);
|
||||
}
|
||||
const createdTerminal = await ctx.client.createTerminal(
|
||||
agent.cwd,
|
||||
"Manual Port Check",
|
||||
undefined,
|
||||
{
|
||||
workspaceId: openedWorktree.workspace.id,
|
||||
},
|
||||
);
|
||||
expect(createdTerminal.error).toBeNull();
|
||||
expect(createdTerminal.terminal).toBeTruthy();
|
||||
const manualTerminalId = createdTerminal.terminal?.id;
|
||||
|
||||
@@ -24,6 +24,26 @@ function tmpCwd(): string {
|
||||
return mkdtempSync(path.join(tmpdir(), "daemon-terminal-e2e-"));
|
||||
}
|
||||
|
||||
interface CreateTerminalInWorkspaceInput {
|
||||
cwd: string;
|
||||
name?: string;
|
||||
options?: { command?: string; args?: string[] };
|
||||
}
|
||||
|
||||
async function createTerminalInWorkspace(
|
||||
client: DaemonClient,
|
||||
input: CreateTerminalInWorkspaceInput,
|
||||
) {
|
||||
const opened = await client.openProject(input.cwd);
|
||||
if (!opened.workspace) {
|
||||
throw new Error(opened.error ?? `Failed to open workspace for ${input.cwd}`);
|
||||
}
|
||||
return client.createTerminal(input.cwd, input.name, undefined, {
|
||||
...input.options,
|
||||
workspaceId: opened.workspace.id,
|
||||
});
|
||||
}
|
||||
|
||||
function createLogger() {
|
||||
return {
|
||||
debug: () => {},
|
||||
@@ -602,7 +622,11 @@ async function measureRepeatCadenceForTerminal(input: {
|
||||
options?: { command?: string; args?: string[] };
|
||||
setupInput?: string;
|
||||
}): Promise<TerminalRepeatCadenceMeasurement> {
|
||||
const created = await ctx.client.createTerminal(input.cwd, input.name, undefined, input.options);
|
||||
const created = await createTerminalInWorkspace(ctx.client, {
|
||||
cwd: input.cwd,
|
||||
name: input.name,
|
||||
options: input.options,
|
||||
});
|
||||
const terminalId = created.terminal!.id;
|
||||
const ws = await connectRawWebSocket(ctx.daemon.port);
|
||||
|
||||
@@ -742,7 +766,7 @@ test("lists terminals for a directory", async () => {
|
||||
|
||||
test("client connects and receives a snapshot of the current terminal state", async () => {
|
||||
const cwd = tmpCwd();
|
||||
const created = await ctx.client.createTerminal(cwd);
|
||||
const created = await createTerminalInWorkspace(ctx.client, { cwd });
|
||||
const terminalId = created.terminal!.id;
|
||||
|
||||
ctx.client.sendTerminalInput(terminalId, {
|
||||
@@ -764,7 +788,7 @@ test("client connects and receives a snapshot of the current terminal state", as
|
||||
|
||||
test("live terminal restore skips the initial snapshot", async () => {
|
||||
const cwd = tmpCwd();
|
||||
const created = await ctx.client.createTerminal(cwd);
|
||||
const created = await createTerminalInWorkspace(ctx.client, { cwd });
|
||||
const terminalId = created.terminal!.id;
|
||||
const ws = await connectRawWebSocket(ctx.daemon.port);
|
||||
|
||||
@@ -809,12 +833,15 @@ test("live terminal restore skips the initial snapshot", async () => {
|
||||
|
||||
test("visible terminal restore sends bounded ANSI history", async () => {
|
||||
const cwd = tmpCwd();
|
||||
const created = await ctx.client.createTerminal(cwd, undefined, undefined, {
|
||||
command: "/bin/sh",
|
||||
args: [
|
||||
"-lc",
|
||||
"i=1; while [ $i -le 1200 ]; do printf 'restore-line-%04d\\n' $i; i=$((i+1)); done; sleep 30",
|
||||
],
|
||||
const created = await createTerminalInWorkspace(ctx.client, {
|
||||
cwd,
|
||||
options: {
|
||||
command: "/bin/sh",
|
||||
args: [
|
||||
"-lc",
|
||||
"i=1; while [ $i -le 1200 ]; do printf 'restore-line-%04d\\n' $i; i=$((i+1)); done; sleep 30",
|
||||
],
|
||||
},
|
||||
});
|
||||
const terminalId = created.terminal!.id;
|
||||
const ws = await connectRawWebSocket(ctx.daemon.port);
|
||||
@@ -853,9 +880,12 @@ test("visible terminal restore sends bounded ANSI history", async () => {
|
||||
|
||||
test("propagates debounced terminal titles through list responses and snapshots", async () => {
|
||||
const cwd = tmpCwd();
|
||||
const created = await ctx.client.createTerminal(cwd, undefined, undefined, {
|
||||
command: "/bin/sh",
|
||||
args: ["-lc", "printf '\\033]0;Build Output\\007'; sleep 2"],
|
||||
const created = await createTerminalInWorkspace(ctx.client, {
|
||||
cwd,
|
||||
options: {
|
||||
command: "/bin/sh",
|
||||
args: ["-lc", "printf '\\033]0;Build Output\\007'; sleep 2"],
|
||||
},
|
||||
});
|
||||
const terminalId = created.terminal!.id;
|
||||
|
||||
@@ -886,7 +916,7 @@ test("propagates debounced terminal titles through list responses and snapshots"
|
||||
|
||||
test("subscribe response is sent before the initial snapshot frame", async () => {
|
||||
const cwd = tmpCwd();
|
||||
const created = await ctx.client.createTerminal(cwd);
|
||||
const created = await createTerminalInWorkspace(ctx.client, { cwd });
|
||||
const terminalId = created.terminal!.id;
|
||||
const ws = await connectRawWebSocket(ctx.daemon.port);
|
||||
|
||||
@@ -982,7 +1012,7 @@ test("subscribe response is sent before the initial snapshot frame", async () =>
|
||||
|
||||
test("client sends input and receives output as raw bytes", async () => {
|
||||
const cwd = tmpCwd();
|
||||
const created = await ctx.client.createTerminal(cwd);
|
||||
const created = await createTerminalInWorkspace(ctx.client, { cwd });
|
||||
const terminalId = created.terminal!.id;
|
||||
|
||||
const outputPromise = waitForTerminalOutput(ctx.client, terminalId, (text) =>
|
||||
@@ -1008,8 +1038,8 @@ test("default zsh terminal does not eagerly flush full state during repeat burst
|
||||
|
||||
test("one client can stream two terminals concurrently", async () => {
|
||||
const cwd = tmpCwd();
|
||||
const firstCreated = await ctx.client.createTerminal(cwd, "first");
|
||||
const secondCreated = await ctx.client.createTerminal(cwd, "second");
|
||||
const firstCreated = await createTerminalInWorkspace(ctx.client, { cwd, name: "first" });
|
||||
const secondCreated = await createTerminalInWorkspace(ctx.client, { cwd, name: "second" });
|
||||
const firstTerminalId = firstCreated.terminal!.id;
|
||||
const secondTerminalId = secondCreated.terminal!.id;
|
||||
|
||||
@@ -1044,7 +1074,7 @@ test("one client can stream two terminals concurrently", async () => {
|
||||
|
||||
test("disconnect and reconnect both receive the current snapshot", async () => {
|
||||
const cwd = tmpCwd();
|
||||
const created = await ctx.client.createTerminal(cwd);
|
||||
const created = await createTerminalInWorkspace(ctx.client, { cwd });
|
||||
const terminalId = created.terminal!.id;
|
||||
|
||||
await ctx.client.subscribeTerminal(terminalId);
|
||||
@@ -1068,8 +1098,10 @@ test("disconnect and reconnect both receive the current snapshot", async () => {
|
||||
|
||||
test("reconnected terminal streams replay active input modes after the snapshot", async () => {
|
||||
const cwd = tmpCwd();
|
||||
const created = await ctx.client.createTerminal(cwd, "input-mode-probe", undefined, {
|
||||
...createInputModeProbeOptions(),
|
||||
const created = await createTerminalInWorkspace(ctx.client, {
|
||||
cwd,
|
||||
name: "input-mode-probe",
|
||||
options: createInputModeProbeOptions(),
|
||||
});
|
||||
const terminalId = created.terminal!.id;
|
||||
const firstWs = await connectRawWebSocket(ctx.daemon.port);
|
||||
@@ -1125,8 +1157,10 @@ test("reconnected terminal streams replay active input modes after the snapshot"
|
||||
|
||||
test("reconnected terminal streams do not replay input modes before they are enabled", async () => {
|
||||
const cwd = tmpCwd();
|
||||
const created = await ctx.client.createTerminal(cwd, "input-mode-inactive-probe", undefined, {
|
||||
...createInputModeProbeOptions(),
|
||||
const created = await createTerminalInWorkspace(ctx.client, {
|
||||
cwd,
|
||||
name: "input-mode-inactive-probe",
|
||||
options: createInputModeProbeOptions(),
|
||||
});
|
||||
const terminalId = created.terminal!.id;
|
||||
const ws = await connectRawWebSocket(ctx.daemon.port);
|
||||
@@ -1146,7 +1180,7 @@ test("reconnected terminal streams do not replay input modes before they are ena
|
||||
|
||||
test("fast output to a slow websocket client falls back to a snapshot", async () => {
|
||||
const cwd = tmpCwd();
|
||||
const created = await ctx.client.createTerminal(cwd);
|
||||
const created = await createTerminalInWorkspace(ctx.client, { cwd });
|
||||
const terminalId = created.terminal!.id;
|
||||
const ws = await connectRawWebSocket(ctx.daemon.port);
|
||||
|
||||
@@ -1179,7 +1213,7 @@ test("fast output to a slow websocket client falls back to a snapshot", async ()
|
||||
|
||||
test("multiple clients on the same terminal each receive output independently", async () => {
|
||||
const cwd = tmpCwd();
|
||||
const created = await ctx.client.createTerminal(cwd);
|
||||
const created = await createTerminalInWorkspace(ctx.client, { cwd });
|
||||
const terminalId = created.terminal!.id;
|
||||
const secondClient = await connectClient(
|
||||
ctx.daemon.port,
|
||||
@@ -1212,7 +1246,7 @@ test("multiple clients on the same terminal each receive output independently",
|
||||
|
||||
test("resize updates server dimensions without sending a live snapshot", async () => {
|
||||
const cwd = tmpCwd();
|
||||
const created = await ctx.client.createTerminal(cwd);
|
||||
const created = await createTerminalInWorkspace(ctx.client, { cwd });
|
||||
const terminalId = created.terminal!.id;
|
||||
await ctx.client.subscribeTerminal(terminalId);
|
||||
ctx.client.sendTerminalInput(terminalId, {
|
||||
@@ -1232,7 +1266,7 @@ test("resize updates server dimensions without sending a live snapshot", async (
|
||||
|
||||
test("resize does not stall streamed output for an attached client", async () => {
|
||||
const cwd = tmpCwd();
|
||||
const created = await ctx.client.createTerminal(cwd);
|
||||
const created = await createTerminalInWorkspace(ctx.client, { cwd });
|
||||
const terminalId = created.terminal!.id;
|
||||
const secondClient = await connectClient(
|
||||
ctx.daemon.port,
|
||||
@@ -1271,7 +1305,7 @@ test("resize does not stall streamed output for an attached client", async () =>
|
||||
|
||||
test("terminal exits notify the client", async () => {
|
||||
const cwd = tmpCwd();
|
||||
const created = await ctx.client.createTerminal(cwd);
|
||||
const created = await createTerminalInWorkspace(ctx.client, { cwd });
|
||||
const terminalId = created.terminal!.id;
|
||||
|
||||
let sawExit = false;
|
||||
@@ -1296,7 +1330,7 @@ test("terminal exits notify the client", async () => {
|
||||
|
||||
test("websocket terminate then new connection gets snapshot with all prior output", async () => {
|
||||
const cwd = tmpCwd();
|
||||
const created = await ctx.client.createTerminal(cwd);
|
||||
const created = await createTerminalInWorkspace(ctx.client, { cwd });
|
||||
const terminalId = created.terminal!.id;
|
||||
const firstSocket = await connectRawWebSocket(ctx.daemon.port);
|
||||
|
||||
@@ -1359,7 +1393,7 @@ test("websocket terminate then new connection gets snapshot with all prior outpu
|
||||
|
||||
test("two clients can both send input and each sees its own output", async () => {
|
||||
const cwd = tmpCwd();
|
||||
const created = await ctx.client.createTerminal(cwd);
|
||||
const created = await createTerminalInWorkspace(ctx.client, { cwd });
|
||||
const terminalId = created.terminal!.id;
|
||||
const secondClient = await connectClient(
|
||||
ctx.daemon.port,
|
||||
@@ -1397,7 +1431,10 @@ test("two clients can both send input and each sees its own output", async () =>
|
||||
|
||||
test("snapshot fidelity through websocket decode preserves dimensions and visible text", async () => {
|
||||
const cwd = tmpCwd();
|
||||
const created = await ctx.client.createTerminal(cwd, "Snapshot Fidelity");
|
||||
const created = await createTerminalInWorkspace(ctx.client, {
|
||||
cwd,
|
||||
name: "Snapshot Fidelity",
|
||||
});
|
||||
const terminalId = created.terminal!.id;
|
||||
|
||||
ctx.client.sendTerminalInput(terminalId, {
|
||||
@@ -1435,7 +1472,7 @@ test("snapshot fidelity through websocket decode preserves dimensions and visibl
|
||||
|
||||
test("terminal exit prevents resubscribe and sends no frames after exit", async () => {
|
||||
const cwd = tmpCwd();
|
||||
const created = await ctx.client.createTerminal(cwd);
|
||||
const created = await createTerminalInWorkspace(ctx.client, { cwd });
|
||||
const terminalId = created.terminal!.id;
|
||||
const ws = await connectRawWebSocket(ctx.daemon.port);
|
||||
|
||||
@@ -1470,7 +1507,7 @@ test("terminal exit prevents resubscribe and sends no frames after exit", async
|
||||
|
||||
test("empty input frame does not crash the server", async () => {
|
||||
const cwd = tmpCwd();
|
||||
const created = await ctx.client.createTerminal(cwd);
|
||||
const created = await createTerminalInWorkspace(ctx.client, { cwd });
|
||||
const terminalId = created.terminal!.id;
|
||||
const ws = await connectRawWebSocket(ctx.daemon.port);
|
||||
|
||||
@@ -1509,7 +1546,7 @@ test("empty input frame does not crash the server", async () => {
|
||||
|
||||
test("1MB output burst keeps frames decodable and terminal usable afterward", async () => {
|
||||
const cwd = tmpCwd();
|
||||
const created = await ctx.client.createTerminal(cwd);
|
||||
const created = await createTerminalInWorkspace(ctx.client, { cwd });
|
||||
const terminalId = created.terminal!.id;
|
||||
const ws = await connectRawWebSocket(ctx.daemon.port);
|
||||
|
||||
@@ -1568,7 +1605,7 @@ describe("capture", () => {
|
||||
test("captures visible terminal output as plain text", async () => {
|
||||
const cwd = tmpCwd();
|
||||
tempDirs.push(cwd);
|
||||
const created = await ctx.client.createTerminal(cwd);
|
||||
const created = await createTerminalInWorkspace(ctx.client, { cwd });
|
||||
const terminalId = created.terminal!.id;
|
||||
|
||||
await ctx.client.subscribeTerminal(terminalId);
|
||||
@@ -1592,7 +1629,7 @@ describe("capture", () => {
|
||||
test("captures with start/end line range", async () => {
|
||||
const cwd = tmpCwd();
|
||||
tempDirs.push(cwd);
|
||||
const created = await ctx.client.createTerminal(cwd);
|
||||
const created = await createTerminalInWorkspace(ctx.client, { cwd });
|
||||
const terminalId = created.terminal!.id;
|
||||
|
||||
await ctx.client.subscribeTerminal(terminalId);
|
||||
@@ -1625,7 +1662,7 @@ describe("capture", () => {
|
||||
test("supports negative line indices", async () => {
|
||||
const cwd = tmpCwd();
|
||||
tempDirs.push(cwd);
|
||||
const created = await ctx.client.createTerminal(cwd);
|
||||
const created = await createTerminalInWorkspace(ctx.client, { cwd });
|
||||
const terminalId = created.terminal!.id;
|
||||
|
||||
await ctx.client.subscribeTerminal(terminalId);
|
||||
@@ -1655,7 +1692,7 @@ describe("capture", () => {
|
||||
test("strips ANSI by default", async () => {
|
||||
const cwd = tmpCwd();
|
||||
tempDirs.push(cwd);
|
||||
const created = await ctx.client.createTerminal(cwd);
|
||||
const created = await createTerminalInWorkspace(ctx.client, { cwd });
|
||||
const terminalId = created.terminal!.id;
|
||||
|
||||
await ctx.client.subscribeTerminal(terminalId);
|
||||
@@ -1686,8 +1723,14 @@ describe("list terminals across directories", () => {
|
||||
const cwd2 = tmpCwd();
|
||||
tempDirs.push(cwd1, cwd2);
|
||||
|
||||
const firstCreated = await ctx.client.createTerminal(cwd1, "first-terminal");
|
||||
const secondCreated = await ctx.client.createTerminal(cwd2, "second-terminal");
|
||||
const firstCreated = await createTerminalInWorkspace(ctx.client, {
|
||||
cwd: cwd1,
|
||||
name: "first-terminal",
|
||||
});
|
||||
const secondCreated = await createTerminalInWorkspace(ctx.client, {
|
||||
cwd: cwd2,
|
||||
name: "second-terminal",
|
||||
});
|
||||
|
||||
const list = await ctx.client.listTerminals();
|
||||
|
||||
@@ -1711,8 +1754,11 @@ describe("list terminals across directories", () => {
|
||||
const cwd2 = tmpCwd();
|
||||
tempDirs.push(cwd1, cwd2);
|
||||
|
||||
const firstCreated = await ctx.client.createTerminal(cwd1, "cwd-one-terminal");
|
||||
await ctx.client.createTerminal(cwd2, "cwd-two-terminal");
|
||||
const firstCreated = await createTerminalInWorkspace(ctx.client, {
|
||||
cwd: cwd1,
|
||||
name: "cwd-one-terminal",
|
||||
});
|
||||
await createTerminalInWorkspace(ctx.client, { cwd: cwd2, name: "cwd-two-terminal" });
|
||||
|
||||
const list = await ctx.client.listTerminals(cwd1);
|
||||
|
||||
|
||||
@@ -231,11 +231,13 @@ function transition(input: {
|
||||
state: "working" | "idle" | "attention" | null;
|
||||
changedAt: number;
|
||||
id?: string;
|
||||
workspaceId?: string;
|
||||
}): TerminalActivityTransitionEvent {
|
||||
return {
|
||||
terminalId: input.id ?? "term-1",
|
||||
name: "bash",
|
||||
cwd: CWD,
|
||||
workspaceId: input.workspaceId ?? "ws-1",
|
||||
activity: input.state ? { state: input.state, changedAt: input.changedAt } : null,
|
||||
previous: { state: input.previousState, changedAt: input.previousChangedAt },
|
||||
};
|
||||
@@ -266,7 +268,7 @@ describe("VoiceAssistantWebSocketServer terminal attention notifications", () =>
|
||||
const payload = readTerminalAttentionMessage(ws);
|
||||
expect(payload.terminalId).toBe("term-1");
|
||||
expect(payload.cwd).toBe(CWD);
|
||||
expect(payload.workspaceId).toBeUndefined();
|
||||
expect(payload.workspaceId).toBe("ws-1");
|
||||
expect(payload.reason).toBe("finished");
|
||||
expect(payload.title).toBe("Terminal finished");
|
||||
expect(payload.body).toBe("bash");
|
||||
@@ -274,9 +276,9 @@ describe("VoiceAssistantWebSocketServer terminal attention notifications", () =>
|
||||
expect(payload.shouldNotify).toBe(false);
|
||||
});
|
||||
|
||||
it("resolves the workspaceId for the terminal cwd into the payload", async () => {
|
||||
it("forwards the terminal event workspaceId into the payload", async () => {
|
||||
const { manager, emit } = createTerminalManager();
|
||||
const { server } = createServer(manager, createWorkspaceRegistry([workspaceRecord()]));
|
||||
const { server } = createServer(manager);
|
||||
const ws = connectClient(server);
|
||||
|
||||
emit(
|
||||
@@ -285,17 +287,18 @@ describe("VoiceAssistantWebSocketServer terminal attention notifications", () =>
|
||||
previousChangedAt: 1000,
|
||||
state: "idle",
|
||||
changedAt: 11001,
|
||||
workspaceId: "ws-event",
|
||||
}),
|
||||
);
|
||||
|
||||
await flushAsync();
|
||||
|
||||
expect(readTerminalAttentionMessage(ws).workspaceId).toBe("ws-1");
|
||||
expect(readTerminalAttentionMessage(ws).workspaceId).toBe("ws-event");
|
||||
});
|
||||
|
||||
it("resolves the parent workspaceId for a subdirectory terminal cwd", async () => {
|
||||
it("keeps the event workspaceId for a subdirectory terminal cwd", async () => {
|
||||
const { manager, emit } = createTerminalManager();
|
||||
const { server } = createServer(manager, createWorkspaceRegistry([workspaceRecord()]));
|
||||
const { server } = createServer(manager);
|
||||
const ws = connectClient(server);
|
||||
|
||||
emit({
|
||||
@@ -313,7 +316,7 @@ describe("VoiceAssistantWebSocketServer terminal attention notifications", () =>
|
||||
expect(readTerminalAttentionMessage(ws).workspaceId).toBe("ws-1");
|
||||
});
|
||||
|
||||
it("omits workspaceId for archived or unregistered workspaces", async () => {
|
||||
it("does not derive terminal attention ownership from the workspace registry", async () => {
|
||||
const { manager, emit } = createTerminalManager();
|
||||
const { server } = createServer(
|
||||
manager,
|
||||
@@ -335,7 +338,7 @@ describe("VoiceAssistantWebSocketServer terminal attention notifications", () =>
|
||||
|
||||
await flushAsync();
|
||||
|
||||
expect(readTerminalAttentionMessage(ws).workspaceId).toBeUndefined();
|
||||
expect(readTerminalAttentionMessage(ws).workspaceId).toBe("ws-1");
|
||||
});
|
||||
|
||||
it("does not broadcast on working -> working (no transition to idle)", async () => {
|
||||
|
||||
@@ -51,6 +51,8 @@ interface DaemonClientLike {
|
||||
createTerminal(
|
||||
cwd: string,
|
||||
name?: string,
|
||||
requestId?: string,
|
||||
options?: { workspaceId?: string },
|
||||
): Promise<{ terminal: { id: string } | null; error: string | null }>;
|
||||
subscribeTerminal(terminalId: string): Promise<{ error: string | null }>;
|
||||
killTerminal(terminalId: string): Promise<unknown>;
|
||||
@@ -62,6 +64,7 @@ interface DaemonClientLike {
|
||||
createAgent(options: {
|
||||
provider: string;
|
||||
cwd: string;
|
||||
workspaceId?: string;
|
||||
model?: string;
|
||||
title?: string;
|
||||
}): Promise<{ id: string }>;
|
||||
@@ -483,6 +486,7 @@ if (process.env.BENCH_INCLUDE_L3 === "1") {
|
||||
async function startLoad(
|
||||
client: DaemonClientLike,
|
||||
cwd: string,
|
||||
workspaceId: string,
|
||||
spec: LevelSpec,
|
||||
): Promise<LoadController> {
|
||||
const agentIds: string[] = [];
|
||||
@@ -490,6 +494,7 @@ async function startLoad(
|
||||
const agent = await client.createAgent({
|
||||
provider: "mock",
|
||||
cwd,
|
||||
workspaceId,
|
||||
model: "five-minute-stream",
|
||||
title: `${spec.id}-load-${i}`,
|
||||
});
|
||||
@@ -511,7 +516,9 @@ async function startLoad(
|
||||
|
||||
let noisyTerminalId: string | null = null;
|
||||
if (spec.secondNoisyTerminal) {
|
||||
const created = await client.createTerminal(cwd, `${spec.id}-noisy`);
|
||||
const created = await client.createTerminal(cwd, `${spec.id}-noisy`, undefined, {
|
||||
workspaceId,
|
||||
});
|
||||
if (created.terminal) {
|
||||
noisyTerminalId = created.terminal.id;
|
||||
await client.subscribeTerminal(noisyTerminalId);
|
||||
@@ -563,11 +570,12 @@ interface LevelResult {
|
||||
async function runLevel(
|
||||
client: DaemonClientLike,
|
||||
cwd: string,
|
||||
workspaceId: string,
|
||||
spec: LevelSpec,
|
||||
measuredTerminalId: string,
|
||||
): Promise<LevelResult> {
|
||||
console.log(`\n=== ${spec.id} (${spec.description}) ===`);
|
||||
const load = await startLoad(client, cwd, spec);
|
||||
const load = await startLoad(client, cwd, workspaceId, spec);
|
||||
try {
|
||||
// Ping sampling runs concurrently with the echo + burst measurements so it
|
||||
// observes the daemon under the same load the latency primitives see.
|
||||
@@ -679,7 +687,11 @@ async function main(): Promise<void> {
|
||||
throw new Error(`Failed to open project: ${opened.error}`);
|
||||
}
|
||||
|
||||
const created = await client.createTerminal(workspaceDir, "measured");
|
||||
const workspaceId = opened.workspace.id;
|
||||
|
||||
const created = await client.createTerminal(workspaceDir, "measured", undefined, {
|
||||
workspaceId,
|
||||
});
|
||||
if (!created.terminal) {
|
||||
throw new Error(`Failed to create terminal: ${created.error}`);
|
||||
}
|
||||
@@ -691,7 +703,7 @@ async function main(): Promise<void> {
|
||||
|
||||
const results: LevelResult[] = [];
|
||||
for (const spec of LEVELS) {
|
||||
const result = await runLevel(client, workspaceDir, spec, measuredTerminalId);
|
||||
const result = await runLevel(client, workspaceDir, workspaceId, spec, measuredTerminalId);
|
||||
results.push(result);
|
||||
// Settle between levels so a previous level's load fully drains.
|
||||
await sleep(2_000);
|
||||
|
||||
Reference in New Issue
Block a user