jinzd-ai-cli 0.1.39 → 0.1.41
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
|
@@ -348,9 +348,47 @@ const stdinLines = Array.isArray(rawStdin) ? rawStdin.map(String)
|
|
|
348
348
|
- [x] **web_fetch DNS 解析时 SSRF 防护**(v0.1.25):新增 `resolveAndCheck()` 函数,用 `dns.promises.lookup()` 预解析域名,检查结果 IP 是否为私有地址。初始 URL 和 redirect 目标均校验。
|
|
349
349
|
- [ ] **`persistentCwd` 全局状态**:bash 工具的当前工作目录是模块级全局变量,多 session 并发时可能串扰。现阶段单 session REPL 无影响,GUI 多会话扩展时需重构为 per-session 状态。
|
|
350
350
|
|
|
351
|
-
## 本轮开发完成记录(2026-03-07,v0.1.
|
|
351
|
+
## 本轮开发完成记录(2026-03-07,v0.1.40 → v0.1.41)
|
|
352
352
|
|
|
353
|
-
### Bug
|
|
353
|
+
### Bug 修复:Kimi 虚假完成声明(方案 C)
|
|
354
|
+
|
|
355
|
+
**问题**:Kimi-k2 在多轮对话中,第二次及后续文件生成请求时,完全跳过 `write_file` 工具调用,直接在回复中虚假声称"✅ 文件已生成"并附上文件路径,实际上文件并未被创建。这与已修复的 XML 伪工具调用(方案 A)不同——这次 Kimi 没有输出任何 XML 标签,纯粹是文本级别的虚假声明。
|
|
356
|
+
|
|
357
|
+
**修复**:`src/providers/kimi.ts` 新增**方案 C(虚假完成检测 + 自动重试)**,三道防线完整覆盖 Kimi 工具调用的所有已知失败模式:
|
|
358
|
+
|
|
359
|
+
| 防线 | 覆盖场景 | 实现 |
|
|
360
|
+
|------|---------|------|
|
|
361
|
+
| 方案 B(预防)| system prompt 规范 | `KIMI_TOOL_CALL_REMINDER` 新增"严禁虚假完成声明"段落 |
|
|
362
|
+
| 方案 A(兜底 1)| XML 伪工具调用 | `parseXmlToolCalls()` 检测并转换 |
|
|
363
|
+
| **方案 C(兜底 2)**| **虚假完成声明** | **`detectsHallucinatedFileOp()` 检测 + 自动注入纠正消息重试** |
|
|
364
|
+
|
|
365
|
+
方案 C 实现细节:
|
|
366
|
+
- `HALLUCINATION_PATTERNS`:6 个正则模式检测"文件路径: xxx"、"已生成完成!"、"已保存到"等虚假声明
|
|
367
|
+
- `detectsHallucinatedFileOp(content)`:当响应为纯文本且匹配虚假声明模式时返回 true
|
|
368
|
+
- 自动重试:将 AI 的虚假回复 + 纠正指令 (`"你刚才没有实际调用 write_file 工具,文件并未被创建!"`) 追加到 `_extraMessages`,重新调用 `super.chatWithTools()`
|
|
369
|
+
- 重试后仍有 XML 伪调用时,走方案 A 解析(组合防线)
|
|
370
|
+
- `mergeUsage()` 合并两次 API 调用的 token 用量
|
|
371
|
+
- 只重试一次,防止无限循环
|
|
372
|
+
- stderr 输出警告提示用户发生了虚假声明救援
|
|
373
|
+
|
|
374
|
+
### 版本与收尾
|
|
375
|
+
- `src/core/constants.ts`:VERSION `0.1.40` → `0.1.41`
|
|
376
|
+
- `package.json`:version 同步
|
|
377
|
+
- 构建验证:`npm run build` 零错误
|
|
378
|
+
|
|
379
|
+
### 本轮变更文件汇总
|
|
380
|
+
|
|
381
|
+
| 文件 | 变更类型 | 说明 |
|
|
382
|
+
|------|---------|------|
|
|
383
|
+
| `src/core/constants.ts` | 修改 | VERSION 0.1.40 → 0.1.41 |
|
|
384
|
+
| `src/providers/kimi.ts` | 重写 | 方案 C 虚假完成检测 + 自动重试 + HALLUCINATION_PATTERNS |
|
|
385
|
+
| `package.json` | 修改 | version 0.1.40 → 0.1.41 |
|
|
386
|
+
|
|
387
|
+
---
|
|
388
|
+
|
|
389
|
+
## 本轮开发完成记录(2026-03-07,v0.1.38 → v0.1.40)
|
|
390
|
+
|
|
391
|
+
### Bug 修复(3 个使用中发现的问题 + 1 个系统改进)
|
|
354
392
|
|
|
355
393
|
**Bug 1:`/init` 输出路径错误** 🔴
|
|
356
394
|
- **问题**:在 `D:\xgitee\ai-courses\prjs\vocational` 运行 `/init`,AICLI.md 被写到 git 根目录 `D:\xgitee\ai-courses\AICLI.md` 而非 cwd
|
|
@@ -368,8 +406,14 @@ const stdinLines = Array.isArray(rawStdin) ? rawStdin.map(String)
|
|
|
368
406
|
- 新增 `FREE_ROUND_TOOLS = new Set(['write_todos'])`:本轮全部工具调用都属于免费工具时,`round--` 回退计数
|
|
369
407
|
- MAX_TOOL_ROUNDS 从 20 → **25**:更充裕的工具调用空间
|
|
370
408
|
|
|
409
|
+
**Bug 4:AI 在 Windows 上反复使用 Unix 命令导致 bash 工具失败** 🟠
|
|
410
|
+
- **问题**:AI 使用 `date +%Y%m%d`、`grep`、`head`、`find | wc -l` 等 Unix 命令,在 Windows PowerShell 上全部失败,每次浪费 1-2 个工具轮次
|
|
411
|
+
- **根因**:system prompt 中未告知 AI 当前操作系统和 shell 环境,AI 默认使用 Unix 命令
|
|
412
|
+
- **修复**:`buildCurrentSystemPrompt()` 中注入操作系统信息 + shell 类型 + 工作目录。Windows 环境下明确提示"不要使用 Unix 命令,应使用 PowerShell 等效命令",列出常见替代(Select-String/Select-Object -First/Get-ChildItem/Get-Date -Format 等)
|
|
413
|
+
- **文件**:`src/repl/repl.ts` `buildCurrentSystemPrompt()` 新增 `envInfo` 段落
|
|
414
|
+
|
|
371
415
|
### 版本与收尾
|
|
372
|
-
- `src/core/constants.ts`:VERSION `0.1.38` → `0.1.
|
|
416
|
+
- `src/core/constants.ts`:VERSION `0.1.38` → `0.1.40`
|
|
373
417
|
- `package.json`:version 同步
|
|
374
418
|
- 构建验证:`npm run build` 零错误
|
|
375
419
|
|
|
@@ -377,10 +421,10 @@ const stdinLines = Array.isArray(rawStdin) ? rawStdin.map(String)
|
|
|
377
421
|
|
|
378
422
|
| 文件 | 变更类型 | 说明 |
|
|
379
423
|
|------|---------|------|
|
|
380
|
-
| `src/core/constants.ts` | 修改 | VERSION 0.1.38 → 0.1.
|
|
381
|
-
| `src/repl/repl.ts` | 修改 | MAX_TOOL_ROUNDS 20→25 + FREE_ROUND_TOOLS +
|
|
424
|
+
| `src/core/constants.ts` | 修改 | VERSION 0.1.38 → 0.1.40 |
|
|
425
|
+
| `src/repl/repl.ts` | 修改 | MAX_TOOL_ROUNDS 20→25 + FREE_ROUND_TOOLS + 轮次耗尽总结 + OS/shell 信息注入 system prompt |
|
|
382
426
|
| `src/repl/commands/index.ts` | 修改 | /init targetDir 从 gitRoot → cwd |
|
|
383
|
-
| `package.json` | 修改 | version 0.1.38 → 0.1.
|
|
427
|
+
| `package.json` | 修改 | version 0.1.38 → 0.1.40 |
|
|
384
428
|
|
|
385
429
|
### 下一步建议
|
|
386
430
|
1. **`/config set` 快捷配置**:REPL 内直接 `/config set ui.theme light` 无需进入向导
|
package/dist/index.js
CHANGED
|
@@ -29,7 +29,7 @@ import {
|
|
|
29
29
|
SUBAGENT_MAX_ROUNDS_LIMIT,
|
|
30
30
|
VERSION,
|
|
31
31
|
runTestsTool
|
|
32
|
-
} from "./chunk-
|
|
32
|
+
} from "./chunk-TBFCZ6WL.js";
|
|
33
33
|
|
|
34
34
|
// src/index.ts
|
|
35
35
|
import { program } from "commander";
|
|
@@ -1255,7 +1255,25 @@ var KIMI_TOOL_CALL_REMINDER = `
|
|
|
1255
1255
|
\u8C03\u7528\u4EFB\u4F55\u5DE5\u5177\uFF08write_file\u3001bash\u3001read_file \u7B49\uFF09\u65F6\uFF0C\u5FC5\u987B\u4E14\u53EA\u80FD\u4F7F\u7528 API \u7684 function calling \u7ED3\u6784\u5316\u63A5\u53E3\u3002
|
|
1256
1256
|
\u4E25\u7981\u5728\u56DE\u590D\u6587\u672C\u4E2D\u8F93\u51FA XML \u683C\u5F0F\u7684\u4F2A\u5DE5\u5177\u8C03\u7528\u6807\u7B7E\uFF08\u5982 <write_file>\u3001<bash>\u3001<read_file> \u7B49\uFF09\u3002
|
|
1257
1257
|
\u6587\u672C\u4E2D\u7684 XML \u6807\u7B7E\u4E0D\u4F1A\u88AB\u7CFB\u7EDF\u6267\u884C\uFF0C\u4F1A\u5BFC\u81F4\u6587\u4EF6\u672A\u5199\u5165\u3001\u547D\u4EE4\u672A\u8FD0\u884C\uFF0C\u4EFB\u52A1\u5F7B\u5E95\u5931\u8D25\u3002
|
|
1258
|
-
\u6BCF\u6B21\u9700\u8981\u5199\u6587\u4EF6\uFF0C\u5FC5\u987B\u8C03\u7528 write_file \u5DE5\u5177 API\uFF0C\u4E0D\u5141\u8BB8\u4EFB\u4F55\u4F8B\u5916\u3002
|
|
1258
|
+
\u6BCF\u6B21\u9700\u8981\u5199\u6587\u4EF6\uFF0C\u5FC5\u987B\u8C03\u7528 write_file \u5DE5\u5177 API\uFF0C\u4E0D\u5141\u8BB8\u4EFB\u4F55\u4F8B\u5916\u3002
|
|
1259
|
+
\u3010\u26A0\uFE0F \u4E25\u7981\u865A\u5047\u5B8C\u6210\u58F0\u660E\u3011
|
|
1260
|
+
\u7EDD\u5BF9\u4E0D\u5141\u8BB8\u5728\u56DE\u590D\u4E2D\u58F0\u79F0"\u6587\u4EF6\u5DF2\u751F\u6210"\u3001"\u6587\u4EF6\u5DF2\u4FDD\u5B58"\u3001"\u5DF2\u5199\u5165"\u7B49\uFF0C\u800C\u4E0D\u5B9E\u9645\u8C03\u7528 write_file \u5DE5\u5177\u3002
|
|
1261
|
+
\u5982\u679C\u7528\u6237\u8981\u6C42\u521B\u5EFA/\u5199\u5165\u6587\u4EF6\uFF0C\u4F60\u5FC5\u987B\u901A\u8FC7 function calling \u8C03\u7528 write_file \u5DE5\u5177\uFF0C\u7CFB\u7EDF\u624D\u4F1A\u6267\u884C\u5199\u5165\u3002
|
|
1262
|
+
\u4EC5\u5728\u6587\u672C\u4E2D\u63CF\u8FF0\u6587\u4EF6\u5185\u5BB9\u800C\u4E0D\u8C03\u7528\u5DE5\u5177 = \u6587\u4EF6\u4E0D\u5B58\u5728 = \u4EFB\u52A1\u5931\u8D25\u3002`;
|
|
1263
|
+
var HALLUCINATION_PATTERNS = [
|
|
1264
|
+
/文件路径[::]\s*`?[^\s`]+/,
|
|
1265
|
+
// 文件路径: `path/to/file`
|
|
1266
|
+
/已生成[::!!]/,
|
|
1267
|
+
// 已生成完成!
|
|
1268
|
+
/已保存到/,
|
|
1269
|
+
// 已保存到指定路径
|
|
1270
|
+
/已写入[::!!]/,
|
|
1271
|
+
// 已写入!
|
|
1272
|
+
/File\s+(?:written|saved|created)/i,
|
|
1273
|
+
// File written / saved / created
|
|
1274
|
+
/生成完成[!!]/
|
|
1275
|
+
// 生成完成!
|
|
1276
|
+
];
|
|
1259
1277
|
var KimiProvider = class extends OpenAICompatibleProvider {
|
|
1260
1278
|
defaultBaseUrl = "https://api.moonshot.ai/v1";
|
|
1261
1279
|
info = {
|
|
@@ -1320,14 +1338,17 @@ var KimiProvider = class extends OpenAICompatibleProvider {
|
|
|
1320
1338
|
]
|
|
1321
1339
|
};
|
|
1322
1340
|
/**
|
|
1323
|
-
* 覆写 chatWithTools
|
|
1341
|
+
* 覆写 chatWithTools,叠加三道防线:
|
|
1324
1342
|
*
|
|
1325
1343
|
* 方案 B(预防):在 system prompt 末尾追加工具调用规范提示,
|
|
1326
|
-
* 指示 Kimi 必须使用 API function calling,禁止输出 XML
|
|
1344
|
+
* 指示 Kimi 必须使用 API function calling,禁止输出 XML 伪工具调用,
|
|
1345
|
+
* 禁止虚假完成声明。
|
|
1346
|
+
*
|
|
1347
|
+
* 方案 A(兜底 1):若 Kimi 在文本中输出了 XML 格式工具调用,
|
|
1348
|
+
* 自动检测并解析,转换为真实 ToolCall 对象送入执行器。
|
|
1327
1349
|
*
|
|
1328
|
-
* 方案
|
|
1329
|
-
*
|
|
1330
|
-
* 确保文件写入等操作不会静默丢失。
|
|
1350
|
+
* 方案 C(兜底 2):若 Kimi 在文本中声称已完成文件操作但未调用任何工具,
|
|
1351
|
+
* 自动检测并注入纠正消息,重新请求一次,强制 Kimi 实际调用工具。
|
|
1331
1352
|
*/
|
|
1332
1353
|
async chatWithTools(request, tools) {
|
|
1333
1354
|
const enhancedRequest = {
|
|
@@ -1344,9 +1365,63 @@ var KimiProvider = class extends OpenAICompatibleProvider {
|
|
|
1344
1365
|
);
|
|
1345
1366
|
return { toolCalls: xmlToolCalls, usage: result.usage };
|
|
1346
1367
|
}
|
|
1368
|
+
const hasWriteTools = tools.some((t) => t.name === "write_file" || t.name === "edit_file");
|
|
1369
|
+
if (hasWriteTools && this.detectsHallucinatedFileOp(result.content)) {
|
|
1370
|
+
process.stderr.write(
|
|
1371
|
+
`[kimi] \u26A0 \u68C0\u6D4B\u5230\u865A\u5047\u5B8C\u6210\u58F0\u660E\uFF08AI \u58F0\u79F0\u5DF2\u5199\u5165\u6587\u4EF6\u4F46\u672A\u8C03\u7528\u5DE5\u5177\uFF09\uFF0C\u6B63\u5728\u5F3A\u5236\u91CD\u65B0\u8BF7\u6C42...
|
|
1372
|
+
`
|
|
1373
|
+
);
|
|
1374
|
+
const existingExtra = enhancedRequest._extraMessages ?? [];
|
|
1375
|
+
const correctionRequest = {
|
|
1376
|
+
...enhancedRequest,
|
|
1377
|
+
_extraMessages: [
|
|
1378
|
+
...existingExtra,
|
|
1379
|
+
{ role: "assistant", content: result.content },
|
|
1380
|
+
{
|
|
1381
|
+
role: "user",
|
|
1382
|
+
content: "\u4F60\u521A\u624D\u6CA1\u6709\u5B9E\u9645\u8C03\u7528 write_file \u5DE5\u5177\uFF0C\u6587\u4EF6\u5E76\u672A\u88AB\u521B\u5EFA\uFF01\u8BF7\u7ACB\u5373\u4F7F\u7528 write_file \u5DE5\u5177\u7684 function calling API \u6267\u884C\u5B9E\u9645\u7684\u6587\u4EF6\u5199\u5165\u64CD\u4F5C\u3002\u4E0D\u8981\u518D\u7528\u6587\u5B57\u63CF\u8FF0\u6587\u4EF6\u5185\u5BB9\uFF0C\u5FC5\u987B\u901A\u8FC7 API \u7684 tool_calls \u673A\u5236\u8C03\u7528 write_file\u3002"
|
|
1383
|
+
}
|
|
1384
|
+
]
|
|
1385
|
+
};
|
|
1386
|
+
const retryResult = await super.chatWithTools(correctionRequest, tools);
|
|
1387
|
+
if ("content" in retryResult && retryResult.content) {
|
|
1388
|
+
const retryXml = this.parseXmlToolCalls(retryResult.content, tools);
|
|
1389
|
+
if (retryXml.length > 0) {
|
|
1390
|
+
process.stderr.write(
|
|
1391
|
+
`[kimi] \u26A0 \u91CD\u8BD5\u540E\u68C0\u6D4B\u5230 ${retryXml.length} \u4E2A XML \u4F2A\u5DE5\u5177\u8C03\u7528\uFF0C\u5DF2\u8F6C\u6362\u6267\u884C\u3002
|
|
1392
|
+
`
|
|
1393
|
+
);
|
|
1394
|
+
const mergedUsage = this.mergeUsage(result.usage, retryResult.usage);
|
|
1395
|
+
return { toolCalls: retryXml, usage: mergedUsage };
|
|
1396
|
+
}
|
|
1397
|
+
}
|
|
1398
|
+
if ("toolCalls" in retryResult && retryResult.usage && result.usage) {
|
|
1399
|
+
retryResult.usage = this.mergeUsage(result.usage, retryResult.usage);
|
|
1400
|
+
}
|
|
1401
|
+
return retryResult;
|
|
1402
|
+
}
|
|
1347
1403
|
}
|
|
1348
1404
|
return result;
|
|
1349
1405
|
}
|
|
1406
|
+
/**
|
|
1407
|
+
* 方案 C 辅助:检测 AI 文本响应中是否存在虚假的文件操作完成声明。
|
|
1408
|
+
*
|
|
1409
|
+
* 当 AI 回复中包含"文件路径: xxx"、"已生成完成!"等模式时,
|
|
1410
|
+
* 且当前工具列表中包含写文件工具,说明 AI 在虚假声称已完成操作。
|
|
1411
|
+
*/
|
|
1412
|
+
detectsHallucinatedFileOp(content) {
|
|
1413
|
+
return HALLUCINATION_PATTERNS.some((pattern) => pattern.test(content));
|
|
1414
|
+
}
|
|
1415
|
+
/**
|
|
1416
|
+
* 合并两次 API 调用的 token 用量
|
|
1417
|
+
*/
|
|
1418
|
+
mergeUsage(a, b) {
|
|
1419
|
+
if (!a && !b) return void 0;
|
|
1420
|
+
return {
|
|
1421
|
+
inputTokens: (a?.inputTokens ?? 0) + (b?.inputTokens ?? 0),
|
|
1422
|
+
outputTokens: (a?.outputTokens ?? 0) + (b?.outputTokens ?? 0)
|
|
1423
|
+
};
|
|
1424
|
+
}
|
|
1350
1425
|
/**
|
|
1351
1426
|
* 方案 A 核心逻辑:从 Kimi 的文本响应中提取 XML 格式伪工具调用。
|
|
1352
1427
|
*
|
|
@@ -3711,7 +3786,7 @@ ${hint}` : "")
|
|
|
3711
3786
|
description: "Run project tests and show structured report",
|
|
3712
3787
|
usage: "/test [command|filter]",
|
|
3713
3788
|
async execute(args, _ctx) {
|
|
3714
|
-
const { executeTests } = await import("./run-tests-
|
|
3789
|
+
const { executeTests } = await import("./run-tests-S54Z3S5Q.js");
|
|
3715
3790
|
const argStr = args.join(" ").trim();
|
|
3716
3791
|
let testArgs = {};
|
|
3717
3792
|
if (argStr) {
|
|
@@ -8376,7 +8451,12 @@ ${projectContext}`);
|
|
|
8376
8451
|
const dateStr = `${now.getFullYear()}\u5E74${pad(now.getMonth() + 1)}\u6708${pad(now.getDate())}\u65E5 ${WEEKDAYS[now.getDay()]}`;
|
|
8377
8452
|
const timeStr = `${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())}`;
|
|
8378
8453
|
const dateTimeInfo = `\u5F53\u524D\u65E5\u671F\u65F6\u95F4\uFF1A${dateStr} ${timeStr}`;
|
|
8379
|
-
const
|
|
8454
|
+
const osName = process.platform === "win32" ? "Windows" : process.platform === "darwin" ? "macOS" : "Linux";
|
|
8455
|
+
const shellInfo = process.platform === "win32" ? "bash \u5DE5\u5177\u4F7F\u7528 PowerShell\uFF08\u975E cmd\uFF09\uFF0C\u8BF7\u4F7F\u7528 PowerShell \u547D\u4EE4\u8BED\u6CD5\u3002\u4E0D\u8981\u4F7F\u7528 Unix \u547D\u4EE4\uFF08\u5982 grep\u3001head\u3001find\u3001wc\u3001date +\u683C\u5F0F \u7B49\uFF09\uFF0C\u5E94\u4F7F\u7528 PowerShell \u7B49\u6548\u547D\u4EE4\uFF08\u5982 Select-String\u3001Select-Object -First\u3001Get-ChildItem\u3001Measure-Object\u3001Get-Date -Format \u7B49\uFF09\u3002" : `bash \u5DE5\u5177\u4F7F\u7528 ${process.env.SHELL || "/bin/bash"}\u3002`;
|
|
8456
|
+
const envInfo = `\u64CD\u4F5C\u7CFB\u7EDF\uFF1A${osName}
|
|
8457
|
+
${shellInfo}
|
|
8458
|
+
\u5DE5\u4F5C\u76EE\u5F55\uFF1A${process.cwd()}`;
|
|
8459
|
+
const parts = [dateTimeInfo + "\n" + envInfo];
|
|
8380
8460
|
const memory = this.loadMemoryContent();
|
|
8381
8461
|
if (memory) {
|
|
8382
8462
|
parts.push(`# Persistent Memory
|