@zqagent/core 0.1.30 → 0.2.5

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