jinzd-ai-cli 0.4.55 → 0.4.57

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.
@@ -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.55";
11
+ var VERSION = "0.4.57";
12
12
  var APP_NAME = "ai-cli";
13
13
  var CONFIG_DIR_NAME = ".aicli";
14
14
  var CONFIG_FILE_NAME = "config.json";
@@ -7,7 +7,7 @@ import {
7
7
  ProviderNotFoundError,
8
8
  RateLimitError,
9
9
  schemaToJsonSchema
10
- } from "./chunk-YQEIQJ6K.js";
10
+ } from "./chunk-G6K64M6X.js";
11
11
  import {
12
12
  APP_NAME,
13
13
  CONFIG_DIR_NAME,
@@ -20,7 +20,7 @@ import {
20
20
  MCP_TOOL_PREFIX,
21
21
  PLUGINS_DIR_NAME,
22
22
  VERSION
23
- } from "./chunk-W7QVBFIJ.js";
23
+ } from "./chunk-C2MNNHJ6.js";
24
24
 
25
25
  // src/config/config-manager.ts
26
26
  import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
@@ -172,6 +172,10 @@ var ConfigSchema = z.object({
172
172
  // 建议范围:25(保守)~ 1000(宽松,接近 Claude Code)。
173
173
  // CLI `--max-tool-rounds <n>` 可覆盖此值。
174
174
  maxToolRounds: z.number().int().min(1).max(1e4).default(200),
175
+ // Auto-pause checkpoint:Agentic 循环每隔多少轮暂停一次,让用户确认方向或中途介入。
176
+ // 默认 50;设为 0 禁用(完全自动执行到完成或 maxToolRounds 用尽)。
177
+ // CLI 与 Web UI 行为一致:CLI 用 readline question 提示,Web UI 弹出对话框。
178
+ autoPauseInterval: z.number().int().min(0).max(1e4).default(50),
175
179
  // 单次工具输出(如 read_file、bash、grep_files)返回给 AI 的最大字符数上限。
176
180
  // 默认 500_000 (~500K chars ≈ 6000-8000 行代码)。
177
181
  // 实际上限还会受模型 contextWindow 动态约束(取 contextWindow/4 作为下限)。
@@ -1216,7 +1220,29 @@ var OpenAICompatibleProvider = class extends BaseProvider {
1216
1220
  return { content: "", usage: void 0 };
1217
1221
  }
1218
1222
  const message = firstChoice.message;
1223
+ const finishReason = firstChoice.finish_reason;
1219
1224
  const usage = toUsage(response.usage);
1225
+ const contentStr = typeof message.content === "string" ? message.content : "";
1226
+ const hasToolCalls = !!(message.tool_calls && message.tool_calls.length > 0);
1227
+ if (!hasToolCalls && contentStr.trim() === "") {
1228
+ const providerId = this.info.id;
1229
+ if (finishReason === "length") {
1230
+ process.stderr.write(
1231
+ `[${providerId}] \u26A0 Empty response with finish_reason='length' \u2014 output/context limit reached. Try /compact, raise maxTokens, or switch to a larger-context model.
1232
+ `
1233
+ );
1234
+ } else if (finishReason === "content_filter") {
1235
+ process.stderr.write(
1236
+ `[${providerId}] \u26A0 Empty response with finish_reason='content_filter' \u2014 content was blocked.
1237
+ `
1238
+ );
1239
+ } else {
1240
+ process.stderr.write(
1241
+ `[${providerId}] \u26A0 Empty response (finish_reason=${finishReason ?? "unknown"}). Model produced no text and no tool calls. Context window may be exhausted.
1242
+ `
1243
+ );
1244
+ }
1245
+ }
1220
1246
  const reasoningContent = message.reasoning_content;
1221
1247
  if (message.tool_calls && message.tool_calls.length > 0) {
1222
1248
  const toolCalls = message.tool_calls.map((tc) => {
@@ -10,7 +10,7 @@ import {
10
10
  SUBAGENT_DEFAULT_MAX_ROUNDS,
11
11
  SUBAGENT_MAX_ROUNDS_LIMIT,
12
12
  runTestsTool
13
- } from "./chunk-W7QVBFIJ.js";
13
+ } from "./chunk-C2MNNHJ6.js";
14
14
 
15
15
  // src/tools/builtin/bash.ts
16
16
  import { execSync } 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.55";
9
+ var VERSION = "0.4.57";
10
10
  var APP_NAME = "ai-cli";
11
11
  var CONFIG_DIR_NAME = ".aicli";
12
12
  var CONFIG_FILE_NAME = "config.json";
@@ -385,7 +385,7 @@ ${content}`);
385
385
  }
386
386
  }
387
387
  async function runTaskMode(config, providers, configManager, topic) {
388
- const { TaskOrchestrator } = await import("./task-orchestrator-MWO6A4KQ.js");
388
+ const { TaskOrchestrator } = await import("./task-orchestrator-FRF6LTWK.js");
389
389
  const orchestrator = new TaskOrchestrator(config, providers, configManager);
390
390
  let interrupted = false;
391
391
  const onSigint = () => {
package/dist/index.js CHANGED
@@ -27,7 +27,7 @@ import {
27
27
  saveDevState,
28
28
  sessionHasMeaningfulContent,
29
29
  setupProxy
30
- } from "./chunk-JL5NK6AR.js";
30
+ } from "./chunk-G5REL4FK.js";
31
31
  import {
32
32
  ToolExecutor,
33
33
  ToolRegistry,
@@ -41,7 +41,7 @@ import {
41
41
  spawnAgentContext,
42
42
  theme,
43
43
  undoStack
44
- } from "./chunk-YQEIQJ6K.js";
44
+ } from "./chunk-G6K64M6X.js";
45
45
  import {
46
46
  fileCheckpoints
47
47
  } from "./chunk-4BKXL7SM.js";
@@ -66,7 +66,7 @@ import {
66
66
  SKILLS_DIR_NAME,
67
67
  VERSION,
68
68
  buildUserIdentityPrompt
69
- } from "./chunk-W7QVBFIJ.js";
69
+ } from "./chunk-C2MNNHJ6.js";
70
70
 
71
71
  // src/index.ts
72
72
  import { program } from "commander";
@@ -2161,7 +2161,7 @@ ${hint}` : "")
2161
2161
  usage: "/test [command|filter]",
2162
2162
  async execute(args, ctx) {
2163
2163
  try {
2164
- const { executeTests } = await import("./run-tests-X4PCLXA2.js");
2164
+ const { executeTests } = await import("./run-tests-NVCAP42D.js");
2165
2165
  const argStr = args.join(" ").trim();
2166
2166
  let testArgs = {};
2167
2167
  if (argStr) {
@@ -3337,7 +3337,7 @@ ${content}
3337
3337
  var FREE_ROUND_TOOLS = /* @__PURE__ */ new Set(["write_todos"]);
3338
3338
  var MAX_CONSECUTIVE_FREE_ROUNDS = 3;
3339
3339
  var MAX_REPEATED_TOOL_CALLS = 2;
3340
- var AUTO_PAUSE_INTERVAL = 10;
3340
+ var DEFAULT_AUTO_PAUSE_INTERVAL = 50;
3341
3341
  function fmtTokens(n) {
3342
3342
  if (n >= 1e6) return `${Math.round(n / 1e5) / 10}M`;
3343
3343
  if (n >= 1e3) return `${Math.round(n / 1024)}K`;
@@ -4833,7 +4833,11 @@ Session '${this.resumeSessionId}' not found.
4833
4833
  const apiMessages = [...messages];
4834
4834
  const extraMessages = [];
4835
4835
  const maxToolRounds = this.maxToolRoundsOverride ?? this.config.get("maxToolRounds") ?? DEFAULT_MAX_TOOL_ROUNDS;
4836
+ const autoPauseIntervalRaw = this.config.get("autoPauseInterval");
4837
+ const autoPauseInterval = typeof autoPauseIntervalRaw === "number" ? autoPauseIntervalRaw : DEFAULT_AUTO_PAUSE_INTERVAL;
4836
4838
  const baseSystemPrompt = (this.buildCurrentSystemPrompt() ?? "") + TOOL_CALL_REMINDER;
4839
+ const pauseHint = autoPauseInterval > 0 ? `
4840
+ - Every ${autoPauseInterval} rounds the user will be asked whether to continue \u2014 use this as a natural checkpoint to report progress.` : "";
4837
4841
  const roundBudgetHint = this.planMode ? `
4838
4842
 
4839
4843
  [Tool Round Budget \u2014 Plan Mode]
@@ -4843,16 +4847,14 @@ You have a maximum of ${maxToolRounds} tool call rounds. You are in READ-ONLY Pl
4843
4847
  - Do NOT write shell commands or code blocks as a substitute for tool calls
4844
4848
  - Do NOT read the same file more than once
4845
4849
  - Call write_todos ONCE to present your plan, then give a text summary
4846
- - If the user asks you to execute anything, respond: "Please type /plan execute to switch to execute mode."
4847
- - Every ${AUTO_PAUSE_INTERVAL} rounds the user will be asked whether to continue.` : `
4850
+ - If the user asks you to execute anything, respond: "Please type /plan execute to switch to execute mode."${pauseHint}` : `
4848
4851
 
4849
4852
  [Tool Round Budget]
4850
4853
  You have a maximum of ${maxToolRounds} tool call rounds for this task. Plan efficiently:
4851
4854
  - Prefer batch operations (e.g. global find-and-replace) over repetitive single edits.
4852
4855
  - Do NOT read the same file more than once \u2014 use the content from previous reads.
4853
4856
  - Prioritize the most critical tasks first in case rounds run out.
4854
- - When remaining rounds are low, focus on completing the current task and summarizing.
4855
- - Every ${AUTO_PAUSE_INTERVAL} rounds the user will be asked whether to continue \u2014 use this as a natural checkpoint to report progress.`;
4857
+ - When remaining rounds are low, focus on completing the current task and summarizing.${pauseHint}`;
4856
4858
  const systemPrompt = baseSystemPrompt + roundBudgetHint;
4857
4859
  const modelParams = this.getModelParams();
4858
4860
  const useStreaming = this.config.get("ui").streaming;
@@ -4862,6 +4864,7 @@ You have a maximum of ${maxToolRounds} tool call rounds for this task. Plan effi
4862
4864
  let consecutiveFreeRounds = 0;
4863
4865
  let lastToolCallSignature = "";
4864
4866
  let repeatedToolCallCount = 0;
4867
+ let emptyResponseRetries = 0;
4865
4868
  const roundToolHistory = [];
4866
4869
  this.setupInterjectionListener();
4867
4870
  try {
@@ -4990,6 +4993,51 @@ You have a maximum of ${maxToolRounds} tool call rounds for this task. Plan effi
4990
4993
  spinner.start(`Retrying... (round ${round + 2}/${maxToolRounds})`);
4991
4994
  continue;
4992
4995
  }
4996
+ if (!result.content || result.content.trim() === "") {
4997
+ if (emptyResponseRetries === 0 && round < maxToolRounds - 1) {
4998
+ emptyResponseRetries++;
4999
+ spinner.stop();
5000
+ if (alreadyRendered) process.stdout.write("\n");
5001
+ process.stderr.write(
5002
+ theme.warning(
5003
+ `\u26A0 AI returned an empty response (no text, no tool calls). Nudging to continue...
5004
+ `
5005
+ )
5006
+ );
5007
+ extraMessages.push({
5008
+ role: "user",
5009
+ content: "Your previous response was empty \u2014 no text and no tool calls. This usually means the context window is nearly full. Please either: (1) continue the task by calling the next tool you need, or (2) give a concise final text summary of what has been accomplished so far and what remains. Do NOT repeat earlier long outputs."
5010
+ });
5011
+ spinner.start(`Retrying... (round ${round + 2}/${maxToolRounds})`);
5012
+ continue;
5013
+ }
5014
+ spinner.stop();
5015
+ if (alreadyRendered) process.stdout.write("\n");
5016
+ process.stderr.write(
5017
+ theme.error(
5018
+ `
5019
+ \u26A0 AI returned empty responses twice in a row. Stopping agentic loop.
5020
+ `
5021
+ )
5022
+ );
5023
+ process.stderr.write(
5024
+ theme.dim(
5025
+ ` Likely causes: context window exhausted, max_tokens too low, or content filter.
5026
+ Try: /compact to reduce context, /clear to reset, or /model to switch.
5027
+
5028
+ `
5029
+ )
5030
+ );
5031
+ if (roundUsage.inputTokens > 0 || roundUsage.outputTokens > 0) {
5032
+ this.addSessionUsage(roundUsage);
5033
+ session.addTokenUsage(roundUsage);
5034
+ if (this.shouldShowTokens()) {
5035
+ this.renderer.renderUsage(roundUsage, this.sessionTokenUsage);
5036
+ }
5037
+ }
5038
+ return;
5039
+ }
5040
+ emptyResponseRetries = 0;
4993
5041
  spinner.stop();
4994
5042
  const finalContent = result.content;
4995
5043
  if (!alreadyRendered) {
@@ -5178,12 +5226,12 @@ You have a maximum of ${maxToolRounds} tool call rounds for this task. Plan effi
5178
5226
  }
5179
5227
  const effectiveRound = round + 1;
5180
5228
  const remaining = maxToolRounds - effectiveRound;
5181
- if (AUTO_PAUSE_INTERVAL > 0 && effectiveRound > 0 && effectiveRound % AUTO_PAUSE_INTERVAL === 0 && remaining > 0) {
5229
+ if (autoPauseInterval > 0 && effectiveRound > 0 && effectiveRound % autoPauseInterval === 0 && remaining > 0) {
5182
5230
  spinner.stop();
5183
5231
  process.stdout.write("\n");
5184
5232
  process.stdout.write(theme.warning(`\u23F8 Auto-pause: ${effectiveRound}/${maxToolRounds} rounds used, ${remaining} remaining
5185
5233
  `));
5186
- const recentHistory = roundToolHistory.slice(-AUTO_PAUSE_INTERVAL);
5234
+ const recentHistory = roundToolHistory.slice(-autoPauseInterval);
5187
5235
  if (recentHistory.length > 0) {
5188
5236
  const toolCounts = /* @__PURE__ */ new Map();
5189
5237
  for (const rh of recentHistory) {
@@ -5561,7 +5609,7 @@ program.command("web").description("Start Web UI server with browser-based chat
5561
5609
  console.error("Error: Invalid port number. Must be between 1 and 65535.");
5562
5610
  process.exit(1);
5563
5611
  }
5564
- const { startWebServer } = await import("./server-YPAZWGUE.js");
5612
+ const { startWebServer } = await import("./server-46J5MXHG.js");
5565
5613
  await startWebServer({ port, host: options.host });
5566
5614
  });
5567
5615
  program.command("user [action] [username]").description("Manage Web UI users (list | create <name> | delete <name> | reset-password <name> | migrate <name>)").action(async (action, username) => {
@@ -5794,7 +5842,7 @@ program.command("hub [topic]").description("Start multi-agent hub (discuss / bra
5794
5842
  }),
5795
5843
  config.get("customProviders")
5796
5844
  );
5797
- const { startHub } = await import("./hub-AUWP4SWJ.js");
5845
+ const { startHub } = await import("./hub-P3BR4JB5.js");
5798
5846
  await startHub(
5799
5847
  {
5800
5848
  topic: topic ?? "",
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  executeTests,
4
4
  runTestsTool
5
- } from "./chunk-W7QVBFIJ.js";
5
+ } from "./chunk-C2MNNHJ6.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-DJ342VFS.js";
4
+ } from "./chunk-H7MNK3YO.js";
5
5
  export {
6
6
  executeTests,
7
7
  runTestsTool
@@ -17,7 +17,7 @@ import {
17
17
  hadPreviousWriteToolCalls,
18
18
  loadDevState,
19
19
  setupProxy
20
- } from "./chunk-JL5NK6AR.js";
20
+ } from "./chunk-G5REL4FK.js";
21
21
  import {
22
22
  AuthManager
23
23
  } from "./chunk-BYNY5JPB.js";
@@ -36,7 +36,7 @@ import {
36
36
  spawnAgentContext,
37
37
  truncateOutput,
38
38
  undoStack
39
- } from "./chunk-YQEIQJ6K.js";
39
+ } from "./chunk-G6K64M6X.js";
40
40
  import "./chunk-4BKXL7SM.js";
41
41
  import {
42
42
  AGENTIC_BEHAVIOR_GUIDELINE,
@@ -56,7 +56,7 @@ import {
56
56
  SKILLS_DIR_NAME,
57
57
  VERSION,
58
58
  buildUserIdentityPrompt
59
- } from "./chunk-W7QVBFIJ.js";
59
+ } from "./chunk-C2MNNHJ6.js";
60
60
 
61
61
  // src/web/server.ts
62
62
  import express from "express";
@@ -501,6 +501,8 @@ var SessionHandler = class _SessionHandler {
501
501
  processing = false;
502
502
  /** Pending ask_user promises */
503
503
  pendingAskUser = /* @__PURE__ */ new Map();
504
+ /** Pending auto-pause promises */
505
+ pendingAutoPause = /* @__PURE__ */ new Map();
504
506
  /** Active system prompt from context files */
505
507
  activeSystemPrompt;
506
508
  /** Directories added via /add-dir */
@@ -602,6 +604,14 @@ var SessionHandler = class _SessionHandler {
602
604
  }
603
605
  return;
604
606
  }
607
+ case "auto_pause_response": {
608
+ const resolve3 = this.pendingAutoPause.get(msg.requestId);
609
+ if (resolve3) {
610
+ this.pendingAutoPause.delete(msg.requestId);
611
+ resolve3({ action: msg.action, message: msg.message });
612
+ }
613
+ return;
614
+ }
605
615
  case "abort":
606
616
  if (this.abortController) {
607
617
  this.abortController.abort();
@@ -617,6 +627,8 @@ var SessionHandler = class _SessionHandler {
617
627
  if (this.abortController) this.abortController.abort();
618
628
  for (const resolve3 of this.pendingAskUser.values()) resolve3(null);
619
629
  this.pendingAskUser.clear();
630
+ for (const resolve3 of this.pendingAutoPause.values()) resolve3({ action: "stop" });
631
+ this.pendingAutoPause.clear();
620
632
  this.saveIfNeeded();
621
633
  }
622
634
  /** 根据当前模型 context window 更新工具输出截断上限 */
@@ -762,19 +774,24 @@ var SessionHandler = class _SessionHandler {
762
774
  const apiMessages = [...messages];
763
775
  const extraMessages = [];
764
776
  const maxToolRounds = this.config.get("maxToolRounds") ?? DEFAULT_MAX_TOOL_ROUNDS;
777
+ const autoPauseIntervalRaw = this.config.get("autoPauseInterval");
778
+ const autoPauseInterval = typeof autoPauseIntervalRaw === "number" ? autoPauseIntervalRaw : 50;
765
779
  const baseSystemPrompt = (this.buildSystemPrompt() ?? "") + TOOL_CALL_REMINDER;
780
+ const pauseHint = autoPauseInterval > 0 ? `
781
+ - Every ${autoPauseInterval} rounds the user will be asked whether to continue \u2014 use this as a natural checkpoint to report progress.` : "";
766
782
  const roundBudgetHint = `
767
783
 
768
784
  [Tool Round Budget]
769
785
  You have a maximum of ${maxToolRounds} tool call rounds for this task. Plan efficiently:
770
786
  - Prefer batch operations (e.g. global find-and-replace) over repetitive single edits.
771
787
  - Prioritize the most critical tasks first in case rounds run out.
772
- - When remaining rounds are low, focus on completing the current task and summarizing.`;
788
+ - When remaining rounds are low, focus on completing the current task and summarizing.${pauseHint}`;
773
789
  const systemPrompt = baseSystemPrompt + roundBudgetHint;
774
790
  const modelParams = this.getModelParams();
775
791
  const roundUsage = { inputTokens: 0, outputTokens: 0, cacheCreationTokens: 0, cacheReadTokens: 0 };
776
792
  const supportsStreamingTools = typeof provider.chatWithToolsStream === "function";
777
793
  let consecutiveFreeRounds = 0;
794
+ const roundToolHistory = [];
778
795
  const warnNoteAt = Math.max(10, Math.floor(maxToolRounds * 0.2));
779
796
  const warnLowAt = Math.max(5, Math.floor(maxToolRounds * 0.1));
780
797
  const warnCriticalAt = Math.max(3, Math.floor(maxToolRounds * 0.05));
@@ -783,6 +800,7 @@ You have a maximum of ${maxToolRounds} tool call rounds for this task. Plan effi
783
800
  let warnedNote = false;
784
801
  let warnedLow = false;
785
802
  let warnedCritical = false;
803
+ let emptyResponseRetries = 0;
786
804
  const ac = new AbortController();
787
805
  this.abortController = ac;
788
806
  try {
@@ -845,6 +863,31 @@ You have a maximum of ${maxToolRounds} tool call rounds for this task. Plan effi
845
863
  roundUsage.cacheCreationTokens += result.usage.cacheCreationTokens ?? 0;
846
864
  roundUsage.cacheReadTokens += result.usage.cacheReadTokens ?? 0;
847
865
  }
866
+ const hasToolCalls = !!(result.toolCalls && result.toolCalls.length > 0);
867
+ const contentBlank = !result.content || result.content.trim() === "";
868
+ if (!hasToolCalls && contentBlank) {
869
+ if (emptyResponseRetries === 0 && round < maxToolRounds - 1) {
870
+ emptyResponseRetries++;
871
+ this.send({
872
+ type: "info",
873
+ message: "\u26A0 AI returned an empty response. Nudging to continue..."
874
+ });
875
+ extraMessages.push({
876
+ role: "user",
877
+ content: "Your previous response was empty \u2014 no text and no tool calls. This usually means the context window is nearly full. Please either: (1) continue the task by calling the next tool you need, or (2) give a concise final text summary of what has been accomplished so far and what remains. Do NOT repeat earlier long outputs."
878
+ });
879
+ continue;
880
+ }
881
+ this.send({
882
+ type: "response_done",
883
+ content: "\u26A0 AI returned empty responses twice in a row. Stopping agentic loop.\n\nLikely causes: context window exhausted, max_tokens too low, or content filter.\nTry: /compact to reduce context, /clear to reset, or switch to a larger-context model.",
884
+ usage: roundUsage
885
+ });
886
+ this.addWebSessionUsage(roundUsage);
887
+ session.addTokenUsage(roundUsage);
888
+ return;
889
+ }
890
+ emptyResponseRetries = 0;
848
891
  if (result.content && !result.toolCalls) {
849
892
  const hasWriteTools = toolDefs.some((t) => t.name === "write_file" || t.name === "edit_file");
850
893
  const alreadyWrote = hadPreviousWriteToolCalls(extraMessages);
@@ -863,6 +906,10 @@ You have a maximum of ${maxToolRounds} tool call rounds for this task. Plan effi
863
906
  return;
864
907
  }
865
908
  if (result.toolCalls && result.toolCalls.length > 0) {
909
+ roundToolHistory.push({
910
+ round: round + 1,
911
+ tools: result.toolCalls.map((tc) => tc.name)
912
+ });
866
913
  googleSearchContext.configManager = this.config;
867
914
  spawnAgentContext.provider = provider;
868
915
  spawnAgentContext.model = this.currentModel;
@@ -890,6 +937,39 @@ You have a maximum of ${maxToolRounds} tool call rounds for this task. Plan effi
890
937
  extraMessages.push({ role: "user", content: msg });
891
938
  }
892
939
  }
940
+ const effectiveRound = round + 1;
941
+ const remaining = maxToolRounds - effectiveRound;
942
+ if (autoPauseInterval > 0 && effectiveRound > 0 && effectiveRound % autoPauseInterval === 0 && remaining > 0 && !ac.signal.aborted) {
943
+ const recentHistory = roundToolHistory.slice(-autoPauseInterval);
944
+ const toolCounts = /* @__PURE__ */ new Map();
945
+ for (const rh of recentHistory) {
946
+ for (const t of rh.tools) toolCounts.set(t, (toolCounts.get(t) || 0) + 1);
947
+ }
948
+ const toolSummary = [...toolCounts.entries()].sort((a, b) => b[1] - a[1]).map(([name, count]) => count > 1 ? `${name}\xD7${count}` : name).join(", ");
949
+ const requestId = `pause_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
950
+ const pauseResp = await new Promise((resolve3) => {
951
+ this.pendingAutoPause.set(requestId, resolve3);
952
+ this.send({
953
+ type: "auto_pause_request",
954
+ requestId,
955
+ currentRound: effectiveRound,
956
+ totalRounds: maxToolRounds,
957
+ toolSummary
958
+ });
959
+ });
960
+ if (ac.signal.aborted) break;
961
+ if (pauseResp.action === "stop") {
962
+ this.send({ type: "info", message: `\u23F8 Stopped by user at ${effectiveRound}/${maxToolRounds}` });
963
+ extraMessages.push({
964
+ role: "user",
965
+ content: `The user has stopped the task at round ${effectiveRound}/${maxToolRounds}. Do not call any more tools. Summarize what has been completed and what remains.`
966
+ });
967
+ break;
968
+ } else if (pauseResp.action === "redirect" && pauseResp.message) {
969
+ this.send({ type: "info", message: `\u26A1 Redirect: "${pauseResp.message}"` });
970
+ extraMessages.push({ role: "user", content: pauseResp.message });
971
+ }
972
+ }
893
973
  }
894
974
  try {
895
975
  const summaryExtra = [
@@ -1736,7 +1816,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
1736
1816
  case "test": {
1737
1817
  this.send({ type: "info", message: "\u{1F9EA} Running tests..." });
1738
1818
  try {
1739
- const { executeTests } = await import("./run-tests-X4PCLXA2.js");
1819
+ const { executeTests } = await import("./run-tests-NVCAP42D.js");
1740
1820
  const argStr = args.join(" ").trim();
1741
1821
  let testArgs = {};
1742
1822
  if (argStr) {
@@ -4,11 +4,11 @@ import {
4
4
  getDangerLevel,
5
5
  googleSearchContext,
6
6
  truncateOutput
7
- } from "./chunk-YQEIQJ6K.js";
7
+ } from "./chunk-G6K64M6X.js";
8
8
  import "./chunk-4BKXL7SM.js";
9
9
  import {
10
10
  SUBAGENT_ALLOWED_TOOLS
11
- } from "./chunk-W7QVBFIJ.js";
11
+ } from "./chunk-C2MNNHJ6.js";
12
12
 
13
13
  // src/hub/task-orchestrator.ts
14
14
  import { createInterface } from "readline";
@@ -176,6 +176,7 @@ function handleServerMessage(msg) {
176
176
  case 'confirm_request': handleConfirmRequest(msg); break;
177
177
  case 'batch_confirm_request': handleBatchConfirmRequest(msg); break;
178
178
  case 'ask_user_request':handleAskUserRequest(msg); break;
179
+ case 'auto_pause_request': handleAutoPauseRequest(msg); break;
179
180
  case 'thinking_start': handleThinkingStart(); break;
180
181
  case 'thinking_delta': handleThinkingDelta(msg.delta); break;
181
182
  case 'thinking_end': handleThinkingEnd(); break;
@@ -377,6 +378,34 @@ function handleAskUserRequest(msg) {
377
378
  setTimeout(() => document.getElementById(`ask-input-${msg.requestId}`)?.focus(), 100);
378
379
  }
379
380
 
381
+ function handleAutoPauseRequest(msg) {
382
+ const el = document.createElement('div');
383
+ el.className = 'confirm-card tool-border-write my-1';
384
+ el.id = `pause-card-${msg.requestId}`;
385
+ const summary = msg.toolSummary ? escapeHtml(msg.toolSummary) : '(none)';
386
+ el.innerHTML = `
387
+ <div class="flex items-center gap-2 mb-2">
388
+ <span class="badge badge-warning badge-sm">⏸ Auto-pause</span>
389
+ <span class="text-xs opacity-70">Round ${msg.currentRound}/${msg.totalRounds} · ${msg.totalRounds - msg.currentRound} remaining</span>
390
+ </div>
391
+ <div class="text-xs opacity-70 mb-2">Recent tools: ${summary}</div>
392
+ <div class="flex gap-2 w-full mb-2">
393
+ <input type="text" id="pause-input-${msg.requestId}"
394
+ class="input input-sm input-bordered flex-1"
395
+ placeholder="Optional: redirect AI with a new instruction..."
396
+ onkeydown="if(event.key==='Enter')respondAutoPause('${msg.requestId}','redirect')">
397
+ </div>
398
+ <div class="flex gap-2">
399
+ <button class="btn btn-success btn-sm flex-1" onclick="respondAutoPause('${msg.requestId}','continue')">▶ Continue</button>
400
+ <button class="btn btn-primary btn-sm flex-1" onclick="respondAutoPause('${msg.requestId}','redirect')">↪ Redirect</button>
401
+ <button class="btn btn-error btn-sm flex-1" onclick="respondAutoPause('${msg.requestId}','stop')">⏹ Stop</button>
402
+ </div>
403
+ `;
404
+ messagesEl.appendChild(el);
405
+ scrollToBottom();
406
+ setTimeout(() => document.getElementById(`pause-input-${msg.requestId}`)?.focus(), 100);
407
+ }
408
+
380
409
  function handleRoundProgress(msg) {
381
410
  const progressBar = document.getElementById('round-progress');
382
411
  const progressBarEl = document.getElementById('round-progress-bar');
@@ -568,6 +597,30 @@ window.respondAskUser = function(requestId) {
568
597
  }
569
598
  };
570
599
 
600
+ window.respondAutoPause = function(requestId, action) {
601
+ const card = document.getElementById(`pause-card-${requestId}`);
602
+ const input = document.getElementById(`pause-input-${requestId}`);
603
+ const typed = input ? input.value.trim() : '';
604
+
605
+ // 若用户在输入框敲回车,action 是 'redirect';若内容为空则视作 continue
606
+ let effective = action;
607
+ let message;
608
+ if (action === 'redirect') {
609
+ if (!typed) { effective = 'continue'; }
610
+ else { message = typed; }
611
+ }
612
+
613
+ send({ type: 'auto_pause_response', requestId, action: effective, message });
614
+
615
+ if (card) {
616
+ card.className = 'confirm-card tool-border-safe my-1 opacity-60';
617
+ const label = effective === 'continue' ? '▶ Continued'
618
+ : effective === 'stop' ? '⏹ Stopped'
619
+ : `↪ Redirected: ${escapeHtml(message || '')}`;
620
+ card.innerHTML = `<span class="text-sm">${label}</span>`;
621
+ }
622
+ };
623
+
571
624
  // DaisyUI light themes → highlight.js light stylesheet; others → dark
572
625
  const LIGHT_DAISYUI_THEMES = new Set(['light', 'cupcake', 'bumblebee', 'emerald', 'corporate', 'garden', 'lofi', 'pastel', 'fantasy', 'wireframe', 'cmyk', 'autumn', 'acid', 'lemonade', 'winter', 'nord']);
573
626
  const HLJS_CDN = 'https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.9.0/build/styles';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jinzd-ai-cli",
3
- "version": "0.4.55",
3
+ "version": "0.4.57",
4
4
  "description": "Cross-platform REPL-style AI CLI with multi-provider support",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",