jinzd-ai-cli 0.4.114 → 0.4.116

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,10 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  AuthManager,
4
+ TOKEN_EXPIRY_MS,
4
5
  __resetLoginAttemptsForTests
5
- } from "./chunk-5UPFMM2A.js";
6
+ } from "./chunk-O7NM4WTS.js";
6
7
  import "./chunk-PDX44BCA.js";
7
8
  export {
8
9
  AuthManager,
10
+ TOKEN_EXPIRY_MS,
9
11
  __resetLoginAttemptsForTests
10
12
  };
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  ConfigManager
4
- } from "./chunk-CP6PALA4.js";
4
+ } from "./chunk-KRTQDZMY.js";
5
5
  import "./chunk-2ZD3YTVM.js";
6
- import "./chunk-UF62SHR7.js";
6
+ import "./chunk-H3F2E4MD.js";
7
7
  import "./chunk-PDX44BCA.js";
8
8
 
9
9
  // src/cli/batch.ts
@@ -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.114";
9
+ var VERSION = "0.4.116";
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.114";
4
+ var VERSION = "0.4.116";
5
5
  var APP_NAME = "ai-cli";
6
6
  var CONFIG_DIR_NAME = ".aicli";
7
7
  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-UF62SHR7.js";
11
+ } from "./chunk-H3F2E4MD.js";
12
12
 
13
13
  // src/config/config-manager.ts
14
14
  import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
@@ -1,11 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/web/auth.ts
4
- import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync, copyFileSync } from "fs";
4
+ import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync, copyFileSync, renameSync, unlinkSync } from "fs";
5
5
  import { join } from "path";
6
6
  import { createHmac, randomBytes, timingSafeEqual, pbkdf2Sync } from "crypto";
7
7
  var USERS_FILE = "users.json";
8
8
  var TOKEN_EXPIRY_HOURS = 24;
9
+ var TOKEN_EXPIRY_MS = TOKEN_EXPIRY_HOURS * 3600 * 1e3;
9
10
  var USERS_DIR = "users";
10
11
  var LOGIN_MAX_FAILS = 5;
11
12
  var LOGIN_LOCKOUT_MS = 15 * 60 * 1e3;
@@ -252,7 +253,17 @@ var AuthManager = class {
252
253
  }
253
254
  saveDB(db) {
254
255
  mkdirSync(this.baseDir, { recursive: true });
255
- writeFileSync(this.usersFile, JSON.stringify(db, null, 2), "utf-8");
256
+ const tmp = `${this.usersFile}.tmp`;
257
+ try {
258
+ writeFileSync(tmp, JSON.stringify(db, null, 2), "utf-8");
259
+ renameSync(tmp, this.usersFile);
260
+ } catch (err) {
261
+ try {
262
+ unlinkSync(tmp);
263
+ } catch {
264
+ }
265
+ throw err;
266
+ }
256
267
  }
257
268
  /** Legacy hash — kept only for migrating old users (v0.2.x) */
258
269
  hashPasswordLegacy(password, salt) {
@@ -278,6 +289,7 @@ var AuthManager = class {
278
289
  };
279
290
 
280
291
  export {
292
+ TOKEN_EXPIRY_MS,
281
293
  __resetLoginAttemptsForTests,
282
294
  AuthManager
283
295
  };
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  schemaToJsonSchema,
4
4
  truncateForPersist
5
- } from "./chunk-XXKWSBRC.js";
5
+ } from "./chunk-YTP26DOV.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-UF62SHR7.js";
21
+ } from "./chunk-H3F2E4MD.js";
22
22
  import {
23
23
  redactJson
24
24
  } from "./chunk-7ZJN4KLV.js";
@@ -72,10 +72,20 @@ var ClaudeProvider = class extends BaseProvider {
72
72
  ]
73
73
  };
74
74
  async initialize(apiKey, options) {
75
- this.client = new Anthropic({
75
+ const clientOptions = {
76
76
  apiKey,
77
77
  baseURL: options?.baseUrl
78
- });
78
+ };
79
+ const proxyUrl = options?.proxy;
80
+ try {
81
+ const { Agent, ProxyAgent, fetch: undiciFetch } = await import("undici");
82
+ const STREAM_BODY_TIMEOUT = 30 * 60 * 1e3;
83
+ const STREAM_HEADERS_TIMEOUT = 5 * 60 * 1e3;
84
+ const dispatcher = proxyUrl ? new ProxyAgent({ uri: proxyUrl, bodyTimeout: STREAM_BODY_TIMEOUT, headersTimeout: STREAM_HEADERS_TIMEOUT }) : new Agent({ bodyTimeout: STREAM_BODY_TIMEOUT, headersTimeout: STREAM_HEADERS_TIMEOUT });
85
+ clientOptions.fetch = ((url, init) => undiciFetch(url, { ...init, dispatcher }));
86
+ } catch {
87
+ }
88
+ this.client = new Anthropic(clientOptions);
79
89
  }
80
90
  /**
81
91
  * 将内部 MessageContentPart[] 格式转换为 Anthropic SDK 期望的 ContentBlockParam[]。
@@ -932,13 +942,20 @@ var OpenAICompatibleProvider = class extends BaseProvider {
932
942
  timeout: this.defaultTimeout
933
943
  };
934
944
  const proxyUrl = options?.proxy;
935
- if (proxyUrl) {
936
- try {
937
- const { ProxyAgent, fetch: undiciFetch } = await import("undici");
938
- const agent = new ProxyAgent({ uri: proxyUrl });
939
- clientOptions.fetch = ((url, init) => undiciFetch(url, { ...init, dispatcher: agent }));
940
- } catch {
941
- }
945
+ try {
946
+ const { Agent, ProxyAgent, fetch: undiciFetch } = await import("undici");
947
+ const STREAM_BODY_TIMEOUT = 30 * 60 * 1e3;
948
+ const STREAM_HEADERS_TIMEOUT = 5 * 60 * 1e3;
949
+ const dispatcher = proxyUrl ? new ProxyAgent({
950
+ uri: proxyUrl,
951
+ bodyTimeout: STREAM_BODY_TIMEOUT,
952
+ headersTimeout: STREAM_HEADERS_TIMEOUT
953
+ }) : new Agent({
954
+ bodyTimeout: STREAM_BODY_TIMEOUT,
955
+ headersTimeout: STREAM_HEADERS_TIMEOUT
956
+ });
957
+ clientOptions.fetch = ((url, init) => undiciFetch(url, { ...init, dispatcher }));
958
+ } catch {
942
959
  }
943
960
  this.client = new OpenAI(clientOptions);
944
961
  }
@@ -1854,6 +1871,40 @@ function peelMetaNarration(content) {
1854
1871
  }
1855
1872
  return out.trim();
1856
1873
  }
1874
+ var META_NARRATION_HARD_MARKERS = [
1875
+ /\[⚠️\s*CONTENT GENERATION MODE\]/,
1876
+ /CONTENT_ONLY_STREAM_REMINDER\b/,
1877
+ /<system-reminder>/i
1878
+ ];
1879
+ var META_NARRATION_HEURISTICS = [
1880
+ /\bthe user (?:is asking me|wants me|is requesting|expects me)\b/i,
1881
+ /\blet me (?:re-?read|re-?consider|reconsider|think about|carefully (?:re-?read|consider))\b/i,
1882
+ /\bI'?m (?:in (?:a )?content-only|in CONTENT-ONLY|currently in)\b/i,
1883
+ /\bI think (?:there might be|I should|I cannot|the (?:user|best)|maybe)\b/i,
1884
+ /\bWait,?\s+let me\b/i,
1885
+ /\bActually,?\s+I\b/i,
1886
+ /\bI need to be honest with the user\b/i,
1887
+ /\bI(?:'m| am) in a special mode\b/i,
1888
+ /\bGiven that I cannot\b/i
1889
+ ];
1890
+ function detectMetaNarration(content) {
1891
+ if (!content) return null;
1892
+ const head = content.slice(0, 2e3);
1893
+ for (const re of META_NARRATION_HARD_MARKERS) {
1894
+ if (re.test(head)) return re.source;
1895
+ }
1896
+ if (/^#{1,3}\s+\S/m.test(head)) return null;
1897
+ let hits = 0;
1898
+ let firstMatch = "";
1899
+ for (const re of META_NARRATION_HEURISTICS) {
1900
+ if (re.test(head)) {
1901
+ hits++;
1902
+ if (!firstMatch) firstMatch = re.source;
1903
+ if (hits >= 2) return `meta-narration:${firstMatch}`;
1904
+ }
1905
+ }
1906
+ return null;
1907
+ }
1857
1908
  function looksLikeDocumentBody(content) {
1858
1909
  if (!content || content.length < 200) return false;
1859
1910
  if (/^#{1,6}\s+\S/m.test(content)) return true;
@@ -4156,6 +4207,7 @@ export {
4156
4207
  buildPhantomCorrectionMessage,
4157
4208
  detectPseudoToolCalls,
4158
4209
  stripPseudoToolCalls,
4210
+ detectMetaNarration,
4159
4211
  looksLikeDocumentBody,
4160
4212
  stripToolCallReminder,
4161
4213
  TEE_FINAL_USER_NUDGE,
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  TEST_TIMEOUT
4
- } from "./chunk-UF62SHR7.js";
4
+ } from "./chunk-H3F2E4MD.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-2WAF7FOX.js";
8
+ } from "./chunk-VJE5V4H2.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-UF62SHR7.js";
21
+ } from "./chunk-H3F2E4MD.js";
22
22
  import {
23
23
  fileCheckpoints
24
24
  } from "./chunk-4BKXL7SM.js";
@@ -372,7 +372,7 @@ Important rules:
372
372
  }
373
373
  updateCwdFromCommand(command, effectiveCwd);
374
374
  pushBashUndoEntries(beforeSnapshot, parsedTargetsBefore, effectiveCwd);
375
- const result = IS_WINDOWS && Buffer.isBuffer(stdout) ? stdout.toString("utf-8") : stdout;
375
+ const result = Buffer.isBuffer(stdout) ? stdout.toString("utf-8") : String(stdout ?? "");
376
376
  return result || "(command completed with no output)";
377
377
  } catch (err) {
378
378
  pushBashUndoEntries(beforeSnapshot, parsedTargetsBefore, effectiveCwd);
@@ -400,12 +400,15 @@ How to recover (pick ONE \u2014 do NOT retry the same command):
400
400
  const stderr = IS_WINDOWS && Buffer.isBuffer(execErr.stderr) ? execErr.stderr.toString("utf-8").trim() : execErr.stderr?.toString().trim() ?? "";
401
401
  const stdout = IS_WINDOWS && Buffer.isBuffer(execErr.stdout) ? execErr.stdout.toString("utf-8").trim() : execErr.stdout?.toString().trim() ?? "";
402
402
  const combined = [stdout, stderr].filter(Boolean).join("\n");
403
+ const hint = buildErrorHint(command, combined);
403
404
  throw new ToolError(
404
405
  "bash",
405
406
  `Exit code ${execErr.status}:
406
407
  ${combined || (execErr.message ?? "Unknown error")}
407
408
 
408
- [Command failed. Report this error to the user. Do not retry with variant commands.]`
409
+ ` + (hint ? `${hint}
410
+
411
+ ` : "") + `[Command failed. Report this error to the user. Do not retry with variant commands.]`
409
412
  );
410
413
  }
411
414
  }
@@ -429,6 +432,31 @@ function fixWindowsDeleteCommand(command) {
429
432
  }
430
433
  );
431
434
  }
435
+ function buildErrorHint(command, stderr) {
436
+ const hints = [];
437
+ if (IS_WINDOWS && /\|\|/.test(command) && /(\|\||is not a valid argument|不是此版本中的有效|ParserError|Unexpected token)/i.test(stderr)) {
438
+ hints.push(
439
+ `Hint: PowerShell parses '||' as a pipeline operator (PS 5.x) or logical-or (PS 7+), it CANNOT be passed inline as SQL string concatenation. Workaround: write the SQL to a local .sql file with write_file, then 'scp' it to the remote host and run 'psql -f /tmp/x.sql'. Do NOT try to escape || or wrap the command differently \u2014 it will not work.`
440
+ );
441
+ }
442
+ if (IS_WINDOWS && /\bpython3\b/.test(command) && /(not recognized|is not recognized|无法将.*识别)/i.test(stderr)) {
443
+ hints.push(
444
+ `Hint: On Windows the launcher is 'python' (or 'py'), not 'python3'. Replace 'python3' with 'python' and retry.`
445
+ );
446
+ }
447
+ if (IS_WINDOWS && /^\s*ssh\b/.test(command) && /\\"/.test(command) && /(syntax error|ERROR:|ParserError|unexpected|parser|意外的|语法|不是此版本)/i.test(stderr)) {
448
+ hints.push(
449
+ `Hint: SSH + nested quotes ("...\\"...\\"") is fragile across PS \u2192 ssh \u2192 remote-shell \u2192 psql layers. Use the file-based flow: (1) write_file locally to a .sql file, (2) 'scp x.sql root@host:/tmp/', (3) 'ssh root@host "sudo -u postgres psql -d <db> -f /tmp/x.sql"'. Or for remote-only SQL: 'ssh host "cat > /tmp/x.sql << \\'SQLEOF\\'\\n...\\nSQLEOF"' then run psql -f. Avoid inline psql -c with embedded quotes.`
450
+ );
451
+ }
452
+ const colMissing = stderr.match(/column "([^"]+)" does not exist/i);
453
+ if (colMissing) {
454
+ hints.push(
455
+ `Hint: PostgreSQL says column "${colMissing[1]}" does not exist. Do NOT retry with a guess \u2014 query the actual schema first: \`psql -c "\\d <table>"\` or \`SELECT column_name FROM information_schema.columns WHERE table_name='<table>';\`. Common pitfall: not every table has a 'deleted' soft-delete column.`
456
+ );
457
+ }
458
+ return hints.length > 0 ? hints.map((h) => `\u{1F4A1} ${h}`).join("\n\n") : null;
459
+ }
432
460
  function snapshotDir(dir) {
433
461
  try {
434
462
  return new Set(readdirSync(dir).map((name) => resolve(dir, name)));
@@ -36,7 +36,7 @@ import {
36
36
  TEST_TIMEOUT,
37
37
  VERSION,
38
38
  buildUserIdentityPrompt
39
- } from "./chunk-UF62SHR7.js";
39
+ } from "./chunk-H3F2E4MD.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-W45U3KQE.js";
39
+ } from "./chunk-BXDHW7JO.js";
40
40
  import {
41
41
  hasSemanticIndex,
42
42
  semanticSearch
@@ -604,10 +604,20 @@ var ClaudeProvider = class extends BaseProvider {
604
604
  ]
605
605
  };
606
606
  async initialize(apiKey, options) {
607
- this.client = new Anthropic({
607
+ const clientOptions = {
608
608
  apiKey,
609
609
  baseURL: options?.baseUrl
610
- });
610
+ };
611
+ const proxyUrl = options?.proxy;
612
+ try {
613
+ const { Agent, ProxyAgent, fetch: undiciFetch } = await import("undici");
614
+ const STREAM_BODY_TIMEOUT = 30 * 60 * 1e3;
615
+ const STREAM_HEADERS_TIMEOUT = 5 * 60 * 1e3;
616
+ const dispatcher = proxyUrl ? new ProxyAgent({ uri: proxyUrl, bodyTimeout: STREAM_BODY_TIMEOUT, headersTimeout: STREAM_HEADERS_TIMEOUT }) : new Agent({ bodyTimeout: STREAM_BODY_TIMEOUT, headersTimeout: STREAM_HEADERS_TIMEOUT });
617
+ clientOptions.fetch = ((url, init) => undiciFetch(url, { ...init, dispatcher }));
618
+ } catch {
619
+ }
620
+ this.client = new Anthropic(clientOptions);
611
621
  }
612
622
  /**
613
623
  * 将内部 MessageContentPart[] 格式转换为 Anthropic SDK 期望的 ContentBlockParam[]。
@@ -1464,13 +1474,20 @@ var OpenAICompatibleProvider = class extends BaseProvider {
1464
1474
  timeout: this.defaultTimeout
1465
1475
  };
1466
1476
  const proxyUrl = options?.proxy;
1467
- if (proxyUrl) {
1468
- try {
1469
- const { ProxyAgent, fetch: undiciFetch } = await import("undici");
1470
- const agent = new ProxyAgent({ uri: proxyUrl });
1471
- clientOptions.fetch = ((url, init) => undiciFetch(url, { ...init, dispatcher: agent }));
1472
- } catch {
1473
- }
1477
+ try {
1478
+ const { Agent, ProxyAgent, fetch: undiciFetch } = await import("undici");
1479
+ const STREAM_BODY_TIMEOUT = 30 * 60 * 1e3;
1480
+ const STREAM_HEADERS_TIMEOUT = 5 * 60 * 1e3;
1481
+ const dispatcher = proxyUrl ? new ProxyAgent({
1482
+ uri: proxyUrl,
1483
+ bodyTimeout: STREAM_BODY_TIMEOUT,
1484
+ headersTimeout: STREAM_HEADERS_TIMEOUT
1485
+ }) : new Agent({
1486
+ bodyTimeout: STREAM_BODY_TIMEOUT,
1487
+ headersTimeout: STREAM_HEADERS_TIMEOUT
1488
+ });
1489
+ clientOptions.fetch = ((url, init) => undiciFetch(url, { ...init, dispatcher }));
1490
+ } catch {
1474
1491
  }
1475
1492
  this.client = new OpenAI(clientOptions);
1476
1493
  }
@@ -2268,6 +2285,40 @@ function peelMetaNarration(content) {
2268
2285
  }
2269
2286
  return out.trim();
2270
2287
  }
2288
+ var META_NARRATION_HARD_MARKERS = [
2289
+ /\[⚠️\s*CONTENT GENERATION MODE\]/,
2290
+ /CONTENT_ONLY_STREAM_REMINDER\b/,
2291
+ /<system-reminder>/i
2292
+ ];
2293
+ var META_NARRATION_HEURISTICS = [
2294
+ /\bthe user (?:is asking me|wants me|is requesting|expects me)\b/i,
2295
+ /\blet me (?:re-?read|re-?consider|reconsider|think about|carefully (?:re-?read|consider))\b/i,
2296
+ /\bI'?m (?:in (?:a )?content-only|in CONTENT-ONLY|currently in)\b/i,
2297
+ /\bI think (?:there might be|I should|I cannot|the (?:user|best)|maybe)\b/i,
2298
+ /\bWait,?\s+let me\b/i,
2299
+ /\bActually,?\s+I\b/i,
2300
+ /\bI need to be honest with the user\b/i,
2301
+ /\bI(?:'m| am) in a special mode\b/i,
2302
+ /\bGiven that I cannot\b/i
2303
+ ];
2304
+ function detectMetaNarration(content) {
2305
+ if (!content) return null;
2306
+ const head = content.slice(0, 2e3);
2307
+ for (const re of META_NARRATION_HARD_MARKERS) {
2308
+ if (re.test(head)) return re.source;
2309
+ }
2310
+ if (/^#{1,3}\s+\S/m.test(head)) return null;
2311
+ let hits = 0;
2312
+ let firstMatch = "";
2313
+ for (const re of META_NARRATION_HEURISTICS) {
2314
+ if (re.test(head)) {
2315
+ hits++;
2316
+ if (!firstMatch) firstMatch = re.source;
2317
+ if (hits >= 2) return `meta-narration:${firstMatch}`;
2318
+ }
2319
+ }
2320
+ return null;
2321
+ }
2271
2322
  function looksLikeDocumentBody(content) {
2272
2323
  if (!content || content.length < 200) return false;
2273
2324
  if (/^#{1,6}\s+\S/m.test(content)) return true;
@@ -3885,7 +3936,7 @@ Important rules:
3885
3936
  }
3886
3937
  updateCwdFromCommand(command, effectiveCwd);
3887
3938
  pushBashUndoEntries(beforeSnapshot, parsedTargetsBefore, effectiveCwd);
3888
- const result = IS_WINDOWS && Buffer.isBuffer(stdout) ? stdout.toString("utf-8") : stdout;
3939
+ const result = Buffer.isBuffer(stdout) ? stdout.toString("utf-8") : String(stdout ?? "");
3889
3940
  return result || "(command completed with no output)";
3890
3941
  } catch (err) {
3891
3942
  pushBashUndoEntries(beforeSnapshot, parsedTargetsBefore, effectiveCwd);
@@ -3913,12 +3964,15 @@ How to recover (pick ONE \u2014 do NOT retry the same command):
3913
3964
  const stderr = IS_WINDOWS && Buffer.isBuffer(execErr.stderr) ? execErr.stderr.toString("utf-8").trim() : execErr.stderr?.toString().trim() ?? "";
3914
3965
  const stdout = IS_WINDOWS && Buffer.isBuffer(execErr.stdout) ? execErr.stdout.toString("utf-8").trim() : execErr.stdout?.toString().trim() ?? "";
3915
3966
  const combined = [stdout, stderr].filter(Boolean).join("\n");
3967
+ const hint = buildErrorHint(command, combined);
3916
3968
  throw new ToolError(
3917
3969
  "bash",
3918
3970
  `Exit code ${execErr.status}:
3919
3971
  ${combined || (execErr.message ?? "Unknown error")}
3920
3972
 
3921
- [Command failed. Report this error to the user. Do not retry with variant commands.]`
3973
+ ` + (hint ? `${hint}
3974
+
3975
+ ` : "") + `[Command failed. Report this error to the user. Do not retry with variant commands.]`
3922
3976
  );
3923
3977
  }
3924
3978
  }
@@ -3942,6 +3996,31 @@ function fixWindowsDeleteCommand(command) {
3942
3996
  }
3943
3997
  );
3944
3998
  }
3999
+ function buildErrorHint(command, stderr) {
4000
+ const hints = [];
4001
+ if (IS_WINDOWS && /\|\|/.test(command) && /(\|\||is not a valid argument|不是此版本中的有效|ParserError|Unexpected token)/i.test(stderr)) {
4002
+ hints.push(
4003
+ `Hint: PowerShell parses '||' as a pipeline operator (PS 5.x) or logical-or (PS 7+), it CANNOT be passed inline as SQL string concatenation. Workaround: write the SQL to a local .sql file with write_file, then 'scp' it to the remote host and run 'psql -f /tmp/x.sql'. Do NOT try to escape || or wrap the command differently \u2014 it will not work.`
4004
+ );
4005
+ }
4006
+ if (IS_WINDOWS && /\bpython3\b/.test(command) && /(not recognized|is not recognized|无法将.*识别)/i.test(stderr)) {
4007
+ hints.push(
4008
+ `Hint: On Windows the launcher is 'python' (or 'py'), not 'python3'. Replace 'python3' with 'python' and retry.`
4009
+ );
4010
+ }
4011
+ if (IS_WINDOWS && /^\s*ssh\b/.test(command) && /\\"/.test(command) && /(syntax error|ERROR:|ParserError|unexpected|parser|意外的|语法|不是此版本)/i.test(stderr)) {
4012
+ hints.push(
4013
+ `Hint: SSH + nested quotes ("...\\"...\\"") is fragile across PS \u2192 ssh \u2192 remote-shell \u2192 psql layers. Use the file-based flow: (1) write_file locally to a .sql file, (2) 'scp x.sql root@host:/tmp/', (3) 'ssh root@host "sudo -u postgres psql -d <db> -f /tmp/x.sql"'. Or for remote-only SQL: 'ssh host "cat > /tmp/x.sql << \\'SQLEOF\\'\\n...\\nSQLEOF"' then run psql -f. Avoid inline psql -c with embedded quotes.`
4014
+ );
4015
+ }
4016
+ const colMissing = stderr.match(/column "([^"]+)" does not exist/i);
4017
+ if (colMissing) {
4018
+ hints.push(
4019
+ `Hint: PostgreSQL says column "${colMissing[1]}" does not exist. Do NOT retry with a guess \u2014 query the actual schema first: \`psql -c "\\d <table>"\` or \`SELECT column_name FROM information_schema.columns WHERE table_name='<table>';\`. Common pitfall: not every table has a 'deleted' soft-delete column.`
4020
+ );
4021
+ }
4022
+ return hints.length > 0 ? hints.map((h) => `\u{1F4A1} ${h}`).join("\n\n") : null;
4023
+ }
3945
4024
  function snapshotDir(dir) {
3946
4025
  try {
3947
4026
  return new Set(readdirSync2(dir).map((name) => resolve(dir, name)));
@@ -10842,6 +10921,31 @@ ${summaryResult.content}`,
10842
10921
  await new Promise((resolve7, reject) => {
10843
10922
  fileStream.end((err) => err ? reject(err) : resolve7());
10844
10923
  });
10924
+ const metaMatch = detectMetaNarration(fullContent);
10925
+ if (metaMatch) {
10926
+ try {
10927
+ unlinkSync4(saveToFile);
10928
+ } catch {
10929
+ }
10930
+ isError = true;
10931
+ 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.
10932
+
10933
+ 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.`;
10934
+ if (teeUsage) {
10935
+ roundUsage.inputTokens += teeUsage.inputTokens;
10936
+ roundUsage.outputTokens += teeUsage.outputTokens;
10937
+ roundUsage.cacheCreationTokens += teeUsage.cacheCreationTokens ?? 0;
10938
+ roundUsage.cacheReadTokens += teeUsage.cacheReadTokens ?? 0;
10939
+ }
10940
+ this.send({
10941
+ type: "tool_call_result",
10942
+ callId: call.id,
10943
+ result: summary,
10944
+ isError: true,
10945
+ endTime: Date.now()
10946
+ });
10947
+ return { content: "", summary, isError: true };
10948
+ }
10845
10949
  const pseudoMatch = detectPseudoToolCalls(fullContent);
10846
10950
  if (pseudoMatch) {
10847
10951
  const cleaned = stripPseudoToolCalls(fullContent);
@@ -10879,9 +10983,13 @@ ${summaryResult.content}`,
10879
10983
  } catch {
10880
10984
  }
10881
10985
  }
10986
+ try {
10987
+ unlinkSync4(saveToFile);
10988
+ } catch {
10989
+ }
10882
10990
  isError = true;
10883
10991
  const msg = err instanceof Error ? err.message : String(err);
10884
- summary = `[save_last_response failed] ${msg}`;
10992
+ 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).`;
10885
10993
  }
10886
10994
  this.send({
10887
10995
  type: "tool_call_result",
@@ -11960,7 +12068,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
11960
12068
  case "test": {
11961
12069
  this.send({ type: "info", message: "\u{1F9EA} Running tests..." });
11962
12070
  try {
11963
- const { executeTests } = await import("./run-tests-OB4CWKKX.js");
12071
+ const { executeTests } = await import("./run-tests-OKQEMYZK.js");
11964
12072
  const argStr = args.join(" ").trim();
11965
12073
  let testArgs = {};
11966
12074
  if (argStr) {
@@ -12944,11 +13052,12 @@ async function setupProxy(configProxy) {
12944
13052
  }
12945
13053
 
12946
13054
  // src/web/auth.ts
12947
- import { existsSync as existsSync21, readFileSync as readFileSync14, writeFileSync as writeFileSync9, mkdirSync as mkdirSync10, readdirSync as readdirSync10, copyFileSync } from "fs";
13055
+ import { existsSync as existsSync21, readFileSync as readFileSync14, writeFileSync as writeFileSync9, mkdirSync as mkdirSync10, readdirSync as readdirSync10, copyFileSync, renameSync as renameSync2, unlinkSync as unlinkSync5 } from "fs";
12948
13056
  import { join as join14 } from "path";
12949
13057
  import { createHmac, randomBytes, timingSafeEqual, pbkdf2Sync } from "crypto";
12950
13058
  var USERS_FILE = "users.json";
12951
13059
  var TOKEN_EXPIRY_HOURS = 24;
13060
+ var TOKEN_EXPIRY_MS = TOKEN_EXPIRY_HOURS * 3600 * 1e3;
12952
13061
  var USERS_DIR = "users";
12953
13062
  var LOGIN_MAX_FAILS = 5;
12954
13063
  var LOGIN_LOCKOUT_MS = 15 * 60 * 1e3;
@@ -13192,7 +13301,17 @@ var AuthManager = class {
13192
13301
  }
13193
13302
  saveDB(db) {
13194
13303
  mkdirSync10(this.baseDir, { recursive: true });
13195
- writeFileSync9(this.usersFile, JSON.stringify(db, null, 2), "utf-8");
13304
+ const tmp = `${this.usersFile}.tmp`;
13305
+ try {
13306
+ writeFileSync9(tmp, JSON.stringify(db, null, 2), "utf-8");
13307
+ renameSync2(tmp, this.usersFile);
13308
+ } catch (err) {
13309
+ try {
13310
+ unlinkSync5(tmp);
13311
+ } catch {
13312
+ }
13313
+ throw err;
13314
+ }
13196
13315
  }
13197
13316
  /** Legacy hash — kept only for migrating old users (v0.2.x) */
13198
13317
  hashPasswordLegacy(password, salt) {
@@ -13406,7 +13525,7 @@ async function startWebServer(options = {}) {
13406
13525
  }
13407
13526
  const token = authManager.login(username, password);
13408
13527
  console.log(` \u2713 User registered via API: ${username}${firstRun ? " (first-run)" : ""}`);
13409
- res.cookie("aicli_token", token, { httpOnly: true, sameSite: "strict", maxAge: 7 * 24 * 3600 * 1e3 });
13528
+ res.cookie("aicli_token", token, { httpOnly: true, sameSite: "strict", maxAge: TOKEN_EXPIRY_MS });
13410
13529
  res.json({ success: true, username });
13411
13530
  });
13412
13531
  app.post("/api/auth/login", (req, res) => {
@@ -13420,7 +13539,7 @@ async function startWebServer(options = {}) {
13420
13539
  res.status(401).json({ error: "Invalid username or password" });
13421
13540
  return;
13422
13541
  }
13423
- res.cookie("aicli_token", token, { httpOnly: true, sameSite: "strict", maxAge: 7 * 24 * 3600 * 1e3 });
13542
+ res.cookie("aicli_token", token, { httpOnly: true, sameSite: "strict", maxAge: TOKEN_EXPIRY_MS });
13424
13543
  res.json({ success: true, username });
13425
13544
  });
13426
13545
  app.post("/api/auth/logout", (_req, res) => {
@@ -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-32YQ6HB2.js");
389
+ const { TaskOrchestrator } = await import("./task-orchestrator-SCH3JUKT.js");
390
390
  const orchestrator = new TaskOrchestrator(config, providers, configManager);
391
391
  let interrupted = false;
392
392
  const onSigint = () => {