jinzd-ai-cli 0.4.318 → 0.4.320
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/dist/{batch-Y7IG5WTY.js → batch-LRYDPB6F.js} +2 -2
- package/dist/{chunk-VXQSMWXN.js → chunk-46M65JDN.js} +99 -62
- package/dist/{chunk-SPSFCJLM.js → chunk-4S2XD3M2.js} +4 -4
- package/dist/{chunk-ZXNQ37FB.js → chunk-5VZHSSLW.js} +1 -1
- package/dist/{chunk-DAWPHJSS.js → chunk-DB4HVUPI.js} +1 -1
- package/dist/{chunk-6VBW3L57.js → chunk-E2W2MI6H.js} +76 -6
- package/dist/{chunk-KPCXZJW2.js → chunk-J7IPNING.js} +1 -1
- package/dist/{chunk-IODFI5GY.js → chunk-N6YFBRQV.js} +28 -16
- package/dist/{chunk-TNEGZIP6.js → chunk-NOAWUEJJ.js} +28 -16
- package/dist/{chunk-BVEEA5BS.js → chunk-P4FJBD2H.js} +1 -1
- package/dist/{chunk-RB6FKHNZ.js → chunk-QB3MECZJ.js} +8 -8
- package/dist/{chunk-ZGIMXWXZ.js → chunk-SKQUWDRM.js} +1 -1
- package/dist/{chunk-7HX3FC67.js → chunk-TKBWA3XJ.js} +4 -4
- package/dist/{chunk-4MLOMT3W.js → chunk-WW2LFOWE.js} +1 -1
- package/dist/{chunk-APGWL4CQ.js → chunk-WY4IWCQ5.js} +1 -1
- package/dist/{chunk-MBI4RPRN.js → chunk-YAMPLTYO.js} +1 -1
- package/dist/{ci-XE42OB7U.js → ci-7TSOMDHV.js} +6 -6
- package/dist/{ci-format-IHLVLQ6B.js → ci-format-6A2RFYZS.js} +2 -2
- package/dist/{constants-HVWL645G.js → constants-EHYM2NS5.js} +1 -1
- package/dist/{constants-XUNVLHWI.js → constants-P33UDQVS.js} +1 -1
- package/dist/{doctor-cli-R3WQP5FD.js → doctor-cli-2B72UMZO.js} +6 -6
- package/dist/electron-server.js +180 -73
- package/dist/{hub-34YQNQF3.js → hub-JACLTKYY.js} +1 -1
- package/dist/index.js +45 -24
- package/dist/{persistent-memory-IUALAZ5K.js → persistent-memory-5ICLP275.js} +2 -2
- package/dist/{persistent-memory-VBVGH3P5.js → persistent-memory-ZIM6ICIR.js} +2 -2
- package/dist/{pr-QEQH3TAH.js → pr-UBGLNROK.js} +6 -6
- package/dist/{run-tests-OBX5CEPQ.js → run-tests-HCID73KE.js} +2 -2
- package/dist/{run-tests-L7FHOQAD.js → run-tests-SYJG4S3N.js} +2 -2
- package/dist/{server-ESLQPXVI.js → server-3UEEPEUD.js} +15 -15
- package/dist/{server-NBAC226K.js → server-BVTFIPB2.js} +6 -6
- package/dist/{task-orchestrator-2IKNOOMG.js → task-orchestrator-6NRCGF5V.js} +9 -7
- package/dist/{usage-B7TQHRIN.js → usage-6V64TTRE.js} +2 -2
- package/package.json +1 -1
|
@@ -4,12 +4,12 @@ import {
|
|
|
4
4
|
} from "./chunk-RS7SKYTS.js";
|
|
5
5
|
import {
|
|
6
6
|
ConfigManager
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-DB4HVUPI.js";
|
|
8
8
|
import "./chunk-TU3L3PW7.js";
|
|
9
9
|
import {
|
|
10
10
|
atomicWriteFileSync
|
|
11
11
|
} from "./chunk-BMLQPSHP.js";
|
|
12
|
-
import "./chunk-
|
|
12
|
+
import "./chunk-J7IPNING.js";
|
|
13
13
|
|
|
14
14
|
// src/cli/batch.ts
|
|
15
15
|
import Anthropic from "@anthropic-ai/sdk";
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
searchPersistentMemories,
|
|
12
12
|
touchMemoryReferences,
|
|
13
13
|
updateMemoryEntry
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-WY4IWCQ5.js";
|
|
15
15
|
import {
|
|
16
16
|
indexProject
|
|
17
17
|
} from "./chunk-RWRZVQCB.js";
|
|
@@ -30,13 +30,13 @@ import {
|
|
|
30
30
|
runHook,
|
|
31
31
|
runLifecycleHooks,
|
|
32
32
|
runTool
|
|
33
|
-
} from "./chunk-
|
|
33
|
+
} from "./chunk-5VZHSSLW.js";
|
|
34
34
|
import {
|
|
35
35
|
buildTruncatedArgumentsError,
|
|
36
36
|
getDangerLevel,
|
|
37
37
|
isFileWriteTool,
|
|
38
38
|
runLeanAgentLoop
|
|
39
|
-
} from "./chunk-
|
|
39
|
+
} from "./chunk-E2W2MI6H.js";
|
|
40
40
|
import {
|
|
41
41
|
EnvLoader,
|
|
42
42
|
NetworkError,
|
|
@@ -60,10 +60,11 @@ import {
|
|
|
60
60
|
decodeWindowsBuffer,
|
|
61
61
|
killProcessTree,
|
|
62
62
|
looksLikeGbkMojibake,
|
|
63
|
+
posixShell,
|
|
63
64
|
recoverGbkMojibake,
|
|
64
65
|
runTestsTool,
|
|
65
66
|
shellInvocation
|
|
66
|
-
} from "./chunk-
|
|
67
|
+
} from "./chunk-NOAWUEJJ.js";
|
|
67
68
|
import {
|
|
68
69
|
CONFIG_DIR_NAME,
|
|
69
70
|
DEFAULT_MAX_TOOL_OUTPUT_CHARS_CAP,
|
|
@@ -72,7 +73,7 @@ import {
|
|
|
72
73
|
SUBAGENT_ALLOWED_TOOLS,
|
|
73
74
|
SUBAGENT_DEFAULT_MAX_ROUNDS,
|
|
74
75
|
SUBAGENT_MAX_ROUNDS_LIMIT
|
|
75
|
-
} from "./chunk-
|
|
76
|
+
} from "./chunk-J7IPNING.js";
|
|
76
77
|
import {
|
|
77
78
|
getCurrentSessionKey,
|
|
78
79
|
getExecutionConfigDir,
|
|
@@ -84,7 +85,7 @@ import {
|
|
|
84
85
|
import { spawn } from "child_process";
|
|
85
86
|
import { platform } from "os";
|
|
86
87
|
var IS_WINDOWS2 = platform() === "win32";
|
|
87
|
-
var SHELL = IS_WINDOWS2 ? "powershell.exe" :
|
|
88
|
+
var SHELL = IS_WINDOWS2 ? "powershell.exe" : posixShell();
|
|
88
89
|
var pool = /* @__PURE__ */ new Map();
|
|
89
90
|
var IDLE_EVICT_MS = 5 * 6e4;
|
|
90
91
|
var evictionTimer = null;
|
|
@@ -93,7 +94,11 @@ function spawnShell(cwd) {
|
|
|
93
94
|
const proc = spawn(SHELL, shellArgs, {
|
|
94
95
|
cwd,
|
|
95
96
|
windowsHide: true,
|
|
96
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
97
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
98
|
+
// POSIX: own process group, so a timeout / Ctrl+C can kill the whole tree
|
|
99
|
+
// (killProcessTree with processGroup=true). Without it only the shell host
|
|
100
|
+
// died and grandchildren were orphaned (2026-10-07 audit A-01).
|
|
101
|
+
detached: !IS_WINDOWS2
|
|
97
102
|
});
|
|
98
103
|
proc.on("exit", () => {
|
|
99
104
|
for (const [key, entry] of pool) {
|
|
@@ -120,13 +125,17 @@ function startEvictionTimer() {
|
|
|
120
125
|
}
|
|
121
126
|
}, 6e4).unref();
|
|
122
127
|
}
|
|
128
|
+
function stderrMarker(id) {
|
|
129
|
+
return `AICLI_MARKER_ERR:${id}`;
|
|
130
|
+
}
|
|
123
131
|
function buildWrappedCommand(command, id, isWindows) {
|
|
124
132
|
const markerStart = `AICLI_MARKER_START:${id}`;
|
|
125
133
|
const markerEnd = `AICLI_MARKER_END:${id}`;
|
|
126
134
|
if (!isWindows) {
|
|
135
|
+
const quoted = `'${command.replace(/'/g, `'\\''`)}'`;
|
|
127
136
|
return `echo "${markerStart}"
|
|
128
|
-
${
|
|
129
|
-
echo "${markerEnd}
|
|
137
|
+
eval ${quoted} </dev/null
|
|
138
|
+
__aicli_rc=$?; echo "${stderrMarker(id)}" >&2; echo "${markerEnd}:$__aicli_rc"`;
|
|
130
139
|
}
|
|
131
140
|
const b64 = Buffer.from(command, "utf-8").toString("base64");
|
|
132
141
|
const decoded = `[System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String('${b64}'))`;
|
|
@@ -158,32 +167,46 @@ async function execInPool(sessionKey, command, timeout, cwd, abortSignal) {
|
|
|
158
167
|
const markerStartPattern = `AICLI_MARKER_START:${id}`;
|
|
159
168
|
const markerEndPattern = `AICLI_MARKER_END:${id}`;
|
|
160
169
|
const proc = entry.proc;
|
|
170
|
+
const errMarker = stderrMarker(id);
|
|
171
|
+
const cleanStderr = (s) => {
|
|
172
|
+
const i = s.indexOf(errMarker);
|
|
173
|
+
return i < 0 ? s : s.slice(0, i);
|
|
174
|
+
};
|
|
175
|
+
const checkDone = () => {
|
|
176
|
+
if (resolved) return;
|
|
177
|
+
const parsed = extractPoolOutput(stdout, markerStartPattern, markerEndPattern);
|
|
178
|
+
if (parsed.exitCode === null) return;
|
|
179
|
+
if (!IS_WINDOWS2 && !stderr.includes(errMarker)) return;
|
|
180
|
+
stdout = parsed.output;
|
|
181
|
+
stderr = cleanStderr(stderr);
|
|
182
|
+
resolved = true;
|
|
183
|
+
cleanup();
|
|
184
|
+
resolve7({ stdout, stderr, exitCode: parsed.exitCode, timedOut, aborted });
|
|
185
|
+
};
|
|
161
186
|
const onData = (chunk) => {
|
|
162
187
|
stdout += IS_WINDOWS2 ? decodeWindowsBuffer(chunk) : chunk.toString("utf-8");
|
|
163
|
-
|
|
164
|
-
const endLine = stdout.split("\n").find((l) => l.includes(markerEndPattern));
|
|
165
|
-
const exitCode = endLine ? parseEndMarker(endLine) : null;
|
|
166
|
-
stdout = stripMarkerOutput(stdout, markerStartPattern, markerEndPattern);
|
|
167
|
-
resolved = true;
|
|
168
|
-
cleanup();
|
|
169
|
-
resolve7({ stdout, stderr, exitCode, timedOut, aborted });
|
|
170
|
-
}
|
|
188
|
+
checkDone();
|
|
171
189
|
};
|
|
172
190
|
const onErr = (chunk) => {
|
|
173
191
|
stderr += IS_WINDOWS2 ? decodeWindowsBuffer(chunk) : chunk.toString("utf-8");
|
|
192
|
+
checkDone();
|
|
174
193
|
};
|
|
175
194
|
const finishPoolFailure = (message, exitCode = null) => {
|
|
176
195
|
if (resolved) return;
|
|
177
196
|
resolved = true;
|
|
178
|
-
stdout =
|
|
197
|
+
stdout = extractPoolOutput(stdout, markerStartPattern, markerEndPattern).output;
|
|
198
|
+
stderr = cleanStderr(stderr);
|
|
179
199
|
stderr += `${stderr ? "\n" : ""}[pool terminated] ${message}`;
|
|
180
200
|
cleanup();
|
|
181
201
|
pool.delete(sessionKey);
|
|
182
|
-
resolve7({ stdout, stderr, exitCode
|
|
202
|
+
resolve7({ stdout, stderr, exitCode, timedOut, aborted });
|
|
183
203
|
};
|
|
184
204
|
const onClose = (code, signal) => {
|
|
185
205
|
const detail = signal ? `signal ${signal}` : `exit code ${code ?? "unknown"}`;
|
|
186
|
-
finishPoolFailure(
|
|
206
|
+
finishPoolFailure(
|
|
207
|
+
`The shell exited before the command finished (${detail}) \u2014 typically an explicit \`exit\`/\`exec\`, or \`set -e\` aborting on a failing command. Shell state (variables, options, functions) was reset; the next command runs in a fresh shell in the same directory.`,
|
|
208
|
+
signal ? null : code
|
|
209
|
+
);
|
|
187
210
|
};
|
|
188
211
|
const onStdinError = (err) => {
|
|
189
212
|
finishPoolFailure(`Shell stdin failed: ${err.message}`);
|
|
@@ -196,10 +219,11 @@ async function execInPool(sessionKey, command, timeout, cwd, abortSignal) {
|
|
|
196
219
|
if (resolved) return;
|
|
197
220
|
timedOut = true;
|
|
198
221
|
resolved = true;
|
|
199
|
-
stdout =
|
|
222
|
+
stdout = extractPoolOutput(stdout, markerStartPattern, markerEndPattern).output;
|
|
223
|
+
stderr = cleanStderr(stderr);
|
|
200
224
|
cleanup();
|
|
201
225
|
resolve7({ stdout, stderr, exitCode: null, timedOut, aborted });
|
|
202
|
-
killProcessTree(proc);
|
|
226
|
+
killProcessTree(proc, true);
|
|
203
227
|
pool.delete(sessionKey);
|
|
204
228
|
}, timeout);
|
|
205
229
|
const onAbort = () => {
|
|
@@ -207,9 +231,10 @@ async function execInPool(sessionKey, command, timeout, cwd, abortSignal) {
|
|
|
207
231
|
aborted = true;
|
|
208
232
|
resolved = true;
|
|
209
233
|
cleanup();
|
|
210
|
-
stdout =
|
|
234
|
+
stdout = extractPoolOutput(stdout, markerStartPattern, markerEndPattern).output;
|
|
235
|
+
stderr = cleanStderr(stderr);
|
|
211
236
|
resolve7({ stdout, stderr, exitCode: null, timedOut, aborted });
|
|
212
|
-
killProcessTree(proc);
|
|
237
|
+
killProcessTree(proc, true);
|
|
213
238
|
pool.delete(sessionKey);
|
|
214
239
|
};
|
|
215
240
|
if (abortSignal?.aborted) {
|
|
@@ -241,36 +266,18 @@ async function execInPool(sessionKey, command, timeout, cwd, abortSignal) {
|
|
|
241
266
|
}
|
|
242
267
|
});
|
|
243
268
|
}
|
|
244
|
-
function
|
|
245
|
-
const
|
|
246
|
-
|
|
247
|
-
const
|
|
248
|
-
|
|
269
|
+
function extractPoolOutput(stdout, startMarker, endMarker) {
|
|
270
|
+
const startMatch = new RegExp(`${escapeRegExp(startMarker)}\\r?\\n`).exec(stdout);
|
|
271
|
+
const bodyStart = startMatch ? startMatch.index + startMatch[0].length : 0;
|
|
272
|
+
const endMatch = new RegExp(`${escapeRegExp(endMarker)}:(\\d+)\\r?\\n`).exec(stdout.slice(bodyStart));
|
|
273
|
+
const body = endMatch ? stdout.slice(bodyStart, bodyStart + endMatch.index) : stdout.slice(bodyStart);
|
|
274
|
+
return {
|
|
275
|
+
output: body.trim(),
|
|
276
|
+
exitCode: endMatch ? Number(endMatch[1]) : null
|
|
277
|
+
};
|
|
249
278
|
}
|
|
250
|
-
function
|
|
251
|
-
|
|
252
|
-
let capturing = false;
|
|
253
|
-
const out = [];
|
|
254
|
-
for (const line of lines) {
|
|
255
|
-
if (line.includes(startMarker)) {
|
|
256
|
-
capturing = true;
|
|
257
|
-
continue;
|
|
258
|
-
}
|
|
259
|
-
if (line.includes(endMarker)) {
|
|
260
|
-
capturing = false;
|
|
261
|
-
continue;
|
|
262
|
-
}
|
|
263
|
-
if (capturing) {
|
|
264
|
-
out.push(line);
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
if (!stdout.includes(endMarker) && out.length === 0) {
|
|
268
|
-
const startIdx = stdout.indexOf(startMarker);
|
|
269
|
-
if (startIdx >= 0) {
|
|
270
|
-
return stdout.slice(startIdx + startMarker.length).trimStart();
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
return out.join("\n").trim();
|
|
279
|
+
function escapeRegExp(s) {
|
|
280
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
274
281
|
}
|
|
275
282
|
function cleanupPoolSession(sessionKey) {
|
|
276
283
|
const entry = pool.get(sessionKey);
|
|
@@ -427,7 +434,7 @@ var undoStack = new UndoStack();
|
|
|
427
434
|
|
|
428
435
|
// src/tools/builtin/bash.ts
|
|
429
436
|
var IS_WINDOWS3 = platform2() === "win32";
|
|
430
|
-
var SHELL2 = IS_WINDOWS3 ? "powershell.exe" :
|
|
437
|
+
var SHELL2 = IS_WINDOWS3 ? "powershell.exe" : posixShell();
|
|
431
438
|
var cwdBySession = /* @__PURE__ */ new Map();
|
|
432
439
|
function getCwd() {
|
|
433
440
|
const key = getCurrentSessionKey();
|
|
@@ -556,7 +563,8 @@ Important rules:
|
|
|
556
563
|
}
|
|
557
564
|
if (timedOut) {
|
|
558
565
|
pushBashUndoEntries(beforeSnapshot, parsedTargetsBefore, effectiveCwd);
|
|
559
|
-
throw new ToolError("bash", formatTimeoutMessage(timeout, MAX_TIMEOUT, command
|
|
566
|
+
throw new ToolError("bash", formatTimeoutMessage(timeout, MAX_TIMEOUT, command, `${stdout}
|
|
567
|
+
${stderr}`));
|
|
560
568
|
}
|
|
561
569
|
if (status !== 0 || timedOut) {
|
|
562
570
|
const poolStdout = (stdout ?? "").trim();
|
|
@@ -575,7 +583,12 @@ ${combined || formatFailureFallback(status)}
|
|
|
575
583
|
}
|
|
576
584
|
pushBashUndoEntries(beforeSnapshot, parsedTargetsBefore, effectiveCwd);
|
|
577
585
|
updateCwdFromCommand(command, effectiveCwd);
|
|
578
|
-
|
|
586
|
+
const out = annotateSuccessOutput(command, suppressBinaryOutput(stdout));
|
|
587
|
+
const poolNote = /\[pool terminated\]/.test(stderr ?? "") ? `
|
|
588
|
+
|
|
589
|
+
${(stderr ?? "").trim()}` : "";
|
|
590
|
+
const body = out ? out + poolNote : emptyOutputResult(command, stderr ?? "");
|
|
591
|
+
return body + backgroundJobNote(command);
|
|
579
592
|
}
|
|
580
593
|
try {
|
|
581
594
|
const { stdout, stderr, status, signal, timedOut, aborted } = await runShell(
|
|
@@ -612,17 +625,18 @@ ${combined || formatFailureFallback(status)}
|
|
|
612
625
|
updateCwdFromCommand(command, effectiveCwd);
|
|
613
626
|
pushBashUndoEntries(beforeSnapshot, parsedTargetsBefore, effectiveCwd);
|
|
614
627
|
const result = Buffer.isBuffer(stdout) ? IS_WINDOWS3 ? decodeWindowsBuffer(stdout) : stdout.toString("utf-8") : String(stdout ?? "");
|
|
615
|
-
return annotateSuccessOutput(command, result) || emptyOutputResult(
|
|
628
|
+
return (annotateSuccessOutput(command, suppressBinaryOutput(result)) || emptyOutputResult(
|
|
616
629
|
command,
|
|
617
630
|
Buffer.isBuffer(stderr) ? IS_WINDOWS3 ? decodeWindowsBuffer(stderr) : stderr.toString("utf-8") : String(stderr ?? "")
|
|
618
|
-
);
|
|
631
|
+
)) + backgroundJobNote(command);
|
|
619
632
|
} catch (err) {
|
|
620
633
|
pushBashUndoEntries(beforeSnapshot, parsedTargetsBefore, effectiveCwd);
|
|
621
634
|
if (err && typeof err === "object") {
|
|
622
635
|
const execErr = err;
|
|
623
636
|
const isTimeout = execErr.code === "ETIMEDOUT" || execErr.status == null && execErr.signal === "SIGTERM" || /ETIMEDOUT/i.test(execErr.message ?? "");
|
|
624
637
|
if (isTimeout) {
|
|
625
|
-
|
|
638
|
+
const captured = [execErr.stdout, execErr.stderr].map((b) => Buffer.isBuffer(b) ? IS_WINDOWS3 ? decodeWindowsBuffer(b) : b.toString("utf-8") : String(b ?? "")).join("\n");
|
|
639
|
+
throw new ToolError("bash", formatTimeoutMessage(timeout, MAX_TIMEOUT, command, captured));
|
|
626
640
|
}
|
|
627
641
|
if ("status" in execErr && execErr.status !== void 0) {
|
|
628
642
|
const stderr = IS_WINDOWS3 && Buffer.isBuffer(execErr.stderr) ? decodeWindowsBuffer(execErr.stderr).trim() : execErr.stderr?.toString().trim() ?? "";
|
|
@@ -655,8 +669,15 @@ function looksLikeTestCommand(command) {
|
|
|
655
669
|
/(?:^|[;&|(\s])(?:go|cargo)\s+test\b/.test(command)
|
|
656
670
|
);
|
|
657
671
|
}
|
|
658
|
-
|
|
672
|
+
var TIMEOUT_CAPTURE_MAX = 2e3;
|
|
673
|
+
function formatTimeoutMessage(timeout, maxTimeout, command, captured) {
|
|
659
674
|
const seconds = Math.round(timeout / 1e3);
|
|
675
|
+
const tail = (captured ?? "").trim();
|
|
676
|
+
const capturedSection = tail ? [
|
|
677
|
+
"Output captured before the timeout (last part \u2014 the cause is often here):",
|
|
678
|
+
tail.length > TIMEOUT_CAPTURE_MAX ? "\u2026" + tail.slice(-TIMEOUT_CAPTURE_MAX) : tail,
|
|
679
|
+
""
|
|
680
|
+
] : [];
|
|
660
681
|
const testRunnerHint = command !== void 0 && looksLikeTestCommand(command) ? [
|
|
661
682
|
"This looks like a TEST run. Prefer the 'run_tests' tool instead of running the test runner through bash: it has a 5-minute timeout, auto-detects the framework, and returns a structured summary (no findstr/grep filtering needed).",
|
|
662
683
|
""
|
|
@@ -664,6 +685,7 @@ function formatTimeoutMessage(timeout, maxTimeout, command) {
|
|
|
664
685
|
return [
|
|
665
686
|
`Command timed out after ${seconds}s.`,
|
|
666
687
|
"",
|
|
688
|
+
...capturedSection,
|
|
667
689
|
...testRunnerHint,
|
|
668
690
|
"The previous command ran for longer than the timeout limit and was killed. This usually means it is scanning a large filesystem tree (recursive Get-ChildItem / find), compressing a big archive, or waiting on a network request.",
|
|
669
691
|
"",
|
|
@@ -836,6 +858,19 @@ ${err}`;
|
|
|
836
858
|
return detectPowerShellBackslashQuote(command) ? base + BACKSLASH_QUOTE_SILENT_WARNING : base;
|
|
837
859
|
}
|
|
838
860
|
var PS_TEXT_READ_CMDLET = /\b(?:Get-Content|gc|cat|type|Select-String|sls|Import-Csv)\b/i;
|
|
861
|
+
function suppressBinaryOutput(stdout) {
|
|
862
|
+
if (!stdout) return stdout;
|
|
863
|
+
const sample = stdout.slice(0, 8e3);
|
|
864
|
+
const replacement = (sample.match(/\uFFFD/g) ?? []).length;
|
|
865
|
+
if (!sample.includes("\0") && !(sample.length >= 64 && replacement / sample.length > 0.3)) {
|
|
866
|
+
return stdout;
|
|
867
|
+
}
|
|
868
|
+
return `[binary output suppressed: ${Buffer.byteLength(stdout, "utf-8")} bytes. Inspect it with a hex dump limited in size (e.g. \`xxd file | head -20\`), or use \`file\` to identify the type.]`;
|
|
869
|
+
}
|
|
870
|
+
function backgroundJobNote(command) {
|
|
871
|
+
if (IS_WINDOWS3 || !/(?:^|\s)&(?![&>])/m.test(command)) return "";
|
|
872
|
+
return "\n\n[Note: this command started a background job (`&`). It is not tracked by aicli \u2014 task_list/task_stop cannot see it and it keeps running after this session. For long-running processes (dev servers, watchers) prefer task_create.]";
|
|
873
|
+
}
|
|
839
874
|
function annotateSuccessOutput(command, stdout) {
|
|
840
875
|
if (!IS_WINDOWS3 || !PS_TEXT_READ_CMDLET.test(command)) return stdout;
|
|
841
876
|
if (!looksLikeGbkMojibake(stdout)) return stdout;
|
|
@@ -1013,7 +1048,9 @@ function runShell(command, opts) {
|
|
|
1013
1048
|
cwd: opts.cwd,
|
|
1014
1049
|
env: opts.env,
|
|
1015
1050
|
windowsHide: true,
|
|
1016
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
1051
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
1052
|
+
// POSIX: own process group so killChild can take the whole tree down (A-01).
|
|
1053
|
+
detached: !IS_WINDOWS3
|
|
1017
1054
|
});
|
|
1018
1055
|
const stdoutChunks = [];
|
|
1019
1056
|
const stderrChunks = [];
|
|
@@ -1070,7 +1107,7 @@ function runShell(command, opts) {
|
|
|
1070
1107
|
});
|
|
1071
1108
|
}
|
|
1072
1109
|
function killChild(child) {
|
|
1073
|
-
killProcessTree(child);
|
|
1110
|
+
killProcessTree(child, true);
|
|
1074
1111
|
}
|
|
1075
1112
|
function updateCwdFromCommand(command, baseCwd) {
|
|
1076
1113
|
const cdMatches = [...command.matchAll(/(?:^|[;&|])\s*cd\s+(['"]?)([^\s;&|'"]+)\1/g)];
|
|
@@ -8,13 +8,13 @@ import {
|
|
|
8
8
|
listHooks,
|
|
9
9
|
listInstalledPlugins,
|
|
10
10
|
resetStats
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-5VZHSSLW.js";
|
|
12
12
|
import {
|
|
13
13
|
ProviderRegistry
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-YAMPLTYO.js";
|
|
15
15
|
import {
|
|
16
16
|
ConfigManager
|
|
17
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-DB4HVUPI.js";
|
|
18
18
|
import {
|
|
19
19
|
getGitRoot
|
|
20
20
|
} from "./chunk-HOSJZMQS.js";
|
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
MCP_PROJECT_CONFIG_NAME,
|
|
27
27
|
MEMORY_FILE_NAME,
|
|
28
28
|
VERSION
|
|
29
|
-
} from "./chunk-
|
|
29
|
+
} from "./chunk-J7IPNING.js";
|
|
30
30
|
|
|
31
31
|
// src/diagnostics/doctor-report.ts
|
|
32
32
|
import { existsSync as existsSync3, statSync as statSync2 } from "fs";
|
|
@@ -14,6 +14,14 @@ var HALLUCINATION_PATTERNS = [
|
|
|
14
14
|
// 已创建!
|
|
15
15
|
/File\s+(?:written|saved|created)\s+(?:to|as|at)/i,
|
|
16
16
|
// File written to / saved as(要求介词)
|
|
17
|
+
// 2026-10-07 审计 B-02:中英文判据不对称。中文「已保存到」要求引号、英文只认 `File saved to`,
|
|
18
|
+
// 于是「已保存到 report.md。」「I saved the changes to src/index.ts.」双双漏检。
|
|
19
|
+
// 改为「后面跟一个带扩展名的文件名」——同样挡得住「已保存到数据库」「saved my time」这类误报。
|
|
20
|
+
/已(?:保存|写入)到?\s*[^\s,,。;;::]{1,120}\.\w{1,6}(?![\w/])/,
|
|
21
|
+
// 动词后直接跟文件名(中间只许冠词 / both / the file 之类),或「动词 … to/into 文件名」。
|
|
22
|
+
// 刻意不放宽成「同句任意文件名」:「I saved you some time by checking README.md」不是写入声明。
|
|
23
|
+
/\b(?:I|We)(?:'ve| have)?\s+(?:now\s+|just\s+|also\s+|successfully\s+)?(?:created|written|wrote|saved|generated)\s+(?:(?:the|a|an|both|all|two|three|new|following)\s+)*(?:(?:files?|documents?|reports?|scripts?)\s+)?[`'"]?[\w./-]+\.(?:md|txt|json|ya?ml|csv|html?|css|[jt]sx?|py|sh|go|rs|java|sql|xml|toml|ini|cfg|pdf|docx?|xlsx?|pptx?)\b/i,
|
|
24
|
+
/\b(?:I|We)(?:'ve| have)?\s+(?:now\s+|just\s+|also\s+|successfully\s+)?(?:created|written|wrote|saved|generated)\b[^.\n]{0,40}?\b(?:to|into)\s+[`'"]?[\w./-]+\.(?:md|txt|json|ya?ml|csv|html?|css|[jt]sx?|py|sh|go|rs|java|sql|xml|toml|ini|cfg|pdf|docx?|xlsx?|pptx?)\b/i,
|
|
17
25
|
/生成完成[!!]/,
|
|
18
26
|
// 生成完成!
|
|
19
27
|
/✅\s*(?:文件|已[生保写创]|第)\S*\.\w{1,5}/,
|
|
@@ -64,6 +72,8 @@ var QUICK_WRITE_KEYWORDS = [
|
|
|
64
72
|
"saved",
|
|
65
73
|
"written",
|
|
66
74
|
"created",
|
|
75
|
+
"wrote",
|
|
76
|
+
"generated",
|
|
67
77
|
"File",
|
|
68
78
|
"file",
|
|
69
79
|
// emoji 勾选标记
|
|
@@ -186,8 +196,13 @@ function extractClaimedFilePaths(content) {
|
|
|
186
196
|
}
|
|
187
197
|
const zhRe = /(?:已保存(?:到)?|已写入(?:到)?|已创建|已生成|文件路径[::]|保存为|写入到)\s*[`'”””]?([^\s`'”””,,。\n]+\.\w{1,6})/g;
|
|
188
198
|
while ((m = zhRe.exec(content)) !== null) add(m[1]);
|
|
189
|
-
const enRe =
|
|
199
|
+
const enRe = /\b(?:saved|written|wrote|created)\b[^.\n]{0,40}?\b(?:to|as|at|into)\s+[`'”]?([^\s`'”\n,]+\.\w{1,6})/gi;
|
|
190
200
|
while ((m = enRe.exec(content)) !== null) add(m[1]);
|
|
201
|
+
const enClaimRe = /\b(?:I|We)(?:'ve| have)?\s+(?:now\s+|just\s+|also\s+|successfully\s+)?(?:created|written|wrote|saved|generated)\b([^\n;]*)/gi;
|
|
202
|
+
while ((m = enClaimRe.exec(content)) !== null) {
|
|
203
|
+
const clause = m[1].split(/[.!?](?:\s|$)|\b(?:based on|from|using|per|according to|referencing|following|after|by|while|when|checking|reading|reviewing)\b/i)[0] ?? "";
|
|
204
|
+
for (const f of clause.matchAll(/[`'"]?([\w./-]+\.\w{1,6})\b/g)) add(f[1]);
|
|
205
|
+
}
|
|
191
206
|
const checkRe = /✅[^\n`]*?[`'”]?([^\s`'”\n,,。]+?\.\w{1,6})/g;
|
|
192
207
|
while ((m = checkRe.exec(content)) !== null) {
|
|
193
208
|
const pathStart = m.index + m[0].lastIndexOf(m[1]);
|
|
@@ -462,8 +477,27 @@ var META_NARRATION_HEURISTICS = [
|
|
|
462
477
|
/(?:我来|让我|容我)\s*(?:整理|梳理|归纳|想想|看看|重新|检查|核实)/u,
|
|
463
478
|
// 过渡语(需要 2+ hits):"以下是:" / "如下:" 作为独立行。文档中可作小节标题,
|
|
464
479
|
// 但配合另一个对话模式(如"好的")时是典型的 AI 内容包装。
|
|
465
|
-
/^.{0,60}(?:以下是|下面是|如下)\s*[::,,]\s*$/mu
|
|
480
|
+
/^.{0,60}(?:以下是|下面是|如下)\s*[::,,]\s*$/mu,
|
|
481
|
+
// 叙述自己要调用工具(2026-10-07 审计 B-05):文档正文不会这样写自己。
|
|
482
|
+
/(?:让我|我来|我将|我会|接下来)\s*(?:调用|使用|用)\s*`?\w+`?\s*工具/u,
|
|
483
|
+
/\bI(?:'ll| will| am going to)\s+(?:now\s+)?(?:use|call|invoke)\s+the\s+`?\w+`?\s+tool\b/i
|
|
484
|
+
];
|
|
485
|
+
var ANNOUNCEMENT_PATTERNS = [
|
|
486
|
+
/^\s*(?:好的|好|收到|明白|了解|嗯|OK|Okay|Sure|Certainly)[,,。.!!\s]/iu,
|
|
487
|
+
/(?:我(?:这就|马上|现在|立即|立刻|来|将|会)|让我|下面我|接下来我)\s*(?:为[您你])?\s*(?:开始|着手|撰写|写|生成|整理|输出|编写|准备|调用|使用)/u,
|
|
488
|
+
/(?:请稍[候等]|稍等|马上就好|这就开始)/u,
|
|
489
|
+
/\b(?:let me|I(?:'ll| will| am going to)|I'm going to)\s+(?:now\s+)?(?:write|draft|generate|create|start|prepare|produce|compose|use|call)\b/i,
|
|
490
|
+
/\b(?:please wait|one moment|stand by)\b/i
|
|
466
491
|
];
|
|
492
|
+
function looksLikeAnnouncementOnly(content) {
|
|
493
|
+
const text = content.trim();
|
|
494
|
+
if (!text || text.length > 300 || text.split("\n").length > 4) return null;
|
|
495
|
+
if (/^#{1,6}\s+\S/m.test(text)) return null;
|
|
496
|
+
for (const re of ANNOUNCEMENT_PATTERNS) {
|
|
497
|
+
if (re.test(text)) return re.source;
|
|
498
|
+
}
|
|
499
|
+
return null;
|
|
500
|
+
}
|
|
467
501
|
var CHINESE_TAIL_CLOSING_MARKERS = [
|
|
468
502
|
/(?:需要你|请你|麻烦你|你看)\s*(?:确认|决定|选择|帮忙|看[一看下]|过目|觉得|认为)/u,
|
|
469
503
|
/(?:随时|有问题|有需要|需要调整|需要修改)\s*(?:联系|找我|告诉|沟通|调整|修改)/u,
|
|
@@ -600,6 +634,15 @@ This fresh stream has NO tools \u2014 output is captured verbatim. STOP emitting
|
|
|
600
634
|
summary: `[save_last_response REJECTED] The content-only stream produced NO text at all (output budget likely spent on reasoning). ${saveToFile} was NOT saved.`
|
|
601
635
|
};
|
|
602
636
|
}
|
|
637
|
+
const announced = looksLikeAnnouncementOnly(rawContent);
|
|
638
|
+
if (announced) {
|
|
639
|
+
return fallback(`announcement only: ${announced}`) ?? {
|
|
640
|
+
kind: "reject",
|
|
641
|
+
reason: "not-a-document",
|
|
642
|
+
matched: announced,
|
|
643
|
+
summary: `[save_last_response REJECTED] The content-only stream produced only an announcement (e.g. "OK, I'll write the report now\u2026"), not the document itself. ${saveToFile} was NOT saved.`
|
|
644
|
+
};
|
|
645
|
+
}
|
|
603
646
|
const { lines, bytes } = teeFileStats(rawContent);
|
|
604
647
|
return {
|
|
605
648
|
kind: "ok",
|
|
@@ -621,6 +664,7 @@ function teeStreamErrorSummary(saveToFile, errMsg) {
|
|
|
621
664
|
// src/core/stalled-intent.ts
|
|
622
665
|
var WAITING_FOR_USER_RE = /[??]\s*$|是否|要不要|需不需要|需要我|要我|可以吗|好吗|行吗|您看|你看看|请确认|确认后|同意后|如果(?:你|您)|若(?:你|您)|如(?:你|您)需要|等你|等待(?:你|您)|待(?:你|您)|我可以|我也可以|还可以|(?:你|您)可以|告诉我|或者(?:让我|我)|可以为(?:你|您)|可以帮|随时|\bwould you\b|\bshall i\b|\bshould i\b|\bdo you want\b|\blet me know\b|\bif you(?:'d| would)? like\b|\bonce you confirm\b|\byour (?:approval|confirmation)\b|\bif you want\b|\bi can also\b/i;
|
|
623
666
|
var COMPLETED_RE = /已|完成|好了|完毕|写好|搞定|\b(?:done|finished|completed|have (?:written|created|generated))\b/i;
|
|
667
|
+
var NEGATION_RE = /没有?|未|无法|不(?:会|能|再|打算|需要|用|必|要)|n't\b|\b(?:not|never|no longer|cannot)\b/i;
|
|
624
668
|
var CN_VERBS = "\u5F00\u5199|\u52A8\u624B|\u7F16\u5199|\u5199\u5165|\u5199|\u751F\u6210|\u521B\u5EFA|\u5236\u4F5C|\u6267\u884C|\u8FD0\u884C|\u5B89\u88C5|\u4FEE\u6539|\u66F4\u65B0|\u4FEE\u590D|\u5B9E\u73B0|\u6784\u5EFA|\u8F93\u51FA|\u4FDD\u5B58|\u8BFB\u53D6|\u67E5\u770B|\u68C0\u67E5|\u8C03\u7528|\u5904\u7406|\u6574\u7406|\u8865\u5145|\u5B8C\u5584|\u91CD\u5199|\u6539\u5199|\u843D\u76D8";
|
|
625
669
|
var CN_FIRST_PERSON_INTENT_RE = new RegExp(
|
|
626
670
|
`(?:\u6211|\u54B1\u4EEC|\u6211\u4EEC|\u8BA9\u6211)(?:\u73B0\u5728|\u8FD9\u5C31|\u9A6C\u4E0A|\u7ACB\u5373|\u7ACB\u523B|\u76F4\u63A5|\u63A5\u4E0B\u6765|\u4E0B\u9762|\u5148|\u518D|\u968F\u540E|\u7136\u540E|\u5C31)*(?:\u5C06|\u4F1A|\u8981|\u6765|\u53BB|\u5F00\u59CB|\u7740\u624B|\u51C6\u5907)?(?:[^\uFF0C,\u3002\uFF01!\uFF1B;\uFF1A:\\n]{0,4})?(?:${CN_VERBS})`
|
|
@@ -642,11 +686,11 @@ function detectStalledIntent(content) {
|
|
|
642
686
|
if (WAITING_FOR_USER_RE.test(tail)) return null;
|
|
643
687
|
const clauses = tail.split(/[,,;;。!!::\n]/).map((c) => c.trim()).filter(Boolean);
|
|
644
688
|
for (const clause of clauses) {
|
|
645
|
-
if (COMPLETED_RE.test(clause)) continue;
|
|
689
|
+
if (COMPLETED_RE.test(clause) || NEGATION_RE.test(clause)) continue;
|
|
646
690
|
if (CN_FIRST_PERSON_INTENT_RE.test(clause) || CN_BARE_INTENT_RE.test(clause)) return clause;
|
|
647
691
|
}
|
|
648
692
|
for (const sentence of tail.split(/(?<=[.!])\s+|\n/)) {
|
|
649
|
-
if (COMPLETED_RE.test(sentence)) continue;
|
|
693
|
+
if (COMPLETED_RE.test(sentence) || NEGATION_RE.test(sentence)) continue;
|
|
650
694
|
const m = EN_INTENT_RE.exec(sentence);
|
|
651
695
|
if (m) return sentence.trim();
|
|
652
696
|
}
|
|
@@ -1094,7 +1138,12 @@ async function runAgentLoop(host) {
|
|
|
1094
1138
|
if (tc.name.startsWith("mcp__")) host.onMcpToolUsed?.(tc.name);
|
|
1095
1139
|
}
|
|
1096
1140
|
host.persistRound?.(toolCalls, toolResults, {
|
|
1097
|
-
|
|
1141
|
+
// 工具调用前模型说的话("A 方向汇报:…"、"先看一下配置")挂在 _streamedText 上:
|
|
1142
|
+
// 本轮上下文里有(buildToolResultMessages 会带上),但此前存档只取 outcome.content,
|
|
1143
|
+
// 而工具调用轮次的 outcome 没有 content——2026-10-07 审计会话 157 条带工具调用的
|
|
1144
|
+
// assistant 消息正文**全部为空**:下一条用户消息时模型看不到自己之前的阶段性汇报,
|
|
1145
|
+
// /session show、导出、回放也全缺这部分(审计 X-01)。
|
|
1146
|
+
assistantContent: outcome.content ?? toolCalls._streamedText,
|
|
1098
1147
|
reasoningContent: outcome.reasoningContent
|
|
1099
1148
|
});
|
|
1100
1149
|
host.afterToolRoundPersist?.(toolCalls, toolResults, extraMessages);
|
|
@@ -1149,6 +1198,7 @@ async function runLeanAgentLoop(opts) {
|
|
|
1149
1198
|
let roundsUsed = 0;
|
|
1150
1199
|
let toolCallCount = 0;
|
|
1151
1200
|
let stalledIntentNudges = 0;
|
|
1201
|
+
let hallucinationRetries = 0;
|
|
1152
1202
|
try {
|
|
1153
1203
|
for (let round = 0; round < opts.maxRounds; round++) {
|
|
1154
1204
|
roundsUsed = round + 1;
|
|
@@ -1164,6 +1214,7 @@ async function runLeanAgentLoop(opts) {
|
|
|
1164
1214
|
timeout: opts.chatParams?.timeout,
|
|
1165
1215
|
thinking: opts.chatParams?.thinking,
|
|
1166
1216
|
thinkingEffort: opts.chatParams?.thinkingEffort,
|
|
1217
|
+
thinkingBudget: opts.chatParams?.thinkingBudget,
|
|
1167
1218
|
...extraMessages.length > 0 ? { _extraMessages: extraMessages } : {}
|
|
1168
1219
|
},
|
|
1169
1220
|
opts.toolDefsForRound(round)
|
|
@@ -1183,6 +1234,24 @@ async function runLeanAgentLoop(opts) {
|
|
|
1183
1234
|
);
|
|
1184
1235
|
continue;
|
|
1185
1236
|
}
|
|
1237
|
+
const nextTools = round < opts.maxRounds - 1 ? opts.toolDefsForRound(round + 1) : [];
|
|
1238
|
+
const canWrite = nextTools.some((t) => t.name === "write_file" || t.name === "edit_file");
|
|
1239
|
+
if (canWrite && hallucinationRetries < 1 && result.content) {
|
|
1240
|
+
const alreadyWrote = hadPreviousWriteToolCalls(extraMessages);
|
|
1241
|
+
const coarse = !alreadyWrote && detectsHallucinatedFileOp(result.content) && extractBashCommands(extraMessages).length === 0;
|
|
1242
|
+
const phantom = coarse || alreadyWrote ? findPhantomClaims(result.content, extraMessages) : [];
|
|
1243
|
+
if (phantom.length > 0 || coarse) {
|
|
1244
|
+
hallucinationRetries++;
|
|
1245
|
+
extraMessages.push(
|
|
1246
|
+
{ role: "assistant", content: result.content },
|
|
1247
|
+
{
|
|
1248
|
+
role: "user",
|
|
1249
|
+
content: phantom.length > 0 ? buildPhantomCorrectionMessage(phantom) : HALLUCINATION_CORRECTION_MESSAGE
|
|
1250
|
+
}
|
|
1251
|
+
);
|
|
1252
|
+
continue;
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1186
1255
|
content = result.content;
|
|
1187
1256
|
break;
|
|
1188
1257
|
}
|
|
@@ -1219,7 +1288,8 @@ function getDangerLevel(toolName, args) {
|
|
|
1219
1288
|
if (/\bgit\s+(?:clean\b|reset\s+--hard\b|push\b[^\n]*(?:--force\b|\s-f\b))/i.test(cmd)) return "destructive";
|
|
1220
1289
|
if (/\brm\s+[^\n]*(?:-\w*[rRfF]\w*|--recursive|--force)\b/.test(cmd)) return "destructive";
|
|
1221
1290
|
if (/\brm\s+\S/.test(cmd)) return "destructive";
|
|
1222
|
-
if (/\brmdir\b|\
|
|
1291
|
+
if (/\brmdir\b|\bmkfs\b|\bdd\s+if=|\bshred\b|\bfdisk\b|\bparted\b/.test(cmd)) return "destructive";
|
|
1292
|
+
if (/(?:^|[;&|(\n]\s*)format(?:\.com)?\s+[A-Za-z]:/im.test(cmd)) return "destructive";
|
|
1223
1293
|
if (/\bshutdown\b|\breboot\b|\bhalt\b|\bpoweroff\b/.test(cmd)) return "destructive";
|
|
1224
1294
|
if (/\bkill\s+-9\b|\bkillall\b/.test(cmd)) return "destructive";
|
|
1225
1295
|
if (/\bRemove-Item\b|\bri\s+\S/i.test(cmd)) return "destructive";
|