jinzd-ai-cli 0.4.190 → 0.4.193

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,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  truncateForPersist
4
- } from "./chunk-5CLH6XAW.js";
4
+ } from "./chunk-SI5EO3NJ.js";
5
5
  import {
6
6
  APP_NAME,
7
7
  CONFIG_DIR_NAME,
@@ -11,7 +11,7 @@ import {
11
11
  MCP_PROTOCOL_VERSION,
12
12
  MCP_TOOL_PREFIX,
13
13
  VERSION
14
- } from "./chunk-4KMDKDAK.js";
14
+ } from "./chunk-P3PTUSP4.js";
15
15
 
16
16
  // src/mcp/client.ts
17
17
  import { spawn } from "child_process";
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/core/constants.ts
4
- var VERSION = "0.4.190";
4
+ var VERSION = "0.4.193";
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
  CONFIG_DIR_NAME,
4
4
  VERSION
5
- } from "./chunk-4KMDKDAK.js";
5
+ } from "./chunk-P3PTUSP4.js";
6
6
 
7
7
  // src/diagnostics/crash-log.ts
8
8
  import {
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  CONFIG_DIR_NAME
4
- } from "./chunk-4KMDKDAK.js";
4
+ } from "./chunk-P3PTUSP4.js";
5
5
  import {
6
6
  atomicWriteFileSync
7
7
  } from "./chunk-IW3Q7AE5.js";
@@ -5,14 +5,15 @@ import {
5
5
  } from "./chunk-T2NL5ZIA.js";
6
6
  import {
7
7
  runTestsTool
8
- } from "./chunk-NV6W7TZW.js";
8
+ } from "./chunk-7PX3ZX4G.js";
9
9
  import {
10
10
  runTool
11
- } from "./chunk-KHS7RSGR.js";
11
+ } from "./chunk-SHI5UFUH.js";
12
12
  import {
13
13
  getDangerLevel,
14
- isFileWriteTool
15
- } from "./chunk-HIU2SH4V.js";
14
+ isFileWriteTool,
15
+ runLeanAgentLoop
16
+ } from "./chunk-JOR572WG.js";
16
17
  import {
17
18
  EnvLoader,
18
19
  NetworkError,
@@ -25,7 +26,7 @@ import {
25
26
  SUBAGENT_ALLOWED_TOOLS,
26
27
  SUBAGENT_DEFAULT_MAX_ROUNDS,
27
28
  SUBAGENT_MAX_ROUNDS_LIMIT
28
- } from "./chunk-4KMDKDAK.js";
29
+ } from "./chunk-P3PTUSP4.js";
29
30
  import {
30
31
  fileCheckpoints
31
32
  } from "./chunk-4BKXL7SM.js";
@@ -3801,9 +3802,19 @@ ${preamble}`;
3801
3802
  }
3802
3803
 
3803
3804
  // src/tools/builtin/save-last-response.ts
3804
- import { writeFileSync as writeFileSync4, mkdirSync as mkdirSync2 } from "fs";
3805
+ import { writeFileSync as writeFileSync4, mkdirSync as mkdirSync2, unlinkSync as unlinkSync2, rmdirSync as rmdirSync2 } from "fs";
3805
3806
  import { dirname as dirname3 } from "path";
3806
3807
  var lastResponseStore = { content: "" };
3808
+ function cleanupRejectedTeeFile(filePath) {
3809
+ try {
3810
+ unlinkSync2(filePath);
3811
+ } catch {
3812
+ }
3813
+ try {
3814
+ rmdirSync2(dirname3(filePath));
3815
+ } catch {
3816
+ }
3817
+ }
3807
3818
  var saveLastResponseTool = {
3808
3819
  definition: {
3809
3820
  name: "save_last_response",
@@ -4345,61 +4356,37 @@ async function runSubAgent(task, maxRounds, agentIndex, ctx) {
4345
4356
  ];
4346
4357
  const subSystemPrompt = buildSubAgentSystemPrompt(task, ctx.systemPrompt);
4347
4358
  const toolDefs = subRegistry.getDefinitions();
4348
- const extraMessages = [];
4349
- const totalUsage = { inputTokens: 0, outputTokens: 0 };
4350
- let finalContent = "";
4351
4359
  printSubAgentHeader(task, maxRounds, agentIndex);
4352
- try {
4353
- for (let round = 0; round < maxRounds; round++) {
4360
+ const loop = await runLeanAgentLoop({
4361
+ provider: ctx.provider,
4362
+ messages: subMessages,
4363
+ model: ctx.model,
4364
+ maxRounds,
4365
+ chatParams: {
4366
+ temperature: ctx.modelParams.temperature,
4367
+ maxTokens: ctx.modelParams.maxTokens,
4368
+ timeout: ctx.modelParams.timeout,
4369
+ thinking: ctx.modelParams.thinking
4370
+ },
4371
+ executeTools: (calls) => subExecutor.executeAll(calls),
4372
+ systemPromptForRound: () => subSystemPrompt,
4373
+ toolDefsForRound: () => toolDefs,
4374
+ onRoundStart: (round) => {
4354
4375
  subExecutor.setRoundInfo(round + 1, maxRounds);
4355
- const result = await ctx.provider.chatWithTools(
4356
- {
4357
- messages: subMessages,
4358
- model: ctx.model,
4359
- systemPrompt: subSystemPrompt,
4360
- stream: false,
4361
- temperature: ctx.modelParams.temperature,
4362
- maxTokens: ctx.modelParams.maxTokens,
4363
- timeout: ctx.modelParams.timeout,
4364
- thinking: ctx.modelParams.thinking,
4365
- ...extraMessages.length > 0 ? { _extraMessages: extraMessages } : {}
4366
- },
4367
- toolDefs
4368
- );
4369
- if (result.usage) {
4370
- totalUsage.inputTokens += result.usage.inputTokens;
4371
- totalUsage.outputTokens += result.usage.outputTokens;
4372
- }
4373
- if ("content" in result) {
4374
- finalContent = result.content;
4375
- break;
4376
- }
4377
4376
  if (ctx.configManager) {
4378
4377
  googleSearchContext.configManager = ctx.configManager;
4379
4378
  }
4380
- const toolResults = await subExecutor.executeAll(result.toolCalls);
4381
- const reasoningContent = "reasoningContent" in result ? result.reasoningContent : void 0;
4382
- const newMsgs = ctx.provider.buildToolResultMessages(
4383
- result.toolCalls,
4384
- toolResults,
4385
- reasoningContent
4386
- );
4387
- extraMessages.push(...newMsgs);
4388
- }
4389
- if (!finalContent) {
4390
- finalContent = `(Sub-agent reached maximum rounds (${maxRounds}) without producing a final response)`;
4391
- }
4392
- } catch (err) {
4393
- const errMsg = err instanceof Error ? err.message : String(err);
4394
- finalContent = `(Sub-agent error: ${errMsg})`;
4395
- process.stderr.write(
4396
- `
4379
+ },
4380
+ onRoundsExhausted: () => `(Sub-agent reached maximum rounds (${maxRounds}) without producing a final response)`,
4381
+ onError: (errMsg) => {
4382
+ process.stderr.write(`
4397
4383
  [spawn_agent] Error in sub-agent loop: ${errMsg}
4398
- `
4399
- );
4400
- }
4401
- printSubAgentFooter(totalUsage, agentIndex);
4402
- return { content: finalContent, usage: totalUsage };
4384
+ `);
4385
+ return `(Sub-agent error: ${errMsg})`;
4386
+ }
4387
+ });
4388
+ printSubAgentFooter(loop.usage, agentIndex);
4389
+ return { content: loop.content, usage: loop.usage };
4403
4390
  }
4404
4391
  var spawnAgentTool = {
4405
4392
  definition: {
@@ -5660,6 +5647,7 @@ export {
5660
5647
  truncateOutput,
5661
5648
  ToolExecutor,
5662
5649
  lastResponseStore,
5650
+ cleanupRejectedTeeFile,
5663
5651
  askUserContext,
5664
5652
  googleSearchContext,
5665
5653
  spawnAgentContext,
@@ -8,7 +8,7 @@ import {
8
8
  CONFIG_FILE_NAME,
9
9
  HISTORY_DIR_NAME,
10
10
  PLUGINS_DIR_NAME
11
- } from "./chunk-4KMDKDAK.js";
11
+ } from "./chunk-P3PTUSP4.js";
12
12
 
13
13
  // src/config/config-manager.ts
14
14
  import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
@@ -6,15 +6,15 @@ import {
6
6
  } from "./chunk-HLWUDRBO.js";
7
7
  import {
8
8
  ProviderRegistry
9
- } from "./chunk-IQ7JE43O.js";
10
- import "./chunk-HIU2SH4V.js";
9
+ } from "./chunk-BGXQGCEO.js";
10
+ import "./chunk-JOR572WG.js";
11
11
  import {
12
12
  ConfigManager
13
- } from "./chunk-X4J2DZB5.js";
13
+ } from "./chunk-VQT27CZK.js";
14
14
  import "./chunk-TZQHYZKT.js";
15
15
  import {
16
16
  VERSION
17
- } from "./chunk-4KMDKDAK.js";
17
+ } from "./chunk-P3PTUSP4.js";
18
18
 
19
19
  // src/cli/ci.ts
20
20
  import { execFileSync, execSync } from "child_process";
@@ -36,7 +36,7 @@ import {
36
36
  TEST_TIMEOUT,
37
37
  VERSION,
38
38
  buildUserIdentityPrompt
39
- } from "./chunk-4KMDKDAK.js";
39
+ } from "./chunk-P3PTUSP4.js";
40
40
  export {
41
41
  AGENTIC_BEHAVIOR_GUIDELINE,
42
42
  APP_NAME,
@@ -2,26 +2,26 @@
2
2
  import {
3
3
  getConfigDirUsage,
4
4
  listRecentCrashes
5
- } from "./chunk-UVW3WLSV.js";
5
+ } from "./chunk-PO3ZY3PN.js";
6
6
  import {
7
7
  ProviderRegistry
8
- } from "./chunk-IQ7JE43O.js";
8
+ } from "./chunk-BGXQGCEO.js";
9
9
  import {
10
10
  getStatsSnapshot,
11
11
  getTopFailingTools,
12
12
  getTopUsedTools,
13
13
  resetStats
14
- } from "./chunk-KHS7RSGR.js";
15
- import "./chunk-HIU2SH4V.js";
14
+ } from "./chunk-SHI5UFUH.js";
15
+ import "./chunk-JOR572WG.js";
16
16
  import {
17
17
  ConfigManager
18
- } from "./chunk-X4J2DZB5.js";
18
+ } from "./chunk-VQT27CZK.js";
19
19
  import "./chunk-TZQHYZKT.js";
20
20
  import {
21
21
  DEV_STATE_FILE_NAME,
22
22
  MEMORY_FILE_NAME,
23
23
  VERSION
24
- } from "./chunk-4KMDKDAK.js";
24
+ } from "./chunk-P3PTUSP4.js";
25
25
  import "./chunk-IW3Q7AE5.js";
26
26
 
27
27
  // src/diagnostics/doctor-cli.ts
@@ -36,7 +36,7 @@ import {
36
36
  VERSION,
37
37
  buildUserIdentityPrompt,
38
38
  runTestsTool
39
- } from "./chunk-ZN5IEPSS.js";
39
+ } from "./chunk-IFETB4PY.js";
40
40
  import {
41
41
  hasSemanticIndex,
42
42
  semanticSearch
@@ -1965,6 +1965,71 @@ You are now in a CONTENT-ONLY streaming pass. The file at the configured path wi
1965
1965
  - If you accidentally start a <tool_call>, STOP and produce the document body instead.
1966
1966
 
1967
1967
  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.`;
1968
+ function isCleanDocumentBody(content) {
1969
+ if (!content) return false;
1970
+ if (detectMetaNarration(content)) return false;
1971
+ if (detectPseudoToolCalls(content)) return false;
1972
+ return looksLikeDocumentBody(content);
1973
+ }
1974
+ function teeFileStats(content) {
1975
+ return { lines: content.split("\n").length, bytes: Buffer.byteLength(content, "utf-8") };
1976
+ }
1977
+ function evaluateTeeContent(rawContent, saveToFile, priorContent) {
1978
+ const fallback = (matched) => {
1979
+ if (priorContent && priorContent !== rawContent && isCleanDocumentBody(priorContent)) {
1980
+ const body = priorContent.trim();
1981
+ const { lines: lines2, bytes: bytes2 } = teeFileStats(body);
1982
+ return {
1983
+ kind: "fallback",
1984
+ content: body,
1985
+ matched,
1986
+ summary: `File saved (fallback to last shown response): ${saveToFile} (${lines2} lines, ${bytes2} bytes). The fresh content-only stream produced no usable document body (matched: ${matched}); saved the response you previously displayed instead. Verify it is the intended document.`
1987
+ };
1988
+ }
1989
+ return null;
1990
+ };
1991
+ const metaMatch = detectMetaNarration(rawContent);
1992
+ if (metaMatch) {
1993
+ return fallback(metaMatch) ?? {
1994
+ kind: "reject",
1995
+ reason: "meta-narration",
1996
+ matched: metaMatch,
1997
+ summary: `[save_last_response REJECTED] Your output was internal reasoning / meta-narration (e.g. "Let me re-read\u2026", "the user is asking me to\u2026") instead of the requested document body (matched: ${metaMatch}). ${saveToFile} was NOT saved.
1998
+
1999
+ This fresh stream has NO tools. Produce ONLY the document body: start with a markdown heading like "# \u5BA1\u8BA1\u62A5\u544A" / "# Audit Report" and write the full content. Do NOT narrate that you will produce the document \u2014 produce it.`
2000
+ };
2001
+ }
2002
+ const pseudoMatch = detectPseudoToolCalls(rawContent);
2003
+ if (pseudoMatch) {
2004
+ const cleaned = stripPseudoToolCalls(rawContent);
2005
+ if (looksLikeDocumentBody(cleaned)) {
2006
+ const { lines: lines2, bytes: bytes2 } = teeFileStats(cleaned);
2007
+ return {
2008
+ kind: "salvaged",
2009
+ content: cleaned,
2010
+ matched: pseudoMatch,
2011
+ summary: `File saved (with cleanup): ${saveToFile} (${lines2} lines, ${bytes2} bytes; pseudo-tool-call markup matching ${pseudoMatch} was stripped before save)`
2012
+ };
2013
+ }
2014
+ return fallback(pseudoMatch) ?? {
2015
+ kind: "reject",
2016
+ reason: "pseudo-tool-call",
2017
+ matched: pseudoMatch,
2018
+ summary: `[save_last_response REJECTED] Your output was tool-call markup with no usable document body (matched: ${pseudoMatch}). ${saveToFile} was NOT saved.
2019
+
2020
+ 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.`
2021
+ };
2022
+ }
2023
+ const { lines, bytes } = teeFileStats(rawContent);
2024
+ return {
2025
+ kind: "ok",
2026
+ content: rawContent,
2027
+ summary: `File saved: ${saveToFile} (${lines} lines, ${bytes} bytes)`
2028
+ };
2029
+ }
2030
+ function teeStreamErrorSummary(saveToFile, errMsg) {
2031
+ 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).`;
2032
+ }
1968
2033
 
1969
2034
  // src/core/agent-loop.ts
1970
2035
  function partialTagTail(s, tag) {
@@ -2414,6 +2479,52 @@ function summarizeRecentTools(history, interval) {
2414
2479
  }
2415
2480
  return [...counts.entries()].sort((a, b) => b[1] - a[1]).map(([name, count]) => count > 1 ? `${name}\xD7${count}` : name).join(", ");
2416
2481
  }
2482
+ async function runLeanAgentLoop(opts) {
2483
+ const extraMessages = [];
2484
+ const usage = { inputTokens: 0, outputTokens: 0 };
2485
+ let content = "";
2486
+ let roundsUsed = 0;
2487
+ let toolCallCount = 0;
2488
+ try {
2489
+ for (let round = 0; round < opts.maxRounds; round++) {
2490
+ roundsUsed = round + 1;
2491
+ opts.onRoundStart?.(round, opts.maxRounds);
2492
+ const result = await opts.provider.chatWithTools(
2493
+ {
2494
+ messages: opts.messages,
2495
+ model: opts.model,
2496
+ systemPrompt: opts.systemPromptForRound(round, opts.maxRounds - round),
2497
+ stream: false,
2498
+ temperature: opts.chatParams?.temperature,
2499
+ maxTokens: opts.chatParams?.maxTokens,
2500
+ timeout: opts.chatParams?.timeout,
2501
+ thinking: opts.chatParams?.thinking,
2502
+ ...extraMessages.length > 0 ? { _extraMessages: extraMessages } : {}
2503
+ },
2504
+ opts.toolDefsForRound(round)
2505
+ );
2506
+ if (result.usage) {
2507
+ usage.inputTokens += result.usage.inputTokens;
2508
+ usage.outputTokens += result.usage.outputTokens;
2509
+ }
2510
+ if ("content" in result) {
2511
+ content = result.content;
2512
+ break;
2513
+ }
2514
+ toolCallCount += result.toolCalls.length;
2515
+ const toolResults = await opts.executeTools(result.toolCalls);
2516
+ const reasoningContent = "reasoningContent" in result ? result.reasoningContent : void 0;
2517
+ extraMessages.push(
2518
+ ...opts.provider.buildToolResultMessages(result.toolCalls, toolResults, reasoningContent)
2519
+ );
2520
+ }
2521
+ if (!content) content = opts.onRoundsExhausted(toolCallCount);
2522
+ } catch (err) {
2523
+ const errMsg = err instanceof Error ? err.message : String(err);
2524
+ content = opts.onError(errMsg);
2525
+ }
2526
+ return { content, usage, roundsUsed, toolCallCount };
2527
+ }
2417
2528
 
2418
2529
  // src/providers/openai-compatible.ts
2419
2530
  function toUsage(u) {
@@ -8329,9 +8440,19 @@ ${preamble}`;
8329
8440
  }
8330
8441
 
8331
8442
  // src/tools/builtin/save-last-response.ts
8332
- import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync5 } from "fs";
8443
+ import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync5, unlinkSync as unlinkSync3, rmdirSync as rmdirSync2 } from "fs";
8333
8444
  import { dirname as dirname4 } from "path";
8334
8445
  var lastResponseStore = { content: "" };
8446
+ function cleanupRejectedTeeFile(filePath) {
8447
+ try {
8448
+ unlinkSync3(filePath);
8449
+ } catch {
8450
+ }
8451
+ try {
8452
+ rmdirSync2(dirname4(filePath));
8453
+ } catch {
8454
+ }
8455
+ }
8335
8456
  var saveLastResponseTool = {
8336
8457
  definition: {
8337
8458
  name: "save_last_response",
@@ -8873,61 +8994,37 @@ async function runSubAgent(task, maxRounds, agentIndex, ctx) {
8873
8994
  ];
8874
8995
  const subSystemPrompt = buildSubAgentSystemPrompt(task, ctx.systemPrompt);
8875
8996
  const toolDefs = subRegistry.getDefinitions();
8876
- const extraMessages = [];
8877
- const totalUsage = { inputTokens: 0, outputTokens: 0 };
8878
- let finalContent = "";
8879
8997
  printSubAgentHeader(task, maxRounds, agentIndex);
8880
- try {
8881
- for (let round = 0; round < maxRounds; round++) {
8998
+ const loop = await runLeanAgentLoop({
8999
+ provider: ctx.provider,
9000
+ messages: subMessages,
9001
+ model: ctx.model,
9002
+ maxRounds,
9003
+ chatParams: {
9004
+ temperature: ctx.modelParams.temperature,
9005
+ maxTokens: ctx.modelParams.maxTokens,
9006
+ timeout: ctx.modelParams.timeout,
9007
+ thinking: ctx.modelParams.thinking
9008
+ },
9009
+ executeTools: (calls) => subExecutor.executeAll(calls),
9010
+ systemPromptForRound: () => subSystemPrompt,
9011
+ toolDefsForRound: () => toolDefs,
9012
+ onRoundStart: (round) => {
8882
9013
  subExecutor.setRoundInfo(round + 1, maxRounds);
8883
- const result = await ctx.provider.chatWithTools(
8884
- {
8885
- messages: subMessages,
8886
- model: ctx.model,
8887
- systemPrompt: subSystemPrompt,
8888
- stream: false,
8889
- temperature: ctx.modelParams.temperature,
8890
- maxTokens: ctx.modelParams.maxTokens,
8891
- timeout: ctx.modelParams.timeout,
8892
- thinking: ctx.modelParams.thinking,
8893
- ...extraMessages.length > 0 ? { _extraMessages: extraMessages } : {}
8894
- },
8895
- toolDefs
8896
- );
8897
- if (result.usage) {
8898
- totalUsage.inputTokens += result.usage.inputTokens;
8899
- totalUsage.outputTokens += result.usage.outputTokens;
8900
- }
8901
- if ("content" in result) {
8902
- finalContent = result.content;
8903
- break;
8904
- }
8905
9014
  if (ctx.configManager) {
8906
9015
  googleSearchContext.configManager = ctx.configManager;
8907
9016
  }
8908
- const toolResults = await subExecutor.executeAll(result.toolCalls);
8909
- const reasoningContent = "reasoningContent" in result ? result.reasoningContent : void 0;
8910
- const newMsgs = ctx.provider.buildToolResultMessages(
8911
- result.toolCalls,
8912
- toolResults,
8913
- reasoningContent
8914
- );
8915
- extraMessages.push(...newMsgs);
8916
- }
8917
- if (!finalContent) {
8918
- finalContent = `(Sub-agent reached maximum rounds (${maxRounds}) without producing a final response)`;
8919
- }
8920
- } catch (err) {
8921
- const errMsg = err instanceof Error ? err.message : String(err);
8922
- finalContent = `(Sub-agent error: ${errMsg})`;
8923
- process.stderr.write(
8924
- `
9017
+ },
9018
+ onRoundsExhausted: () => `(Sub-agent reached maximum rounds (${maxRounds}) without producing a final response)`,
9019
+ onError: (errMsg) => {
9020
+ process.stderr.write(`
8925
9021
  [spawn_agent] Error in sub-agent loop: ${errMsg}
8926
- `
8927
- );
8928
- }
8929
- printSubAgentFooter(totalUsage, agentIndex);
8930
- return { content: finalContent, usage: totalUsage };
9022
+ `);
9023
+ return `(Sub-agent error: ${errMsg})`;
9024
+ }
9025
+ });
9026
+ printSubAgentFooter(loop.usage, agentIndex);
9027
+ return { content: loop.content, usage: loop.usage };
8931
9028
  }
8932
9029
  var spawnAgentTool = {
8933
9030
  definition: {
@@ -11174,7 +11271,7 @@ import { existsSync as existsSync19, readFileSync as readFileSync13 } from "fs";
11174
11271
  import { join as join12 } from "path";
11175
11272
 
11176
11273
  // src/repl/dev-state.ts
11177
- import { existsSync as existsSync18, readFileSync as readFileSync12, writeFileSync as writeFileSync6, unlinkSync as unlinkSync3, mkdirSync as mkdirSync9 } from "fs";
11274
+ import { existsSync as existsSync18, readFileSync as readFileSync12, writeFileSync as writeFileSync6, unlinkSync as unlinkSync4, mkdirSync as mkdirSync9 } from "fs";
11178
11275
  import { join as join11 } from "path";
11179
11276
  import { homedir as homedir6 } from "os";
11180
11277
  function getDevStatePath() {
@@ -11448,7 +11545,7 @@ function autoTrimSessionIfNeeded(session, sizeLimit = SESSION_SIZE_LIMIT) {
11448
11545
  }
11449
11546
 
11450
11547
  // src/web/session-handler.ts
11451
- import { existsSync as existsSync21, readFileSync as readFileSync14, appendFileSync as appendFileSync3, writeFileSync as writeFileSync7, mkdirSync as mkdirSync10, readdirSync as readdirSync9, statSync as statSync8, createWriteStream, unlinkSync as unlinkSync4 } from "fs";
11548
+ import { existsSync as existsSync21, readFileSync as readFileSync14, appendFileSync as appendFileSync3, writeFileSync as writeFileSync7, mkdirSync as mkdirSync10, readdirSync as readdirSync9, statSync as statSync8, createWriteStream } from "fs";
11452
11549
  import { join as join15, resolve as resolve5, dirname as dirname5 } from "path";
11453
11550
  import { execSync as execSync3 } from "child_process";
11454
11551
 
@@ -12156,6 +12253,20 @@ async function persistDiscussion(state2, config, defaultProvider, defaultModel)
12156
12253
  }
12157
12254
 
12158
12255
  // src/web/session-handler.ts
12256
+ function lastAssistantText(messages) {
12257
+ for (let i = messages.length - 1; i >= 0; i--) {
12258
+ const m = messages[i];
12259
+ if (!m || m.role !== "assistant") continue;
12260
+ const c = m.content;
12261
+ if (typeof c === "string") return c;
12262
+ if (Array.isArray(c)) {
12263
+ const text = c.filter((p) => p && p.type === "text" && typeof p.text === "string").map((p) => p.text).join("");
12264
+ if (text) return text;
12265
+ }
12266
+ return void 0;
12267
+ }
12268
+ return void 0;
12269
+ }
12159
12270
  var SessionHandler = class _SessionHandler {
12160
12271
  ws;
12161
12272
  config;
@@ -12965,53 +13076,18 @@ ${summaryContent}`,
12965
13076
  await new Promise((resolve7, reject) => {
12966
13077
  fileStream.end((err) => err ? reject(err) : resolve7());
12967
13078
  });
12968
- const metaMatch = detectMetaNarration(fullContent);
12969
- if (metaMatch) {
12970
- try {
12971
- unlinkSync4(saveToFile);
12972
- } catch {
12973
- }
13079
+ const verdict = evaluateTeeContent(fullContent, saveToFile, lastAssistantText(apiMessages));
13080
+ if (verdict.kind === "reject") {
13081
+ cleanupRejectedTeeFile(saveToFile);
12974
13082
  isError = true;
12975
- summary = `[save_last_response REJECTED] Your output was internal reasoning / meta-narration (e.g. "Let me re-read\u2026", "the user is asking me to\u2026") instead of the requested document body (matched: ${metaMatch}). ${saveToFile} was NOT saved.
12976
-
12977
- This fresh stream has NO tools. Produce ONLY the document body: start with a markdown heading and write the full content. Do NOT narrate that you will produce the document \u2014 produce it.`;
12978
- if (teeUsage) {
12979
- roundUsage.inputTokens += teeUsage.inputTokens;
12980
- roundUsage.outputTokens += teeUsage.outputTokens;
12981
- roundUsage.cacheCreationTokens += teeUsage.cacheCreationTokens ?? 0;
12982
- roundUsage.cacheReadTokens += teeUsage.cacheReadTokens ?? 0;
12983
- }
12984
- this.send({
12985
- type: "tool_call_result",
12986
- callId: call.id,
12987
- toolName: call.name,
12988
- content: summary,
12989
- isError: true
12990
- });
12991
- return { content: "", summary, isError: true };
12992
- }
12993
- const pseudoMatch = detectPseudoToolCalls(fullContent);
12994
- if (pseudoMatch) {
12995
- const cleaned = stripPseudoToolCalls(fullContent);
12996
- if (looksLikeDocumentBody(cleaned)) {
12997
- writeFileSync7(saveToFile, cleaned, "utf-8");
12998
- fullContent = cleaned;
12999
- const lines = cleaned.split("\n").length;
13000
- const bytes = Buffer.byteLength(cleaned, "utf-8");
13001
- summary = `File saved (with cleanup): ${saveToFile} (${lines} lines, ${bytes} bytes; pseudo-tool-call markup matching ${pseudoMatch} was stripped before save)`;
13002
- undoStack.push(saveToFile, `save_last_response: ${saveToFile}`);
13003
- } else {
13004
- try {
13005
- unlinkSync4(saveToFile);
13006
- } catch {
13007
- }
13008
- isError = true;
13009
- 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.`;
13010
- }
13083
+ summary = verdict.summary;
13084
+ fullContent = "";
13011
13085
  } else {
13012
- const lines = fullContent.split("\n").length;
13013
- const bytes = Buffer.byteLength(fullContent, "utf-8");
13014
- summary = `File saved: ${saveToFile} (${lines} lines, ${bytes} bytes)`;
13086
+ if (verdict.kind === "salvaged" || verdict.kind === "fallback") {
13087
+ writeFileSync7(saveToFile, verdict.content, "utf-8");
13088
+ fullContent = verdict.content;
13089
+ }
13090
+ summary = verdict.summary;
13015
13091
  undoStack.push(saveToFile, `save_last_response: ${saveToFile}`);
13016
13092
  }
13017
13093
  if (teeUsage) {
@@ -13027,13 +13103,10 @@ This fresh stream has NO tools. Produce ONLY the document body: start with a mar
13027
13103
  } catch {
13028
13104
  }
13029
13105
  }
13030
- try {
13031
- unlinkSync4(saveToFile);
13032
- } catch {
13033
- }
13106
+ cleanupRejectedTeeFile(saveToFile);
13034
13107
  isError = true;
13035
13108
  const msg = err instanceof Error ? err.message : String(err);
13036
- summary = `[save_last_response failed] streaming was interrupted: ${msg}. ${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).`;
13109
+ summary = teeStreamErrorSummary(saveToFile, msg);
13037
13110
  }
13038
13111
  this.send({
13039
13112
  type: "tool_call_result",
@@ -14076,7 +14149,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
14076
14149
  case "test": {
14077
14150
  this.send({ type: "info", message: "\u{1F9EA} Running tests..." });
14078
14151
  try {
14079
- const { executeTests } = await import("./run-tests-OTZE5CEN.js");
14152
+ const { executeTests } = await import("./run-tests-4QWMA4ZF.js");
14080
14153
  const argStr = args.join(" ").trim();
14081
14154
  let testArgs = {};
14082
14155
  if (argStr) {
@@ -154,7 +154,7 @@ ${content}`);
154
154
  }
155
155
  }
156
156
  async function runTaskMode(config, providers, configManager, topic) {
157
- const { TaskOrchestrator } = await import("./task-orchestrator-DEWKVJGQ.js");
157
+ const { TaskOrchestrator } = await import("./task-orchestrator-RI63ZZNN.js");
158
158
  const orchestrator = new TaskOrchestrator(config, providers, configManager);
159
159
  let interrupted = false;
160
160
  const onSigint = () => {