refactor(agent)!: focus tools on SCADA
This commit is contained in:
@@ -22,26 +22,26 @@ describe("resolveRequestedStreamSessionId", () => {
|
||||
expect(
|
||||
resolveRequestedStreamSessionId({
|
||||
sessionId: " existing-session ",
|
||||
promptText: "检查压力异常",
|
||||
promptText: "检查 SCADA 液位异常",
|
||||
createClientSessionId,
|
||||
}),
|
||||
).toBe("existing-session");
|
||||
|
||||
expect(
|
||||
resolveRequestedStreamSessionId({
|
||||
promptText: "检查压力异常",
|
||||
promptText: "检查 SCADA 液位异常",
|
||||
createClientSessionId,
|
||||
}),
|
||||
).toBeUndefined();
|
||||
});
|
||||
|
||||
it("keeps the pressure trend demo on its generated client session id", () => {
|
||||
it("does not special-case removed SCADA trend demo prompts", () => {
|
||||
expect(
|
||||
resolveRequestedStreamSessionId({
|
||||
promptText: "展示最近压力趋势",
|
||||
promptText: "展示最近液位趋势",
|
||||
createClientSessionId: () => "demo-session",
|
||||
}),
|
||||
).toBe("demo-session");
|
||||
).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -92,7 +92,7 @@ describe("generateSessionTitle", () => {
|
||||
messages: async () => [
|
||||
{
|
||||
info: { role: "assistant" },
|
||||
parts: [{ type: "text", text: "标题:泵站压力异常排查。" }],
|
||||
parts: [{ type: "text", text: "标题:SCADA 液位异常排查。" }],
|
||||
},
|
||||
],
|
||||
abortSession: async () => undefined,
|
||||
@@ -100,14 +100,14 @@ describe("generateSessionTitle", () => {
|
||||
|
||||
const title = await generateSessionTitle(runtime, {
|
||||
sessionId: "chat-session",
|
||||
latestUserMessage: "检查一下三号泵站最近压力波动的原因",
|
||||
latestAssistantMessage: "三号泵站压力波动主要与夜间阀门开度变化有关。",
|
||||
latestUserMessage: "检查一下 82c7d04c 资产最近液位波动的原因",
|
||||
latestAssistantMessage: "该 SCADA 资产液位波动主要与夜间水位快速变化时段重合。",
|
||||
fallbackTitle: "新对话",
|
||||
});
|
||||
|
||||
expect(title).toBe("泵站压力异常排查");
|
||||
expect(titlePrompt).toContain("用户:检查一下三号泵站最近压力波动的原因");
|
||||
expect(titlePrompt).toContain("助手:三号泵站压力波动主要与夜间阀门开度变化有关。");
|
||||
expect(title).toBe("SCADA 液位异常排查");
|
||||
expect(titlePrompt).toContain("用户:检查一下 82c7d04c 资产最近液位波动的原因");
|
||||
expect(titlePrompt).toContain("助手:该 SCADA 资产液位波动主要与夜间水位快速变化时段重合。");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -124,32 +124,32 @@ describe("buildPromptWithLearningContext", () => {
|
||||
{
|
||||
recentTurns: [],
|
||||
persistedMessages: [
|
||||
{ role: "user", content: "先分析 3 号泵站夜间压力波动" },
|
||||
{ role: "user", content: "先分析 82c7d04c 资产夜间液位波动" },
|
||||
{
|
||||
role: "assistant",
|
||||
content: "已定位到夜间阀门开度变化与压力波动时间段重合,下一步准备对比相邻支路。",
|
||||
content: "已定位到夜间液位变化与读数异常时段重合,下一步准备对比温度和液位曲线。",
|
||||
isError: true,
|
||||
},
|
||||
{ role: "assistant", content: "⚠️ **请求已中断**", isError: true },
|
||||
],
|
||||
message: "继续刚才的分析,并补充相邻支路影响",
|
||||
message: "继续刚才的分析,并补充温度指标对照",
|
||||
},
|
||||
);
|
||||
|
||||
expect(prompt).toContain("用户:先分析 3 号泵站夜间压力波动");
|
||||
expect(prompt).toContain("用户:先分析 82c7d04c 资产夜间液位波动");
|
||||
expect(prompt).toContain(
|
||||
"助手:已定位到夜间阀门开度变化与压力波动时间段重合,下一步准备对比相邻支路。",
|
||||
"助手:已定位到夜间液位变化与读数异常时段重合,下一步准备对比温度和液位曲线。",
|
||||
);
|
||||
expect(prompt).not.toContain("⚠️ **请求已中断**");
|
||||
expect(prompt).toContain("[Current user request]\n继续刚才的分析,并补充相邻支路影响");
|
||||
expect(prompt).toContain("[Current user request]\n继续刚才的分析,并补充温度指标对照");
|
||||
});
|
||||
|
||||
it("falls back to history turns when frontend state is unavailable", async () => {
|
||||
const recentTurns: SessionTurnRecord[] = [
|
||||
{
|
||||
id: "turn-1",
|
||||
userMessage: "检查 DMA-2 夜间漏损异常",
|
||||
assistantMessage: "DMA-2 在 02:00-04:00 出现持续最小夜流抬升。",
|
||||
userMessage: "检查 82c7d04c 资产夜间液位异常",
|
||||
assistantMessage: "82c7d04c 在 02:00-04:00 出现持续液位抬升。",
|
||||
timestamp: new Date().toISOString(),
|
||||
toolCallCount: 1,
|
||||
},
|
||||
@@ -161,12 +161,12 @@ describe("buildPromptWithLearningContext", () => {
|
||||
"project-1",
|
||||
{
|
||||
recentTurns,
|
||||
message: "继续给出排查建议",
|
||||
message: "继续给出数据核查建议",
|
||||
},
|
||||
);
|
||||
|
||||
expect(prompt).toContain("用户:检查 DMA-2 夜间漏损异常");
|
||||
expect(prompt).toContain("助手:DMA-2 在 02:00-04:00 出现持续最小夜流抬升。");
|
||||
expect(prompt).toContain("用户:检查 82c7d04c 资产夜间液位异常");
|
||||
expect(prompt).toContain("助手:82c7d04c 在 02:00-04:00 出现持续液位抬升。");
|
||||
});
|
||||
|
||||
it("skips restored conversation injection when reusing an existing opencode session", async () => {
|
||||
@@ -216,20 +216,20 @@ describe("buildPromptWithLearningContext", () => {
|
||||
describe("extractLatestFrontendTurn", () => {
|
||||
it("extracts the latest valid frontend user and assistant turn", () => {
|
||||
const turn = extractLatestFrontendTurn([
|
||||
{ role: "user", content: "检查 DMA-2 漏损" },
|
||||
{ role: "user", content: "检查 82c7d04c 液位异常" },
|
||||
{
|
||||
role: "assistant",
|
||||
content: "DMA-2 夜间最小流量持续抬升。",
|
||||
content: "82c7d04c 夜间液位持续抬升。",
|
||||
progress: [{ id: "tool-dma", phase: "tool" }],
|
||||
},
|
||||
{ role: "user", content: "继续分析相邻分区" },
|
||||
{ role: "user", content: "继续分析同类资产" },
|
||||
{ role: "assistant", content: "⚠️ **请求已中断**", isError: true },
|
||||
]);
|
||||
|
||||
expect(turn).toEqual({
|
||||
assistantMessage: "DMA-2 夜间最小流量持续抬升。",
|
||||
assistantMessage: "82c7d04c 夜间液位持续抬升。",
|
||||
toolCallCount: 1,
|
||||
userMessage: "检查 DMA-2 漏损",
|
||||
userMessage: "检查 82c7d04c 液位异常",
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -239,7 +239,7 @@ describe("buildForkedSessionUiState", () => {
|
||||
const forked = buildForkedSessionUiState(
|
||||
{
|
||||
messages: [
|
||||
{ role: "user", content: "画压力曲线" },
|
||||
{ role: "user", content: "画液位曲线" },
|
||||
{
|
||||
role: "assistant",
|
||||
content: "已生成图表",
|
||||
@@ -265,7 +265,7 @@ describe("buildForkedSessionUiState", () => {
|
||||
sessionId: "forked-session",
|
||||
isTitleManuallyEdited: false,
|
||||
messages: [
|
||||
{ role: "user", content: "画压力曲线" },
|
||||
{ role: "user", content: "画液位曲线" },
|
||||
{
|
||||
role: "assistant",
|
||||
content: "已生成图表",
|
||||
|
||||
@@ -20,12 +20,12 @@ const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
|
||||
|
||||
describe("streamPromptResponse", () => {
|
||||
it("does not split short Chinese text only because it has punctuation", () => {
|
||||
expect(detectTokenSmoothingChunk("管网压力异常,需要继续分析", "zh")).toBeNull();
|
||||
expect(detectTokenSmoothingChunk("管网压力异常需要继续分析。", "zh")).toBeNull();
|
||||
expect(detectTokenSmoothingChunk("SCADA 液位异常,需要继续分析", "zh")).toBeNull();
|
||||
expect(detectTokenSmoothingChunk("SCADA 液位异常需要继续分析。", "zh")).toBeNull();
|
||||
});
|
||||
|
||||
it("does not immediately release a single Chinese character", () => {
|
||||
expect(detectTokenSmoothingChunk("管", "zh")).toBeNull();
|
||||
expect(detectTokenSmoothingChunk("液", "zh")).toBeNull();
|
||||
});
|
||||
|
||||
it("keeps non-Chinese text on the word path even with zh locale", () => {
|
||||
@@ -33,7 +33,7 @@ describe("streamPromptResponse", () => {
|
||||
});
|
||||
|
||||
it("falls back to bounded Chinese chunks when punctuation is absent", () => {
|
||||
const content = "管网压力异常需要继续分析东部主干供水走廊和相关阀门状态变化";
|
||||
const content = "SCADA 液位异常需要继续分析水质温度和液位读数变化";
|
||||
const chunk = detectTokenSmoothingChunk(content, "zh");
|
||||
|
||||
expect(chunk).not.toBeNull();
|
||||
@@ -72,11 +72,11 @@ describe("streamPromptResponse", () => {
|
||||
write: (event, data) => events.push({ event, data }),
|
||||
});
|
||||
|
||||
smoother.writeToken("管");
|
||||
smoother.writeToken("液");
|
||||
await sleep(8);
|
||||
expect(events).toHaveLength(0);
|
||||
|
||||
const content = "网压力异常需要继续分析东部主干供水走廊和相关阀门状态";
|
||||
const content = "位异常需要继续分析水质温度和液位读数持续变化趋势";
|
||||
for (const char of Array.from(content)) {
|
||||
smoother.writeToken(char);
|
||||
}
|
||||
@@ -84,7 +84,7 @@ describe("streamPromptResponse", () => {
|
||||
|
||||
expect(events.length).toBeGreaterThan(0);
|
||||
await smoother.flush();
|
||||
expect(events.map((item) => item.data.content).join("")).toBe(`管${content}`);
|
||||
expect(events.map((item) => item.data.content).join("")).toBe(`液${content}`);
|
||||
});
|
||||
|
||||
it("smooths long Chinese text without waiting for paragraph-sized buffers", async () => {
|
||||
@@ -97,7 +97,7 @@ describe("streamPromptResponse", () => {
|
||||
write: (event, data) => events.push({ event, data }),
|
||||
});
|
||||
|
||||
const content = "管网压力异常需要继续分析东部主干供水走廊和相关阀门状态变化";
|
||||
const content = "SCADA 液位异常需要继续分析水质温度和液位读数变化";
|
||||
for (const char of Array.from(content)) {
|
||||
smoother.writeToken(char);
|
||||
}
|
||||
@@ -133,7 +133,7 @@ describe("streamPromptResponse", () => {
|
||||
sessionID: "runtime-session-1",
|
||||
partID: "text-part-1",
|
||||
field: "text",
|
||||
delta: "管网压力异常需要继续分析",
|
||||
delta: "SCADA 液位异常需要继续分析",
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -142,7 +142,7 @@ describe("streamPromptResponse", () => {
|
||||
sessionID: "runtime-session-1",
|
||||
partID: "text-part-1",
|
||||
field: "text",
|
||||
delta: "东部主干供水走廊和相关阀门状态。",
|
||||
delta: "水质温度和液位读数。",
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -172,7 +172,7 @@ describe("streamPromptResponse", () => {
|
||||
expect(tokenEvents.length).toBeGreaterThan(0);
|
||||
expect(tokenEvents.every((item) => events.indexOf(item) < doneIndex)).toBe(true);
|
||||
expect(tokenEvents.map((item) => item.data.content).join("")).toBe(
|
||||
"管网压力异常需要继续分析东部主干供水走廊和相关阀门状态。",
|
||||
"SCADA 液位异常需要继续分析水质温度和液位读数。",
|
||||
);
|
||||
});
|
||||
|
||||
@@ -535,7 +535,7 @@ describe("streamPromptResponse", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("maps visual tool calls to UIEnvelope SSE payloads", async () => {
|
||||
it("maps zoom_to_map tool calls to UIEnvelope SSE payloads", async () => {
|
||||
const runtime = {
|
||||
subscribeEvents: async () =>
|
||||
createEventStream([
|
||||
@@ -544,20 +544,19 @@ describe("streamPromptResponse", () => {
|
||||
properties: {
|
||||
sessionID: "runtime-session-1",
|
||||
part: {
|
||||
id: "tool-part-chart",
|
||||
id: "tool-part-map",
|
||||
sessionID: "runtime-session-1",
|
||||
messageID: "message-1",
|
||||
type: "tool",
|
||||
callID: "call-chart",
|
||||
tool: "show_chart",
|
||||
callID: "call-map",
|
||||
tool: "zoom_to_map",
|
||||
state: {
|
||||
status: "running",
|
||||
input: {
|
||||
reason: "展示压力趋势",
|
||||
title: "压力趋势",
|
||||
chart_type: "line",
|
||||
x_data: ["00:00", "01:00"],
|
||||
series: [{ name: "P-1", data: [0.4, 0.42] }],
|
||||
reason: "定位 SCADA 资产附近位置",
|
||||
x: 121.47,
|
||||
y: 31.23,
|
||||
source_crs: "EPSG:4326",
|
||||
},
|
||||
time: { start: Date.now() },
|
||||
},
|
||||
@@ -587,14 +586,15 @@ describe("streamPromptResponse", () => {
|
||||
|
||||
expect(events.find((item) => item.event === "tool_call")?.data).toMatchObject({
|
||||
session_id: "client-session-1",
|
||||
tool: "show_chart",
|
||||
tool: "zoom_to_map",
|
||||
});
|
||||
expect(events.find((item) => item.event === "ui_envelope")?.data).toMatchObject({
|
||||
session_id: "client-session-1",
|
||||
envelope: {
|
||||
kind: "chart",
|
||||
kind: "map_action",
|
||||
schemaVersion: "agent-ui@1",
|
||||
grammar: "echarts-safe-subset",
|
||||
action: "zoom_to_map",
|
||||
surface: "map_overlay",
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
@@ -8,13 +8,13 @@ import {
|
||||
} from "../../src/routes/chatUiState.js";
|
||||
|
||||
describe("appendBackendToolArtifact", () => {
|
||||
it("persists show_chart tool calls as chart artifacts", () => {
|
||||
it("persists removed visual tool calls as generic tool artifacts", () => {
|
||||
const artifacts = appendBackendToolArtifact([], {
|
||||
session_id: "session-1",
|
||||
tool: "show_chart",
|
||||
reason: "测试折线图渲染",
|
||||
params: {
|
||||
title: "压力曲线",
|
||||
title: "液位曲线",
|
||||
chart_type: "line",
|
||||
x_data: ["00:00", "01:00"],
|
||||
series: [{ name: "P-101", data: [0.42, 0.41] }],
|
||||
@@ -24,8 +24,8 @@ describe("appendBackendToolArtifact", () => {
|
||||
expect(artifacts).toHaveLength(1);
|
||||
expect(artifacts[0]).toMatchObject({
|
||||
tool: "show_chart",
|
||||
kind: "chart",
|
||||
title: "压力曲线",
|
||||
kind: "tool",
|
||||
title: "液位曲线",
|
||||
description: "测试折线图渲染",
|
||||
params: {
|
||||
chart_type: "line",
|
||||
@@ -50,9 +50,9 @@ describe("appendBackendUiEnvelope", () => {
|
||||
envelope: {
|
||||
kind: "map_action",
|
||||
schemaVersion: "agent-ui@1",
|
||||
action: "locate_features",
|
||||
action: "zoom_to_map",
|
||||
surface: "map_overlay",
|
||||
params: { ids: ["J-1"], feature_type: "junction" },
|
||||
params: { x: 121.47, y: 31.23, source_crs: "EPSG:4326" },
|
||||
},
|
||||
});
|
||||
|
||||
@@ -63,9 +63,9 @@ describe("appendBackendUiEnvelope", () => {
|
||||
envelope: {
|
||||
kind: "map_action",
|
||||
schemaVersion: "agent-ui@1",
|
||||
action: "locate_features",
|
||||
action: "zoom_to_map",
|
||||
surface: "map_overlay",
|
||||
params: { ids: ["J-1"], feature_type: "junction" },
|
||||
params: { x: 121.47, y: 31.23, source_crs: "EPSG:4326" },
|
||||
},
|
||||
renderStatus: "pending",
|
||||
},
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
import { describe, expect, it } from "bun:test";
|
||||
|
||||
import {
|
||||
createPressureTrendDemoEnvelopePayload,
|
||||
createPressureTrendDemoToolCall,
|
||||
isPressureTrendDemoPrompt,
|
||||
} from "../../src/routes/pressureTrendDemo.js";
|
||||
|
||||
describe("pressure trend demo", () => {
|
||||
it("matches the controlled demo prompt", () => {
|
||||
expect(isPressureTrendDemoPrompt("展示最近压力趋势")).toBe(true);
|
||||
expect(isPressureTrendDemoPrompt("请 展示 最近 压力 趋势")).toBe(true);
|
||||
expect(isPressureTrendDemoPrompt("展示最近流量趋势")).toBe(false);
|
||||
});
|
||||
|
||||
it("creates a show_chart tool call that maps to a chart UIEnvelope", () => {
|
||||
const toolCall = createPressureTrendDemoToolCall();
|
||||
const payload = createPressureTrendDemoEnvelopePayload("client-session-1", toolCall);
|
||||
|
||||
expect(toolCall.tool).toBe("show_chart");
|
||||
expect(payload).toMatchObject({
|
||||
session_id: "client-session-1",
|
||||
envelope: {
|
||||
kind: "chart",
|
||||
schemaVersion: "agent-ui@1",
|
||||
grammar: "echarts-safe-subset",
|
||||
surface: "chat_inline",
|
||||
spec: {
|
||||
title: "最近压力趋势",
|
||||
chart_type: "line",
|
||||
},
|
||||
data: {
|
||||
x_data: ["10:00", "11:00", "12:00", "13:00", "14:00", "15:00"],
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user