jinzd-ai-cli 0.4.24 → 0.4.26
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 +8 -3
- package/README.zh-CN.md +8 -3
- package/dist/chunk-4BKXL7SM.js +98 -0
- package/dist/{chunk-PDVX5QJA.js → chunk-5GZQLJAY.js} +1068 -201
- package/dist/{chunk-UA4BVWKV.js → chunk-AHH5I2U6.js} +1 -1
- package/dist/{chunk-XMTMCMAP.js → chunk-ETMUP3CY.js} +1 -1
- package/dist/chunk-SKET65WZ.js +96 -0
- package/dist/{chunk-GBMVHLPA.js → chunk-SS7BQZ5R.js} +2 -198
- package/dist/{hub-YN245LMP.js → hub-JOYPSPR2.js} +1 -1
- package/dist/index.js +178 -504
- package/dist/{run-tests-2S6SYL2M.js → run-tests-25BZE3KQ.js} +1 -1
- package/dist/{run-tests-7ZBI4ZTU.js → run-tests-L3JNRB6X.js} +1 -1
- package/dist/{server-SD5ICBFP.js → server-SZZXQZWY.js} +128 -7
- package/dist/{task-orchestrator-C472QXTJ.js → task-orchestrator-4N5UUA6L.js} +3 -2
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -8,7 +8,6 @@ import {
|
|
|
8
8
|
SessionManager,
|
|
9
9
|
SkillManager,
|
|
10
10
|
TOOL_CALL_REMINDER,
|
|
11
|
-
checkPermission,
|
|
12
11
|
clearDevState,
|
|
13
12
|
detectsHallucinatedFileOp,
|
|
14
13
|
formatGitContextForPrompt,
|
|
@@ -18,27 +17,26 @@ import {
|
|
|
18
17
|
hadPreviousWriteToolCalls,
|
|
19
18
|
loadDevState,
|
|
20
19
|
parseSimpleYaml,
|
|
21
|
-
renderDiff,
|
|
22
|
-
runHook,
|
|
23
20
|
saveDevState,
|
|
24
21
|
sessionHasMeaningfulContent,
|
|
25
22
|
setupProxy
|
|
26
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-SS7BQZ5R.js";
|
|
27
24
|
import {
|
|
25
|
+
ToolExecutor,
|
|
28
26
|
ToolRegistry,
|
|
29
27
|
askUserContext,
|
|
30
|
-
getActiveMaxChars,
|
|
31
|
-
getDangerLevel,
|
|
32
28
|
googleSearchContext,
|
|
33
29
|
initTheme,
|
|
34
|
-
isFileWriteTool,
|
|
35
30
|
lastResponseStore,
|
|
31
|
+
renderDiff,
|
|
36
32
|
setContextWindow,
|
|
37
33
|
spawnAgentContext,
|
|
38
34
|
theme,
|
|
39
|
-
truncateOutput,
|
|
40
35
|
undoStack
|
|
41
|
-
} from "./chunk-
|
|
36
|
+
} from "./chunk-5GZQLJAY.js";
|
|
37
|
+
import {
|
|
38
|
+
fileCheckpoints
|
|
39
|
+
} from "./chunk-4BKXL7SM.js";
|
|
42
40
|
import {
|
|
43
41
|
AGENTIC_BEHAVIOR_GUIDELINE,
|
|
44
42
|
AUTHOR,
|
|
@@ -59,16 +57,16 @@ import {
|
|
|
59
57
|
SKILLS_DIR_NAME,
|
|
60
58
|
VERSION,
|
|
61
59
|
buildUserIdentityPrompt
|
|
62
|
-
} from "./chunk-
|
|
60
|
+
} from "./chunk-AHH5I2U6.js";
|
|
63
61
|
|
|
64
62
|
// src/index.ts
|
|
65
63
|
import { program } from "commander";
|
|
66
64
|
|
|
67
65
|
// src/repl/repl.ts
|
|
68
66
|
import * as readline from "readline";
|
|
69
|
-
import { existsSync as
|
|
67
|
+
import { existsSync as existsSync4, readFileSync as readFileSync3, readdirSync as readdirSync3, statSync as statSync3 } from "fs";
|
|
70
68
|
import { join as join4, resolve as resolve2, extname as extname2, dirname as dirname3, basename as basename2 } from "path";
|
|
71
|
-
import
|
|
69
|
+
import chalk4 from "chalk";
|
|
72
70
|
|
|
73
71
|
// src/repl/renderer.ts
|
|
74
72
|
import chalk from "chalk";
|
|
@@ -197,7 +195,7 @@ var Renderer = class {
|
|
|
197
195
|
console.log(theme.dim(" Gemini (Google) \xB7 Zhipu (GLM) \xB7 Custom OpenAI-compatible"));
|
|
198
196
|
console.log(HR);
|
|
199
197
|
const mcpToolCount = mcpInfo?.tools ?? 0;
|
|
200
|
-
const toolTotal =
|
|
198
|
+
const toolTotal = 19 + pluginCount + mcpToolCount;
|
|
201
199
|
const extras = [];
|
|
202
200
|
if (pluginCount > 0) extras.push(`${pluginCount} plugin(s)`);
|
|
203
201
|
if (mcpToolCount > 0) extras.push(`${mcpToolCount} MCP`);
|
|
@@ -219,13 +217,17 @@ var Renderer = class {
|
|
|
219
217
|
console.log(tool("write_todos", "Break tasks into subtask list with real-time progress display"));
|
|
220
218
|
console.log(tool("spawn_agent", "Delegate to independent sub-agent (isolated dialog + auto tool-call loop)"));
|
|
221
219
|
console.log(tool("run_tests", "Run project tests and return structured report (auto-detect Maven/npm/pytest etc.)"));
|
|
220
|
+
console.log(tool("task_create", "Start a command running in the background"));
|
|
221
|
+
console.log(tool("task_list", "List background tasks and their status/output"));
|
|
222
|
+
console.log(tool("task_stop", "Stop a running background task"));
|
|
222
223
|
console.log(HR);
|
|
223
|
-
console.log(theme.dim(" REPL Commands (
|
|
224
|
+
console.log(theme.dim(" REPL Commands (40):"));
|
|
224
225
|
console.log(theme.dim(" /help /about /provider /model /clear /compact /plan /session"));
|
|
225
226
|
console.log(theme.dim(" /system /context /status /search /undo /export /copy /paste"));
|
|
226
227
|
console.log(theme.dim(" /cost /init /skill /tools /plugins /mcp /config /checkpoint"));
|
|
227
|
-
console.log(theme.dim(" /review /
|
|
228
|
-
console.log(theme.dim(" /doctor /bug /think /diff /fork
|
|
228
|
+
console.log(theme.dim(" /review /security-review /rewind /commands /test /scaffold"));
|
|
229
|
+
console.log(theme.dim(" /add-dir /memory /profile /doctor /bug /think /diff /fork"));
|
|
230
|
+
console.log(theme.dim(" /yolo /exit"));
|
|
229
231
|
console.log(HR);
|
|
230
232
|
console.log(theme.dim(" Key Features:"));
|
|
231
233
|
console.log(feat("Agentic loop (up to 25 tool-call rounds, final answer streamed)"));
|
|
@@ -836,6 +838,39 @@ Please structure your review as follows:
|
|
|
836
838
|
|
|
837
839
|
Severity levels: \u{1F534} Critical / \u{1F7E1} Warning / \u{1F535} Info`;
|
|
838
840
|
}
|
|
841
|
+
function buildSecurityReviewPrompt(diff, gitContextStr) {
|
|
842
|
+
return `# Security Vulnerability Review
|
|
843
|
+
|
|
844
|
+
Analyze the following code changes **exclusively for security vulnerabilities**.
|
|
845
|
+
|
|
846
|
+
## Categories to check:
|
|
847
|
+
1. **Injection** \u2014 SQL, command, path traversal, XSS, template injection
|
|
848
|
+
2. **Authentication & Authorization** \u2014 hardcoded credentials, missing auth checks, privilege escalation
|
|
849
|
+
3. **Secrets & Sensitive Data** \u2014 API keys, tokens, passwords in code, logging sensitive data
|
|
850
|
+
4. **Input Validation** \u2014 missing validation, unsafe deserialization, buffer issues
|
|
851
|
+
5. **Cryptography** \u2014 weak algorithms, improper random, hardcoded IVs/salts
|
|
852
|
+
6. **Dependencies** \u2014 known vulnerable packages, unsafe dynamic imports
|
|
853
|
+
7. **File System** \u2014 path traversal, unsafe file permissions, symlink attacks
|
|
854
|
+
8. **Network** \u2014 SSRF, insecure protocols, missing TLS validation
|
|
855
|
+
|
|
856
|
+
## Git Status
|
|
857
|
+
${gitContextStr}
|
|
858
|
+
|
|
859
|
+
## Code Changes (diff)
|
|
860
|
+
\`\`\`diff
|
|
861
|
+
${diff}
|
|
862
|
+
\`\`\`
|
|
863
|
+
|
|
864
|
+
## Output Format
|
|
865
|
+
For each finding:
|
|
866
|
+
- **Severity**: \u{1F534} CRITICAL / \u{1F7E0} HIGH / \u{1F7E1} MEDIUM / \u{1F535} LOW / \u2139\uFE0F INFO
|
|
867
|
+
- **Category**: (from list above)
|
|
868
|
+
- **File & location**: file:line
|
|
869
|
+
- **Description**: what the vulnerability is and how it could be exploited
|
|
870
|
+
- **Recommended fix**: specific code change to resolve
|
|
871
|
+
|
|
872
|
+
If no security issues found, state "\u2705 No security vulnerabilities detected" with a brief explanation of what was checked.`;
|
|
873
|
+
}
|
|
839
874
|
var CommandRegistry = class {
|
|
840
875
|
commands = /* @__PURE__ */ new Map();
|
|
841
876
|
register(command) {
|
|
@@ -884,6 +919,8 @@ function createDefaultCommands() {
|
|
|
884
919
|
" /skill [name|off|list] - Manage agent skills (reusable prompt packs)",
|
|
885
920
|
" /checkpoint [save|restore|delete] <name> - Session checkpoints",
|
|
886
921
|
" /review [--staged] [--detailed] - AI code review from git diff",
|
|
922
|
+
" /security-review [--staged] - Security vulnerability scan on git diff",
|
|
923
|
+
" /rewind [list|<n>] - Rewind to message N (restores files too)",
|
|
887
924
|
" /commands [reload] - List/reload custom commands (~/.aicli/commands/)",
|
|
888
925
|
" /test [command|filter] - Run project tests and show structured report",
|
|
889
926
|
" /scaffold <description> - Generate project scaffolding with AI",
|
|
@@ -1908,6 +1945,108 @@ ${hint}` : "")
|
|
|
1908
1945
|
}
|
|
1909
1946
|
}
|
|
1910
1947
|
},
|
|
1948
|
+
// ── /security-review ──────────────────────────────────────────
|
|
1949
|
+
{
|
|
1950
|
+
name: "security-review",
|
|
1951
|
+
description: "Security vulnerability scan on git diff",
|
|
1952
|
+
usage: "/security-review [--staged]",
|
|
1953
|
+
async execute(args, ctx) {
|
|
1954
|
+
const gitCtx = getGitContext();
|
|
1955
|
+
if (!gitCtx) {
|
|
1956
|
+
ctx.renderer.renderError("Not a git repository.");
|
|
1957
|
+
return;
|
|
1958
|
+
}
|
|
1959
|
+
const staged = args.includes("--staged");
|
|
1960
|
+
let diff;
|
|
1961
|
+
try {
|
|
1962
|
+
const cmd = staged ? "git diff --staged" : "git diff";
|
|
1963
|
+
diff = execSync2(cmd, { encoding: "utf-8", timeout: 1e4 }).trim();
|
|
1964
|
+
} catch {
|
|
1965
|
+
ctx.renderer.renderError("Failed to run git diff.");
|
|
1966
|
+
return;
|
|
1967
|
+
}
|
|
1968
|
+
if (!diff) {
|
|
1969
|
+
console.log(theme.dim(" No changes to review." + (staged ? "" : " Try --staged for staged changes.")));
|
|
1970
|
+
return;
|
|
1971
|
+
}
|
|
1972
|
+
const MAX_DIFF = 8e3;
|
|
1973
|
+
let truncated = false;
|
|
1974
|
+
if (diff.length > MAX_DIFF) {
|
|
1975
|
+
const head = diff.slice(0, Math.floor(MAX_DIFF * 0.7));
|
|
1976
|
+
const tail = diff.slice(diff.length - Math.floor(MAX_DIFF * 0.2));
|
|
1977
|
+
diff = head + "\n\n... [diff truncated, " + diff.length + " chars total] ...\n\n" + tail;
|
|
1978
|
+
truncated = true;
|
|
1979
|
+
}
|
|
1980
|
+
const prompt = buildSecurityReviewPrompt(diff, formatGitContextForPrompt(gitCtx));
|
|
1981
|
+
console.log(theme.dim(" \u{1F512} Scanning for security vulnerabilities..."));
|
|
1982
|
+
try {
|
|
1983
|
+
const review = await ctx.chatOnce(prompt, { temperature: 0.2, maxTokens: 8192 });
|
|
1984
|
+
console.log();
|
|
1985
|
+
console.log(review);
|
|
1986
|
+
console.log();
|
|
1987
|
+
if (truncated) {
|
|
1988
|
+
console.log(theme.warning(" \u26A0 Diff was truncated. Consider reviewing smaller changesets."));
|
|
1989
|
+
}
|
|
1990
|
+
} catch (err) {
|
|
1991
|
+
ctx.renderer.renderError(`Security review failed: ${err.message}`);
|
|
1992
|
+
}
|
|
1993
|
+
}
|
|
1994
|
+
},
|
|
1995
|
+
// ── /rewind ───────────────────────────────────────────────────
|
|
1996
|
+
{
|
|
1997
|
+
name: "rewind",
|
|
1998
|
+
description: "Rewind conversation to a previous message and restore file states",
|
|
1999
|
+
usage: "/rewind [list | <n>]",
|
|
2000
|
+
execute(args, ctx) {
|
|
2001
|
+
const session = ctx.sessions.current;
|
|
2002
|
+
if (!session || session.messages.length === 0) {
|
|
2003
|
+
console.log(theme.dim(" No messages to rewind."));
|
|
2004
|
+
return;
|
|
2005
|
+
}
|
|
2006
|
+
const sub = args[0];
|
|
2007
|
+
if (sub === "list" || !sub) {
|
|
2008
|
+
const msgs = session.messages;
|
|
2009
|
+
console.log(theme.primary(`
|
|
2010
|
+
Conversation messages (${msgs.length} total):
|
|
2011
|
+
`));
|
|
2012
|
+
for (let i = 0; i < msgs.length; i++) {
|
|
2013
|
+
const m = msgs[i];
|
|
2014
|
+
const role = m.role.padEnd(10);
|
|
2015
|
+
const text = getContentText(m.content).replace(/\n/g, " ").slice(0, 70);
|
|
2016
|
+
const hasCheckpoint = fileCheckpoints.getMessageIndices().includes(i) ? " \u{1F4CC}" : "";
|
|
2017
|
+
console.log(theme.dim(` [${String(i + 1).padStart(3)}]`) + ` ${theme.info(role)} ${text}${hasCheckpoint}`);
|
|
2018
|
+
}
|
|
2019
|
+
console.log();
|
|
2020
|
+
console.log(theme.dim(" Usage: /rewind <n> \u2014 rewind to message N (1-based)"));
|
|
2021
|
+
console.log(theme.dim(" \u{1F4CC} = file checkpoint exists at this point"));
|
|
2022
|
+
console.log();
|
|
2023
|
+
return;
|
|
2024
|
+
}
|
|
2025
|
+
const n = parseInt(sub, 10);
|
|
2026
|
+
if (isNaN(n) || n < 1 || n > session.messages.length) {
|
|
2027
|
+
ctx.renderer.renderError(`Invalid message number: ${sub}. Range: 1-${session.messages.length}`);
|
|
2028
|
+
return;
|
|
2029
|
+
}
|
|
2030
|
+
const targetIndex = n;
|
|
2031
|
+
const messagesRemoved = session.messages.length - targetIndex;
|
|
2032
|
+
const { restored, deleted, files } = fileCheckpoints.restoreToMessageIndex(targetIndex);
|
|
2033
|
+
session.messages = session.messages.slice(0, targetIndex);
|
|
2034
|
+
session.checkpoints = session.checkpoints.filter((c) => c.messageIndex <= targetIndex);
|
|
2035
|
+
session.updated = /* @__PURE__ */ new Date();
|
|
2036
|
+
console.log(theme.success(`
|
|
2037
|
+
\u2713 Rewound to message ${n}`));
|
|
2038
|
+
console.log(theme.dim(` Messages removed: ${messagesRemoved}`));
|
|
2039
|
+
if (restored > 0 || deleted > 0) {
|
|
2040
|
+
console.log(theme.dim(` Files restored: ${restored}, files deleted: ${deleted}`));
|
|
2041
|
+
for (const f of files) {
|
|
2042
|
+
console.log(theme.dim(` ${f}`));
|
|
2043
|
+
}
|
|
2044
|
+
} else {
|
|
2045
|
+
console.log(theme.dim(" No file changes to revert."));
|
|
2046
|
+
}
|
|
2047
|
+
console.log();
|
|
2048
|
+
}
|
|
2049
|
+
},
|
|
1911
2050
|
// ── /commands ─────────────────────────────────────────────────
|
|
1912
2051
|
{
|
|
1913
2052
|
name: "commands",
|
|
@@ -1944,7 +2083,7 @@ ${hint}` : "")
|
|
|
1944
2083
|
usage: "/test [command|filter]",
|
|
1945
2084
|
async execute(args, ctx) {
|
|
1946
2085
|
try {
|
|
1947
|
-
const { executeTests } = await import("./run-tests-
|
|
2086
|
+
const { executeTests } = await import("./run-tests-L3JNRB6X.js");
|
|
1948
2087
|
const argStr = args.join(" ").trim();
|
|
1949
2088
|
let testArgs = {};
|
|
1950
2089
|
if (argStr) {
|
|
@@ -2576,472 +2715,6 @@ function selectFromList(prompt, items, initialIndex = 0) {
|
|
|
2576
2715
|
});
|
|
2577
2716
|
}
|
|
2578
2717
|
|
|
2579
|
-
// src/tools/executor.ts
|
|
2580
|
-
import chalk4 from "chalk";
|
|
2581
|
-
import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
|
|
2582
|
-
var ToolExecutor = class {
|
|
2583
|
-
constructor(registry) {
|
|
2584
|
-
this.registry = registry;
|
|
2585
|
-
}
|
|
2586
|
-
round = 0;
|
|
2587
|
-
totalRounds = 0;
|
|
2588
|
-
/** readline 接口引用,由 repl.ts 注入,用于 confirm() 读取用户输入 */
|
|
2589
|
-
rl = null;
|
|
2590
|
-
/**
|
|
2591
|
-
* confirm() 进行中标志。
|
|
2592
|
-
* repl.ts 的主循环 line handler 在此为 true 时忽略输入,
|
|
2593
|
-
* 防止用户输入 "y"+Enter 被同时触发 once('line') 和主循环 on('line')。
|
|
2594
|
-
*/
|
|
2595
|
-
confirming = false;
|
|
2596
|
-
/** 在 confirm 期间用户输入的 slash 命令,由 repl.ts 主循环消费 */
|
|
2597
|
-
pendingSlashCommand = null;
|
|
2598
|
-
/** confirm() 的取消回调,由 SIGINT handler 调用 */
|
|
2599
|
-
cancelConfirmFn = null;
|
|
2600
|
-
/**
|
|
2601
|
-
* 会话级 auto-approve:跳过所有 write/destructive 确认(仅当前会话有效)。
|
|
2602
|
-
* 通过 /yolo 命令切换。destructive 操作仍会显示警告但不阻塞。
|
|
2603
|
-
*/
|
|
2604
|
-
sessionAutoApprove = false;
|
|
2605
|
-
/**
|
|
2606
|
-
* 由外部(repl.ts SIGINT handler)调用,将当前 confirm() 等待视为用户按 N 取消。
|
|
2607
|
-
* 若当前没有 confirm() 进行中,无操作。
|
|
2608
|
-
*/
|
|
2609
|
-
cancelConfirm() {
|
|
2610
|
-
if (this.cancelConfirmFn) {
|
|
2611
|
-
this.cancelConfirmFn();
|
|
2612
|
-
}
|
|
2613
|
-
}
|
|
2614
|
-
setRoundInfo(current, total) {
|
|
2615
|
-
this.round = current;
|
|
2616
|
-
this.totalRounds = total;
|
|
2617
|
-
}
|
|
2618
|
-
/**
|
|
2619
|
-
* 注入 readline 接口,供 confirm() 使用。
|
|
2620
|
-
* 必须在 start() 之前调用,rl 初始化后立即注入。
|
|
2621
|
-
*/
|
|
2622
|
-
setReadline(rl) {
|
|
2623
|
-
this.rl = rl;
|
|
2624
|
-
}
|
|
2625
|
-
/** 钩子配置(可选) */
|
|
2626
|
-
hookConfig;
|
|
2627
|
-
/** 权限规则(可选) */
|
|
2628
|
-
permissionRules = [];
|
|
2629
|
-
defaultPermission = "confirm";
|
|
2630
|
-
/** 注入 hooks 和 permission rules 配置 */
|
|
2631
|
-
setConfig(opts) {
|
|
2632
|
-
this.hookConfig = opts.hookConfig;
|
|
2633
|
-
if (opts.permissionRules) this.permissionRules = opts.permissionRules;
|
|
2634
|
-
if (opts.defaultPermission) this.defaultPermission = opts.defaultPermission;
|
|
2635
|
-
}
|
|
2636
|
-
async execute(call) {
|
|
2637
|
-
const tool = this.registry.get(call.name);
|
|
2638
|
-
if (!tool) {
|
|
2639
|
-
return {
|
|
2640
|
-
callId: call.id,
|
|
2641
|
-
content: `Unknown tool: ${call.name}`,
|
|
2642
|
-
isError: true
|
|
2643
|
-
};
|
|
2644
|
-
}
|
|
2645
|
-
const dangerLevel = getDangerLevel(call.name, call.arguments);
|
|
2646
|
-
runHook(this.hookConfig?.preToolExecution, {
|
|
2647
|
-
tool: call.name,
|
|
2648
|
-
dangerLevel,
|
|
2649
|
-
args: JSON.stringify(call.arguments).slice(0, 200)
|
|
2650
|
-
});
|
|
2651
|
-
if (this.permissionRules.length > 0) {
|
|
2652
|
-
const action = checkPermission(call.name, call.arguments, dangerLevel, this.permissionRules, this.defaultPermission);
|
|
2653
|
-
if (action === "deny") {
|
|
2654
|
-
return { callId: call.id, content: `[Permission denied] Tool ${call.name} is blocked by permission rules. Do not retry.`, isError: true };
|
|
2655
|
-
}
|
|
2656
|
-
if (action === "auto-approve") {
|
|
2657
|
-
this.printToolCall(call);
|
|
2658
|
-
try {
|
|
2659
|
-
const rawContent = await tool.execute(call.arguments);
|
|
2660
|
-
const content = truncateOutput(rawContent, call.name);
|
|
2661
|
-
const wasTruncated = content !== rawContent;
|
|
2662
|
-
this.printToolResult(call.name, rawContent, false, wasTruncated);
|
|
2663
|
-
runHook(this.hookConfig?.postToolExecution, { tool: call.name, status: "ok" });
|
|
2664
|
-
return { callId: call.id, content, isError: false };
|
|
2665
|
-
} catch (err) {
|
|
2666
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
2667
|
-
this.printToolResult(call.name, message, true, false);
|
|
2668
|
-
runHook(this.hookConfig?.postToolExecution, { tool: call.name, status: "error" });
|
|
2669
|
-
return { callId: call.id, content: message, isError: true };
|
|
2670
|
-
}
|
|
2671
|
-
}
|
|
2672
|
-
}
|
|
2673
|
-
if (this.sessionAutoApprove && dangerLevel !== "safe") {
|
|
2674
|
-
this.printToolCall(call);
|
|
2675
|
-
if (dangerLevel === "write") this.printDiffPreview(call);
|
|
2676
|
-
console.log(theme.warning(" \u26A1 Auto-approved (session /yolo mode)"));
|
|
2677
|
-
try {
|
|
2678
|
-
const rawContent = await tool.execute(call.arguments);
|
|
2679
|
-
const content = truncateOutput(rawContent, call.name);
|
|
2680
|
-
const wasTruncated = content !== rawContent;
|
|
2681
|
-
this.printToolResult(call.name, rawContent, false, wasTruncated);
|
|
2682
|
-
runHook(this.hookConfig?.postToolExecution, { tool: call.name, status: "ok" });
|
|
2683
|
-
return { callId: call.id, content, isError: false };
|
|
2684
|
-
} catch (err) {
|
|
2685
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
2686
|
-
this.printToolResult(call.name, message, true, false);
|
|
2687
|
-
runHook(this.hookConfig?.postToolExecution, { tool: call.name, status: "error" });
|
|
2688
|
-
return { callId: call.id, content: message, isError: true };
|
|
2689
|
-
}
|
|
2690
|
-
}
|
|
2691
|
-
if (dangerLevel === "write") {
|
|
2692
|
-
this.printToolCall(call);
|
|
2693
|
-
this.printDiffPreview(call);
|
|
2694
|
-
const confirmed = await this.confirm(call, dangerLevel);
|
|
2695
|
-
if (!confirmed) {
|
|
2696
|
-
return {
|
|
2697
|
-
callId: call.id,
|
|
2698
|
-
content: `[User cancelled] The user declined the ${call.name} operation. Do not retry without asking.`,
|
|
2699
|
-
isError: true
|
|
2700
|
-
};
|
|
2701
|
-
}
|
|
2702
|
-
} else if (dangerLevel === "destructive") {
|
|
2703
|
-
const confirmed = await this.confirm(call, dangerLevel);
|
|
2704
|
-
if (!confirmed) {
|
|
2705
|
-
return {
|
|
2706
|
-
callId: call.id,
|
|
2707
|
-
content: `[User cancelled] The user declined the destructive ${call.name} operation. Do not retry without asking.`,
|
|
2708
|
-
isError: true
|
|
2709
|
-
};
|
|
2710
|
-
}
|
|
2711
|
-
this.printToolCall(call);
|
|
2712
|
-
} else {
|
|
2713
|
-
this.printToolCall(call);
|
|
2714
|
-
}
|
|
2715
|
-
try {
|
|
2716
|
-
const rawContent = await tool.execute(call.arguments);
|
|
2717
|
-
const content = truncateOutput(rawContent, call.name);
|
|
2718
|
-
const wasTruncated = content !== rawContent;
|
|
2719
|
-
this.printToolResult(call.name, rawContent, false, wasTruncated);
|
|
2720
|
-
runHook(this.hookConfig?.postToolExecution, { tool: call.name, status: "ok" });
|
|
2721
|
-
return { callId: call.id, content, isError: false };
|
|
2722
|
-
} catch (err) {
|
|
2723
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
2724
|
-
this.printToolResult(call.name, message, true, false);
|
|
2725
|
-
runHook(this.hookConfig?.postToolExecution, { tool: call.name, status: "error" });
|
|
2726
|
-
return { callId: call.id, content: message, isError: true };
|
|
2727
|
-
}
|
|
2728
|
-
}
|
|
2729
|
-
async executeAll(calls) {
|
|
2730
|
-
const safeCalls = [];
|
|
2731
|
-
const fileWriteCalls = [];
|
|
2732
|
-
const otherCalls = [];
|
|
2733
|
-
for (let i = 0; i < calls.length; i++) {
|
|
2734
|
-
const call = calls[i];
|
|
2735
|
-
const level = getDangerLevel(call.name, call.arguments);
|
|
2736
|
-
if (level === "safe") {
|
|
2737
|
-
safeCalls.push({ idx: i, call });
|
|
2738
|
-
} else if (isFileWriteTool(call.name) && level === "write") {
|
|
2739
|
-
fileWriteCalls.push({ idx: i, call });
|
|
2740
|
-
} else {
|
|
2741
|
-
otherCalls.push({ idx: i, call });
|
|
2742
|
-
}
|
|
2743
|
-
}
|
|
2744
|
-
const results = new Array(calls.length);
|
|
2745
|
-
await Promise.all(
|
|
2746
|
-
safeCalls.map(async ({ idx, call }) => {
|
|
2747
|
-
results[idx] = await this.execute(call);
|
|
2748
|
-
})
|
|
2749
|
-
);
|
|
2750
|
-
if (fileWriteCalls.length === 1) {
|
|
2751
|
-
const { idx, call } = fileWriteCalls[0];
|
|
2752
|
-
results[idx] = await this.execute(call);
|
|
2753
|
-
} else if (fileWriteCalls.length >= 2) {
|
|
2754
|
-
const batchResults = await this.executeBatchFileWrites(fileWriteCalls.map((f) => f.call));
|
|
2755
|
-
for (let i = 0; i < fileWriteCalls.length; i++) {
|
|
2756
|
-
results[fileWriteCalls[i].idx] = batchResults[i];
|
|
2757
|
-
}
|
|
2758
|
-
}
|
|
2759
|
-
for (const { idx, call } of otherCalls) {
|
|
2760
|
-
results[idx] = await this.execute(call);
|
|
2761
|
-
}
|
|
2762
|
-
return results;
|
|
2763
|
-
}
|
|
2764
|
-
/**
|
|
2765
|
-
* 批量文件写入:展示所有文件的编号列表 + diff 预览,
|
|
2766
|
-
* 然后让用户 approve all / reject all / 选择性 approve。
|
|
2767
|
-
*/
|
|
2768
|
-
async executeBatchFileWrites(calls) {
|
|
2769
|
-
console.log();
|
|
2770
|
-
console.log(theme.heading(`\u270E Batch file writes (${calls.length} files):`));
|
|
2771
|
-
console.log(theme.dim("\u2500".repeat(50)));
|
|
2772
|
-
for (let i = 0; i < calls.length; i++) {
|
|
2773
|
-
const call = calls[i];
|
|
2774
|
-
const filePath = String(call.arguments["path"] ?? "");
|
|
2775
|
-
console.log(theme.warning(` [${i + 1}] `) + chalk4.white(call.name) + theme.dim(": ") + theme.accent(filePath));
|
|
2776
|
-
this.printDiffPreview(call);
|
|
2777
|
-
}
|
|
2778
|
-
console.log(theme.dim("\u2500".repeat(50)));
|
|
2779
|
-
const decision = this.sessionAutoApprove ? "all" : await this.batchConfirm(calls.length);
|
|
2780
|
-
if (this.sessionAutoApprove) {
|
|
2781
|
-
console.log(theme.warning(" \u26A1 All auto-approved (session /yolo mode)"));
|
|
2782
|
-
}
|
|
2783
|
-
const results = [];
|
|
2784
|
-
for (let i = 0; i < calls.length; i++) {
|
|
2785
|
-
const call = calls[i];
|
|
2786
|
-
const approved = decision === "all" || decision !== "none" && decision.has(i + 1);
|
|
2787
|
-
if (approved) {
|
|
2788
|
-
const tool = this.registry.get(call.name);
|
|
2789
|
-
if (!tool) {
|
|
2790
|
-
results.push({ callId: call.id, content: `Unknown tool: ${call.name}`, isError: true });
|
|
2791
|
-
continue;
|
|
2792
|
-
}
|
|
2793
|
-
try {
|
|
2794
|
-
const rawContent = await tool.execute(call.arguments);
|
|
2795
|
-
const content = truncateOutput(rawContent, call.name);
|
|
2796
|
-
const wasTruncated = content !== rawContent;
|
|
2797
|
-
this.printToolResult(call.name, rawContent, false, wasTruncated);
|
|
2798
|
-
results.push({ callId: call.id, content, isError: false });
|
|
2799
|
-
} catch (err) {
|
|
2800
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
2801
|
-
this.printToolResult(call.name, message, true, false);
|
|
2802
|
-
results.push({ callId: call.id, content: message, isError: true });
|
|
2803
|
-
}
|
|
2804
|
-
} else {
|
|
2805
|
-
console.log(theme.dim(` [${i + 1}] `) + theme.dim("rejected"));
|
|
2806
|
-
results.push({ callId: call.id, content: `[User rejected] The user rejected this ${call.name} operation. Do not retry without asking.`, isError: true });
|
|
2807
|
-
}
|
|
2808
|
-
}
|
|
2809
|
-
return results;
|
|
2810
|
-
}
|
|
2811
|
-
/**
|
|
2812
|
-
* 批量确认:让用户选择 approve all / reject all / 指定编号。
|
|
2813
|
-
* 返回 'all' | 'none' | Set<number>(1-based 编号)
|
|
2814
|
-
*/
|
|
2815
|
-
batchConfirm(count) {
|
|
2816
|
-
const prompt = theme.warning(` [a]pprove all [r]eject all [1,${count > 1 ? count : "2"},..] approve specific: `);
|
|
2817
|
-
if (!this.rl) {
|
|
2818
|
-
process.stdout.write(theme.warning("No readline: auto-rejected.\n"));
|
|
2819
|
-
return Promise.resolve("none");
|
|
2820
|
-
}
|
|
2821
|
-
const rl = this.rl;
|
|
2822
|
-
const rlAny = rl;
|
|
2823
|
-
const savedOutput = rlAny.output;
|
|
2824
|
-
rlAny.output = process.stdout;
|
|
2825
|
-
rl.resume();
|
|
2826
|
-
process.stdout.write(prompt);
|
|
2827
|
-
this.confirming = true;
|
|
2828
|
-
return new Promise((resolve3) => {
|
|
2829
|
-
let completed = false;
|
|
2830
|
-
const cleanup = (result) => {
|
|
2831
|
-
if (completed) return;
|
|
2832
|
-
completed = true;
|
|
2833
|
-
rl.removeListener("line", onLine);
|
|
2834
|
-
this.cancelConfirmFn = null;
|
|
2835
|
-
rl.pause();
|
|
2836
|
-
rlAny.output = savedOutput;
|
|
2837
|
-
this.confirming = false;
|
|
2838
|
-
resolve3(result);
|
|
2839
|
-
};
|
|
2840
|
-
const onLine = (line) => {
|
|
2841
|
-
const trimmed = line.trim();
|
|
2842
|
-
if (trimmed.startsWith("/")) {
|
|
2843
|
-
this.pendingSlashCommand = trimmed;
|
|
2844
|
-
process.stdout.write(theme.dim(`
|
|
2845
|
-
(command "${trimmed}" queued, will execute after current operation)
|
|
2846
|
-
`));
|
|
2847
|
-
cleanup("none");
|
|
2848
|
-
return;
|
|
2849
|
-
}
|
|
2850
|
-
const input2 = trimmed.toLowerCase();
|
|
2851
|
-
if (input2 === "a" || input2 === "all" || input2 === "y") {
|
|
2852
|
-
cleanup("all");
|
|
2853
|
-
} else if (input2 === "r" || input2 === "reject" || input2 === "n" || input2 === "") {
|
|
2854
|
-
cleanup("none");
|
|
2855
|
-
} else {
|
|
2856
|
-
const nums = input2.split(/[,\s]+/).map(Number).filter((n) => !isNaN(n) && n >= 1 && n <= count);
|
|
2857
|
-
if (nums.length > 0) {
|
|
2858
|
-
cleanup(new Set(nums));
|
|
2859
|
-
} else {
|
|
2860
|
-
cleanup("none");
|
|
2861
|
-
}
|
|
2862
|
-
}
|
|
2863
|
-
};
|
|
2864
|
-
this.cancelConfirmFn = () => {
|
|
2865
|
-
process.stdout.write(theme.dim("\n(cancelled)\n"));
|
|
2866
|
-
cleanup("none");
|
|
2867
|
-
};
|
|
2868
|
-
try {
|
|
2869
|
-
rl.once("line", onLine);
|
|
2870
|
-
} catch {
|
|
2871
|
-
cleanup("none");
|
|
2872
|
-
}
|
|
2873
|
-
});
|
|
2874
|
-
}
|
|
2875
|
-
printToolCall(call) {
|
|
2876
|
-
const dangerLevel = getDangerLevel(call.name, call.arguments);
|
|
2877
|
-
console.log();
|
|
2878
|
-
const icon = dangerLevel === "write" ? theme.toolCall("\u270E Tool: ") : theme.heading(theme.accent("\u2699 Tool: "));
|
|
2879
|
-
const roundBadge = this.totalRounds > 0 ? theme.dim(` [${this.round}/${this.totalRounds}]`) : "";
|
|
2880
|
-
console.log(icon + chalk4.white(call.name) + roundBadge);
|
|
2881
|
-
for (const [key, val] of Object.entries(call.arguments)) {
|
|
2882
|
-
let valStr;
|
|
2883
|
-
if (Array.isArray(val)) {
|
|
2884
|
-
const json = JSON.stringify(val);
|
|
2885
|
-
valStr = json.length > 160 ? json.slice(0, 160) + "..." : json;
|
|
2886
|
-
} else if (typeof val === "string" && val.length > 120) {
|
|
2887
|
-
valStr = val.slice(0, 120) + "...";
|
|
2888
|
-
} else {
|
|
2889
|
-
valStr = String(val);
|
|
2890
|
-
}
|
|
2891
|
-
console.log(theme.dim(` ${key}: `) + chalk4.white(valStr));
|
|
2892
|
-
}
|
|
2893
|
-
}
|
|
2894
|
-
/**
|
|
2895
|
-
* 对 write_file / edit_file 在执行前展示 diff 预览。
|
|
2896
|
-
* - write_file:比较旧文件内容与新内容
|
|
2897
|
-
* - edit_file (replace):比较旧字符串与新字符串
|
|
2898
|
-
* - edit_file (insert/delete):显示操作摘要,不做 diff(变化明确)
|
|
2899
|
-
*/
|
|
2900
|
-
printDiffPreview(call) {
|
|
2901
|
-
if (call.name === "write_file") {
|
|
2902
|
-
const filePath = String(call.arguments["path"] ?? "");
|
|
2903
|
-
const newContent = String(call.arguments["content"] ?? "");
|
|
2904
|
-
if (!filePath) return;
|
|
2905
|
-
if (existsSync3(filePath)) {
|
|
2906
|
-
let oldContent;
|
|
2907
|
-
try {
|
|
2908
|
-
oldContent = readFileSync2(filePath, "utf-8");
|
|
2909
|
-
} catch {
|
|
2910
|
-
return;
|
|
2911
|
-
}
|
|
2912
|
-
if (oldContent === newContent) {
|
|
2913
|
-
console.log(theme.dim(" (file content unchanged)"));
|
|
2914
|
-
return;
|
|
2915
|
-
}
|
|
2916
|
-
const diff = renderDiff(oldContent, newContent, { filePath, contextLines: 3 });
|
|
2917
|
-
console.log(theme.dim(" \u2500\u2500 diff preview \u2500\u2500"));
|
|
2918
|
-
console.log(diff);
|
|
2919
|
-
console.log();
|
|
2920
|
-
} else {
|
|
2921
|
-
const lines = newContent.split("\n");
|
|
2922
|
-
const preview = lines.slice(0, 20).map((l) => theme.success(`+ ${l}`)).join("\n");
|
|
2923
|
-
const more = lines.length > 20 ? theme.dim(`
|
|
2924
|
-
... (+${lines.length - 20} more lines)`) : "";
|
|
2925
|
-
console.log(theme.dim(" \u2500\u2500 new file preview \u2500\u2500"));
|
|
2926
|
-
console.log(preview + more);
|
|
2927
|
-
console.log();
|
|
2928
|
-
}
|
|
2929
|
-
} else if (call.name === "edit_file") {
|
|
2930
|
-
const filePath = String(call.arguments["path"] ?? "");
|
|
2931
|
-
if (!filePath || !existsSync3(filePath)) return;
|
|
2932
|
-
const oldStr = call.arguments["old_str"];
|
|
2933
|
-
const newStr = call.arguments["new_str"];
|
|
2934
|
-
if (oldStr !== void 0) {
|
|
2935
|
-
const diff = renderDiff(
|
|
2936
|
-
String(oldStr),
|
|
2937
|
-
String(newStr ?? ""),
|
|
2938
|
-
{ filePath, contextLines: 2 }
|
|
2939
|
-
);
|
|
2940
|
-
console.log(theme.dim(" \u2500\u2500 diff preview \u2500\u2500"));
|
|
2941
|
-
console.log(diff);
|
|
2942
|
-
console.log();
|
|
2943
|
-
} else if (call.arguments["insert_after_line"] !== void 0) {
|
|
2944
|
-
const line = Number(call.arguments["insert_after_line"]);
|
|
2945
|
-
const insertContent = String(call.arguments["insert_content"] ?? "");
|
|
2946
|
-
const insertLines = insertContent.split("\n");
|
|
2947
|
-
const preview = insertLines.slice(0, 5).map((l) => theme.success(`+ ${l}`)).join("\n");
|
|
2948
|
-
const more = insertLines.length > 5 ? theme.dim(`
|
|
2949
|
-
... (+${insertLines.length - 5} more lines)`) : "";
|
|
2950
|
-
console.log(theme.dim(` \u2500\u2500 insert after line ${line} \u2500\u2500`));
|
|
2951
|
-
console.log(preview + more);
|
|
2952
|
-
console.log();
|
|
2953
|
-
} else if (call.arguments["delete_from_line"] !== void 0) {
|
|
2954
|
-
const from = Number(call.arguments["delete_from_line"]);
|
|
2955
|
-
const to = Number(call.arguments["delete_to_line"] ?? from);
|
|
2956
|
-
let fileContent;
|
|
2957
|
-
try {
|
|
2958
|
-
fileContent = readFileSync2(filePath, "utf-8");
|
|
2959
|
-
} catch {
|
|
2960
|
-
return;
|
|
2961
|
-
}
|
|
2962
|
-
const fileLines = fileContent.split("\n");
|
|
2963
|
-
const deleted = fileLines.slice(from - 1, to);
|
|
2964
|
-
const preview = deleted.slice(0, 5).map((l) => theme.error(`- ${l}`)).join("\n");
|
|
2965
|
-
const more = deleted.length > 5 ? theme.dim(`
|
|
2966
|
-
... (-${deleted.length - 5} more lines)`) : "";
|
|
2967
|
-
console.log(theme.dim(` \u2500\u2500 delete lines ${from}\u2013${to} \u2500\u2500`));
|
|
2968
|
-
console.log(preview + more);
|
|
2969
|
-
console.log();
|
|
2970
|
-
}
|
|
2971
|
-
}
|
|
2972
|
-
}
|
|
2973
|
-
printToolResult(name, content, isError, wasTruncated) {
|
|
2974
|
-
if (isError) {
|
|
2975
|
-
console.log(theme.error(`\u26A0 ${name} error: `) + theme.dim(content.slice(0, 300)));
|
|
2976
|
-
} else {
|
|
2977
|
-
const lines = content.split("\n");
|
|
2978
|
-
const maxLines = name === "run_interactive" ? 40 : 8;
|
|
2979
|
-
const preview = lines.slice(0, maxLines).join("\n");
|
|
2980
|
-
const moreLines = lines.length > maxLines ? theme.dim(`
|
|
2981
|
-
... (${lines.length - maxLines} more lines)`) : "";
|
|
2982
|
-
const truncatedNote = wasTruncated ? theme.warning(`
|
|
2983
|
-
\u26A1 Output truncated to ${getActiveMaxChars()} chars before sending to AI`) : "";
|
|
2984
|
-
console.log(theme.toolResult("\u2713 Result: ") + theme.dim(preview) + moreLines + truncatedNote);
|
|
2985
|
-
}
|
|
2986
|
-
console.log();
|
|
2987
|
-
}
|
|
2988
|
-
confirm(call, level) {
|
|
2989
|
-
const color = level === "destructive" ? theme.error : theme.warning;
|
|
2990
|
-
const label = level === "destructive" ? "\u26A0 DESTRUCTIVE" : "\u270E Write";
|
|
2991
|
-
console.log();
|
|
2992
|
-
console.log(color(`${label} operation: `) + theme.heading(call.name));
|
|
2993
|
-
for (const [key, val] of Object.entries(call.arguments)) {
|
|
2994
|
-
const valStr = typeof val === "string" && val.length > 200 ? val.slice(0, 200) + "..." : String(val);
|
|
2995
|
-
console.log(theme.dim(` ${key}: `) + valStr);
|
|
2996
|
-
}
|
|
2997
|
-
if (!this.rl) {
|
|
2998
|
-
process.stdout.write(theme.warning("No readline: auto-rejected.\n"));
|
|
2999
|
-
return Promise.resolve(false);
|
|
3000
|
-
}
|
|
3001
|
-
const rl = this.rl;
|
|
3002
|
-
const rlAny = rl;
|
|
3003
|
-
const savedOutput = rlAny.output;
|
|
3004
|
-
rlAny.output = process.stdout;
|
|
3005
|
-
rl.resume();
|
|
3006
|
-
process.stdout.write(color("Proceed? [y/N] (type y + Enter to confirm) "));
|
|
3007
|
-
this.confirming = true;
|
|
3008
|
-
return new Promise((resolve3) => {
|
|
3009
|
-
let completed = false;
|
|
3010
|
-
const cleanup = (answer) => {
|
|
3011
|
-
if (completed) return;
|
|
3012
|
-
completed = true;
|
|
3013
|
-
rl.removeListener("line", onLine);
|
|
3014
|
-
this.cancelConfirmFn = null;
|
|
3015
|
-
rl.pause();
|
|
3016
|
-
rlAny.output = savedOutput;
|
|
3017
|
-
this.confirming = false;
|
|
3018
|
-
resolve3(answer === "y");
|
|
3019
|
-
};
|
|
3020
|
-
const onLine = (line) => {
|
|
3021
|
-
const trimmed = line.trim();
|
|
3022
|
-
if (trimmed.startsWith("/")) {
|
|
3023
|
-
this.pendingSlashCommand = trimmed;
|
|
3024
|
-
process.stdout.write(theme.dim(`
|
|
3025
|
-
(command "${trimmed}" queued, will execute after current operation)
|
|
3026
|
-
`));
|
|
3027
|
-
cleanup("n");
|
|
3028
|
-
return;
|
|
3029
|
-
}
|
|
3030
|
-
cleanup(trimmed.toLowerCase());
|
|
3031
|
-
};
|
|
3032
|
-
this.cancelConfirmFn = () => {
|
|
3033
|
-
process.stdout.write(theme.dim("\n(cancelled)\n"));
|
|
3034
|
-
cleanup("n");
|
|
3035
|
-
};
|
|
3036
|
-
try {
|
|
3037
|
-
rl.once("line", onLine);
|
|
3038
|
-
} catch {
|
|
3039
|
-
cleanup("n");
|
|
3040
|
-
}
|
|
3041
|
-
});
|
|
3042
|
-
}
|
|
3043
|
-
};
|
|
3044
|
-
|
|
3045
2718
|
// src/tools/builtin/stream-to-file.ts
|
|
3046
2719
|
var streamToFileContext = {
|
|
3047
2720
|
provider: null,
|
|
@@ -3335,13 +3008,13 @@ Managing ${displayName} API Key`);
|
|
|
3335
3008
|
};
|
|
3336
3009
|
|
|
3337
3010
|
// src/repl/custom-commands.ts
|
|
3338
|
-
import { existsSync as
|
|
3011
|
+
import { existsSync as existsSync3, readFileSync as readFileSync2, readdirSync as readdirSync2, mkdirSync as mkdirSync3 } from "fs";
|
|
3339
3012
|
import { join as join3, extname } from "path";
|
|
3340
3013
|
import { execSync as execSync3 } from "child_process";
|
|
3341
3014
|
function parseCommandFile(filePath) {
|
|
3342
3015
|
let content;
|
|
3343
3016
|
try {
|
|
3344
|
-
content =
|
|
3017
|
+
content = readFileSync2(filePath, "utf-8");
|
|
3345
3018
|
} catch {
|
|
3346
3019
|
return null;
|
|
3347
3020
|
}
|
|
@@ -3365,7 +3038,7 @@ function expandTemplate(template, args) {
|
|
|
3365
3038
|
result = result.replace(/\{\{input\}\}/g, args.join(" "));
|
|
3366
3039
|
result = result.replace(/\{\{file:([^}]+)\}\}/g, (_m, p) => {
|
|
3367
3040
|
try {
|
|
3368
|
-
return
|
|
3041
|
+
return readFileSync2(p.trim(), "utf-8");
|
|
3369
3042
|
} catch {
|
|
3370
3043
|
return `[Error: cannot read ${p.trim()}]`;
|
|
3371
3044
|
}
|
|
@@ -3390,7 +3063,7 @@ var CustomCommandManager = class {
|
|
|
3390
3063
|
commands = /* @__PURE__ */ new Map();
|
|
3391
3064
|
loadCommands() {
|
|
3392
3065
|
this.commands.clear();
|
|
3393
|
-
if (!
|
|
3066
|
+
if (!existsSync3(this.commandsDir)) {
|
|
3394
3067
|
mkdirSync3(this.commandsDir, { recursive: true });
|
|
3395
3068
|
return 0;
|
|
3396
3069
|
}
|
|
@@ -3473,7 +3146,7 @@ function parseAtReferences(input2, cwd) {
|
|
|
3473
3146
|
const absPath = resolve2(cwd, rawPath);
|
|
3474
3147
|
const ext = extname2(rawPath).toLowerCase();
|
|
3475
3148
|
const mime = IMAGE_MIME[ext];
|
|
3476
|
-
if (!
|
|
3149
|
+
if (!existsSync4(absPath)) {
|
|
3477
3150
|
refs.push({ path: rawPath, type: "notfound" });
|
|
3478
3151
|
continue;
|
|
3479
3152
|
}
|
|
@@ -3483,7 +3156,7 @@ function parseAtReferences(input2, cwd) {
|
|
|
3483
3156
|
refs.push({ path: rawPath, type: "toolarge" });
|
|
3484
3157
|
continue;
|
|
3485
3158
|
}
|
|
3486
|
-
const data =
|
|
3159
|
+
const data = readFileSync3(absPath).toString("base64");
|
|
3487
3160
|
imageParts.push({
|
|
3488
3161
|
type: "image_url",
|
|
3489
3162
|
image_url: { url: `data:${mime};base64,${data}` }
|
|
@@ -3491,7 +3164,7 @@ function parseAtReferences(input2, cwd) {
|
|
|
3491
3164
|
refs.push({ path: rawPath, type: "image" });
|
|
3492
3165
|
textBody = textBody.replace(match[0], "").trim();
|
|
3493
3166
|
} else {
|
|
3494
|
-
const content =
|
|
3167
|
+
const content = readFileSync3(absPath, "utf-8");
|
|
3495
3168
|
const inlined = `
|
|
3496
3169
|
|
|
3497
3170
|
[File: ${rawPath}]
|
|
@@ -3741,7 +3414,7 @@ ${treeLines.join("\n")}`
|
|
|
3741
3414
|
if (!TEXT_EXTS.has(ext) && !isSpecial) continue;
|
|
3742
3415
|
if (st.size > MAX_FILE_CHARS * 3) continue;
|
|
3743
3416
|
try {
|
|
3744
|
-
let content =
|
|
3417
|
+
let content = readFileSync3(fullPath, "utf-8");
|
|
3745
3418
|
if (content.length > MAX_FILE_CHARS) {
|
|
3746
3419
|
content = content.slice(0, MAX_FILE_CHARS) + `
|
|
3747
3420
|
... (truncated, ${content.length} chars total)`;
|
|
@@ -3771,7 +3444,7 @@ ${content}
|
|
|
3771
3444
|
*/
|
|
3772
3445
|
addExtraContextDir(dirPath) {
|
|
3773
3446
|
const absPath = resolve2(dirPath);
|
|
3774
|
-
if (!
|
|
3447
|
+
if (!existsSync4(absPath)) {
|
|
3775
3448
|
return { success: false, charCount: 0, added: false, error: `Directory not found: ${dirPath}` };
|
|
3776
3449
|
}
|
|
3777
3450
|
let isDir;
|
|
@@ -3806,8 +3479,8 @@ ${content}
|
|
|
3806
3479
|
findContextFile(dir, candidates = CONTEXT_FILE_CANDIDATES) {
|
|
3807
3480
|
for (const candidate of candidates) {
|
|
3808
3481
|
const fullPath = join4(dir, candidate);
|
|
3809
|
-
if (
|
|
3810
|
-
const content =
|
|
3482
|
+
if (existsSync4(fullPath)) {
|
|
3483
|
+
const content = readFileSync3(fullPath, "utf-8").trim();
|
|
3811
3484
|
if (content) return { filePath: fullPath, content };
|
|
3812
3485
|
}
|
|
3813
3486
|
}
|
|
@@ -3836,9 +3509,9 @@ ${content}
|
|
|
3836
3509
|
const gitRoot = getGitRoot(cwd);
|
|
3837
3510
|
const projectRoot = gitRoot ?? cwd;
|
|
3838
3511
|
const mcpPath = join4(projectRoot, MCP_PROJECT_CONFIG_NAME);
|
|
3839
|
-
if (!
|
|
3512
|
+
if (!existsSync4(mcpPath)) return null;
|
|
3840
3513
|
try {
|
|
3841
|
-
const raw = JSON.parse(
|
|
3514
|
+
const raw = JSON.parse(readFileSync3(mcpPath, "utf-8"));
|
|
3842
3515
|
const servers = raw?.mcpServers;
|
|
3843
3516
|
if (!servers || typeof servers !== "object") {
|
|
3844
3517
|
process.stderr.write(
|
|
@@ -3884,8 +3557,8 @@ ${content}
|
|
|
3884
3557
|
);
|
|
3885
3558
|
return { layers: [], mergedContent: "" };
|
|
3886
3559
|
}
|
|
3887
|
-
if (
|
|
3888
|
-
const content =
|
|
3560
|
+
if (existsSync4(fullPath)) {
|
|
3561
|
+
const content = readFileSync3(fullPath, "utf-8").trim();
|
|
3889
3562
|
if (content) {
|
|
3890
3563
|
const layer = {
|
|
3891
3564
|
level: "project",
|
|
@@ -3943,8 +3616,8 @@ ${content}
|
|
|
3943
3616
|
*/
|
|
3944
3617
|
loadMemoryContent() {
|
|
3945
3618
|
const memoryPath = join4(this.config.getConfigDir(), MEMORY_FILE_NAME);
|
|
3946
|
-
if (!
|
|
3947
|
-
let content =
|
|
3619
|
+
if (!existsSync4(memoryPath)) return null;
|
|
3620
|
+
let content = readFileSync3(memoryPath, "utf-8").trim();
|
|
3948
3621
|
if (!content) return null;
|
|
3949
3622
|
if (content.length > MEMORY_MAX_CHARS) {
|
|
3950
3623
|
content = content.slice(-MEMORY_MAX_CHARS);
|
|
@@ -4179,7 +3852,7 @@ ${response.content.trim()}
|
|
|
4179
3852
|
const planTag = this.planMode ? theme.warning("[PLAN]") : "";
|
|
4180
3853
|
const modelParams = this.getModelParams();
|
|
4181
3854
|
const thinkTag = modelParams.thinking ? theme.accent("[THINK]") : "";
|
|
4182
|
-
const promptStr = theme.success(`[${this.currentProvider}]`) + skillTag + planTag + thinkTag +
|
|
3855
|
+
const promptStr = theme.success(`[${this.currentProvider}]`) + skillTag + planTag + thinkTag + chalk4.white(" > ");
|
|
4183
3856
|
this.rl.setPrompt(promptStr);
|
|
4184
3857
|
}
|
|
4185
3858
|
showPrompt() {
|
|
@@ -4698,7 +4371,7 @@ Session '${this.resumeSessionId}' not found.
|
|
|
4698
4371
|
const dir = normalized.includes("/") ? dirname3(normalized) : ".";
|
|
4699
4372
|
const prefix = normalized.includes("/") ? basename2(normalized) : normalized;
|
|
4700
4373
|
const absDir = resolve2(process.cwd(), dir);
|
|
4701
|
-
if (!
|
|
4374
|
+
if (!existsSync4(absDir)) return [];
|
|
4702
4375
|
const entries = readdirSync3(absDir);
|
|
4703
4376
|
const results = [];
|
|
4704
4377
|
for (const entry of entries) {
|
|
@@ -5244,6 +4917,7 @@ You have a maximum of ${MAX_TOOL_ROUNDS} tool call rounds for this task. Plan ef
|
|
|
5244
4917
|
spawnAgentContext.systemPrompt = systemPrompt;
|
|
5245
4918
|
spawnAgentContext.modelParams = modelParams;
|
|
5246
4919
|
spawnAgentContext.configManager = this.config;
|
|
4920
|
+
ToolExecutor.currentMessageIndex = session.messages.length;
|
|
5247
4921
|
const toolResults = await this.toolExecutor.executeAll(result.toolCalls);
|
|
5248
4922
|
const thisRoundTools = result.toolCalls.map((tc) => tc.name);
|
|
5249
4923
|
roundToolHistory.push({ round: round + 1, tools: thisRoundTools });
|
|
@@ -5691,7 +5365,7 @@ program.command("web").description("Start Web UI server with browser-based chat
|
|
|
5691
5365
|
console.error("Error: Invalid port number. Must be between 1 and 65535.");
|
|
5692
5366
|
process.exit(1);
|
|
5693
5367
|
}
|
|
5694
|
-
const { startWebServer } = await import("./server-
|
|
5368
|
+
const { startWebServer } = await import("./server-SZZXQZWY.js");
|
|
5695
5369
|
await startWebServer({ port, host: options.host });
|
|
5696
5370
|
});
|
|
5697
5371
|
program.command("user [action] [username]").description("Manage Web UI users (list | create <name> | delete <name> | reset-password <name> | migrate <name>)").action(async (action, username) => {
|
|
@@ -5924,7 +5598,7 @@ program.command("hub [topic]").description("Start multi-agent hub (discuss / bra
|
|
|
5924
5598
|
}),
|
|
5925
5599
|
config.get("customProviders")
|
|
5926
5600
|
);
|
|
5927
|
-
const { startHub } = await import("./hub-
|
|
5601
|
+
const { startHub } = await import("./hub-JOYPSPR2.js");
|
|
5928
5602
|
await startHub(
|
|
5929
5603
|
{
|
|
5930
5604
|
topic: topic ?? "",
|