jinzd-ai-cli 0.4.12 → 0.4.13

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.
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  EnvLoader,
4
4
  schemaToJsonSchema
5
- } from "./chunk-A3S7PUMM.js";
5
+ } from "./chunk-TXCLZ72H.js";
6
6
  import {
7
7
  APP_NAME,
8
8
  CONFIG_DIR_NAME,
@@ -15,7 +15,7 @@ import {
15
15
  MCP_TOOL_PREFIX,
16
16
  PLUGINS_DIR_NAME,
17
17
  VERSION
18
- } from "./chunk-Z5IRVRGL.js";
18
+ } from "./chunk-DP3M26PP.js";
19
19
 
20
20
  // src/config/config-manager.ts
21
21
  import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
@@ -277,7 +277,8 @@ ${err}`
277
277
  if (rawValue === "true") value = true;
278
278
  else if (rawValue === "false") value = false;
279
279
  else if (rawValue !== "" && !isNaN(Number(rawValue))) value = Number(rawValue);
280
- let current = this.config;
280
+ const draft = JSON.parse(JSON.stringify(this.config));
281
+ let current = draft;
281
282
  for (let i = 0; i < keys.length - 1; i++) {
282
283
  const key = keys[i];
283
284
  if (current[key] == null || typeof current[key] !== "object") {
@@ -286,6 +287,12 @@ ${err}`
286
287
  current = current[key];
287
288
  }
288
289
  current[keys[keys.length - 1]] = value;
290
+ const result = ConfigSchema.safeParse(draft);
291
+ if (!result.success) {
292
+ const firstErr = result.error.errors[0];
293
+ throw new ConfigError(`Invalid config value for "${path}": ${firstErr?.message ?? "validation failed"}`);
294
+ }
295
+ this.config = result.data;
289
296
  this.save();
290
297
  }
291
298
  /** 获取完整配置对象的格式化 JSON 字符串(用于 /config show 等展示) */
@@ -2535,6 +2542,7 @@ var McpClient = class {
2535
2542
  config;
2536
2543
  process = null;
2537
2544
  nextId = 1;
2545
+ // M8: wraps at MAX_SAFE_INTEGER via getNextId()
2538
2546
  connected = false;
2539
2547
  serverInfo = null;
2540
2548
  /** stderr 收集(最多保留最后 2KB,用于错误报告) */
@@ -2667,6 +2675,7 @@ var McpClient = class {
2667
2675
  return reject(new Error(`MCP server [${this.serverId}] stdin not writable`));
2668
2676
  }
2669
2677
  const id = this.nextId++;
2678
+ if (this.nextId > Number.MAX_SAFE_INTEGER - 1) this.nextId = 1;
2670
2679
  const request = {
2671
2680
  jsonrpc: "2.0",
2672
2681
  id,
@@ -8,7 +8,7 @@ import { platform } from "os";
8
8
  import chalk from "chalk";
9
9
 
10
10
  // src/core/constants.ts
11
- var VERSION = "0.4.12";
11
+ var VERSION = "0.4.13";
12
12
  var APP_NAME = "ai-cli";
13
13
  var CONFIG_DIR_NAME = ".aicli";
14
14
  var CONFIG_FILE_NAME = "config.json";
@@ -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.12";
9
+ var VERSION = "0.4.13";
10
10
  var APP_NAME = "ai-cli";
11
11
  var CONFIG_DIR_NAME = ".aicli";
12
12
  var CONFIG_FILE_NAME = "config.json";
@@ -6,7 +6,7 @@ import {
6
6
  SUBAGENT_DEFAULT_MAX_ROUNDS,
7
7
  SUBAGENT_MAX_ROUNDS_LIMIT,
8
8
  runTestsTool
9
- } from "./chunk-Z5IRVRGL.js";
9
+ } from "./chunk-DP3M26PP.js";
10
10
 
11
11
  // src/tools/builtin/bash.ts
12
12
  import { execSync } from "child_process";
@@ -534,6 +534,13 @@ Suggestion: read existing text versions (.md / .txt) in the project, or install
534
534
  if (BINARY_EXTENSIONS.has(ext)) {
535
535
  return `[Binary file: ${filePath} (${ext})]
536
536
  This is a binary file and cannot be read as text. If there is a text version (.md / .txt) in the project, please read that instead.`;
537
+ }
538
+ try {
539
+ const fstat = statSync2(normalizedPath);
540
+ if (fstat.size > MAX_FILE_BYTES) {
541
+ return `[File too large: ${filePath} | ${(fstat.size / 1024 / 1024).toFixed(1)} MB exceeds ${MAX_FILE_BYTES / 1024 / 1024} MB limit]`;
542
+ }
543
+ } catch {
537
544
  }
538
545
  const buf = readFileSync2(normalizedPath);
539
546
  if (encoding === "base64") {
@@ -1003,6 +1010,10 @@ Supports regex. Automatically skips node_modules, dist, .git directories.`,
1003
1010
  const maxResults = Math.max(1, Number(args["max_results"] ?? 50));
1004
1011
  if (!pattern) throw new Error("pattern is required");
1005
1012
  if (!existsSync6(rootPath)) throw new Error(`Path not found: ${rootPath}`);
1013
+ const MAX_PATTERN_LENGTH = 1e3;
1014
+ if (pattern.length > MAX_PATTERN_LENGTH) {
1015
+ throw new Error(`Pattern too long (${pattern.length} chars, max ${MAX_PATTERN_LENGTH}). Use a shorter pattern.`);
1016
+ }
1006
1017
  let regex;
1007
1018
  try {
1008
1019
  regex = new RegExp(pattern, ignoreCase ? "gi" : "g");
@@ -1386,11 +1397,11 @@ ${stderr}`);
1386
1397
  // src/tools/builtin/web-fetch.ts
1387
1398
  import { promises as dnsPromises } from "dns";
1388
1399
  function htmlToText(html) {
1400
+ let text = html.replace(/<script[\s\S]*?<\/script>/gi, "").replace(/<style[\s\S]*?<\/style>/gi, "").replace(/<noscript[\s\S]*?<\/noscript>/gi, "").replace(/<svg[\s\S]*?<\/svg>/gi, "");
1389
1401
  const HTML_REGEX_LIMIT = 2e5;
1390
- if (html.length > HTML_REGEX_LIMIT) {
1391
- html = html.slice(0, HTML_REGEX_LIMIT);
1402
+ if (text.length > HTML_REGEX_LIMIT) {
1403
+ text = text.slice(0, HTML_REGEX_LIMIT);
1392
1404
  }
1393
- let text = html.replace(/<script[\s\S]*?<\/script>/gi, "").replace(/<style[\s\S]*?<\/style>/gi, "").replace(/<noscript[\s\S]*?<\/noscript>/gi, "").replace(/<svg[\s\S]*?<\/svg>/gi, "");
1394
1405
  text = text.replace(/<h([1-6])[^>]*>([\s\S]*?)<\/h\1>/gi, (_m, lvl, content) => {
1395
1406
  const prefix = "#".repeat(Number(lvl));
1396
1407
  return `
@@ -1848,6 +1859,14 @@ var EnvLoader = class {
1848
1859
  if (val) return val;
1849
1860
  }
1850
1861
  const dynamicEnvVar = `AICLI_API_KEY_${providerId.toUpperCase().replace(/-/g, "_")}`;
1862
+ if (fixedEnvVar && fixedEnvVar !== dynamicEnvVar) {
1863
+ const fixedVal = process.env[fixedEnvVar];
1864
+ const dynVal = process.env[dynamicEnvVar];
1865
+ if (fixedVal && dynVal && fixedVal !== dynVal) {
1866
+ process.stderr.write(`[warn] env var collision: ${fixedEnvVar} and ${dynamicEnvVar} have different values for provider "${providerId}". Using ${fixedEnvVar}.
1867
+ `);
1868
+ }
1869
+ }
1851
1870
  return process.env[dynamicEnvVar] || void 0;
1852
1871
  }
1853
1872
  static getDefaultProvider() {
@@ -381,7 +381,7 @@ ${content}`);
381
381
  }
382
382
  }
383
383
  async function runTaskMode(config, providers, configManager, topic) {
384
- const { TaskOrchestrator } = await import("./task-orchestrator-A5X2GUSH.js");
384
+ const { TaskOrchestrator } = await import("./task-orchestrator-XXOS7WHR.js");
385
385
  const orchestrator = new TaskOrchestrator(config, providers, configManager);
386
386
  let interrupted = false;
387
387
  const onSigint = () => {
package/dist/index.js CHANGED
@@ -23,7 +23,7 @@ import {
23
23
  saveDevState,
24
24
  sessionHasMeaningfulContent,
25
25
  setupProxy
26
- } from "./chunk-JPCRXQ2E.js";
26
+ } from "./chunk-52BIUGQ2.js";
27
27
  import {
28
28
  ToolRegistry,
29
29
  askUserContext,
@@ -38,7 +38,7 @@ import {
38
38
  theme,
39
39
  truncateOutput,
40
40
  undoStack
41
- } from "./chunk-A3S7PUMM.js";
41
+ } from "./chunk-TXCLZ72H.js";
42
42
  import {
43
43
  AGENTIC_BEHAVIOR_GUIDELINE,
44
44
  AUTHOR,
@@ -58,7 +58,7 @@ import {
58
58
  REPO_URL,
59
59
  SKILLS_DIR_NAME,
60
60
  VERSION
61
- } from "./chunk-Z5IRVRGL.js";
61
+ } from "./chunk-DP3M26PP.js";
62
62
 
63
63
  // src/index.ts
64
64
  import { program } from "commander";
@@ -1914,7 +1914,7 @@ ${hint}` : "")
1914
1914
  description: "Run project tests and show structured report",
1915
1915
  usage: "/test [command|filter]",
1916
1916
  async execute(args, _ctx) {
1917
- const { executeTests } = await import("./run-tests-EUOAU3RZ.js");
1917
+ const { executeTests } = await import("./run-tests-PVVT37LK.js");
1918
1918
  const argStr = args.join(" ").trim();
1919
1919
  let testArgs = {};
1920
1920
  if (argStr) {
@@ -5528,7 +5528,7 @@ program.command("web").description("Start Web UI server with browser-based chat
5528
5528
  console.error("Error: Invalid port number. Must be between 1 and 65535.");
5529
5529
  process.exit(1);
5530
5530
  }
5531
- const { startWebServer } = await import("./server-PWWT4P52.js");
5531
+ const { startWebServer } = await import("./server-KPNMFMDU.js");
5532
5532
  await startWebServer({ port, host: options.host });
5533
5533
  });
5534
5534
  program.command("user [action] [username]").description("Manage Web UI users (list | create <name> | delete <name> | reset-password <name> | migrate <name>)").action(async (action, username) => {
@@ -5761,7 +5761,7 @@ program.command("hub [topic]").description("Start multi-agent hub (discuss / bra
5761
5761
  }),
5762
5762
  config.get("customProviders")
5763
5763
  );
5764
- const { startHub } = await import("./hub-J5ZOVM4Q.js");
5764
+ const { startHub } = await import("./hub-LURPCJ4Z.js");
5765
5765
  await startHub(
5766
5766
  {
5767
5767
  topic: topic ?? "",
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  executeTests,
4
4
  runTestsTool
5
- } from "./chunk-Z5IRVRGL.js";
5
+ } from "./chunk-DP3M26PP.js";
6
6
  export {
7
7
  executeTests,
8
8
  runTestsTool
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  executeTests,
3
3
  runTestsTool
4
- } from "./chunk-3HJSWTKO.js";
4
+ } from "./chunk-TLOCRYJC.js";
5
5
  export {
6
6
  executeTests,
7
7
  runTestsTool
@@ -18,7 +18,7 @@ import {
18
18
  renderDiff,
19
19
  runHook,
20
20
  setupProxy
21
- } from "./chunk-JPCRXQ2E.js";
21
+ } from "./chunk-52BIUGQ2.js";
22
22
  import {
23
23
  AuthManager
24
24
  } from "./chunk-BYNY5JPB.js";
@@ -32,7 +32,7 @@ import {
32
32
  spawnAgentContext,
33
33
  truncateOutput,
34
34
  undoStack
35
- } from "./chunk-A3S7PUMM.js";
35
+ } from "./chunk-TXCLZ72H.js";
36
36
  import {
37
37
  AGENTIC_BEHAVIOR_GUIDELINE,
38
38
  CONTEXT_FILE_CANDIDATES,
@@ -44,7 +44,7 @@ import {
44
44
  PLAN_MODE_SYSTEM_ADDON,
45
45
  SKILLS_DIR_NAME,
46
46
  VERSION
47
- } from "./chunk-Z5IRVRGL.js";
47
+ } from "./chunk-DP3M26PP.js";
48
48
 
49
49
  // src/web/server.ts
50
50
  import express from "express";
@@ -57,7 +57,7 @@ import { networkInterfaces } from "os";
57
57
  // src/web/tool-executor-web.ts
58
58
  import { randomUUID } from "crypto";
59
59
  import { existsSync, readFileSync } from "fs";
60
- var ToolExecutorWeb = class {
60
+ var ToolExecutorWeb = class _ToolExecutorWeb {
61
61
  constructor(registry, ws) {
62
62
  this.registry = registry;
63
63
  this.ws = ws;
@@ -71,6 +71,10 @@ var ToolExecutorWeb = class {
71
71
  pendingConfirms = /* @__PURE__ */ new Map();
72
72
  /** Pending batch confirm promises */
73
73
  pendingBatchConfirms = /* @__PURE__ */ new Map();
74
+ /** M7 fix: timers for confirm cleanup if client crashes */
75
+ pendingTimers = /* @__PURE__ */ new Map();
76
+ static CONFIRM_TIMEOUT_MS = 5 * 60 * 1e3;
77
+ // 5 minutes
74
78
  /** Publicly readable by SessionHandler to check if confirm is active */
75
79
  confirming = false;
76
80
  /** Session-level auto-approve toggle (/yolo command) */
@@ -86,10 +90,19 @@ var ToolExecutorWeb = class {
86
90
  if (opts.permissionRules) this.permissionRules = opts.permissionRules;
87
91
  if (opts.defaultPermission) this.defaultPermission = opts.defaultPermission;
88
92
  }
93
+ /** Clear M7 timeout timer for a requestId */
94
+ clearPendingTimer(requestId) {
95
+ const timer = this.pendingTimers.get(requestId);
96
+ if (timer) {
97
+ clearTimeout(timer);
98
+ this.pendingTimers.delete(requestId);
99
+ }
100
+ }
89
101
  /** Resolve a pending confirm from client response */
90
102
  resolveConfirm(requestId, approved) {
91
103
  const resolve3 = this.pendingConfirms.get(requestId);
92
104
  if (resolve3) {
105
+ this.clearPendingTimer(requestId);
93
106
  this.pendingConfirms.delete(requestId);
94
107
  this.confirming = false;
95
108
  resolve3(approved);
@@ -99,6 +112,7 @@ var ToolExecutorWeb = class {
99
112
  resolveBatchConfirm(requestId, decision) {
100
113
  const resolve3 = this.pendingBatchConfirms.get(requestId);
101
114
  if (resolve3) {
115
+ this.clearPendingTimer(requestId);
102
116
  this.pendingBatchConfirms.delete(requestId);
103
117
  this.confirming = false;
104
118
  if (decision === "all" || decision === "none") {
@@ -190,6 +204,11 @@ var ToolExecutorWeb = class {
190
204
  this.send(msg);
191
205
  return new Promise((resolve3) => {
192
206
  this.pendingConfirms.set(requestId, resolve3);
207
+ this.pendingTimers.set(requestId, setTimeout(() => {
208
+ if (this.pendingConfirms.has(requestId)) {
209
+ this.resolveConfirm(requestId, false);
210
+ }
211
+ }, _ToolExecutorWeb.CONFIRM_TIMEOUT_MS));
193
212
  });
194
213
  }
195
214
  /** WebSocket-based batch confirm */
@@ -210,6 +229,11 @@ var ToolExecutorWeb = class {
210
229
  this.send(msg);
211
230
  return new Promise((resolve3) => {
212
231
  this.pendingBatchConfirms.set(requestId, resolve3);
232
+ this.pendingTimers.set(requestId, setTimeout(() => {
233
+ if (this.pendingBatchConfirms.has(requestId)) {
234
+ this.resolveBatchConfirm(requestId, "none");
235
+ }
236
+ }, _ToolExecutorWeb.CONFIRM_TIMEOUT_MS));
213
237
  });
214
238
  }
215
239
  async execute(call) {
@@ -1448,7 +1472,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
1448
1472
  case "test": {
1449
1473
  this.send({ type: "info", message: "\u{1F9EA} Running tests..." });
1450
1474
  try {
1451
- const { executeTests } = await import("./run-tests-EUOAU3RZ.js");
1475
+ const { executeTests } = await import("./run-tests-PVVT37LK.js");
1452
1476
  const argStr = args.join(" ").trim();
1453
1477
  let testArgs = {};
1454
1478
  if (argStr) {
@@ -4,14 +4,14 @@ import {
4
4
  getDangerLevel,
5
5
  googleSearchContext,
6
6
  truncateOutput
7
- } from "./chunk-A3S7PUMM.js";
7
+ } from "./chunk-TXCLZ72H.js";
8
8
  import {
9
9
  SUBAGENT_ALLOWED_TOOLS
10
- } from "./chunk-Z5IRVRGL.js";
10
+ } from "./chunk-DP3M26PP.js";
11
11
 
12
12
  // src/hub/task-orchestrator.ts
13
13
  import { createInterface } from "readline";
14
- import { existsSync, readFileSync, writeFileSync, unlinkSync } from "fs";
14
+ import { existsSync, readFileSync, writeFileSync, unlinkSync, renameSync } from "fs";
15
15
  import { join } from "path";
16
16
  import chalk from "chalk";
17
17
 
@@ -266,7 +266,9 @@ var TaskOrchestrator = class {
266
266
  }
267
267
  saveState(plan) {
268
268
  try {
269
- writeFileSync(this.stateFilePath, JSON.stringify(plan, null, 2), "utf-8");
269
+ const tmpPath = this.stateFilePath + ".tmp";
270
+ writeFileSync(tmpPath, JSON.stringify(plan, null, 2), "utf-8");
271
+ renameSync(tmpPath, this.stateFilePath);
270
272
  } catch {
271
273
  }
272
274
  }
@@ -357,13 +359,29 @@ Example:
357
359
  if (!Array.isArray(parsed) || parsed.length === 0) {
358
360
  throw new Error("Empty task list");
359
361
  }
362
+ const validRoleIds = new Set(roles.map((r) => r.id));
363
+ for (let i = 0; i < parsed.length; i++) {
364
+ const t = parsed[i];
365
+ if (typeof t !== "object" || t === null) {
366
+ throw new Error(`Task #${i + 1}: not an object`);
367
+ }
368
+ if (typeof t.description !== "string" || !t.description.trim()) {
369
+ throw new Error(`Task #${i + 1}: missing or empty "description"`);
370
+ }
371
+ if (typeof t.assignee !== "string" || !t.assignee.trim()) {
372
+ throw new Error(`Task #${i + 1}: missing or empty "assignee"`);
373
+ }
374
+ if (t.dependencies != null && !Array.isArray(t.dependencies)) {
375
+ throw new Error(`Task #${i + 1}: "dependencies" must be an array`);
376
+ }
377
+ }
360
378
  const roleMap = new Map(roles.map((r) => [r.id, r.name]));
361
379
  const tasks = parsed.map((t, i) => ({
362
- id: t.id ?? i + 1,
380
+ id: typeof t.id === "number" ? t.id : i + 1,
363
381
  description: t.description,
364
382
  assignee: t.assignee,
365
383
  assigneeName: roleMap.get(t.assignee) ?? t.assignee,
366
- dependencies: t.dependencies ?? [],
384
+ dependencies: Array.isArray(t.dependencies) ? t.dependencies : [],
367
385
  status: "pending"
368
386
  }));
369
387
  return { goal, tasks };
@@ -428,7 +446,15 @@ Example:
428
446
  if (!nextTask) {
429
447
  const pending = plan.tasks.filter((t) => t.status === "pending");
430
448
  if (pending.length === 0) break;
431
- console.log(chalk.red(` \u2717 Cannot proceed: ${pending.length} task(s) have unmet dependencies.`));
449
+ const failedIds = new Set(plan.tasks.filter((t) => t.status === "failed").map((t) => t.id));
450
+ const hasCycle = pending.every(
451
+ (t) => t.dependencies.some((d) => !completedIds.has(d) && !failedIds.has(d) && pending.some((p) => p.id === d))
452
+ );
453
+ if (hasCycle) {
454
+ console.log(chalk.red(` \u2717 Circular dependency detected among ${pending.length} task(s):`));
455
+ } else {
456
+ console.log(chalk.red(` \u2717 Cannot proceed: ${pending.length} task(s) have unmet dependencies.`));
457
+ }
432
458
  for (const t of pending) {
433
459
  const unmet = t.dependencies.filter((d) => !completedIds.has(d));
434
460
  console.log(chalk.dim(` Task #${t.id}: waiting on [${unmet.join(", ")}]`));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jinzd-ai-cli",
3
- "version": "0.4.12",
3
+ "version": "0.4.13",
4
4
  "description": "Cross-platform REPL-style AI CLI with multi-provider support",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",