@zqagent/core 0.1.30 → 0.2.4

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 (86) hide show
  1. package/CONTRIBUTING.md +65 -0
  2. package/LICENSE +190 -0
  3. package/README.md +42 -165
  4. package/README.oss.en.md +161 -0
  5. package/README.oss.md +165 -0
  6. package/README.oss.zh-TW.md +165 -0
  7. package/SECURITY.md +51 -0
  8. package/dist/{ToolRegistry-BmvPxgMr.d.cts → ToolRegistry-C3pTFiaJ.d.cts} +13 -4
  9. package/dist/{ToolRegistry-BmvPxgMr.d.ts → ToolRegistry-C3pTFiaJ.d.ts} +13 -4
  10. package/dist/cli/index.cjs +17799 -2062
  11. package/dist/cli/index.cjs.map +1 -1
  12. package/dist/cli/index.js +16729 -985
  13. package/dist/cli/index.js.map +1 -1
  14. package/dist/{explore-OI6htHN1.d.cts → explore-D4lCKXbA.d.ts} +160 -19
  15. package/dist/{explore-Pmhyk4gv.d.ts → explore-Dj1j_GyN.d.cts} +160 -19
  16. package/dist/index.cjs +3626 -453
  17. package/dist/index.cjs.map +1 -1
  18. package/dist/index.d.cts +337 -27
  19. package/dist/index.d.ts +337 -27
  20. package/dist/index.js +3617 -452
  21. package/dist/index.js.map +1 -1
  22. package/dist/server/index.cjs +14029 -1482
  23. package/dist/server/index.cjs.map +1 -1
  24. package/dist/server/index.js +14036 -1482
  25. package/dist/server/index.js.map +1 -1
  26. package/dist/tools/ask.cjs +14 -0
  27. package/dist/tools/ask.cjs.map +1 -1
  28. package/dist/tools/ask.d.cts +8 -2
  29. package/dist/tools/ask.d.ts +8 -2
  30. package/dist/tools/ask.js +13 -0
  31. package/dist/tools/ask.js.map +1 -1
  32. package/dist/tools/diff.cjs +59 -30
  33. package/dist/tools/diff.cjs.map +1 -1
  34. package/dist/tools/diff.d.cts +1 -1
  35. package/dist/tools/diff.d.ts +1 -1
  36. package/dist/tools/diff.js +59 -30
  37. package/dist/tools/diff.js.map +1 -1
  38. package/dist/tools/directories.d.cts +1 -1
  39. package/dist/tools/directories.d.ts +1 -1
  40. package/dist/tools/explore.cjs +1050 -309
  41. package/dist/tools/explore.cjs.map +1 -1
  42. package/dist/tools/explore.d.cts +2 -2
  43. package/dist/tools/explore.d.ts +2 -2
  44. package/dist/tools/explore.js +1057 -309
  45. package/dist/tools/explore.js.map +1 -1
  46. package/dist/tools/files.cjs +58 -43
  47. package/dist/tools/files.cjs.map +1 -1
  48. package/dist/tools/files.d.cts +10 -4
  49. package/dist/tools/files.d.ts +10 -4
  50. package/dist/tools/files.js +56 -42
  51. package/dist/tools/files.js.map +1 -1
  52. package/dist/tools/git.cjs +816 -63
  53. package/dist/tools/git.cjs.map +1 -1
  54. package/dist/tools/git.d.cts +200 -2
  55. package/dist/tools/git.d.ts +200 -2
  56. package/dist/tools/git.js +788 -62
  57. package/dist/tools/git.js.map +1 -1
  58. package/dist/tools/media.cjs +1191 -0
  59. package/dist/tools/media.cjs.map +1 -0
  60. package/dist/tools/media.d.cts +31 -0
  61. package/dist/tools/media.d.ts +31 -0
  62. package/dist/tools/media.js +1159 -0
  63. package/dist/tools/media.js.map +1 -0
  64. package/dist/tools/plan.d.cts +1 -1
  65. package/dist/tools/plan.d.ts +1 -1
  66. package/dist/tools/search.cjs +513 -33
  67. package/dist/tools/search.cjs.map +1 -1
  68. package/dist/tools/search.d.cts +12 -1
  69. package/dist/tools/search.d.ts +12 -1
  70. package/dist/tools/search.js +508 -33
  71. package/dist/tools/search.js.map +1 -1
  72. package/dist/tools/shell.cjs +631 -41
  73. package/dist/tools/shell.cjs.map +1 -1
  74. package/dist/tools/shell.d.cts +37 -4
  75. package/dist/tools/shell.d.ts +37 -4
  76. package/dist/tools/shell.js +626 -40
  77. package/dist/tools/shell.js.map +1 -1
  78. package/dist/tools/todo.d.cts +1 -1
  79. package/dist/tools/todo.d.ts +1 -1
  80. package/dist/tools/web.cjs +51 -18
  81. package/dist/tools/web.cjs.map +1 -1
  82. package/dist/tools/web.d.cts +1 -1
  83. package/dist/tools/web.d.ts +1 -1
  84. package/dist/tools/web.js +51 -18
  85. package/dist/tools/web.js.map +1 -1
  86. package/package.json +43 -8
@@ -30,87 +30,677 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/tools/shell.ts
31
31
  var shell_exports = {};
32
32
  __export(shell_exports, {
33
- registerShellTool: () => registerShellTool
33
+ decodeOutput: () => decodeOutput,
34
+ killShellTask: () => killShellTask,
35
+ listShellTasks: () => listShellTasks,
36
+ registerShellTool: () => registerShellTool,
37
+ resetShellSession: () => resetShellSession
34
38
  });
35
39
  module.exports = __toCommonJS(shell_exports);
36
- var import_child_process = require("child_process");
40
+ var fs2 = __toESM(require("fs"), 1);
41
+ var path2 = __toESM(require("path"), 1);
42
+
43
+ // src/tools/execAsync.ts
44
+ var import_node_child_process2 = require("child_process");
45
+
46
+ // src/tools/killTree.ts
47
+ var import_node_child_process = require("child_process");
48
+ function killTree(pid) {
49
+ if (!pid) return;
50
+ if (process.platform === "win32") {
51
+ try {
52
+ (0, import_node_child_process.spawnSync)("taskkill", ["/pid", String(pid), "/T", "/F"], { windowsHide: true });
53
+ } catch {
54
+ }
55
+ return;
56
+ }
57
+ try {
58
+ process.kill(-pid, "SIGKILL");
59
+ } catch {
60
+ try {
61
+ process.kill(pid, "SIGKILL");
62
+ } catch {
63
+ }
64
+ }
65
+ }
66
+
67
+ // src/tools/execAsync.ts
68
+ var liveChildren = /* @__PURE__ */ new Set();
69
+ var exitCleanupInstalled = false;
70
+ function trackChild(pid) {
71
+ if (!pid) return;
72
+ liveChildren.add(pid);
73
+ if (exitCleanupInstalled) return;
74
+ exitCleanupInstalled = true;
75
+ process.once("exit", () => {
76
+ for (const p of liveChildren) killTree(p);
77
+ });
78
+ }
79
+ var DEFAULT_MAX_BUFFER = 1024 * 1024 * 5;
80
+ var KILL_GRACE_MS = 1e3;
81
+ function makeSink(maxBuffer) {
82
+ const chunks = [];
83
+ let len = 0;
84
+ let over = false;
85
+ return {
86
+ push(chunk) {
87
+ if (len >= maxBuffer) {
88
+ over = true;
89
+ return;
90
+ }
91
+ const room = maxBuffer - len;
92
+ if (chunk.length > room) {
93
+ chunks.push(chunk.subarray(0, room));
94
+ len = maxBuffer;
95
+ over = true;
96
+ return;
97
+ }
98
+ chunks.push(chunk);
99
+ len += chunk.length;
100
+ },
101
+ done: () => Buffer.concat(chunks),
102
+ over: () => over
103
+ };
104
+ }
105
+ function execAsync(file, argv, opts = {}) {
106
+ return new Promise((resolve2) => {
107
+ const maxBuffer = opts.maxBuffer ?? DEFAULT_MAX_BUFFER;
108
+ const out = makeSink(maxBuffer);
109
+ const err = makeSink(maxBuffer);
110
+ if (opts.signal?.aborted) {
111
+ resolve2({
112
+ status: null,
113
+ stdout: Buffer.alloc(0),
114
+ stderr: Buffer.alloc(0),
115
+ timedOut: false,
116
+ truncated: false,
117
+ aborted: true
118
+ });
119
+ return;
120
+ }
121
+ let child;
122
+ try {
123
+ child = (0, import_node_child_process2.spawn)(file, argv, {
124
+ ...opts.cwd !== void 0 ? { cwd: opts.cwd } : {},
125
+ ...opts.env !== void 0 ? { env: opts.env } : {},
126
+ // 自成进程组(POSIX):超时/中断时用 killTree 一次收掉整棵进程树
127
+ detached: process.platform !== "win32",
128
+ stdio: [opts.input === void 0 ? "ignore" : "pipe", "pipe", "pipe"],
129
+ windowsHide: true
130
+ });
131
+ } catch (e) {
132
+ resolve2({
133
+ status: null,
134
+ stdout: Buffer.alloc(0),
135
+ stderr: Buffer.alloc(0),
136
+ error: e,
137
+ timedOut: false,
138
+ truncated: false
139
+ });
140
+ return;
141
+ }
142
+ trackChild(child.pid);
143
+ let settled = false;
144
+ let timedOut = false;
145
+ let aborted = false;
146
+ let spawnError;
147
+ let timer = null;
148
+ let graceTimer = null;
149
+ const onAbort = () => {
150
+ aborted = true;
151
+ killAndSettle();
152
+ };
153
+ const finish = (status) => {
154
+ if (settled) return;
155
+ settled = true;
156
+ if (timer) clearTimeout(timer);
157
+ if (graceTimer) clearTimeout(graceTimer);
158
+ if (child.pid !== void 0) liveChildren.delete(child.pid);
159
+ opts.signal?.removeEventListener("abort", onAbort);
160
+ const result = {
161
+ status,
162
+ stdout: out.done(),
163
+ stderr: err.done(),
164
+ timedOut,
165
+ truncated: out.over() || err.over()
166
+ };
167
+ if (aborted) result.aborted = true;
168
+ if (spawnError) result.error = spawnError;
169
+ resolve2(result);
170
+ };
171
+ const killAndSettle = () => {
172
+ killTree(child.pid);
173
+ if (settled || graceTimer) return;
174
+ graceTimer = setTimeout(() => {
175
+ child.stdout?.destroy();
176
+ child.stderr?.destroy();
177
+ finish(null);
178
+ }, KILL_GRACE_MS);
179
+ };
180
+ if (opts.timeoutMs !== void 0 && opts.timeoutMs > 0) {
181
+ timer = setTimeout(() => {
182
+ timedOut = true;
183
+ killAndSettle();
184
+ }, opts.timeoutMs);
185
+ }
186
+ opts.signal?.addEventListener("abort", onAbort, { once: true });
187
+ child.stdout?.on("data", (c) => out.push(c));
188
+ child.stderr?.on("data", (c) => err.push(c));
189
+ child.on("error", (e) => {
190
+ spawnError = e;
191
+ if (child.pid === void 0) finish(null);
192
+ });
193
+ if (opts.input !== void 0 && child.stdin) {
194
+ child.stdin.on("error", () => {
195
+ });
196
+ child.stdin.end(opts.input);
197
+ }
198
+ child.on("close", (code) => finish(code));
199
+ });
200
+ }
201
+
202
+ // src/tools/shellRuntime.ts
37
203
  var fs = __toESM(require("fs"), 1);
38
204
  var path = __toESM(require("path"), 1);
39
- function makeRunShell(workspace) {
40
- let cwd = workspace;
41
- return function run_shell(args) {
205
+ var WINDOWS_SUFFIXES = [".exe", ".cmd", ".bat", ".com", ""];
206
+ function pathApi(platform) {
207
+ return platform === "win32" ? path.win32 : path.posix;
208
+ }
209
+ function defaultFileExists(p) {
210
+ try {
211
+ return fs.statSync(p).isFile();
212
+ } catch {
213
+ return false;
214
+ }
215
+ }
216
+ function joinPath(platform, ...parts) {
217
+ return pathApi(platform).join(...parts);
218
+ }
219
+ function findExecutable(name, ctx) {
220
+ if (name.includes("/") || name.includes("\\")) {
221
+ return ctx.fileExists(name) ? name : null;
222
+ }
223
+ const suffixes = ctx.platform === "win32" ? WINDOWS_SUFFIXES : [""];
224
+ const delimiter = ctx.platform === "win32" ? ";" : ":";
225
+ const dirs = (ctx.env.PATH ?? "").split(delimiter).filter((d) => d.length > 0);
226
+ for (const dir of dirs) {
227
+ for (const suffix of suffixes) {
228
+ const candidate = joinPath(ctx.platform, dir, name + suffix);
229
+ if (ctx.fileExists(candidate)) return candidate;
230
+ }
231
+ }
232
+ return null;
233
+ }
234
+ function kindFromBasename(base) {
235
+ const b = base.toLowerCase().replace(/\.(exe|cmd|bat|com)$/, "");
236
+ if (b === "busybox") return "busybox";
237
+ if (b === "powershell" || b === "pwsh") return "powershell";
238
+ if (b === "cmd") return "cmd";
239
+ return "sh";
240
+ }
241
+ function busyboxArgs(file, command) {
242
+ const base = (file ? path.win32.basename(file) : "busybox").toLowerCase().replace(/\.(exe|cmd|bat|com)$/, "");
243
+ return base === "busybox" ? ["sh", "-c", command] : ["-c", command];
244
+ }
245
+ function argsFor(kind, command) {
246
+ switch (kind) {
247
+ case "busybox":
248
+ return ["sh", "-c", command];
249
+ case "powershell":
250
+ return ["-NoProfile", "-NonInteractive", "-Command", command];
251
+ case "cmd":
252
+ return ["/d", "/s", "/c", command];
253
+ default:
254
+ return ["-c", command];
255
+ }
256
+ }
257
+ function makeSpec(kind, file, label) {
258
+ return {
259
+ kind,
260
+ file,
261
+ label,
262
+ buildArgs: (command) => kind === "busybox" ? busyboxArgs(file, command) : argsFor(kind, command)
263
+ };
264
+ }
265
+ function defaultLabelFor(kind) {
266
+ switch (kind) {
267
+ case "busybox":
268
+ return "busybox ash";
269
+ case "powershell":
270
+ return "PowerShell";
271
+ case "cmd":
272
+ return "cmd.exe";
273
+ case "default":
274
+ return "\u7CFB\u7EDF\u9ED8\u8BA4 shell";
275
+ default:
276
+ return "sh";
277
+ }
278
+ }
279
+ function findBusybox(ctx, execPath) {
280
+ const explicit = (ctx.env.ZQAGENT_BUSYBOX ?? "").trim();
281
+ if (explicit) return ctx.fileExists(explicit) ? explicit : null;
282
+ const exeDir = pathApi(ctx.platform).dirname(execPath);
283
+ const candidates = [
284
+ joinPath(ctx.platform, exeDir, "busybox.exe"),
285
+ joinPath(ctx.platform, exeDir, "sh.exe"),
286
+ joinPath(ctx.platform, exeDir, "bin", "busybox.exe"),
287
+ joinPath(ctx.platform, exeDir, "bin", "sh.exe")
288
+ ];
289
+ for (const candidate of candidates) {
290
+ if (ctx.fileExists(candidate)) return candidate;
291
+ }
292
+ return null;
293
+ }
294
+ function resolveRequested(requested, ctx, execPath) {
295
+ const lower = requested.toLowerCase();
296
+ if (lower === "cmd") {
297
+ const file = findExecutable("cmd", ctx) ?? ctx.env.ComSpec ?? ctx.env.COMSPEC ?? "cmd.exe";
298
+ return makeSpec("cmd", file, "cmd.exe");
299
+ }
300
+ if (lower === "powershell" || lower === "pwsh") {
301
+ const file = findExecutable(lower, ctx) ?? findExecutable("powershell", ctx) ?? `${lower}.exe`;
302
+ return makeSpec("powershell", file, "PowerShell");
303
+ }
304
+ if (lower === "busybox") {
305
+ const found = findBusybox(ctx, execPath) ?? findExecutable("busybox", ctx);
306
+ return makeSpec("busybox", found ?? requested, "busybox ash");
307
+ }
308
+ if (["sh", "bash", "dash", "zsh", "ash"].includes(lower)) {
309
+ return makeSpec("sh", findExecutable(lower, ctx) ?? lower, lower);
310
+ }
311
+ const base = pathApi(ctx.platform).basename(requested);
312
+ const kind = kindFromBasename(base);
313
+ return makeSpec(kind, requested, base || defaultLabelFor(kind));
314
+ }
315
+ function resolveShellSpec(opts = {}) {
316
+ const platform = opts.platform ?? process.platform;
317
+ const env = opts.env ?? process.env;
318
+ const execPath = opts.execPath ?? process.execPath;
319
+ const ctx = {
320
+ platform,
321
+ env,
322
+ fileExists: opts.fileExists ?? defaultFileExists
323
+ };
324
+ const requested = (env.ZQAGENT_SHELL ?? "").trim();
325
+ if (requested && requested.toLowerCase() !== "auto") {
326
+ return resolveRequested(requested, ctx, execPath);
327
+ }
328
+ if (platform !== "win32") {
329
+ return makeSpec("default", null, "/bin/sh");
330
+ }
331
+ const busybox = findBusybox(ctx, execPath);
332
+ if (busybox) return makeSpec("busybox", busybox, "busybox ash (POSIX)");
333
+ const bash = findExecutable("bash", ctx);
334
+ if (bash) return makeSpec("sh", bash, "bash (Git for Windows)");
335
+ const shell = findExecutable("pwsh", ctx) ?? findExecutable("powershell", ctx);
336
+ if (shell) return makeSpec("powershell", shell, "PowerShell");
337
+ return makeSpec("default", null, env.ComSpec ?? env.COMSPEC ?? "cmd.exe");
338
+ }
339
+ function wrapCommand(kind, command, platform = process.platform) {
340
+ if (kind === "powershell") {
341
+ return "$OutputEncoding = [Console]::OutputEncoding = [Text.Encoding]::UTF8; " + command + "\nif ($null -ne $LASTEXITCODE) { exit $LASTEXITCODE }";
342
+ }
343
+ if (kind === "cmd" || kind === "default" && platform === "win32") {
344
+ return `chcp 65001>nul & ${command}`;
345
+ }
346
+ return command;
347
+ }
348
+ function describeShell(spec, platform) {
349
+ if (spec.kind === "sh" || spec.kind === "busybox") {
350
+ return `\u5F53\u524D\u5E73\u53F0 ${platform}\uFF0Cshell=${spec.label}\uFF1A\u8BF7\u4F7F\u7528 POSIX \u98CE\u683C\u547D\u4EE4\u3002`;
351
+ }
352
+ if (spec.kind === "powershell") {
353
+ return `\u5F53\u524D\u5E73\u53F0 ${platform}\uFF0Cshell=PowerShell\uFF1Als/cat/rm/cp/mv/pwd \u7B49\u522B\u540D\u53EF\u7528\uFF0C\u4E5F\u53EF\u7528 cmdlet\uFF1B\u8DEF\u5F84\u5206\u9694\u7B26\u4E3A "\\"\u3002`;
354
+ }
355
+ if (spec.kind === "default" && platform !== "win32") {
356
+ return `\u5F53\u524D\u5E73\u53F0 ${platform}\uFF0Cshell=${spec.label}\uFF1A\u8BF7\u4F7F\u7528 POSIX \u98CE\u683C\u547D\u4EE4\u3002`;
357
+ }
358
+ return `\u5F53\u524D\u5E73\u53F0 ${platform}\uFF0Cshell=${spec.label}\uFF1A\u8BF7\u4F7F\u7528 Windows \u547D\u4EE4\uFF08dir/type/del/copy/move/findstr\uFF09\uFF0C\u591A\u6761\u547D\u4EE4\u7528 & \u6216 && \u8FDE\u63A5\uFF1B\u4E0D\u8981\u4F7F\u7528 ls/cat/rm/grep \u7B49 POSIX \u547D\u4EE4\u3002`;
359
+ }
360
+ function parseCdChain(command, opts = {}) {
361
+ const platform = opts.platform ?? process.platform;
362
+ const trimmed = command.trim();
363
+ const chain = /^([\s\S]*?)\s*&&\s*([\s\S]+)$/.exec(trimmed);
364
+ const head = (chain ? chain[1] : trimmed).trim();
365
+ const rest = chain ? chain[2].trim() : null;
366
+ const m = /^(?:cd|set-location|sl|pushd|push-location)\b\s*([\s\S]*)$/i.exec(head);
367
+ if (!m) return { target: null, rest: null };
368
+ let target = m[1].trim();
369
+ target = target.replace(/^-(?:path|literalpath)\s+/i, "");
370
+ if (platform === "win32") target = target.replace(/^\/d\s+/i, "");
371
+ target = target.replace(/^"(.*)"$/, "$1").replace(/^'(.*)'$/, "$1").trim();
372
+ if (!target || target === "-") return { target: null, rest: null };
373
+ return { target, rest };
374
+ }
375
+
376
+ // src/tools/shellBackground.ts
377
+ var import_node_child_process3 = require("child_process");
378
+ var DEFAULT_BUFFER_LIMIT = 64 * 1024;
379
+ var RingBuffer = class {
380
+ constructor(limit = DEFAULT_BUFFER_LIMIT) {
381
+ this.limit = limit;
382
+ }
383
+ limit;
384
+ chunks = [];
385
+ size = 0;
386
+ add(chunk) {
387
+ if (!chunk) return;
388
+ this.chunks.push(chunk);
389
+ this.size += chunk.length;
390
+ while (this.size > this.limit && this.chunks.length > 1) {
391
+ this.size -= this.chunks.shift().length;
392
+ }
393
+ if (this.size > this.limit) {
394
+ const joined = this.chunks.join("");
395
+ this.chunks = [joined.slice(-this.limit)];
396
+ this.size = this.chunks[0].length;
397
+ }
398
+ }
399
+ get() {
400
+ const joined = this.chunks.join("");
401
+ return joined.length > this.limit ? joined.slice(-this.limit) : joined;
402
+ }
403
+ };
404
+ var ShellTaskManager = class {
405
+ tasks = /* @__PURE__ */ new Map();
406
+ seq = 0;
407
+ cleanupInstalled = false;
408
+ /** 启动一个后台任务,立即返回快照(不等退出) */
409
+ start(opts) {
410
+ this.installCleanup();
411
+ const id = `shell-${++this.seq}-${Date.now().toString(36)}`;
412
+ const child = (0, import_node_child_process3.spawn)(opts.file, opts.argv, {
413
+ cwd: opts.cwd,
414
+ env: opts.env,
415
+ detached: process.platform !== "win32",
416
+ windowsHide: true,
417
+ stdio: ["ignore", "pipe", "pipe"]
418
+ });
419
+ const task = {
420
+ id,
421
+ command: opts.command,
422
+ scope: opts.scope ?? null,
423
+ child,
424
+ stdout: new RingBuffer(),
425
+ stderr: new RingBuffer(),
426
+ status: "running",
427
+ exitCode: null,
428
+ signal: null,
429
+ startedAt: Date.now(),
430
+ endedAt: null
431
+ };
432
+ this.tasks.set(id, task);
433
+ child.stdout?.on("data", (d) => task.stdout.add(d.toString("utf-8")));
434
+ child.stderr?.on("data", (d) => task.stderr.add(d.toString("utf-8")));
435
+ child.on("error", (err) => task.stderr.add(`[spawn error] ${err.message}
436
+ `));
437
+ child.on("exit", (code, signal) => {
438
+ task.exitCode = code;
439
+ task.signal = signal;
440
+ task.endedAt = Date.now();
441
+ if (task.status !== "killed") task.status = "exited";
442
+ });
443
+ child.unref();
444
+ return this.snapshot(task);
445
+ }
446
+ /** 读取任务输出与状态 */
447
+ output(taskId) {
448
+ const task = this.tasks.get(taskId);
449
+ if (!task) return { error: `\u672A\u77E5\u7684\u540E\u53F0\u4EFB\u52A1: ${taskId}` };
450
+ return this.snapshot(task);
451
+ }
452
+ /** 终止任务(进程树)并返回快照;传 scope 时只允许终止该归属下的任务 */
453
+ kill(taskId, scope) {
454
+ const task = this.tasks.get(taskId);
455
+ if (!task) return { error: `\u672A\u77E5\u7684\u540E\u53F0\u4EFB\u52A1: ${taskId}` };
456
+ if (scope !== void 0 && task.scope !== scope) {
457
+ return { error: `\u672A\u77E5\u7684\u540E\u53F0\u4EFB\u52A1: ${taskId}` };
458
+ }
459
+ if (task.status === "running") {
460
+ task.status = "killed";
461
+ task.endedAt = Date.now();
462
+ killTree(task.child.pid);
463
+ }
464
+ return this.snapshot(task);
465
+ }
466
+ /** 列出任务快照(调试/展示用);传 scope 时只返回该归属下的任务 */
467
+ list(scope) {
468
+ const all = [...this.tasks.values()];
469
+ return (scope === void 0 ? all : all.filter((t) => t.scope === scope)).map(
470
+ (t) => this.snapshot(t)
471
+ );
472
+ }
473
+ /** 终止全部任务(进程退出时调用) */
474
+ disposeAll() {
475
+ for (const task of this.tasks.values()) {
476
+ if (task.status === "running") {
477
+ task.status = "killed";
478
+ killTree(task.child.pid);
479
+ }
480
+ }
481
+ }
482
+ snapshot(task) {
483
+ return {
484
+ task_id: task.id,
485
+ command: task.command,
486
+ status: task.status,
487
+ running: task.status === "running",
488
+ exit_code: task.exitCode,
489
+ signal: task.signal,
490
+ stdout: task.stdout.get(),
491
+ stderr: task.stderr.get(),
492
+ duration_ms: (task.endedAt ?? Date.now()) - task.startedAt
493
+ };
494
+ }
495
+ installCleanup() {
496
+ if (this.cleanupInstalled) return;
497
+ this.cleanupInstalled = true;
498
+ process.once("exit", () => this.disposeAll());
499
+ }
500
+ };
501
+
502
+ // src/tools/shell.ts
503
+ var MAX_STDOUT = 1e4;
504
+ var MAX_STDERR = 5e3;
505
+ var MAX_BUFFER = 1024 * 1024 * 5;
506
+ var shellSessions = /* @__PURE__ */ new Map();
507
+ function sessionFor(workspace) {
508
+ const key = path2.resolve(workspace);
509
+ let s = shellSessions.get(key);
510
+ if (!s) {
511
+ s = { cwd: key };
512
+ shellSessions.set(key, s);
513
+ }
514
+ return s;
515
+ }
516
+ function resetShellSession(workspace) {
517
+ shellSessions.delete(path2.resolve(workspace));
518
+ }
519
+ function resolveInvocation(spec, prepared) {
520
+ if (spec.file !== null) {
521
+ return { file: spec.file, argv: spec.buildArgs(prepared) };
522
+ }
523
+ if (process.platform === "win32") {
524
+ const comspec = process.env.ComSpec ?? process.env.COMSPEC ?? "cmd.exe";
525
+ return { file: comspec, argv: ["/d", "/s", "/c", prepared] };
526
+ }
527
+ return { file: "/bin/sh", argv: ["-c", prepared] };
528
+ }
529
+ function shellEnv() {
530
+ return {
531
+ ...process.env,
532
+ GIT_TERMINAL_PROMPT: "0",
533
+ PYTHONUNBUFFERED: "1",
534
+ PYTHONUTF8: "1",
535
+ PYTHONIOENCODING: "utf-8"
536
+ };
537
+ }
538
+ function decodeOutput(buf) {
539
+ if (!buf || buf.length === 0) return "";
540
+ try {
541
+ return new TextDecoder("utf-8", { fatal: true }).decode(buf);
542
+ } catch {
543
+ for (const enc of ["gbk", "big5", "shift_jis", "latin1"]) {
544
+ try {
545
+ return new TextDecoder(enc).decode(buf);
546
+ } catch {
547
+ }
548
+ }
549
+ return buf.toString("latin1");
550
+ }
551
+ }
552
+ function makeRunShell(workspace, spec, tasks, scope) {
553
+ const session = sessionFor(workspace);
554
+ return async function run_shell(args, ctx) {
42
555
  const command = String(args.command ?? "");
43
556
  const description = String(args.description ?? "").slice(0, 100);
557
+ const background = args.run_in_background === true;
44
558
  const rawTimeout = Number(args.timeout_seconds ?? 120);
45
559
  const timeoutSec = Number.isFinite(rawTimeout) ? rawTimeout : 120;
46
560
  const timeout = Math.min(Math.max(timeoutSec, 1), 600) * 1e3;
47
561
  if (!command.trim()) {
48
562
  return { error: "\u547D\u4EE4\u4E0D\u80FD\u4E3A\u7A7A" };
49
563
  }
50
- try {
51
- const result = (0, import_child_process.execSync)(command, {
52
- cwd,
53
- timeout,
54
- maxBuffer: 1024 * 1024 * 5,
55
- // 5MB 输出上限
56
- encoding: "utf-8",
57
- // 关闭 git 的交互式凭据提示;Python 输出不缓冲
58
- env: { ...process.env, GIT_TERMINAL_PROMPT: "0", PYTHONUNBUFFERED: "1" }
59
- });
60
- if (command.trim().startsWith("cd ")) {
61
- const newDir = command.trim().slice(3).trim();
62
- try {
63
- const resolved = path.resolve(cwd, newDir);
64
- if (fs.existsSync(resolved) && fs.statSync(resolved).isDirectory()) {
65
- cwd = resolved;
66
- }
67
- } catch {
564
+ const { target, rest } = parseCdChain(command);
565
+ let toRun = command;
566
+ if (target) {
567
+ try {
568
+ const resolved = path2.resolve(session.cwd, target);
569
+ if (fs2.existsSync(resolved) && fs2.statSync(resolved).isDirectory()) {
570
+ session.cwd = resolved;
68
571
  }
572
+ } catch {
69
573
  }
574
+ if (rest) toRun = rest;
575
+ }
576
+ const prepared = wrapCommand(spec.kind, toRun);
577
+ const { file, argv } = resolveInvocation(spec, prepared);
578
+ const env = shellEnv();
579
+ if (background) {
580
+ const snap = tasks.start({ command, file, argv, cwd: session.cwd, env, scope });
581
+ return {
582
+ task_id: snap.task_id,
583
+ status: snap.status,
584
+ cwd: session.cwd,
585
+ note: "\u5DF2\u5728\u540E\u53F0\u542F\u52A8\u3002\u7528 shell_task_output \u67E5\u770B\u8F93\u51FA\uFF0Cshell_task_kill \u7EC8\u6B62\u3002"
586
+ };
587
+ }
588
+ const result = await execAsync(file, argv, {
589
+ cwd: session.cwd,
590
+ timeoutMs: timeout,
591
+ maxBuffer: MAX_BUFFER,
592
+ env,
593
+ signal: ctx?.signal
594
+ });
595
+ const stdout = decodeOutput(result.stdout).slice(-MAX_STDOUT);
596
+ const stderr = decodeOutput(result.stderr).slice(-MAX_STDERR);
597
+ if (result.aborted) {
70
598
  return {
71
- stdout: result.toString().slice(-1e4),
72
- // 返回最后 10K
73
- stderr: "",
74
- exit_code: 0,
75
- cwd
599
+ stdout,
600
+ stderr,
601
+ exit_code: result.status ?? 1,
602
+ cwd: session.cwd,
603
+ interrupted: true,
604
+ error: "\u547D\u4EE4\u5DF2\u88AB\u7528\u6237\u4E2D\u65AD"
76
605
  };
77
- } catch (exc) {
78
- const err = exc;
606
+ }
607
+ if (result.error && !result.timedOut) {
608
+ const message = result.error.message;
79
609
  return {
80
- stdout: (err.stdout?.toString() ?? "").slice(-5e3),
81
- stderr: (err.stderr?.toString() ?? "").slice(-2e3),
82
- exit_code: err.status ?? 1,
83
- error: err.message?.slice(0, 200),
84
- cwd
610
+ stdout,
611
+ stderr: stderr || message,
612
+ exit_code: 1,
613
+ cwd: session.cwd,
614
+ error: message.slice(0, 200)
85
615
  };
86
616
  }
617
+ return {
618
+ stdout,
619
+ stderr,
620
+ exit_code: result.status ?? 1,
621
+ cwd: session.cwd,
622
+ ...result.timedOut ? { timed_out: true, error: `\u547D\u4EE4\u8D85\u65F6\uFF08${timeoutSec}s\uFF09\u540E\u5DF2\u7EC8\u6B62` } : {}
623
+ };
87
624
  };
88
625
  }
89
- function registerShellTool(registry, workspace) {
90
- const shell = makeRunShell(workspace);
626
+ var shellTasks = new ShellTaskManager();
627
+ function listShellTasks(scope) {
628
+ return shellTasks.list(scope);
629
+ }
630
+ function killShellTask(taskId, scope) {
631
+ return shellTasks.kill(taskId, scope);
632
+ }
633
+ function registerShellTool(registry, workspace, scope) {
634
+ const spec = resolveShellSpec();
635
+ const tasks = shellTasks;
636
+ const shell = makeRunShell(workspace, spec, tasks, scope ?? null);
91
637
  registry.register(
92
- (args) => shell(args),
638
+ (args, ctx) => shell(args, ctx),
93
639
  { category: "shell", riskLevel: "high", capabilities: ["run_shell"], requiresApproval: true },
94
640
  {
95
641
  type: "function",
96
642
  function: {
97
643
  name: "run_shell",
98
- description: "\u5728\u6301\u4E45\u5316 shell \u4F1A\u8BDD\u4E2D\u8FD0\u884C\u547D\u4EE4 (cwd \u548C\u73AF\u5883\u53D8\u91CF\u5728\u591A\u6B21\u8C03\u7528\u4E4B\u95F4\u4FDD\u6301)\u3002\u8F93\u51FA\u622A\u65AD\u5230\u6700\u540E\u90E8\u5206\u3002\u4F20\u9012 description \u8BF4\u660E\u547D\u4EE4\u7528\u9014 (\u663E\u793A\u5728\u5BA1\u6279\u63D0\u793A\u4E2D)\uFF0C\u4F20\u9012 timeout_seconds \u63A7\u5236\u8D85\u65F6\u3002",
644
+ description: `\u5728\u6301\u4E45\u5316 shell \u4F1A\u8BDD\u4E2D\u8FD0\u884C\u547D\u4EE4 (cwd \u548C\u73AF\u5883\u53D8\u91CF\u5728\u591A\u6B21\u8C03\u7528\u4E4B\u95F4\u4FDD\u6301)\u3002\u8F93\u51FA\u622A\u65AD\u5230\u6700\u540E\u90E8\u5206\uFF0Cstdout \u4E0E stderr \u90FD\u4F1A\u8FD4\u56DE\u3002\u4F20\u9012 description \u8BF4\u660E\u547D\u4EE4\u7528\u9014 (\u663E\u793A\u5728\u5BA1\u6279\u63D0\u793A\u4E2D)\uFF0C\u4F20\u9012 timeout_seconds \u63A7\u5236\u8D85\u65F6\u3002\u5BF9\u4E8E\u5F00\u53D1\u670D\u52A1\u5668 / watch \u7B49\u957F\u9A7B\u8FDB\u7A0B\uFF0C\u8BBE\u7F6E run_in_background=true\uFF0C\u968F\u540E\u7528 shell_task_output \u8F6E\u8BE2\u8F93\u51FA\u3001shell_task_kill \u7EC8\u6B62\u3002\u8FD0\u884C\u73AF\u5883\uFF1A${describeShell(spec, process.platform)}`,
99
645
  parameters: {
100
646
  type: "object",
101
647
  properties: {
102
648
  command: { type: "string", description: "\u8981\u6267\u884C\u7684 shell \u547D\u4EE4" },
103
649
  description: { type: "string", description: "\u547D\u4EE4\u7528\u9014\u7684\u7B80\u77ED\u63CF\u8FF0 (\u663E\u793A\u5728\u5BA1\u6279\u4E2D)" },
104
- timeout_seconds: { type: "integer", description: "\u8D85\u65F6\u79D2\u6570 (\u9ED8\u8BA4 120\uFF0C\u6700\u5927 600)" }
650
+ timeout_seconds: { type: "integer", description: "\u8D85\u65F6\u79D2\u6570 (\u9ED8\u8BA4 120\uFF0C\u6700\u5927 600)" },
651
+ run_in_background: {
652
+ type: "boolean",
653
+ description: "\u662F\u5426\u540E\u53F0\u8FD0\u884C (\u9ED8\u8BA4 false)\u3002\u9002\u5408\u5F00\u53D1\u670D\u52A1\u5668\u7B49\u957F\u9A7B\u8FDB\u7A0B\u3002"
654
+ }
105
655
  },
106
656
  required: ["command"]
107
657
  }
108
658
  }
109
659
  }
110
660
  );
661
+ registry.register(
662
+ (args) => tasks.output(String(args.task_id ?? "")),
663
+ { category: "shell", riskLevel: "low", capabilities: ["run_shell"], requiresApproval: false },
664
+ {
665
+ type: "function",
666
+ function: {
667
+ name: "shell_task_output",
668
+ description: "\u67E5\u770B run_shell(run_in_background=true) \u542F\u52A8\u7684\u540E\u53F0\u4EFB\u52A1\u7684\u7D2F\u8BA1\u8F93\u51FA\u4E0E\u72B6\u6001\u3002\u8FD4\u56DE status (running/exited/killed)\u3001exit_code\u3001stdout\u3001stderr\u3002",
669
+ parameters: {
670
+ type: "object",
671
+ properties: {
672
+ task_id: { type: "string", description: "run_shell \u8FD4\u56DE\u7684 task_id" }
673
+ },
674
+ required: ["task_id"]
675
+ }
676
+ }
677
+ }
678
+ );
679
+ registry.register(
680
+ (args) => tasks.kill(String(args.task_id ?? ""), scope ?? null),
681
+ { category: "shell", riskLevel: "medium", capabilities: ["run_shell"], requiresApproval: false },
682
+ {
683
+ type: "function",
684
+ function: {
685
+ name: "shell_task_kill",
686
+ description: "\u7EC8\u6B62\u540E\u53F0\u4EFB\u52A1\uFF08\u8FDE\u540C\u5176\u5B50\u8FDB\u7A0B\u6811\uFF09\u3002\u8FD4\u56DE\u4EFB\u52A1\u7684\u6700\u7EC8\u72B6\u6001\u3002",
687
+ parameters: {
688
+ type: "object",
689
+ properties: {
690
+ task_id: { type: "string", description: "run_shell \u8FD4\u56DE\u7684 task_id" }
691
+ },
692
+ required: ["task_id"]
693
+ }
694
+ }
695
+ }
696
+ );
111
697
  }
112
698
  // Annotate the CommonJS export names for ESM import in node:
113
699
  0 && (module.exports = {
114
- registerShellTool
700
+ decodeOutput,
701
+ killShellTask,
702
+ listShellTasks,
703
+ registerShellTool,
704
+ resetShellSession
115
705
  });
116
706
  //# sourceMappingURL=shell.cjs.map