jinzd-ai-cli 0.1.21 → 0.1.22

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 (3) hide show
  1. package/CLAUDE.md +40 -3
  2. package/dist/index.js +485 -150
  3. package/package.json +1 -1
package/CLAUDE.md CHANGED
@@ -52,7 +52,7 @@ src/
52
52
  │ └── manager.ts # McpManager(多服务器管理,工具发现与注册,MCP→Tool 转换)
53
53
  └── tools/
54
54
  ├── types.ts # ToolDefinition / ToolCall / ToolResult / DangerLevel / getDangerLevel
55
- ├── registry.ts # ToolRegistry(注册全部内置工具,共14个)
55
+ ├── registry.ts # ToolRegistry(注册全部内置工具,共15个)
56
56
  ├── executor.ts # ToolExecutor(确认逻辑 + printToolCall/printToolResult)
57
57
  └── builtin/
58
58
  ├── bash.ts # bash 工具(Windows: PowerShell + Buffer→UTF-8;Unix: $SHELL)
@@ -62,7 +62,8 @@ src/
62
62
  ├── run-interactive.ts # run_interactive 工具(spawn 直连可执行文件,stdin_lines 依次输入)
63
63
  ├── ask-user.ts # ask_user 工具(agentic 循环中向用户提问,等待文本回答)
64
64
  ├── write-todos.ts # write_todos 工具(任务拆解与进度跟踪,终端实时渲染)
65
- └── google-search.ts # google_search 工具(Google Custom Search API 搜索网页)
65
+ ├── google-search.ts # google_search 工具(Google Custom Search API 搜索网页)
66
+ └── spawn-agent.ts # spawn_agent 工具(独立子代理 agentic 循环 + SubAgentExecutor)
66
67
  ```
67
68
 
68
69
  ## 常用命令
@@ -205,6 +206,7 @@ AICLI_NO_STREAM 设为 1 禁用流式输出
205
206
  | `ask_user` | safe | AI 在 agentic 循环中向用户提问,等待文本回答后继续执行 |
206
207
  | `write_todos` | safe | AI 拆解复杂任务为子任务列表,终端实时渲染进度(pending/in_progress/completed) |
207
208
  | `google_search` | safe | Google Custom Search API 搜索网页,需配置 API Key + Search Engine ID (cx) |
209
+ | `spawn_agent` | safe | 委派独立子代理执行特定任务(隔离对话 + agentic 循环,write 自动确认,destructive 阻止) |
208
210
  | `mcp__*` | safe | MCP 服务器暴露的动态工具(命名格式:`mcp__<serverId>__<toolName>`) |
209
211
 
210
212
  ### 危险级别与确认机制
@@ -308,7 +310,7 @@ const stdinLines = Array.isArray(rawStdin) ? rawStdin.map(String)
308
310
  - [x] **`/compact` 上下文压缩**(2026-02-25):调用当前 provider 生成对话摘要,替换 session.messages 前 N 条为 user/assistant 摘要对,保留最近 4 条。`session.compact()` 方法 + `repl.compactSession()` + `/compact [instruction]` 命令。
309
311
  - [x] **Headless 非交互模式(`-p` flag)**(2026-02-25):`echo "..." | aicli -p "review"` 单轮对话后退出,`--json` 输出 `{content,provider,model,usage}`,支持 stdin 内容拼接,解锁 CI/CD 脚本场景。
310
312
  - [x] **Plan Mode 规划模式**(2026-02-25):`/plan` 进入只读规划(AI 只能用只读工具白名单),提示符显示 `[PLAN]` 黄色标记,`/plan execute` 切回正常模式,`/plan exit` 放弃计划。
311
- - [ ] **Sub-agents 系统**:`spawn_agent` 工具在子进程中运行独立 agentic 循环,支持并行任务分解。
313
+ - [x] **Sub-agents 系统**(2026-02-27):`spawn_agent` 工具在同进程中运行独立 agentic 循环。子代理拥有隔离对话、过滤后的工具集(SUBAGENT_ALLOWED_TOOLS)、自动确认 write 操作、阻止 destructive 操作。SubAgentExecutor 带前缀终端输出。
312
314
 
313
315
  ### P1 — 重要差距
314
316
  - [ ] **Agent Skills 系统**:可复用的专业技能包(`.aicli/skills/`)
@@ -333,6 +335,41 @@ const stdinLines = Array.isArray(rawStdin) ? rawStdin.map(String)
333
335
  - [ ] **web_fetch DNS 解析时 SSRF 防护**:当前只检查 URL hostname 字符串,若 hostname 是域名(非裸 IP)则未检查其解析后的 IP 是否为私有地址。需在发起请求后对实际连接 IP 做二次校验(复杂,低频风险)。
334
336
  - [ ] **`persistentCwd` 全局状态**:bash 工具的当前工作目录是模块级全局变量,多 session 并发时可能串扰。现阶段单 session REPL 无影响,GUI 多会话扩展时需重构为 per-session 状态。
335
337
 
338
+ ## 本轮开发完成记录(2026-02-27,v0.1.21 → v0.1.22)
339
+
340
+ ### 新增功能:Sub-Agent 子代理系统(`spawn_agent` 工具)
341
+
342
+ **背景**:P0 路线图最后一个核心功能。允许主 AI 将复杂子任务委派给独立子代理执行,子代理拥有隔离对话和 agentic 循环。
343
+
344
+ **实现**:
345
+ - 新增 `src/tools/builtin/spawn-agent.ts`:
346
+ - `spawnAgentContext` 共享上下文对象(遵循 streamToFileContext 模式,由 repl.ts 注入)
347
+ - `SubAgentExecutor` 类:子代理专用工具执行器,write 自动确认、destructive 直接阻止、带 ` ┃ ` 前缀的嵌套终端输出
348
+ - 独立 agentic 循环:chatWithTools → executeAll → buildToolResultMessages,最多 max_rounds 轮
349
+ - 子代理 system prompt:继承父级 system prompt + 子代理模式说明(任务规则、工具限制)
350
+ - `src/core/constants.ts`:新增 `SUBAGENT_DEFAULT_MAX_ROUNDS`(10) / `SUBAGENT_MAX_ROUNDS_LIMIT`(15) / `SUBAGENT_ALLOWED_TOOLS`(11个工具白名单)
351
+ - `src/tools/registry.ts`:新增 `unregister(name)` 方法(供子代理创建过滤工具集);注册 `spawnAgentTool`
352
+ - `src/tools/types.ts`:`getDangerLevel` 中 `spawn_agent` → `safe`
353
+ - `src/repl/repl.ts`:handleChatWithTools 中注入 spawnAgentContext(provider/model/systemPrompt/modelParams/configManager)
354
+
355
+ **工具参数**:
356
+ - `task`(string, required):子代理任务描述,包含所有必要上下文
357
+ - `max_rounds`(number, optional, default 10, 限制 1-15):最大工具调用轮次
358
+
359
+ **安全设计**:
360
+ - 递归防护:子代理工具集不含 `spawn_agent`
361
+ - 用户交互隔离:子代理无 `ask_user`、`save_memory`、`save_last_response`
362
+ - 破坏性操作阻止:`destructive` 级别工具调用直接返回错误
363
+ - 写操作自动确认:`write` 级别无需用户确认(主 AI 已明确委派)
364
+ - 轮次上限:max_rounds 限制在 1-15,防止无限循环
365
+
366
+ **终端输出**:子代理工具调用和结果使用 ` ┃ ` 前缀 + 箱形边框(┏/┗),清晰区分父级与子代理输出
367
+
368
+ ### 架构变更
369
+ - `src/repl/renderer.ts`:`/about` 工具计数 14 → 15,新增 `spawn_agent` 条目和 Sub-Agent 特性条目
370
+
371
+ ---
372
+
336
373
  ## 本轮开发完成记录(2026-02-25,v0.1.18 → v0.1.20)
337
374
 
338
375
  ### Bug 修复:Kimi XML 伪工具调用(v0.1.19)
package/dist/index.js CHANGED
@@ -129,7 +129,7 @@ var EnvLoader = class {
129
129
  };
130
130
 
131
131
  // src/core/constants.ts
132
- var VERSION = "0.1.21";
132
+ var VERSION = "0.1.22";
133
133
  var APP_NAME = "ai-cli";
134
134
  var CONFIG_DIR_NAME = ".aicli";
135
135
  var CONFIG_FILE_NAME = "config.json";
@@ -173,22 +173,36 @@ var PLAN_MODE_SYSTEM_ADDON = `# \u{1F50D} Plan Mode \u2014 \u53EA\u8BFB\u89C4\u5
173
173
  - \u6F5C\u5728\u98CE\u9669\u548C\u6CE8\u610F\u4E8B\u9879
174
174
 
175
175
  \u5B8C\u6210\u89C4\u5212\u540E\uFF0C\u8BF7\u660E\u786E\u544A\u77E5\u7528\u6237\uFF1A\u8F93\u5165 \`/plan execute\` \u5F00\u59CB\u6267\u884C\u8BA1\u5212\uFF0C\u6216 \`/plan exit\` \u653E\u5F03\u6B64\u8BA1\u5212\u3002`;
176
+ var SUBAGENT_DEFAULT_MAX_ROUNDS = 10;
177
+ var SUBAGENT_MAX_ROUNDS_LIMIT = 15;
178
+ var SUBAGENT_ALLOWED_TOOLS = /* @__PURE__ */ new Set([
179
+ "bash",
180
+ "read_file",
181
+ "write_file",
182
+ "edit_file",
183
+ "list_dir",
184
+ "grep_files",
185
+ "glob_files",
186
+ "run_interactive",
187
+ "web_fetch",
188
+ "google_search",
189
+ "write_todos"
190
+ ]);
176
191
  var AUTHOR = "\u664B\u6B63\u4E1C";
177
192
  var AUTHOR_EMAIL = "zhengdong.jin@gmail.com";
178
193
  var DESCRIPTION = "\u8DE8\u5E73\u53F0 REPL \u98CE\u683C AI \u5BF9\u8BDD\u5DE5\u5177\uFF0C\u652F\u6301\u591A Provider \u4E0E Agentic \u5DE5\u5177\u8C03\u7528";
179
194
 
180
195
  // src/core/errors.ts
181
196
  var AiCliError = class extends Error {
182
- constructor(message) {
183
- super(message);
197
+ constructor(message, options) {
198
+ super(message, options);
184
199
  this.name = "AiCliError";
185
200
  }
186
201
  };
187
202
  var ProviderError = class extends AiCliError {
188
203
  constructor(providerId, message, cause) {
189
- super(`[${providerId}] ${message}`);
204
+ super(`[${providerId}] ${message}`, cause !== void 0 ? { cause } : void 0);
190
205
  this.providerId = providerId;
191
- this.cause = cause;
192
206
  this.name = "ProviderError";
193
207
  }
194
208
  };
@@ -263,6 +277,13 @@ ${err}`
263
277
  this.config[key] = value;
264
278
  this.save();
265
279
  }
280
+ /**
281
+ * 仅修改内存中的配置值,不持久化到磁盘。
282
+ * 用于 CLI 命令行参数覆盖(-p, -m, --no-stream),使其只对当前进程生效。
283
+ */
284
+ setTransient(key, value) {
285
+ this.config[key] = value;
286
+ }
266
287
  isFirstRun() {
267
288
  return !existsSync(this.configPath);
268
289
  }
@@ -343,7 +364,8 @@ var ClaudeProvider = class extends BaseProvider {
343
364
  max_tokens: request.maxTokens ?? 8192,
344
365
  temperature: request.temperature
345
366
  });
346
- const content = response.content[0].type === "text" ? response.content[0].text : "";
367
+ const firstBlock = response.content?.[0];
368
+ const content = firstBlock?.type === "text" ? firstBlock.text : "";
347
369
  return {
348
370
  content,
349
371
  model: response.model,
@@ -551,6 +573,9 @@ var GeminiProvider = class extends BaseProvider {
551
573
  }
552
574
  async chat(request) {
553
575
  try {
576
+ if (request.messages.length === 0) {
577
+ return { content: "", model: request.model, usage: void 0 };
578
+ }
554
579
  const genModel = this.client.getGenerativeModel({
555
580
  model: request.model,
556
581
  systemInstruction: request.systemPrompt
@@ -571,6 +596,10 @@ var GeminiProvider = class extends BaseProvider {
571
596
  }
572
597
  async *chatStream(request) {
573
598
  try {
599
+ if (request.messages.length === 0) {
600
+ yield { delta: "", done: true };
601
+ return;
602
+ }
574
603
  const genModel = this.client.getGenerativeModel({
575
604
  model: request.model,
576
605
  systemInstruction: request.systemPrompt
@@ -598,6 +627,9 @@ var GeminiProvider = class extends BaseProvider {
598
627
  }
599
628
  async chatWithTools(request, tools) {
600
629
  try {
630
+ if (request.messages.length === 0) {
631
+ return { content: "", usage: void 0 };
632
+ }
601
633
  const geminiTools = [{
602
634
  functionDeclarations: tools.map((t) => ({
603
635
  name: t.name,
@@ -756,8 +788,12 @@ var OpenAICompatibleProvider = class extends BaseProvider {
756
788
  }, {
757
789
  timeout: request.timeout ?? this.defaultTimeout
758
790
  });
791
+ const firstChoice = response.choices?.[0];
792
+ if (!firstChoice) {
793
+ return { content: "", model: response.model, usage: void 0 };
794
+ }
759
795
  return {
760
- content: response.choices[0].message.content ?? "",
796
+ content: firstChoice.message.content ?? "",
761
797
  model: response.model,
762
798
  usage: response.usage ? {
763
799
  inputTokens: response.usage.prompt_tokens,
@@ -846,7 +882,11 @@ var OpenAICompatibleProvider = class extends BaseProvider {
846
882
  }, {
847
883
  timeout: request.timeout ?? this.defaultTimeout
848
884
  });
849
- const message = response.choices[0].message;
885
+ const firstChoice = response.choices?.[0];
886
+ if (!firstChoice) {
887
+ return { content: "", usage: void 0 };
888
+ }
889
+ const message = firstChoice.message;
850
890
  const usage = response.usage ? {
851
891
  inputTokens: response.usage.prompt_tokens,
852
892
  outputTokens: response.usage.completion_tokens
@@ -1372,21 +1412,42 @@ var Session = class _Session {
1372
1412
  }))
1373
1413
  };
1374
1414
  }
1415
+ /**
1416
+ * 从磁盘 JSON 数据恢复 Session 实例。
1417
+ * 添加运行时校验:损坏或不兼容的历史文件会抛出明确错误,而非 TypeError 崩溃。
1418
+ */
1375
1419
  static fromJSON(data) {
1376
- const session = new _Session(data.id, data.provider, data.model);
1377
- session.title = data.title;
1378
- session.created = new Date(data.created);
1379
- session.updated = new Date(data.updated);
1380
- if (data.tokenUsage) {
1420
+ const d = data;
1421
+ if (!d || typeof d !== "object") {
1422
+ throw new Error("Invalid session data: expected an object");
1423
+ }
1424
+ if (typeof d.id !== "string" || typeof d.provider !== "string" || typeof d.model !== "string") {
1425
+ throw new Error("Invalid session data: missing or invalid id/provider/model fields");
1426
+ }
1427
+ if (!Array.isArray(d.messages)) {
1428
+ throw new Error("Invalid session data: messages is not an array");
1429
+ }
1430
+ const session = new _Session(d.id, d.provider, d.model);
1431
+ session.title = typeof d.title === "string" ? d.title : void 0;
1432
+ const created = new Date(d.created);
1433
+ const updated = new Date(d.updated);
1434
+ session.created = isNaN(created.getTime()) ? /* @__PURE__ */ new Date() : created;
1435
+ session.updated = isNaN(updated.getTime()) ? /* @__PURE__ */ new Date() : updated;
1436
+ const tu = d.tokenUsage;
1437
+ if (tu && typeof tu === "object") {
1381
1438
  session.tokenUsage = {
1382
- inputTokens: data.tokenUsage.inputTokens ?? 0,
1383
- outputTokens: data.tokenUsage.outputTokens ?? 0
1439
+ inputTokens: typeof tu.inputTokens === "number" ? tu.inputTokens : 0,
1440
+ outputTokens: typeof tu.outputTokens === "number" ? tu.outputTokens : 0
1384
1441
  };
1385
1442
  }
1386
- session.messages = data.messages.map((m) => ({
1387
- ...m,
1388
- timestamp: new Date(m.timestamp)
1389
- }));
1443
+ session.messages = d.messages.map((m) => {
1444
+ const ts = new Date(m.timestamp);
1445
+ return {
1446
+ role: m.role ?? "user",
1447
+ content: m.content,
1448
+ timestamp: isNaN(ts.getTime()) ? /* @__PURE__ */ new Date() : ts
1449
+ };
1450
+ });
1390
1451
  return session;
1391
1452
  }
1392
1453
  };
@@ -1504,7 +1565,7 @@ var SessionManager = class {
1504
1565
  import * as readline from "readline";
1505
1566
  import { existsSync as existsSync15, readFileSync as readFileSync10 } from "fs";
1506
1567
  import { join as join10, resolve as resolve4, extname as extname3 } from "path";
1507
- import chalk9 from "chalk";
1568
+ import chalk10 from "chalk";
1508
1569
 
1509
1570
  // src/repl/renderer.ts
1510
1571
  import chalk from "chalk";
@@ -1585,7 +1646,7 @@ var Renderer = class {
1585
1646
  console.log(chalk.dim(" Gemini (Google) \xB7 \u667A\u8C31\u6E05\u8A00 \xB7 \u81EA\u5B9A\u4E49 OpenAI \u517C\u5BB9"));
1586
1647
  console.log(HR);
1587
1648
  const mcpToolCount = mcpInfo?.tools ?? 0;
1588
- const toolTotal = 14 + pluginCount + mcpToolCount;
1649
+ const toolTotal = 15 + pluginCount + mcpToolCount;
1589
1650
  const extras = [];
1590
1651
  if (pluginCount > 0) extras.push(`${pluginCount} \u4E2A\u63D2\u4EF6`);
1591
1652
  if (mcpToolCount > 0) extras.push(`${mcpToolCount} \u4E2A MCP`);
@@ -1605,6 +1666,7 @@ var Renderer = class {
1605
1666
  console.log(tool("save_memory", "\u5C06\u91CD\u8981\u4FE1\u606F\u6301\u4E45\u5316\u5230 ~/.aicli/memory.md\uFF0C\u8DE8\u4F1A\u8BDD\u81EA\u52A8\u6CE8\u5165"));
1606
1667
  console.log(tool("ask_user", "\u5411\u7528\u6237\u63D0\u95EE\u5E76\u7B49\u5F85\u56DE\u7B54\uFF08agentic \u5FAA\u73AF\u4E2D\u8BF7\u6C42\u6F84\u6E05\uFF09"));
1607
1668
  console.log(tool("write_todos", "\u62C6\u89E3\u4EFB\u52A1\u4E3A\u5B50\u4EFB\u52A1\u5217\u8868\uFF0C\u5B9E\u65F6\u663E\u793A\u8FDB\u5EA6"));
1669
+ console.log(tool("spawn_agent", "\u59D4\u6D3E\u72EC\u7ACB\u5B50\u4EE3\u7406\u6267\u884C\u7279\u5B9A\u4EFB\u52A1\uFF08\u9694\u79BB\u5BF9\u8BDD + \u81EA\u52A8\u5DE5\u5177\u8C03\u7528\u5FAA\u73AF\uFF09"));
1608
1670
  console.log(HR);
1609
1671
  console.log(chalk.gray(" REPL \u547D\u4EE4\uFF0819\u4E2A\uFF09\uFF1A"));
1610
1672
  console.log(chalk.dim(" /help /about /provider /model /clear /compact /plan /session"));
@@ -1626,6 +1688,7 @@ var Renderer = class {
1626
1688
  console.log(feat('Headless \u6A21\u5F0F\uFF1Aaicli -p "..." \u5355\u8F6E\u975E\u4EA4\u4E92\uFF0C\u652F\u6301 stdin \u7BA1\u9053\u4E0E --json \u8F93\u51FA'));
1627
1689
  console.log(feat("/compact \u4E0A\u4E0B\u6587\u538B\u7F29\uFF1A\u751F\u6210\u6458\u8981\u66FF\u6362\u65E7\u6D88\u606F\uFF0C\u4FDD\u7559\u6700\u8FD1 4 \u6761\uFF0C\u89E3\u51B3 context \u6EA2\u51FA"));
1628
1690
  console.log(feat("Kimi \u5DE5\u5177\u8C03\u7528\u53EF\u9760\u6027\u589E\u5F3A\uFF1AXML \u4F2A\u8C03\u7528\u81EA\u52A8\u68C0\u6D4B\u5E76\u8F6C\u6362\u4E3A\u771F\u5B9E API \u8C03\u7528\uFF08v0.1.19\uFF09"));
1691
+ console.log(feat("Sub-Agent \u5B50\u4EE3\u7406\uFF1Aspawn_agent \u59D4\u6D3E\u72EC\u7ACB\u5B50\u4EE3\u7406\uFF0C\u9694\u79BB\u5FAA\u73AF\u6267\u884C\u590D\u6742\u5B50\u4EFB\u52A1"));
1629
1692
  console.log(feat("\u72EC\u7ACB\u53EF\u6267\u884C\u6587\u4EF6\u6253\u5305\uFF08~56MB\uFF0C\u65E0\u9700 Node.js \u73AF\u5883\uFF09"));
1630
1693
  console.log();
1631
1694
  }
@@ -4070,6 +4133,305 @@ function formatResults(query, data, requested) {
4070
4133
  return header + "\n" + results.join("\n\n");
4071
4134
  }
4072
4135
 
4136
+ // src/tools/builtin/spawn-agent.ts
4137
+ import chalk6 from "chalk";
4138
+
4139
+ // src/tools/types.ts
4140
+ function getDangerLevel(toolName, args) {
4141
+ if (toolName.startsWith("mcp__")) return "safe";
4142
+ if (toolName === "bash") {
4143
+ const cmd = String(args["command"] ?? "");
4144
+ if (/\brm\s+[^\n]*(?:-\w*[rRfF]\w*|--recursive|--force)\b/.test(cmd)) return "destructive";
4145
+ if (/\brm\s+\S/.test(cmd)) return "destructive";
4146
+ if (/\brmdir\b|\bformat\b|\bmkfs\b/.test(cmd)) return "destructive";
4147
+ if (/\bRemove-Item\b.*(?:-Recurse|-Force)|\bri\s+.*-(?:Recurse|Force)\b|\brd\s+\/s\b|\brmdir\s+\/s\b/i.test(cmd)) return "destructive";
4148
+ if (/\bdel\s+\S/.test(cmd)) return "destructive";
4149
+ if (/\becho\b.*>>?|\btee\b|\bcp\b|\bmv\b/.test(cmd)) return "write";
4150
+ if (/\bSet-Content\b|\bOut-File\b|\bAdd-Content\b|\bCopy-Item\b|\bMove-Item\b/i.test(cmd)) return "write";
4151
+ return "safe";
4152
+ }
4153
+ if (toolName === "write_file") return "write";
4154
+ if (toolName === "edit_file") return "write";
4155
+ if (toolName === "save_last_response") return "write";
4156
+ if (toolName === "run_interactive") {
4157
+ const exe = String(args["executable"] ?? "").toLowerCase();
4158
+ if (/\b(rm|rmdir|del|format|mkfs|Remove-Item)\b/i.test(exe)) return "destructive";
4159
+ if (/\b(bash|sh|zsh|cmd|powershell|pwsh|python|node|ruby|perl)\b/i.test(exe)) return "write";
4160
+ return "write";
4161
+ }
4162
+ if (toolName === "read_file" || toolName === "list_dir" || toolName === "grep_files" || toolName === "glob_files" || toolName === "web_fetch" || toolName === "save_memory" || toolName === "ask_user" || toolName === "write_todos" || toolName === "google_search" || toolName === "spawn_agent") return "safe";
4163
+ return "write";
4164
+ }
4165
+
4166
+ // src/tools/builtin/spawn-agent.ts
4167
+ var spawnAgentContext = {
4168
+ provider: null,
4169
+ model: "",
4170
+ systemPrompt: void 0,
4171
+ modelParams: {},
4172
+ configManager: null
4173
+ };
4174
+ var PREFIX = chalk6.dim(" \u2503 ");
4175
+ var MAX_TOOL_OUTPUT_CHARS = 12e3;
4176
+ function truncateOutput(content, toolName) {
4177
+ if (content.length <= MAX_TOOL_OUTPUT_CHARS) return content;
4178
+ const keepHead = Math.floor(MAX_TOOL_OUTPUT_CHARS * 0.7);
4179
+ const keepTail = Math.floor(MAX_TOOL_OUTPUT_CHARS * 0.2);
4180
+ const omitted = content.length - keepHead - keepTail;
4181
+ const lines = content.split("\n").length;
4182
+ const head = content.slice(0, keepHead);
4183
+ const tail = content.slice(content.length - keepTail);
4184
+ return head + `
4185
+
4186
+ ... [\u8F93\u51FA\u5DF2\u622A\u65AD\uFF1A\u5171 ${content.length} \u5B57\u7B26 / ${lines} \u884C\uFF0C\u7701\u7565\u4E86\u4E2D\u95F4 ${omitted} \u5B57\u7B26\u3002\u5982\u9700\u67E5\u770B\u5B8C\u6574\u5185\u5BB9\uFF0C\u8BF7\u4F7F\u7528 read_file \u5206\u6BB5\u8BFB\u53D6` + (toolName === "bash" ? "\uFF0C\u6216\u7F29\u5C0F\u547D\u4EE4\u8303\u56F4" : "") + `] ...
4187
+
4188
+ ` + tail;
4189
+ }
4190
+ var SubAgentExecutor = class {
4191
+ constructor(registry) {
4192
+ this.registry = registry;
4193
+ }
4194
+ round = 0;
4195
+ totalRounds = 0;
4196
+ setRoundInfo(current, total) {
4197
+ this.round = current;
4198
+ this.totalRounds = total;
4199
+ }
4200
+ async execute(call) {
4201
+ const tool = this.registry.get(call.name);
4202
+ if (!tool) {
4203
+ return {
4204
+ callId: call.id,
4205
+ content: `Unknown tool: ${call.name}`,
4206
+ isError: true
4207
+ };
4208
+ }
4209
+ const dangerLevel = getDangerLevel(call.name, call.arguments);
4210
+ if (dangerLevel === "destructive") {
4211
+ this.printPrefixed(
4212
+ chalk6.red("\u26A0 BLOCKED: ") + `Destructive operation ${chalk6.bold(call.name)} not allowed in sub-agent`
4213
+ );
4214
+ return {
4215
+ callId: call.id,
4216
+ content: "Destructive operations are not allowed in sub-agents.",
4217
+ isError: true
4218
+ };
4219
+ }
4220
+ this.printToolCall(call, dangerLevel);
4221
+ try {
4222
+ const rawContent = await tool.execute(call.arguments);
4223
+ const content = truncateOutput(rawContent, call.name);
4224
+ const wasTruncated = content !== rawContent;
4225
+ this.printToolResult(call.name, rawContent, false, wasTruncated);
4226
+ return { callId: call.id, content, isError: false };
4227
+ } catch (err) {
4228
+ const message = err instanceof Error ? err.message : String(err);
4229
+ this.printToolResult(call.name, message, true, false);
4230
+ return { callId: call.id, content: message, isError: true };
4231
+ }
4232
+ }
4233
+ async executeAll(calls) {
4234
+ const results = [];
4235
+ for (const call of calls) {
4236
+ results.push(await this.execute(call));
4237
+ }
4238
+ return results;
4239
+ }
4240
+ // ── 带前缀的终端输出 ──
4241
+ printPrefixed(text) {
4242
+ for (const line of text.split("\n")) {
4243
+ console.log(PREFIX + line);
4244
+ }
4245
+ }
4246
+ printToolCall(call, dangerLevel) {
4247
+ console.log(PREFIX);
4248
+ const icon = dangerLevel === "write" ? chalk6.yellow("\u270E Tool: ") : chalk6.bold.cyan("\u2699 Tool: ");
4249
+ const roundBadge = this.totalRounds > 0 ? chalk6.dim(` [${this.round}/${this.totalRounds}]`) : "";
4250
+ console.log(PREFIX + icon + chalk6.white(call.name) + roundBadge);
4251
+ for (const [key, val] of Object.entries(call.arguments)) {
4252
+ let valStr;
4253
+ if (Array.isArray(val)) {
4254
+ const json = JSON.stringify(val);
4255
+ valStr = json.length > 160 ? json.slice(0, 160) + "..." : json;
4256
+ } else if (typeof val === "string" && val.length > 120) {
4257
+ valStr = val.slice(0, 120) + "...";
4258
+ } else {
4259
+ valStr = String(val);
4260
+ }
4261
+ console.log(PREFIX + chalk6.gray(` ${key}: `) + chalk6.white(valStr));
4262
+ }
4263
+ }
4264
+ printToolResult(name, content, isError, wasTruncated) {
4265
+ if (isError) {
4266
+ console.log(
4267
+ PREFIX + chalk6.red(`\u26A0 ${name} error: `) + chalk6.gray(content.slice(0, 300))
4268
+ );
4269
+ } else {
4270
+ const lines = content.split("\n");
4271
+ const maxLines = name === "run_interactive" ? 40 : 8;
4272
+ const preview = lines.slice(0, maxLines);
4273
+ const prefixedPreview = preview.map((l) => PREFIX + " " + chalk6.gray(l)).join("\n");
4274
+ const moreLines = lines.length > maxLines ? "\n" + PREFIX + chalk6.gray(` ... (${lines.length - maxLines} more lines)`) : "";
4275
+ const truncNote = wasTruncated ? "\n" + PREFIX + chalk6.yellow(` \u26A1 \u8F93\u51FA\u5DF2\u622A\u65AD\u81F3 ${MAX_TOOL_OUTPUT_CHARS} \u5B57\u7B26`) : "";
4276
+ console.log(PREFIX + chalk6.green("\u2713 Result:"));
4277
+ console.log(prefixedPreview + moreLines + truncNote);
4278
+ }
4279
+ }
4280
+ };
4281
+ function buildSubAgentSystemPrompt(task, parentSystemPrompt) {
4282
+ const parts = [];
4283
+ if (parentSystemPrompt) {
4284
+ parts.push(parentSystemPrompt);
4285
+ }
4286
+ parts.push(
4287
+ `# Sub-Agent Mode
4288
+
4289
+ \u4F60\u662F\u4E00\u4E2A\u4E13\u6CE8\u7684\u5B50\u4EE3\u7406\uFF08sub-agent\uFF09\uFF0C\u88AB\u4E3B\u4EE3\u7406\u59D4\u6D3E\u6765\u5B8C\u6210\u4E00\u4E2A\u7279\u5B9A\u4EFB\u52A1\u3002
4290
+
4291
+ **\u4F60\u7684\u4EFB\u52A1**\uFF1A
4292
+ ${task}
4293
+
4294
+ **\u5DE5\u4F5C\u89C4\u5219**\uFF1A
4295
+ 1. \u4E13\u6CE8\u4E8E\u5B8C\u6210\u4F60\u7684\u4EFB\u52A1\uFF0C\u4E0D\u8981\u504F\u79BB
4296
+ 2. \u5B8C\u6210\u540E\u8F93\u51FA\u4E00\u4E2A\u7B80\u6D01\u7684\u603B\u7ED3\uFF0C\u5305\u542B\uFF1A\u505A\u4E86\u4EC0\u4E48\u3001\u4FEE\u6539\u4E86\u54EA\u4E9B\u6587\u4EF6\u3001\u7ED3\u679C\u5982\u4F55
4297
+ 3. \u4F60\u65E0\u6CD5\u4E0E\u7528\u6237\u4EA4\u4E92\uFF08\u6CA1\u6709 ask_user \u5DE5\u5177\uFF09\uFF0C\u4F9D\u9760\u4EFB\u52A1\u63CF\u8FF0\u4E2D\u7684\u4FE1\u606F\u72EC\u7ACB\u5B8C\u6210
4298
+ 4. \u4F60\u65E0\u6CD5\u521B\u5EFA\u5B50\u4EE3\u7406\uFF08\u6CA1\u6709 spawn_agent \u5DE5\u5177\uFF09
4299
+ 5. \u7834\u574F\u6027\u64CD\u4F5C\uFF08rm -rf \u7B49\uFF09\u4F1A\u88AB\u81EA\u52A8\u963B\u6B62
4300
+ 6. \u5C3D\u91CF\u9AD8\u6548\uFF0C\u51CF\u5C11\u4E0D\u5FC5\u8981\u7684\u5DE5\u5177\u8C03\u7528\u8F6E\u6B21`
4301
+ );
4302
+ return parts.join("\n\n---\n\n");
4303
+ }
4304
+ function printSubAgentHeader(task, maxRounds) {
4305
+ console.log();
4306
+ console.log(chalk6.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"));
4307
+ console.log(PREFIX + chalk6.bold.magenta("\u{1F916} Sub-Agent Spawned"));
4308
+ console.log(
4309
+ PREFIX + chalk6.gray("Task: ") + chalk6.white(task.slice(0, 120) + (task.length > 120 ? "..." : ""))
4310
+ );
4311
+ console.log(PREFIX + chalk6.gray(`Max rounds: ${maxRounds}`));
4312
+ console.log(chalk6.dim(" \u2503"));
4313
+ }
4314
+ function printSubAgentFooter(usage) {
4315
+ console.log(PREFIX);
4316
+ console.log(PREFIX + chalk6.bold.magenta("Sub-Agent Complete"));
4317
+ if (usage.inputTokens > 0 || usage.outputTokens > 0) {
4318
+ console.log(
4319
+ PREFIX + chalk6.dim(
4320
+ `Tokens: ${usage.inputTokens} in / ${usage.outputTokens} out`
4321
+ )
4322
+ );
4323
+ }
4324
+ console.log(chalk6.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"));
4325
+ console.log();
4326
+ }
4327
+ var spawnAgentTool = {
4328
+ definition: {
4329
+ name: "spawn_agent",
4330
+ description: "\u59D4\u6D3E\u4E00\u4E2A\u72EC\u7ACB\u7684\u5B50\u4EE3\u7406\uFF08sub-agent\uFF09\u6765\u5B8C\u6210\u7279\u5B9A\u5B50\u4EFB\u52A1\u3002\u5B50\u4EE3\u7406\u62E5\u6709\u72EC\u7ACB\u7684\u5BF9\u8BDD\u4E0A\u4E0B\u6587\u548C agentic \u5DE5\u5177\u8C03\u7528\u5FAA\u73AF\uFF0C\u53EF\u4F7F\u7528 bash\u3001\u6587\u4EF6\u8BFB\u5199\u3001\u7F16\u8F91\u3001\u641C\u7D22\u7B49\u5DE5\u5177\uFF0C\u4F46\u65E0\u6CD5\u4E0E\u7528\u6237\u4EA4\u4E92\u6216\u521B\u5EFA\u66F4\u591A\u5B50\u4EE3\u7406\u3002\u9002\u7528\u4E8E\u53EF\u72EC\u7ACB\u5B8C\u6210\u7684\u5B50\u4EFB\u52A1\uFF0C\u5982\uFF1A\u91CD\u6784\u67D0\u4E2A\u6A21\u5757\u3001\u4E3A\u67D0\u4E2A\u6587\u4EF6\u7F16\u5199\u6D4B\u8BD5\u3001\u8C03\u7814\u67D0\u4E2A\u6280\u672F\u65B9\u6848\u3001\u5B9E\u73B0\u67D0\u4E2A\u5177\u4F53\u529F\u80FD\u3002\u8FD4\u56DE\u5B50\u4EE3\u7406\u7684\u6267\u884C\u7ED3\u679C\u6458\u8981\u3002",
4331
+ parameters: {
4332
+ task: {
4333
+ type: "string",
4334
+ description: "\u5B50\u4EE3\u7406\u7684\u4EFB\u52A1\u63CF\u8FF0\u3002\u5E94\u5305\u542B\u6240\u6709\u5FC5\u8981\u4E0A\u4E0B\u6587\uFF1A\u6587\u4EF6\u8DEF\u5F84\u3001\u9700\u6C42\u3001\u7EA6\u675F\u3001\u9884\u671F\u4EA7\u51FA\u3002\u5B50\u4EE3\u7406\u65E0\u6CD5\u8BBF\u95EE\u7236\u7EA7\u5BF9\u8BDD\u5386\u53F2\uFF0C\u6240\u6709\u4FE1\u606F\u5FC5\u987B\u5728\u6B64\u53C2\u6570\u4E2D\u63D0\u4F9B\u3002",
4335
+ required: true
4336
+ },
4337
+ max_rounds: {
4338
+ type: "number",
4339
+ description: "\u5B50\u4EE3\u7406\u6700\u5927\u5DE5\u5177\u8C03\u7528\u8F6E\u6B21\uFF081-15\uFF0C\u9ED8\u8BA4 10\uFF09\u3002\u7B80\u5355\u4EFB\u52A1\u7528\u8F83\u5C11\u8F6E\u6B21\uFF0C\u590D\u6742\u4EFB\u52A1\u7528\u66F4\u591A\u3002",
4340
+ required: false
4341
+ }
4342
+ },
4343
+ dangerous: false
4344
+ },
4345
+ async execute(args) {
4346
+ const task = String(args["task"] ?? "").trim();
4347
+ if (!task) throw new Error("task parameter is required");
4348
+ const rawMaxRounds = Number(args["max_rounds"] ?? SUBAGENT_DEFAULT_MAX_ROUNDS);
4349
+ const maxRounds = Math.min(
4350
+ Math.max(Math.round(rawMaxRounds), 1),
4351
+ SUBAGENT_MAX_ROUNDS_LIMIT
4352
+ );
4353
+ const ctx = spawnAgentContext;
4354
+ if (!ctx.provider) {
4355
+ throw new Error("spawn_agent: provider not initialized (context not injected)");
4356
+ }
4357
+ const subRegistry = new ToolRegistry();
4358
+ for (const tool of subRegistry.listAll()) {
4359
+ if (!SUBAGENT_ALLOWED_TOOLS.has(tool.definition.name)) {
4360
+ subRegistry.unregister(tool.definition.name);
4361
+ }
4362
+ }
4363
+ const subExecutor = new SubAgentExecutor(subRegistry);
4364
+ const subMessages = [
4365
+ { role: "user", content: task, timestamp: /* @__PURE__ */ new Date() }
4366
+ ];
4367
+ const subSystemPrompt = buildSubAgentSystemPrompt(task, ctx.systemPrompt);
4368
+ const toolDefs = subRegistry.getDefinitions();
4369
+ const extraMessages = [];
4370
+ const totalUsage = { inputTokens: 0, outputTokens: 0 };
4371
+ let finalContent = "";
4372
+ printSubAgentHeader(task, maxRounds);
4373
+ try {
4374
+ for (let round = 0; round < maxRounds; round++) {
4375
+ subExecutor.setRoundInfo(round + 1, maxRounds);
4376
+ const result = await ctx.provider.chatWithTools(
4377
+ {
4378
+ messages: subMessages,
4379
+ model: ctx.model,
4380
+ systemPrompt: subSystemPrompt,
4381
+ stream: false,
4382
+ temperature: ctx.modelParams.temperature,
4383
+ maxTokens: ctx.modelParams.maxTokens,
4384
+ timeout: ctx.modelParams.timeout,
4385
+ thinking: ctx.modelParams.thinking,
4386
+ ...extraMessages.length > 0 ? { _extraMessages: extraMessages } : {}
4387
+ },
4388
+ toolDefs
4389
+ );
4390
+ if (result.usage) {
4391
+ totalUsage.inputTokens += result.usage.inputTokens;
4392
+ totalUsage.outputTokens += result.usage.outputTokens;
4393
+ }
4394
+ if ("content" in result) {
4395
+ finalContent = result.content;
4396
+ break;
4397
+ }
4398
+ if (ctx.configManager) {
4399
+ googleSearchContext.configManager = ctx.configManager;
4400
+ }
4401
+ const toolResults = await subExecutor.executeAll(result.toolCalls);
4402
+ const reasoningContent = "reasoningContent" in result ? result.reasoningContent : void 0;
4403
+ const newMsgs = ctx.provider.buildToolResultMessages(
4404
+ result.toolCalls,
4405
+ toolResults,
4406
+ reasoningContent
4407
+ );
4408
+ extraMessages.push(...newMsgs);
4409
+ }
4410
+ if (!finalContent) {
4411
+ finalContent = `(Sub-agent reached maximum rounds (${maxRounds}) without producing a final response)`;
4412
+ }
4413
+ } catch (err) {
4414
+ const errMsg = err instanceof Error ? err.message : String(err);
4415
+ finalContent = `(Sub-agent error: ${errMsg})`;
4416
+ process.stderr.write(
4417
+ `
4418
+ [spawn_agent] Error in sub-agent loop: ${errMsg}
4419
+ `
4420
+ );
4421
+ }
4422
+ printSubAgentFooter(totalUsage);
4423
+ const lines = [
4424
+ "## Sub-Agent Result",
4425
+ "",
4426
+ finalContent,
4427
+ "",
4428
+ "---",
4429
+ `Token usage: ${totalUsage.inputTokens} input, ${totalUsage.outputTokens} output`
4430
+ ];
4431
+ return lines.join("\n");
4432
+ }
4433
+ };
4434
+
4073
4435
  // src/tools/registry.ts
4074
4436
  import { pathToFileURL } from "url";
4075
4437
  import { existsSync as existsSync11, mkdirSync as mkdirSync8, readdirSync as readdirSync5 } from "fs";
@@ -4093,6 +4455,7 @@ var ToolRegistry = class {
4093
4455
  this.register(askUserTool);
4094
4456
  this.register(writeTodosTool);
4095
4457
  this.register(googleSearchTool);
4458
+ this.register(spawnAgentTool);
4096
4459
  }
4097
4460
  register(tool) {
4098
4461
  this.tools.set(tool.definition.name, tool);
@@ -4100,6 +4463,10 @@ var ToolRegistry = class {
4100
4463
  get(name) {
4101
4464
  return this.tools.get(name);
4102
4465
  }
4466
+ /** 注销指定工具(子代理创建过滤后的工具集时使用) */
4467
+ unregister(name) {
4468
+ return this.tools.delete(name);
4469
+ }
4103
4470
  /** 返回所有工具的 schema,用于发送给 AI */
4104
4471
  getDefinitions() {
4105
4472
  return [...this.tools.values()].map((t) => t.definition);
@@ -4199,32 +4566,11 @@ var ToolRegistry = class {
4199
4566
  };
4200
4567
 
4201
4568
  // src/tools/executor.ts
4202
- import chalk7 from "chalk";
4569
+ import chalk8 from "chalk";
4203
4570
  import { existsSync as existsSync12, readFileSync as readFileSync8 } from "fs";
4204
4571
 
4205
- // src/tools/types.ts
4206
- function getDangerLevel(toolName, args) {
4207
- if (toolName.startsWith("mcp__")) return "safe";
4208
- if (toolName === "bash") {
4209
- const cmd = String(args["command"] ?? "");
4210
- if (/\brm\s+[^\n]*(?:-\w*[rRfF]\w*|--recursive|--force)\b/.test(cmd)) return "destructive";
4211
- if (/\brm\s+\S/.test(cmd)) return "destructive";
4212
- if (/\brmdir\b|\bformat\b|\bmkfs\b/.test(cmd)) return "destructive";
4213
- if (/\bRemove-Item\b.*(?:-Recurse|-Force)|\bri\s+.*-(?:Recurse|Force)\b|\brd\s+\/s\b|\brmdir\s+\/s\b/i.test(cmd)) return "destructive";
4214
- if (/\bdel\s+\S/.test(cmd)) return "destructive";
4215
- if (/\becho\b.*>>?|\btee\b|\bcp\b|\bmv\b/.test(cmd)) return "write";
4216
- if (/\bSet-Content\b|\bOut-File\b|\bAdd-Content\b|\bCopy-Item\b|\bMove-Item\b/i.test(cmd)) return "write";
4217
- return "safe";
4218
- }
4219
- if (toolName === "write_file") return "write";
4220
- if (toolName === "edit_file") return "write";
4221
- if (toolName === "save_last_response") return "write";
4222
- if (toolName === "read_file" || toolName === "list_dir" || toolName === "grep_files" || toolName === "glob_files" || toolName === "run_interactive" || toolName === "web_fetch" || toolName === "save_memory" || toolName === "ask_user" || toolName === "write_todos" || toolName === "google_search") return "safe";
4223
- return "write";
4224
- }
4225
-
4226
4572
  // src/tools/diff-utils.ts
4227
- import chalk6 from "chalk";
4573
+ import chalk7 from "chalk";
4228
4574
  function renderDiff(oldText, newText, opts = {}) {
4229
4575
  const contextLines = opts.contextLines ?? 3;
4230
4576
  const maxLines = opts.maxLines ?? 120;
@@ -4233,21 +4579,21 @@ function renderDiff(oldText, newText, opts = {}) {
4233
4579
  const newLines = newText.split("\n");
4234
4580
  const hunks = computeHunks(oldLines, newLines, contextLines);
4235
4581
  if (hunks.length === 0) {
4236
- return chalk6.dim(" (no changes)");
4582
+ return chalk7.dim(" (no changes)");
4237
4583
  }
4238
4584
  const output = [];
4239
4585
  if (filePath) {
4240
- output.push(chalk6.bold.white(`--- ${filePath} (before)`));
4241
- output.push(chalk6.bold.white(`+++ ${filePath} (after)`));
4586
+ output.push(chalk7.bold.white(`--- ${filePath} (before)`));
4587
+ output.push(chalk7.bold.white(`+++ ${filePath} (after)`));
4242
4588
  }
4243
4589
  let totalDisplayed = 0;
4244
4590
  for (const hunk of hunks) {
4245
4591
  if (totalDisplayed >= maxLines) {
4246
- output.push(chalk6.dim(` ... (diff truncated, too many changes)`));
4592
+ output.push(chalk7.dim(` ... (diff truncated, too many changes)`));
4247
4593
  break;
4248
4594
  }
4249
4595
  output.push(
4250
- chalk6.cyan(
4596
+ chalk7.cyan(
4251
4597
  `@@ -${hunk.oldStart + 1},${hunk.oldCount} +${hunk.newStart + 1},${hunk.newCount} @@`
4252
4598
  )
4253
4599
  );
@@ -4255,11 +4601,11 @@ function renderDiff(oldText, newText, opts = {}) {
4255
4601
  if (totalDisplayed >= maxLines) break;
4256
4602
  totalDisplayed++;
4257
4603
  if (line.type === "context") {
4258
- output.push(chalk6.dim(` ${line.text}`));
4604
+ output.push(chalk7.dim(` ${line.text}`));
4259
4605
  } else if (line.type === "remove") {
4260
- output.push(chalk6.red(`- ${line.text}`));
4606
+ output.push(chalk7.red(`- ${line.text}`));
4261
4607
  } else {
4262
- output.push(chalk6.green(`+ ${line.text}`));
4608
+ output.push(chalk7.green(`+ ${line.text}`));
4263
4609
  }
4264
4610
  }
4265
4611
  }
@@ -4377,11 +4723,11 @@ function simpleDiff(oldLines, newLines) {
4377
4723
  }
4378
4724
 
4379
4725
  // src/tools/executor.ts
4380
- var MAX_TOOL_OUTPUT_CHARS = 12e3;
4381
- function truncateOutput(content, toolName) {
4382
- if (content.length <= MAX_TOOL_OUTPUT_CHARS) return content;
4383
- const keepHead = Math.floor(MAX_TOOL_OUTPUT_CHARS * 0.7);
4384
- const keepTail = Math.floor(MAX_TOOL_OUTPUT_CHARS * 0.2);
4726
+ var MAX_TOOL_OUTPUT_CHARS2 = 12e3;
4727
+ function truncateOutput2(content, toolName) {
4728
+ if (content.length <= MAX_TOOL_OUTPUT_CHARS2) return content;
4729
+ const keepHead = Math.floor(MAX_TOOL_OUTPUT_CHARS2 * 0.7);
4730
+ const keepTail = Math.floor(MAX_TOOL_OUTPUT_CHARS2 * 0.2);
4385
4731
  const omitted = content.length - keepHead - keepTail;
4386
4732
  const lines = content.split("\n").length;
4387
4733
  const head = content.slice(0, keepHead);
@@ -4464,7 +4810,7 @@ var ToolExecutor = class {
4464
4810
  }
4465
4811
  try {
4466
4812
  const rawContent = await tool.execute(call.arguments);
4467
- const content = truncateOutput(rawContent, call.name);
4813
+ const content = truncateOutput2(rawContent, call.name);
4468
4814
  const wasTruncated = content !== rawContent;
4469
4815
  this.printToolResult(call.name, rawContent, false, wasTruncated);
4470
4816
  return { callId: call.id, content, isError: false };
@@ -4484,9 +4830,9 @@ var ToolExecutor = class {
4484
4830
  printToolCall(call) {
4485
4831
  const dangerLevel = getDangerLevel(call.name, call.arguments);
4486
4832
  console.log();
4487
- const icon = dangerLevel === "write" ? chalk7.yellow("\u270E Tool: ") : chalk7.bold.cyan("\u2699 Tool: ");
4488
- const roundBadge = this.totalRounds > 0 ? chalk7.dim(` [${this.round}/${this.totalRounds}]`) : "";
4489
- console.log(icon + chalk7.white(call.name) + roundBadge);
4833
+ const icon = dangerLevel === "write" ? chalk8.yellow("\u270E Tool: ") : chalk8.bold.cyan("\u2699 Tool: ");
4834
+ const roundBadge = this.totalRounds > 0 ? chalk8.dim(` [${this.round}/${this.totalRounds}]`) : "";
4835
+ console.log(icon + chalk8.white(call.name) + roundBadge);
4490
4836
  for (const [key, val] of Object.entries(call.arguments)) {
4491
4837
  let valStr;
4492
4838
  if (Array.isArray(val)) {
@@ -4497,7 +4843,7 @@ var ToolExecutor = class {
4497
4843
  } else {
4498
4844
  valStr = String(val);
4499
4845
  }
4500
- console.log(chalk7.gray(` ${key}: `) + chalk7.white(valStr));
4846
+ console.log(chalk8.gray(` ${key}: `) + chalk8.white(valStr));
4501
4847
  }
4502
4848
  }
4503
4849
  /**
@@ -4519,19 +4865,19 @@ var ToolExecutor = class {
4519
4865
  return;
4520
4866
  }
4521
4867
  if (oldContent === newContent) {
4522
- console.log(chalk7.dim(" (file content unchanged)"));
4868
+ console.log(chalk8.dim(" (file content unchanged)"));
4523
4869
  return;
4524
4870
  }
4525
4871
  const diff = renderDiff(oldContent, newContent, { filePath, contextLines: 3 });
4526
- console.log(chalk7.dim(" \u2500\u2500 diff preview \u2500\u2500"));
4872
+ console.log(chalk8.dim(" \u2500\u2500 diff preview \u2500\u2500"));
4527
4873
  console.log(diff);
4528
4874
  console.log();
4529
4875
  } else {
4530
4876
  const lines = newContent.split("\n");
4531
- const preview = lines.slice(0, 20).map((l) => chalk7.green(`+ ${l}`)).join("\n");
4532
- const more = lines.length > 20 ? chalk7.dim(`
4877
+ const preview = lines.slice(0, 20).map((l) => chalk8.green(`+ ${l}`)).join("\n");
4878
+ const more = lines.length > 20 ? chalk8.dim(`
4533
4879
  ... (+${lines.length - 20} more lines)`) : "";
4534
- console.log(chalk7.dim(" \u2500\u2500 new file preview \u2500\u2500"));
4880
+ console.log(chalk8.dim(" \u2500\u2500 new file preview \u2500\u2500"));
4535
4881
  console.log(preview + more);
4536
4882
  console.log();
4537
4883
  }
@@ -4546,17 +4892,17 @@ var ToolExecutor = class {
4546
4892
  String(newStr ?? ""),
4547
4893
  { filePath, contextLines: 2 }
4548
4894
  );
4549
- console.log(chalk7.dim(" \u2500\u2500 diff preview \u2500\u2500"));
4895
+ console.log(chalk8.dim(" \u2500\u2500 diff preview \u2500\u2500"));
4550
4896
  console.log(diff);
4551
4897
  console.log();
4552
4898
  } else if (call.arguments["insert_after_line"] !== void 0) {
4553
4899
  const line = Number(call.arguments["insert_after_line"]);
4554
4900
  const insertContent = String(call.arguments["insert_content"] ?? "");
4555
4901
  const insertLines = insertContent.split("\n");
4556
- const preview = insertLines.slice(0, 5).map((l) => chalk7.green(`+ ${l}`)).join("\n");
4557
- const more = insertLines.length > 5 ? chalk7.dim(`
4902
+ const preview = insertLines.slice(0, 5).map((l) => chalk8.green(`+ ${l}`)).join("\n");
4903
+ const more = insertLines.length > 5 ? chalk8.dim(`
4558
4904
  ... (+${insertLines.length - 5} more lines)`) : "";
4559
- console.log(chalk7.dim(` \u2500\u2500 insert after line ${line} \u2500\u2500`));
4905
+ console.log(chalk8.dim(` \u2500\u2500 insert after line ${line} \u2500\u2500`));
4560
4906
  console.log(preview + more);
4561
4907
  console.log();
4562
4908
  } else if (call.arguments["delete_from_line"] !== void 0) {
@@ -4570,10 +4916,10 @@ var ToolExecutor = class {
4570
4916
  }
4571
4917
  const fileLines = fileContent.split("\n");
4572
4918
  const deleted = fileLines.slice(from - 1, to);
4573
- const preview = deleted.slice(0, 5).map((l) => chalk7.red(`- ${l}`)).join("\n");
4574
- const more = deleted.length > 5 ? chalk7.dim(`
4919
+ const preview = deleted.slice(0, 5).map((l) => chalk8.red(`- ${l}`)).join("\n");
4920
+ const more = deleted.length > 5 ? chalk8.dim(`
4575
4921
  ... (-${deleted.length - 5} more lines)`) : "";
4576
- console.log(chalk7.dim(` \u2500\u2500 delete lines ${from}\u2013${to} \u2500\u2500`));
4922
+ console.log(chalk8.dim(` \u2500\u2500 delete lines ${from}\u2013${to} \u2500\u2500`));
4577
4923
  console.log(preview + more);
4578
4924
  console.log();
4579
4925
  }
@@ -4581,27 +4927,27 @@ var ToolExecutor = class {
4581
4927
  }
4582
4928
  printToolResult(name, content, isError, wasTruncated) {
4583
4929
  if (isError) {
4584
- console.log(chalk7.red(`\u26A0 ${name} error: `) + chalk7.gray(content.slice(0, 300)));
4930
+ console.log(chalk8.red(`\u26A0 ${name} error: `) + chalk8.gray(content.slice(0, 300)));
4585
4931
  } else {
4586
4932
  const lines = content.split("\n");
4587
4933
  const maxLines = name === "run_interactive" ? 40 : 8;
4588
4934
  const preview = lines.slice(0, maxLines).join("\n");
4589
- const moreLines = lines.length > maxLines ? chalk7.gray(`
4935
+ const moreLines = lines.length > maxLines ? chalk8.gray(`
4590
4936
  ... (${lines.length - maxLines} more lines)`) : "";
4591
- const truncatedNote = wasTruncated ? chalk7.yellow(`
4592
- \u26A1 \u8F93\u51FA\u5DF2\u622A\u65AD\u81F3 ${MAX_TOOL_OUTPUT_CHARS} \u5B57\u7B26\u540E\u8FD4\u56DE\u7ED9 AI`) : "";
4593
- console.log(chalk7.green("\u2713 Result: ") + chalk7.gray(preview) + moreLines + truncatedNote);
4937
+ const truncatedNote = wasTruncated ? chalk8.yellow(`
4938
+ \u26A1 \u8F93\u51FA\u5DF2\u622A\u65AD\u81F3 ${MAX_TOOL_OUTPUT_CHARS2} \u5B57\u7B26\u540E\u8FD4\u56DE\u7ED9 AI`) : "";
4939
+ console.log(chalk8.green("\u2713 Result: ") + chalk8.gray(preview) + moreLines + truncatedNote);
4594
4940
  }
4595
4941
  console.log();
4596
4942
  }
4597
4943
  confirm(call, level) {
4598
- const color = level === "destructive" ? chalk7.red : chalk7.yellow;
4944
+ const color = level === "destructive" ? chalk8.red : chalk8.yellow;
4599
4945
  const label = level === "destructive" ? "\u26A0 DESTRUCTIVE" : "\u270E Write";
4600
4946
  console.log();
4601
- console.log(color(`${label} operation: `) + chalk7.bold(call.name));
4947
+ console.log(color(`${label} operation: `) + chalk8.bold(call.name));
4602
4948
  for (const [key, val] of Object.entries(call.arguments)) {
4603
4949
  const valStr = typeof val === "string" && val.length > 200 ? val.slice(0, 200) + "..." : String(val);
4604
- console.log(chalk7.gray(` ${key}: `) + valStr);
4950
+ console.log(chalk8.gray(` ${key}: `) + valStr);
4605
4951
  }
4606
4952
  if (!this.rl) {
4607
4953
  process.stdout.write(color("No readline: auto-rejected.\n"));
@@ -4625,7 +4971,7 @@ var ToolExecutor = class {
4625
4971
  };
4626
4972
  const onLine = (line) => cleanup(line.trim().toLowerCase());
4627
4973
  this.cancelConfirmFn = () => {
4628
- process.stdout.write(chalk7.gray("\n(cancelled)\n"));
4974
+ process.stdout.write(chalk8.gray("\n(cancelled)\n"));
4629
4975
  cleanup("n");
4630
4976
  };
4631
4977
  rl.once("line", onLine);
@@ -4644,7 +4990,7 @@ var streamToFileContext = {
4644
4990
 
4645
4991
  // src/repl/setup-wizard.ts
4646
4992
  import { password, select, input } from "@inquirer/prompts";
4647
- import chalk8 from "chalk";
4993
+ import chalk9 from "chalk";
4648
4994
  var PROVIDERS = [
4649
4995
  { value: "claude", name: "Claude (Anthropic)" },
4650
4996
  { value: "gemini", name: "Gemini (Google)" },
@@ -4661,7 +5007,7 @@ var SetupWizard = class {
4661
5007
  this.config = config;
4662
5008
  }
4663
5009
  async runFirstRun() {
4664
- console.log(chalk8.bold.cyan("\nWelcome to ai-cli!\n"));
5010
+ console.log(chalk9.bold.cyan("\nWelcome to ai-cli!\n"));
4665
5011
  console.log("Let's set up your first AI provider.\n");
4666
5012
  try {
4667
5013
  const providerId = await select({
@@ -4671,19 +5017,19 @@ var SetupWizard = class {
4671
5017
  await this.setupProvider(providerId);
4672
5018
  this.config.set("defaultProvider", providerId);
4673
5019
  this.config.save();
4674
- console.log(chalk8.green("\nSetup complete! Starting ai-cli...\n"));
5020
+ console.log(chalk9.green("\nSetup complete! Starting ai-cli...\n"));
4675
5021
  return true;
4676
5022
  } catch {
4677
5023
  return false;
4678
5024
  }
4679
5025
  }
4680
5026
  async runFull() {
4681
- console.log(chalk8.bold.cyan("\nai-cli Configuration\n"));
5027
+ console.log(chalk9.bold.cyan("\nai-cli Configuration\n"));
4682
5028
  let running = true;
4683
5029
  while (running) {
4684
5030
  const currentProxy = this.config.get("proxy") ?? "";
4685
- const proxyStatus = currentProxy ? chalk8.green(`[${currentProxy}]`) : chalk8.gray("[\u672A\u914D\u7F6E]");
4686
- const googleKeyStatus = this.config.getApiKey("google-search") ? chalk8.green("[\u5DF2\u914D\u7F6E]") : chalk8.gray("[\u672A\u914D\u7F6E]");
5031
+ const proxyStatus = currentProxy ? chalk9.green(`[${currentProxy}]`) : chalk9.gray("[\u672A\u914D\u7F6E]");
5032
+ const googleKeyStatus = this.config.getApiKey("google-search") ? chalk9.green("[\u5DF2\u914D\u7F6E]") : chalk9.gray("[\u672A\u914D\u7F6E]");
4687
5033
  const action = await select({
4688
5034
  message: "What would you like to configure?",
4689
5035
  choices: [
@@ -4701,7 +5047,7 @@ var SetupWizard = class {
4701
5047
  } else if (action === "apikey") {
4702
5048
  const choicesWithStatus = PROVIDERS.map((p) => {
4703
5049
  const existingKey = this.config.getApiKey(p.value);
4704
- const status = existingKey ? chalk8.green(`[${maskKey(existingKey)}]`) : chalk8.gray("[\u672A\u914D\u7F6E]");
5050
+ const status = existingKey ? chalk9.green(`[${maskKey(existingKey)}]`) : chalk9.gray("[\u672A\u914D\u7F6E]");
4705
5051
  return { value: p.value, name: `${p.name} ${status}` };
4706
5052
  });
4707
5053
  const providerId = await select({
@@ -4716,7 +5062,7 @@ var SetupWizard = class {
4716
5062
  });
4717
5063
  this.config.set("defaultProvider", providerId);
4718
5064
  this.config.save();
4719
- console.log(chalk8.green(`Default provider set to: ${providerId}
5065
+ console.log(chalk9.green(`Default provider set to: ${providerId}
4720
5066
  `));
4721
5067
  } else {
4722
5068
  running = false;
@@ -4730,9 +5076,9 @@ var SetupWizard = class {
4730
5076
  console.log(`
4731
5077
  Managing ${displayName} API Key`);
4732
5078
  if (existingKey) {
4733
- console.log(chalk8.gray(` Current: ${maskKey(existingKey)}`));
5079
+ console.log(chalk9.gray(` Current: ${maskKey(existingKey)}`));
4734
5080
  } else {
4735
- console.log(chalk8.gray(" Current: (\u672A\u914D\u7F6E)"));
5081
+ console.log(chalk9.gray(" Current: (\u672A\u914D\u7F6E)"));
4736
5082
  }
4737
5083
  const choices = existingKey ? [
4738
5084
  { value: "keep", name: "\u4FDD\u6301\u4E0D\u53D8 (keep current key)" },
@@ -4744,7 +5090,7 @@ Managing ${displayName} API Key`);
4744
5090
  choices
4745
5091
  });
4746
5092
  if (action === "show" && existingKey) {
4747
- console.log(chalk8.yellow(`
5093
+ console.log(chalk9.yellow(`
4748
5094
  \u5B8C\u6574 Key: ${existingKey}
4749
5095
  `));
4750
5096
  const updateAfterShow = await select({
@@ -4763,15 +5109,15 @@ Managing ${displayName} API Key`);
4763
5109
  validate: (val) => val.length > 0 || "API key cannot be empty"
4764
5110
  });
4765
5111
  this.config.setApiKey(providerId, newKey);
4766
- console.log(chalk8.green(`API key saved for ${displayName}: ${maskKey(newKey)}
5112
+ console.log(chalk9.green(`API key saved for ${displayName}: ${maskKey(newKey)}
4767
5113
  `));
4768
5114
  }
4769
5115
  async setupProxy() {
4770
5116
  const current = this.config.get("proxy") ?? "";
4771
5117
  console.log("\nHTTP/HTTPS Proxy Configuration");
4772
- console.log(chalk8.gray(" Used for providers that require a proxy (e.g. Gemini in China)"));
5118
+ console.log(chalk9.gray(" Used for providers that require a proxy (e.g. Gemini in China)"));
4773
5119
  if (current) {
4774
- console.log(chalk8.gray(` Current: ${current}`));
5120
+ console.log(chalk9.gray(` Current: ${current}`));
4775
5121
  }
4776
5122
  const action = await select({
4777
5123
  message: "Action:",
@@ -4788,7 +5134,7 @@ Managing ${displayName} API Key`);
4788
5134
  if (action === "clear") {
4789
5135
  this.config.set("proxy", void 0);
4790
5136
  this.config.save();
4791
- console.log(chalk8.green("Proxy cleared.\n"));
5137
+ console.log(chalk9.green("Proxy cleared.\n"));
4792
5138
  return;
4793
5139
  }
4794
5140
  const proxyUrl = await input({
@@ -4804,21 +5150,21 @@ Managing ${displayName} API Key`);
4804
5150
  });
4805
5151
  this.config.set("proxy", proxyUrl);
4806
5152
  this.config.save();
4807
- console.log(chalk8.green(`Proxy saved: ${proxyUrl}
5153
+ console.log(chalk9.green(`Proxy saved: ${proxyUrl}
4808
5154
  `));
4809
5155
  }
4810
5156
  async setupGoogleSearch() {
4811
5157
  console.log("\nGoogle Custom Search Configuration");
4812
- console.log(chalk8.gray(" Enables AI to search the web via google_search tool"));
4813
- console.log(chalk8.gray(" Requires: API Key + Search Engine ID (cx)"));
4814
- console.log(chalk8.gray(" Get API Key: https://console.cloud.google.com/apis/credentials"));
4815
- console.log(chalk8.gray(" Get CX: https://programmablesearchengine.google.com/"));
5158
+ console.log(chalk9.gray(" Enables AI to search the web via google_search tool"));
5159
+ console.log(chalk9.gray(" Requires: API Key + Search Engine ID (cx)"));
5160
+ console.log(chalk9.gray(" Get API Key: https://console.cloud.google.com/apis/credentials"));
5161
+ console.log(chalk9.gray(" Get CX: https://programmablesearchengine.google.com/"));
4816
5162
  const existingKey = this.config.getApiKey("google-search");
4817
5163
  if (existingKey) {
4818
- console.log(chalk8.gray(`
5164
+ console.log(chalk9.gray(`
4819
5165
  API Key: ${maskKey(existingKey)}`));
4820
5166
  } else {
4821
- console.log(chalk8.gray("\n API Key: (\u672A\u914D\u7F6E)"));
5167
+ console.log(chalk9.gray("\n API Key: (\u672A\u914D\u7F6E)"));
4822
5168
  }
4823
5169
  const keyAction = await select({
4824
5170
  message: "API Key:",
@@ -4836,14 +5182,14 @@ Managing ${displayName} API Key`);
4836
5182
  validate: (val) => val.length > 0 || "API key cannot be empty"
4837
5183
  });
4838
5184
  this.config.setApiKey("google-search", newKey);
4839
- console.log(chalk8.green(` API Key saved: ${maskKey(newKey)}`));
5185
+ console.log(chalk9.green(` API Key saved: ${maskKey(newKey)}`));
4840
5186
  }
4841
5187
  const existingCx = this.config.get("googleSearchEngineId") ?? "";
4842
5188
  if (existingCx) {
4843
- console.log(chalk8.gray(`
5189
+ console.log(chalk9.gray(`
4844
5190
  Search Engine ID: ${existingCx}`));
4845
5191
  } else {
4846
- console.log(chalk8.gray("\n Search Engine ID: (\u672A\u914D\u7F6E)"));
5192
+ console.log(chalk9.gray("\n Search Engine ID: (\u672A\u914D\u7F6E)"));
4847
5193
  }
4848
5194
  const cxAction = await select({
4849
5195
  message: "Search Engine ID (cx):",
@@ -4863,7 +5209,7 @@ Managing ${displayName} API Key`);
4863
5209
  });
4864
5210
  this.config.set("googleSearchEngineId", newCx);
4865
5211
  this.config.save();
4866
- console.log(chalk8.green(` Search Engine ID saved: ${newCx}`));
5212
+ console.log(chalk9.green(` Search Engine ID saved: ${newCx}`));
4867
5213
  }
4868
5214
  console.log();
4869
5215
  }
@@ -5832,7 +6178,7 @@ ${response.content.trim()}
5832
6178
  }
5833
6179
  }
5834
6180
  refreshPrompt() {
5835
- const promptStr = this.planMode ? chalk9.green(`[${this.currentProvider}]`) + chalk9.yellow("[PLAN]") + chalk9.white(" > ") : chalk9.green(`[${this.currentProvider}]`) + chalk9.white(" > ");
6181
+ const promptStr = this.planMode ? chalk10.green(`[${this.currentProvider}]`) + chalk10.yellow("[PLAN]") + chalk10.white(" > ") : chalk10.green(`[${this.currentProvider}]`) + chalk10.white(" > ");
5836
6182
  this.rl.setPrompt(promptStr);
5837
6183
  }
5838
6184
  showPrompt() {
@@ -5867,13 +6213,13 @@ ${response.content.trim()}
5867
6213
  if (layers.length === 1) {
5868
6214
  const l = layers[0];
5869
6215
  process.stdout.write(
5870
- chalk9.dim(` \u{1F4C4} Context loaded: ${l.filePath} (${l.charCount} chars)
6216
+ chalk10.dim(` \u{1F4C4} Context loaded: ${l.filePath} (${l.charCount} chars)
5871
6217
  `)
5872
6218
  );
5873
6219
  } else {
5874
6220
  const summary = layers.map((l) => `${l.level}: ${l.charCount}`).join(", ");
5875
6221
  process.stdout.write(
5876
- chalk9.dim(` \u{1F4C4} Context loaded: ${layers.length} layers (${summary} chars)
6222
+ chalk10.dim(` \u{1F4C4} Context loaded: ${layers.length} layers (${summary} chars)
5877
6223
  `)
5878
6224
  );
5879
6225
  }
@@ -5881,25 +6227,25 @@ ${response.content.trim()}
5881
6227
  const memoryInfo = this.loadMemoryContent();
5882
6228
  if (memoryInfo) {
5883
6229
  process.stdout.write(
5884
- chalk9.dim(` \u{1F4DD} Memory loaded: ${memoryInfo.entryCount} entries (${memoryInfo.content.length} chars)
6230
+ chalk10.dim(` \u{1F4DD} Memory loaded: ${memoryInfo.entryCount} entries (${memoryInfo.content.length} chars)
5885
6231
  `)
5886
6232
  );
5887
6233
  }
5888
6234
  const devStateContent = loadDevState();
5889
6235
  if (devStateContent) {
5890
6236
  process.stdout.write(
5891
- chalk9.dim(` \u{1F504} Dev state handoff: loaded (${devStateContent.length} chars)
6237
+ chalk10.dim(` \u{1F504} Dev state handoff: loaded (${devStateContent.length} chars)
5892
6238
  `)
5893
6239
  );
5894
6240
  }
5895
6241
  if (gitCtx) {
5896
- const statusSummary = gitCtx.stagedFiles.length + gitCtx.changedFiles.length > 0 ? chalk9.yellow(` (${gitCtx.stagedFiles.length} staged, ${gitCtx.changedFiles.length} modified)`) : chalk9.dim(" (clean)");
6242
+ const statusSummary = gitCtx.stagedFiles.length + gitCtx.changedFiles.length > 0 ? chalk10.yellow(` (${gitCtx.stagedFiles.length} staged, ${gitCtx.changedFiles.length} modified)`) : chalk10.dim(" (clean)");
5897
6243
  process.stdout.write(
5898
- chalk9.dim(` \u{1F500} Git branch: `) + chalk9.cyan(gitCtx.branch) + statusSummary + "\n"
6244
+ chalk10.dim(` \u{1F500} Git branch: `) + chalk10.cyan(gitCtx.branch) + statusSummary + "\n"
5899
6245
  );
5900
6246
  }
5901
6247
  if (pluginCount > 0) {
5902
- process.stdout.write(chalk9.dim(` \u{1F50C} Plugins loaded: ${pluginCount} tool(s) from plugins/
6248
+ process.stdout.write(chalk10.dim(` \u{1F50C} Plugins loaded: ${pluginCount} tool(s) from plugins/
5903
6249
  `));
5904
6250
  }
5905
6251
  const mcpServers = this.config.get("mcpServers");
@@ -5914,7 +6260,7 @@ ${response.content.trim()}
5914
6260
  const totalTools = this.mcpManager.getTotalToolCount();
5915
6261
  if (connectedCount > 0) {
5916
6262
  process.stdout.write(
5917
- chalk9.dim(` \u{1F50C} MCP: ${connectedCount} server(s), ${totalTools} tool(s)
6263
+ chalk10.dim(` \u{1F50C} MCP: ${connectedCount} server(s), ${totalTools} tool(s)
5918
6264
  `)
5919
6265
  );
5920
6266
  }
@@ -5982,13 +6328,13 @@ ${response.content.trim()}
5982
6328
  const { parts, hasImage, refs } = parseAtReferences(userInput, process.cwd());
5983
6329
  for (const ref of refs) {
5984
6330
  if (ref.type === "notfound") {
5985
- process.stdout.write(chalk9.yellow(` \u26A0 File not found: ${ref.path}
6331
+ process.stdout.write(chalk10.yellow(` \u26A0 File not found: ${ref.path}
5986
6332
  `));
5987
6333
  } else if (ref.type === "image") {
5988
- process.stdout.write(chalk9.dim(` \u{1F4CE} Image: ${ref.path}
6334
+ process.stdout.write(chalk10.dim(` \u{1F4CE} Image: ${ref.path}
5989
6335
  `));
5990
6336
  } else {
5991
- process.stdout.write(chalk9.dim(` \u{1F4C4} File: ${ref.path}
6337
+ process.stdout.write(chalk10.dim(` \u{1F4C4} File: ${ref.path}
5992
6338
  `));
5993
6339
  }
5994
6340
  }
@@ -5997,13 +6343,13 @@ ${response.content.trim()}
5997
6343
  const visionHint = this.getVisionModelHint();
5998
6344
  if (visionHint) {
5999
6345
  process.stdout.write(
6000
- chalk9.yellow(` \u2716 Vision not supported \u2013 ${visionHint}
6346
+ chalk10.yellow(` \u2716 Vision not supported \u2013 ${visionHint}
6001
6347
  `)
6002
6348
  );
6003
6349
  return;
6004
6350
  }
6005
6351
  process.stdout.write(
6006
- chalk9.dim(` \u{1F5BC} Vision request \u2013 sending image to ${this.currentProvider}
6352
+ chalk10.dim(` \u{1F5BC} Vision request \u2013 sending image to ${this.currentProvider}
6007
6353
  `)
6008
6354
  );
6009
6355
  }
@@ -6159,29 +6505,8 @@ ${response.content.trim()}
6159
6505
  }
6160
6506
  if ("content" in result) {
6161
6507
  spinner.stop();
6162
- let finalContent;
6163
- if (useStreaming) {
6164
- const stream = provider.chatStream({
6165
- messages: apiMessages,
6166
- model: this.currentModel,
6167
- systemPrompt,
6168
- stream: true,
6169
- temperature: modelParams.temperature,
6170
- maxTokens: modelParams.maxTokens,
6171
- timeout: modelParams.timeout,
6172
- thinking: modelParams.thinking,
6173
- ...extraMessages.length > 0 ? { _extraMessages: extraMessages } : {}
6174
- });
6175
- const { content: streamContent, usage: streamUsage } = await this.renderer.renderStream(stream);
6176
- finalContent = streamContent;
6177
- if (streamUsage) {
6178
- roundUsage.inputTokens += streamUsage.inputTokens;
6179
- roundUsage.outputTokens += streamUsage.outputTokens;
6180
- }
6181
- } else {
6182
- this.renderer.renderResponse(result.content);
6183
- finalContent = result.content;
6184
- }
6508
+ const finalContent = result.content;
6509
+ this.renderer.renderResponse(finalContent);
6185
6510
  lastResponseStore.content = finalContent;
6186
6511
  session.addMessage({
6187
6512
  role: "assistant",
@@ -6257,6 +6582,11 @@ ${response.content.trim()}
6257
6582
  streamToFileContext.extraMessages = extraMessages;
6258
6583
  streamToFileContext.temperature = modelParams.temperature;
6259
6584
  streamToFileContext.timeout = modelParams.timeout;
6585
+ spawnAgentContext.provider = provider;
6586
+ spawnAgentContext.model = this.currentModel;
6587
+ spawnAgentContext.systemPrompt = systemPrompt;
6588
+ spawnAgentContext.modelParams = modelParams;
6589
+ spawnAgentContext.configManager = this.config;
6260
6590
  const toolResults = await this.toolExecutor.executeAll(result.toolCalls);
6261
6591
  const reasoningContent = "reasoningContent" in result ? result.reasoningContent : void 0;
6262
6592
  const newMsgs = provider.buildToolResultMessages(result.toolCalls, toolResults, reasoningContent);
@@ -6370,7 +6700,7 @@ ${response.content.trim()}
6370
6700
  this.mcpManager?.closeAll().catch(() => {
6371
6701
  });
6372
6702
  this.rl.close();
6373
- console.log(chalk9.gray("\nGoodbye!"));
6703
+ console.log(chalk10.gray("\nGoodbye!"));
6374
6704
  process.exit(0);
6375
6705
  }
6376
6706
  };
@@ -6378,6 +6708,11 @@ ${response.content.trim()}
6378
6708
  // src/core/event-bus.ts
6379
6709
  import { EventEmitter } from "events";
6380
6710
  var EventBus = class extends EventEmitter {
6711
+ constructor() {
6712
+ super();
6713
+ this.on("app.error", () => {
6714
+ });
6715
+ }
6381
6716
  emit(event, data) {
6382
6717
  return super.emit(event, data);
6383
6718
  }
@@ -6594,11 +6929,11 @@ async function startRepl(options) {
6594
6929
  }
6595
6930
  }
6596
6931
  if (options.provider) {
6597
- config.set("defaultProvider", options.provider);
6932
+ config.setTransient("defaultProvider", options.provider);
6598
6933
  }
6599
6934
  if (options.stream === false) {
6600
6935
  const ui = config.get("ui");
6601
- config.set("ui", { ...ui, streaming: false });
6936
+ config.setTransient("ui", { ...ui, streaming: false });
6602
6937
  }
6603
6938
  const events = new EventBus();
6604
6939
  const providers = new ProviderRegistry();
@@ -6629,7 +6964,7 @@ Provider '${options.provider}' is not configured. Run: ai-cli config
6629
6964
  if (options.model) {
6630
6965
  const defaultModels = config.get("defaultModels");
6631
6966
  const provider = options.provider ?? config.getDefaultProvider();
6632
- config.set("defaultModels", { ...defaultModels, [provider]: options.model });
6967
+ config.setTransient("defaultModels", { ...defaultModels, [provider]: options.model });
6633
6968
  }
6634
6969
  process.on("SIGTERM", async () => {
6635
6970
  await sessions.save();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jinzd-ai-cli",
3
- "version": "0.1.21",
3
+ "version": "0.1.22",
4
4
  "description": "Cross-platform REPL-style AI CLI with multi-provider support",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",