feat(agent): support Responses steering and project-aware tools
Use the dedicated TJWATER_AGENT_URL and Responses stream protocol. Keep running turns active while users steer or queue follow-ups and edit pending delivery. Refresh the model catalog before generation and preserve approval mode selection. Integrate scoped investigation messaging/presentation, geographic place selection, current Agent composer styles and supporting workbench contracts. Validation: 77 focused Vitest checks, 21 browser cases, lint, typecheck and production build. Preserve direct Server/Agent routing and project authentication.
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
import type { OperationalTask } from "../../src/features/workbench/api/operational-task-client";
|
||||
|
||||
const now = "2026-09-23T09:00:00Z";
|
||||
const sources = ["peripheral_test_data", "scada_devices", "workbench_runtime", "scada_readings", "work_orders", "other_saved_data"];
|
||||
const sourceRef = (source: string) => `${source}:sha256:${"7d5852b69d8e3d4e127800882683c3319e58".repeat(2)}`;
|
||||
const snapshotKey = "query_scope_with_a_long_unbroken_field_name";
|
||||
|
||||
const task: OperationalTask = {
|
||||
project_id: "11111111-1111-4111-8111-111111111111",
|
||||
task_id: "22222222-2222-4222-8222-222222222222",
|
||||
task_type: "event_investigation", workflow: "water_quality_complaint",
|
||||
lifecycle_status: "completed", active_run: null, status: "completed", priority: "normal",
|
||||
revision: 1, execution_generation: 1, execution_request_id: null,
|
||||
title: "水质投诉调查", summary: "居民反映异味",
|
||||
source: { kind: "operator", external_id: null, external_revision: null, event_kind: "water_quality_complaint" },
|
||||
trigger: { kind: "manual", occurred_at: now, detail: "演示小区" }, scheduled_for: null,
|
||||
agent_session_id: null, agent_lease_expires_at: null, current_plan_revision: null, current_report_revision: 1,
|
||||
created_at: now, updated_at: now, latest_plan: null,
|
||||
latest_report: {
|
||||
revision: 1, execution_generation: 1, source_session_id: "evidence-layout-session", published_at: now, published_by: "agent",
|
||||
report: {
|
||||
title: "调查报告", current_conclusion: "需现场核查用户端。", conclusion_boundary: "仅覆盖已有记录。",
|
||||
evidence_refs: sources.slice(0, 5), recommendations: [], pending_items: [],
|
||||
stage: "final", outcome: "concluded", stop_reason: null, data_gaps: []
|
||||
}
|
||||
},
|
||||
evidence: sources.map((source) => ({
|
||||
evidence_id: source, kind: "system_data", summary: `${source} 数据快照`, source_label: source,
|
||||
source_ref: sourceRef(source), observed_at: now, recorded_at: now, recorded_by: "agent", has_snapshot: true
|
||||
})),
|
||||
actions: [], access_requests: [], work_order_snapshot: null,
|
||||
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
|
||||
}
|
||||
};
|
||||
|
||||
test("调查证据与数据快照的长引用在桌面和窄屏完整换行", async ({ page }, testInfo) => {
|
||||
await page.route("http://127.0.0.1:8787/api/v1/**", (route) => route.fulfill({ json: { items: [] } }));
|
||||
await page.goto("/", { waitUntil: "domcontentloaded" });
|
||||
await page.setContent('<html lang="zh-CN"><head><link rel="stylesheet" href="/src/styles.css"></head><body><main id="report" style="max-width:480px;margin:auto;padding:16px"></main></body></html>');
|
||||
await page.evaluate(async ({ task, snapshotKey }) => {
|
||||
const reactModule = await import("/node_modules/.vite/deps/react.js");
|
||||
const React = reactModule.default ?? reactModule;
|
||||
const clientModule = await import("/node_modules/.vite/deps/react-dom_client.js");
|
||||
const { createRoot } = clientModule.default ?? clientModule;
|
||||
const { AgentExecutionPlanPanel } = await import("/src/features/workbench/components/agent-execution-plan-panel.tsx");
|
||||
const noop = async () => undefined;
|
||||
createRoot(document.getElementById("report")).render(React.createElement(AgentExecutionPlanPanel, {
|
||||
task, onContinue: noop, onDecision: noop, onExecute: noop, onReconcile: noop, onRefresh: noop,
|
||||
onLoadEvidenceSnapshot: async (evidenceId: string) => ({
|
||||
source: "workbench_runtime", origin: "system_data",
|
||||
source_ref: task.evidence.find((item) => item.evidence_id === evidenceId)!.source_ref,
|
||||
queried_at: task.updated_at, truncated: false, data: { [snapshotKey]: "long_snapshot_value_".repeat(8) }
|
||||
})
|
||||
}));
|
||||
}, { task, snapshotKey });
|
||||
const evidence = page.getByRole("region", { name: "主要依据", exact: true });
|
||||
await expect(evidence).toBeVisible();
|
||||
for (const name of ["更多引用依据 · 2 条", "其他已保存资料"]) {
|
||||
const summary = evidence.locator("summary").filter({ hasText: name });
|
||||
await summary.focus();
|
||||
await page.keyboard.press("Enter");
|
||||
await expect(summary.locator("..")).toHaveAttribute("open", "");
|
||||
}
|
||||
for (const source of sources) {
|
||||
const card = evidence.locator("details").filter({ has: page.locator("summary", { hasText: `${source} 数据快照` }) }).last();
|
||||
await card.locator("summary").first().click();
|
||||
await expect(card.locator("dd.font-mono")).toHaveText(sourceRef(source));
|
||||
await card.getByRole("button", { name: "查看数据快照", exact: true }).click();
|
||||
await expect(card.getByRole("region", { name: "数据快照" })).toContainText(snapshotKey);
|
||||
}
|
||||
for (const width of [1440, 705, 375, 320]) {
|
||||
await page.setViewportSize({ width, height: 900 });
|
||||
const overflow = await evidence.evaluate((section) => {
|
||||
return [...section.querySelectorAll("dd, dt, [aria-label='数据快照'] p")].flatMap((element) => {
|
||||
const rect = element.getBoundingClientRect();
|
||||
const card = element.closest("details")!.getBoundingClientRect();
|
||||
return element.scrollWidth > element.clientWidth + 1 || rect.right > card.right + 1 || rect.left < card.left - 1
|
||||
? [{ text: element.textContent, width: element.clientWidth, scrollWidth: element.scrollWidth, right: rect.right, cardRight: card.right }]
|
||||
: [];
|
||||
});
|
||||
});
|
||||
expect(overflow, `证据字段在 ${width}px 下不应撑出卡片`).toEqual([]);
|
||||
await evidence.screenshot({ path: testInfo.outputPath(`evidence-${width}.png`) });
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user