From b857ca543d8233cc6e9d54c18b4699a813bca2cc Mon Sep 17 00:00:00 2001 From: Huarch Date: Wed, 29 Apr 2026 11:58:13 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=B3=A8=E9=87=8A=E4=BB=A5?= =?UTF-8?q?=E8=AF=B4=E6=98=8E=E5=B7=A5=E5=85=B7=E5=92=8C=E4=BC=9A=E8=AF=9D?= =?UTF-8?q?=E7=9A=84=E4=B8=8A=E4=B8=8B=E6=96=87=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .opencode/tools/dynamic_http_call.ts | 1 + .opencode/tools/locate_features.ts | 1 + .opencode/tools/show_chart.ts | 1 + .opencode/tools/view_history.ts | 1 + .opencode/tools/view_scada.ts | 1 + dist/config.js | 1 + dist/server.js | 3 +++ dist/session/registry.js | 1 + src/config.ts | 1 + src/server.ts | 3 +++ src/session/registry.ts | 1 + 11 files changed, 15 insertions(+) diff --git a/.opencode/tools/dynamic_http_call.ts b/.opencode/tools/dynamic_http_call.ts index 79be8c0..1eea28e 100644 --- a/.opencode/tools/dynamic_http_call.ts +++ b/.opencode/tools/dynamic_http_call.ts @@ -18,6 +18,7 @@ export default tool({ .describe("Query arguments object."), }, async execute(args, context) { + // 工具本身不直接持有用户 token;通过 sessionID 回调 Agent 服务,由服务侧补齐用户上下文。 const response = await fetch(`${internalBaseUrl}/internal/tools/dynamic-http-call`, { method: "POST", headers: { diff --git a/.opencode/tools/locate_features.ts b/.opencode/tools/locate_features.ts index 3c5081e..3b862e3 100644 --- a/.opencode/tools/locate_features.ts +++ b/.opencode/tools/locate_features.ts @@ -9,6 +9,7 @@ export default tool({ .describe("Type of feature to locate."), }, async execute() { + // 前端工具只负责生成 tool part,真正的地图动作由 Agent SSE 适配层转发给浏览器执行。 return "已在地图上定位到指定要素。"; }, }); diff --git a/.opencode/tools/show_chart.ts b/.opencode/tools/show_chart.ts index 201aa3d..c1aa510 100644 --- a/.opencode/tools/show_chart.ts +++ b/.opencode/tools/show_chart.ts @@ -22,6 +22,7 @@ export default tool({ y_axis_name: tool.schema.string().optional().describe("Y-axis display name."), }, async execute() { + // 图表数据已经在工具参数里,前端收到 tool_call 后直接渲染,不再二次请求后端。 return "图表将在对话中显示。"; }, }); diff --git a/.opencode/tools/view_history.ts b/.opencode/tools/view_history.ts index 455281b..85d61b2 100644 --- a/.opencode/tools/view_history.ts +++ b/.opencode/tools/view_history.ts @@ -13,6 +13,7 @@ export default tool({ end_time: tool.schema.string().optional().describe("Optional ISO8601 end time."), }, async execute() { + // 返回短确认即可;面板打开动作由前端根据 tool_call 参数完成。 return "已打开计算结果面板。"; }, }); diff --git a/.opencode/tools/view_scada.ts b/.opencode/tools/view_scada.ts index 5f094b8..86376ee 100644 --- a/.opencode/tools/view_scada.ts +++ b/.opencode/tools/view_scada.ts @@ -16,6 +16,7 @@ export default tool({ end_time: tool.schema.string().optional().describe("Optional ISO8601 end time."), }, async execute() { + // SCADA 面板仍在浏览器侧执行,工具结果不承载实际监测数据。 return "已打开 SCADA 监测面板。"; }, }); diff --git a/dist/config.js b/dist/config.js index 1a55ea5..67583a4 100644 --- a/dist/config.js +++ b/dist/config.js @@ -1,4 +1,5 @@ import { z } from "zod"; +// 统一在启动时解析环境变量,避免业务代码里散落字符串默认值。 const envSchema = z.object({ NODE_ENV: z.string().default("development"), PORT: z.coerce.number().int().positive().default(8788), diff --git a/dist/server.js b/dist/server.js index a11523e..3f4a279 100644 --- a/dist/server.js +++ b/dist/server.js @@ -12,6 +12,7 @@ const app = express(); const registry = new SessionRegistry(config.SESSION_TTL_SECONDS); const sessionBridge = new ChatSessionBridge(registry, opencodeRuntime); const internalToken = config.AGENT_INTERNAL_TOKEN ?? randomUUID(); +// 这个 token 只用于 .opencode/tools 回调本服务,避免把 internal endpoint 暴露成无鉴权入口。 process.env.TJWATER_AGENT_INTERNAL_TOKEN = internalToken; app.use(cors()); app.use(express.json({ limit: "1mb" })); @@ -49,6 +50,7 @@ app.post("/internal/tools/dynamic-http-call", async (req, res) => { return; } try { + // opencode 工具运行在 .opencode 侧,这里负责把工具调用重新绑定到当前用户/项目上下文。 const result = await dynamicHttpExecutor.execute({ path: req.body?.path, method: req.body?.method, @@ -71,6 +73,7 @@ const server = app.listen(config.PORT, config.HOST, () => { const shutdown = async () => { logger.info("shutting down TJWaterAgent"); server.close(); + // 同步关闭 embedded opencode server,避免本服务退出后留下孤儿进程。 await opencodeRuntime.dispose(); }; process.on("SIGINT", () => { diff --git a/dist/session/registry.js b/dist/session/registry.js index b181c22..d66f731 100644 --- a/dist/session/registry.js +++ b/dist/session/registry.js @@ -43,6 +43,7 @@ export class SessionRegistry { return expired; } makeKey(context) { + // 会话隔离不能只看 conversationId;同一浏览器会话切换用户或项目时必须映射到不同 opencode session。 const digest = crypto .createHash("sha256") .update([ diff --git a/src/config.ts b/src/config.ts index d7ef2e2..8c65ec0 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,5 +1,6 @@ import { z } from "zod"; +// 统一在启动时解析环境变量,避免业务代码里散落字符串默认值。 const envSchema = z.object({ NODE_ENV: z.string().default("development"), PORT: z.coerce.number().int().positive().default(8788), diff --git a/src/server.ts b/src/server.ts index f0c8af8..d15caab 100644 --- a/src/server.ts +++ b/src/server.ts @@ -16,6 +16,7 @@ const registry = new SessionRegistry(config.SESSION_TTL_SECONDS); const sessionBridge = new ChatSessionBridge(registry, opencodeRuntime); const internalToken = config.AGENT_INTERNAL_TOKEN ?? randomUUID(); +// 这个 token 只用于 .opencode/tools 回调本服务,避免把 internal endpoint 暴露成无鉴权入口。 process.env.TJWATER_AGENT_INTERNAL_TOKEN = internalToken; app.use(cors()); @@ -57,6 +58,7 @@ app.post("/internal/tools/dynamic-http-call", async (req, res) => { } try { + // opencode 工具运行在 .opencode 侧,这里负责把工具调用重新绑定到当前用户/项目上下文。 const result = await dynamicHttpExecutor.execute( { path: req.body?.path, @@ -87,6 +89,7 @@ const server = app.listen(config.PORT, config.HOST, () => { const shutdown = async () => { logger.info("shutting down TJWaterAgent"); server.close(); + // 同步关闭 embedded opencode server,避免本服务退出后留下孤儿进程。 await opencodeRuntime.dispose(); }; diff --git a/src/session/registry.ts b/src/session/registry.ts index 04260e6..47ca49e 100644 --- a/src/session/registry.ts +++ b/src/session/registry.ts @@ -62,6 +62,7 @@ export class SessionRegistry { } private makeKey(context: SessionContext): string { + // 会话隔离不能只看 conversationId;同一浏览器会话切换用户或项目时必须映射到不同 opencode session。 const digest = crypto .createHash("sha256") .update(