jinzd-ai-cli 0.4.213 → 0.4.215

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.
Files changed (28) hide show
  1. package/README.md +5 -2
  2. package/README.zh-CN.md +5 -2
  3. package/dist/{batch-PKY63TME.js → batch-74H5SA7P.js} +2 -2
  4. package/dist/{chunk-MRGWPCFQ.js → chunk-2224JGA6.js} +1 -1
  5. package/dist/{chunk-UDMU4FUF.js → chunk-3TSHNZKI.js} +1 -1
  6. package/dist/{chunk-THNECAAY.js → chunk-6C3JYNM6.js} +380 -74
  7. package/dist/{chunk-IWLVH32D.js → chunk-7JES2NWR.js} +1 -1
  8. package/dist/{chunk-FGUKBIYO.js → chunk-KL7UBVSQ.js} +2 -2
  9. package/dist/{chunk-J35V3IAH.js → chunk-RS4WBI73.js} +1 -1
  10. package/dist/{chunk-7NTFMLJJ.js → chunk-X6OXS7KU.js} +1 -1
  11. package/dist/{chunk-G3PW3AOB.js → chunk-ZOF5NFKW.js} +1 -1
  12. package/dist/{ci-HBZ6CIW5.js → ci-3ALK2XJN.js} +2 -2
  13. package/dist/{constants-CAUBTSFW.js → constants-IN2HXJK7.js} +1 -1
  14. package/dist/{doctor-cli-5Z5UWYW6.js → doctor-cli-7XL4TCVT.js} +4 -4
  15. package/dist/electron-server.js +558 -184
  16. package/dist/{hub-ZRADBR76.js → hub-VAOG5EY6.js} +1 -1
  17. package/dist/index.js +17 -16
  18. package/dist/{run-tests-7J3PNWVS.js → run-tests-J2JQ57R3.js} +1 -1
  19. package/dist/{run-tests-DSOJBJWA.js → run-tests-QJD43AML.js} +2 -2
  20. package/dist/{server-U4I7RI42.js → server-VGPZOISQ.js} +88 -8
  21. package/dist/{server-PWBWHVYB.js → server-WDLV3W4F.js} +4 -4
  22. package/dist/{task-orchestrator-RUXWRMQV.js → task-orchestrator-QFOCO3N7.js} +4 -4
  23. package/dist/{usage-2P6TIBTN.js → usage-IEB476NE.js} +2 -2
  24. package/dist/web/client/actions.js +1 -0
  25. package/dist/web/client/app.js +183 -11
  26. package/dist/web/client/index.html +21 -3
  27. package/dist/web/client/style.css +62 -0
  28. package/package.json +1 -1
@@ -5,10 +5,10 @@ import {
5
5
  } from "./chunk-T2NL5ZIA.js";
6
6
  import {
7
7
  runTestsTool
8
- } from "./chunk-UDMU4FUF.js";
8
+ } from "./chunk-3TSHNZKI.js";
9
9
  import {
10
10
  runTool
11
- } from "./chunk-7NTFMLJJ.js";
11
+ } from "./chunk-X6OXS7KU.js";
12
12
  import {
13
13
  getDangerLevel,
14
14
  isFileWriteTool,
@@ -26,7 +26,7 @@ import {
26
26
  SUBAGENT_ALLOWED_TOOLS,
27
27
  SUBAGENT_DEFAULT_MAX_ROUNDS,
28
28
  SUBAGENT_MAX_ROUNDS_LIMIT
29
- } from "./chunk-IWLVH32D.js";
29
+ } from "./chunk-7JES2NWR.js";
30
30
  import {
31
31
  fileCheckpoints
32
32
  } from "./chunk-4BKXL7SM.js";
@@ -4896,6 +4896,218 @@ function formatResults2(query, data, _requested) {
4896
4896
  return header + "\n" + results.join("\n\n");
4897
4897
  }
4898
4898
 
4899
+ // src/agents/agent-config.ts
4900
+ import { existsSync as existsSync12, readdirSync as readdirSync6, readFileSync as readFileSync9 } from "fs";
4901
+ import { join as join7 } from "path";
4902
+ import { homedir as homedir4 } from "os";
4903
+ var READ_ONLY_TOOLS2 = /* @__PURE__ */ new Set([
4904
+ "read_file",
4905
+ "list_dir",
4906
+ "grep_files",
4907
+ "glob_files",
4908
+ "web_fetch",
4909
+ "google_search",
4910
+ "write_todos",
4911
+ "find_symbol",
4912
+ "get_outline",
4913
+ "find_references",
4914
+ "search_code"
4915
+ ]);
4916
+ var BUILTIN_AGENTS = [
4917
+ {
4918
+ name: "explorer",
4919
+ description: "Read-only code exploration and evidence gathering.",
4920
+ permissionProfile: "read-only",
4921
+ allowedTools: [...READ_ONLY_TOOLS2],
4922
+ maxToolRounds: 10,
4923
+ contextPolicy: "task-only",
4924
+ system: "Act as a read-only explorer. Inspect code, collect evidence, and do not modify files.",
4925
+ source: "builtin"
4926
+ },
4927
+ {
4928
+ name: "worker",
4929
+ description: "Implementation and focused fixes within sub-agent safety limits.",
4930
+ permissionProfile: "workspace-write",
4931
+ maxToolRounds: 15,
4932
+ contextPolicy: "task-only",
4933
+ system: "Act as a focused implementation worker. Keep edits scoped and report changed files.",
4934
+ source: "builtin"
4935
+ },
4936
+ {
4937
+ name: "reviewer",
4938
+ description: "Code review focused on bugs, regressions, and missing tests.",
4939
+ permissionProfile: "read-only",
4940
+ allowedTools: [...READ_ONLY_TOOLS2],
4941
+ maxToolRounds: 12,
4942
+ contextPolicy: "task-only",
4943
+ system: "Act as a code reviewer. Lead with concrete findings, file evidence, and residual risks.",
4944
+ source: "builtin"
4945
+ },
4946
+ {
4947
+ name: "security",
4948
+ description: "Security audit with read-only evidence collection.",
4949
+ permissionProfile: "read-only",
4950
+ allowedTools: [...READ_ONLY_TOOLS2],
4951
+ maxToolRounds: 12,
4952
+ contextPolicy: "task-only",
4953
+ system: "Act as a security reviewer. Look for trust-boundary, injection, filesystem, network, and secret-handling issues.",
4954
+ source: "builtin"
4955
+ },
4956
+ {
4957
+ name: "tester",
4958
+ description: "Test planning and regression analysis without direct shell execution.",
4959
+ permissionProfile: "read-only",
4960
+ allowedTools: [...READ_ONLY_TOOLS2],
4961
+ maxToolRounds: 12,
4962
+ contextPolicy: "task-only",
4963
+ system: "Act as a tester. Inspect tests, identify coverage gaps, and propose precise regression checks.",
4964
+ source: "builtin"
4965
+ }
4966
+ ];
4967
+ function normalizeName(name) {
4968
+ return name.trim().toLowerCase();
4969
+ }
4970
+ function isStringArray(value) {
4971
+ return Array.isArray(value) && value.every((item) => typeof item === "string");
4972
+ }
4973
+ function parseAgentConfig(raw, source, path3) {
4974
+ if (!raw || typeof raw !== "object") return null;
4975
+ const obj = raw;
4976
+ if (typeof obj.name !== "string" || obj.name.trim() === "") return null;
4977
+ const cfg = {
4978
+ name: obj.name.trim(),
4979
+ source,
4980
+ path: path3
4981
+ };
4982
+ if (typeof obj.description === "string") cfg.description = obj.description;
4983
+ if (typeof obj.provider === "string") cfg.provider = obj.provider;
4984
+ if (typeof obj.model === "string") cfg.model = obj.model;
4985
+ if (typeof obj.system === "string") cfg.system = obj.system;
4986
+ if (typeof obj.instructions === "string") cfg.instructions = obj.instructions;
4987
+ if (typeof obj.systemInstructions === "string") cfg.systemInstructions = obj.systemInstructions;
4988
+ if (isStringArray(obj.allowedTools)) cfg.allowedTools = obj.allowedTools;
4989
+ if (isStringArray(obj.blockedTools)) cfg.blockedTools = obj.blockedTools;
4990
+ if (typeof obj.permissionProfile === "string") cfg.permissionProfile = obj.permissionProfile;
4991
+ if (typeof obj.maxToolRounds === "number" && Number.isFinite(obj.maxToolRounds)) cfg.maxToolRounds = obj.maxToolRounds;
4992
+ if (typeof obj.contextPolicy === "string") cfg.contextPolicy = obj.contextPolicy;
4993
+ return cfg;
4994
+ }
4995
+ function loadAgentDir(dir, source) {
4996
+ if (!existsSync12(dir)) return [];
4997
+ const out = [];
4998
+ for (const file of readdirSync6(dir)) {
4999
+ if (!file.endsWith(".json")) continue;
5000
+ const path3 = join7(dir, file);
5001
+ try {
5002
+ const parsed = JSON.parse(readFileSync9(path3, "utf-8"));
5003
+ const cfg = parseAgentConfig(parsed, source, path3);
5004
+ if (cfg) out.push(cfg);
5005
+ } catch {
5006
+ }
5007
+ }
5008
+ return out;
5009
+ }
5010
+ function getAgentDirs(configDir, cwd = process.cwd()) {
5011
+ return {
5012
+ user: join7(configDir ?? join7(homedir4(), CONFIG_DIR_NAME), "agents"),
5013
+ project: join7(cwd, CONFIG_DIR_NAME, "agents")
5014
+ };
5015
+ }
5016
+ function listAgentConfigs(configDir, cwd = process.cwd()) {
5017
+ const dirs = getAgentDirs(configDir, cwd);
5018
+ const byName = /* @__PURE__ */ new Map();
5019
+ for (const cfg of BUILTIN_AGENTS) byName.set(normalizeName(cfg.name), { ...cfg });
5020
+ for (const cfg of loadAgentDir(dirs.user, "user")) byName.set(normalizeName(cfg.name), cfg);
5021
+ for (const cfg of loadAgentDir(dirs.project, "project")) byName.set(normalizeName(cfg.name), cfg);
5022
+ return [...byName.values()].sort((a, b) => a.name.localeCompare(b.name));
5023
+ }
5024
+ function resolveAgentConfig(name, configDir, cwd = process.cwd()) {
5025
+ const target = normalizeName(name);
5026
+ return listAgentConfigs(configDir, cwd).find((cfg) => normalizeName(cfg.name) === target);
5027
+ }
5028
+ function getAgentInstructions(agent) {
5029
+ return agent.system ?? agent.systemInstructions ?? agent.instructions;
5030
+ }
5031
+ function getEffectiveAgentTools(agent) {
5032
+ let allowed = new Set(SUBAGENT_ALLOWED_TOOLS);
5033
+ if (agent.permissionProfile === "read-only") {
5034
+ allowed = new Set([...allowed].filter((name) => READ_ONLY_TOOLS2.has(name)));
5035
+ }
5036
+ if (agent.allowedTools && agent.allowedTools.length > 0) {
5037
+ const requested = new Set(agent.allowedTools);
5038
+ allowed = new Set([...allowed].filter((name) => requested.has(name)));
5039
+ }
5040
+ if (agent.blockedTools && agent.blockedTools.length > 0) {
5041
+ for (const name of agent.blockedTools) allowed.delete(name);
5042
+ }
5043
+ return allowed;
5044
+ }
5045
+ function getEffectiveMaxRounds(requestedMaxRounds, agent) {
5046
+ const agentMax = agent.maxToolRounds && agent.maxToolRounds > 0 ? Math.round(agent.maxToolRounds) : SUBAGENT_MAX_ROUNDS_LIMIT;
5047
+ return Math.min(requestedMaxRounds, agentMax, SUBAGENT_MAX_ROUNDS_LIMIT);
5048
+ }
5049
+
5050
+ // src/agents/agent-runtime.ts
5051
+ var nextId = 1;
5052
+ var preferredAgentName = "worker";
5053
+ var runs = [];
5054
+ var MAX_RUNS = 50;
5055
+ function nowIso() {
5056
+ return (/* @__PURE__ */ new Date()).toISOString();
5057
+ }
5058
+ function startAgentRun(input) {
5059
+ const run = {
5060
+ id: `agent-${nextId++}`,
5061
+ agentName: input.agentName,
5062
+ task: input.task,
5063
+ status: "running",
5064
+ startedAt: nowIso(),
5065
+ toolNames: [],
5066
+ agentIndex: input.agentIndex,
5067
+ stopRequested: false
5068
+ };
5069
+ runs.unshift(run);
5070
+ if (runs.length > MAX_RUNS) runs.splice(MAX_RUNS);
5071
+ return run;
5072
+ }
5073
+ function recordAgentTool(runId, toolName) {
5074
+ const run = runs.find((r) => r.id === runId);
5075
+ if (!run) return;
5076
+ if (!run.toolNames.includes(toolName)) run.toolNames.push(toolName);
5077
+ }
5078
+ function finishAgentRun(runId, status, summary, error) {
5079
+ const run = runs.find((r) => r.id === runId);
5080
+ if (!run) return;
5081
+ run.status = status;
5082
+ run.finishedAt = nowIso();
5083
+ if (summary) run.summary = summary;
5084
+ if (error) run.error = error;
5085
+ }
5086
+ function listAgentRuns() {
5087
+ return runs.map((run) => ({ ...run, toolNames: [...run.toolNames] }));
5088
+ }
5089
+ function requestAgentStop(idOrAll) {
5090
+ const target = idOrAll.trim().toLowerCase();
5091
+ let count = 0;
5092
+ for (const run of runs) {
5093
+ if (run.status !== "running") continue;
5094
+ if (target === "all" || run.id.toLowerCase() === target) {
5095
+ run.stopRequested = true;
5096
+ count++;
5097
+ }
5098
+ }
5099
+ return count;
5100
+ }
5101
+ function isAgentStopRequested(runId) {
5102
+ return runs.find((r) => r.id === runId)?.stopRequested ?? false;
5103
+ }
5104
+ function setPreferredAgentName(name) {
5105
+ preferredAgentName = name.trim() || "worker";
5106
+ }
5107
+ function getPreferredAgentName() {
5108
+ return preferredAgentName;
5109
+ }
5110
+
4899
5111
  // src/tools/builtin/spawn-agent.ts
4900
5112
  var spawnAgentContext = {
4901
5113
  provider: null,
@@ -4910,18 +5122,27 @@ function agentPrefix(agentIndex) {
4910
5122
  return theme.dim(` \u2503${agentIndex} `);
4911
5123
  }
4912
5124
  var SubAgentExecutor = class {
4913
- constructor(registry, agentIndex = null) {
5125
+ constructor(registry, agentIndex = null, runId) {
4914
5126
  this.registry = registry;
5127
+ this.runId = runId;
4915
5128
  this.prefix = agentPrefix(agentIndex);
4916
5129
  }
4917
5130
  round = 0;
4918
5131
  totalRounds = 0;
4919
5132
  prefix = PREFIX;
5133
+ toolNames = [];
4920
5134
  setRoundInfo(current, total) {
4921
5135
  this.round = current;
4922
5136
  this.totalRounds = total;
4923
5137
  }
4924
5138
  async execute(call) {
5139
+ if (this.runId && isAgentStopRequested(this.runId)) {
5140
+ return {
5141
+ callId: call.id,
5142
+ content: "Sub-agent stop requested.",
5143
+ isError: true
5144
+ };
5145
+ }
4925
5146
  const tool = this.registry.get(call.name);
4926
5147
  if (!tool) {
4927
5148
  return {
@@ -4942,6 +5163,8 @@ var SubAgentExecutor = class {
4942
5163
  };
4943
5164
  }
4944
5165
  this.printToolCall(call, dangerLevel);
5166
+ if (!this.toolNames.includes(call.name)) this.toolNames.push(call.name);
5167
+ if (this.runId) recordAgentTool(this.runId, call.name);
4945
5168
  try {
4946
5169
  const rawContent = await runTool(tool, call.arguments, call.name);
4947
5170
  const content = truncateOutput(rawContent, call.name);
@@ -4961,7 +5184,6 @@ var SubAgentExecutor = class {
4961
5184
  }
4962
5185
  return results;
4963
5186
  }
4964
- // ── 带前缀的终端输出 ──
4965
5187
  printPrefixed(text) {
4966
5188
  for (const line of text.split("\n")) {
4967
5189
  console.log(this.prefix + line);
@@ -5002,7 +5224,7 @@ var SubAgentExecutor = class {
5002
5224
  }
5003
5225
  }
5004
5226
  };
5005
- function buildSubAgentSystemPrompt(task, parentSystemPrompt) {
5227
+ function buildSubAgentSystemPrompt(task, agent, parentSystemPrompt) {
5006
5228
  const parts = [];
5007
5229
  if (parentSystemPrompt) {
5008
5230
  parts.push(parentSystemPrompt);
@@ -5010,6 +5232,8 @@ function buildSubAgentSystemPrompt(task, parentSystemPrompt) {
5010
5232
  parts.push(
5011
5233
  `# Sub-Agent Mode
5012
5234
 
5235
+ **Agent Role**: ${agent.name}${agent.description ? ` \u2014 ${agent.description}` : ""}
5236
+
5013
5237
  You are a focused sub-agent delegated by the main agent to complete a specific task.
5014
5238
 
5015
5239
  **Your Task**:
@@ -5023,13 +5247,19 @@ ${task}
5023
5247
  5. Destructive operations (rm -rf etc.) will be automatically blocked
5024
5248
  6. Be efficient, minimize unnecessary tool call rounds`
5025
5249
  );
5250
+ const agentInstructions = getAgentInstructions(agent);
5251
+ if (agentInstructions) {
5252
+ parts.push(`# Agent Instructions
5253
+
5254
+ ${agentInstructions}`);
5255
+ }
5026
5256
  return parts.join("\n\n---\n\n");
5027
5257
  }
5028
- function printSubAgentHeader(task, maxRounds, agentIndex = null) {
5258
+ function printSubAgentHeader(task, maxRounds, agentName, agentIndex = null) {
5029
5259
  const prefix = agentPrefix(agentIndex);
5030
5260
  console.log();
5031
5261
  console.log(theme.dim(" \u250F\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"));
5032
- const label = agentIndex !== null ? `\u{1F916} Sub-Agent #${agentIndex} Spawned` : "\u{1F916} Sub-Agent Spawned";
5262
+ const label = agentIndex !== null ? `\u{1F916} Sub-Agent #${agentIndex} Spawned (${agentName})` : `\u{1F916} Sub-Agent Spawned (${agentName})`;
5033
5263
  console.log(prefix + theme.toolCall(label));
5034
5264
  console.log(
5035
5265
  prefix + theme.dim("Task: ") + (task.slice(0, 120) + (task.length > 120 ? "..." : ""))
@@ -5052,58 +5282,110 @@ function printSubAgentFooter(usage, agentIndex = null) {
5052
5282
  console.log(theme.dim(" \u2517\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"));
5053
5283
  console.log();
5054
5284
  }
5055
- async function runSubAgent(task, maxRounds, agentIndex, ctx) {
5285
+ function summarizeContent(content) {
5286
+ const flat = content.replace(/\s+/g, " ").trim();
5287
+ return flat.length > 500 ? flat.slice(0, 497) + "..." : flat;
5288
+ }
5289
+ function extractEvidence(content, toolNames) {
5290
+ const evidence = /* @__PURE__ */ new Set();
5291
+ if (toolNames.length > 0) evidence.add(`Tools: ${toolNames.join(", ")}`);
5292
+ const matches = content.match(/(?:[\w.-]+[\\/])+[\w.-]+\.(?:ts|tsx|js|jsx|json|md|css|html|py|go|rs|yml|yaml)/g) ?? [];
5293
+ for (const match of matches.slice(0, 8)) evidence.add(match);
5294
+ return [...evidence].slice(0, 10);
5295
+ }
5296
+ function formatAgentResult(input) {
5297
+ const lines = [input.title, "", `**Agent**: ${input.agentName}`];
5298
+ if (input.task) lines.push(`**Task**: ${input.task}`);
5299
+ lines.push("", "### Summary", input.summary || "(no summary)", "", "### Key Evidence");
5300
+ if (input.evidence.length > 0) {
5301
+ for (const item of input.evidence) lines.push(`- ${item}`);
5302
+ } else {
5303
+ lines.push("- No structured evidence extracted.");
5304
+ }
5305
+ lines.push("", "### Tools Used");
5306
+ lines.push(input.toolNames.length > 0 ? input.toolNames.join(", ") : "No tools used.");
5307
+ lines.push("", "### Full Result", input.content, "");
5308
+ lines.push(`*Tokens: ${input.usage.inputTokens} in / ${input.usage.outputTokens} out*`);
5309
+ return lines;
5310
+ }
5311
+ async function runSubAgent(task, maxRounds, agentIndex, ctx, agent) {
5056
5312
  if (!ctx.provider) {
5057
5313
  throw new ToolError("spawn_agent", "provider not initialized (context not injected)");
5058
5314
  }
5315
+ const providerFromConfig = agent.provider && ctx.providers?.has(agent.provider) ? ctx.providers.get(agent.provider) : void 0;
5316
+ const provider = providerFromConfig ?? ctx.provider;
5317
+ const model = agent.model ?? (providerFromConfig?.info?.defaultModel ?? ctx.model);
5318
+ const run = startAgentRun({
5319
+ agentName: agent.name,
5320
+ task,
5321
+ agentIndex: agentIndex ?? void 0
5322
+ });
5323
+ const effectiveTools = getEffectiveAgentTools(agent);
5059
5324
  const subRegistry = new ToolRegistry();
5060
5325
  for (const tool of subRegistry.listAll()) {
5061
- if (!SUBAGENT_ALLOWED_TOOLS.has(tool.definition.name)) {
5326
+ if (!SUBAGENT_ALLOWED_TOOLS.has(tool.definition.name) || !effectiveTools.has(tool.definition.name)) {
5062
5327
  subRegistry.unregister(tool.definition.name);
5063
5328
  }
5064
5329
  }
5065
- const subExecutor = new SubAgentExecutor(subRegistry, agentIndex);
5330
+ const subExecutor = new SubAgentExecutor(subRegistry, agentIndex, run.id);
5066
5331
  const subMessages = [
5067
5332
  { role: "user", content: task, timestamp: /* @__PURE__ */ new Date() }
5068
5333
  ];
5069
- const subSystemPrompt = buildSubAgentSystemPrompt(task, ctx.systemPrompt);
5334
+ const subSystemPrompt = buildSubAgentSystemPrompt(task, agent, ctx.systemPrompt);
5070
5335
  const toolDefs = subRegistry.getDefinitions();
5071
- printSubAgentHeader(task, maxRounds, agentIndex);
5072
- const loop = await runLeanAgentLoop({
5073
- provider: ctx.provider,
5074
- messages: subMessages,
5075
- model: ctx.model,
5076
- maxRounds,
5077
- chatParams: {
5078
- temperature: ctx.modelParams.temperature,
5079
- maxTokens: ctx.modelParams.maxTokens,
5080
- timeout: ctx.modelParams.timeout,
5081
- thinking: ctx.modelParams.thinking
5082
- },
5083
- executeTools: (calls) => subExecutor.executeAll(calls),
5084
- systemPromptForRound: () => subSystemPrompt,
5085
- toolDefsForRound: () => toolDefs,
5086
- onRoundStart: (round) => {
5087
- subExecutor.setRoundInfo(round + 1, maxRounds);
5088
- if (ctx.configManager) {
5089
- googleSearchContext.configManager = ctx.configManager;
5090
- }
5091
- },
5092
- onRoundsExhausted: () => `(Sub-agent reached maximum rounds (${maxRounds}) without producing a final response)`,
5093
- onError: (errMsg) => {
5094
- process.stderr.write(`
5336
+ printSubAgentHeader(task, maxRounds, agent.name, agentIndex);
5337
+ try {
5338
+ const loop = await runLeanAgentLoop({
5339
+ provider,
5340
+ messages: subMessages,
5341
+ model,
5342
+ maxRounds,
5343
+ chatParams: {
5344
+ temperature: ctx.modelParams.temperature,
5345
+ maxTokens: ctx.modelParams.maxTokens,
5346
+ timeout: ctx.modelParams.timeout,
5347
+ thinking: ctx.modelParams.thinking
5348
+ },
5349
+ executeTools: (calls) => subExecutor.executeAll(calls),
5350
+ systemPromptForRound: () => subSystemPrompt,
5351
+ toolDefsForRound: () => toolDefs,
5352
+ onRoundStart: (round) => {
5353
+ subExecutor.setRoundInfo(round + 1, maxRounds);
5354
+ if (ctx.configManager) {
5355
+ googleSearchContext.configManager = ctx.configManager;
5356
+ }
5357
+ },
5358
+ onRoundsExhausted: () => `(Sub-agent reached maximum rounds (${maxRounds}) without producing a final response)`,
5359
+ onError: (errMsg) => {
5360
+ process.stderr.write(`
5095
5361
  [spawn_agent] Error in sub-agent loop: ${errMsg}
5096
5362
  `);
5097
- return `(Sub-agent error: ${errMsg})`;
5098
- }
5099
- });
5100
- printSubAgentFooter(loop.usage, agentIndex);
5101
- return { content: loop.content, usage: loop.usage };
5363
+ return `(Sub-agent error: ${errMsg})`;
5364
+ }
5365
+ });
5366
+ printSubAgentFooter(loop.usage, agentIndex);
5367
+ const summary = summarizeContent(loop.content);
5368
+ const evidence = extractEvidence(loop.content, subExecutor.toolNames);
5369
+ const stopped = isAgentStopRequested(run.id);
5370
+ finishAgentRun(run.id, stopped ? "stopped" : "ok", summary);
5371
+ return {
5372
+ content: loop.content,
5373
+ usage: loop.usage,
5374
+ toolNames: subExecutor.toolNames,
5375
+ summary,
5376
+ evidence,
5377
+ agentName: agent.name
5378
+ };
5379
+ } catch (err) {
5380
+ const message = err instanceof Error ? err.message : String(err);
5381
+ finishAgentRun(run.id, "error", void 0, message);
5382
+ throw err;
5383
+ }
5102
5384
  }
5103
5385
  var spawnAgentTool = {
5104
5386
  definition: {
5105
5387
  name: "spawn_agent",
5106
- description: "Delegate subtask(s) to independent sub-agent(s). Each sub-agent has its own conversation context and agentic tool-call loop, with access to bash, file read/write, edit, search, etc., but cannot interact with the user or spawn more sub-agents. Suitable for independently completable subtasks like: refactoring a module, writing tests, researching a technical approach, or implementing a specific feature. Pass `task` for a single sub-agent, or `tasks` (array) to run multiple sub-agents IN PARALLEL. Parallel mode is ideal for independent research/analysis tasks \u2014 network-bound API calls overlap while the main thread stays responsive. Returns a summary of each sub-agent result.",
5388
+ description: "Delegate subtask(s) to independent named agents. Each sub-agent has its own conversation context and agentic tool-call loop, inherits the parent safety boundary, and can only narrow the sub-agent tool whitelist. Pass `agent` to select a built-in or configured role. Built-ins: explorer, worker, reviewer, security, tester. Pass `task` for a single sub-agent, or `tasks` (array) to run multiple sub-agents IN PARALLEL. Returns summary, key evidence, and tools used for each sub-agent result.",
5107
5389
  parameters: {
5108
5390
  task: {
5109
5391
  type: "string",
@@ -5121,7 +5403,12 @@ var spawnAgentTool = {
5121
5403
  },
5122
5404
  max_rounds: {
5123
5405
  type: "number",
5124
- description: "Max tool-call rounds per sub-agent (1-15, default 10). Applied to each sub-agent in parallel mode.",
5406
+ description: "Max tool-call rounds per sub-agent (1-30, default 15). Agent config can only reduce this value.",
5407
+ required: false
5408
+ },
5409
+ agent: {
5410
+ type: "string",
5411
+ description: "Agent role name from built-ins or ~/.aicli/agents / .aicli/agents JSON configs. Built-ins: explorer, worker, reviewer, security, tester.",
5125
5412
  required: false
5126
5413
  }
5127
5414
  },
@@ -5141,10 +5428,17 @@ var spawnAgentTool = {
5141
5428
  Math.max(Math.round(rawMaxRounds), 1),
5142
5429
  SUBAGENT_MAX_ROUNDS_LIMIT
5143
5430
  );
5431
+ const requestedAgentName = typeof args["agent"] === "string" && args["agent"].trim() ? args["agent"].trim() : getPreferredAgentName();
5144
5432
  const ctx = spawnAgentContext;
5145
5433
  if (!ctx.provider) {
5146
5434
  throw new ToolError("spawn_agent", "provider not initialized (context not injected)");
5147
5435
  }
5436
+ const agent = resolveAgentConfig(requestedAgentName, ctx.configManager?.getConfigDir());
5437
+ if (!agent) {
5438
+ const available = listAgentConfigs(ctx.configManager?.getConfigDir()).map((cfg) => cfg.name).join(", ");
5439
+ throw new ToolError("spawn_agent", `unknown agent "${requestedAgentName}". Available agents: ${available}`);
5440
+ }
5441
+ const effectiveMaxRounds = getEffectiveMaxRounds(maxRounds, agent);
5148
5442
  const hookConfig = ctx.configManager?.get("hooks") ?? void 0;
5149
5443
  const hookConfigDir = ctx.configManager?.getConfigDir() ?? process.cwd();
5150
5444
  const taskCount = singleTask ? 1 : tasksArr.length;
@@ -5152,7 +5446,8 @@ var spawnAgentTool = {
5152
5446
  task: singleTask || void 0,
5153
5447
  tasks: tasksArr.length > 0 ? tasksArr : void 0,
5154
5448
  taskCount,
5155
- maxRounds
5449
+ maxRounds: effectiveMaxRounds,
5450
+ agent: agent.name
5156
5451
  };
5157
5452
  const startDecisions = runLifecycleHooks(hookConfig, "SubagentStart", hookPayload, hookConfigDir);
5158
5453
  const startDeny = startDecisions.find((d) => d.action === "deny");
@@ -5164,32 +5459,37 @@ var spawnAgentTool = {
5164
5459
  subAgentGuard.active = true;
5165
5460
  try {
5166
5461
  if (singleTask) {
5167
- const { content, usage } = await runSubAgent(singleTask, maxRounds, null, ctx);
5462
+ const result = await runSubAgent(singleTask, effectiveMaxRounds, null, ctx, agent);
5168
5463
  subagentStatus = "ok";
5169
- return [
5170
- "## Sub-Agent Result",
5171
- "",
5172
- content,
5173
- "",
5174
- "---",
5175
- `Token usage: ${usage.inputTokens} input, ${usage.outputTokens} output`
5176
- ].join("\n");
5464
+ return formatAgentResult({
5465
+ title: "## Sub-Agent Result",
5466
+ content: result.content,
5467
+ usage: result.usage,
5468
+ toolNames: result.toolNames,
5469
+ summary: result.summary,
5470
+ evidence: result.evidence,
5471
+ agentName: result.agentName
5472
+ }).join("\n");
5177
5473
  }
5178
5474
  console.log();
5179
- console.log(theme.toolCall(`\u{1F916} Spawning ${tasksArr.length} sub-agents in parallel...`));
5475
+ console.log(theme.toolCall(`\u{1F916} Spawning ${tasksArr.length} sub-agents in parallel as ${agent.name}...`));
5180
5476
  const results = await Promise.all(
5181
- tasksArr.map((t, i) => runSubAgent(t, maxRounds, i + 1, ctx))
5477
+ tasksArr.map((t, i) => runSubAgent(t, effectiveMaxRounds, i + 1, ctx, agent))
5182
5478
  );
5183
5479
  const totalIn = results.reduce((s, r) => s + r.usage.inputTokens, 0);
5184
5480
  const totalOut = results.reduce((s, r) => s + r.usage.outputTokens, 0);
5185
5481
  const lines = [`## Parallel Sub-Agent Results (${results.length} agents)`, ""];
5186
5482
  results.forEach((r, i) => {
5187
- lines.push(`### Sub-Agent #${i + 1}`);
5188
- lines.push(`**Task**: ${tasksArr[i].slice(0, 200)}${tasksArr[i].length > 200 ? "..." : ""}`);
5189
- lines.push("");
5190
- lines.push(r.content);
5191
- lines.push("");
5192
- lines.push(`*Tokens: ${r.usage.inputTokens} in / ${r.usage.outputTokens} out*`);
5483
+ lines.push(...formatAgentResult({
5484
+ title: `### Sub-Agent #${i + 1}`,
5485
+ task: `${tasksArr[i].slice(0, 200)}${tasksArr[i].length > 200 ? "..." : ""}`,
5486
+ content: r.content,
5487
+ usage: r.usage,
5488
+ toolNames: r.toolNames,
5489
+ summary: r.summary,
5490
+ evidence: r.evidence,
5491
+ agentName: r.agentName
5492
+ }));
5193
5493
  lines.push("");
5194
5494
  });
5195
5495
  lines.push("---");
@@ -5420,14 +5720,14 @@ var taskStopTool = {
5420
5720
 
5421
5721
  // src/tools/builtin/git-tools.ts
5422
5722
  import { execFileSync as execFileSync2 } from "child_process";
5423
- import { existsSync as existsSync12 } from "fs";
5424
- import { join as join7 } from "path";
5723
+ import { existsSync as existsSync13 } from "fs";
5724
+ import { join as join8 } from "path";
5425
5725
  function assertGitRepo(cwd) {
5426
5726
  let dir = cwd;
5427
5727
  const root = dir.split(/[\\/]/)[0] + (dir.includes("\\") ? "\\" : "/");
5428
5728
  while (dir && dir !== root) {
5429
- if (existsSync12(join7(dir, ".git"))) return;
5430
- const parent = join7(dir, "..");
5729
+ if (existsSync13(join8(dir, ".git"))) return;
5730
+ const parent = join8(dir, "..");
5431
5731
  if (parent === dir) break;
5432
5732
  dir = parent;
5433
5733
  }
@@ -5692,7 +5992,7 @@ ${commitOutput.trim()}`;
5692
5992
  };
5693
5993
 
5694
5994
  // src/tools/builtin/notebook-edit.ts
5695
- import { readFileSync as readFileSync9, existsSync as existsSync13 } from "fs";
5995
+ import { readFileSync as readFileSync10, existsSync as existsSync14 } from "fs";
5696
5996
  import { writeFile } from "fs/promises";
5697
5997
  import { resolve as resolve6, extname as extname2 } from "path";
5698
5998
  var notebookEditTool = {
@@ -5747,10 +6047,10 @@ var notebookEditTool = {
5747
6047
  if (extname2(absPath).toLowerCase() !== ".ipynb") {
5748
6048
  throw new ToolError("notebook_edit", "path must point to a .ipynb file");
5749
6049
  }
5750
- if (!existsSync13(absPath)) {
6050
+ if (!existsSync14(absPath)) {
5751
6051
  throw new ToolError("notebook_edit", `Notebook not found: ${filePath}`);
5752
6052
  }
5753
- const raw = readFileSync9(absPath, "utf-8");
6053
+ const raw = readFileSync10(absPath, "utf-8");
5754
6054
  const nb = parseNotebook(raw);
5755
6055
  const cellIdx0 = cellIndexRaw - 1;
5756
6056
  undoStack.push(absPath, `notebook_edit (${action}): ${filePath}`);
@@ -6167,8 +6467,8 @@ function estimateToolDefinitionTokens(def) {
6167
6467
 
6168
6468
  // src/tools/registry.ts
6169
6469
  import { pathToFileURL } from "url";
6170
- import { existsSync as existsSync14, mkdirSync as mkdirSync5, readdirSync as readdirSync6 } from "fs";
6171
- import { join as join8 } from "path";
6470
+ import { existsSync as existsSync15, mkdirSync as mkdirSync5, readdirSync as readdirSync7 } from "fs";
6471
+ import { join as join9 } from "path";
6172
6472
  var ToolRegistry = class {
6173
6473
  tools = /* @__PURE__ */ new Map();
6174
6474
  pluginToolNames = /* @__PURE__ */ new Set();
@@ -6329,7 +6629,7 @@ var ToolRegistry = class {
6329
6629
  * Returns the number of successfully loaded plugins.
6330
6630
  */
6331
6631
  async loadPlugins(pluginsDir, allowPlugins = false) {
6332
- if (!existsSync14(pluginsDir)) {
6632
+ if (!existsSync15(pluginsDir)) {
6333
6633
  try {
6334
6634
  mkdirSync5(pluginsDir, { recursive: true });
6335
6635
  } catch {
@@ -6338,7 +6638,7 @@ var ToolRegistry = class {
6338
6638
  }
6339
6639
  let files;
6340
6640
  try {
6341
- files = readdirSync6(pluginsDir).filter((f) => f.endsWith(".js"));
6641
+ files = readdirSync7(pluginsDir).filter((f) => f.endsWith(".js"));
6342
6642
  } catch {
6343
6643
  return 0;
6344
6644
  }
@@ -6355,12 +6655,12 @@ var ToolRegistry = class {
6355
6655
  process.stderr.write(
6356
6656
  `
6357
6657
  [plugins] \u26A0 Loading ${files.length} plugin(s) with FULL system privileges:
6358
- ` + files.map((f) => ` + ${join8(pluginsDir, f)}`).join("\n") + "\n\n"
6658
+ ` + files.map((f) => ` + ${join9(pluginsDir, f)}`).join("\n") + "\n\n"
6359
6659
  );
6360
6660
  let loaded = 0;
6361
6661
  for (const file of files) {
6362
6662
  try {
6363
- const fileUrl = pathToFileURL(join8(pluginsDir, file)).href;
6663
+ const fileUrl = pathToFileURL(join9(pluginsDir, file)).href;
6364
6664
  const mod = await import(fileUrl);
6365
6665
  const tool = mod.tool ?? mod.default?.tool ?? mod.default;
6366
6666
  if (!tool || typeof tool.execute !== "function" || !tool.definition?.name) {
@@ -6421,6 +6721,12 @@ export {
6421
6721
  cleanupRejectedTeeFile,
6422
6722
  askUserContext,
6423
6723
  googleSearchContext,
6724
+ listAgentConfigs,
6725
+ resolveAgentConfig,
6726
+ listAgentRuns,
6727
+ requestAgentStop,
6728
+ setPreferredAgentName,
6729
+ getPreferredAgentName,
6424
6730
  spawnAgentContext,
6425
6731
  estimateTokens,
6426
6732
  ToolRegistry
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/core/constants.ts
4
- var VERSION = "0.4.213";
4
+ var VERSION = "0.4.215";
5
5
  var APP_NAME = "ai-cli";
6
6
  var CONFIG_DIR_NAME = ".aicli";
7
7
  var CONFIG_FILE_NAME = "config.json";