jinzd-ai-cli 0.4.204 → 0.4.206
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/{batch-ZP52GVVD.js → batch-6PORMJWT.js} +2 -2
- package/dist/{chunk-JLZ5QA2I.js → chunk-2WV6AGHM.js} +1 -1
- package/dist/{chunk-26YWVRLT.js → chunk-BQIV4ZQT.js} +1 -1
- package/dist/{chunk-T5RTPF2Q.js → chunk-KJHMRAJU.js} +1 -1
- package/dist/{chunk-PIZWD5JN.js → chunk-KOU4KX7J.js} +2 -2
- package/dist/{chunk-UZISJ3KZ.js → chunk-QMXC327F.js} +3 -3
- package/dist/{chunk-SCQOEDVL.js → chunk-QNUVZO5X.js} +1 -1
- package/dist/{chunk-B75A6AG4.js → chunk-QQYSZMET.js} +108 -64
- package/dist/{chunk-GYN2PTY2.js → chunk-SFCNFX42.js} +1 -1
- package/dist/{chunk-BENRC3OO.js → chunk-TW47X5AO.js} +1 -1
- package/dist/{chunk-SMFRJCXB.js → chunk-XPBEJB27.js} +8 -1
- package/dist/{ci-LWE2I5QW.js → ci-AYMV66P3.js} +9 -9
- package/dist/{constants-JRY7PDEY.js → constants-NHGTSHKT.js} +1 -1
- package/dist/{doctor-cli-KPUULUPS.js → doctor-cli-WKH7T4AW.js} +6 -6
- package/dist/electron-server.js +131 -82
- package/dist/{hub-4CDJPNIM.js → hub-6R3M3NTC.js} +1 -1
- package/dist/index.js +29 -27
- package/dist/{run-tests-RQWSG25U.js → run-tests-5F2OBWIK.js} +2 -2
- package/dist/{run-tests-6G2F7OSC.js → run-tests-NBCA4KEG.js} +1 -1
- package/dist/{server-XNB76DRR.js → server-37EXC4EB.js} +5 -5
- package/dist/{server-MF6UDMBZ.js → server-LDPQ3DLK.js} +23 -20
- package/dist/{task-orchestrator-PKZ2UTOK.js → task-orchestrator-2DTDVCMR.js} +5 -5
- package/dist/{usage-Z64KJASD.js → usage-SZWZFX3T.js} +2 -2
- package/dist/web/client/app.js +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -100,7 +100,7 @@ Use `@filepath` to reference files or images directly in your prompt.
|
|
|
100
100
|
### Web UI
|
|
101
101
|
|
|
102
102
|
```bash
|
|
103
|
-
aicli web # Start on localhost:
|
|
103
|
+
aicli web # Start on localhost:3000
|
|
104
104
|
aicli web --port 8080 # Custom port
|
|
105
105
|
aicli web --host 0.0.0.0 # LAN access (shows QR-friendly URL)
|
|
106
106
|
```
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
ConfigManager
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-TW47X5AO.js";
|
|
5
5
|
import "./chunk-TZQHYZKT.js";
|
|
6
|
-
import "./chunk-
|
|
6
|
+
import "./chunk-QNUVZO5X.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
|
import {
|
|
3
3
|
truncateForPersist
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-QQYSZMET.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-
|
|
14
|
+
} from "./chunk-QNUVZO5X.js";
|
|
15
15
|
|
|
16
16
|
// src/mcp/client.ts
|
|
17
17
|
import { spawn } from "child_process";
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
detectsHallucinatedFileOp,
|
|
4
4
|
repairToolCallArguments,
|
|
5
5
|
schemaToJsonSchema
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-XPBEJB27.js";
|
|
7
7
|
import {
|
|
8
8
|
AuthError,
|
|
9
9
|
ProviderError,
|
|
@@ -31,8 +31,8 @@ var BaseProvider = class {
|
|
|
31
31
|
* 将 Message[] 转换为 OpenAI API 格式的消息数组。
|
|
32
32
|
* content 为 string 时直接传递;为 MessageContentPart[] 时保留数组格式(vision 请求)。
|
|
33
33
|
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
34
|
+
* 基类只提供降级格式:跳过 role='tool' 和带 toolCalls 的 assistant 消息。
|
|
35
|
+
* 需要完整工具历史 roundtrip 的 provider 应覆盖消息构造逻辑,例如 OpenAI-compatible provider。
|
|
36
36
|
*/
|
|
37
37
|
normalizeMessages(messages) {
|
|
38
38
|
return messages.filter((m) => m.role !== "tool" && !m.toolCalls).map((m) => ({ role: m.role, content: m.content }));
|
|
@@ -5,15 +5,15 @@ import {
|
|
|
5
5
|
} from "./chunk-T2NL5ZIA.js";
|
|
6
6
|
import {
|
|
7
7
|
runTestsTool
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-SFCNFX42.js";
|
|
9
9
|
import {
|
|
10
10
|
runTool
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-2WV6AGHM.js";
|
|
12
12
|
import {
|
|
13
13
|
getDangerLevel,
|
|
14
14
|
isFileWriteTool,
|
|
15
15
|
runLeanAgentLoop
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-XPBEJB27.js";
|
|
17
17
|
import {
|
|
18
18
|
EnvLoader,
|
|
19
19
|
NetworkError,
|
|
@@ -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-
|
|
29
|
+
} from "./chunk-QNUVZO5X.js";
|
|
30
30
|
import {
|
|
31
31
|
fileCheckpoints
|
|
32
32
|
} from "./chunk-4BKXL7SM.js";
|
|
@@ -40,11 +40,14 @@ import {
|
|
|
40
40
|
import {
|
|
41
41
|
loadIndex
|
|
42
42
|
} from "./chunk-CKH4KQ4E.js";
|
|
43
|
+
import {
|
|
44
|
+
atomicWriteFileSync
|
|
45
|
+
} from "./chunk-IW3Q7AE5.js";
|
|
43
46
|
|
|
44
47
|
// src/tools/builtin/bash.ts
|
|
45
48
|
import { spawn } from "child_process";
|
|
46
49
|
import { existsSync as existsSync2, readdirSync, statSync } from "fs";
|
|
47
|
-
import { platform } from "os";
|
|
50
|
+
import { platform as platform2 } from "os";
|
|
48
51
|
import { resolve } from "path";
|
|
49
52
|
|
|
50
53
|
// src/tools/undo-stack.ts
|
|
@@ -196,9 +199,32 @@ function runWithSessionKey(sessionKey, fn) {
|
|
|
196
199
|
return als.run({ sessionKey: key }, fn);
|
|
197
200
|
}
|
|
198
201
|
|
|
199
|
-
// src/tools/
|
|
202
|
+
// src/tools/win-shell.ts
|
|
203
|
+
import { platform } from "os";
|
|
200
204
|
var IS_WINDOWS = platform() === "win32";
|
|
201
|
-
var
|
|
205
|
+
var WIN_UTF8_PREAMBLE = "[Console]::OutputEncoding = [System.Text.Encoding]::UTF8; $OutputEncoding = [System.Text.Encoding]::UTF8; ";
|
|
206
|
+
function shellInvocation(command) {
|
|
207
|
+
if (IS_WINDOWS) {
|
|
208
|
+
return {
|
|
209
|
+
shell: "powershell.exe",
|
|
210
|
+
args: ["-NoProfile", "-NonInteractive", "-Command", WIN_UTF8_PREAMBLE + command]
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
return { shell: process.env["SHELL"] ?? "/bin/bash", args: ["-c", command] };
|
|
214
|
+
}
|
|
215
|
+
function decodeWindowsBuffer(buf) {
|
|
216
|
+
const utf8 = buf.toString("utf-8");
|
|
217
|
+
if (!utf8.includes("\uFFFD")) return utf8;
|
|
218
|
+
try {
|
|
219
|
+
return new TextDecoder("gbk").decode(buf);
|
|
220
|
+
} catch {
|
|
221
|
+
return utf8;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// src/tools/builtin/bash.ts
|
|
226
|
+
var IS_WINDOWS2 = platform2() === "win32";
|
|
227
|
+
var SHELL = IS_WINDOWS2 ? "powershell.exe" : process.env["SHELL"] ?? "/bin/bash";
|
|
202
228
|
var cwdBySession = /* @__PURE__ */ new Map();
|
|
203
229
|
function getCwd() {
|
|
204
230
|
const key = getCurrentSessionKey();
|
|
@@ -215,7 +241,7 @@ function setCwd(next) {
|
|
|
215
241
|
var bashTool = {
|
|
216
242
|
definition: {
|
|
217
243
|
name: "bash",
|
|
218
|
-
description:
|
|
244
|
+
description: IS_WINDOWS2 ? `Execute commands in PowerShell. Supports mkdir, ls, cat, python, etc.
|
|
219
245
|
Important rules:
|
|
220
246
|
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
247
|
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 +257,7 @@ Important rules:
|
|
|
231
257
|
parameters: {
|
|
232
258
|
command: {
|
|
233
259
|
type: "string",
|
|
234
|
-
description:
|
|
260
|
+
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
261
|
required: true
|
|
236
262
|
},
|
|
237
263
|
cwd: {
|
|
@@ -283,9 +309,9 @@ Important rules:
|
|
|
283
309
|
setCwd(resolved);
|
|
284
310
|
}
|
|
285
311
|
let actualCommand;
|
|
286
|
-
if (
|
|
312
|
+
if (IS_WINDOWS2) {
|
|
287
313
|
const fixedCommand = fixWindowsDeleteCommand(command);
|
|
288
|
-
actualCommand =
|
|
314
|
+
actualCommand = WIN_UTF8_PREAMBLE + fixedCommand;
|
|
289
315
|
} else {
|
|
290
316
|
actualCommand = command;
|
|
291
317
|
}
|
|
@@ -329,7 +355,7 @@ Important rules:
|
|
|
329
355
|
}
|
|
330
356
|
updateCwdFromCommand(command, effectiveCwd);
|
|
331
357
|
pushBashUndoEntries(beforeSnapshot, parsedTargetsBefore, effectiveCwd);
|
|
332
|
-
const result = Buffer.isBuffer(stdout) ?
|
|
358
|
+
const result = Buffer.isBuffer(stdout) ? IS_WINDOWS2 ? decodeWindowsBuffer(stdout) : stdout.toString("utf-8") : String(stdout ?? "");
|
|
333
359
|
return result || "(command completed with no output)";
|
|
334
360
|
} catch (err) {
|
|
335
361
|
pushBashUndoEntries(beforeSnapshot, parsedTargetsBefore, effectiveCwd);
|
|
@@ -354,8 +380,8 @@ How to recover (pick ONE \u2014 do NOT retry the same command):
|
|
|
354
380
|
);
|
|
355
381
|
}
|
|
356
382
|
if ("status" in execErr && execErr.status !== void 0) {
|
|
357
|
-
const stderr =
|
|
358
|
-
const stdout =
|
|
383
|
+
const stderr = IS_WINDOWS2 && Buffer.isBuffer(execErr.stderr) ? decodeWindowsBuffer(execErr.stderr).trim() : execErr.stderr?.toString().trim() ?? "";
|
|
384
|
+
const stdout = IS_WINDOWS2 && Buffer.isBuffer(execErr.stdout) ? decodeWindowsBuffer(execErr.stdout).trim() : execErr.stdout?.toString().trim() ?? "";
|
|
359
385
|
const combined = [stdout, stderr].filter(Boolean).join("\n");
|
|
360
386
|
const hint = buildErrorHint(command, combined);
|
|
361
387
|
throw new ToolError(
|
|
@@ -389,23 +415,14 @@ function fixWindowsDeleteCommand(command) {
|
|
|
389
415
|
}
|
|
390
416
|
);
|
|
391
417
|
}
|
|
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
418
|
function buildErrorHint(command, stderr) {
|
|
402
419
|
const hints = [];
|
|
403
|
-
if (
|
|
420
|
+
if (IS_WINDOWS2 && /\|\|/.test(command) && /(\|\||is not a valid argument|不是此版本中的有效|ParserError|Unexpected token)/i.test(stderr)) {
|
|
404
421
|
hints.push(
|
|
405
422
|
`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
423
|
);
|
|
407
424
|
}
|
|
408
|
-
if (
|
|
425
|
+
if (IS_WINDOWS2 && /\bpython3\b/.test(command)) {
|
|
409
426
|
const trivialStderr = stderr.trim().length === 0 || /^[·\s]*exit 1\b/i.test(stderr.trim());
|
|
410
427
|
if (/(not recognized|is not recognized|无法将.*识别)/i.test(stderr) || trivialStderr) {
|
|
411
428
|
hints.push(
|
|
@@ -413,27 +430,27 @@ function buildErrorHint(command, stderr) {
|
|
|
413
430
|
);
|
|
414
431
|
}
|
|
415
432
|
}
|
|
416
|
-
if (
|
|
433
|
+
if (IS_WINDOWS2 && /^\s*ssh\b/.test(command) && /\\"/.test(command) && /(syntax error|ERROR:|ParserError|unexpected|parser|意外的|语法|不是此版本|所在位置\s+行)/i.test(stderr)) {
|
|
417
434
|
hints.push(
|
|
418
435
|
`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
436
|
);
|
|
420
437
|
}
|
|
421
|
-
if (
|
|
438
|
+
if (IS_WINDOWS2 && /\bmkdir\b/.test(command) && /\s-p\b/.test(command) && /(已存在|already exists|ItemExistsUnauthorizedAccessError|exists)/i.test(stderr)) {
|
|
422
439
|
hints.push(
|
|
423
440
|
`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
441
|
);
|
|
425
442
|
}
|
|
426
|
-
if (
|
|
443
|
+
if (IS_WINDOWS2 && /Remove-Item/i.test(command) && /cannot find path|no such file|exit 1/i.test(stderr || command)) {
|
|
427
444
|
hints.push(
|
|
428
445
|
`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
446
|
);
|
|
430
447
|
}
|
|
431
|
-
if (
|
|
448
|
+
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
449
|
hints.push(
|
|
433
450
|
`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
451
|
);
|
|
435
452
|
}
|
|
436
|
-
if (
|
|
453
|
+
if (IS_WINDOWS2 && /\bWrite-(?:Content|File)\b/i.test(command) && /(not recognized|无法将.*识别|不是.*cmdlet|CommandNotFoundException)/i.test(stderr)) {
|
|
437
454
|
hints.push(
|
|
438
455
|
`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
456
|
);
|
|
@@ -454,6 +471,18 @@ function detectBlockingCommand(command) {
|
|
|
454
471
|
if (logcat && !/(?:^|\s)-(?:d|t|c|g)\b/.test(logcat[1] ?? "")) {
|
|
455
472
|
return blockingMessage("adb logcat", "stream device logs");
|
|
456
473
|
}
|
|
474
|
+
if (/\bStart-Process\b/i.test(command) && /\b(?:emulator|qemu[\w-]*)(?:\.exe)?\b/i.test(command)) {
|
|
475
|
+
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.
|
|
476
|
+
|
|
477
|
+
Use the 'task_create' tool instead so the process is tracked and stoppable:
|
|
478
|
+
task_create(command: "<sdk>\\emulator\\emulator.exe -avd <name> -no-boot-anim", description: "launch emulator")
|
|
479
|
+
|
|
480
|
+
Then poll readiness with SHORT bash calls (these DO exit):
|
|
481
|
+
adb devices # is the device listed?
|
|
482
|
+
adb shell getprop sys.boot_completed # prints "1" once fully booted
|
|
483
|
+
|
|
484
|
+
[Do NOT launch long-running GUI processes via Start-Process in bash \u2014 use task_create.]`;
|
|
485
|
+
}
|
|
457
486
|
return null;
|
|
458
487
|
}
|
|
459
488
|
function blockingMessage(example, what) {
|
|
@@ -533,7 +562,7 @@ function pushBashUndoEntries(beforeSnapshot, parsedTargetsBefore, cwd) {
|
|
|
533
562
|
}
|
|
534
563
|
function runShell(command, opts) {
|
|
535
564
|
return new Promise((resolvePromise) => {
|
|
536
|
-
const shellArgs =
|
|
565
|
+
const shellArgs = IS_WINDOWS2 ? ["-NoProfile", "-NonInteractive", "-Command", command] : ["-c", command];
|
|
537
566
|
const child = spawn(SHELL, shellArgs, {
|
|
538
567
|
cwd: opts.cwd,
|
|
539
568
|
env: opts.env,
|
|
@@ -596,7 +625,7 @@ function runShell(command, opts) {
|
|
|
596
625
|
}
|
|
597
626
|
function killChild(child) {
|
|
598
627
|
if (child.killed || child.exitCode !== null) return;
|
|
599
|
-
if (
|
|
628
|
+
if (IS_WINDOWS2 && child.pid) {
|
|
600
629
|
try {
|
|
601
630
|
spawn("taskkill", ["/PID", String(child.pid), "/T", "/F"], {
|
|
602
631
|
windowsHide: true,
|
|
@@ -1021,7 +1050,7 @@ ${content}`;
|
|
|
1021
1050
|
};
|
|
1022
1051
|
|
|
1023
1052
|
// src/tools/builtin/write-file.ts
|
|
1024
|
-
import {
|
|
1053
|
+
import { appendFileSync, mkdirSync } from "fs";
|
|
1025
1054
|
import { dirname as dirname2 } from "path";
|
|
1026
1055
|
|
|
1027
1056
|
// src/tools/executor.ts
|
|
@@ -1496,8 +1525,8 @@ var ToolExecutor = class {
|
|
|
1496
1525
|
/** confirm() 的取消回调,由 SIGINT handler 调用 */
|
|
1497
1526
|
cancelConfirmFn = null;
|
|
1498
1527
|
/**
|
|
1499
|
-
* 会话级 auto-approve
|
|
1500
|
-
*
|
|
1528
|
+
* 会话级 auto-approve:跳过 write 确认(仅当前会话有效)。
|
|
1529
|
+
* destructive 操作仍然必须逐次确认。
|
|
1501
1530
|
*/
|
|
1502
1531
|
sessionAutoApprove = false;
|
|
1503
1532
|
/**
|
|
@@ -1581,7 +1610,7 @@ var ToolExecutor = class {
|
|
|
1581
1610
|
}
|
|
1582
1611
|
}
|
|
1583
1612
|
}
|
|
1584
|
-
if (this.sessionAutoApprove && dangerLevel
|
|
1613
|
+
if (this.sessionAutoApprove && dangerLevel === "write") {
|
|
1585
1614
|
this.printToolCall(call);
|
|
1586
1615
|
if (dangerLevel === "write") this.printDiffPreview(call);
|
|
1587
1616
|
console.log(theme.warning(" \u26A1 Auto-approved (session /yolo mode)"));
|
|
@@ -2110,7 +2139,7 @@ Do NOT split a long document into many write_file(append=true) calls. That patte
|
|
|
2110
2139
|
if (appendMode) {
|
|
2111
2140
|
appendFileSync(filePath, content, encoding);
|
|
2112
2141
|
} else {
|
|
2113
|
-
|
|
2142
|
+
atomicWriteFileSync(filePath, content);
|
|
2114
2143
|
}
|
|
2115
2144
|
const lines = content.split("\n").length;
|
|
2116
2145
|
const mode = appendMode ? "appended" : "written";
|
|
@@ -2126,7 +2155,7 @@ Do NOT split a long document into many write_file(append=true) calls. That patte
|
|
|
2126
2155
|
};
|
|
2127
2156
|
|
|
2128
2157
|
// src/tools/builtin/edit-file.ts
|
|
2129
|
-
import { readFileSync as readFileSync4,
|
|
2158
|
+
import { readFileSync as readFileSync4, existsSync as existsSync5 } from "fs";
|
|
2130
2159
|
|
|
2131
2160
|
// src/tools/builtin/patch-apply.ts
|
|
2132
2161
|
function parseUnifiedDiff(patch) {
|
|
@@ -2525,7 +2554,7 @@ Note: Path can be absolute or relative to cwd.`,
|
|
|
2525
2554
|
if (res.ok && res.appliedCount > 0 && res.content !== void 0) {
|
|
2526
2555
|
undoStack.push(filePath, `edit_file (patch ${res.appliedCount}/${hunks.length}): ${filePath}`);
|
|
2527
2556
|
fileCheckpoints.snapshot(filePath, ToolExecutor.currentMessageIndex);
|
|
2528
|
-
|
|
2557
|
+
atomicWriteFileSync(filePath, res.content);
|
|
2529
2558
|
}
|
|
2530
2559
|
return lines.join("\n");
|
|
2531
2560
|
}
|
|
@@ -2559,7 +2588,7 @@ Note: Path can be absolute or relative to cwd.`,
|
|
|
2559
2588
|
if (writeChanges) {
|
|
2560
2589
|
undoStack.push(filePath, `edit_file (batch ${appliedCount}/${edits.length}): ${filePath}`);
|
|
2561
2590
|
fileCheckpoints.snapshot(filePath, ToolExecutor.currentMessageIndex);
|
|
2562
|
-
|
|
2591
|
+
atomicWriteFileSync(filePath, working);
|
|
2563
2592
|
}
|
|
2564
2593
|
const lines = [];
|
|
2565
2594
|
if (anyFailed && stopOnError) {
|
|
@@ -2597,7 +2626,7 @@ Please read the file first and use exact text.`;
|
|
|
2597
2626
|
const label = res.info?.mode === "ignore_whitespace" ? "edit_file (ws-replace)" : res.info?.mode === "replace_all" ? "edit_file (replace_all)" : "edit_file (replace)";
|
|
2598
2627
|
undoStack.push(filePath, `${label}: ${filePath}`);
|
|
2599
2628
|
fileCheckpoints.snapshot(filePath, ToolExecutor.currentMessageIndex);
|
|
2600
|
-
|
|
2629
|
+
atomicWriteFileSync(filePath, res.content);
|
|
2601
2630
|
if (res.info?.mode === "replace_all") {
|
|
2602
2631
|
return `Successfully edited ${filePath}${modeLabel}
|
|
2603
2632
|
Replaced: ${res.info.replacedCount} occurrence(s) of ${truncatePreview(oldStr)}
|
|
@@ -2621,7 +2650,7 @@ Please read the file first and use exact text.`;
|
|
|
2621
2650
|
undoStack.push(filePath, `edit_file (insert): ${filePath}`);
|
|
2622
2651
|
fileCheckpoints.snapshot(filePath, ToolExecutor.currentMessageIndex);
|
|
2623
2652
|
lines.splice(afterLine, 0, content);
|
|
2624
|
-
|
|
2653
|
+
atomicWriteFileSync(filePath, lines.join("\n"));
|
|
2625
2654
|
return `Successfully inserted ${content.split("\n").length} line(s) after line ${afterLine} in ${filePath}`;
|
|
2626
2655
|
}
|
|
2627
2656
|
if (args["delete_from_line"] !== void 0) {
|
|
@@ -2637,7 +2666,7 @@ Please read the file first and use exact text.`;
|
|
|
2637
2666
|
undoStack.push(filePath, `edit_file (delete): ${filePath}`);
|
|
2638
2667
|
fileCheckpoints.snapshot(filePath, ToolExecutor.currentMessageIndex);
|
|
2639
2668
|
const deleted = lines.splice(fromLine - 1, toLine - fromLine + 1);
|
|
2640
|
-
|
|
2669
|
+
atomicWriteFileSync(filePath, lines.join("\n"));
|
|
2641
2670
|
return `Successfully deleted lines ${fromLine}-${toLine} (${deleted.length} lines) from ${filePath}`;
|
|
2642
2671
|
}
|
|
2643
2672
|
throw new ToolError(
|
|
@@ -3122,8 +3151,8 @@ function collectMatchingFiles(dirPath, rootPath, regex, results, maxResults) {
|
|
|
3122
3151
|
|
|
3123
3152
|
// src/tools/builtin/run-interactive.ts
|
|
3124
3153
|
import { spawn as spawn2 } from "child_process";
|
|
3125
|
-
import { platform as
|
|
3126
|
-
var
|
|
3154
|
+
import { platform as platform3 } from "os";
|
|
3155
|
+
var IS_WINDOWS3 = platform3() === "win32";
|
|
3127
3156
|
var runInteractiveTool = {
|
|
3128
3157
|
definition: {
|
|
3129
3158
|
name: "run_interactive",
|
|
@@ -3203,7 +3232,7 @@ var runInteractiveTool = {
|
|
|
3203
3232
|
let lineIdx = 0;
|
|
3204
3233
|
const writeNextLine = () => {
|
|
3205
3234
|
if (lineIdx < stdinLines.length && !child.stdin.destroyed) {
|
|
3206
|
-
const line = stdinLines[lineIdx++] + (
|
|
3235
|
+
const line = stdinLines[lineIdx++] + (IS_WINDOWS3 ? "\r\n" : "\n");
|
|
3207
3236
|
const canContinue = child.stdin.write(line);
|
|
3208
3237
|
if (canContinue) {
|
|
3209
3238
|
setTimeout(writeNextLine, 150);
|
|
@@ -3842,7 +3871,7 @@ ${preamble}`;
|
|
|
3842
3871
|
}
|
|
3843
3872
|
|
|
3844
3873
|
// src/tools/builtin/save-last-response.ts
|
|
3845
|
-
import {
|
|
3874
|
+
import { mkdirSync as mkdirSync2, unlinkSync as unlinkSync2, rmdirSync as rmdirSync2 } from "fs";
|
|
3846
3875
|
import { dirname as dirname3 } from "path";
|
|
3847
3876
|
var lastResponseStore = { content: "" };
|
|
3848
3877
|
function cleanupRejectedTeeFile(filePath) {
|
|
@@ -3893,7 +3922,7 @@ Any of these triggers means use save_last_response, NOT write_file:
|
|
|
3893
3922
|
}
|
|
3894
3923
|
undoStack.push(filePath, `save_last_response: ${filePath}`);
|
|
3895
3924
|
mkdirSync2(dirname3(filePath), { recursive: true });
|
|
3896
|
-
|
|
3925
|
+
atomicWriteFileSync(filePath, content);
|
|
3897
3926
|
const lines = content.split("\n").length;
|
|
3898
3927
|
return `File saved: ${filePath} (${lines} lines, ${content.length} bytes)`;
|
|
3899
3928
|
}
|
|
@@ -4516,16 +4545,29 @@ var spawnAgentTool = {
|
|
|
4516
4545
|
// src/tools/builtin/task-manager.ts
|
|
4517
4546
|
import { spawn as spawn3 } from "child_process";
|
|
4518
4547
|
import { randomUUID } from "crypto";
|
|
4519
|
-
import { platform as
|
|
4548
|
+
import { platform as platform4 } from "os";
|
|
4520
4549
|
var MAX_OUTPUT_CHARS = 1e4;
|
|
4550
|
+
var MAX_OUTPUT_BYTES = 64 * 1024;
|
|
4521
4551
|
var MAX_TASKS = 20;
|
|
4522
4552
|
var tasks = /* @__PURE__ */ new Map();
|
|
4523
|
-
function
|
|
4524
|
-
|
|
4525
|
-
|
|
4526
|
-
|
|
4553
|
+
function capChunks(chunks) {
|
|
4554
|
+
let total = 0;
|
|
4555
|
+
for (const c of chunks) total += c.length;
|
|
4556
|
+
while (total > MAX_OUTPUT_BYTES && chunks.length > 1) {
|
|
4557
|
+
total -= chunks.shift().length;
|
|
4527
4558
|
}
|
|
4528
4559
|
}
|
|
4560
|
+
function decodeField(chunks) {
|
|
4561
|
+
const buf = Buffer.concat(chunks);
|
|
4562
|
+
const text = IS_WINDOWS ? decodeWindowsBuffer(buf) : buf.toString("utf-8");
|
|
4563
|
+
return text.length > MAX_OUTPUT_CHARS ? "... [truncated] ...\n" + text.slice(-MAX_OUTPUT_CHARS) : text;
|
|
4564
|
+
}
|
|
4565
|
+
function appendOutput(task, field, chunk) {
|
|
4566
|
+
const chunks = field === "stdout" ? task._stdoutChunks : task._stderrChunks;
|
|
4567
|
+
chunks.push(chunk);
|
|
4568
|
+
capChunks(chunks);
|
|
4569
|
+
task[field] = decodeField(chunks);
|
|
4570
|
+
}
|
|
4529
4571
|
function createTask(command, description) {
|
|
4530
4572
|
if (tasks.size >= MAX_TASKS) {
|
|
4531
4573
|
for (const [id2, t] of tasks) {
|
|
@@ -4536,13 +4578,11 @@ function createTask(command, description) {
|
|
|
4536
4578
|
}
|
|
4537
4579
|
}
|
|
4538
4580
|
const id = randomUUID().slice(0, 8);
|
|
4539
|
-
const
|
|
4540
|
-
const
|
|
4541
|
-
const shellFlag = isWin ? "/c" : "-c";
|
|
4542
|
-
const proc = spawn3(shell, [shellFlag, command], {
|
|
4581
|
+
const { shell, args } = shellInvocation(command);
|
|
4582
|
+
const proc = spawn3(shell, args, {
|
|
4543
4583
|
stdio: ["ignore", "pipe", "pipe"],
|
|
4544
4584
|
detached: false,
|
|
4545
|
-
env: { ...process.env, PYTHONUTF8: "1" }
|
|
4585
|
+
env: { ...process.env, PYTHONUTF8: "1", PYTHONIOENCODING: "utf-8" }
|
|
4546
4586
|
});
|
|
4547
4587
|
const task = {
|
|
4548
4588
|
id,
|
|
@@ -4554,10 +4594,12 @@ function createTask(command, description) {
|
|
|
4554
4594
|
status: "running",
|
|
4555
4595
|
exitCode: null,
|
|
4556
4596
|
stdout: "",
|
|
4557
|
-
stderr: ""
|
|
4597
|
+
stderr: "",
|
|
4598
|
+
_stdoutChunks: [],
|
|
4599
|
+
_stderrChunks: []
|
|
4558
4600
|
};
|
|
4559
|
-
proc.stdout?.on("data", (chunk) => appendOutput(task, "stdout", chunk
|
|
4560
|
-
proc.stderr?.on("data", (chunk) => appendOutput(task, "stderr", chunk
|
|
4601
|
+
proc.stdout?.on("data", (chunk) => appendOutput(task, "stdout", chunk));
|
|
4602
|
+
proc.stderr?.on("data", (chunk) => appendOutput(task, "stderr", chunk));
|
|
4561
4603
|
proc.on("close", (code) => {
|
|
4562
4604
|
task.endTime = Date.now();
|
|
4563
4605
|
task.exitCode = code;
|
|
@@ -4573,7 +4615,9 @@ Process error: ${err.message}`;
|
|
|
4573
4615
|
return task;
|
|
4574
4616
|
}
|
|
4575
4617
|
function listTasks() {
|
|
4576
|
-
return [...tasks.values()].map(
|
|
4618
|
+
return [...tasks.values()].map(
|
|
4619
|
+
({ process: _p, _stdoutChunks: _o, _stderrChunks: _e, ...rest }) => rest
|
|
4620
|
+
);
|
|
4577
4621
|
}
|
|
4578
4622
|
function getTaskOutput(id) {
|
|
4579
4623
|
const task = tasks.get(id);
|
|
@@ -4585,7 +4629,7 @@ function stopTask(id) {
|
|
|
4585
4629
|
if (!task || task.status !== "running") return false;
|
|
4586
4630
|
try {
|
|
4587
4631
|
const proc = task.process;
|
|
4588
|
-
if (
|
|
4632
|
+
if (platform4() === "win32" && proc.pid) {
|
|
4589
4633
|
try {
|
|
4590
4634
|
spawn3("taskkill", ["/PID", String(proc.pid), "/T", "/F"], {
|
|
4591
4635
|
windowsHide: true,
|
|
@@ -4620,7 +4664,7 @@ function stopTask(id) {
|
|
|
4620
4664
|
var taskCreateTool = {
|
|
4621
4665
|
definition: {
|
|
4622
4666
|
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.`,
|
|
4667
|
+
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
4668
|
parameters: {
|
|
4625
4669
|
command: {
|
|
4626
4670
|
type: "string",
|
|
@@ -5,7 +5,14 @@ function isFileWriteTool(name) {
|
|
|
5
5
|
return name === "write_file" || name === "edit_file" || name === "notebook_edit";
|
|
6
6
|
}
|
|
7
7
|
function getDangerLevel(toolName, args) {
|
|
8
|
-
if (toolName.startsWith("mcp__"))
|
|
8
|
+
if (toolName.startsWith("mcp__")) {
|
|
9
|
+
const rawName = toolName.split("__").pop() ?? "";
|
|
10
|
+
const normalized = rawName.toLowerCase().replace(/[^a-z0-9]+/g, "_");
|
|
11
|
+
if (/^(read|get|list|search|find|query|fetch|status|describe|inspect|show|lookup)(?:_|$)/.test(normalized)) {
|
|
12
|
+
return "safe";
|
|
13
|
+
}
|
|
14
|
+
return "write";
|
|
15
|
+
}
|
|
9
16
|
if (toolName === "bash") {
|
|
10
17
|
const cmd = String(args["command"] ?? "");
|
|
11
18
|
if (/\brm\s+[^\n]*(?:-\w*[rRfF]\w*|--recursive|--force)\b/.test(cmd)) return "destructive";
|
|
@@ -6,18 +6,18 @@ import {
|
|
|
6
6
|
} from "./chunk-HLWUDRBO.js";
|
|
7
7
|
import {
|
|
8
8
|
ProviderRegistry
|
|
9
|
-
} from "./chunk-
|
|
10
|
-
import "./chunk-
|
|
9
|
+
} from "./chunk-QMXC327F.js";
|
|
10
|
+
import "./chunk-XPBEJB27.js";
|
|
11
11
|
import {
|
|
12
12
|
ConfigManager
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-TW47X5AO.js";
|
|
14
14
|
import "./chunk-TZQHYZKT.js";
|
|
15
15
|
import {
|
|
16
16
|
VERSION
|
|
17
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-QNUVZO5X.js";
|
|
18
18
|
|
|
19
19
|
// src/cli/ci.ts
|
|
20
|
-
import { execFileSync
|
|
20
|
+
import { execFileSync } from "child_process";
|
|
21
21
|
var CI_COMMENT_MARKER = "<!-- aicli-ci-review -->";
|
|
22
22
|
function fetchDiff(opts) {
|
|
23
23
|
if (opts.diffOverride != null) return opts.diffOverride;
|
|
@@ -34,7 +34,8 @@ function fetchDiff(opts) {
|
|
|
34
34
|
}
|
|
35
35
|
if (opts.base) {
|
|
36
36
|
try {
|
|
37
|
-
|
|
37
|
+
validateGitRef(opts.base);
|
|
38
|
+
return execFileSync("git", ["diff", `${opts.base}...HEAD`], {
|
|
38
39
|
encoding: "utf-8",
|
|
39
40
|
maxBuffer: 50 * 1024 * 1024,
|
|
40
41
|
timeout: 3e4
|
|
@@ -45,18 +46,17 @@ function fetchDiff(opts) {
|
|
|
45
46
|
}
|
|
46
47
|
throw new Error("aicli ci: must supply --pr <num> or --base <ref> to determine the diff source");
|
|
47
48
|
}
|
|
48
|
-
function
|
|
49
|
+
function validateGitRef(ref) {
|
|
49
50
|
if (!/^[A-Za-z0-9._/\-]+$/.test(ref)) {
|
|
50
51
|
throw new Error(`unsafe ref: ${ref}`);
|
|
51
52
|
}
|
|
52
|
-
return ref;
|
|
53
53
|
}
|
|
54
54
|
function buildGitContextStr(opts) {
|
|
55
55
|
const parts = [];
|
|
56
56
|
if (opts.pr != null) parts.push(`PR: #${opts.pr}`);
|
|
57
57
|
if (opts.base) parts.push(`Base: ${opts.base}`);
|
|
58
58
|
try {
|
|
59
|
-
const branch =
|
|
59
|
+
const branch = execFileSync("git", ["rev-parse", "--abbrev-ref", "HEAD"], { encoding: "utf-8", timeout: 5e3 }).trim();
|
|
60
60
|
if (branch) parts.push(`Branch: ${branch}`);
|
|
61
61
|
} catch {
|
|
62
62
|
}
|