import { homedir } from "node:os"; import { isAbsolute, join, relative, resolve } from "node:path"; const WINDOWS_DRIVE_RE = /^[a-zA-Z]:[\\/]/; const HOME_PATH_RE = /^~(?:[\\/]|$)/; export function resolveWorkspacePath(cwd: string, inputPath: string): string { const normalized = normalizeExternalPath(inputPath); return isAbsolute(normalized) || WINDOWS_DRIVE_RE.test(normalized) ? resolve(normalized) : resolve(cwd, normalized); } export function isExternalAbsolutePath(inputPath: string): boolean { const normalized = normalizeExternalPath(inputPath); return HOME_PATH_RE.test(inputPath.trim()) || isAbsolute(normalized) || WINDOWS_DRIVE_RE.test(normalized); } export function isPathInsideWorkspace(cwd: string, inputPath: string): boolean { const workspace = resolve(cwd); const target = resolveWorkspacePath(cwd, inputPath); const rel = relative(workspace, target); return rel === "" || (!rel.startsWith("..") && !isAbsolute(rel)); } export function workspacePathBlockReason(cwd: string, inputPath: string, action = "访问"): string | undefined { if (isPathInsideWorkspace(cwd, inputPath)) return undefined; return `拒绝${action}工作区外路径 ${inputPath}。执行指令:改用当前业务项目内的相对路径;确需处理外部文件时,先复制到当前工作区并确认不会泄露凭证、Token、连接串或个人文件。`; } export function normalizeExternalPath(inputPath: string): string { let value = inputPath.trim(); if (HOME_PATH_RE.test(value)) { const rest = value.slice(1).replace(/^[\\/]/, ""); value = rest ? join(homedir(), rest) : homedir(); } if (value.startsWith("file://")) { try { value = new URL(value).pathname; } catch { // Keep original value when it is not a valid file URL. } } if (process.platform === "win32") { const wsl = value.match(/^\/mnt\/([a-zA-Z])\/(.*)$/); if (wsl) return `${wsl[1].toUpperCase()}:\\${wsl[2].replace(/\//g, "\\")}`; const msys = value.match(/^\/([a-zA-Z])\/(.*)$/); if (msys) return `${msys[1].toUpperCase()}:\\${msys[2].replace(/\//g, "\\")}`; } return value; } export function hasUnixDrivePath(inputPath: string): boolean { return /^\/mnt\/[a-zA-Z]\//.test(inputPath.trim()) || /^\/[a-zA-Z]\//.test(inputPath.trim()); } export function windowsPathHint(inputPath: string): string | undefined { if (process.platform !== "win32" || !hasUnixDrivePath(inputPath)) return undefined; return normalizeExternalPath(inputPath); } const SOURCE_EXTENSIONS = new Set([ ".java", ".kt", ".kts", ".xml", ".properties", ".yml", ".yaml", ".sql", ".ksql", ".cs", ".py", ]); /** * 判断路径是否指向源码文件(Java/Kotlin/XML/SQL/C#/Python 等)。 * * 迁移说明:此函数原位于 harness/path-policy.ts,随 V2 重构迁移至 platform/path.ts, * 作为平台级路径工具的一部分,供 tool-gateway 和 delegation 模块共同使用。 */ export function isSourceLikePath(path: string): boolean { const normalized = normalizeRelativePath(path); const lastSegment = normalized.split("/").at(-1) ?? normalized; const dotIndex = lastSegment.lastIndexOf("."); if (dotIndex < 0) return false; return SOURCE_EXTENSIONS.has(lastSegment.slice(dotIndex).toLowerCase()); } function normalizeRelativePath(path: string): string { return path.replace(/\\/g, "/").replace(/^\.\//, "").replace(/^\/+/, ""); }