jinzd-ai-cli 0.4.2 → 0.4.4

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-E2HVDEWW.js";
5
+ } from "./chunk-FUPDFYHI.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-UB6BRUQ4.js";
18
+ } from "./chunk-QAYMV7WE.js";
19
19
 
20
20
  // src/config/config-manager.ts
21
21
  import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
@@ -6,7 +6,7 @@ import {
6
6
  SUBAGENT_DEFAULT_MAX_ROUNDS,
7
7
  SUBAGENT_MAX_ROUNDS_LIMIT,
8
8
  runTestsTool
9
- } from "./chunk-UB6BRUQ4.js";
9
+ } from "./chunk-QAYMV7WE.js";
10
10
 
11
11
  // src/tools/builtin/bash.ts
12
12
  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.2";
9
+ var VERSION = "0.4.4";
10
10
  var APP_NAME = "ai-cli";
11
11
  var CONFIG_DIR_NAME = ".aicli";
12
12
  var CONFIG_FILE_NAME = "config.json";
@@ -59,8 +59,8 @@ You are currently in read-only planning (Plan) mode.
59
59
  - Focus your analysis on reading files and producing actionable plans.
60
60
 
61
61
  Once planning is complete, clearly inform the user: type \`/plan execute\` to begin executing the plan, or \`/plan exit\` to discard it.`;
62
- var SUBAGENT_DEFAULT_MAX_ROUNDS = 10;
63
- var SUBAGENT_MAX_ROUNDS_LIMIT = 15;
62
+ var SUBAGENT_DEFAULT_MAX_ROUNDS = 15;
63
+ var SUBAGENT_MAX_ROUNDS_LIMIT = 30;
64
64
  var SUBAGENT_ALLOWED_TOOLS = /* @__PURE__ */ new Set([
65
65
  "bash",
66
66
  "read_file",
@@ -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.2";
11
+ var VERSION = "0.4.4";
12
12
  var APP_NAME = "ai-cli";
13
13
  var CONFIG_DIR_NAME = ".aicli";
14
14
  var CONFIG_FILE_NAME = "config.json";
@@ -62,8 +62,8 @@ You are currently in read-only planning (Plan) mode.
62
62
  - Focus your analysis on reading files and producing actionable plans.
63
63
 
64
64
  Once planning is complete, clearly inform the user: type \`/plan execute\` to begin executing the plan, or \`/plan exit\` to discard it.`;
65
- var SUBAGENT_DEFAULT_MAX_ROUNDS = 10;
66
- var SUBAGENT_MAX_ROUNDS_LIMIT = 15;
65
+ var SUBAGENT_DEFAULT_MAX_ROUNDS = 15;
66
+ var SUBAGENT_MAX_ROUNDS_LIMIT = 30;
67
67
  var SUBAGENT_ALLOWED_TOOLS = /* @__PURE__ */ new Set([
68
68
  "bash",
69
69
  "read_file",
@@ -366,7 +366,7 @@ ${content}`);
366
366
  defaultModel,
367
367
  maxRounds: options.maxRounds ?? (mode === "task" ? 15 : 10),
368
368
  enableTools: mode === "task",
369
- maxToolRoundsPerTurn: mode === "task" ? 15 : void 0,
369
+ maxToolRoundsPerTurn: mode === "task" ? 30 : void 0,
370
370
  context,
371
371
  contextFiles: contextFileNames.length > 0 ? contextFileNames : void 0
372
372
  };
@@ -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-GONUQ6TN.js");
384
+ const { TaskOrchestrator } = await import("./task-orchestrator-XUMAUKLC.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-LP2ZH4HH.js";
26
+ } from "./chunk-DGH7SQCZ.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-E2HVDEWW.js";
41
+ } from "./chunk-FUPDFYHI.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-UB6BRUQ4.js";
61
+ } from "./chunk-QAYMV7WE.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-4WOG5THP.js");
1917
+ const { executeTests } = await import("./run-tests-KPWEEJKA.js");
1918
1918
  const argStr = args.join(" ").trim();
1919
1919
  let testArgs = {};
1920
1920
  if (argStr) {
@@ -5524,7 +5524,7 @@ program.command("web").description("Start Web UI server with browser-based chat
5524
5524
  console.error("Error: Invalid port number. Must be between 1 and 65535.");
5525
5525
  process.exit(1);
5526
5526
  }
5527
- const { startWebServer } = await import("./server-JNWKHPBS.js");
5527
+ const { startWebServer } = await import("./server-USQZD3PU.js");
5528
5528
  await startWebServer({ port, host: options.host });
5529
5529
  });
5530
5530
  program.command("user [action] [username]").description("Manage Web UI users (list | create <name> | delete <name> | reset-password <name> | migrate <name>)").action(async (action, username) => {
@@ -5757,7 +5757,7 @@ program.command("hub [topic]").description("Start multi-agent hub (discuss / bra
5757
5757
  }),
5758
5758
  config.get("customProviders")
5759
5759
  );
5760
- const { startHub } = await import("./hub-OXZNGBAY.js");
5760
+ const { startHub } = await import("./hub-ZP7OZJMH.js");
5761
5761
  await startHub(
5762
5762
  {
5763
5763
  topic: topic ?? "",
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  executeTests,
3
3
  runTestsTool
4
- } from "./chunk-54EGIKT5.js";
4
+ } from "./chunk-GGIRUPT6.js";
5
5
  export {
6
6
  executeTests,
7
7
  runTestsTool
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  executeTests,
4
4
  runTestsTool
5
- } from "./chunk-UB6BRUQ4.js";
5
+ } from "./chunk-QAYMV7WE.js";
6
6
  export {
7
7
  executeTests,
8
8
  runTestsTool
@@ -18,7 +18,7 @@ import {
18
18
  renderDiff,
19
19
  runHook,
20
20
  setupProxy
21
- } from "./chunk-LP2ZH4HH.js";
21
+ } from "./chunk-DGH7SQCZ.js";
22
22
  import {
23
23
  AuthManager
24
24
  } from "./chunk-CPLT6CD3.js";
@@ -32,7 +32,7 @@ import {
32
32
  spawnAgentContext,
33
33
  truncateOutput,
34
34
  undoStack
35
- } from "./chunk-E2HVDEWW.js";
35
+ } from "./chunk-FUPDFYHI.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-UB6BRUQ4.js";
47
+ } from "./chunk-QAYMV7WE.js";
48
48
 
49
49
  // src/web/server.ts
50
50
  import express from "express";
@@ -1440,7 +1440,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
1440
1440
  case "test": {
1441
1441
  this.send({ type: "info", message: "\u{1F9EA} Running tests..." });
1442
1442
  try {
1443
- const { executeTests } = await import("./run-tests-4WOG5THP.js");
1443
+ const { executeTests } = await import("./run-tests-KPWEEJKA.js");
1444
1444
  const argStr = args.join(" ").trim();
1445
1445
  let testArgs = {};
1446
1446
  if (argStr) {
@@ -4,13 +4,15 @@ import {
4
4
  getDangerLevel,
5
5
  googleSearchContext,
6
6
  truncateOutput
7
- } from "./chunk-E2HVDEWW.js";
7
+ } from "./chunk-FUPDFYHI.js";
8
8
  import {
9
9
  SUBAGENT_ALLOWED_TOOLS
10
- } from "./chunk-UB6BRUQ4.js";
10
+ } from "./chunk-QAYMV7WE.js";
11
11
 
12
12
  // src/hub/task-orchestrator.ts
13
13
  import { createInterface } from "readline";
14
+ import { existsSync, readFileSync, writeFileSync, unlinkSync } from "fs";
15
+ import { join } from "path";
14
16
  import chalk from "chalk";
15
17
 
16
18
  // src/hub/task-executor.ts
@@ -63,6 +65,7 @@ ${task}`, timestamp: /* @__PURE__ */ new Date() }
63
65
  let finalContent = "";
64
66
  let roundsUsed = 0;
65
67
  const executor = new TaskToolExecutor(registry, onToolCall, onToolResult);
68
+ let toolCallCount = 0;
66
69
  try {
67
70
  for (let round = 0; round < maxRounds; round++) {
68
71
  roundsUsed = round + 1;
@@ -71,17 +74,24 @@ ${task}`, timestamp: /* @__PURE__ */ new Date() }
71
74
  if (configManager) {
72
75
  googleSearchContext.configManager = configManager;
73
76
  }
77
+ let activeSystemPrompt = systemPrompt;
78
+ if (round === maxRounds - 2) {
79
+ activeSystemPrompt += "\n\n\u26A0\uFE0F You have 2 rounds remaining. Start wrapping up \u2014 finish current work and prepare your summary.";
80
+ } else if (round === maxRounds - 1) {
81
+ activeSystemPrompt += "\n\n\u{1F6D1} LAST ROUND. Do NOT call any more tools. You MUST respond with a text summary of what you accomplished. List files created/modified and the current state.";
82
+ }
74
83
  const result = await provider.chatWithTools(
75
84
  {
76
85
  messages,
77
86
  model,
78
- systemPrompt,
87
+ systemPrompt: activeSystemPrompt,
79
88
  stream: false,
80
89
  temperature: 0.3,
81
90
  maxTokens: 8192,
82
91
  ...extraMessages.length > 0 ? { _extraMessages: extraMessages } : {}
83
92
  },
84
- toolDefs
93
+ // On the very last round, send empty tools to force text response
94
+ round === maxRounds - 1 ? [] : toolDefs
85
95
  );
86
96
  if (result.usage) {
87
97
  totalUsage.inputTokens += result.usage.inputTokens;
@@ -91,13 +101,18 @@ ${task}`, timestamp: /* @__PURE__ */ new Date() }
91
101
  finalContent = result.content;
92
102
  break;
93
103
  }
104
+ toolCallCount += result.toolCalls.length;
94
105
  const toolResults = await executor.executeAll(result.toolCalls);
95
106
  const reasoningContent = "reasoningContent" in result ? result.reasoningContent : void 0;
96
107
  const newMsgs = provider.buildToolResultMessages(result.toolCalls, toolResults, reasoningContent);
97
108
  extraMessages.push(...newMsgs);
98
109
  }
99
110
  if (!finalContent) {
100
- finalContent = `(Task agent reached maximum rounds (${maxRounds}) without final response)`;
111
+ if (toolCallCount > 0) {
112
+ finalContent = `(Task completed work using ${toolCallCount} tool calls across ${maxRounds} rounds, but did not produce a final summary)`;
113
+ } else {
114
+ finalContent = `(Task agent reached maximum rounds (${maxRounds}) without producing output)`;
115
+ }
101
116
  }
102
117
  } catch (err) {
103
118
  const errMsg = err instanceof Error ? err.message : String(err);
@@ -105,7 +120,8 @@ ${task}`, timestamp: /* @__PURE__ */ new Date() }
105
120
  }
106
121
  return {
107
122
  content: finalContent,
108
- success: !finalContent.startsWith("(Task"),
123
+ // Success if: got a real response, OR did useful tool work (soft success)
124
+ success: !finalContent.startsWith("(Task") || toolCallCount > 0,
109
125
  roundsUsed,
110
126
  usage: totalUsage
111
127
  };
@@ -158,6 +174,7 @@ var TaskToolExecutor = class {
158
174
  };
159
175
 
160
176
  // src/hub/task-orchestrator.ts
177
+ var TASK_STATE_FILE = ".aicli-task.json";
161
178
  var TaskOrchestrator = class {
162
179
  constructor(config, providers, configManager) {
163
180
  this.config = config;
@@ -169,7 +186,7 @@ var TaskOrchestrator = class {
169
186
  this.aborted = true;
170
187
  }
171
188
  /**
172
- * Run the full task mode workflow.
189
+ * Run the full task mode workflow, with resume support.
173
190
  */
174
191
  async run(goal) {
175
192
  const provider = this.providers.get(this.config.defaultProvider);
@@ -177,23 +194,85 @@ var TaskOrchestrator = class {
177
194
  console.error(chalk.red(` \u2717 Provider "${this.config.defaultProvider}" not available.`));
178
195
  return;
179
196
  }
180
- this.renderPhaseHeader("PLAN", "Architect is decomposing the goal into tasks...");
181
- const plan = await this.generatePlan(goal, provider);
182
- if (!plan || plan.tasks.length === 0) {
183
- console.log(chalk.red(" \u2717 Failed to generate a task plan."));
184
- return;
197
+ const saved = this.loadState();
198
+ let plan = null;
199
+ if (saved && saved.tasks.some((t) => t.status === "pending" || t.status === "failed")) {
200
+ const doneCount = saved.tasks.filter((t) => t.status === "done").length;
201
+ const totalCount = saved.tasks.length;
202
+ console.log();
203
+ console.log(chalk.bold.yellow(` \u26A1 Found saved task plan (${doneCount}/${totalCount} completed)`));
204
+ console.log(chalk.dim(` Goal: ${saved.goal}`));
205
+ console.log();
206
+ const resume = await this.askResume();
207
+ if (resume) {
208
+ plan = saved;
209
+ for (const t of plan.tasks) {
210
+ if (t.status === "failed") t.status = "pending";
211
+ }
212
+ this.renderPlan(plan);
213
+ }
185
214
  }
186
- this.renderPlan(plan);
187
- this.renderPhaseHeader("APPROVE", "Review the task plan");
188
- const approved = await this.waitForApproval(plan);
189
- if (!approved) {
190
- console.log(chalk.yellow(" \u26A0 Task plan rejected. Exiting."));
191
- return;
215
+ if (!plan) {
216
+ this.renderPhaseHeader("PLAN", "Architect is decomposing the goal into tasks...");
217
+ plan = await this.generatePlan(goal, provider);
218
+ if (!plan || plan.tasks.length === 0) {
219
+ console.log(chalk.red(" \u2717 Failed to generate a task plan."));
220
+ return;
221
+ }
222
+ this.renderPlan(plan);
223
+ this.renderPhaseHeader("APPROVE", "Review the task plan");
224
+ const approved = await this.waitForApproval(plan);
225
+ if (!approved) {
226
+ console.log(chalk.yellow(" \u26A0 Task plan rejected. Exiting."));
227
+ return;
228
+ }
192
229
  }
193
230
  this.renderPhaseHeader("EXECUTE", "Agents are executing tasks...");
194
231
  await this.executePlan(plan, provider);
195
232
  this.renderPhaseHeader("REVIEW", "Generating project summary...");
196
233
  await this.generateReview(plan, provider);
234
+ const allDone = plan.tasks.every((t) => t.status === "done");
235
+ if (allDone) {
236
+ this.deleteState();
237
+ }
238
+ }
239
+ // ── State Persistence ────────────────────────────────────────────
240
+ get stateFilePath() {
241
+ return join(process.cwd(), TASK_STATE_FILE);
242
+ }
243
+ saveState(plan) {
244
+ try {
245
+ writeFileSync(this.stateFilePath, JSON.stringify(plan, null, 2), "utf-8");
246
+ } catch {
247
+ }
248
+ }
249
+ loadState() {
250
+ try {
251
+ if (!existsSync(this.stateFilePath)) return null;
252
+ const raw = readFileSync(this.stateFilePath, "utf-8");
253
+ const parsed = JSON.parse(raw);
254
+ if (!parsed.goal || !Array.isArray(parsed.tasks)) return null;
255
+ return parsed;
256
+ } catch {
257
+ return null;
258
+ }
259
+ }
260
+ deleteState() {
261
+ try {
262
+ if (existsSync(this.stateFilePath)) unlinkSync(this.stateFilePath);
263
+ } catch {
264
+ }
265
+ }
266
+ async askResume() {
267
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
268
+ try {
269
+ const answer = await new Promise((resolve) => {
270
+ rl.question(chalk.green(" Resume from saved state? (y/n) > "), resolve);
271
+ });
272
+ return answer.trim().toLowerCase() === "y" || answer.trim().toLowerCase() === "yes";
273
+ } finally {
274
+ rl.close();
275
+ }
197
276
  }
198
277
  // ── Phase 1: Plan Generation ──────────────────────────────────────
199
278
  async generatePlan(goal, provider) {
@@ -305,9 +384,12 @@ Example:
305
384
  }
306
385
  // ── Phase 3: Task Execution ───────────────────────────────────────
307
386
  async executePlan(plan, provider) {
308
- const maxRoundsPerTask = this.config.maxToolRoundsPerTurn ?? 15;
387
+ const maxRoundsPerTask = this.config.maxToolRoundsPerTurn ?? 30;
309
388
  const roleMap = new Map(this.config.roles.map((r) => [r.id, r]));
310
- const completedIds = /* @__PURE__ */ new Set();
389
+ const completedIds = new Set(
390
+ plan.tasks.filter((t) => t.status === "done").map((t) => t.id)
391
+ );
392
+ this.saveState(plan);
311
393
  while (!this.aborted) {
312
394
  const nextTask = plan.tasks.find(
313
395
  (t) => t.status === "pending" && t.dependencies.every((d) => completedIds.has(d))
@@ -354,6 +436,7 @@ Example:
354
436
  if (result.success) {
355
437
  completedIds.add(nextTask.id);
356
438
  }
439
+ this.saveState(plan);
357
440
  this.renderTaskEnd(nextTask, result);
358
441
  if (this.aborted) {
359
442
  console.log(chalk.yellow("\n \u26A0 Execution interrupted by user."));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jinzd-ai-cli",
3
- "version": "0.4.2",
3
+ "version": "0.4.4",
4
4
  "description": "Cross-platform REPL-style AI CLI with multi-provider support",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",