/** * 旧工具网关 —— 同步门禁决策,拦截工具调用并判断是否允许执行。 * * 保留原因:当 KCODE_GATE_RUNNER_V2 未启用时,kingdee-harness-tool-events.ts * 仍通过 evaluateToolGateway() 走此旧路径。V2 启用后,门禁逻辑由 * GateRunner + PolicyRegistry 异步处理。 * * 源码写入阻断、旗舰路径、TDD 红绿等规则已全部迁移至 V2 对应策略。 */ import type { ActiveRun } from "./types.ts"; import { subagentToolCallBlockReason, type DelegationRole } from "./delegation.ts"; import { isSourceLikePath, isExternalAbsolutePath, isPathInsideWorkspace, windowsPathHint, workspacePathBlockReason } from "../platform/path.ts"; export type ToolGatewayBlockKind = | "external-read" | "external-write" | "windows-path" | "document-reader" | "subagent-boundary" | "source-write" | "unregistered-write-tool"; export interface ToolGatewayInput { cwd: string; toolName: string; path?: string; run?: ActiveRun; subagentRole?: DelegationRole; } export interface ToolGatewayPass { allowed: true; } export interface ToolGatewayBlock { allowed: false; kind: ToolGatewayBlockKind; level: "info" | "warning"; reason: string; } export type ToolGatewayDecision = ToolGatewayPass | ToolGatewayBlock; const WRITE_LIKE_TOOLS = new Set(["write", "edit"]); const READ_LIKE_TOOLS = new Set(["read", "kd_doc_read", "kd_anchor_read"]); const POTENTIAL_WRITE_TOOL_PATTERN = /(^|[_-])(write|edit|patch|apply|replace|delete|remove|move|copy|rename|create|save)([_-]|$)|notebook[_-]?edit/i; export function isWriteLikeToolCall(toolName: string): boolean { return WRITE_LIKE_TOOLS.has(toolName); } export function evaluateToolGateway(input: ToolGatewayInput): ToolGatewayDecision { const unregisteredWriteReason = unregisteredWriteToolBlockReason(input.toolName, input.path); if (unregisteredWriteReason) { return { allowed: false, kind: "unregistered-write-tool", level: "warning", reason: unregisteredWriteReason }; } const externalWriteReason = externalWriteBlockReason(input.cwd, input.toolName, input.path); if (externalWriteReason) { return { allowed: false, kind: "external-write", level: "warning", reason: externalWriteReason }; } const externalReadReason = externalReadBlockReason(input.cwd, input.toolName, input.path); if (externalReadReason) { return { allowed: false, kind: "external-read", level: "warning", reason: externalReadReason }; } const pathHint = input.path ? windowsPathHint(input.path) : undefined; if (pathHint && (input.toolName === "read" || isWriteLikeToolCall(input.toolName))) { return { allowed: false, kind: "windows-path", level: "warning", reason: `当前是 Windows 工作区,路径 ${input.path} 不是本项目使用的路径形式。执行指令:改用项目相对路径;必须使用绝对路径时,使用 Windows 路径 ${pathHint}。`, }; } const documentReason = documentReadBlockReason(input.toolName, input.path); if (documentReason) { return { allowed: false, kind: "document-reader", level: "info", reason: documentReason }; } if (input.subagentRole) { const reason = subagentToolCallBlockReason({ role: input.subagentRole, toolName: input.toolName, path: input.path, cwd: input.cwd, run: input.run, sourceLike: input.path ? isSourceLikePath(input.path) : false, sourceWriteBlockReason: undefined, // V2 GateRunner 已接管 }); if (reason) { return { allowed: false, kind: "subagent-boundary", level: "warning", reason }; } } // V2 GateRunner 已接管源码写入阻断 return { allowed: true }; } function isReadLikeToolCall(toolName: string): boolean { return READ_LIKE_TOOLS.has(toolName); } function unregisteredWriteToolBlockReason(toolName: string, path: string | undefined): string | undefined { if (!path || isWriteLikeToolCall(toolName)) return undefined; if (!POTENTIAL_WRITE_TOOL_PATTERN.test(toolName)) return undefined; return `拒绝未登记写类工具 ${toolName} 写入 ${path}。执行指令:新增写类工具必须先接入 isWriteLikeToolCall、Write Transaction、Source Anchor 和后置条件校验;未接入前禁止执行该工具。`; } function externalWriteBlockReason(cwd: string, toolName: string, path: string | undefined): string | undefined { if (!path || !isWriteLikeToolCall(toolName)) return undefined; if (!isExternalAbsolutePath(path) || isPathInsideWorkspace(cwd, path)) return undefined; return `拒绝写入工作区外路径 ${path}。执行指令:先确认当前业务项目根目录;生产源码必须使用当前项目相对路径,并受 PLAN.md 批准文件、execute 阶段和证据门禁约束。禁止直接写入 C:/Users、~\\Desktop、Desktop 或其他非当前工作区路径。`; } function externalReadBlockReason(cwd: string, toolName: string, path: string | undefined): string | undefined { if (!path || !isReadLikeToolCall(toolName)) return undefined; return workspacePathBlockReason(cwd, path, "读取"); } function documentReadBlockReason(toolName: string, path: string | undefined): string | undefined { if (toolName !== "read" || !path) return undefined; const ext = path.toLowerCase().split(".").pop(); if (ext !== "pdf" && ext !== "docx" && ext !== "doc" && ext !== "xlsx" && ext !== "xls" && ext !== "csv") return undefined; return `read 工具无法解析 .${ext} 二进制格式。执行指令:改用 kd_doc_read 工具读取此文件,参数 path="${path}"。`; }