jinzd-ai-cli 0.4.109 → 0.4.111

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-JLHY6AOZ.js";
4
+ } from "./chunk-VYDXL2PC.js";
5
5
  import "./chunk-2ZD3YTVM.js";
6
- import "./chunk-LZR3DYW3.js";
6
+ import "./chunk-CPIQXP7Q.js";
7
7
  import "./chunk-PDX44BCA.js";
8
8
 
9
9
  // src/cli/batch.ts
@@ -5,7 +5,7 @@ import {
5
5
  } from "./chunk-3BICTI5M.js";
6
6
  import {
7
7
  runTestsTool
8
- } from "./chunk-CUQ4HXHA.js";
8
+ } from "./chunk-5LKW2GOF.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-LZR3DYW3.js";
21
+ } from "./chunk-CPIQXP7Q.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
  import {
3
3
  TEST_TIMEOUT
4
- } from "./chunk-LZR3DYW3.js";
4
+ } from "./chunk-CPIQXP7Q.js";
5
5
 
6
6
  // src/tools/builtin/run-tests.ts
7
7
  import { execSync, spawnSync } from "child_process";
@@ -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.109";
9
+ var VERSION = "0.4.111";
10
10
  var APP_NAME = "ai-cli";
11
11
  var CONFIG_DIR_NAME = ".aicli";
12
12
  var CONFIG_FILE_NAME = "config.json";
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/core/constants.ts
4
- var VERSION = "0.4.109";
4
+ var VERSION = "0.4.111";
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-N62X6OKJ.js";
5
+ } from "./chunk-5EVKTBUU.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-LZR3DYW3.js";
21
+ } from "./chunk-CPIQXP7Q.js";
22
22
  import {
23
23
  redactJson
24
24
  } from "./chunk-7ZJN4KLV.js";
@@ -1772,6 +1772,68 @@ function buildPhantomCorrectionMessage(phantoms) {
1772
1772
  const list = phantoms.map((p) => ` - ${p}`).join("\n");
1773
1773
  return "You claimed to have written the following file(s), but no matching write_file tool call was actually made in this turn:\n" + list + '\n\nEach of these files does NOT exist on disk. You MUST now invoke write_file (via the function calling API) for every missing file listed above. Do NOT output another "completion summary" until the tool calls have actually been made.';
1774
1774
  }
1775
+ var PSEUDO_TOOL_CALL_PATTERNS = [
1776
+ // <tool_call name="..."> ... </tool_call> (DeepSeek V4 thinking, GLM)
1777
+ /<tool_call\s+name\s*=\s*["'][\w._-]+["']/,
1778
+ // <function_calls> ... </function_calls> (Anthropic-style as text)
1779
+ /<\/?function_calls\s*>/,
1780
+ // <invoke name="..." /> (Anthropic XML tool-call, which is real for
1781
+ // Claude API but is text/garbage for any other provider's plain stream)
1782
+ /<invoke\s+name\s*=\s*["'][\w._-]+["']/,
1783
+ // <tool_use> ... <tool_use_id> (Claude flavor leaked into text)
1784
+ /<tool_use(?:_id)?\b/,
1785
+ // ```tool_call\n...\n``` markdown fences (Kimi/Zhipu fallback)
1786
+ /```\s*tool_call\b/i,
1787
+ // Bare JSON tool-call block: lines starting with `{"name":"...","arguments":`
1788
+ /^\s*\{\s*"name"\s*:\s*"[\w._-]+"\s*,\s*"arguments"\s*:/m
1789
+ ];
1790
+ function detectPseudoToolCalls(content) {
1791
+ if (!content || content.length === 0) return null;
1792
+ for (const re of PSEUDO_TOOL_CALL_PATTERNS) {
1793
+ if (re.test(content)) return re.source;
1794
+ }
1795
+ return null;
1796
+ }
1797
+ function stripPseudoToolCalls(content) {
1798
+ if (!content) return content;
1799
+ let out = content;
1800
+ out = out.replace(/<tool_call\b[^>]*>[\s\S]*?<\/tool_call>/gi, "");
1801
+ out = out.replace(/<tool_call\b[^>]*\/>/gi, "");
1802
+ out = out.replace(/<function_calls\b[^>]*>[\s\S]*?<\/function_calls>/gi, "");
1803
+ out = out.replace(/<invoke\b[^>]*>[\s\S]*?<\/invoke>/gi, "");
1804
+ out = out.replace(/<invoke\b[^>]*\/>/gi, "");
1805
+ out = out.replace(/<tool_use(?:_id)?\b[^>]*>[\s\S]*?<\/tool_use(?:_id)?>/gi, "");
1806
+ out = out.replace(/```\s*tool_call\b[\s\S]*?```/gi, "");
1807
+ out = out.replace(/<think\b[^>]*>[\s\S]*?<\/think>/gi, "");
1808
+ out = out.replace(/^\s*\{\s*"name"\s*:\s*"[\w._-]+"\s*,\s*"arguments"\s*:[\s\S]*?\}\s*$/gm, "");
1809
+ out = out.replace(/\n{3,}/g, "\n\n").trim();
1810
+ return out;
1811
+ }
1812
+ function looksLikeDocumentBody(content) {
1813
+ if (!content || content.length < 200) return false;
1814
+ if (/^#{1,6}\s+\S/m.test(content)) return true;
1815
+ const paragraphs = content.split(/\n\s*\n/).filter((p) => p.trim().length > 30);
1816
+ if (paragraphs.length >= 3) return true;
1817
+ return false;
1818
+ }
1819
+ function stripToolCallReminder(systemPrompt) {
1820
+ if (!systemPrompt) return systemPrompt;
1821
+ const idx = systemPrompt.indexOf("[\u26A0\uFE0F Mandatory Tool Call Policy]");
1822
+ if (idx === -1) return systemPrompt;
1823
+ return systemPrompt.slice(0, idx).trimEnd();
1824
+ }
1825
+ 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.`;
1826
+ var CONTENT_ONLY_STREAM_REMINDER = `
1827
+
1828
+ [\u26A0\uFE0F CONTENT GENERATION MODE]
1829
+ You are now in a CONTENT-ONLY streaming pass. The file at the configured path will receive every token of THIS response.
1830
+ - Do NOT emit <tool_call>, </tool_call>, <function_calls>, <invoke>, <tool_use>, or any tool-call XML/JSON markup.
1831
+ - Do NOT print "I will now call ...", "let me read ...", "<think>" reasoning blocks (the surrounding REPL handles those separately \u2014 they should not enter the saved file).
1832
+ - Do NOT pretend to call tools. There are NO tools available in this stream \u2014 only your text output is captured.
1833
+ - Produce ONLY the requested document body. Markdown is fine. Code blocks are fine. Tool-call markup is NOT.
1834
+ - If you accidentally start a <tool_call>, STOP and produce the document body instead.
1835
+
1836
+ The file is closed and named when this stream ends. If your output contains pseudo-tool-call markup, the save will be REJECTED and you will be asked to retry.`;
1775
1837
 
1776
1838
  // src/providers/kimi.ts
1777
1839
  var KIMI_XML_REMINDER = `
@@ -4047,6 +4109,12 @@ export {
4047
4109
  extractWrittenFilePaths,
4048
4110
  findPhantomClaims,
4049
4111
  buildPhantomCorrectionMessage,
4112
+ detectPseudoToolCalls,
4113
+ stripPseudoToolCalls,
4114
+ looksLikeDocumentBody,
4115
+ stripToolCallReminder,
4116
+ TEE_FINAL_USER_NUDGE,
4117
+ CONTENT_ONLY_STREAM_REMINDER,
4050
4118
  ProviderRegistry,
4051
4119
  getContentText,
4052
4120
  SessionManager,
@@ -8,7 +8,7 @@ import {
8
8
  CONFIG_FILE_NAME,
9
9
  HISTORY_DIR_NAME,
10
10
  PLUGINS_DIR_NAME
11
- } from "./chunk-LZR3DYW3.js";
11
+ } from "./chunk-CPIQXP7Q.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-LZR3DYW3.js";
39
+ } from "./chunk-CPIQXP7Q.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-OKRTYLB6.js";
39
+ } from "./chunk-AVMGKU4E.js";
40
40
  import {
41
41
  hasSemanticIndex,
42
42
  semanticSearch
@@ -2186,6 +2186,68 @@ CRITICAL \u2014 Batch file generation rules:
2186
2186
  4. Only produce a text summary AFTER all write_file calls have been made and returned success.
2187
2187
  5. The system compares every "file saved" claim against actual tool calls. Phantom claims trigger an automatic retry \u2014 do not waste rounds.`;
2188
2188
  var HALLUCINATION_CORRECTION_MESSAGE = "You did NOT actually call the write_file tool \u2014 the file was NOT created! Please immediately use the write_file tool via the function calling API to perform the actual file write. Do NOT describe file content in text \u2014 you MUST invoke write_file through the tool_calls mechanism.";
2189
+ var PSEUDO_TOOL_CALL_PATTERNS = [
2190
+ // <tool_call name="..."> ... </tool_call> (DeepSeek V4 thinking, GLM)
2191
+ /<tool_call\s+name\s*=\s*["'][\w._-]+["']/,
2192
+ // <function_calls> ... </function_calls> (Anthropic-style as text)
2193
+ /<\/?function_calls\s*>/,
2194
+ // <invoke name="..." /> (Anthropic XML tool-call, which is real for
2195
+ // Claude API but is text/garbage for any other provider's plain stream)
2196
+ /<invoke\s+name\s*=\s*["'][\w._-]+["']/,
2197
+ // <tool_use> ... <tool_use_id> (Claude flavor leaked into text)
2198
+ /<tool_use(?:_id)?\b/,
2199
+ // ```tool_call\n...\n``` markdown fences (Kimi/Zhipu fallback)
2200
+ /```\s*tool_call\b/i,
2201
+ // Bare JSON tool-call block: lines starting with `{"name":"...","arguments":`
2202
+ /^\s*\{\s*"name"\s*:\s*"[\w._-]+"\s*,\s*"arguments"\s*:/m
2203
+ ];
2204
+ function detectPseudoToolCalls(content) {
2205
+ if (!content || content.length === 0) return null;
2206
+ for (const re of PSEUDO_TOOL_CALL_PATTERNS) {
2207
+ if (re.test(content)) return re.source;
2208
+ }
2209
+ return null;
2210
+ }
2211
+ function stripPseudoToolCalls(content) {
2212
+ if (!content) return content;
2213
+ let out = content;
2214
+ out = out.replace(/<tool_call\b[^>]*>[\s\S]*?<\/tool_call>/gi, "");
2215
+ out = out.replace(/<tool_call\b[^>]*\/>/gi, "");
2216
+ out = out.replace(/<function_calls\b[^>]*>[\s\S]*?<\/function_calls>/gi, "");
2217
+ out = out.replace(/<invoke\b[^>]*>[\s\S]*?<\/invoke>/gi, "");
2218
+ out = out.replace(/<invoke\b[^>]*\/>/gi, "");
2219
+ out = out.replace(/<tool_use(?:_id)?\b[^>]*>[\s\S]*?<\/tool_use(?:_id)?>/gi, "");
2220
+ out = out.replace(/```\s*tool_call\b[\s\S]*?```/gi, "");
2221
+ out = out.replace(/<think\b[^>]*>[\s\S]*?<\/think>/gi, "");
2222
+ out = out.replace(/^\s*\{\s*"name"\s*:\s*"[\w._-]+"\s*,\s*"arguments"\s*:[\s\S]*?\}\s*$/gm, "");
2223
+ out = out.replace(/\n{3,}/g, "\n\n").trim();
2224
+ return out;
2225
+ }
2226
+ function looksLikeDocumentBody(content) {
2227
+ if (!content || content.length < 200) return false;
2228
+ if (/^#{1,6}\s+\S/m.test(content)) return true;
2229
+ const paragraphs = content.split(/\n\s*\n/).filter((p) => p.trim().length > 30);
2230
+ if (paragraphs.length >= 3) return true;
2231
+ return false;
2232
+ }
2233
+ function stripToolCallReminder(systemPrompt) {
2234
+ if (!systemPrompt) return systemPrompt;
2235
+ const idx = systemPrompt.indexOf("[\u26A0\uFE0F Mandatory Tool Call Policy]");
2236
+ if (idx === -1) return systemPrompt;
2237
+ return systemPrompt.slice(0, idx).trimEnd();
2238
+ }
2239
+ 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.`;
2240
+ var CONTENT_ONLY_STREAM_REMINDER = `
2241
+
2242
+ [\u26A0\uFE0F CONTENT GENERATION MODE]
2243
+ You are now in a CONTENT-ONLY streaming pass. The file at the configured path will receive every token of THIS response.
2244
+ - Do NOT emit <tool_call>, </tool_call>, <function_calls>, <invoke>, <tool_use>, or any tool-call XML/JSON markup.
2245
+ - Do NOT print "I will now call ...", "let me read ...", "<think>" reasoning blocks (the surrounding REPL handles those separately \u2014 they should not enter the saved file).
2246
+ - Do NOT pretend to call tools. There are NO tools available in this stream \u2014 only your text output is captured.
2247
+ - Produce ONLY the requested document body. Markdown is fine. Code blocks are fine. Tool-call markup is NOT.
2248
+ - If you accidentally start a <tool_call>, STOP and produce the document body instead.
2249
+
2250
+ The file is closed and named when this stream ends. If your output contains pseudo-tool-call markup, the save will be REJECTED and you will be asked to retry.`;
2189
2251
 
2190
2252
  // src/providers/kimi.ts
2191
2253
  var KIMI_XML_REMINDER = `
@@ -9787,7 +9849,7 @@ function autoTrimSessionIfNeeded(session, sizeLimit = SESSION_SIZE_LIMIT) {
9787
9849
  }
9788
9850
 
9789
9851
  // src/web/session-handler.ts
9790
- import { existsSync as existsSync20, readFileSync as readFileSync13, appendFileSync as appendFileSync3, writeFileSync as writeFileSync8, mkdirSync as mkdirSync9, readdirSync as readdirSync9, statSync as statSync8, createWriteStream } from "fs";
9852
+ import { existsSync as existsSync20, readFileSync as readFileSync13, appendFileSync as appendFileSync3, writeFileSync as writeFileSync8, mkdirSync as mkdirSync9, readdirSync as readdirSync9, statSync as statSync8, createWriteStream, unlinkSync as unlinkSync4 } from "fs";
9791
9853
  import { join as join13, resolve as resolve5, dirname as dirname4 } from "path";
9792
9854
  import { execSync as execSync3 } from "child_process";
9793
9855
 
@@ -10663,10 +10725,12 @@ ${summaryResult.content}`,
10663
10725
  try {
10664
10726
  mkdirSync9(dirname4(saveToFile), { recursive: true });
10665
10727
  fileStream = createWriteStream(saveToFile, { encoding: "utf-8" });
10728
+ const teeSystemPrompt = stripToolCallReminder(systemPrompt ?? "") + CONTENT_ONLY_STREAM_REMINDER;
10729
+ const teeExtraMessages = extraMessages.length > 0 ? [...extraMessages, { role: "user", content: TEE_FINAL_USER_NUDGE }] : [{ role: "user", content: TEE_FINAL_USER_NUDGE }];
10666
10730
  const chatRequest = {
10667
10731
  messages: apiMessages,
10668
10732
  model: this.currentModel,
10669
- systemPrompt,
10733
+ systemPrompt: teeSystemPrompt,
10670
10734
  systemPromptVolatile,
10671
10735
  stream: true,
10672
10736
  temperature: modelParams.temperature,
@@ -10675,7 +10739,7 @@ ${summaryResult.content}`,
10675
10739
  thinking: modelParams.thinking,
10676
10740
  thinkingBudget: modelParams.thinkingBudget,
10677
10741
  signal: ac.signal,
10678
- ...extraMessages.length > 0 ? { _extraMessages: extraMessages } : {}
10742
+ _extraMessages: teeExtraMessages
10679
10743
  };
10680
10744
  const stream = provider.chatStream(chatRequest);
10681
10745
  for await (const chunk of stream) {
@@ -10691,10 +10755,30 @@ ${summaryResult.content}`,
10691
10755
  await new Promise((resolve7, reject) => {
10692
10756
  fileStream.end((err) => err ? reject(err) : resolve7());
10693
10757
  });
10694
- const lines = fullContent.split("\n").length;
10695
- const bytes = Buffer.byteLength(fullContent, "utf-8");
10696
- summary = `File saved: ${saveToFile} (${lines} lines, ${bytes} bytes)`;
10697
- undoStack.push(saveToFile, `save_last_response: ${saveToFile}`);
10758
+ const pseudoMatch = detectPseudoToolCalls(fullContent);
10759
+ if (pseudoMatch) {
10760
+ const cleaned = stripPseudoToolCalls(fullContent);
10761
+ if (looksLikeDocumentBody(cleaned)) {
10762
+ writeFileSync8(saveToFile, cleaned, "utf-8");
10763
+ fullContent = cleaned;
10764
+ const lines = cleaned.split("\n").length;
10765
+ const bytes = Buffer.byteLength(cleaned, "utf-8");
10766
+ summary = `File saved (with cleanup): ${saveToFile} (${lines} lines, ${bytes} bytes; pseudo-tool-call markup matching ${pseudoMatch} was stripped before save)`;
10767
+ undoStack.push(saveToFile, `save_last_response: ${saveToFile}`);
10768
+ } else {
10769
+ try {
10770
+ unlinkSync4(saveToFile);
10771
+ } catch {
10772
+ }
10773
+ isError = true;
10774
+ 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.`;
10775
+ }
10776
+ } else {
10777
+ const lines = fullContent.split("\n").length;
10778
+ const bytes = Buffer.byteLength(fullContent, "utf-8");
10779
+ summary = `File saved: ${saveToFile} (${lines} lines, ${bytes} bytes)`;
10780
+ undoStack.push(saveToFile, `save_last_response: ${saveToFile}`);
10781
+ }
10698
10782
  if (teeUsage) {
10699
10783
  roundUsage.inputTokens += teeUsage.inputTokens;
10700
10784
  roundUsage.outputTokens += teeUsage.outputTokens;
@@ -11789,7 +11873,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
11789
11873
  case "test": {
11790
11874
  this.send({ type: "info", message: "\u{1F9EA} Running tests..." });
11791
11875
  try {
11792
- const { executeTests } = await import("./run-tests-TTP4QJVN.js");
11876
+ const { executeTests } = await import("./run-tests-TTU6DNLI.js");
11793
11877
  const argStr = args.join(" ").trim();
11794
11878
  let testArgs = {};
11795
11879
  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-XSNTJYNR.js");
389
+ const { TaskOrchestrator } = await import("./task-orchestrator-IJFF7XLQ.js");
390
390
  const orchestrator = new TaskOrchestrator(config, providers, configManager);
391
391
  let interrupted = false;
392
392
  const onSigint = () => {
package/dist/index.js CHANGED
@@ -1,17 +1,20 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
+ CONTENT_ONLY_STREAM_REMINDER,
3
4
  HALLUCINATION_CORRECTION_MESSAGE,
4
5
  McpManager,
5
6
  ProviderRegistry,
6
7
  SNAPSHOT_PROMPT,
7
8
  SessionManager,
8
9
  SkillManager,
10
+ TEE_FINAL_USER_NUDGE,
9
11
  TOOL_CALL_REMINDER,
10
12
  autoTrimSessionIfNeeded,
11
13
  buildPhantomCorrectionMessage,
12
14
  buildWriteRoundReminder,
13
15
  clearDevState,
14
16
  computeCost,
17
+ detectPseudoToolCalls,
15
18
  detectsHallucinatedFileOp,
16
19
  extractWrittenFilePaths,
17
20
  findPhantomClaims,
@@ -20,15 +23,18 @@ import {
20
23
  getPricing,
21
24
  hadPreviousWriteToolCalls,
22
25
  loadDevState,
26
+ looksLikeDocumentBody,
23
27
  parseSimpleYaml,
24
28
  persistToolRound,
25
29
  saveDevState,
26
30
  sessionHasMeaningfulContent,
27
- setupProxy
28
- } from "./chunk-3UJIJBKB.js";
31
+ setupProxy,
32
+ stripPseudoToolCalls,
33
+ stripToolCallReminder
34
+ } from "./chunk-GRIEVPII.js";
29
35
  import {
30
36
  ConfigManager
31
- } from "./chunk-JLHY6AOZ.js";
37
+ } from "./chunk-VYDXL2PC.js";
32
38
  import {
33
39
  ToolExecutor,
34
40
  ToolRegistry,
@@ -47,10 +53,10 @@ import {
47
53
  spawnAgentContext,
48
54
  theme,
49
55
  undoStack
50
- } from "./chunk-N62X6OKJ.js";
56
+ } from "./chunk-5EVKTBUU.js";
51
57
  import "./chunk-3BICTI5M.js";
52
58
  import "./chunk-2DXY7UGF.js";
53
- import "./chunk-CUQ4HXHA.js";
59
+ import "./chunk-5LKW2GOF.js";
54
60
  import "./chunk-2ZD3YTVM.js";
55
61
  import {
56
62
  AGENTIC_BEHAVIOR_GUIDELINE,
@@ -73,7 +79,7 @@ import {
73
79
  SKILLS_DIR_NAME,
74
80
  VERSION,
75
81
  buildUserIdentityPrompt
76
- } from "./chunk-LZR3DYW3.js";
82
+ } from "./chunk-CPIQXP7Q.js";
77
83
  import {
78
84
  formatGitContextForPrompt,
79
85
  getGitContext,
@@ -100,7 +106,7 @@ import { program } from "commander";
100
106
 
101
107
  // src/repl/repl.ts
102
108
  import * as readline from "readline";
103
- import { existsSync as existsSync5, readFileSync as readFileSync4, readdirSync as readdirSync3, statSync as statSync3 } from "fs";
109
+ import { existsSync as existsSync5, readFileSync as readFileSync4, readdirSync as readdirSync3, statSync as statSync3, unlinkSync as unlinkSync2, writeFileSync as writeFileSync3 } from "fs";
104
110
  import { join as join5, resolve as resolve2, extname as extname2, dirname as dirname3, basename as basename2 } from "path";
105
111
  import chalk4 from "chalk";
106
112
 
@@ -1594,7 +1600,7 @@ ${text}
1594
1600
  const { join: join6 } = await import("path");
1595
1601
  const { existsSync: existsSync6 } = await import("fs");
1596
1602
  const { getGitRoot: getGitRoot2 } = await import("./git-context-7KIP4X2V.js");
1597
- const { MCP_PROJECT_CONFIG_NAME: MCP_PROJECT_CONFIG_NAME2 } = await import("./constants-K52FVIX5.js");
1603
+ const { MCP_PROJECT_CONFIG_NAME: MCP_PROJECT_CONFIG_NAME2 } = await import("./constants-NPGSV4QY.js");
1598
1604
  const { approveProject, hashMcpFile } = await import("./project-trust-IFM7FXEV.js");
1599
1605
  const cwd = process.cwd();
1600
1606
  const projectRoot = getGitRoot2(cwd) ?? cwd;
@@ -2644,7 +2650,7 @@ ${hint}` : "")
2644
2650
  usage: "/test [command|filter]",
2645
2651
  async execute(args, ctx) {
2646
2652
  try {
2647
- const { executeTests } = await import("./run-tests-ZBDJYOB2.js");
2653
+ const { executeTests } = await import("./run-tests-QBNLSYCO.js");
2648
2654
  const argStr = args.join(" ").trim();
2649
2655
  let testArgs = {};
2650
2656
  if (argStr) {
@@ -6237,10 +6243,12 @@ ${mcpBudgetNote}` : "");
6237
6243
  } else {
6238
6244
  const teeAc = this.setupStreamInterrupt();
6239
6245
  try {
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 }];
6240
6248
  const genStream = provider.chatStream({
6241
6249
  messages: apiMessages,
6242
6250
  model: effectiveModel,
6243
- systemPrompt,
6251
+ systemPrompt: teeSystemPrompt,
6244
6252
  systemPromptVolatile,
6245
6253
  stream: true,
6246
6254
  temperature: modelParams.temperature,
@@ -6249,13 +6257,87 @@ ${mcpBudgetNote}` : "");
6249
6257
  thinking: modelParams.thinking,
6250
6258
  thinkingBudget: modelParams.thinkingBudget,
6251
6259
  signal: teeAc.signal,
6252
- ...extraMessages.length > 0 ? { _extraMessages: extraMessages } : {}
6260
+ _extraMessages: teeExtraMessages
6253
6261
  });
6254
6262
  const teeShowTokens = this.shouldShowTokens();
6255
6263
  const { content: genContent, usage: genUsage, tokensShown: teeTokShown } = await this.renderer.renderStream(
6256
6264
  genStream,
6257
6265
  { saveToFile, showTokens: teeShowTokens, sessionTotal: teeShowTokens ? { ...this.sessionTokenUsage } : void 0, signal: teeAc.signal }
6258
6266
  );
6267
+ const pseudoMatch = detectPseudoToolCalls(genContent);
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
+ }
6312
+ try {
6313
+ unlinkSync2(saveToFile);
6314
+ } catch {
6315
+ }
6316
+ process.stdout.write(theme.error(
6317
+ `
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.
6320
+
6321
+ `
6322
+ ));
6323
+ const errorResults = result.toolCalls.map((tc) => ({
6324
+ callId: tc.id,
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.
6326
+
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]`,
6328
+ isError: tc.name === "save_last_response"
6329
+ }));
6330
+ const reasoningContent3 = "reasoningContent" in result ? result.reasoningContent : void 0;
6331
+ const newMsgs3 = provider.buildToolResultMessages(result.toolCalls, errorResults, reasoningContent3);
6332
+ extraMessages.push(...newMsgs3);
6333
+ if (genUsage) {
6334
+ roundUsage.inputTokens += genUsage.inputTokens;
6335
+ roundUsage.outputTokens += genUsage.outputTokens;
6336
+ roundUsage.cacheCreationTokens += genUsage.cacheCreationTokens ?? 0;
6337
+ roundUsage.cacheReadTokens += genUsage.cacheReadTokens ?? 0;
6338
+ }
6339
+ continue;
6340
+ }
6259
6341
  lastResponseStore.content = genContent;
6260
6342
  if (genUsage) {
6261
6343
  roundUsage.inputTokens += genUsage.inputTokens;
@@ -6800,7 +6882,7 @@ program.command("web").description("Start Web UI server with browser-based chat
6800
6882
  console.error("Error: Invalid port number. Must be between 1 and 65535.");
6801
6883
  process.exit(1);
6802
6884
  }
6803
- const { startWebServer } = await import("./server-VDMQC43J.js");
6885
+ const { startWebServer } = await import("./server-42ZKNS56.js");
6804
6886
  await startWebServer({ port, host: options.host });
6805
6887
  });
6806
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) => {
@@ -6923,7 +7005,7 @@ program.command("sessions").description("List recent conversation sessions").act
6923
7005
  });
6924
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) => {
6925
7007
  try {
6926
- const batch = await import("./batch-BFOFTLFS.js");
7008
+ const batch = await import("./batch-XIDQVPRL.js");
6927
7009
  switch (action) {
6928
7010
  case "submit":
6929
7011
  if (!arg) {
@@ -6966,7 +7048,7 @@ program.command("batch <action> [arg] [arg2]").description("Anthropic Message Ba
6966
7048
  }
6967
7049
  });
6968
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) => {
6969
- const { startMcpServer } = await import("./server-LEPDI3OL.js");
7051
+ const { startMcpServer } = await import("./server-5TGJOWML.js");
6970
7052
  await startMcpServer({
6971
7053
  allowDestructive: !!options.allowDestructive,
6972
7054
  allowOutsideCwd: !!options.allowOutsideCwd,
@@ -7093,7 +7175,7 @@ program.command("hub [topic]").description("Start multi-agent hub (discuss / bra
7093
7175
  }),
7094
7176
  config.get("customProviders")
7095
7177
  );
7096
- const { startHub } = await import("./hub-4XJ5ZTM4.js");
7178
+ const { startHub } = await import("./hub-T34FCE5J.js");
7097
7179
  await startHub(
7098
7180
  {
7099
7181
  topic: topic ?? "",
@@ -2,8 +2,8 @@
2
2
  import {
3
3
  executeTests,
4
4
  runTestsTool
5
- } from "./chunk-CUQ4HXHA.js";
6
- import "./chunk-LZR3DYW3.js";
5
+ } from "./chunk-5LKW2GOF.js";
6
+ import "./chunk-CPIQXP7Q.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-OKRTYLB6.js";
4
+ } from "./chunk-AVMGKU4E.js";
5
5
  import "./chunk-3RG5ZIWI.js";
6
6
  export {
7
7
  executeTests,
@@ -3,25 +3,31 @@ import {
3
3
  AuthManager
4
4
  } from "./chunk-BYNY5JPB.js";
5
5
  import {
6
+ CONTENT_ONLY_STREAM_REMINDER,
6
7
  HALLUCINATION_CORRECTION_MESSAGE,
7
8
  McpManager,
8
9
  ProviderRegistry,
9
10
  SessionManager,
10
11
  SkillManager,
12
+ TEE_FINAL_USER_NUDGE,
11
13
  TOOL_CALL_REMINDER,
12
14
  autoTrimSessionIfNeeded,
13
15
  computeCost,
16
+ detectPseudoToolCalls,
14
17
  detectsHallucinatedFileOp,
15
18
  formatCost,
16
19
  getContentText,
17
20
  hadPreviousWriteToolCalls,
18
21
  loadDevState,
22
+ looksLikeDocumentBody,
19
23
  persistToolRound,
20
- setupProxy
21
- } from "./chunk-3UJIJBKB.js";
24
+ setupProxy,
25
+ stripPseudoToolCalls,
26
+ stripToolCallReminder
27
+ } from "./chunk-GRIEVPII.js";
22
28
  import {
23
29
  ConfigManager
24
- } from "./chunk-JLHY6AOZ.js";
30
+ } from "./chunk-VYDXL2PC.js";
25
31
  import {
26
32
  ToolExecutor,
27
33
  ToolRegistry,
@@ -39,10 +45,10 @@ import {
39
45
  spawnAgentContext,
40
46
  truncateOutput,
41
47
  undoStack
42
- } from "./chunk-N62X6OKJ.js";
48
+ } from "./chunk-5EVKTBUU.js";
43
49
  import "./chunk-3BICTI5M.js";
44
50
  import "./chunk-2DXY7UGF.js";
45
- import "./chunk-CUQ4HXHA.js";
51
+ import "./chunk-5LKW2GOF.js";
46
52
  import "./chunk-2ZD3YTVM.js";
47
53
  import {
48
54
  AGENTIC_BEHAVIOR_GUIDELINE,
@@ -62,7 +68,7 @@ import {
62
68
  SKILLS_DIR_NAME,
63
69
  VERSION,
64
70
  buildUserIdentityPrompt
65
- } from "./chunk-LZR3DYW3.js";
71
+ } from "./chunk-CPIQXP7Q.js";
66
72
  import {
67
73
  formatGitContextForPrompt,
68
74
  getGitContext,
@@ -473,7 +479,7 @@ function loadMemoryContent(configDir) {
473
479
  }
474
480
 
475
481
  // src/web/session-handler.ts
476
- import { existsSync as existsSync3, readFileSync as readFileSync3, appendFileSync, writeFileSync, mkdirSync, readdirSync, statSync, createWriteStream } from "fs";
482
+ import { existsSync as existsSync3, readFileSync as readFileSync3, appendFileSync, writeFileSync, mkdirSync, readdirSync, statSync, createWriteStream, unlinkSync } from "fs";
477
483
  import { join as join2, resolve, dirname } from "path";
478
484
  import { execSync } from "child_process";
479
485
  var FREE_ROUND_TOOLS = /* @__PURE__ */ new Set(["write_todos"]);
@@ -1257,10 +1263,12 @@ ${summaryResult.content}`,
1257
1263
  try {
1258
1264
  mkdirSync(dirname(saveToFile), { recursive: true });
1259
1265
  fileStream = createWriteStream(saveToFile, { encoding: "utf-8" });
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 }];
1260
1268
  const chatRequest = {
1261
1269
  messages: apiMessages,
1262
1270
  model: this.currentModel,
1263
- systemPrompt,
1271
+ systemPrompt: teeSystemPrompt,
1264
1272
  systemPromptVolatile,
1265
1273
  stream: true,
1266
1274
  temperature: modelParams.temperature,
@@ -1269,7 +1277,7 @@ ${summaryResult.content}`,
1269
1277
  thinking: modelParams.thinking,
1270
1278
  thinkingBudget: modelParams.thinkingBudget,
1271
1279
  signal: ac.signal,
1272
- ...extraMessages.length > 0 ? { _extraMessages: extraMessages } : {}
1280
+ _extraMessages: teeExtraMessages
1273
1281
  };
1274
1282
  const stream = provider.chatStream(chatRequest);
1275
1283
  for await (const chunk of stream) {
@@ -1285,10 +1293,30 @@ ${summaryResult.content}`,
1285
1293
  await new Promise((resolve3, reject) => {
1286
1294
  fileStream.end((err) => err ? reject(err) : resolve3());
1287
1295
  });
1288
- const lines = fullContent.split("\n").length;
1289
- const bytes = Buffer.byteLength(fullContent, "utf-8");
1290
- summary = `File saved: ${saveToFile} (${lines} lines, ${bytes} bytes)`;
1291
- undoStack.push(saveToFile, `save_last_response: ${saveToFile}`);
1296
+ const pseudoMatch = detectPseudoToolCalls(fullContent);
1297
+ if (pseudoMatch) {
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.`;
1313
+ }
1314
+ } else {
1315
+ const lines = fullContent.split("\n").length;
1316
+ const bytes = Buffer.byteLength(fullContent, "utf-8");
1317
+ summary = `File saved: ${saveToFile} (${lines} lines, ${bytes} bytes)`;
1318
+ undoStack.push(saveToFile, `save_last_response: ${saveToFile}`);
1319
+ }
1292
1320
  if (teeUsage) {
1293
1321
  roundUsage.inputTokens += teeUsage.inputTokens;
1294
1322
  roundUsage.outputTokens += teeUsage.outputTokens;
@@ -2383,7 +2411,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
2383
2411
  case "test": {
2384
2412
  this.send({ type: "info", message: "\u{1F9EA} Running tests..." });
2385
2413
  try {
2386
- const { executeTests } = await import("./run-tests-ZBDJYOB2.js");
2414
+ const { executeTests } = await import("./run-tests-QBNLSYCO.js");
2387
2415
  const argStr = args.join(" ").trim();
2388
2416
  let testArgs = {};
2389
2417
  if (argStr) {
@@ -3,14 +3,14 @@ import {
3
3
  ToolRegistry,
4
4
  getDangerLevel,
5
5
  schemaToJsonSchema
6
- } from "./chunk-N62X6OKJ.js";
6
+ } from "./chunk-5EVKTBUU.js";
7
7
  import "./chunk-3BICTI5M.js";
8
8
  import "./chunk-2DXY7UGF.js";
9
- import "./chunk-CUQ4HXHA.js";
9
+ import "./chunk-5LKW2GOF.js";
10
10
  import "./chunk-2ZD3YTVM.js";
11
11
  import {
12
12
  VERSION
13
- } from "./chunk-LZR3DYW3.js";
13
+ } from "./chunk-CPIQXP7Q.js";
14
14
  import "./chunk-4BKXL7SM.js";
15
15
  import "./chunk-7ZJN4KLV.js";
16
16
  import "./chunk-KHYD3WXE.js";
@@ -4,14 +4,14 @@ import {
4
4
  getDangerLevel,
5
5
  googleSearchContext,
6
6
  truncateOutput
7
- } from "./chunk-N62X6OKJ.js";
7
+ } from "./chunk-5EVKTBUU.js";
8
8
  import "./chunk-3BICTI5M.js";
9
9
  import "./chunk-2DXY7UGF.js";
10
- import "./chunk-CUQ4HXHA.js";
10
+ import "./chunk-5LKW2GOF.js";
11
11
  import "./chunk-2ZD3YTVM.js";
12
12
  import {
13
13
  SUBAGENT_ALLOWED_TOOLS
14
- } from "./chunk-LZR3DYW3.js";
14
+ } from "./chunk-CPIQXP7Q.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.109",
3
+ "version": "0.4.111",
4
4
  "description": "Cross-platform REPL-style AI CLI with multi-provider support",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",