jinzd-ai-cli 0.4.204 → 0.4.205

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.
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  ConfigManager
4
- } from "./chunk-BENRC3OO.js";
4
+ } from "./chunk-TZ7SNVSG.js";
5
5
  import "./chunk-TZQHYZKT.js";
6
- import "./chunk-SCQOEDVL.js";
6
+ import "./chunk-3RET7PL7.js";
7
7
  import {
8
8
  atomicWriteFileSync
9
9
  } from "./chunk-IW3Q7AE5.js";
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/core/constants.ts
4
- var VERSION = "0.4.204";
4
+ var VERSION = "0.4.205";
5
5
  var APP_NAME = "ai-cli";
6
6
  var CONFIG_DIR_NAME = ".aicli";
7
7
  var CONFIG_FILE_NAME = "config.json";
@@ -6,7 +6,7 @@ import { platform } from "os";
6
6
  import chalk from "chalk";
7
7
 
8
8
  // src/core/constants.ts
9
- var VERSION = "0.4.204";
9
+ var VERSION = "0.4.205";
10
10
  var APP_NAME = "ai-cli";
11
11
  var CONFIG_DIR_NAME = ".aicli";
12
12
  var CONFIG_FILE_NAME = "config.json";
@@ -5,10 +5,10 @@ import {
5
5
  } from "./chunk-T2NL5ZIA.js";
6
6
  import {
7
7
  runTestsTool
8
- } from "./chunk-GYN2PTY2.js";
8
+ } from "./chunk-VOV2PWZU.js";
9
9
  import {
10
10
  runTool
11
- } from "./chunk-JLZ5QA2I.js";
11
+ } from "./chunk-XIYSFN4U.js";
12
12
  import {
13
13
  getDangerLevel,
14
14
  isFileWriteTool,
@@ -26,7 +26,7 @@ import {
26
26
  SUBAGENT_ALLOWED_TOOLS,
27
27
  SUBAGENT_DEFAULT_MAX_ROUNDS,
28
28
  SUBAGENT_MAX_ROUNDS_LIMIT
29
- } from "./chunk-SCQOEDVL.js";
29
+ } from "./chunk-3RET7PL7.js";
30
30
  import {
31
31
  fileCheckpoints
32
32
  } from "./chunk-4BKXL7SM.js";
@@ -44,7 +44,7 @@ import {
44
44
  // src/tools/builtin/bash.ts
45
45
  import { spawn } from "child_process";
46
46
  import { existsSync as existsSync2, readdirSync, statSync } from "fs";
47
- import { platform } from "os";
47
+ import { platform as platform2 } from "os";
48
48
  import { resolve } from "path";
49
49
 
50
50
  // src/tools/undo-stack.ts
@@ -196,9 +196,32 @@ function runWithSessionKey(sessionKey, fn) {
196
196
  return als.run({ sessionKey: key }, fn);
197
197
  }
198
198
 
199
- // src/tools/builtin/bash.ts
199
+ // src/tools/win-shell.ts
200
+ import { platform } from "os";
200
201
  var IS_WINDOWS = platform() === "win32";
201
- var SHELL = IS_WINDOWS ? "powershell.exe" : process.env["SHELL"] ?? "/bin/bash";
202
+ var WIN_UTF8_PREAMBLE = "[Console]::OutputEncoding = [System.Text.Encoding]::UTF8; $OutputEncoding = [System.Text.Encoding]::UTF8; ";
203
+ function shellInvocation(command) {
204
+ if (IS_WINDOWS) {
205
+ return {
206
+ shell: "powershell.exe",
207
+ args: ["-NoProfile", "-NonInteractive", "-Command", WIN_UTF8_PREAMBLE + command]
208
+ };
209
+ }
210
+ return { shell: process.env["SHELL"] ?? "/bin/bash", args: ["-c", command] };
211
+ }
212
+ function decodeWindowsBuffer(buf) {
213
+ const utf8 = buf.toString("utf-8");
214
+ if (!utf8.includes("\uFFFD")) return utf8;
215
+ try {
216
+ return new TextDecoder("gbk").decode(buf);
217
+ } catch {
218
+ return utf8;
219
+ }
220
+ }
221
+
222
+ // src/tools/builtin/bash.ts
223
+ var IS_WINDOWS2 = platform2() === "win32";
224
+ var SHELL = IS_WINDOWS2 ? "powershell.exe" : process.env["SHELL"] ?? "/bin/bash";
202
225
  var cwdBySession = /* @__PURE__ */ new Map();
203
226
  function getCwd() {
204
227
  const key = getCurrentSessionKey();
@@ -215,7 +238,7 @@ function setCwd(next) {
215
238
  var bashTool = {
216
239
  definition: {
217
240
  name: "bash",
218
- description: IS_WINDOWS ? `Execute commands in PowerShell. Supports mkdir, ls, cat, python, etc.
241
+ description: IS_WINDOWS2 ? `Execute commands in PowerShell. Supports mkdir, ls, cat, python, etc.
219
242
  Important rules:
220
243
  1. Each bash call runs in an independent subprocess; cd commands do not persist. To run in a specific directory, use the cwd parameter, or combine commands: e.g. "cd mydir; ls" or "mkdir mydir; cd mydir; New-Item file.txt".
221
244
  2. If a command fails (returns an error or non-zero exit code), stop immediately, report the error to the user, and do not retry the same or similar commands.
@@ -231,7 +254,7 @@ Important rules:
231
254
  parameters: {
232
255
  command: {
233
256
  type: "string",
234
- description: IS_WINDOWS ? `PowerShell command to execute. Combine multiple commands with semicolons, e.g.: "mkdir mydir; Set-Content mydir/file.txt 'content'"` : `${SHELL} command to execute. Combine multiple commands with &&, e.g.: "mkdir -p mydir && echo 'content' > mydir/file.txt"`,
257
+ description: IS_WINDOWS2 ? `PowerShell command to execute. Combine multiple commands with semicolons, e.g.: "mkdir mydir; Set-Content mydir/file.txt 'content'"` : `${SHELL} command to execute. Combine multiple commands with &&, e.g.: "mkdir -p mydir && echo 'content' > mydir/file.txt"`,
235
258
  required: true
236
259
  },
237
260
  cwd: {
@@ -283,9 +306,9 @@ Important rules:
283
306
  setCwd(resolved);
284
307
  }
285
308
  let actualCommand;
286
- if (IS_WINDOWS) {
309
+ if (IS_WINDOWS2) {
287
310
  const fixedCommand = fixWindowsDeleteCommand(command);
288
- actualCommand = `[Console]::OutputEncoding = [System.Text.Encoding]::UTF8; $OutputEncoding = [System.Text.Encoding]::UTF8; ${fixedCommand}`;
311
+ actualCommand = WIN_UTF8_PREAMBLE + fixedCommand;
289
312
  } else {
290
313
  actualCommand = command;
291
314
  }
@@ -329,7 +352,7 @@ Important rules:
329
352
  }
330
353
  updateCwdFromCommand(command, effectiveCwd);
331
354
  pushBashUndoEntries(beforeSnapshot, parsedTargetsBefore, effectiveCwd);
332
- const result = Buffer.isBuffer(stdout) ? IS_WINDOWS ? decodeWindowsBuffer(stdout) : stdout.toString("utf-8") : String(stdout ?? "");
355
+ const result = Buffer.isBuffer(stdout) ? IS_WINDOWS2 ? decodeWindowsBuffer(stdout) : stdout.toString("utf-8") : String(stdout ?? "");
333
356
  return result || "(command completed with no output)";
334
357
  } catch (err) {
335
358
  pushBashUndoEntries(beforeSnapshot, parsedTargetsBefore, effectiveCwd);
@@ -354,8 +377,8 @@ How to recover (pick ONE \u2014 do NOT retry the same command):
354
377
  );
355
378
  }
356
379
  if ("status" in execErr && execErr.status !== void 0) {
357
- const stderr = IS_WINDOWS && Buffer.isBuffer(execErr.stderr) ? decodeWindowsBuffer(execErr.stderr).trim() : execErr.stderr?.toString().trim() ?? "";
358
- const stdout = IS_WINDOWS && Buffer.isBuffer(execErr.stdout) ? decodeWindowsBuffer(execErr.stdout).trim() : execErr.stdout?.toString().trim() ?? "";
380
+ const stderr = IS_WINDOWS2 && Buffer.isBuffer(execErr.stderr) ? decodeWindowsBuffer(execErr.stderr).trim() : execErr.stderr?.toString().trim() ?? "";
381
+ const stdout = IS_WINDOWS2 && Buffer.isBuffer(execErr.stdout) ? decodeWindowsBuffer(execErr.stdout).trim() : execErr.stdout?.toString().trim() ?? "";
359
382
  const combined = [stdout, stderr].filter(Boolean).join("\n");
360
383
  const hint = buildErrorHint(command, combined);
361
384
  throw new ToolError(
@@ -389,23 +412,14 @@ function fixWindowsDeleteCommand(command) {
389
412
  }
390
413
  );
391
414
  }
392
- function decodeWindowsBuffer(buf) {
393
- const utf8 = buf.toString("utf-8");
394
- if (!utf8.includes("\uFFFD")) return utf8;
395
- try {
396
- return new TextDecoder("gbk").decode(buf);
397
- } catch {
398
- return utf8;
399
- }
400
- }
401
415
  function buildErrorHint(command, stderr) {
402
416
  const hints = [];
403
- if (IS_WINDOWS && /\|\|/.test(command) && /(\|\||is not a valid argument|不是此版本中的有效|ParserError|Unexpected token)/i.test(stderr)) {
417
+ if (IS_WINDOWS2 && /\|\|/.test(command) && /(\|\||is not a valid argument|不是此版本中的有效|ParserError|Unexpected token)/i.test(stderr)) {
404
418
  hints.push(
405
419
  `Hint: PowerShell parses '||' as a pipeline operator (PS 5.x) or logical-or (PS 7+), it CANNOT be passed inline as SQL string concatenation. Workaround: write the SQL to a local .sql file with write_file, then 'scp' it to the remote host and run 'psql -f /tmp/x.sql'. Do NOT try to escape || or wrap the command differently \u2014 it will not work.`
406
420
  );
407
421
  }
408
- if (IS_WINDOWS && /\bpython3\b/.test(command)) {
422
+ if (IS_WINDOWS2 && /\bpython3\b/.test(command)) {
409
423
  const trivialStderr = stderr.trim().length === 0 || /^[·\s]*exit 1\b/i.test(stderr.trim());
410
424
  if (/(not recognized|is not recognized|无法将.*识别)/i.test(stderr) || trivialStderr) {
411
425
  hints.push(
@@ -413,27 +427,27 @@ function buildErrorHint(command, stderr) {
413
427
  );
414
428
  }
415
429
  }
416
- if (IS_WINDOWS && /^\s*ssh\b/.test(command) && /\\"/.test(command) && /(syntax error|ERROR:|ParserError|unexpected|parser|意外的|语法|不是此版本|所在位置\s+行)/i.test(stderr)) {
430
+ if (IS_WINDOWS2 && /^\s*ssh\b/.test(command) && /\\"/.test(command) && /(syntax error|ERROR:|ParserError|unexpected|parser|意外的|语法|不是此版本|所在位置\s+行)/i.test(stderr)) {
417
431
  hints.push(
418
432
  `Hint: SSH + nested quotes ("...\\"...\\"") is fragile across PS \u2192 ssh \u2192 remote-shell \u2192 psql layers. Use the file-based flow: (1) write_file locally to a .sql file, (2) 'scp x.sql root@host:/tmp/', (3) 'ssh root@host "sudo -u postgres psql -d <db> -f /tmp/x.sql"'. Or for remote-only SQL: 'ssh host "cat > /tmp/x.sql << \\'SQLEOF\\'\\n...\\nSQLEOF"' then run psql -f. Avoid inline psql -c with embedded quotes.`
419
433
  );
420
434
  }
421
- if (IS_WINDOWS && /\bmkdir\b/.test(command) && /\s-p\b/.test(command) && /(已存在|already exists|ItemExistsUnauthorizedAccessError|exists)/i.test(stderr)) {
435
+ if (IS_WINDOWS2 && /\bmkdir\b/.test(command) && /\s-p\b/.test(command) && /(已存在|already exists|ItemExistsUnauthorizedAccessError|exists)/i.test(stderr)) {
422
436
  hints.push(
423
437
  `Hint: PowerShell's mkdir does not support the '-p' flag. When the directory already exists it exits with code 1. Use instead: New-Item -ItemType Directory -Force -Path <dir> ('-Force' silently succeeds even if the directory already exists, mimicking 'mkdir -p').`
424
438
  );
425
439
  }
426
- if (IS_WINDOWS && /Remove-Item/i.test(command) && /cannot find path|no such file|exit 1/i.test(stderr || command)) {
440
+ if (IS_WINDOWS2 && /Remove-Item/i.test(command) && /cannot find path|no such file|exit 1/i.test(stderr || command)) {
427
441
  hints.push(
428
442
  `Hint: On Windows, "Remove-Item ... -ErrorAction SilentlyContinue" still makes the powershell process exit 1 when no files match (even though the error is silenced). Use Test-Path as a guard: @('tmp_a.sql','tmp_b.sql','tmp_c.csv') | Where-Object { Test-Path $_ } | Remove-Item -Force \u2014 Test-Path returns a bool without raising, so missing paths are filtered out and exit code is 0.`
429
443
  );
430
444
  }
431
- if (IS_WINDOWS && /-(?:EA|ErrorAction)\s+(?:SilentlyContinue|Ignore)\b/i.test(command) && /\b(Get-ChildItem|Get-Item|Get-Content|gci|gi|gc|dir|ls)\b/i.test(command) && !/Remove-Item/i.test(command) && !/Test-Path/i.test(command) && /cannot find path|no such file|does not exist|exit 1|不存在|找不到/i.test(stderr || command)) {
445
+ if (IS_WINDOWS2 && /-(?:EA|ErrorAction)\s+(?:SilentlyContinue|Ignore)\b/i.test(command) && /\b(Get-ChildItem|Get-Item|Get-Content|gci|gi|gc|dir|ls)\b/i.test(command) && !/Remove-Item/i.test(command) && !/Test-Path/i.test(command) && /cannot find path|no such file|does not exist|exit 1|不存在|找不到/i.test(stderr || command)) {
432
446
  hints.push(
433
447
  `Hint: On Windows, "-ErrorAction SilentlyContinue" silences the error MESSAGE but does NOT change the exit code \u2014 the process still exits 1 when the path doesn't exist (because powershell.exe -NonInteractive flips the exit code on any non-terminating error, silenced or not). If you're just probing for existence, use Test-Path instead \u2014 it returns $true/$false without raising: \`if (Test-Path ./mydir) { Get-ChildItem ./mydir }\` or unconditionally use \`New-Item -ItemType Directory -Force -Path ./mydir\` which is idempotent. Don't retry the same -ErrorAction SilentlyContinue probe expecting a different result.`
434
448
  );
435
449
  }
436
- if (IS_WINDOWS && /\bWrite-(?:Content|File)\b/i.test(command) && /(not recognized|无法将.*识别|不是.*cmdlet|CommandNotFoundException)/i.test(stderr)) {
450
+ if (IS_WINDOWS2 && /\bWrite-(?:Content|File)\b/i.test(command) && /(not recognized|无法将.*识别|不是.*cmdlet|CommandNotFoundException)/i.test(stderr)) {
437
451
  hints.push(
438
452
  `Hint: 'Write-Content' / 'Write-File' are NOT PowerShell cmdlets (common LLM hallucination). To write a file, prefer the built-in 'write_file' tool \u2014 it handles encoding/escaping cleanly and supports diff preview. If you must stay in bash: use 'Set-Content -Path <file> -Value <string> -Encoding utf8' (overwrites) or 'Add-Content' (appends) or 'Out-File -FilePath <file> -Encoding utf8'.`
439
453
  );
@@ -454,6 +468,18 @@ function detectBlockingCommand(command) {
454
468
  if (logcat && !/(?:^|\s)-(?:d|t|c|g)\b/.test(logcat[1] ?? "")) {
455
469
  return blockingMessage("adb logcat", "stream device logs");
456
470
  }
471
+ if (/\bStart-Process\b/i.test(command) && /\b(?:emulator|qemu[\w-]*)(?:\.exe)?\b/i.test(command)) {
472
+ return `This launches a long-running emulator/qemu process via Start-Process, which DETACHES a GUI child that aicli cannot track or stop with task_stop \u2014 and in practice that child often dies together with this bash call, leaving nothing running.
473
+
474
+ Use the 'task_create' tool instead so the process is tracked and stoppable:
475
+ task_create(command: "<sdk>\\emulator\\emulator.exe -avd <name> -no-boot-anim", description: "launch emulator")
476
+
477
+ Then poll readiness with SHORT bash calls (these DO exit):
478
+ adb devices # is the device listed?
479
+ adb shell getprop sys.boot_completed # prints "1" once fully booted
480
+
481
+ [Do NOT launch long-running GUI processes via Start-Process in bash \u2014 use task_create.]`;
482
+ }
457
483
  return null;
458
484
  }
459
485
  function blockingMessage(example, what) {
@@ -533,7 +559,7 @@ function pushBashUndoEntries(beforeSnapshot, parsedTargetsBefore, cwd) {
533
559
  }
534
560
  function runShell(command, opts) {
535
561
  return new Promise((resolvePromise) => {
536
- const shellArgs = IS_WINDOWS ? ["-NoProfile", "-NonInteractive", "-Command", command] : ["-c", command];
562
+ const shellArgs = IS_WINDOWS2 ? ["-NoProfile", "-NonInteractive", "-Command", command] : ["-c", command];
537
563
  const child = spawn(SHELL, shellArgs, {
538
564
  cwd: opts.cwd,
539
565
  env: opts.env,
@@ -596,7 +622,7 @@ function runShell(command, opts) {
596
622
  }
597
623
  function killChild(child) {
598
624
  if (child.killed || child.exitCode !== null) return;
599
- if (IS_WINDOWS && child.pid) {
625
+ if (IS_WINDOWS2 && child.pid) {
600
626
  try {
601
627
  spawn("taskkill", ["/PID", String(child.pid), "/T", "/F"], {
602
628
  windowsHide: true,
@@ -3122,8 +3148,8 @@ function collectMatchingFiles(dirPath, rootPath, regex, results, maxResults) {
3122
3148
 
3123
3149
  // src/tools/builtin/run-interactive.ts
3124
3150
  import { spawn as spawn2 } from "child_process";
3125
- import { platform as platform2 } from "os";
3126
- var IS_WINDOWS2 = platform2() === "win32";
3151
+ import { platform as platform3 } from "os";
3152
+ var IS_WINDOWS3 = platform3() === "win32";
3127
3153
  var runInteractiveTool = {
3128
3154
  definition: {
3129
3155
  name: "run_interactive",
@@ -3203,7 +3229,7 @@ var runInteractiveTool = {
3203
3229
  let lineIdx = 0;
3204
3230
  const writeNextLine = () => {
3205
3231
  if (lineIdx < stdinLines.length && !child.stdin.destroyed) {
3206
- const line = stdinLines[lineIdx++] + (IS_WINDOWS2 ? "\r\n" : "\n");
3232
+ const line = stdinLines[lineIdx++] + (IS_WINDOWS3 ? "\r\n" : "\n");
3207
3233
  const canContinue = child.stdin.write(line);
3208
3234
  if (canContinue) {
3209
3235
  setTimeout(writeNextLine, 150);
@@ -4516,16 +4542,29 @@ var spawnAgentTool = {
4516
4542
  // src/tools/builtin/task-manager.ts
4517
4543
  import { spawn as spawn3 } from "child_process";
4518
4544
  import { randomUUID } from "crypto";
4519
- import { platform as platform3 } from "os";
4545
+ import { platform as platform4 } from "os";
4520
4546
  var MAX_OUTPUT_CHARS = 1e4;
4547
+ var MAX_OUTPUT_BYTES = 64 * 1024;
4521
4548
  var MAX_TASKS = 20;
4522
4549
  var tasks = /* @__PURE__ */ new Map();
4523
- function appendOutput(task, field, chunk) {
4524
- task[field] += chunk;
4525
- if (task[field].length > MAX_OUTPUT_CHARS) {
4526
- task[field] = "... [truncated] ...\n" + task[field].slice(-MAX_OUTPUT_CHARS);
4550
+ function capChunks(chunks) {
4551
+ let total = 0;
4552
+ for (const c of chunks) total += c.length;
4553
+ while (total > MAX_OUTPUT_BYTES && chunks.length > 1) {
4554
+ total -= chunks.shift().length;
4527
4555
  }
4528
4556
  }
4557
+ function decodeField(chunks) {
4558
+ const buf = Buffer.concat(chunks);
4559
+ const text = IS_WINDOWS ? decodeWindowsBuffer(buf) : buf.toString("utf-8");
4560
+ return text.length > MAX_OUTPUT_CHARS ? "... [truncated] ...\n" + text.slice(-MAX_OUTPUT_CHARS) : text;
4561
+ }
4562
+ function appendOutput(task, field, chunk) {
4563
+ const chunks = field === "stdout" ? task._stdoutChunks : task._stderrChunks;
4564
+ chunks.push(chunk);
4565
+ capChunks(chunks);
4566
+ task[field] = decodeField(chunks);
4567
+ }
4529
4568
  function createTask(command, description) {
4530
4569
  if (tasks.size >= MAX_TASKS) {
4531
4570
  for (const [id2, t] of tasks) {
@@ -4536,13 +4575,11 @@ function createTask(command, description) {
4536
4575
  }
4537
4576
  }
4538
4577
  const id = randomUUID().slice(0, 8);
4539
- const isWin = platform3() === "win32";
4540
- const shell = isWin ? "cmd" : "sh";
4541
- const shellFlag = isWin ? "/c" : "-c";
4542
- const proc = spawn3(shell, [shellFlag, command], {
4578
+ const { shell, args } = shellInvocation(command);
4579
+ const proc = spawn3(shell, args, {
4543
4580
  stdio: ["ignore", "pipe", "pipe"],
4544
4581
  detached: false,
4545
- env: { ...process.env, PYTHONUTF8: "1" }
4582
+ env: { ...process.env, PYTHONUTF8: "1", PYTHONIOENCODING: "utf-8" }
4546
4583
  });
4547
4584
  const task = {
4548
4585
  id,
@@ -4554,10 +4591,12 @@ function createTask(command, description) {
4554
4591
  status: "running",
4555
4592
  exitCode: null,
4556
4593
  stdout: "",
4557
- stderr: ""
4594
+ stderr: "",
4595
+ _stdoutChunks: [],
4596
+ _stderrChunks: []
4558
4597
  };
4559
- proc.stdout?.on("data", (chunk) => appendOutput(task, "stdout", chunk.toString("utf-8")));
4560
- proc.stderr?.on("data", (chunk) => appendOutput(task, "stderr", chunk.toString("utf-8")));
4598
+ proc.stdout?.on("data", (chunk) => appendOutput(task, "stdout", chunk));
4599
+ proc.stderr?.on("data", (chunk) => appendOutput(task, "stderr", chunk));
4561
4600
  proc.on("close", (code) => {
4562
4601
  task.endTime = Date.now();
4563
4602
  task.exitCode = code;
@@ -4573,7 +4612,9 @@ Process error: ${err.message}`;
4573
4612
  return task;
4574
4613
  }
4575
4614
  function listTasks() {
4576
- return [...tasks.values()].map(({ process: _p, ...rest }) => rest);
4615
+ return [...tasks.values()].map(
4616
+ ({ process: _p, _stdoutChunks: _o, _stderrChunks: _e, ...rest }) => rest
4617
+ );
4577
4618
  }
4578
4619
  function getTaskOutput(id) {
4579
4620
  const task = tasks.get(id);
@@ -4585,7 +4626,7 @@ function stopTask(id) {
4585
4626
  if (!task || task.status !== "running") return false;
4586
4627
  try {
4587
4628
  const proc = task.process;
4588
- if (platform3() === "win32" && proc.pid) {
4629
+ if (platform4() === "win32" && proc.pid) {
4589
4630
  try {
4590
4631
  spawn3("taskkill", ["/PID", String(proc.pid), "/T", "/F"], {
4591
4632
  windowsHide: true,
@@ -4620,7 +4661,7 @@ function stopTask(id) {
4620
4661
  var taskCreateTool = {
4621
4662
  definition: {
4622
4663
  name: "task_create",
4623
- description: `Start a command running in the background and return immediately with a task ID (monitor via task_list, stop via task_stop). Use this for ANY long-running or never-exiting command, including: dev servers (npm run dev, metro / react-native start), Android emulator launch (emulator -avd X), device log streaming (adb logcat), file watchers, and builds you want to monitor while continuing other work. Running such commands via 'bash' instead would block until timeout.`,
4664
+ description: `Start a command running in the background and return immediately with a task ID (monitor via task_list, stop via task_stop). Use this for ANY long-running or never-exiting command, including: dev servers (npm run dev, metro / react-native start), Android emulator launch (emulator -avd X), device log streaming (adb logcat), file watchers, and builds you want to monitor while continuing other work. Running such commands via 'bash' instead would block until timeout. The command runs in the SAME shell as the 'bash' tool (PowerShell on Windows, $SHELL on Unix), so a command you verified with 'bash' can be moved here verbatim \u2014 write it in PowerShell syntax on Windows (e.g. & "C:\\path\\app.exe" -flag), NOT cmd.exe syntax.`,
4624
4665
  parameters: {
4625
4666
  command: {
4626
4667
  type: "string",
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  CONFIG_DIR_NAME,
4
4
  VERSION
5
- } from "./chunk-SCQOEDVL.js";
5
+ } from "./chunk-3RET7PL7.js";
6
6
 
7
7
  // src/diagnostics/crash-log.ts
8
8
  import {
@@ -8,7 +8,7 @@ import {
8
8
  CONFIG_FILE_NAME,
9
9
  HISTORY_DIR_NAME,
10
10
  PLUGINS_DIR_NAME
11
- } from "./chunk-SCQOEDVL.js";
11
+ } from "./chunk-3RET7PL7.js";
12
12
 
13
13
  // src/config/config-manager.ts
14
14
  import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  TEST_TIMEOUT
4
- } from "./chunk-SCQOEDVL.js";
4
+ } from "./chunk-3RET7PL7.js";
5
5
 
6
6
  // src/tools/builtin/run-tests.ts
7
7
  import { execSync, spawnSync } from "child_process";
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  CONFIG_DIR_NAME
4
- } from "./chunk-SCQOEDVL.js";
4
+ } from "./chunk-3RET7PL7.js";
5
5
  import {
6
6
  atomicWriteFileSync
7
7
  } from "./chunk-IW3Q7AE5.js";
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  truncateForPersist
4
- } from "./chunk-B75A6AG4.js";
4
+ } from "./chunk-43MWRT2C.js";
5
5
  import {
6
6
  APP_NAME,
7
7
  CONFIG_DIR_NAME,
@@ -11,7 +11,7 @@ import {
11
11
  MCP_PROTOCOL_VERSION,
12
12
  MCP_TOOL_PREFIX,
13
13
  VERSION
14
- } from "./chunk-SCQOEDVL.js";
14
+ } from "./chunk-3RET7PL7.js";
15
15
 
16
16
  // src/mcp/client.ts
17
17
  import { spawn } from "child_process";
@@ -10,11 +10,11 @@ import {
10
10
  import "./chunk-SMFRJCXB.js";
11
11
  import {
12
12
  ConfigManager
13
- } from "./chunk-BENRC3OO.js";
13
+ } from "./chunk-TZ7SNVSG.js";
14
14
  import "./chunk-TZQHYZKT.js";
15
15
  import {
16
16
  VERSION
17
- } from "./chunk-SCQOEDVL.js";
17
+ } from "./chunk-3RET7PL7.js";
18
18
 
19
19
  // src/cli/ci.ts
20
20
  import { execFileSync, execSync } from "child_process";
@@ -36,7 +36,7 @@ import {
36
36
  TEST_TIMEOUT,
37
37
  VERSION,
38
38
  buildUserIdentityPrompt
39
- } from "./chunk-SCQOEDVL.js";
39
+ } from "./chunk-3RET7PL7.js";
40
40
  export {
41
41
  AGENTIC_BEHAVIOR_GUIDELINE,
42
42
  APP_NAME,
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  getConfigDirUsage,
4
4
  listRecentCrashes
5
- } from "./chunk-T5RTPF2Q.js";
5
+ } from "./chunk-JVYH66N2.js";
6
6
  import {
7
7
  ProviderRegistry
8
8
  } from "./chunk-UZISJ3KZ.js";
@@ -11,17 +11,17 @@ import {
11
11
  getTopFailingTools,
12
12
  getTopUsedTools,
13
13
  resetStats
14
- } from "./chunk-JLZ5QA2I.js";
14
+ } from "./chunk-XIYSFN4U.js";
15
15
  import "./chunk-SMFRJCXB.js";
16
16
  import {
17
17
  ConfigManager
18
- } from "./chunk-BENRC3OO.js";
18
+ } from "./chunk-TZ7SNVSG.js";
19
19
  import "./chunk-TZQHYZKT.js";
20
20
  import {
21
21
  DEV_STATE_FILE_NAME,
22
22
  MEMORY_FILE_NAME,
23
23
  VERSION
24
- } from "./chunk-SCQOEDVL.js";
24
+ } from "./chunk-3RET7PL7.js";
25
25
  import "./chunk-IW3Q7AE5.js";
26
26
 
27
27
  // src/diagnostics/doctor-cli.ts
@@ -36,7 +36,7 @@ import {
36
36
  VERSION,
37
37
  buildUserIdentityPrompt,
38
38
  runTestsTool
39
- } from "./chunk-26YWVRLT.js";
39
+ } from "./chunk-43H3D2PK.js";
40
40
  import {
41
41
  hasSemanticIndex,
42
42
  semanticSearch
@@ -4725,7 +4725,7 @@ var SessionManager = class {
4725
4725
  // src/tools/builtin/bash.ts
4726
4726
  import { spawn } from "child_process";
4727
4727
  import { existsSync as existsSync4, readdirSync as readdirSync2, statSync } from "fs";
4728
- import { platform } from "os";
4728
+ import { platform as platform2 } from "os";
4729
4729
  import { resolve } from "path";
4730
4730
 
4731
4731
  // src/tools/undo-stack.ts
@@ -4863,9 +4863,32 @@ function runWithSessionKey(sessionKey, fn) {
4863
4863
  return als.run({ sessionKey: key }, fn);
4864
4864
  }
4865
4865
 
4866
- // src/tools/builtin/bash.ts
4866
+ // src/tools/win-shell.ts
4867
+ import { platform } from "os";
4867
4868
  var IS_WINDOWS = platform() === "win32";
4868
- var SHELL = IS_WINDOWS ? "powershell.exe" : process.env["SHELL"] ?? "/bin/bash";
4869
+ var WIN_UTF8_PREAMBLE = "[Console]::OutputEncoding = [System.Text.Encoding]::UTF8; $OutputEncoding = [System.Text.Encoding]::UTF8; ";
4870
+ function shellInvocation(command) {
4871
+ if (IS_WINDOWS) {
4872
+ return {
4873
+ shell: "powershell.exe",
4874
+ args: ["-NoProfile", "-NonInteractive", "-Command", WIN_UTF8_PREAMBLE + command]
4875
+ };
4876
+ }
4877
+ return { shell: process.env["SHELL"] ?? "/bin/bash", args: ["-c", command] };
4878
+ }
4879
+ function decodeWindowsBuffer(buf) {
4880
+ const utf8 = buf.toString("utf-8");
4881
+ if (!utf8.includes("\uFFFD")) return utf8;
4882
+ try {
4883
+ return new TextDecoder("gbk").decode(buf);
4884
+ } catch {
4885
+ return utf8;
4886
+ }
4887
+ }
4888
+
4889
+ // src/tools/builtin/bash.ts
4890
+ var IS_WINDOWS2 = platform2() === "win32";
4891
+ var SHELL = IS_WINDOWS2 ? "powershell.exe" : process.env["SHELL"] ?? "/bin/bash";
4869
4892
  var cwdBySession = /* @__PURE__ */ new Map();
4870
4893
  function getCwd() {
4871
4894
  const key = getCurrentSessionKey();
@@ -4882,7 +4905,7 @@ function setCwd(next) {
4882
4905
  var bashTool = {
4883
4906
  definition: {
4884
4907
  name: "bash",
4885
- description: IS_WINDOWS ? `Execute commands in PowerShell. Supports mkdir, ls, cat, python, etc.
4908
+ description: IS_WINDOWS2 ? `Execute commands in PowerShell. Supports mkdir, ls, cat, python, etc.
4886
4909
  Important rules:
4887
4910
  1. Each bash call runs in an independent subprocess; cd commands do not persist. To run in a specific directory, use the cwd parameter, or combine commands: e.g. "cd mydir; ls" or "mkdir mydir; cd mydir; New-Item file.txt".
4888
4911
  2. If a command fails (returns an error or non-zero exit code), stop immediately, report the error to the user, and do not retry the same or similar commands.
@@ -4898,7 +4921,7 @@ Important rules:
4898
4921
  parameters: {
4899
4922
  command: {
4900
4923
  type: "string",
4901
- description: IS_WINDOWS ? `PowerShell command to execute. Combine multiple commands with semicolons, e.g.: "mkdir mydir; Set-Content mydir/file.txt 'content'"` : `${SHELL} command to execute. Combine multiple commands with &&, e.g.: "mkdir -p mydir && echo 'content' > mydir/file.txt"`,
4924
+ description: IS_WINDOWS2 ? `PowerShell command to execute. Combine multiple commands with semicolons, e.g.: "mkdir mydir; Set-Content mydir/file.txt 'content'"` : `${SHELL} command to execute. Combine multiple commands with &&, e.g.: "mkdir -p mydir && echo 'content' > mydir/file.txt"`,
4902
4925
  required: true
4903
4926
  },
4904
4927
  cwd: {
@@ -4950,9 +4973,9 @@ Important rules:
4950
4973
  setCwd(resolved);
4951
4974
  }
4952
4975
  let actualCommand;
4953
- if (IS_WINDOWS) {
4976
+ if (IS_WINDOWS2) {
4954
4977
  const fixedCommand = fixWindowsDeleteCommand(command);
4955
- actualCommand = `[Console]::OutputEncoding = [System.Text.Encoding]::UTF8; $OutputEncoding = [System.Text.Encoding]::UTF8; ${fixedCommand}`;
4978
+ actualCommand = WIN_UTF8_PREAMBLE + fixedCommand;
4956
4979
  } else {
4957
4980
  actualCommand = command;
4958
4981
  }
@@ -4996,7 +5019,7 @@ Important rules:
4996
5019
  }
4997
5020
  updateCwdFromCommand(command, effectiveCwd);
4998
5021
  pushBashUndoEntries(beforeSnapshot, parsedTargetsBefore, effectiveCwd);
4999
- const result = Buffer.isBuffer(stdout) ? IS_WINDOWS ? decodeWindowsBuffer(stdout) : stdout.toString("utf-8") : String(stdout ?? "");
5022
+ const result = Buffer.isBuffer(stdout) ? IS_WINDOWS2 ? decodeWindowsBuffer(stdout) : stdout.toString("utf-8") : String(stdout ?? "");
5000
5023
  return result || "(command completed with no output)";
5001
5024
  } catch (err) {
5002
5025
  pushBashUndoEntries(beforeSnapshot, parsedTargetsBefore, effectiveCwd);
@@ -5021,8 +5044,8 @@ How to recover (pick ONE \u2014 do NOT retry the same command):
5021
5044
  );
5022
5045
  }
5023
5046
  if ("status" in execErr && execErr.status !== void 0) {
5024
- const stderr = IS_WINDOWS && Buffer.isBuffer(execErr.stderr) ? decodeWindowsBuffer(execErr.stderr).trim() : execErr.stderr?.toString().trim() ?? "";
5025
- const stdout = IS_WINDOWS && Buffer.isBuffer(execErr.stdout) ? decodeWindowsBuffer(execErr.stdout).trim() : execErr.stdout?.toString().trim() ?? "";
5047
+ const stderr = IS_WINDOWS2 && Buffer.isBuffer(execErr.stderr) ? decodeWindowsBuffer(execErr.stderr).trim() : execErr.stderr?.toString().trim() ?? "";
5048
+ const stdout = IS_WINDOWS2 && Buffer.isBuffer(execErr.stdout) ? decodeWindowsBuffer(execErr.stdout).trim() : execErr.stdout?.toString().trim() ?? "";
5026
5049
  const combined = [stdout, stderr].filter(Boolean).join("\n");
5027
5050
  const hint = buildErrorHint(command, combined);
5028
5051
  throw new ToolError(
@@ -5056,23 +5079,14 @@ function fixWindowsDeleteCommand(command) {
5056
5079
  }
5057
5080
  );
5058
5081
  }
5059
- function decodeWindowsBuffer(buf) {
5060
- const utf8 = buf.toString("utf-8");
5061
- if (!utf8.includes("\uFFFD")) return utf8;
5062
- try {
5063
- return new TextDecoder("gbk").decode(buf);
5064
- } catch {
5065
- return utf8;
5066
- }
5067
- }
5068
5082
  function buildErrorHint(command, stderr) {
5069
5083
  const hints = [];
5070
- if (IS_WINDOWS && /\|\|/.test(command) && /(\|\||is not a valid argument|不是此版本中的有效|ParserError|Unexpected token)/i.test(stderr)) {
5084
+ if (IS_WINDOWS2 && /\|\|/.test(command) && /(\|\||is not a valid argument|不是此版本中的有效|ParserError|Unexpected token)/i.test(stderr)) {
5071
5085
  hints.push(
5072
5086
  `Hint: PowerShell parses '||' as a pipeline operator (PS 5.x) or logical-or (PS 7+), it CANNOT be passed inline as SQL string concatenation. Workaround: write the SQL to a local .sql file with write_file, then 'scp' it to the remote host and run 'psql -f /tmp/x.sql'. Do NOT try to escape || or wrap the command differently \u2014 it will not work.`
5073
5087
  );
5074
5088
  }
5075
- if (IS_WINDOWS && /\bpython3\b/.test(command)) {
5089
+ if (IS_WINDOWS2 && /\bpython3\b/.test(command)) {
5076
5090
  const trivialStderr = stderr.trim().length === 0 || /^[·\s]*exit 1\b/i.test(stderr.trim());
5077
5091
  if (/(not recognized|is not recognized|无法将.*识别)/i.test(stderr) || trivialStderr) {
5078
5092
  hints.push(
@@ -5080,27 +5094,27 @@ function buildErrorHint(command, stderr) {
5080
5094
  );
5081
5095
  }
5082
5096
  }
5083
- if (IS_WINDOWS && /^\s*ssh\b/.test(command) && /\\"/.test(command) && /(syntax error|ERROR:|ParserError|unexpected|parser|意外的|语法|不是此版本|所在位置\s+行)/i.test(stderr)) {
5097
+ if (IS_WINDOWS2 && /^\s*ssh\b/.test(command) && /\\"/.test(command) && /(syntax error|ERROR:|ParserError|unexpected|parser|意外的|语法|不是此版本|所在位置\s+行)/i.test(stderr)) {
5084
5098
  hints.push(
5085
5099
  `Hint: SSH + nested quotes ("...\\"...\\"") is fragile across PS \u2192 ssh \u2192 remote-shell \u2192 psql layers. Use the file-based flow: (1) write_file locally to a .sql file, (2) 'scp x.sql root@host:/tmp/', (3) 'ssh root@host "sudo -u postgres psql -d <db> -f /tmp/x.sql"'. Or for remote-only SQL: 'ssh host "cat > /tmp/x.sql << \\'SQLEOF\\'\\n...\\nSQLEOF"' then run psql -f. Avoid inline psql -c with embedded quotes.`
5086
5100
  );
5087
5101
  }
5088
- if (IS_WINDOWS && /\bmkdir\b/.test(command) && /\s-p\b/.test(command) && /(已存在|already exists|ItemExistsUnauthorizedAccessError|exists)/i.test(stderr)) {
5102
+ if (IS_WINDOWS2 && /\bmkdir\b/.test(command) && /\s-p\b/.test(command) && /(已存在|already exists|ItemExistsUnauthorizedAccessError|exists)/i.test(stderr)) {
5089
5103
  hints.push(
5090
5104
  `Hint: PowerShell's mkdir does not support the '-p' flag. When the directory already exists it exits with code 1. Use instead: New-Item -ItemType Directory -Force -Path <dir> ('-Force' silently succeeds even if the directory already exists, mimicking 'mkdir -p').`
5091
5105
  );
5092
5106
  }
5093
- if (IS_WINDOWS && /Remove-Item/i.test(command) && /cannot find path|no such file|exit 1/i.test(stderr || command)) {
5107
+ if (IS_WINDOWS2 && /Remove-Item/i.test(command) && /cannot find path|no such file|exit 1/i.test(stderr || command)) {
5094
5108
  hints.push(
5095
5109
  `Hint: On Windows, "Remove-Item ... -ErrorAction SilentlyContinue" still makes the powershell process exit 1 when no files match (even though the error is silenced). Use Test-Path as a guard: @('tmp_a.sql','tmp_b.sql','tmp_c.csv') | Where-Object { Test-Path $_ } | Remove-Item -Force \u2014 Test-Path returns a bool without raising, so missing paths are filtered out and exit code is 0.`
5096
5110
  );
5097
5111
  }
5098
- if (IS_WINDOWS && /-(?:EA|ErrorAction)\s+(?:SilentlyContinue|Ignore)\b/i.test(command) && /\b(Get-ChildItem|Get-Item|Get-Content|gci|gi|gc|dir|ls)\b/i.test(command) && !/Remove-Item/i.test(command) && !/Test-Path/i.test(command) && /cannot find path|no such file|does not exist|exit 1|不存在|找不到/i.test(stderr || command)) {
5112
+ if (IS_WINDOWS2 && /-(?:EA|ErrorAction)\s+(?:SilentlyContinue|Ignore)\b/i.test(command) && /\b(Get-ChildItem|Get-Item|Get-Content|gci|gi|gc|dir|ls)\b/i.test(command) && !/Remove-Item/i.test(command) && !/Test-Path/i.test(command) && /cannot find path|no such file|does not exist|exit 1|不存在|找不到/i.test(stderr || command)) {
5099
5113
  hints.push(
5100
5114
  `Hint: On Windows, "-ErrorAction SilentlyContinue" silences the error MESSAGE but does NOT change the exit code \u2014 the process still exits 1 when the path doesn't exist (because powershell.exe -NonInteractive flips the exit code on any non-terminating error, silenced or not). If you're just probing for existence, use Test-Path instead \u2014 it returns $true/$false without raising: \`if (Test-Path ./mydir) { Get-ChildItem ./mydir }\` or unconditionally use \`New-Item -ItemType Directory -Force -Path ./mydir\` which is idempotent. Don't retry the same -ErrorAction SilentlyContinue probe expecting a different result.`
5101
5115
  );
5102
5116
  }
5103
- if (IS_WINDOWS && /\bWrite-(?:Content|File)\b/i.test(command) && /(not recognized|无法将.*识别|不是.*cmdlet|CommandNotFoundException)/i.test(stderr)) {
5117
+ if (IS_WINDOWS2 && /\bWrite-(?:Content|File)\b/i.test(command) && /(not recognized|无法将.*识别|不是.*cmdlet|CommandNotFoundException)/i.test(stderr)) {
5104
5118
  hints.push(
5105
5119
  `Hint: 'Write-Content' / 'Write-File' are NOT PowerShell cmdlets (common LLM hallucination). To write a file, prefer the built-in 'write_file' tool \u2014 it handles encoding/escaping cleanly and supports diff preview. If you must stay in bash: use 'Set-Content -Path <file> -Value <string> -Encoding utf8' (overwrites) or 'Add-Content' (appends) or 'Out-File -FilePath <file> -Encoding utf8'.`
5106
5120
  );
@@ -5121,6 +5135,18 @@ function detectBlockingCommand(command) {
5121
5135
  if (logcat && !/(?:^|\s)-(?:d|t|c|g)\b/.test(logcat[1] ?? "")) {
5122
5136
  return blockingMessage("adb logcat", "stream device logs");
5123
5137
  }
5138
+ if (/\bStart-Process\b/i.test(command) && /\b(?:emulator|qemu[\w-]*)(?:\.exe)?\b/i.test(command)) {
5139
+ return `This launches a long-running emulator/qemu process via Start-Process, which DETACHES a GUI child that aicli cannot track or stop with task_stop \u2014 and in practice that child often dies together with this bash call, leaving nothing running.
5140
+
5141
+ Use the 'task_create' tool instead so the process is tracked and stoppable:
5142
+ task_create(command: "<sdk>\\emulator\\emulator.exe -avd <name> -no-boot-anim", description: "launch emulator")
5143
+
5144
+ Then poll readiness with SHORT bash calls (these DO exit):
5145
+ adb devices # is the device listed?
5146
+ adb shell getprop sys.boot_completed # prints "1" once fully booted
5147
+
5148
+ [Do NOT launch long-running GUI processes via Start-Process in bash \u2014 use task_create.]`;
5149
+ }
5124
5150
  return null;
5125
5151
  }
5126
5152
  function blockingMessage(example, what) {
@@ -5200,7 +5226,7 @@ function pushBashUndoEntries(beforeSnapshot, parsedTargetsBefore, cwd) {
5200
5226
  }
5201
5227
  function runShell(command, opts) {
5202
5228
  return new Promise((resolvePromise) => {
5203
- const shellArgs = IS_WINDOWS ? ["-NoProfile", "-NonInteractive", "-Command", command] : ["-c", command];
5229
+ const shellArgs = IS_WINDOWS2 ? ["-NoProfile", "-NonInteractive", "-Command", command] : ["-c", command];
5204
5230
  const child = spawn(SHELL, shellArgs, {
5205
5231
  cwd: opts.cwd,
5206
5232
  env: opts.env,
@@ -5263,7 +5289,7 @@ function runShell(command, opts) {
5263
5289
  }
5264
5290
  function killChild(child) {
5265
5291
  if (child.killed || child.exitCode !== null) return;
5266
- if (IS_WINDOWS && child.pid) {
5292
+ if (IS_WINDOWS2 && child.pid) {
5267
5293
  try {
5268
5294
  spawn("taskkill", ["/PID", String(child.pid), "/T", "/F"], {
5269
5295
  windowsHide: true,
@@ -7837,8 +7863,8 @@ function collectMatchingFiles(dirPath, rootPath, regex, results, maxResults) {
7837
7863
 
7838
7864
  // src/tools/builtin/run-interactive.ts
7839
7865
  import { spawn as spawn2 } from "child_process";
7840
- import { platform as platform2 } from "os";
7841
- var IS_WINDOWS2 = platform2() === "win32";
7866
+ import { platform as platform3 } from "os";
7867
+ var IS_WINDOWS3 = platform3() === "win32";
7842
7868
  var runInteractiveTool = {
7843
7869
  definition: {
7844
7870
  name: "run_interactive",
@@ -7918,7 +7944,7 @@ var runInteractiveTool = {
7918
7944
  let lineIdx = 0;
7919
7945
  const writeNextLine = () => {
7920
7946
  if (lineIdx < stdinLines.length && !child.stdin.destroyed) {
7921
- const line = stdinLines[lineIdx++] + (IS_WINDOWS2 ? "\r\n" : "\n");
7947
+ const line = stdinLines[lineIdx++] + (IS_WINDOWS3 ? "\r\n" : "\n");
7922
7948
  const canContinue = child.stdin.write(line);
7923
7949
  if (canContinue) {
7924
7950
  setTimeout(writeNextLine, 150);
@@ -9231,16 +9257,29 @@ var spawnAgentTool = {
9231
9257
  // src/tools/builtin/task-manager.ts
9232
9258
  import { spawn as spawn3 } from "child_process";
9233
9259
  import { randomUUID } from "crypto";
9234
- import { platform as platform3 } from "os";
9260
+ import { platform as platform4 } from "os";
9235
9261
  var MAX_OUTPUT_CHARS = 1e4;
9262
+ var MAX_OUTPUT_BYTES = 64 * 1024;
9236
9263
  var MAX_TASKS = 20;
9237
9264
  var tasks = /* @__PURE__ */ new Map();
9238
- function appendOutput(task, field, chunk) {
9239
- task[field] += chunk;
9240
- if (task[field].length > MAX_OUTPUT_CHARS) {
9241
- task[field] = "... [truncated] ...\n" + task[field].slice(-MAX_OUTPUT_CHARS);
9265
+ function capChunks(chunks) {
9266
+ let total = 0;
9267
+ for (const c of chunks) total += c.length;
9268
+ while (total > MAX_OUTPUT_BYTES && chunks.length > 1) {
9269
+ total -= chunks.shift().length;
9242
9270
  }
9243
9271
  }
9272
+ function decodeField(chunks) {
9273
+ const buf = Buffer.concat(chunks);
9274
+ const text = IS_WINDOWS ? decodeWindowsBuffer(buf) : buf.toString("utf-8");
9275
+ return text.length > MAX_OUTPUT_CHARS ? "... [truncated] ...\n" + text.slice(-MAX_OUTPUT_CHARS) : text;
9276
+ }
9277
+ function appendOutput(task, field, chunk) {
9278
+ const chunks = field === "stdout" ? task._stdoutChunks : task._stderrChunks;
9279
+ chunks.push(chunk);
9280
+ capChunks(chunks);
9281
+ task[field] = decodeField(chunks);
9282
+ }
9244
9283
  function createTask(command, description) {
9245
9284
  if (tasks.size >= MAX_TASKS) {
9246
9285
  for (const [id2, t] of tasks) {
@@ -9251,13 +9290,11 @@ function createTask(command, description) {
9251
9290
  }
9252
9291
  }
9253
9292
  const id = randomUUID().slice(0, 8);
9254
- const isWin = platform3() === "win32";
9255
- const shell = isWin ? "cmd" : "sh";
9256
- const shellFlag = isWin ? "/c" : "-c";
9257
- const proc = spawn3(shell, [shellFlag, command], {
9293
+ const { shell, args } = shellInvocation(command);
9294
+ const proc = spawn3(shell, args, {
9258
9295
  stdio: ["ignore", "pipe", "pipe"],
9259
9296
  detached: false,
9260
- env: { ...process.env, PYTHONUTF8: "1" }
9297
+ env: { ...process.env, PYTHONUTF8: "1", PYTHONIOENCODING: "utf-8" }
9261
9298
  });
9262
9299
  const task = {
9263
9300
  id,
@@ -9269,10 +9306,12 @@ function createTask(command, description) {
9269
9306
  status: "running",
9270
9307
  exitCode: null,
9271
9308
  stdout: "",
9272
- stderr: ""
9309
+ stderr: "",
9310
+ _stdoutChunks: [],
9311
+ _stderrChunks: []
9273
9312
  };
9274
- proc.stdout?.on("data", (chunk) => appendOutput(task, "stdout", chunk.toString("utf-8")));
9275
- proc.stderr?.on("data", (chunk) => appendOutput(task, "stderr", chunk.toString("utf-8")));
9313
+ proc.stdout?.on("data", (chunk) => appendOutput(task, "stdout", chunk));
9314
+ proc.stderr?.on("data", (chunk) => appendOutput(task, "stderr", chunk));
9276
9315
  proc.on("close", (code) => {
9277
9316
  task.endTime = Date.now();
9278
9317
  task.exitCode = code;
@@ -9288,7 +9327,9 @@ Process error: ${err.message}`;
9288
9327
  return task;
9289
9328
  }
9290
9329
  function listTasks() {
9291
- return [...tasks.values()].map(({ process: _p, ...rest }) => rest);
9330
+ return [...tasks.values()].map(
9331
+ ({ process: _p, _stdoutChunks: _o, _stderrChunks: _e, ...rest }) => rest
9332
+ );
9292
9333
  }
9293
9334
  function getTaskOutput(id) {
9294
9335
  const task = tasks.get(id);
@@ -9300,7 +9341,7 @@ function stopTask(id) {
9300
9341
  if (!task || task.status !== "running") return false;
9301
9342
  try {
9302
9343
  const proc = task.process;
9303
- if (platform3() === "win32" && proc.pid) {
9344
+ if (platform4() === "win32" && proc.pid) {
9304
9345
  try {
9305
9346
  spawn3("taskkill", ["/PID", String(proc.pid), "/T", "/F"], {
9306
9347
  windowsHide: true,
@@ -9335,7 +9376,7 @@ function stopTask(id) {
9335
9376
  var taskCreateTool = {
9336
9377
  definition: {
9337
9378
  name: "task_create",
9338
- description: `Start a command running in the background and return immediately with a task ID (monitor via task_list, stop via task_stop). Use this for ANY long-running or never-exiting command, including: dev servers (npm run dev, metro / react-native start), Android emulator launch (emulator -avd X), device log streaming (adb logcat), file watchers, and builds you want to monitor while continuing other work. Running such commands via 'bash' instead would block until timeout.`,
9379
+ description: `Start a command running in the background and return immediately with a task ID (monitor via task_list, stop via task_stop). Use this for ANY long-running or never-exiting command, including: dev servers (npm run dev, metro / react-native start), Android emulator launch (emulator -avd X), device log streaming (adb logcat), file watchers, and builds you want to monitor while continuing other work. Running such commands via 'bash' instead would block until timeout. The command runs in the SAME shell as the 'bash' tool (PowerShell on Windows, $SHELL on Unix), so a command you verified with 'bash' can be moved here verbatim \u2014 write it in PowerShell syntax on Windows (e.g. & "C:\\path\\app.exe" -flag), NOT cmd.exe syntax.`,
9339
9380
  parameters: {
9340
9381
  command: {
9341
9382
  type: "string",
@@ -14325,7 +14366,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
14325
14366
  case "test": {
14326
14367
  this.send({ type: "info", message: "\u{1F9EA} Running tests..." });
14327
14368
  try {
14328
- const { executeTests } = await import("./run-tests-6G2F7OSC.js");
14369
+ const { executeTests } = await import("./run-tests-FGRWBNVF.js");
14329
14370
  const argStr = args.join(" ").trim();
14330
14371
  let testArgs = {};
14331
14372
  if (argStr) {
@@ -154,7 +154,7 @@ ${content}`);
154
154
  }
155
155
  }
156
156
  async function runTaskMode(config, providers, configManager, topic) {
157
- const { TaskOrchestrator } = await import("./task-orchestrator-PKZ2UTOK.js");
157
+ const { TaskOrchestrator } = await import("./task-orchestrator-LJP5XZSC.js");
158
158
  const orchestrator = new TaskOrchestrator(config, providers, configManager);
159
159
  let interrupted = false;
160
160
  const onSigint = () => {
package/dist/index.js CHANGED
@@ -15,7 +15,7 @@ import {
15
15
  saveDevState,
16
16
  sessionHasMeaningfulContent,
17
17
  setupProxy
18
- } from "./chunk-PIZWD5JN.js";
18
+ } from "./chunk-ZSC4YLEJ.js";
19
19
  import {
20
20
  ToolExecutor,
21
21
  ToolRegistry,
@@ -35,10 +35,10 @@ import {
35
35
  spawnAgentContext,
36
36
  theme,
37
37
  undoStack
38
- } from "./chunk-B75A6AG4.js";
38
+ } from "./chunk-43MWRT2C.js";
39
39
  import "./chunk-T2NL5ZIA.js";
40
40
  import "./chunk-BXP6YZ2P.js";
41
- import "./chunk-GYN2PTY2.js";
41
+ import "./chunk-VOV2PWZU.js";
42
42
  import {
43
43
  SessionManager,
44
44
  getContentText
@@ -55,7 +55,7 @@ import {
55
55
  getConfigDirUsage,
56
56
  listRecentCrashes,
57
57
  writeCrashLog
58
- } from "./chunk-T5RTPF2Q.js";
58
+ } from "./chunk-JVYH66N2.js";
59
59
  import {
60
60
  ProviderRegistry
61
61
  } from "./chunk-UZISJ3KZ.js";
@@ -64,7 +64,7 @@ import {
64
64
  getTopFailingTools,
65
65
  getTopUsedTools,
66
66
  installFlushOnExit
67
- } from "./chunk-JLZ5QA2I.js";
67
+ } from "./chunk-XIYSFN4U.js";
68
68
  import {
69
69
  CONTENT_ONLY_STREAM_REMINDER,
70
70
  TEE_FINAL_USER_NUDGE,
@@ -86,7 +86,7 @@ import {
86
86
  } from "./chunk-SMFRJCXB.js";
87
87
  import {
88
88
  ConfigManager
89
- } from "./chunk-BENRC3OO.js";
89
+ } from "./chunk-TZ7SNVSG.js";
90
90
  import {
91
91
  AuthError,
92
92
  ProviderError,
@@ -113,7 +113,7 @@ import {
113
113
  SKILLS_DIR_NAME,
114
114
  VERSION,
115
115
  buildUserIdentityPrompt
116
- } from "./chunk-SCQOEDVL.js";
116
+ } from "./chunk-3RET7PL7.js";
117
117
  import {
118
118
  formatGitContextForPrompt,
119
119
  getGitContext,
@@ -1828,7 +1828,7 @@ No tools match "${filter}".
1828
1828
  const { join: join5 } = await import("path");
1829
1829
  const { existsSync: existsSync5 } = await import("fs");
1830
1830
  const { getGitRoot: getGitRoot2 } = await import("./git-context-EXOEHQSF.js");
1831
- const { MCP_PROJECT_CONFIG_NAME: MCP_PROJECT_CONFIG_NAME2 } = await import("./constants-JRY7PDEY.js");
1831
+ const { MCP_PROJECT_CONFIG_NAME: MCP_PROJECT_CONFIG_NAME2 } = await import("./constants-GMQMJRIO.js");
1832
1832
  const { approveProject, hashMcpFile } = await import("./project-trust-NKYHL3VZ.js");
1833
1833
  const cwd = process.cwd();
1834
1834
  const projectRoot = getGitRoot2(cwd) ?? cwd;
@@ -2889,7 +2889,7 @@ ${hint}` : "")
2889
2889
  usage: "/test [command|filter]",
2890
2890
  async execute(args, ctx) {
2891
2891
  try {
2892
- const { executeTests } = await import("./run-tests-RQWSG25U.js");
2892
+ const { executeTests } = await import("./run-tests-ZFAEFEYK.js");
2893
2893
  const argStr = args.join(" ").trim();
2894
2894
  let testArgs = {};
2895
2895
  if (argStr) {
@@ -7267,7 +7267,7 @@ program.command("web").description("Start Web UI server with browser-based chat
7267
7267
  console.error("Error: Invalid port number. Must be between 1 and 65535.");
7268
7268
  process.exit(1);
7269
7269
  }
7270
- const { startWebServer } = await import("./server-MF6UDMBZ.js");
7270
+ const { startWebServer } = await import("./server-ML6ZL3J5.js");
7271
7271
  await startWebServer({ port, host: options.host });
7272
7272
  });
7273
7273
  program.command("user [action] [username]").description("Manage Web UI users (list | create <name> | delete <name> | reset-password <name> | logout-all <name> | migrate <name>)").action(async (action, username) => {
@@ -7434,16 +7434,16 @@ program.command("sessions").description("List recent conversation sessions").opt
7434
7434
  console.log(footer + "\n");
7435
7435
  });
7436
7436
  program.command("usage").description("Show token + cost usage grouped by provider/model (cross-session)").option("--days <n>", "Only the last N days (inclusive of today)").option("--month <ym>", "Only a specific month, format YYYY-MM (e.g. 2026-06)").option("--json", "Output as JSON (for scripting)").action(async (options) => {
7437
- const { runUsageCli } = await import("./usage-Z64KJASD.js");
7437
+ const { runUsageCli } = await import("./usage-YX2235W5.js");
7438
7438
  await runUsageCli(options);
7439
7439
  });
7440
7440
  program.command("doctor").description("Health check: API keys, config, MCP, recent crashes, tool usage, disk usage").option("--json", "Output as JSON (for scripting)").option("--reset-stats", "Reset accumulated tool usage statistics").action(async (options) => {
7441
- const { runDoctorCli } = await import("./doctor-cli-KPUULUPS.js");
7441
+ const { runDoctorCli } = await import("./doctor-cli-M5PPO3L3.js");
7442
7442
  await runDoctorCli({ json: !!options.json, resetStats: !!options.resetStats });
7443
7443
  });
7444
7444
  program.command("batch <action> [arg] [arg2]").description("Anthropic Message Batches: submit | list | status <id> | results <id> [out] | cancel <id>").option("--dry-run", "Parse and validate input without submitting (submit only)").action(async (action, arg, arg2, options) => {
7445
7445
  try {
7446
- const batch = await import("./batch-ZP52GVVD.js");
7446
+ const batch = await import("./batch-RBPABHS7.js");
7447
7447
  switch (action) {
7448
7448
  case "submit":
7449
7449
  if (!arg) {
@@ -7486,7 +7486,7 @@ program.command("batch <action> [arg] [arg2]").description("Anthropic Message Ba
7486
7486
  }
7487
7487
  });
7488
7488
  program.command("mcp-serve").description("Start an MCP server over STDIO, exposing aicli's built-in tools to Claude Desktop / Cursor / other MCP clients").option("--allow-destructive", "Allow bash / run_interactive / task_create (always destructive in MCP mode)").option("--allow-outside-cwd", "Allow tool path arguments to escape the sandbox root \u2014 disabled by default").option("--tools <list>", "Comma-separated whitelist of tools to expose (default: all eligible tools)").option("--cwd <path>", "Working directory AND sandbox root (default: current directory)").action(async (options) => {
7489
- const { startMcpServer } = await import("./server-XNB76DRR.js");
7489
+ const { startMcpServer } = await import("./server-RKP7JRRE.js");
7490
7490
  await startMcpServer({
7491
7491
  allowDestructive: !!options.allowDestructive,
7492
7492
  allowOutsideCwd: !!options.allowOutsideCwd,
@@ -7495,7 +7495,7 @@ program.command("mcp-serve").description("Start an MCP server over STDIO, exposi
7495
7495
  });
7496
7496
  });
7497
7497
  program.command("ci").description("Headless PR review (code + security) \u2014 reads git/gh diff, optionally posts to PR. Designed for GitHub Actions.").option("--pr <num>", "PR number; diff fetched via `gh pr diff <num>`", (v) => parseInt(v, 10)).option("--base <ref>", "Base ref for `git diff <ref>...HEAD` (ignored when --pr set)").option("--post", "Post review as a PR comment (requires gh CLI + GH_TOKEN, needs --pr)").option("--no-update", "Always create a new comment instead of updating the previous aicli review").option("--skip-code", "Skip the code review section").option("--skip-security", "Skip the security review section").option("--detailed", "Use the detailed code-review prompt").option("--max-diff <n>", "Max diff chars sent to the model (default 30000)", (v) => parseInt(v, 10)).option("--provider <id>", "Override provider (default: config.defaultProvider)").option("--model <id>", "Override model").option("--dry-run", "Print result to stdout instead of posting (overrides --post)").action(async (options) => {
7498
- const { runCi } = await import("./ci-LWE2I5QW.js");
7498
+ const { runCi } = await import("./ci-ABE74AXH.js");
7499
7499
  const result = await runCi({
7500
7500
  pr: options.pr,
7501
7501
  base: options.base,
@@ -7641,7 +7641,7 @@ program.command("hub [topic]").description("Start multi-agent hub (discuss / bra
7641
7641
  }),
7642
7642
  config.get("customProviders")
7643
7643
  );
7644
- const { startHub } = await import("./hub-4CDJPNIM.js");
7644
+ const { startHub } = await import("./hub-NCU5JNCK.js");
7645
7645
  await startHub(
7646
7646
  {
7647
7647
  topic: topic ?? "",
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  executeTests,
3
3
  runTestsTool
4
- } from "./chunk-26YWVRLT.js";
4
+ } from "./chunk-43H3D2PK.js";
5
5
  export {
6
6
  executeTests,
7
7
  runTestsTool
@@ -2,8 +2,8 @@
2
2
  import {
3
3
  executeTests,
4
4
  runTestsTool
5
- } from "./chunk-GYN2PTY2.js";
6
- import "./chunk-SCQOEDVL.js";
5
+ } from "./chunk-VOV2PWZU.js";
6
+ import "./chunk-3RET7PL7.js";
7
7
  export {
8
8
  executeTests,
9
9
  runTestsTool
@@ -19,7 +19,7 @@ import {
19
19
  loadDevState,
20
20
  persistToolRound,
21
21
  setupProxy
22
- } from "./chunk-PIZWD5JN.js";
22
+ } from "./chunk-ZSC4YLEJ.js";
23
23
  import {
24
24
  ToolExecutor,
25
25
  ToolRegistry,
@@ -38,10 +38,10 @@ import {
38
38
  spawnAgentContext,
39
39
  truncateOutput,
40
40
  undoStack
41
- } from "./chunk-B75A6AG4.js";
41
+ } from "./chunk-43MWRT2C.js";
42
42
  import "./chunk-T2NL5ZIA.js";
43
43
  import "./chunk-BXP6YZ2P.js";
44
- import "./chunk-GYN2PTY2.js";
44
+ import "./chunk-VOV2PWZU.js";
45
45
  import {
46
46
  SessionManager,
47
47
  getContentText
@@ -55,7 +55,7 @@ import {
55
55
  } from "./chunk-UZISJ3KZ.js";
56
56
  import {
57
57
  runTool
58
- } from "./chunk-JLZ5QA2I.js";
58
+ } from "./chunk-XIYSFN4U.js";
59
59
  import {
60
60
  CONTENT_ONLY_STREAM_REMINDER,
61
61
  TEE_FINAL_USER_NUDGE,
@@ -74,7 +74,7 @@ import {
74
74
  } from "./chunk-SMFRJCXB.js";
75
75
  import {
76
76
  ConfigManager
77
- } from "./chunk-BENRC3OO.js";
77
+ } from "./chunk-TZ7SNVSG.js";
78
78
  import "./chunk-TZQHYZKT.js";
79
79
  import {
80
80
  AGENTIC_BEHAVIOR_GUIDELINE,
@@ -94,7 +94,7 @@ import {
94
94
  SKILLS_DIR_NAME,
95
95
  VERSION,
96
96
  buildUserIdentityPrompt
97
- } from "./chunk-SCQOEDVL.js";
97
+ } from "./chunk-3RET7PL7.js";
98
98
  import {
99
99
  formatGitContextForPrompt,
100
100
  getGitContext,
@@ -2456,7 +2456,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
2456
2456
  case "test": {
2457
2457
  this.send({ type: "info", message: "\u{1F9EA} Running tests..." });
2458
2458
  try {
2459
- const { executeTests } = await import("./run-tests-RQWSG25U.js");
2459
+ const { executeTests } = await import("./run-tests-ZFAEFEYK.js");
2460
2460
  const argStr = args.join(" ").trim();
2461
2461
  let testArgs = {};
2462
2462
  if (argStr) {
@@ -1,13 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  ToolRegistry
4
- } from "./chunk-B75A6AG4.js";
4
+ } from "./chunk-43MWRT2C.js";
5
5
  import "./chunk-T2NL5ZIA.js";
6
6
  import "./chunk-BXP6YZ2P.js";
7
- import "./chunk-GYN2PTY2.js";
7
+ import "./chunk-VOV2PWZU.js";
8
8
  import {
9
9
  runTool
10
- } from "./chunk-JLZ5QA2I.js";
10
+ } from "./chunk-XIYSFN4U.js";
11
11
  import {
12
12
  getDangerLevel,
13
13
  schemaToJsonSchema
@@ -15,7 +15,7 @@ import {
15
15
  import "./chunk-TZQHYZKT.js";
16
16
  import {
17
17
  VERSION
18
- } from "./chunk-SCQOEDVL.js";
18
+ } from "./chunk-3RET7PL7.js";
19
19
  import "./chunk-4BKXL7SM.js";
20
20
  import "./chunk-TB4W4Y4T.js";
21
21
  import "./chunk-KHYD3WXE.js";
@@ -3,13 +3,13 @@ import {
3
3
  ToolRegistry,
4
4
  googleSearchContext,
5
5
  truncateOutput
6
- } from "./chunk-B75A6AG4.js";
6
+ } from "./chunk-43MWRT2C.js";
7
7
  import "./chunk-T2NL5ZIA.js";
8
8
  import "./chunk-BXP6YZ2P.js";
9
- import "./chunk-GYN2PTY2.js";
9
+ import "./chunk-VOV2PWZU.js";
10
10
  import {
11
11
  runTool
12
- } from "./chunk-JLZ5QA2I.js";
12
+ } from "./chunk-XIYSFN4U.js";
13
13
  import {
14
14
  getDangerLevel,
15
15
  runLeanAgentLoop
@@ -17,7 +17,7 @@ import {
17
17
  import "./chunk-TZQHYZKT.js";
18
18
  import {
19
19
  SUBAGENT_ALLOWED_TOOLS
20
- } from "./chunk-SCQOEDVL.js";
20
+ } from "./chunk-3RET7PL7.js";
21
21
  import "./chunk-4BKXL7SM.js";
22
22
  import "./chunk-TB4W4Y4T.js";
23
23
  import "./chunk-KHYD3WXE.js";
@@ -8,9 +8,9 @@ import {
8
8
  } from "./chunk-E44DTERW.js";
9
9
  import {
10
10
  ConfigManager
11
- } from "./chunk-BENRC3OO.js";
11
+ } from "./chunk-TZ7SNVSG.js";
12
12
  import "./chunk-TZQHYZKT.js";
13
- import "./chunk-SCQOEDVL.js";
13
+ import "./chunk-3RET7PL7.js";
14
14
  import "./chunk-IW3Q7AE5.js";
15
15
 
16
16
  // src/cli/usage.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jinzd-ai-cli",
3
- "version": "0.4.204",
3
+ "version": "0.4.205",
4
4
  "description": "Cross-platform REPL-style AI CLI with multi-provider support",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",