diff --git a/packages/kilo-vscode/tests/unit/agent-manager-terminal-state.test.ts b/packages/kilo-vscode/tests/unit/agent-manager-terminal-state.test.ts index 737a90e54c..ac3a8613b3 100644 --- a/packages/kilo-vscode/tests/unit/agent-manager-terminal-state.test.ts +++ b/packages/kilo-vscode/tests/unit/agent-manager-terminal-state.test.ts @@ -197,6 +197,45 @@ describe("Agent Manager terminal state", () => { }) }) + it("hydrates script terminals into independent project contexts", () => { + createRoot((dispose) => { + const item = scene() + item.dispatch({ + type: "agentManager.scriptTerminals", + terminals: [ + { + terminalId: "script:setup-a", + projectId: "prj-a", + worktreeId: "wt-1", + kind: "setup", + title: "Setup", + wsUrl: "ws://script:setup-a", + state: "running", + font, + }, + { + terminalId: "script:setup-b", + projectId: "prj-b", + worktreeId: "wt-1", + kind: "setup", + title: "Setup", + wsUrl: "ws://script:setup-b", + state: "running", + font, + }, + ], + } satisfies ExtensionMessage) + + expect(item.state.sidesForContext("prj-a:wt-1").map((term) => term.id)).toEqual(["script:setup-a"]) + expect(item.state.sidesForContext("prj-b:wt-1").map((term) => term.id)).toEqual(["script:setup-b"]) + expect(item.events.running).toEqual([ + { contextKey: "prj-a:wt-1", terminalId: "script:setup-a" }, + { contextKey: "prj-b:wt-1", terminalId: "script:setup-b" }, + ]) + dispose() + }) + }) + it("activates a Setup terminal that hydrates before its worktree is selected", () => { createRoot((dispose) => { const item = scene(LOCAL) diff --git a/packages/kilo-vscode/tests/unit/script-terminal-manager.test.ts b/packages/kilo-vscode/tests/unit/script-terminal-manager.test.ts index 8feae7e2fa..c9f97d8b6a 100644 --- a/packages/kilo-vscode/tests/unit/script-terminal-manager.test.ts +++ b/packages/kilo-vscode/tests/unit/script-terminal-manager.test.ts @@ -156,6 +156,32 @@ describe("ScriptTerminalManager", () => { expect(ctx.snapshots.at(-1)?.[0]?.worktreeId).toBeNull() }) + it("keeps identical worktree script terminals independent across projects", async () => { + let id = 0 + const ctx = harness({ + create: async () => ({ + data: { location: { directory: config.cwd }, data: { ...info(), id: `pty-${++id}` } }, + }), + get: async () => ({ + data: { location: { directory: config.cwd }, data: { ...info(), id: `pty-${id}` } }, + }), + }) + + await ctx.manager.start("setup", { ...config, projectId: "prj-a" }, () => undefined) + await ctx.manager.start("setup", { ...config, projectId: "prj-b" }, () => undefined) + + expect(ctx.snapshots.at(-1)).toEqual([ + expect.objectContaining({ projectId: "prj-a", worktreeId: "wt-1", kind: "setup" }), + expect.objectContaining({ projectId: "prj-b", worktreeId: "wt-1", kind: "setup" }), + ]) + expect(ctx.manager.active("setup", "wt-1", "prj-a")).toBe(true) + expect(ctx.manager.active("setup", "wt-1", "prj-b")).toBe(true) + + await ctx.manager.clear("setup", "wt-1", "prj-a") + expect(ctx.manager.active("setup", "wt-1", "prj-a")).toBe(false) + expect(ctx.manager.active("setup", "wt-1", "prj-b")).toBe(true) + }) + it("builds canonical authenticated replay URLs", () => { const value = buildScriptTerminalWsUrl( { baseUrl: "http://127.0.0.1:4096", password: "secret" }, diff --git a/packages/kilo-vscode/tests/unit/setup-script-task.test.ts b/packages/kilo-vscode/tests/unit/setup-script-task.test.ts index 1b961d23d9..7714cad6c2 100644 --- a/packages/kilo-vscode/tests/unit/setup-script-task.test.ts +++ b/packages/kilo-vscode/tests/unit/setup-script-task.test.ts @@ -312,7 +312,10 @@ describe("runWorktreeSetupScript", () => { return dir } - function flow(script: boolean, opts?: { destination?: "vscode" | "agentManager"; code?: number }) { + function flow( + script: boolean, + opts?: { destination?: "vscode" | "agentManager"; code?: number; projectId?: string }, + ) { const posted: AgentManagerOutMessage[] = [] const runs: string[] = [] const ctx = harness() @@ -323,6 +326,7 @@ describe("runWorktreeSetupScript", () => { const input = { service: new SetupScriptService(root(script)), destination: opts?.destination ?? ("vscode" as const), + projectId: opts?.projectId, worktreeId: "wt-1", trusted: () => true, manager: ctx.manager, @@ -349,6 +353,17 @@ describe("runWorktreeSetupScript", () => { expect(scene.runs).toEqual(["/repo/worktree"]) }) + it("stamps progress and embedded terminals with the owning project", async () => { + const scene = flow(true, { destination: "agentManager", projectId: "prj-a" }) + const result = runWorktreeSetupScript(scene.input, { worktreePath: "/repo/worktree", repoPath: "/repo" }) + await new Promise((resolve) => setTimeout(resolve, 0)) + + expect(scene.posted[0]).toMatchObject({ projectId: "prj-a", worktreeId: "wt-1" }) + expect(scene.ctx.starts[0]?.config).toMatchObject({ projectId: "prj-a", worktreeId: "wt-1" }) + scene.ctx.starts[0]?.done({ exitCode: 0 }) + await result + }) + it("stays silent when no setup script is configured", async () => { const scene = flow(false) await runWorktreeSetupScript(scene.input, { worktreePath: "/repo/worktree", repoPath: "/repo" }) diff --git a/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx b/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx index f031eead53..c0765acb2e 100644 --- a/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx +++ b/packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx @@ -2358,10 +2358,10 @@ const AgentManagerContent: Component = () => { terminalDestination={sideCtl.destination} terminalDestinationActive={() => sidePanel() === "terminal"} terminalKeybind={() => kb().showTerminal ?? ""} - onTerminalDestinationOpen={() => { - cancelAmbientSetup() - sideCtl.openPreferred("tab_toolbar") - }} + onTerminalDestinationOpen={() => { + cancelAmbientSetup() + sideCtl.openPreferred("tab_toolbar") + }} onTerminalDestinationChoose={sideCtl.choose} track={metrics.click} />