jinzd-ai-cli 0.1.40 → 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,6 +348,44 @@ 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.40 → v0.1.41)
352
+
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
+
351
389
  ## 本轮开发完成记录(2026-03-07,v0.1.38 → v0.1.40)
352
390
 
353
391
  ### Bug 修复(3 个使用中发现的问题 + 1 个系统改进)
@@ -8,7 +8,7 @@ import { platform } from "os";
8
8
  import chalk from "chalk";
9
9
 
10
10
  // src/core/constants.ts
11
- var VERSION = "0.1.40";
11
+ var VERSION = "0.1.41";
12
12
  var APP_NAME = "ai-cli";
13
13
  var CONFIG_DIR_NAME = ".aicli";
14
14
  var CONFIG_FILE_NAME = "config.json";
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-5XTQNTUG.js";
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
- * 方案 A(兜底):若 Kimi 仍然在文本中输出了 XML 格式工具调用,
1329
- * 自动检测并解析,转换为真实 ToolCall 对象送入执行器,
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-COTUXAOM.js");
3789
+ const { executeTests } = await import("./run-tests-S54Z3S5Q.js");
3715
3790
  const argStr = args.join(" ").trim();
3716
3791
  let testArgs = {};
3717
3792
  if (argStr) {
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  executeTests,
4
4
  runTestsTool
5
- } from "./chunk-5XTQNTUG.js";
5
+ } from "./chunk-TBFCZ6WL.js";
6
6
  export {
7
7
  executeTests,
8
8
  runTestsTool
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jinzd-ai-cli",
3
- "version": "0.1.40",
3
+ "version": "0.1.41",
4
4
  "description": "Cross-platform REPL-style AI CLI with multi-provider support",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",