@zqagent/core 0.1.30 → 0.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CONTRIBUTING.md +65 -0
- package/LICENSE +190 -0
- package/README.md +102 -146
- package/README.oss.en.md +161 -0
- package/README.oss.md +165 -0
- package/README.oss.zh-TW.md +165 -0
- package/SECURITY.md +51 -0
- package/dist/{ToolRegistry-BmvPxgMr.d.cts → ToolRegistry-C3pTFiaJ.d.cts} +13 -4
- package/dist/{ToolRegistry-BmvPxgMr.d.ts → ToolRegistry-C3pTFiaJ.d.ts} +13 -4
- package/dist/cli/index.cjs +17799 -2062
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.js +16729 -985
- package/dist/cli/index.js.map +1 -1
- package/dist/{explore-OI6htHN1.d.cts → explore-D4lCKXbA.d.ts} +160 -19
- package/dist/{explore-Pmhyk4gv.d.ts → explore-Dj1j_GyN.d.cts} +160 -19
- package/dist/index.cjs +3626 -453
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +337 -27
- package/dist/index.d.ts +337 -27
- package/dist/index.js +3617 -452
- package/dist/index.js.map +1 -1
- package/dist/server/index.cjs +14029 -1482
- package/dist/server/index.cjs.map +1 -1
- package/dist/server/index.js +14036 -1482
- package/dist/server/index.js.map +1 -1
- package/dist/tools/ask.cjs +14 -0
- package/dist/tools/ask.cjs.map +1 -1
- package/dist/tools/ask.d.cts +8 -2
- package/dist/tools/ask.d.ts +8 -2
- package/dist/tools/ask.js +13 -0
- package/dist/tools/ask.js.map +1 -1
- package/dist/tools/diff.cjs +59 -30
- package/dist/tools/diff.cjs.map +1 -1
- package/dist/tools/diff.d.cts +1 -1
- package/dist/tools/diff.d.ts +1 -1
- package/dist/tools/diff.js +59 -30
- package/dist/tools/diff.js.map +1 -1
- package/dist/tools/directories.d.cts +1 -1
- package/dist/tools/directories.d.ts +1 -1
- package/dist/tools/explore.cjs +1050 -309
- package/dist/tools/explore.cjs.map +1 -1
- package/dist/tools/explore.d.cts +2 -2
- package/dist/tools/explore.d.ts +2 -2
- package/dist/tools/explore.js +1057 -309
- package/dist/tools/explore.js.map +1 -1
- package/dist/tools/files.cjs +58 -43
- package/dist/tools/files.cjs.map +1 -1
- package/dist/tools/files.d.cts +10 -4
- package/dist/tools/files.d.ts +10 -4
- package/dist/tools/files.js +56 -42
- package/dist/tools/files.js.map +1 -1
- package/dist/tools/git.cjs +816 -63
- package/dist/tools/git.cjs.map +1 -1
- package/dist/tools/git.d.cts +200 -2
- package/dist/tools/git.d.ts +200 -2
- package/dist/tools/git.js +788 -62
- package/dist/tools/git.js.map +1 -1
- package/dist/tools/media.cjs +1191 -0
- package/dist/tools/media.cjs.map +1 -0
- package/dist/tools/media.d.cts +31 -0
- package/dist/tools/media.d.ts +31 -0
- package/dist/tools/media.js +1159 -0
- package/dist/tools/media.js.map +1 -0
- package/dist/tools/plan.d.cts +1 -1
- package/dist/tools/plan.d.ts +1 -1
- package/dist/tools/search.cjs +513 -33
- package/dist/tools/search.cjs.map +1 -1
- package/dist/tools/search.d.cts +12 -1
- package/dist/tools/search.d.ts +12 -1
- package/dist/tools/search.js +508 -33
- package/dist/tools/search.js.map +1 -1
- package/dist/tools/shell.cjs +631 -41
- package/dist/tools/shell.cjs.map +1 -1
- package/dist/tools/shell.d.cts +37 -4
- package/dist/tools/shell.d.ts +37 -4
- package/dist/tools/shell.js +626 -40
- package/dist/tools/shell.js.map +1 -1
- package/dist/tools/todo.d.cts +1 -1
- package/dist/tools/todo.d.ts +1 -1
- package/dist/tools/web.cjs +51 -18
- package/dist/tools/web.cjs.map +1 -1
- package/dist/tools/web.d.cts +1 -1
- package/dist/tools/web.d.ts +1 -1
- package/dist/tools/web.js +51 -18
- package/dist/tools/web.js.map +1 -1
- package/package.json +43 -8
package/dist/tools/files.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/tools/files.ts","../../src/tools/pathSafety.ts"],"sourcesContent":["/**\n * 文件系统工具\n *\n * 提供 read_file / write_file / list_files 三个文件工具的实现,\n * 并注册到 ToolRegistry。所有工具都做了路径范围检查(限制在\n * 工作区内,防止通过 ../ 逃逸)。实现为工厂函数(makeXxx),\n * 以便为每个引擎实例绑定各自的工作区根目录。\n */\nimport * as fs from \"fs\";\nimport * as path from \"path\";\nimport { ToolRegistry, RiskClass, type ToolMetadata } from \"./ToolRegistry.js\";\nimport { isWithinRoot } from \"./pathSafety.js\";\n\n/** read_file 默认最大读取行数 */\nconst DEFAULT_MAX_LINES = 2000;\n/** 单行最大字符数(超长行截断,防止输出爆炸) */\nconst MAX_LINE_CHARS = 500;\n\n/**\n * read_file — 行号标注版 (对齐 Python 版)\n *\n * 返回带行号的内容,格式 ' 12\\ttext',便于引用为 path:line;\n * 大文件通过 start_line / max_lines 窗口化阅读。\n */\nfunction makeReadFile(workspace: string) {\n const root = path.resolve(workspace);\n\n return function read_file(args: Record<string, unknown>) {\n const filePath = String(args.path ?? \"\");\n const startLine = Math.max(1, Number(args.start_line ?? 1));\n const maxLines = Math.min(Number(args.max_lines ?? DEFAULT_MAX_LINES), DEFAULT_MAX_LINES);\n\n // 路径范围检查 + 存在性/文件类型检查\n const target = path.resolve(root, filePath);\n if (!isWithinRoot(root, target)) {\n return { error: \"路径在工作区之外\" };\n }\n if (!fs.existsSync(target) || !fs.statSync(target).isFile()) {\n return { error: `不是文件: ${filePath}` };\n }\n\n // 按窗口截取行,超长行截断并标注\n const selected: string[] = [];\n let total = 0;\n try {\n const content = fs.readFileSync(target, \"utf-8\");\n const lines = content.split(\"\\n\");\n total = lines.length;\n for (let i = startLine - 1; i < Math.min(startLine - 1 + maxLines, lines.length); i++) {\n let text = lines[i];\n if (text.length > MAX_LINE_CHARS) {\n text = text.slice(0, MAX_LINE_CHARS) + \"… (line truncated)\";\n }\n selected.push(`${String(i + 1).padStart(6)}\\t${text}`);\n }\n } catch (exc) {\n return { error: `读取失败: ${String(exc)}` };\n }\n\n const end = startLine + selected.length - 1;\n const result: Record<string, unknown> = {\n path: path.relative(root, target),\n start_line: startLine,\n end_line: end,\n total_lines: total,\n content: selected.join(\"\\n\"),\n };\n // 文件未读完时提示继续阅读的入口\n if (end < total) {\n result.note = `显示第 ${startLine}-${end} 行,共 ${total} 行;调用 read_file(start_line=${end + 1}) 继续阅读`;\n }\n return result;\n };\n}\n\n/**\n * write_file — 路径范围检查 + UTF-8 写入\n *\n * 自动创建父目录;overwrite=false 时拒绝覆盖已存在文件。\n */\nfunction makeWriteFile(workspace: string) {\n const root = path.resolve(workspace);\n\n return function write_file(args: Record<string, unknown>) {\n const filePath = String(args.path ?? \"\");\n const content = String(args.content ?? \"\");\n const overwrite = args.overwrite !== false;\n\n // 路径范围 + 类型检查(不能写目录、默认不覆盖已有文件)\n const target = path.resolve(root, filePath);\n if (!isWithinRoot(root, target)) {\n return { error: \"路径在工作区之外\" };\n }\n if (fs.existsSync(target) && fs.statSync(target).isDirectory()) {\n return { error: \"路径是目录,不是文件\" };\n }\n if (fs.existsSync(target) && !overwrite) {\n return { error: \"文件已存在 (overwrite=false)\" };\n }\n\n try {\n fs.mkdirSync(path.dirname(target), { recursive: true });\n fs.writeFileSync(target, content, \"utf-8\");\n } catch (exc) {\n return { error: `写入失败: ${String(exc)}` };\n }\n\n return path.relative(root, target);\n };\n}\n\n/**\n * list_files — 列出目录内容\n *\n * 跳过隐藏文件(. 开头)及 node_modules / __pycache__ / dist 等\n * 无意义目录;目录项以 / 后缀标识;结果按名称排序。\n */\nfunction makeListFiles(workspace: string) {\n const root = path.resolve(workspace);\n\n return function list_files(args: Record<string, unknown>) {\n const dirPath = String(args.path ?? \".\");\n const maxResults = Math.min(Number(args.max_results ?? 100), 100);\n const target = path.resolve(root, dirPath);\n if (!isWithinRoot(root, target)) return { error: \"路径在工作区之外\" };\n if (!fs.existsSync(target) || !fs.statSync(target).isDirectory()) {\n return { error: `不是目录: ${dirPath}` };\n }\n\n try {\n const entries = fs.readdirSync(target, { withFileTypes: true });\n const results: string[] = [];\n for (const e of entries) {\n if (results.length >= maxResults) break;\n if (e.name.startsWith(\".\")) continue;\n if (e.name === \"node_modules\" || e.name === \"__pycache__\" || e.name === \"dist\") continue;\n results.push(e.isDirectory() ? `${e.name}/` : e.name);\n }\n results.sort();\n return { path: path.relative(root, target), entries: results.slice(0, maxResults) };\n } catch (exc) {\n return { error: `列出文件失败: ${String(exc)}` };\n }\n };\n}\n\n/** replace_in_file — 精确文本替换 (对齐 Python 版 aisuite) */\nfunction makeReplaceInFile(workspace: string) {\n const root = path.resolve(workspace);\n\n return function replace_in_file(args: Record<string, unknown>) {\n const filePath = String(args.path ?? \"\");\n const oldStr = String(args.old ?? \"\");\n const newStr = String(args.new ?? \"\");\n const expectedReplacements = Math.max(1, Number(args.expected_replacements ?? 1));\n\n if (!oldStr) return { error: \"old 不能为空\" };\n\n const target = path.resolve(root, filePath);\n if (!isWithinRoot(root, target)) return { error: \"路径在工作区之外\" };\n if (!fs.existsSync(target) || !fs.statSync(target).isFile()) {\n return { error: `不是文件: ${filePath}` };\n }\n\n try {\n const content = fs.readFileSync(target, \"utf-8\");\n const count = content.split(oldStr).length - 1;\n\n if (count !== expectedReplacements) {\n return { error: `期望 ${expectedReplacements} 处替换,实际找到 ${count} 处: ${filePath}` };\n }\n\n const updated = content.replaceAll(oldStr, newStr);\n fs.writeFileSync(target, updated, \"utf-8\");\n return {\n path: path.relative(root, target),\n replacements: count,\n chars_before: content.length,\n chars_after: updated.length,\n };\n } catch (exc) {\n return { error: `替换失败: ${String(exc)}` };\n }\n };\n}\n\n/**\n * 注册文件工具到 ToolRegistry\n *\n * read_file / list_files 为低风险(无需审批);write_file 为\n * 中风险且 requiresApproval=true(由权限引擎决定是否需要审批)。\n */\nexport function registerFileTools(registry: ToolRegistry, workspace: string): void {\n const readFile = makeReadFile(workspace);\n registry.register(\n (args) => readFile(args),\n { category: \"filesystem\", riskLevel: \"low\", capabilities: [\"read_file\"], requiresApproval: false },\n {\n type: \"function\",\n function: {\n name: \"read_file\",\n description: \"读取文本文件,返回带行号的内容 (格式: ' 12\\\\\\\\ttext'),以便引用为 path:line。大文件窗口化:传递 start_line 从上一次读取的位置继续。只读。\",\n parameters: {\n type: \"object\",\n properties: {\n path: { type: \"string\", description: \"文件路径 (相对于工作区)\" },\n start_line: { type: \"integer\", description: \"起始行号 (从1开始,默认1)\" },\n max_lines: { type: \"integer\", description: `最大行数 (默认 ${DEFAULT_MAX_LINES})` },\n },\n required: [\"path\"],\n },\n },\n },\n );\n\n const writeFile = makeWriteFile(workspace);\n registry.register(\n (args) => writeFile(args),\n { category: \"filesystem\", riskLevel: \"medium\", capabilities: [\"write_file\"], requiresApproval: true },\n {\n type: \"function\",\n function: {\n name: \"write_file\",\n description: \"写入一个 UTF-8 文本文件。用于创建新文件或完全重写现有文件。\",\n parameters: {\n type: \"object\",\n properties: {\n path: { type: \"string\", description: \"文件路径 (相对于工作区)\" },\n content: { type: \"string\", description: \"文件内容 (UTF-8)\" },\n overwrite: { type: \"boolean\", description: \"是否覆盖已存在的文件 (默认 true)\" },\n },\n required: [\"path\", \"content\"],\n },\n },\n },\n );\n\n const replaceFile = makeReplaceInFile(workspace);\n registry.register(\n (args) => replaceFile(args),\n { category: \"filesystem\", riskLevel: \"medium\", capabilities: [\"edit_file\"], requiresApproval: true },\n {\n type: \"function\",\n function: {\n name: \"replace_in_file\",\n description: \"在文件中精确替换一段文本。先按预期次数精确匹配 old 字符串,然后替换为 new。用于小范围精确编辑。\",\n parameters: {\n type: \"object\",\n properties: {\n path: { type: \"string\", description: \"文件路径 (相对于工作区)\" },\n old: { type: \"string\", description: \"要被替换的精确文本\" },\n new: { type: \"string\", description: \"替换后的新文本\" },\n expected_replacements: { type: \"integer\", description: \"期望的替换次数 (默认 1)\" },\n },\n required: [\"path\", \"old\", \"new\"],\n },\n },\n },\n );\n\n const listFiles = makeListFiles(workspace);\n registry.register(\n (args) => listFiles(args),\n { category: \"filesystem\", riskLevel: \"low\", capabilities: [\"list_files\"], requiresApproval: false },\n {\n type: \"function\",\n function: {\n name: \"list_files\",\n description: \"列出目录下的文件和子目录。跳过隐藏文件和 node_modules 等目录。\",\n parameters: {\n type: \"object\",\n properties: {\n path: { type: \"string\", description: \"目录路径 (相对于工作区,默认 '.')\" },\n max_results: { type: \"integer\", description: \"最大结果数 (默认 100)\" },\n },\n },\n },\n },\n );\n}\n","/**\n * 路径安全辅助函数\n *\n * 判断目标路径是否真正位于工作区根目录之内。\n *\n * 仅做字符串比较(path.relative)无法防御符号链接逃逸:\n * 工作区内若存在指向外部的 symlink,`workspace/link/secret.txt`\n * 的字符串前缀合法,但实际读写发生在链接目标(工作区外)。\n *\n * 因此这里用 fs.realpathSync 解析符号链接后再与根目录的 realpath\n * 比较。目标尚不存在(如 write_file 新建文件)时,向上回溯到最近\n * 已存在的祖先目录解析,再拼回剩余路径段。\n */\nimport * as fs from \"fs\";\nimport * as path from \"path\";\n\n/**\n * 判断 target(相对 workspace 的路径)是否安全地位于 workspace 内。\n *\n * - 解析符号链接:路径中任何 symlink 组件都会先 realpath 再校验;\n * broken symlink(存在但无法解析)一律拒绝。\n * - 目标不存在时(写新文件):回溯到最近存在的祖先做 realpath,\n * 再拼接剩余段,仍能捕获祖先链中的 symlink 逃逸。\n */\nexport function isWithinRoot(workspace: string, target: string): boolean {\n let rootReal: string;\n try {\n rootReal = fs.realpathSync(workspace);\n } catch {\n // 根目录不存在(如权限引擎尚未初始化工作区):\n // 没有实际目录结构即无 symlink 逃逸面,退化为纯字符串路径比较。\n const rootAbs = path.resolve(workspace);\n const abs = path.resolve(workspace, target);\n const rel = path.relative(rootAbs, abs);\n return !rel.startsWith(\"..\") && !path.isAbsolute(rel);\n }\n\n const abs = path.resolve(workspace, target);\n let real: string;\n try {\n real = fs.realpathSync(abs);\n } catch {\n // abs 不存在:向上回溯找最近存在的祖先\n let probe = abs;\n const tail: string[] = [];\n for (;;) {\n // 组件存在(含 broken symlink)但 realpath 失败 → 无法解析,拒绝\n const exists = fs.existsSync(probe) || fs.lstatSync(probe, { throwIfNoEntry: false }) !== undefined;\n if (exists) {\n try {\n real = path.join(fs.realpathSync(probe), ...tail);\n break;\n } catch {\n return false;\n }\n }\n tail.unshift(path.basename(probe));\n const parent = path.dirname(probe);\n if (parent === probe) return false; // 已到文件系统根\n probe = parent;\n }\n }\n\n const rel = path.relative(rootReal, real);\n return !rel.startsWith(\"..\") && !path.isAbsolute(rel);\n}\n"],"mappings":"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","names":["fs","path","abs","rel"]}
|
|
1
|
+
{"version":3,"sources":["../../src/tools/files.ts","../../src/tools/pathSafety.ts"],"sourcesContent":["/**\n * 文件系统工具\n *\n * 提供 read_file / write_file / list_files 三个文件工具的实现,\n * 并注册到 ToolRegistry。所有工具都做了路径范围检查(限制在\n * 工作区内,防止通过 ../ 逃逸)。实现为工厂函数(makeXxx),\n * 以便为每个引擎实例绑定各自的工作区根目录。\n */\nimport * as fs from \"fs\";\nimport * as path from \"path\";\nimport { ToolRegistry, RiskClass, type ToolMetadata } from \"./ToolRegistry.js\";\nimport { resolveWithinRoot } from \"./pathSafety.js\";\n\n/** read_file 默认最大读取行数 */\nconst DEFAULT_MAX_LINES = 2000;\n/** 单行最大字符数(超长行截断,防止输出爆炸) */\nconst MAX_LINE_CHARS = 500;\n\n/**\n * read_file — 行号标注版 (对齐 Python 版)\n *\n * 返回带行号的内容,格式 ' 12\\ttext',便于引用为 path:line;\n * 大文件通过 start_line / max_lines 窗口化阅读。\n */\nfunction makeReadFile(workspace: string) {\n const root = path.resolve(workspace);\n\n return function read_file(args: Record<string, unknown>) {\n const filePath = String(args.path ?? \"\");\n const startLine = Math.max(1, Number(args.start_line ?? 1));\n const maxLines = Math.min(Number(args.max_lines ?? DEFAULT_MAX_LINES), DEFAULT_MAX_LINES);\n\n // 路径范围检查 + 存在性/文件类型检查。\n // 用解析后的真实路径做后续 fs 操作,避免校验后 symlink 组件被替换。\n const resolved = resolveWithinRoot(root, filePath);\n if (!resolved) {\n return { error: \"路径在工作区之外\" };\n }\n const target = resolved.real;\n if (!fs.existsSync(target) || !fs.statSync(target).isFile()) {\n return { error: `不是文件: ${filePath}` };\n }\n\n // 按窗口截取行,超长行截断并标注\n const selected: string[] = [];\n let total = 0;\n try {\n const content = fs.readFileSync(target, \"utf-8\");\n const lines = content.split(\"\\n\");\n total = lines.length;\n for (let i = startLine - 1; i < Math.min(startLine - 1 + maxLines, lines.length); i++) {\n let text = lines[i];\n if (text.length > MAX_LINE_CHARS) {\n text = text.slice(0, MAX_LINE_CHARS) + \"… (line truncated)\";\n }\n selected.push(`${String(i + 1).padStart(6)}\\t${text}`);\n }\n } catch (exc) {\n return { error: `读取失败: ${String(exc)}` };\n }\n\n const end = startLine + selected.length - 1;\n const result: Record<string, unknown> = {\n path: path.relative(resolved.root, target),\n start_line: startLine,\n end_line: end,\n total_lines: total,\n content: selected.join(\"\\n\"),\n };\n // 文件未读完时提示继续阅读的入口\n if (end < total) {\n result.note = `显示第 ${startLine}-${end} 行,共 ${total} 行;调用 read_file(start_line=${end + 1}) 继续阅读`;\n }\n return result;\n };\n}\n\n/**\n * write_file — 路径范围检查 + UTF-8 写入\n *\n * 自动创建父目录;overwrite=false 时拒绝覆盖已存在文件。\n */\nfunction makeWriteFile(workspace: string) {\n const root = path.resolve(workspace);\n\n return function write_file(args: Record<string, unknown>) {\n const filePath = String(args.path ?? \"\");\n const content = String(args.content ?? \"\");\n const overwrite = args.overwrite !== false;\n\n // 路径范围 + 类型检查(不能写目录、默认不覆盖已有文件)\n const resolved = resolveWithinRoot(root, filePath);\n if (!resolved) {\n return { error: \"路径在工作区之外\" };\n }\n const target = resolved.real;\n if (fs.existsSync(target) && fs.statSync(target).isDirectory()) {\n return { error: \"路径是目录,不是文件\" };\n }\n if (fs.existsSync(target) && !overwrite) {\n return { error: \"文件已存在 (overwrite=false)\" };\n }\n\n try {\n fs.mkdirSync(path.dirname(target), { recursive: true });\n fs.writeFileSync(target, content, \"utf-8\");\n } catch (exc) {\n return { error: `写入失败: ${String(exc)}` };\n }\n\n return path.relative(resolved.root, target);\n };\n}\n\n/**\n * list_files — 列出目录内容\n *\n * 跳过隐藏文件(. 开头)及 node_modules / __pycache__ / dist 等\n * 无意义目录;目录项以 / 后缀标识;结果按名称排序。\n */\nfunction makeListFiles(workspace: string) {\n const root = path.resolve(workspace);\n\n return function list_files(args: Record<string, unknown>) {\n const dirPath = String(args.path ?? \".\");\n const maxResults = Math.min(Number(args.max_results ?? 100), 100);\n const resolved = resolveWithinRoot(root, dirPath);\n if (!resolved) return { error: \"路径在工作区之外\" };\n const target = resolved.real;\n if (!fs.existsSync(target) || !fs.statSync(target).isDirectory()) {\n return { error: `不是目录: ${dirPath}` };\n }\n\n try {\n const entries = fs.readdirSync(target, { withFileTypes: true });\n const results: string[] = [];\n for (const e of entries) {\n if (results.length >= maxResults) break;\n if (e.name.startsWith(\".\")) continue;\n if (e.name === \"node_modules\" || e.name === \"__pycache__\" || e.name === \"dist\") continue;\n results.push(e.isDirectory() ? `${e.name}/` : e.name);\n }\n results.sort();\n return { path: path.relative(resolved.root, target), entries: results.slice(0, maxResults) };\n } catch (exc) {\n return { error: `列出文件失败: ${String(exc)}` };\n }\n };\n}\n\n/** replace_in_file — 精确文本替换 (对齐 Python 版 aisuite) */\nfunction makeReplaceInFile(workspace: string) {\n const root = path.resolve(workspace);\n\n return function replace_in_file(args: Record<string, unknown>) {\n const filePath = String(args.path ?? \"\");\n const oldStr = String(args.old ?? \"\");\n const newStr = String(args.new ?? \"\");\n const expectedReplacements = Math.max(1, Number(args.expected_replacements ?? 1));\n\n if (!oldStr) return { error: \"old 不能为空\" };\n\n const resolved = resolveWithinRoot(root, filePath);\n if (!resolved) return { error: \"路径在工作区之外\" };\n const target = resolved.real;\n if (!fs.existsSync(target) || !fs.statSync(target).isFile()) {\n return { error: `不是文件: ${filePath}` };\n }\n\n try {\n const content = fs.readFileSync(target, \"utf-8\");\n const count = content.split(oldStr).length - 1;\n\n if (count !== expectedReplacements) {\n return { error: `期望 ${expectedReplacements} 处替换,实际找到 ${count} 处: ${filePath}` };\n }\n\n const updated = content.replaceAll(oldStr, newStr);\n fs.writeFileSync(target, updated, \"utf-8\");\n return {\n path: path.relative(resolved.root, target),\n replacements: count,\n chars_before: content.length,\n chars_after: updated.length,\n };\n } catch (exc) {\n return { error: `替换失败: ${String(exc)}` };\n }\n };\n}\n\n/**\n * 注册只读文件工具(read_file / list_files)。\n *\n * 供 explore 子代理等「只读」场景使用,使声明的 schema 与实际能力一致。\n */\nexport function registerReadOnlyFileTools(registry: ToolRegistry, workspace: string): void {\n const readFile = makeReadFile(workspace);\n registry.register(\n (args) => readFile(args),\n { category: \"filesystem\", riskLevel: \"low\", capabilities: [\"read_file\"], requiresApproval: false },\n {\n type: \"function\",\n function: {\n name: \"read_file\",\n description: \"读取文本文件,返回带行号的内容 (格式: ' 12\\\\\\\\ttext'),以便引用为 path:line。大文件窗口化:传递 start_line 从上一次读取的位置继续。只读。\",\n parameters: {\n type: \"object\",\n properties: {\n path: { type: \"string\", description: \"文件路径 (相对于工作区)\" },\n start_line: { type: \"integer\", description: \"起始行号 (从1开始,默认1)\" },\n max_lines: { type: \"integer\", description: `最大行数 (默认 ${DEFAULT_MAX_LINES})` },\n },\n required: [\"path\"],\n },\n },\n },\n );\n\n const listFiles = makeListFiles(workspace);\n registry.register(\n (args) => listFiles(args),\n { category: \"filesystem\", riskLevel: \"low\", capabilities: [\"list_files\"], requiresApproval: false },\n {\n type: \"function\",\n function: {\n name: \"list_files\",\n description: \"列出目录下的文件和子目录。跳过隐藏文件和 node_modules 等目录。\",\n parameters: {\n type: \"object\",\n properties: {\n path: { type: \"string\", description: \"目录路径 (相对于工作区,默认 '.')\" },\n max_results: { type: \"integer\", description: \"最大结果数 (默认 100)\" },\n },\n },\n },\n },\n );\n}\n\n/**\n * 注册文件工具到 ToolRegistry\n *\n * read_file / list_files 为低风险(无需审批);write_file / replace_in_file\n * 为中风险且 requiresApproval=true(由权限引擎决定是否需要审批)。\n */\nexport function registerFileTools(registry: ToolRegistry, workspace: string): void {\n registerReadOnlyFileTools(registry, workspace);\n\n const writeFile = makeWriteFile(workspace);\n registry.register(\n (args) => writeFile(args),\n { category: \"filesystem\", riskLevel: \"medium\", capabilities: [\"write_file\"], requiresApproval: true },\n {\n type: \"function\",\n function: {\n name: \"write_file\",\n description: \"写入一个 UTF-8 文本文件。用于创建新文件或完全重写现有文件。\",\n parameters: {\n type: \"object\",\n properties: {\n path: { type: \"string\", description: \"文件路径 (相对于工作区)\" },\n content: { type: \"string\", description: \"文件内容 (UTF-8)\" },\n overwrite: { type: \"boolean\", description: \"是否覆盖已存在的文件 (默认 true)\" },\n },\n required: [\"path\", \"content\"],\n },\n },\n },\n );\n\n const replaceFile = makeReplaceInFile(workspace);\n registry.register(\n (args) => replaceFile(args),\n { category: \"filesystem\", riskLevel: \"medium\", capabilities: [\"edit_file\"], requiresApproval: true },\n {\n type: \"function\",\n function: {\n name: \"replace_in_file\",\n description: \"在文件中精确替换一段文本。先按预期次数精确匹配 old 字符串,然后替换为 new。用于小范围精确编辑。\",\n parameters: {\n type: \"object\",\n properties: {\n path: { type: \"string\", description: \"文件路径 (相对于工作区)\" },\n old: { type: \"string\", description: \"要被替换的精确文本\" },\n new: { type: \"string\", description: \"替换后的新文本\" },\n expected_replacements: { type: \"integer\", description: \"期望的替换次数 (默认 1)\" },\n },\n required: [\"path\", \"old\", \"new\"],\n },\n },\n },\n );\n}\n","/**\n * 路径安全辅助函数\n *\n * 判断目标路径是否真正位于工作区根目录之内。\n *\n * 仅做字符串比较(path.relative)无法防御符号链接逃逸:\n * 工作区内若存在指向外部的 symlink,`workspace/link/secret.txt`\n * 的字符串前缀合法,但实际读写发生在链接目标(工作区外)。\n *\n * 因此这里用 fs.realpathSync 解析符号链接后再与根目录的 realpath\n * 比较。目标尚不存在(如 write_file 新建文件)时,向上回溯到最近\n * 已存在的祖先目录解析,再拼回剩余路径段。\n *\n * 校验通过时把解析后的真实路径一并返回:调用方必须用它(而非原始\n * 相对路径)做实际 fs 操作,否则「校验后、操作前」路径组件被替换为\n * 指向外部的 symlink 仍会逃逸(TOCTOU)。\n */\nimport * as fs from \"fs\";\nimport * as path from \"path\";\n\n/**\n * 判断 `path.relative(root, target)` 的结果是否落在 root 之内。\n *\n * 逃逸必然恰好是 `..` 或以 `..` + 分隔符开头,不能用 `startsWith(\"..\")`:\n * 工作区里名为 `..foo` 的合法条目会被误判为逃逸而拒绝。\n * 跨盘符时(Windows)path.relative 返回绝对路径,一并拒绝。\n */\nfunction isInsideRelative(rel: string): boolean {\n return (\n !path.isAbsolute(rel) && rel !== \"..\" && !rel.startsWith(`..${path.sep}`)\n );\n}\n\n/** 是否为文件系统根(\"/\"、\"C:\\\\\") */\nfunction isFilesystemRoot(p: string): boolean {\n return path.parse(p).root === p;\n}\n\n/** 校验通过的路径:root 为工作区根的真实路径,real 为目标的真实路径 */\nexport interface ResolvedPath {\n root: string;\n real: string;\n}\n\n/**\n * 校验 target(相对 workspace 的路径)是否位于 workspace 内,并返回\n * 解析符号链接后的真实路径;不在工作区内时返回 null。\n *\n * - 解析符号链接:路径中任何 symlink 组件都会先 realpath 再校验;\n * broken symlink(存在但无法解析)一律拒绝。\n * - 目标不存在时(写新文件):回溯到最近存在的祖先做 realpath,\n * 再拼接剩余段,仍能捕获祖先链中的 symlink 逃逸。\n * - 工作区根若就是文件系统根(`WORKSPACE=/`):此时任何绝对路径都在\n * 其内,等同没有沙箱,直接判为越界。\n */\nexport function resolveWithinRoot(\n workspace: string,\n target: string,\n): ResolvedPath | null {\n const abs = path.resolve(workspace, target);\n\n let rootReal: string;\n try {\n rootReal = fs.realpathSync(workspace);\n } catch {\n // 根目录不存在(如权限引擎尚未初始化工作区):\n // 没有实际目录结构即无 symlink 逃逸面,退化为纯字符串路径比较。\n const rootAbs = path.resolve(workspace);\n if (!isInsideRelative(path.relative(rootAbs, abs))) return null;\n return { root: rootAbs, real: abs };\n }\n if (isFilesystemRoot(rootReal)) return null;\n\n let real: string;\n try {\n real = fs.realpathSync(abs);\n } catch {\n // abs 不存在:向上回溯找最近存在的祖先\n let probe = abs;\n const tail: string[] = [];\n for (;;) {\n // 组件存在(含 broken symlink)但 realpath 失败 → 无法解析,拒绝\n const exists = fs.existsSync(probe) || fs.lstatSync(probe, { throwIfNoEntry: false }) !== undefined;\n if (exists) {\n try {\n real = path.join(fs.realpathSync(probe), ...tail);\n break;\n } catch {\n return null;\n }\n }\n tail.unshift(path.basename(probe));\n const parent = path.dirname(probe);\n if (parent === probe) return null; // 已到文件系统根\n probe = parent;\n }\n }\n\n if (!isInsideRelative(path.relative(rootReal, real))) return null;\n return { root: rootReal, real };\n}\n\n/**\n * 判断 target 是否安全地位于 workspace 内(不需要真实路径时用这个)。\n *\n * 注意:删除类操作不应据此改用 realpath 去 unlink —— 那会删掉符号链接\n * 指向的目标而非链接本身;这类操作请用 resolveEntryWithinRoot。\n */\nexport function isWithinRoot(workspace: string, target: string): boolean {\n return resolveWithinRoot(workspace, target) !== null;\n}\n\n/**\n * 解析「目录项本身」的路径:父目录取 realpath,最后一段保持原样。\n *\n * 供 unlink / rename 等作用于条目自身的操作用:父目录不可被 symlink\n * 替换,而最后一段若是符号链接,被操作的是链接本身,不会被跟随。\n */\nexport function resolveEntryWithinRoot(\n workspace: string,\n target: string,\n): ResolvedPath | null {\n const abs = path.resolve(workspace, target);\n const parent = resolveWithinRoot(workspace, path.dirname(abs));\n if (!parent) return null;\n return { root: parent.root, real: path.join(parent.real, path.basename(abs)) };\n}\n"],"mappings":";AAQA,YAAYA,SAAQ;AACpB,YAAYC,WAAU;;;ACQtB,YAAY,QAAQ;AACpB,YAAY,UAAU;AAStB,SAAS,iBAAiB,KAAsB;AAC9C,SACE,CAAM,gBAAW,GAAG,KAAK,QAAQ,QAAQ,CAAC,IAAI,WAAW,KAAU,QAAG,EAAE;AAE5E;AAGA,SAAS,iBAAiB,GAAoB;AAC5C,SAAY,WAAM,CAAC,EAAE,SAAS;AAChC;AAmBO,SAAS,kBACd,WACA,QACqB;AACrB,QAAM,MAAW,aAAQ,WAAW,MAAM;AAE1C,MAAI;AACJ,MAAI;AACF,eAAc,gBAAa,SAAS;AAAA,EACtC,QAAQ;AAGN,UAAM,UAAe,aAAQ,SAAS;AACtC,QAAI,CAAC,iBAAsB,cAAS,SAAS,GAAG,CAAC,EAAG,QAAO;AAC3D,WAAO,EAAE,MAAM,SAAS,MAAM,IAAI;AAAA,EACpC;AACA,MAAI,iBAAiB,QAAQ,EAAG,QAAO;AAEvC,MAAI;AACJ,MAAI;AACF,WAAU,gBAAa,GAAG;AAAA,EAC5B,QAAQ;AAEN,QAAI,QAAQ;AACZ,UAAM,OAAiB,CAAC;AACxB,eAAS;AAEP,YAAM,SAAY,cAAW,KAAK,KAAQ,aAAU,OAAO,EAAE,gBAAgB,MAAM,CAAC,MAAM;AAC1F,UAAI,QAAQ;AACV,YAAI;AACF,iBAAY,UAAQ,gBAAa,KAAK,GAAG,GAAG,IAAI;AAChD;AAAA,QACF,QAAQ;AACN,iBAAO;AAAA,QACT;AAAA,MACF;AACA,WAAK,QAAa,cAAS,KAAK,CAAC;AACjC,YAAM,SAAc,aAAQ,KAAK;AACjC,UAAI,WAAW,MAAO,QAAO;AAC7B,cAAQ;AAAA,IACV;AAAA,EACF;AAEA,MAAI,CAAC,iBAAsB,cAAS,UAAU,IAAI,CAAC,EAAG,QAAO;AAC7D,SAAO,EAAE,MAAM,UAAU,KAAK;AAChC;;;ADtFA,IAAM,oBAAoB;AAE1B,IAAM,iBAAiB;AAQvB,SAAS,aAAa,WAAmB;AACvC,QAAM,OAAY,cAAQ,SAAS;AAEnC,SAAO,SAAS,UAAU,MAA+B;AACvD,UAAM,WAAW,OAAO,KAAK,QAAQ,EAAE;AACvC,UAAM,YAAY,KAAK,IAAI,GAAG,OAAO,KAAK,cAAc,CAAC,CAAC;AAC1D,UAAM,WAAW,KAAK,IAAI,OAAO,KAAK,aAAa,iBAAiB,GAAG,iBAAiB;AAIxF,UAAM,WAAW,kBAAkB,MAAM,QAAQ;AACjD,QAAI,CAAC,UAAU;AACb,aAAO,EAAE,OAAO,mDAAW;AAAA,IAC7B;AACA,UAAM,SAAS,SAAS;AACxB,QAAI,CAAI,eAAW,MAAM,KAAK,CAAI,aAAS,MAAM,EAAE,OAAO,GAAG;AAC3D,aAAO,EAAE,OAAO,6BAAS,QAAQ,GAAG;AAAA,IACtC;AAGA,UAAM,WAAqB,CAAC;AAC5B,QAAI,QAAQ;AACZ,QAAI;AACF,YAAM,UAAa,iBAAa,QAAQ,OAAO;AAC/C,YAAM,QAAQ,QAAQ,MAAM,IAAI;AAChC,cAAQ,MAAM;AACd,eAAS,IAAI,YAAY,GAAG,IAAI,KAAK,IAAI,YAAY,IAAI,UAAU,MAAM,MAAM,GAAG,KAAK;AACrF,YAAI,OAAO,MAAM,CAAC;AAClB,YAAI,KAAK,SAAS,gBAAgB;AAChC,iBAAO,KAAK,MAAM,GAAG,cAAc,IAAI;AAAA,QACzC;AACA,iBAAS,KAAK,GAAG,OAAO,IAAI,CAAC,EAAE,SAAS,CAAC,CAAC,IAAK,IAAI,EAAE;AAAA,MACvD;AAAA,IACF,SAAS,KAAK;AACZ,aAAO,EAAE,OAAO,6BAAS,OAAO,GAAG,CAAC,GAAG;AAAA,IACzC;AAEA,UAAM,MAAM,YAAY,SAAS,SAAS;AAC1C,UAAM,SAAkC;AAAA,MACtC,MAAW,eAAS,SAAS,MAAM,MAAM;AAAA,MACzC,YAAY;AAAA,MACZ,UAAU;AAAA,MACV,aAAa;AAAA,MACb,SAAS,SAAS,KAAK,IAAI;AAAA,IAC7B;AAEA,QAAI,MAAM,OAAO;AACf,aAAO,OAAO,sBAAO,SAAS,IAAI,GAAG,uBAAQ,KAAK,kDAA8B,MAAM,CAAC;AAAA,IACzF;AACA,WAAO;AAAA,EACT;AACF;AAOA,SAAS,cAAc,WAAmB;AACxC,QAAM,OAAY,cAAQ,SAAS;AAEnC,SAAO,SAAS,WAAW,MAA+B;AACxD,UAAM,WAAW,OAAO,KAAK,QAAQ,EAAE;AACvC,UAAM,UAAU,OAAO,KAAK,WAAW,EAAE;AACzC,UAAM,YAAY,KAAK,cAAc;AAGrC,UAAM,WAAW,kBAAkB,MAAM,QAAQ;AACjD,QAAI,CAAC,UAAU;AACb,aAAO,EAAE,OAAO,mDAAW;AAAA,IAC7B;AACA,UAAM,SAAS,SAAS;AACxB,QAAO,eAAW,MAAM,KAAQ,aAAS,MAAM,EAAE,YAAY,GAAG;AAC9D,aAAO,EAAE,OAAO,+DAAa;AAAA,IAC/B;AACA,QAAO,eAAW,MAAM,KAAK,CAAC,WAAW;AACvC,aAAO,EAAE,OAAO,mDAA0B;AAAA,IAC5C;AAEA,QAAI;AACF,MAAG,cAAe,cAAQ,MAAM,GAAG,EAAE,WAAW,KAAK,CAAC;AACtD,MAAG,kBAAc,QAAQ,SAAS,OAAO;AAAA,IAC3C,SAAS,KAAK;AACZ,aAAO,EAAE,OAAO,6BAAS,OAAO,GAAG,CAAC,GAAG;AAAA,IACzC;AAEA,WAAY,eAAS,SAAS,MAAM,MAAM;AAAA,EAC5C;AACF;AAQA,SAAS,cAAc,WAAmB;AACxC,QAAM,OAAY,cAAQ,SAAS;AAEnC,SAAO,SAAS,WAAW,MAA+B;AACxD,UAAM,UAAU,OAAO,KAAK,QAAQ,GAAG;AACvC,UAAM,aAAa,KAAK,IAAI,OAAO,KAAK,eAAe,GAAG,GAAG,GAAG;AAChE,UAAM,WAAW,kBAAkB,MAAM,OAAO;AAChD,QAAI,CAAC,SAAU,QAAO,EAAE,OAAO,mDAAW;AAC1C,UAAM,SAAS,SAAS;AACxB,QAAI,CAAI,eAAW,MAAM,KAAK,CAAI,aAAS,MAAM,EAAE,YAAY,GAAG;AAChE,aAAO,EAAE,OAAO,6BAAS,OAAO,GAAG;AAAA,IACrC;AAEA,QAAI;AACF,YAAM,UAAa,gBAAY,QAAQ,EAAE,eAAe,KAAK,CAAC;AAC9D,YAAM,UAAoB,CAAC;AAC3B,iBAAW,KAAK,SAAS;AACvB,YAAI,QAAQ,UAAU,WAAY;AAClC,YAAI,EAAE,KAAK,WAAW,GAAG,EAAG;AAC5B,YAAI,EAAE,SAAS,kBAAkB,EAAE,SAAS,iBAAiB,EAAE,SAAS,OAAQ;AAChF,gBAAQ,KAAK,EAAE,YAAY,IAAI,GAAG,EAAE,IAAI,MAAM,EAAE,IAAI;AAAA,MACtD;AACA,cAAQ,KAAK;AACb,aAAO,EAAE,MAAW,eAAS,SAAS,MAAM,MAAM,GAAG,SAAS,QAAQ,MAAM,GAAG,UAAU,EAAE;AAAA,IAC7F,SAAS,KAAK;AACZ,aAAO,EAAE,OAAO,yCAAW,OAAO,GAAG,CAAC,GAAG;AAAA,IAC3C;AAAA,EACF;AACF;AAGA,SAAS,kBAAkB,WAAmB;AAC5C,QAAM,OAAY,cAAQ,SAAS;AAEnC,SAAO,SAAS,gBAAgB,MAA+B;AAC7D,UAAM,WAAW,OAAO,KAAK,QAAQ,EAAE;AACvC,UAAM,SAAS,OAAO,KAAK,OAAO,EAAE;AACpC,UAAM,SAAS,OAAO,KAAK,OAAO,EAAE;AACpC,UAAM,uBAAuB,KAAK,IAAI,GAAG,OAAO,KAAK,yBAAyB,CAAC,CAAC;AAEhF,QAAI,CAAC,OAAQ,QAAO,EAAE,OAAO,+BAAW;AAExC,UAAM,WAAW,kBAAkB,MAAM,QAAQ;AACjD,QAAI,CAAC,SAAU,QAAO,EAAE,OAAO,mDAAW;AAC1C,UAAM,SAAS,SAAS;AACxB,QAAI,CAAI,eAAW,MAAM,KAAK,CAAI,aAAS,MAAM,EAAE,OAAO,GAAG;AAC3D,aAAO,EAAE,OAAO,6BAAS,QAAQ,GAAG;AAAA,IACtC;AAEA,QAAI;AACF,YAAM,UAAa,iBAAa,QAAQ,OAAO;AAC/C,YAAM,QAAQ,QAAQ,MAAM,MAAM,EAAE,SAAS;AAE7C,UAAI,UAAU,sBAAsB;AAClC,eAAO,EAAE,OAAO,gBAAM,oBAAoB,qDAAa,KAAK,YAAO,QAAQ,GAAG;AAAA,MAChF;AAEA,YAAM,UAAU,QAAQ,WAAW,QAAQ,MAAM;AACjD,MAAG,kBAAc,QAAQ,SAAS,OAAO;AACzC,aAAO;AAAA,QACL,MAAW,eAAS,SAAS,MAAM,MAAM;AAAA,QACzC,cAAc;AAAA,QACd,cAAc,QAAQ;AAAA,QACtB,aAAa,QAAQ;AAAA,MACvB;AAAA,IACF,SAAS,KAAK;AACZ,aAAO,EAAE,OAAO,6BAAS,OAAO,GAAG,CAAC,GAAG;AAAA,IACzC;AAAA,EACF;AACF;AAOO,SAAS,0BAA0B,UAAwB,WAAyB;AACzF,QAAM,WAAW,aAAa,SAAS;AACvC,WAAS;AAAA,IACP,CAAC,SAAS,SAAS,IAAI;AAAA,IACvB,EAAE,UAAU,cAAc,WAAW,OAAO,cAAc,CAAC,WAAW,GAAG,kBAAkB,MAAM;AAAA,IACjG;AAAA,MACE,MAAM;AAAA,MACN,UAAU;AAAA,QACR,MAAM;AAAA,QACN,aAAa;AAAA,QACb,YAAY;AAAA,UACV,MAAM;AAAA,UACN,YAAY;AAAA,YACV,MAAM,EAAE,MAAM,UAAU,aAAa,kEAAgB;AAAA,YACrD,YAAY,EAAE,MAAM,WAAW,aAAa,oEAAkB;AAAA,YAC9D,WAAW,EAAE,MAAM,WAAW,aAAa,0CAAY,iBAAiB,IAAI;AAAA,UAC9E;AAAA,UACA,UAAU,CAAC,MAAM;AAAA,QACnB;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,QAAM,YAAY,cAAc,SAAS;AACzC,WAAS;AAAA,IACP,CAAC,SAAS,UAAU,IAAI;AAAA,IACxB,EAAE,UAAU,cAAc,WAAW,OAAO,cAAc,CAAC,YAAY,GAAG,kBAAkB,MAAM;AAAA,IAClG;AAAA,MACE,MAAM;AAAA,MACN,UAAU;AAAA,QACR,MAAM;AAAA,QACN,aAAa;AAAA,QACb,YAAY;AAAA,UACV,MAAM;AAAA,UACN,YAAY;AAAA,YACV,MAAM,EAAE,MAAM,UAAU,aAAa,wFAAuB;AAAA,YAC5D,aAAa,EAAE,MAAM,WAAW,aAAa,oDAAiB;AAAA,UAChE;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;AAQO,SAAS,kBAAkB,UAAwB,WAAyB;AACjF,4BAA0B,UAAU,SAAS;AAE7C,QAAM,YAAY,cAAc,SAAS;AACzC,WAAS;AAAA,IACP,CAAC,SAAS,UAAU,IAAI;AAAA,IACxB,EAAE,UAAU,cAAc,WAAW,UAAU,cAAc,CAAC,YAAY,GAAG,kBAAkB,KAAK;AAAA,IACpG;AAAA,MACE,MAAM;AAAA,MACN,UAAU;AAAA,QACR,MAAM;AAAA,QACN,aAAa;AAAA,QACb,YAAY;AAAA,UACV,MAAM;AAAA,UACN,YAAY;AAAA,YACV,MAAM,EAAE,MAAM,UAAU,aAAa,kEAAgB;AAAA,YACrD,SAAS,EAAE,MAAM,UAAU,aAAa,mCAAe;AAAA,YACvD,WAAW,EAAE,MAAM,WAAW,aAAa,mFAAuB;AAAA,UACpE;AAAA,UACA,UAAU,CAAC,QAAQ,SAAS;AAAA,QAC9B;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,QAAM,cAAc,kBAAkB,SAAS;AAC/C,WAAS;AAAA,IACP,CAAC,SAAS,YAAY,IAAI;AAAA,IAC1B,EAAE,UAAU,cAAc,WAAW,UAAU,cAAc,CAAC,WAAW,GAAG,kBAAkB,KAAK;AAAA,IACnG;AAAA,MACE,MAAM;AAAA,MACN,UAAU;AAAA,QACR,MAAM;AAAA,QACN,aAAa;AAAA,QACb,YAAY;AAAA,UACV,MAAM;AAAA,UACN,YAAY;AAAA,YACV,MAAM,EAAE,MAAM,UAAU,aAAa,kEAAgB;AAAA,YACrD,KAAK,EAAE,MAAM,UAAU,aAAa,yDAAY;AAAA,YAChD,KAAK,EAAE,MAAM,UAAU,aAAa,6CAAU;AAAA,YAC9C,uBAAuB,EAAE,MAAM,WAAW,aAAa,8DAAiB;AAAA,UAC1E;AAAA,UACA,UAAU,CAAC,QAAQ,OAAO,KAAK;AAAA,QACjC;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;","names":["fs","path"]}
|