mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-09-16 09:38:41 +00:00
* feat(agents): support Unicode display names for custom agents * fix(agents): preserve and validate Unicode display names * fix(agents): tolerate invalid stored labels and reject invisible names * fix(agents): identify agent in invalid display name warning * style(frontend): format agent display name fallback --------- Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
679 lines
22 KiB
TypeScript
679 lines
22 KiB
TypeScript
import { expect, test } from "@playwright/test";
|
|
|
|
import {
|
|
handleRunStream,
|
|
mockLangGraphAPI,
|
|
MOCK_RUN_ID,
|
|
MOCK_THREAD_ID,
|
|
} from "./utils/mock-api";
|
|
|
|
const MOCK_AGENTS = [
|
|
{
|
|
name: "test-agent",
|
|
description: "A test agent for E2E tests",
|
|
system_prompt: "You are a test agent.",
|
|
},
|
|
{
|
|
name: "second-agent",
|
|
description: "Another test agent for E2E tests",
|
|
system_prompt: "You are another test agent.",
|
|
},
|
|
];
|
|
|
|
test.describe("Agent chat", () => {
|
|
test("display name length counts emoji as code points", async ({ page }) => {
|
|
mockLangGraphAPI(page, { agents: [MOCK_AGENTS[0]!] });
|
|
let savedName: string | undefined;
|
|
await page.route("**/api/agents/test-agent", async (route) => {
|
|
if (route.request().method() !== "PUT") return route.fallback();
|
|
const request = route.request().postDataJSON() as {
|
|
display_name: string;
|
|
};
|
|
savedName = request.display_name;
|
|
await route.fulfill({ json: { ...MOCK_AGENTS[0], ...request } });
|
|
});
|
|
await page.goto("/workspace/agents");
|
|
await page.getByTitle("Agent settings", { exact: true }).click();
|
|
const input = page.getByLabel("Display name", { exact: true });
|
|
await input.fill("🦌".repeat(101));
|
|
await page.getByRole("button", { name: "Save", exact: true }).click();
|
|
await expect(
|
|
page.getByText("Display name must be at most 100 Unicode code points."),
|
|
).toBeVisible();
|
|
expect(savedName).toBeUndefined();
|
|
await expect(page.getByRole("dialog")).toBeVisible();
|
|
await input.fill("🦌".repeat(100));
|
|
await expect(input).toHaveValue("🦌".repeat(100));
|
|
await page.getByRole("button", { name: "Save", exact: true }).click();
|
|
await expect.poll(() => savedName).toBe("🦌".repeat(100));
|
|
await expect(page.getByRole("dialog")).toBeHidden();
|
|
});
|
|
|
|
test("Unicode display names keep the stable agent route and run context", async ({
|
|
page,
|
|
}, testInfo) => {
|
|
let streamBody: Record<string, unknown> | undefined;
|
|
const agent = { ...MOCK_AGENTS[0]!, display_name: "" };
|
|
mockLangGraphAPI(page, {
|
|
agents: [agent],
|
|
runStreamHandler: async (route) => {
|
|
streamBody = route.request().postDataJSON() as Record<string, unknown>;
|
|
await handleRunStream(route);
|
|
},
|
|
});
|
|
await page.route("**/api/agents/test-agent", async (route) => {
|
|
if (route.request().method() !== "PUT") return route.fallback();
|
|
const request = route.request().postDataJSON() as {
|
|
display_name: string;
|
|
};
|
|
expect(request.display_name).toBe("代码审查助手");
|
|
agent.display_name = request.display_name;
|
|
await route.fulfill({ json: agent });
|
|
});
|
|
await page.goto("/workspace/agents");
|
|
await page.getByTitle("Agent settings", { exact: true }).click();
|
|
await page.getByLabel("Display name", { exact: true }).fill("代码审查助手");
|
|
await page.screenshot({
|
|
path: testInfo.outputPath("display-name-settings.png"),
|
|
});
|
|
await page.getByRole("button", { name: "Save", exact: true }).click();
|
|
await expect(page.getByRole("dialog")).toBeHidden();
|
|
await expect(page.getByText("代码审查助手", { exact: true })).toBeVisible();
|
|
await page.getByRole("button", { name: "Chat", exact: true }).click();
|
|
await page.waitForURL("**/workspace/agents/test-agent/chats/**");
|
|
await expect(
|
|
page.getByText("代码审查助手", { exact: true }).first(),
|
|
).toBeVisible();
|
|
await page.screenshot({
|
|
path: testInfo.outputPath("display-name-gallery.png"),
|
|
});
|
|
const textarea = page.getByPlaceholder(/how can i assist you/i);
|
|
await textarea.fill("Review this code");
|
|
await textarea.press("Enter");
|
|
await expect.poll(() => streamBody).toBeDefined();
|
|
expect(streamBody).toMatchObject({ context: { agent_name: "test-agent" } });
|
|
});
|
|
|
|
test("agent gallery page loads and shows agents", async ({ page }) => {
|
|
mockLangGraphAPI(page, { agents: MOCK_AGENTS });
|
|
|
|
await page.goto("/workspace/agents");
|
|
|
|
// The agent card should appear with the agent name
|
|
await expect(page.getByText("test-agent")).toBeVisible({
|
|
timeout: 15_000,
|
|
});
|
|
});
|
|
|
|
for (const { label, toolGroups } of [
|
|
{ label: "empty", toolGroups: [] },
|
|
{ label: "unrestricted", toolGroups: null },
|
|
]) {
|
|
test(`agent gallery shows skill badges with ${label} tool groups`, async ({
|
|
page,
|
|
}) => {
|
|
mockLangGraphAPI(page, {
|
|
agents: [
|
|
{
|
|
...MOCK_AGENTS[0]!,
|
|
tool_groups: toolGroups,
|
|
skills: ["data-analysis"],
|
|
},
|
|
],
|
|
});
|
|
|
|
await page.goto("/workspace/agents");
|
|
|
|
const card = page.locator('[data-slot="card"]').filter({
|
|
has: page.getByText("test-agent", { exact: true }),
|
|
});
|
|
await expect(card).toBeVisible({ timeout: 15_000 });
|
|
await expect(
|
|
card.getByText("data-analysis", { exact: true }),
|
|
).toBeVisible();
|
|
});
|
|
}
|
|
|
|
test("agent gallery hides the badge block with no tool groups or skills", async ({
|
|
page,
|
|
}) => {
|
|
mockLangGraphAPI(page, {
|
|
agents: [{ ...MOCK_AGENTS[0]!, tool_groups: [], skills: [] }],
|
|
});
|
|
|
|
await page.goto("/workspace/agents");
|
|
|
|
const card = page.locator('[data-slot="card"]').filter({
|
|
has: page.getByText("test-agent", { exact: true }),
|
|
});
|
|
await expect(card).toBeVisible({ timeout: 15_000 });
|
|
await expect(card.locator('[data-slot="card-content"]')).toHaveCount(0);
|
|
});
|
|
|
|
test("agent chat page loads with input box and AI disclaimer", async ({
|
|
page,
|
|
}) => {
|
|
mockLangGraphAPI(page, { agents: MOCK_AGENTS });
|
|
|
|
await page.goto("/workspace/agents/test-agent/chats/new");
|
|
|
|
// The prompt input textarea should be visible
|
|
const textarea = page.getByPlaceholder(/how can i assist you/i);
|
|
await expect(textarea).toBeVisible({ timeout: 15_000 });
|
|
await expect(
|
|
page.getByText("DeerFlow is AI and can make mistakes", { exact: true }),
|
|
).toBeVisible();
|
|
});
|
|
|
|
test("continues an IM-selected thread with the same agent from the sidebar", async ({
|
|
page,
|
|
}) => {
|
|
const threadId = "00000000-0000-0000-0000-000000000168";
|
|
let streamBody: Record<string, unknown> | undefined;
|
|
mockLangGraphAPI(page, {
|
|
agents: [
|
|
{
|
|
name: "researcher",
|
|
description: "Research agent selected from an IM channel",
|
|
},
|
|
],
|
|
threads: [
|
|
{
|
|
thread_id: threadId,
|
|
title: "IM research conversation",
|
|
metadata: {
|
|
channel_source: { type: "im_channel", provider: "telegram" },
|
|
channel_agent_name: "researcher",
|
|
agent_name: "researcher",
|
|
},
|
|
},
|
|
],
|
|
runStreamHandler: async (route) => {
|
|
streamBody = route.request().postDataJSON() as Record<string, unknown>;
|
|
await handleRunStream(route);
|
|
},
|
|
});
|
|
|
|
await page.goto("/workspace/chats/new");
|
|
const threadLink = page
|
|
.locator("[data-sidebar='sidebar']")
|
|
.locator(`a[href='/workspace/agents/researcher/chats/${threadId}']`);
|
|
await expect(threadLink).toBeVisible({ timeout: 15_000 });
|
|
await threadLink.click();
|
|
await page.waitForURL(`**/workspace/agents/researcher/chats/${threadId}`);
|
|
|
|
const textarea = page.getByPlaceholder(/how can i assist you/i);
|
|
await expect(textarea).toBeVisible({ timeout: 15_000 });
|
|
await textarea.fill("Continue this research");
|
|
await textarea.press("Enter");
|
|
|
|
await expect.poll(() => streamBody).toBeDefined();
|
|
expect(streamBody).toMatchObject({
|
|
context: {
|
|
agent_name: "researcher",
|
|
thread_id: threadId,
|
|
},
|
|
});
|
|
});
|
|
|
|
test("mobile agent welcome keeps the sidebar trigger clickable", async ({
|
|
page,
|
|
}) => {
|
|
await page.setViewportSize({ width: 390, height: 664 });
|
|
mockLangGraphAPI(page, {
|
|
agents: [
|
|
{
|
|
...MOCK_AGENTS[0]!,
|
|
description: "这是一个用于验证移动端欢迎页布局的测试智能体。".repeat(
|
|
16,
|
|
),
|
|
},
|
|
],
|
|
});
|
|
|
|
await page.goto("/workspace/agents/test-agent/chats/new");
|
|
await page.evaluate(() => {
|
|
document.cookie = "locale=zh-CN; path=/; SameSite=Lax";
|
|
});
|
|
await page.reload();
|
|
|
|
const sidebarTrigger = page
|
|
.locator("[data-sidebar='trigger']:visible")
|
|
.first();
|
|
await expect(sidebarTrigger).toBeVisible({ timeout: 15_000 });
|
|
const triggerBox = await sidebarTrigger.boundingBox();
|
|
expect(triggerBox).not.toBeNull();
|
|
const triggerReceivesPointerEvents = await page.evaluate(
|
|
({ x, y }) => {
|
|
const trigger = document.elementFromPoint(x, y);
|
|
return trigger?.closest("[data-sidebar='trigger']") !== null;
|
|
},
|
|
{
|
|
x: triggerBox!.x + triggerBox!.width / 2,
|
|
y: triggerBox!.y + triggerBox!.height / 2,
|
|
},
|
|
);
|
|
expect(triggerReceivesPointerEvents).toBe(true);
|
|
await page.mouse.click(
|
|
triggerBox!.x + triggerBox!.width / 2,
|
|
triggerBox!.y + triggerBox!.height / 2,
|
|
);
|
|
|
|
await expect(
|
|
page.locator("[data-mobile='true'][data-sidebar='sidebar']"),
|
|
).toBeVisible();
|
|
});
|
|
|
|
test("keeps new-chat drafts isolated between agents", async ({ page }) => {
|
|
mockLangGraphAPI(page, { agents: MOCK_AGENTS });
|
|
|
|
await page.goto("/workspace/agents/test-agent/chats/new");
|
|
const firstAgentInput = page.getByPlaceholder(/how can i assist you/i);
|
|
await expect(firstAgentInput).toBeVisible({ timeout: 15_000 });
|
|
await firstAgentInput.fill("Draft for the first agent");
|
|
|
|
await page.goto("/workspace/agents/second-agent/chats/new");
|
|
const secondAgentInput = page.getByPlaceholder(/how can i assist you/i);
|
|
await expect(secondAgentInput).toHaveValue("");
|
|
await secondAgentInput.fill("Draft for the second agent");
|
|
|
|
await page.goto("/workspace/agents/test-agent/chats/new");
|
|
await expect(page.getByPlaceholder(/how can i assist you/i)).toHaveValue(
|
|
"Draft for the first agent",
|
|
);
|
|
});
|
|
|
|
test("agent chat page shows agent badge", async ({ page }) => {
|
|
mockLangGraphAPI(page, { agents: MOCK_AGENTS });
|
|
|
|
await page.goto("/workspace/agents/test-agent/chats/new");
|
|
|
|
// The agent badge should display in the header (scoped to header to avoid
|
|
// matching the welcome area which also shows the agent name)
|
|
await expect(
|
|
page.locator("header span", { hasText: "test-agent" }),
|
|
).toBeVisible({ timeout: 15_000 });
|
|
});
|
|
|
|
for (const {
|
|
name,
|
|
toolGroups,
|
|
browserControlEnabled,
|
|
expectedVisible,
|
|
mock,
|
|
} of [
|
|
{
|
|
name: "shows Browser Live for an explicit browser tool group",
|
|
toolGroups: ["browser"],
|
|
browserControlEnabled: true,
|
|
expectedVisible: true,
|
|
},
|
|
{
|
|
name: "shows Browser Live when tool groups are unrestricted",
|
|
toolGroups: null,
|
|
browserControlEnabled: true,
|
|
expectedVisible: true,
|
|
},
|
|
{
|
|
name: "hides Browser Live without the browser tool group",
|
|
toolGroups: ["web"],
|
|
browserControlEnabled: true,
|
|
expectedVisible: false,
|
|
},
|
|
{
|
|
name: "hides Browser Live when browser control is unavailable",
|
|
toolGroups: ["browser"],
|
|
browserControlEnabled: false,
|
|
expectedVisible: false,
|
|
},
|
|
{
|
|
name: "hides Browser Live in mock custom-agent chats",
|
|
toolGroups: ["browser"],
|
|
browserControlEnabled: true,
|
|
expectedVisible: false,
|
|
mock: true,
|
|
},
|
|
]) {
|
|
test(name, async ({ page }) => {
|
|
const agent = {
|
|
name: "browser-agent",
|
|
description: "A custom agent for Browser Live tests",
|
|
tool_groups: toolGroups,
|
|
};
|
|
mockLangGraphAPI(page, {
|
|
agents: [agent],
|
|
features: { browserControlEnabled },
|
|
threads: [
|
|
{
|
|
thread_id: MOCK_THREAD_ID,
|
|
title: "Browser agent conversation",
|
|
agent_name: agent.name,
|
|
messages: [
|
|
{
|
|
type: "ai",
|
|
id: "msg-ai-browser-agent",
|
|
content: "Ready to browse",
|
|
},
|
|
],
|
|
},
|
|
],
|
|
});
|
|
|
|
const featuresLoaded = page.waitForResponse(
|
|
(response) =>
|
|
new URL(response.url()).pathname === "/api/features" &&
|
|
response.status() === 200,
|
|
);
|
|
await page.goto(
|
|
`/workspace/agents/${agent.name}/chats/${MOCK_THREAD_ID}${mock ? "?mock=true" : ""}`,
|
|
);
|
|
await featuresLoaded;
|
|
if (mock) {
|
|
await expect(
|
|
page.locator("header span", { hasText: agent.name }),
|
|
).toBeVisible({ timeout: 15_000 });
|
|
} else {
|
|
await expect(page.getByText("Ready to browse")).toBeVisible({
|
|
timeout: 15_000,
|
|
});
|
|
}
|
|
|
|
const browserTrigger = page.getByTestId("browser-trigger");
|
|
if (expectedVisible) {
|
|
await expect(browserTrigger).toBeVisible();
|
|
await browserTrigger.click();
|
|
await expect(
|
|
page.getByPlaceholder("Enter a URL and press Enter"),
|
|
).toBeVisible();
|
|
} else {
|
|
await expect(browserTrigger).toHaveCount(0);
|
|
}
|
|
});
|
|
}
|
|
|
|
test("hides Browser Live before a custom-agent thread is created", async ({
|
|
page,
|
|
}) => {
|
|
mockLangGraphAPI(page, {
|
|
agents: [
|
|
{
|
|
name: "browser-agent",
|
|
description: "A custom agent for Browser Live tests",
|
|
tool_groups: ["browser"],
|
|
},
|
|
],
|
|
features: { browserControlEnabled: true },
|
|
});
|
|
|
|
await page.goto("/workspace/agents/browser-agent/chats/new");
|
|
await expect(page.getByPlaceholder(/how can i assist you/i)).toBeVisible({
|
|
timeout: 15_000,
|
|
});
|
|
await expect(page.getByTestId("browser-trigger")).toHaveCount(0);
|
|
});
|
|
|
|
test("agent chat can regenerate its latest response", async ({ page }) => {
|
|
const humanMessage = {
|
|
type: "human",
|
|
id: "msg-human-agent",
|
|
content: [{ type: "text", text: "Original agent question" }],
|
|
};
|
|
const aiMessage = {
|
|
type: "ai",
|
|
id: "msg-ai-agent",
|
|
content: "Custom agent response",
|
|
};
|
|
mockLangGraphAPI(page, {
|
|
agents: MOCK_AGENTS,
|
|
threads: [
|
|
{
|
|
thread_id: MOCK_THREAD_ID,
|
|
title: "Agent conversation",
|
|
agent_name: "test-agent",
|
|
messages: [humanMessage, aiMessage],
|
|
},
|
|
],
|
|
});
|
|
|
|
let prepareMessageId: string | undefined;
|
|
let streamBody: Record<string, unknown> | undefined;
|
|
await page.route(
|
|
`**/api/threads/${MOCK_THREAD_ID}/runs/regenerate/prepare`,
|
|
(route) => {
|
|
prepareMessageId = (
|
|
route.request().postDataJSON() as { message_id?: string }
|
|
).message_id;
|
|
return route.fulfill({
|
|
status: 200,
|
|
contentType: "application/json",
|
|
body: JSON.stringify({
|
|
input: { messages: [humanMessage] },
|
|
checkpoint: {
|
|
checkpoint_id: "checkpoint-before-human",
|
|
checkpoint_ns: "",
|
|
checkpoint_map: null,
|
|
},
|
|
metadata: {
|
|
regenerate_from_message_id: aiMessage.id,
|
|
regenerate_from_run_id: `run-${MOCK_THREAD_ID}`,
|
|
regenerate_checkpoint_id: "checkpoint-before-human",
|
|
},
|
|
target_run_id: `run-${MOCK_THREAD_ID}`,
|
|
}),
|
|
});
|
|
},
|
|
);
|
|
await page.route(
|
|
`**/api/langgraph/threads/${MOCK_THREAD_ID}/runs/stream`,
|
|
(route) => {
|
|
streamBody = route.request().postDataJSON() as Record<string, unknown>;
|
|
return handleRunStream(route);
|
|
},
|
|
);
|
|
|
|
await page.goto(`/workspace/agents/test-agent/chats/${MOCK_THREAD_ID}`);
|
|
await expect(page.getByText(aiMessage.content)).toBeVisible({
|
|
timeout: 15_000,
|
|
});
|
|
|
|
await page.evaluate((selectedText) => {
|
|
const element = Array.from(document.querySelectorAll("p")).find(
|
|
(candidate) => candidate.textContent?.includes(selectedText),
|
|
);
|
|
const textNode = element?.firstChild;
|
|
if (!element || !textNode) {
|
|
throw new Error("Unable to find the custom agent response");
|
|
}
|
|
const range = document.createRange();
|
|
range.selectNodeContents(textNode);
|
|
const selection = window.getSelection();
|
|
selection?.removeAllRanges();
|
|
selection?.addRange(range);
|
|
element.dispatchEvent(new MouseEvent("mouseup", { bubbles: true }));
|
|
}, aiMessage.content);
|
|
await expect(
|
|
page.getByRole("button", { name: "Ask in side chat" }),
|
|
).toBeVisible();
|
|
await page.keyboard.press("Escape");
|
|
|
|
const assistantTurn = page.locator("[data-assistant-turn]").last();
|
|
await assistantTurn.hover();
|
|
await page.getByRole("button", { name: "Regenerate" }).click();
|
|
|
|
await expect.poll(() => prepareMessageId).toBe(aiMessage.id);
|
|
await expect.poll(() => streamBody).toBeDefined();
|
|
expect(streamBody).toMatchObject({
|
|
checkpoint: {
|
|
checkpoint_id: "checkpoint-before-human",
|
|
checkpoint_ns: "",
|
|
checkpoint_map: null,
|
|
},
|
|
metadata: {
|
|
regenerate_from_message_id: aiMessage.id,
|
|
regenerate_from_run_id: `run-${MOCK_THREAD_ID}`,
|
|
regenerate_checkpoint_id: "checkpoint-before-human",
|
|
},
|
|
context: {
|
|
agent_name: "test-agent",
|
|
thread_id: MOCK_THREAD_ID,
|
|
},
|
|
});
|
|
});
|
|
|
|
test("agent chat can edit and rerun its latest user message", async ({
|
|
page,
|
|
}) => {
|
|
const humanMessage = {
|
|
type: "human",
|
|
id: "msg-human-agent",
|
|
content: [{ type: "text", text: "Original agent question" }],
|
|
};
|
|
const replacementHumanMessage = {
|
|
type: "human",
|
|
id: "msg-human-agent-edited",
|
|
content: [{ type: "text", text: "Edited agent question" }],
|
|
};
|
|
const aiMessage = {
|
|
type: "ai",
|
|
id: "msg-ai-agent",
|
|
content: "Custom agent response",
|
|
};
|
|
mockLangGraphAPI(page, {
|
|
agents: MOCK_AGENTS,
|
|
threads: [
|
|
{
|
|
thread_id: MOCK_THREAD_ID,
|
|
title: "Agent conversation",
|
|
agent_name: "test-agent",
|
|
messages: [humanMessage, aiMessage],
|
|
},
|
|
],
|
|
});
|
|
let historyRows = [
|
|
{ run_id: `run-${MOCK_THREAD_ID}`, content: humanMessage },
|
|
{ run_id: `run-${MOCK_THREAD_ID}`, content: aiMessage },
|
|
];
|
|
await page.route(
|
|
`**/api/threads/${MOCK_THREAD_ID}/messages/page`,
|
|
(route) =>
|
|
route.fulfill({
|
|
status: 200,
|
|
contentType: "application/json",
|
|
body: JSON.stringify({
|
|
data: historyRows.map((row, index) => ({
|
|
run_id: row.run_id,
|
|
seq: index + 1,
|
|
content: row.content,
|
|
metadata: { caller: "lead_agent" },
|
|
created_at: `2025-01-01T00:00:${String(index).padStart(2, "0")}Z`,
|
|
})),
|
|
has_more: false,
|
|
next_before_seq: null,
|
|
}),
|
|
}),
|
|
);
|
|
|
|
let prepareBody:
|
|
| { human_message_id?: string; replacement_text?: string }
|
|
| undefined;
|
|
let streamBody: Record<string, unknown> | undefined;
|
|
await page.route(
|
|
`**/api/threads/${MOCK_THREAD_ID}/runs/edit-regenerate/prepare`,
|
|
(route) => {
|
|
prepareBody = route.request().postDataJSON() as {
|
|
human_message_id?: string;
|
|
replacement_text?: string;
|
|
};
|
|
return route.fulfill({
|
|
status: 200,
|
|
contentType: "application/json",
|
|
body: JSON.stringify({
|
|
input: { messages: [replacementHumanMessage] },
|
|
checkpoint: {
|
|
checkpoint_id: "checkpoint-before-human",
|
|
checkpoint_ns: "",
|
|
checkpoint_map: null,
|
|
},
|
|
metadata: {
|
|
replay_kind: "edit",
|
|
regenerate_from_message_id: aiMessage.id,
|
|
regenerate_from_run_id: `run-${MOCK_THREAD_ID}`,
|
|
regenerate_checkpoint_id: "checkpoint-before-human",
|
|
edit_from_message_id: humanMessage.id,
|
|
edit_message_id: replacementHumanMessage.id,
|
|
edit_version_group_id: humanMessage.id,
|
|
},
|
|
target_run_id: `run-${MOCK_THREAD_ID}`,
|
|
replacement_human_message_id: replacementHumanMessage.id,
|
|
source_message_ids: [humanMessage.id, aiMessage.id],
|
|
}),
|
|
});
|
|
},
|
|
);
|
|
await page.route(
|
|
`**/api/langgraph/threads/${MOCK_THREAD_ID}/runs/stream`,
|
|
(route) => {
|
|
streamBody = route.request().postDataJSON() as Record<string, unknown>;
|
|
historyRows = [
|
|
{ run_id: MOCK_RUN_ID, content: replacementHumanMessage },
|
|
{
|
|
run_id: MOCK_RUN_ID,
|
|
content: {
|
|
type: "ai",
|
|
id: "msg-ai-1",
|
|
content: "Hello from DeerFlow!",
|
|
},
|
|
},
|
|
];
|
|
return handleRunStream(route);
|
|
},
|
|
);
|
|
|
|
await page.goto(`/workspace/agents/test-agent/chats/${MOCK_THREAD_ID}`);
|
|
await expect(page.getByText("Original agent question")).toBeVisible({
|
|
timeout: 15_000,
|
|
});
|
|
|
|
const humanTurn = page.getByText("Original agent question");
|
|
await humanTurn.hover();
|
|
await page.getByRole("button", { name: "Edit and rerun" }).click();
|
|
|
|
const editor = page.locator("textarea").first();
|
|
await expect(editor).toHaveValue("Original agent question");
|
|
await editor.fill("Edited agent question");
|
|
await page.getByRole("button", { name: "Update and rerun" }).click();
|
|
|
|
await expect
|
|
.poll(() => prepareBody)
|
|
.toEqual({
|
|
human_message_id: humanMessage.id,
|
|
replacement_text: "Edited agent question",
|
|
});
|
|
await expect.poll(() => streamBody).toBeDefined();
|
|
expect(streamBody).toMatchObject({
|
|
input: { messages: [replacementHumanMessage] },
|
|
checkpoint: {
|
|
checkpoint_id: "checkpoint-before-human",
|
|
checkpoint_ns: "",
|
|
checkpoint_map: null,
|
|
},
|
|
metadata: {
|
|
replay_kind: "edit",
|
|
regenerate_from_message_id: aiMessage.id,
|
|
regenerate_from_run_id: `run-${MOCK_THREAD_ID}`,
|
|
regenerate_checkpoint_id: "checkpoint-before-human",
|
|
edit_from_message_id: humanMessage.id,
|
|
edit_message_id: replacementHumanMessage.id,
|
|
edit_version_group_id: humanMessage.id,
|
|
},
|
|
context: {
|
|
agent_name: "test-agent",
|
|
thread_id: MOCK_THREAD_ID,
|
|
},
|
|
});
|
|
await expect(page.getByText("Edited agent question")).toBeVisible();
|
|
await expect(page.getByText("Original agent question")).not.toBeVisible();
|
|
await expect(page.getByText("Hello from DeerFlow!")).toBeVisible();
|
|
});
|
|
});
|