From 3cf3a634230dcc57215735dcb35c9c54dd138068 Mon Sep 17 00:00:00 2001 From: Mohamed Boudra Date: Tue, 16 Jun 2026 17:35:16 +0700 Subject: [PATCH] Pass workspace ownership when creating terminals --- packages/app/e2e/helpers/seed-client.ts | 2 +- packages/app/e2e/helpers/terminal-dsl.ts | 7 +- packages/app/e2e/launcher-tab.spec.ts | 18 ++- packages/cli/src/commands/terminal/create.ts | 13 +- .../daemon-e2e/git-operations.e2e.test.ts | 13 +- .../server/daemon-e2e/terminal.e2e.test.ts | 126 ++++++++++++------ ...cket-server.terminal-notifications.test.ts | 19 +-- scripts/benchmark-terminal-latency.ts | 20 ++- 8 files changed, 159 insertions(+), 59 deletions(-) diff --git a/packages/app/e2e/helpers/seed-client.ts b/packages/app/e2e/helpers/seed-client.ts index 1766c923f..6f860cb42 100644 --- a/packages/app/e2e/helpers/seed-client.ts +++ b/packages/app/e2e/helpers/seed-client.ts @@ -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; diff --git a/packages/app/e2e/helpers/terminal-dsl.ts b/packages/app/e2e/helpers/terminal-dsl.ts index 90b521c6a..78ae2ada3 100644 --- a/packages/app/e2e/helpers/terminal-dsl.ts +++ b/packages/app/e2e/helpers/terminal-dsl.ts @@ -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 = diff --git a/packages/app/e2e/launcher-tab.spec.ts b/packages/app/e2e/launcher-tab.spec.ts index 68fd56110..15d916709 100644 --- a/packages/app/e2e/launcher-tab.spec.ts +++ b/packages/app/e2e/launcher-tab.spec.ts @@ -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; diff --git a/packages/cli/src/commands/terminal/create.ts b/packages/cli/src/commands/terminal/create.ts index 71e662052..b71833b87 100644 --- a/packages/cli/src/commands/terminal/create.ts +++ b/packages/cli/src/commands/terminal/create.ts @@ -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", diff --git a/packages/server/src/server/daemon-e2e/git-operations.e2e.test.ts b/packages/server/src/server/daemon-e2e/git-operations.e2e.test.ts index 09c29f076..64ba85eb4 100644 --- a/packages/server/src/server/daemon-e2e/git-operations.e2e.test.ts +++ b/packages/server/src/server/daemon-e2e/git-operations.e2e.test.ts @@ -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; diff --git a/packages/server/src/server/daemon-e2e/terminal.e2e.test.ts b/packages/server/src/server/daemon-e2e/terminal.e2e.test.ts index 7b3667a0e..d96d85fa3 100644 --- a/packages/server/src/server/daemon-e2e/terminal.e2e.test.ts +++ b/packages/server/src/server/daemon-e2e/terminal.e2e.test.ts @@ -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 { - 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); diff --git a/packages/server/src/server/websocket-server.terminal-notifications.test.ts b/packages/server/src/server/websocket-server.terminal-notifications.test.ts index 54d0d92f3..2ef83adff 100644 --- a/packages/server/src/server/websocket-server.terminal-notifications.test.ts +++ b/packages/server/src/server/websocket-server.terminal-notifications.test.ts @@ -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 () => { diff --git a/scripts/benchmark-terminal-latency.ts b/scripts/benchmark-terminal-latency.ts index 9c6d4a626..926822ae8 100644 --- a/scripts/benchmark-terminal-latency.ts +++ b/scripts/benchmark-terminal-latency.ts @@ -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; @@ -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 { 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 { 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 { 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 { 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);