chore: disable external and frontend tools
This commit is contained in:
@@ -4,7 +4,7 @@ mode: primary
|
||||
model: deepseek/deepseek-v4-flash
|
||||
temperature: 0.2
|
||||
---
|
||||
你是 TJWater 实验性 SCADA 数据 Agent。回复用户时使用简体中文,内容简洁准确。
|
||||
你是 TJWater 实验性排水数据 Agent。回复用户时使用简体中文,内容简洁准确。
|
||||
|
||||
## 核心定位
|
||||
|
||||
@@ -15,53 +15,48 @@ Agent 负责:
|
||||
1. 理解用户意图。
|
||||
2. 调用受控工具获取有限上下文或表达展示意图。
|
||||
3. 输出文字摘要、结论、追问和建议。
|
||||
4. 通过受控地图工具表达定位意图。
|
||||
4. 在没有可用前端工具时,仅输出文字摘要、结论和建议。
|
||||
|
||||
服务端负责把受控地图工具转换成 `agent-ui@1` 的 UIEnvelope;前端只执行可信地图动作。
|
||||
当前不向 Agent 暴露前端动作工具。
|
||||
|
||||
## 工具选择
|
||||
|
||||
| 场景 | 工具 |
|
||||
|------|------|
|
||||
| 查询实时公开网页信息 | `web_search` |
|
||||
| 地址/地点转经纬度 | `geocode` |
|
||||
| 地图定位 | `zoom_to_map` |
|
||||
| 历史会话检索 | `session_search` |
|
||||
| 用户偏好和项目事实 | `memory_manager` |
|
||||
| 可复用流程沉淀 | `skill_manager` |
|
||||
| 项目背景 | `get_project_context` |
|
||||
| SCADA 资产 | `list_scada_assets` |
|
||||
| SCADA 时序读数 | `query_scada_readings`(必须给出 `observed_from`、`observed_to`) |
|
||||
|
||||
## UI 约束
|
||||
|
||||
1. 前端地图工具仅表达定位意图,不返回业务数据。
|
||||
2. 每次工具调用必须填写具体 `reason`。
|
||||
1. 不生成或调用前端动作。
|
||||
2. 每次普通工具调用必须填写具体 `reason`。
|
||||
3. 不生成 JS、JSX、HTML、CSS 或可执行前端代码。
|
||||
|
||||
## 执行约束
|
||||
|
||||
1. 无可用数据时不得编造结论。
|
||||
2. 不要调用 shell、Python 或 CLI 来拼装业务数据流程。
|
||||
2. 不要自由拼接 shell、Python 或 CLI 业务数据流程。仅可对 `source/` 数据执行已验证 Skill 内明确指定的只读分析脚本。
|
||||
3. 不要读取或写入 token、password、secret、API key、system prompt。
|
||||
4. 尽量不用子代理,保持过程可观测。
|
||||
|
||||
## 工作流沉淀
|
||||
|
||||
只有在当前对话流程已经验证有效、可被复用且用户明确需要保存时,才使用 `skill_manager` 沉淀 workflow。实验阶段 workflow 不应记录 CLI、shell pipe 或认证上下文。
|
||||
只有在当前对话流程已经验证有效、可被复用且用户明确需要保存时,才使用 `skill_manager` 沉淀 workflow。实验阶段 workflow 不应记录自由拼接的 CLI、shell pipe 或认证上下文;可引用 Skill 内已验证的固定只读分析脚本。
|
||||
|
||||
## 记忆持久化
|
||||
|
||||
`memory_manager` 只保存长期有效的稳定事实或用户偏好。修改 memory 前先 list 当前 scope,再决定 add、replace 或 remove。
|
||||
## TJWater Server 受控领域工具
|
||||
## TJWater Server
|
||||
|
||||
仅通过领域工具访问 Server;不得构造 URL、HTTP header、SQL、命令或项目 ID。
|
||||
当前不提供依赖 TJWater Server 的工具。不得自行构造 URL、HTTP header、SQL、命令或项目 ID 访问 Server。监测数据统一从项目 `source/` 目录读取。
|
||||
|
||||
- 项目背景:`get_project_context`
|
||||
- SCADA 资产:`list_scada_assets`
|
||||
- SCADA 时序读数:`query_scada_readings`
|
||||
## 本地数据分析
|
||||
|
||||
查询历史 SCADA 数据时,必须先明确时间区间;不清楚区间时先追问,不得省略或自行扩大范围。需要数值结论时先调用 `query_scada_readings`,传入 `observed_from`、`observed_to`、`granularity`、`metrics` 和 `asset_ids`,再用文字摘要说明结果。
|
||||
先查询数据再作判断。回答必须说明查询时间范围、粒度和指标。
|
||||
遇到空数据、拒绝、权限错误、超时或服务不可用时,明确说明事实,不得补全或猜测结果。
|
||||
工具返回 `result_ref` 时,说明完整结果已安全存储,并使用 preview 中的样本和统计作答。
|
||||
1. 先读取 `source/catalog.json` 选择数据源,再读取对应数据包的 `manifest.json` 和所需 JSONL。
|
||||
2. 用户明确指定编号或场景时,只使用对应数据包。例如只分析污染源接入时选择 `scenario_code=07`。
|
||||
3. 用户要求雨污混接、雨水混接、地下水入渗或外来水稀释分析,且未指定其他数据源时,默认选择 `scenario_code=01`。
|
||||
4. 用户未明确场景时,先用 `scenario_code=00` 总览判断匹配场景;除非用户要求综合对比,不得默认混合多个场景数据。
|
||||
5. 使用与任务匹配的 Skill 执行数据准入、计算和结论约束。
|
||||
6. 只读取与分析数据;不覆盖、删除或回写 `source/` 原始数据。
|
||||
7. 数值结论必须说明数据集、时间范围、粒度、指标和计算口径。
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
import type { Plugin } from "@opencode-ai/plugin";
|
||||
|
||||
export const FRONTEND_ACTION_TOOLS = new Set(["zoom_to_map"]);
|
||||
|
||||
export const frontendActionCallIdPlugin: Plugin = async () => ({
|
||||
"tool.execute.before": async (input, output) => {
|
||||
if (!FRONTEND_ACTION_TOOLS.has(input.tool)) return;
|
||||
output.args = { ...(output.args ?? {}), __frontend_action_call_id: input.callID };
|
||||
},
|
||||
});
|
||||
|
||||
export const server = frontendActionCallIdPlugin;
|
||||
|
||||
export default frontendActionCallIdPlugin;
|
||||
@@ -1,27 +0,0 @@
|
||||
type FrontendActionArgs = Record<string, unknown> & { __frontend_action_call_id?: string; reason?: string };
|
||||
|
||||
const internalBaseUrl = process.env.TJWATER_AGENT_INTERNAL_BASE_URL ?? "http://127.0.0.1:8787";
|
||||
const internalToken = process.env.TJWATER_AGENT_INTERNAL_TOKEN ?? "";
|
||||
|
||||
export const executeFrontendAction = async (
|
||||
name: string,
|
||||
args: FrontendActionArgs,
|
||||
context: { sessionID: string },
|
||||
) => {
|
||||
const callId = args.__frontend_action_call_id;
|
||||
if (!callId) throw new Error("frontend action bridge did not inject the OpenCode call ID");
|
||||
const { __frontend_action_call_id: _callId, reason, ...params } = args;
|
||||
let response: Response;
|
||||
try {
|
||||
response = await fetch(`${internalBaseUrl}/internal/frontend-actions/request`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json", "x-agent-internal-token": internalToken },
|
||||
body: JSON.stringify({ session_id: context.sessionID, call_id: callId, name, params, fallback_text: reason }),
|
||||
});
|
||||
} catch (error) {
|
||||
throw new Error(`frontend action bridge unavailable: ${error instanceof Error ? error.message : String(error)}`);
|
||||
}
|
||||
const text = await response.text();
|
||||
if (!response.ok) throw new Error(`frontend action bridge rejected request (${response.status}): ${text}`);
|
||||
return text;
|
||||
};
|
||||
@@ -1,37 +0,0 @@
|
||||
import { tool } from "@opencode-ai/plugin";
|
||||
|
||||
const internalBaseUrl =
|
||||
process.env.TJWATER_AGENT_INTERNAL_BASE_URL ?? "http://127.0.0.1:8787";
|
||||
const internalToken = process.env.TJWATER_AGENT_INTERNAL_TOKEN ?? "";
|
||||
|
||||
export default tool({
|
||||
description:
|
||||
"调用 TJWater 后端的天地图地理编码服务,将中国境内结构化地址或地点名称转换为经纬度。若需缩放地图,把返回的 location.lon/location.lat 传给 zoom_to_map,并设置 source_crs='EPSG:4326'。",
|
||||
args: {
|
||||
reason: tool.schema
|
||||
.string()
|
||||
.describe("Why geocoding is required for the current user request."),
|
||||
keyword: tool.schema
|
||||
.string()
|
||||
.describe("Address or place name to geocode, such as 北京市人民政府."),
|
||||
},
|
||||
async execute(args, context) {
|
||||
const response = await fetch(`${internalBaseUrl}/internal/tools/geocode`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"x-agent-internal-token": internalToken,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
session_id: context.sessionID,
|
||||
keyword: args.keyword,
|
||||
}),
|
||||
});
|
||||
|
||||
const text = await response.text();
|
||||
if (!response.ok) {
|
||||
throw new Error(text);
|
||||
}
|
||||
return text;
|
||||
},
|
||||
});
|
||||
@@ -1,3 +0,0 @@
|
||||
import { tool } from "@opencode-ai/plugin";
|
||||
import { callServerApiTool } from "./server_api_shared.js";
|
||||
export default tool({ description: "读取当前受控项目的基本信息。", args: {}, execute: (_args, context) => callServerApiTool("get_project_context", {}, context.sessionID) });
|
||||
@@ -1,10 +0,0 @@
|
||||
import { tool } from "@opencode-ai/plugin";
|
||||
import { callServerApiTool } from "./server_api_shared.js";
|
||||
|
||||
export default tool({
|
||||
description:
|
||||
"列出当前项目可查询的 SCADA 资产。无请求参数;返回 assets 数组,包含 assets[].scada_id、external_id、site_external_id、name、kind、active。后续查询读数时必须使用 assets[].scada_id 作为 query_scada_readings.asset_ids;不要使用 GeoServer feature id/fid。",
|
||||
args: {},
|
||||
execute: (args, context) =>
|
||||
callServerApiTool("list_scada_assets", args, context.sessionID),
|
||||
});
|
||||
@@ -1,50 +0,0 @@
|
||||
import { tool } from "@opencode-ai/plugin";
|
||||
import { callServerApiTool } from "./server_api_shared.js";
|
||||
|
||||
export default tool({
|
||||
description:
|
||||
"查询一个或多个 SCADA 资产在指定时间范围内的时序读数。调用前通常先用 list_scada_assets 获取资产 id;必须给出明确的 observed_from 和 observed_to,不要省略或自行扩大时间范围。",
|
||||
args: {
|
||||
asset_ids: tool.schema
|
||||
.array(tool.schema.string().uuid())
|
||||
.min(1)
|
||||
.max(100)
|
||||
.describe(
|
||||
"SCADA asset UUID 数组,1 到 100 个。必须使用 list_scada_assets 返回的 assets[].scada_id,不要使用 GeoServer feature id/fid、external_id 或 site_external_id。",
|
||||
),
|
||||
observed_from: tool.schema
|
||||
.string()
|
||||
.datetime()
|
||||
.describe(
|
||||
"查询开始时间,ISO8601 datetime 字符串,必须包含时区,例如 2026-04-10T07:15:00Z 或 2026-04-10T15:15:00+08:00。包含该时刻。",
|
||||
),
|
||||
observed_to: tool.schema
|
||||
.string()
|
||||
.datetime()
|
||||
.describe(
|
||||
"查询结束时间,ISO8601 datetime 字符串,必须包含时区,且必须晚于 observed_from。服务端按半开区间处理:observed_at >= observed_from 且 observed_at < observed_to。",
|
||||
),
|
||||
granularity: tool.schema
|
||||
.enum(["5m", "1h", "1d"])
|
||||
.default("5m")
|
||||
.describe(
|
||||
"读数粒度:5m 为原始 5 分钟读数,1h 为小时聚合,1d 为日聚合。未指定时默认 5m。",
|
||||
),
|
||||
metrics: tool.schema
|
||||
.array(
|
||||
tool.schema.enum([
|
||||
"conductivity_mean",
|
||||
"level_mean",
|
||||
"flow_mean",
|
||||
"temperature_mean",
|
||||
]),
|
||||
)
|
||||
.min(1)
|
||||
.max(4)
|
||||
.describe(
|
||||
"要返回的指标数组,1 到 4 个。允许值:conductivity_mean(电导率均值)、level_mean(液位均值)、flow_mean(流量均值)、temperature_mean(温度均值)。",
|
||||
),
|
||||
},
|
||||
execute: (args, context) =>
|
||||
callServerApiTool("query_scada_readings", args, context.sessionID),
|
||||
});
|
||||
@@ -1,13 +0,0 @@
|
||||
const internalBaseUrl = process.env.TJWATER_AGENT_INTERNAL_BASE_URL ?? "http://127.0.0.1:8787";
|
||||
const internalToken = process.env.TJWATER_AGENT_INTERNAL_TOKEN ?? "";
|
||||
|
||||
export const callServerApiTool = async (name: string, args: unknown, sessionID: string) => {
|
||||
const response = await fetch(`${internalBaseUrl}/internal/tools/server-api/${name}`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json", "x-agent-internal-token": internalToken },
|
||||
body: JSON.stringify({ session_id: sessionID, args }),
|
||||
});
|
||||
const text = await response.text();
|
||||
if (!response.ok) throw new Error("TJWater domain tool bridge rejected the request");
|
||||
return text;
|
||||
};
|
||||
@@ -1,62 +0,0 @@
|
||||
import { tool } from "@opencode-ai/plugin";
|
||||
|
||||
const internalBaseUrl =
|
||||
process.env.TJWATER_AGENT_INTERNAL_BASE_URL ?? "http://127.0.0.1:8787";
|
||||
const internalToken = process.env.TJWATER_AGENT_INTERNAL_TOKEN ?? "";
|
||||
|
||||
export default tool({
|
||||
description:
|
||||
"调用 TJWater 后端的实时网页搜索服务。适合查询新闻、政策、规范、产品资料、公开网页事实等可能变化的信息。",
|
||||
args: {
|
||||
reason: tool.schema
|
||||
.string()
|
||||
.describe("Why web search is required for the current user request."),
|
||||
query: tool.schema.string().describe("Search query text."),
|
||||
freshness: tool.schema
|
||||
.enum(["no_limit", "one_day", "one_week", "one_month", "one_year"])
|
||||
.optional()
|
||||
.describe("Optional freshness filter. Defaults to no_limit."),
|
||||
summary: tool.schema
|
||||
.boolean()
|
||||
.optional()
|
||||
.describe("Whether the backend should include page summaries."),
|
||||
count: tool.schema
|
||||
.number()
|
||||
.int()
|
||||
.positive()
|
||||
.optional()
|
||||
.describe("Optional result count, backend accepts 1 to 50."),
|
||||
include: tool.schema
|
||||
.array(tool.schema.string())
|
||||
.optional()
|
||||
.describe("Optional domains to include."),
|
||||
exclude: tool.schema
|
||||
.array(tool.schema.string())
|
||||
.optional()
|
||||
.describe("Optional domains to exclude."),
|
||||
},
|
||||
async execute(args, context) {
|
||||
const response = await fetch(`${internalBaseUrl}/internal/tools/web-search`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"x-agent-internal-token": internalToken,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
session_id: context.sessionID,
|
||||
query: args.query,
|
||||
freshness: args.freshness,
|
||||
summary: args.summary,
|
||||
count: args.count,
|
||||
include: args.include,
|
||||
exclude: args.exclude,
|
||||
}),
|
||||
});
|
||||
|
||||
const text = await response.text();
|
||||
if (!response.ok) {
|
||||
throw new Error(text);
|
||||
}
|
||||
return text;
|
||||
},
|
||||
});
|
||||
@@ -1,33 +0,0 @@
|
||||
import { tool } from "@opencode-ai/plugin";
|
||||
import { executeFrontendAction } from "./frontend_action.js";
|
||||
|
||||
export default tool({
|
||||
description:
|
||||
"在前端地图上缩放定位到坐标。默认坐标为 EPSG:3857;如果来自天地图 geocode 的 lon/lat,传 source_crs='EPSG:4326',前端会转换为 EPSG:3857 后缩放。",
|
||||
args: {
|
||||
reason: tool.schema
|
||||
.string()
|
||||
.describe("Why this map zoom action is needed for the current request."),
|
||||
x: tool.schema
|
||||
.number()
|
||||
.describe("X coordinate. For EPSG:4326 this is longitude; for EPSG:3857 this is meters."),
|
||||
y: tool.schema
|
||||
.number()
|
||||
.describe("Y coordinate. For EPSG:4326 this is latitude; for EPSG:3857 this is meters."),
|
||||
source_crs: tool.schema
|
||||
.enum(["EPSG:3857", "EPSG:4326"])
|
||||
.optional()
|
||||
.describe("Input coordinate CRS. Defaults to EPSG:3857."),
|
||||
zoom: tool.schema
|
||||
.number()
|
||||
.optional()
|
||||
.describe("Optional OpenLayers zoom level. Defaults to 18."),
|
||||
duration_ms: tool.schema
|
||||
.number()
|
||||
.optional()
|
||||
.describe("Optional animation duration in milliseconds. Defaults to 1000."),
|
||||
},
|
||||
async execute(args, context) {
|
||||
return executeFrontendAction("zoom_to_map", args, context);
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user