jinzd-ai-cli 0.4.273 → 0.4.276

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.
Files changed (103) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +610 -610
  3. package/README.zh-CN.md +559 -559
  4. package/dist/agent-client-6GX6QQDU.js +0 -0
  5. package/dist/auth-PE3Z5OHS.js +0 -0
  6. package/dist/{batch-LOW3GM26.js → batch-F7SKIBI3.js} +2 -2
  7. package/dist/chat-index-2D5GK2DB.js +0 -0
  8. package/dist/chunk-23SJVOQD.js +0 -0
  9. package/dist/chunk-3FEZSLGI.js +0 -0
  10. package/dist/chunk-4BKXL7SM.js +0 -0
  11. package/dist/{chunk-AUEX6RNY.js → chunk-4KTK2OYU.js} +54 -21
  12. package/dist/{chunk-M3NXF4GD.js → chunk-4NH4Q7QK.js} +1 -1
  13. package/dist/chunk-5C76XDDF.js +0 -0
  14. package/dist/chunk-6YZEZFX5.js +0 -0
  15. package/dist/{chunk-FHXLAJJG.js → chunk-AOISQVXS.js} +1 -1
  16. package/dist/{chunk-FNLWPYP5.js → chunk-ATD2Y3PB.js} +1 -1
  17. package/dist/chunk-BXP6YZ2P.js +0 -0
  18. package/dist/chunk-CGDYO52A.js +0 -0
  19. package/dist/chunk-CKH4KQ4E.js +0 -0
  20. package/dist/{chunk-JT6UN5C6.js → chunk-DPTEC6AQ.js} +3 -0
  21. package/dist/{chunk-EDAUDILT.js → chunk-FLDYAXJR.js} +1 -1
  22. package/dist/chunk-GHWZVPUV.js +0 -0
  23. package/dist/chunk-GLSR2NR3.js +0 -0
  24. package/dist/chunk-HLWUDRBO.js +0 -0
  25. package/dist/chunk-HOSJZMQS.js +0 -0
  26. package/dist/chunk-IW3Q7AE5.js +0 -0
  27. package/dist/chunk-KHYD3WXE.js +0 -0
  28. package/dist/{chunk-Z3UXZ5EI.js → chunk-KWSGSSWN.js} +1 -1
  29. package/dist/{chunk-4FIDMC2K.js → chunk-MCKUGWOY.js} +69 -4
  30. package/dist/chunk-MX75TNE5.js +0 -0
  31. package/dist/{chunk-FN7DE4HA.js → chunk-MXPUOF5K.js} +4 -4
  32. package/dist/chunk-NR5U6LVG.js +0 -0
  33. package/dist/{chunk-ZWCQQZT5.js → chunk-NXAHXWPF.js} +1 -1
  34. package/dist/{chunk-R4YGDJPL.js → chunk-NXNXWM6H.js} +5 -5
  35. package/dist/chunk-NYNSYLFY.js +0 -0
  36. package/dist/{chunk-WSCZRQDR.js → chunk-OMBKLWLJ.js} +1 -1
  37. package/dist/chunk-RYQBBEMM.js +0 -0
  38. package/dist/{chunk-3OBY7WSS.js → chunk-TEXT4DAT.js} +7 -1
  39. package/dist/chunk-TU3L3PW7.js +0 -0
  40. package/dist/{chunk-LEK5WGKV.js → chunk-UNL7NR55.js} +1 -1
  41. package/dist/chunk-UUSRWSSX.js +0 -0
  42. package/dist/{chunk-T65VFCID.js → chunk-VF7DMSKF.js} +1 -1
  43. package/dist/chunk-VOIOUJXI.js +0 -0
  44. package/dist/{chunk-MTQBSOZW.js → chunk-WYDP2WEQ.js} +4 -4
  45. package/dist/chunk-XNFIYS7G.js +0 -0
  46. package/dist/{ci-C5YMQXCS.js → ci-VURFRMWQ.js} +6 -6
  47. package/dist/{ci-format-R6EMNOGZ.js → ci-format-AFJJIURF.js} +2 -2
  48. package/dist/config-command-E5DHADEV.js +0 -0
  49. package/dist/{constants-QJCNMBPJ.js → constants-WO7ERENL.js} +1 -1
  50. package/dist/{doctor-cli-5CM2I6OF.js → doctor-cli-BGGILTHE.js} +6 -6
  51. package/dist/electron-server.js +167 -57
  52. package/dist/file-checkpoint-NKBHGC7L.js +0 -0
  53. package/dist/git-context-EXOEHQSF.js +0 -0
  54. package/dist/{hub-7JBSZOSD.js → hub-Z73RLNJI.js} +1 -1
  55. package/dist/hub-server-RF6LURXY.js +0 -0
  56. package/dist/index.js +57 -43
  57. package/dist/indexer-VZETNE4H.js +0 -0
  58. package/dist/persist-PQM4BRI4.js +0 -0
  59. package/dist/{persistent-memory-4E53HW3U.js → persistent-memory-5QMARQJF.js} +2 -2
  60. package/dist/{persistent-memory-KYRY56PG.js → persistent-memory-CI544RLT.js} +2 -2
  61. package/dist/{pr-NPUGZSXB.js → pr-24REDMGV.js} +6 -6
  62. package/dist/project-trust-NKYHL3VZ.js +0 -0
  63. package/dist/{run-tests-5FHQ5IBX.js → run-tests-72VNZXNT.js} +2 -2
  64. package/dist/{run-tests-SU5CDQKF.js → run-tests-JJ3KOY5E.js} +2 -2
  65. package/dist/semantic-HLAE2O4F.js +0 -0
  66. package/dist/{server-ER36M7TR.js → server-JMXC2N3F.js} +40 -29
  67. package/dist/{server-77YS5NNN.js → server-QJHCJDA7.js} +6 -6
  68. package/dist/setup-wizard-S55ICNHH.js +0 -0
  69. package/dist/store-MWNHVGJT.js +0 -0
  70. package/dist/{task-orchestrator-56PQ7LGV.js → task-orchestrator-DJN36HP5.js} +6 -6
  71. package/dist/{usage-3YATXRFZ.js → usage-HXJVDETY.js} +3 -3
  72. package/dist/vector-store-BBDXB5IQ.js +0 -0
  73. package/dist/wasm/web-tree-sitter.wasm +0 -0
  74. package/dist/web/client/actions.js +104 -104
  75. package/dist/web/client/actions.js.br +0 -0
  76. package/dist/web/client/actions.js.gz +0 -0
  77. package/dist/web/client/api.js.gz +0 -0
  78. package/dist/web/client/app.js.gz +0 -0
  79. package/dist/web/client/dom.js.gz +0 -0
  80. package/dist/web/client/icon.svg +22 -22
  81. package/dist/web/client/icon.svg.br +0 -0
  82. package/dist/web/client/icon.svg.gz +0 -0
  83. package/dist/web/client/index.html +388 -388
  84. package/dist/web/client/index.html.br +0 -0
  85. package/dist/web/client/index.html.gz +0 -0
  86. package/dist/web/client/manifest.json +15 -15
  87. package/dist/web/client/sidebar-tabs.js.gz +0 -0
  88. package/dist/web/client/state.js.gz +0 -0
  89. package/dist/web/client/style.css +991 -991
  90. package/dist/web/client/style.css.br +0 -0
  91. package/dist/web/client/style.css.gz +0 -0
  92. package/dist/web/client/sw.js +85 -85
  93. package/dist/web/client/sw.js.br +0 -0
  94. package/dist/web/client/sw.js.gz +0 -0
  95. package/dist/web/client/templates.js.gz +0 -0
  96. package/dist/web/client/util.js.gz +0 -0
  97. package/dist/web/client/vendor/daisyui-full.min.css.gz +0 -0
  98. package/dist/web/client/vendor/github-dark.min.css.gz +0 -0
  99. package/dist/web/client/vendor/github.min.css.gz +0 -0
  100. package/dist/web/client/vendor/highlight.min.js.gz +0 -0
  101. package/dist/web/client/vendor/marked.min.js.gz +0 -0
  102. package/dist/web/client/vendor/tailwind.js.gz +0 -0
  103. package/package.json +178 -178
@@ -40,14 +40,14 @@ import {
40
40
  touchMemoryReferences,
41
41
  updateMemoryApproval,
42
42
  updateMemoryEntry
43
- } from "./chunk-M3NXF4GD.js";
43
+ } from "./chunk-4NH4Q7QK.js";
44
44
  import {
45
45
  redactJson,
46
46
  scanString
47
47
  } from "./chunk-FSC6KEWU.js";
48
48
  import {
49
49
  runTestsTool
50
- } from "./chunk-FNLWPYP5.js";
50
+ } from "./chunk-ATD2Y3PB.js";
51
51
  import {
52
52
  AGENTIC_BEHAVIOR_GUIDELINE,
53
53
  APP_NAME,
@@ -80,7 +80,7 @@ import {
80
80
  SUBAGENT_MAX_ROUNDS_LIMIT,
81
81
  VERSION,
82
82
  buildUserIdentityPrompt
83
- } from "./chunk-LEK5WGKV.js";
83
+ } from "./chunk-UNL7NR55.js";
84
84
  import {
85
85
  hasSemanticIndex,
86
86
  semanticSearch
@@ -99,7 +99,7 @@ import express from "express";
99
99
  import { createServer } from "http";
100
100
  import { WebSocketServer } from "ws";
101
101
  import { join as join26, dirname as dirname10, resolve as resolve11, relative as relative6, sep as sep5, extname as extname3, resolve as resolvePath } from "path";
102
- import { existsSync as existsSync31, readFileSync as readFileSync25, readdirSync as readdirSync15, statSync as statSync14, realpathSync as realpathSync3, createReadStream } from "fs";
102
+ import { existsSync as existsSync31, readFileSync as readFileSync25, readdirSync as readdirSync15, statSync as statSync15, realpathSync as realpathSync3, createReadStream } from "fs";
103
103
  import { networkInterfaces } from "os";
104
104
 
105
105
  // src/config/config-manager.ts
@@ -817,6 +817,9 @@ var PRICING_TABLE = {
817
817
  "glm-4.6": { input: 0.6, output: 2.2 },
818
818
  "glm-4.6v": { input: 0.6, output: 2.2 },
819
819
  "glm-5": { input: 0.85, output: 2.85 },
820
+ // GLM-5.3(2026-08-14 发布)官方公告口径「与 GLM-5.2 同价」,直连 API $1.40 / $4.40。
821
+ // 注意它**始终思考**,思考 token 按输出价计费 —— 实际成本高度取决于 reasoning_effort。
822
+ "glm-5.3": { input: 1.4, output: 4.4 },
820
823
  // GLM-5.2(2026-06)官方直连 API 定价 $1.40 / $4.40。
821
824
  "glm-5.2": { input: 1.4, output: 4.4 },
822
825
  // GLM-5V-Turbo(2026-04 视觉编程旗舰)官方直连 API 定价 $1.20 / $4.00。
@@ -2407,12 +2410,12 @@ function findPhantomClaims(content, extraMessages) {
2407
2410
  const claimed = extractClaimedFilePaths(content);
2408
2411
  if (claimed.length === 0) return [];
2409
2412
  const normalize = (p) => p.replace(/\\/g, "/").toLowerCase().replace(/^\.\//, "");
2410
- const basename8 = (p) => {
2413
+ const basename9 = (p) => {
2411
2414
  const parts = normalize(p).split("/");
2412
2415
  return parts[parts.length - 1] ?? "";
2413
2416
  };
2414
2417
  const written = extractWrittenFilePaths(extraMessages).map(normalize);
2415
- const writtenBases = new Set(written.map(basename8));
2418
+ const writtenBases = new Set(written.map(basename9));
2416
2419
  const writtenFull = new Set(written);
2417
2420
  const bashText = extractBashCommands(extraMessages).map((c) => c.replace(/\\/g, "/").toLowerCase()).join("\n");
2418
2421
  return claimed.filter((raw) => {
@@ -2421,8 +2424,8 @@ function findPhantomClaims(content, extraMessages) {
2421
2424
  for (const w of writtenFull) {
2422
2425
  if (w.endsWith("/" + norm2) || norm2.endsWith("/" + w)) return false;
2423
2426
  }
2424
- if (writtenBases.has(basename8(norm2))) return false;
2425
- const base = basename8(norm2);
2427
+ if (writtenBases.has(basename9(norm2))) return false;
2428
+ const base = basename9(norm2);
2426
2429
  if (base && bashText.includes(base)) return false;
2427
2430
  return true;
2428
2431
  });
@@ -2578,6 +2581,11 @@ var META_NARRATION_HEURISTICS = [
2578
2581
  /(?:需要你|请你|麻烦你|还要你|你看)\s*(?:确认|决定|选择|帮忙|看[一看下]|过目)/u,
2579
2582
  /(?:总结|概括|归纳|整理)[一下]?(?:核心|主要|关键)?(?:思路|要点|内容|设计)/u,
2580
2583
  /(?:随时|有问题|有需要|需要调整|需要修改|需要补充)\s*(?:联系|找我|告诉|沟通|调整|修改|补充|说)/u,
2584
+ // 「已(全部)生成并保存到 `path`」——对**文件**的汇报,不是文件的正文。
2585
+ // v0.4.274 实战:deferred 赛道的最终回复是一段「4 份教案已全部生成并保存到
2586
+ // `./精简版/` 目录下」的汇报表格,旧启发式只命中 1 条不足阈值,于是它被当文档
2587
+ // 正文落盘、覆盖掉模型刚用 write_file 写好的教案。仍受 2 hits 阈值与标题逃逸约束。
2588
+ /(?:已|均已|都已)[^\n]{0,10}(?:生成|写入|保存|创建)[^\n]{0,6}(?:到|至|于)\s*[`'"]/u,
2581
2589
  /^(?:.{0,40})(?:好的[,,,!\s]|嗯[,,,!\s]|收到[了]?[,,,!\s]|了解[了]?[,,,!\s]|明白[了]?[,,,!\s])/mu,
2582
2590
  /(?:我来|让我|容我)\s*(?:整理|梳理|归纳|想想|看看|重新|检查|核实)/u,
2583
2591
  // 过渡语(需要 2+ hits):"以下是:" / "如下:" 作为独立行。文档中可作小节标题,
@@ -2723,7 +2731,8 @@ function buildDeferredSaveInstruction(saveToFile) {
2723
2731
  return `[save_last_response \u65E0\u6CD5\u81EA\u52A8\u751F\u6210\u6587\u6863]
2724
2732
  content-only \u6D41\u4EA7\u51FA\u7684\u662F\u5DE5\u5177\u8C03\u7528\u6807\u8BB0 / \u5185\u5FC3\u72EC\u767D\u800C\u975E\u6587\u6863\u6B63\u6587\uFF0C${saveToFile} \u672A\u4FDD\u5B58\u3002
2725
2733
 
2726
- \u4E0D\u8981\u518D\u8C03\u7528 save_last_response\u3002\u8BF7\u76F4\u63A5\u7528**\u666E\u901A\u6587\u672C\u56DE\u590D**\u628A\u5B8C\u6574\u6587\u6863\u5199\u51FA\u6765\uFF1A\u7EAF markdown\uFF0C\u7B2C\u4E00\u4E2A\u5B57\u7B26\u5C31\u662F\u9876\u7EA7\u6807\u9898\uFF08\u5982 "# \u5BA1\u8BA1\u62A5\u544A"\uFF09\uFF0C\u4E0D\u8981\u4EFB\u4F55\u5DE5\u5177\u8C03\u7528\u3001\u4E0D\u8981\u524D\u8A00\u94FA\u57AB\u3002\u7CFB\u7EDF\u4F1A\u81EA\u52A8\u628A\u4F60\u8FD9\u6761\u56DE\u590D\u4FDD\u5B58\u5230 ${saveToFile}\u3002`;
2734
+ \u4E0D\u8981\u518D\u8C03\u7528 save_last_response\u3002\u8BF7\u76F4\u63A5\u7528**\u666E\u901A\u6587\u672C\u56DE\u590D**\u628A\u5B8C\u6574\u6587\u6863\u5199\u51FA\u6765\uFF1A\u7EAF markdown\uFF0C\u7B2C\u4E00\u4E2A\u5B57\u7B26\u5C31\u662F\u9876\u7EA7\u6807\u9898\uFF08\u5982 "# \u5BA1\u8BA1\u62A5\u544A"\uFF09\uFF0C\u4E0D\u8981\u4EFB\u4F55\u5DE5\u5177\u8C03\u7528\u3001\u4E0D\u8981\u524D\u8A00\u94FA\u57AB\u3002\u7CFB\u7EDF\u4F1A\u81EA\u52A8\u628A\u4F60\u8FD9\u6761\u56DE\u590D\u4FDD\u5B58\u5230 ${saveToFile}\u3002
2735
+ \u82E5\u4F60\u6539\u7528 write_file \u7B49\u5DE5\u5177\u76F4\u63A5\u628A\u6587\u4EF6\u5199\u5B8C\uFF08\u591A\u6587\u4EF6\u4EFB\u52A1\u4E0B\u66F4\u5408\u9002\uFF09\uFF0C\u4E5F\u53EF\u4EE5\u2014\u2014\u7CFB\u7EDF\u68C0\u6D4B\u5230 ${saveToFile} \u5DF2\u7531\u5DE5\u5177\u5199\u51FA\u65F6\uFF0C\u4E0D\u4F1A\u518D\u7528\u4F60\u7684\u56DE\u590D\u8986\u76D6\u5B83\u3002`;
2727
2736
  }
2728
2737
  function teeStreamErrorSummary(saveToFile, errMsg) {
2729
2738
  return `[save_last_response failed] streaming was interrupted: ${errMsg}. ${saveToFile} (partial) was deleted. Retry \u2014 and consider producing a more compact output (split very large reports across multiple save_last_response calls if the previous attempt timed out).`;
@@ -3914,25 +3923,6 @@ var DeepSeekProvider = class extends OpenAICompatibleProvider {
3914
3923
  }
3915
3924
  };
3916
3925
 
3917
- // src/providers/static-config.ts
3918
- function createStaticProviderClass(config) {
3919
- return class extends OpenAICompatibleProvider {
3920
- defaultBaseUrl = config.baseUrl;
3921
- defaultTimeout = config.defaultTimeout ?? 12e4;
3922
- enableStreamingToolCalls = config.enableStreamingToolCalls ?? true;
3923
- reliableContentOnlyTee = config.reliableContentOnlyTee ?? true;
3924
- info = {
3925
- id: config.id,
3926
- displayName: config.displayName,
3927
- defaultModel: config.defaultModel,
3928
- apiKeyEnvVar: config.apiKeyEnvVar,
3929
- requiresApiKey: config.requiresApiKey ?? true,
3930
- baseUrl: config.baseUrl,
3931
- models: config.models
3932
- };
3933
- };
3934
- }
3935
-
3936
3926
  // src/providers/zhipu.ts
3937
3927
  var ZHIPU_CONFIG = {
3938
3928
  id: "zhipu",
@@ -3942,6 +3932,7 @@ var ZHIPU_CONFIG = {
3942
3932
  baseUrl: "https://open.bigmodel.cn/api/paas/v4",
3943
3933
  defaultTimeout: 3e5,
3944
3934
  models: [
3935
+ { id: "glm-5.3", displayName: "GLM-5.3 (2026-08 Flagship, 1M, Coding/Security, \u59CB\u7EC8\u601D\u8003)", contextWindow: 1048576, supportsStreaming: true, supportsThinking: true },
3945
3936
  { id: "glm-5.2", displayName: "GLM-5.2 (2026-06 Flagship, 1M, Coding)", contextWindow: 1048576, supportsStreaming: true, supportsThinking: true },
3946
3937
  { id: "glm-5v-turbo", displayName: "GLM-5V-Turbo (Multimodal Vision + Coding)", contextWindow: 202752, supportsStreaming: true },
3947
3938
  { id: "glm-5.1", displayName: "GLM-5.1 (2026 Flagship, 200K, Agent+Code)", contextWindow: 204800, supportsStreaming: true, supportsThinking: true },
@@ -3951,7 +3942,39 @@ var ZHIPU_CONFIG = {
3951
3942
  { id: "glm-4.6", displayName: "GLM-4.6 (200K, \u4E2D\u6587\u5199\u4F5C\u63A8\u8350)", contextWindow: 204800, supportsStreaming: true }
3952
3943
  ]
3953
3944
  };
3954
- var ZhipuProvider = createStaticProviderClass(ZHIPU_CONFIG);
3945
+ var REASONING_EFFORT_MODELS = /^glm-5/i;
3946
+ var ZhipuProvider = class extends OpenAICompatibleProvider {
3947
+ defaultBaseUrl = ZHIPU_CONFIG.baseUrl;
3948
+ /** GLM-5 系列深度思考生成很长,沿用 300s 超时。 */
3949
+ defaultTimeout = ZHIPU_CONFIG.defaultTimeout ?? 12e4;
3950
+ info = {
3951
+ id: ZHIPU_CONFIG.id,
3952
+ displayName: ZHIPU_CONFIG.displayName,
3953
+ defaultModel: ZHIPU_CONFIG.defaultModel,
3954
+ apiKeyEnvVar: ZHIPU_CONFIG.apiKeyEnvVar,
3955
+ requiresApiKey: true,
3956
+ baseUrl: ZHIPU_CONFIG.baseUrl,
3957
+ models: ZHIPU_CONFIG.models
3958
+ };
3959
+ /**
3960
+ * GLM-5 系列透传 `reasoning_effort`。
3961
+ *
3962
+ * 刻意**不**与 `request.thinking` 绑定:GLM-5.3 无论发不发 thinking 字段都会思考,
3963
+ * 档位是这个模型上唯一能压成本的旋钮(思考 token 按输出价 $4.40/MTok 计费),
3964
+ * 若跟着 `thinking` 走,用户 `/think off` 反而会落回服务端默认的 max —— 最贵的一档。
3965
+ */
3966
+ buildChatRequestParams(request) {
3967
+ const params = super.buildChatRequestParams(request);
3968
+ if (request.thinkingEffort && REASONING_EFFORT_MODELS.test(request.model)) {
3969
+ params["reasoning_effort"] = request.thinkingEffort;
3970
+ }
3971
+ return params;
3972
+ }
3973
+ /** GLM-5 系列支持 low/high/max 三档(官方文档 + 2026-08-20 实测)。 */
3974
+ get supportsReasoningEffort() {
3975
+ return true;
3976
+ }
3977
+ };
3955
3978
 
3956
3979
  // src/providers/kimi.ts
3957
3980
  var KIMI_XML_REMINDER = `
@@ -4130,6 +4153,25 @@ var KimiProvider = class _KimiProvider extends OpenAICompatibleProvider {
4130
4153
  }
4131
4154
  };
4132
4155
 
4156
+ // src/providers/static-config.ts
4157
+ function createStaticProviderClass(config) {
4158
+ return class extends OpenAICompatibleProvider {
4159
+ defaultBaseUrl = config.baseUrl;
4160
+ defaultTimeout = config.defaultTimeout ?? 12e4;
4161
+ enableStreamingToolCalls = config.enableStreamingToolCalls ?? true;
4162
+ reliableContentOnlyTee = config.reliableContentOnlyTee ?? true;
4163
+ info = {
4164
+ id: config.id,
4165
+ displayName: config.displayName,
4166
+ defaultModel: config.defaultModel,
4167
+ apiKeyEnvVar: config.apiKeyEnvVar,
4168
+ requiresApiKey: config.requiresApiKey ?? true,
4169
+ baseUrl: config.baseUrl,
4170
+ models: config.models
4171
+ };
4172
+ };
4173
+ }
4174
+
4133
4175
  // src/providers/minimax.ts
4134
4176
  var MINIMAX_CONFIG = {
4135
4177
  id: "minimax",
@@ -14453,12 +14495,70 @@ function loadContextFiles(options) {
14453
14495
  };
14454
14496
  }
14455
14497
 
14498
+ // src/tools/deferred-save.ts
14499
+ import { statSync as statSync10 } from "fs";
14500
+ import { basename as basename8 } from "path";
14501
+ function snapshotFile(path3) {
14502
+ try {
14503
+ const st = statSync10(path3);
14504
+ return { exists: true, size: st.size, mtimeMs: st.mtimeMs };
14505
+ } catch {
14506
+ return { exists: false, size: 0, mtimeMs: 0 };
14507
+ }
14508
+ }
14509
+ function armDeferredSave(path3) {
14510
+ return { path: path3, armedSnapshot: snapshotFile(path3) };
14511
+ }
14512
+ function snapshotChanged(a, b) {
14513
+ return a.exists !== b.exists || a.size !== b.size || a.mtimeMs !== b.mtimeMs;
14514
+ }
14515
+ var SAVE_CLAIM_PATTERNS = [
14516
+ /(?:已|均已|都已)[^\n]{0,12}(?:保存|写入|生成|创建|落盘)/u,
14517
+ /(?:保存|写入|生成|创建)(?:到|至|于)\s*[`'"]/u,
14518
+ /\b(?:saved|written|created|generated)\s+(?:to|at|as|into)\b/i
14519
+ ];
14520
+ function narratesSavedTarget(content, path3) {
14521
+ const base = basename8(path3);
14522
+ if (!base || !content.includes(base)) return null;
14523
+ for (const re of SAVE_CLAIM_PATTERNS) {
14524
+ if (re.test(content)) return re.source;
14525
+ }
14526
+ return null;
14527
+ }
14528
+ function decideDeferredSave(params) {
14529
+ const { content, path: path3, armedSnapshot, currentSnapshot } = params;
14530
+ const body = stripOuterCodeFence(content);
14531
+ if (snapshotChanged(armedSnapshot, currentSnapshot)) {
14532
+ return {
14533
+ kind: "skip",
14534
+ reason: "target-already-written",
14535
+ message: `\u21AA \u8DF3\u8FC7 deferred \u843D\u76D8\uFF1A${path3} \u5DF2\u5728\u672C\u8F6E\u88AB\u5DE5\u5177\u5199\u5165\uFF08${currentSnapshot.size} bytes\uFF09\uFF0C\u4E0D\u7528\u6700\u7EC8\u56DE\u590D\u8986\u76D6\u5B83\u3002`
14536
+ };
14537
+ }
14538
+ const narrated = narratesSavedTarget(body, path3);
14539
+ if (narrated) {
14540
+ return {
14541
+ kind: "skip",
14542
+ reason: "narrates-target",
14543
+ message: `\u21AA \u8DF3\u8FC7 deferred \u843D\u76D8\uFF1A\u6700\u7EC8\u56DE\u590D\u662F\u5173\u4E8E ${basename8(path3)} \u7684\u6C47\u62A5\u800C\u975E\u6587\u6863\u6B63\u6587\uFF08matched: ${narrated}\uFF09\u3002`
14544
+ };
14545
+ }
14546
+ if (!isCleanDocumentBody(body)) {
14547
+ return {
14548
+ kind: "skip",
14549
+ reason: "not-document-body",
14550
+ message: `\u21AA \u8DF3\u8FC7 deferred \u843D\u76D8\uFF1A\u6700\u7EC8\u56DE\u590D\u4E0D\u50CF\u53EF\u76F4\u63A5\u843D\u76D8\u7684\u6587\u6863\u6B63\u6587\uFF08${path3} \u672A\u6539\u52A8\uFF09\u3002`
14551
+ };
14552
+ }
14553
+ return { kind: "save", body };
14554
+ }
14555
+
14456
14556
  // src/web/session-handler.ts
14457
14557
  import { mkdirSync as mkdirSync14, createWriteStream as createWriteStream2 } from "fs";
14458
14558
  import { dirname as dirname9 } from "path";
14459
14559
 
14460
14560
  // src/repl/commands/project-init.ts
14461
- import { existsSync as existsSync26, readFileSync as readFileSync21, readdirSync as readdirSync11, statSync as statSync10 } from "fs";
14561
+ import { existsSync as existsSync26, readFileSync as readFileSync21, readdirSync as readdirSync11, statSync as statSync11 } from "fs";
14462
14562
  import { join as join20 } from "path";
14463
14563
  var SCAN_SKIP_DIRS = /* @__PURE__ */ new Set([
14464
14564
  "node_modules",
@@ -14503,11 +14603,11 @@ function scanDirTree(dir, maxDepth = 2, maxEntries = 80) {
14503
14603
  const sorted = filtered.sort((a, b) => {
14504
14604
  let aIsDir = false, bIsDir = false;
14505
14605
  try {
14506
- aIsDir = statSync10(join20(d, a)).isDirectory();
14606
+ aIsDir = statSync11(join20(d, a)).isDirectory();
14507
14607
  } catch {
14508
14608
  }
14509
14609
  try {
14510
- bIsDir = statSync10(join20(d, b)).isDirectory();
14610
+ bIsDir = statSync11(join20(d, b)).isDirectory();
14511
14611
  } catch {
14512
14612
  }
14513
14613
  if (aIsDir !== bIsDir) return aIsDir ? -1 : 1;
@@ -14520,7 +14620,7 @@ function scanDirTree(dir, maxDepth = 2, maxEntries = 80) {
14520
14620
  const connector = isLast ? "+-- " : "|-- ";
14521
14621
  let isDir;
14522
14622
  try {
14523
- isDir = statSync10(fullPath).isDirectory();
14623
+ isDir = statSync11(fullPath).isDirectory();
14524
14624
  } catch {
14525
14625
  continue;
14526
14626
  }
@@ -15549,7 +15649,7 @@ function costSourceNote(source) {
15549
15649
  }
15550
15650
 
15551
15651
  // src/web/commands/_shared.ts
15552
- import { existsSync as existsSync29, readFileSync as readFileSync23, writeFileSync as writeFileSync3, mkdirSync as mkdirSync13, readdirSync as readdirSync13, statSync as statSync13, createWriteStream } from "fs";
15652
+ import { existsSync as existsSync29, readFileSync as readFileSync23, writeFileSync as writeFileSync3, mkdirSync as mkdirSync13, readdirSync as readdirSync13, statSync as statSync14, createWriteStream } from "fs";
15553
15653
  import { join as join24, resolve as resolve9, dirname as dirname8 } from "path";
15554
15654
 
15555
15655
  // src/cli/review-prompts.ts
@@ -15622,7 +15722,7 @@ function readGitDiff(options = {}) {
15622
15722
  }
15623
15723
 
15624
15724
  // src/diagnostics/doctor-report.ts
15625
- import { existsSync as existsSync28, statSync as statSync12 } from "fs";
15725
+ import { existsSync as existsSync28, statSync as statSync13 } from "fs";
15626
15726
  import { join as join23 } from "path";
15627
15727
  import { arch as arch2, platform as platform7, release as release2 } from "os";
15628
15728
 
@@ -15632,7 +15732,7 @@ import {
15632
15732
  mkdirSync as mkdirSync12,
15633
15733
  readdirSync as readdirSync12,
15634
15734
  readFileSync as readFileSync22,
15635
- statSync as statSync11,
15735
+ statSync as statSync12,
15636
15736
  unlinkSync as unlinkSync6,
15637
15737
  writeFileSync as writeFileSync2
15638
15738
  } from "fs";
@@ -15661,7 +15761,7 @@ function listRecentCrashes(limit = 10, configDir) {
15661
15761
  if (!name.startsWith(CRASH_LOG_PREFIX) || !name.endsWith(".log")) continue;
15662
15762
  const full = join22(dir, name);
15663
15763
  try {
15664
- const st = statSync11(full);
15764
+ const st = statSync12(full);
15665
15765
  const head = readFileSync22(full, "utf-8").split("\n").slice(0, 20);
15666
15766
  const tsLine = head.find((l) => l.startsWith("timestamp:"));
15667
15767
  const kindLine = head.find((l) => l.startsWith("kind:"));
@@ -15702,7 +15802,7 @@ function getConfigDirUsage(configDir) {
15702
15802
  }
15703
15803
  function dirSize(path3) {
15704
15804
  try {
15705
- const st = statSync11(path3);
15805
+ const st = statSync12(path3);
15706
15806
  if (st.isFile()) return st.size;
15707
15807
  if (!st.isDirectory()) return 0;
15708
15808
  let sum = 0;
@@ -15721,7 +15821,7 @@ function checkFile(label, path3) {
15721
15821
  let sizeBytes = null;
15722
15822
  if (exists) {
15723
15823
  try {
15724
- sizeBytes = statSync12(path3).size;
15824
+ sizeBytes = statSync13(path3).size;
15725
15825
  } catch {
15726
15826
  }
15727
15827
  }
@@ -16043,7 +16143,7 @@ Cache: write=${cacheCreate} read=${cacheRead}` : "";
16043
16143
  Cost: ${formatCost(cost)}` : "";
16044
16144
  let memoryLine = "";
16045
16145
  try {
16046
- const { countPendingMemories: countPendingMemories2 } = await import("./persistent-memory-KYRY56PG.js");
16146
+ const { countPendingMemories: countPendingMemories2 } = await import("./persistent-memory-CI544RLT.js");
16047
16147
  const pending = countPendingMemories2(ctx.config.getConfigDir());
16048
16148
  if (pending > 0) memoryLine = `
16049
16149
  Memory: \u26A0 ${pending} pending approval \u2014 see the Memory panel or /memory`;
@@ -16856,7 +16956,7 @@ async function handleTest(args, ctx) {
16856
16956
  const isCommand = argStr.includes(" ") || /^(mvn|gradle|npm|pytest|cargo|go)\b/.test(argStr);
16857
16957
  testArgs = isCommand ? { command: argStr } : { filter: argStr };
16858
16958
  }
16859
- const runTests = ctx.runTests ?? (await import("./run-tests-5FHQ5IBX.js")).executeTests;
16959
+ const runTests = ctx.runTests ?? (await import("./run-tests-72VNZXNT.js")).executeTests;
16860
16960
  const report = await runTests(testArgs);
16861
16961
  ctx.send({ type: "info", message: report });
16862
16962
  } catch (err) {
@@ -17192,7 +17292,7 @@ async function handleMemory(args, ctx) {
17192
17292
  ctx.handleMemoryManage(sub, args[1], sub === "expire" ? args[2] : void 0);
17193
17293
  } else if (sub === "export") {
17194
17294
  if (args[1] === "md") {
17195
- const { exportMemoryEntries: exportMemoryEntries2 } = await import("./persistent-memory-KYRY56PG.js");
17295
+ const { exportMemoryEntries: exportMemoryEntries2 } = await import("./persistent-memory-CI544RLT.js");
17196
17296
  ctx.send({
17197
17297
  type: "export_data",
17198
17298
  format: "md",
@@ -17235,7 +17335,7 @@ Use /add-dir remove to clear.` : "No directories added.\nUsage: /add-dir <path>
17235
17335
  ctx.send({ type: "error", message: `Directory not found: ${dirPath}` });
17236
17336
  return;
17237
17337
  }
17238
- if (!statSync13(dirPath).isDirectory()) {
17338
+ if (!statSync14(dirPath).isDirectory()) {
17239
17339
  ctx.send({ type: "error", message: `Not a directory: ${dirPath}` });
17240
17340
  return;
17241
17341
  }
@@ -18570,16 +18670,26 @@ Try: /compact to reduce context, /clear to reset, or switch to a larger-context
18570
18670
  },
18571
18671
  onFinalContent: (content, { reasoningContent }) => {
18572
18672
  this.send({ type: "response_done", content, usage });
18573
- if (pendingTeeSave && isCleanDocumentBody(content)) {
18574
- try {
18575
- mkdirSync14(dirname9(pendingTeeSave), { recursive: true });
18576
- const bodyToSave = stripOuterCodeFence(content);
18577
- atomicWriteFileSync(pendingTeeSave, bodyToSave);
18578
- undoStack.push(pendingTeeSave, `save_last_response (deferred): ${pendingTeeSave}`);
18579
- const lines = bodyToSave.split("\n").length;
18580
- this.send({ type: "info", message: `\u2705 Saved (from reply): ${pendingTeeSave} (${lines} lines, ${bodyToSave.length} chars)` });
18581
- } catch (saveErr) {
18582
- this.send({ type: "info", message: `\u2717 Could not save to ${pendingTeeSave}: ${saveErr.message ?? saveErr}` });
18673
+ if (pendingTeeSave) {
18674
+ const target = pendingTeeSave.path;
18675
+ const decision = decideDeferredSave({
18676
+ content,
18677
+ path: target,
18678
+ armedSnapshot: pendingTeeSave.armedSnapshot,
18679
+ currentSnapshot: snapshotFile(target)
18680
+ });
18681
+ if (decision.kind === "save") {
18682
+ try {
18683
+ mkdirSync14(dirname9(target), { recursive: true });
18684
+ atomicWriteFileSync(target, decision.body);
18685
+ undoStack.push(target, `save_last_response (deferred): ${target}`);
18686
+ const lines = decision.body.split("\n").length;
18687
+ this.send({ type: "info", message: `\u2705 Saved (from reply): ${target} (${lines} lines, ${decision.body.length} chars)` });
18688
+ } catch (saveErr) {
18689
+ this.send({ type: "info", message: `\u2717 Could not save to ${target}: ${saveErr.message ?? saveErr}` });
18690
+ }
18691
+ } else {
18692
+ this.send({ type: "info", message: decision.message });
18583
18693
  }
18584
18694
  pendingTeeSave = null;
18585
18695
  }
@@ -18640,7 +18750,7 @@ ${summaryContent}`,
18640
18750
  // 与 REPL 不同:Web 端 tee 成功后继续 agentic 循环(返回 'continue'),
18641
18751
  // 让模型基于工具结果给出最终文本。
18642
18752
  runSaveLastResponseTee: async ({ toolCalls, call, saveToFile, extraMessages, reasoningContent }) => {
18643
- if (pendingTeeSave === saveToFile) {
18753
+ if (pendingTeeSave?.path === saveToFile) {
18644
18754
  const results = toolCalls.map((tc) => ({
18645
18755
  callId: tc.id,
18646
18756
  content: tc.id === call.id ? buildDeferredSaveInstruction(saveToFile) : "[skipped: write the document as plain text, not via tools]",
@@ -18651,7 +18761,7 @@ ${summaryContent}`,
18651
18761
  }
18652
18762
  const freshPrior = freshPriorContent(lastAssistantText(apiMessages), lastAssistantAtTurnStart);
18653
18763
  if (provider.supportsContentOnlyTee === false && !freshPrior) {
18654
- pendingTeeSave = saveToFile;
18764
+ pendingTeeSave = armDeferredSave(saveToFile);
18655
18765
  this.send({ type: "info", message: `\u21AA \u8DF3\u8FC7 tee\uFF08${this.currentProvider} \u4E0D\u64C5\u957F content-only \u751F\u6210\uFF09\uFF0C\u6539\u7528\u666E\u901A\u6587\u672C\u5199\u62A5\u544A\uFF0C\u5C06\u81EA\u52A8\u4FDD\u5B58\u5230 ${saveToFile}` });
18656
18766
  const results = toolCalls.map((tc) => ({
18657
18767
  callId: tc.id,
@@ -18674,7 +18784,7 @@ ${summaryContent}`,
18674
18784
  usage,
18675
18785
  freshPrior
18676
18786
  );
18677
- if (teeResult.deferred) pendingTeeSave = saveToFile;
18787
+ if (teeResult.deferred) pendingTeeSave = armDeferredSave(saveToFile);
18678
18788
  const teeToolResults = toolCalls.map((tc) => {
18679
18789
  if (tc.id === call.id) {
18680
18790
  return {
@@ -20062,7 +20172,7 @@ async function startWebServer(options = {}) {
20062
20172
  return;
20063
20173
  }
20064
20174
  try {
20065
- const stat = statSync14(fullPath);
20175
+ const stat = statSync15(fullPath);
20066
20176
  if (stat.size > 512 * 1024) {
20067
20177
  res.json({ error: `File too large (${(stat.size / 1024).toFixed(0)} KB, max 512 KB)` });
20068
20178
  return;
File without changes
File without changes
@@ -158,7 +158,7 @@ ${content}`);
158
158
  }
159
159
  }
160
160
  async function runTaskMode(config, providers, configManager, topic) {
161
- const { TaskOrchestrator } = await import("./task-orchestrator-56PQ7LGV.js");
161
+ const { TaskOrchestrator } = await import("./task-orchestrator-DJN36HP5.js");
162
162
  const orchestrator = new TaskOrchestrator(config, providers, configManager);
163
163
  let interrupted = false;
164
164
  const onSigint = () => {
File without changes