@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/git.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/tools/git.ts"],"sourcesContent":["import { execFileSync } from \"child_process\";\nimport { ToolRegistry } from \"./ToolRegistry.js\";\n\n/**\n * git_log — 提交历史 (对齐 Python 版 tools/git.py)\n *\n * 安全说明:全部使用 execFileSync 参数数组形式,用户提供的 path 等\n * 参数不经 shell 解释,杜绝命令注入。\n */\nfunction makeGitLog(workspace: string) {\n return function git_log(args: Record<string, unknown>) {\n const filePath = args.path ? String(args.path) : undefined;\n const maxCount = Math.min(Number(args.max_count ?? 20), 200);\n\n if (maxCount < 1) return { error: \"max_count 必须 >= 1\" };\n\n const cmd = [\n \"-C\", workspace,\n \"log\", `-n${maxCount}`,\n \"--pretty=format:%h%x1f%an%x1f%ad%x1f%s\",\n \"--date=short\",\n ];\n if (filePath) cmd.push(\"--\", filePath);\n\n try {\n const out = execFileSync(\"git\", cmd, { encoding: \"utf-8\", timeout: 15000 });\n const commits = out.trim().split(\"\\n\").filter(Boolean).map((line) => {\n const parts = line.split(\"\\x1f\");\n if (parts.length === 4) {\n return { hash: parts[0], author: parts[1], date: parts[2], subject: parts[3] };\n }\n return { raw: line };\n });\n return { count: commits.length, commits };\n } catch (exc: unknown) {\n const err = exc as { stderr?: string; message?: string };\n return { error: (err.stderr ?? err.message ?? \"git log failed\").slice(0, 300) };\n }\n };\n}\n\n/** git_status — 工作区状态 (对齐 aisuite git toolkit) */\nfunction makeGitStatus(workspace: string) {\n return function git_status(_args: Record<string, unknown>) {\n try {\n const out = execFileSync(\n \"git\",\n [\"-C\", workspace, \"status\", \"--porcelain\"],\n { encoding: \"utf-8\", timeout: 10000 },\n );\n const lines = out.trim().split(\"\\n\").filter(Boolean);\n const entries = lines.map((line) => {\n const status = line.slice(0, 2).trim();\n const file = line.slice(3);\n return { status, file };\n });\n return {\n clean: entries.length === 0,\n entries,\n count: entries.length,\n };\n } catch (exc: unknown) {\n const err = exc as { stderr?: string; message?: string };\n // 非 git 仓库\n if ((err.stderr ?? \"\").toLowerCase().includes(\"not a git repository\")) {\n return { error: \"不是 git 仓库\" };\n }\n return { error: (err.stderr ?? err.message ?? \"git status failed\").slice(0, 300) };\n }\n };\n}\n\n/** git_diff — 差异对比 (对齐 aisuite git toolkit) */\nfunction makeGitDiff(workspace: string) {\n return function git_diff(args: Record<string, unknown>) {\n const staged = args.staged === true;\n const filePath = args.path ? String(args.path) : undefined;\n const contextLines = Math.min(Number(args.context ?? 3), 10);\n\n const cmd = [\"diff\"];\n if (staged) cmd.push(\"--staged\");\n cmd.push(`-U${contextLines}`);\n if (filePath) cmd.push(\"--\", filePath);\n\n try {\n const out = execFileSync(\"git\", [\"-C\", workspace, ...cmd], {\n encoding: \"utf-8\",\n timeout: 15000,\n maxBuffer: 1024 * 1024 * 2,\n });\n return {\n staged,\n path: filePath ?? null,\n diff: out.slice(0, 50000),\n truncated: out.length > 50000,\n };\n } catch (exc: unknown) {\n const err = exc as { stderr?: string; message?: string };\n return { error: (err.stderr ?? err.message ?? \"git diff failed\").slice(0, 300) };\n }\n };\n}\n\n/** 注册 Git 工具 */\nexport function registerGitTools(registry: ToolRegistry, workspace: string): void {\n const gitLog = makeGitLog(workspace);\n registry.register(\n (args) => gitLog(args),\n { category: \"git\", riskLevel: \"low\", capabilities: [\"git\"], requiresApproval: false },\n {\n type: \"function\",\n function: {\n name: \"git_log\",\n description: \"查看最近的 git 提交历史 (hash, 作者, 日期, 主题)。可选地限定到某个路径。用于在编辑前了解代码的演进过程。只读。\",\n parameters: {\n type: \"object\",\n properties: {\n path: { type: \"string\", description: \"可选:限定到某个文件或目录\" },\n max_count: { type: \"integer\", description: \"返回多少条提交 (默认 20,最大 200)\" },\n },\n },\n },\n },\n );\n\n const gitStatus = makeGitStatus(workspace);\n registry.register(\n (args) => gitStatus(args),\n { category: \"git\", riskLevel: \"low\", capabilities: [\"git\"], requiresApproval: false },\n {\n type: \"function\",\n function: {\n name: \"git_status\",\n description: \"查看工作区 git 状态 (已修改、已暂存、未跟踪的文件)。只读。\",\n parameters: {\n type: \"object\",\n properties: {},\n },\n },\n },\n );\n\n const gitDiff = makeGitDiff(workspace);\n registry.register(\n (args) => gitDiff(args),\n { category: \"git\", riskLevel: \"low\", capabilities: [\"git\"], requiresApproval: false },\n {\n type: \"function\",\n function: {\n name: \"git_diff\",\n description: \"查看工作区与暂存区或最近提交之间的差异。可选地查看已暂存的更改。只读。\",\n parameters: {\n type: \"object\",\n properties: {\n staged: { type: \"boolean\", description: \"是否查看已暂存的更改 (默认 false)\" },\n path: { type: \"string\", description: \"可选:限定到某个文件\" },\n context: { type: \"integer\", description: \"上下文行数 (默认 3)\" },\n },\n },\n },\n },\n );\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/tools/git.ts","../../src/tools/execAsync.ts","../../src/tools/killTree.ts","../../src/tools/shellBackground.ts","../../src/tools/shell.ts"],"sourcesContent":["/**\n * Git 工具\n *\n * - 只读工具(注册给 Agent):git_log / git_status / git_diff;\n * - 控制台 Git 面板(**不**注册,经 REST / 舰队指令调用,见文件末尾):\n * 只读 git_branches / git_refs / git_show,\n * 写操作 git_fetch / git_stage / git_unstage / git_commit / git_checkout / git_pull / git_push。\n *\n * 要点:\n * - 全部用异步 execAsync(参数数组),用户参数不经 shell 解释,杜绝命令注入;\n * - 统一带 `-c core.quotepath=false`,避免非 ASCII 文件名被输出成八进制转义;\n * - path 指向子目录里的另一个仓库时,用 `git -C <path> rev-parse --show-toplevel`\n * 自动定位到最近的仓库根,而不是永远以工作区根为准。\n * - 工作区可包含多个仓库(根 + 子目录),`repo` 参数(工作区相对路径)用于\n * 显式选定仓库;`scanGitRepos` 供控制台面板列出这些仓库。\n */\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport { ToolRegistry } from \"./ToolRegistry.js\";\nimport { execAsync } from \"./execAsync.js\";\nimport { decodeOutput } from \"./shell.js\";\n\ninterface GitRunResult {\n status: number;\n stdout: string;\n stderr: string;\n}\n\n/** 运行 git(带 core.quotepath=false,禁用交互式凭据提示) */\nasync function runGit(repoDir: string, args: string[], timeout = 15000): Promise<GitRunResult> {\n const res = await execAsync(\"git\", [\"-c\", \"core.quotepath=false\", ...args], {\n cwd: repoDir,\n timeoutMs: timeout,\n maxBuffer: 1024 * 1024 * 5,\n env: { ...process.env, GIT_TERMINAL_PROMPT: \"0\" },\n });\n return {\n status: res.status ?? 1,\n stdout: decodeOutput(res.stdout),\n stderr: decodeOutput(res.stderr) || (res.error?.message ?? \"\"),\n };\n}\n\n/**\n * 定位仓库根。\n *\n * pathArg 给出时,从它(文件取其所在目录)向上找最近的 git 仓库;\n * 找不到就回退到工作区根。这样 path 指向子目录里的另一个仓库也能工作。\n */\nasync function resolveRepoRoot(workspace: string, pathArg?: string): Promise<string> {\n let dir = workspace;\n if (pathArg) {\n const abs = path.resolve(workspace, pathArg);\n try {\n dir = fs.statSync(abs).isDirectory() ? abs : path.dirname(abs);\n } catch {\n dir = abs; // 不存在时交给 git 报错并回退\n }\n }\n const res = await runGit(dir, [\"rev-parse\", \"--show-toplevel\"], 10000);\n if (res.status === 0 && res.stdout.trim()) return res.stdout.trim();\n return workspace;\n}\n\n/**\n * repo 参数(工作区相对路径,空 = 工作区根)→ 绝对路径。\n *\n * 只接受落在工作区内的路径(避免面板传 `../` 越界操作工作区外的仓库);\n * 越界或类型不对时返回 undefined,回退到「按 path / 工作区根定位」的旧行为。\n */\nfunction repoDirArg(workspace: string, raw: unknown): string | undefined {\n if (typeof raw !== \"string\") return undefined;\n const v = raw.trim();\n if (!v) return undefined;\n const base = path.resolve(workspace);\n const abs = path.resolve(base, v);\n const rel = path.relative(base, abs);\n if (rel.startsWith(\"..\") || path.isAbsolute(rel)) return undefined;\n return abs;\n}\n\n/** 把 path 参数转成相对于仓库根 pathspec(供 `--` 使用) */\nfunction toPathspec(repoRoot: string, workspace: string, pathArg?: string): string | null {\n if (!pathArg) return null;\n const abs = path.resolve(workspace, pathArg);\n let rel = path.relative(repoRoot, abs);\n if (rel === \"\") return \".\";\n if (rel.startsWith(\"..\") || path.isAbsolute(rel)) {\n // repoRoot(git --show-toplevel 输出)与 abs 的路径形式可能不一致:\n // 符号链接(macOS /var→/private/var)、Windows 短路径/大小写/盘符形式等。\n // 用 realpath 归一后再算一次;仍无法表达就返回 null(退回整仓,而非报错)。\n try {\n const realRepo = fs.realpathSync(repoRoot);\n const realAbs = fs.realpathSync(abs);\n const rel2 = path.relative(realRepo, realAbs);\n if (rel2 === \"\") return \".\";\n if (!rel2.startsWith(\"..\") && !path.isAbsolute(rel2)) return rel2;\n } catch {\n /* 路径不存在等:忽略 */\n }\n return null;\n }\n return rel;\n}\n\n/** git_log — 提交历史 */\nexport function makeGitLog(workspace: string) {\n return async function git_log(args: Record<string, unknown>) {\n const filePath = args.path ? String(args.path) : undefined;\n const maxCount = Math.min(Number(args.max_count ?? 20), 200);\n\n if (maxCount < 1) return { error: \"max_count 必须 >= 1\" };\n\n const repo = await resolveRepoRoot(workspace, repoDirArg(workspace, args.repo) ?? filePath);\n const cmd = [\n \"log\",\n `-n${maxCount}`,\n \"--pretty=format:%h%x1f%an%x1f%ad%x1f%s\",\n \"--date=short\",\n ];\n const pathspec = toPathspec(repo, workspace, filePath);\n if (pathspec) cmd.push(\"--\", pathspec);\n\n const res = await runGit(repo, cmd);\n if (res.status !== 0) {\n return { error: (res.stderr || \"git log failed\").trim().slice(0, 300) };\n }\n const commits = res.stdout\n .split(\"\\n\")\n .map((l) => l.replace(/\\r$/, \"\"))\n .filter((l) => l.length > 0)\n .map((line) => {\n const parts = line.split(\"\\x1f\");\n if (parts.length === 4) {\n return { hash: parts[0], author: parts[1], date: parts[2], subject: parts[3] };\n }\n return { raw: line };\n });\n return { repo, count: commits.length, commits };\n };\n}\n\n/** git_status — 工作区状态 */\nexport function makeGitStatus(workspace: string) {\n return async function git_status(args: Record<string, unknown>) {\n const filePath = args.path ? String(args.path) : undefined;\n const repo = await resolveRepoRoot(workspace, repoDirArg(workspace, args.repo) ?? filePath);\n const res = await runGit(repo, [\"status\", \"--porcelain\"], 10000);\n\n if (res.status !== 0) {\n const msg = res.stderr;\n if (msg.toLowerCase().includes(\"not a git repository\")) {\n return { error: \"不是 git 仓库\" };\n }\n return { error: (msg || \"git status failed\").trim().slice(0, 300) };\n }\n\n // 注意:不能整体 trim —— porcelain 第一行常以空格开头(\" M path\"),\n // trim 会吃掉它,随后按固定宽度切列就会多切掉路径首字符。\n const lines = res.stdout\n .split(\"\\n\")\n .map((l) => l.replace(/\\r$/, \"\"))\n .filter((l) => l.length > 0);\n const entries = lines.map((line) => {\n const status = line.slice(0, 2).trim();\n // 两列分别保留:索引(暂存区)与工作区状态,供控制台 Git 面板区分\n // 「已暂存」与「未暂存」(trim 后的 status 无法表达这一点)\n const index = line.slice(0, 1).trim();\n const worktree = line.slice(1, 2).trim();\n let file = line.slice(3);\n // 重命名形如 \"R old -> new\",取新路径\n const renameArrow = file.indexOf(\" -> \");\n if (renameArrow >= 0) file = file.slice(renameArrow + 4);\n return { status, file, index, worktree };\n });\n return { repo, clean: entries.length === 0, entries, count: entries.length };\n };\n}\n\n/** git_diff — 差异对比 */\nexport function makeGitDiff(workspace: string) {\n return async function git_diff(args: Record<string, unknown>) {\n const staged = args.staged === true;\n const filePath = args.path ? String(args.path) : undefined;\n const contextLines = Math.min(Number(args.context ?? 3), 10);\n\n const repo = await resolveRepoRoot(workspace, repoDirArg(workspace, args.repo) ?? filePath);\n const cmd = [\"diff\"];\n if (staged) cmd.push(\"--staged\");\n cmd.push(`-U${contextLines}`);\n const pathspec = toPathspec(repo, workspace, filePath);\n if (pathspec) cmd.push(\"--\", pathspec);\n\n const res = await runGit(repo, cmd);\n if (res.status !== 0) {\n return { error: (res.stderr || \"git diff failed\").trim().slice(0, 300) };\n }\n return {\n repo,\n staged,\n path: filePath ?? null,\n diff: res.stdout.slice(0, 50000),\n truncated: res.stdout.length > 50000,\n };\n };\n}\n\n/** 注册 Git 工具 */\nexport function registerGitTools(registry: ToolRegistry, workspace: string): void {\n const gitLog = makeGitLog(workspace);\n registry.register(\n (args) => gitLog(args),\n { category: \"git\", riskLevel: \"low\", capabilities: [\"git\"], requiresApproval: false },\n {\n type: \"function\",\n function: {\n name: \"git_log\",\n description: \"查看最近的 git 提交历史 (hash, 作者, 日期, 主题)。可选地限定到某个路径(该路径所在仓库会被自动定位)。用于在编辑前了解代码的演进过程。只读。\",\n parameters: {\n type: \"object\",\n properties: {\n path: { type: \"string\", description: \"可选:限定到某个文件或目录(并按其所在仓库查询)\" },\n max_count: { type: \"integer\", description: \"返回多少条提交 (默认 20,最大 200)\" },\n },\n },\n },\n },\n );\n\n const gitStatus = makeGitStatus(workspace);\n registry.register(\n (args) => gitStatus(args),\n { category: \"git\", riskLevel: \"low\", capabilities: [\"git\"], requiresApproval: false },\n {\n type: \"function\",\n function: {\n name: \"git_status\",\n description: \"查看工作区 git 状态 (已修改、已暂存、未跟踪的文件)。只读。\",\n parameters: {\n type: \"object\",\n properties: {\n path: { type: \"string\", description: \"可选:优先查询该路径所在的仓库(默认工作区根)\" },\n },\n },\n },\n },\n );\n\n const gitDiff = makeGitDiff(workspace);\n registry.register(\n (args) => gitDiff(args),\n { category: \"git\", riskLevel: \"low\", capabilities: [\"git\"], requiresApproval: false },\n {\n type: \"function\",\n function: {\n name: \"git_diff\",\n description: \"查看工作区与暂存区或最近提交之间的差异。可选地查看已暂存的更改。只读。\",\n parameters: {\n type: \"object\",\n properties: {\n staged: { type: \"boolean\", description: \"是否查看已暂存的更改 (默认 false)\" },\n path: { type: \"string\", description: \"可选:限定到某个文件(并按其在仓库中的路径)\" },\n context: { type: \"integer\", description: \"上下文行数 (默认 3)\" },\n },\n },\n },\n },\n );\n}\n\n/* ===================== Git 写操作(控制台 Git 面板) =====================\n *\n * 与只读工具分开:以下函数**不**注册进 ToolRegistry(Agent 不能调用),\n * 只由 REST / 舰队指令转发控制台面板上的手动操作。参数全部经 runGit 的\n * 参数数组执行,分支名/提交说明/路径都不经 shell 解释。\n */\n\n/** 写操作结果:ok=false 时 error 为可直接展示给用户的原因 */\nexport interface GitOpResult {\n ok: boolean;\n stdout: string;\n stderr: string;\n error?: string;\n}\n\n/** 回传输出上限(面板只展示 git 的一两行提示) */\nconst OP_OUTPUT_LIMIT = 8000;\n\nfunction opResult(res: GitRunResult): GitOpResult {\n const stdout = res.stdout.trim().slice(-OP_OUTPUT_LIMIT);\n const stderr = res.stderr.trim().slice(-OP_OUTPUT_LIMIT);\n if (res.status !== 0) {\n return {\n ok: false,\n stdout,\n stderr,\n error: (stderr || stdout || \"git 命令失败\").slice(0, 500),\n };\n }\n return { ok: true, stdout, stderr };\n}\n\nfunction failed(error: string): GitOpResult {\n return { ok: false, stdout: \"\", stderr: \"\", error };\n}\n\n/** 分支名校验(拒绝空、选项注入与 git 非法字符);返回 null 表示可用 */\nexport function gitRefProblem(name: string): string | null {\n const v = name.trim();\n if (!v) return \"分支名不能为空\";\n if (v.startsWith(\"-\")) return \"分支名不能以 - 开头\";\n // eslint-disable-next-line no-control-regex\n if (/[\\s~^:?*\\[\\\\\\x00-\\x1f\\x7f]/.test(v)) return \"分支名包含空白或非法字符\";\n if (v.includes(\"..\") || v.includes(\"//\") || v.endsWith(\"/\") || v.endsWith(\".lock\")) {\n return \"分支名不合法\";\n }\n return null;\n}\n\n/** payload 里的 paths(字符串或数组)→ 去空白后的非空数组 */\nfunction normalizePaths(raw: unknown): string[] {\n const list = Array.isArray(raw) ? raw : typeof raw === \"string\" ? [raw] : [];\n return list.map((p) => String(p).trim()).filter((p) => p.length > 0);\n}\n\n/** 工作区相对路径 → 仓库内 pathspec(无法表达时丢弃) */\nfunction toPathspecs(repoRoot: string, workspace: string, paths: string[]): string[] {\n const out: string[] = [];\n for (const p of paths) {\n const spec = toPathspec(repoRoot, workspace, p);\n if (spec) out.push(spec);\n }\n return out;\n}\n\n/** git_branches — 本地分支列表 + 当前分支(detached 时 current 为短 hash) */\nexport function makeGitBranches(workspace: string) {\n return async function git_branches(args: Record<string, unknown>) {\n const repo = await resolveRepoRoot(workspace, repoDirArg(workspace, args.repo));\n const list = await runGit(\n repo,\n [\n \"for-each-ref\",\n \"--sort=-committerdate\",\n // 制表符作字段分隔(git 的 --format 不支持 %x1f 之类的十六进制转义)\n \"--format=%(refname:short)\\t%(HEAD)\\t%(upstream:short)\",\n \"refs/heads\",\n ],\n 10000,\n );\n if (list.status !== 0) {\n const msg = list.stderr;\n if (msg.toLowerCase().includes(\"not a git repository\")) return { error: \"不是 git 仓库\" };\n return { error: (msg || \"git branch failed\").trim().slice(0, 300) };\n }\n\n // 空仓库(尚无提交)时 refs/heads 为空,但 symbolic-ref 仍能给出分支名\n const head = await runGit(repo, [\"symbolic-ref\", \"--short\", \"HEAD\"], 10000);\n const detached = head.status !== 0;\n let current = detached ? \"\" : head.stdout.trim();\n if (detached) {\n const rev = await runGit(repo, [\"rev-parse\", \"--short\", \"HEAD\"], 10000);\n current = rev.status === 0 ? rev.stdout.trim() : \"\";\n }\n\n const branches = list.stdout\n .split(\"\\n\")\n .map((l) => l.replace(/\\r$/, \"\"))\n .filter((l) => l.length > 0)\n .map((line) => {\n const [name, headMark, upstream] = line.split(\"\\t\");\n return {\n name,\n current: headMark?.trim() === \"*\" || (!!current && name === current),\n ...(upstream ? { upstream } : {}),\n };\n });\n return { repo, current, detached, branches };\n };\n}\n\n/** 提交标识(hash / ref 名)校验:拒绝空、选项注入与 git 非法字符;返回 null 表示可用 */\nexport function gitObjectProblem(name: string): string | null {\n const v = name.trim();\n if (!v) return \"提交标识不能为空\";\n if (v.startsWith(\"-\")) return \"提交标识不能以 - 开头\";\n // eslint-disable-next-line no-control-regex\n if (/[\\s~^:?*\\[\\\\\\x00-\\x1f\\x7f]/.test(v)) return \"提交标识包含空白或非法字符\";\n if (v.includes(\"..\") || v.includes(\"//\") || v.endsWith(\"/\") || v.endsWith(\".lock\")) {\n return \"提交标识不合法\";\n }\n return null;\n}\n\n/**\n * git_refs — 引用列表(控制台 Git 面板)\n *\n * 一次返回本地分支 / 远程分支 / 标签三组,供面板分组展示与切换。\n * 远程分支与标签只有在 `git fetch` 之后才是服务端的最新状态(见 makeGitFetch)。\n */\nexport function makeGitRefs(workspace: string) {\n return async function git_refs(args: Record<string, unknown>) {\n const repo = await resolveRepoRoot(workspace, repoDirArg(workspace, args.repo));\n const list = await runGit(\n repo,\n [\n \"for-each-ref\",\n \"--sort=-committerdate\",\n \"--format=%(refname)\\t%(refname:short)\\t%(HEAD)\\t%(upstream:short)\\t%(objectname:short)\",\n \"refs/heads\",\n \"refs/remotes\",\n \"refs/tags\",\n ],\n 15000,\n );\n if (list.status !== 0) {\n const msg = list.stderr;\n if (msg.toLowerCase().includes(\"not a git repository\")) return { error: \"不是 git 仓库\" };\n return { error: (msg || \"git for-each-ref failed\").trim().slice(0, 300) };\n }\n\n const head = await runGit(repo, [\"symbolic-ref\", \"--short\", \"HEAD\"], 10000);\n const detached = head.status !== 0;\n let current = detached ? \"\" : head.stdout.trim();\n if (detached) {\n const rev = await runGit(repo, [\"rev-parse\", \"--short\", \"HEAD\"], 10000);\n current = rev.status === 0 ? rev.stdout.trim() : \"\";\n }\n\n const local: Array<{ name: string; current?: boolean; upstream?: string }> = [];\n const remote: Array<{ name: string }> = [];\n const tags: Array<{ name: string; target?: string }> = [];\n\n for (const raw of list.stdout.split(\"\\n\")) {\n const line = raw.replace(/\\r$/, \"\");\n if (!line) continue;\n const [refname, short, headMark, upstream, target] = line.split(\"\\t\");\n if (!refname || !short) continue;\n if (refname.startsWith(\"refs/heads/\")) {\n local.push({\n name: short,\n current: headMark?.trim() === \"*\" || (!!current && short === current),\n ...(upstream ? { upstream } : {}),\n });\n continue;\n }\n if (refname.startsWith(\"refs/remotes/\")) {\n // origin/HEAD 之类的符号引用不是真实分支\n // (refname 是完整名 refs/remotes/origin/HEAD,short 只有 \"origin\")\n if (refname.endsWith(\"/HEAD\")) continue;\n remote.push({ name: short });\n continue;\n }\n if (refname.startsWith(\"refs/tags/\")) {\n tags.push({ name: short, ...(target ? { target } : {}) });\n }\n }\n\n return { repo, current, detached, local, remote, tags };\n };\n}\n\n/**\n * git_show — 单次提交详情(meta + 变更文件 + diff)\n *\n * path 省略时返回整个提交的 diff(截断 50000 字符);给出 path 时只返回该文件\n * 在此提交中的 diff(相对第一父提交,merge commit 也能给出可读结果)。\n */\nexport function makeGitShow(workspace: string) {\n return async function git_show(args: Record<string, unknown>) {\n const hash = String(args.hash ?? \"\").trim();\n const problem = gitObjectProblem(hash);\n if (problem) return { error: problem };\n const filePath = args.path ? String(args.path) : undefined;\n const contextLines = Math.min(Number(args.context ?? 3), 10);\n\n const repo = await resolveRepoRoot(workspace, repoDirArg(workspace, args.repo) ?? filePath);\n\n const metaRes = await runGit(\n repo,\n [\n \"show\",\n \"-s\",\n \"--first-parent\",\n \"--date=iso-strict\",\n \"--format=%H%x1f%h%x1f%an%x1f%ae%x1f%ad%x1f%s%x1f%b\",\n hash,\n ],\n 15000,\n );\n if (metaRes.status !== 0) {\n return { error: (metaRes.stderr || \"git show failed\").trim().slice(0, 300) };\n }\n const parts = metaRes.stdout.split(\"\\x1f\");\n const commit = {\n hash: (parts[0] ?? \"\").trim(),\n short: (parts[1] ?? \"\").trim(),\n author: (parts[2] ?? \"\").trim(),\n authorEmail: (parts[3] ?? \"\").trim(),\n date: (parts[4] ?? \"\").trim(),\n subject: (parts[5] ?? \"\").trim(),\n body: (parts.slice(6).join(\"\\x1f\") ?? \"\").trim(),\n };\n if (!commit.hash) return { error: \"未找到该提交\" };\n\n const filesRes = await runGit(\n repo,\n [\"show\", \"--name-status\", \"--format=\", \"--first-parent\", commit.hash],\n 15000,\n );\n const files = filesRes.stdout\n .split(\"\\n\")\n .map((l) => l.replace(/\\r$/, \"\"))\n .filter((l) => l.trim().length > 0)\n .map((line) => {\n const cols = line.split(\"\\t\");\n // 重命名 / 复制形如 \"R100\\told\\tnew\",取新路径\n const status = (cols[0] ?? \"\").trim();\n const file = (cols[cols.length - 1] ?? \"\").trim();\n return { status, file };\n })\n .filter((f) => f.file.length > 0);\n\n const cmd = [\"show\", \"--format=\", \"--first-parent\", `-U${contextLines}`, commit.hash];\n const pathspec = toPathspec(repo, workspace, filePath);\n if (pathspec) cmd.push(\"--\", pathspec);\n const diffRes = await runGit(repo, cmd, 20000);\n if (diffRes.status !== 0) {\n return { error: (diffRes.stderr || \"git show diff failed\").trim().slice(0, 300) };\n }\n\n return {\n repo,\n commit,\n files,\n path: filePath ?? null,\n diff: diffRes.stdout.slice(0, 50000),\n truncated: diffRes.stdout.length > 50000,\n };\n };\n}\n\n/** git_stage — 暂存文件(paths 为空 = 全部改动) */\nexport function makeGitStage(workspace: string) {\n return async function git_stage(args: Record<string, unknown>) {\n const paths = normalizePaths(args.paths);\n const repo = await resolveRepoRoot(workspace, repoDirArg(workspace, args.repo));\n const specs = paths.length > 0 ? toPathspecs(repo, workspace, paths) : [];\n if (paths.length > 0 && specs.length === 0) return failed(\"路径不在仓库内\");\n const cmd = specs.length > 0 ? [\"add\", \"--\", ...specs] : [\"add\", \"-A\"];\n return opResult(await runGit(repo, cmd, 30000));\n };\n}\n\n/** git_unstage — 取消暂存(paths 为空 = 全部) */\nexport function makeGitUnstage(workspace: string) {\n return async function git_unstage(args: Record<string, unknown>) {\n const paths = normalizePaths(args.paths);\n const repo = await resolveRepoRoot(workspace, repoDirArg(workspace, args.repo));\n const specs = paths.length > 0 ? toPathspecs(repo, workspace, paths) : [];\n if (paths.length > 0 && specs.length === 0) return failed(\"路径不在仓库内\");\n const cmd = specs.length > 0 ? [\"restore\", \"--staged\", \"--\", ...specs] : [\"reset\", \"-q\"];\n return opResult(await runGit(repo, cmd, 30000));\n };\n}\n\n/** git_commit — 提交已暂存内容 */\nexport function makeGitCommit(workspace: string) {\n return async function git_commit(args: Record<string, unknown>) {\n const message = String(args.message ?? \"\").trim();\n if (!message) return failed(\"提交说明不能为空\");\n const repo = await resolveRepoRoot(workspace, repoDirArg(workspace, args.repo));\n return opResult(await runGit(repo, [\"commit\", \"-m\", message], 60000));\n };\n}\n\n/** git_checkout — 切换分支(create=true 时新建并切换) */\nexport function makeGitCheckout(workspace: string) {\n return async function git_checkout(args: Record<string, unknown>) {\n const branch = String(args.branch ?? \"\").trim();\n const problem = gitRefProblem(branch);\n if (problem) return failed(problem);\n const repo = await resolveRepoRoot(workspace, repoDirArg(workspace, args.repo));\n const cmd = args.create === true ? [\"checkout\", \"-b\", branch] : [\"checkout\", branch];\n return opResult(await runGit(repo, cmd, 30000));\n };\n}\n\n/** git_pull — 拉取当前分支的上游 */\nexport function makeGitPull(workspace: string) {\n return async function git_pull(args: Record<string, unknown>) {\n const repo = await resolveRepoRoot(workspace, repoDirArg(workspace, args.repo));\n return opResult(await runGit(repo, [\"pull\"], 60000));\n };\n}\n\n/** git_push — 推送当前分支(无 upstream 且存在 origin 时按首次推送补 -u) */\nexport function makeGitPush(workspace: string) {\n return async function git_push(args: Record<string, unknown>) {\n const repo = await resolveRepoRoot(workspace, repoDirArg(workspace, args.repo));\n const upstream = await runGit(\n repo,\n [\"rev-parse\", \"--abbrev-ref\", \"--symbolic-full-name\", \"@{u}\"],\n 10000,\n );\n if (upstream.status !== 0) {\n const head = await runGit(repo, [\"symbolic-ref\", \"--short\", \"HEAD\"], 10000);\n const remotes = await runGit(repo, [\"remote\"], 10000);\n const hasOrigin =\n remotes.status === 0 && remotes.stdout.split(\"\\n\").some((r) => r.trim() === \"origin\");\n if (head.status === 0 && hasOrigin) {\n return opResult(await runGit(repo, [\"push\", \"-u\", \"origin\", head.stdout.trim()], 60000));\n }\n }\n return opResult(await runGit(repo, [\"push\"], 60000));\n };\n}\n\n/** git_fetch — 刷新服务端分支与标签(--all --prune --tags) */\nexport function makeGitFetch(workspace: string) {\n return async function git_fetch(args: Record<string, unknown>) {\n const repo = await resolveRepoRoot(workspace, repoDirArg(workspace, args.repo));\n const remotes = await runGit(repo, [\"remote\"], 10000);\n if (remotes.status !== 0) {\n const msg = remotes.stderr;\n if (msg.toLowerCase().includes(\"not a git repository\")) return failed(\"不是 git 仓库\");\n return failed((msg || \"git remote failed\").trim().slice(0, 300));\n }\n if (!remotes.stdout.trim()) return failed(\"未配置远端仓库\");\n return opResult(await runGit(repo, [\"fetch\", \"--all\", \"--prune\", \"--tags\"], 60000));\n };\n}\n\n/* ===================== 多仓库扫描(控制台 Git 面板) =====================\n *\n * 工作区根可能不是仓库,或只是若干独立仓库的父目录(子模块 / 多仓库工程)。\n * 面板需要先列出这些仓库,再让用户选定一个进行操作。\n * 扫描只读 `.git` 元数据(不启动 git 进程),并限制深度与目录数,\n * 避免在超大工作区上遍历过久、拖住节点事件循环。\n */\n\n/** 扫描时跳过的目录(依赖 / 其他 VCS 元数据;.git 不进入,避免遍历对象库) */\nconst SCAN_SKIP_DIRS = new Set([\"node_modules\", \".git\", \".hg\", \".svn\"]);\n\n/** 默认扫描深度(工作区根记为 0)与目录数上限 */\nconst SCAN_DEFAULT_DEPTH = 3;\nconst SCAN_MAX_DIRS = 2000;\n\nexport interface GitRepoInfo {\n /** 相对工作区的路径;根仓库为 \"\" */\n path: string;\n /** 目录名(根仓库为工作区目录名) */\n name: string;\n /** 当前分支;detached 时为短 hash */\n branch?: string;\n detached?: boolean;\n}\n\n/** 读取仓库 HEAD 的当前分支(.git 为文件时表示子模块 / worktree) */\nfunction readRepoHead(repoDir: string): { branch?: string; detached?: boolean } {\n const dotGit = path.join(repoDir, \".git\");\n try {\n let headFile: string;\n if (fs.statSync(dotGit).isDirectory()) {\n headFile = path.join(dotGit, \"HEAD\");\n } else {\n const raw = fs.readFileSync(dotGit, \"utf-8\");\n const m = /^gitdir:\\s*(.+)$/m.exec(raw);\n if (!m) return {};\n headFile = path.join(path.resolve(repoDir, m[1].trim()), \"HEAD\");\n }\n const head = fs.readFileSync(headFile, \"utf-8\").trim();\n const prefix = \"ref: refs/heads/\";\n if (head.startsWith(prefix)) return { branch: head.slice(prefix.length), detached: false };\n if (/^[0-9a-f]{7,40}$/i.test(head)) return { branch: head.slice(0, 7), detached: true };\n } catch {\n /* 空仓库(无 HEAD)/ 权限不足:忽略分支信息 */\n }\n return {};\n}\n\n/**\n * 扫描工作区及其子目录中的 git 仓库。\n *\n * 遇到仓库后仍继续向下扫描:子模块与「仓库里嵌套独立仓库」的工程都能被发现。\n * 根仓库排在第一位,其余按相对路径排序。\n */\nexport function scanGitRepos(\n workspace: string,\n depth = SCAN_DEFAULT_DEPTH,\n): { repos: GitRepoInfo[]; truncated?: boolean } {\n const root = path.resolve(workspace);\n const maxDepth = Number.isFinite(depth)\n ? Math.min(Math.max(Math.floor(depth), 0), 6)\n : SCAN_DEFAULT_DEPTH;\n const repos: GitRepoInfo[] = [];\n const stack: Array<{ dir: string; depth: number }> = [{ dir: root, depth: 0 }];\n let visited = 0;\n let truncated = false;\n\n while (stack.length > 0) {\n const { dir, depth: d } = stack.pop()!;\n visited += 1;\n if (visited > SCAN_MAX_DIRS) {\n truncated = true;\n break;\n }\n const rel = path.relative(root, dir).split(path.sep).join(\"/\");\n if (fs.existsSync(path.join(dir, \".git\"))) {\n repos.push({\n path: rel,\n name: rel ? path.basename(dir) : path.basename(root),\n ...readRepoHead(dir),\n });\n }\n if (d >= maxDepth) continue;\n let entries: fs.Dirent[];\n try {\n entries = fs.readdirSync(dir, { withFileTypes: true });\n } catch {\n continue; // 权限不足等:跳过该目录\n }\n for (const e of entries) {\n // isDirectory() 对符号链接目录为 false,天然避免环路\n if (!e.isDirectory() || SCAN_SKIP_DIRS.has(e.name)) continue;\n stack.push({ dir: path.join(dir, e.name), depth: d + 1 });\n }\n }\n\n repos.sort((a, b) =>\n a.path === \"\" ? -1 : b.path === \"\" ? 1 : a.path.localeCompare(b.path),\n );\n return { repos, ...(truncated ? { truncated: true } : {}) };\n}\n\n/** git_repos — 列出工作区内的所有 git 仓库(控制台 Git 面板的仓库选择器) */\nexport function makeGitRepos(workspace: string) {\n return async function git_repos(args: Record<string, unknown>) {\n const root = path.resolve(workspace);\n try {\n if (!fs.statSync(root).isDirectory()) return { root, repos: [], error: \"工作区不是目录\" };\n } catch {\n return { root, repos: [], error: \"工作区目录不存在\" };\n }\n const rawDepth = Number(args.depth);\n const depth = Number.isFinite(rawDepth) ? rawDepth : SCAN_DEFAULT_DEPTH;\n return { root, ...scanGitRepos(root, depth) };\n };\n}\n","/**\n * 异步子进程执行(spawnSync 的异步替身)\n *\n * 为什么需要:spawnSync 期间 Node 事件循环被整体阻塞,单进程节点上多个会话\n * 并发跑命令时会互相拖死——一条长命令(run_shell / ffmpeg 可达数百秒)就让\n * server 停止响应,SSE 心跳、HTTP 请求、其他会话的工具调用全部排队。\n * 换成异步 spawn 后,等待子进程的时间可以正常处理事件循环。\n *\n * 与 spawnSync 的差异(异步 spawn 不自带的语义在这里补齐):\n * - 超时:自行计时 + SIGKILL,结果里给出 `timedOut` 布尔\n * (spawnSync 是 ETIMEDOUT 错误码);\n * - 中断:监听 opts.signal,abort 时终止整棵进程树,结果里给出 `aborted` 布尔;\n * - 结算兜底:kill 后若仍有后代持有管道(`close` 不触发),宽限期到点强制结算,\n * 保证 Promise 一定有界,绝不永久挂起;\n * - 输出上限:自行累积 + 截断,结果里给出 `truncated` 布尔\n * (spawnSync 是 ENOBUFS 错误);\n * - stdout/stderr 保留原始 Buffer,交调用方的 decodeOutput 做编码回退。\n *\n * 参数一律用数组传递,不经 shell 解释,保持与原先一致的注入防护。\n */\nimport { spawn } from \"node:child_process\";\nimport { killTree } from \"./killTree.js\";\n\nexport interface ExecAsyncOptions {\n cwd?: string;\n /** 超时毫秒数;到点 SIGKILL 子进程并置 timedOut。不传则不限时 */\n timeoutMs?: number;\n /** stdout / stderr 各自的累积上限(字节);超出部分丢弃并置 truncated */\n maxBuffer?: number;\n env?: NodeJS.ProcessEnv;\n /** 需要向 stdin 写入的内容(如 git apply 的补丁);不传则 stdin 关闭 */\n input?: string;\n /**\n * 中断信号:abort 时终止整棵进程树并置 aborted。\n * 用于把 TurnEngine 的用户中断传导到正在执行的命令。\n */\n signal?: AbortSignal;\n}\n\nexport interface ExecAsyncResult {\n /** 退出码;被信号杀死或启动失败时为 null */\n status: number | null;\n stdout: Buffer;\n stderr: Buffer;\n /** 仅启动失败(命令不存在 ENOENT、无执行权限等)时存在 */\n error?: NodeJS.ErrnoException;\n timedOut: boolean;\n truncated: boolean;\n /** 是否因 signal abort 被终止 */\n aborted?: boolean;\n}\n\n/**\n * 存活的子进程(pid)。子进程以 detached 启动(自成进程组,便于整组终止),\n * 因此终端 Ctrl+C 不再波及它们;进程退出时统一收掉,避免遗留孤儿。\n */\nconst liveChildren = new Set<number>();\nlet exitCleanupInstalled = false;\n\nfunction trackChild(pid: number | undefined): void {\n if (!pid) return;\n liveChildren.add(pid);\n if (exitCleanupInstalled) return;\n exitCleanupInstalled = true;\n process.once(\"exit\", () => {\n for (const p of liveChildren) killTree(p);\n });\n}\n\nconst DEFAULT_MAX_BUFFER = 1024 * 1024 * 5;\n\n/**\n * 终止进程树后等待 stdio 关闭的宽限期(毫秒)。\n *\n * 结算挂在 `close` 上,而 `close` 要求子进程的 stdout/stderr 全部关闭。子进程虽以\n * detached 启动(自成进程组,killTree 的 `-pid` 覆盖整组),但仍有后代存活的可能:\n * detached 未生效、`process.kill(-pid)` 抛错走了只杀直接 pid 的兜底分支、或后代\n * 自行 setsid / 被 reparent 脱离进程组。此时管道写端不关,`close` 永不触发,\n * Promise 永久挂起(会话卡在 running,interrupt 也因等不到安全点而无效)。\n */\nconst KILL_GRACE_MS = 1000;\n\n/** 累积器:到上限后丢弃后续数据,但仍消费流避免子进程写阻塞 */\nfunction makeSink(maxBuffer: number): {\n push: (chunk: Buffer) => void;\n done: () => Buffer;\n over: () => boolean;\n} {\n const chunks: Buffer[] = [];\n let len = 0;\n let over = false;\n return {\n push(chunk: Buffer) {\n if (len >= maxBuffer) {\n over = true;\n return;\n }\n const room = maxBuffer - len;\n if (chunk.length > room) {\n chunks.push(chunk.subarray(0, room));\n len = maxBuffer;\n over = true;\n return;\n }\n chunks.push(chunk);\n len += chunk.length;\n },\n done: () => Buffer.concat(chunks),\n over: () => over,\n };\n}\n\nexport function execAsync(\n file: string,\n argv: string[],\n opts: ExecAsyncOptions = {},\n): Promise<ExecAsyncResult> {\n return new Promise<ExecAsyncResult>((resolve) => {\n const maxBuffer = opts.maxBuffer ?? DEFAULT_MAX_BUFFER;\n const out = makeSink(maxBuffer);\n const err = makeSink(maxBuffer);\n\n // 调用前已被中断:不必启动进程\n if (opts.signal?.aborted) {\n resolve({\n status: null,\n stdout: Buffer.alloc(0),\n stderr: Buffer.alloc(0),\n timedOut: false,\n truncated: false,\n aborted: true,\n });\n return;\n }\n\n let child: ReturnType<typeof spawn>;\n try {\n child = spawn(file, argv, {\n ...(opts.cwd !== undefined ? { cwd: opts.cwd } : {}),\n ...(opts.env !== undefined ? { env: opts.env } : {}),\n // 自成进程组(POSIX):超时/中断时用 killTree 一次收掉整棵进程树\n detached: process.platform !== \"win32\",\n stdio: [opts.input === undefined ? \"ignore\" : \"pipe\", \"pipe\", \"pipe\"],\n windowsHide: true,\n });\n } catch (e) {\n resolve({\n status: null,\n stdout: Buffer.alloc(0),\n stderr: Buffer.alloc(0),\n error: e as NodeJS.ErrnoException,\n timedOut: false,\n truncated: false,\n });\n return;\n }\n\n trackChild(child.pid);\n\n let settled = false;\n let timedOut = false;\n let aborted = false;\n let spawnError: NodeJS.ErrnoException | undefined;\n let timer: ReturnType<typeof setTimeout> | null = null;\n let graceTimer: ReturnType<typeof setTimeout> | null = null;\n\n const onAbort = (): void => {\n aborted = true;\n killAndSettle();\n };\n\n const finish = (status: number | null): void => {\n if (settled) return;\n settled = true;\n if (timer) clearTimeout(timer);\n if (graceTimer) clearTimeout(graceTimer);\n if (child.pid !== undefined) liveChildren.delete(child.pid);\n opts.signal?.removeEventListener(\"abort\", onAbort);\n const result: ExecAsyncResult = {\n status,\n stdout: out.done(),\n stderr: err.done(),\n timedOut,\n truncated: out.over() || err.over(),\n };\n if (aborted) result.aborted = true;\n if (spawnError) result.error = spawnError;\n resolve(result);\n };\n\n /**\n * 终止子进程树,并在宽限期内仍未结算时强制结算。\n *\n * 只调 killTree 是不够的:结算依赖 `close`,而后代存活会让它永不触发\n * (原因见 KILL_GRACE_MS 注释)——本次会话卡死的直接成因。\n */\n const killAndSettle = (): void => {\n killTree(child.pid);\n if (settled || graceTimer) return;\n graceTimer = setTimeout(() => {\n // 输出已随 kill 前的 data 事件收完;这里放下可能仍被后代持有的管道,\n // 结算语义已由 timedOut / aborted 确定,不再继续等待\n child.stdout?.destroy();\n child.stderr?.destroy();\n finish(null);\n }, KILL_GRACE_MS);\n };\n\n if (opts.timeoutMs !== undefined && opts.timeoutMs > 0) {\n timer = setTimeout(() => {\n timedOut = true;\n killAndSettle();\n }, opts.timeoutMs);\n }\n\n opts.signal?.addEventListener(\"abort\", onAbort, { once: true });\n\n child.stdout?.on(\"data\", (c: Buffer) => out.push(c));\n child.stderr?.on(\"data\", (c: Buffer) => err.push(c));\n\n child.on(\"error\", (e) => {\n spawnError = e as NodeJS.ErrnoException;\n // 进程根本没起来时不会有 close 事件,立即结算避免调用方永久挂起\n if (child.pid === undefined) finish(null);\n });\n\n if (opts.input !== undefined && child.stdin) {\n // 子进程可能已退出(EPIPE),忽略写入错误\n child.stdin.on(\"error\", () => {});\n child.stdin.end(opts.input);\n }\n\n child.on(\"close\", (code) => finish(code));\n });\n}\n","/**\n * 终止整棵进程树\n *\n * 前台命令(execAsync 的超时/中断)与后台任务(ShellTaskManager.kill)共用:\n * 只有以 detached 启动的子进程才是进程组组长,负 pid 才能覆盖其派生的孙进程。\n */\nimport { spawnSync } from \"node:child_process\";\n\nexport function killTree(pid: number | undefined): void {\n if (!pid) return;\n if (process.platform === \"win32\") {\n try {\n spawnSync(\"taskkill\", [\"/pid\", String(pid), \"/T\", \"/F\"], { windowsHide: true });\n } catch {\n /* 尽力而为 */\n }\n return;\n }\n // POSIX:detached 启动使子进程成为进程组组长,负 pid 即整组\n try {\n process.kill(-pid, \"SIGKILL\");\n } catch {\n try {\n process.kill(pid, \"SIGKILL\");\n } catch {\n /* 已退出 */\n }\n }\n}\n","/**\n * 后台 shell 任务管理器\n *\n * 支撑 run_shell 的 `run_in_background` 能力(开发服务器 / watch 进程等长驻任务):\n * - start():detached 启动,stdout/stderr 进环形缓冲,立即返回 task_id;\n * - output():读取某任务的累计输出与状态;\n * - kill():终止整棵进程树(Windows 用 taskkill /T /F,POSIX 用进程组信号);\n * - disposeAll():进程退出时统一清理,避免残留子进程。\n *\n * 输出缓冲有上限(默认 64KB/流),超出丢弃最旧内容,防止长驻任务把内存吃满。\n */\nimport { spawn, type ChildProcess } from \"node:child_process\";\nimport { killTree } from \"./killTree.js\";\n\n/** 每个输出流的缓冲上限(字符) */\nconst DEFAULT_BUFFER_LIMIT = 64 * 1024;\n\n/** 固定长度的环形字符缓冲:超出上限时从头部丢弃 */\nclass RingBuffer {\n private chunks: string[] = [];\n private size = 0;\n\n constructor(private readonly limit = DEFAULT_BUFFER_LIMIT) {}\n\n add(chunk: string): void {\n if (!chunk) return;\n this.chunks.push(chunk);\n this.size += chunk.length;\n while (this.size > this.limit && this.chunks.length > 1) {\n this.size -= this.chunks.shift()!.length;\n }\n // 单个 chunk 就可能超限:直接截取尾部\n if (this.size > this.limit) {\n const joined = this.chunks.join(\"\");\n this.chunks = [joined.slice(-this.limit)];\n this.size = this.chunks[0]!.length;\n }\n }\n\n get(): string {\n const joined = this.chunks.join(\"\");\n return joined.length > this.limit ? joined.slice(-this.limit) : joined;\n }\n}\n\ninterface BackgroundTask {\n id: string;\n command: string;\n /** 归属标识(server 传租户 id),用于按项目过滤任务列表 */\n scope: string | null;\n child: ChildProcess;\n stdout: RingBuffer;\n stderr: RingBuffer;\n status: \"running\" | \"exited\" | \"killed\";\n exitCode: number | null;\n signal: NodeJS.Signals | null;\n startedAt: number;\n endedAt: number | null;\n}\n\nexport interface BackgroundTaskSnapshot {\n task_id: string;\n command: string;\n status: \"running\" | \"exited\" | \"killed\";\n running: boolean;\n exit_code: number | null;\n signal: string | null;\n stdout: string;\n stderr: string;\n duration_ms: number;\n}\n\nexport interface StartOptions {\n command: string;\n file: string;\n argv: string[];\n cwd: string;\n env: NodeJS.ProcessEnv;\n /** 归属标识(如租户 id),用于按项目过滤任务列表 */\n scope?: string | null;\n}\n\nexport class ShellTaskManager {\n private readonly tasks = new Map<string, BackgroundTask>();\n private seq = 0;\n private cleanupInstalled = false;\n\n /** 启动一个后台任务,立即返回快照(不等退出) */\n start(opts: StartOptions): BackgroundTaskSnapshot {\n this.installCleanup();\n const id = `shell-${++this.seq}-${Date.now().toString(36)}`;\n const child = spawn(opts.file, opts.argv, {\n cwd: opts.cwd,\n env: opts.env,\n detached: process.platform !== \"win32\",\n windowsHide: true,\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n\n const task: BackgroundTask = {\n id,\n command: opts.command,\n scope: opts.scope ?? null,\n child,\n stdout: new RingBuffer(),\n stderr: new RingBuffer(),\n status: \"running\",\n exitCode: null,\n signal: null,\n startedAt: Date.now(),\n endedAt: null,\n };\n this.tasks.set(id, task);\n\n child.stdout?.on(\"data\", (d: Buffer) => task.stdout.add(d.toString(\"utf-8\")));\n child.stderr?.on(\"data\", (d: Buffer) => task.stderr.add(d.toString(\"utf-8\")));\n child.on(\"error\", (err) => task.stderr.add(`[spawn error] ${err.message}\\n`));\n child.on(\"exit\", (code, signal) => {\n task.exitCode = code;\n task.signal = signal;\n task.endedAt = Date.now();\n // 已被 kill() 标记为 killed 的保持 killed,否则记为正常退出\n if (task.status !== \"killed\") task.status = \"exited\";\n });\n // 允许父进程退出后子进程继续(配合 disposeAll 显式清理)\n child.unref();\n\n return this.snapshot(task);\n }\n\n /** 读取任务输出与状态 */\n output(taskId: string): BackgroundTaskSnapshot | { error: string } {\n const task = this.tasks.get(taskId);\n if (!task) return { error: `未知的后台任务: ${taskId}` };\n return this.snapshot(task);\n }\n\n /** 终止任务(进程树)并返回快照;传 scope 时只允许终止该归属下的任务 */\n kill(taskId: string, scope?: string | null): BackgroundTaskSnapshot | { error: string } {\n const task = this.tasks.get(taskId);\n if (!task) return { error: `未知的后台任务: ${taskId}` };\n if (scope !== undefined && task.scope !== scope) {\n return { error: `未知的后台任务: ${taskId}` };\n }\n if (task.status === \"running\") {\n task.status = \"killed\";\n task.endedAt = Date.now();\n killTree(task.child.pid);\n }\n return this.snapshot(task);\n }\n\n /** 列出任务快照(调试/展示用);传 scope 时只返回该归属下的任务 */\n list(scope?: string | null): BackgroundTaskSnapshot[] {\n const all = [...this.tasks.values()];\n return (scope === undefined ? all : all.filter((t) => t.scope === scope)).map((t) =>\n this.snapshot(t),\n );\n }\n\n /** 终止全部任务(进程退出时调用) */\n disposeAll(): void {\n for (const task of this.tasks.values()) {\n if (task.status === \"running\") {\n task.status = \"killed\";\n killTree(task.child.pid);\n }\n }\n }\n\n private snapshot(task: BackgroundTask): BackgroundTaskSnapshot {\n return {\n task_id: task.id,\n command: task.command,\n status: task.status,\n running: task.status === \"running\",\n exit_code: task.exitCode,\n signal: task.signal,\n stdout: task.stdout.get(),\n stderr: task.stderr.get(),\n duration_ms: (task.endedAt ?? Date.now()) - task.startedAt,\n };\n }\n\n private installCleanup(): void {\n if (this.cleanupInstalled) return;\n this.cleanupInstalled = true;\n process.once(\"exit\", () => this.disposeAll());\n }\n}\n","/**\n * Shell 工具\n *\n * 提供 run_shell 工具(对齐 Python 版 LocalExecutor):\n * 在持久化 shell 会话中执行命令。cwd 在多次调用之间保持\n * (支持 cd / pushd 命令切换目录,状态按工作区存放,引擎重建也不丢),\n * 输出截断到末尾部分,超时与缓冲区大小均有上限。\n *\n * 用哪个 shell 由 shellRuntime 决定:POSIX 下沿用 Node 默认 /bin/sh\n * (行为不变),Windows 下优先 POSIX 风格 shell(busybox / Git Bash /\n * PowerShell),都没有才回退 cmd.exe。\n *\n * 执行统一走异步 execAsync:成功与失败都能拿到真实的 stdout / stderr / exit_code\n * (旧实现用 execSync,成功分支把 stderr 丢弃、exit_code 恒为 0);异步而非\n * spawnSync,避免命令执行期间阻塞事件循环、拖住其他会话与心跳。\n * 另外支持 run_in_background,配合 shell_task_output / shell_task_kill\n * 管理开发服务器等长驻进程。\n */\nimport * as fs from \"fs\";\nimport * as path from \"path\";\nimport { ToolRegistry } from \"./ToolRegistry.js\";\nimport { execAsync } from \"./execAsync.js\";\nimport {\n describeShell,\n parseCdChain,\n resolveShellSpec,\n wrapCommand,\n type ShellSpec,\n} from \"./shellRuntime.js\";\nimport { ShellTaskManager } from \"./shellBackground.js\";\nimport type { BackgroundTaskSnapshot } from \"./shellBackground.js\";\nimport type { ToolExecContext } from \"./ToolRegistry.js\";\n\n/** 输出截断上限(字符) */\nconst MAX_STDOUT = 10000;\nconst MAX_STDERR = 5000;\n/** 单次命令输出缓冲上限(字节) */\nconst MAX_BUFFER = 1024 * 1024 * 5;\n\n/**\n * 持久 shell 会话状态:按工作区根目录存放 cwd。\n *\n * 之所以放在模块级而不是 makeRunShell 的闭包里:CLI 在 /mode、/agent、/model、\n * reset 时会重建引擎(重新调用 registerShellTool),闭包里的 cwd 会随之丢失。\n * 以工作区为键既可跨引擎重建保持,又不会在多租户之间串目录。\n */\nconst shellSessions = new Map<string, { cwd: string }>();\n\nfunction sessionFor(workspace: string): { cwd: string } {\n // 用规范化后的绝对路径作键:避免尾斜杠 / 相对路径在不同注册点产生两个会话\n const key = path.resolve(workspace);\n let s = shellSessions.get(key);\n if (!s) {\n s = { cwd: key };\n shellSessions.set(key, s);\n }\n return s;\n}\n\n/** 测试用:重置某个工作区的 shell 会话状态 */\nexport function resetShellSession(workspace: string): void {\n shellSessions.delete(path.resolve(workspace));\n}\n\n/** 解析实际执行的 shell 可执行文件与 argv 构造器(default 时按平台兜底) */\nfunction resolveInvocation(\n spec: ShellSpec,\n prepared: string,\n): { file: string; argv: string[] } {\n if (spec.file !== null) {\n return { file: spec.file, argv: spec.buildArgs(prepared) };\n }\n if (process.platform === \"win32\") {\n const comspec = process.env.ComSpec ?? process.env.COMSPEC ?? \"cmd.exe\";\n return { file: comspec, argv: [\"/d\", \"/s\", \"/c\", prepared] };\n }\n return { file: \"/bin/sh\", argv: [\"-c\", prepared] };\n}\n\n/** 供 run_shell 与后台任务共用的环境变量 */\nfunction shellEnv(): NodeJS.ProcessEnv {\n return {\n ...process.env,\n GIT_TERMINAL_PROMPT: \"0\",\n PYTHONUNBUFFERED: \"1\",\n PYTHONUTF8: \"1\",\n PYTHONIOENCODING: \"utf-8\",\n };\n}\n\n/**\n * 解码子进程输出。\n *\n * 正常情况下命令前已注入 UTF-8 编码设置,输出就是 UTF-8。但 PowerShell 的\n * **语法错误(ParserError)** 会在脚本执行前就抛出,此时编码设置那行还没跑,\n * stderr 用的是控制台 OEM 代码页(中文 Windows 为 936/GBK)——直接按 UTF-8\n * 读会得到乱码。这里先严格按 UTF-8 解,失败再回退到 GBK 等常见编码。\n */\nexport function decodeOutput(buf: Buffer | null | undefined): string {\n if (!buf || buf.length === 0) return \"\";\n try {\n return new TextDecoder(\"utf-8\", { fatal: true }).decode(buf);\n } catch {\n for (const enc of [\"gbk\", \"big5\", \"shift_jis\", \"latin1\"]) {\n try {\n return new TextDecoder(enc).decode(buf);\n } catch {\n /* 当前 Node 构建不支持该编码,继续尝试 */\n }\n }\n return buf.toString(\"latin1\");\n }\n}\n\n/**\n * run_shell — 持久化 shell (对齐 Python 版 LocalExecutor)\n *\n * 内部维护 cwd 状态:识别 cd / pushd 命令并更新,使后续命令在新目录下执行。\n * 注意:不要用 require()(ESM 下未定义),必须用 import 的 fs/path。\n */\nfunction makeRunShell(workspace: string, spec: ShellSpec, tasks: ShellTaskManager, scope: string | null) {\n const session = sessionFor(workspace);\n\n return async function run_shell(args: Record<string, unknown>, ctx?: ToolExecContext) {\n const command = String(args.command ?? \"\");\n const description = String(args.description ?? \"\").slice(0, 100);\n const background = args.run_in_background === true;\n // 超时上限 600s,换算为毫秒;非法值回退默认 120s\n const rawTimeout = Number(args.timeout_seconds ?? 120);\n const timeoutSec = Number.isFinite(rawTimeout) ? rawTimeout : 120;\n const timeout = Math.min(Math.max(timeoutSec, 1), 600) * 1000;\n\n if (!command.trim()) {\n return { error: \"命令不能为空\" };\n }\n\n // 切目录:在执行前解析,这样 `cd dir && 其他命令` 的剩余部分能在新目录下跑\n // (否则目录变更只作用于那个子 shell,无法跨调用保持)。\n const { target, rest } = parseCdChain(command);\n let toRun = command;\n if (target) {\n try {\n const resolved = path.resolve(session.cwd, target);\n if (fs.existsSync(resolved) && fs.statSync(resolved).isDirectory()) {\n session.cwd = resolved;\n }\n } catch {\n /* ignore */\n }\n if (rest) toRun = rest;\n }\n\n const prepared = wrapCommand(spec.kind, toRun);\n const { file, argv } = resolveInvocation(spec, prepared);\n const env = shellEnv();\n\n // ---- 后台执行 ----\n if (background) {\n const snap = tasks.start({ command, file, argv, cwd: session.cwd, env, scope });\n return {\n task_id: snap.task_id,\n status: snap.status,\n cwd: session.cwd,\n note: \"已在后台启动。用 shell_task_output 查看输出,shell_task_kill 终止。\",\n };\n }\n\n // ---- 前台执行 ----\n // 不用 encoding: \"utf-8\",先拿原始字节再自适应解码(见 decodeOutput)。\n const result = await execAsync(file, argv, {\n cwd: session.cwd,\n timeoutMs: timeout,\n maxBuffer: MAX_BUFFER,\n env,\n signal: ctx?.signal,\n });\n\n const stdout = decodeOutput(result.stdout).slice(-MAX_STDOUT);\n const stderr = decodeOutput(result.stderr).slice(-MAX_STDERR);\n\n // 被用户中断:报告为中断而非普通失败,引擎随即按中断收尾\n if (result.aborted) {\n return {\n stdout,\n stderr,\n exit_code: result.status ?? 1,\n cwd: session.cwd,\n interrupted: true,\n error: \"命令已被用户中断\",\n };\n }\n\n // 非超时的启动错误(如 shell 不存在)单独报告\n if (result.error && !result.timedOut) {\n const message = result.error.message;\n return {\n stdout,\n stderr: stderr || message,\n exit_code: 1,\n cwd: session.cwd,\n error: message.slice(0, 200),\n };\n }\n\n return {\n stdout,\n stderr,\n exit_code: result.status ?? 1,\n cwd: session.cwd,\n ...(result.timedOut\n ? { timed_out: true, error: `命令超时(${timeoutSec}s)后已终止` }\n : {}),\n };\n };\n}\n\n/**\n * 后台任务表:与 shellSessions 同理,放模块级单例。\n * 若每次注册都新建,引擎重建后旧 task_id 会查不到、后台进程也失去跟踪。\n */\nconst shellTasks = new ShellTaskManager();\n\n/** 列出后台任务快照;scope 为 undefined 时返回全部(控制台按项目过滤用) */\nexport function listShellTasks(scope?: string | null): BackgroundTaskSnapshot[] {\n return shellTasks.list(scope);\n}\n\n/** 终止后台任务(连同进程树);scope 用于限定归属,防止跨项目误杀 */\nexport function killShellTask(\n taskId: string,\n scope?: string | null,\n): BackgroundTaskSnapshot | { error: string } {\n return shellTasks.kill(taskId, scope);\n}\n\n/**\n * 注册 Shell 工具(高风险:requiresApproval=true,由权限引擎门控)\n *\n * @param scope 后台任务归属(server 传租户 id);用于按项目过滤任务列表与终止\n */\nexport function registerShellTool(\n registry: ToolRegistry,\n workspace: string,\n scope?: string | null,\n): void {\n const spec = resolveShellSpec();\n const tasks = shellTasks;\n const shell = makeRunShell(workspace, spec, tasks, scope ?? null);\n\n registry.register(\n (args, ctx) => shell(args, ctx),\n { category: \"shell\", riskLevel: \"high\", capabilities: [\"run_shell\"], requiresApproval: true },\n {\n type: \"function\",\n function: {\n name: \"run_shell\",\n description:\n \"在持久化 shell 会话中运行命令 (cwd 和环境变量在多次调用之间保持)。\" +\n \"输出截断到最后部分,stdout 与 stderr 都会返回。\" +\n \"传递 description 说明命令用途 (显示在审批提示中),\" +\n \"传递 timeout_seconds 控制超时。\" +\n \"对于开发服务器 / watch 等长驻进程,设置 run_in_background=true,\" +\n \"随后用 shell_task_output 轮询输出、shell_task_kill 终止。\" +\n `运行环境:${describeShell(spec, process.platform)}`,\n parameters: {\n type: \"object\",\n properties: {\n command: { type: \"string\", description: \"要执行的 shell 命令\" },\n description: { type: \"string\", description: \"命令用途的简短描述 (显示在审批中)\" },\n timeout_seconds: { type: \"integer\", description: \"超时秒数 (默认 120,最大 600)\" },\n run_in_background: {\n type: \"boolean\",\n description: \"是否后台运行 (默认 false)。适合开发服务器等长驻进程。\",\n },\n },\n required: [\"command\"],\n },\n },\n },\n );\n\n // ---- shell_task_output ----\n registry.register(\n (args) => tasks.output(String(args.task_id ?? \"\")),\n { category: \"shell\", riskLevel: \"low\", capabilities: [\"run_shell\"], requiresApproval: false },\n {\n type: \"function\",\n function: {\n name: \"shell_task_output\",\n description:\n \"查看 run_shell(run_in_background=true) 启动的后台任务的累计输出与状态。\" +\n \"返回 status (running/exited/killed)、exit_code、stdout、stderr。\",\n parameters: {\n type: \"object\",\n properties: {\n task_id: { type: \"string\", description: \"run_shell 返回的 task_id\" },\n },\n required: [\"task_id\"],\n },\n },\n },\n );\n\n // ---- shell_task_kill ----\n registry.register(\n (args) => tasks.kill(String(args.task_id ?? \"\"), scope ?? null),\n { category: \"shell\", riskLevel: \"medium\", capabilities: [\"run_shell\"], requiresApproval: false },\n {\n type: \"function\",\n function: {\n name: \"shell_task_kill\",\n description: \"终止后台任务(连同其子进程树)。返回任务的最终状态。\",\n parameters: {\n type: \"object\",\n properties: {\n task_id: { type: \"string\", description: \"run_shell 返回的 task_id\" },\n },\n required: [\"task_id\"],\n },\n },\n },\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAgBA,SAAoB;AACpB,WAAsB;;;ACGtB,IAAAA,6BAAsB;;;ACdtB,gCAA0B;AAEnB,SAAS,SAAS,KAA+B;AACtD,MAAI,CAAC,IAAK;AACV,MAAI,QAAQ,aAAa,SAAS;AAChC,QAAI;AACF,+CAAU,YAAY,CAAC,QAAQ,OAAO,GAAG,GAAG,MAAM,IAAI,GAAG,EAAE,aAAa,KAAK,CAAC;AAAA,IAChF,QAAQ;AAAA,IAER;AACA;AAAA,EACF;AAEA,MAAI;AACF,YAAQ,KAAK,CAAC,KAAK,SAAS;AAAA,EAC9B,QAAQ;AACN,QAAI;AACF,cAAQ,KAAK,KAAK,SAAS;AAAA,IAC7B,QAAQ;AAAA,IAER;AAAA,EACF;AACF;;;AD4BA,IAAM,eAAe,oBAAI,IAAY;AACrC,IAAI,uBAAuB;AAE3B,SAAS,WAAW,KAA+B;AACjD,MAAI,CAAC,IAAK;AACV,eAAa,IAAI,GAAG;AACpB,MAAI,qBAAsB;AAC1B,yBAAuB;AACvB,UAAQ,KAAK,QAAQ,MAAM;AACzB,eAAW,KAAK,aAAc,UAAS,CAAC;AAAA,EAC1C,CAAC;AACH;AAEA,IAAM,qBAAqB,OAAO,OAAO;AAWzC,IAAM,gBAAgB;AAGtB,SAAS,SAAS,WAIhB;AACA,QAAM,SAAmB,CAAC;AAC1B,MAAI,MAAM;AACV,MAAI,OAAO;AACX,SAAO;AAAA,IACL,KAAK,OAAe;AAClB,UAAI,OAAO,WAAW;AACpB,eAAO;AACP;AAAA,MACF;AACA,YAAM,OAAO,YAAY;AACzB,UAAI,MAAM,SAAS,MAAM;AACvB,eAAO,KAAK,MAAM,SAAS,GAAG,IAAI,CAAC;AACnC,cAAM;AACN,eAAO;AACP;AAAA,MACF;AACA,aAAO,KAAK,KAAK;AACjB,aAAO,MAAM;AAAA,IACf;AAAA,IACA,MAAM,MAAM,OAAO,OAAO,MAAM;AAAA,IAChC,MAAM,MAAM;AAAA,EACd;AACF;AAEO,SAAS,UACd,MACA,MACA,OAAyB,CAAC,GACA;AAC1B,SAAO,IAAI,QAAyB,CAACC,aAAY;AAC/C,UAAM,YAAY,KAAK,aAAa;AACpC,UAAM,MAAM,SAAS,SAAS;AAC9B,UAAM,MAAM,SAAS,SAAS;AAG9B,QAAI,KAAK,QAAQ,SAAS;AACxB,MAAAA,SAAQ;AAAA,QACN,QAAQ;AAAA,QACR,QAAQ,OAAO,MAAM,CAAC;AAAA,QACtB,QAAQ,OAAO,MAAM,CAAC;AAAA,QACtB,UAAU;AAAA,QACV,WAAW;AAAA,QACX,SAAS;AAAA,MACX,CAAC;AACD;AAAA,IACF;AAEA,QAAI;AACJ,QAAI;AACF,kBAAQ,kCAAM,MAAM,MAAM;AAAA,QACxB,GAAI,KAAK,QAAQ,SAAY,EAAE,KAAK,KAAK,IAAI,IAAI,CAAC;AAAA,QAClD,GAAI,KAAK,QAAQ,SAAY,EAAE,KAAK,KAAK,IAAI,IAAI,CAAC;AAAA;AAAA,QAElD,UAAU,QAAQ,aAAa;AAAA,QAC/B,OAAO,CAAC,KAAK,UAAU,SAAY,WAAW,QAAQ,QAAQ,MAAM;AAAA,QACpE,aAAa;AAAA,MACf,CAAC;AAAA,IACH,SAAS,GAAG;AACV,MAAAA,SAAQ;AAAA,QACN,QAAQ;AAAA,QACR,QAAQ,OAAO,MAAM,CAAC;AAAA,QACtB,QAAQ,OAAO,MAAM,CAAC;AAAA,QACtB,OAAO;AAAA,QACP,UAAU;AAAA,QACV,WAAW;AAAA,MACb,CAAC;AACD;AAAA,IACF;AAEA,eAAW,MAAM,GAAG;AAEpB,QAAI,UAAU;AACd,QAAI,WAAW;AACf,QAAI,UAAU;AACd,QAAI;AACJ,QAAI,QAA8C;AAClD,QAAI,aAAmD;AAEvD,UAAM,UAAU,MAAY;AAC1B,gBAAU;AACV,oBAAc;AAAA,IAChB;AAEA,UAAM,SAAS,CAAC,WAAgC;AAC9C,UAAI,QAAS;AACb,gBAAU;AACV,UAAI,MAAO,cAAa,KAAK;AAC7B,UAAI,WAAY,cAAa,UAAU;AACvC,UAAI,MAAM,QAAQ,OAAW,cAAa,OAAO,MAAM,GAAG;AAC1D,WAAK,QAAQ,oBAAoB,SAAS,OAAO;AACjD,YAAM,SAA0B;AAAA,QAC9B;AAAA,QACA,QAAQ,IAAI,KAAK;AAAA,QACjB,QAAQ,IAAI,KAAK;AAAA,QACjB;AAAA,QACA,WAAW,IAAI,KAAK,KAAK,IAAI,KAAK;AAAA,MACpC;AACA,UAAI,QAAS,QAAO,UAAU;AAC9B,UAAI,WAAY,QAAO,QAAQ;AAC/B,MAAAA,SAAQ,MAAM;AAAA,IAChB;AAQA,UAAM,gBAAgB,MAAY;AAChC,eAAS,MAAM,GAAG;AAClB,UAAI,WAAW,WAAY;AAC3B,mBAAa,WAAW,MAAM;AAG5B,cAAM,QAAQ,QAAQ;AACtB,cAAM,QAAQ,QAAQ;AACtB,eAAO,IAAI;AAAA,MACb,GAAG,aAAa;AAAA,IAClB;AAEA,QAAI,KAAK,cAAc,UAAa,KAAK,YAAY,GAAG;AACtD,cAAQ,WAAW,MAAM;AACvB,mBAAW;AACX,sBAAc;AAAA,MAChB,GAAG,KAAK,SAAS;AAAA,IACnB;AAEA,SAAK,QAAQ,iBAAiB,SAAS,SAAS,EAAE,MAAM,KAAK,CAAC;AAE9D,UAAM,QAAQ,GAAG,QAAQ,CAAC,MAAc,IAAI,KAAK,CAAC,CAAC;AACnD,UAAM,QAAQ,GAAG,QAAQ,CAAC,MAAc,IAAI,KAAK,CAAC,CAAC;AAEnD,UAAM,GAAG,SAAS,CAAC,MAAM;AACvB,mBAAa;AAEb,UAAI,MAAM,QAAQ,OAAW,QAAO,IAAI;AAAA,IAC1C,CAAC;AAED,QAAI,KAAK,UAAU,UAAa,MAAM,OAAO;AAE3C,YAAM,MAAM,GAAG,SAAS,MAAM;AAAA,MAAC,CAAC;AAChC,YAAM,MAAM,IAAI,KAAK,KAAK;AAAA,IAC5B;AAEA,UAAM,GAAG,SAAS,CAAC,SAAS,OAAO,IAAI,CAAC;AAAA,EAC1C,CAAC;AACH;;;AE/NA,IAAAC,6BAAyC;AAIzC,IAAM,uBAAuB,KAAK;AAGlC,IAAM,aAAN,MAAiB;AAAA,EAIf,YAA6B,QAAQ,sBAAsB;AAA9B;AAAA,EAA+B;AAAA,EAA/B;AAAA,EAHrB,SAAmB,CAAC;AAAA,EACpB,OAAO;AAAA,EAIf,IAAI,OAAqB;AACvB,QAAI,CAAC,MAAO;AACZ,SAAK,OAAO,KAAK,KAAK;AACtB,SAAK,QAAQ,MAAM;AACnB,WAAO,KAAK,OAAO,KAAK,SAAS,KAAK,OAAO,SAAS,GAAG;AACvD,WAAK,QAAQ,KAAK,OAAO,MAAM,EAAG;AAAA,IACpC;AAEA,QAAI,KAAK,OAAO,KAAK,OAAO;AAC1B,YAAM,SAAS,KAAK,OAAO,KAAK,EAAE;AAClC,WAAK,SAAS,CAAC,OAAO,MAAM,CAAC,KAAK,KAAK,CAAC;AACxC,WAAK,OAAO,KAAK,OAAO,CAAC,EAAG;AAAA,IAC9B;AAAA,EACF;AAAA,EAEA,MAAc;AACZ,UAAM,SAAS,KAAK,OAAO,KAAK,EAAE;AAClC,WAAO,OAAO,SAAS,KAAK,QAAQ,OAAO,MAAM,CAAC,KAAK,KAAK,IAAI;AAAA,EAClE;AACF;AAuCO,IAAM,mBAAN,MAAuB;AAAA,EACX,QAAQ,oBAAI,IAA4B;AAAA,EACjD,MAAM;AAAA,EACN,mBAAmB;AAAA;AAAA,EAG3B,MAAM,MAA4C;AAChD,SAAK,eAAe;AACpB,UAAM,KAAK,SAAS,EAAE,KAAK,GAAG,IAAI,KAAK,IAAI,EAAE,SAAS,EAAE,CAAC;AACzD,UAAM,YAAQ,kCAAM,KAAK,MAAM,KAAK,MAAM;AAAA,MACxC,KAAK,KAAK;AAAA,MACV,KAAK,KAAK;AAAA,MACV,UAAU,QAAQ,aAAa;AAAA,MAC/B,aAAa;AAAA,MACb,OAAO,CAAC,UAAU,QAAQ,MAAM;AAAA,IAClC,CAAC;AAED,UAAM,OAAuB;AAAA,MAC3B;AAAA,MACA,SAAS,KAAK;AAAA,MACd,OAAO,KAAK,SAAS;AAAA,MACrB;AAAA,MACA,QAAQ,IAAI,WAAW;AAAA,MACvB,QAAQ,IAAI,WAAW;AAAA,MACvB,QAAQ;AAAA,MACR,UAAU;AAAA,MACV,QAAQ;AAAA,MACR,WAAW,KAAK,IAAI;AAAA,MACpB,SAAS;AAAA,IACX;AACA,SAAK,MAAM,IAAI,IAAI,IAAI;AAEvB,UAAM,QAAQ,GAAG,QAAQ,CAAC,MAAc,KAAK,OAAO,IAAI,EAAE,SAAS,OAAO,CAAC,CAAC;AAC5E,UAAM,QAAQ,GAAG,QAAQ,CAAC,MAAc,KAAK,OAAO,IAAI,EAAE,SAAS,OAAO,CAAC,CAAC;AAC5E,UAAM,GAAG,SAAS,CAAC,QAAQ,KAAK,OAAO,IAAI,iBAAiB,IAAI,OAAO;AAAA,CAAI,CAAC;AAC5E,UAAM,GAAG,QAAQ,CAAC,MAAM,WAAW;AACjC,WAAK,WAAW;AAChB,WAAK,SAAS;AACd,WAAK,UAAU,KAAK,IAAI;AAExB,UAAI,KAAK,WAAW,SAAU,MAAK,SAAS;AAAA,IAC9C,CAAC;AAED,UAAM,MAAM;AAEZ,WAAO,KAAK,SAAS,IAAI;AAAA,EAC3B;AAAA;AAAA,EAGA,OAAO,QAA4D;AACjE,UAAM,OAAO,KAAK,MAAM,IAAI,MAAM;AAClC,QAAI,CAAC,KAAM,QAAO,EAAE,OAAO,+CAAY,MAAM,GAAG;AAChD,WAAO,KAAK,SAAS,IAAI;AAAA,EAC3B;AAAA;AAAA,EAGA,KAAK,QAAgB,OAAmE;AACtF,UAAM,OAAO,KAAK,MAAM,IAAI,MAAM;AAClC,QAAI,CAAC,KAAM,QAAO,EAAE,OAAO,+CAAY,MAAM,GAAG;AAChD,QAAI,UAAU,UAAa,KAAK,UAAU,OAAO;AAC/C,aAAO,EAAE,OAAO,+CAAY,MAAM,GAAG;AAAA,IACvC;AACA,QAAI,KAAK,WAAW,WAAW;AAC7B,WAAK,SAAS;AACd,WAAK,UAAU,KAAK,IAAI;AACxB,eAAS,KAAK,MAAM,GAAG;AAAA,IACzB;AACA,WAAO,KAAK,SAAS,IAAI;AAAA,EAC3B;AAAA;AAAA,EAGA,KAAK,OAAiD;AACpD,UAAM,MAAM,CAAC,GAAG,KAAK,MAAM,OAAO,CAAC;AACnC,YAAQ,UAAU,SAAY,MAAM,IAAI,OAAO,CAAC,MAAM,EAAE,UAAU,KAAK,GAAG;AAAA,MAAI,CAAC,MAC7E,KAAK,SAAS,CAAC;AAAA,IACjB;AAAA,EACF;AAAA;AAAA,EAGA,aAAmB;AACjB,eAAW,QAAQ,KAAK,MAAM,OAAO,GAAG;AACtC,UAAI,KAAK,WAAW,WAAW;AAC7B,aAAK,SAAS;AACd,iBAAS,KAAK,MAAM,GAAG;AAAA,MACzB;AAAA,IACF;AAAA,EACF;AAAA,EAEQ,SAAS,MAA8C;AAC7D,WAAO;AAAA,MACL,SAAS,KAAK;AAAA,MACd,SAAS,KAAK;AAAA,MACd,QAAQ,KAAK;AAAA,MACb,SAAS,KAAK,WAAW;AAAA,MACzB,WAAW,KAAK;AAAA,MAChB,QAAQ,KAAK;AAAA,MACb,QAAQ,KAAK,OAAO,IAAI;AAAA,MACxB,QAAQ,KAAK,OAAO,IAAI;AAAA,MACxB,cAAc,KAAK,WAAW,KAAK,IAAI,KAAK,KAAK;AAAA,IACnD;AAAA,EACF;AAAA,EAEQ,iBAAuB;AAC7B,QAAI,KAAK,iBAAkB;AAC3B,SAAK,mBAAmB;AACxB,YAAQ,KAAK,QAAQ,MAAM,KAAK,WAAW,CAAC;AAAA,EAC9C;AACF;;;ACxJA,IAAM,aAAa,OAAO,OAAO;AA6D1B,SAAS,aAAa,KAAwC;AACnE,MAAI,CAAC,OAAO,IAAI,WAAW,EAAG,QAAO;AACrC,MAAI;AACF,WAAO,IAAI,YAAY,SAAS,EAAE,OAAO,KAAK,CAAC,EAAE,OAAO,GAAG;AAAA,EAC7D,QAAQ;AACN,eAAW,OAAO,CAAC,OAAO,QAAQ,aAAa,QAAQ,GAAG;AACxD,UAAI;AACF,eAAO,IAAI,YAAY,GAAG,EAAE,OAAO,GAAG;AAAA,MACxC,QAAQ;AAAA,MAER;AAAA,IACF;AACA,WAAO,IAAI,SAAS,QAAQ;AAAA,EAC9B;AACF;AA4GA,IAAM,aAAa,IAAI,iBAAiB;;;AJ/LxC,eAAe,OAAO,SAAiB,MAAgB,UAAU,MAA8B;AAC7F,QAAM,MAAM,MAAM,UAAU,OAAO,CAAC,MAAM,wBAAwB,GAAG,IAAI,GAAG;AAAA,IAC1E,KAAK;AAAA,IACL,WAAW;AAAA,IACX,WAAW,OAAO,OAAO;AAAA,IACzB,KAAK,EAAE,GAAG,QAAQ,KAAK,qBAAqB,IAAI;AAAA,EAClD,CAAC;AACD,SAAO;AAAA,IACL,QAAQ,IAAI,UAAU;AAAA,IACtB,QAAQ,aAAa,IAAI,MAAM;AAAA,IAC/B,QAAQ,aAAa,IAAI,MAAM,MAAM,IAAI,OAAO,WAAW;AAAA,EAC7D;AACF;AAQA,eAAe,gBAAgB,WAAmB,SAAmC;AACnF,MAAI,MAAM;AACV,MAAI,SAAS;AACX,UAAM,MAAW,aAAQ,WAAW,OAAO;AAC3C,QAAI;AACF,YAAS,YAAS,GAAG,EAAE,YAAY,IAAI,MAAW,aAAQ,GAAG;AAAA,IAC/D,QAAQ;AACN,YAAM;AAAA,IACR;AAAA,EACF;AACA,QAAM,MAAM,MAAM,OAAO,KAAK,CAAC,aAAa,iBAAiB,GAAG,GAAK;AACrE,MAAI,IAAI,WAAW,KAAK,IAAI,OAAO,KAAK,EAAG,QAAO,IAAI,OAAO,KAAK;AAClE,SAAO;AACT;AAQA,SAAS,WAAW,WAAmB,KAAkC;AACvE,MAAI,OAAO,QAAQ,SAAU,QAAO;AACpC,QAAM,IAAI,IAAI,KAAK;AACnB,MAAI,CAAC,EAAG,QAAO;AACf,QAAM,OAAY,aAAQ,SAAS;AACnC,QAAM,MAAW,aAAQ,MAAM,CAAC;AAChC,QAAM,MAAW,cAAS,MAAM,GAAG;AACnC,MAAI,IAAI,WAAW,IAAI,KAAU,gBAAW,GAAG,EAAG,QAAO;AACzD,SAAO;AACT;AAGA,SAAS,WAAW,UAAkB,WAAmB,SAAiC;AACxF,MAAI,CAAC,QAAS,QAAO;AACrB,QAAM,MAAW,aAAQ,WAAW,OAAO;AAC3C,MAAI,MAAW,cAAS,UAAU,GAAG;AACrC,MAAI,QAAQ,GAAI,QAAO;AACvB,MAAI,IAAI,WAAW,IAAI,KAAU,gBAAW,GAAG,GAAG;AAIhD,QAAI;AACF,YAAM,WAAc,gBAAa,QAAQ;AACzC,YAAM,UAAa,gBAAa,GAAG;AACnC,YAAM,OAAY,cAAS,UAAU,OAAO;AAC5C,UAAI,SAAS,GAAI,QAAO;AACxB,UAAI,CAAC,KAAK,WAAW,IAAI,KAAK,CAAM,gBAAW,IAAI,EAAG,QAAO;AAAA,IAC/D,QAAQ;AAAA,IAER;AACA,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAGO,SAAS,WAAW,WAAmB;AAC5C,SAAO,eAAe,QAAQ,MAA+B;AAC3D,UAAM,WAAW,KAAK,OAAO,OAAO,KAAK,IAAI,IAAI;AACjD,UAAM,WAAW,KAAK,IAAI,OAAO,KAAK,aAAa,EAAE,GAAG,GAAG;AAE3D,QAAI,WAAW,EAAG,QAAO,EAAE,OAAO,8BAAoB;AAEtD,UAAM,OAAO,MAAM,gBAAgB,WAAW,WAAW,WAAW,KAAK,IAAI,KAAK,QAAQ;AAC1F,UAAM,MAAM;AAAA,MACV;AAAA,MACA,KAAK,QAAQ;AAAA,MACb;AAAA,MACA;AAAA,IACF;AACA,UAAM,WAAW,WAAW,MAAM,WAAW,QAAQ;AACrD,QAAI,SAAU,KAAI,KAAK,MAAM,QAAQ;AAErC,UAAM,MAAM,MAAM,OAAO,MAAM,GAAG;AAClC,QAAI,IAAI,WAAW,GAAG;AACpB,aAAO,EAAE,QAAQ,IAAI,UAAU,kBAAkB,KAAK,EAAE,MAAM,GAAG,GAAG,EAAE;AAAA,IACxE;AACA,UAAM,UAAU,IAAI,OACjB,MAAM,IAAI,EACV,IAAI,CAAC,MAAM,EAAE,QAAQ,OAAO,EAAE,CAAC,EAC/B,OAAO,CAAC,MAAM,EAAE,SAAS,CAAC,EAC1B,IAAI,CAAC,SAAS;AACb,YAAM,QAAQ,KAAK,MAAM,GAAM;AAC/B,UAAI,MAAM,WAAW,GAAG;AACtB,eAAO,EAAE,MAAM,MAAM,CAAC,GAAG,QAAQ,MAAM,CAAC,GAAG,MAAM,MAAM,CAAC,GAAG,SAAS,MAAM,CAAC,EAAE;AAAA,MAC/E;AACA,aAAO,EAAE,KAAK,KAAK;AAAA,IACrB,CAAC;AACH,WAAO,EAAE,MAAM,OAAO,QAAQ,QAAQ,QAAQ;AAAA,EAChD;AACF;AAGO,SAAS,cAAc,WAAmB;AAC/C,SAAO,eAAe,WAAW,MAA+B;AAC9D,UAAM,WAAW,KAAK,OAAO,OAAO,KAAK,IAAI,IAAI;AACjD,UAAM,OAAO,MAAM,gBAAgB,WAAW,WAAW,WAAW,KAAK,IAAI,KAAK,QAAQ;AAC1F,UAAM,MAAM,MAAM,OAAO,MAAM,CAAC,UAAU,aAAa,GAAG,GAAK;AAE/D,QAAI,IAAI,WAAW,GAAG;AACpB,YAAM,MAAM,IAAI;AAChB,UAAI,IAAI,YAAY,EAAE,SAAS,sBAAsB,GAAG;AACtD,eAAO,EAAE,OAAO,gCAAY;AAAA,MAC9B;AACA,aAAO,EAAE,QAAQ,OAAO,qBAAqB,KAAK,EAAE,MAAM,GAAG,GAAG,EAAE;AAAA,IACpE;AAIA,UAAM,QAAQ,IAAI,OACf,MAAM,IAAI,EACV,IAAI,CAAC,MAAM,EAAE,QAAQ,OAAO,EAAE,CAAC,EAC/B,OAAO,CAAC,MAAM,EAAE,SAAS,CAAC;AAC7B,UAAM,UAAU,MAAM,IAAI,CAAC,SAAS;AAClC,YAAM,SAAS,KAAK,MAAM,GAAG,CAAC,EAAE,KAAK;AAGrC,YAAM,QAAQ,KAAK,MAAM,GAAG,CAAC,EAAE,KAAK;AACpC,YAAM,WAAW,KAAK,MAAM,GAAG,CAAC,EAAE,KAAK;AACvC,UAAI,OAAO,KAAK,MAAM,CAAC;AAEvB,YAAM,cAAc,KAAK,QAAQ,MAAM;AACvC,UAAI,eAAe,EAAG,QAAO,KAAK,MAAM,cAAc,CAAC;AACvD,aAAO,EAAE,QAAQ,MAAM,OAAO,SAAS;AAAA,IACzC,CAAC;AACD,WAAO,EAAE,MAAM,OAAO,QAAQ,WAAW,GAAG,SAAS,OAAO,QAAQ,OAAO;AAAA,EAC7E;AACF;AAGO,SAAS,YAAY,WAAmB;AAC7C,SAAO,eAAe,SAAS,MAA+B;AAC5D,UAAM,SAAS,KAAK,WAAW;AAC/B,UAAM,WAAW,KAAK,OAAO,OAAO,KAAK,IAAI,IAAI;AACjD,UAAM,eAAe,KAAK,IAAI,OAAO,KAAK,WAAW,CAAC,GAAG,EAAE;AAE3D,UAAM,OAAO,MAAM,gBAAgB,WAAW,WAAW,WAAW,KAAK,IAAI,KAAK,QAAQ;AAC1F,UAAM,MAAM,CAAC,MAAM;AACnB,QAAI,OAAQ,KAAI,KAAK,UAAU;AAC/B,QAAI,KAAK,KAAK,YAAY,EAAE;AAC5B,UAAM,WAAW,WAAW,MAAM,WAAW,QAAQ;AACrD,QAAI,SAAU,KAAI,KAAK,MAAM,QAAQ;AAErC,UAAM,MAAM,MAAM,OAAO,MAAM,GAAG;AAClC,QAAI,IAAI,WAAW,GAAG;AACpB,aAAO,EAAE,QAAQ,IAAI,UAAU,mBAAmB,KAAK,EAAE,MAAM,GAAG,GAAG,EAAE;AAAA,IACzE;AACA,WAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA,MAAM,YAAY;AAAA,MAClB,MAAM,IAAI,OAAO,MAAM,GAAG,GAAK;AAAA,MAC/B,WAAW,IAAI,OAAO,SAAS;AAAA,IACjC;AAAA,EACF;AACF;AAGO,SAAS,iBAAiB,UAAwB,WAAyB;AAChF,QAAM,SAAS,WAAW,SAAS;AACnC,WAAS;AAAA,IACP,CAAC,SAAS,OAAO,IAAI;AAAA,IACrB,EAAE,UAAU,OAAO,WAAW,OAAO,cAAc,CAAC,KAAK,GAAG,kBAAkB,MAAM;AAAA,IACpF;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,mJAA2B;AAAA,YAChE,WAAW,EAAE,MAAM,WAAW,aAAa,qFAAyB;AAAA,UACtE;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,QAAM,YAAY,cAAc,SAAS;AACzC,WAAS;AAAA,IACP,CAAC,SAAS,UAAU,IAAI;AAAA,IACxB,EAAE,UAAU,OAAO,WAAW,OAAO,cAAc,CAAC,KAAK,GAAG,kBAAkB,MAAM;AAAA,IACpF;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,6IAA0B;AAAA,UACjE;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,QAAM,UAAU,YAAY,SAAS;AACrC,WAAS;AAAA,IACP,CAAC,SAAS,QAAQ,IAAI;AAAA,IACtB,EAAE,UAAU,OAAO,WAAW,OAAO,cAAc,CAAC,KAAK,GAAG,kBAAkB,MAAM;AAAA,IACpF;AAAA,MACE,MAAM;AAAA,MACN,UAAU;AAAA,QACR,MAAM;AAAA,QACN,aAAa;AAAA,QACb,YAAY;AAAA,UACV,MAAM;AAAA,UACN,YAAY;AAAA,YACV,QAAQ,EAAE,MAAM,WAAW,aAAa,oFAAwB;AAAA,YAChE,MAAM,EAAE,MAAM,UAAU,aAAa,uIAAyB;AAAA,YAC9D,SAAS,EAAE,MAAM,WAAW,aAAa,kDAAe;AAAA,UAC1D;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;AAkBA,IAAM,kBAAkB;AAExB,SAAS,SAAS,KAAgC;AAChD,QAAM,SAAS,IAAI,OAAO,KAAK,EAAE,MAAM,CAAC,eAAe;AACvD,QAAM,SAAS,IAAI,OAAO,KAAK,EAAE,MAAM,CAAC,eAAe;AACvD,MAAI,IAAI,WAAW,GAAG;AACpB,WAAO;AAAA,MACL,IAAI;AAAA,MACJ;AAAA,MACA;AAAA,MACA,QAAQ,UAAU,UAAU,gCAAY,MAAM,GAAG,GAAG;AAAA,IACtD;AAAA,EACF;AACA,SAAO,EAAE,IAAI,MAAM,QAAQ,OAAO;AACpC;AAEA,SAAS,OAAO,OAA4B;AAC1C,SAAO,EAAE,IAAI,OAAO,QAAQ,IAAI,QAAQ,IAAI,MAAM;AACpD;AAGO,SAAS,cAAc,MAA6B;AACzD,QAAM,IAAI,KAAK,KAAK;AACpB,MAAI,CAAC,EAAG,QAAO;AACf,MAAI,EAAE,WAAW,GAAG,EAAG,QAAO;AAE9B,MAAI,6BAA6B,KAAK,CAAC,EAAG,QAAO;AACjD,MAAI,EAAE,SAAS,IAAI,KAAK,EAAE,SAAS,IAAI,KAAK,EAAE,SAAS,GAAG,KAAK,EAAE,SAAS,OAAO,GAAG;AAClF,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAGA,SAAS,eAAe,KAAwB;AAC9C,QAAM,OAAO,MAAM,QAAQ,GAAG,IAAI,MAAM,OAAO,QAAQ,WAAW,CAAC,GAAG,IAAI,CAAC;AAC3E,SAAO,KAAK,IAAI,CAAC,MAAM,OAAO,CAAC,EAAE,KAAK,CAAC,EAAE,OAAO,CAAC,MAAM,EAAE,SAAS,CAAC;AACrE;AAGA,SAAS,YAAY,UAAkB,WAAmB,OAA2B;AACnF,QAAM,MAAgB,CAAC;AACvB,aAAW,KAAK,OAAO;AACrB,UAAM,OAAO,WAAW,UAAU,WAAW,CAAC;AAC9C,QAAI,KAAM,KAAI,KAAK,IAAI;AAAA,EACzB;AACA,SAAO;AACT;AAGO,SAAS,gBAAgB,WAAmB;AACjD,SAAO,eAAe,aAAa,MAA+B;AAChE,UAAM,OAAO,MAAM,gBAAgB,WAAW,WAAW,WAAW,KAAK,IAAI,CAAC;AAC9E,UAAM,OAAO,MAAM;AAAA,MACjB;AAAA,MACA;AAAA,QACE;AAAA,QACA;AAAA;AAAA,QAEA;AAAA,QACA;AAAA,MACF;AAAA,MACA;AAAA,IACF;AACA,QAAI,KAAK,WAAW,GAAG;AACrB,YAAM,MAAM,KAAK;AACjB,UAAI,IAAI,YAAY,EAAE,SAAS,sBAAsB,EAAG,QAAO,EAAE,OAAO,gCAAY;AACpF,aAAO,EAAE,QAAQ,OAAO,qBAAqB,KAAK,EAAE,MAAM,GAAG,GAAG,EAAE;AAAA,IACpE;AAGA,UAAM,OAAO,MAAM,OAAO,MAAM,CAAC,gBAAgB,WAAW,MAAM,GAAG,GAAK;AAC1E,UAAM,WAAW,KAAK,WAAW;AACjC,QAAI,UAAU,WAAW,KAAK,KAAK,OAAO,KAAK;AAC/C,QAAI,UAAU;AACZ,YAAM,MAAM,MAAM,OAAO,MAAM,CAAC,aAAa,WAAW,MAAM,GAAG,GAAK;AACtE,gBAAU,IAAI,WAAW,IAAI,IAAI,OAAO,KAAK,IAAI;AAAA,IACnD;AAEA,UAAM,WAAW,KAAK,OACnB,MAAM,IAAI,EACV,IAAI,CAAC,MAAM,EAAE,QAAQ,OAAO,EAAE,CAAC,EAC/B,OAAO,CAAC,MAAM,EAAE,SAAS,CAAC,EAC1B,IAAI,CAAC,SAAS;AACb,YAAM,CAAC,MAAM,UAAU,QAAQ,IAAI,KAAK,MAAM,GAAI;AAClD,aAAO;AAAA,QACL;AAAA,QACA,SAAS,UAAU,KAAK,MAAM,OAAQ,CAAC,CAAC,WAAW,SAAS;AAAA,QAC5D,GAAI,WAAW,EAAE,SAAS,IAAI,CAAC;AAAA,MACjC;AAAA,IACF,CAAC;AACH,WAAO,EAAE,MAAM,SAAS,UAAU,SAAS;AAAA,EAC7C;AACF;AAGO,SAAS,iBAAiB,MAA6B;AAC5D,QAAM,IAAI,KAAK,KAAK;AACpB,MAAI,CAAC,EAAG,QAAO;AACf,MAAI,EAAE,WAAW,GAAG,EAAG,QAAO;AAE9B,MAAI,6BAA6B,KAAK,CAAC,EAAG,QAAO;AACjD,MAAI,EAAE,SAAS,IAAI,KAAK,EAAE,SAAS,IAAI,KAAK,EAAE,SAAS,GAAG,KAAK,EAAE,SAAS,OAAO,GAAG;AAClF,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAQO,SAAS,YAAY,WAAmB;AAC7C,SAAO,eAAe,SAAS,MAA+B;AAC5D,UAAM,OAAO,MAAM,gBAAgB,WAAW,WAAW,WAAW,KAAK,IAAI,CAAC;AAC9E,UAAM,OAAO,MAAM;AAAA,MACjB;AAAA,MACA;AAAA,QACE;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,MACA;AAAA,IACF;AACA,QAAI,KAAK,WAAW,GAAG;AACrB,YAAM,MAAM,KAAK;AACjB,UAAI,IAAI,YAAY,EAAE,SAAS,sBAAsB,EAAG,QAAO,EAAE,OAAO,gCAAY;AACpF,aAAO,EAAE,QAAQ,OAAO,2BAA2B,KAAK,EAAE,MAAM,GAAG,GAAG,EAAE;AAAA,IAC1E;AAEA,UAAM,OAAO,MAAM,OAAO,MAAM,CAAC,gBAAgB,WAAW,MAAM,GAAG,GAAK;AAC1E,UAAM,WAAW,KAAK,WAAW;AACjC,QAAI,UAAU,WAAW,KAAK,KAAK,OAAO,KAAK;AAC/C,QAAI,UAAU;AACZ,YAAM,MAAM,MAAM,OAAO,MAAM,CAAC,aAAa,WAAW,MAAM,GAAG,GAAK;AACtE,gBAAU,IAAI,WAAW,IAAI,IAAI,OAAO,KAAK,IAAI;AAAA,IACnD;AAEA,UAAM,QAAuE,CAAC;AAC9E,UAAM,SAAkC,CAAC;AACzC,UAAM,OAAiD,CAAC;AAExD,eAAW,OAAO,KAAK,OAAO,MAAM,IAAI,GAAG;AACzC,YAAM,OAAO,IAAI,QAAQ,OAAO,EAAE;AAClC,UAAI,CAAC,KAAM;AACX,YAAM,CAAC,SAAS,OAAO,UAAU,UAAU,MAAM,IAAI,KAAK,MAAM,GAAI;AACpE,UAAI,CAAC,WAAW,CAAC,MAAO;AACxB,UAAI,QAAQ,WAAW,aAAa,GAAG;AACrC,cAAM,KAAK;AAAA,UACT,MAAM;AAAA,UACN,SAAS,UAAU,KAAK,MAAM,OAAQ,CAAC,CAAC,WAAW,UAAU;AAAA,UAC7D,GAAI,WAAW,EAAE,SAAS,IAAI,CAAC;AAAA,QACjC,CAAC;AACD;AAAA,MACF;AACA,UAAI,QAAQ,WAAW,eAAe,GAAG;AAGvC,YAAI,QAAQ,SAAS,OAAO,EAAG;AAC/B,eAAO,KAAK,EAAE,MAAM,MAAM,CAAC;AAC3B;AAAA,MACF;AACA,UAAI,QAAQ,WAAW,YAAY,GAAG;AACpC,aAAK,KAAK,EAAE,MAAM,OAAO,GAAI,SAAS,EAAE,OAAO,IAAI,CAAC,EAAG,CAAC;AAAA,MAC1D;AAAA,IACF;AAEA,WAAO,EAAE,MAAM,SAAS,UAAU,OAAO,QAAQ,KAAK;AAAA,EACxD;AACF;AAQO,SAAS,YAAY,WAAmB;AAC7C,SAAO,eAAe,SAAS,MAA+B;AAC5D,UAAM,OAAO,OAAO,KAAK,QAAQ,EAAE,EAAE,KAAK;AAC1C,UAAM,UAAU,iBAAiB,IAAI;AACrC,QAAI,QAAS,QAAO,EAAE,OAAO,QAAQ;AACrC,UAAM,WAAW,KAAK,OAAO,OAAO,KAAK,IAAI,IAAI;AACjD,UAAM,eAAe,KAAK,IAAI,OAAO,KAAK,WAAW,CAAC,GAAG,EAAE;AAE3D,UAAM,OAAO,MAAM,gBAAgB,WAAW,WAAW,WAAW,KAAK,IAAI,KAAK,QAAQ;AAE1F,UAAM,UAAU,MAAM;AAAA,MACpB;AAAA,MACA;AAAA,QACE;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,MACA;AAAA,IACF;AACA,QAAI,QAAQ,WAAW,GAAG;AACxB,aAAO,EAAE,QAAQ,QAAQ,UAAU,mBAAmB,KAAK,EAAE,MAAM,GAAG,GAAG,EAAE;AAAA,IAC7E;AACA,UAAM,QAAQ,QAAQ,OAAO,MAAM,GAAM;AACzC,UAAM,SAAS;AAAA,MACb,OAAO,MAAM,CAAC,KAAK,IAAI,KAAK;AAAA,MAC5B,QAAQ,MAAM,CAAC,KAAK,IAAI,KAAK;AAAA,MAC7B,SAAS,MAAM,CAAC,KAAK,IAAI,KAAK;AAAA,MAC9B,cAAc,MAAM,CAAC,KAAK,IAAI,KAAK;AAAA,MACnC,OAAO,MAAM,CAAC,KAAK,IAAI,KAAK;AAAA,MAC5B,UAAU,MAAM,CAAC,KAAK,IAAI,KAAK;AAAA,MAC/B,OAAO,MAAM,MAAM,CAAC,EAAE,KAAK,GAAM,KAAK,IAAI,KAAK;AAAA,IACjD;AACA,QAAI,CAAC,OAAO,KAAM,QAAO,EAAE,OAAO,uCAAS;AAE3C,UAAM,WAAW,MAAM;AAAA,MACrB;AAAA,MACA,CAAC,QAAQ,iBAAiB,aAAa,kBAAkB,OAAO,IAAI;AAAA,MACpE;AAAA,IACF;AACA,UAAM,QAAQ,SAAS,OACpB,MAAM,IAAI,EACV,IAAI,CAAC,MAAM,EAAE,QAAQ,OAAO,EAAE,CAAC,EAC/B,OAAO,CAAC,MAAM,EAAE,KAAK,EAAE,SAAS,CAAC,EACjC,IAAI,CAAC,SAAS;AACb,YAAM,OAAO,KAAK,MAAM,GAAI;AAE5B,YAAM,UAAU,KAAK,CAAC,KAAK,IAAI,KAAK;AACpC,YAAM,QAAQ,KAAK,KAAK,SAAS,CAAC,KAAK,IAAI,KAAK;AAChD,aAAO,EAAE,QAAQ,KAAK;AAAA,IACxB,CAAC,EACA,OAAO,CAAC,MAAM,EAAE,KAAK,SAAS,CAAC;AAElC,UAAM,MAAM,CAAC,QAAQ,aAAa,kBAAkB,KAAK,YAAY,IAAI,OAAO,IAAI;AACpF,UAAM,WAAW,WAAW,MAAM,WAAW,QAAQ;AACrD,QAAI,SAAU,KAAI,KAAK,MAAM,QAAQ;AACrC,UAAM,UAAU,MAAM,OAAO,MAAM,KAAK,GAAK;AAC7C,QAAI,QAAQ,WAAW,GAAG;AACxB,aAAO,EAAE,QAAQ,QAAQ,UAAU,wBAAwB,KAAK,EAAE,MAAM,GAAG,GAAG,EAAE;AAAA,IAClF;AAEA,WAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA;AAAA,MACA,MAAM,YAAY;AAAA,MAClB,MAAM,QAAQ,OAAO,MAAM,GAAG,GAAK;AAAA,MACnC,WAAW,QAAQ,OAAO,SAAS;AAAA,IACrC;AAAA,EACF;AACF;AAGO,SAAS,aAAa,WAAmB;AAC9C,SAAO,eAAe,UAAU,MAA+B;AAC7D,UAAM,QAAQ,eAAe,KAAK,KAAK;AACvC,UAAM,OAAO,MAAM,gBAAgB,WAAW,WAAW,WAAW,KAAK,IAAI,CAAC;AAC9E,UAAM,QAAQ,MAAM,SAAS,IAAI,YAAY,MAAM,WAAW,KAAK,IAAI,CAAC;AACxE,QAAI,MAAM,SAAS,KAAK,MAAM,WAAW,EAAG,QAAO,OAAO,4CAAS;AACnE,UAAM,MAAM,MAAM,SAAS,IAAI,CAAC,OAAO,MAAM,GAAG,KAAK,IAAI,CAAC,OAAO,IAAI;AACrE,WAAO,SAAS,MAAM,OAAO,MAAM,KAAK,GAAK,CAAC;AAAA,EAChD;AACF;AAGO,SAAS,eAAe,WAAmB;AAChD,SAAO,eAAe,YAAY,MAA+B;AAC/D,UAAM,QAAQ,eAAe,KAAK,KAAK;AACvC,UAAM,OAAO,MAAM,gBAAgB,WAAW,WAAW,WAAW,KAAK,IAAI,CAAC;AAC9E,UAAM,QAAQ,MAAM,SAAS,IAAI,YAAY,MAAM,WAAW,KAAK,IAAI,CAAC;AACxE,QAAI,MAAM,SAAS,KAAK,MAAM,WAAW,EAAG,QAAO,OAAO,4CAAS;AACnE,UAAM,MAAM,MAAM,SAAS,IAAI,CAAC,WAAW,YAAY,MAAM,GAAG,KAAK,IAAI,CAAC,SAAS,IAAI;AACvF,WAAO,SAAS,MAAM,OAAO,MAAM,KAAK,GAAK,CAAC;AAAA,EAChD;AACF;AAGO,SAAS,cAAc,WAAmB;AAC/C,SAAO,eAAe,WAAW,MAA+B;AAC9D,UAAM,UAAU,OAAO,KAAK,WAAW,EAAE,EAAE,KAAK;AAChD,QAAI,CAAC,QAAS,QAAO,OAAO,kDAAU;AACtC,UAAM,OAAO,MAAM,gBAAgB,WAAW,WAAW,WAAW,KAAK,IAAI,CAAC;AAC9E,WAAO,SAAS,MAAM,OAAO,MAAM,CAAC,UAAU,MAAM,OAAO,GAAG,GAAK,CAAC;AAAA,EACtE;AACF;AAGO,SAAS,gBAAgB,WAAmB;AACjD,SAAO,eAAe,aAAa,MAA+B;AAChE,UAAM,SAAS,OAAO,KAAK,UAAU,EAAE,EAAE,KAAK;AAC9C,UAAM,UAAU,cAAc,MAAM;AACpC,QAAI,QAAS,QAAO,OAAO,OAAO;AAClC,UAAM,OAAO,MAAM,gBAAgB,WAAW,WAAW,WAAW,KAAK,IAAI,CAAC;AAC9E,UAAM,MAAM,KAAK,WAAW,OAAO,CAAC,YAAY,MAAM,MAAM,IAAI,CAAC,YAAY,MAAM;AACnF,WAAO,SAAS,MAAM,OAAO,MAAM,KAAK,GAAK,CAAC;AAAA,EAChD;AACF;AAGO,SAAS,YAAY,WAAmB;AAC7C,SAAO,eAAe,SAAS,MAA+B;AAC5D,UAAM,OAAO,MAAM,gBAAgB,WAAW,WAAW,WAAW,KAAK,IAAI,CAAC;AAC9E,WAAO,SAAS,MAAM,OAAO,MAAM,CAAC,MAAM,GAAG,GAAK,CAAC;AAAA,EACrD;AACF;AAGO,SAAS,YAAY,WAAmB;AAC7C,SAAO,eAAe,SAAS,MAA+B;AAC5D,UAAM,OAAO,MAAM,gBAAgB,WAAW,WAAW,WAAW,KAAK,IAAI,CAAC;AAC9E,UAAM,WAAW,MAAM;AAAA,MACrB;AAAA,MACA,CAAC,aAAa,gBAAgB,wBAAwB,MAAM;AAAA,MAC5D;AAAA,IACF;AACA,QAAI,SAAS,WAAW,GAAG;AACzB,YAAM,OAAO,MAAM,OAAO,MAAM,CAAC,gBAAgB,WAAW,MAAM,GAAG,GAAK;AAC1E,YAAM,UAAU,MAAM,OAAO,MAAM,CAAC,QAAQ,GAAG,GAAK;AACpD,YAAM,YACJ,QAAQ,WAAW,KAAK,QAAQ,OAAO,MAAM,IAAI,EAAE,KAAK,CAAC,MAAM,EAAE,KAAK,MAAM,QAAQ;AACtF,UAAI,KAAK,WAAW,KAAK,WAAW;AAClC,eAAO,SAAS,MAAM,OAAO,MAAM,CAAC,QAAQ,MAAM,UAAU,KAAK,OAAO,KAAK,CAAC,GAAG,GAAK,CAAC;AAAA,MACzF;AAAA,IACF;AACA,WAAO,SAAS,MAAM,OAAO,MAAM,CAAC,MAAM,GAAG,GAAK,CAAC;AAAA,EACrD;AACF;AAGO,SAAS,aAAa,WAAmB;AAC9C,SAAO,eAAe,UAAU,MAA+B;AAC7D,UAAM,OAAO,MAAM,gBAAgB,WAAW,WAAW,WAAW,KAAK,IAAI,CAAC;AAC9E,UAAM,UAAU,MAAM,OAAO,MAAM,CAAC,QAAQ,GAAG,GAAK;AACpD,QAAI,QAAQ,WAAW,GAAG;AACxB,YAAM,MAAM,QAAQ;AACpB,UAAI,IAAI,YAAY,EAAE,SAAS,sBAAsB,EAAG,QAAO,OAAO,+BAAW;AACjF,aAAO,QAAQ,OAAO,qBAAqB,KAAK,EAAE,MAAM,GAAG,GAAG,CAAC;AAAA,IACjE;AACA,QAAI,CAAC,QAAQ,OAAO,KAAK,EAAG,QAAO,OAAO,4CAAS;AACnD,WAAO,SAAS,MAAM,OAAO,MAAM,CAAC,SAAS,SAAS,WAAW,QAAQ,GAAG,GAAK,CAAC;AAAA,EACpF;AACF;AAWA,IAAM,iBAAiB,oBAAI,IAAI,CAAC,gBAAgB,QAAQ,OAAO,MAAM,CAAC;AAGtE,IAAM,qBAAqB;AAC3B,IAAM,gBAAgB;AAatB,SAAS,aAAa,SAA0D;AAC9E,QAAM,SAAc,UAAK,SAAS,MAAM;AACxC,MAAI;AACF,QAAI;AACJ,QAAO,YAAS,MAAM,EAAE,YAAY,GAAG;AACrC,iBAAgB,UAAK,QAAQ,MAAM;AAAA,IACrC,OAAO;AACL,YAAM,MAAS,gBAAa,QAAQ,OAAO;AAC3C,YAAM,IAAI,oBAAoB,KAAK,GAAG;AACtC,UAAI,CAAC,EAAG,QAAO,CAAC;AAChB,iBAAgB,UAAU,aAAQ,SAAS,EAAE,CAAC,EAAE,KAAK,CAAC,GAAG,MAAM;AAAA,IACjE;AACA,UAAM,OAAU,gBAAa,UAAU,OAAO,EAAE,KAAK;AACrD,UAAM,SAAS;AACf,QAAI,KAAK,WAAW,MAAM,EAAG,QAAO,EAAE,QAAQ,KAAK,MAAM,OAAO,MAAM,GAAG,UAAU,MAAM;AACzF,QAAI,oBAAoB,KAAK,IAAI,EAAG,QAAO,EAAE,QAAQ,KAAK,MAAM,GAAG,CAAC,GAAG,UAAU,KAAK;AAAA,EACxF,QAAQ;AAAA,EAER;AACA,SAAO,CAAC;AACV;AAQO,SAAS,aACd,WACA,QAAQ,oBACuC;AAC/C,QAAM,OAAY,aAAQ,SAAS;AACnC,QAAM,WAAW,OAAO,SAAS,KAAK,IAClC,KAAK,IAAI,KAAK,IAAI,KAAK,MAAM,KAAK,GAAG,CAAC,GAAG,CAAC,IAC1C;AACJ,QAAM,QAAuB,CAAC;AAC9B,QAAM,QAA+C,CAAC,EAAE,KAAK,MAAM,OAAO,EAAE,CAAC;AAC7E,MAAI,UAAU;AACd,MAAI,YAAY;AAEhB,SAAO,MAAM,SAAS,GAAG;AACvB,UAAM,EAAE,KAAK,OAAO,EAAE,IAAI,MAAM,IAAI;AACpC,eAAW;AACX,QAAI,UAAU,eAAe;AAC3B,kBAAY;AACZ;AAAA,IACF;AACA,UAAM,MAAW,cAAS,MAAM,GAAG,EAAE,MAAW,QAAG,EAAE,KAAK,GAAG;AAC7D,QAAO,cAAgB,UAAK,KAAK,MAAM,CAAC,GAAG;AACzC,YAAM,KAAK;AAAA,QACT,MAAM;AAAA,QACN,MAAM,MAAW,cAAS,GAAG,IAAS,cAAS,IAAI;AAAA,QACnD,GAAG,aAAa,GAAG;AAAA,MACrB,CAAC;AAAA,IACH;AACA,QAAI,KAAK,SAAU;AACnB,QAAI;AACJ,QAAI;AACF,gBAAa,eAAY,KAAK,EAAE,eAAe,KAAK,CAAC;AAAA,IACvD,QAAQ;AACN;AAAA,IACF;AACA,eAAW,KAAK,SAAS;AAEvB,UAAI,CAAC,EAAE,YAAY,KAAK,eAAe,IAAI,EAAE,IAAI,EAAG;AACpD,YAAM,KAAK,EAAE,KAAU,UAAK,KAAK,EAAE,IAAI,GAAG,OAAO,IAAI,EAAE,CAAC;AAAA,IAC1D;AAAA,EACF;AAEA,QAAM;AAAA,IAAK,CAAC,GAAG,MACb,EAAE,SAAS,KAAK,KAAK,EAAE,SAAS,KAAK,IAAI,EAAE,KAAK,cAAc,EAAE,IAAI;AAAA,EACtE;AACA,SAAO,EAAE,OAAO,GAAI,YAAY,EAAE,WAAW,KAAK,IAAI,CAAC,EAAG;AAC5D;AAGO,SAAS,aAAa,WAAmB;AAC9C,SAAO,eAAe,UAAU,MAA+B;AAC7D,UAAM,OAAY,aAAQ,SAAS;AACnC,QAAI;AACF,UAAI,CAAI,YAAS,IAAI,EAAE,YAAY,EAAG,QAAO,EAAE,MAAM,OAAO,CAAC,GAAG,OAAO,6CAAU;AAAA,IACnF,QAAQ;AACN,aAAO,EAAE,MAAM,OAAO,CAAC,GAAG,OAAO,mDAAW;AAAA,IAC9C;AACA,UAAM,WAAW,OAAO,KAAK,KAAK;AAClC,UAAM,QAAQ,OAAO,SAAS,QAAQ,IAAI,WAAW;AACrD,WAAO,EAAE,MAAM,GAAG,aAAa,MAAM,KAAK,EAAE;AAAA,EAC9C;AACF;","names":["import_node_child_process","resolve","import_node_child_process"]}
|
package/dist/tools/git.d.cts
CHANGED
|
@@ -1,6 +1,204 @@
|
|
|
1
|
-
import { T as ToolRegistry } from '../ToolRegistry-
|
|
1
|
+
import { T as ToolRegistry } from '../ToolRegistry-C3pTFiaJ.cjs';
|
|
2
2
|
|
|
3
|
+
/** git_log — 提交历史 */
|
|
4
|
+
declare function makeGitLog(workspace: string): (args: Record<string, unknown>) => Promise<{
|
|
5
|
+
error: string;
|
|
6
|
+
repo?: undefined;
|
|
7
|
+
count?: undefined;
|
|
8
|
+
commits?: undefined;
|
|
9
|
+
} | {
|
|
10
|
+
repo: string;
|
|
11
|
+
count: number;
|
|
12
|
+
commits: ({
|
|
13
|
+
hash: string;
|
|
14
|
+
author: string;
|
|
15
|
+
date: string;
|
|
16
|
+
subject: string;
|
|
17
|
+
raw?: undefined;
|
|
18
|
+
} | {
|
|
19
|
+
raw: string;
|
|
20
|
+
hash?: undefined;
|
|
21
|
+
author?: undefined;
|
|
22
|
+
date?: undefined;
|
|
23
|
+
subject?: undefined;
|
|
24
|
+
})[];
|
|
25
|
+
error?: undefined;
|
|
26
|
+
}>;
|
|
27
|
+
/** git_status — 工作区状态 */
|
|
28
|
+
declare function makeGitStatus(workspace: string): (args: Record<string, unknown>) => Promise<{
|
|
29
|
+
error: string;
|
|
30
|
+
repo?: undefined;
|
|
31
|
+
clean?: undefined;
|
|
32
|
+
entries?: undefined;
|
|
33
|
+
count?: undefined;
|
|
34
|
+
} | {
|
|
35
|
+
repo: string;
|
|
36
|
+
clean: boolean;
|
|
37
|
+
entries: {
|
|
38
|
+
status: string;
|
|
39
|
+
file: string;
|
|
40
|
+
index: string;
|
|
41
|
+
worktree: string;
|
|
42
|
+
}[];
|
|
43
|
+
count: number;
|
|
44
|
+
error?: undefined;
|
|
45
|
+
}>;
|
|
46
|
+
/** git_diff — 差异对比 */
|
|
47
|
+
declare function makeGitDiff(workspace: string): (args: Record<string, unknown>) => Promise<{
|
|
48
|
+
error: string;
|
|
49
|
+
repo?: undefined;
|
|
50
|
+
staged?: undefined;
|
|
51
|
+
path?: undefined;
|
|
52
|
+
diff?: undefined;
|
|
53
|
+
truncated?: undefined;
|
|
54
|
+
} | {
|
|
55
|
+
repo: string;
|
|
56
|
+
staged: boolean;
|
|
57
|
+
path: string | null;
|
|
58
|
+
diff: string;
|
|
59
|
+
truncated: boolean;
|
|
60
|
+
error?: undefined;
|
|
61
|
+
}>;
|
|
3
62
|
/** 注册 Git 工具 */
|
|
4
63
|
declare function registerGitTools(registry: ToolRegistry, workspace: string): void;
|
|
64
|
+
/** 写操作结果:ok=false 时 error 为可直接展示给用户的原因 */
|
|
65
|
+
interface GitOpResult {
|
|
66
|
+
ok: boolean;
|
|
67
|
+
stdout: string;
|
|
68
|
+
stderr: string;
|
|
69
|
+
error?: string;
|
|
70
|
+
}
|
|
71
|
+
/** 分支名校验(拒绝空、选项注入与 git 非法字符);返回 null 表示可用 */
|
|
72
|
+
declare function gitRefProblem(name: string): string | null;
|
|
73
|
+
/** git_branches — 本地分支列表 + 当前分支(detached 时 current 为短 hash) */
|
|
74
|
+
declare function makeGitBranches(workspace: string): (args: Record<string, unknown>) => Promise<{
|
|
75
|
+
error: string;
|
|
76
|
+
repo?: undefined;
|
|
77
|
+
current?: undefined;
|
|
78
|
+
detached?: undefined;
|
|
79
|
+
branches?: undefined;
|
|
80
|
+
} | {
|
|
81
|
+
repo: string;
|
|
82
|
+
current: string;
|
|
83
|
+
detached: boolean;
|
|
84
|
+
branches: {
|
|
85
|
+
upstream?: string | undefined;
|
|
86
|
+
name: string;
|
|
87
|
+
current: boolean;
|
|
88
|
+
}[];
|
|
89
|
+
error?: undefined;
|
|
90
|
+
}>;
|
|
91
|
+
/** 提交标识(hash / ref 名)校验:拒绝空、选项注入与 git 非法字符;返回 null 表示可用 */
|
|
92
|
+
declare function gitObjectProblem(name: string): string | null;
|
|
93
|
+
/**
|
|
94
|
+
* git_refs — 引用列表(控制台 Git 面板)
|
|
95
|
+
*
|
|
96
|
+
* 一次返回本地分支 / 远程分支 / 标签三组,供面板分组展示与切换。
|
|
97
|
+
* 远程分支与标签只有在 `git fetch` 之后才是服务端的最新状态(见 makeGitFetch)。
|
|
98
|
+
*/
|
|
99
|
+
declare function makeGitRefs(workspace: string): (args: Record<string, unknown>) => Promise<{
|
|
100
|
+
error: string;
|
|
101
|
+
repo?: undefined;
|
|
102
|
+
current?: undefined;
|
|
103
|
+
detached?: undefined;
|
|
104
|
+
local?: undefined;
|
|
105
|
+
remote?: undefined;
|
|
106
|
+
tags?: undefined;
|
|
107
|
+
} | {
|
|
108
|
+
repo: string;
|
|
109
|
+
current: string;
|
|
110
|
+
detached: boolean;
|
|
111
|
+
local: {
|
|
112
|
+
name: string;
|
|
113
|
+
current?: boolean;
|
|
114
|
+
upstream?: string;
|
|
115
|
+
}[];
|
|
116
|
+
remote: {
|
|
117
|
+
name: string;
|
|
118
|
+
}[];
|
|
119
|
+
tags: {
|
|
120
|
+
name: string;
|
|
121
|
+
target?: string;
|
|
122
|
+
}[];
|
|
123
|
+
error?: undefined;
|
|
124
|
+
}>;
|
|
125
|
+
/**
|
|
126
|
+
* git_show — 单次提交详情(meta + 变更文件 + diff)
|
|
127
|
+
*
|
|
128
|
+
* path 省略时返回整个提交的 diff(截断 50000 字符);给出 path 时只返回该文件
|
|
129
|
+
* 在此提交中的 diff(相对第一父提交,merge commit 也能给出可读结果)。
|
|
130
|
+
*/
|
|
131
|
+
declare function makeGitShow(workspace: string): (args: Record<string, unknown>) => Promise<{
|
|
132
|
+
error: string;
|
|
133
|
+
repo?: undefined;
|
|
134
|
+
commit?: undefined;
|
|
135
|
+
files?: undefined;
|
|
136
|
+
path?: undefined;
|
|
137
|
+
diff?: undefined;
|
|
138
|
+
truncated?: undefined;
|
|
139
|
+
} | {
|
|
140
|
+
repo: string;
|
|
141
|
+
commit: {
|
|
142
|
+
hash: string;
|
|
143
|
+
short: string;
|
|
144
|
+
author: string;
|
|
145
|
+
authorEmail: string;
|
|
146
|
+
date: string;
|
|
147
|
+
subject: string;
|
|
148
|
+
body: string;
|
|
149
|
+
};
|
|
150
|
+
files: {
|
|
151
|
+
status: string;
|
|
152
|
+
file: string;
|
|
153
|
+
}[];
|
|
154
|
+
path: string | null;
|
|
155
|
+
diff: string;
|
|
156
|
+
truncated: boolean;
|
|
157
|
+
error?: undefined;
|
|
158
|
+
}>;
|
|
159
|
+
/** git_stage — 暂存文件(paths 为空 = 全部改动) */
|
|
160
|
+
declare function makeGitStage(workspace: string): (args: Record<string, unknown>) => Promise<GitOpResult>;
|
|
161
|
+
/** git_unstage — 取消暂存(paths 为空 = 全部) */
|
|
162
|
+
declare function makeGitUnstage(workspace: string): (args: Record<string, unknown>) => Promise<GitOpResult>;
|
|
163
|
+
/** git_commit — 提交已暂存内容 */
|
|
164
|
+
declare function makeGitCommit(workspace: string): (args: Record<string, unknown>) => Promise<GitOpResult>;
|
|
165
|
+
/** git_checkout — 切换分支(create=true 时新建并切换) */
|
|
166
|
+
declare function makeGitCheckout(workspace: string): (args: Record<string, unknown>) => Promise<GitOpResult>;
|
|
167
|
+
/** git_pull — 拉取当前分支的上游 */
|
|
168
|
+
declare function makeGitPull(workspace: string): (args: Record<string, unknown>) => Promise<GitOpResult>;
|
|
169
|
+
/** git_push — 推送当前分支(无 upstream 且存在 origin 时按首次推送补 -u) */
|
|
170
|
+
declare function makeGitPush(workspace: string): (args: Record<string, unknown>) => Promise<GitOpResult>;
|
|
171
|
+
/** git_fetch — 刷新服务端分支与标签(--all --prune --tags) */
|
|
172
|
+
declare function makeGitFetch(workspace: string): (args: Record<string, unknown>) => Promise<GitOpResult>;
|
|
173
|
+
interface GitRepoInfo {
|
|
174
|
+
/** 相对工作区的路径;根仓库为 "" */
|
|
175
|
+
path: string;
|
|
176
|
+
/** 目录名(根仓库为工作区目录名) */
|
|
177
|
+
name: string;
|
|
178
|
+
/** 当前分支;detached 时为短 hash */
|
|
179
|
+
branch?: string;
|
|
180
|
+
detached?: boolean;
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* 扫描工作区及其子目录中的 git 仓库。
|
|
184
|
+
*
|
|
185
|
+
* 遇到仓库后仍继续向下扫描:子模块与「仓库里嵌套独立仓库」的工程都能被发现。
|
|
186
|
+
* 根仓库排在第一位,其余按相对路径排序。
|
|
187
|
+
*/
|
|
188
|
+
declare function scanGitRepos(workspace: string, depth?: number): {
|
|
189
|
+
repos: GitRepoInfo[];
|
|
190
|
+
truncated?: boolean;
|
|
191
|
+
};
|
|
192
|
+
/** git_repos — 列出工作区内的所有 git 仓库(控制台 Git 面板的仓库选择器) */
|
|
193
|
+
declare function makeGitRepos(workspace: string): (args: Record<string, unknown>) => Promise<{
|
|
194
|
+
root: string;
|
|
195
|
+
repos: never[];
|
|
196
|
+
error: string;
|
|
197
|
+
} | {
|
|
198
|
+
repos: GitRepoInfo[];
|
|
199
|
+
truncated?: boolean;
|
|
200
|
+
root: string;
|
|
201
|
+
error?: undefined;
|
|
202
|
+
}>;
|
|
5
203
|
|
|
6
|
-
export { registerGitTools };
|
|
204
|
+
export { type GitOpResult, type GitRepoInfo, gitObjectProblem, gitRefProblem, makeGitBranches, makeGitCheckout, makeGitCommit, makeGitDiff, makeGitFetch, makeGitLog, makeGitPull, makeGitPush, makeGitRefs, makeGitRepos, makeGitShow, makeGitStage, makeGitStatus, makeGitUnstage, registerGitTools, scanGitRepos };
|
package/dist/tools/git.d.ts
CHANGED
|
@@ -1,6 +1,204 @@
|
|
|
1
|
-
import { T as ToolRegistry } from '../ToolRegistry-
|
|
1
|
+
import { T as ToolRegistry } from '../ToolRegistry-C3pTFiaJ.js';
|
|
2
2
|
|
|
3
|
+
/** git_log — 提交历史 */
|
|
4
|
+
declare function makeGitLog(workspace: string): (args: Record<string, unknown>) => Promise<{
|
|
5
|
+
error: string;
|
|
6
|
+
repo?: undefined;
|
|
7
|
+
count?: undefined;
|
|
8
|
+
commits?: undefined;
|
|
9
|
+
} | {
|
|
10
|
+
repo: string;
|
|
11
|
+
count: number;
|
|
12
|
+
commits: ({
|
|
13
|
+
hash: string;
|
|
14
|
+
author: string;
|
|
15
|
+
date: string;
|
|
16
|
+
subject: string;
|
|
17
|
+
raw?: undefined;
|
|
18
|
+
} | {
|
|
19
|
+
raw: string;
|
|
20
|
+
hash?: undefined;
|
|
21
|
+
author?: undefined;
|
|
22
|
+
date?: undefined;
|
|
23
|
+
subject?: undefined;
|
|
24
|
+
})[];
|
|
25
|
+
error?: undefined;
|
|
26
|
+
}>;
|
|
27
|
+
/** git_status — 工作区状态 */
|
|
28
|
+
declare function makeGitStatus(workspace: string): (args: Record<string, unknown>) => Promise<{
|
|
29
|
+
error: string;
|
|
30
|
+
repo?: undefined;
|
|
31
|
+
clean?: undefined;
|
|
32
|
+
entries?: undefined;
|
|
33
|
+
count?: undefined;
|
|
34
|
+
} | {
|
|
35
|
+
repo: string;
|
|
36
|
+
clean: boolean;
|
|
37
|
+
entries: {
|
|
38
|
+
status: string;
|
|
39
|
+
file: string;
|
|
40
|
+
index: string;
|
|
41
|
+
worktree: string;
|
|
42
|
+
}[];
|
|
43
|
+
count: number;
|
|
44
|
+
error?: undefined;
|
|
45
|
+
}>;
|
|
46
|
+
/** git_diff — 差异对比 */
|
|
47
|
+
declare function makeGitDiff(workspace: string): (args: Record<string, unknown>) => Promise<{
|
|
48
|
+
error: string;
|
|
49
|
+
repo?: undefined;
|
|
50
|
+
staged?: undefined;
|
|
51
|
+
path?: undefined;
|
|
52
|
+
diff?: undefined;
|
|
53
|
+
truncated?: undefined;
|
|
54
|
+
} | {
|
|
55
|
+
repo: string;
|
|
56
|
+
staged: boolean;
|
|
57
|
+
path: string | null;
|
|
58
|
+
diff: string;
|
|
59
|
+
truncated: boolean;
|
|
60
|
+
error?: undefined;
|
|
61
|
+
}>;
|
|
3
62
|
/** 注册 Git 工具 */
|
|
4
63
|
declare function registerGitTools(registry: ToolRegistry, workspace: string): void;
|
|
64
|
+
/** 写操作结果:ok=false 时 error 为可直接展示给用户的原因 */
|
|
65
|
+
interface GitOpResult {
|
|
66
|
+
ok: boolean;
|
|
67
|
+
stdout: string;
|
|
68
|
+
stderr: string;
|
|
69
|
+
error?: string;
|
|
70
|
+
}
|
|
71
|
+
/** 分支名校验(拒绝空、选项注入与 git 非法字符);返回 null 表示可用 */
|
|
72
|
+
declare function gitRefProblem(name: string): string | null;
|
|
73
|
+
/** git_branches — 本地分支列表 + 当前分支(detached 时 current 为短 hash) */
|
|
74
|
+
declare function makeGitBranches(workspace: string): (args: Record<string, unknown>) => Promise<{
|
|
75
|
+
error: string;
|
|
76
|
+
repo?: undefined;
|
|
77
|
+
current?: undefined;
|
|
78
|
+
detached?: undefined;
|
|
79
|
+
branches?: undefined;
|
|
80
|
+
} | {
|
|
81
|
+
repo: string;
|
|
82
|
+
current: string;
|
|
83
|
+
detached: boolean;
|
|
84
|
+
branches: {
|
|
85
|
+
upstream?: string | undefined;
|
|
86
|
+
name: string;
|
|
87
|
+
current: boolean;
|
|
88
|
+
}[];
|
|
89
|
+
error?: undefined;
|
|
90
|
+
}>;
|
|
91
|
+
/** 提交标识(hash / ref 名)校验:拒绝空、选项注入与 git 非法字符;返回 null 表示可用 */
|
|
92
|
+
declare function gitObjectProblem(name: string): string | null;
|
|
93
|
+
/**
|
|
94
|
+
* git_refs — 引用列表(控制台 Git 面板)
|
|
95
|
+
*
|
|
96
|
+
* 一次返回本地分支 / 远程分支 / 标签三组,供面板分组展示与切换。
|
|
97
|
+
* 远程分支与标签只有在 `git fetch` 之后才是服务端的最新状态(见 makeGitFetch)。
|
|
98
|
+
*/
|
|
99
|
+
declare function makeGitRefs(workspace: string): (args: Record<string, unknown>) => Promise<{
|
|
100
|
+
error: string;
|
|
101
|
+
repo?: undefined;
|
|
102
|
+
current?: undefined;
|
|
103
|
+
detached?: undefined;
|
|
104
|
+
local?: undefined;
|
|
105
|
+
remote?: undefined;
|
|
106
|
+
tags?: undefined;
|
|
107
|
+
} | {
|
|
108
|
+
repo: string;
|
|
109
|
+
current: string;
|
|
110
|
+
detached: boolean;
|
|
111
|
+
local: {
|
|
112
|
+
name: string;
|
|
113
|
+
current?: boolean;
|
|
114
|
+
upstream?: string;
|
|
115
|
+
}[];
|
|
116
|
+
remote: {
|
|
117
|
+
name: string;
|
|
118
|
+
}[];
|
|
119
|
+
tags: {
|
|
120
|
+
name: string;
|
|
121
|
+
target?: string;
|
|
122
|
+
}[];
|
|
123
|
+
error?: undefined;
|
|
124
|
+
}>;
|
|
125
|
+
/**
|
|
126
|
+
* git_show — 单次提交详情(meta + 变更文件 + diff)
|
|
127
|
+
*
|
|
128
|
+
* path 省略时返回整个提交的 diff(截断 50000 字符);给出 path 时只返回该文件
|
|
129
|
+
* 在此提交中的 diff(相对第一父提交,merge commit 也能给出可读结果)。
|
|
130
|
+
*/
|
|
131
|
+
declare function makeGitShow(workspace: string): (args: Record<string, unknown>) => Promise<{
|
|
132
|
+
error: string;
|
|
133
|
+
repo?: undefined;
|
|
134
|
+
commit?: undefined;
|
|
135
|
+
files?: undefined;
|
|
136
|
+
path?: undefined;
|
|
137
|
+
diff?: undefined;
|
|
138
|
+
truncated?: undefined;
|
|
139
|
+
} | {
|
|
140
|
+
repo: string;
|
|
141
|
+
commit: {
|
|
142
|
+
hash: string;
|
|
143
|
+
short: string;
|
|
144
|
+
author: string;
|
|
145
|
+
authorEmail: string;
|
|
146
|
+
date: string;
|
|
147
|
+
subject: string;
|
|
148
|
+
body: string;
|
|
149
|
+
};
|
|
150
|
+
files: {
|
|
151
|
+
status: string;
|
|
152
|
+
file: string;
|
|
153
|
+
}[];
|
|
154
|
+
path: string | null;
|
|
155
|
+
diff: string;
|
|
156
|
+
truncated: boolean;
|
|
157
|
+
error?: undefined;
|
|
158
|
+
}>;
|
|
159
|
+
/** git_stage — 暂存文件(paths 为空 = 全部改动) */
|
|
160
|
+
declare function makeGitStage(workspace: string): (args: Record<string, unknown>) => Promise<GitOpResult>;
|
|
161
|
+
/** git_unstage — 取消暂存(paths 为空 = 全部) */
|
|
162
|
+
declare function makeGitUnstage(workspace: string): (args: Record<string, unknown>) => Promise<GitOpResult>;
|
|
163
|
+
/** git_commit — 提交已暂存内容 */
|
|
164
|
+
declare function makeGitCommit(workspace: string): (args: Record<string, unknown>) => Promise<GitOpResult>;
|
|
165
|
+
/** git_checkout — 切换分支(create=true 时新建并切换) */
|
|
166
|
+
declare function makeGitCheckout(workspace: string): (args: Record<string, unknown>) => Promise<GitOpResult>;
|
|
167
|
+
/** git_pull — 拉取当前分支的上游 */
|
|
168
|
+
declare function makeGitPull(workspace: string): (args: Record<string, unknown>) => Promise<GitOpResult>;
|
|
169
|
+
/** git_push — 推送当前分支(无 upstream 且存在 origin 时按首次推送补 -u) */
|
|
170
|
+
declare function makeGitPush(workspace: string): (args: Record<string, unknown>) => Promise<GitOpResult>;
|
|
171
|
+
/** git_fetch — 刷新服务端分支与标签(--all --prune --tags) */
|
|
172
|
+
declare function makeGitFetch(workspace: string): (args: Record<string, unknown>) => Promise<GitOpResult>;
|
|
173
|
+
interface GitRepoInfo {
|
|
174
|
+
/** 相对工作区的路径;根仓库为 "" */
|
|
175
|
+
path: string;
|
|
176
|
+
/** 目录名(根仓库为工作区目录名) */
|
|
177
|
+
name: string;
|
|
178
|
+
/** 当前分支;detached 时为短 hash */
|
|
179
|
+
branch?: string;
|
|
180
|
+
detached?: boolean;
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* 扫描工作区及其子目录中的 git 仓库。
|
|
184
|
+
*
|
|
185
|
+
* 遇到仓库后仍继续向下扫描:子模块与「仓库里嵌套独立仓库」的工程都能被发现。
|
|
186
|
+
* 根仓库排在第一位,其余按相对路径排序。
|
|
187
|
+
*/
|
|
188
|
+
declare function scanGitRepos(workspace: string, depth?: number): {
|
|
189
|
+
repos: GitRepoInfo[];
|
|
190
|
+
truncated?: boolean;
|
|
191
|
+
};
|
|
192
|
+
/** git_repos — 列出工作区内的所有 git 仓库(控制台 Git 面板的仓库选择器) */
|
|
193
|
+
declare function makeGitRepos(workspace: string): (args: Record<string, unknown>) => Promise<{
|
|
194
|
+
root: string;
|
|
195
|
+
repos: never[];
|
|
196
|
+
error: string;
|
|
197
|
+
} | {
|
|
198
|
+
repos: GitRepoInfo[];
|
|
199
|
+
truncated?: boolean;
|
|
200
|
+
root: string;
|
|
201
|
+
error?: undefined;
|
|
202
|
+
}>;
|
|
5
203
|
|
|
6
|
-
export { registerGitTools };
|
|
204
|
+
export { type GitOpResult, type GitRepoInfo, gitObjectProblem, gitRefProblem, makeGitBranches, makeGitCheckout, makeGitCommit, makeGitDiff, makeGitFetch, makeGitLog, makeGitPull, makeGitPush, makeGitRefs, makeGitRepos, makeGitShow, makeGitStage, makeGitStatus, makeGitUnstage, registerGitTools, scanGitRepos };
|