jinzd-ai-cli 0.4.213 → 0.4.214

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.
@@ -37,7 +37,7 @@ import {
37
37
  VERSION,
38
38
  buildUserIdentityPrompt,
39
39
  runTestsTool
40
- } from "./chunk-J35V3IAH.js";
40
+ } from "./chunk-JOJA35RK.js";
41
41
  import {
42
42
  hasSemanticIndex,
43
43
  semanticSearch
@@ -61,8 +61,8 @@ import {
61
61
  import express from "express";
62
62
  import { createServer } from "http";
63
63
  import { WebSocketServer } from "ws";
64
- import { join as join21, dirname as dirname6, resolve as resolve9, relative as relative4, sep as sep3 } from "path";
65
- import { existsSync as existsSync27, readFileSync as readFileSync21, readdirSync as readdirSync12, statSync as statSync10, realpathSync } from "fs";
64
+ import { join as join22, dirname as dirname6, resolve as resolve9, relative as relative4, sep as sep3 } from "path";
65
+ import { existsSync as existsSync28, readFileSync as readFileSync22, readdirSync as readdirSync13, statSync as statSync10, realpathSync } from "fs";
66
66
  import { networkInterfaces } from "os";
67
67
 
68
68
  // src/config/config-manager.ts
@@ -9663,6 +9663,218 @@ function formatResults2(query, data, _requested) {
9663
9663
  return header + "\n" + results.join("\n\n");
9664
9664
  }
9665
9665
 
9666
+ // src/agents/agent-config.ts
9667
+ import { existsSync as existsSync15, readdirSync as readdirSync7, readFileSync as readFileSync12 } from "fs";
9668
+ import { join as join10 } from "path";
9669
+ import { homedir as homedir6 } from "os";
9670
+ var READ_ONLY_TOOLS2 = /* @__PURE__ */ new Set([
9671
+ "read_file",
9672
+ "list_dir",
9673
+ "grep_files",
9674
+ "glob_files",
9675
+ "web_fetch",
9676
+ "google_search",
9677
+ "write_todos",
9678
+ "find_symbol",
9679
+ "get_outline",
9680
+ "find_references",
9681
+ "search_code"
9682
+ ]);
9683
+ var BUILTIN_AGENTS = [
9684
+ {
9685
+ name: "explorer",
9686
+ description: "Read-only code exploration and evidence gathering.",
9687
+ permissionProfile: "read-only",
9688
+ allowedTools: [...READ_ONLY_TOOLS2],
9689
+ maxToolRounds: 10,
9690
+ contextPolicy: "task-only",
9691
+ system: "Act as a read-only explorer. Inspect code, collect evidence, and do not modify files.",
9692
+ source: "builtin"
9693
+ },
9694
+ {
9695
+ name: "worker",
9696
+ description: "Implementation and focused fixes within sub-agent safety limits.",
9697
+ permissionProfile: "workspace-write",
9698
+ maxToolRounds: 15,
9699
+ contextPolicy: "task-only",
9700
+ system: "Act as a focused implementation worker. Keep edits scoped and report changed files.",
9701
+ source: "builtin"
9702
+ },
9703
+ {
9704
+ name: "reviewer",
9705
+ description: "Code review focused on bugs, regressions, and missing tests.",
9706
+ permissionProfile: "read-only",
9707
+ allowedTools: [...READ_ONLY_TOOLS2],
9708
+ maxToolRounds: 12,
9709
+ contextPolicy: "task-only",
9710
+ system: "Act as a code reviewer. Lead with concrete findings, file evidence, and residual risks.",
9711
+ source: "builtin"
9712
+ },
9713
+ {
9714
+ name: "security",
9715
+ description: "Security audit with read-only evidence collection.",
9716
+ permissionProfile: "read-only",
9717
+ allowedTools: [...READ_ONLY_TOOLS2],
9718
+ maxToolRounds: 12,
9719
+ contextPolicy: "task-only",
9720
+ system: "Act as a security reviewer. Look for trust-boundary, injection, filesystem, network, and secret-handling issues.",
9721
+ source: "builtin"
9722
+ },
9723
+ {
9724
+ name: "tester",
9725
+ description: "Test planning and regression analysis without direct shell execution.",
9726
+ permissionProfile: "read-only",
9727
+ allowedTools: [...READ_ONLY_TOOLS2],
9728
+ maxToolRounds: 12,
9729
+ contextPolicy: "task-only",
9730
+ system: "Act as a tester. Inspect tests, identify coverage gaps, and propose precise regression checks.",
9731
+ source: "builtin"
9732
+ }
9733
+ ];
9734
+ function normalizeName(name) {
9735
+ return name.trim().toLowerCase();
9736
+ }
9737
+ function isStringArray(value) {
9738
+ return Array.isArray(value) && value.every((item) => typeof item === "string");
9739
+ }
9740
+ function parseAgentConfig(raw, source, path3) {
9741
+ if (!raw || typeof raw !== "object") return null;
9742
+ const obj = raw;
9743
+ if (typeof obj.name !== "string" || obj.name.trim() === "") return null;
9744
+ const cfg = {
9745
+ name: obj.name.trim(),
9746
+ source,
9747
+ path: path3
9748
+ };
9749
+ if (typeof obj.description === "string") cfg.description = obj.description;
9750
+ if (typeof obj.provider === "string") cfg.provider = obj.provider;
9751
+ if (typeof obj.model === "string") cfg.model = obj.model;
9752
+ if (typeof obj.system === "string") cfg.system = obj.system;
9753
+ if (typeof obj.instructions === "string") cfg.instructions = obj.instructions;
9754
+ if (typeof obj.systemInstructions === "string") cfg.systemInstructions = obj.systemInstructions;
9755
+ if (isStringArray(obj.allowedTools)) cfg.allowedTools = obj.allowedTools;
9756
+ if (isStringArray(obj.blockedTools)) cfg.blockedTools = obj.blockedTools;
9757
+ if (typeof obj.permissionProfile === "string") cfg.permissionProfile = obj.permissionProfile;
9758
+ if (typeof obj.maxToolRounds === "number" && Number.isFinite(obj.maxToolRounds)) cfg.maxToolRounds = obj.maxToolRounds;
9759
+ if (typeof obj.contextPolicy === "string") cfg.contextPolicy = obj.contextPolicy;
9760
+ return cfg;
9761
+ }
9762
+ function loadAgentDir(dir, source) {
9763
+ if (!existsSync15(dir)) return [];
9764
+ const out = [];
9765
+ for (const file of readdirSync7(dir)) {
9766
+ if (!file.endsWith(".json")) continue;
9767
+ const path3 = join10(dir, file);
9768
+ try {
9769
+ const parsed = JSON.parse(readFileSync12(path3, "utf-8"));
9770
+ const cfg = parseAgentConfig(parsed, source, path3);
9771
+ if (cfg) out.push(cfg);
9772
+ } catch {
9773
+ }
9774
+ }
9775
+ return out;
9776
+ }
9777
+ function getAgentDirs(configDir, cwd = process.cwd()) {
9778
+ return {
9779
+ user: join10(configDir ?? join10(homedir6(), CONFIG_DIR_NAME), "agents"),
9780
+ project: join10(cwd, CONFIG_DIR_NAME, "agents")
9781
+ };
9782
+ }
9783
+ function listAgentConfigs(configDir, cwd = process.cwd()) {
9784
+ const dirs = getAgentDirs(configDir, cwd);
9785
+ const byName = /* @__PURE__ */ new Map();
9786
+ for (const cfg of BUILTIN_AGENTS) byName.set(normalizeName(cfg.name), { ...cfg });
9787
+ for (const cfg of loadAgentDir(dirs.user, "user")) byName.set(normalizeName(cfg.name), cfg);
9788
+ for (const cfg of loadAgentDir(dirs.project, "project")) byName.set(normalizeName(cfg.name), cfg);
9789
+ return [...byName.values()].sort((a, b) => a.name.localeCompare(b.name));
9790
+ }
9791
+ function resolveAgentConfig(name, configDir, cwd = process.cwd()) {
9792
+ const target = normalizeName(name);
9793
+ return listAgentConfigs(configDir, cwd).find((cfg) => normalizeName(cfg.name) === target);
9794
+ }
9795
+ function getAgentInstructions(agent) {
9796
+ return agent.system ?? agent.systemInstructions ?? agent.instructions;
9797
+ }
9798
+ function getEffectiveAgentTools(agent) {
9799
+ let allowed = new Set(SUBAGENT_ALLOWED_TOOLS);
9800
+ if (agent.permissionProfile === "read-only") {
9801
+ allowed = new Set([...allowed].filter((name) => READ_ONLY_TOOLS2.has(name)));
9802
+ }
9803
+ if (agent.allowedTools && agent.allowedTools.length > 0) {
9804
+ const requested = new Set(agent.allowedTools);
9805
+ allowed = new Set([...allowed].filter((name) => requested.has(name)));
9806
+ }
9807
+ if (agent.blockedTools && agent.blockedTools.length > 0) {
9808
+ for (const name of agent.blockedTools) allowed.delete(name);
9809
+ }
9810
+ return allowed;
9811
+ }
9812
+ function getEffectiveMaxRounds(requestedMaxRounds, agent) {
9813
+ const agentMax = agent.maxToolRounds && agent.maxToolRounds > 0 ? Math.round(agent.maxToolRounds) : SUBAGENT_MAX_ROUNDS_LIMIT;
9814
+ return Math.min(requestedMaxRounds, agentMax, SUBAGENT_MAX_ROUNDS_LIMIT);
9815
+ }
9816
+
9817
+ // src/agents/agent-runtime.ts
9818
+ var nextId = 1;
9819
+ var preferredAgentName = "worker";
9820
+ var runs = [];
9821
+ var MAX_RUNS = 50;
9822
+ function nowIso() {
9823
+ return (/* @__PURE__ */ new Date()).toISOString();
9824
+ }
9825
+ function startAgentRun(input) {
9826
+ const run = {
9827
+ id: `agent-${nextId++}`,
9828
+ agentName: input.agentName,
9829
+ task: input.task,
9830
+ status: "running",
9831
+ startedAt: nowIso(),
9832
+ toolNames: [],
9833
+ agentIndex: input.agentIndex,
9834
+ stopRequested: false
9835
+ };
9836
+ runs.unshift(run);
9837
+ if (runs.length > MAX_RUNS) runs.splice(MAX_RUNS);
9838
+ return run;
9839
+ }
9840
+ function recordAgentTool(runId, toolName) {
9841
+ const run = runs.find((r) => r.id === runId);
9842
+ if (!run) return;
9843
+ if (!run.toolNames.includes(toolName)) run.toolNames.push(toolName);
9844
+ }
9845
+ function finishAgentRun(runId, status, summary, error) {
9846
+ const run = runs.find((r) => r.id === runId);
9847
+ if (!run) return;
9848
+ run.status = status;
9849
+ run.finishedAt = nowIso();
9850
+ if (summary) run.summary = summary;
9851
+ if (error) run.error = error;
9852
+ }
9853
+ function listAgentRuns() {
9854
+ return runs.map((run) => ({ ...run, toolNames: [...run.toolNames] }));
9855
+ }
9856
+ function requestAgentStop(idOrAll) {
9857
+ const target = idOrAll.trim().toLowerCase();
9858
+ let count = 0;
9859
+ for (const run of runs) {
9860
+ if (run.status !== "running") continue;
9861
+ if (target === "all" || run.id.toLowerCase() === target) {
9862
+ run.stopRequested = true;
9863
+ count++;
9864
+ }
9865
+ }
9866
+ return count;
9867
+ }
9868
+ function isAgentStopRequested(runId) {
9869
+ return runs.find((r) => r.id === runId)?.stopRequested ?? false;
9870
+ }
9871
+ function setPreferredAgentName(name) {
9872
+ preferredAgentName = name.trim() || "worker";
9873
+ }
9874
+ function getPreferredAgentName() {
9875
+ return preferredAgentName;
9876
+ }
9877
+
9666
9878
  // src/tools/builtin/spawn-agent.ts
9667
9879
  var spawnAgentContext = {
9668
9880
  provider: null,
@@ -9677,18 +9889,27 @@ function agentPrefix(agentIndex) {
9677
9889
  return theme.dim(` \u2503${agentIndex} `);
9678
9890
  }
9679
9891
  var SubAgentExecutor = class {
9680
- constructor(registry, agentIndex = null) {
9892
+ constructor(registry, agentIndex = null, runId) {
9681
9893
  this.registry = registry;
9894
+ this.runId = runId;
9682
9895
  this.prefix = agentPrefix(agentIndex);
9683
9896
  }
9684
9897
  round = 0;
9685
9898
  totalRounds = 0;
9686
9899
  prefix = PREFIX;
9900
+ toolNames = [];
9687
9901
  setRoundInfo(current, total) {
9688
9902
  this.round = current;
9689
9903
  this.totalRounds = total;
9690
9904
  }
9691
9905
  async execute(call) {
9906
+ if (this.runId && isAgentStopRequested(this.runId)) {
9907
+ return {
9908
+ callId: call.id,
9909
+ content: "Sub-agent stop requested.",
9910
+ isError: true
9911
+ };
9912
+ }
9692
9913
  const tool = this.registry.get(call.name);
9693
9914
  if (!tool) {
9694
9915
  return {
@@ -9709,6 +9930,8 @@ var SubAgentExecutor = class {
9709
9930
  };
9710
9931
  }
9711
9932
  this.printToolCall(call, dangerLevel);
9933
+ if (!this.toolNames.includes(call.name)) this.toolNames.push(call.name);
9934
+ if (this.runId) recordAgentTool(this.runId, call.name);
9712
9935
  try {
9713
9936
  const rawContent = await runTool(tool, call.arguments, call.name);
9714
9937
  const content = truncateOutput(rawContent, call.name);
@@ -9728,7 +9951,6 @@ var SubAgentExecutor = class {
9728
9951
  }
9729
9952
  return results;
9730
9953
  }
9731
- // ── 带前缀的终端输出 ──
9732
9954
  printPrefixed(text) {
9733
9955
  for (const line of text.split("\n")) {
9734
9956
  console.log(this.prefix + line);
@@ -9769,7 +9991,7 @@ var SubAgentExecutor = class {
9769
9991
  }
9770
9992
  }
9771
9993
  };
9772
- function buildSubAgentSystemPrompt(task, parentSystemPrompt) {
9994
+ function buildSubAgentSystemPrompt(task, agent, parentSystemPrompt) {
9773
9995
  const parts = [];
9774
9996
  if (parentSystemPrompt) {
9775
9997
  parts.push(parentSystemPrompt);
@@ -9777,6 +9999,8 @@ function buildSubAgentSystemPrompt(task, parentSystemPrompt) {
9777
9999
  parts.push(
9778
10000
  `# Sub-Agent Mode
9779
10001
 
10002
+ **Agent Role**: ${agent.name}${agent.description ? ` \u2014 ${agent.description}` : ""}
10003
+
9780
10004
  You are a focused sub-agent delegated by the main agent to complete a specific task.
9781
10005
 
9782
10006
  **Your Task**:
@@ -9790,13 +10014,19 @@ ${task}
9790
10014
  5. Destructive operations (rm -rf etc.) will be automatically blocked
9791
10015
  6. Be efficient, minimize unnecessary tool call rounds`
9792
10016
  );
10017
+ const agentInstructions = getAgentInstructions(agent);
10018
+ if (agentInstructions) {
10019
+ parts.push(`# Agent Instructions
10020
+
10021
+ ${agentInstructions}`);
10022
+ }
9793
10023
  return parts.join("\n\n---\n\n");
9794
10024
  }
9795
- function printSubAgentHeader(task, maxRounds, agentIndex = null) {
10025
+ function printSubAgentHeader(task, maxRounds, agentName, agentIndex = null) {
9796
10026
  const prefix = agentPrefix(agentIndex);
9797
10027
  console.log();
9798
10028
  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"));
9799
- const label = agentIndex !== null ? `\u{1F916} Sub-Agent #${agentIndex} Spawned` : "\u{1F916} Sub-Agent Spawned";
10029
+ const label = agentIndex !== null ? `\u{1F916} Sub-Agent #${agentIndex} Spawned (${agentName})` : `\u{1F916} Sub-Agent Spawned (${agentName})`;
9800
10030
  console.log(prefix + theme.toolCall(label));
9801
10031
  console.log(
9802
10032
  prefix + theme.dim("Task: ") + (task.slice(0, 120) + (task.length > 120 ? "..." : ""))
@@ -9819,58 +10049,110 @@ function printSubAgentFooter(usage, agentIndex = null) {
9819
10049
  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"));
9820
10050
  console.log();
9821
10051
  }
9822
- async function runSubAgent(task, maxRounds, agentIndex, ctx) {
10052
+ function summarizeContent(content) {
10053
+ const flat = content.replace(/\s+/g, " ").trim();
10054
+ return flat.length > 500 ? flat.slice(0, 497) + "..." : flat;
10055
+ }
10056
+ function extractEvidence(content, toolNames) {
10057
+ const evidence = /* @__PURE__ */ new Set();
10058
+ if (toolNames.length > 0) evidence.add(`Tools: ${toolNames.join(", ")}`);
10059
+ const matches = content.match(/(?:[\w.-]+[\\/])+[\w.-]+\.(?:ts|tsx|js|jsx|json|md|css|html|py|go|rs|yml|yaml)/g) ?? [];
10060
+ for (const match of matches.slice(0, 8)) evidence.add(match);
10061
+ return [...evidence].slice(0, 10);
10062
+ }
10063
+ function formatAgentResult(input) {
10064
+ const lines = [input.title, "", `**Agent**: ${input.agentName}`];
10065
+ if (input.task) lines.push(`**Task**: ${input.task}`);
10066
+ lines.push("", "### Summary", input.summary || "(no summary)", "", "### Key Evidence");
10067
+ if (input.evidence.length > 0) {
10068
+ for (const item of input.evidence) lines.push(`- ${item}`);
10069
+ } else {
10070
+ lines.push("- No structured evidence extracted.");
10071
+ }
10072
+ lines.push("", "### Tools Used");
10073
+ lines.push(input.toolNames.length > 0 ? input.toolNames.join(", ") : "No tools used.");
10074
+ lines.push("", "### Full Result", input.content, "");
10075
+ lines.push(`*Tokens: ${input.usage.inputTokens} in / ${input.usage.outputTokens} out*`);
10076
+ return lines;
10077
+ }
10078
+ async function runSubAgent(task, maxRounds, agentIndex, ctx, agent) {
9823
10079
  if (!ctx.provider) {
9824
10080
  throw new ToolError("spawn_agent", "provider not initialized (context not injected)");
9825
10081
  }
10082
+ const providerFromConfig = agent.provider && ctx.providers?.has(agent.provider) ? ctx.providers.get(agent.provider) : void 0;
10083
+ const provider = providerFromConfig ?? ctx.provider;
10084
+ const model = agent.model ?? (providerFromConfig?.info?.defaultModel ?? ctx.model);
10085
+ const run = startAgentRun({
10086
+ agentName: agent.name,
10087
+ task,
10088
+ agentIndex: agentIndex ?? void 0
10089
+ });
10090
+ const effectiveTools = getEffectiveAgentTools(agent);
9826
10091
  const subRegistry = new ToolRegistry();
9827
10092
  for (const tool of subRegistry.listAll()) {
9828
- if (!SUBAGENT_ALLOWED_TOOLS.has(tool.definition.name)) {
10093
+ if (!SUBAGENT_ALLOWED_TOOLS.has(tool.definition.name) || !effectiveTools.has(tool.definition.name)) {
9829
10094
  subRegistry.unregister(tool.definition.name);
9830
10095
  }
9831
10096
  }
9832
- const subExecutor = new SubAgentExecutor(subRegistry, agentIndex);
10097
+ const subExecutor = new SubAgentExecutor(subRegistry, agentIndex, run.id);
9833
10098
  const subMessages = [
9834
10099
  { role: "user", content: task, timestamp: /* @__PURE__ */ new Date() }
9835
10100
  ];
9836
- const subSystemPrompt = buildSubAgentSystemPrompt(task, ctx.systemPrompt);
10101
+ const subSystemPrompt = buildSubAgentSystemPrompt(task, agent, ctx.systemPrompt);
9837
10102
  const toolDefs = subRegistry.getDefinitions();
9838
- printSubAgentHeader(task, maxRounds, agentIndex);
9839
- const loop = await runLeanAgentLoop({
9840
- provider: ctx.provider,
9841
- messages: subMessages,
9842
- model: ctx.model,
9843
- maxRounds,
9844
- chatParams: {
9845
- temperature: ctx.modelParams.temperature,
9846
- maxTokens: ctx.modelParams.maxTokens,
9847
- timeout: ctx.modelParams.timeout,
9848
- thinking: ctx.modelParams.thinking
9849
- },
9850
- executeTools: (calls) => subExecutor.executeAll(calls),
9851
- systemPromptForRound: () => subSystemPrompt,
9852
- toolDefsForRound: () => toolDefs,
9853
- onRoundStart: (round) => {
9854
- subExecutor.setRoundInfo(round + 1, maxRounds);
9855
- if (ctx.configManager) {
9856
- googleSearchContext.configManager = ctx.configManager;
9857
- }
9858
- },
9859
- onRoundsExhausted: () => `(Sub-agent reached maximum rounds (${maxRounds}) without producing a final response)`,
9860
- onError: (errMsg) => {
9861
- process.stderr.write(`
10103
+ printSubAgentHeader(task, maxRounds, agent.name, agentIndex);
10104
+ try {
10105
+ const loop = await runLeanAgentLoop({
10106
+ provider,
10107
+ messages: subMessages,
10108
+ model,
10109
+ maxRounds,
10110
+ chatParams: {
10111
+ temperature: ctx.modelParams.temperature,
10112
+ maxTokens: ctx.modelParams.maxTokens,
10113
+ timeout: ctx.modelParams.timeout,
10114
+ thinking: ctx.modelParams.thinking
10115
+ },
10116
+ executeTools: (calls) => subExecutor.executeAll(calls),
10117
+ systemPromptForRound: () => subSystemPrompt,
10118
+ toolDefsForRound: () => toolDefs,
10119
+ onRoundStart: (round) => {
10120
+ subExecutor.setRoundInfo(round + 1, maxRounds);
10121
+ if (ctx.configManager) {
10122
+ googleSearchContext.configManager = ctx.configManager;
10123
+ }
10124
+ },
10125
+ onRoundsExhausted: () => `(Sub-agent reached maximum rounds (${maxRounds}) without producing a final response)`,
10126
+ onError: (errMsg) => {
10127
+ process.stderr.write(`
9862
10128
  [spawn_agent] Error in sub-agent loop: ${errMsg}
9863
10129
  `);
9864
- return `(Sub-agent error: ${errMsg})`;
9865
- }
9866
- });
9867
- printSubAgentFooter(loop.usage, agentIndex);
9868
- return { content: loop.content, usage: loop.usage };
10130
+ return `(Sub-agent error: ${errMsg})`;
10131
+ }
10132
+ });
10133
+ printSubAgentFooter(loop.usage, agentIndex);
10134
+ const summary = summarizeContent(loop.content);
10135
+ const evidence = extractEvidence(loop.content, subExecutor.toolNames);
10136
+ const stopped = isAgentStopRequested(run.id);
10137
+ finishAgentRun(run.id, stopped ? "stopped" : "ok", summary);
10138
+ return {
10139
+ content: loop.content,
10140
+ usage: loop.usage,
10141
+ toolNames: subExecutor.toolNames,
10142
+ summary,
10143
+ evidence,
10144
+ agentName: agent.name
10145
+ };
10146
+ } catch (err) {
10147
+ const message = err instanceof Error ? err.message : String(err);
10148
+ finishAgentRun(run.id, "error", void 0, message);
10149
+ throw err;
10150
+ }
9869
10151
  }
9870
10152
  var spawnAgentTool = {
9871
10153
  definition: {
9872
10154
  name: "spawn_agent",
9873
- 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.",
10155
+ 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.",
9874
10156
  parameters: {
9875
10157
  task: {
9876
10158
  type: "string",
@@ -9888,7 +10170,12 @@ var spawnAgentTool = {
9888
10170
  },
9889
10171
  max_rounds: {
9890
10172
  type: "number",
9891
- description: "Max tool-call rounds per sub-agent (1-15, default 10). Applied to each sub-agent in parallel mode.",
10173
+ description: "Max tool-call rounds per sub-agent (1-30, default 15). Agent config can only reduce this value.",
10174
+ required: false
10175
+ },
10176
+ agent: {
10177
+ type: "string",
10178
+ description: "Agent role name from built-ins or ~/.aicli/agents / .aicli/agents JSON configs. Built-ins: explorer, worker, reviewer, security, tester.",
9892
10179
  required: false
9893
10180
  }
9894
10181
  },
@@ -9908,10 +10195,17 @@ var spawnAgentTool = {
9908
10195
  Math.max(Math.round(rawMaxRounds), 1),
9909
10196
  SUBAGENT_MAX_ROUNDS_LIMIT
9910
10197
  );
10198
+ const requestedAgentName = typeof args["agent"] === "string" && args["agent"].trim() ? args["agent"].trim() : getPreferredAgentName();
9911
10199
  const ctx = spawnAgentContext;
9912
10200
  if (!ctx.provider) {
9913
10201
  throw new ToolError("spawn_agent", "provider not initialized (context not injected)");
9914
10202
  }
10203
+ const agent = resolveAgentConfig(requestedAgentName, ctx.configManager?.getConfigDir());
10204
+ if (!agent) {
10205
+ const available = listAgentConfigs(ctx.configManager?.getConfigDir()).map((cfg) => cfg.name).join(", ");
10206
+ throw new ToolError("spawn_agent", `unknown agent "${requestedAgentName}". Available agents: ${available}`);
10207
+ }
10208
+ const effectiveMaxRounds = getEffectiveMaxRounds(maxRounds, agent);
9915
10209
  const hookConfig = ctx.configManager?.get("hooks") ?? void 0;
9916
10210
  const hookConfigDir = ctx.configManager?.getConfigDir() ?? process.cwd();
9917
10211
  const taskCount = singleTask ? 1 : tasksArr.length;
@@ -9919,7 +10213,8 @@ var spawnAgentTool = {
9919
10213
  task: singleTask || void 0,
9920
10214
  tasks: tasksArr.length > 0 ? tasksArr : void 0,
9921
10215
  taskCount,
9922
- maxRounds
10216
+ maxRounds: effectiveMaxRounds,
10217
+ agent: agent.name
9923
10218
  };
9924
10219
  const startDecisions = runLifecycleHooks(hookConfig, "SubagentStart", hookPayload, hookConfigDir);
9925
10220
  const startDeny = startDecisions.find((d) => d.action === "deny");
@@ -9931,32 +10226,37 @@ var spawnAgentTool = {
9931
10226
  subAgentGuard.active = true;
9932
10227
  try {
9933
10228
  if (singleTask) {
9934
- const { content, usage } = await runSubAgent(singleTask, maxRounds, null, ctx);
10229
+ const result = await runSubAgent(singleTask, effectiveMaxRounds, null, ctx, agent);
9935
10230
  subagentStatus = "ok";
9936
- return [
9937
- "## Sub-Agent Result",
9938
- "",
9939
- content,
9940
- "",
9941
- "---",
9942
- `Token usage: ${usage.inputTokens} input, ${usage.outputTokens} output`
9943
- ].join("\n");
10231
+ return formatAgentResult({
10232
+ title: "## Sub-Agent Result",
10233
+ content: result.content,
10234
+ usage: result.usage,
10235
+ toolNames: result.toolNames,
10236
+ summary: result.summary,
10237
+ evidence: result.evidence,
10238
+ agentName: result.agentName
10239
+ }).join("\n");
9944
10240
  }
9945
10241
  console.log();
9946
- console.log(theme.toolCall(`\u{1F916} Spawning ${tasksArr.length} sub-agents in parallel...`));
10242
+ console.log(theme.toolCall(`\u{1F916} Spawning ${tasksArr.length} sub-agents in parallel as ${agent.name}...`));
9947
10243
  const results = await Promise.all(
9948
- tasksArr.map((t, i) => runSubAgent(t, maxRounds, i + 1, ctx))
10244
+ tasksArr.map((t, i) => runSubAgent(t, effectiveMaxRounds, i + 1, ctx, agent))
9949
10245
  );
9950
10246
  const totalIn = results.reduce((s, r) => s + r.usage.inputTokens, 0);
9951
10247
  const totalOut = results.reduce((s, r) => s + r.usage.outputTokens, 0);
9952
10248
  const lines = [`## Parallel Sub-Agent Results (${results.length} agents)`, ""];
9953
10249
  results.forEach((r, i) => {
9954
- lines.push(`### Sub-Agent #${i + 1}`);
9955
- lines.push(`**Task**: ${tasksArr[i].slice(0, 200)}${tasksArr[i].length > 200 ? "..." : ""}`);
9956
- lines.push("");
9957
- lines.push(r.content);
9958
- lines.push("");
9959
- lines.push(`*Tokens: ${r.usage.inputTokens} in / ${r.usage.outputTokens} out*`);
10250
+ lines.push(...formatAgentResult({
10251
+ title: `### Sub-Agent #${i + 1}`,
10252
+ task: `${tasksArr[i].slice(0, 200)}${tasksArr[i].length > 200 ? "..." : ""}`,
10253
+ content: r.content,
10254
+ usage: r.usage,
10255
+ toolNames: r.toolNames,
10256
+ summary: r.summary,
10257
+ evidence: r.evidence,
10258
+ agentName: r.agentName
10259
+ }));
9960
10260
  lines.push("");
9961
10261
  });
9962
10262
  lines.push("---");
@@ -10187,14 +10487,14 @@ var taskStopTool = {
10187
10487
 
10188
10488
  // src/tools/builtin/git-tools.ts
10189
10489
  import { execFileSync as execFileSync2 } from "child_process";
10190
- import { existsSync as existsSync15 } from "fs";
10191
- import { join as join10 } from "path";
10490
+ import { existsSync as existsSync16 } from "fs";
10491
+ import { join as join11 } from "path";
10192
10492
  function assertGitRepo(cwd) {
10193
10493
  let dir = cwd;
10194
10494
  const root = dir.split(/[\\/]/)[0] + (dir.includes("\\") ? "\\" : "/");
10195
10495
  while (dir && dir !== root) {
10196
- if (existsSync15(join10(dir, ".git"))) return;
10197
- const parent = join10(dir, "..");
10496
+ if (existsSync16(join11(dir, ".git"))) return;
10497
+ const parent = join11(dir, "..");
10198
10498
  if (parent === dir) break;
10199
10499
  dir = parent;
10200
10500
  }
@@ -10459,7 +10759,7 @@ ${commitOutput.trim()}`;
10459
10759
  };
10460
10760
 
10461
10761
  // src/tools/builtin/notebook-edit.ts
10462
- import { readFileSync as readFileSync12, existsSync as existsSync16 } from "fs";
10762
+ import { readFileSync as readFileSync13, existsSync as existsSync17 } from "fs";
10463
10763
  import { writeFile } from "fs/promises";
10464
10764
  import { resolve as resolve6, extname as extname2 } from "path";
10465
10765
  var notebookEditTool = {
@@ -10514,10 +10814,10 @@ var notebookEditTool = {
10514
10814
  if (extname2(absPath).toLowerCase() !== ".ipynb") {
10515
10815
  throw new ToolError("notebook_edit", "path must point to a .ipynb file");
10516
10816
  }
10517
- if (!existsSync16(absPath)) {
10817
+ if (!existsSync17(absPath)) {
10518
10818
  throw new ToolError("notebook_edit", `Notebook not found: ${filePath}`);
10519
10819
  }
10520
- const raw = readFileSync12(absPath, "utf-8");
10820
+ const raw = readFileSync13(absPath, "utf-8");
10521
10821
  const nb = parseNotebook(raw);
10522
10822
  const cellIdx0 = cellIndexRaw - 1;
10523
10823
  undoStack.push(absPath, `notebook_edit (${action}): ${filePath}`);
@@ -10934,8 +11234,8 @@ function estimateToolDefinitionTokens(def) {
10934
11234
 
10935
11235
  // src/tools/registry.ts
10936
11236
  import { pathToFileURL } from "url";
10937
- import { existsSync as existsSync17, mkdirSync as mkdirSync8, readdirSync as readdirSync7 } from "fs";
10938
- import { join as join11 } from "path";
11237
+ import { existsSync as existsSync18, mkdirSync as mkdirSync8, readdirSync as readdirSync8 } from "fs";
11238
+ import { join as join12 } from "path";
10939
11239
  var ToolRegistry = class {
10940
11240
  tools = /* @__PURE__ */ new Map();
10941
11241
  pluginToolNames = /* @__PURE__ */ new Set();
@@ -11096,7 +11396,7 @@ var ToolRegistry = class {
11096
11396
  * Returns the number of successfully loaded plugins.
11097
11397
  */
11098
11398
  async loadPlugins(pluginsDir, allowPlugins = false) {
11099
- if (!existsSync17(pluginsDir)) {
11399
+ if (!existsSync18(pluginsDir)) {
11100
11400
  try {
11101
11401
  mkdirSync8(pluginsDir, { recursive: true });
11102
11402
  } catch {
@@ -11105,7 +11405,7 @@ var ToolRegistry = class {
11105
11405
  }
11106
11406
  let files;
11107
11407
  try {
11108
- files = readdirSync7(pluginsDir).filter((f) => f.endsWith(".js"));
11408
+ files = readdirSync8(pluginsDir).filter((f) => f.endsWith(".js"));
11109
11409
  } catch {
11110
11410
  return 0;
11111
11411
  }
@@ -11122,12 +11422,12 @@ var ToolRegistry = class {
11122
11422
  process.stderr.write(
11123
11423
  `
11124
11424
  [plugins] \u26A0 Loading ${files.length} plugin(s) with FULL system privileges:
11125
- ` + files.map((f) => ` + ${join11(pluginsDir, f)}`).join("\n") + "\n\n"
11425
+ ` + files.map((f) => ` + ${join12(pluginsDir, f)}`).join("\n") + "\n\n"
11126
11426
  );
11127
11427
  let loaded = 0;
11128
11428
  for (const file of files) {
11129
11429
  try {
11130
- const fileUrl = pathToFileURL(join11(pluginsDir, file)).href;
11430
+ const fileUrl = pathToFileURL(join12(pluginsDir, file)).href;
11131
11431
  const mod = await import(fileUrl);
11132
11432
  const tool = mod.tool ?? mod.default?.tool ?? mod.default;
11133
11433
  if (!tool || typeof tool.execute !== "function" || !tool.definition?.name) {
@@ -11673,11 +11973,11 @@ var McpManager = class {
11673
11973
  };
11674
11974
 
11675
11975
  // src/skills/manager.ts
11676
- import { existsSync as existsSync18, readdirSync as readdirSync8, mkdirSync as mkdirSync9, statSync as statSync7 } from "fs";
11677
- import { join as join12 } from "path";
11976
+ import { existsSync as existsSync19, readdirSync as readdirSync9, mkdirSync as mkdirSync9, statSync as statSync7 } from "fs";
11977
+ import { join as join13 } from "path";
11678
11978
 
11679
11979
  // src/skills/types.ts
11680
- import { readFileSync as readFileSync13 } from "fs";
11980
+ import { readFileSync as readFileSync14 } from "fs";
11681
11981
  import { basename as basename5 } from "path";
11682
11982
  function parseSimpleYaml(yaml) {
11683
11983
  const result = {};
@@ -11699,7 +11999,7 @@ function parseYamlArray(value) {
11699
11999
  function parseSkillFile(filePath) {
11700
12000
  let raw;
11701
12001
  try {
11702
- raw = readFileSync13(filePath, "utf-8");
12002
+ raw = readFileSync14(filePath, "utf-8");
11703
12003
  } catch {
11704
12004
  return null;
11705
12005
  }
@@ -11742,7 +12042,7 @@ var SkillManager = class {
11742
12042
  /** 发现并加载 skillsDir 下所有 .md 文件,返回加载数量 */
11743
12043
  loadSkills() {
11744
12044
  this.skills.clear();
11745
- if (!existsSync18(this.skillsDir)) {
12045
+ if (!existsSync19(this.skillsDir)) {
11746
12046
  try {
11747
12047
  mkdirSync9(this.skillsDir, { recursive: true });
11748
12048
  } catch {
@@ -11751,20 +12051,20 @@ var SkillManager = class {
11751
12051
  }
11752
12052
  let entries;
11753
12053
  try {
11754
- entries = readdirSync8(this.skillsDir);
12054
+ entries = readdirSync9(this.skillsDir);
11755
12055
  } catch {
11756
12056
  return 0;
11757
12057
  }
11758
12058
  for (const entry of entries) {
11759
12059
  let filePath;
11760
- const fullPath = join12(this.skillsDir, entry);
12060
+ const fullPath = join13(this.skillsDir, entry);
11761
12061
  if (entry.endsWith(".md")) {
11762
12062
  filePath = fullPath;
11763
12063
  } else {
11764
12064
  try {
11765
12065
  if (statSync7(fullPath).isDirectory()) {
11766
- const skillMd = join12(fullPath, "SKILL.md");
11767
- if (existsSync18(skillMd)) {
12066
+ const skillMd = join13(fullPath, "SKILL.md");
12067
+ if (existsSync19(skillMd)) {
11768
12068
  filePath = skillMd;
11769
12069
  } else {
11770
12070
  continue;
@@ -11829,7 +12129,7 @@ var SkillManager = class {
11829
12129
  // src/web/tool-executor-web.ts
11830
12130
  import { randomUUID as randomUUID2 } from "crypto";
11831
12131
  import { tmpdir as tmpdir2 } from "os";
11832
- import { existsSync as existsSync19, readFileSync as readFileSync14 } from "fs";
12132
+ import { existsSync as existsSync20, readFileSync as readFileSync15 } from "fs";
11833
12133
  var ToolExecutorWeb = class _ToolExecutorWeb {
11834
12134
  constructor(registry, ws) {
11835
12135
  this.registry = registry;
@@ -11968,9 +12268,9 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
11968
12268
  if (call.name === "write_file") {
11969
12269
  const filePath = String(call.arguments["path"] ?? "");
11970
12270
  const newContent = String(call.arguments["content"] ?? "");
11971
- if (filePath && existsSync19(filePath)) {
12271
+ if (filePath && existsSync20(filePath)) {
11972
12272
  try {
11973
- const old = readFileSync14(filePath, "utf-8");
12273
+ const old = readFileSync15(filePath, "utf-8");
11974
12274
  return renderDiff(old, newContent, { filePath });
11975
12275
  } catch {
11976
12276
  }
@@ -12273,20 +12573,20 @@ var ToolExecutorWeb = class _ToolExecutorWeb {
12273
12573
  };
12274
12574
 
12275
12575
  // src/core/system-prompt-builder.ts
12276
- import { existsSync as existsSync21, readFileSync as readFileSync16 } from "fs";
12277
- import { join as join14 } from "path";
12576
+ import { existsSync as existsSync22, readFileSync as readFileSync17 } from "fs";
12577
+ import { join as join15 } from "path";
12278
12578
 
12279
12579
  // src/repl/dev-state.ts
12280
- import { existsSync as existsSync20, readFileSync as readFileSync15, unlinkSync as unlinkSync4, mkdirSync as mkdirSync10 } from "fs";
12281
- import { join as join13 } from "path";
12282
- import { homedir as homedir6 } from "os";
12580
+ import { existsSync as existsSync21, readFileSync as readFileSync16, unlinkSync as unlinkSync4, mkdirSync as mkdirSync10 } from "fs";
12581
+ import { join as join14 } from "path";
12582
+ import { homedir as homedir7 } from "os";
12283
12583
  function getDevStatePath() {
12284
- return join13(homedir6(), CONFIG_DIR_NAME, DEV_STATE_FILE_NAME);
12584
+ return join14(homedir7(), CONFIG_DIR_NAME, DEV_STATE_FILE_NAME);
12285
12585
  }
12286
12586
  function loadDevState() {
12287
12587
  const path3 = getDevStatePath();
12288
- if (!existsSync20(path3)) return null;
12289
- const content = readFileSync15(path3, "utf-8").trim();
12588
+ if (!existsSync21(path3)) return null;
12589
+ const content = readFileSync16(path3, "utf-8").trim();
12290
12590
  return content || null;
12291
12591
  }
12292
12592
 
@@ -12344,9 +12644,9 @@ ${ctx.activeSkill.content}`);
12344
12644
  return { stable: stableParts.join("\n\n---\n\n"), volatile };
12345
12645
  }
12346
12646
  function loadMemoryContent(configDir) {
12347
- const memoryPath = join14(configDir, MEMORY_FILE_NAME);
12348
- if (!existsSync21(memoryPath)) return null;
12349
- let content = readFileSync16(memoryPath, "utf-8").trim();
12647
+ const memoryPath = join15(configDir, MEMORY_FILE_NAME);
12648
+ if (!existsSync22(memoryPath)) return null;
12649
+ let content = readFileSync17(memoryPath, "utf-8").trim();
12350
12650
  if (!content) return null;
12351
12651
  if (content.length > MEMORY_MAX_CHARS) {
12352
12652
  content = content.slice(-MEMORY_MAX_CHARS);
@@ -12555,8 +12855,8 @@ function autoTrimSessionIfNeeded(session, sizeLimit = SESSION_SIZE_LIMIT) {
12555
12855
  }
12556
12856
 
12557
12857
  // src/core/context-files.ts
12558
- import { existsSync as existsSync22, readFileSync as readFileSync17 } from "fs";
12559
- import { join as join15, relative as relative3, resolve as resolve7 } from "path";
12858
+ import { existsSync as existsSync23, readFileSync as readFileSync18 } from "fs";
12859
+ import { join as join16, relative as relative3, resolve as resolve7 } from "path";
12560
12860
  function uniqueNonEmpty(values) {
12561
12861
  const seen = /* @__PURE__ */ new Set();
12562
12862
  const out = [];
@@ -12586,7 +12886,7 @@ function readContextFile(level, filePath, cwd, maxBytes) {
12586
12886
  const name = filePath.split(/[\\/]/).pop() ?? filePath;
12587
12887
  const shown = displayPath(filePath, cwd);
12588
12888
  try {
12589
- const raw = readFileSync17(filePath);
12889
+ const raw = readFileSync18(filePath);
12590
12890
  const byteCount = raw.byteLength;
12591
12891
  if (byteCount === 0) {
12592
12892
  return {
@@ -12633,8 +12933,8 @@ function readContextFile(level, filePath, cwd, maxBytes) {
12633
12933
  function findFirstContextFile(level, dir, cwd, candidates, maxBytes) {
12634
12934
  const skipped = [];
12635
12935
  for (const candidate of candidates) {
12636
- const filePath = join15(dir, candidate);
12637
- if (!existsSync22(filePath)) continue;
12936
+ const filePath = join16(dir, candidate);
12937
+ if (!existsSync23(filePath)) continue;
12638
12938
  const result = readContextFile(level, filePath, cwd, maxBytes);
12639
12939
  if (result.skipped) skipped.push(result.skipped);
12640
12940
  if (result.layer) return { layer: result.layer, skipped };
@@ -12661,7 +12961,7 @@ function loadContextFiles(options) {
12661
12961
  });
12662
12962
  return { layers: [], mergedContent: "", skipped, totalChars: 0, totalBytes: 0, maxBytes, candidates };
12663
12963
  }
12664
- if (!existsSync22(filePath)) {
12964
+ if (!existsSync23(filePath)) {
12665
12965
  skipped.push({
12666
12966
  level: "single",
12667
12967
  filePath,
@@ -12712,13 +13012,13 @@ function loadContextFiles(options) {
12712
13012
  }
12713
13013
 
12714
13014
  // src/web/session-handler.ts
12715
- import { existsSync as existsSync25, readFileSync as readFileSync19, writeFileSync as writeFileSync2, mkdirSync as mkdirSync11, readdirSync as readdirSync10, statSync as statSync9, createWriteStream } from "fs";
12716
- import { join as join19, resolve as resolve8, dirname as dirname5 } from "path";
13015
+ import { existsSync as existsSync26, readFileSync as readFileSync20, writeFileSync as writeFileSync2, mkdirSync as mkdirSync11, readdirSync as readdirSync11, statSync as statSync9, createWriteStream } from "fs";
13016
+ import { join as join20, resolve as resolve8, dirname as dirname5 } from "path";
12717
13017
 
12718
13018
  // src/tools/git-context.ts
12719
13019
  import { execSync as execSync2 } from "child_process";
12720
- import { existsSync as existsSync23 } from "fs";
12721
- import { join as join16 } from "path";
13020
+ import { existsSync as existsSync24 } from "fs";
13021
+ import { join as join17 } from "path";
12722
13022
  function runGit2(cmd, cwd) {
12723
13023
  try {
12724
13024
  return execSync2(`git ${cmd}`, {
@@ -12735,7 +13035,7 @@ function getGitRoot(cwd = process.cwd()) {
12735
13035
  return runGit2("rev-parse --show-toplevel", cwd);
12736
13036
  }
12737
13037
  function getGitContext(cwd = process.cwd()) {
12738
- if (!existsSync23(join16(cwd, ".git"))) {
13038
+ if (!existsSync24(join17(cwd, ".git"))) {
12739
13039
  const result = runGit2("rev-parse --git-dir", cwd);
12740
13040
  if (!result) return null;
12741
13041
  }
@@ -12864,8 +13164,8 @@ If no security issues found, state "\u2705 No security vulnerabilities detected"
12864
13164
  }
12865
13165
 
12866
13166
  // src/repl/commands/project-init.ts
12867
- import { existsSync as existsSync24, readFileSync as readFileSync18, readdirSync as readdirSync9, statSync as statSync8 } from "fs";
12868
- import { join as join17 } from "path";
13167
+ import { existsSync as existsSync25, readFileSync as readFileSync19, readdirSync as readdirSync10, statSync as statSync8 } from "fs";
13168
+ import { join as join18 } from "path";
12869
13169
  var SCAN_SKIP_DIRS = /* @__PURE__ */ new Set([
12870
13170
  "node_modules",
12871
13171
  ".git",
@@ -12901,7 +13201,7 @@ function scanDirTree(dir, maxDepth = 2, maxEntries = 80) {
12901
13201
  if (depth > maxDepth || count >= maxEntries) return;
12902
13202
  let entries;
12903
13203
  try {
12904
- entries = readdirSync9(d);
13204
+ entries = readdirSync10(d);
12905
13205
  } catch {
12906
13206
  return;
12907
13207
  }
@@ -12909,11 +13209,11 @@ function scanDirTree(dir, maxDepth = 2, maxEntries = 80) {
12909
13209
  const sorted = filtered.sort((a, b) => {
12910
13210
  let aIsDir = false, bIsDir = false;
12911
13211
  try {
12912
- aIsDir = statSync8(join17(d, a)).isDirectory();
13212
+ aIsDir = statSync8(join18(d, a)).isDirectory();
12913
13213
  } catch {
12914
13214
  }
12915
13215
  try {
12916
- bIsDir = statSync8(join17(d, b)).isDirectory();
13216
+ bIsDir = statSync8(join18(d, b)).isDirectory();
12917
13217
  } catch {
12918
13218
  }
12919
13219
  if (aIsDir !== bIsDir) return aIsDir ? -1 : 1;
@@ -12921,7 +13221,7 @@ function scanDirTree(dir, maxDepth = 2, maxEntries = 80) {
12921
13221
  });
12922
13222
  for (let i = 0; i < sorted.length && count < maxEntries; i++) {
12923
13223
  const name = sorted[i];
12924
- const fullPath = join17(d, name);
13224
+ const fullPath = join18(d, name);
12925
13225
  const isLast = i === sorted.length - 1;
12926
13226
  const connector = isLast ? "+-- " : "|-- ";
12927
13227
  let isDir;
@@ -12948,7 +13248,7 @@ function scanProject(cwd) {
12948
13248
  configFiles: [],
12949
13249
  directoryStructure: ""
12950
13250
  };
12951
- const check = (file) => existsSync24(join17(cwd, file));
13251
+ const check = (file) => existsSync25(join18(cwd, file));
12952
13252
  const configCandidates = [
12953
13253
  "package.json",
12954
13254
  "tsconfig.json",
@@ -12975,7 +13275,7 @@ function scanProject(cwd) {
12975
13275
  info.type = "node";
12976
13276
  info.language = check("tsconfig.json") ? "TypeScript" : "JavaScript";
12977
13277
  try {
12978
- const pkg = JSON.parse(readFileSync18(join17(cwd, "package.json"), "utf-8"));
13278
+ const pkg = JSON.parse(readFileSync19(join18(cwd, "package.json"), "utf-8"));
12979
13279
  const scripts = pkg.scripts ?? {};
12980
13280
  info.buildCommand = scripts.build ? "npm run build" : void 0;
12981
13281
  info.testCommand = scripts.test ? "npm test" : void 0;
@@ -13635,7 +13935,7 @@ function resolveRoleProviders(roles, lookup, defaultProvider, defaultModel, avai
13635
13935
  }
13636
13936
 
13637
13937
  // src/hub/persist.ts
13638
- import { join as join18 } from "path";
13938
+ import { join as join19 } from "path";
13639
13939
  function discussionToMessages(state2) {
13640
13940
  const out = [];
13641
13941
  const t0 = state2.messages[0]?.timestamp ?? /* @__PURE__ */ new Date();
@@ -13673,7 +13973,7 @@ async function persistDiscussion(state2, config, defaultProvider, defaultModel)
13673
13973
  session.title = `[Hub] ${state2.topic.slice(0, 48)}`.replace(/\n/g, " ");
13674
13974
  session.titleAiGenerated = true;
13675
13975
  await sm.save();
13676
- return { id: session.id, path: join18(config.getHistoryDir(), `${session.id}.json`) };
13976
+ return { id: session.id, path: join19(config.getHistoryDir(), `${session.id}.json`) };
13677
13977
  }
13678
13978
 
13679
13979
  // src/web/session-handler.ts
@@ -13833,7 +14133,19 @@ var SessionHandler = class {
13833
14133
  costUsd,
13834
14134
  providers: providerList,
13835
14135
  branches,
13836
- activeBranchId: sess?.activeBranchId ?? "main"
14136
+ activeBranchId: sess?.activeBranchId ?? "main",
14137
+ agents: listAgentRuns().slice(0, 20).map((run) => ({
14138
+ id: run.id,
14139
+ agentName: run.agentName,
14140
+ task: run.task,
14141
+ status: run.status,
14142
+ startedAt: run.startedAt,
14143
+ finishedAt: run.finishedAt,
14144
+ summary: run.summary,
14145
+ error: run.error,
14146
+ toolNames: run.toolNames,
14147
+ agentIndex: run.agentIndex
14148
+ }))
13837
14149
  });
13838
14150
  }
13839
14151
  async handleMessage(raw) {
@@ -14310,6 +14622,7 @@ Details: ${errMsg.split("\n")[0]}
14310
14622
  ${systemPromptVolatile}` : systemPrompt;
14311
14623
  spawnAgentContext.modelParams = modelParams;
14312
14624
  spawnAgentContext.configManager = this.config;
14625
+ spawnAgentContext.providers = this.providers;
14313
14626
  ToolExecutor.currentMessageIndex = this.sessions.current?.messages.length ?? 0;
14314
14627
  return this.toolExecutor.executeAll(toolCalls);
14315
14628
  },
@@ -14904,6 +15217,7 @@ Tokens: in=${this.sessionTokenUsage.inputTokens} out=${this.sessionTokenUsage.ou
14904
15217
  " /export [md|json] \u2014 Export conversation",
14905
15218
  " /skill [name|off|list|reload] \u2014 Manage agent skills",
14906
15219
  " /memory [show|add|clear] \u2014 Persistent memory management",
15220
+ " /agent list|switch|stop|summary \u2014 Manage named sub-agents",
14907
15221
  " /yolo [on|off] \u2014 Toggle auto-approve (skip confirmations)",
14908
15222
  " /search <keyword> \u2014 Search across all session histories",
14909
15223
  " /undo [list|<n>] \u2014 Undo file operations",
@@ -15036,6 +15350,66 @@ ${activated.meta.description || ""}` });
15036
15350
  }
15037
15351
  break;
15038
15352
  }
15353
+ case "agent": {
15354
+ const sub = args[0]?.toLowerCase() ?? "summary";
15355
+ if (sub === "list") {
15356
+ const agents = listAgentConfigs(this.config.getConfigDir());
15357
+ const lines = [`Agents (${agents.length})`, ""];
15358
+ for (const agent of agents) {
15359
+ const active = agent.name === getPreferredAgentName() ? " *" : " ";
15360
+ const model = [agent.provider, agent.model].filter(Boolean).join("/") || "inherit";
15361
+ lines.push(`${active} ${agent.name} [${agent.source ?? "builtin"}] \u2014 ${agent.description ?? ""}`);
15362
+ lines.push(` model: ${model} \xB7 permission: ${agent.permissionProfile ?? "workspace-write"} \xB7 maxRounds: ${agent.maxToolRounds ?? "inherit"}`);
15363
+ }
15364
+ lines.push("", "Config dirs: ~/.aicli/agents/ and .aicli/agents/");
15365
+ this.send({ type: "info", message: lines.join("\n") });
15366
+ break;
15367
+ }
15368
+ if (sub === "switch") {
15369
+ const name2 = args[1];
15370
+ if (!name2) {
15371
+ this.send({ type: "error", message: "Usage: /agent switch <name>" });
15372
+ break;
15373
+ }
15374
+ const agent = resolveAgentConfig(name2, this.config.getConfigDir());
15375
+ if (!agent) {
15376
+ const available = listAgentConfigs(this.config.getConfigDir()).map((a) => a.name).join(", ");
15377
+ this.send({ type: "error", message: `Unknown agent: ${name2}
15378
+ Available: ${available}` });
15379
+ break;
15380
+ }
15381
+ setPreferredAgentName(agent.name);
15382
+ this.send({ type: "info", message: `Preferred spawn_agent role: ${agent.name}` });
15383
+ this.sendStatus();
15384
+ break;
15385
+ }
15386
+ if (sub === "stop") {
15387
+ const target = args[1] ?? "all";
15388
+ const count = requestAgentStop(target);
15389
+ this.send({ type: "info", message: count > 0 ? `Stop requested for ${count} running agent(s).` : "No matching running agents." });
15390
+ this.sendStatus();
15391
+ break;
15392
+ }
15393
+ if (sub === "summary") {
15394
+ const runs2 = listAgentRuns();
15395
+ if (runs2.length === 0) {
15396
+ this.send({ type: "info", message: "No sub-agent runs yet." });
15397
+ break;
15398
+ }
15399
+ const lines = [`Recent agent runs (${runs2.length})`, ""];
15400
+ for (const run of runs2.slice(0, 20)) {
15401
+ lines.push(`${run.id} ${run.status} ${run.agentName} ${run.startedAt}`);
15402
+ lines.push(` task: ${run.task.slice(0, 120)}${run.task.length > 120 ? "..." : ""}`);
15403
+ if (run.toolNames.length > 0) lines.push(` tools: ${run.toolNames.join(", ")}`);
15404
+ if (run.summary) lines.push(` summary: ${run.summary.slice(0, 180)}${run.summary.length > 180 ? "..." : ""}`);
15405
+ if (run.error) lines.push(` error: ${run.error}`);
15406
+ }
15407
+ this.send({ type: "info", message: lines.join("\n") });
15408
+ break;
15409
+ }
15410
+ this.send({ type: "error", message: "Usage: /agent list|switch <name>|stop <id|all>|summary" });
15411
+ break;
15412
+ }
15039
15413
  // ── /yolo ──────────────────────────────────────────────────────
15040
15414
  case "auto": {
15041
15415
  const sub = args[0]?.toLowerCase() ?? "status";
@@ -15178,9 +15552,9 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
15178
15552
  let modifiedFiles = 0;
15179
15553
  const diffLines2 = [];
15180
15554
  for (const [filePath, { earliest }] of fileMap) {
15181
- const currentContent = existsSync25(filePath) ? (() => {
15555
+ const currentContent = existsSync26(filePath) ? (() => {
15182
15556
  try {
15183
- return readFileSync19(filePath, "utf-8");
15557
+ return readFileSync20(filePath, "utf-8");
15184
15558
  } catch {
15185
15559
  return null;
15186
15560
  }
@@ -15696,7 +16070,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
15696
16070
  case "test": {
15697
16071
  this.send({ type: "info", message: "\u{1F9EA} Running tests..." });
15698
16072
  try {
15699
- const { executeTests } = await import("./run-tests-7J3PNWVS.js");
16073
+ const { executeTests } = await import("./run-tests-R6FL6CQ3.js");
15700
16074
  const argStr = args.join(" ").trim();
15701
16075
  let testArgs = {};
15702
16076
  if (argStr) {
@@ -15713,9 +16087,9 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
15713
16087
  // ── /init ───────────────────────────────────────────────────────
15714
16088
  case "init": {
15715
16089
  const cwd = process.cwd();
15716
- const targetPath = join19(cwd, "AICLI.md");
16090
+ const targetPath = join20(cwd, "AICLI.md");
15717
16091
  const force = args.includes("--force");
15718
- if (existsSync25(targetPath) && !force) {
16092
+ if (existsSync26(targetPath) && !force) {
15719
16093
  this.send({ type: "info", message: `AICLI.md already exists at ${targetPath}
15720
16094
  Use /init --force to overwrite.` });
15721
16095
  break;
@@ -15748,7 +16122,7 @@ Use /context reload to load it.` });
15748
16122
  lines.push("**Config Files:**");
15749
16123
  lines.push(` Dir: ${configDir}`);
15750
16124
  const checkFile = (label, filePath) => {
15751
- const exists = existsSync25(filePath);
16125
+ const exists = existsSync26(filePath);
15752
16126
  let extra = "";
15753
16127
  if (exists) {
15754
16128
  try {
@@ -15758,9 +16132,9 @@ Use /context reload to load it.` });
15758
16132
  }
15759
16133
  lines.push(` ${exists ? "\u2713" : "\u2013"} ${label.padEnd(14)} ${exists ? filePath + extra : "(not found)"}`);
15760
16134
  };
15761
- checkFile("config.json", join19(configDir, "config.json"));
15762
- checkFile("memory.md", join19(configDir, MEMORY_FILE_NAME));
15763
- checkFile("dev-state.md", join19(configDir, "dev-state.md"));
16135
+ checkFile("config.json", join20(configDir, "config.json"));
16136
+ checkFile("memory.md", join20(configDir, MEMORY_FILE_NAME));
16137
+ checkFile("dev-state.md", join20(configDir, "dev-state.md"));
15764
16138
  lines.push("");
15765
16139
  if (this.mcpManager) {
15766
16140
  lines.push("**MCP Servers:**");
@@ -15948,7 +16322,7 @@ Use /add-dir remove to clear.` : "No directories added.\nUsage: /add-dir <path>
15948
16322
  break;
15949
16323
  }
15950
16324
  const dirPath = resolve8(sub);
15951
- if (!existsSync25(dirPath)) {
16325
+ if (!existsSync26(dirPath)) {
15952
16326
  this.send({ type: "error", message: `Directory not found: ${dirPath}` });
15953
16327
  break;
15954
16328
  }
@@ -15964,14 +16338,14 @@ It will be included in AI context for subsequent messages.` });
15964
16338
  // ── /commands ───────────────────────────────────────────────────
15965
16339
  case "commands": {
15966
16340
  const configDir = this.config.getConfigDir();
15967
- const commandsDir = join19(configDir, CUSTOM_COMMANDS_DIR_NAME);
15968
- if (!existsSync25(commandsDir)) {
16341
+ const commandsDir = join20(configDir, CUSTOM_COMMANDS_DIR_NAME);
16342
+ if (!existsSync26(commandsDir)) {
15969
16343
  this.send({ type: "info", message: `No custom commands directory.
15970
16344
  Create: ${commandsDir}/ with .md files.` });
15971
16345
  break;
15972
16346
  }
15973
16347
  try {
15974
- const files = readdirSync10(commandsDir).filter((f) => f.endsWith(".md"));
16348
+ const files = readdirSync11(commandsDir).filter((f) => f.endsWith(".md"));
15975
16349
  if (files.length === 0) {
15976
16350
  this.send({ type: "info", message: `No custom commands found in ${commandsDir}
15977
16351
  Add .md files to create commands.` });
@@ -15991,7 +16365,7 @@ Add .md files to create commands.` });
15991
16365
  // ── /plugins ────────────────────────────────────────────────────
15992
16366
  case "plugins": {
15993
16367
  const configDir = this.config.getConfigDir();
15994
- const pluginsDir = join19(configDir, PLUGINS_DIR_NAME);
16368
+ const pluginsDir = join20(configDir, PLUGINS_DIR_NAME);
15995
16369
  const pluginTools = this.toolRegistry.listPluginTools();
15996
16370
  const lines = [`\u{1F50C} **Plugins:**`, `Dir: ${pluginsDir}`, ""];
15997
16371
  if (pluginTools.length === 0) {
@@ -16188,11 +16562,11 @@ Add .md files to create commands.` });
16188
16562
  }
16189
16563
  memoryShow() {
16190
16564
  const configDir = this.config.getConfigDir();
16191
- const memPath = join19(configDir, MEMORY_FILE_NAME);
16565
+ const memPath = join20(configDir, MEMORY_FILE_NAME);
16192
16566
  let content = "";
16193
16567
  try {
16194
- if (existsSync25(memPath)) {
16195
- content = readFileSync19(memPath, "utf-8");
16568
+ if (existsSync26(memPath)) {
16569
+ content = readFileSync20(memPath, "utf-8");
16196
16570
  }
16197
16571
  } catch (err) {
16198
16572
  process.stderr.write(`[web] Failed to read memory file: ${err instanceof Error ? err.message : err}
@@ -16206,11 +16580,11 @@ Add .md files to create commands.` });
16206
16580
  }
16207
16581
  memoryAdd(text) {
16208
16582
  const configDir = this.config.getConfigDir();
16209
- const memPath = join19(configDir, MEMORY_FILE_NAME);
16583
+ const memPath = join20(configDir, MEMORY_FILE_NAME);
16210
16584
  try {
16211
16585
  mkdirSync11(configDir, { recursive: true });
16212
16586
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().slice(0, 19).replace("T", " ");
16213
- const previous = existsSync25(memPath) ? readFileSync19(memPath, "utf-8") : "";
16587
+ const previous = existsSync26(memPath) ? readFileSync20(memPath, "utf-8") : "";
16214
16588
  const entry = `
16215
16589
  - [${timestamp}] ${text}
16216
16590
  `;
@@ -16222,7 +16596,7 @@ Add .md files to create commands.` });
16222
16596
  }
16223
16597
  memoryClear() {
16224
16598
  const configDir = this.config.getConfigDir();
16225
- const memPath = join19(configDir, MEMORY_FILE_NAME);
16599
+ const memPath = join20(configDir, MEMORY_FILE_NAME);
16226
16600
  try {
16227
16601
  atomicWriteFileSync(memPath, "");
16228
16602
  this.send({ type: "info", message: "\u{1F5D1}\uFE0F Persistent memory cleared." });
@@ -16456,8 +16830,8 @@ async function setupProxy(configProxy) {
16456
16830
  }
16457
16831
 
16458
16832
  // src/web/auth.ts
16459
- import { existsSync as existsSync26, readFileSync as readFileSync20, writeFileSync as writeFileSync3, mkdirSync as mkdirSync12, readdirSync as readdirSync11, copyFileSync } from "fs";
16460
- import { join as join20 } from "path";
16833
+ import { existsSync as existsSync27, readFileSync as readFileSync21, writeFileSync as writeFileSync3, mkdirSync as mkdirSync12, readdirSync as readdirSync12, copyFileSync } from "fs";
16834
+ import { join as join21 } from "path";
16461
16835
  import { createHmac, randomBytes, timingSafeEqual, pbkdf2Sync } from "crypto";
16462
16836
  var USERS_FILE = "users.json";
16463
16837
  var TOKEN_EXPIRY_HOURS = 24;
@@ -16472,7 +16846,7 @@ var AuthManager = class {
16472
16846
  db;
16473
16847
  constructor(baseDir) {
16474
16848
  this.baseDir = baseDir;
16475
- this.usersFile = join20(baseDir, USERS_FILE);
16849
+ this.usersFile = join21(baseDir, USERS_FILE);
16476
16850
  this.db = this.loadOrCreate();
16477
16851
  }
16478
16852
  // ── Public API ─────────────────────────────────────────────────
@@ -16501,9 +16875,9 @@ var AuthManager = class {
16501
16875
  }
16502
16876
  const salt = randomBytes(16).toString("hex");
16503
16877
  const passwordHash = this.hashPassword(password, salt);
16504
- const dataDir = join20(USERS_DIR, username);
16505
- const fullDataDir = join20(this.baseDir, dataDir);
16506
- mkdirSync12(join20(fullDataDir, "history"), { recursive: true });
16878
+ const dataDir = join21(USERS_DIR, username);
16879
+ const fullDataDir = join21(this.baseDir, dataDir);
16880
+ mkdirSync12(join21(fullDataDir, "history"), { recursive: true });
16507
16881
  const user = {
16508
16882
  username,
16509
16883
  passwordHash,
@@ -16618,7 +16992,7 @@ var AuthManager = class {
16618
16992
  getUserDataDir(username) {
16619
16993
  const user = this.db.users.find((u) => u.username === username);
16620
16994
  if (!user) throw new Error(`User not found: ${username}`);
16621
- return join20(this.baseDir, user.dataDir);
16995
+ return join21(this.baseDir, user.dataDir);
16622
16996
  }
16623
16997
  /** List all usernames */
16624
16998
  listUsers() {
@@ -16654,30 +17028,30 @@ var AuthManager = class {
16654
17028
  const err = this.register(username, password);
16655
17029
  if (err) return err;
16656
17030
  const userDir = this.getUserDataDir(username);
16657
- const globalConfig = join20(this.baseDir, "config.json");
16658
- if (existsSync26(globalConfig)) {
17031
+ const globalConfig = join21(this.baseDir, "config.json");
17032
+ if (existsSync27(globalConfig)) {
16659
17033
  try {
16660
- const content = readFileSync20(globalConfig, "utf-8");
16661
- writeFileSync3(join20(userDir, "config.json"), content, "utf-8");
17034
+ const content = readFileSync21(globalConfig, "utf-8");
17035
+ writeFileSync3(join21(userDir, "config.json"), content, "utf-8");
16662
17036
  } catch {
16663
17037
  }
16664
17038
  }
16665
- const globalMemory = join20(this.baseDir, "memory.md");
16666
- if (existsSync26(globalMemory)) {
17039
+ const globalMemory = join21(this.baseDir, "memory.md");
17040
+ if (existsSync27(globalMemory)) {
16667
17041
  try {
16668
- const content = readFileSync20(globalMemory, "utf-8");
16669
- writeFileSync3(join20(userDir, "memory.md"), content, "utf-8");
17042
+ const content = readFileSync21(globalMemory, "utf-8");
17043
+ writeFileSync3(join21(userDir, "memory.md"), content, "utf-8");
16670
17044
  } catch {
16671
17045
  }
16672
17046
  }
16673
- const globalHistory = join20(this.baseDir, "history");
16674
- if (existsSync26(globalHistory)) {
17047
+ const globalHistory = join21(this.baseDir, "history");
17048
+ if (existsSync27(globalHistory)) {
16675
17049
  try {
16676
- const files = readdirSync11(globalHistory).filter((f) => f.endsWith(".json"));
16677
- const userHistory = join20(userDir, "history");
17050
+ const files = readdirSync12(globalHistory).filter((f) => f.endsWith(".json"));
17051
+ const userHistory = join21(userDir, "history");
16678
17052
  for (const f of files) {
16679
17053
  try {
16680
- copyFileSync(join20(globalHistory, f), join20(userHistory, f));
17054
+ copyFileSync(join21(globalHistory, f), join21(userHistory, f));
16681
17055
  } catch {
16682
17056
  }
16683
17057
  }
@@ -16688,9 +17062,9 @@ var AuthManager = class {
16688
17062
  }
16689
17063
  // ── Private methods ────────────────────────────────────────────
16690
17064
  loadOrCreate() {
16691
- if (existsSync26(this.usersFile)) {
17065
+ if (existsSync27(this.usersFile)) {
16692
17066
  try {
16693
- return JSON.parse(readFileSync20(this.usersFile, "utf-8"));
17067
+ return JSON.parse(readFileSync21(this.usersFile, "utf-8"));
16694
17068
  } catch {
16695
17069
  }
16696
17070
  }
@@ -16807,8 +17181,8 @@ async function startWebServer(options = {}) {
16807
17181
  }
16808
17182
  }
16809
17183
  let skillManager = null;
16810
- const skillsDir = join21(config.getConfigDir(), SKILLS_DIR_NAME);
16811
- if (existsSync27(skillsDir)) {
17184
+ const skillsDir = join22(config.getConfigDir(), SKILLS_DIR_NAME);
17185
+ if (existsSync28(skillsDir)) {
16812
17186
  skillManager = new SkillManager(skillsDir, config.get("ui").skillSizeWarn);
16813
17187
  skillManager.loadSkills();
16814
17188
  const count = skillManager.listSkills().length;
@@ -16879,18 +17253,18 @@ async function startWebServer(options = {}) {
16879
17253
  next();
16880
17254
  };
16881
17255
  const moduleDir = getModuleDir();
16882
- let clientDir = join21(moduleDir, "web", "client");
16883
- if (!existsSync27(clientDir)) {
16884
- clientDir = join21(moduleDir, "client");
17256
+ let clientDir = join22(moduleDir, "web", "client");
17257
+ if (!existsSync28(clientDir)) {
17258
+ clientDir = join22(moduleDir, "client");
16885
17259
  }
16886
- if (!existsSync27(clientDir)) {
16887
- clientDir = join21(moduleDir, "..", "..", "src", "web", "client");
17260
+ if (!existsSync28(clientDir)) {
17261
+ clientDir = join22(moduleDir, "..", "..", "src", "web", "client");
16888
17262
  }
16889
- if (!existsSync27(clientDir)) {
16890
- clientDir = join21(process.cwd(), "src", "web", "client");
17263
+ if (!existsSync28(clientDir)) {
17264
+ clientDir = join22(process.cwd(), "src", "web", "client");
16891
17265
  }
16892
17266
  console.log(` Static files: ${clientDir}`);
16893
- app.use("/vendor", express.static(join21(clientDir, "vendor"), { maxAge: "1y", immutable: true }));
17267
+ app.use("/vendor", express.static(join22(clientDir, "vendor"), { maxAge: "1y", immutable: true }));
16894
17268
  app.use(express.static(clientDir));
16895
17269
  app.get("/api/status", (_req, res) => {
16896
17270
  res.json({
@@ -16957,7 +17331,7 @@ async function startWebServer(options = {}) {
16957
17331
  app.get("/api/files", requireAuth, (req, res) => {
16958
17332
  const cwd = process.cwd();
16959
17333
  const prefix = req.query.prefix || "";
16960
- const targetDir = join21(cwd, prefix);
17334
+ const targetDir = join22(cwd, prefix);
16961
17335
  try {
16962
17336
  const canonicalTarget = realpathSync(resolve9(targetDir));
16963
17337
  const canonicalCwd = realpathSync(resolve9(cwd));
@@ -16971,10 +17345,10 @@ async function startWebServer(options = {}) {
16971
17345
  }
16972
17346
  try {
16973
17347
  const SKIP = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "dist-cjs", "release", "__pycache__", ".next", ".nuxt", "coverage", ".cache"]);
16974
- const entries = readdirSync12(targetDir, { withFileTypes: true });
17348
+ const entries = readdirSync13(targetDir, { withFileTypes: true });
16975
17349
  const files = entries.filter((e) => !SKIP.has(e.name) && !e.name.startsWith(".")).slice(0, 50).map((e) => ({
16976
17350
  name: e.name,
16977
- path: relative4(cwd, join21(targetDir, e.name)).replace(/\\/g, "/"),
17351
+ path: relative4(cwd, join22(targetDir, e.name)).replace(/\\/g, "/"),
16978
17352
  isDir: e.isDirectory()
16979
17353
  }));
16980
17354
  res.json({ files });
@@ -17010,8 +17384,8 @@ async function startWebServer(options = {}) {
17010
17384
  try {
17011
17385
  const authUser = req._authUser;
17012
17386
  const histDir = authUser ? getUserShared(authUser).config.getHistoryDir() : config.getHistoryDir();
17013
- const filePath = join21(histDir, `${id}.json`);
17014
- if (!existsSync27(filePath)) {
17387
+ const filePath = join22(histDir, `${id}.json`);
17388
+ if (!existsSync28(filePath)) {
17015
17389
  res.status(404).json({ error: "Session not found" });
17016
17390
  return;
17017
17391
  }
@@ -17026,7 +17400,7 @@ async function startWebServer(options = {}) {
17026
17400
  res.status(404).json({ error: "Session not found" });
17027
17401
  return;
17028
17402
  }
17029
- const data = JSON.parse(readFileSync21(filePath, "utf-8"));
17403
+ const data = JSON.parse(readFileSync22(filePath, "utf-8"));
17030
17404
  res.json({ session: data });
17031
17405
  } catch (err) {
17032
17406
  res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
@@ -17039,7 +17413,7 @@ async function startWebServer(options = {}) {
17039
17413
  return;
17040
17414
  }
17041
17415
  const cwd = process.cwd();
17042
- const fullPath = resolve9(join21(cwd, filePath));
17416
+ const fullPath = resolve9(join22(cwd, filePath));
17043
17417
  try {
17044
17418
  const canonicalFull = realpathSync(fullPath);
17045
17419
  const canonicalCwd = realpathSync(resolve9(cwd));
@@ -17057,7 +17431,7 @@ async function startWebServer(options = {}) {
17057
17431
  res.json({ error: `File too large (${(stat.size / 1024).toFixed(0)} KB, max 512 KB)` });
17058
17432
  return;
17059
17433
  }
17060
- const content = readFileSync21(fullPath, "utf-8");
17434
+ const content = readFileSync22(fullPath, "utf-8");
17061
17435
  res.json({ content, size: stat.size });
17062
17436
  } catch {
17063
17437
  res.json({ error: "Cannot read file" });
@@ -17312,17 +17686,17 @@ function resolveProjectMcpPath() {
17312
17686
  const cwd = process.cwd();
17313
17687
  const gitRoot = getGitRoot(cwd);
17314
17688
  const projectRoot = gitRoot ?? cwd;
17315
- const configPath = join21(projectRoot, MCP_PROJECT_CONFIG_NAME);
17316
- return existsSync27(configPath) ? configPath : null;
17689
+ const configPath = join22(projectRoot, MCP_PROJECT_CONFIG_NAME);
17690
+ return existsSync28(configPath) ? configPath : null;
17317
17691
  }
17318
17692
  function loadProjectMcpConfig() {
17319
17693
  const cwd = process.cwd();
17320
17694
  const gitRoot = getGitRoot(cwd);
17321
17695
  const projectRoot = gitRoot ?? cwd;
17322
- const configPath = join21(projectRoot, MCP_PROJECT_CONFIG_NAME);
17323
- if (!existsSync27(configPath)) return null;
17696
+ const configPath = join22(projectRoot, MCP_PROJECT_CONFIG_NAME);
17697
+ if (!existsSync28(configPath)) return null;
17324
17698
  try {
17325
- const raw = JSON.parse(readFileSync21(configPath, "utf-8"));
17699
+ const raw = JSON.parse(readFileSync22(configPath, "utf-8"));
17326
17700
  return raw.mcpServers ?? raw;
17327
17701
  } catch {
17328
17702
  return null;