jinzd-ai-cli 0.4.23 → 0.4.25
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/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 +170 -500
- 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-V3IZSAMO.js} +126 -7
- package/dist/{task-orchestrator-C472QXTJ.js → task-orchestrator-4N5UUA6L.js} +3 -2
- package/dist/web/client/app.js +16 -3
- 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";
|
|
@@ -836,6 +834,39 @@ Please structure your review as follows:
|
|
|
836
834
|
|
|
837
835
|
Severity levels: \u{1F534} Critical / \u{1F7E1} Warning / \u{1F535} Info`;
|
|
838
836
|
}
|
|
837
|
+
function buildSecurityReviewPrompt(diff, gitContextStr) {
|
|
838
|
+
return `# Security Vulnerability Review
|
|
839
|
+
|
|
840
|
+
Analyze the following code changes **exclusively for security vulnerabilities**.
|
|
841
|
+
|
|
842
|
+
## Categories to check:
|
|
843
|
+
1. **Injection** \u2014 SQL, command, path traversal, XSS, template injection
|
|
844
|
+
2. **Authentication & Authorization** \u2014 hardcoded credentials, missing auth checks, privilege escalation
|
|
845
|
+
3. **Secrets & Sensitive Data** \u2014 API keys, tokens, passwords in code, logging sensitive data
|
|
846
|
+
4. **Input Validation** \u2014 missing validation, unsafe deserialization, buffer issues
|
|
847
|
+
5. **Cryptography** \u2014 weak algorithms, improper random, hardcoded IVs/salts
|
|
848
|
+
6. **Dependencies** \u2014 known vulnerable packages, unsafe dynamic imports
|
|
849
|
+
7. **File System** \u2014 path traversal, unsafe file permissions, symlink attacks
|
|
850
|
+
8. **Network** \u2014 SSRF, insecure protocols, missing TLS validation
|
|
851
|
+
|
|
852
|
+
## Git Status
|
|
853
|
+
${gitContextStr}
|
|
854
|
+
|
|
855
|
+
## Code Changes (diff)
|
|
856
|
+
\`\`\`diff
|
|
857
|
+
${diff}
|
|
858
|
+
\`\`\`
|
|
859
|
+
|
|
860
|
+
## Output Format
|
|
861
|
+
For each finding:
|
|
862
|
+
- **Severity**: \u{1F534} CRITICAL / \u{1F7E0} HIGH / \u{1F7E1} MEDIUM / \u{1F535} LOW / \u2139\uFE0F INFO
|
|
863
|
+
- **Category**: (from list above)
|
|
864
|
+
- **File & location**: file:line
|
|
865
|
+
- **Description**: what the vulnerability is and how it could be exploited
|
|
866
|
+
- **Recommended fix**: specific code change to resolve
|
|
867
|
+
|
|
868
|
+
If no security issues found, state "\u2705 No security vulnerabilities detected" with a brief explanation of what was checked.`;
|
|
869
|
+
}
|
|
839
870
|
var CommandRegistry = class {
|
|
840
871
|
commands = /* @__PURE__ */ new Map();
|
|
841
872
|
register(command) {
|
|
@@ -884,6 +915,8 @@ function createDefaultCommands() {
|
|
|
884
915
|
" /skill [name|off|list] - Manage agent skills (reusable prompt packs)",
|
|
885
916
|
" /checkpoint [save|restore|delete] <name> - Session checkpoints",
|
|
886
917
|
" /review [--staged] [--detailed] - AI code review from git diff",
|
|
918
|
+
" /security-review [--staged] - Security vulnerability scan on git diff",
|
|
919
|
+
" /rewind [list|<n>] - Rewind to message N (restores files too)",
|
|
887
920
|
" /commands [reload] - List/reload custom commands (~/.aicli/commands/)",
|
|
888
921
|
" /test [command|filter] - Run project tests and show structured report",
|
|
889
922
|
" /scaffold <description> - Generate project scaffolding with AI",
|
|
@@ -1908,6 +1941,108 @@ ${hint}` : "")
|
|
|
1908
1941
|
}
|
|
1909
1942
|
}
|
|
1910
1943
|
},
|
|
1944
|
+
// ── /security-review ──────────────────────────────────────────
|
|
1945
|
+
{
|
|
1946
|
+
name: "security-review",
|
|
1947
|
+
description: "Security vulnerability scan on git diff",
|
|
1948
|
+
usage: "/security-review [--staged]",
|
|
1949
|
+
async execute(args, ctx) {
|
|
1950
|
+
const gitCtx = getGitContext();
|
|
1951
|
+
if (!gitCtx) {
|
|
1952
|
+
ctx.renderer.renderError("Not a git repository.");
|
|
1953
|
+
return;
|
|
1954
|
+
}
|
|
1955
|
+
const staged = args.includes("--staged");
|
|
1956
|
+
let diff;
|
|
1957
|
+
try {
|
|
1958
|
+
const cmd = staged ? "git diff --staged" : "git diff";
|
|
1959
|
+
diff = execSync2(cmd, { encoding: "utf-8", timeout: 1e4 }).trim();
|
|
1960
|
+
} catch {
|
|
1961
|
+
ctx.renderer.renderError("Failed to run git diff.");
|
|
1962
|
+
return;
|
|
1963
|
+
}
|
|
1964
|
+
if (!diff) {
|
|
1965
|
+
console.log(theme.dim(" No changes to review." + (staged ? "" : " Try --staged for staged changes.")));
|
|
1966
|
+
return;
|
|
1967
|
+
}
|
|
1968
|
+
const MAX_DIFF = 8e3;
|
|
1969
|
+
let truncated = false;
|
|
1970
|
+
if (diff.length > MAX_DIFF) {
|
|
1971
|
+
const head = diff.slice(0, Math.floor(MAX_DIFF * 0.7));
|
|
1972
|
+
const tail = diff.slice(diff.length - Math.floor(MAX_DIFF * 0.2));
|
|
1973
|
+
diff = head + "\n\n... [diff truncated, " + diff.length + " chars total] ...\n\n" + tail;
|
|
1974
|
+
truncated = true;
|
|
1975
|
+
}
|
|
1976
|
+
const prompt = buildSecurityReviewPrompt(diff, formatGitContextForPrompt(gitCtx));
|
|
1977
|
+
console.log(theme.dim(" \u{1F512} Scanning for security vulnerabilities..."));
|
|
1978
|
+
try {
|
|
1979
|
+
const review = await ctx.chatOnce(prompt, { temperature: 0.2, maxTokens: 8192 });
|
|
1980
|
+
console.log();
|
|
1981
|
+
console.log(review);
|
|
1982
|
+
console.log();
|
|
1983
|
+
if (truncated) {
|
|
1984
|
+
console.log(theme.warning(" \u26A0 Diff was truncated. Consider reviewing smaller changesets."));
|
|
1985
|
+
}
|
|
1986
|
+
} catch (err) {
|
|
1987
|
+
ctx.renderer.renderError(`Security review failed: ${err.message}`);
|
|
1988
|
+
}
|
|
1989
|
+
}
|
|
1990
|
+
},
|
|
1991
|
+
// ── /rewind ───────────────────────────────────────────────────
|
|
1992
|
+
{
|
|
1993
|
+
name: "rewind",
|
|
1994
|
+
description: "Rewind conversation to a previous message and restore file states",
|
|
1995
|
+
usage: "/rewind [list | <n>]",
|
|
1996
|
+
execute(args, ctx) {
|
|
1997
|
+
const session = ctx.sessions.current;
|
|
1998
|
+
if (!session || session.messages.length === 0) {
|
|
1999
|
+
console.log(theme.dim(" No messages to rewind."));
|
|
2000
|
+
return;
|
|
2001
|
+
}
|
|
2002
|
+
const sub = args[0];
|
|
2003
|
+
if (sub === "list" || !sub) {
|
|
2004
|
+
const msgs = session.messages;
|
|
2005
|
+
console.log(theme.primary(`
|
|
2006
|
+
Conversation messages (${msgs.length} total):
|
|
2007
|
+
`));
|
|
2008
|
+
for (let i = 0; i < msgs.length; i++) {
|
|
2009
|
+
const m = msgs[i];
|
|
2010
|
+
const role = m.role.padEnd(10);
|
|
2011
|
+
const text = getContentText(m.content).replace(/\n/g, " ").slice(0, 70);
|
|
2012
|
+
const hasCheckpoint = fileCheckpoints.getMessageIndices().includes(i) ? " \u{1F4CC}" : "";
|
|
2013
|
+
console.log(theme.dim(` [${String(i + 1).padStart(3)}]`) + ` ${theme.info(role)} ${text}${hasCheckpoint}`);
|
|
2014
|
+
}
|
|
2015
|
+
console.log();
|
|
2016
|
+
console.log(theme.dim(" Usage: /rewind <n> \u2014 rewind to message N (1-based)"));
|
|
2017
|
+
console.log(theme.dim(" \u{1F4CC} = file checkpoint exists at this point"));
|
|
2018
|
+
console.log();
|
|
2019
|
+
return;
|
|
2020
|
+
}
|
|
2021
|
+
const n = parseInt(sub, 10);
|
|
2022
|
+
if (isNaN(n) || n < 1 || n > session.messages.length) {
|
|
2023
|
+
ctx.renderer.renderError(`Invalid message number: ${sub}. Range: 1-${session.messages.length}`);
|
|
2024
|
+
return;
|
|
2025
|
+
}
|
|
2026
|
+
const targetIndex = n;
|
|
2027
|
+
const messagesRemoved = session.messages.length - targetIndex;
|
|
2028
|
+
const { restored, deleted, files } = fileCheckpoints.restoreToMessageIndex(targetIndex);
|
|
2029
|
+
session.messages = session.messages.slice(0, targetIndex);
|
|
2030
|
+
session.checkpoints = session.checkpoints.filter((c) => c.messageIndex <= targetIndex);
|
|
2031
|
+
session.updated = /* @__PURE__ */ new Date();
|
|
2032
|
+
console.log(theme.success(`
|
|
2033
|
+
\u2713 Rewound to message ${n}`));
|
|
2034
|
+
console.log(theme.dim(` Messages removed: ${messagesRemoved}`));
|
|
2035
|
+
if (restored > 0 || deleted > 0) {
|
|
2036
|
+
console.log(theme.dim(` Files restored: ${restored}, files deleted: ${deleted}`));
|
|
2037
|
+
for (const f of files) {
|
|
2038
|
+
console.log(theme.dim(` ${f}`));
|
|
2039
|
+
}
|
|
2040
|
+
} else {
|
|
2041
|
+
console.log(theme.dim(" No file changes to revert."));
|
|
2042
|
+
}
|
|
2043
|
+
console.log();
|
|
2044
|
+
}
|
|
2045
|
+
},
|
|
1911
2046
|
// ── /commands ─────────────────────────────────────────────────
|
|
1912
2047
|
{
|
|
1913
2048
|
name: "commands",
|
|
@@ -1944,7 +2079,7 @@ ${hint}` : "")
|
|
|
1944
2079
|
usage: "/test [command|filter]",
|
|
1945
2080
|
async execute(args, ctx) {
|
|
1946
2081
|
try {
|
|
1947
|
-
const { executeTests } = await import("./run-tests-
|
|
2082
|
+
const { executeTests } = await import("./run-tests-L3JNRB6X.js");
|
|
1948
2083
|
const argStr = args.join(" ").trim();
|
|
1949
2084
|
let testArgs = {};
|
|
1950
2085
|
if (argStr) {
|
|
@@ -2576,472 +2711,6 @@ function selectFromList(prompt, items, initialIndex = 0) {
|
|
|
2576
2711
|
});
|
|
2577
2712
|
}
|
|
2578
2713
|
|
|
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
2714
|
// src/tools/builtin/stream-to-file.ts
|
|
3046
2715
|
var streamToFileContext = {
|
|
3047
2716
|
provider: null,
|
|
@@ -3335,13 +3004,13 @@ Managing ${displayName} API Key`);
|
|
|
3335
3004
|
};
|
|
3336
3005
|
|
|
3337
3006
|
// src/repl/custom-commands.ts
|
|
3338
|
-
import { existsSync as
|
|
3007
|
+
import { existsSync as existsSync3, readFileSync as readFileSync2, readdirSync as readdirSync2, mkdirSync as mkdirSync3 } from "fs";
|
|
3339
3008
|
import { join as join3, extname } from "path";
|
|
3340
3009
|
import { execSync as execSync3 } from "child_process";
|
|
3341
3010
|
function parseCommandFile(filePath) {
|
|
3342
3011
|
let content;
|
|
3343
3012
|
try {
|
|
3344
|
-
content =
|
|
3013
|
+
content = readFileSync2(filePath, "utf-8");
|
|
3345
3014
|
} catch {
|
|
3346
3015
|
return null;
|
|
3347
3016
|
}
|
|
@@ -3365,7 +3034,7 @@ function expandTemplate(template, args) {
|
|
|
3365
3034
|
result = result.replace(/\{\{input\}\}/g, args.join(" "));
|
|
3366
3035
|
result = result.replace(/\{\{file:([^}]+)\}\}/g, (_m, p) => {
|
|
3367
3036
|
try {
|
|
3368
|
-
return
|
|
3037
|
+
return readFileSync2(p.trim(), "utf-8");
|
|
3369
3038
|
} catch {
|
|
3370
3039
|
return `[Error: cannot read ${p.trim()}]`;
|
|
3371
3040
|
}
|
|
@@ -3390,7 +3059,7 @@ var CustomCommandManager = class {
|
|
|
3390
3059
|
commands = /* @__PURE__ */ new Map();
|
|
3391
3060
|
loadCommands() {
|
|
3392
3061
|
this.commands.clear();
|
|
3393
|
-
if (!
|
|
3062
|
+
if (!existsSync3(this.commandsDir)) {
|
|
3394
3063
|
mkdirSync3(this.commandsDir, { recursive: true });
|
|
3395
3064
|
return 0;
|
|
3396
3065
|
}
|
|
@@ -3473,7 +3142,7 @@ function parseAtReferences(input2, cwd) {
|
|
|
3473
3142
|
const absPath = resolve2(cwd, rawPath);
|
|
3474
3143
|
const ext = extname2(rawPath).toLowerCase();
|
|
3475
3144
|
const mime = IMAGE_MIME[ext];
|
|
3476
|
-
if (!
|
|
3145
|
+
if (!existsSync4(absPath)) {
|
|
3477
3146
|
refs.push({ path: rawPath, type: "notfound" });
|
|
3478
3147
|
continue;
|
|
3479
3148
|
}
|
|
@@ -3483,7 +3152,7 @@ function parseAtReferences(input2, cwd) {
|
|
|
3483
3152
|
refs.push({ path: rawPath, type: "toolarge" });
|
|
3484
3153
|
continue;
|
|
3485
3154
|
}
|
|
3486
|
-
const data =
|
|
3155
|
+
const data = readFileSync3(absPath).toString("base64");
|
|
3487
3156
|
imageParts.push({
|
|
3488
3157
|
type: "image_url",
|
|
3489
3158
|
image_url: { url: `data:${mime};base64,${data}` }
|
|
@@ -3491,7 +3160,7 @@ function parseAtReferences(input2, cwd) {
|
|
|
3491
3160
|
refs.push({ path: rawPath, type: "image" });
|
|
3492
3161
|
textBody = textBody.replace(match[0], "").trim();
|
|
3493
3162
|
} else {
|
|
3494
|
-
const content =
|
|
3163
|
+
const content = readFileSync3(absPath, "utf-8");
|
|
3495
3164
|
const inlined = `
|
|
3496
3165
|
|
|
3497
3166
|
[File: ${rawPath}]
|
|
@@ -3741,7 +3410,7 @@ ${treeLines.join("\n")}`
|
|
|
3741
3410
|
if (!TEXT_EXTS.has(ext) && !isSpecial) continue;
|
|
3742
3411
|
if (st.size > MAX_FILE_CHARS * 3) continue;
|
|
3743
3412
|
try {
|
|
3744
|
-
let content =
|
|
3413
|
+
let content = readFileSync3(fullPath, "utf-8");
|
|
3745
3414
|
if (content.length > MAX_FILE_CHARS) {
|
|
3746
3415
|
content = content.slice(0, MAX_FILE_CHARS) + `
|
|
3747
3416
|
... (truncated, ${content.length} chars total)`;
|
|
@@ -3771,7 +3440,7 @@ ${content}
|
|
|
3771
3440
|
*/
|
|
3772
3441
|
addExtraContextDir(dirPath) {
|
|
3773
3442
|
const absPath = resolve2(dirPath);
|
|
3774
|
-
if (!
|
|
3443
|
+
if (!existsSync4(absPath)) {
|
|
3775
3444
|
return { success: false, charCount: 0, added: false, error: `Directory not found: ${dirPath}` };
|
|
3776
3445
|
}
|
|
3777
3446
|
let isDir;
|
|
@@ -3806,8 +3475,8 @@ ${content}
|
|
|
3806
3475
|
findContextFile(dir, candidates = CONTEXT_FILE_CANDIDATES) {
|
|
3807
3476
|
for (const candidate of candidates) {
|
|
3808
3477
|
const fullPath = join4(dir, candidate);
|
|
3809
|
-
if (
|
|
3810
|
-
const content =
|
|
3478
|
+
if (existsSync4(fullPath)) {
|
|
3479
|
+
const content = readFileSync3(fullPath, "utf-8").trim();
|
|
3811
3480
|
if (content) return { filePath: fullPath, content };
|
|
3812
3481
|
}
|
|
3813
3482
|
}
|
|
@@ -3836,9 +3505,9 @@ ${content}
|
|
|
3836
3505
|
const gitRoot = getGitRoot(cwd);
|
|
3837
3506
|
const projectRoot = gitRoot ?? cwd;
|
|
3838
3507
|
const mcpPath = join4(projectRoot, MCP_PROJECT_CONFIG_NAME);
|
|
3839
|
-
if (!
|
|
3508
|
+
if (!existsSync4(mcpPath)) return null;
|
|
3840
3509
|
try {
|
|
3841
|
-
const raw = JSON.parse(
|
|
3510
|
+
const raw = JSON.parse(readFileSync3(mcpPath, "utf-8"));
|
|
3842
3511
|
const servers = raw?.mcpServers;
|
|
3843
3512
|
if (!servers || typeof servers !== "object") {
|
|
3844
3513
|
process.stderr.write(
|
|
@@ -3884,8 +3553,8 @@ ${content}
|
|
|
3884
3553
|
);
|
|
3885
3554
|
return { layers: [], mergedContent: "" };
|
|
3886
3555
|
}
|
|
3887
|
-
if (
|
|
3888
|
-
const content =
|
|
3556
|
+
if (existsSync4(fullPath)) {
|
|
3557
|
+
const content = readFileSync3(fullPath, "utf-8").trim();
|
|
3889
3558
|
if (content) {
|
|
3890
3559
|
const layer = {
|
|
3891
3560
|
level: "project",
|
|
@@ -3943,8 +3612,8 @@ ${content}
|
|
|
3943
3612
|
*/
|
|
3944
3613
|
loadMemoryContent() {
|
|
3945
3614
|
const memoryPath = join4(this.config.getConfigDir(), MEMORY_FILE_NAME);
|
|
3946
|
-
if (!
|
|
3947
|
-
let content =
|
|
3615
|
+
if (!existsSync4(memoryPath)) return null;
|
|
3616
|
+
let content = readFileSync3(memoryPath, "utf-8").trim();
|
|
3948
3617
|
if (!content) return null;
|
|
3949
3618
|
if (content.length > MEMORY_MAX_CHARS) {
|
|
3950
3619
|
content = content.slice(-MEMORY_MAX_CHARS);
|
|
@@ -4179,7 +3848,7 @@ ${response.content.trim()}
|
|
|
4179
3848
|
const planTag = this.planMode ? theme.warning("[PLAN]") : "";
|
|
4180
3849
|
const modelParams = this.getModelParams();
|
|
4181
3850
|
const thinkTag = modelParams.thinking ? theme.accent("[THINK]") : "";
|
|
4182
|
-
const promptStr = theme.success(`[${this.currentProvider}]`) + skillTag + planTag + thinkTag +
|
|
3851
|
+
const promptStr = theme.success(`[${this.currentProvider}]`) + skillTag + planTag + thinkTag + chalk4.white(" > ");
|
|
4183
3852
|
this.rl.setPrompt(promptStr);
|
|
4184
3853
|
}
|
|
4185
3854
|
showPrompt() {
|
|
@@ -4698,7 +4367,7 @@ Session '${this.resumeSessionId}' not found.
|
|
|
4698
4367
|
const dir = normalized.includes("/") ? dirname3(normalized) : ".";
|
|
4699
4368
|
const prefix = normalized.includes("/") ? basename2(normalized) : normalized;
|
|
4700
4369
|
const absDir = resolve2(process.cwd(), dir);
|
|
4701
|
-
if (!
|
|
4370
|
+
if (!existsSync4(absDir)) return [];
|
|
4702
4371
|
const entries = readdirSync3(absDir);
|
|
4703
4372
|
const results = [];
|
|
4704
4373
|
for (const entry of entries) {
|
|
@@ -5244,6 +4913,7 @@ You have a maximum of ${MAX_TOOL_ROUNDS} tool call rounds for this task. Plan ef
|
|
|
5244
4913
|
spawnAgentContext.systemPrompt = systemPrompt;
|
|
5245
4914
|
spawnAgentContext.modelParams = modelParams;
|
|
5246
4915
|
spawnAgentContext.configManager = this.config;
|
|
4916
|
+
ToolExecutor.currentMessageIndex = session.messages.length;
|
|
5247
4917
|
const toolResults = await this.toolExecutor.executeAll(result.toolCalls);
|
|
5248
4918
|
const thisRoundTools = result.toolCalls.map((tc) => tc.name);
|
|
5249
4919
|
roundToolHistory.push({ round: round + 1, tools: thisRoundTools });
|
|
@@ -5691,7 +5361,7 @@ program.command("web").description("Start Web UI server with browser-based chat
|
|
|
5691
5361
|
console.error("Error: Invalid port number. Must be between 1 and 65535.");
|
|
5692
5362
|
process.exit(1);
|
|
5693
5363
|
}
|
|
5694
|
-
const { startWebServer } = await import("./server-
|
|
5364
|
+
const { startWebServer } = await import("./server-V3IZSAMO.js");
|
|
5695
5365
|
await startWebServer({ port, host: options.host });
|
|
5696
5366
|
});
|
|
5697
5367
|
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 +5594,7 @@ program.command("hub [topic]").description("Start multi-agent hub (discuss / bra
|
|
|
5924
5594
|
}),
|
|
5925
5595
|
config.get("customProviders")
|
|
5926
5596
|
);
|
|
5927
|
-
const { startHub } = await import("./hub-
|
|
5597
|
+
const { startHub } = await import("./hub-JOYPSPR2.js");
|
|
5928
5598
|
await startHub(
|
|
5929
5599
|
{
|
|
5930
5600
|
topic: topic ?? "",
|