Files
AgentCompany/tests/e2e/ceo-chat.spec.ts
T
ZacharyZhang-NY c7eafb2836 Founder-CEO chat round trip proven end to end; agents see the founder
loadChatContext labels human senders "[Name (founder)]" so a woken agent
knows it answers its boss, not a teammate. New integration test drives
the whole loop — founder DM wakes the CEO, the prompt names the founder,
the reply lands in the same pinned conversation and thread, the wake
task closes — and a Playwright spec pins the UI contract: badged CEO row
first, selected by default, message queues the wake run, channel recency
never displaces the pin. AGENTS.md documents the feature contracts.
2026-07-14 00:23:49 -04:00

103 lines
5.2 KiB
TypeScript

import { _electron as electron, expect, test } from "@playwright/test";
import { mkdirSync } from "node:fs";
import { tmpdir } from "node:os";
import { join, resolve } from "node:path";
import { randomUUID } from "node:crypto";
import { seedBuiltinSkillsFixture } from "./builtin-skills-fixture";
/**
* The founder's direct line to the CEO: onboarding leaves a pinned CEO
* conversation at the top of Messages, the page opens on it, a founder
* message lands there and queues a CEO wake run, and no amount of channel
* recency displaces the pin.
*/
test("the pinned CEO conversation is the Messages front door", async () => {
const profileDir = join(tmpdir(), `agentcompany-new-e2e-ceo-${randomUUID()}`);
mkdirSync(profileDir, { recursive: true });
const builtinDir = seedBuiltinSkillsFixture(join(tmpdir(), `agentcompany-new-e2e-ceo-builtin-${randomUUID()}`));
const app = await electron.launch({
args: [resolve("out/main/index.js")],
env: {
...process.env,
AGENTCOMPANY_NEW_PROFILE_DIR: profileDir,
AGENTCOMPANY_NEW_AUTORUN: "0",
AGENTCOMPANY_NEW_BUILTIN_SKILLS_DIR: builtinDir,
},
});
try {
const page = await app.firstWindow();
await expect(page.getByRole("heading", { name: "Welcome" })).toBeVisible();
await page.getByRole("button", { name: "Get started" }).click();
await page.getByRole("button", { name: /^OpenAI API key/ }).click();
await page.getByLabel("API key").fill("e2e-secret");
await page.getByLabel("Model").fill("gpt-5.4");
await page.getByRole("button", { name: "Continue" }).click();
await page.getByLabel("Company name").fill("FlowPilot");
await page.getByLabel("Your name").fill("Yang");
await page.getByLabel("Description").fill("A CSM analytics product for SMBs.");
await page.getByRole("button", { name: "Continue" }).click();
await page.getByRole("button", { name: "Continue" }).click();
await page.getByLabel("Goal").fill("Ship the FlowPilot MVP.");
await page.getByLabel("Acceptance criteria").fill("A working product, not documents about one.");
await page.getByRole("button", { name: "Launch" }).click();
await expect(page.getByRole("heading", { name: "Connectors" })).toBeVisible();
await page.getByRole("button", { name: "Finish" }).click();
// The CEO conversation is the first row, badged, and already selected —
// opening Messages IS opening the CEO chat.
await page.getByRole("button", { name: "Messages" }).click();
const firstRow = page.locator(".convo-item").first();
await expect(firstRow).toContainText("CEO");
await expect(firstRow.locator(".convo-ceo-badge")).toBeVisible();
await expect(firstRow).toHaveClass(/active/);
await expect(firstRow).toContainText("Ask about progress, goals, or direction");
// A founder message lands in the conversation and queues the CEO's wake
// run — the reply pipeline is armed even with autonomous execution off.
await page.getByRole("textbox", { name: "Message" }).fill("How are we tracking against the goal?");
await page.getByRole("button", { name: "Send" }).click();
const chatLog = page.getByRole("log", { name: "Conversation messages" });
await expect(chatLog.getByText("How are we tracking against the goal?")).toBeVisible();
const wake = await page.evaluate(async () => {
const api = (window as unknown as { agentCompany: { loadSnapshot: () => Promise<{ ok: true; data: {
agents: Array<{ id: string; roleLevel: string }>;
taskRuns: Array<{ agentId: string | null; trigger: string; state: string }>;
} }> } }).agentCompany;
const snapshot = await api.loadSnapshot();
const ceo = snapshot.data.agents.find((agent) => agent.roleLevel === "c_suite")!;
return snapshot.data.taskRuns.filter((run) => run.agentId === ceo.id && run.trigger === "chat_mention").map((run) => run.state);
});
expect(wake).toEqual(["queued"]);
// Recency pressure: newer traffic in a channel must not displace the pin.
await page.evaluate(async () => {
const api = (window as unknown as { agentCompany: {
loadSnapshot: () => Promise<{ ok: true; data: {
activeOrgId: string;
employees: Array<{ id: string; orgId: string }>;
chatSpaces: Array<{ id: string; orgId: string; name: string }>;
} }>;
sendMessage: (input: unknown) => Promise<unknown>;
} }).agentCompany;
const snapshot = await api.loadSnapshot();
const orgId = snapshot.data.activeOrgId;
const employee = snapshot.data.employees.find((item) => item.orgId === orgId)!;
const general = snapshot.data.chatSpaces.find((item) => item.orgId === orgId && item.name === "general")!;
await api.sendMessage({
orgId,
spaceId: general.id,
threadId: null,
senderType: "employee",
senderId: employee.id,
bodyMd: "Channel chatter arriving after the founder's CEO message.",
attachmentFileIds: [],
});
});
await expect(page.locator(".convo-item").first().locator(".convo-ceo-badge")).toBeVisible();
await expect(page.locator(".convo-item").first()).toContainText("How are we tracking against the goal?");
} finally {
await app.close();
}
});