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/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-GBMVHLPA.js";
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-PDVX5QJA.js";
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-UA4BVWKV.js";
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 existsSync5, readFileSync as readFileSync4, readdirSync as readdirSync3, statSync as statSync3 } from "fs";
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 chalk5 from "chalk";
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 = 16 + pluginCount + mcpToolCount;
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 (38):"));
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 /commands /test /scaffold /add-dir /memory /profile"));
228
- console.log(theme.dim(" /doctor /bug /think /diff /fork /yolo /exit"));
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-7ZBI4ZTU.js");
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 existsSync4, readFileSync as readFileSync3, readdirSync as readdirSync2, mkdirSync as mkdirSync3 } from "fs";
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 = readFileSync3(filePath, "utf-8");
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 readFileSync3(p.trim(), "utf-8");
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 (!existsSync4(this.commandsDir)) {
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 (!existsSync5(absPath)) {
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 = readFileSync4(absPath).toString("base64");
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 = readFileSync4(absPath, "utf-8");
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 = readFileSync4(fullPath, "utf-8");
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 (!existsSync5(absPath)) {
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 (existsSync5(fullPath)) {
3810
- const content = readFileSync4(fullPath, "utf-8").trim();
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 (!existsSync5(mcpPath)) return null;
3512
+ if (!existsSync4(mcpPath)) return null;
3840
3513
  try {
3841
- const raw = JSON.parse(readFileSync4(mcpPath, "utf-8"));
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 (existsSync5(fullPath)) {
3888
- const content = readFileSync4(fullPath, "utf-8").trim();
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 (!existsSync5(memoryPath)) return null;
3947
- let content = readFileSync4(memoryPath, "utf-8").trim();
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 + chalk5.white(" > ");
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 (!existsSync5(absDir)) return [];
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-SD5ICBFP.js");
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-YN245LMP.js");
5601
+ const { startHub } = await import("./hub-JOYPSPR2.js");
5928
5602
  await startHub(
5929
5603
  {
5930
5604
  topic: topic ?? "",