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
+7 -7
View File
@@ -15,7 +15,7 @@ test("real model publishes local components, restores them and receives form inp
test.setTimeout(300_000);
const target = new URL(realAgentUrl!);
expect(["127.0.0.1", "localhost"]).toContain(target.hostname);
const base = `${target.origin}/api/v1/agent/chat`;
const base = `${target.origin}/api/v1`;
const health = await request.get(`${target.origin}/health`);
expect(health.ok()).toBe(true);
expect(await health.json()).toMatchObject({ ready: true, warmed_up: true });
@@ -26,7 +26,7 @@ test("real model publishes local components, restores them and receives form inp
body: `globalThis.__TJWATER_CONFIG__ = ${JSON.stringify({
TJWATER_AUTH_MODE: "disabled",
TJWATER_SERVER_API_BASE_URL: "https://tjwater-api.invalid",
TJWATER_AGENT_API_BASE_URL: target.origin,
TJWATER_AGENT_URL: target.origin,
TJWATER_MAP_URL: "https://workbench-map.invalid/geoserver",
TJWATER_GEOSERVER_WORKSPACE: "tjwater_next",
TJWATER_ENABLE_MSW: "false"
@@ -66,7 +66,7 @@ test("real model publishes local components, restores them and receives form inp
await page.getByRole("menuitem", { name: new RegExp(requestedModelLabel) }).click();
}
const firstResponse = page.waitForResponse(
(response) => response.url() === `${base}/stream` && response.request().method() === "POST"
(response) => response.url() === `${base}/responses` && response.request().method() === "POST"
);
await panel.getByRole("button", { name: "发送 Agent 指令" }).click();
const stream = await firstResponse;
@@ -103,18 +103,18 @@ test("real model publishes local components, restores them and receives form inp
.getByLabel("现场位置(必填)")
.fill("演练:东侧阀门旁,未实地核验。本轮仍为功能验收,请不要写业务证据或记忆,只确认收到。");
const secondResponse = page.waitForResponse(
(response) => response.url() === `${base}/stream` && response.request().method() === "POST"
(response) => response.url() === `${base}/responses` && response.request().method() === "POST"
);
await panel.getByRole("button", { name: "发送补充资料" }).click();
const submitted = await secondResponse;
const submittedBody = submitted.request().postDataJSON() as {
messages: Array<{ role: string; parts: Array<{ type: string; text?: string }> }>;
input: Array<{ role: string; content: Array<{ type: string; text?: string }> }>;
};
expect(
submittedBody.messages
submittedBody.input
.filter((message) => message.role === "user")
.at(-1)
?.parts.some((part) => part.text?.includes("现场位置:演练:东侧阀门旁"))
?.content.some((part) => part.text?.includes("现场位置:演练:东侧阀门旁"))
).toBe(true);
expect(submitted.ok()).toBe(true);
await expect(panel.getByRole("button", { name: "发送 Agent 指令" })).toBeVisible({