import { expect, test } from "@playwright/test"; import type { OperationalTask, OperationalTaskSummary } from "../../src/features/workbench/api/operational-task-client"; import { mockAgentApi } from "./support/mock-agent-api"; const projectId = "324ff7ee-c2d4-5184-a84b-48fb14a2933e"; const taskId = "82000000-0000-4000-8000-000000000011"; const runId = "82500000-0000-4000-8000-000000000011"; const previousRunId = "82500000-0000-4000-8000-000000000010"; const sessionId = "investigation-execution-session"; const previousSessionId = "investigation-execution-session-previous"; const now = "2026-10-30T08:00:00Z"; const latestRun: NonNullable = { run_id: runId, task_id: taskId, generation: 2, execution_request_id: "85000000-0000-4000-8000-000000000011", status: "succeeded", revision: 4, agent_session_id: sessionId, agent_instance_id: "agent-supply-1", lease_expires_at: null, heartbeat_at: now, started_at: now, finished_at: now, failure_code: null, failure_detail: null, dispatch_attempts: 1, next_dispatch_at: now, created_at: now, updated_at: now }; const previousRun: NonNullable = { ...latestRun, run_id: previousRunId, generation: 1, execution_request_id: "85000000-0000-4000-8000-000000000010", agent_session_id: previousSessionId, created_at: "2026-10-29T08:00:00Z", updated_at: "2026-10-29T08:05:00Z" }; const latestReport: NonNullable = { revision: 2, execution_generation: 2, source_session_id: sessionId, published_at: now, published_by: "agent", report: { title: "南桥爆管调查报告", current_conclusion: "第二轮已完成现场记录复核", conclusion_boundary: "以第二轮调查数据为准", evidence_refs: [], recommendations: [], pending_items: [] } }; const previousReport: NonNullable = { ...latestReport, revision: 1, execution_generation: 1, source_session_id: previousSessionId, published_at: "2026-10-29T08:05:00Z", report: { ...latestReport.report, current_conclusion: "第一轮完成压力与流量核对", conclusion_boundary: "尚未取得现场阀门记录" } }; const task: OperationalTask = { project_id: projectId, task_id: taskId, task_type: "event_investigation", workflow: "burst-report", lifecycle_status: "completed", active_run: null, latest_run: latestRun, status: "completed", priority: "normal", revision: 2, execution_generation: 2, execution_request_id: "85000000-0000-4000-8000-000000000011", title: "南桥爆管调查", summary: "核对压力、流量和现场反馈", source: { kind: "operator", external_id: null, external_revision: null, event_kind: "burst" }, trigger: { kind: "manual", occurred_at: now, detail: "调试调查执行历史" }, scheduled_for: null, agent_session_id: sessionId, agent_lease_expires_at: null, current_plan_revision: null, current_report_revision: 2, created_at: now, updated_at: now, latest_plan: null, latest_report: latestReport, latest_progress: null, evidence: [], actions: [], access_requests: [], permissions: { can_publish_artifacts: false, can_propose_actions: false, can_sync_external_feedback: false, can_decide_work_orders: false, can_execute_work_orders: false, can_decide_commands: false, can_execute_commands: false, can_decide_access: false }, work_order_snapshot: null }; const summary = (item: OperationalTask): OperationalTaskSummary => { const { latest_plan, latest_report, latest_progress, evidence, actions, access_requests, permissions, work_order_snapshot, ...value } = item; void latest_plan; void latest_report; void latest_progress; void evidence; void actions; void access_requests; void permissions; void work_order_snapshot; return value; }; test("调查任务执行记录自动刷新并可创建调试会话追加消息", async ({ page }, testInfo) => { await page.emulateMedia({ reducedMotion: "reduce" }); await mockAgentApi(page); let executionHistoryReads = 0; let debugBranchCreates = 0; const openedBindings: Array<{ runId: string | null; sessionId: string | null }> = []; let debugBranchPayload: unknown; await page.route(`http://127.0.0.1:8787/api/v1/task/${taskId}/execution-session?**`, async (route) => { executionHistoryReads += 1; const refreshed = executionHistoryReads > 1; const query = new URL(route.request().url()).searchParams; openedBindings.push({ runId: query.get("run_id"), sessionId: query.get("session_id") }); await route.fulfill({ headers: { "Access-Control-Allow-Origin": "*" }, json: { id: previousSessionId, session_id: previousSessionId, title: "南桥爆管调查 · 第一轮执行记录", purpose: "task_execution", task_id: taskId, status: "active", created_at: now, updated_at: refreshed ? "2026-10-30T08:01:00Z" : now, messages: [ { id: "execution-user", role: "user", parts: [{ type: "text", text: "执行后台调查任务" }] }, { id: "execution-assistant", role: "assistant", parts: [ { type: "data-progress", data: { id: "pressure-flow", phase: "tool", title: "核对第一轮压力与流量", detail: "查询监测记录并校验时间范围。", status: "completed" } }, { type: "text", text: "第一轮已完成压力与流量证据核对" } ] }, ...(refreshed ? [{ id: "execution-refresh", role: "assistant", parts: [{ type: "text", text: "第一轮自动同步发现现场阀门记录" }] }] : []) ], is_streaming: !refreshed, run_status: refreshed ? "completed" : "running", read_only: true } }); }); page.on("request", (request) => { if (request.method() === "POST" && request.url().endsWith(`/task/${taskId}/execution-session/debug`)) { debugBranchCreates += 1; debugBranchPayload = request.postDataJSON(); } }); await page.route("**/runtime-config.js", (route) => route.fulfill({ contentType: "text/javascript", body: `globalThis.__TJWATER_CONFIG__=${JSON.stringify({ TJWATER_AUTH_MODE: "disabled", TJWATER_SERVER_API_BASE_URL: "http://server.test", TJWATER_MAP_URL: "http://map.test/geoserver", TJWATER_GEOSERVER_WORKSPACE: "tjwater_next" })}` })); await page.route("http://server.test/api/v1/projects**", (route) => route.fulfill({ json: { items: [{ project_id: projectId, gs_workspace: "tjwater_next", status: "active" }], total: 1, limit: 1000, offset: 0 } })); await page.route("http://server.test/api/v1/scada-devices**", (route) => route.fulfill({ json: { items: [], total: 0, limit: 1000, offset: 0 } })); await page.route("http://server.test/api/v1/workbench/sync-snapshot", (route) => route.fulfill({ json: { schema: "workbench-sync-snapshot@1", event_cursor: 0, runtime: { availability: "not_configured", source: "none", scenario_availability: "not_configured", artifact_availability: "not_configured", schedule_availability: "not_configured", work_order_availability: "not_configured", alert_availability: "not_configured", generated_at: now, conditions: [], alerts: [], scenarios: [], latest_analysis: null, work_orders: [] }, tasks: [summary(task)], notifications: [], generated_at: now } })); await page.route("http://server.test/api/v1/workbench/events**", (route) => route.fulfill({ contentType: "text/event-stream", body: ": keepalive\n\n" })); await page.route("http://server.test/api/v1/operational-task-notifications**", (route) => route.fulfill({ json: { items: [], limit: 100, offset: 0, next_offset: null } })); await page.route("http://server.test/api/v1/operational-tasks**", (route) => { const path = new URL(route.request().url()).pathname; if (path.endsWith("/operational-tasks/schedules")) return route.fulfill({ json: { items: [] } }); if (path.endsWith(`/operational-tasks/${taskId}/reports`)) return route.fulfill({ json: { items: [latestReport, previousReport], next_offset: null } }); if (path.endsWith(`/operational-tasks/${taskId}/runs`)) return route.fulfill({ json: { items: [latestRun, previousRun], next_cursor: null } }); if (path.endsWith(`/operational-tasks/${taskId}`)) return route.fulfill({ json: task }); if (path.endsWith("/operational-tasks")) return route.fulfill({ json: { items: [summary(task)], limit: 50, offset: 0, next_offset: null } }); return route.fulfill({ status: 404, json: {} }); }); await page.route("http://map.test/**", (route) => route.fulfill({ status: 204 })); await page.goto("/"); await page.getByRole("button", { name: "工况任务", exact: true }).click(); const taskPanel = page.getByRole("region", { name: "工况任务", exact: true }); const expand = taskPanel.getByRole("button", { name: "展开工况任务" }); if (await expand.isVisible()) await expand.click(); await taskPanel.getByRole("button", { name: /调查任务/ }).click(); await taskPanel.getByText("南桥爆管调查", { exact: true }).first().click(); await expect(taskPanel.getByText("调查任务 · 第 2 轮", { exact: true })).toBeVisible(); await taskPanel.getByRole("button", { name: "展开报告版本" }).click(); await page.getByRole("dialog", { name: "报告版本" }).getByRole("button", { name: /第 1 轮 · 报告第 1 版/ }).click(); await expect(taskPanel.getByText("调查任务 · 第 1 轮", { exact: true })).toBeVisible(); const executionHistory = taskPanel.getByRole("button", { name: "执行记录" }); await expect(executionHistory).toBeVisible(); await executionHistory.click(); const agentPanel = page.getByRole("complementary", { name: "Agent 命令面板" }); await expect(agentPanel.getByRole("note").getByText("执行记录自动同步", { exact: true })).toBeVisible(); await expect(agentPanel).toContainText("第一轮已完成压力与流量证据核对"); const processToggle = agentPanel.getByRole("button", { name: "查看处理过程", exact: true }).first(); await expect(processToggle).toBeVisible(); await processToggle.click(); await expect(agentPanel.getByText("核对第一轮压力与流量", { exact: true })).toBeVisible(); await expect(agentPanel.locator("textarea")).toBeVisible(); await expect(agentPanel).toContainText("第一轮自动同步发现现场阀门记录", { timeout: 5_000 }); expect(executionHistoryReads).toBeGreaterThanOrEqual(2); expect(openedBindings).toEqual(expect.arrayContaining([ { runId: previousRunId, sessionId: previousSessionId } ])); await agentPanel.locator("textarea").fill("继续核对失败前最后一次工具调用"); await agentPanel.getByRole("button", { name: "发送 Agent 指令" }).click(); await expect.poll(() => debugBranchCreates).toBe(1); expect(debugBranchPayload).toEqual({ run_id: previousRunId, session_id: previousSessionId }); await expect(agentPanel).toContainText("继续核对失败前最后一次工具调用"); await expect(agentPanel.getByRole("note").getByText("执行记录自动同步", { exact: true })).toHaveCount(0); await testInfo.attach("desktop-execution-history", { body: await agentPanel.screenshot(), contentType: "image/png" }); await page.getByRole("button", { name: "折叠 Agent 面板", exact: true }).click(); await page.setViewportSize({ width: 375, height: 720 }); const openTasks = page.getByRole("button", { name: "打开工况任务", exact: true }); if (await openTasks.isVisible()) await openTasks.click(); const mobilePanel = page.getByRole("region", { name: "工况任务", exact: true }); const mobileExpand = mobilePanel.getByRole("button", { name: "展开工况任务" }); if (await mobileExpand.isVisible()) await mobileExpand.click(); await mobilePanel.getByRole("button", { name: /调查任务/ }).click(); await expect(mobilePanel.getByRole("button", { name: "执行记录" })).toBeVisible(); await testInfo.attach("mobile-execution-history-button", { body: await page.getByRole("region", { name: "工况任务抽屉" }).screenshot(), contentType: "image/png" }); });