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.
- package/CLAUDE.md +40 -3
- package/dist/index.js +485 -150
- 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(注册全部内置工具,共
|
|
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
|
-
|
|
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
|
-
- [
|
|
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.
|
|
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
|
|
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:
|
|
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
|
|
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
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
if (
|
|
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:
|
|
1383
|
-
outputTokens:
|
|
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 =
|
|
1387
|
-
|
|
1388
|
-
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
4582
|
+
return chalk7.dim(" (no changes)");
|
|
4237
4583
|
}
|
|
4238
4584
|
const output = [];
|
|
4239
4585
|
if (filePath) {
|
|
4240
|
-
output.push(
|
|
4241
|
-
output.push(
|
|
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(
|
|
4592
|
+
output.push(chalk7.dim(` ... (diff truncated, too many changes)`));
|
|
4247
4593
|
break;
|
|
4248
4594
|
}
|
|
4249
4595
|
output.push(
|
|
4250
|
-
|
|
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(
|
|
4604
|
+
output.push(chalk7.dim(` ${line.text}`));
|
|
4259
4605
|
} else if (line.type === "remove") {
|
|
4260
|
-
output.push(
|
|
4606
|
+
output.push(chalk7.red(`- ${line.text}`));
|
|
4261
4607
|
} else {
|
|
4262
|
-
output.push(
|
|
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
|
|
4381
|
-
function
|
|
4382
|
-
if (content.length <=
|
|
4383
|
-
const keepHead = Math.floor(
|
|
4384
|
-
const keepTail = Math.floor(
|
|
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 =
|
|
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" ?
|
|
4488
|
-
const roundBadge = this.totalRounds > 0 ?
|
|
4489
|
-
console.log(icon +
|
|
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(
|
|
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(
|
|
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(
|
|
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) =>
|
|
4532
|
-
const more = lines.length > 20 ?
|
|
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(
|
|
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(
|
|
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) =>
|
|
4557
|
-
const more = insertLines.length > 5 ?
|
|
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(
|
|
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) =>
|
|
4574
|
-
const more = deleted.length > 5 ?
|
|
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(
|
|
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(
|
|
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 ?
|
|
4935
|
+
const moreLines = lines.length > maxLines ? chalk8.gray(`
|
|
4590
4936
|
... (${lines.length - maxLines} more lines)`) : "";
|
|
4591
|
-
const truncatedNote = wasTruncated ?
|
|
4592
|
-
\u26A1 \u8F93\u51FA\u5DF2\u622A\u65AD\u81F3 ${
|
|
4593
|
-
console.log(
|
|
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" ?
|
|
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: `) +
|
|
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(
|
|
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(
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
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 ?
|
|
4686
|
-
const googleKeyStatus = this.config.getApiKey("google-search") ?
|
|
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 ?
|
|
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(
|
|
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(
|
|
5079
|
+
console.log(chalk9.gray(` Current: ${maskKey(existingKey)}`));
|
|
4734
5080
|
} else {
|
|
4735
|
-
console.log(
|
|
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(
|
|
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(
|
|
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(
|
|
5118
|
+
console.log(chalk9.gray(" Used for providers that require a proxy (e.g. Gemini in China)"));
|
|
4773
5119
|
if (current) {
|
|
4774
|
-
console.log(
|
|
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(
|
|
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(
|
|
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(
|
|
4813
|
-
console.log(
|
|
4814
|
-
console.log(
|
|
4815
|
-
console.log(
|
|
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(
|
|
5164
|
+
console.log(chalk9.gray(`
|
|
4819
5165
|
API Key: ${maskKey(existingKey)}`));
|
|
4820
5166
|
} else {
|
|
4821
|
-
console.log(
|
|
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(
|
|
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(
|
|
5189
|
+
console.log(chalk9.gray(`
|
|
4844
5190
|
Search Engine ID: ${existingCx}`));
|
|
4845
5191
|
} else {
|
|
4846
|
-
console.log(
|
|
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(
|
|
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 ?
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 ?
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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(
|
|
6334
|
+
process.stdout.write(chalk10.dim(` \u{1F4CE} Image: ${ref.path}
|
|
5989
6335
|
`));
|
|
5990
6336
|
} else {
|
|
5991
|
-
process.stdout.write(
|
|
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
|
-
|
|
6346
|
+
chalk10.yellow(` \u2716 Vision not supported \u2013 ${visionHint}
|
|
6001
6347
|
`)
|
|
6002
6348
|
);
|
|
6003
6349
|
return;
|
|
6004
6350
|
}
|
|
6005
6351
|
process.stdout.write(
|
|
6006
|
-
|
|
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
|
-
|
|
6163
|
-
|
|
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(
|
|
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.
|
|
6932
|
+
config.setTransient("defaultProvider", options.provider);
|
|
6598
6933
|
}
|
|
6599
6934
|
if (options.stream === false) {
|
|
6600
6935
|
const ui = config.get("ui");
|
|
6601
|
-
config.
|
|
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.
|
|
6967
|
+
config.setTransient("defaultModels", { ...defaultModels, [provider]: options.model });
|
|
6633
6968
|
}
|
|
6634
6969
|
process.on("SIGTERM", async () => {
|
|
6635
6970
|
await sessions.save();
|