Each project (space) now has a main agent and a main conversation of its own. The user talks to it, and it delegates to the project's specialists. Creating a project sets both up and opens the conversation, and deleting an otherwise empty project removes them with it. Claude-Session: https://claude.ai/code/session_01R8hSsKTB3W3e61wD6kvzur Co-authored-by: Claude <noreply@anthropic.com>
88 lines
4.0 KiB
TypeScript
88 lines
4.0 KiB
TypeScript
import { expect, type Locator, test } from "@playwright/test";
|
|
import { captureScreenshot, completeOnboarding, rpc, signup, waitForChatComposer } from "./helpers";
|
|
|
|
function projectSpace(projekte: Locator, name: string) {
|
|
return projekte.locator('[data-sidebar-group^="project-space:"]').filter({ hasText: name });
|
|
}
|
|
|
|
test("a new project opens straight into the conversation with its own main agent", async ({
|
|
page,
|
|
}, testInfo) => {
|
|
const stamp = Date.now();
|
|
await signup(page, `project-main-${stamp}@rakazo.test`, "password12", "Project Main");
|
|
await completeOnboarding(page);
|
|
const homeSpaceId = await page.evaluate(() => window.localStorage.getItem("rakazo:space-id"));
|
|
const homeMain = await rpc<{ id: string }>(page, "bots/ensureMain", {});
|
|
|
|
const projekte = page.locator(".sidebar-projekte");
|
|
await projekte.getByTestId("create-project-trigger").click();
|
|
const dialog = page.getByRole("dialog", { name: "New project" });
|
|
await dialog.getByLabel("Name").fill("Launch");
|
|
await dialog.getByRole("button", { name: "Create project", exact: true }).click();
|
|
await page.waitForURL(/\/app\/g\/[^/]+$/, { timeout: 30_000 });
|
|
await expect
|
|
.poll(() => page.evaluate(() => window.localStorage.getItem("rakazo:space-id")))
|
|
.not.toBe(homeSpaceId);
|
|
await waitForChatComposer(page);
|
|
|
|
// The project is one conversation, led by a main agent of its own.
|
|
const projectEntry = projectSpace(projekte, "Launch").getByRole("button", {
|
|
name: "Chief, main conversation",
|
|
});
|
|
await expect(projectEntry).toBeVisible({ timeout: 30_000 });
|
|
await expect(projekte.getByRole("button", { name: "Starten", exact: true })).toHaveCount(0);
|
|
const groupId = new URL(page.url()).pathname.split("/").at(-1)!;
|
|
const snapshot = await rpc<{ leadBotId?: string }>(page, "threads/get", { groupId });
|
|
expect(snapshot.leadBotId).toBeTruthy();
|
|
expect(snapshot.leadBotId).not.toBe(homeMain.id);
|
|
|
|
const composer = page.getByRole("combobox", { name: "Message" });
|
|
await composer.fill("What should we do first?");
|
|
await composer.press("Enter");
|
|
const transcript = page.getByTestId("transcript");
|
|
await expect(transcript).toContainText(/on it|handled/i, { timeout: 60_000 });
|
|
await captureScreenshot(page, testInfo, "project-main-conversation");
|
|
});
|
|
|
|
test("a project's main agent hands work to the project's specialist", async ({
|
|
page,
|
|
}, testInfo) => {
|
|
const stamp = Date.now();
|
|
await signup(page, `project-delegate-${stamp}@rakazo.test`, "password12", "Project Delegate");
|
|
await completeOnboarding(page);
|
|
|
|
const projekte = page.locator(".sidebar-projekte");
|
|
await projekte.getByTestId("create-project-trigger").click();
|
|
const dialog = page.getByRole("dialog", { name: "New project" });
|
|
await dialog.getByLabel("Name").fill("Website");
|
|
await dialog.getByRole("button", { name: "Create project", exact: true }).click();
|
|
await page.waitForURL(/\/app\/g\/[^/]+$/, { timeout: 30_000 });
|
|
await waitForChatComposer(page);
|
|
const groupId = new URL(page.url()).pathname.split("/").at(-1)!;
|
|
|
|
const designer = await rpc<{ id: string }>(page, "bots/create", {
|
|
name: "Designer",
|
|
title: "",
|
|
description: "",
|
|
instructions: "",
|
|
notifyOnFinish: true,
|
|
});
|
|
const composer = page.getByRole("combobox", { name: "Message" });
|
|
await composer.fill("delegate to the bot named Designer: sketch a landing page");
|
|
await composer.press("Enter");
|
|
|
|
const transcript = page.getByTestId("transcript");
|
|
const card = transcript.getByTestId("delegated-task-card");
|
|
await expect(card).toBeVisible({ timeout: 60_000 });
|
|
await expect(card).toContainText("Designer");
|
|
await expect(card).toHaveAttribute("data-status", "done", { timeout: 60_000 });
|
|
await expect(
|
|
transcript.getByText("closing the task: sketch a landing page.").first(),
|
|
).toBeVisible({ timeout: 60_000 });
|
|
const snapshot = await rpc<{ members?: Array<{ botId: string }> }>(page, "threads/get", {
|
|
groupId,
|
|
});
|
|
expect(snapshot.members?.map((member) => member.botId)).toContain(designer.id);
|
|
await captureScreenshot(page, testInfo, "project-main-agent-delegates");
|
|
});
|