@zqagent/core 0.2.20 → 0.3.6

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.
Files changed (238) hide show
  1. package/README.md +1 -1
  2. package/README.oss.en.md +3 -3
  3. package/README.oss.md +8 -3
  4. package/README.oss.zh-TW.md +3 -3
  5. package/dist/McpClient-CEQBUPuC.d.cts +145 -0
  6. package/dist/McpClient-CEQBUPuC.d.ts +145 -0
  7. package/dist/{ToolRegistry-C3pTFiaJ.d.cts → ToolRegistry-Cy0fQxrb.d.ts} +2 -111
  8. package/dist/{ToolRegistry-C3pTFiaJ.d.ts → ToolRegistry-DouDvn9E.d.cts} +2 -111
  9. package/dist/{explore-RtZtb-V6.d.ts → TurnEngine-BPKhWuSI.d.cts} +108 -73
  10. package/dist/{explore-BgRhj6SP.d.cts → TurnEngine-DtGEuVLT.d.ts} +108 -73
  11. package/dist/conflictGuard-BBOOIxxK.d.cts +42 -0
  12. package/dist/conflictGuard-BBOOIxxK.d.ts +42 -0
  13. package/dist/index.cjs +5418 -1096
  14. package/dist/index.cjs.map +1 -1
  15. package/dist/index.d.cts +577 -195
  16. package/dist/index.d.ts +577 -195
  17. package/dist/index.js +5299 -1011
  18. package/dist/index.js.map +1 -1
  19. package/dist/internal/crypto/secrets.cjs +25 -0
  20. package/dist/internal/crypto/secrets.cjs.map +1 -0
  21. package/dist/internal/crypto/secrets.d.cts +2 -0
  22. package/dist/internal/crypto/secrets.d.ts +2 -0
  23. package/dist/internal/crypto/secrets.js +3 -0
  24. package/dist/internal/crypto/secrets.js.map +1 -0
  25. package/dist/internal/engine/FileLogHook.cjs +179 -0
  26. package/dist/internal/engine/FileLogHook.cjs.map +1 -0
  27. package/dist/internal/engine/FileLogHook.d.cts +67 -0
  28. package/dist/internal/engine/FileLogHook.d.ts +67 -0
  29. package/dist/internal/engine/FileLogHook.js +144 -0
  30. package/dist/internal/engine/FileLogHook.js.map +1 -0
  31. package/dist/internal/engine/approvalView.cjs +25 -0
  32. package/dist/internal/engine/approvalView.cjs.map +1 -0
  33. package/dist/internal/engine/approvalView.d.cts +1 -0
  34. package/dist/internal/engine/approvalView.d.ts +1 -0
  35. package/dist/internal/engine/approvalView.js +3 -0
  36. package/dist/internal/engine/approvalView.js.map +1 -0
  37. package/dist/internal/engine/limits.cjs +59 -0
  38. package/dist/internal/engine/limits.cjs.map +1 -0
  39. package/dist/internal/engine/limits.d.cts +32 -0
  40. package/dist/internal/engine/limits.d.ts +32 -0
  41. package/dist/internal/engine/limits.js +31 -0
  42. package/dist/internal/engine/limits.js.map +1 -0
  43. package/dist/internal/engine/sessionNaming.cjs +47 -0
  44. package/dist/internal/engine/sessionNaming.cjs.map +1 -0
  45. package/dist/internal/engine/sessionNaming.d.cts +25 -0
  46. package/dist/internal/engine/sessionNaming.d.ts +25 -0
  47. package/dist/internal/engine/sessionNaming.js +20 -0
  48. package/dist/internal/engine/sessionNaming.js.map +1 -0
  49. package/dist/internal/llmConfig.cjs +561 -0
  50. package/dist/internal/llmConfig.cjs.map +1 -0
  51. package/dist/internal/llmConfig.d.cts +75 -0
  52. package/dist/internal/llmConfig.d.ts +75 -0
  53. package/dist/internal/llmConfig.js +535 -0
  54. package/dist/internal/llmConfig.js.map +1 -0
  55. package/dist/internal/mcp/builtin/index.cjs +685 -0
  56. package/dist/internal/mcp/builtin/index.cjs.map +1 -0
  57. package/dist/internal/mcp/builtin/index.d.cts +21 -0
  58. package/dist/internal/mcp/builtin/index.d.ts +21 -0
  59. package/dist/internal/mcp/builtin/index.js +652 -0
  60. package/dist/internal/mcp/builtin/index.js.map +1 -0
  61. package/dist/internal/mcp/config.cjs +278 -0
  62. package/dist/internal/mcp/config.cjs.map +1 -0
  63. package/dist/internal/mcp/config.d.cts +61 -0
  64. package/dist/internal/mcp/config.d.ts +61 -0
  65. package/dist/internal/mcp/config.js +235 -0
  66. package/dist/internal/mcp/config.js.map +1 -0
  67. package/dist/internal/mcp/presets.cjs +184 -0
  68. package/dist/internal/mcp/presets.cjs.map +1 -0
  69. package/dist/internal/mcp/presets.d.cts +39 -0
  70. package/dist/internal/mcp/presets.d.ts +39 -0
  71. package/dist/internal/mcp/presets.js +158 -0
  72. package/dist/internal/mcp/presets.js.map +1 -0
  73. package/dist/internal/node-name.cjs +45 -0
  74. package/dist/internal/node-name.cjs.map +1 -0
  75. package/dist/internal/node-name.d.cts +3 -0
  76. package/dist/internal/node-name.d.ts +3 -0
  77. package/dist/internal/node-name.js +10 -0
  78. package/dist/internal/node-name.js.map +1 -0
  79. package/dist/internal/paths.cjs +63 -0
  80. package/dist/internal/paths.cjs.map +1 -0
  81. package/dist/internal/paths.d.cts +17 -0
  82. package/dist/internal/paths.d.ts +17 -0
  83. package/dist/internal/paths.js +25 -0
  84. package/dist/internal/paths.js.map +1 -0
  85. package/dist/internal/providers/diagnostics.cjs +288 -0
  86. package/dist/internal/providers/diagnostics.cjs.map +1 -0
  87. package/dist/internal/providers/diagnostics.d.cts +103 -0
  88. package/dist/internal/providers/diagnostics.d.ts +103 -0
  89. package/dist/internal/providers/diagnostics.js +250 -0
  90. package/dist/internal/providers/diagnostics.js.map +1 -0
  91. package/dist/internal/providers/models.cjs +699 -0
  92. package/dist/internal/providers/models.cjs.map +1 -0
  93. package/dist/internal/providers/models.d.cts +136 -0
  94. package/dist/internal/providers/models.d.ts +136 -0
  95. package/dist/internal/providers/models.js +654 -0
  96. package/dist/internal/providers/models.js.map +1 -0
  97. package/dist/internal/terminal.cjs +161 -0
  98. package/dist/internal/terminal.cjs.map +1 -0
  99. package/dist/internal/terminal.d.cts +58 -0
  100. package/dist/internal/terminal.d.ts +58 -0
  101. package/dist/internal/terminal.js +128 -0
  102. package/dist/internal/terminal.js.map +1 -0
  103. package/dist/internal/tools/execAsync.cjs +187 -0
  104. package/dist/internal/tools/execAsync.cjs.map +1 -0
  105. package/dist/internal/tools/execAsync.d.cts +30 -0
  106. package/dist/internal/tools/execAsync.d.ts +30 -0
  107. package/dist/internal/tools/execAsync.js +162 -0
  108. package/dist/internal/tools/execAsync.js.map +1 -0
  109. package/dist/internal/tools/git.cjs +935 -0
  110. package/dist/internal/tools/git.cjs.map +1 -0
  111. package/dist/internal/tools/git.d.cts +206 -0
  112. package/dist/internal/tools/git.d.ts +206 -0
  113. package/dist/internal/tools/git.js +883 -0
  114. package/dist/internal/tools/git.js.map +1 -0
  115. package/dist/internal/tools/pathSafety.cjs +99 -0
  116. package/dist/internal/tools/pathSafety.cjs.map +1 -0
  117. package/dist/internal/tools/pathSafety.d.cts +44 -0
  118. package/dist/internal/tools/pathSafety.d.ts +44 -0
  119. package/dist/internal/tools/pathSafety.js +61 -0
  120. package/dist/internal/tools/pathSafety.js.map +1 -0
  121. package/dist/internal/tools/plan.cjs +146 -0
  122. package/dist/internal/tools/plan.cjs.map +1 -0
  123. package/dist/internal/tools/plan.d.cts +48 -0
  124. package/dist/internal/tools/plan.d.ts +48 -0
  125. package/dist/internal/tools/plan.js +117 -0
  126. package/dist/internal/tools/plan.js.map +1 -0
  127. package/dist/internal/tools/ripgrep.cjs +118 -0
  128. package/dist/internal/tools/ripgrep.cjs.map +1 -0
  129. package/dist/internal/tools/ripgrep.d.cts +6 -0
  130. package/dist/internal/tools/ripgrep.d.ts +6 -0
  131. package/dist/internal/tools/ripgrep.js +89 -0
  132. package/dist/internal/tools/ripgrep.js.map +1 -0
  133. package/dist/internal/tools/sandbox.cjs +253 -0
  134. package/dist/internal/tools/sandbox.cjs.map +1 -0
  135. package/dist/internal/tools/sandbox.d.cts +67 -0
  136. package/dist/internal/tools/sandbox.d.ts +67 -0
  137. package/dist/internal/tools/sandbox.js +213 -0
  138. package/dist/internal/tools/sandbox.js.map +1 -0
  139. package/dist/internal/tools/shell.cjs +771 -0
  140. package/dist/internal/tools/shell.cjs.map +1 -0
  141. package/dist/internal/tools/shell.d.cts +47 -0
  142. package/dist/internal/tools/shell.d.ts +47 -0
  143. package/dist/internal/tools/shell.js +731 -0
  144. package/dist/internal/tools/shell.js.map +1 -0
  145. package/dist/internal/tools/shellRuntime.cjs +220 -0
  146. package/dist/internal/tools/shellRuntime.cjs.map +1 -0
  147. package/dist/internal/tools/shellRuntime.d.cts +67 -0
  148. package/dist/internal/tools/shellRuntime.d.ts +67 -0
  149. package/dist/internal/tools/shellRuntime.js +181 -0
  150. package/dist/internal/tools/shellRuntime.js.map +1 -0
  151. package/dist/internal/update/controller.cjs +1035 -0
  152. package/dist/internal/update/controller.cjs.map +1 -0
  153. package/dist/internal/update/controller.d.cts +150 -0
  154. package/dist/internal/update/controller.d.ts +150 -0
  155. package/dist/internal/update/controller.js +991 -0
  156. package/dist/internal/update/controller.js.map +1 -0
  157. package/dist/internal/update/platform.cjs +234 -0
  158. package/dist/internal/update/platform.cjs.map +1 -0
  159. package/dist/internal/update/platform.d.cts +77 -0
  160. package/dist/internal/update/platform.d.ts +77 -0
  161. package/dist/internal/update/platform.js +184 -0
  162. package/dist/internal/update/platform.js.map +1 -0
  163. package/dist/tools/ask.d.cts +3 -1
  164. package/dist/tools/ask.d.ts +3 -1
  165. package/dist/tools/diff.cjs +17 -6
  166. package/dist/tools/diff.cjs.map +1 -1
  167. package/dist/tools/diff.d.cts +5 -2
  168. package/dist/tools/diff.d.ts +5 -2
  169. package/dist/tools/diff.js +17 -6
  170. package/dist/tools/diff.js.map +1 -1
  171. package/dist/tools/directories.d.cts +3 -1
  172. package/dist/tools/directories.d.ts +3 -1
  173. package/dist/tools/explore.cjs +1778 -204
  174. package/dist/tools/explore.cjs.map +1 -1
  175. package/dist/tools/explore.d.cts +82 -2
  176. package/dist/tools/explore.d.ts +82 -2
  177. package/dist/tools/explore.js +1780 -204
  178. package/dist/tools/explore.js.map +1 -1
  179. package/dist/tools/files.cjs +56 -21
  180. package/dist/tools/files.cjs.map +1 -1
  181. package/dist/tools/files.d.cts +6 -3
  182. package/dist/tools/files.d.ts +6 -3
  183. package/dist/tools/files.js +56 -21
  184. package/dist/tools/files.js.map +1 -1
  185. package/dist/tools/git.cjs +34 -25
  186. package/dist/tools/git.cjs.map +1 -1
  187. package/dist/tools/git.d.cts +4 -204
  188. package/dist/tools/git.d.ts +4 -204
  189. package/dist/tools/git.js +34 -25
  190. package/dist/tools/git.js.map +1 -1
  191. package/dist/tools/media.cjs +280 -3
  192. package/dist/tools/media.cjs.map +1 -1
  193. package/dist/tools/media.d.cts +3 -1
  194. package/dist/tools/media.d.ts +3 -1
  195. package/dist/tools/media.js +278 -1
  196. package/dist/tools/media.js.map +1 -1
  197. package/dist/tools/plan.cjs +62 -0
  198. package/dist/tools/plan.cjs.map +1 -1
  199. package/dist/tools/plan.d.cts +4 -17
  200. package/dist/tools/plan.d.ts +4 -17
  201. package/dist/tools/plan.js +59 -0
  202. package/dist/tools/plan.js.map +1 -1
  203. package/dist/tools/search.cjs +3 -0
  204. package/dist/tools/search.cjs.map +1 -1
  205. package/dist/tools/search.d.cts +3 -1
  206. package/dist/tools/search.d.ts +3 -1
  207. package/dist/tools/search.js +3 -0
  208. package/dist/tools/search.js.map +1 -1
  209. package/dist/tools/shell.cjs +76 -11
  210. package/dist/tools/shell.cjs.map +1 -1
  211. package/dist/tools/shell.d.cts +5 -39
  212. package/dist/tools/shell.d.ts +5 -39
  213. package/dist/tools/shell.js +75 -11
  214. package/dist/tools/shell.js.map +1 -1
  215. package/dist/tools/todo.d.cts +3 -1
  216. package/dist/tools/todo.d.ts +3 -1
  217. package/dist/tools/web.cjs +279 -5
  218. package/dist/tools/web.cjs.map +1 -1
  219. package/dist/tools/web.d.cts +3 -1
  220. package/dist/tools/web.d.ts +3 -1
  221. package/dist/tools/web.js +275 -3
  222. package/dist/tools/web.js.map +1 -1
  223. package/dist/types-BYyXWWPW.d.cts +237 -0
  224. package/dist/types-BYyXWWPW.d.ts +237 -0
  225. package/package.json +26 -37
  226. package/bin/zqagent +0 -2
  227. package/dist/cli/index.cjs +0 -25218
  228. package/dist/cli/index.cjs.map +0 -1
  229. package/dist/cli/index.d.cts +0 -2
  230. package/dist/cli/index.d.ts +0 -2
  231. package/dist/cli/index.js +0 -25201
  232. package/dist/cli/index.js.map +0 -1
  233. package/dist/server/index.cjs +0 -20510
  234. package/dist/server/index.cjs.map +0 -1
  235. package/dist/server/index.d.cts +0 -2
  236. package/dist/server/index.d.ts +0 -2
  237. package/dist/server/index.js +0 -20493
  238. package/dist/server/index.js.map +0 -1
@@ -1 +1 @@
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":"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","names":["import_node_child_process","resolve","import_node_child_process"]}
1
+ {"version":3,"sources":["../../src/tools/git.ts","../../src/tools/execAsync.ts","../../src/tools/killTree.ts","../../src/tools/shellBackground.ts","../../src/tools/sandbox.ts","../../src/tools/shell.ts","../../src/tools/pathSafety.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\";\nimport { isInsideRelative } from \"./pathSafety.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 (!isInsideRelative(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 (!isInsideRelative(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 (isInsideRelative(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 沙箱(OS 级文件系统约束)\n *\n * 目标:让 `run_shell` 在工作区内可以免审批执行,而不是靠命令名白名单\n * 逐个放行——后者既挡不住 `python -c` 这类间接执行,也让日常开发寸步难行。\n *\n * 各平台取当前可用的最优方案:\n * - macOS:`/usr/bin/sandbox-exec`(SBPL profile)。系统自带,进程级,\n * 毫秒级开销;默认放行读与网络,只把 `file-write*` 收窄到工作区 + 临时目录。\n * - Linux:`bubblewrap`(bwrap)。用 `--ro-bind / /` 把整机只读挂载,\n * 再把工作区 `--bind` 成可写,二者叠加即为「只在工作区内可写」。\n * 需要发行版装了 bwrap 且允许非特权 user namespace(容器里通常需\n * `--security-opt seccomp=unconfined` 或 privileged)。\n * - Windows:没有进程级文件系统沙箱的等价物(AppContainer 需要额外的\n * 原生 helper 与 ACL 配置,不属于零依赖方案)。此处明确返回\n * `enforced: false`,由权限层退化为「工作区路径启发式」判定\n * (见 permissions/workspaceGuard.ts)——它是降低打扰的策略,不是安全边界。\n *\n * 探测结果做进程级缓存:CLI 在 /mode、/agent 等场景会重建引擎,\n * 但沙箱能力不会变,不必每次 spawn 探测进程。\n */\nimport { spawnSync } from \"node:child_process\";\nimport * as fs from \"fs\";\nimport * as os from \"os\";\nimport * as path from \"path\";\n\n/** 沙箱后端 */\nexport type SandboxBackend = \"sandbox-exec\" | \"bwrap\" | \"none\";\n\n/** 配置取值:auto=探测到就用;off=禁用;required=探测不到时明确报错 */\nexport type SandboxMode = \"auto\" | \"off\" | \"required\";\n\nexport interface SandboxOptions {\n mode?: SandboxMode;\n /** 额外可写路径(绝对路径,支持 ~;相对路径按 workspace 解析) */\n extraWritable?: string[];\n /** 工作区根(解析相对路径的额外可写项) */\n workspace?: string;\n platform?: NodeJS.Platform;\n env?: NodeJS.ProcessEnv;\n fileExists?: (p: string) => boolean;\n /** 可用性探测(测试注入);提供时不走缓存 */\n probe?: (backend: Exclude<SandboxBackend, \"none\">, bin: string) => boolean;\n}\n\n/** 沙箱方案(可跨调用复用) */\nexport interface SandboxPlan {\n backend: SandboxBackend;\n /** OS 级文件系统约束是否生效 */\n enforced: boolean;\n /** 沙箱可执行文件;未生效时为 null */\n bin: string | null;\n /** 允许写入的路径(绝对路径,已去重) */\n writable: string[];\n mode: SandboxMode;\n /** 状态说明(日志 / 工具描述用) */\n reason: string;\n}\n\n/** 一次待执行的子进程调用(file + argv,不经 shell 解释) */\nexport interface SandboxInvocation {\n file: string;\n argv: string[];\n}\n\n/** 解析 ~ 与环境变量无关的常见写法 */\nfunction expandHome(p: string, env: NodeJS.ProcessEnv): string {\n if (p === \"~\") return env.HOME ?? p;\n if (p.startsWith(\"~/\") || p.startsWith(\"~\\\\\")) {\n return path.join(env.HOME ?? \"~\", p.slice(2));\n }\n return p;\n}\n\n/**\n * 工具链缓存目录:这些必须可写,否则 `pnpm install` / `npm ci` 会因\n * store / cache 落在工作区外而报 EPERM。只放行缓存与包仓库,\n * 不放行 `~/.ssh`、shell 配置等敏感位置。\n */\nfunction toolchainWritable(\n platform: NodeJS.Platform,\n env: NodeJS.ProcessEnv,\n): string[] {\n const home = env.HOME ?? os.homedir();\n const rel =\n platform === \"darwin\"\n ? [\"Library/pnpm\", \"Library/Caches\", \".npm\", \".cache\"]\n : [\".npm\", \".cache\", \".local/share/pnpm\", \".local/state/pnpm\", \".pnpm-store\"];\n return rel.map((r) => path.join(home, r));\n}\n\n/**\n * 加入工具链缓存目录。\n *\n * 只加**真实存在**的目录:bwrap 对不存在的 bind 源会直接报错并终止整个命令\n * (`Can't find source path ~/.pnpm-store`),一个没装过的缓存目录就能让沙箱\n * 里所有命令失败。缓存目录不存在本身也无所谓——第一次 `pnpm install` 会自己建。\n */\nfunction addToolchainWritable(\n platform: NodeJS.Platform,\n env: NodeJS.ProcessEnv,\n add: (p: string) => void,\n exists: (p: string) => boolean,\n): void {\n for (const dir of toolchainWritable(platform, env)) {\n if (exists(dir)) add(dir);\n }\n}\n\n/** 收集去重后的可写路径(工作区 + 临时目录 + 用户追加) */\nfunction collectWritable(opts: {\n workspace: string;\n extraWritable: string[];\n platform: NodeJS.Platform;\n env: NodeJS.ProcessEnv;\n /** 路径存在性判定(测试可注入) */\n exists: (p: string) => boolean;\n}): string[] {\n const out = new Set<string>();\n const add = (p: string): void => {\n if (!p) return;\n const abs = path.resolve(p);\n out.add(abs);\n // macOS 的 /tmp、/var 都是符号链接,SBPL 按真实路径匹配,两个都加上\n try {\n out.add(fs.realpathSync(abs));\n } catch {\n /* 路径尚不存在:只保留原始绝对路径 */\n }\n };\n\n add(opts.workspace);\n\n if (opts.platform === \"darwin\") {\n const tmp = os.tmpdir();\n add(tmp);\n add(\"/tmp\");\n add(\"/private/tmp\");\n add(\"/dev\");\n addToolchainWritable(opts.platform, opts.env, add, opts.exists);\n } else if (opts.platform !== \"win32\") {\n add(\"/tmp\");\n add(\"/dev\");\n addToolchainWritable(opts.platform, opts.env, add, opts.exists);\n }\n\n for (const raw of opts.extraWritable) {\n const expanded = expandHome(String(raw ?? \"\").trim(), opts.env);\n if (!expanded) continue;\n add(path.resolve(opts.workspace, expanded));\n }\n\n return [...out];\n}\n\n/** 默认探测:真正跑一次最小命令,确认后端在本机可用(含 user namespace 限制) */\nfunction defaultProbe(\n backend: Exclude<SandboxBackend, \"none\">,\n bin: string,\n): boolean {\n const args =\n backend === \"sandbox-exec\"\n ? [\"-p\", \"(version 1)(allow default)\", \"/bin/sh\", \"-c\", \"true\"]\n : [\"--ro-bind\", \"/\", \"/\", \"--dev-bind\", \"/dev\", \"/dev\", \"--\", \"/bin/true\"];\n try {\n const r = spawnSync(bin, args, { timeout: 3000, stdio: \"ignore\" });\n return r.status === 0;\n } catch {\n return false;\n }\n}\n\n/** 是否为正则化的模式取值 */\nfunction normalizeMode(raw: string | undefined): SandboxMode {\n const v = String(raw ?? \"\").trim().toLowerCase();\n if (v === \"off\" || v === \"none\" || v === \"false\") return \"off\";\n if (v === \"required\" || v === \"on\" || v === \"true\") return \"required\";\n return \"auto\";\n}\n\n/** 缓存键:同平台、同模式、同工作区、同追加路径只探测一次 */\nconst planCache = new Map<string, SandboxPlan>();\n\n/**\n * 解析当前平台可用的沙箱方案。\n *\n * `mode: \"off\"` 直接返回未生效;`required` 在探测失败时通过 `reason`\n * 说明原因(调用方可据此拒绝启动或提示用户安装 bwrap)。\n */\nexport function resolveSandbox(opts: SandboxOptions = {}): SandboxPlan {\n const platform = opts.platform ?? process.platform;\n const env = opts.env ?? process.env;\n const workspace = path.resolve(opts.workspace ?? process.cwd());\n const mode = normalizeMode(opts.mode ?? env.ZQAGENT_SANDBOX);\n const extraWritable = opts.extraWritable ?? [];\n // 目录(而非文件)的存在性:缓存目录不存在时不能写进 bind 列表,\n // 否则 bwrap 会因为「源路径不存在」让整条命令失败。\n // 复用 fileExists 注入点,测试可用它模拟「目录都存在」。\n const pathExists: (p: string) => boolean =\n opts.fileExists ??\n ((p: string) => {\n try {\n return fs.existsSync(p);\n } catch {\n return false;\n }\n });\n const writable = collectWritable({\n workspace,\n extraWritable,\n platform,\n env,\n exists: pathExists,\n });\n\n const cacheKey = [\n platform,\n mode,\n workspace,\n writable.join(\"\\u0000\"),\n opts.probe ? \"injected\" : \"default\",\n ].join(\"|\");\n const cached = planCache.get(cacheKey);\n if (cached && !opts.probe) return cached;\n\n const finish = (plan: SandboxPlan): SandboxPlan => {\n if (!opts.probe) planCache.set(cacheKey, plan);\n return plan;\n };\n\n if (mode === \"off\") {\n return finish({\n backend: \"none\",\n enforced: false,\n bin: null,\n writable,\n mode,\n reason: \"沙箱已按配置关闭\",\n });\n }\n\n const fileExists = opts.fileExists ?? ((p: string) => {\n try {\n return fs.statSync(p).isFile();\n } catch {\n return false;\n }\n });\n\n const candidate: { backend: Exclude<SandboxBackend, \"none\">; bin: string } | null =\n platform === \"darwin\"\n ? { backend: \"sandbox-exec\", bin: \"/usr/bin/sandbox-exec\" }\n : platform === \"linux\"\n ? { backend: \"bwrap\", bin: \"/usr/bin/bwrap\" }\n : null;\n\n if (!candidate) {\n return finish({\n backend: \"none\",\n enforced: false,\n bin: null,\n writable,\n mode,\n reason:\n platform === \"win32\"\n ? \"Windows 无零依赖的进程级文件系统沙箱,改用工作区路径判定\"\n : `平台 ${platform} 无受支持的沙箱后端`,\n });\n }\n\n if (!fileExists(candidate.bin)) {\n return finish({\n backend: \"none\",\n enforced: false,\n bin: null,\n writable,\n mode,\n reason: `未找到 ${candidate.bin}(${\n candidate.backend === \"bwrap\" ? \"安装 bubblewrap 后可启用\" : \"系统缺少 sandbox-exec\"\n }),改用工作区路径判定`,\n });\n }\n\n const probe = opts.probe ?? defaultProbe;\n if (!probe(candidate.backend, candidate.bin)) {\n return finish({\n backend: \"none\",\n enforced: false,\n bin: null,\n writable,\n mode,\n reason: `${candidate.bin} 探测失败(容器内可能禁用了 user namespace),改用工作区路径判定`,\n });\n }\n\n return finish({\n backend: candidate.backend,\n enforced: true,\n bin: candidate.bin,\n writable,\n mode,\n reason:\n candidate.backend === \"sandbox-exec\"\n ? \"macOS sandbox-exec:只允许写入工作区与临时目录\"\n : \"Linux bubblewrap:只允许写入工作区与临时目录\",\n });\n}\n\n/** SBPL 字符串字面量转义 */\nfunction sbplString(value: string): string {\n return `\"${value.replace(/\\\\/g, \"\\\\\\\\\").replace(/\"/g, '\\\\\"')}\"`;\n}\n\n/**\n * 生成 macOS sandbox profile。\n *\n * 采用「allow default + deny file-write* + 白名单放行」而不是\n * 「deny default」:后者要逐项放行进程、信号、sysctl、mach lookup 等,\n * 极易把 git / node / 包管理器弄挂,维护成本也高。这里只收窄写权限,\n * 读与网络保持默认——与 bwrap 方案的能力边界一致。\n */\nexport function buildMacProfile(writable: string[]): string {\n const rules = writable.map((p) => ` (subpath ${sbplString(p)})`).join(\"\\n\");\n return [\n \"(version 1)\",\n \"(allow default)\",\n \"(deny file-write*)\",\n \"(allow file-write*\",\n rules,\n \")\",\n ].join(\"\\n\");\n}\n\n/** 生成 bubblewrap 的参数前缀 */\nexport function buildBwrapArgs(writable: string[], cwd: string): string[] {\n const args: string[] = [\"--ro-bind\", \"/\", \"/\", \"--dev-bind\", \"/dev\", \"/dev\"];\n // /tmp 用独立 tmpfs:避免把宿主机 /tmp 暴露成可写(工作区内的 /tmp 除外)\n if (!writable.includes(\"/tmp\")) args.push(\"--tmpfs\", \"/tmp\");\n for (const dir of writable) {\n if (dir === \"/dev\" || dir === \"/proc\") continue;\n args.push(\"--bind\", dir, dir);\n }\n args.push(\"--chdir\", cwd, \"--die-with-parent\");\n return args;\n}\n\n/**\n * 把一次 shell 调用包进沙箱。\n *\n * 未生效时原样返回(调用方无需分支)。参数一律走 argv,不拼字符串,\n * 避免用户命令中的引号 / 空格影响外层命令。\n */\nexport function wrapWithSandbox(\n plan: SandboxPlan | null | undefined,\n invocation: SandboxInvocation,\n ctx: { cwd: string },\n): SandboxInvocation {\n if (!plan || !plan.enforced || !plan.bin) return invocation;\n\n if (plan.backend === \"sandbox-exec\") {\n return {\n file: plan.bin,\n argv: [\"-p\", buildMacProfile(plan.writable), invocation.file, ...invocation.argv],\n };\n }\n\n return {\n file: plan.bin,\n argv: [...buildBwrapArgs(plan.writable, ctx.cwd), \"--\", invocation.file, ...invocation.argv],\n };\n}\n\n/** 一行状态说明,用于日志与工具描述 */\nexport function describeSandbox(plan: SandboxPlan | null | undefined): string {\n if (!plan) return \"未配置\";\n return plan.enforced ? plan.reason : `未启用(${plan.reason})`;\n}\n\n/** 测试用:清空探测缓存 */\nexport function resetSandboxCache(): void {\n planCache.clear();\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\";\nimport {\n describeSandbox,\n wrapWithSandbox,\n type SandboxPlan,\n} from \"./sandbox.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 * 键必须带上归属(owner):多个会话共享同一 workspaceRoot 时,如果只按工作区\n * 做键,A 会话 `cd src` 会让 B 会话的下一条命令也在 src 下执行——两个并行会话\n * 互相串目录。owner 由调用方给出会话级标识(server 传 tenant/session)。\n * 未给 owner 时(CLI 单会话)退化为按工作区,保持既有行为。\n */\nconst shellSessions = new Map<string, { cwd: string }>();\n\n/** 会话键:工作区绝对路径 + 可选的归属标识(用 NUL 分隔,不会与路径字符冲突) */\nfunction shellKey(workspace: string, owner?: string | null): string {\n // 用规范化后的绝对路径作键:避免尾斜杠 / 相对路径在不同注册点产生两个会话\n const root = path.resolve(workspace);\n const o = String(owner ?? \"\").trim();\n return o ? `${root}\\u0000${o}` : root;\n}\n\nfunction sessionFor(workspace: string, owner?: string | null): { cwd: string } {\n const key = shellKey(workspace, owner);\n let s = shellSessions.get(key);\n if (!s) {\n s = { cwd: path.resolve(workspace) };\n shellSessions.set(key, s);\n }\n return s;\n}\n\n/** 测试用:重置某个工作区的 shell 会话状态 */\nexport function resetShellSession(workspace: string, owner?: string | null): void {\n shellSessions.delete(shellKey(workspace, owner));\n}\n\n/** 当前 shell 会话的 cwd(供权限引擎判定相对路径是否越界) */\nexport function getShellCwd(workspace: string, owner?: string | null): string {\n return sessionFor(workspace, owner).cwd;\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(\n workspace: string,\n spec: ShellSpec,\n tasks: ShellTaskManager,\n scope: string | null,\n sandbox: SandboxPlan | null,\n sessionKey: string | null,\n) {\n const session = sessionFor(workspace, sessionKey);\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 // 沙箱在这里套:命令仍是完整的 shell 调用,只是外层多一层\n // sandbox-exec / bwrap,用来把可写范围收窄到工作区\n const { file, argv } = wrapWithSandbox(\n sandbox,\n resolveInvocation(spec, prepared),\n { cwd: session.cwd },\n );\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 * @param sandbox OS 级沙箱方案(未启用时传 null 或省略)\n * @param sessionKey shell 会话归属(server 传 tenant/session)。多会话共享同一\n * 工作区时必须传,否则它们的 cwd 会互相覆盖;CLI 单会话可省略\n */\nexport function registerShellTool(\n registry: ToolRegistry,\n workspace: string,\n scope?: string | null,\n sandbox?: SandboxPlan | null,\n sessionKey?: string | null,\n): void {\n const spec = resolveShellSpec();\n const tasks = shellTasks;\n const shell = makeRunShell(\n workspace,\n spec,\n tasks,\n scope ?? null,\n sandbox ?? null,\n sessionKey ?? null,\n );\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 (sandbox?.enforced\n ? `沙箱:${describeSandbox(sandbox)};工作区外的写入会被系统拒绝。`\n : \"\"),\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","/**\n * 路径安全辅助函数\n *\n * 判断目标路径是否真正位于工作区根目录之内。\n *\n * 仅做字符串比较(path.relative)无法防御符号链接逃逸:\n * 工作区内若存在指向外部的 symlink,`workspace/link/secret.txt`\n * 的字符串前缀合法,但实际读写发生在链接目标(工作区外)。\n *\n * 因此这里用 fs.realpathSync 解析符号链接后再与根目录的 realpath\n * 比较。目标尚不存在(如 write_file 新建文件)时,向上回溯到最近\n * 已存在的祖先目录解析,再拼回剩余路径段。\n *\n * 校验通过时把解析后的真实路径一并返回:调用方必须用它(而非原始\n * 相对路径)做实际 fs 操作,否则「校验后、操作前」路径组件被替换为\n * 指向外部的 symlink 仍会逃逸(TOCTOU)。\n */\nimport * as fs from \"fs\";\nimport * as path from \"path\";\n\n/**\n * 判断 `path.relative(root, target)` 的结果是否落在 root 之内。\n *\n * 逃逸必然恰好是 `..` 或以 `..` + 分隔符开头,不能用 `startsWith(\"..\")`:\n * 工作区里名为 `..foo` 的合法条目会被误判为逃逸而拒绝。\n * 跨盘符时(Windows)path.relative 返回绝对路径,一并拒绝。\n *\n * 导出供自行做 `path.relative` 校验的调用方复用(如目录浏览新建目录、\n * git 面板的 repo / path 参数),避免各处重复写 `startsWith(\"..\")`。\n */\nexport function isInsideRelative(rel: string): boolean {\n return (\n !path.isAbsolute(rel) && rel !== \"..\" && !rel.startsWith(`..${path.sep}`)\n );\n}\n\n/** 是否为文件系统根(\"/\"、\"C:\\\\\") */\nfunction isFilesystemRoot(p: string): boolean {\n return path.parse(p).root === p;\n}\n\n/** 校验通过的路径:root 为工作区根的真实路径,real 为目标的真实路径 */\nexport interface ResolvedPath {\n root: string;\n real: string;\n}\n\n/**\n * 校验 target(相对 workspace 的路径)是否位于 workspace 内,并返回\n * 解析符号链接后的真实路径;不在工作区内时返回 null。\n *\n * - 解析符号链接:路径中任何 symlink 组件都会先 realpath 再校验;\n * broken symlink(存在但无法解析)一律拒绝。\n * - 目标不存在时(写新文件):回溯到最近存在的祖先做 realpath,\n * 再拼接剩余段,仍能捕获祖先链中的 symlink 逃逸。\n * - 工作区根若就是文件系统根(`WORKSPACE=/`):此时任何绝对路径都在\n * 其内,等同没有沙箱,直接判为越界。\n */\nexport function resolveWithinRoot(\n workspace: string,\n target: string,\n): ResolvedPath | null {\n const abs = path.resolve(workspace, target);\n\n let rootReal: string;\n try {\n rootReal = fs.realpathSync(workspace);\n } catch {\n // 根目录不存在(如权限引擎尚未初始化工作区):\n // 没有实际目录结构即无 symlink 逃逸面,退化为纯字符串路径比较。\n const rootAbs = path.resolve(workspace);\n if (!isInsideRelative(path.relative(rootAbs, abs))) return null;\n return { root: rootAbs, real: abs };\n }\n if (isFilesystemRoot(rootReal)) return null;\n\n let real: string;\n try {\n real = fs.realpathSync(abs);\n } catch {\n // abs 不存在:向上回溯找最近存在的祖先\n let probe = abs;\n const tail: string[] = [];\n for (;;) {\n // 组件存在(含 broken symlink)但 realpath 失败 → 无法解析,拒绝\n const exists = fs.existsSync(probe) || fs.lstatSync(probe, { throwIfNoEntry: false }) !== undefined;\n if (exists) {\n try {\n real = path.join(fs.realpathSync(probe), ...tail);\n break;\n } catch {\n return null;\n }\n }\n tail.unshift(path.basename(probe));\n const parent = path.dirname(probe);\n if (parent === probe) return null; // 已到文件系统根\n probe = parent;\n }\n }\n\n if (!isInsideRelative(path.relative(rootReal, real))) return null;\n return { root: rootReal, real };\n}\n\n/**\n * 判断 target 是否安全地位于 workspace 内(不需要真实路径时用这个)。\n *\n * 注意:删除类操作不应据此改用 realpath 去 unlink —— 那会删掉符号链接\n * 指向的目标而非链接本身;这类操作请用 resolveEntryWithinRoot。\n */\nexport function isWithinRoot(workspace: string, target: string): boolean {\n return resolveWithinRoot(workspace, target) !== null;\n}\n\n/**\n * 解析「目录项本身」的路径:父目录取 realpath,最后一段保持原样。\n *\n * 供 unlink / rename 等作用于条目自身的操作用:父目录不可被 symlink\n * 替换,而最后一段若是符号链接,被操作的是链接本身,不会被跟随。\n */\nexport function resolveEntryWithinRoot(\n workspace: string,\n target: string,\n): ResolvedPath | null {\n const abs = path.resolve(workspace, target);\n const parent = resolveWithinRoot(workspace, path.dirname(abs));\n if (!parent) return null;\n return { root: parent.root, real: path.join(parent.real, path.basename(abs)) };\n}\n"],"mappings":"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","names":["path","import_node_child_process","resolve","import_node_child_process","import_node_child_process"]}