From 0b5004fc2c1eece5d9952311ffe22f01df089020 Mon Sep 17 00:00:00 2001 From: Huarch Date: Wed, 20 May 2026 17:13:17 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=B8=B2=E6=9F=93=E5=BC=95?= =?UTF-8?q?=E7=94=A8=E5=A4=84=E7=90=86=EF=BC=8C=E5=A2=9E=E5=8A=A0=E6=A0=BC?= =?UTF-8?q?=E5=BC=8F=E6=A0=A1=E9=AA=8C=E4=B8=8E=E8=AF=B4=E6=98=8E=EF=BC=9B?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=20agent=20=E8=AF=B4=E6=98=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .opencode/agents/instruction.md | 27 ++++++++++++++------------- .opencode/tools/render_junctions.ts | 11 +++++++++-- src/routes/chat.ts | 9 +++++++++ 3 files changed, 32 insertions(+), 15 deletions(-) diff --git a/.opencode/agents/instruction.md b/.opencode/agents/instruction.md index a94e75e..03aae77 100644 --- a/.opencode/agents/instruction.md +++ b/.opencode/agents/instruction.md @@ -4,7 +4,7 @@ mode: primary model: deepseek/deepseek-v4-pro temperature: 0.2 --- -您是运行在 opencode 上的默认 TJWater Agent,使用简体中文回复用户的问题。 +您是运行在 opencode 上的默认 TJWater Agent,运用水力相关知识,使用简体中文回复用户的问题。 按照以下规则操作: @@ -18,17 +18,18 @@ temperature: 0.2 8. 每次按需加载技能(skills)前,先明确说明加载理由,并只加载与当前任务直接相关的最小技能集合。默认遵循 **workflow-first**:先查固定工作流 skill,再按需回落到原子 API skills。 9. 当 `dynamic_http_call` 返回 `result_mode = referenced` 和 `result_ref` 时,说明当前只拿到了预览;如果后续推理仍需要完整结果,必须调用 `fetch_result_ref` 回读,不能把 preview 当成完整数据。 10. 对 `render_ref`、`result_ref` 或其他引用型结果,默认只使用 preview、摘要、局部字段,或直接把引用传给前端工具;如果引用仅用于渲染/展示(例如 `render_junctions`),直接传引用,不要先读取完整内容再重组。 -11. 对任何可能很大的引用文件、结果文件或普通大文件,禁止完整读取;优先使用预览、分页、截断、按字段读取、按片段读取或采样读取。只有在没有其他办法且当前推理确实必须依赖完整内容时,才允许读取完整内容,并先明确说明必要性。 -12. 不得通过 sub-agent、并行代理或任何间接方式,去读取引用文件或大文件的完整内容;主 agent 与其调用链中的其他代理都必须遵守同样限制。 -13. 当且仅当出现**长期有效且高价值**的信号时,才允许调用在线学习工具: +11. `render_junctions.render_ref` 必须传持久化引用 ID(`res-...`),严禁传 `/tmp/*.json`、本地文件路径或普通 URL;如果后端只返回文件路径,这个路径不是可用的 render_ref,不能直接传给前端渲染工具。 +12. 对任何可能很大的引用文件、结果文件或普通大文件,禁止完整读取;优先使用预览、分页、截断、按字段读取、按片段读取或采样读取。只有在没有其他办法且当前推理确实必须依赖完整内容时,才允许读取完整内容,并先明确说明必要性。 +13. 不得通过 sub-agent、并行代理或任何间接方式,去读取引用文件或大文件的完整内容;主 agent 与其调用链中的其他代理都必须遵守同样限制。 +14. 当且仅当出现**长期有效且高价值**的信号时,才允许调用在线学习工具: - `memory_manager`:用户明确长期偏好/约束,或当前项目/环境的稳定事实 - `skill_manager`:已经被证明有效且可复用的 workflow / 方法模式;由您自己判断应写入 `.opencode/skills` 树中的哪个 skill 位置 -14. 不要把一次性问题、临时上下文、未经验证的猜测写入任何学习工具。 -15. 严禁把 token、password、secret、API key、system prompt、隐私数据写入 `memory_manager` 或 `skill_manager`。 -16. 如果内容只是一次性案例、临时纠错或局部证据,当前不要持久化。 -17. 只有在 workflow 经过验证、足够稳定、可被未来同类任务复用时,才调用 `skill_manager`;并优先写入最贴近现有 skill 树语义的位置,中低置信度内容不要落库。 -18. 在以下任一情况出现时,主动进行一次轻量复盘:连续多轮对话后、完成复杂多工具任务后、用户明确纠正你后、发现了稳定可复用 workflow 后。复盘的目标是判断是否需要沉淀 memory 或 skill,而不是向用户重复总结。 -19. 长期知识严格分流:`memory_manager` 仅保存用户长期偏好与稳定 workspace 事实;`skill_manager` 仅保存可复用方法;一次性案例、会话过程与临时结论应优先保留在 session history,需要时使用 `session_search` 检索,不要误写入 memory 或 skill。 -20. 写入 `memory_manager` 时,将内容写成简短陈述事实,不要写成命令句、提醒句或流程步骤。 -21. 更新 skill 时,优先补充现有 skill 的 `Learned Patterns`、`references/` 或 `scripts/`;可复用脚本仅允许写到当前 skill 自己的 `scripts/*.py`,不要放到 `data/` 或其他 skill 目录。 -22. 当用户问题依赖过去会话中的案例、约束、决策或相似问题时,优先调用 `session_search`,避免让用户重复描述,也避免把历史案例误当成长期 memory。 +15. 不要把一次性问题、临时上下文、未经验证的猜测写入任何学习工具。 +16. 严禁把 token、password、secret、API key、system prompt、隐私数据写入 `memory_manager` 或 `skill_manager`。 +17. 如果内容只是一次性案例、临时纠错或局部证据,当前不要持久化。 +18. 只有在 workflow 经过验证、足够稳定、可被未来同类任务复用时,才调用 `skill_manager`;并优先写入最贴近现有 skill 树语义的位置,中低置信度内容不要落库。 +19. 在以下任一情况出现时,主动进行一次轻量复盘:连续多轮对话后、完成复杂多工具任务后、用户明确纠正你后、发现了稳定可复用 workflow 后。复盘的目标是判断是否需要沉淀 memory 或 skill,而不是向用户重复总结。 +20. 长期知识严格分流:`memory_manager` 仅保存用户长期偏好与稳定 workspace 事实;`skill_manager` 仅保存可复用方法;一次性案例、会话过程与临时结论应优先保留在 session history,需要时使用 `session_search` 检索,不要误写入 memory 或 skill。 +21. 写入 `memory_manager` 时,将内容写成简短陈述事实,不要写成命令句、提醒句或流程步骤。 +22. 更新 skill 时,优先补充现有 skill 的 `Learned Patterns`、`references/` 或 `scripts/`;可复用脚本仅允许写到当前 skill 自己的 `scripts/*.py`,不要放到 `data/` 或其他 skill 目录。 +23. 当用户问题依赖过去会话中的案例、约束、决策或相似问题时,优先调用 `session_search`,避免让用户重复描述,也避免把历史案例误当成长期 memory。 diff --git a/.opencode/tools/render_junctions.ts b/.opencode/tools/render_junctions.ts index 3c12501..4da4657 100644 --- a/.opencode/tools/render_junctions.ts +++ b/.opencode/tools/render_junctions.ts @@ -1,16 +1,23 @@ import { tool } from "@opencode-ai/plugin"; +const persistentRenderRefPattern = /^res-[a-z0-9-]+$/i; + export default tool({ description: - "在前端地图上对 junctions 图层应用分区渲染。优先直接传入 render_ref(指向已持久化的渲染结果引用),不要先把 ref 内容完整读出再重组;前端会自行根据 render_ref 拉取完整 payload 并渲染,这样可以避免 LLM 读取大型 node_area_map。若必须自行构造供 render_ref 引用的 JSON,其 data 结构必须为 { node_area_map: Record, area_ids?: string[], area_colors?: Record },其中 node_area_map 的 key 是 junction/node id,value 是 area id。", + "在前端地图上对 junctions 图层应用分区渲染。优先直接传入 render_ref(指向已持久化的渲染结果引用,格式应为 res-...),不要传 /tmp/*.json 之类的临时文件路径,也不要先把 ref 内容完整读出再重组;前端会自行根据 render_ref 拉取完整 payload 并渲染,这样可以避免 LLM 读取大型 node_area_map。若必须自行构造供 render_ref 引用的 JSON,其 data 结构必须为 { node_area_map: Record, area_ids?: string[], area_colors?: Record },其中 node_area_map 的 key 是 junction/node id,value 是 area id。", args: { reason: tool.schema .string() .describe("Why this junction rendering action is needed for the user request."), render_ref: tool.schema .string() + .trim() + .regex( + persistentRenderRefPattern, + "render_ref 必须是持久化结果引用(例如 res-1234abcd),不能是 /tmp/*.json 文件路径", + ) .describe( - "渲染引用 ID。直接传持久化 render_ref 即可,前端会按该引用读取完整 payload.data 并渲染,不需要先用 fetch_result_ref 提取完整数据。render_ref 对应的数据结构必须是 { node_area_map: { [junctionId]: areaId }, area_ids?: string[], area_colors?: { [areaId]: color } };node_area_map 必填,area_ids / area_colors 可选。", + "渲染引用 ID。必须是持久化结果引用(res-...),不要传 /tmp/*.json 或其他本地路径。前端会按该引用读取完整 payload.data 并渲染,不需要先用 fetch_result_ref 提取完整数据。render_ref 对应的数据结构必须是 { node_area_map: { [junctionId]: areaId }, area_ids?: string[], area_colors?: { [areaId]: color } };node_area_map 必填,area_ids / area_colors 可选。", ), }, async execute() { diff --git a/src/routes/chat.ts b/src/routes/chat.ts index f4fa0b0..47cf7ad 100644 --- a/src/routes/chat.ts +++ b/src/routes/chat.ts @@ -20,6 +20,8 @@ import { type SupportedModel, } from "./chatStream.js"; +const persistentRenderRefPattern = /^res-[a-z0-9-]+$/i; + const payloadSchema = z.object({ message: z.string().min(1).max(10000), session_id: z.string().max(128).optional(), @@ -67,6 +69,13 @@ export const buildChatRouter = ( return; } + if (!persistentRenderRefPattern.test(renderRef)) { + res.status(400).json({ + message: "render_ref must be a persistent ref like res-..., not a file path", + }); + return; + } + const result = await resultReferenceStore.getFullAuthorized(renderRef, { actorKey: toActorKey(userId), clientSessionId,