mirror of
https://github.com/l0ng-ai/openidea.git
synced 2026-11-06 08:07:49 +00:00
Agent sessions can be opened in the Agents tool window beside the editor (Open in Tool Window / Open in Editor in the task menus, or by dragging between the editor and the tool window); opening in an editor tab stays the default. Fixes in review: - A docked conversation kept the previous task's subagent view after switching tasks: the session's local state is now keyed by task. - Moving a conversation between the editor and the tool window lost its scroll position: position and follow-output are kept per conversation (task or subagent). - In a short bottom tool window the message area shrank to a few pixels: wide windows place the task list beside the conversation, narrow ones cap the list height. - Drag and drop and list selection now go through the agents.dockTask / undockTask / openTask actions. Co-authored-by: a-rookie-of-C-language <186315048+a-rookie-of-C-language@users.noreply.github.com>
120 lines
4.7 KiB
TypeScript
120 lines
4.7 KiB
TypeScript
/** Docking agent conversations between the editor and the Agents tool window by drag & drop. */
|
|
import { expect, test, type Page } from "@playwright/test";
|
|
|
|
const TASK = {
|
|
id: "t1",
|
|
title: "Repro task",
|
|
status: "ready" as const,
|
|
isolation: "local" as const,
|
|
provider: "codex",
|
|
model: null,
|
|
cwd: "/dev/acme-api",
|
|
branch: null,
|
|
base: null,
|
|
baseCommit: null,
|
|
error: null,
|
|
createdMs: 0,
|
|
updatedMs: 0,
|
|
config: [],
|
|
commands: [],
|
|
usage: null,
|
|
queue: [],
|
|
};
|
|
|
|
/** Seeds one task in the agents store (no backend in plain-browser mode) and docks it. */
|
|
async function seedAndDock(page: Page) {
|
|
await page.evaluate(async (task) => {
|
|
// Paths go through variables: knip can't resolve these browser-absolute URLs.
|
|
const storePath = "/src/features/agents/store.ts";
|
|
const store = await import(storePath);
|
|
store.useAgents.setState({
|
|
loaded: true,
|
|
error: null,
|
|
providers: [],
|
|
tasks: new Map([[task.id, task]]),
|
|
unread: new Set(),
|
|
docked: null,
|
|
});
|
|
store.dockTask(task.id);
|
|
}, TASK);
|
|
await page.locator("span[title='Repro task']").first().waitFor();
|
|
}
|
|
|
|
test("drags the docked conversation back onto the editor", async ({ page }) => {
|
|
await page.goto("/");
|
|
await page.getByRole("tree", { name: "Project" }).waitFor();
|
|
// An editor pane, so the session tab has a content drop target to land on.
|
|
await page.getByRole("treeitem", { name: "README.md", exact: true }).dblclick();
|
|
await page.getByRole("tab", { name: "README.md" }).waitFor();
|
|
await seedAndDock(page);
|
|
|
|
const title = page.locator("span[title='Repro task']").first();
|
|
const to = await page.getByRole("tabpanel").first().boundingBox();
|
|
const from = await title.boundingBox();
|
|
await page.mouse.move(from!.x + from!.width / 2, from!.y + from!.height / 2);
|
|
await page.mouse.down();
|
|
await page.mouse.move(to!.x + to!.width / 2, to!.y + to!.height / 2, { steps: 10 });
|
|
await page.mouse.up();
|
|
|
|
await expect(page.getByRole("tab", { name: "Repro task" })).toBeVisible();
|
|
await expect(page.getByRole("tree", { name: "Agent tasks" })).toBeVisible();
|
|
});
|
|
|
|
test("drags the docked conversation back onto the empty editor", async ({ page }) => {
|
|
await page.goto("/");
|
|
await page.getByRole("tree", { name: "Project" }).waitFor();
|
|
// Docking closes the session tab; with no other tabs the editor is empty and
|
|
// must still take the drop (regression: the empty state had no drop target).
|
|
await seedAndDock(page);
|
|
await expect(page.locator("[data-editor-area] [role='tabpanel']")).toHaveCount(0);
|
|
|
|
const title = page.locator("span[title='Repro task']").first();
|
|
const to = await page.getByText("Drop files here to open them").boundingBox();
|
|
const from = await title.boundingBox();
|
|
await page.mouse.move(from!.x + from!.width / 2, from!.y + from!.height / 2);
|
|
await page.mouse.down();
|
|
await page.mouse.move(to!.x + to!.width / 2, to!.y + to!.height / 2, { steps: 10 });
|
|
await page.mouse.up();
|
|
|
|
await expect(page.getByRole("tab", { name: "Repro task" })).toBeVisible();
|
|
});
|
|
|
|
test("docks a session tab dropped on the tool window", async ({ page }) => {
|
|
await page.goto("/");
|
|
await page.getByRole("tree", { name: "Project" }).waitFor();
|
|
await page.getByRole("treeitem", { name: "README.md", exact: true }).dblclick();
|
|
await page.getByRole("tab", { name: "README.md" }).waitFor();
|
|
await page.evaluate(async (task) => {
|
|
const storePath = "/src/features/agents/store.ts";
|
|
const modelPath = "/src/features/agents/model.ts";
|
|
const contributionsPath = "/src/workbench/contributions.ts";
|
|
const store = await import(storePath);
|
|
const { TOOL_WINDOW } = await import(modelPath);
|
|
const { toolWindowService } = await import(contributionsPath);
|
|
store.useAgents.setState({
|
|
loaded: true,
|
|
error: null,
|
|
providers: [],
|
|
tasks: new Map([[task.id, task]]),
|
|
unread: new Set(),
|
|
docked: null,
|
|
});
|
|
toolWindowService().show(TOOL_WINDOW, true);
|
|
store.openTask(task.id);
|
|
}, TASK);
|
|
await page.getByRole("tab", { name: "Repro task" }).waitFor();
|
|
|
|
const tab = page.getByRole("tab", { name: "Repro task" });
|
|
const to = await page.getByRole("tree", { name: "Agent tasks" }).boundingBox();
|
|
const from = await tab.boundingBox();
|
|
await page.mouse.move(from!.x + from!.width / 2, from!.y + from!.height / 2);
|
|
await page.mouse.down();
|
|
await page.mouse.move(to!.x + to!.width / 2, to!.y + to!.height / 2, { steps: 10 });
|
|
await page.mouse.up();
|
|
|
|
// Docked: the tab closes and the tool window gives itself to the conversation.
|
|
await expect(page.getByRole("tab", { name: "Repro task" })).toHaveCount(0);
|
|
await expect(page.getByRole("tree", { name: "Agent tasks" })).toHaveCount(0);
|
|
await expect(page.locator("span[title='Repro task']").first()).toBeVisible();
|
|
});
|