jinzd-ai-cli 0.4.110 → 0.4.112

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.
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  ConfigManager
4
- } from "./chunk-BLJIPN2F.js";
4
+ } from "./chunk-FM5VYCXA.js";
5
5
  import "./chunk-2ZD3YTVM.js";
6
- import "./chunk-SQPICAHN.js";
6
+ import "./chunk-AWZ63EVH.js";
7
7
  import "./chunk-PDX44BCA.js";
8
8
 
9
9
  // src/cli/batch.ts
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  TEST_TIMEOUT
4
- } from "./chunk-SQPICAHN.js";
4
+ } from "./chunk-AWZ63EVH.js";
5
5
 
6
6
  // src/tools/builtin/run-tests.ts
7
7
  import { execSync, spawnSync } from "child_process";
@@ -5,7 +5,7 @@ import {
5
5
  } from "./chunk-3BICTI5M.js";
6
6
  import {
7
7
  runTestsTool
8
- } from "./chunk-M77QS5QW.js";
8
+ } from "./chunk-5XHNTLRS.js";
9
9
  import {
10
10
  EnvLoader,
11
11
  NetworkError,
@@ -18,7 +18,7 @@ import {
18
18
  SUBAGENT_ALLOWED_TOOLS,
19
19
  SUBAGENT_DEFAULT_MAX_ROUNDS,
20
20
  SUBAGENT_MAX_ROUNDS_LIMIT
21
- } from "./chunk-SQPICAHN.js";
21
+ } from "./chunk-AWZ63EVH.js";
22
22
  import {
23
23
  fileCheckpoints
24
24
  } from "./chunk-4BKXL7SM.js";
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/core/constants.ts
4
- var VERSION = "0.4.110";
4
+ var VERSION = "0.4.112";
5
5
  var APP_NAME = "ai-cli";
6
6
  var CONFIG_DIR_NAME = ".aicli";
7
7
  var CONFIG_FILE_NAME = "config.json";
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  schemaToJsonSchema,
4
4
  truncateForPersist
5
- } from "./chunk-LKWFPJIP.js";
5
+ } from "./chunk-7XXZWPTN.js";
6
6
  import {
7
7
  AuthError,
8
8
  ProviderError,
@@ -18,7 +18,7 @@ import {
18
18
  MCP_PROTOCOL_VERSION,
19
19
  MCP_TOOL_PREFIX,
20
20
  VERSION
21
- } from "./chunk-SQPICAHN.js";
21
+ } from "./chunk-AWZ63EVH.js";
22
22
  import {
23
23
  redactJson
24
24
  } from "./chunk-7ZJN4KLV.js";
@@ -1785,7 +1785,16 @@ var PSEUDO_TOOL_CALL_PATTERNS = [
1785
1785
  // ```tool_call\n...\n``` markdown fences (Kimi/Zhipu fallback)
1786
1786
  /```\s*tool_call\b/i,
1787
1787
  // Bare JSON tool-call block: lines starting with `{"name":"...","arguments":`
1788
- /^\s*\{\s*"name"\s*:\s*"[\w._-]+"\s*,\s*"arguments"\s*:/m
1788
+ /^\s*\{\s*"name"\s*:\s*"[\w._-]+"\s*,\s*"arguments"\s*:/m,
1789
+ // v0.4.112: <think> ... </think> reasoning blocks. The REPL renderer
1790
+ // suppresses these from terminal output, but tee mode writes the raw
1791
+ // delta to disk → reasoning leaks into the saved file. We saw a 600-line
1792
+ // 审计报告.md whose first 57 lines were the model's planning monologue.
1793
+ /<think\b[^>]*>/i,
1794
+ // v0.4.112: leading ```markdown / ```md fence wrapping the entire document.
1795
+ // DeepSeek V4 Pro Thinking sometimes "politely" wraps its document output
1796
+ // in a markdown fence. The fence ends up literally in the saved file.
1797
+ /^\s*```\s*(?:markdown|md|gfm)\b/im
1789
1798
  ];
1790
1799
  function detectPseudoToolCalls(content) {
1791
1800
  if (!content || content.length === 0) return null;
@@ -1794,6 +1803,71 @@ function detectPseudoToolCalls(content) {
1794
1803
  }
1795
1804
  return null;
1796
1805
  }
1806
+ function stripPseudoToolCalls(content) {
1807
+ if (!content) return content;
1808
+ let out = content;
1809
+ out = out.replace(/<tool_call\b[^>]*>[\s\S]*?<\/tool_call>/gi, "");
1810
+ out = out.replace(/<tool_call\b[^>]*\/>/gi, "");
1811
+ out = out.replace(/<function_calls\b[^>]*>[\s\S]*?<\/function_calls>/gi, "");
1812
+ out = out.replace(/<invoke\b[^>]*>[\s\S]*?<\/invoke>/gi, "");
1813
+ out = out.replace(/<invoke\b[^>]*\/>/gi, "");
1814
+ out = out.replace(/<tool_use(?:_id)?\b[^>]*>[\s\S]*?<\/tool_use(?:_id)?>/gi, "");
1815
+ out = out.replace(/```\s*tool_call\b[\s\S]*?```/gi, "");
1816
+ out = out.replace(/<think\b[^>]*>[\s\S]*?<\/think>/gi, "");
1817
+ out = out.replace(/<think\b[^>]*>[\s\S]*?(?=^#{1,3}\s+\S|\n\s*\n)/im, "");
1818
+ out = out.replace(/^\s*\{\s*"name"\s*:\s*"[\w._-]+"\s*,\s*"arguments"\s*:[\s\S]*?\}\s*$/gm, "");
1819
+ out = unwrapDocumentFence(out);
1820
+ out = peelMetaNarration(out);
1821
+ out = out.replace(/\n{3,}/g, "\n\n").trim();
1822
+ return out;
1823
+ }
1824
+ function unwrapDocumentFence(content) {
1825
+ const trimmed = content.trim();
1826
+ const open = trimmed.match(/^```\s*(markdown|md|gfm)?\s*\n/i);
1827
+ if (!open) return content;
1828
+ const afterOpen = trimmed.slice(open[0].length);
1829
+ const closeMatch = afterOpen.match(/\n```\s*$/);
1830
+ if (!closeMatch) return content;
1831
+ const inner = afterOpen.slice(0, afterOpen.length - closeMatch[0].length);
1832
+ if (inner.length < 200) return content;
1833
+ return inner;
1834
+ }
1835
+ function peelMetaNarration(content) {
1836
+ let out = content;
1837
+ const firstHeadingMatch = out.match(/^#{1,3}\s+\S.*$/m);
1838
+ if (firstHeadingMatch && firstHeadingMatch.index !== void 0) {
1839
+ const before = out.slice(0, firstHeadingMatch.index);
1840
+ const hasIntroMarker = /(?:以下(?:即为|是|就是)|这是|Here\s+is|Below\s+is|完整的?(?:审计报告|内容|文档)|审计报告(?:如下|的完整内容))/i.test(before);
1841
+ if (before.length > 0 && before.length < 800 && hasIntroMarker) {
1842
+ out = out.slice(firstHeadingMatch.index);
1843
+ }
1844
+ if (out.startsWith("---\n")) {
1845
+ const headingAfterRule = out.slice(4).match(/^#{1,3}\s+\S/m);
1846
+ if (headingAfterRule && headingAfterRule.index !== void 0 && headingAfterRule.index < 100) {
1847
+ out = out.slice(4 + headingAfterRule.index);
1848
+ }
1849
+ }
1850
+ }
1851
+ const codaMatch = out.match(/\n[^\n]*?(?:以上(?:即为|就是|内容|为完整的?)|Above\s+is\s+the|本报告已经|该报告(?:已经|包含)|报告(?:已|至此)结束)[^\n]*$/i);
1852
+ if (codaMatch && codaMatch.index !== void 0 && codaMatch.index > out.length / 2) {
1853
+ out = out.slice(0, codaMatch.index);
1854
+ }
1855
+ return out.trim();
1856
+ }
1857
+ function looksLikeDocumentBody(content) {
1858
+ if (!content || content.length < 200) return false;
1859
+ if (/^#{1,6}\s+\S/m.test(content)) return true;
1860
+ const paragraphs = content.split(/\n\s*\n/).filter((p) => p.trim().length > 30);
1861
+ if (paragraphs.length >= 3) return true;
1862
+ return false;
1863
+ }
1864
+ function stripToolCallReminder(systemPrompt) {
1865
+ if (!systemPrompt) return systemPrompt;
1866
+ const idx = systemPrompt.indexOf("[\u26A0\uFE0F Mandatory Tool Call Policy]");
1867
+ if (idx === -1) return systemPrompt;
1868
+ return systemPrompt.slice(0, idx).trimEnd();
1869
+ }
1870
+ var TEE_FINAL_USER_NUDGE = `\u26A0\uFE0F STOP using tools NOW. The save_last_response tee stream is open and capturing every token of THIS response. Output ONLY the requested document body, in markdown. The very first character of your response must be the document's top-level heading (e.g. "# \u5BA1\u8BA1\u62A5\u544A" / "# Audit Report"). Do NOT print <tool_call>, </tool_call>, <function_calls>, <invoke>, <tool_use>, <think>, or any other tool-call markup. Do NOT narrate that you will produce the document \u2014 just produce it. Do NOT pretend to call tools \u2014 there are none in this stream.`;
1797
1871
  var CONTENT_ONLY_STREAM_REMINDER = `
1798
1872
 
1799
1873
  [\u26A0\uFE0F CONTENT GENERATION MODE]
@@ -4081,6 +4155,10 @@ export {
4081
4155
  findPhantomClaims,
4082
4156
  buildPhantomCorrectionMessage,
4083
4157
  detectPseudoToolCalls,
4158
+ stripPseudoToolCalls,
4159
+ looksLikeDocumentBody,
4160
+ stripToolCallReminder,
4161
+ TEE_FINAL_USER_NUDGE,
4084
4162
  CONTENT_ONLY_STREAM_REMINDER,
4085
4163
  ProviderRegistry,
4086
4164
  getContentText,
@@ -6,7 +6,7 @@ import { platform } from "os";
6
6
  import chalk from "chalk";
7
7
 
8
8
  // src/core/constants.ts
9
- var VERSION = "0.4.110";
9
+ var VERSION = "0.4.112";
10
10
  var APP_NAME = "ai-cli";
11
11
  var CONFIG_DIR_NAME = ".aicli";
12
12
  var CONFIG_FILE_NAME = "config.json";
@@ -8,7 +8,7 @@ import {
8
8
  CONFIG_FILE_NAME,
9
9
  HISTORY_DIR_NAME,
10
10
  PLUGINS_DIR_NAME
11
- } from "./chunk-SQPICAHN.js";
11
+ } from "./chunk-AWZ63EVH.js";
12
12
 
13
13
  // src/config/config-manager.ts
14
14
  import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
@@ -36,7 +36,7 @@ import {
36
36
  TEST_TIMEOUT,
37
37
  VERSION,
38
38
  buildUserIdentityPrompt
39
- } from "./chunk-SQPICAHN.js";
39
+ } from "./chunk-AWZ63EVH.js";
40
40
  import "./chunk-PDX44BCA.js";
41
41
  export {
42
42
  AGENTIC_BEHAVIOR_GUIDELINE,
@@ -36,7 +36,7 @@ import {
36
36
  VERSION,
37
37
  buildUserIdentityPrompt,
38
38
  runTestsTool
39
- } from "./chunk-GFDEAYPM.js";
39
+ } from "./chunk-DEXCXFLP.js";
40
40
  import {
41
41
  hasSemanticIndex,
42
42
  semanticSearch
@@ -2199,7 +2199,16 @@ var PSEUDO_TOOL_CALL_PATTERNS = [
2199
2199
  // ```tool_call\n...\n``` markdown fences (Kimi/Zhipu fallback)
2200
2200
  /```\s*tool_call\b/i,
2201
2201
  // Bare JSON tool-call block: lines starting with `{"name":"...","arguments":`
2202
- /^\s*\{\s*"name"\s*:\s*"[\w._-]+"\s*,\s*"arguments"\s*:/m
2202
+ /^\s*\{\s*"name"\s*:\s*"[\w._-]+"\s*,\s*"arguments"\s*:/m,
2203
+ // v0.4.112: <think> ... </think> reasoning blocks. The REPL renderer
2204
+ // suppresses these from terminal output, but tee mode writes the raw
2205
+ // delta to disk → reasoning leaks into the saved file. We saw a 600-line
2206
+ // 审计报告.md whose first 57 lines were the model's planning monologue.
2207
+ /<think\b[^>]*>/i,
2208
+ // v0.4.112: leading ```markdown / ```md fence wrapping the entire document.
2209
+ // DeepSeek V4 Pro Thinking sometimes "politely" wraps its document output
2210
+ // in a markdown fence. The fence ends up literally in the saved file.
2211
+ /^\s*```\s*(?:markdown|md|gfm)\b/im
2203
2212
  ];
2204
2213
  function detectPseudoToolCalls(content) {
2205
2214
  if (!content || content.length === 0) return null;
@@ -2208,6 +2217,71 @@ function detectPseudoToolCalls(content) {
2208
2217
  }
2209
2218
  return null;
2210
2219
  }
2220
+ function stripPseudoToolCalls(content) {
2221
+ if (!content) return content;
2222
+ let out = content;
2223
+ out = out.replace(/<tool_call\b[^>]*>[\s\S]*?<\/tool_call>/gi, "");
2224
+ out = out.replace(/<tool_call\b[^>]*\/>/gi, "");
2225
+ out = out.replace(/<function_calls\b[^>]*>[\s\S]*?<\/function_calls>/gi, "");
2226
+ out = out.replace(/<invoke\b[^>]*>[\s\S]*?<\/invoke>/gi, "");
2227
+ out = out.replace(/<invoke\b[^>]*\/>/gi, "");
2228
+ out = out.replace(/<tool_use(?:_id)?\b[^>]*>[\s\S]*?<\/tool_use(?:_id)?>/gi, "");
2229
+ out = out.replace(/```\s*tool_call\b[\s\S]*?```/gi, "");
2230
+ out = out.replace(/<think\b[^>]*>[\s\S]*?<\/think>/gi, "");
2231
+ out = out.replace(/<think\b[^>]*>[\s\S]*?(?=^#{1,3}\s+\S|\n\s*\n)/im, "");
2232
+ out = out.replace(/^\s*\{\s*"name"\s*:\s*"[\w._-]+"\s*,\s*"arguments"\s*:[\s\S]*?\}\s*$/gm, "");
2233
+ out = unwrapDocumentFence(out);
2234
+ out = peelMetaNarration(out);
2235
+ out = out.replace(/\n{3,}/g, "\n\n").trim();
2236
+ return out;
2237
+ }
2238
+ function unwrapDocumentFence(content) {
2239
+ const trimmed = content.trim();
2240
+ const open = trimmed.match(/^```\s*(markdown|md|gfm)?\s*\n/i);
2241
+ if (!open) return content;
2242
+ const afterOpen = trimmed.slice(open[0].length);
2243
+ const closeMatch = afterOpen.match(/\n```\s*$/);
2244
+ if (!closeMatch) return content;
2245
+ const inner = afterOpen.slice(0, afterOpen.length - closeMatch[0].length);
2246
+ if (inner.length < 200) return content;
2247
+ return inner;
2248
+ }
2249
+ function peelMetaNarration(content) {
2250
+ let out = content;
2251
+ const firstHeadingMatch = out.match(/^#{1,3}\s+\S.*$/m);
2252
+ if (firstHeadingMatch && firstHeadingMatch.index !== void 0) {
2253
+ const before = out.slice(0, firstHeadingMatch.index);
2254
+ const hasIntroMarker = /(?:以下(?:即为|是|就是)|这是|Here\s+is|Below\s+is|完整的?(?:审计报告|内容|文档)|审计报告(?:如下|的完整内容))/i.test(before);
2255
+ if (before.length > 0 && before.length < 800 && hasIntroMarker) {
2256
+ out = out.slice(firstHeadingMatch.index);
2257
+ }
2258
+ if (out.startsWith("---\n")) {
2259
+ const headingAfterRule = out.slice(4).match(/^#{1,3}\s+\S/m);
2260
+ if (headingAfterRule && headingAfterRule.index !== void 0 && headingAfterRule.index < 100) {
2261
+ out = out.slice(4 + headingAfterRule.index);
2262
+ }
2263
+ }
2264
+ }
2265
+ const codaMatch = out.match(/\n[^\n]*?(?:以上(?:即为|就是|内容|为完整的?)|Above\s+is\s+the|本报告已经|该报告(?:已经|包含)|报告(?:已|至此)结束)[^\n]*$/i);
2266
+ if (codaMatch && codaMatch.index !== void 0 && codaMatch.index > out.length / 2) {
2267
+ out = out.slice(0, codaMatch.index);
2268
+ }
2269
+ return out.trim();
2270
+ }
2271
+ function looksLikeDocumentBody(content) {
2272
+ if (!content || content.length < 200) return false;
2273
+ if (/^#{1,6}\s+\S/m.test(content)) return true;
2274
+ const paragraphs = content.split(/\n\s*\n/).filter((p) => p.trim().length > 30);
2275
+ if (paragraphs.length >= 3) return true;
2276
+ return false;
2277
+ }
2278
+ function stripToolCallReminder(systemPrompt) {
2279
+ if (!systemPrompt) return systemPrompt;
2280
+ const idx = systemPrompt.indexOf("[\u26A0\uFE0F Mandatory Tool Call Policy]");
2281
+ if (idx === -1) return systemPrompt;
2282
+ return systemPrompt.slice(0, idx).trimEnd();
2283
+ }
2284
+ var TEE_FINAL_USER_NUDGE = `\u26A0\uFE0F STOP using tools NOW. The save_last_response tee stream is open and capturing every token of THIS response. Output ONLY the requested document body, in markdown. The very first character of your response must be the document's top-level heading (e.g. "# \u5BA1\u8BA1\u62A5\u544A" / "# Audit Report"). Do NOT print <tool_call>, </tool_call>, <function_calls>, <invoke>, <tool_use>, <think>, or any other tool-call markup. Do NOT narrate that you will produce the document \u2014 just produce it. Do NOT pretend to call tools \u2014 there are none in this stream.`;
2211
2285
  var CONTENT_ONLY_STREAM_REMINDER = `
2212
2286
 
2213
2287
  [\u26A0\uFE0F CONTENT GENERATION MODE]
@@ -10696,7 +10770,8 @@ ${summaryResult.content}`,
10696
10770
  try {
10697
10771
  mkdirSync9(dirname4(saveToFile), { recursive: true });
10698
10772
  fileStream = createWriteStream(saveToFile, { encoding: "utf-8" });
10699
- const teeSystemPrompt = (systemPrompt ?? "") + CONTENT_ONLY_STREAM_REMINDER;
10773
+ const teeSystemPrompt = stripToolCallReminder(systemPrompt ?? "") + CONTENT_ONLY_STREAM_REMINDER;
10774
+ const teeExtraMessages = extraMessages.length > 0 ? [...extraMessages, { role: "user", content: TEE_FINAL_USER_NUDGE }] : [{ role: "user", content: TEE_FINAL_USER_NUDGE }];
10700
10775
  const chatRequest = {
10701
10776
  messages: apiMessages,
10702
10777
  model: this.currentModel,
@@ -10709,7 +10784,7 @@ ${summaryResult.content}`,
10709
10784
  thinking: modelParams.thinking,
10710
10785
  thinkingBudget: modelParams.thinkingBudget,
10711
10786
  signal: ac.signal,
10712
- ...extraMessages.length > 0 ? { _extraMessages: extraMessages } : {}
10787
+ _extraMessages: teeExtraMessages
10713
10788
  };
10714
10789
  const stream = provider.chatStream(chatRequest);
10715
10790
  for await (const chunk of stream) {
@@ -10727,12 +10802,22 @@ ${summaryResult.content}`,
10727
10802
  });
10728
10803
  const pseudoMatch = detectPseudoToolCalls(fullContent);
10729
10804
  if (pseudoMatch) {
10730
- try {
10731
- unlinkSync4(saveToFile);
10732
- } catch {
10805
+ const cleaned = stripPseudoToolCalls(fullContent);
10806
+ if (looksLikeDocumentBody(cleaned)) {
10807
+ writeFileSync8(saveToFile, cleaned, "utf-8");
10808
+ fullContent = cleaned;
10809
+ const lines = cleaned.split("\n").length;
10810
+ const bytes = Buffer.byteLength(cleaned, "utf-8");
10811
+ summary = `File saved (with cleanup): ${saveToFile} (${lines} lines, ${bytes} bytes; pseudo-tool-call markup matching ${pseudoMatch} was stripped before save)`;
10812
+ undoStack.push(saveToFile, `save_last_response: ${saveToFile}`);
10813
+ } else {
10814
+ try {
10815
+ unlinkSync4(saveToFile);
10816
+ } catch {
10817
+ }
10818
+ isError = true;
10819
+ summary = `[save_last_response REJECTED] Your output was tool-call markup with no usable document body (matched: ${pseudoMatch}). ${saveToFile} was NOT saved. This fresh stream has NO tools \u2014 output is captured verbatim. STOP emitting <tool_call>, <function_calls>, <invoke>, <think>, or JSON tool blocks. Produce the document body NOW: start with a markdown heading and write the full content.`;
10733
10820
  }
10734
- isError = true;
10735
- summary = `[save_last_response REJECTED] Your fresh streaming output contained pseudo-tool-call markup (matched: ${pseudoMatch}). The file at "${saveToFile}" was NOT saved (deleted). This fresh stream has NO tools attached \u2014 output is captured verbatim. Do NOT print <tool_call>, <function_calls>, <invoke>, or {"name":"...","arguments":...} JSON. Try again: produce ONLY the document body.`;
10736
10821
  } else {
10737
10822
  const lines = fullContent.split("\n").length;
10738
10823
  const bytes = Buffer.byteLength(fullContent, "utf-8");
@@ -11833,7 +11918,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
11833
11918
  case "test": {
11834
11919
  this.send({ type: "info", message: "\u{1F9EA} Running tests..." });
11835
11920
  try {
11836
- const { executeTests } = await import("./run-tests-XVK66TGP.js");
11921
+ const { executeTests } = await import("./run-tests-V73IZCNW.js");
11837
11922
  const argStr = args.join(" ").trim();
11838
11923
  let testArgs = {};
11839
11924
  if (argStr) {
@@ -386,7 +386,7 @@ ${content}`);
386
386
  }
387
387
  }
388
388
  async function runTaskMode(config, providers, configManager, topic) {
389
- const { TaskOrchestrator } = await import("./task-orchestrator-2TGWMUUT.js");
389
+ const { TaskOrchestrator } = await import("./task-orchestrator-RPYZT2WL.js");
390
390
  const orchestrator = new TaskOrchestrator(config, providers, configManager);
391
391
  let interrupted = false;
392
392
  const onSigint = () => {
package/dist/index.js CHANGED
@@ -7,6 +7,7 @@ import {
7
7
  SNAPSHOT_PROMPT,
8
8
  SessionManager,
9
9
  SkillManager,
10
+ TEE_FINAL_USER_NUDGE,
10
11
  TOOL_CALL_REMINDER,
11
12
  autoTrimSessionIfNeeded,
12
13
  buildPhantomCorrectionMessage,
@@ -22,15 +23,18 @@ import {
22
23
  getPricing,
23
24
  hadPreviousWriteToolCalls,
24
25
  loadDevState,
26
+ looksLikeDocumentBody,
25
27
  parseSimpleYaml,
26
28
  persistToolRound,
27
29
  saveDevState,
28
30
  sessionHasMeaningfulContent,
29
- setupProxy
30
- } from "./chunk-YIHYQVV7.js";
31
+ setupProxy,
32
+ stripPseudoToolCalls,
33
+ stripToolCallReminder
34
+ } from "./chunk-BWZJGCO6.js";
31
35
  import {
32
36
  ConfigManager
33
- } from "./chunk-BLJIPN2F.js";
37
+ } from "./chunk-FM5VYCXA.js";
34
38
  import {
35
39
  ToolExecutor,
36
40
  ToolRegistry,
@@ -49,10 +53,10 @@ import {
49
53
  spawnAgentContext,
50
54
  theme,
51
55
  undoStack
52
- } from "./chunk-LKWFPJIP.js";
56
+ } from "./chunk-7XXZWPTN.js";
53
57
  import "./chunk-3BICTI5M.js";
54
58
  import "./chunk-2DXY7UGF.js";
55
- import "./chunk-M77QS5QW.js";
59
+ import "./chunk-5XHNTLRS.js";
56
60
  import "./chunk-2ZD3YTVM.js";
57
61
  import {
58
62
  AGENTIC_BEHAVIOR_GUIDELINE,
@@ -75,7 +79,7 @@ import {
75
79
  SKILLS_DIR_NAME,
76
80
  VERSION,
77
81
  buildUserIdentityPrompt
78
- } from "./chunk-SQPICAHN.js";
82
+ } from "./chunk-AWZ63EVH.js";
79
83
  import {
80
84
  formatGitContextForPrompt,
81
85
  getGitContext,
@@ -102,7 +106,7 @@ import { program } from "commander";
102
106
 
103
107
  // src/repl/repl.ts
104
108
  import * as readline from "readline";
105
- import { existsSync as existsSync5, readFileSync as readFileSync4, readdirSync as readdirSync3, statSync as statSync3, unlinkSync as unlinkSync2 } from "fs";
109
+ import { existsSync as existsSync5, readFileSync as readFileSync4, readdirSync as readdirSync3, statSync as statSync3, unlinkSync as unlinkSync2, writeFileSync as writeFileSync3 } from "fs";
106
110
  import { join as join5, resolve as resolve2, extname as extname2, dirname as dirname3, basename as basename2 } from "path";
107
111
  import chalk4 from "chalk";
108
112
 
@@ -1596,7 +1600,7 @@ ${text}
1596
1600
  const { join: join6 } = await import("path");
1597
1601
  const { existsSync: existsSync6 } = await import("fs");
1598
1602
  const { getGitRoot: getGitRoot2 } = await import("./git-context-7KIP4X2V.js");
1599
- const { MCP_PROJECT_CONFIG_NAME: MCP_PROJECT_CONFIG_NAME2 } = await import("./constants-EVSUKVTR.js");
1603
+ const { MCP_PROJECT_CONFIG_NAME: MCP_PROJECT_CONFIG_NAME2 } = await import("./constants-OCYK6U3N.js");
1600
1604
  const { approveProject, hashMcpFile } = await import("./project-trust-IFM7FXEV.js");
1601
1605
  const cwd = process.cwd();
1602
1606
  const projectRoot = getGitRoot2(cwd) ?? cwd;
@@ -2646,7 +2650,7 @@ ${hint}` : "")
2646
2650
  usage: "/test [command|filter]",
2647
2651
  async execute(args, ctx) {
2648
2652
  try {
2649
- const { executeTests } = await import("./run-tests-M73756VV.js");
2653
+ const { executeTests } = await import("./run-tests-JMKGA7XU.js");
2650
2654
  const argStr = args.join(" ").trim();
2651
2655
  let testArgs = {};
2652
2656
  if (argStr) {
@@ -6239,7 +6243,8 @@ ${mcpBudgetNote}` : "");
6239
6243
  } else {
6240
6244
  const teeAc = this.setupStreamInterrupt();
6241
6245
  try {
6242
- const teeSystemPrompt = (systemPrompt ?? "") + CONTENT_ONLY_STREAM_REMINDER;
6246
+ const teeSystemPrompt = stripToolCallReminder(systemPrompt ?? "") + CONTENT_ONLY_STREAM_REMINDER;
6247
+ const teeExtraMessages = extraMessages.length > 0 ? [...extraMessages, { role: "user", content: TEE_FINAL_USER_NUDGE }] : [{ role: "user", content: TEE_FINAL_USER_NUDGE }];
6243
6248
  const genStream = provider.chatStream({
6244
6249
  messages: apiMessages,
6245
6250
  model: effectiveModel,
@@ -6252,7 +6257,7 @@ ${mcpBudgetNote}` : "");
6252
6257
  thinking: modelParams.thinking,
6253
6258
  thinkingBudget: modelParams.thinkingBudget,
6254
6259
  signal: teeAc.signal,
6255
- ...extraMessages.length > 0 ? { _extraMessages: extraMessages } : {}
6260
+ _extraMessages: teeExtraMessages
6256
6261
  });
6257
6262
  const teeShowTokens = this.shouldShowTokens();
6258
6263
  const { content: genContent, usage: genUsage, tokensShown: teeTokShown } = await this.renderer.renderStream(
@@ -6261,22 +6266,65 @@ ${mcpBudgetNote}` : "");
6261
6266
  );
6262
6267
  const pseudoMatch = detectPseudoToolCalls(genContent);
6263
6268
  if (pseudoMatch) {
6269
+ const cleaned = stripPseudoToolCalls(genContent);
6270
+ if (looksLikeDocumentBody(cleaned)) {
6271
+ try {
6272
+ writeFileSync3(saveToFile, cleaned, "utf-8");
6273
+ process.stdout.write(theme.warning(
6274
+ `
6275
+ \u26A0 Salvaged save: stripped pseudo-tool-call markup (matched: ${pseudoMatch})
6276
+ ${saveToFile} now contains the cleaned document (${cleaned.length} chars; was ${genContent.length}).
6277
+
6278
+ `
6279
+ ));
6280
+ lastResponseStore.content = cleaned;
6281
+ if (genUsage) {
6282
+ roundUsage.inputTokens += genUsage.inputTokens;
6283
+ roundUsage.outputTokens += genUsage.outputTokens;
6284
+ roundUsage.cacheCreationTokens += genUsage.cacheCreationTokens ?? 0;
6285
+ roundUsage.cacheReadTokens += genUsage.cacheReadTokens ?? 0;
6286
+ }
6287
+ session.addMessage({ role: "assistant", content: cleaned, timestamp: /* @__PURE__ */ new Date() });
6288
+ this.events.emit("message.after", { content: cleaned });
6289
+ const lines2 = cleaned.split("\n").length;
6290
+ const bytes2 = Buffer.byteLength(cleaned, "utf-8");
6291
+ const okResults = result.toolCalls.map((tc) => ({
6292
+ callId: tc.id,
6293
+ content: tc.name === "save_last_response" ? `File saved (with cleanup): ${saveToFile} (${lines2} lines, ${bytes2} bytes; pseudo-tool-call markup was stripped before save)` : `[skipped: file already saved by tee streaming]`,
6294
+ isError: false
6295
+ }));
6296
+ const reasoningContent4 = "reasoningContent" in result ? result.reasoningContent : void 0;
6297
+ const newMsgs4 = provider.buildToolResultMessages(result.toolCalls, okResults, reasoningContent4);
6298
+ extraMessages.push(...newMsgs4);
6299
+ if (roundUsage.inputTokens > 0 || roundUsage.outputTokens > 0) {
6300
+ this.addSessionUsage(roundUsage, effectiveModel);
6301
+ session.addTokenUsage(roundUsage);
6302
+ if (teeShowTokens && !teeTokShown) {
6303
+ this.renderer.renderUsage(roundUsage, this.sessionTokenUsage);
6304
+ }
6305
+ }
6306
+ return;
6307
+ } catch (writeErr) {
6308
+ process.stderr.write(`[tee] salvage write failed: ${writeErr.message ?? writeErr}
6309
+ `);
6310
+ }
6311
+ }
6264
6312
  try {
6265
6313
  unlinkSync2(saveToFile);
6266
6314
  } catch {
6267
6315
  }
6268
6316
  process.stdout.write(theme.error(
6269
6317
  `
6270
- \u2717 Rejected save: response contained pseudo-tool-call markup (matched: ${pseudoMatch})
6271
- ${saveToFile} was deleted; the model will be asked to regenerate without tool markup.
6318
+ \u2717 Rejected save: response was pseudo-tool-call markup with no usable document body (matched: ${pseudoMatch})
6319
+ ${saveToFile} was deleted; asking model to retry.
6272
6320
 
6273
6321
  `
6274
6322
  ));
6275
6323
  const errorResults = result.toolCalls.map((tc) => ({
6276
6324
  callId: tc.id,
6277
- content: tc.name === "save_last_response" ? `[save_last_response REJECTED] Your fresh streaming output contained pseudo-tool-call markup (e.g. <tool_call>, <function_calls>, or {"name":"...","arguments":...}). The file at "${saveToFile}" was NOT saved (it was deleted to prevent garbage on disk).
6325
+ content: tc.name === "save_last_response" ? `[save_last_response REJECTED] Your output was tool-call XML/JSON with no document body. ${saveToFile} was NOT saved.
6278
6326
 
6279
- This fresh stream has NO tools attached \u2014 your output is captured verbatim. Do NOT print tool-call XML/JSON. Try again: produce ONLY the document body (markdown is fine; tool markup is NOT).` : `[skipped: save_last_response was rejected and other parallel calls are abandoned]`,
6327
+ This fresh stream has NO tools \u2014 output is captured verbatim. STOP emitting <tool_call>, <function_calls>, <invoke>, <think>, or JSON tool blocks. Produce the document body NOW: start with a markdown heading like "# \u5BA1\u8BA1\u62A5\u544A" and write the full report.` : `[skipped: save_last_response was rejected and other parallel calls are abandoned]`,
6280
6328
  isError: tc.name === "save_last_response"
6281
6329
  }));
6282
6330
  const reasoningContent3 = "reasoningContent" in result ? result.reasoningContent : void 0;
@@ -6834,7 +6882,7 @@ program.command("web").description("Start Web UI server with browser-based chat
6834
6882
  console.error("Error: Invalid port number. Must be between 1 and 65535.");
6835
6883
  process.exit(1);
6836
6884
  }
6837
- const { startWebServer } = await import("./server-YRX5VO5N.js");
6885
+ const { startWebServer } = await import("./server-YY6CUP3K.js");
6838
6886
  await startWebServer({ port, host: options.host });
6839
6887
  });
6840
6888
  program.command("user [action] [username]").description("Manage Web UI users (list | create <name> | delete <name> | reset-password <name> | migrate <name>)").action(async (action, username) => {
@@ -6957,7 +7005,7 @@ program.command("sessions").description("List recent conversation sessions").act
6957
7005
  });
6958
7006
  program.command("batch <action> [arg] [arg2]").description("Anthropic Message Batches: submit | list | status <id> | results <id> [out] | cancel <id>").option("--dry-run", "Parse and validate input without submitting (submit only)").action(async (action, arg, arg2, options) => {
6959
7007
  try {
6960
- const batch = await import("./batch-V255CIMB.js");
7008
+ const batch = await import("./batch-QQGRQ45S.js");
6961
7009
  switch (action) {
6962
7010
  case "submit":
6963
7011
  if (!arg) {
@@ -7000,7 +7048,7 @@ program.command("batch <action> [arg] [arg2]").description("Anthropic Message Ba
7000
7048
  }
7001
7049
  });
7002
7050
  program.command("mcp-serve").description("Start an MCP server over STDIO, exposing aicli's built-in tools to Claude Desktop / Cursor / other MCP clients").option("--allow-destructive", "Allow bash / run_interactive / task_create (always destructive in MCP mode)").option("--allow-outside-cwd", "Allow tool path arguments to escape the sandbox root \u2014 disabled by default").option("--tools <list>", "Comma-separated whitelist of tools to expose (default: all eligible tools)").option("--cwd <path>", "Working directory AND sandbox root (default: current directory)").action(async (options) => {
7003
- const { startMcpServer } = await import("./server-54OXGXJ6.js");
7051
+ const { startMcpServer } = await import("./server-WWFOPWWJ.js");
7004
7052
  await startMcpServer({
7005
7053
  allowDestructive: !!options.allowDestructive,
7006
7054
  allowOutsideCwd: !!options.allowOutsideCwd,
@@ -7127,7 +7175,7 @@ program.command("hub [topic]").description("Start multi-agent hub (discuss / bra
7127
7175
  }),
7128
7176
  config.get("customProviders")
7129
7177
  );
7130
- const { startHub } = await import("./hub-O3EQ2LDY.js");
7178
+ const { startHub } = await import("./hub-7KYFVLHM.js");
7131
7179
  await startHub(
7132
7180
  {
7133
7181
  topic: topic ?? "",
@@ -2,8 +2,8 @@
2
2
  import {
3
3
  executeTests,
4
4
  runTestsTool
5
- } from "./chunk-M77QS5QW.js";
6
- import "./chunk-SQPICAHN.js";
5
+ } from "./chunk-5XHNTLRS.js";
6
+ import "./chunk-AWZ63EVH.js";
7
7
  import "./chunk-PDX44BCA.js";
8
8
  export {
9
9
  executeTests,
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  executeTests,
3
3
  runTestsTool
4
- } from "./chunk-GFDEAYPM.js";
4
+ } from "./chunk-DEXCXFLP.js";
5
5
  import "./chunk-3RG5ZIWI.js";
6
6
  export {
7
7
  executeTests,
@@ -3,14 +3,14 @@ import {
3
3
  ToolRegistry,
4
4
  getDangerLevel,
5
5
  schemaToJsonSchema
6
- } from "./chunk-LKWFPJIP.js";
6
+ } from "./chunk-7XXZWPTN.js";
7
7
  import "./chunk-3BICTI5M.js";
8
8
  import "./chunk-2DXY7UGF.js";
9
- import "./chunk-M77QS5QW.js";
9
+ import "./chunk-5XHNTLRS.js";
10
10
  import "./chunk-2ZD3YTVM.js";
11
11
  import {
12
12
  VERSION
13
- } from "./chunk-SQPICAHN.js";
13
+ } from "./chunk-AWZ63EVH.js";
14
14
  import "./chunk-4BKXL7SM.js";
15
15
  import "./chunk-7ZJN4KLV.js";
16
16
  import "./chunk-KHYD3WXE.js";
@@ -9,6 +9,7 @@ import {
9
9
  ProviderRegistry,
10
10
  SessionManager,
11
11
  SkillManager,
12
+ TEE_FINAL_USER_NUDGE,
12
13
  TOOL_CALL_REMINDER,
13
14
  autoTrimSessionIfNeeded,
14
15
  computeCost,
@@ -18,12 +19,15 @@ import {
18
19
  getContentText,
19
20
  hadPreviousWriteToolCalls,
20
21
  loadDevState,
22
+ looksLikeDocumentBody,
21
23
  persistToolRound,
22
- setupProxy
23
- } from "./chunk-YIHYQVV7.js";
24
+ setupProxy,
25
+ stripPseudoToolCalls,
26
+ stripToolCallReminder
27
+ } from "./chunk-BWZJGCO6.js";
24
28
  import {
25
29
  ConfigManager
26
- } from "./chunk-BLJIPN2F.js";
30
+ } from "./chunk-FM5VYCXA.js";
27
31
  import {
28
32
  ToolExecutor,
29
33
  ToolRegistry,
@@ -41,10 +45,10 @@ import {
41
45
  spawnAgentContext,
42
46
  truncateOutput,
43
47
  undoStack
44
- } from "./chunk-LKWFPJIP.js";
48
+ } from "./chunk-7XXZWPTN.js";
45
49
  import "./chunk-3BICTI5M.js";
46
50
  import "./chunk-2DXY7UGF.js";
47
- import "./chunk-M77QS5QW.js";
51
+ import "./chunk-5XHNTLRS.js";
48
52
  import "./chunk-2ZD3YTVM.js";
49
53
  import {
50
54
  AGENTIC_BEHAVIOR_GUIDELINE,
@@ -64,7 +68,7 @@ import {
64
68
  SKILLS_DIR_NAME,
65
69
  VERSION,
66
70
  buildUserIdentityPrompt
67
- } from "./chunk-SQPICAHN.js";
71
+ } from "./chunk-AWZ63EVH.js";
68
72
  import {
69
73
  formatGitContextForPrompt,
70
74
  getGitContext,
@@ -1259,7 +1263,8 @@ ${summaryResult.content}`,
1259
1263
  try {
1260
1264
  mkdirSync(dirname(saveToFile), { recursive: true });
1261
1265
  fileStream = createWriteStream(saveToFile, { encoding: "utf-8" });
1262
- const teeSystemPrompt = (systemPrompt ?? "") + CONTENT_ONLY_STREAM_REMINDER;
1266
+ const teeSystemPrompt = stripToolCallReminder(systemPrompt ?? "") + CONTENT_ONLY_STREAM_REMINDER;
1267
+ const teeExtraMessages = extraMessages.length > 0 ? [...extraMessages, { role: "user", content: TEE_FINAL_USER_NUDGE }] : [{ role: "user", content: TEE_FINAL_USER_NUDGE }];
1263
1268
  const chatRequest = {
1264
1269
  messages: apiMessages,
1265
1270
  model: this.currentModel,
@@ -1272,7 +1277,7 @@ ${summaryResult.content}`,
1272
1277
  thinking: modelParams.thinking,
1273
1278
  thinkingBudget: modelParams.thinkingBudget,
1274
1279
  signal: ac.signal,
1275
- ...extraMessages.length > 0 ? { _extraMessages: extraMessages } : {}
1280
+ _extraMessages: teeExtraMessages
1276
1281
  };
1277
1282
  const stream = provider.chatStream(chatRequest);
1278
1283
  for await (const chunk of stream) {
@@ -1290,12 +1295,22 @@ ${summaryResult.content}`,
1290
1295
  });
1291
1296
  const pseudoMatch = detectPseudoToolCalls(fullContent);
1292
1297
  if (pseudoMatch) {
1293
- try {
1294
- unlinkSync(saveToFile);
1295
- } catch {
1298
+ const cleaned = stripPseudoToolCalls(fullContent);
1299
+ if (looksLikeDocumentBody(cleaned)) {
1300
+ writeFileSync(saveToFile, cleaned, "utf-8");
1301
+ fullContent = cleaned;
1302
+ const lines = cleaned.split("\n").length;
1303
+ const bytes = Buffer.byteLength(cleaned, "utf-8");
1304
+ summary = `File saved (with cleanup): ${saveToFile} (${lines} lines, ${bytes} bytes; pseudo-tool-call markup matching ${pseudoMatch} was stripped before save)`;
1305
+ undoStack.push(saveToFile, `save_last_response: ${saveToFile}`);
1306
+ } else {
1307
+ try {
1308
+ unlinkSync(saveToFile);
1309
+ } catch {
1310
+ }
1311
+ isError = true;
1312
+ summary = `[save_last_response REJECTED] Your output was tool-call markup with no usable document body (matched: ${pseudoMatch}). ${saveToFile} was NOT saved. This fresh stream has NO tools \u2014 output is captured verbatim. STOP emitting <tool_call>, <function_calls>, <invoke>, <think>, or JSON tool blocks. Produce the document body NOW: start with a markdown heading and write the full content.`;
1296
1313
  }
1297
- isError = true;
1298
- summary = `[save_last_response REJECTED] Your fresh streaming output contained pseudo-tool-call markup (matched: ${pseudoMatch}). The file at "${saveToFile}" was NOT saved (deleted). This fresh stream has NO tools attached \u2014 output is captured verbatim. Do NOT print <tool_call>, <function_calls>, <invoke>, or {"name":"...","arguments":...} JSON. Try again: produce ONLY the document body.`;
1299
1314
  } else {
1300
1315
  const lines = fullContent.split("\n").length;
1301
1316
  const bytes = Buffer.byteLength(fullContent, "utf-8");
@@ -2396,7 +2411,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
2396
2411
  case "test": {
2397
2412
  this.send({ type: "info", message: "\u{1F9EA} Running tests..." });
2398
2413
  try {
2399
- const { executeTests } = await import("./run-tests-M73756VV.js");
2414
+ const { executeTests } = await import("./run-tests-JMKGA7XU.js");
2400
2415
  const argStr = args.join(" ").trim();
2401
2416
  let testArgs = {};
2402
2417
  if (argStr) {
@@ -4,14 +4,14 @@ import {
4
4
  getDangerLevel,
5
5
  googleSearchContext,
6
6
  truncateOutput
7
- } from "./chunk-LKWFPJIP.js";
7
+ } from "./chunk-7XXZWPTN.js";
8
8
  import "./chunk-3BICTI5M.js";
9
9
  import "./chunk-2DXY7UGF.js";
10
- import "./chunk-M77QS5QW.js";
10
+ import "./chunk-5XHNTLRS.js";
11
11
  import "./chunk-2ZD3YTVM.js";
12
12
  import {
13
13
  SUBAGENT_ALLOWED_TOOLS
14
- } from "./chunk-SQPICAHN.js";
14
+ } from "./chunk-AWZ63EVH.js";
15
15
  import "./chunk-4BKXL7SM.js";
16
16
  import "./chunk-7ZJN4KLV.js";
17
17
  import "./chunk-KHYD3WXE.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jinzd-ai-cli",
3
- "version": "0.4.110",
3
+ "version": "0.4.112",
4
4
  "description": "Cross-platform REPL-style AI CLI with multi-provider support",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",