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:
2026-10-19 18:46:58 +08:00
parent 2c5bf15cce
commit ec23d44b02
131 changed files with 6663 additions and 1204 deletions
@@ -1,4 +1,4 @@
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
import { fireEvent, render, screen, waitFor, within } from "@testing-library/react";
import type { ComponentProps } from "react";
import { describe, expect, it, vi } from "vitest";
import { type AgentExecutionPlan } from "@/features/agent";
@@ -130,6 +130,25 @@ function taskPanel(task: OperationalTask, overrides: Partial<ComponentProps<type
return { ...props, rerender };
}
it("references the selected report round instead of the latest task round, including legacy reports", () => {
const task = { ...taskBase, execution_generation: 2 };
const { rerender, ...props } = taskPanel(task);
const button = screen.getByRole("button", { name: "Agent 协作" });
expect(button).toHaveAttribute("title", "打开调查协作对话,引用此任务,不会直接发送。");
fireEvent.click(button);
expect(props.onContinue).toHaveBeenLastCalledWith({
context: { report_revision: task.latest_report!.revision },
reportGeneration: task.latest_report!.execution_generation
});
rerender(<AgentExecutionPlanPanel {...props} task={{ ...task,
latest_report: { ...task.latest_report!, execution_generation: null }
}} />);
fireEvent.click(screen.getByRole("button", { name: "Agent 协作" }));
expect(props.onContinue).toHaveBeenLastCalledWith({
context: { report_revision: task.latest_report!.revision }, reportGeneration: null
});
});
describe("operational task approval and evidence", () => {
it("shows the selected v2 route first and keeps unchosen work available", () => {
const node = (id: string, title: string, status: "completed" | "waiting" | "skipped") => ({
@@ -294,7 +313,8 @@ describe("operational task approval and evidence", () => {
onContinue={vi.fn()} onDecision={vi.fn()} onExecute={vi.fn()} onReconcile={vi.fn()}
onRefresh={vi.fn()} onLoadEvidenceSnapshot={vi.fn()}
/>);
fireEvent.click(screen.getByRole("button", { name: "查看新报告" }));
// Live reading follows published reports without requiring an update click.
expect(screen.queryByRole("button", { name: "查看新报告" })).not.toBeInTheDocument();
expect(screen.getByText(/正在阅读:历史报告/)).toBeVisible();
});
@@ -347,11 +367,24 @@ describe("operational task approval and evidence", () => {
task={{ ...currentPlanTask, latest_plan: { ...latestPlan, execution_generation: null } }}
{...props}
/>);
// An unassigned historical plan cannot supply a numbered report's diagram.
expect(screen.queryByRole("heading", { name: "调查脉络" })).not.toBeInTheDocument();
expect(within(screen.getByRole("region", { name: "任务报告" }))
.getByText(taskBase.latest_report!.report.current_conclusion, { exact: true })).toBeVisible();
rerender(<AgentExecutionPlanPanel
task={{ ...currentPlanTask,
latest_plan: { ...latestPlan, execution_generation: null },
latest_report: { ...taskBase.latest_report!, execution_generation: null }
}}
{...props}
/>);
// The legacy relationship remains readable when both records lack a round.
expect(screen.getByRole("heading", { name: "调查脉络" })).toBeVisible();
expect(screen.queryByText(/generation|第\s*\d+\s*代/i)).not.toBeInTheDocument();
});
it("shows exact payload and evidence metadata to a viewer without action controls", () => {
it("shows exact payload and evidence metadata to a viewer without action controls", async () => {
taskPanel(taskBase);
expect(screen.getByText("工单标题")).toBeVisible();
expect(screen.getByText("位置")).toBeVisible();
@@ -363,9 +396,10 @@ describe("operational task approval and evidence", () => {
expect(screen.getByText(/当前账号仅可查看/)).toBeVisible();
expect(screen.queryByText(/operational_action\.work_order\.approve/)).not.toBeInTheDocument();
expect(screen.queryByRole("button", { name: "批准创建预约工单" })).not.toBeInTheDocument();
fireEvent.click(screen.getByLabelText("主要依据").querySelector("summary")!);
expect(screen.getByText("SCADA 水质监测")).toBeVisible();
expect(screen.getByText("系统数据")).toBeVisible();
fireEvent.click(screen.getByRole("button", { name: "展开主要依据" }));
fireEvent.click(screen.getByRole("dialog", { name: "主要依据" }).querySelector("summary")!);
await waitFor(() => expect(within(screen.getByRole("dialog", { name: "主要依据" })).getByText("SCADA 水质监测")).toBeVisible());
expect(within(screen.getByRole("dialog", { name: "主要依据" })).getByText("系统数据")).toBeVisible();
});
it("keeps pending state after a decision network error", async () => {
@@ -449,7 +483,7 @@ describe("operational task approval and evidence", () => {
actions: [completedAction]
});
fireEvent.click(screen.getByText("任务时间线"));
fireEvent.click(screen.getByRole("button", { name: "展开任务时间线" }));
const items = [...screen.getByLabelText("任务时间线记录").querySelectorAll("li")];
expect(items.map((item) => item.textContent)).toEqual([
expect.stringContaining("预约工单已创建,等待现场处理"),