jinzd-ai-cli 0.1.26 → 0.1.28
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/CLAUDE.md
CHANGED
|
@@ -337,7 +337,7 @@ const stdinLines = Array.isArray(rawStdin) ? rawStdin.map(String)
|
|
|
337
337
|
## 已知待改进项
|
|
338
338
|
|
|
339
339
|
### 低优先级
|
|
340
|
-
- [
|
|
340
|
+
- [x] **macOS/Linux 完整测试**(2026-03-01):已在 Linux 环境完成测试,基本无问题。跨平台逻辑(`$SHELL` fallback、UTF-8 env vars)验证通过。
|
|
341
341
|
- [x] **Token 用量显示**:已通过 `/cost` 命令实现(v0.1.23),显示 session 累计 input/output/total tokens。
|
|
342
342
|
- [ ] **GitHub 仓库迁移**:当前在 gitee,npm 包受众需要 GitHub 托管。
|
|
343
343
|
- [x] **web_fetch DNS 解析时 SSRF 防护**(v0.1.25):新增 `resolveAndCheck()` 函数,用 `dns.promises.lookup()` 预解析域名,检查结果 IP 是否为私有地址。初始 URL 和 redirect 目标均校验。
|
|
@@ -802,3 +802,94 @@ const stdinLines = Array.isArray(rawStdin) ? rawStdin.map(String)
|
|
|
802
802
|
| run_interactive stdin 截断 | 通过 shell 包装 spawn 时 stdin pipe 被截断 | 直接 `spawn(pythonExe, args)` 不经过任何 shell |
|
|
803
803
|
| Ctrl+C 在工具确认时异常退出 | SIGINT handler 直接调用 `handleExit()`,未检查 `confirm()` 状态 | SIGINT handler 先检查 `confirming` 标志,是则 `cancelConfirm()` 取消而非退出 |
|
|
804
804
|
| 日期注入在 pkg exe 中显示错误 | `toLocaleDateString/toLocaleTimeString` 依赖完整 ICU,pkg 精简版不支持 | 改为手动数字拼接:`${year}年${month}月${day}日 ${weekday} ${HH}:${mm}:${ss}` |
|
|
805
|
+
|
|
806
|
+
---
|
|
807
|
+
|
|
808
|
+
## 代码审查报告(2026-03-01)
|
|
809
|
+
|
|
810
|
+
> 全面扫描 src/ 目录 35+ 个 TypeScript 源文件,共发现 **4 高危 + 8 中危 + 7 低危** 问题。
|
|
811
|
+
|
|
812
|
+
### 🔴 高危问题(立即修复)
|
|
813
|
+
|
|
814
|
+
**H1 — MCP pendingRequests 内存泄漏**
|
|
815
|
+
- **文件**:`src/mcp/client.ts`
|
|
816
|
+
- **描述**:进程崩溃时 `pendingRequests.delete(id)` 可能不触发,timer 未清理;长期运行时内存持续增长
|
|
817
|
+
- **修复**:在 `sendRequest` 中用 `finally` 确保 `pendingRequests.delete(id)` + `clearTimeout(timer)` 总被执行
|
|
818
|
+
|
|
819
|
+
**H2 — readline 竞态条件:`confirming` 标志失效**
|
|
820
|
+
- **文件**:`src/tools/executor.ts`(行 58, 155, 307, 320-356)
|
|
821
|
+
- **描述**:`batchConfirm()` 提前返回 `'none'` 时 `confirming = true` 与实际状态不匹配;`once('line')` 在 edge case 下可能触发两次,误消费用户输入;`ask_user` 工具的 `askUserContext.prompting` 存在同样问题
|
|
822
|
+
- **修复**:使用 `completed` 布尔标志防止二次触发,或改用 Promise-based 状态机
|
|
823
|
+
|
|
824
|
+
**H3 — 工具执行错误语义混淆(isError 混用)**
|
|
825
|
+
- **文件**:`src/tools/executor.ts`(行 128, 160, 170)
|
|
826
|
+
- **描述**:权限 deny 和用户取消均返回 `{ isError: false }`,AI 无法区分"用户拒绝"与"工具正常返回",可能产生错误推理
|
|
827
|
+
- **修复**:统一为 `isError: true`,内容前缀加 `[User cancelled]` 或 `[Permission denied]`
|
|
828
|
+
|
|
829
|
+
**H4 — MCP 断开后无恢复机制**
|
|
830
|
+
- **文件**:`src/mcp/client.ts` + `src/mcp/manager.ts`
|
|
831
|
+
- **描述**:服务器子进程意外退出后,客户端对象仍留在 Map 中;工具列表可见但调用时 silent failure;用户需重启 CLI 才能恢复
|
|
832
|
+
- **修复**:`callTool()` 中检测 `isConnected`,返回友好错误提示;可选:实现指数退避自动重连
|
|
833
|
+
|
|
834
|
+
### 🟠 中危问题(近期修复)
|
|
835
|
+
|
|
836
|
+
| # | 文件 | 问题描述 | 修复方向 |
|
|
837
|
+
|---|------|---------|---------|
|
|
838
|
+
| M5 | `src/tools/builtin/bash.ts:76-86` | `cwd` 指向不存在目录时不报错,AI 误判 cd 成功 | `existsSync` 校验后再设置 `effectiveCwd` |
|
|
839
|
+
| M6 | `src/tools/builtin/web-fetch.ts` | SSRF:重定向链中间 URL 不检查(仅检查首尾) | 对每个中间重定向 URL 均调用 `resolveAndCheck()` |
|
|
840
|
+
| M7 | `src/tools/builtin/run-interactive.ts:131-144` | stdin 写入无背压处理,大量输入可能溢出 | 监听 `drain` 事件,写满后暂停直到缓冲区释放 |
|
|
841
|
+
| M8 | `src/session/session-manager.ts` | 会话 JSON 损坏时错误被吞噬,无日志,用户无法诊断 | `catch` 中输出 `stderr` 警告含文件名和错误信息 |
|
|
842
|
+
| M9 | `src/mcp/client.ts` | `close()` 时事件监听器未移除,长期运行积累僵尸监听器 | `killProcess()` 中调用 `removeAllListeners()` |
|
|
843
|
+
| M10 | `src/tools/permissions.ts` | `pathPattern` 直接 `new RegExp()`,恶意配置可触发 ReDoS | 加 try/catch 包裹正则编译,或限制正则复杂度 |
|
|
844
|
+
| M11 | `src/repl/repl.ts`(上下文加载) | `contextFile` 设为相对路径可能遍历项目外目录 | `resolve()` 后校验路径以项目目录为前缀 |
|
|
845
|
+
| M12 | `src/mcp/manager.ts` | MCP 工具 schema 扁平化时丢失嵌套结构,AI 传参可能失败 | `convertDefinition()` 支持递归转换嵌套 object/array |
|
|
846
|
+
|
|
847
|
+
### 🟡 低危问题(后续改进)
|
|
848
|
+
|
|
849
|
+
| # | 文件 | 问题描述 |
|
|
850
|
+
|---|------|---------|
|
|
851
|
+
| L1 | `src/tools/builtin/run-tests.ts:35` | `JSON.parse(package.json)` 无细粒度错误处理 |
|
|
852
|
+
| L2 | `src/tools/builtin/web-fetch.ts` | 恶意 HTML 大量 script 标签时正则性能下降 |
|
|
853
|
+
| L3 | `src/session/session.ts` | `new Date(d.created)` 非法字符串返回 Invalid Date,比较失败 |
|
|
854
|
+
| L4 | `src/tools/builtin/ask-user.ts` / `google-search.ts` | 模块级全局上下文,多会话架构下会串扰 |
|
|
855
|
+
| L5 | `src/config/schema.ts` | `modelParams.timeout` 与 provider 级 `timeout` 优先级不清晰 |
|
|
856
|
+
| L6 | `src/tools/executor.ts` | `call.arguments['path']` 未验证是否绝对路径,null 传入可能异常 |
|
|
857
|
+
| L7 | `src/repl/repl.ts` | 主循环 `line` handler 是 async 但无整体 try/catch,未捕获异常可能停响应 |
|
|
858
|
+
|
|
859
|
+
---
|
|
860
|
+
|
|
861
|
+
## 与 Claude Code 功能对比差距(2026-03-01)
|
|
862
|
+
|
|
863
|
+
### ✅ ai-cli 已超越或持平 Claude Code 的功能
|
|
864
|
+
|
|
865
|
+
- 多 Provider 支持(Claude Code 仅支持 Claude 系列)
|
|
866
|
+
- 三层级上下文文件(Claude Code 为双层)
|
|
867
|
+
- Dev State 开发状态交接
|
|
868
|
+
- Agent Skills 系统
|
|
869
|
+
- 企业级权限规则 + Hooks 系统
|
|
870
|
+
|
|
871
|
+
### ❌ 缺失功能路线图(新增)
|
|
872
|
+
|
|
873
|
+
#### P0 — 核心竞争力缺口
|
|
874
|
+
- [ ] **中断生成**:`Escape` 键立即停止 AI 流式输出(当前无法中断,必须等待完成)
|
|
875
|
+
- [ ] **多模态输入(图片)**:支持粘贴/引用图片路径作为输入(当前纯文本)
|
|
876
|
+
- [ ] **`/add-dir` 命令**:运行时动态添加目录到上下文
|
|
877
|
+
- [ ] **并行工具调用**:AI 一次返回多个工具同时执行(当前为分组串行)
|
|
878
|
+
|
|
879
|
+
#### P1 — 重要差距
|
|
880
|
+
- [ ] **`/memory` 命令**:查看/编辑/清理 memory.md 内容(当前只能由工具写入)
|
|
881
|
+
- [ ] **`/doctor` 健康检查**:诊断 API Key、网络、配置问题
|
|
882
|
+
- [ ] **`/bug` 反馈命令**:一键提交 bug 报告
|
|
883
|
+
- [ ] **Vim 编辑模式**:命令行输入支持 vim 键绑定
|
|
884
|
+
- [ ] **桌面通知**:长任务完成时发送系统通知
|
|
885
|
+
- [ ] **`--allowedTools` / `--blockedTools` 启动参数**:启动时动态限制工具集
|
|
886
|
+
- [ ] **流式 JSON 输出**:`--output-format streaming-json` 逐行输出(当前仅支持一次性 `--json`)
|
|
887
|
+
|
|
888
|
+
#### P2 — 体验增强
|
|
889
|
+
- [ ] **项目级 `.mcp.json`**:项目根目录独立 MCP 配置,优先全局 config
|
|
890
|
+
- [ ] **IDE 集成**:VS Code / JetBrains 扩展(架构已准备就绪)
|
|
891
|
+
- [ ] **OAuth/浏览器登录**:无需手动填 API Key
|
|
892
|
+
- [ ] **`--resume <id>` 启动参数**:命令行直接恢复指定会话
|
|
893
|
+
- [ ] **Extended Thinking**:Claude 3.7 深度推理模式集成
|
|
894
|
+
- [ ] **Word wrap 配置**:终端输出折行宽度可配置
|
|
895
|
+
- [ ] **主题/颜色自定义**:dark/light/custom 主题
|
|
@@ -94,7 +94,11 @@ function detectProject(cwd) {
|
|
|
94
94
|
if (testScript && testScript !== 'echo "Error: no test specified" && exit 1') {
|
|
95
95
|
return { type: "node", framework: "npm", command: "npm test" };
|
|
96
96
|
}
|
|
97
|
-
} catch {
|
|
97
|
+
} catch (err) {
|
|
98
|
+
process.stderr.write(
|
|
99
|
+
`[Warning] Failed to parse package.json: ${err instanceof Error ? err.message : String(err)}
|
|
100
|
+
`
|
|
101
|
+
);
|
|
98
102
|
}
|
|
99
103
|
}
|
|
100
104
|
if (existsSync(join(cwd, "pyproject.toml")) || existsSync(join(cwd, "setup.py")) || existsSync(join(cwd, "pytest.ini"))) {
|
package/dist/index.js
CHANGED
|
@@ -27,7 +27,7 @@ import {
|
|
|
27
27
|
SUBAGENT_MAX_ROUNDS_LIMIT,
|
|
28
28
|
VERSION,
|
|
29
29
|
runTestsTool
|
|
30
|
-
} from "./chunk-
|
|
30
|
+
} from "./chunk-3EJWGNHV.js";
|
|
31
31
|
|
|
32
32
|
// src/index.ts
|
|
33
33
|
import { program } from "commander";
|
|
@@ -75,6 +75,11 @@ var ConfigSchema = z.object({
|
|
|
75
75
|
apiKeys: z.record(z.string()).default({}),
|
|
76
76
|
customBaseUrls: z.record(z.string()).default({}),
|
|
77
77
|
// Per-provider timeout in ms (e.g. { deepseek: 60000 })
|
|
78
|
+
// ⚠️ Timeout 优先级说明(L5):
|
|
79
|
+
// 1. modelParams[modelId].timeout — 最高优先级,精确到具体模型(如 deepseek-reasoner 需要更长超时)
|
|
80
|
+
// 2. timeouts[providerId] — Provider 级默认,覆盖内置默认值
|
|
81
|
+
// 3. 内置 Provider 的硬编码默认值 — 最低兜底(如 OpenAI 兼容系列默认 60000ms)
|
|
82
|
+
// 实现位置:src/providers/registry.ts initialize(),将 timeouts[id] 注入 provider
|
|
78
83
|
timeouts: z.record(z.number()).default({}),
|
|
79
84
|
// HTTP/HTTPS 代理地址(Node.js 不自动使用系统代理,需显式配置)
|
|
80
85
|
// 例:http://127.0.0.1:10809
|
|
@@ -1477,6 +1482,10 @@ var Session = class _Session {
|
|
|
1477
1482
|
};
|
|
1478
1483
|
|
|
1479
1484
|
// src/session/session-manager.ts
|
|
1485
|
+
function safeDate(value) {
|
|
1486
|
+
const d = new Date(value);
|
|
1487
|
+
return isNaN(d.getTime()) ? /* @__PURE__ */ new Date(0) : d;
|
|
1488
|
+
}
|
|
1480
1489
|
var SessionManager = class {
|
|
1481
1490
|
constructor(config) {
|
|
1482
1491
|
this.config = config;
|
|
@@ -1522,11 +1531,15 @@ var SessionManager = class {
|
|
|
1522
1531
|
provider: data.provider,
|
|
1523
1532
|
model: data.model,
|
|
1524
1533
|
messageCount: data.messages?.length ?? 0,
|
|
1525
|
-
created:
|
|
1526
|
-
updated:
|
|
1534
|
+
created: safeDate(data.created),
|
|
1535
|
+
updated: safeDate(data.updated),
|
|
1527
1536
|
title: data.title
|
|
1528
1537
|
});
|
|
1529
|
-
} catch {
|
|
1538
|
+
} catch (err) {
|
|
1539
|
+
process.stderr.write(
|
|
1540
|
+
`[Warning] Skipping corrupted session file "${file}": ${err instanceof Error ? err.message : String(err)}
|
|
1541
|
+
`
|
|
1542
|
+
);
|
|
1530
1543
|
}
|
|
1531
1544
|
}
|
|
1532
1545
|
return metas.sort((a, b) => b.updated.getTime() - a.updated.getTime());
|
|
@@ -1571,14 +1584,18 @@ var SessionManager = class {
|
|
|
1571
1584
|
provider: data.provider,
|
|
1572
1585
|
model: data.model,
|
|
1573
1586
|
messageCount: messages.length,
|
|
1574
|
-
created:
|
|
1575
|
-
updated:
|
|
1587
|
+
created: safeDate(data.created),
|
|
1588
|
+
updated: safeDate(data.updated),
|
|
1576
1589
|
title: data.title
|
|
1577
1590
|
},
|
|
1578
1591
|
matches
|
|
1579
1592
|
});
|
|
1580
1593
|
}
|
|
1581
|
-
} catch {
|
|
1594
|
+
} catch (err) {
|
|
1595
|
+
process.stderr.write(
|
|
1596
|
+
`[Warning] Skipping corrupted session file "${filePath}": ${err instanceof Error ? err.message : String(err)}
|
|
1597
|
+
`
|
|
1598
|
+
);
|
|
1582
1599
|
}
|
|
1583
1600
|
}
|
|
1584
1601
|
return results.sort((a, b) => b.sessionMeta.updated.getTime() - a.sessionMeta.updated.getTime());
|
|
@@ -3182,7 +3199,7 @@ ${text}
|
|
|
3182
3199
|
description: "Run project tests and show structured report",
|
|
3183
3200
|
usage: "/test [command|filter]",
|
|
3184
3201
|
async execute(args, _ctx) {
|
|
3185
|
-
const { executeTests } = await import("./run-tests-
|
|
3202
|
+
const { executeTests } = await import("./run-tests-MKMB3TXE.js");
|
|
3186
3203
|
const argStr = args.join(" ").trim();
|
|
3187
3204
|
let testArgs = {};
|
|
3188
3205
|
if (argStr) {
|
|
@@ -3455,12 +3472,13 @@ var bashTool = {
|
|
|
3455
3472
|
let effectiveCwd = persistentCwd;
|
|
3456
3473
|
if (cwdArg) {
|
|
3457
3474
|
const resolved = resolve2(persistentCwd, cwdArg);
|
|
3458
|
-
if (existsSync6(resolved)) {
|
|
3459
|
-
|
|
3460
|
-
|
|
3461
|
-
|
|
3462
|
-
effectiveCwd = resolved;
|
|
3475
|
+
if (!existsSync6(resolved)) {
|
|
3476
|
+
throw new Error(
|
|
3477
|
+
`cwd directory does not exist: "${resolved}". Create it first (e.g. mkdir -p "${resolved}") before specifying it as cwd.`
|
|
3478
|
+
);
|
|
3463
3479
|
}
|
|
3480
|
+
effectiveCwd = resolved;
|
|
3481
|
+
persistentCwd = resolved;
|
|
3464
3482
|
}
|
|
3465
3483
|
let actualCommand;
|
|
3466
3484
|
if (IS_WINDOWS) {
|
|
@@ -4300,10 +4318,13 @@ var runInteractiveTool = {
|
|
|
4300
4318
|
let lineIdx = 0;
|
|
4301
4319
|
const writeNextLine = () => {
|
|
4302
4320
|
if (lineIdx < stdinLines.length && !child.stdin.destroyed) {
|
|
4303
|
-
|
|
4304
|
-
|
|
4305
|
-
|
|
4306
|
-
|
|
4321
|
+
const line = stdinLines[lineIdx++] + (IS_WINDOWS2 ? "\r\n" : "\n");
|
|
4322
|
+
const canContinue = child.stdin.write(line);
|
|
4323
|
+
if (canContinue) {
|
|
4324
|
+
setTimeout(writeNextLine, 150);
|
|
4325
|
+
} else {
|
|
4326
|
+
child.stdin.once("drain", () => setTimeout(writeNextLine, 150));
|
|
4327
|
+
}
|
|
4307
4328
|
} else if (!child.stdin.destroyed) {
|
|
4308
4329
|
child.stdin.end();
|
|
4309
4330
|
}
|
|
@@ -4346,6 +4367,10 @@ ${stderr}`);
|
|
|
4346
4367
|
// src/tools/builtin/web-fetch.ts
|
|
4347
4368
|
import { promises as dnsPromises } from "dns";
|
|
4348
4369
|
function htmlToText(html) {
|
|
4370
|
+
const HTML_REGEX_LIMIT = 2e5;
|
|
4371
|
+
if (html.length > HTML_REGEX_LIMIT) {
|
|
4372
|
+
html = html.slice(0, HTML_REGEX_LIMIT);
|
|
4373
|
+
}
|
|
4349
4374
|
let text = html.replace(/<script[\s\S]*?<\/script>/gi, "").replace(/<style[\s\S]*?<\/style>/gi, "").replace(/<noscript[\s\S]*?<\/noscript>/gi, "").replace(/<svg[\s\S]*?<\/svg>/gi, "");
|
|
4350
4375
|
text = text.replace(/<h([1-6])[^>]*>([\s\S]*?)<\/h\1>/gi, (_m, lvl, content) => {
|
|
4351
4376
|
const prefix = "#".repeat(Number(lvl));
|
|
@@ -4450,28 +4475,46 @@ var webFetchTool = {
|
|
|
4450
4475
|
let rawHtml;
|
|
4451
4476
|
let finalUrl;
|
|
4452
4477
|
let contentType;
|
|
4478
|
+
const MAX_REDIRECTS = 10;
|
|
4479
|
+
const FETCH_HEADERS = {
|
|
4480
|
+
"User-Agent": "Mozilla/5.0 (compatible; ai-cli/1.0; +https://github.com/ai-cli)",
|
|
4481
|
+
Accept: "text/html,application/xhtml+xml,text/plain,*/*",
|
|
4482
|
+
"Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8"
|
|
4483
|
+
};
|
|
4453
4484
|
try {
|
|
4454
|
-
|
|
4455
|
-
|
|
4456
|
-
|
|
4457
|
-
|
|
4458
|
-
|
|
4459
|
-
|
|
4460
|
-
}
|
|
4461
|
-
|
|
4462
|
-
|
|
4463
|
-
|
|
4464
|
-
|
|
4465
|
-
|
|
4466
|
-
|
|
4467
|
-
|
|
4468
|
-
if (
|
|
4469
|
-
|
|
4485
|
+
let currentUrl = url;
|
|
4486
|
+
let resp = null;
|
|
4487
|
+
for (let hop = 0; hop <= MAX_REDIRECTS; hop++) {
|
|
4488
|
+
const parsedHop = new URL(currentUrl);
|
|
4489
|
+
if (isPrivateHost(parsedHop.hostname)) {
|
|
4490
|
+
throw new Error(`Blocked: redirect to private/internal address "${currentUrl}".`);
|
|
4491
|
+
}
|
|
4492
|
+
await resolveAndCheck(parsedHop.hostname);
|
|
4493
|
+
const r = await fetch(currentUrl, {
|
|
4494
|
+
signal: controller.signal,
|
|
4495
|
+
headers: FETCH_HEADERS,
|
|
4496
|
+
redirect: "manual"
|
|
4497
|
+
// 手动控制重定向
|
|
4498
|
+
});
|
|
4499
|
+
if (r.status >= 300 && r.status < 400) {
|
|
4500
|
+
if (hop >= MAX_REDIRECTS) {
|
|
4501
|
+
throw new Error(`Too many redirects (>${MAX_REDIRECTS}): ${url}`);
|
|
4502
|
+
}
|
|
4503
|
+
const location = r.headers.get("Location");
|
|
4504
|
+
if (!location) {
|
|
4505
|
+
resp = r;
|
|
4506
|
+
break;
|
|
4507
|
+
}
|
|
4508
|
+
currentUrl = new URL(location, currentUrl).href;
|
|
4509
|
+
continue;
|
|
4470
4510
|
}
|
|
4471
|
-
|
|
4472
|
-
|
|
4473
|
-
if (e.message.startsWith("Blocked:")) throw e;
|
|
4511
|
+
resp = r;
|
|
4512
|
+
break;
|
|
4474
4513
|
}
|
|
4514
|
+
clearTimeout(timeoutId);
|
|
4515
|
+
if (!resp) throw new Error(`Too many redirects (>${MAX_REDIRECTS}): ${url}`);
|
|
4516
|
+
finalUrl = currentUrl;
|
|
4517
|
+
contentType = resp.headers.get("content-type") ?? "";
|
|
4475
4518
|
if (!resp.ok) {
|
|
4476
4519
|
throw new Error(`HTTP ${resp.status} ${resp.statusText}`);
|
|
4477
4520
|
}
|
|
@@ -5592,7 +5635,7 @@ var ToolExecutor = class {
|
|
|
5592
5635
|
if (this.permissionRules.length > 0) {
|
|
5593
5636
|
const action = checkPermission(call.name, call.arguments, dangerLevel, this.permissionRules, this.defaultPermission);
|
|
5594
5637
|
if (action === "deny") {
|
|
5595
|
-
return { callId: call.id, content: `Permission denied by rule for tool: ${call.name}`, isError:
|
|
5638
|
+
return { callId: call.id, content: `Permission denied by rule for tool: ${call.name}`, isError: true };
|
|
5596
5639
|
}
|
|
5597
5640
|
if (action === "auto-approve") {
|
|
5598
5641
|
this.printToolCall(call);
|
|
@@ -5619,7 +5662,7 @@ var ToolExecutor = class {
|
|
|
5619
5662
|
return {
|
|
5620
5663
|
callId: call.id,
|
|
5621
5664
|
content: "User cancelled the operation.",
|
|
5622
|
-
isError:
|
|
5665
|
+
isError: true
|
|
5623
5666
|
};
|
|
5624
5667
|
}
|
|
5625
5668
|
} else if (dangerLevel === "destructive") {
|
|
@@ -5628,7 +5671,7 @@ var ToolExecutor = class {
|
|
|
5628
5671
|
return {
|
|
5629
5672
|
callId: call.id,
|
|
5630
5673
|
content: "User cancelled the operation.",
|
|
5631
|
-
isError:
|
|
5674
|
+
isError: true
|
|
5632
5675
|
};
|
|
5633
5676
|
}
|
|
5634
5677
|
this.printToolCall(call);
|
|
@@ -5721,7 +5764,7 @@ var ToolExecutor = class {
|
|
|
5721
5764
|
}
|
|
5722
5765
|
} else {
|
|
5723
5766
|
console.log(chalk8.gray(` [${i + 1}] `) + chalk8.dim("rejected"));
|
|
5724
|
-
results.push({ callId: call.id, content: "User rejected the operation.", isError:
|
|
5767
|
+
results.push({ callId: call.id, content: "User rejected the operation.", isError: true });
|
|
5725
5768
|
}
|
|
5726
5769
|
}
|
|
5727
5770
|
return results;
|
|
@@ -5752,7 +5795,10 @@ var ToolExecutor = class {
|
|
|
5752
5795
|
this.confirming = false;
|
|
5753
5796
|
resolve5(result);
|
|
5754
5797
|
};
|
|
5798
|
+
let completed = false;
|
|
5755
5799
|
const onLine = (line) => {
|
|
5800
|
+
if (completed) return;
|
|
5801
|
+
completed = true;
|
|
5756
5802
|
const input2 = line.trim().toLowerCase();
|
|
5757
5803
|
if (input2 === "a" || input2 === "all" || input2 === "y") {
|
|
5758
5804
|
cleanup("all");
|
|
@@ -5768,6 +5814,8 @@ var ToolExecutor = class {
|
|
|
5768
5814
|
}
|
|
5769
5815
|
};
|
|
5770
5816
|
this.cancelConfirmFn = () => {
|
|
5817
|
+
if (completed) return;
|
|
5818
|
+
completed = true;
|
|
5771
5819
|
process.stdout.write(chalk8.gray("\n(cancelled)\n"));
|
|
5772
5820
|
cleanup("none");
|
|
5773
5821
|
};
|
|
@@ -5916,8 +5964,15 @@ var ToolExecutor = class {
|
|
|
5916
5964
|
this.confirming = false;
|
|
5917
5965
|
resolve5(answer === "y");
|
|
5918
5966
|
};
|
|
5919
|
-
|
|
5967
|
+
let completed = false;
|
|
5968
|
+
const onLine = (line) => {
|
|
5969
|
+
if (completed) return;
|
|
5970
|
+
completed = true;
|
|
5971
|
+
cleanup(line.trim().toLowerCase());
|
|
5972
|
+
};
|
|
5920
5973
|
this.cancelConfirmFn = () => {
|
|
5974
|
+
if (completed) return;
|
|
5975
|
+
completed = true;
|
|
5921
5976
|
process.stdout.write(chalk8.gray("\n(cancelled)\n"));
|
|
5922
5977
|
cleanup("n");
|
|
5923
5978
|
};
|
|
@@ -6525,7 +6580,9 @@ var McpClient = class {
|
|
|
6525
6580
|
// ══════════════════════════════════════════════════════════════════
|
|
6526
6581
|
ensureConnected() {
|
|
6527
6582
|
if (!this.connected) {
|
|
6528
|
-
throw new Error(
|
|
6583
|
+
throw new Error(
|
|
6584
|
+
`MCP server [${this.serverId}] is not connected` + (this.errorMessage ? `: ${this.errorMessage}` : "")
|
|
6585
|
+
);
|
|
6529
6586
|
}
|
|
6530
6587
|
}
|
|
6531
6588
|
/** Promise 超时包装 */
|
|
@@ -6551,9 +6608,13 @@ var McpClient = class {
|
|
|
6551
6608
|
}
|
|
6552
6609
|
this.pendingRequests.clear();
|
|
6553
6610
|
}
|
|
6554
|
-
/**
|
|
6611
|
+
/** 杀掉子进程,并移除所有事件监听器防止僵尸引用 */
|
|
6555
6612
|
killProcess() {
|
|
6556
6613
|
if (this.process) {
|
|
6614
|
+
this.process.removeAllListeners("error");
|
|
6615
|
+
this.process.removeAllListeners("exit");
|
|
6616
|
+
this.process.stdout?.removeAllListeners("data");
|
|
6617
|
+
this.process.stderr?.removeAllListeners("data");
|
|
6557
6618
|
try {
|
|
6558
6619
|
this.process.stdin?.end();
|
|
6559
6620
|
this.process.kill();
|
|
@@ -6661,6 +6722,11 @@ var McpManager = class {
|
|
|
6661
6722
|
return {
|
|
6662
6723
|
definition,
|
|
6663
6724
|
execute: async (args) => {
|
|
6725
|
+
if (!client.isConnected) {
|
|
6726
|
+
throw new Error(
|
|
6727
|
+
`MCP server [${serverId}] is disconnected` + (client.errorMessage ? `: ${client.errorMessage}` : "") + `. Use /mcp to check status, or restart ai-cli to reconnect.`
|
|
6728
|
+
);
|
|
6729
|
+
}
|
|
6664
6730
|
try {
|
|
6665
6731
|
const result = await client.callTool(mcpTool.name, args);
|
|
6666
6732
|
if (result.isError) {
|
|
@@ -7011,7 +7077,14 @@ var Repl = class {
|
|
|
7011
7077
|
if (setting === false) return { layers: [], mergedContent: "" };
|
|
7012
7078
|
const cwd = process.cwd();
|
|
7013
7079
|
if (setting !== "auto") {
|
|
7014
|
-
const fullPath =
|
|
7080
|
+
const fullPath = resolve4(cwd, String(setting));
|
|
7081
|
+
if (!fullPath.startsWith(resolve4(cwd))) {
|
|
7082
|
+
process.stderr.write(
|
|
7083
|
+
`[Warning] contextFile path "${setting}" is outside current directory, ignoring.
|
|
7084
|
+
`
|
|
7085
|
+
);
|
|
7086
|
+
return { layers: [], mergedContent: "" };
|
|
7087
|
+
}
|
|
7015
7088
|
if (existsSync18(fullPath)) {
|
|
7016
7089
|
const content = readFileSync13(fullPath, "utf-8").trim();
|
|
7017
7090
|
if (content) {
|