zapmyco 0.14.0 → 0.16.0
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/dist/cli/index.mjs +1565 -398
- package/dist/cli/index.mjs.map +1 -1
- package/dist/index.d.mts +187 -1771
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/{loader-BD5Odmr5.mjs → loader-CWzMinwj.mjs} +1142 -257
- package/dist/loader-CWzMinwj.mjs.map +1 -0
- package/package.json +7 -4
- package/dist/loader-BD5Odmr5.mjs.map +0 -1
package/dist/cli/index.mjs
CHANGED
|
@@ -1,29 +1,30 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { A as configureLogger,
|
|
2
|
+
import { A as configureLogger, D as eventBus, G as SESSION_DIR_NAME, K as VERSION, L as WebError, O as DEFAULT_COMPACTION_CONFIG, V as loadSkills, W as APP_NAME, d as SubAgentManager, f as SecurityBlockedError, g as runWithToolGuardContext, h as getToolGuardContext, i as AgentLlmFacade, j as logger, m as createToolInfoResolver, n as loadConfig, p as ToolGuard, q as __require, t as HOME_CONFIG_PATH, v as createLlmBasedAgent, z as ZapmycoErrorCode } from "../loader-CWzMinwj.mjs";
|
|
3
3
|
import { createHash, randomBytes } from "node:crypto";
|
|
4
4
|
import { appendFileSync, existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, renameSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
5
|
-
import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
|
|
5
|
+
import { mkdir, readFile, readdir, rename, stat, writeFile } from "node:fs/promises";
|
|
6
6
|
import * as os from "node:os";
|
|
7
7
|
import { homedir, tmpdir } from "node:os";
|
|
8
8
|
import * as path from "node:path";
|
|
9
9
|
import { dirname, isAbsolute, join, normalize, relative, resolve } from "node:path";
|
|
10
10
|
import { EventEmitter } from "node:events";
|
|
11
|
-
import { getModels, getProviders } from "@earendil-works/pi-ai";
|
|
12
11
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
13
12
|
import chalk, { Chalk } from "chalk";
|
|
14
13
|
import { Command } from "commander";
|
|
15
14
|
import { execFile, spawn, spawnSync } from "node:child_process";
|
|
16
|
-
import { CombinedAutocompleteProvider, Container, Editor, Input, Key, ProcessTerminal, SelectList, TUI, getKeybindings, matchesKey, truncateToWidth, wrapTextWithAnsi } from "@
|
|
15
|
+
import { CombinedAutocompleteProvider, Container, Editor, Input, Key, ProcessTerminal, SelectList, TUI, getKeybindings, matchesKey, truncateToWidth, wrapTextWithAnsi } from "@earendil-works/pi-tui";
|
|
17
16
|
import i18next from "i18next";
|
|
18
17
|
import TurndownService from "turndown";
|
|
19
18
|
import { lookup } from "node:dns/promises";
|
|
20
19
|
import { promisify } from "node:util";
|
|
20
|
+
import chokidar from "chokidar";
|
|
21
21
|
import { marked } from "marked";
|
|
22
|
+
import "gray-matter";
|
|
22
23
|
import { Client } from "@modelcontextprotocol/sdk/client";
|
|
23
24
|
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
24
25
|
|
|
25
26
|
//#region src/cli/repl/command-registry.ts
|
|
26
|
-
const log$
|
|
27
|
+
const log$25 = logger.child("repl:command-registry");
|
|
27
28
|
/**
|
|
28
29
|
* 命令注册表
|
|
29
30
|
*/
|
|
@@ -39,7 +40,7 @@ var CommandRegistry = class {
|
|
|
39
40
|
*/
|
|
40
41
|
register(cmd) {
|
|
41
42
|
const canonicalName = cmd.name.toLowerCase();
|
|
42
|
-
if (this.commands.has(canonicalName)) log$
|
|
43
|
+
if (this.commands.has(canonicalName)) log$25.warn(`命令 "${canonicalName}" 已存在,将被覆盖`);
|
|
43
44
|
this.commands.set(canonicalName, cmd);
|
|
44
45
|
for (const alias of cmd.aliases) {
|
|
45
46
|
const lowerAlias = alias.toLowerCase();
|
|
@@ -63,11 +64,34 @@ var CommandRegistry = class {
|
|
|
63
64
|
return Array.from(this.commands.values());
|
|
64
65
|
}
|
|
65
66
|
/**
|
|
67
|
+
* 注销单个命令
|
|
68
|
+
*/
|
|
69
|
+
unregister(name) {
|
|
70
|
+
const lowerName = name.toLowerCase();
|
|
71
|
+
const cmd = this.commands.get(lowerName);
|
|
72
|
+
if (!cmd) return false;
|
|
73
|
+
for (const alias of cmd.aliases) this.aliasMap.delete(alias.toLowerCase());
|
|
74
|
+
return this.commands.delete(lowerName);
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* 注销所有指定来源的命令
|
|
78
|
+
*
|
|
79
|
+
* @returns 被注销的命令名称列表
|
|
80
|
+
*/
|
|
81
|
+
unregisterBySource(source) {
|
|
82
|
+
const removed = [];
|
|
83
|
+
for (const [name, cmd] of this.commands) if (cmd.source === source) {
|
|
84
|
+
this.unregister(name);
|
|
85
|
+
removed.push(name);
|
|
86
|
+
}
|
|
87
|
+
return removed;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
66
90
|
* 分发并执行命令
|
|
67
91
|
*/
|
|
68
92
|
async dispatch(parsed) {
|
|
69
93
|
if (parsed.kind !== "command") {
|
|
70
|
-
log$
|
|
94
|
+
log$25.warn("dispatch 收到了非 command 类型的输入");
|
|
71
95
|
return;
|
|
72
96
|
}
|
|
73
97
|
const cmd = this.getCommand(parsed.name);
|
|
@@ -79,7 +103,7 @@ var CommandRegistry = class {
|
|
|
79
103
|
await cmd.handler(parsed.args, this.session);
|
|
80
104
|
} catch (error) {
|
|
81
105
|
const message = error instanceof Error ? error.message : String(error);
|
|
82
|
-
log$
|
|
106
|
+
log$25.error(`命令 /${cmd.name} 执行出错`, {}, error);
|
|
83
107
|
console.log(`\n 命令执行出错: ${message}\n`);
|
|
84
108
|
}
|
|
85
109
|
}
|
|
@@ -342,7 +366,7 @@ function formatAgentInstanceTree(instances, options = {}) {
|
|
|
342
366
|
for (const inst of instances) {
|
|
343
367
|
const parentKey = inst.parentInstanceId;
|
|
344
368
|
if (!childrenMap.has(parentKey)) childrenMap.set(parentKey, []);
|
|
345
|
-
childrenMap.get(parentKey)
|
|
369
|
+
childrenMap.get(parentKey)?.push(inst);
|
|
346
370
|
}
|
|
347
371
|
for (let i = 0; i < roots.length; i++) {
|
|
348
372
|
const root = roots[i];
|
|
@@ -446,6 +470,49 @@ const noColor = {
|
|
|
446
470
|
reset: ""
|
|
447
471
|
};
|
|
448
472
|
|
|
473
|
+
//#endregion
|
|
474
|
+
//#region src/core/agent-team/types.ts
|
|
475
|
+
/**
|
|
476
|
+
* Agent 默认安全工具集
|
|
477
|
+
*
|
|
478
|
+
* 只读和搜索工具,不含任何可能产生副作用的操作。
|
|
479
|
+
* 子 Agent 默认使用此集合。
|
|
480
|
+
*/
|
|
481
|
+
const AGENT_SAFE_TOOLS = [
|
|
482
|
+
"ReadFile",
|
|
483
|
+
"Glob",
|
|
484
|
+
"Grep",
|
|
485
|
+
"WebFetch",
|
|
486
|
+
"WebSearch",
|
|
487
|
+
"GetCurrentTime",
|
|
488
|
+
"GetWorkdirInfo"
|
|
489
|
+
];
|
|
490
|
+
/**
|
|
491
|
+
* Agent 标准工具集
|
|
492
|
+
*
|
|
493
|
+
* safe + 文件写入 + Shell 执行。
|
|
494
|
+
*/
|
|
495
|
+
const AGENT_STANDARD_TOOLS = [
|
|
496
|
+
...AGENT_SAFE_TOOLS,
|
|
497
|
+
"WriteFile",
|
|
498
|
+
"EditFile",
|
|
499
|
+
"Exec",
|
|
500
|
+
"Process",
|
|
501
|
+
"TaskManage",
|
|
502
|
+
"Memory"
|
|
503
|
+
];
|
|
504
|
+
/**
|
|
505
|
+
* Coordinator 专用工具集
|
|
506
|
+
*
|
|
507
|
+
* 借鉴 Claude Code:Coordinator 只保留编排相关工具,
|
|
508
|
+
* 强制专注于任务分解和结果整合。
|
|
509
|
+
*/
|
|
510
|
+
const COORDINATOR_TOOLS = [
|
|
511
|
+
"AgentTool",
|
|
512
|
+
"SendMessage",
|
|
513
|
+
"TaskStop"
|
|
514
|
+
];
|
|
515
|
+
|
|
449
516
|
//#endregion
|
|
450
517
|
//#region src/core/agent-team/agent-instance-manager.ts
|
|
451
518
|
/**
|
|
@@ -456,7 +523,7 @@ const noColor = {
|
|
|
456
523
|
*
|
|
457
524
|
* @module core/agent-team
|
|
458
525
|
*/
|
|
459
|
-
const log$
|
|
526
|
+
const log$24 = logger.child("agent-instance-manager");
|
|
460
527
|
/**
|
|
461
528
|
* Agent 实例状态转换表
|
|
462
529
|
*
|
|
@@ -514,14 +581,16 @@ var AgentInstanceManager = class extends EventEmitter {
|
|
|
514
581
|
agent,
|
|
515
582
|
inbox: [],
|
|
516
583
|
task,
|
|
517
|
-
createdAt: Date.now()
|
|
584
|
+
createdAt: Date.now(),
|
|
585
|
+
toolCallHistory: [],
|
|
586
|
+
toolCallGroups: []
|
|
518
587
|
};
|
|
519
588
|
this.instances.set(instance.instanceId, instance);
|
|
520
589
|
if (parentInstanceId) {
|
|
521
590
|
const parent = this.instances.get(parentInstanceId);
|
|
522
591
|
if (parent) parent.childInstanceIds.push(instance.instanceId);
|
|
523
592
|
}
|
|
524
|
-
log$
|
|
593
|
+
log$24.debug("注册 Agent 实例", {
|
|
525
594
|
instanceId: instance.instanceId,
|
|
526
595
|
typeId: definition.typeId,
|
|
527
596
|
depth,
|
|
@@ -544,12 +613,12 @@ var AgentInstanceManager = class extends EventEmitter {
|
|
|
544
613
|
transition(instanceId, newStatus) {
|
|
545
614
|
const instance = this.instances.get(instanceId);
|
|
546
615
|
if (!instance) {
|
|
547
|
-
log$
|
|
616
|
+
log$24.warn("状态转换失败:实例不存在", { instanceId });
|
|
548
617
|
return false;
|
|
549
618
|
}
|
|
550
619
|
const allowed = VALID_TRANSITIONS[instance.status];
|
|
551
620
|
if (!allowed.includes(newStatus)) {
|
|
552
|
-
log$
|
|
621
|
+
log$24.warn("状态转换拒绝", {
|
|
553
622
|
instanceId,
|
|
554
623
|
from: instance.status,
|
|
555
624
|
to: newStatus,
|
|
@@ -559,7 +628,7 @@ var AgentInstanceManager = class extends EventEmitter {
|
|
|
559
628
|
}
|
|
560
629
|
const oldStatus = instance.status;
|
|
561
630
|
instance.status = newStatus;
|
|
562
|
-
log$
|
|
631
|
+
log$24.debug("Agent 实例状态转换", {
|
|
563
632
|
instanceId,
|
|
564
633
|
typeId: instance.typeId,
|
|
565
634
|
from: oldStatus,
|
|
@@ -598,6 +667,34 @@ var AgentInstanceManager = class extends EventEmitter {
|
|
|
598
667
|
return this.instances.get(instanceId)?.currentActivity;
|
|
599
668
|
}
|
|
600
669
|
/**
|
|
670
|
+
* 记录工具调用事件到实例历史
|
|
671
|
+
*
|
|
672
|
+
* 将工具调用记录推入 toolCallHistory(ring-buffer 风格),
|
|
673
|
+
* 同步更新 currentActivity,并发射 instance:toolcall 事件。
|
|
674
|
+
*
|
|
675
|
+
* @param instanceId - 实例 ID
|
|
676
|
+
* @param record - 工具调用记录
|
|
677
|
+
*/
|
|
678
|
+
recordToolCall(instanceId, record) {
|
|
679
|
+
const instance = this.instances.get(instanceId);
|
|
680
|
+
if (!instance) return;
|
|
681
|
+
instance.toolCallHistory.push(record);
|
|
682
|
+
if (instance.toolCallHistory.length > 100) instance.toolCallHistory = instance.toolCallHistory.slice(-100);
|
|
683
|
+
instance.toolCallGroups = [];
|
|
684
|
+
const runningCount = instance.toolCallHistory.filter((t) => t.status === "running" || t.status === "completed").length;
|
|
685
|
+
instance.currentActivity = {
|
|
686
|
+
toolName: record.toolName,
|
|
687
|
+
toolUses: runningCount,
|
|
688
|
+
args: record.argsDisplay,
|
|
689
|
+
startedAt: record.startedAt
|
|
690
|
+
};
|
|
691
|
+
this.emit("instance:toolcall", {
|
|
692
|
+
instanceId,
|
|
693
|
+
typeId: instance.typeId,
|
|
694
|
+
record
|
|
695
|
+
});
|
|
696
|
+
}
|
|
697
|
+
/**
|
|
601
698
|
* 获取指定实例
|
|
602
699
|
*
|
|
603
700
|
* @param instanceId - 实例 ID
|
|
@@ -690,7 +787,7 @@ var AgentInstanceManager = class extends EventEmitter {
|
|
|
690
787
|
instance.agent.removeAllListeners();
|
|
691
788
|
instance.agent.systemPromptOverride = null;
|
|
692
789
|
this.instances.delete(instanceId);
|
|
693
|
-
log$
|
|
790
|
+
log$24.debug("Agent 实例已清理", { instanceId });
|
|
694
791
|
}
|
|
695
792
|
/**
|
|
696
793
|
* 清理所有终态的实例
|
|
@@ -1141,7 +1238,7 @@ const BUILTIN_AGENT_TYPES = [
|
|
|
1141
1238
|
*
|
|
1142
1239
|
* @module core/agent-team
|
|
1143
1240
|
*/
|
|
1144
|
-
const log$
|
|
1241
|
+
const log$23 = logger.child("agent-type-registry");
|
|
1145
1242
|
/**
|
|
1146
1243
|
* Agent 类型注册中心
|
|
1147
1244
|
*
|
|
@@ -1165,14 +1262,14 @@ var AgentTypeRegistry = class {
|
|
|
1165
1262
|
*/
|
|
1166
1263
|
register(definition) {
|
|
1167
1264
|
const existing = this.types.get(definition.typeId);
|
|
1168
|
-
if (existing) log$
|
|
1265
|
+
if (existing) log$23.info("覆盖已注册的 Agent 类型", {
|
|
1169
1266
|
typeId: definition.typeId,
|
|
1170
1267
|
oldSource: existing.source,
|
|
1171
1268
|
newSource: definition.source
|
|
1172
1269
|
});
|
|
1173
1270
|
this.types.set(definition.typeId, definition);
|
|
1174
1271
|
this.refreshCache();
|
|
1175
|
-
log$
|
|
1272
|
+
log$23.debug("注册 Agent 类型", {
|
|
1176
1273
|
typeId: definition.typeId,
|
|
1177
1274
|
source: definition.source
|
|
1178
1275
|
});
|
|
@@ -1185,7 +1282,7 @@ var AgentTypeRegistry = class {
|
|
|
1185
1282
|
registerAll(definitions) {
|
|
1186
1283
|
for (const def of definitions) this.types.set(def.typeId, def);
|
|
1187
1284
|
this.refreshCache();
|
|
1188
|
-
log$
|
|
1285
|
+
log$23.info("批量注册 Agent 类型", { count: definitions.length });
|
|
1189
1286
|
}
|
|
1190
1287
|
/**
|
|
1191
1288
|
* 注销 Agent 类型
|
|
@@ -1197,7 +1294,7 @@ var AgentTypeRegistry = class {
|
|
|
1197
1294
|
const result = this.types.delete(typeId);
|
|
1198
1295
|
if (result) {
|
|
1199
1296
|
this.refreshCache();
|
|
1200
|
-
log$
|
|
1297
|
+
log$23.debug("注销 Agent 类型", { typeId });
|
|
1201
1298
|
}
|
|
1202
1299
|
return result;
|
|
1203
1300
|
}
|
|
@@ -1284,7 +1381,7 @@ var AgentTypeRegistry = class {
|
|
|
1284
1381
|
loadBuiltinTypes() {
|
|
1285
1382
|
for (const def of BUILTIN_AGENT_TYPES) this.types.set(def.typeId, def);
|
|
1286
1383
|
this.refreshCache();
|
|
1287
|
-
log$
|
|
1384
|
+
log$23.info("加载内置 Agent 类型", { count: BUILTIN_AGENT_TYPES.length });
|
|
1288
1385
|
}
|
|
1289
1386
|
/**
|
|
1290
1387
|
* 刷新可见类型缓存
|
|
@@ -1403,7 +1500,7 @@ function createCacheCommand() {
|
|
|
1403
1500
|
const lines = [];
|
|
1404
1501
|
lines.push("");
|
|
1405
1502
|
lines.push(chalk.cyan(" Prompt 缓存状态"));
|
|
1406
|
-
lines.push(
|
|
1503
|
+
lines.push(` ${chalk.gray("─".repeat(40))}`);
|
|
1407
1504
|
lines.push(chalk.gray(" 缓存命中率: ") + formatPercent(stats.hitRate));
|
|
1408
1505
|
lines.push(chalk.gray(" 平均缓存读取比例: ") + formatPercent(stats.averageCacheRatio));
|
|
1409
1506
|
lines.push(chalk.gray(" 总调用次数: ") + chalk.white(String(stats.totalCalls)));
|
|
@@ -1445,11 +1542,11 @@ function formatTokens(tokens) {
|
|
|
1445
1542
|
function createClearCommand() {
|
|
1446
1543
|
return {
|
|
1447
1544
|
name: "clear",
|
|
1448
|
-
aliases: [],
|
|
1449
|
-
description: "
|
|
1545
|
+
aliases: ["reset", "new"],
|
|
1546
|
+
description: "清空会话上下文,重置 Agent 状态",
|
|
1450
1547
|
usage: "/clear",
|
|
1451
1548
|
handler(_args, session) {
|
|
1452
|
-
session.
|
|
1549
|
+
session.clearAgentContext();
|
|
1453
1550
|
session.getInputParser().reset();
|
|
1454
1551
|
}
|
|
1455
1552
|
};
|
|
@@ -1504,7 +1601,7 @@ function updateSettingsFile(path, value) {
|
|
|
1504
1601
|
parsedValue = JSON.parse(value);
|
|
1505
1602
|
} catch {}
|
|
1506
1603
|
setByDotPath(config, path, parsedValue);
|
|
1507
|
-
writeFileSync(HOME_CONFIG_PATH, JSON.stringify(config, null, 2)
|
|
1604
|
+
writeFileSync(HOME_CONFIG_PATH, `${JSON.stringify(config, null, 2)}\n`, "utf-8");
|
|
1508
1605
|
return {
|
|
1509
1606
|
success: true,
|
|
1510
1607
|
message: ""
|
|
@@ -1721,7 +1818,7 @@ function renderSecurityDefault(report) {
|
|
|
1721
1818
|
/** 简单柱状图 */
|
|
1722
1819
|
function barChart(score) {
|
|
1723
1820
|
const filled = Math.round(score / 10);
|
|
1724
|
-
return "█".repeat(filled) + "░".repeat(10 - filled)
|
|
1821
|
+
return `${"█".repeat(filled) + "░".repeat(10 - filled)} ${score}/100`;
|
|
1725
1822
|
}
|
|
1726
1823
|
/**
|
|
1727
1824
|
* 创建 security 命令定义
|
|
@@ -1912,7 +2009,7 @@ var en_default = {
|
|
|
1912
2009
|
"listProvidersUsage": "/settings list-providers — List all known providers",
|
|
1913
2010
|
"listModelsUsage": "/settings list-models <name> — List available models for a provider",
|
|
1914
2011
|
"knownProviders": "Known providers:",
|
|
1915
|
-
"noModelsAvailable": "No models available
|
|
2012
|
+
"noModelsAvailable": "No models available",
|
|
1916
2013
|
"hintConfigureFirst": "Hint: use /settings to configure this provider first",
|
|
1917
2014
|
"availableModels": "available models:"
|
|
1918
2015
|
},
|
|
@@ -2096,7 +2193,7 @@ var zh_CN_default = {
|
|
|
2096
2193
|
"listProvidersUsage": "/settings list-providers — 列出所有已知提供商",
|
|
2097
2194
|
"listModelsUsage": "/settings list-models <name> — 列出提供商可用的模型",
|
|
2098
2195
|
"knownProviders": "已知提供商:",
|
|
2099
|
-
"noModelsAvailable": "
|
|
2196
|
+
"noModelsAvailable": "没有可用的模型",
|
|
2100
2197
|
"hintConfigureFirst": "提示: 使用 /settings 先配置此提供商",
|
|
2101
2198
|
"availableModels": "可用模型:"
|
|
2102
2199
|
},
|
|
@@ -2435,7 +2532,7 @@ function readSettings() {
|
|
|
2435
2532
|
}
|
|
2436
2533
|
/** Write back to settings.json */
|
|
2437
2534
|
function writeSettings(settings) {
|
|
2438
|
-
writeFileSync(HOME_CONFIG_PATH, JSON.stringify(settings, null, 2)
|
|
2535
|
+
writeFileSync(HOME_CONFIG_PATH, `${JSON.stringify(settings, null, 2)}\n`, "utf-8");
|
|
2439
2536
|
}
|
|
2440
2537
|
/** Safely set a nested property (prototype-chain safe) */
|
|
2441
2538
|
function _setByDotPath(obj, path, value) {
|
|
@@ -2465,15 +2562,6 @@ function _getByDotPath(obj, path) {
|
|
|
2465
2562
|
|
|
2466
2563
|
//#endregion
|
|
2467
2564
|
//#region src/cli/repl/commands/settings-cmd.ts
|
|
2468
|
-
/**
|
|
2469
|
-
* /settings command — interactive configuration menu
|
|
2470
|
-
*
|
|
2471
|
-
* TUI overlay-based graphical configuration interface:
|
|
2472
|
-
* - View and change the default model
|
|
2473
|
-
* - Configure API keys, models, and base URLs for existing providers
|
|
2474
|
-
* - Add new providers from a curated list
|
|
2475
|
-
* - All changes sync to ~/.zapmyco/settings.json in real-time
|
|
2476
|
-
*/
|
|
2477
2565
|
/** Curated list of known providers (sorted by popularity) */
|
|
2478
2566
|
const KNOWN_PROVIDERS = [
|
|
2479
2567
|
{
|
|
@@ -2550,6 +2638,18 @@ const KNOWN_PROVIDERS = [
|
|
|
2550
2638
|
label: "OpenCode"
|
|
2551
2639
|
}
|
|
2552
2640
|
];
|
|
2641
|
+
/** Built-in model IDs for known providers (fallback when not in config) */
|
|
2642
|
+
const BUILTIN_MODEL_IDS = {
|
|
2643
|
+
anthropic: [
|
|
2644
|
+
"claude-sonnet-4-20250514",
|
|
2645
|
+
"claude-3-5-haiku-latest",
|
|
2646
|
+
"claude-3-opus-latest"
|
|
2647
|
+
],
|
|
2648
|
+
deepseek: ["deepseek-v4-pro", "deepseek-v4-flash"],
|
|
2649
|
+
glm: ["glm-4", "glm-4v"],
|
|
2650
|
+
kimi: ["moonshot-v1-8k", "moonshot-v1-32k"],
|
|
2651
|
+
minimax: ["minimax-text-01"]
|
|
2652
|
+
};
|
|
2553
2653
|
/** Supported language locales */
|
|
2554
2654
|
const SUPPORTED_LOCALES = [{
|
|
2555
2655
|
value: "zh-CN",
|
|
@@ -2575,14 +2675,12 @@ function hasApiKey(config, providerName) {
|
|
|
2575
2675
|
const keyStr = String(key);
|
|
2576
2676
|
return keyStr.length > 0 && keyStr !== "${}";
|
|
2577
2677
|
}
|
|
2578
|
-
/** Get available model IDs for a provider (from config or
|
|
2678
|
+
/** Get available model IDs for a provider (from config or built-in list) */
|
|
2579
2679
|
function getProviderModels(config, providerName) {
|
|
2580
2680
|
const models = _getByDotPath(config, `llm.providers.${providerName}.models`);
|
|
2581
2681
|
if (models && typeof models === "object" && Object.keys(models).length > 0) return Object.keys(models);
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
if (piModels && piModels.length > 0) return piModels.map((m) => m.id);
|
|
2585
|
-
} catch {}
|
|
2682
|
+
const builtinModels = BUILTIN_MODEL_IDS[providerName];
|
|
2683
|
+
if (builtinModels && builtinModels.length > 0) return builtinModels;
|
|
2586
2684
|
return [];
|
|
2587
2685
|
}
|
|
2588
2686
|
/**
|
|
@@ -2766,7 +2864,7 @@ async function handleInteractiveMode(tui, session, config) {
|
|
|
2766
2864
|
label: id,
|
|
2767
2865
|
description: ""
|
|
2768
2866
|
})), { onExit: exitAll });
|
|
2769
|
-
if (selected
|
|
2867
|
+
if (selected?.value) {
|
|
2770
2868
|
const modelId = selected.value;
|
|
2771
2869
|
const settings = readSettings();
|
|
2772
2870
|
_setByDotPath(settings, `llm.providers.${providerName}.models.${modelId}`, { id: modelId });
|
|
@@ -2809,7 +2907,9 @@ async function handleInteractiveMode(tui, session, config) {
|
|
|
2809
2907
|
*/
|
|
2810
2908
|
const handleModelSlotSelect = async (configKey, successMsgKey) => {
|
|
2811
2909
|
const configuredProviders = _getByDotPath(state.current, "llm.providers");
|
|
2812
|
-
const
|
|
2910
|
+
const configuredProviderNames = configuredProviders ? Object.keys(configuredProviders) : [];
|
|
2911
|
+
const builtinProviderNames = Object.keys(BUILTIN_MODEL_IDS);
|
|
2912
|
+
const allProviders = [...new Set([...configuredProviderNames, ...builtinProviderNames])];
|
|
2813
2913
|
const enabledItems = [];
|
|
2814
2914
|
const disabledItems = [];
|
|
2815
2915
|
for (const providerName of allProviders) {
|
|
@@ -2840,7 +2940,7 @@ async function handleInteractiveMode(tui, session, config) {
|
|
|
2840
2940
|
return;
|
|
2841
2941
|
}
|
|
2842
2942
|
const selected = await showSelectList(tui, modelItems, { onExit: exitAll });
|
|
2843
|
-
if (!selected
|
|
2943
|
+
if (!selected?.value) return;
|
|
2844
2944
|
const selectedKey = selected.value;
|
|
2845
2945
|
const slashIndex = selectedKey.indexOf("/");
|
|
2846
2946
|
const providerName = selectedKey.slice(0, slashIndex);
|
|
@@ -2991,7 +3091,7 @@ async function handleInteractiveMode(tui, session, config) {
|
|
|
2991
3091
|
maxVisible: 12,
|
|
2992
3092
|
onExit: exitAll
|
|
2993
3093
|
});
|
|
2994
|
-
if (!selected
|
|
3094
|
+
if (!selected?.value) continue;
|
|
2995
3095
|
const providerName = selected.value;
|
|
2996
3096
|
const existingProviders = _getByDotPath(state.current, "llm.providers");
|
|
2997
3097
|
if (existingProviders && providerName in existingProviders) {
|
|
@@ -3033,7 +3133,7 @@ async function handleInteractiveMode(tui, session, config) {
|
|
|
3033
3133
|
...loc,
|
|
3034
3134
|
label: loc.value === currentLocale ? `${loc.label} ✓` : loc.label
|
|
3035
3135
|
})), { onExit: exitAll });
|
|
3036
|
-
if (!selected
|
|
3136
|
+
if (!selected?.value) continue;
|
|
3037
3137
|
if (selected.value !== currentLocale) {
|
|
3038
3138
|
setConfigValue(session, "locale", selected.value);
|
|
3039
3139
|
setLocale(selected.value);
|
|
@@ -3067,23 +3167,152 @@ function createStatusCommand() {
|
|
|
3067
3167
|
};
|
|
3068
3168
|
}
|
|
3069
3169
|
|
|
3170
|
+
//#endregion
|
|
3171
|
+
//#region src/core/agent-team/agent-tool-categorizer.ts
|
|
3172
|
+
/** 内置工具分类映射表 */
|
|
3173
|
+
const TOOL_CATEGORIES = {
|
|
3174
|
+
ReadFile: {
|
|
3175
|
+
category: "read",
|
|
3176
|
+
label: "Read"
|
|
3177
|
+
},
|
|
3178
|
+
Glob: {
|
|
3179
|
+
category: "search",
|
|
3180
|
+
label: "Search"
|
|
3181
|
+
},
|
|
3182
|
+
Grep: {
|
|
3183
|
+
category: "search",
|
|
3184
|
+
label: "Search"
|
|
3185
|
+
},
|
|
3186
|
+
WriteFile: {
|
|
3187
|
+
category: "write",
|
|
3188
|
+
label: "Write"
|
|
3189
|
+
},
|
|
3190
|
+
EditFile: {
|
|
3191
|
+
category: "edit",
|
|
3192
|
+
label: "Edit"
|
|
3193
|
+
},
|
|
3194
|
+
Exec: {
|
|
3195
|
+
category: "exec",
|
|
3196
|
+
label: "Exec"
|
|
3197
|
+
},
|
|
3198
|
+
Process: {
|
|
3199
|
+
category: "exec",
|
|
3200
|
+
label: "Process"
|
|
3201
|
+
},
|
|
3202
|
+
WebFetch: {
|
|
3203
|
+
category: "web",
|
|
3204
|
+
label: "WebFetch"
|
|
3205
|
+
},
|
|
3206
|
+
WebSearch: {
|
|
3207
|
+
category: "web",
|
|
3208
|
+
label: "WebSearch"
|
|
3209
|
+
},
|
|
3210
|
+
TaskManage: {
|
|
3211
|
+
category: "task",
|
|
3212
|
+
label: "Task"
|
|
3213
|
+
},
|
|
3214
|
+
Memory: {
|
|
3215
|
+
category: "task",
|
|
3216
|
+
label: "Memory"
|
|
3217
|
+
},
|
|
3218
|
+
AgentTool: {
|
|
3219
|
+
category: "other",
|
|
3220
|
+
label: "Spawn"
|
|
3221
|
+
}
|
|
3222
|
+
};
|
|
3223
|
+
/**
|
|
3224
|
+
* 对工具名称进行分类
|
|
3225
|
+
*
|
|
3226
|
+
* @param toolName - 工具名称
|
|
3227
|
+
* @returns 分类信息和展示标签
|
|
3228
|
+
*/
|
|
3229
|
+
function categorizeTool(toolName) {
|
|
3230
|
+
return TOOL_CATEGORIES[toolName] ?? {
|
|
3231
|
+
category: "other",
|
|
3232
|
+
label: toolName
|
|
3233
|
+
};
|
|
3234
|
+
}
|
|
3235
|
+
|
|
3236
|
+
//#endregion
|
|
3237
|
+
//#region src/core/agent-team/agent-progress-processor.ts
|
|
3238
|
+
/**
|
|
3239
|
+
* Agent 工具调用分组处理器
|
|
3240
|
+
*
|
|
3241
|
+
* 将扁平的工具调用记录按分类合为展示分组,
|
|
3242
|
+
* 类似 claude-code 的 processProgressMessages()。
|
|
3243
|
+
*
|
|
3244
|
+
* @module core/agent-team
|
|
3245
|
+
*/
|
|
3246
|
+
/**
|
|
3247
|
+
* 连续相同分类调用的最小分组阈值。
|
|
3248
|
+
* >= 2 时合并为分组(如 "Read 3 files"),
|
|
3249
|
+
* 否则保持独立条目(如 "ReadFile: src/foo.ts")。
|
|
3250
|
+
*/
|
|
3251
|
+
const GROUP_THRESHOLD = 2;
|
|
3252
|
+
/**
|
|
3253
|
+
* 将扁平的工具调用记录处理为展示分组
|
|
3254
|
+
*
|
|
3255
|
+
* @param records - 工具调用记录列表
|
|
3256
|
+
* @returns 有序的展示分组
|
|
3257
|
+
*/
|
|
3258
|
+
function buildToolCallGroups(records) {
|
|
3259
|
+
const groups = [];
|
|
3260
|
+
let i = 0;
|
|
3261
|
+
while (i < records.length) {
|
|
3262
|
+
const record = records[i];
|
|
3263
|
+
if (!record) {
|
|
3264
|
+
i++;
|
|
3265
|
+
continue;
|
|
3266
|
+
}
|
|
3267
|
+
const { category, label } = categorizeTool(record.toolName);
|
|
3268
|
+
let j = i + 1;
|
|
3269
|
+
while (j < records.length) {
|
|
3270
|
+
const next = records[j];
|
|
3271
|
+
if (!next) break;
|
|
3272
|
+
if (categorizeTool(next.toolName).category !== category) break;
|
|
3273
|
+
j++;
|
|
3274
|
+
}
|
|
3275
|
+
const count = j - i;
|
|
3276
|
+
const batch = records.slice(i, j);
|
|
3277
|
+
if (count >= GROUP_THRESHOLD) groups.push({
|
|
3278
|
+
category,
|
|
3279
|
+
label,
|
|
3280
|
+
calls: batch,
|
|
3281
|
+
count,
|
|
3282
|
+
startTime: batch[0]?.startedAt ?? 0,
|
|
3283
|
+
endTime: batch[batch.length - 1]?.endedAt
|
|
3284
|
+
});
|
|
3285
|
+
else for (const r of batch) groups.push({
|
|
3286
|
+
category,
|
|
3287
|
+
label,
|
|
3288
|
+
calls: [r],
|
|
3289
|
+
count: 1,
|
|
3290
|
+
startTime: r.startedAt,
|
|
3291
|
+
endTime: r.endedAt
|
|
3292
|
+
});
|
|
3293
|
+
i = j;
|
|
3294
|
+
}
|
|
3295
|
+
return groups;
|
|
3296
|
+
}
|
|
3297
|
+
|
|
3070
3298
|
//#endregion
|
|
3071
3299
|
//#region src/cli/repl/components/agent-status-bar.ts
|
|
3072
3300
|
/**
|
|
3073
3301
|
* Agent 状态栏组件
|
|
3074
3302
|
*
|
|
3075
3303
|
* 实时显示正在运行的子 Agent 状态,类似 Claude Code 的 "Running N agents…" 效果。
|
|
3304
|
+
* 支持每个 Agent 的工具调用历史展示、分组摘要、"+N more" 折叠展开。
|
|
3076
3305
|
* 固定在 OutputArea 和 Editor 之间,无活跃 Agent 时自动隐藏。
|
|
3077
3306
|
*
|
|
3078
3307
|
* @module cli/repl/components
|
|
3079
3308
|
*/
|
|
3080
|
-
/** 状态图标映射 */
|
|
3309
|
+
/** 状态图标映射 — 纯文字符号,无 emoji */
|
|
3081
3310
|
const STATUS_ICONS$1 = {
|
|
3082
3311
|
idle: "○",
|
|
3083
|
-
running: "
|
|
3312
|
+
running: "●",
|
|
3084
3313
|
paused: "◐",
|
|
3085
|
-
completed: "
|
|
3086
|
-
failed: "
|
|
3314
|
+
completed: "✔",
|
|
3315
|
+
failed: "✘",
|
|
3087
3316
|
cancelled: "◌"
|
|
3088
3317
|
};
|
|
3089
3318
|
/** Loading 动画帧 */
|
|
@@ -3105,32 +3334,120 @@ const TREE_BRANCH = "├── ";
|
|
|
3105
3334
|
const TREE_LAST = "└── ";
|
|
3106
3335
|
const TREE_PIPE = "│ ";
|
|
3107
3336
|
const TREE_SPACE = " ";
|
|
3108
|
-
|
|
3337
|
+
/** 工具名 → 可读动词描述 */
|
|
3338
|
+
const TOOL_DESCRIPTIONS = {
|
|
3339
|
+
AgentTool: "派发子任务",
|
|
3340
|
+
Grep: "搜索代码",
|
|
3341
|
+
ReadFile: "读取文件",
|
|
3342
|
+
Glob: "查找文件",
|
|
3343
|
+
Exec: "执行命令",
|
|
3344
|
+
WriteFile: "写入文件",
|
|
3345
|
+
EditFile: "编辑文件",
|
|
3346
|
+
WebFetch: "抓取网页",
|
|
3347
|
+
WebSearch: "搜索网络",
|
|
3348
|
+
SendMessage: "发送消息",
|
|
3349
|
+
TaskStop: "停止任务"
|
|
3350
|
+
};
|
|
3351
|
+
/** 获取工具的可读描述 */
|
|
3352
|
+
function getToolDescription(toolName) {
|
|
3353
|
+
return TOOL_DESCRIPTIONS[toolName] ?? toolName;
|
|
3354
|
+
}
|
|
3109
3355
|
/**
|
|
3110
3356
|
* Agent 状态栏组件
|
|
3111
3357
|
*
|
|
3112
3358
|
* 从 AgentInstanceManager 读取活跃实例状态,渲染为紧凑状态栏。
|
|
3113
3359
|
*/
|
|
3114
3360
|
var AgentStatusBar = class extends Container {
|
|
3115
|
-
/**
|
|
3116
|
-
#
|
|
3361
|
+
/** AnimationManager 实例(用于渲染周期驱动的动画) */
|
|
3362
|
+
#animationManager;
|
|
3363
|
+
/** 是否展开显示详情(默认展开) */
|
|
3364
|
+
#expanded = true;
|
|
3365
|
+
/** 按实例的工具调用列表展开状态 */
|
|
3366
|
+
#agentExpanded = /* @__PURE__ */ new Map();
|
|
3117
3367
|
/** loading 动画帧索引 */
|
|
3118
3368
|
#loadingFrame = 0;
|
|
3119
|
-
/**
|
|
3120
|
-
#
|
|
3369
|
+
/** AnimationManager 回调注销函数 */
|
|
3370
|
+
#unregLoading = null;
|
|
3371
|
+
/** 上次帧推进时间戳 */
|
|
3372
|
+
#lastLoadingTick = 0;
|
|
3121
3373
|
/** 上次活跃实例快照(用于检测变化) */
|
|
3122
3374
|
#lastActiveCount = 0;
|
|
3375
|
+
constructor(animationManager) {
|
|
3376
|
+
super();
|
|
3377
|
+
this.#animationManager = animationManager;
|
|
3378
|
+
}
|
|
3379
|
+
/** 当前模型名称 */
|
|
3380
|
+
#modelName = null;
|
|
3381
|
+
/** 累积非缓存 input tokens(usage.input = totalInput - cacheRead) */
|
|
3382
|
+
#inputTokens = 0;
|
|
3383
|
+
/** 累积 cache read tokens(缓存命中) */
|
|
3384
|
+
#cacheReadTokens = 0;
|
|
3385
|
+
/** 累积 output tokens */
|
|
3386
|
+
#outputTokens = 0;
|
|
3387
|
+
/** 任务已耗时(ms) */
|
|
3388
|
+
#durationMs = 0;
|
|
3123
3389
|
/**
|
|
3124
3390
|
* 切换展开/折叠状态
|
|
3391
|
+
*
|
|
3392
|
+
* @param instanceId - 指定实例 ID 时,切换该实例的工具调用列表展开/折叠;不传时切换整体状态栏
|
|
3125
3393
|
*/
|
|
3126
|
-
toggle() {
|
|
3127
|
-
|
|
3394
|
+
toggle(instanceId) {
|
|
3395
|
+
if (instanceId) {
|
|
3396
|
+
const current = this.#agentExpanded.get(instanceId) ?? false;
|
|
3397
|
+
this.#agentExpanded.set(instanceId, !current);
|
|
3398
|
+
} else {
|
|
3399
|
+
this.#expanded = !this.#expanded;
|
|
3400
|
+
if (!this.#expanded) this.#agentExpanded.clear();
|
|
3401
|
+
}
|
|
3128
3402
|
this.invalidate();
|
|
3129
3403
|
}
|
|
3404
|
+
/** 展开/折叠当前活跃 Agent 的工具调用详情 */
|
|
3405
|
+
toggleActiveAgentDetails() {
|
|
3406
|
+
const active = getAgentInstanceManager().listActive();
|
|
3407
|
+
if (active.length === 0) return;
|
|
3408
|
+
const first = active[0];
|
|
3409
|
+
if (first) this.toggle(first.instanceId);
|
|
3410
|
+
}
|
|
3130
3411
|
/** 获取当前展开状态 */
|
|
3131
3412
|
get isExpanded() {
|
|
3132
3413
|
return this.#expanded;
|
|
3133
3414
|
}
|
|
3415
|
+
/**
|
|
3416
|
+
* 设置当前模型名称
|
|
3417
|
+
*/
|
|
3418
|
+
setModelName(name) {
|
|
3419
|
+
this.#modelName = name;
|
|
3420
|
+
this.invalidate();
|
|
3421
|
+
}
|
|
3422
|
+
/**
|
|
3423
|
+
* 更新 Token 统计数据
|
|
3424
|
+
*
|
|
3425
|
+
* @param inputTokens - 非缓存 input tokens(usage.input)
|
|
3426
|
+
* @param cacheRead - 缓存读取 tokens(usage.cacheRead)
|
|
3427
|
+
* @param outputTokens - 输出 tokens
|
|
3428
|
+
* @param durationMs - 任务已耗时(毫秒)
|
|
3429
|
+
*/
|
|
3430
|
+
updateTokenStats(inputTokens, cacheRead, outputTokens, durationMs) {
|
|
3431
|
+
this.#inputTokens = inputTokens;
|
|
3432
|
+
this.#cacheReadTokens = cacheRead;
|
|
3433
|
+
this.#outputTokens = outputTokens;
|
|
3434
|
+
if (durationMs !== void 0) this.#durationMs = durationMs;
|
|
3435
|
+
this.invalidate();
|
|
3436
|
+
}
|
|
3437
|
+
/**
|
|
3438
|
+
* 清除 Token 数据(执行结束后调用)
|
|
3439
|
+
*/
|
|
3440
|
+
clearTokenStats() {
|
|
3441
|
+
this.#modelName = null;
|
|
3442
|
+
this.#inputTokens = 0;
|
|
3443
|
+
this.#cacheReadTokens = 0;
|
|
3444
|
+
this.#outputTokens = 0;
|
|
3445
|
+
this.invalidate();
|
|
3446
|
+
}
|
|
3447
|
+
/** 是否有 Token 信息要显示 */
|
|
3448
|
+
get hasTokenInfo() {
|
|
3449
|
+
return this.#modelName !== null;
|
|
3450
|
+
}
|
|
3134
3451
|
invalidate() {
|
|
3135
3452
|
super.invalidate();
|
|
3136
3453
|
}
|
|
@@ -3139,6 +3456,7 @@ var AgentStatusBar = class extends Container {
|
|
|
3139
3456
|
if (activeInstances.length === 0) {
|
|
3140
3457
|
this.#stopLoading();
|
|
3141
3458
|
this.#lastActiveCount = 0;
|
|
3459
|
+
if (this.hasTokenInfo) return [truncateToWidth(this.#renderTokenInfoLine(), width)];
|
|
3142
3460
|
return [];
|
|
3143
3461
|
}
|
|
3144
3462
|
if (this.#lastActiveCount === 0) this.#startLoading();
|
|
@@ -3148,56 +3466,85 @@ var AgentStatusBar = class extends Container {
|
|
|
3148
3466
|
}, 0);
|
|
3149
3467
|
const totalDuration = this.#formatDuration(Math.max(...activeInstances.map((i) => Date.now() - i.createdAt)));
|
|
3150
3468
|
const frame = LOADING_FRAMES$2[this.#loadingFrame % LOADING_FRAMES$2.length] ?? "";
|
|
3151
|
-
if (!this.#expanded)
|
|
3152
|
-
|
|
3469
|
+
if (!this.#expanded) {
|
|
3470
|
+
const lines = [];
|
|
3471
|
+
lines.push(truncateToWidth(this.#renderCollapsed(frame, activeInstances.length, totalToolUses, totalDuration), width));
|
|
3472
|
+
if (this.hasTokenInfo) lines.push(truncateToWidth(this.#renderTokenInfoLine(), width));
|
|
3473
|
+
return lines;
|
|
3474
|
+
}
|
|
3475
|
+
const lines = this.#renderExpanded(frame, activeInstances, width);
|
|
3476
|
+
if (this.hasTokenInfo) lines.push(truncateToWidth(this.#renderTokenInfoLine(), width));
|
|
3477
|
+
return lines;
|
|
3153
3478
|
}
|
|
3154
3479
|
/** 渲染折叠模式单行 */
|
|
3155
|
-
#renderCollapsed(frame, count,
|
|
3156
|
-
return ` ${chalk.cyan(`${frame}
|
|
3480
|
+
#renderCollapsed(frame, count, _toolUses, duration) {
|
|
3481
|
+
return ` ${chalk.cyan(`${frame} ${count} agent${count > 1 ? "s" : ""}`)} ${chalk.gray(`· ${duration}`)}`;
|
|
3157
3482
|
}
|
|
3158
3483
|
/** 渲染展开模式多行 */
|
|
3159
3484
|
#renderExpanded(frame, instances, width) {
|
|
3160
3485
|
const lines = [];
|
|
3161
3486
|
const count = instances.length;
|
|
3162
|
-
const
|
|
3163
|
-
const
|
|
3164
|
-
lines.push(
|
|
3487
|
+
const totalDuration = this.#formatDuration(Math.max(...instances.map((i) => Date.now() - i.createdAt)));
|
|
3488
|
+
const header = chalk.cyan(` ${frame} ${count} agent${count > 1 ? "s" : ""} · ${totalDuration}`);
|
|
3489
|
+
lines.push(truncateToWidth(header, width));
|
|
3165
3490
|
for (let i = 0; i < instances.length; i++) {
|
|
3166
3491
|
const inst = instances[i];
|
|
3167
3492
|
const isLast = i === instances.length - 1;
|
|
3168
3493
|
const connector = isLast ? TREE_LAST : TREE_BRANCH;
|
|
3169
3494
|
const childPrefix = isLast ? TREE_SPACE : TREE_PIPE;
|
|
3170
|
-
const
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3495
|
+
const agentLines = this.#renderAgentDetail(inst, connector, childPrefix, width);
|
|
3496
|
+
lines.push(...agentLines);
|
|
3497
|
+
}
|
|
3498
|
+
return lines;
|
|
3499
|
+
}
|
|
3500
|
+
/**
|
|
3501
|
+
* 渲染单个 Agent 的详情(header + 工具调用列表)
|
|
3502
|
+
*/
|
|
3503
|
+
#renderAgentDetail(inst, connector, childPrefix, width) {
|
|
3504
|
+
const lines = [];
|
|
3505
|
+
const icon = STATUS_ICONS$1[inst.status] ?? "?";
|
|
3506
|
+
const statusColor = inst.status === "running" ? chalk.yellow : chalk.gray;
|
|
3507
|
+
const typeLabel = chalk.bold(inst.typeId);
|
|
3508
|
+
const act = inst.currentActivity;
|
|
3509
|
+
let activityDesc = "";
|
|
3510
|
+
if (act) activityDesc = `正在${getToolDescription(act.toolName)}`;
|
|
3511
|
+
else {
|
|
3512
|
+
const totalCalls = inst.toolCallHistory.length;
|
|
3513
|
+
if (totalCalls > 0) {
|
|
3514
|
+
if (inst.toolCallHistory.every((t) => t.status === "completed" || t.status === "failed")) activityDesc = `已完成 ${totalCalls} 次调用`;
|
|
3183
3515
|
}
|
|
3184
3516
|
}
|
|
3517
|
+
const duration = chalk.gray(`· ${this.#formatDuration(Date.now() - inst.createdAt)}`);
|
|
3518
|
+
let line1;
|
|
3519
|
+
if (activityDesc) line1 = ` ${chalk.dim(connector)}${statusColor(icon)} ${typeLabel} ${chalk.dim(`· ${activityDesc}`)} ${duration}`;
|
|
3520
|
+
else line1 = ` ${chalk.dim(connector)}${statusColor(icon)} ${typeLabel} ${duration}`;
|
|
3521
|
+
lines.push(truncateToWidth(line1, width));
|
|
3522
|
+
const totalCalls = inst.toolCallHistory.length;
|
|
3523
|
+
if (totalCalls > 0 && !act) {
|
|
3524
|
+
const summaries = buildToolCallGroups(inst.toolCallHistory).map((g) => `${getToolDescription(g.label)} ${g.count}次`);
|
|
3525
|
+
if (summaries.length > 0) lines.push(truncateToWidth(` ${chalk.dim(childPrefix + TREE_SPACE)}${chalk.dim(summaries.join(" "))}`, width));
|
|
3526
|
+
} else if (totalCalls > 0 && act) lines.push(truncateToWidth(` ${chalk.dim(childPrefix + TREE_SPACE)}${chalk.dim(`已完成 ${totalCalls} 次工具调用`)}`, width));
|
|
3527
|
+
if (inst.status === "running" && !act) lines.push(truncateToWidth(` ${chalk.dim(childPrefix + TREE_SPACE)}${chalk.dim("(ctrl+b to run in background)")}`, width));
|
|
3185
3528
|
return lines;
|
|
3186
3529
|
}
|
|
3187
|
-
/** 启动 loading
|
|
3530
|
+
/** 启动 loading 动画(由 animationManager 在渲染周期中驱动) */
|
|
3188
3531
|
#startLoading() {
|
|
3189
|
-
if (this.#
|
|
3190
|
-
this.#
|
|
3532
|
+
if (this.#unregLoading) return;
|
|
3533
|
+
this.#lastLoadingTick = 0;
|
|
3534
|
+
this.#unregLoading = this.#animationManager.register((now) => {
|
|
3535
|
+
if (now - this.#lastLoadingTick < LOADING_INTERVAL_MS$1) return;
|
|
3536
|
+
this.#lastLoadingTick = now;
|
|
3191
3537
|
this.#loadingFrame = (this.#loadingFrame + 1) % LOADING_FRAMES$2.length;
|
|
3192
3538
|
this.invalidate();
|
|
3193
|
-
}
|
|
3539
|
+
});
|
|
3194
3540
|
}
|
|
3195
3541
|
/** 停止 loading 动画 */
|
|
3196
3542
|
#stopLoading() {
|
|
3197
|
-
if (this.#
|
|
3198
|
-
|
|
3199
|
-
this.#
|
|
3543
|
+
if (this.#unregLoading) {
|
|
3544
|
+
this.#unregLoading();
|
|
3545
|
+
this.#unregLoading = null;
|
|
3200
3546
|
}
|
|
3547
|
+
this.#loadingFrame = 0;
|
|
3201
3548
|
}
|
|
3202
3549
|
/** 格式化持续时间为可读字符串 */
|
|
3203
3550
|
#formatDuration(ms) {
|
|
@@ -3205,24 +3552,37 @@ var AgentStatusBar = class extends Container {
|
|
|
3205
3552
|
if (ms < 6e4) return `${(ms / 1e3).toFixed(1)}s`;
|
|
3206
3553
|
return `${Math.floor(ms / 6e4)}m${Math.floor(ms % 6e4 / 1e3)}s`;
|
|
3207
3554
|
}
|
|
3555
|
+
/** 格式化 Token 数字
|
|
3556
|
+
*
|
|
3557
|
+
* - < 1,000: 原始数字(456)
|
|
3558
|
+
* - ≥ 1,000: 千分位分隔(1,234)
|
|
3559
|
+
* - ≥ 10,000: K 单位(15.3K)
|
|
3560
|
+
* - ≥ 1,000,000: M 单位(1.2M)
|
|
3561
|
+
*/
|
|
3562
|
+
#formatTokenCount(n) {
|
|
3563
|
+
if (n >= 1e6) return `${(n / 1e6).toFixed(1)}M`;
|
|
3564
|
+
if (n >= 1e4) return `${(n / 1e3).toFixed(1)}K`;
|
|
3565
|
+
return n.toLocaleString();
|
|
3566
|
+
}
|
|
3567
|
+
/** 渲染 Token 信息行 */
|
|
3568
|
+
#renderTokenInfoLine() {
|
|
3569
|
+
const modelStr = chalk.cyan(this.#modelName ?? "");
|
|
3570
|
+
const separator = chalk.gray(" · ");
|
|
3571
|
+
const totalInput = this.#inputTokens + this.#cacheReadTokens;
|
|
3572
|
+
const missTokens = this.#inputTokens;
|
|
3573
|
+
const cacheRate = totalInput > 0 ? Math.round(this.#cacheReadTokens / totalInput * 100) : 0;
|
|
3574
|
+
const inStr = chalk.white(this.#formatTokenCount(totalInput));
|
|
3575
|
+
const hitStr = chalk.green(this.#formatTokenCount(this.#cacheReadTokens));
|
|
3576
|
+
const rateStr = cacheRate >= 80 ? chalk.green(`${cacheRate}%`) : chalk.yellow(`${cacheRate}%`);
|
|
3577
|
+
const missStr = chalk.yellow(this.#formatTokenCount(missTokens));
|
|
3578
|
+
const outStr = chalk.cyan(this.#formatTokenCount(this.#outputTokens));
|
|
3579
|
+
const durationStr = chalk.magenta(this.#formatDuration(this.#durationMs));
|
|
3580
|
+
return ` ${modelStr}${separator}${chalk.gray("IN")} ${inStr}${separator}${chalk.gray("HIT")} ${hitStr} ${chalk.dim(`(${rateStr})`)}${separator}${chalk.gray("MISS")} ${missStr}${separator}${chalk.gray("OUT")} ${outStr}${separator}${durationStr}`;
|
|
3581
|
+
}
|
|
3208
3582
|
};
|
|
3209
3583
|
|
|
3210
3584
|
//#endregion
|
|
3211
3585
|
//#region src/cli/repl/components/custom-editor.ts
|
|
3212
|
-
/**
|
|
3213
|
-
* 自定义编辑器组件
|
|
3214
|
-
*
|
|
3215
|
-
* 继承自 pi-tui 的 Editor,添加 zapmyco 特有的快捷键处理:
|
|
3216
|
-
* - Ctrl+C: 取消任务 / 二次退出
|
|
3217
|
-
* - Ctrl+D: 退出
|
|
3218
|
-
* - Ctrl+O: 打开外部编辑器编辑输入
|
|
3219
|
-
* - Escape: 取消当前输入
|
|
3220
|
-
*
|
|
3221
|
-
* 同时 override render() 以:
|
|
3222
|
-
* - 去掉 Editor 默认的上下边框(───)
|
|
3223
|
-
* - 添加简洁的输入提示符(❯ )
|
|
3224
|
-
* - 执行中时显示 loading spinner
|
|
3225
|
-
*/
|
|
3226
3586
|
/** 输入提示符 */
|
|
3227
3587
|
const PROMPT_PREFIX = "❯ ";
|
|
3228
3588
|
/** loading 动画帧 */
|
|
@@ -3246,36 +3606,42 @@ function isBorderLine(line) {
|
|
|
3246
3606
|
return /^[\s─┌┐├┤└┘↑↓\-0-9a-zA-Z]+$/.test(stripped);
|
|
3247
3607
|
}
|
|
3248
3608
|
var ZapmycoEditor = class extends Editor {
|
|
3609
|
+
/** AnimationManager 实例(用于渲染周期驱动的动画) */
|
|
3610
|
+
#animationManager;
|
|
3611
|
+
/** AnimationManager 回调注销函数 */
|
|
3612
|
+
#unregLoading = null;
|
|
3613
|
+
/** 上次帧推进时间戳 */
|
|
3614
|
+
#lastLoadingTick = 0;
|
|
3615
|
+
constructor(tui, theme, animationManager, options) {
|
|
3616
|
+
super(tui, theme, options);
|
|
3617
|
+
this.#animationManager = animationManager;
|
|
3618
|
+
}
|
|
3249
3619
|
/** Escape 键回调 */
|
|
3250
3620
|
onEscape;
|
|
3251
3621
|
/** Ctrl+C 回调 */
|
|
3252
3622
|
onCtrlC;
|
|
3253
3623
|
/** Ctrl+D 回调 */
|
|
3254
3624
|
onCtrlD;
|
|
3255
|
-
/** Ctrl+
|
|
3625
|
+
/** Ctrl+G 回调(打开外部编辑器) */
|
|
3256
3626
|
onOpenEditor;
|
|
3257
|
-
/** Ctrl+
|
|
3258
|
-
|
|
3259
|
-
/** Ctrl+
|
|
3627
|
+
/** Ctrl+B 回调(后台运行当前任务) */
|
|
3628
|
+
onRunInBackground;
|
|
3629
|
+
/** Ctrl+T 回调(展开/折叠 thinking 内容) */
|
|
3260
3630
|
onToggleThinking;
|
|
3261
|
-
/** Ctrl+Shift+O 回调(展开/折叠 Agent 状态栏) */
|
|
3262
|
-
onToggleAgentBar;
|
|
3263
3631
|
/** 是否正在执行(用于显示 loading) */
|
|
3264
3632
|
#executing = false;
|
|
3265
3633
|
/** 是否显示 spinner(执行期间禁用输入但不一定显示 spinner) */
|
|
3266
3634
|
#showSpinner = true;
|
|
3267
3635
|
/** loading 动画帧索引 */
|
|
3268
3636
|
#loadingFrame = 0;
|
|
3269
|
-
/** loading 动画定时器 */
|
|
3270
|
-
#loadingTimer;
|
|
3271
3637
|
/** 审批模式状态 */
|
|
3272
3638
|
#approvalState = null;
|
|
3273
3639
|
/** 进入审批模式,在编辑器区域显示审批选项 */
|
|
3274
3640
|
enterApprovalMode(title, options) {
|
|
3275
3641
|
this.#executing = false;
|
|
3276
|
-
if (this.#
|
|
3277
|
-
|
|
3278
|
-
this.#
|
|
3642
|
+
if (this.#unregLoading) {
|
|
3643
|
+
this.#unregLoading();
|
|
3644
|
+
this.#unregLoading = null;
|
|
3279
3645
|
}
|
|
3280
3646
|
this.#approvalState = {
|
|
3281
3647
|
title,
|
|
@@ -3310,23 +3676,17 @@ var ZapmycoEditor = class extends Editor {
|
|
|
3310
3676
|
if (this.getText().length === 0 && this.onCtrlD) this.onCtrlD();
|
|
3311
3677
|
return;
|
|
3312
3678
|
}
|
|
3313
|
-
if (matchesKey(data, Key.ctrl("
|
|
3679
|
+
if (matchesKey(data, Key.ctrl("g"))) {
|
|
3314
3680
|
if (this.onOpenEditor) this.onOpenEditor();
|
|
3315
3681
|
return;
|
|
3316
3682
|
}
|
|
3317
|
-
if (matchesKey(data, Key.
|
|
3318
|
-
this.
|
|
3683
|
+
if (matchesKey(data, Key.ctrl("b")) && this.onRunInBackground) {
|
|
3684
|
+
this.onRunInBackground();
|
|
3319
3685
|
return;
|
|
3320
3686
|
}
|
|
3321
|
-
if (matchesKey(data, Key.ctrl("t"))) {
|
|
3322
|
-
|
|
3323
|
-
|
|
3324
|
-
return;
|
|
3325
|
-
}
|
|
3326
|
-
if (this.onToggleThinking) {
|
|
3327
|
-
this.onToggleThinking();
|
|
3328
|
-
return;
|
|
3329
|
-
}
|
|
3687
|
+
if (matchesKey(data, Key.ctrl("t")) && this.onToggleThinking) {
|
|
3688
|
+
this.onToggleThinking();
|
|
3689
|
+
return;
|
|
3330
3690
|
}
|
|
3331
3691
|
if (matchesKey(data, Key.ctrl("y")) && this.onToggleThinking) {
|
|
3332
3692
|
this.onToggleThinking();
|
|
@@ -3377,13 +3737,19 @@ var ZapmycoEditor = class extends Editor {
|
|
|
3377
3737
|
this.#showSpinner = showSpinner;
|
|
3378
3738
|
if (executing && showSpinner) {
|
|
3379
3739
|
this.#loadingFrame = 0;
|
|
3380
|
-
this.#
|
|
3740
|
+
this.#lastLoadingTick = 0;
|
|
3741
|
+
this.#unregLoading = this.#animationManager.register((now) => {
|
|
3742
|
+
if (now - this.#lastLoadingTick < 100) return;
|
|
3743
|
+
this.#lastLoadingTick = now;
|
|
3381
3744
|
this.#loadingFrame = (this.#loadingFrame + 1) % LOADING_FRAMES$1.length;
|
|
3382
3745
|
this.tui?.requestRender();
|
|
3383
|
-
}
|
|
3384
|
-
} else
|
|
3385
|
-
|
|
3386
|
-
|
|
3746
|
+
});
|
|
3747
|
+
} else {
|
|
3748
|
+
if (this.#unregLoading) {
|
|
3749
|
+
this.#unregLoading();
|
|
3750
|
+
this.#unregLoading = null;
|
|
3751
|
+
}
|
|
3752
|
+
this.#loadingFrame = 0;
|
|
3387
3753
|
}
|
|
3388
3754
|
this.tui?.requestRender();
|
|
3389
3755
|
}
|
|
@@ -3453,17 +3819,13 @@ var ZapmycoEditor = class extends Editor {
|
|
|
3453
3819
|
* 显示 TaskManage 创建的任务列表,支持折叠/展开两种模式。
|
|
3454
3820
|
* 类似 Claude Code 的 TaskListV2,使用 pi-tui Container 实现。
|
|
3455
3821
|
*
|
|
3456
|
-
*
|
|
3457
|
-
* 📋 3 tasks · ◼ 1 in_progress · ◻ 1 pending · ✔ 1 completed Ctrl+T expand
|
|
3458
|
-
*
|
|
3459
|
-
* 展开模式(Ctrl+T 切换):
|
|
3822
|
+
* 有任务时始终展开(多行):
|
|
3460
3823
|
* ⠋ #1 Search files
|
|
3461
3824
|
* ◻ #2 Core logic
|
|
3462
3825
|
* ◻ #3 Tests ▸ blocked by #1
|
|
3463
3826
|
* ✔ #4 Analysis
|
|
3464
3827
|
*
|
|
3465
3828
|
* 1 in_progress · 1 pending · 1 completed
|
|
3466
|
-
* (Ctrl+T collapse)
|
|
3467
3829
|
*
|
|
3468
3830
|
* 自动隐藏:无任务时 render() 返回 []。
|
|
3469
3831
|
* 进行中任务:显示 loading spinner 动画 + cyan 高亮。
|
|
@@ -3499,34 +3861,22 @@ const STATUS_ICONS = {
|
|
|
3499
3861
|
* 固定在 OutputArea 和 AgentStatusBar 之间。
|
|
3500
3862
|
*/
|
|
3501
3863
|
var TaskStatusBar = class extends Container {
|
|
3502
|
-
/**
|
|
3503
|
-
|
|
3504
|
-
*
|
|
3505
|
-
* - false(默认):根据任务状态自动决定展开/折叠
|
|
3506
|
-
* - true:用户通过 Ctrl+T 手动折叠,覆盖自动行为
|
|
3507
|
-
*/
|
|
3508
|
-
#forceCollapsed = false;
|
|
3864
|
+
/** AnimationManager 实例(用于渲染周期驱动的动画) */
|
|
3865
|
+
#animationManager;
|
|
3509
3866
|
/** TaskStore 引用(只读,不写) */
|
|
3510
3867
|
#store;
|
|
3511
3868
|
/** loading 动画帧索引 */
|
|
3512
3869
|
#loadingFrame = 0;
|
|
3513
|
-
/**
|
|
3514
|
-
#
|
|
3870
|
+
/** AnimationManager 回调注销函数 */
|
|
3871
|
+
#unregLoading = null;
|
|
3872
|
+
/** 上次帧推进时间戳 */
|
|
3873
|
+
#lastLoadingTick = 0;
|
|
3515
3874
|
/** 上次是否有 in_progress 任务(用于启停动画) */
|
|
3516
3875
|
#hadInProgress = false;
|
|
3517
|
-
constructor(store) {
|
|
3876
|
+
constructor(store, animationManager) {
|
|
3518
3877
|
super();
|
|
3519
3878
|
this.#store = store;
|
|
3520
|
-
|
|
3521
|
-
/**
|
|
3522
|
-
* 切换展开/折叠状态
|
|
3523
|
-
*
|
|
3524
|
-
* 用户手动 Ctrl+T 时调用,设置 forceCollapsed 标记。
|
|
3525
|
-
* 当新任务出现时,forceCollapsed 会被自动重置。
|
|
3526
|
-
*/
|
|
3527
|
-
toggle() {
|
|
3528
|
-
this.#forceCollapsed = !this.#forceCollapsed;
|
|
3529
|
-
this.invalidate();
|
|
3879
|
+
this.#animationManager = animationManager;
|
|
3530
3880
|
}
|
|
3531
3881
|
/** 当前是否处于展开状态 */
|
|
3532
3882
|
get isExpanded() {
|
|
@@ -3535,22 +3885,16 @@ var TaskStatusBar = class extends Container {
|
|
|
3535
3885
|
}
|
|
3536
3886
|
/**
|
|
3537
3887
|
* 当 TaskStore 变化时由外部调用
|
|
3538
|
-
*
|
|
3539
|
-
* 如果出现新的活跃任务,自动展开;全部完成后自动折叠。
|
|
3540
3888
|
*/
|
|
3541
3889
|
onTasksChanged() {
|
|
3542
|
-
const summary = this.#store.summary();
|
|
3543
|
-
if (summary.pending > 0 || summary.in_progress > 0) this.#forceCollapsed = false;
|
|
3544
3890
|
this.invalidate();
|
|
3545
3891
|
}
|
|
3546
3892
|
invalidate() {
|
|
3547
3893
|
super.invalidate();
|
|
3548
3894
|
}
|
|
3549
|
-
/**
|
|
3895
|
+
/** 判断当前是否应展开(有任务时始终展开) */
|
|
3550
3896
|
#shouldExpand(summary) {
|
|
3551
|
-
|
|
3552
|
-
if (this.#forceCollapsed) return false;
|
|
3553
|
-
return summary.pending > 0 || summary.in_progress > 0;
|
|
3897
|
+
return summary.total > 0;
|
|
3554
3898
|
}
|
|
3555
3899
|
/**
|
|
3556
3900
|
* 管理 loading 动画的启停
|
|
@@ -3562,21 +3906,24 @@ var TaskStatusBar = class extends Container {
|
|
|
3562
3906
|
if (hasInProgress && !this.#hadInProgress) this.#startLoading();
|
|
3563
3907
|
else if (!hasInProgress && this.#hadInProgress) this.#stopLoading();
|
|
3564
3908
|
}
|
|
3565
|
-
/** 启动 loading
|
|
3909
|
+
/** 启动 loading 动画(由 animationManager 在渲染周期中驱动) */
|
|
3566
3910
|
#startLoading() {
|
|
3567
|
-
if (this.#
|
|
3568
|
-
this.#
|
|
3911
|
+
if (this.#unregLoading) return;
|
|
3912
|
+
this.#lastLoadingTick = 0;
|
|
3913
|
+
this.#unregLoading = this.#animationManager.register((now) => {
|
|
3914
|
+
if (now - this.#lastLoadingTick < LOADING_INTERVAL_MS) return;
|
|
3915
|
+
this.#lastLoadingTick = now;
|
|
3569
3916
|
this.#loadingFrame = (this.#loadingFrame + 1) % LOADING_FRAMES.length;
|
|
3570
3917
|
this.invalidate();
|
|
3571
|
-
}
|
|
3918
|
+
});
|
|
3572
3919
|
}
|
|
3573
3920
|
/** 停止 loading 动画 */
|
|
3574
3921
|
#stopLoading() {
|
|
3575
|
-
if (this.#
|
|
3576
|
-
|
|
3577
|
-
this.#
|
|
3578
|
-
this.#loadingFrame = 0;
|
|
3922
|
+
if (this.#unregLoading) {
|
|
3923
|
+
this.#unregLoading();
|
|
3924
|
+
this.#unregLoading = null;
|
|
3579
3925
|
}
|
|
3926
|
+
this.#loadingFrame = 0;
|
|
3580
3927
|
}
|
|
3581
3928
|
/** 获取当前 in_progress 的图标(可能为动画帧) */
|
|
3582
3929
|
#getInProgressIcon() {
|
|
@@ -3606,8 +3953,7 @@ var TaskStatusBar = class extends Container {
|
|
|
3606
3953
|
if (summary.pending > 0) parts.push(`${STATUS_ICONS.pending} ${summary.pending} pending`);
|
|
3607
3954
|
if (summary.completed > 0) parts.push(chalk.green(`${STATUS_ICONS.completed} ${summary.completed} completed`));
|
|
3608
3955
|
if (summary.cancelled > 0) parts.push(chalk.red.dim(`${STATUS_ICONS.cancelled} ${summary.cancelled} cancelled`));
|
|
3609
|
-
|
|
3610
|
-
return ` ${parts.join(" · ")} ${hint}`;
|
|
3956
|
+
return ` ${parts.join(" · ")}`;
|
|
3611
3957
|
}
|
|
3612
3958
|
/** 渲染展开模式多行 */
|
|
3613
3959
|
#renderExpanded(tasks, summary, width) {
|
|
@@ -3621,7 +3967,6 @@ var TaskStatusBar = class extends Container {
|
|
|
3621
3967
|
if (summary.completed > 0) summaryParts.push(chalk.green(`${summary.completed} completed`));
|
|
3622
3968
|
if (summary.cancelled > 0) summaryParts.push(chalk.red.dim(`${summary.cancelled} cancelled`));
|
|
3623
3969
|
lines.push(` ${summaryParts.join(" · ")}`);
|
|
3624
|
-
lines.push(chalk.dim(" (Ctrl+T collapse)"));
|
|
3625
3970
|
return lines;
|
|
3626
3971
|
}
|
|
3627
3972
|
/** 渲染单个任务行 */
|
|
@@ -3771,7 +4116,7 @@ function parseField(field, spec) {
|
|
|
3771
4116
|
function parseFieldPart(part, spec) {
|
|
3772
4117
|
if (part.startsWith("*/")) {
|
|
3773
4118
|
const step = parseInt(part.slice(2), 10);
|
|
3774
|
-
if (isNaN(step) || step < 1) return null;
|
|
4119
|
+
if (Number.isNaN(step) || step < 1) return null;
|
|
3775
4120
|
return new StepMatcher(step, spec.min);
|
|
3776
4121
|
}
|
|
3777
4122
|
if (part === "*") return new WildcardMatcher();
|
|
@@ -3779,12 +4124,12 @@ function parseFieldPart(part, spec) {
|
|
|
3779
4124
|
if (rangeMatch) {
|
|
3780
4125
|
const start = parseInt(rangeMatch[1] ?? "", 10);
|
|
3781
4126
|
const end = parseInt(rangeMatch[2] ?? "", 10);
|
|
3782
|
-
if (isNaN(start) || isNaN(end)) return null;
|
|
4127
|
+
if (Number.isNaN(start) || Number.isNaN(end)) return null;
|
|
3783
4128
|
if (start < spec.min || end > spec.max || start > end) return null;
|
|
3784
4129
|
return new RangeMatcher(start, end);
|
|
3785
4130
|
}
|
|
3786
4131
|
const value = parseInt(part, 10);
|
|
3787
|
-
if (!isNaN(value) && value >= spec.min && value <= spec.max) return new ValueMatcher([value]);
|
|
4132
|
+
if (!Number.isNaN(value) && value >= spec.min && value <= spec.max) return new ValueMatcher([value]);
|
|
3788
4133
|
return null;
|
|
3789
4134
|
}
|
|
3790
4135
|
/**
|
|
@@ -3975,7 +4320,7 @@ const CRON_CONSTANTS = {
|
|
|
3975
4320
|
*
|
|
3976
4321
|
* @module cli/repl/cron/cron-scheduler
|
|
3977
4322
|
*/
|
|
3978
|
-
const log$
|
|
4323
|
+
const log$22 = logger.child("cron:scheduler");
|
|
3979
4324
|
var CronScheduler = class extends EventEmitter {
|
|
3980
4325
|
store;
|
|
3981
4326
|
jobs = [];
|
|
@@ -3996,7 +4341,7 @@ var CronScheduler = class extends EventEmitter {
|
|
|
3996
4341
|
if (this.running) return;
|
|
3997
4342
|
const loadedJobs = await this.store.load();
|
|
3998
4343
|
this.jobs = loadedJobs;
|
|
3999
|
-
log$
|
|
4344
|
+
log$22.info(`调度器启动,加载 ${loadedJobs.length} 个 durable 任务`);
|
|
4000
4345
|
await this.handleMissedJobs();
|
|
4001
4346
|
this.checkAutoExpiry();
|
|
4002
4347
|
this.running = true;
|
|
@@ -4012,7 +4357,7 @@ var CronScheduler = class extends EventEmitter {
|
|
|
4012
4357
|
clearInterval(this.timer);
|
|
4013
4358
|
this.timer = null;
|
|
4014
4359
|
}
|
|
4015
|
-
log$
|
|
4360
|
+
log$22.info("调度器已停止");
|
|
4016
4361
|
}
|
|
4017
4362
|
/** 添加任务 */
|
|
4018
4363
|
async addJob(job) {
|
|
@@ -4024,7 +4369,7 @@ var CronScheduler = class extends EventEmitter {
|
|
|
4024
4369
|
this.jobs.push(job);
|
|
4025
4370
|
await this.store.persist(this.jobs);
|
|
4026
4371
|
} else this.sessionJobs.push(job);
|
|
4027
|
-
log$
|
|
4372
|
+
log$22.info("任务已添加", {
|
|
4028
4373
|
id: job.id,
|
|
4029
4374
|
cron: job.cron,
|
|
4030
4375
|
durable: job.durable
|
|
@@ -4154,7 +4499,7 @@ var CronScheduler = class extends EventEmitter {
|
|
|
4154
4499
|
}, delay);
|
|
4155
4500
|
}
|
|
4156
4501
|
if (toDelete.length > 0) {
|
|
4157
|
-
log$
|
|
4502
|
+
log$22.info(`跳过 ${toDelete.length} 个错过的一次性任务(超出补发上限)`);
|
|
4158
4503
|
this.emit("missed-overflow", {
|
|
4159
4504
|
count: toDelete.length,
|
|
4160
4505
|
jobIds: toDelete.map((m) => m.id)
|
|
@@ -4172,7 +4517,7 @@ var CronScheduler = class extends EventEmitter {
|
|
|
4172
4517
|
job.lastFiredAt = now;
|
|
4173
4518
|
job.fireCount++;
|
|
4174
4519
|
this.removeJob(job.id);
|
|
4175
|
-
log$
|
|
4520
|
+
log$22.info("任务已过期并触发最后一次", { id: job.id });
|
|
4176
4521
|
}
|
|
4177
4522
|
}
|
|
4178
4523
|
}
|
|
@@ -4233,7 +4578,7 @@ function applyOneShotJitter(jobId, rawNext) {
|
|
|
4233
4578
|
*
|
|
4234
4579
|
* @module cli/repl/cron/cron-store
|
|
4235
4580
|
*/
|
|
4236
|
-
const log$
|
|
4581
|
+
const log$21 = logger.child("cron:store");
|
|
4237
4582
|
const STORE_FILE = join(join(homedir(), ".zapmyco", "cron"), "scheduled_tasks.json");
|
|
4238
4583
|
var CronStore = class {
|
|
4239
4584
|
filePath;
|
|
@@ -4257,13 +4602,13 @@ var CronStore = class {
|
|
|
4257
4602
|
const raw = await readFile(this.filePath, "utf-8");
|
|
4258
4603
|
const data = JSON.parse(raw);
|
|
4259
4604
|
if (!Array.isArray(data)) {
|
|
4260
|
-
log$
|
|
4605
|
+
log$21.warn("存储文件格式无效(非数组),将使用空列表");
|
|
4261
4606
|
return [];
|
|
4262
4607
|
}
|
|
4263
4608
|
return this.validateJobs(data);
|
|
4264
4609
|
} catch (err) {
|
|
4265
4610
|
if (err.code === "ENOENT") return [];
|
|
4266
|
-
log$
|
|
4611
|
+
log$21.warn("加载定时任务文件失败,将使用空列表", { error: err instanceof Error ? err.message : String(err) });
|
|
4267
4612
|
return [];
|
|
4268
4613
|
}
|
|
4269
4614
|
}
|
|
@@ -4310,7 +4655,7 @@ var CronStore = class {
|
|
|
4310
4655
|
if (typeof obj.maxFires === "number") job.maxFires = obj.maxFires;
|
|
4311
4656
|
valid.push(job);
|
|
4312
4657
|
}
|
|
4313
|
-
if (valid.length < raw.length) log$
|
|
4658
|
+
if (valid.length < raw.length) log$21.warn(`跳过 ${raw.length - valid.length} 个无效任务条目`);
|
|
4314
4659
|
return valid;
|
|
4315
4660
|
}
|
|
4316
4661
|
};
|
|
@@ -4328,7 +4673,7 @@ function getCronStore() {
|
|
|
4328
4673
|
* 基于内存的环形缓冲区,记录 REPL 会话中的用户输入和执行结果。
|
|
4329
4674
|
* 支持文件持久化到 ~/.zapmyco/history.json,跨会话恢复。
|
|
4330
4675
|
*/
|
|
4331
|
-
const log$
|
|
4676
|
+
const log$20 = logger.child("history:store");
|
|
4332
4677
|
/** 默认最大历史条数 */
|
|
4333
4678
|
const DEFAULT_MAX_SIZE = 100;
|
|
4334
4679
|
/** 历史文件存储路径 */
|
|
@@ -4387,13 +4732,13 @@ var HistoryStore = class {
|
|
|
4387
4732
|
if (Array.isArray(data.entries)) {
|
|
4388
4733
|
this.entries = data.entries.slice(-this.maxSize);
|
|
4389
4734
|
this.nextId = typeof data.nextId === "number" ? data.nextId : 1;
|
|
4390
|
-
log$
|
|
4735
|
+
log$20.debug("历史记录已加载", {
|
|
4391
4736
|
count: this.entries.length,
|
|
4392
4737
|
nextId: this.nextId
|
|
4393
4738
|
});
|
|
4394
4739
|
}
|
|
4395
4740
|
} catch {
|
|
4396
|
-
log$
|
|
4741
|
+
log$20.debug("无历史文件或加载失败,使用空历史");
|
|
4397
4742
|
}
|
|
4398
4743
|
}
|
|
4399
4744
|
/** 持久化历史记录到文件 */
|
|
@@ -4406,7 +4751,7 @@ var HistoryStore = class {
|
|
|
4406
4751
|
}, null, 2);
|
|
4407
4752
|
writeFileSync(this.filePath, data, "utf-8");
|
|
4408
4753
|
} catch (err) {
|
|
4409
|
-
log$
|
|
4754
|
+
log$20.warn("历史记录保存失败", { error: err instanceof Error ? err.message : String(err) });
|
|
4410
4755
|
}
|
|
4411
4756
|
}
|
|
4412
4757
|
};
|
|
@@ -4531,7 +4876,7 @@ const OVERLAY_OPTIONS = {
|
|
|
4531
4876
|
/** 截断文本到指定宽度 */
|
|
4532
4877
|
function truncate(text, maxLen) {
|
|
4533
4878
|
if (text.length <= maxLen) return text;
|
|
4534
|
-
return text.slice(0, maxLen - 1)
|
|
4879
|
+
return `${text.slice(0, maxLen - 1)}…`;
|
|
4535
4880
|
}
|
|
4536
4881
|
var AskUserQuestionComponent = class {
|
|
4537
4882
|
tui;
|
|
@@ -5042,7 +5387,7 @@ var OutputFormatter = class {
|
|
|
5042
5387
|
const c = this.getColor();
|
|
5043
5388
|
const lines = [
|
|
5044
5389
|
"",
|
|
5045
|
-
c.bold(`
|
|
5390
|
+
c.bold(` ${t("output.taskGraph.title")}:`),
|
|
5046
5391
|
c.gray(` ${t("output.taskGraph.total", {
|
|
5047
5392
|
count: graph.nodes.size,
|
|
5048
5393
|
layers: graph.layers.length
|
|
@@ -5181,7 +5526,7 @@ var OutputFormatter = class {
|
|
|
5181
5526
|
lines.push(" 分类评分:");
|
|
5182
5527
|
const bar = (s) => {
|
|
5183
5528
|
const filled = Math.round(s / 10);
|
|
5184
|
-
return c.green("█".repeat(filled)) + c.gray("░".repeat(10 - filled))
|
|
5529
|
+
return `${c.green("█".repeat(filled)) + c.gray("░".repeat(10 - filled))} ${s}/100`;
|
|
5185
5530
|
};
|
|
5186
5531
|
lines.push(` permissions: ${bar(report.scores.permissions)}`);
|
|
5187
5532
|
lines.push(` shell: ${bar(report.scores.shell)}`);
|
|
@@ -5288,7 +5633,7 @@ var Renderer = class {
|
|
|
5288
5633
|
*
|
|
5289
5634
|
* @module core/agent-team
|
|
5290
5635
|
*/
|
|
5291
|
-
const log$
|
|
5636
|
+
const log$19 = logger.child("background-store");
|
|
5292
5637
|
/**
|
|
5293
5638
|
* 后台任务持久化存储
|
|
5294
5639
|
*
|
|
@@ -5318,14 +5663,14 @@ var BackgroundTaskStore = class {
|
|
|
5318
5663
|
const entries = JSON.parse(raw);
|
|
5319
5664
|
this.tasks.clear();
|
|
5320
5665
|
for (const entry of entries) this.tasks.set(entry.taskId, entry);
|
|
5321
|
-
log$
|
|
5666
|
+
log$19.debug("后台任务已加载", {
|
|
5322
5667
|
count: entries.length,
|
|
5323
5668
|
path: this.filePath
|
|
5324
5669
|
});
|
|
5325
5670
|
return entries;
|
|
5326
5671
|
}
|
|
5327
5672
|
} catch (err) {
|
|
5328
|
-
log$
|
|
5673
|
+
log$19.warn("后台任务加载失败", {
|
|
5329
5674
|
error: String(err),
|
|
5330
5675
|
path: this.filePath
|
|
5331
5676
|
});
|
|
@@ -5384,7 +5729,7 @@ var BackgroundTaskStore = class {
|
|
|
5384
5729
|
const entries = Array.from(this.tasks.values());
|
|
5385
5730
|
writeFileSync(this.filePath, JSON.stringify(entries, null, 2), "utf-8");
|
|
5386
5731
|
} catch (err) {
|
|
5387
|
-
log$
|
|
5732
|
+
log$19.error("后台任务持久化失败", {
|
|
5388
5733
|
error: String(err),
|
|
5389
5734
|
path: this.filePath
|
|
5390
5735
|
});
|
|
@@ -5410,7 +5755,7 @@ var BackgroundTaskStore = class {
|
|
|
5410
5755
|
}
|
|
5411
5756
|
if (cleaned > 0) {
|
|
5412
5757
|
this.persist();
|
|
5413
|
-
log$
|
|
5758
|
+
log$19.info("清理过期后台任务", { cleaned });
|
|
5414
5759
|
}
|
|
5415
5760
|
return cleaned;
|
|
5416
5761
|
}
|
|
@@ -5427,7 +5772,7 @@ var BackgroundTaskStore = class {
|
|
|
5427
5772
|
*
|
|
5428
5773
|
* @module core/agent-team
|
|
5429
5774
|
*/
|
|
5430
|
-
const log$
|
|
5775
|
+
const log$18 = logger.child("agent-message-bus");
|
|
5431
5776
|
/**
|
|
5432
5777
|
* Agent 消息总线(单例)
|
|
5433
5778
|
*
|
|
@@ -5461,12 +5806,12 @@ var AgentMessageBus = class {
|
|
|
5461
5806
|
};
|
|
5462
5807
|
const targetInstance = getAgentInstanceManager().get(toAgentId);
|
|
5463
5808
|
if (targetInstance) targetInstance.inbox.push(fullMessage);
|
|
5464
|
-
else log$
|
|
5809
|
+
else log$18.warn("目标 Agent 实例不存在,消息丢弃", {
|
|
5465
5810
|
toAgentId,
|
|
5466
5811
|
messageId: fullMessage.messageId
|
|
5467
5812
|
});
|
|
5468
5813
|
this.emitter.emit(`msg:${toAgentId}`, fullMessage);
|
|
5469
|
-
log$
|
|
5814
|
+
log$18.debug("消息已投递", {
|
|
5470
5815
|
from: fromAgentId,
|
|
5471
5816
|
to: toAgentId,
|
|
5472
5817
|
type: fullMessage.type,
|
|
@@ -5549,7 +5894,7 @@ function getAgentMessageBus() {
|
|
|
5549
5894
|
*
|
|
5550
5895
|
* @module core/agent-team
|
|
5551
5896
|
*/
|
|
5552
|
-
const log$
|
|
5897
|
+
const log$17 = logger.child("background-agent-manager");
|
|
5553
5898
|
/**
|
|
5554
5899
|
* 后台 Agent 管理器(单例)
|
|
5555
5900
|
*/
|
|
@@ -5606,7 +5951,7 @@ var BackgroundAgentManager = class {
|
|
|
5606
5951
|
parentAgentId: params.parentInstanceId
|
|
5607
5952
|
});
|
|
5608
5953
|
this.runAsync(taskId, params, abortController, timeoutMs).catch((err) => {
|
|
5609
|
-
log$
|
|
5954
|
+
log$17.error("后台 Agent 意外崩溃", {
|
|
5610
5955
|
taskId,
|
|
5611
5956
|
error: String(err)
|
|
5612
5957
|
});
|
|
@@ -5626,7 +5971,7 @@ var BackgroundAgentManager = class {
|
|
|
5626
5971
|
const orchestrator = this.orchestrator;
|
|
5627
5972
|
const messageBus = getAgentMessageBus();
|
|
5628
5973
|
const timeoutHandle = setTimeout(() => {
|
|
5629
|
-
log$
|
|
5974
|
+
log$17.warn("后台 Agent 超时,自动取消", {
|
|
5630
5975
|
taskId,
|
|
5631
5976
|
timeoutMs
|
|
5632
5977
|
});
|
|
@@ -5670,7 +6015,7 @@ var BackgroundAgentManager = class {
|
|
|
5670
6015
|
taskId,
|
|
5671
6016
|
requiresResponse: false
|
|
5672
6017
|
});
|
|
5673
|
-
log$
|
|
6018
|
+
log$17.info("后台 Agent 完成通知已发送", {
|
|
5674
6019
|
taskId,
|
|
5675
6020
|
instanceId: result.instanceId,
|
|
5676
6021
|
parentId: params.parentInstanceId,
|
|
@@ -5697,7 +6042,7 @@ var BackgroundAgentManager = class {
|
|
|
5697
6042
|
completedAt: Date.now(),
|
|
5698
6043
|
error
|
|
5699
6044
|
});
|
|
5700
|
-
log$
|
|
6045
|
+
log$17.warn("后台 Agent 失败", {
|
|
5701
6046
|
taskId,
|
|
5702
6047
|
error
|
|
5703
6048
|
});
|
|
@@ -5734,7 +6079,7 @@ var BackgroundAgentManager = class {
|
|
|
5734
6079
|
if (runtime.instanceId) try {
|
|
5735
6080
|
await getAgentInstanceManager().cancel(runtime.instanceId);
|
|
5736
6081
|
} catch {}
|
|
5737
|
-
log$
|
|
6082
|
+
log$17.info("后台 Agent 已取消", { taskId });
|
|
5738
6083
|
return true;
|
|
5739
6084
|
}
|
|
5740
6085
|
/**
|
|
@@ -5743,13 +6088,13 @@ var BackgroundAgentManager = class {
|
|
|
5743
6088
|
restore() {
|
|
5744
6089
|
this.store.load();
|
|
5745
6090
|
const stale = this.store.cleanStale();
|
|
5746
|
-
if (stale > 0) log$
|
|
6091
|
+
if (stale > 0) log$17.info("跨会话恢复:清理了过期后台任务", { count: stale });
|
|
5747
6092
|
const active = this.store.listActive();
|
|
5748
6093
|
for (const entry of active) this.store.updateStatus(entry.taskId, "failed", {
|
|
5749
6094
|
completedAt: Date.now(),
|
|
5750
6095
|
error: "会话终止导致任务丢失"
|
|
5751
6096
|
});
|
|
5752
|
-
if (active.length > 0) log$
|
|
6097
|
+
if (active.length > 0) log$17.info("跨会话恢复:标记活跃任务为 failed", { count: active.length });
|
|
5753
6098
|
}
|
|
5754
6099
|
};
|
|
5755
6100
|
/** 全局单例 */
|
|
@@ -5968,7 +6313,7 @@ function createDeferred() {
|
|
|
5968
6313
|
*
|
|
5969
6314
|
* @module core/question/question-manager
|
|
5970
6315
|
*/
|
|
5971
|
-
const log$
|
|
6316
|
+
const log$16 = logger.child("question-manager");
|
|
5972
6317
|
/** 问题超时时间(5 分钟) */
|
|
5973
6318
|
const QUESTION_TIMEOUT_MS = 300 * 1e3;
|
|
5974
6319
|
var QuestionManager = class {
|
|
@@ -6014,13 +6359,13 @@ var QuestionManager = class {
|
|
|
6014
6359
|
requestId,
|
|
6015
6360
|
questionCount: params.questions.length
|
|
6016
6361
|
});
|
|
6017
|
-
log$
|
|
6362
|
+
log$16.debug("提问已发出", {
|
|
6018
6363
|
requestId,
|
|
6019
6364
|
questionCount: params.questions.length
|
|
6020
6365
|
});
|
|
6021
6366
|
const timeout = setTimeout(() => {
|
|
6022
6367
|
if (!deferred.isSettled) {
|
|
6023
|
-
log$
|
|
6368
|
+
log$16.warn("提问超时,自动取消", { requestId });
|
|
6024
6369
|
deferred.reject(/* @__PURE__ */ new Error("提问超时,用户未在 5 分钟内回答"));
|
|
6025
6370
|
this.pending.delete(requestId);
|
|
6026
6371
|
eventBus.emit("question:timeout", { requestId });
|
|
@@ -6034,7 +6379,7 @@ var QuestionManager = class {
|
|
|
6034
6379
|
requestId,
|
|
6035
6380
|
answerCount: Object.keys(result.answers).length
|
|
6036
6381
|
});
|
|
6037
|
-
log$
|
|
6382
|
+
log$16.debug("提问已回答", { requestId });
|
|
6038
6383
|
return result;
|
|
6039
6384
|
} catch (err) {
|
|
6040
6385
|
clearTimeout(timeout);
|
|
@@ -6043,7 +6388,7 @@ var QuestionManager = class {
|
|
|
6043
6388
|
requestId,
|
|
6044
6389
|
reason: err instanceof Error ? err.message : String(err)
|
|
6045
6390
|
});
|
|
6046
|
-
log$
|
|
6391
|
+
log$16.debug("提问已取消", {
|
|
6047
6392
|
requestId,
|
|
6048
6393
|
error: err instanceof Error ? err.message : String(err)
|
|
6049
6394
|
});
|
|
@@ -6065,7 +6410,7 @@ var QuestionManager = class {
|
|
|
6065
6410
|
const count = this.pending.size;
|
|
6066
6411
|
for (const [, entry] of this.pending) if (!entry.deferred.isSettled) entry.deferred.reject(error);
|
|
6067
6412
|
this.pending.clear();
|
|
6068
|
-
if (count > 0) log$
|
|
6413
|
+
if (count > 0) log$16.debug("已清理所有待处理问题", { count });
|
|
6069
6414
|
}
|
|
6070
6415
|
};
|
|
6071
6416
|
let globalQuestionManager = null;
|
|
@@ -7143,7 +7488,7 @@ function isPathWithinWorkdir(resolvedPath, workdir) {
|
|
|
7143
7488
|
try {
|
|
7144
7489
|
normalizedWorkdir = realpathSync(normalizedWorkdir).replace(/\\/g, "/");
|
|
7145
7490
|
} catch {}
|
|
7146
|
-
if (!normalizedPath.startsWith(normalizedWorkdir
|
|
7491
|
+
if (!normalizedPath.startsWith(`${normalizedWorkdir}/`) && normalizedPath !== normalizedWorkdir) return false;
|
|
7147
7492
|
return true;
|
|
7148
7493
|
}
|
|
7149
7494
|
/**
|
|
@@ -9298,7 +9643,7 @@ function runForeground(childProcess, timeoutSec, signal) {
|
|
|
9298
9643
|
childProcess.on("error", (err) => {
|
|
9299
9644
|
settle({
|
|
9300
9645
|
stdout: stdoutChunks.join(""),
|
|
9301
|
-
stderr: stderrChunks.join("")
|
|
9646
|
+
stderr: `${stderrChunks.join("")}\n[进程错误: ${err.message}]`,
|
|
9302
9647
|
exitCode: -1,
|
|
9303
9648
|
signal: null,
|
|
9304
9649
|
timedOut: false,
|
|
@@ -9619,7 +9964,7 @@ function formatDuration(ms) {
|
|
|
9619
9964
|
}
|
|
9620
9965
|
function truncateCommand(command, maxLen = 80) {
|
|
9621
9966
|
if (command.length <= maxLen) return command;
|
|
9622
|
-
return command.slice(0, maxLen - 3)
|
|
9967
|
+
return `${command.slice(0, maxLen - 3)}...`;
|
|
9623
9968
|
}
|
|
9624
9969
|
|
|
9625
9970
|
//#endregion
|
|
@@ -9713,6 +10058,137 @@ function parseArgs(args) {
|
|
|
9713
10058
|
if (current.length > 0) result.push(current);
|
|
9714
10059
|
return result;
|
|
9715
10060
|
}
|
|
10061
|
+
/** 代码块模式: ```! ... ``` */
|
|
10062
|
+
const SHELL_BLOCK_PATTERN = /```!\s*\n?([\s\S]*?)\n?```/g;
|
|
10063
|
+
/** 行内模式: !`command`(前面需有空白或行首) */
|
|
10064
|
+
const SHELL_INLINE_PATTERN = /(?<=^|\s)!`([^`]+)`/gm;
|
|
10065
|
+
/** Shell 命令默认超时(秒) */
|
|
10066
|
+
const SKILL_SHELL_TIMEOUT_SEC = 30;
|
|
10067
|
+
/** Shell 命令输出最大字符数 */
|
|
10068
|
+
const SKILL_SHELL_MAX_OUTPUT_CHARS = 1e4;
|
|
10069
|
+
/**
|
|
10070
|
+
* 执行 SKILL.md 中的 Shell 命令
|
|
10071
|
+
*
|
|
10072
|
+
* 支持两种语法:
|
|
10073
|
+
* 1. 代码块: ```! echo "hello" ```
|
|
10074
|
+
* 2. 行内: 前导 !`echo "hello"`
|
|
10075
|
+
*/
|
|
10076
|
+
async function executeShellCommandsInSkill(content) {
|
|
10077
|
+
if (!content.includes("```!") && !content.includes("!`")) return content;
|
|
10078
|
+
let result = content;
|
|
10079
|
+
result = await replaceBlockCommands(result);
|
|
10080
|
+
result = await replaceInlineCommands(result);
|
|
10081
|
+
return result;
|
|
10082
|
+
}
|
|
10083
|
+
/** 处理 ```! ... ``` 代码块 */
|
|
10084
|
+
async function replaceBlockCommands(content) {
|
|
10085
|
+
const matches = [];
|
|
10086
|
+
const pattern = new RegExp(SHELL_BLOCK_PATTERN.source, "g");
|
|
10087
|
+
for (let match = pattern.exec(content); match !== null; match = pattern.exec(content)) matches.push({
|
|
10088
|
+
full: match[0],
|
|
10089
|
+
command: (match[1] ?? "").trim()
|
|
10090
|
+
});
|
|
10091
|
+
if (matches.length === 0) return content;
|
|
10092
|
+
const results = await Promise.all(matches.map(async (m) => ({
|
|
10093
|
+
target: m.full,
|
|
10094
|
+
replacement: await executeSingleCommand(m.command, false)
|
|
10095
|
+
})));
|
|
10096
|
+
let result = content;
|
|
10097
|
+
for (const { target, replacement } of results) result = result.replace(target, replacement);
|
|
10098
|
+
return result;
|
|
10099
|
+
}
|
|
10100
|
+
/** 处理 !`command` 行内命令 */
|
|
10101
|
+
async function replaceInlineCommands(content) {
|
|
10102
|
+
const matches = [];
|
|
10103
|
+
const pattern = new RegExp(SHELL_INLINE_PATTERN.source, "gm");
|
|
10104
|
+
for (let match = pattern.exec(content); match !== null; match = pattern.exec(content)) matches.push({
|
|
10105
|
+
full: match[0],
|
|
10106
|
+
command: (match[1] ?? "").trim()
|
|
10107
|
+
});
|
|
10108
|
+
if (matches.length === 0) return content;
|
|
10109
|
+
let result = content;
|
|
10110
|
+
for (const m of matches) {
|
|
10111
|
+
const output = await executeSingleCommand(m.command, true);
|
|
10112
|
+
result = result.replace(m.full, output);
|
|
10113
|
+
}
|
|
10114
|
+
return result;
|
|
10115
|
+
}
|
|
10116
|
+
/** 执行单个 Shell 命令并返回替换文本 */
|
|
10117
|
+
async function executeSingleCommand(command, inline) {
|
|
10118
|
+
if (!command) return "";
|
|
10119
|
+
const security = checkCommandSecurity(command);
|
|
10120
|
+
if (security.blocked) return `[Shell 命令被阻断] 命令: ${command} 原因: ${security.reason ?? "未知"}`;
|
|
10121
|
+
if (security.requiresApproval) return `[Shell 命令需审批] 命令: ${command} 原因: ${security.reason ?? "需要用户确认"}`;
|
|
10122
|
+
const shell = process.env.SHELL || "/bin/bash";
|
|
10123
|
+
try {
|
|
10124
|
+
const { stdout, stderr, exitCode } = await spawnCommand(shell, command);
|
|
10125
|
+
return formatShellOutput(stdout, stderr, exitCode, inline);
|
|
10126
|
+
} catch (err) {
|
|
10127
|
+
return `[Shell 命令执行失败] ${err instanceof Error ? err.message : String(err)}`;
|
|
10128
|
+
}
|
|
10129
|
+
}
|
|
10130
|
+
/** spawn 执行命令并返回结构化结果 */
|
|
10131
|
+
function spawnCommand(shell, command) {
|
|
10132
|
+
return new Promise((resolve) => {
|
|
10133
|
+
const child = spawn(shell, ["-c", command], {
|
|
10134
|
+
env: sanitizeEnv(),
|
|
10135
|
+
stdio: [
|
|
10136
|
+
"pipe",
|
|
10137
|
+
"pipe",
|
|
10138
|
+
"pipe"
|
|
10139
|
+
]
|
|
10140
|
+
});
|
|
10141
|
+
const stdoutChunks = [];
|
|
10142
|
+
const stderrChunks = [];
|
|
10143
|
+
child.stdout?.on("data", (data) => {
|
|
10144
|
+
stdoutChunks.push(data.toString());
|
|
10145
|
+
});
|
|
10146
|
+
child.stderr?.on("data", (data) => {
|
|
10147
|
+
stderrChunks.push(data.toString());
|
|
10148
|
+
});
|
|
10149
|
+
let settled = false;
|
|
10150
|
+
const settle = (result) => {
|
|
10151
|
+
if (settled) return;
|
|
10152
|
+
settled = true;
|
|
10153
|
+
clearTimeout(timer);
|
|
10154
|
+
resolve(result);
|
|
10155
|
+
};
|
|
10156
|
+
child.on("exit", (code) => {
|
|
10157
|
+
settle({
|
|
10158
|
+
stdout: stdoutChunks.join(""),
|
|
10159
|
+
stderr: stderrChunks.join(""),
|
|
10160
|
+
exitCode: code
|
|
10161
|
+
});
|
|
10162
|
+
});
|
|
10163
|
+
child.on("error", () => {
|
|
10164
|
+
settle({
|
|
10165
|
+
stdout: "",
|
|
10166
|
+
stderr: "进程启动失败",
|
|
10167
|
+
exitCode: -1
|
|
10168
|
+
});
|
|
10169
|
+
});
|
|
10170
|
+
const timer = setTimeout(() => {
|
|
10171
|
+
try {
|
|
10172
|
+
child.kill("SIGTERM");
|
|
10173
|
+
} catch {}
|
|
10174
|
+
setTimeout(() => {
|
|
10175
|
+
try {
|
|
10176
|
+
child.kill("SIGKILL");
|
|
10177
|
+
} catch {}
|
|
10178
|
+
}, 2e3);
|
|
10179
|
+
}, SKILL_SHELL_TIMEOUT_SEC * 1e3);
|
|
10180
|
+
});
|
|
10181
|
+
}
|
|
10182
|
+
/** 格式化 shell 命令输出 */
|
|
10183
|
+
function formatShellOutput(stdout, stderr, exitCode, inline) {
|
|
10184
|
+
let output = stdout;
|
|
10185
|
+
if (stderr) output += (output ? "\n" : "") + (inline ? `[stderr: ${stderr.trim()}]` : `[stderr]\n${stderr.trim()}`);
|
|
10186
|
+
output = stripAnsi(output);
|
|
10187
|
+
output = redactSensitiveInfo(output);
|
|
10188
|
+
output = truncateOutput(output, SKILL_SHELL_MAX_OUTPUT_CHARS);
|
|
10189
|
+
if (exitCode !== null && exitCode !== 0) output += `\n(退出码: ${exitCode})`;
|
|
10190
|
+
return output || "(无输出)";
|
|
10191
|
+
}
|
|
9716
10192
|
/**
|
|
9717
10193
|
* 创建 Skill 工具
|
|
9718
10194
|
*
|
|
@@ -9781,7 +10257,7 @@ function createSkillTool(_config) {
|
|
|
9781
10257
|
const endIdx = content.indexOf("---", 3);
|
|
9782
10258
|
if (endIdx !== -1) bodyContent = content.slice(endIdx + 3).trim();
|
|
9783
10259
|
}
|
|
9784
|
-
const
|
|
10260
|
+
const withShell = await executeShellCommandsInSkill(substituteVariables(bodyContent, params.args, skill.baseDir, skill.name));
|
|
9785
10261
|
const hint = skill.frontmatter["argument-hint"] ? ` [${skill.frontmatter["argument-hint"]}]` : "";
|
|
9786
10262
|
const instructionParts = [
|
|
9787
10263
|
`# Skill: ${skill.name}${hint}`,
|
|
@@ -9790,12 +10266,12 @@ function createSkillTool(_config) {
|
|
|
9790
10266
|
"",
|
|
9791
10267
|
"---",
|
|
9792
10268
|
"",
|
|
9793
|
-
|
|
10269
|
+
withShell,
|
|
9794
10270
|
"",
|
|
9795
10271
|
"---",
|
|
9796
10272
|
`Base directory: ${skill.baseDir}`
|
|
9797
10273
|
];
|
|
9798
|
-
if (!
|
|
10274
|
+
if (!withShell.includes(params.args ?? "") && params.args) instructionParts.push("", `ARGUMENTS: ${params.args}`);
|
|
9799
10275
|
return {
|
|
9800
10276
|
content: [{
|
|
9801
10277
|
type: "text",
|
|
@@ -9833,8 +10309,8 @@ function getSkillCommandSpecs(entries) {
|
|
|
9833
10309
|
* @param args - 用户传递的参数(可选)
|
|
9834
10310
|
* @returns 格式化后的完整指令文本
|
|
9835
10311
|
*/
|
|
9836
|
-
function formatSkillContent(skill, args) {
|
|
9837
|
-
const
|
|
10312
|
+
async function formatSkillContent(skill, args) {
|
|
10313
|
+
const withShell = await executeShellCommandsInSkill(substituteVariables(skill.body, args, skill.baseDir, skill.name));
|
|
9838
10314
|
const hint = skill.frontmatter["argument-hint"] ? ` [${skill.frontmatter["argument-hint"]}]` : "";
|
|
9839
10315
|
const instructionParts = [
|
|
9840
10316
|
`# Skill: ${skill.name}${hint}`,
|
|
@@ -9843,12 +10319,12 @@ function formatSkillContent(skill, args) {
|
|
|
9843
10319
|
"",
|
|
9844
10320
|
"---",
|
|
9845
10321
|
"",
|
|
9846
|
-
|
|
10322
|
+
withShell,
|
|
9847
10323
|
"",
|
|
9848
10324
|
"---",
|
|
9849
10325
|
`Base directory: ${skill.baseDir}`
|
|
9850
10326
|
];
|
|
9851
|
-
if (!
|
|
10327
|
+
if (!withShell.includes(args ?? "") && args) instructionParts.push("", `ARGUMENTS: ${args}`);
|
|
9852
10328
|
return instructionParts.filter(Boolean).join("\n");
|
|
9853
10329
|
}
|
|
9854
10330
|
/**
|
|
@@ -11360,7 +11836,7 @@ var WorktreeStore = class {
|
|
|
11360
11836
|
* @module core/worktree
|
|
11361
11837
|
*/
|
|
11362
11838
|
const execFileAsync = promisify(execFile);
|
|
11363
|
-
const log$
|
|
11839
|
+
const log$15 = logger.child("worktree-manager");
|
|
11364
11840
|
let globalWorktreeManager = null;
|
|
11365
11841
|
/** 获取全局 WorktreeManager 实例 */
|
|
11366
11842
|
function getWorktreeManager() {
|
|
@@ -11396,7 +11872,7 @@ var WorktreeManager = class {
|
|
|
11396
11872
|
throw new WorktreeError("无法确定 git 仓库根目录,请确保在 git 仓库中运行", "NOT_GIT_REPO");
|
|
11397
11873
|
}
|
|
11398
11874
|
try {
|
|
11399
|
-
log$
|
|
11875
|
+
log$15.info("创建 worktree", {
|
|
11400
11876
|
branchName,
|
|
11401
11877
|
worktreePath,
|
|
11402
11878
|
gitRoot
|
|
@@ -11424,14 +11900,14 @@ var WorktreeManager = class {
|
|
|
11424
11900
|
});
|
|
11425
11901
|
} catch (err) {
|
|
11426
11902
|
const msg = err instanceof Error ? err.message : String(err);
|
|
11427
|
-
log$
|
|
11903
|
+
log$15.warn("创建分支失败,清理 worktree", {
|
|
11428
11904
|
branchName,
|
|
11429
11905
|
error: msg
|
|
11430
11906
|
});
|
|
11431
11907
|
try {
|
|
11432
11908
|
await this.removeByPath(worktreePath, branchName, true);
|
|
11433
11909
|
} catch (err) {
|
|
11434
|
-
log$
|
|
11910
|
+
log$15.warn("Worktree 创建失败后清理出错", {
|
|
11435
11911
|
worktreePath,
|
|
11436
11912
|
error: err instanceof Error ? err.message : String(err)
|
|
11437
11913
|
});
|
|
@@ -11456,7 +11932,7 @@ var WorktreeManager = class {
|
|
|
11456
11932
|
createdBy: info.createdBy,
|
|
11457
11933
|
status: "active"
|
|
11458
11934
|
});
|
|
11459
|
-
log$
|
|
11935
|
+
log$15.info("Worktree 创建成功", {
|
|
11460
11936
|
id: info.id,
|
|
11461
11937
|
path: worktreePath
|
|
11462
11938
|
});
|
|
@@ -11468,13 +11944,13 @@ var WorktreeManager = class {
|
|
|
11468
11944
|
async remove(id, discardChanges) {
|
|
11469
11945
|
const info = this.activeWorktrees.get(id);
|
|
11470
11946
|
if (!info) {
|
|
11471
|
-
log$
|
|
11947
|
+
log$15.warn("尝试删除不存在的 worktree", { id });
|
|
11472
11948
|
return;
|
|
11473
11949
|
}
|
|
11474
11950
|
await this.removeByPath(info.worktreePath, info.branchName, discardChanges);
|
|
11475
11951
|
this.activeWorktrees.delete(id);
|
|
11476
11952
|
this.store.delete(id);
|
|
11477
|
-
log$
|
|
11953
|
+
log$15.info("Worktree 已删除", { id });
|
|
11478
11954
|
}
|
|
11479
11955
|
/**
|
|
11480
11956
|
* 通过路径删除 worktree(内部方法)
|
|
@@ -11484,7 +11960,7 @@ var WorktreeManager = class {
|
|
|
11484
11960
|
try {
|
|
11485
11961
|
await execFileAsync("git", ["worktree", "prune"], { timeout: 1e4 });
|
|
11486
11962
|
} catch (err) {
|
|
11487
|
-
log$
|
|
11963
|
+
log$15.warn("Worktree prune 失败(路径不存在)", {
|
|
11488
11964
|
worktreePath,
|
|
11489
11965
|
error: err instanceof Error ? err.message : String(err)
|
|
11490
11966
|
});
|
|
@@ -11498,7 +11974,7 @@ var WorktreeManager = class {
|
|
|
11498
11974
|
await execFileAsync("git", args, { timeout: 15e3 });
|
|
11499
11975
|
} catch (err) {
|
|
11500
11976
|
const msg = err instanceof Error ? err.message : String(err);
|
|
11501
|
-
log$
|
|
11977
|
+
log$15.warn("git worktree remove 失败", {
|
|
11502
11978
|
worktreePath,
|
|
11503
11979
|
error: msg
|
|
11504
11980
|
});
|
|
@@ -11510,7 +11986,7 @@ var WorktreeManager = class {
|
|
|
11510
11986
|
branchName
|
|
11511
11987
|
], { timeout: 1e4 });
|
|
11512
11988
|
} catch (err) {
|
|
11513
|
-
log$
|
|
11989
|
+
log$15.warn("删除 worktree 分支失败", {
|
|
11514
11990
|
branchName,
|
|
11515
11991
|
error: err instanceof Error ? err.message : String(err)
|
|
11516
11992
|
});
|
|
@@ -11518,7 +11994,7 @@ var WorktreeManager = class {
|
|
|
11518
11994
|
try {
|
|
11519
11995
|
await execFileAsync("git", ["worktree", "prune"], { timeout: 1e4 });
|
|
11520
11996
|
} catch (err) {
|
|
11521
|
-
log$
|
|
11997
|
+
log$15.warn("Worktree prune 失败", { error: err instanceof Error ? err.message : String(err) });
|
|
11522
11998
|
}
|
|
11523
11999
|
}
|
|
11524
12000
|
/**
|
|
@@ -11540,7 +12016,7 @@ var WorktreeManager = class {
|
|
|
11540
12016
|
await this.remove(id, true);
|
|
11541
12017
|
return { cleaned: true };
|
|
11542
12018
|
}
|
|
11543
|
-
log$
|
|
12019
|
+
log$15.info("Worktree 有变更,保留", {
|
|
11544
12020
|
id,
|
|
11545
12021
|
path: info.worktreePath
|
|
11546
12022
|
});
|
|
@@ -11560,7 +12036,7 @@ var WorktreeManager = class {
|
|
|
11560
12036
|
});
|
|
11561
12037
|
return stdout.trim().length > 0;
|
|
11562
12038
|
} catch (err) {
|
|
11563
|
-
log$
|
|
12039
|
+
log$15.warn("检查 worktree 变更状态失败", {
|
|
11564
12040
|
worktreePath,
|
|
11565
12041
|
error: err instanceof Error ? err.message : String(err)
|
|
11566
12042
|
});
|
|
@@ -11580,7 +12056,7 @@ var WorktreeManager = class {
|
|
|
11580
12056
|
await this.removeByPath(record.worktreePath, record.branchName, true);
|
|
11581
12057
|
cleaned++;
|
|
11582
12058
|
} catch (err) {
|
|
11583
|
-
log$
|
|
12059
|
+
log$15.warn("过期 worktree 清理失败", {
|
|
11584
12060
|
id: record.id,
|
|
11585
12061
|
path: record.worktreePath,
|
|
11586
12062
|
error: err instanceof Error ? err.message : String(err)
|
|
@@ -11589,7 +12065,7 @@ var WorktreeManager = class {
|
|
|
11589
12065
|
this.activeWorktrees.delete(record.id);
|
|
11590
12066
|
this.store.delete(record.id);
|
|
11591
12067
|
}
|
|
11592
|
-
if (cleaned > 0) log$
|
|
12068
|
+
if (cleaned > 0) log$15.info("过期 worktree 清理完成", { cleaned });
|
|
11593
12069
|
return cleaned;
|
|
11594
12070
|
}
|
|
11595
12071
|
listActive() {
|
|
@@ -11607,48 +12083,16 @@ var WorktreeManager = class {
|
|
|
11607
12083
|
};
|
|
11608
12084
|
|
|
11609
12085
|
//#endregion
|
|
11610
|
-
//#region src/core/agent-team/
|
|
12086
|
+
//#region src/core/agent-team/agent-factory.ts
|
|
12087
|
+
const log$14 = logger.child("agent-factory");
|
|
11611
12088
|
/**
|
|
11612
|
-
* Agent
|
|
12089
|
+
* 创建 Agent 实例
|
|
11613
12090
|
*
|
|
11614
|
-
*
|
|
11615
|
-
*
|
|
11616
|
-
|
|
11617
|
-
|
|
11618
|
-
|
|
11619
|
-
"Glob",
|
|
11620
|
-
"Grep",
|
|
11621
|
-
"WebFetch",
|
|
11622
|
-
"WebSearch",
|
|
11623
|
-
"GetCurrentTime",
|
|
11624
|
-
"GetWorkdirInfo"
|
|
11625
|
-
];
|
|
11626
|
-
/**
|
|
11627
|
-
* Agent 标准工具集
|
|
11628
|
-
*
|
|
11629
|
-
* safe + 文件写入 + Shell 执行。
|
|
11630
|
-
*/
|
|
11631
|
-
const AGENT_STANDARD_TOOLS = [
|
|
11632
|
-
...AGENT_SAFE_TOOLS,
|
|
11633
|
-
"WriteFile",
|
|
11634
|
-
"EditFile",
|
|
11635
|
-
"Exec",
|
|
11636
|
-
"Process",
|
|
11637
|
-
"TaskManage",
|
|
11638
|
-
"Memory"
|
|
11639
|
-
];
|
|
11640
|
-
|
|
11641
|
-
//#endregion
|
|
11642
|
-
//#region src/core/agent-team/agent-factory.ts
|
|
11643
|
-
const log$9 = logger.child("agent-factory");
|
|
11644
|
-
/**
|
|
11645
|
-
* 创建 Agent 实例
|
|
11646
|
-
*
|
|
11647
|
-
* 根据类型定义创建一个隔离的 LlmBasedAgent,包括:
|
|
11648
|
-
* - 独立的 PiAgent 实例(消息历史隔离)
|
|
11649
|
-
* - 类型特定的工具集(根据 toolPolicy 解析)
|
|
11650
|
-
* - 类型特定的系统提示词
|
|
11651
|
-
* - Security 配置覆盖注入
|
|
12091
|
+
* 根据类型定义创建一个隔离的 LlmBasedAgent,包括:
|
|
12092
|
+
* - 独立的 PiAgent 实例(消息历史隔离)
|
|
12093
|
+
* - 类型特定的工具集(根据 toolPolicy 解析)
|
|
12094
|
+
* - 类型特定的系统提示词
|
|
12095
|
+
* - Security 配置覆盖注入
|
|
11652
12096
|
*
|
|
11653
12097
|
* @param definition - Agent 类型定义
|
|
11654
12098
|
* @param instance - Agent 实例(部分填充,含 depth/task 等)
|
|
@@ -11658,6 +12102,7 @@ const log$9 = logger.child("agent-factory");
|
|
|
11658
12102
|
* @returns 完全初始化的 LlmBasedAgent
|
|
11659
12103
|
*/
|
|
11660
12104
|
function createAgentFromType(definition, instance, parentAgent, availableTools, config) {
|
|
12105
|
+
const thinkingLevel = resolveAgentThinkingLevel(definition.thinkingLevel, config.thinkingLevel, parentAgent);
|
|
11661
12106
|
const agent = createLlmBasedAgent({
|
|
11662
12107
|
agentId: instance.instanceId,
|
|
11663
12108
|
displayName: definition.displayName,
|
|
@@ -11665,14 +12110,15 @@ function createAgentFromType(definition, instance, parentAgent, availableTools,
|
|
|
11665
12110
|
runtimeConfig: {
|
|
11666
12111
|
enabled: true,
|
|
11667
12112
|
toolExecution: "sequential",
|
|
11668
|
-
maxTurns: definition.maxTurns
|
|
12113
|
+
maxTurns: definition.maxTurns,
|
|
12114
|
+
thinkingLevel
|
|
11669
12115
|
}
|
|
11670
12116
|
});
|
|
11671
12117
|
shareParentResources(agent, parentAgent, definition);
|
|
11672
12118
|
const tools = resolveTools(definition, availableTools, instance.depth, config);
|
|
11673
12119
|
agent.registerTools(tools);
|
|
11674
12120
|
agent.systemPromptOverride = buildSystemPrompt(definition, instance.task.description, config);
|
|
11675
|
-
log$
|
|
12121
|
+
log$14.debug("创建 Agent 实例", {
|
|
11676
12122
|
typeId: definition.typeId,
|
|
11677
12123
|
instanceId: instance.instanceId,
|
|
11678
12124
|
depth: instance.depth,
|
|
@@ -11736,6 +12182,17 @@ function buildSystemPrompt(definition, taskDescription, _config) {
|
|
|
11736
12182
|
return definition.getSystemPrompt(ctx);
|
|
11737
12183
|
}
|
|
11738
12184
|
/**
|
|
12185
|
+
* 解析 Agent 的 thinkingLevel
|
|
12186
|
+
*
|
|
12187
|
+
* 优先级:definition.thinkingLevel > config.thinkingLevel > 'off'
|
|
12188
|
+
* 'inherit' 值:从父 Agent 读取当前 thinkingLevel
|
|
12189
|
+
*/
|
|
12190
|
+
function resolveAgentThinkingLevel(definitionLevel, configLevel, parentAgent) {
|
|
12191
|
+
const level = definitionLevel ?? configLevel ?? "off";
|
|
12192
|
+
if (level === "inherit") return parentAgent.innerAgent.state.thinkingLevel ?? "medium";
|
|
12193
|
+
return level;
|
|
12194
|
+
}
|
|
12195
|
+
/**
|
|
11739
12196
|
* 共享父 Agent 的 Model 和 API Key 给子 Agent
|
|
11740
12197
|
*
|
|
11741
12198
|
* 如果 Agent 类型声明了偏好的模型(definition.model),则通过 AgentLlmFacade
|
|
@@ -11753,8 +12210,14 @@ function shareParentResources(subAgent, parentAgent, definition) {
|
|
|
11753
12210
|
subAgent.llmFacade = parentAgent.llmFacade;
|
|
11754
12211
|
if (definition?.model) {
|
|
11755
12212
|
const resolvedModel = resolveModelForDefinition(definition.model, parentAgent);
|
|
11756
|
-
if (resolvedModel)
|
|
11757
|
-
|
|
12213
|
+
if (resolvedModel) {
|
|
12214
|
+
subAgent.innerAgent.state.model = resolvedModel;
|
|
12215
|
+
if (resolvedModel.id !== parentInner.state.model?.id) log$14.info(`子 Agent [${definition?.typeId}] 模型切换: ${parentInner.state.model?.id ?? "N/A"} → ${resolvedModel.id}`, {
|
|
12216
|
+
typeId: definition?.typeId,
|
|
12217
|
+
parentModel: parentInner.state.model?.id,
|
|
12218
|
+
childModel: resolvedModel.id
|
|
12219
|
+
});
|
|
12220
|
+
} else subAgent.innerAgent.state.model = parentInner.state.model;
|
|
11758
12221
|
} else subAgent.innerAgent.state.model = parentInner.state.model;
|
|
11759
12222
|
subAgent.innerAgent.getApiKey = parentAgent.llmFacade.createGetApiKeyFn();
|
|
11760
12223
|
return;
|
|
@@ -11764,7 +12227,7 @@ function shareParentResources(subAgent, parentAgent, definition) {
|
|
|
11764
12227
|
if (parentGetApiKey) subAgent.innerAgent.getApiKey = parentGetApiKey;
|
|
11765
12228
|
}
|
|
11766
12229
|
/**
|
|
11767
|
-
* 根据 Agent 类型声明的 model
|
|
12230
|
+
* 根据 Agent 类型声明的 model 值解析本地 Model 对象
|
|
11768
12231
|
*
|
|
11769
12232
|
* 支持三种格式:
|
|
11770
12233
|
* - 语义名称: 'analysis' → 指向 analysisModel 槽位
|
|
@@ -11774,7 +12237,7 @@ function shareParentResources(subAgent, parentAgent, definition) {
|
|
|
11774
12237
|
*
|
|
11775
12238
|
* @param model - Agent 类型声明的 model 值
|
|
11776
12239
|
* @param parentAgent - 父 Agent(用于获取 AgentLlmFacade)
|
|
11777
|
-
* @returns
|
|
12240
|
+
* @returns 解析后的本地 Model 对象,解析失败返回 undefined
|
|
11778
12241
|
*/
|
|
11779
12242
|
function resolveModelForDefinition(model, parentAgent) {
|
|
11780
12243
|
const facade = parentAgent.llmFacade;
|
|
@@ -11788,14 +12251,14 @@ function resolveModelForDefinition(model, parentAgent) {
|
|
|
11788
12251
|
}
|
|
11789
12252
|
} catch (error) {
|
|
11790
12253
|
const message = error instanceof Error ? error.message : String(error);
|
|
11791
|
-
log$
|
|
12254
|
+
log$14.warn(`解析 Agent 类型偏好模型 [${model}] 失败`, { error: message });
|
|
11792
12255
|
return;
|
|
11793
12256
|
}
|
|
11794
12257
|
}
|
|
11795
12258
|
|
|
11796
12259
|
//#endregion
|
|
11797
12260
|
//#region src/core/agent-team/agent-result-aggregator.ts
|
|
11798
|
-
const log$
|
|
12261
|
+
const log$13 = logger.child("agent-result-aggregator");
|
|
11799
12262
|
/** 零值 TokenUsage */
|
|
11800
12263
|
const ZERO_TOKEN = {
|
|
11801
12264
|
inputTokens: 0,
|
|
@@ -11825,7 +12288,7 @@ function aggregateResults(teamId, workerResults) {
|
|
|
11825
12288
|
estimatedCostUsd: sum.estimatedCostUsd + (r.tokenUsage?.estimatedCostUsd ?? 0)
|
|
11826
12289
|
}), { ...ZERO_TOKEN });
|
|
11827
12290
|
const summary = buildTeamSummary(workerResults);
|
|
11828
|
-
log$
|
|
12291
|
+
log$13.debug("Team 结果聚合完成", {
|
|
11829
12292
|
teamId,
|
|
11830
12293
|
total: workerResults.length,
|
|
11831
12294
|
succeeded,
|
|
@@ -11903,7 +12366,7 @@ function escapeMarkdownTable(text) {
|
|
|
11903
12366
|
|
|
11904
12367
|
//#endregion
|
|
11905
12368
|
//#region src/core/agent-team/agent-orchestrator.ts
|
|
11906
|
-
const log$
|
|
12369
|
+
const log$12 = logger.child("agent-orchestrator");
|
|
11907
12370
|
/**
|
|
11908
12371
|
* Agent 编排器
|
|
11909
12372
|
*
|
|
@@ -11936,7 +12399,7 @@ var AgentOrchestrator = class {
|
|
|
11936
12399
|
const startTime = Date.now();
|
|
11937
12400
|
const defaultType = getAgentTypeRegistry().getDefault();
|
|
11938
12401
|
if (!defaultType) throw new Error("无法获取默认 Agent 类型(general-purpose)");
|
|
11939
|
-
log$
|
|
12402
|
+
log$12.info("开始扁平并行执行", {
|
|
11940
12403
|
count: specs.length,
|
|
11941
12404
|
maxConcurrent: this.flatConfig.maxConcurrent,
|
|
11942
12405
|
hasContext: context != null
|
|
@@ -11944,7 +12407,7 @@ var AgentOrchestrator = class {
|
|
|
11944
12407
|
const allResults = [];
|
|
11945
12408
|
for (let i = 0; i < specs.length; i += this.flatConfig.maxConcurrent) {
|
|
11946
12409
|
const batch = specs.slice(i, i + this.flatConfig.maxConcurrent);
|
|
11947
|
-
log$
|
|
12410
|
+
log$12.debug("执行扁平批次", {
|
|
11948
12411
|
batchStart: i,
|
|
11949
12412
|
batchSize: batch.length,
|
|
11950
12413
|
totalSpecs: specs.length
|
|
@@ -11954,7 +12417,7 @@ var AgentOrchestrator = class {
|
|
|
11954
12417
|
}
|
|
11955
12418
|
const succeeded = allResults.filter((r) => r.status === "success").length;
|
|
11956
12419
|
const totalDuration = Date.now() - startTime;
|
|
11957
|
-
log$
|
|
12420
|
+
log$12.info("扁平并行执行完成", {
|
|
11958
12421
|
total: allResults.length,
|
|
11959
12422
|
succeeded,
|
|
11960
12423
|
failed: allResults.length - succeeded,
|
|
@@ -12029,7 +12492,7 @@ var AgentOrchestrator = class {
|
|
|
12029
12492
|
} catch (error) {
|
|
12030
12493
|
const duration = Date.now() - startTime;
|
|
12031
12494
|
const message = error instanceof Error ? error.message : String(error);
|
|
12032
|
-
log$
|
|
12495
|
+
log$12.warn("扁平子任务执行失败", {
|
|
12033
12496
|
specId: spec.id,
|
|
12034
12497
|
error: message,
|
|
12035
12498
|
duration
|
|
@@ -12086,7 +12549,7 @@ var AgentOrchestrator = class {
|
|
|
12086
12549
|
const parentInstanceId = options?.parentInstanceId ?? "";
|
|
12087
12550
|
const depth = (parentInstanceId && instanceManager.get(parentInstanceId) ? instanceManager.get(parentInstanceId)?.depth ?? 0 : 0) + 1;
|
|
12088
12551
|
if (depth > this.teamConfig.maxGlobalDepth) {
|
|
12089
|
-
log$
|
|
12552
|
+
log$12.warn("Worker 创建被拒绝:超过全局最大深度", {
|
|
12090
12553
|
typeId,
|
|
12091
12554
|
depth,
|
|
12092
12555
|
maxGlobalDepth: this.teamConfig.maxGlobalDepth
|
|
@@ -12208,7 +12671,7 @@ var AgentOrchestrator = class {
|
|
|
12208
12671
|
} catch (error) {
|
|
12209
12672
|
const duration = Date.now() - startTime;
|
|
12210
12673
|
const message = error instanceof Error ? error.message : String(error);
|
|
12211
|
-
log$
|
|
12674
|
+
log$12.warn("Worker 执行失败", {
|
|
12212
12675
|
typeId,
|
|
12213
12676
|
instanceId,
|
|
12214
12677
|
error: message,
|
|
@@ -12251,12 +12714,12 @@ var AgentOrchestrator = class {
|
|
|
12251
12714
|
const wm = getWorktreeManager();
|
|
12252
12715
|
if (wm) try {
|
|
12253
12716
|
const cleanResult = await wm.autoCleanIfNoChanges(worktreeInfo.id);
|
|
12254
|
-
if (!cleanResult.cleaned) log$
|
|
12717
|
+
if (!cleanResult.cleaned) log$12.info("Worktree 有变更,保留", {
|
|
12255
12718
|
id: worktreeInfo.id,
|
|
12256
12719
|
path: cleanResult.worktreePath
|
|
12257
12720
|
});
|
|
12258
12721
|
} catch (err) {
|
|
12259
|
-
log$
|
|
12722
|
+
log$12.warn("Worktree 清理失败", {
|
|
12260
12723
|
id: worktreeInfo.id,
|
|
12261
12724
|
error: err instanceof Error ? err.message : String(err)
|
|
12262
12725
|
});
|
|
@@ -12276,7 +12739,7 @@ var AgentOrchestrator = class {
|
|
|
12276
12739
|
*/
|
|
12277
12740
|
async spawnTeam(taskDescription, workerSpecs) {
|
|
12278
12741
|
const teamId = `team-${Date.now()}-${++this.teamCounter}`;
|
|
12279
|
-
log$
|
|
12742
|
+
log$12.info("创建 Team", {
|
|
12280
12743
|
teamId,
|
|
12281
12744
|
workerCount: workerSpecs.length,
|
|
12282
12745
|
taskDescription
|
|
@@ -12309,7 +12772,29 @@ var AgentOrchestrator = class {
|
|
|
12309
12772
|
const instanceManager = getAgentInstanceManager();
|
|
12310
12773
|
const instance = instanceManager.get(instanceId);
|
|
12311
12774
|
if (!instance) return;
|
|
12312
|
-
if (event.
|
|
12775
|
+
if (event.detail?.isStart) instanceManager.recordToolCall(instanceId, {
|
|
12776
|
+
toolName: event.detail.toolName,
|
|
12777
|
+
toolCallId: event.detail.toolCallId,
|
|
12778
|
+
argsDisplay: event.detail.argsDisplay,
|
|
12779
|
+
status: "running",
|
|
12780
|
+
startedAt: Date.now()
|
|
12781
|
+
});
|
|
12782
|
+
else if (event.detail?.isEnd) {
|
|
12783
|
+
const lastRunning = [...instance.toolCallHistory].reverse().find((t) => t.status === "running" && t.toolName === event.detail?.toolName);
|
|
12784
|
+
if (lastRunning) {
|
|
12785
|
+
lastRunning.status = event.detail.isError ? "failed" : "completed";
|
|
12786
|
+
lastRunning.endedAt = Date.now();
|
|
12787
|
+
}
|
|
12788
|
+
instanceManager.emit("instance:toolcall", {
|
|
12789
|
+
instanceId,
|
|
12790
|
+
typeId: instance.typeId,
|
|
12791
|
+
record: {
|
|
12792
|
+
toolName: event.detail.toolName,
|
|
12793
|
+
status: event.detail.isError ? "failed" : "completed",
|
|
12794
|
+
startedAt: 0
|
|
12795
|
+
}
|
|
12796
|
+
});
|
|
12797
|
+
} else if (event.percent === 0) {
|
|
12313
12798
|
const toolName = event.message;
|
|
12314
12799
|
const prevUses = instance.currentActivity?.toolUses ?? 0;
|
|
12315
12800
|
const parenIdx = toolName.indexOf("(");
|
|
@@ -12322,7 +12807,7 @@ var AgentOrchestrator = class {
|
|
|
12322
12807
|
startedAt: Date.now()
|
|
12323
12808
|
};
|
|
12324
12809
|
instanceManager.setActivity(instanceId, activity);
|
|
12325
|
-
}
|
|
12810
|
+
}
|
|
12326
12811
|
};
|
|
12327
12812
|
agent.on(agent.EVENT_PROGRESS, progressHandler);
|
|
12328
12813
|
return () => {
|
|
@@ -12628,6 +13113,84 @@ function createReplBuiltinTools(webConfig, taskStore, skillConfig, parentAgent,
|
|
|
12628
13113
|
return tools;
|
|
12629
13114
|
}
|
|
12630
13115
|
|
|
13116
|
+
//#endregion
|
|
13117
|
+
//#region src/cli/repl/skill-watcher.ts
|
|
13118
|
+
/**
|
|
13119
|
+
* Skill 文件变更监视器
|
|
13120
|
+
*
|
|
13121
|
+
* 使用 chokidar 监视技能目录中的 SKILL.md 文件变更,
|
|
13122
|
+
* 通过防抖机制触发外部回调,实现动态技能发现。
|
|
13123
|
+
*
|
|
13124
|
+
* @module cli/repl/skill-watcher
|
|
13125
|
+
*/
|
|
13126
|
+
const log$11 = logger.child("repl:skill-watcher");
|
|
13127
|
+
/** 防抖延迟(毫秒) */
|
|
13128
|
+
const DEBOUNCE_MS = 500;
|
|
13129
|
+
/**
|
|
13130
|
+
* Skill 文件变更监视器
|
|
13131
|
+
*/
|
|
13132
|
+
var SkillWatcher = class {
|
|
13133
|
+
watcher = null;
|
|
13134
|
+
debounceTimer = null;
|
|
13135
|
+
_ready = false;
|
|
13136
|
+
/** 监视器是否已就绪 */
|
|
13137
|
+
get ready() {
|
|
13138
|
+
return this._ready;
|
|
13139
|
+
}
|
|
13140
|
+
/**
|
|
13141
|
+
* 启动监视
|
|
13142
|
+
*
|
|
13143
|
+
* 监视指定目录下 `**` 深层匹配 `SKILL.md` 文件变更。
|
|
13144
|
+
* 忽略隐藏目录,`ignoreInitial: true` 避免初始扫描触发 add 事件。
|
|
13145
|
+
*/
|
|
13146
|
+
start(options) {
|
|
13147
|
+
if (this.watcher) return;
|
|
13148
|
+
this.watcher = chokidar.watch(options.watchDirs.map((dir) => `${dir}/**/SKILL.md`), {
|
|
13149
|
+
ignored: /(^|[/\\])\./,
|
|
13150
|
+
persistent: true,
|
|
13151
|
+
ignoreInitial: true,
|
|
13152
|
+
depth: 2
|
|
13153
|
+
});
|
|
13154
|
+
this.watcher.on("ready", () => {
|
|
13155
|
+
this._ready = true;
|
|
13156
|
+
log$11.info("技能文件监视已就绪", { dirs: options.watchDirs });
|
|
13157
|
+
}).on("add", (path) => {
|
|
13158
|
+
log$11.debug("技能文件新增", { path });
|
|
13159
|
+
this.debounceNotify(options.onChanged);
|
|
13160
|
+
}).on("change", (path) => {
|
|
13161
|
+
log$11.debug("技能文件修改", { path });
|
|
13162
|
+
this.debounceNotify(options.onChanged);
|
|
13163
|
+
}).on("unlink", (path) => {
|
|
13164
|
+
log$11.debug("技能文件删除", { path });
|
|
13165
|
+
this.debounceNotify(options.onChanged);
|
|
13166
|
+
});
|
|
13167
|
+
}
|
|
13168
|
+
/**
|
|
13169
|
+
* 停止监视并清理资源
|
|
13170
|
+
*/
|
|
13171
|
+
stop() {
|
|
13172
|
+
if (this.debounceTimer) {
|
|
13173
|
+
clearTimeout(this.debounceTimer);
|
|
13174
|
+
this.debounceTimer = null;
|
|
13175
|
+
}
|
|
13176
|
+
if (this.watcher) {
|
|
13177
|
+
this.watcher.close();
|
|
13178
|
+
this.watcher = null;
|
|
13179
|
+
}
|
|
13180
|
+
this._ready = false;
|
|
13181
|
+
}
|
|
13182
|
+
/**
|
|
13183
|
+
* 防抖通知:批量操作(如 git pull)时合并为一次回调
|
|
13184
|
+
*/
|
|
13185
|
+
debounceNotify(callback) {
|
|
13186
|
+
if (this.debounceTimer) clearTimeout(this.debounceTimer);
|
|
13187
|
+
this.debounceTimer = setTimeout(() => {
|
|
13188
|
+
this.debounceTimer = null;
|
|
13189
|
+
callback();
|
|
13190
|
+
}, DEBOUNCE_MS);
|
|
13191
|
+
}
|
|
13192
|
+
};
|
|
13193
|
+
|
|
12631
13194
|
//#endregion
|
|
12632
13195
|
//#region src/cli/repl/theme.ts
|
|
12633
13196
|
/** 根据颜色开关获取 chalk 实例 */
|
|
@@ -13113,6 +13676,233 @@ function createLspTool(lspManager) {
|
|
|
13113
13676
|
};
|
|
13114
13677
|
}
|
|
13115
13678
|
|
|
13679
|
+
//#endregion
|
|
13680
|
+
//#region src/cli/repl/tools/send-message.ts
|
|
13681
|
+
/**
|
|
13682
|
+
* 创建 SendMessage 工具注册
|
|
13683
|
+
*
|
|
13684
|
+
* @param currentAgentInstanceId - 当前 Agent 的实例 ID(用于 fromAgentId)
|
|
13685
|
+
* @returns ToolRegistration
|
|
13686
|
+
*/
|
|
13687
|
+
function createSendMessageTool(currentAgentInstanceId) {
|
|
13688
|
+
return {
|
|
13689
|
+
id: "SendMessage",
|
|
13690
|
+
label: "发送消息",
|
|
13691
|
+
defaultRisk: "medium",
|
|
13692
|
+
description: [
|
|
13693
|
+
"向其他 Agent 发送消息,用于 Agent 间通信。",
|
|
13694
|
+
"",
|
|
13695
|
+
"### 何时使用此工具",
|
|
13696
|
+
"1. 执行过程中需要向父 Agent/Coordinator 确认某些信息",
|
|
13697
|
+
"2. 发现任务描述不够明确,需要澄清",
|
|
13698
|
+
"3. 需要向 Coordinator 报告中间进度或部分结果",
|
|
13699
|
+
"4. 需要告知 Coordinator 任务可以提前结束",
|
|
13700
|
+
"",
|
|
13701
|
+
"### 何时不使用此工具",
|
|
13702
|
+
"- 任务已完全完成(直接返回结果即可)",
|
|
13703
|
+
"- 不需要和其他 Agent 交互",
|
|
13704
|
+
"",
|
|
13705
|
+
"### 参数说明",
|
|
13706
|
+
"- **toAgentId**: 目标 Agent 的实例 ID。使用 `parent` 表示父 Agent",
|
|
13707
|
+
"- **message**: 要发送的消息内容",
|
|
13708
|
+
"- **messageType**: 消息类型",
|
|
13709
|
+
" - `question`: 需要确认或回答的问题",
|
|
13710
|
+
" - `progress`: 进度报告",
|
|
13711
|
+
" - `task_result`: 部分结果或中间产出",
|
|
13712
|
+
"",
|
|
13713
|
+
`### 你的实例 ID: \`${currentAgentInstanceId}\``
|
|
13714
|
+
].join("\n"),
|
|
13715
|
+
parameters: {
|
|
13716
|
+
type: "object",
|
|
13717
|
+
properties: {
|
|
13718
|
+
toAgentId: {
|
|
13719
|
+
type: "string",
|
|
13720
|
+
description: "目标 Agent 实例 ID(或 \"parent\" 表示父 Agent)"
|
|
13721
|
+
},
|
|
13722
|
+
message: {
|
|
13723
|
+
type: "string",
|
|
13724
|
+
description: "消息内容"
|
|
13725
|
+
},
|
|
13726
|
+
messageType: {
|
|
13727
|
+
type: "string",
|
|
13728
|
+
enum: [
|
|
13729
|
+
"question",
|
|
13730
|
+
"progress",
|
|
13731
|
+
"task_result"
|
|
13732
|
+
],
|
|
13733
|
+
description: "消息类型",
|
|
13734
|
+
default: "progress"
|
|
13735
|
+
}
|
|
13736
|
+
},
|
|
13737
|
+
required: ["toAgentId", "message"]
|
|
13738
|
+
},
|
|
13739
|
+
execute: async (_toolCallId, params) => {
|
|
13740
|
+
const p = params;
|
|
13741
|
+
const messageBus = getAgentMessageBus();
|
|
13742
|
+
const msgType = p.messageType === "question" ? "question" : p.messageType === "result" ? "task_result" : "progress";
|
|
13743
|
+
const fullMessage = messageBus.publish(currentAgentInstanceId, p.toAgentId, {
|
|
13744
|
+
type: msgType,
|
|
13745
|
+
payload: p.message,
|
|
13746
|
+
requiresResponse: p.messageType === "question"
|
|
13747
|
+
});
|
|
13748
|
+
return {
|
|
13749
|
+
content: [{
|
|
13750
|
+
type: "text",
|
|
13751
|
+
text: `消息已发送 (ID: ${fullMessage.messageId})\n目标: ${p.toAgentId}\n类型: ${p.messageType ?? "progress"}`
|
|
13752
|
+
}],
|
|
13753
|
+
details: fullMessage
|
|
13754
|
+
};
|
|
13755
|
+
}
|
|
13756
|
+
};
|
|
13757
|
+
}
|
|
13758
|
+
|
|
13759
|
+
//#endregion
|
|
13760
|
+
//#region src/cli/repl/tools/task-stop.ts
|
|
13761
|
+
/**
|
|
13762
|
+
* 创建 TaskStop 工具注册
|
|
13763
|
+
*
|
|
13764
|
+
* @returns ToolRegistration
|
|
13765
|
+
*/
|
|
13766
|
+
function createTaskStopTool() {
|
|
13767
|
+
return {
|
|
13768
|
+
id: "TaskStop",
|
|
13769
|
+
label: "停止任务",
|
|
13770
|
+
defaultRisk: "medium",
|
|
13771
|
+
description: [
|
|
13772
|
+
"停止正在运行中的子 Agent 任务。",
|
|
13773
|
+
"",
|
|
13774
|
+
"### 何时使用此工具",
|
|
13775
|
+
"1. 子 Agent 执行方向错误,需要中止",
|
|
13776
|
+
"2. 用户需求变更,当前子任务不再需要",
|
|
13777
|
+
"3. 子 Agent 陷入死循环或异常状态",
|
|
13778
|
+
"4. 需要立即停止所有子任务以重新规划",
|
|
13779
|
+
"",
|
|
13780
|
+
"### 参数说明",
|
|
13781
|
+
"- **task_id**: 要停止的子 Agent 实例 ID(AgentTool 返回的 instanceId)",
|
|
13782
|
+
"",
|
|
13783
|
+
"### 使用示例",
|
|
13784
|
+
"停止指定任务: `{ \"task_id\": \"agent-xyz-123\" }`"
|
|
13785
|
+
].join("\n"),
|
|
13786
|
+
parameters: {
|
|
13787
|
+
type: "object",
|
|
13788
|
+
properties: { task_id: {
|
|
13789
|
+
type: "string",
|
|
13790
|
+
description: "要停止的子 Agent 实例 ID(AgentTool 返回结果中的 instanceId)"
|
|
13791
|
+
} },
|
|
13792
|
+
required: ["task_id"]
|
|
13793
|
+
},
|
|
13794
|
+
execute: async (_toolCallId, params) => {
|
|
13795
|
+
const { task_id } = params;
|
|
13796
|
+
const instanceManager = getAgentInstanceManager();
|
|
13797
|
+
const instance = instanceManager.get(task_id);
|
|
13798
|
+
if (!instance) return {
|
|
13799
|
+
content: [{
|
|
13800
|
+
type: "text",
|
|
13801
|
+
text: `⚠️ 未找到任务 \`${task_id}\``
|
|
13802
|
+
}],
|
|
13803
|
+
details: {
|
|
13804
|
+
taskId: task_id,
|
|
13805
|
+
found: false
|
|
13806
|
+
}
|
|
13807
|
+
};
|
|
13808
|
+
if (instance.status !== "running" && instance.status !== "idle") return {
|
|
13809
|
+
content: [{
|
|
13810
|
+
type: "text",
|
|
13811
|
+
text: `⚠️ 任务 \`${task_id}\` 当前状态为 \`${instance.status}\`,无法停止`
|
|
13812
|
+
}],
|
|
13813
|
+
details: {
|
|
13814
|
+
taskId: task_id,
|
|
13815
|
+
status: instance.status,
|
|
13816
|
+
stopped: false
|
|
13817
|
+
}
|
|
13818
|
+
};
|
|
13819
|
+
const cancelled = await instanceManager.cancel(task_id);
|
|
13820
|
+
return {
|
|
13821
|
+
content: [{
|
|
13822
|
+
type: "text",
|
|
13823
|
+
text: `✅ 任务 \`${task_id}\` 已停止`
|
|
13824
|
+
}],
|
|
13825
|
+
details: {
|
|
13826
|
+
taskId: task_id,
|
|
13827
|
+
stopped: true,
|
|
13828
|
+
cancelled: cancelled.length
|
|
13829
|
+
}
|
|
13830
|
+
};
|
|
13831
|
+
}
|
|
13832
|
+
};
|
|
13833
|
+
}
|
|
13834
|
+
|
|
13835
|
+
//#endregion
|
|
13836
|
+
//#region src/cli/repl/utils/animation-manager.ts
|
|
13837
|
+
/**
|
|
13838
|
+
* AnimationManager — 将 spinner/计时器动画从 setInterval 迁移到渲染周期驱动
|
|
13839
|
+
*
|
|
13840
|
+
* 原理:
|
|
13841
|
+
* 通过 monkey-patch pi-tui 的 doRender() 方法,在每次实际渲染前统一推进
|
|
13842
|
+
* 所有已注册的 spinner 帧,从而:
|
|
13843
|
+
* 1. 消除对 setInterval (macrotask) 的依赖,避免 Timer 阶段被事件循环饿死
|
|
13844
|
+
* 2. 帧推进与渲染同步,视觉上更流畅
|
|
13845
|
+
* 3. 减少事件循环中的定时器数量,降低调度开销
|
|
13846
|
+
*
|
|
13847
|
+
* 使用方式:
|
|
13848
|
+
* ```typescript
|
|
13849
|
+
* const anim = new AnimationManager();
|
|
13850
|
+
* anim.bind(tui);
|
|
13851
|
+
*
|
|
13852
|
+
* const unsub = anim.register((now) => {
|
|
13853
|
+
* // 根据 now 判断是否推进帧
|
|
13854
|
+
* frame = (frame + 1) % frames.length;
|
|
13855
|
+
* });
|
|
13856
|
+
* ```
|
|
13857
|
+
*/
|
|
13858
|
+
var AnimationManager = class {
|
|
13859
|
+
/** 已注册的帧推进回调 */
|
|
13860
|
+
#callbacks = /* @__PURE__ */ new Set();
|
|
13861
|
+
/** 绑定的 TUI 实例 */
|
|
13862
|
+
#tui = null;
|
|
13863
|
+
/** 备份的原始 doRender */
|
|
13864
|
+
#originalDoRender = null;
|
|
13865
|
+
/**
|
|
13866
|
+
* 注册一个帧推进回调。
|
|
13867
|
+
* 回调会在每次 doRender() 执行前被调用,传入当前 performance.now() 时间戳。
|
|
13868
|
+
* 回调应该根据时间戳自行控制帧推进频率(如每 100ms 推进一帧)。
|
|
13869
|
+
*
|
|
13870
|
+
* @returns 注销函数
|
|
13871
|
+
*/
|
|
13872
|
+
register(cb) {
|
|
13873
|
+
this.#callbacks.add(cb);
|
|
13874
|
+
return () => {
|
|
13875
|
+
this.#callbacks.delete(cb);
|
|
13876
|
+
};
|
|
13877
|
+
}
|
|
13878
|
+
/**
|
|
13879
|
+
* 绑定到 TUI 实例,monkey-patch 其 doRender 方法。
|
|
13880
|
+
* 如果已经绑定到另一个 TUI 实例,会先解除绑定。
|
|
13881
|
+
*/
|
|
13882
|
+
bind(tui) {
|
|
13883
|
+
if (this.#tui) this.unbind();
|
|
13884
|
+
this.#tui = tui;
|
|
13885
|
+
const tuiObj = tui;
|
|
13886
|
+
if (typeof tuiObj.doRender !== "function") return;
|
|
13887
|
+
this.#originalDoRender = tuiObj.doRender.bind(tui);
|
|
13888
|
+
const callbacks = this.#callbacks;
|
|
13889
|
+
const self = this;
|
|
13890
|
+
tuiObj.doRender = function() {
|
|
13891
|
+
const now = performance.now();
|
|
13892
|
+
for (const cb of callbacks) cb(now);
|
|
13893
|
+
self.#originalDoRender?.call(this);
|
|
13894
|
+
};
|
|
13895
|
+
}
|
|
13896
|
+
/**
|
|
13897
|
+
* 解除绑定,恢复 TUI 实例的原始 doRender。
|
|
13898
|
+
*/
|
|
13899
|
+
unbind() {
|
|
13900
|
+
if (this.#originalDoRender && this.#tui) this.#tui.doRender = this.#originalDoRender;
|
|
13901
|
+
this.#originalDoRender = null;
|
|
13902
|
+
this.#tui = null;
|
|
13903
|
+
}
|
|
13904
|
+
};
|
|
13905
|
+
|
|
13116
13906
|
//#endregion
|
|
13117
13907
|
//#region src/cli/repl/utils/markdown-formatter.ts
|
|
13118
13908
|
/**
|
|
@@ -13239,9 +14029,9 @@ function formatTable(token, colorEnabled) {
|
|
|
13239
14029
|
output += "┌";
|
|
13240
14030
|
for (let i = 0; i < columnCount; i++) {
|
|
13241
14031
|
const cw = colWidths[i] ?? 3;
|
|
13242
|
-
output += "─".repeat(cw + 2)
|
|
14032
|
+
output += `${"─".repeat(cw + 2)}┬`;
|
|
13243
14033
|
}
|
|
13244
|
-
output = output.slice(0, -1)
|
|
14034
|
+
output = `${output.slice(0, -1)}┐${EOL}`;
|
|
13245
14035
|
output += "│ ";
|
|
13246
14036
|
for (let i = 0; i < columnCount; i++) {
|
|
13247
14037
|
const cell = token.header[i];
|
|
@@ -13278,9 +14068,9 @@ function formatTable(token, colorEnabled) {
|
|
|
13278
14068
|
output += "└";
|
|
13279
14069
|
for (let i = 0; i < columnCount; i++) {
|
|
13280
14070
|
const cw = colWidths[i] ?? 3;
|
|
13281
|
-
output += "─".repeat(cw + 2)
|
|
14071
|
+
output += `${"─".repeat(cw + 2)}┴`;
|
|
13282
14072
|
}
|
|
13283
|
-
output = output.slice(0, -1)
|
|
14073
|
+
output = `${output.slice(0, -1)}┘${EOL}`;
|
|
13284
14074
|
return output + EOL;
|
|
13285
14075
|
}
|
|
13286
14076
|
/**
|
|
@@ -13347,6 +14137,93 @@ function normalizeMcpConfig(raw) {
|
|
|
13347
14137
|
return servers;
|
|
13348
14138
|
}
|
|
13349
14139
|
|
|
14140
|
+
//#endregion
|
|
14141
|
+
//#region src/core/agent-team/markdown-agent-parser.ts
|
|
14142
|
+
const log$10 = logger.child("markdown-agent-parser");
|
|
14143
|
+
|
|
14144
|
+
//#endregion
|
|
14145
|
+
//#region src/core/agent-team/agent-generator.ts
|
|
14146
|
+
const log$9 = logger.child("agent-generator");
|
|
14147
|
+
|
|
14148
|
+
//#endregion
|
|
14149
|
+
//#region src/core/agent-team/agent-memory.ts
|
|
14150
|
+
const log$8 = logger.child("agent-memory");
|
|
14151
|
+
/** 记忆根目录 */
|
|
14152
|
+
const AGENT_MEMORY_DIR = join(homedir(), ".zapmyco", "memory", "agents");
|
|
14153
|
+
|
|
14154
|
+
//#endregion
|
|
14155
|
+
//#region src/core/agent-team/coordinator-prompt.ts
|
|
14156
|
+
/**
|
|
14157
|
+
* 主 Agent Coordinator 模式系统提示词
|
|
14158
|
+
*
|
|
14159
|
+
* 当 agentTeam.defaultMode === 'coordinator' 时,
|
|
14160
|
+
* 主 REPL Agent 的 system prompt 被替换为此提示词,
|
|
14161
|
+
* 强制其专注于编排而非亲自执行。
|
|
14162
|
+
*
|
|
14163
|
+
* @module core/agent-team
|
|
14164
|
+
*/
|
|
14165
|
+
/**
|
|
14166
|
+
* 获取主 Agent Coordinator 模式系统提示词
|
|
14167
|
+
*
|
|
14168
|
+
* @param workdir - 当前工作目录
|
|
14169
|
+
* @returns 系统提示词文本
|
|
14170
|
+
*/
|
|
14171
|
+
function getMainCoordinatorSystemPrompt(workdir) {
|
|
14172
|
+
return [
|
|
14173
|
+
"你是一个团队协调者(Coordinator),负责编排多个专业子 Agent 协同完成复杂任务。",
|
|
14174
|
+
"",
|
|
14175
|
+
"## 核心原则",
|
|
14176
|
+
"**你绝对不能直接执行任何具体工作。** 你必须将所有任务委派给合适的子 Agent。",
|
|
14177
|
+
"不要使用 ReadFile、WriteFile、EditFile、Exec、Glob、Grep 等具体执行工具。",
|
|
14178
|
+
"你的工具仅限于 AgentTool、SendMessage、TaskStop 三个编排工具。",
|
|
14179
|
+
"",
|
|
14180
|
+
"## 核心职责",
|
|
14181
|
+
"- 分析用户任务并制定执行计划",
|
|
14182
|
+
"- 将复杂任务拆解为可独立执行的子任务",
|
|
14183
|
+
"- 为每个子任务匹配最合适的 Agent 类型",
|
|
14184
|
+
"- 协调子 Agent 执行顺序,处理依赖关系",
|
|
14185
|
+
"- 汇总所有子 Agent 的输出为统一结论",
|
|
14186
|
+
"",
|
|
14187
|
+
"## 可用 Agent 类型",
|
|
14188
|
+
"- **researcher**: 信息搜集、技术调研、文档检索(只有只读工具,不能修改文件)",
|
|
14189
|
+
"- **coder**: 代码生成与修改(有读写工具和 Shell 权限)",
|
|
14190
|
+
"- **reviewer**: 代码审查、质量检查(只有只读工具)",
|
|
14191
|
+
"- **planner**: 方案设计与架构规划(有标准工具集,可派生子 Agent 调研)",
|
|
14192
|
+
"- **general-purpose**: 通用任务执行(有标准工具集)",
|
|
14193
|
+
"",
|
|
14194
|
+
"## 可用工具",
|
|
14195
|
+
"- **AgentTool**: 创建并派遣子 Agent(使用 subagent_type 指定类型)",
|
|
14196
|
+
" - 同步模式(默认): 等待子 Agent 完成后获取完整结果",
|
|
14197
|
+
" - 异步模式(run_in_background=true): 后台执行,立即返回 taskId,完成后自动通知",
|
|
14198
|
+
"- **SendMessage**: 向子 Agent 发送后续指令、回答问题或调整方向(使用其 agent ID 作为 to 参数)",
|
|
14199
|
+
"- **TaskStop**: 停止运行中的子 Agent",
|
|
14200
|
+
"",
|
|
14201
|
+
"## 工作流程",
|
|
14202
|
+
"1. **分析任务**: 理解用户需求,明确目标和约束条件",
|
|
14203
|
+
"2. **拆解子任务**: 将复杂任务分解为可并行或串行执行的独立子任务",
|
|
14204
|
+
"3. **匹配类型**: 为每个子任务选择最合适的 Agent 类型",
|
|
14205
|
+
"4. **派发执行**: 调用 AgentTool 创建子 Agent(无依赖的子任务应并行派发)",
|
|
14206
|
+
"5. **汇总结果**: 整合所有子 Agent 的输出,向用户汇报结论",
|
|
14207
|
+
"",
|
|
14208
|
+
"## 工作规则",
|
|
14209
|
+
"- **绝不亲自执行**: 绝不使用 ReadFile、WriteFile、Exec、Glob、Grep、WebFetch、WebSearch 等直接执行工具",
|
|
14210
|
+
"- **并行优先**: 无依赖的子任务应同时派发以提升效率",
|
|
14211
|
+
"- **先规划后执行**: 在派发子 Agent 之前先制定清晰的执行计划",
|
|
14212
|
+
"- **监控进度**: 通过 SendMessage 接收子 Agent 的进度汇报和提问",
|
|
14213
|
+
"- **务实汇总**: 整合结果时保持客观,不添加未经验证的信息",
|
|
14214
|
+
"- **后台任务**: 长时间运行的任务使用 run_in_background=true,任务完成后会自动收到通知",
|
|
14215
|
+
"- **通知处理**: 当收到后台任务完成的通知消息时,汇总所有结果后一并回复用户",
|
|
14216
|
+
"",
|
|
14217
|
+
`## 工作目录\n${workdir}`,
|
|
14218
|
+
"",
|
|
14219
|
+
"> 记住:你是协调者,不是执行者。你的价值在于编排和整合,而不是亲自干活。"
|
|
14220
|
+
].join("\n");
|
|
14221
|
+
}
|
|
14222
|
+
|
|
14223
|
+
//#endregion
|
|
14224
|
+
//#region src/core/agent-team/user-agent-loader.ts
|
|
14225
|
+
const log$7 = logger.child("user-agent-loader");
|
|
14226
|
+
|
|
13350
14227
|
//#endregion
|
|
13351
14228
|
//#region src/core/lsp/diagnostics.ts
|
|
13352
14229
|
function createDiagnosticCollector() {
|
|
@@ -13609,10 +14486,10 @@ function createLspClient(config) {
|
|
|
13609
14486
|
const reader = createMessageReader(handleMessage, (err) => trace("←", `Reader error: ${err.message}`), () => {
|
|
13610
14487
|
if (!isStopping) rejectAllPending(new LspError("LSP stdout closed", "STDOUT_CLOSED"));
|
|
13611
14488
|
}, (msg) => trace("←", msg));
|
|
13612
|
-
childProcess.stdout
|
|
14489
|
+
childProcess.stdout?.on("data", (chunk) => {
|
|
13613
14490
|
reader.feed(chunk);
|
|
13614
14491
|
});
|
|
13615
|
-
childProcess.stdout
|
|
14492
|
+
childProcess.stdout?.on("end", () => {
|
|
13616
14493
|
if (!isStopping) rejectAllPending(new LspError("LSP stdout closed unexpectedly", "STDOUT_CLOSED"));
|
|
13617
14494
|
});
|
|
13618
14495
|
messageWriter = createMessageWriter(childProcess.stdin, (msg) => trace("→", msg));
|
|
@@ -13666,7 +14543,7 @@ function createLspClient(config) {
|
|
|
13666
14543
|
timer,
|
|
13667
14544
|
method
|
|
13668
14545
|
});
|
|
13669
|
-
messageWriter
|
|
14546
|
+
messageWriter?.write(request).catch((err) => {
|
|
13670
14547
|
clearTimeout(timer);
|
|
13671
14548
|
pendingRequests.delete(id);
|
|
13672
14549
|
reject(err);
|
|
@@ -13706,19 +14583,19 @@ function createLspClient(config) {
|
|
|
13706
14583
|
}
|
|
13707
14584
|
async function shutdown() {
|
|
13708
14585
|
if (!childProcess || isStopping) return;
|
|
13709
|
-
isStopping = true;
|
|
13710
14586
|
try {
|
|
13711
14587
|
await sendRequest("shutdown");
|
|
13712
14588
|
} catch {}
|
|
13713
14589
|
try {
|
|
13714
14590
|
await sendNotification("exit");
|
|
13715
14591
|
} catch {}
|
|
14592
|
+
isStopping = true;
|
|
13716
14593
|
await new Promise((resolve) => {
|
|
13717
14594
|
const timer = setTimeout(() => {
|
|
13718
14595
|
if (childProcess && !childProcess.killed) childProcess.kill("SIGKILL");
|
|
13719
14596
|
resolve();
|
|
13720
14597
|
}, 2e3);
|
|
13721
|
-
childProcess
|
|
14598
|
+
childProcess?.once("exit", () => {
|
|
13722
14599
|
clearTimeout(timer);
|
|
13723
14600
|
resolve();
|
|
13724
14601
|
});
|
|
@@ -13776,7 +14653,7 @@ function createLspServerInstance(config) {
|
|
|
13776
14653
|
if (openedDocuments.get(uri)) return;
|
|
13777
14654
|
await ensureRunning();
|
|
13778
14655
|
try {
|
|
13779
|
-
await client
|
|
14656
|
+
await client?.sendNotification("textDocument/didOpen", { textDocument: {
|
|
13780
14657
|
uri,
|
|
13781
14658
|
languageId,
|
|
13782
14659
|
version: 1,
|
|
@@ -13848,6 +14725,7 @@ function createLspServerInstance(config) {
|
|
|
13848
14725
|
async function request(method, params) {
|
|
13849
14726
|
await ensureRunning();
|
|
13850
14727
|
requestCount++;
|
|
14728
|
+
if (!client) throw new LspError("CLIENT_NOT_INITIALIZED", "LSP 客户端未初始化");
|
|
13851
14729
|
try {
|
|
13852
14730
|
return await client.sendRequest(method, params);
|
|
13853
14731
|
} catch (err) {
|
|
@@ -14200,7 +15078,7 @@ function withTimeout(promise, ms) {
|
|
|
14200
15078
|
/**
|
|
14201
15079
|
* 将单个 MCP Tool 转换为 zapmyco ToolRegistration
|
|
14202
15080
|
*
|
|
14203
|
-
* MCP SDK 的 TextContent / ImageContent
|
|
15081
|
+
* MCP SDK 的 TextContent / ImageContent 与本地类型结构兼容
|
|
14204
15082
|
* (都是 { type, text/data, mimeType }),因此直接透传 content。
|
|
14205
15083
|
*/
|
|
14206
15084
|
function mcpToolToRegistration(mcpTool, serverName, client) {
|
|
@@ -14752,7 +15630,7 @@ var AuditLogger = class {
|
|
|
14752
15630
|
const entries = this.buffer.splice(0);
|
|
14753
15631
|
try {
|
|
14754
15632
|
this.rotateIfNeeded();
|
|
14755
|
-
const lines = entries.map((e) => JSON.stringify(e)).join("\n")
|
|
15633
|
+
const lines = `${entries.map((e) => JSON.stringify(e)).join("\n")}\n`;
|
|
14756
15634
|
appendFileSync(this.filePath, lines, "utf-8");
|
|
14757
15635
|
} catch (err) {
|
|
14758
15636
|
log$3.error("审计日志写入失败", { error: err instanceof Error ? err.message : String(err) });
|
|
@@ -14856,7 +15734,7 @@ function resolveConfig(config = {}) {
|
|
|
14856
15734
|
function matchToolPattern(pattern, toolId) {
|
|
14857
15735
|
if (pattern === toolId) return true;
|
|
14858
15736
|
if (pattern === "*") return true;
|
|
14859
|
-
return new RegExp(
|
|
15737
|
+
return new RegExp(`^${pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*")}$`).test(toolId);
|
|
14860
15738
|
}
|
|
14861
15739
|
/**
|
|
14862
15740
|
* 参数模式匹配
|
|
@@ -15519,7 +16397,7 @@ function threatSeverity(level) {
|
|
|
15519
16397
|
/**
|
|
15520
16398
|
* REPL 会话核心(pi-tui 版)
|
|
15521
16399
|
*
|
|
15522
|
-
* 使用 @
|
|
16400
|
+
* 使用 @earendil-works/pi-tui 框架替代 readline,
|
|
15523
16401
|
* 实现完整的 TUI 交互式 REPL:
|
|
15524
16402
|
* - Editor 组件自带上下边框
|
|
15525
16403
|
* - 差量渲染,无闪烁
|
|
@@ -15547,9 +16425,24 @@ function getApiKeyErrorHelp(errorMessage) {
|
|
|
15547
16425
|
*/
|
|
15548
16426
|
var OutputArea = class extends Container {
|
|
15549
16427
|
lines = [];
|
|
16428
|
+
/** 逐行缓存的渲染结果(避免每帧对所有历史行重新调用 wrapTextWithAnsi) */
|
|
16429
|
+
lineCache = /* @__PURE__ */ new Map();
|
|
16430
|
+
/** 缓存生成时使用的终端宽度,变化时重建 */
|
|
16431
|
+
cacheWidth = 0;
|
|
15550
16432
|
render(width) {
|
|
16433
|
+
if (width !== this.cacheWidth) {
|
|
16434
|
+
this.cacheWidth = width;
|
|
16435
|
+
this.lineCache.clear();
|
|
16436
|
+
}
|
|
15551
16437
|
const result = [];
|
|
15552
|
-
for (
|
|
16438
|
+
for (let i = 0; i < this.lines.length; i++) {
|
|
16439
|
+
let cached = this.lineCache.get(i);
|
|
16440
|
+
if (!cached) {
|
|
16441
|
+
cached = wrapTextWithAnsi(this.lines[i] ?? "", width);
|
|
16442
|
+
this.lineCache.set(i, cached);
|
|
16443
|
+
}
|
|
16444
|
+
result.push(...cached);
|
|
16445
|
+
}
|
|
15553
16446
|
return result;
|
|
15554
16447
|
}
|
|
15555
16448
|
/** 追加多行内容,返回新追加行中第一行的索引 */
|
|
@@ -15562,33 +16455,45 @@ var OutputArea = class extends Container {
|
|
|
15562
16455
|
/** 追加文本到当前行末尾(用于流式输出) */
|
|
15563
16456
|
appendText(text) {
|
|
15564
16457
|
if (this.lines.length === 0) this.lines.push(text);
|
|
15565
|
-
else
|
|
16458
|
+
else {
|
|
16459
|
+
this.lines[this.lines.length - 1] += text;
|
|
16460
|
+
this.lineCache.delete(this.lines.length - 1);
|
|
16461
|
+
}
|
|
15566
16462
|
this.invalidate();
|
|
15567
16463
|
}
|
|
15568
16464
|
/** 替换最后一行的完整内容(用于 spinner 动画和首 chunk 替换),返回行索引 */
|
|
15569
16465
|
replaceLastLine(text) {
|
|
15570
|
-
if (this.lines.length > 0)
|
|
15571
|
-
|
|
16466
|
+
if (this.lines.length > 0) {
|
|
16467
|
+
this.lines[this.lines.length - 1] = text;
|
|
16468
|
+
this.lineCache.delete(this.lines.length - 1);
|
|
16469
|
+
} else this.lines.push(text);
|
|
15572
16470
|
this.invalidate();
|
|
15573
16471
|
return this.lines.length - 1;
|
|
15574
16472
|
}
|
|
15575
16473
|
/** 在指定位置插入/删除/替换行(原子操作) */
|
|
15576
16474
|
spliceLines(startIndex, deleteCount, insertLines) {
|
|
15577
16475
|
this.lines.splice(startIndex, deleteCount, ...insertLines);
|
|
16476
|
+
this.invalidateCacheFrom(startIndex);
|
|
15578
16477
|
this.invalidate();
|
|
15579
16478
|
}
|
|
15580
16479
|
/** 更新指定索引行的内容 */
|
|
15581
16480
|
updateLine(index, text) {
|
|
15582
16481
|
if (index >= 0 && index < this.lines.length) {
|
|
15583
16482
|
this.lines[index] = text;
|
|
16483
|
+
this.lineCache.delete(index);
|
|
15584
16484
|
this.invalidate();
|
|
15585
16485
|
}
|
|
15586
16486
|
}
|
|
15587
16487
|
/** 清空所有内容 */
|
|
15588
16488
|
clear() {
|
|
15589
16489
|
this.lines = [];
|
|
16490
|
+
this.lineCache.clear();
|
|
15590
16491
|
this.invalidate();
|
|
15591
16492
|
}
|
|
16493
|
+
/** 使从指定索引开始的所有缓存行失效 */
|
|
16494
|
+
invalidateCacheFrom(startIndex) {
|
|
16495
|
+
for (const key of this.lineCache.keys()) if (key >= startIndex) this.lineCache.delete(key);
|
|
16496
|
+
}
|
|
15592
16497
|
};
|
|
15593
16498
|
/** 默认提示符(用于显示/格式化) */
|
|
15594
16499
|
const DEFAULT_PROMPT = "❯ ";
|
|
@@ -15612,6 +16517,8 @@ var ReplSession = class {
|
|
|
15612
16517
|
renderer;
|
|
15613
16518
|
history;
|
|
15614
16519
|
currentTaskAbort = null;
|
|
16520
|
+
/** 动画管理器(渲染周期驱动的 spinner/计时器,替代 setInterval) */
|
|
16521
|
+
animationManager = new AnimationManager();
|
|
15615
16522
|
/** 欢迎语逐字输出定时器引用(用于 shutdown 时清理) */
|
|
15616
16523
|
welcomeTypewriterInterval;
|
|
15617
16524
|
/** Agent 实例(会话级复用,替代直接 LLM 调用) */
|
|
@@ -15640,6 +16547,7 @@ var ReplSession = class {
|
|
|
15640
16547
|
auditLogger;
|
|
15641
16548
|
secretRedactor;
|
|
15642
16549
|
skillGuard;
|
|
16550
|
+
skillWatcher;
|
|
15643
16551
|
/** 交互式提问管理器 */
|
|
15644
16552
|
questionManager;
|
|
15645
16553
|
stats = {
|
|
@@ -15663,6 +16571,7 @@ var ReplSession = class {
|
|
|
15663
16571
|
const theme = createTheme(this.options.color);
|
|
15664
16572
|
const terminal = new ProcessTerminal();
|
|
15665
16573
|
this.tui = new TUI(terminal);
|
|
16574
|
+
this.animationManager.bind(this.tui);
|
|
15666
16575
|
getKeybindings().setUserBindings({
|
|
15667
16576
|
"tui.select.up": ["up"],
|
|
15668
16577
|
"tui.select.down": ["down"],
|
|
@@ -15671,13 +16580,13 @@ var ReplSession = class {
|
|
|
15671
16580
|
});
|
|
15672
16581
|
this.taskStore = new TaskStore();
|
|
15673
16582
|
this.outputArea = new OutputArea();
|
|
15674
|
-
this.agentStatusBar = new AgentStatusBar();
|
|
15675
|
-
this.taskStatusBar = new TaskStatusBar(this.taskStore);
|
|
16583
|
+
this.agentStatusBar = new AgentStatusBar(this.animationManager);
|
|
16584
|
+
this.taskStatusBar = new TaskStatusBar(this.taskStore, this.animationManager);
|
|
15676
16585
|
this.taskStore.onChange(() => {
|
|
15677
16586
|
this.taskStatusBar.onTasksChanged();
|
|
15678
16587
|
this.tui.requestRender();
|
|
15679
16588
|
});
|
|
15680
|
-
this.editor = new ZapmycoEditor(this.tui, theme.editorTheme);
|
|
16589
|
+
this.editor = new ZapmycoEditor(this.tui, theme.editorTheme, this.animationManager);
|
|
15681
16590
|
const root = new Container();
|
|
15682
16591
|
root.addChild(this.outputArea);
|
|
15683
16592
|
root.addChild(this.taskStatusBar);
|
|
@@ -15701,6 +16610,7 @@ var ReplSession = class {
|
|
|
15701
16610
|
log.warn("记忆快照冻结失败,将使用空快照", { error: err instanceof Error ? err.message : String(err) });
|
|
15702
16611
|
this.agent.memorySnapshot = "";
|
|
15703
16612
|
});
|
|
16613
|
+
this.skillWatcher = new SkillWatcher();
|
|
15704
16614
|
if (this.config.skill?.enabled !== false) this.initSkills();
|
|
15705
16615
|
this.initSecurity();
|
|
15706
16616
|
this.questionManager = getQuestionManager();
|
|
@@ -15762,6 +16672,7 @@ var ReplSession = class {
|
|
|
15762
16672
|
this.cronScheduler.stop();
|
|
15763
16673
|
this.cronScheduler = null;
|
|
15764
16674
|
}
|
|
16675
|
+
this.skillWatcher.stop();
|
|
15765
16676
|
if (this.auditLogger) this.auditLogger.destroy();
|
|
15766
16677
|
if (this.mcpManager) {
|
|
15767
16678
|
await this.mcpManager.shutdown();
|
|
@@ -15790,6 +16701,37 @@ var ReplSession = class {
|
|
|
15790
16701
|
getHistoryStore() {
|
|
15791
16702
|
return this.history;
|
|
15792
16703
|
}
|
|
16704
|
+
/**
|
|
16705
|
+
* 彻底清空 Agent 会话上下文。
|
|
16706
|
+
*
|
|
16707
|
+
* - 清空消息历史、Token 统计、缓存、授权
|
|
16708
|
+
* - 保留配置、记忆、定时任务
|
|
16709
|
+
* - 效果等价于"新开一个会话"
|
|
16710
|
+
*
|
|
16711
|
+
* 由 /clear 命令调用。
|
|
16712
|
+
*/
|
|
16713
|
+
clearAgentContext() {
|
|
16714
|
+
this.cancelCurrentTask();
|
|
16715
|
+
this.currentTaskId = null;
|
|
16716
|
+
this._state = "idle";
|
|
16717
|
+
this.agent.resetContext();
|
|
16718
|
+
this.conversationHistory = [];
|
|
16719
|
+
this.stats = {
|
|
16720
|
+
totalRequests: 0,
|
|
16721
|
+
successCount: 0,
|
|
16722
|
+
failureCount: 0,
|
|
16723
|
+
totalTokens: 0,
|
|
16724
|
+
totalCostUsd: 0,
|
|
16725
|
+
state: "idle"
|
|
16726
|
+
};
|
|
16727
|
+
this.permissionStore.clear();
|
|
16728
|
+
this.questionManager.rejectAll(/* @__PURE__ */ new Error("会话已重置"));
|
|
16729
|
+
this.editor.setExecuting(false);
|
|
16730
|
+
this.outputArea.clear();
|
|
16731
|
+
this.taskStore.clear();
|
|
16732
|
+
this.agentStatusBar.clearTokenStats();
|
|
16733
|
+
this.tui.requestRender();
|
|
16734
|
+
}
|
|
15793
16735
|
/** 获取会话统计 */
|
|
15794
16736
|
getStats() {
|
|
15795
16737
|
return { ...this.stats };
|
|
@@ -15891,8 +16833,72 @@ var ReplSession = class {
|
|
|
15891
16833
|
const execFailStyle = (s) => colorEnabled ? chalk.red(s) : s;
|
|
15892
16834
|
const dimStyle = (s) => colorEnabled ? chalk.gray(s) : s;
|
|
15893
16835
|
let spinnerActive = true;
|
|
15894
|
-
|
|
15895
|
-
let
|
|
16836
|
+
/** 动画管理器帧推进计时(spinner,100ms间隔) */
|
|
16837
|
+
let lastSpinnerTick = 0;
|
|
16838
|
+
/** thinking 计时动画活跃标志(替代 thinkingElapsedInterval setInterval) */
|
|
16839
|
+
let thinkingTimerActive = false;
|
|
16840
|
+
let thinkingFrame = 0;
|
|
16841
|
+
let lastThinkingTick = 0;
|
|
16842
|
+
/** AnimationManager 回调注销函数集合(finally 中统一清理) */
|
|
16843
|
+
const animCleanup = [];
|
|
16844
|
+
/**
|
|
16845
|
+
* 合并渲染请求——在同一个微任务内的多次调用合并为一次 requestRender。
|
|
16846
|
+
* 流式事件(OUTPUT/THINKING/PROGRESS)短时间密集到达时,避免为每个事件
|
|
16847
|
+
* 都调用 requestRender(即使 pi-tui 内部有 16ms 去抖,仍可减少调度开销)。
|
|
16848
|
+
*/
|
|
16849
|
+
let coalesceRenderPending = false;
|
|
16850
|
+
const requestCoalescedRender = () => {
|
|
16851
|
+
if (coalesceRenderPending) return;
|
|
16852
|
+
coalesceRenderPending = true;
|
|
16853
|
+
queueMicrotask(() => {
|
|
16854
|
+
coalesceRenderPending = false;
|
|
16855
|
+
this.tui.requestRender();
|
|
16856
|
+
});
|
|
16857
|
+
};
|
|
16858
|
+
const mainAgentDef = this.isCoordinatorMode() ? {
|
|
16859
|
+
typeId: "repl-coordinator",
|
|
16860
|
+
displayName: "协调者",
|
|
16861
|
+
whenToUse: "Main REPL coordinator",
|
|
16862
|
+
role: "coordinator",
|
|
16863
|
+
capabilities: [{
|
|
16864
|
+
id: "agent-orchestration",
|
|
16865
|
+
name: "Agent 编排",
|
|
16866
|
+
description: "编排多个子 Agent 协同完成复杂任务",
|
|
16867
|
+
category: "planning"
|
|
16868
|
+
}],
|
|
16869
|
+
toolPolicy: { mode: "inherit" },
|
|
16870
|
+
permissionMode: "inherit",
|
|
16871
|
+
source: "builtin",
|
|
16872
|
+
maxTurns: 100,
|
|
16873
|
+
maxSpawnDepth: 2,
|
|
16874
|
+
getSystemPrompt: () => "",
|
|
16875
|
+
color: "#e67e22"
|
|
16876
|
+
} : {
|
|
16877
|
+
typeId: "Explore",
|
|
16878
|
+
displayName: "Explore Agent",
|
|
16879
|
+
whenToUse: "Main REPL agent",
|
|
16880
|
+
role: "universal",
|
|
16881
|
+
capabilities: [],
|
|
16882
|
+
toolPolicy: { mode: "inherit" },
|
|
16883
|
+
permissionMode: "inherit",
|
|
16884
|
+
source: "builtin",
|
|
16885
|
+
maxTurns: 100,
|
|
16886
|
+
maxSpawnDepth: 0,
|
|
16887
|
+
getSystemPrompt: () => "",
|
|
16888
|
+
color: "#3498db"
|
|
16889
|
+
};
|
|
16890
|
+
const mainAgent = this.agent;
|
|
16891
|
+
const instanceManager = getAgentInstanceManager();
|
|
16892
|
+
const mainInstance = instanceManager.register(mainAgentDef, mainAgent, {
|
|
16893
|
+
taskId,
|
|
16894
|
+
description: rawInput.slice(0, 200),
|
|
16895
|
+
mode: "sync",
|
|
16896
|
+
timeoutMs: this.config.scheduler.taskTimeoutMs,
|
|
16897
|
+
inheritContext: false
|
|
16898
|
+
}, null, 0);
|
|
16899
|
+
instanceManager.transition(mainInstance.instanceId, "running");
|
|
16900
|
+
/** Token 信息推送定时器 */
|
|
16901
|
+
let tokenPushInterval;
|
|
15896
16902
|
try {
|
|
15897
16903
|
this._state = "executing";
|
|
15898
16904
|
this.updateStatsState();
|
|
@@ -15909,12 +16915,13 @@ var ReplSession = class {
|
|
|
15909
16915
|
this.outputArea.append([userStyle(displayLabel ?? rawInput), LOADING_FRAMES$1[0] ?? ""]);
|
|
15910
16916
|
let spinnerFrame = 0;
|
|
15911
16917
|
spinnerActive = true;
|
|
15912
|
-
|
|
16918
|
+
animCleanup.push(this.animationManager.register((now) => {
|
|
15913
16919
|
if (!spinnerActive) return;
|
|
16920
|
+
if (now - lastSpinnerTick < 100) return;
|
|
16921
|
+
lastSpinnerTick = now;
|
|
15914
16922
|
spinnerFrame = (spinnerFrame + 1) % LOADING_FRAMES$1.length;
|
|
15915
16923
|
this.outputArea.replaceLastLine(LOADING_FRAMES$1[spinnerFrame] ?? "");
|
|
15916
|
-
|
|
15917
|
-
}, 100);
|
|
16924
|
+
}));
|
|
15918
16925
|
let spinnerStopped = false;
|
|
15919
16926
|
let outputAccumulator = "";
|
|
15920
16927
|
let thinkingAccumulator = "";
|
|
@@ -15959,20 +16966,20 @@ var ReplSession = class {
|
|
|
15959
16966
|
};
|
|
15960
16967
|
const outputHandler = (event) => {
|
|
15961
16968
|
if (event.taskId !== taskId || !event.text) return;
|
|
16969
|
+
if (hasExecutedTool) {
|
|
16970
|
+
hasExecutedTool = false;
|
|
16971
|
+
instanceManager.transition(mainInstance.instanceId, "completed");
|
|
16972
|
+
}
|
|
15962
16973
|
if (!spinnerStopped) {
|
|
15963
16974
|
spinnerStopped = true;
|
|
15964
16975
|
spinnerActive = false;
|
|
15965
|
-
clearInterval(spinnerInterval);
|
|
15966
16976
|
streamMode = "response";
|
|
15967
16977
|
thinkingAccumulator = "";
|
|
15968
16978
|
outputAccumulator = event.text;
|
|
15969
16979
|
responseLineIndex = this.outputArea.replaceLastLine(responseStyle(outputAccumulator));
|
|
15970
16980
|
} else if (streamMode !== "response") {
|
|
15971
16981
|
streamMode = "response";
|
|
15972
|
-
|
|
15973
|
-
clearInterval(thinkingElapsedInterval);
|
|
15974
|
-
thinkingElapsedInterval = void 0;
|
|
15975
|
-
}
|
|
16982
|
+
thinkingTimerActive = false;
|
|
15976
16983
|
thinkingAccumulator = "";
|
|
15977
16984
|
outputAccumulator = event.text;
|
|
15978
16985
|
if (thinkingHeaderLineIndex !== null) {
|
|
@@ -15984,7 +16991,7 @@ var ReplSession = class {
|
|
|
15984
16991
|
outputAccumulator += event.text;
|
|
15985
16992
|
this.outputArea.replaceLastLine(responseStyle(outputAccumulator));
|
|
15986
16993
|
}
|
|
15987
|
-
|
|
16994
|
+
requestCoalescedRender();
|
|
15988
16995
|
};
|
|
15989
16996
|
const thinkingHandler = (event) => {
|
|
15990
16997
|
if (event.taskId !== taskId || !event.text) return;
|
|
@@ -15995,7 +17002,6 @@ var ReplSession = class {
|
|
|
15995
17002
|
if (!spinnerStopped) {
|
|
15996
17003
|
spinnerStopped = true;
|
|
15997
17004
|
spinnerActive = false;
|
|
15998
|
-
clearInterval(spinnerInterval);
|
|
15999
17005
|
thinkingHeaderLineIndex = this.outputArea.replaceLastLine(dimStyle(` ${LOADING_FRAMES$1[0] ?? ""} Thinking...`));
|
|
16000
17006
|
} else thinkingHeaderLineIndex = this.outputArea.append([dimStyle(` ${LOADING_FRAMES$1[0] ?? ""} Thinking...`)]);
|
|
16001
17007
|
thinkingAccumulator = event.text;
|
|
@@ -16004,16 +17010,19 @@ var ReplSession = class {
|
|
|
16004
17010
|
this.outputArea.append([dimStyle(THINKING_CONTINUE_PREFIX + thinkingAccumulator)]);
|
|
16005
17011
|
thinkingContentLineCount = 1;
|
|
16006
17012
|
} else if (thinkingDisplayMode === "collapse") {
|
|
16007
|
-
|
|
16008
|
-
|
|
17013
|
+
thinkingTimerActive = true;
|
|
17014
|
+
thinkingFrame = 0;
|
|
17015
|
+
animCleanup.push(this.animationManager.register((now) => {
|
|
17016
|
+
if (!thinkingTimerActive) return;
|
|
16009
17017
|
if (thinkingHeaderLineIndex === null) return;
|
|
16010
17018
|
if (thinkingContentLineCount > 0) return;
|
|
17019
|
+
if (now - lastThinkingTick < 200) return;
|
|
17020
|
+
lastThinkingTick = now;
|
|
16011
17021
|
const elapsed = ((Date.now() - thinkingStartTimeMs) / 1e3).toFixed(1);
|
|
16012
17022
|
const frame = LOADING_FRAMES$1[thinkingFrame % LOADING_FRAMES$1.length];
|
|
16013
17023
|
thinkingFrame++;
|
|
16014
17024
|
this.outputArea.updateLine(thinkingHeaderLineIndex, dimStyle(` ${frame} Thinking... (${elapsed}s)`));
|
|
16015
|
-
|
|
16016
|
-
}, 200);
|
|
17025
|
+
}));
|
|
16017
17026
|
}
|
|
16018
17027
|
} else {
|
|
16019
17028
|
thinkingAccumulator += event.text;
|
|
@@ -16024,38 +17033,87 @@ var ReplSession = class {
|
|
|
16024
17033
|
this.outputArea.replaceLastLine(dimStyle(THINKING_CONTINUE_PREFIX + lastLine));
|
|
16025
17034
|
}
|
|
16026
17035
|
}
|
|
16027
|
-
|
|
17036
|
+
requestCoalescedRender();
|
|
16028
17037
|
};
|
|
16029
17038
|
const errorHandler = (event) => {
|
|
16030
17039
|
if (event.taskId === taskId) log.error("Agent 执行中收到 error 事件", { error: event.error.message });
|
|
16031
17040
|
};
|
|
16032
17041
|
let execLineIndex;
|
|
16033
17042
|
let execMessage;
|
|
17043
|
+
/** 是否执行过工具调用(用于判断输出阶段时是否结束 agent 状态) */
|
|
17044
|
+
let hasExecutedTool = false;
|
|
17045
|
+
/** 连续同类型工具调用的分组跟踪(用于 OutputArea 展示 ⎿ 分组) */
|
|
17046
|
+
let lastToolCategory = "";
|
|
17047
|
+
let lastToolLabel = "";
|
|
17048
|
+
let toolGroupCount = 0;
|
|
17049
|
+
let toolGroupLineIndex = null;
|
|
17050
|
+
let toolGroupSampleArg = "";
|
|
16034
17051
|
const progressHandler = (event) => {
|
|
16035
17052
|
if (event.taskId !== taskId) return;
|
|
17053
|
+
if (event.detail?.isStart) instanceManager.recordToolCall(mainInstance.instanceId, {
|
|
17054
|
+
toolName: event.detail.toolName,
|
|
17055
|
+
toolCallId: event.detail.toolCallId,
|
|
17056
|
+
argsDisplay: event.detail.argsDisplay,
|
|
17057
|
+
status: "running",
|
|
17058
|
+
startedAt: Date.now()
|
|
17059
|
+
});
|
|
17060
|
+
else if (event.detail?.isEnd) {
|
|
17061
|
+
const mainInst = instanceManager.get(mainInstance.instanceId);
|
|
17062
|
+
if (mainInst) {
|
|
17063
|
+
const lastRunning = [...mainInst.toolCallHistory].reverse().find((t) => t.status === "running" && t.toolName === event.detail?.toolName);
|
|
17064
|
+
if (lastRunning) {
|
|
17065
|
+
lastRunning.status = event.detail.isError ? "failed" : "completed";
|
|
17066
|
+
lastRunning.endedAt = Date.now();
|
|
17067
|
+
}
|
|
17068
|
+
}
|
|
17069
|
+
}
|
|
16036
17070
|
if (event.percent === 0) {
|
|
16037
|
-
if (
|
|
16038
|
-
|
|
16039
|
-
thinkingElapsedInterval = void 0;
|
|
17071
|
+
if (thinkingTimerActive && streamMode === "thinking") {
|
|
17072
|
+
thinkingTimerActive = false;
|
|
16040
17073
|
if (thinkingHeaderLineIndex !== null) {
|
|
16041
17074
|
this.outputArea.spliceLines(thinkingHeaderLineIndex, 1, []);
|
|
16042
17075
|
thinkingHeaderLineIndex = null;
|
|
16043
|
-
|
|
17076
|
+
requestCoalescedRender();
|
|
16044
17077
|
}
|
|
16045
17078
|
}
|
|
17079
|
+
hasExecutedTool = true;
|
|
16046
17080
|
if (event.message.startsWith("Exec(")) {
|
|
16047
17081
|
execMessage = event.message;
|
|
16048
17082
|
execLineIndex = this.outputArea.append([execStyle(` ⏺ ${event.message}`)]);
|
|
16049
|
-
|
|
16050
|
-
|
|
16051
|
-
|
|
17083
|
+
lastToolCategory = "";
|
|
17084
|
+
toolGroupCount = 0;
|
|
17085
|
+
toolGroupLineIndex = null;
|
|
17086
|
+
} else if (event.message.startsWith("TaskManage(")) {
|
|
17087
|
+
log.debug("TaskManage 工具调用已记录,跳过 TUI 展示");
|
|
17088
|
+
lastToolCategory = "";
|
|
17089
|
+
toolGroupCount = 0;
|
|
17090
|
+
toolGroupLineIndex = null;
|
|
17091
|
+
} else if (event.detail?.toolName) {
|
|
17092
|
+
const { category, label } = categorizeTool(event.detail.toolName);
|
|
17093
|
+
const argsPart = event.detail.argsDisplay ?? "";
|
|
17094
|
+
if (category === lastToolCategory && toolGroupLineIndex !== null) {
|
|
17095
|
+
toolGroupCount++;
|
|
17096
|
+
const sampleDisplay = toolGroupSampleArg ? ` (e.g. ${toolGroupSampleArg.slice(0, 50)})` : "";
|
|
17097
|
+
const groupedText = toolStyle(` ⎿ ${lastToolLabel} ${toolGroupCount} items${sampleDisplay}`);
|
|
17098
|
+
this.outputArea.updateLine(toolGroupLineIndex, groupedText);
|
|
17099
|
+
} else {
|
|
17100
|
+
lastToolCategory = category;
|
|
17101
|
+
lastToolLabel = label;
|
|
17102
|
+
toolGroupCount = 1;
|
|
17103
|
+
toolGroupSampleArg = argsPart;
|
|
17104
|
+
const sampleDisplay = argsPart ? ` (e.g. ${argsPart.slice(0, 50)})` : "";
|
|
17105
|
+
const lineText = toolStyle(` ⎿ ${label} ${toolGroupCount} item${sampleDisplay}`);
|
|
17106
|
+
toolGroupLineIndex = this.outputArea.append([lineText]);
|
|
17107
|
+
}
|
|
17108
|
+
} else this.outputArea.append([toolStyle(` → ${event.message}`)]);
|
|
17109
|
+
requestCoalescedRender();
|
|
16052
17110
|
} else if (event.percent === 100 && execLineIndex !== void 0 && execMessage) {
|
|
16053
17111
|
if (event.message.startsWith("工具 Exec")) {
|
|
16054
17112
|
const style = event.message.includes("完成") ? execSuccessStyle : execFailStyle;
|
|
16055
17113
|
this.outputArea.updateLine(execLineIndex, style(` ⏺ ${execMessage}`));
|
|
16056
17114
|
execLineIndex = void 0;
|
|
16057
17115
|
execMessage = void 0;
|
|
16058
|
-
|
|
17116
|
+
requestCoalescedRender();
|
|
16059
17117
|
}
|
|
16060
17118
|
}
|
|
16061
17119
|
};
|
|
@@ -16066,13 +17124,45 @@ var ReplSession = class {
|
|
|
16066
17124
|
this.currentTaskId = taskId;
|
|
16067
17125
|
const originalOnToggleThinking = this.editor.onToggleThinking;
|
|
16068
17126
|
if (thinkingDisplayMode === "collapse") this.editor.onToggleThinking = toggleThinking;
|
|
17127
|
+
this.agentStatusBar.clearTokenStats();
|
|
17128
|
+
const agentModel = this.agent.innerAgent.state.model;
|
|
17129
|
+
const modelName = typeof agentModel?.id === "string" ? agentModel.id : "unknown";
|
|
17130
|
+
this.agentStatusBar.setModelName(modelName);
|
|
17131
|
+
tokenPushInterval = setInterval(() => {
|
|
17132
|
+
const usage = this.agent.tokenTracker.getUsage();
|
|
17133
|
+
this.agentStatusBar.updateTokenStats(usage.inputTokens, usage.cacheReadTokens, usage.outputTokens, Date.now() - startTime);
|
|
17134
|
+
this.tui.requestRender();
|
|
17135
|
+
}, 2e3);
|
|
16069
17136
|
log.debug("开始通过 Agent 执行目标", {
|
|
16070
17137
|
taskId,
|
|
16071
17138
|
taskDescription: rawInput.slice(0, 100)
|
|
16072
17139
|
});
|
|
17140
|
+
let effectiveTaskDescription = rawInput;
|
|
17141
|
+
if (this.isCoordinatorMode()) {
|
|
17142
|
+
const pendingMessages = getAgentMessageBus().drainInbox("repl-chat-agent");
|
|
17143
|
+
if (pendingMessages.length > 0) effectiveTaskDescription = [
|
|
17144
|
+
"## 以下后台任务已完成",
|
|
17145
|
+
...pendingMessages.map((msg) => {
|
|
17146
|
+
try {
|
|
17147
|
+
const parsed = JSON.parse(msg.payload);
|
|
17148
|
+
if (parsed.type === "task_result") return [
|
|
17149
|
+
`[任务完成通知]`,
|
|
17150
|
+
`任务 ID: ${parsed.taskId}`,
|
|
17151
|
+
`类型: ${parsed.typeId}`,
|
|
17152
|
+
`状态: ${parsed.status}`,
|
|
17153
|
+
parsed.summary ? `\n结果:\n${parsed.summary}` : "",
|
|
17154
|
+
parsed.error ? `错误: ${parsed.error}` : ""
|
|
17155
|
+
].join("\n");
|
|
17156
|
+
} catch {}
|
|
17157
|
+
return `[消息 - ${msg.fromAgentId}]\n${msg.payload}`;
|
|
17158
|
+
}),
|
|
17159
|
+
"---",
|
|
17160
|
+
rawInput
|
|
17161
|
+
].join("\n\n");
|
|
17162
|
+
}
|
|
16073
17163
|
const taskResult = await this.agent.execute({
|
|
16074
17164
|
taskId,
|
|
16075
|
-
taskDescription:
|
|
17165
|
+
taskDescription: effectiveTaskDescription,
|
|
16076
17166
|
workdir: process.cwd(),
|
|
16077
17167
|
options: {
|
|
16078
17168
|
timeout: this.config.scheduler.taskTimeoutMs,
|
|
@@ -16084,6 +17174,12 @@ var ReplSession = class {
|
|
|
16084
17174
|
this.agent.off(this.agent.EVENT_ERROR, errorHandler);
|
|
16085
17175
|
this.agent.off(this.agent.EVENT_PROGRESS, progressHandler);
|
|
16086
17176
|
this.editor.onToggleThinking = originalOnToggleThinking;
|
|
17177
|
+
const mainInst = instanceManager.get(mainInstance.instanceId);
|
|
17178
|
+
if (mainInst && mainInst.status === "running") {
|
|
17179
|
+
const mainAgentStatus = taskResult.status === "success" ? "completed" : "failed";
|
|
17180
|
+
instanceManager.transition(mainInstance.instanceId, mainAgentStatus);
|
|
17181
|
+
}
|
|
17182
|
+
this.tui.requestRender();
|
|
16087
17183
|
log.debug("Agent 执行完成", {
|
|
16088
17184
|
taskId,
|
|
16089
17185
|
status: taskResult.status,
|
|
@@ -16098,7 +17194,6 @@ var ReplSession = class {
|
|
|
16098
17194
|
""
|
|
16099
17195
|
]);
|
|
16100
17196
|
spinnerActive = false;
|
|
16101
|
-
clearInterval(spinnerInterval);
|
|
16102
17197
|
try {
|
|
16103
17198
|
const compactionResult = await this.agent.compact();
|
|
16104
17199
|
if (compactionResult.success) {
|
|
@@ -16110,17 +17205,10 @@ var ReplSession = class {
|
|
|
16110
17205
|
this.outputArea.append([chalk.red(" 上下文整理异常,请手动执行 /compact 后重试"), ""]);
|
|
16111
17206
|
}
|
|
16112
17207
|
spinnerActive = true;
|
|
16113
|
-
spinnerInterval = setInterval(() => {
|
|
16114
|
-
if (!spinnerActive) return;
|
|
16115
|
-
spinnerFrame = (spinnerFrame + 1) % LOADING_FRAMES$1.length;
|
|
16116
|
-
this.outputArea.replaceLastLine(LOADING_FRAMES$1[spinnerFrame] ?? "");
|
|
16117
|
-
this.tui.requestRender();
|
|
16118
|
-
}, 100);
|
|
16119
17208
|
}
|
|
16120
17209
|
const outputText = typeof taskResult.output === "string" ? taskResult.output : taskResult.output != null ? JSON.stringify(taskResult.output) : null;
|
|
16121
17210
|
if (spinnerActive) {
|
|
16122
17211
|
spinnerActive = false;
|
|
16123
|
-
clearInterval(spinnerInterval);
|
|
16124
17212
|
if (outputText) this.outputArea.replaceLastLine(responseStyle(outputText));
|
|
16125
17213
|
else if (taskResult.status !== "success") {
|
|
16126
17214
|
const errorMsg = taskResult.error?.message ?? t("session.agentErrorMessage");
|
|
@@ -16232,11 +17320,7 @@ var ReplSession = class {
|
|
|
16232
17320
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
16233
17321
|
log.error("目标执行失败", { input: rawInput }, err);
|
|
16234
17322
|
spinnerActive = false;
|
|
16235
|
-
|
|
16236
|
-
if (thinkingElapsedInterval) {
|
|
16237
|
-
clearInterval(thinkingElapsedInterval);
|
|
16238
|
-
thinkingElapsedInterval = void 0;
|
|
16239
|
-
}
|
|
17323
|
+
thinkingTimerActive = false;
|
|
16240
17324
|
this.stats.totalRequests++;
|
|
16241
17325
|
this.stats.failureCount++;
|
|
16242
17326
|
eventBus.emit("goal:failed", {
|
|
@@ -16300,11 +17384,15 @@ var ReplSession = class {
|
|
|
16300
17384
|
};
|
|
16301
17385
|
} finally {
|
|
16302
17386
|
spinnerActive = false;
|
|
16303
|
-
|
|
16304
|
-
|
|
16305
|
-
|
|
16306
|
-
|
|
17387
|
+
thinkingTimerActive = false;
|
|
17388
|
+
for (const unregister of animCleanup) unregister();
|
|
17389
|
+
animCleanup.length = 0;
|
|
17390
|
+
if (tokenPushInterval) {
|
|
17391
|
+
clearInterval(tokenPushInterval);
|
|
17392
|
+
tokenPushInterval = void 0;
|
|
16307
17393
|
}
|
|
17394
|
+
const finalUsage = this.agent.tokenTracker.getUsage();
|
|
17395
|
+
this.agentStatusBar.updateTokenStats(finalUsage.inputTokens, finalUsage.cacheReadTokens, finalUsage.outputTokens, Date.now() - startTime);
|
|
16308
17396
|
this._state = "idle";
|
|
16309
17397
|
this.updateStatsState();
|
|
16310
17398
|
this.editor.setExecuting(false);
|
|
@@ -16464,7 +17552,7 @@ var ReplSession = class {
|
|
|
16464
17552
|
/**
|
|
16465
17553
|
* 创建 REPL 专用的 Agent 实例
|
|
16466
17554
|
*
|
|
16467
|
-
* Agent
|
|
17555
|
+
* Agent 复用本地 Model 对象进行 LLM 调用,
|
|
16468
17556
|
* 因此需要从 config.llm 解析 model 并注入到 Agent state。
|
|
16469
17557
|
*/
|
|
16470
17558
|
createReplAgent() {
|
|
@@ -16480,7 +17568,7 @@ var ReplSession = class {
|
|
|
16480
17568
|
runtimeConfig: this.config.agentRuntime ?? {}
|
|
16481
17569
|
});
|
|
16482
17570
|
const facade = new AgentLlmFacade(this.config.llm);
|
|
16483
|
-
agent.innerAgent.state.model = facade.
|
|
17571
|
+
agent.innerAgent.state.model = facade.resolveResolvedModel();
|
|
16484
17572
|
agent.innerAgent.getApiKey = facade.createGetApiKeyFn();
|
|
16485
17573
|
agent.llmFacade = facade;
|
|
16486
17574
|
agent.innerAgent.cacheRetention = this.config.agentRuntime?.cacheRetention;
|
|
@@ -16509,17 +17597,36 @@ var ReplSession = class {
|
|
|
16509
17597
|
return agent;
|
|
16510
17598
|
}
|
|
16511
17599
|
/**
|
|
17600
|
+
* 判断是否处于 Coordinator 模式
|
|
17601
|
+
*
|
|
17602
|
+
* 当 agentTeam.defaultMode === 'coordinator' 时,
|
|
17603
|
+
* 主 Agent 切换为编排模式:工具受限、系统提示词替换。
|
|
17604
|
+
*/
|
|
17605
|
+
isCoordinatorMode() {
|
|
17606
|
+
return this.config.agentTeam?.defaultMode === "coordinator";
|
|
17607
|
+
}
|
|
17608
|
+
/**
|
|
16512
17609
|
* 注册 REPL 场景下的基础工具
|
|
16513
17610
|
*
|
|
16514
17611
|
* 在注册内置工具后,异步初始化 MCP 工具。
|
|
16515
17612
|
* MCP 连接不阻塞内置工具注册——Agent 立即可用内置工具,
|
|
16516
17613
|
* MCP 工具在连接完成后自动追加。
|
|
17614
|
+
*
|
|
17615
|
+
* 在 Coordinator 模式下,主 Agent 仅注册编排工具
|
|
17616
|
+
*(AgentTool + SendMessage + TaskStop),完整工具集
|
|
17617
|
+
* 仍传递给 AgentOrchestrator 供子 Agent 使用。
|
|
16517
17618
|
*/
|
|
16518
17619
|
registerBuiltinTools() {
|
|
16519
17620
|
const rawTools = createReplBuiltinTools(this.config.web, this.taskStore, this.config.skill, this.agent, this.config.subAgent, this.cronScheduler ?? void 0, this.config.agentTeam, this.worktreeManager, this.toolGuard);
|
|
16520
17621
|
this.permissionEngine.setToolInfoResolver(createToolInfoResolver(rawTools));
|
|
16521
|
-
const
|
|
17622
|
+
const mainTools = this.isCoordinatorMode() ? [
|
|
17623
|
+
...rawTools.filter((t) => COORDINATOR_TOOLS.includes(t.id)),
|
|
17624
|
+
createSendMessageTool("repl-chat-agent"),
|
|
17625
|
+
createTaskStopTool()
|
|
17626
|
+
] : rawTools;
|
|
17627
|
+
const guardedTools = this.toolGuard.wrapAll(mainTools);
|
|
16522
17628
|
this.agent.registerTools(guardedTools);
|
|
17629
|
+
if (this.isCoordinatorMode()) this.agent.systemPromptOverride = getMainCoordinatorSystemPrompt(process.cwd());
|
|
16523
17630
|
const mcpServers = this.config.mcp ? normalizeMcpConfig(this.config.mcp) : [];
|
|
16524
17631
|
if (mcpServers.length > 0) initializeMcpTools(mcpServers, this.agent).then((manager) => {
|
|
16525
17632
|
this.mcpManager = manager;
|
|
@@ -16567,33 +17674,90 @@ var ReplSession = class {
|
|
|
16567
17674
|
}, process.cwd()).then((entries) => {
|
|
16568
17675
|
setSkillEntries(entries);
|
|
16569
17676
|
this.agent.skillEntries = entries;
|
|
16570
|
-
|
|
16571
|
-
this.agent.skillPrompt = snapshot.prompt;
|
|
17677
|
+
this.agent.resetSentSkills();
|
|
16572
17678
|
this._registerSkillCommands(entries);
|
|
16573
17679
|
this.buildAutocompleteProvider();
|
|
16574
|
-
|
|
16575
|
-
const summary = this.skillGuard.getThreatSummary(results);
|
|
16576
|
-
if (summary.danger > 0 || summary.warning > 0) {
|
|
16577
|
-
log.warn("Skill 威胁扫描完成", summary);
|
|
16578
|
-
for (const result of results) if (!result.passed) for (const v of result.violations) eventBus.emit("security:violation", {
|
|
16579
|
-
toolId: "Skill",
|
|
16580
|
-
type: "skill-threat",
|
|
16581
|
-
message: `[${result.skillName}] ${v.reason}`,
|
|
16582
|
-
params: {
|
|
16583
|
-
skillPath: result.skillPath,
|
|
16584
|
-
ruleId: v.ruleId
|
|
16585
|
-
}
|
|
16586
|
-
});
|
|
16587
|
-
}
|
|
17680
|
+
this.scanSkillSecurity(entries);
|
|
16588
17681
|
log.info("Skill 系统初始化完成", {
|
|
16589
|
-
count:
|
|
16590
|
-
names:
|
|
17682
|
+
count: entries.length,
|
|
17683
|
+
names: entries.map((e) => e.skill.name)
|
|
16591
17684
|
});
|
|
17685
|
+
this.startSkillWatcher();
|
|
16592
17686
|
}).catch((err) => {
|
|
16593
17687
|
log.error("Skill 加载失败", { error: err instanceof Error ? err.message : String(err) });
|
|
16594
17688
|
});
|
|
16595
17689
|
}
|
|
16596
17690
|
/**
|
|
17691
|
+
* 启动技能文件变更监视
|
|
17692
|
+
*
|
|
17693
|
+
* 监视项目级(.agents/skills/、.zapmyco/skills/)和用户级
|
|
17694
|
+
*(~/.zapmyco/skills/)技能目录,文件变更时自动重新加载。
|
|
17695
|
+
*/
|
|
17696
|
+
startSkillWatcher() {
|
|
17697
|
+
const watchDirs = [];
|
|
17698
|
+
for (const dir of [
|
|
17699
|
+
join(process.cwd(), ".agents", "skills"),
|
|
17700
|
+
join(process.cwd(), ".zapmyco", "skills"),
|
|
17701
|
+
join(homedir(), ".zapmyco", "skills")
|
|
17702
|
+
]) try {
|
|
17703
|
+
if (statSync(dir).isDirectory()) watchDirs.push(dir);
|
|
17704
|
+
} catch {}
|
|
17705
|
+
if (watchDirs.length === 0) return;
|
|
17706
|
+
this.skillWatcher.start({
|
|
17707
|
+
watchDirs,
|
|
17708
|
+
onChanged: () => this.reloadSkills()
|
|
17709
|
+
});
|
|
17710
|
+
}
|
|
17711
|
+
/**
|
|
17712
|
+
* 重新加载所有技能(文件变更时调用)
|
|
17713
|
+
*
|
|
17714
|
+
* 幂等操作:重新扫描磁盘、更新全局状态、刷新命令注册和自动补全。
|
|
17715
|
+
*/
|
|
17716
|
+
reloadSkills() {
|
|
17717
|
+
const skillConfig = this.config.skill;
|
|
17718
|
+
if (!skillConfig?.enabled) return;
|
|
17719
|
+
log.info("技能文件变更,重新加载技能...");
|
|
17720
|
+
loadSkills({
|
|
17721
|
+
enabled: true,
|
|
17722
|
+
extraDirs: skillConfig.loadDirs,
|
|
17723
|
+
maxSkillsInPrompt: skillConfig.maxSkillsInPrompt,
|
|
17724
|
+
maxSkillFileBytes: skillConfig.maxSkillFileBytes
|
|
17725
|
+
}, process.cwd()).then((entries) => {
|
|
17726
|
+
setSkillEntries(entries);
|
|
17727
|
+
this.agent.skillEntries = entries;
|
|
17728
|
+
this.agent.resetSentSkills();
|
|
17729
|
+
this.registry.unregisterBySource("skill");
|
|
17730
|
+
this._registerSkillCommands(entries);
|
|
17731
|
+
this.buildAutocompleteProvider();
|
|
17732
|
+
this.scanSkillSecurity(entries);
|
|
17733
|
+
log.info("技能重新加载完成", {
|
|
17734
|
+
count: entries.length,
|
|
17735
|
+
names: entries.map((e) => e.skill.name)
|
|
17736
|
+
});
|
|
17737
|
+
}).catch((err) => {
|
|
17738
|
+
log.error("技能重新加载失败", { error: err instanceof Error ? err.message : String(err) });
|
|
17739
|
+
});
|
|
17740
|
+
}
|
|
17741
|
+
/**
|
|
17742
|
+
* 扫描 Skill 安全威胁
|
|
17743
|
+
*/
|
|
17744
|
+
scanSkillSecurity(entries) {
|
|
17745
|
+
const results = this.skillGuard.scanAll(entries);
|
|
17746
|
+
const summary = this.skillGuard.getThreatSummary(results);
|
|
17747
|
+
if (summary.danger > 0 || summary.warning > 0) {
|
|
17748
|
+
log.warn("Skill 威胁扫描完成", summary);
|
|
17749
|
+
for (const result of results) if (!result.passed) for (const v of result.violations) eventBus.emit("security:violation", {
|
|
17750
|
+
toolId: "Skill",
|
|
17751
|
+
type: "skill-threat",
|
|
17752
|
+
message: `[${result.skillName}] ${v.reason}`,
|
|
17753
|
+
params: {
|
|
17754
|
+
skillPath: result.skillPath,
|
|
17755
|
+
ruleId: v.ruleId
|
|
17756
|
+
}
|
|
17757
|
+
});
|
|
17758
|
+
}
|
|
17759
|
+
}
|
|
17760
|
+
/**
|
|
16597
17761
|
* 为 user-invocable 技能注册斜杠命令
|
|
16598
17762
|
*
|
|
16599
17763
|
* 将每个用户可调用的技能注册为 /skill-name 格式的 CLI 命令。
|
|
@@ -16611,11 +17775,12 @@ var ReplSession = class {
|
|
|
16611
17775
|
description: spec.description,
|
|
16612
17776
|
aliases: [],
|
|
16613
17777
|
usage: spec.name,
|
|
17778
|
+
source: "skill",
|
|
16614
17779
|
handler: async (args) => {
|
|
16615
17780
|
const argsStr = args.join(" ");
|
|
16616
17781
|
const entry = getSkillEntries().find((e) => sanitizeSkillCommandName(e.skill.name) === spec.name);
|
|
16617
17782
|
if (entry) {
|
|
16618
|
-
const skillContent = formatSkillContent(entry.skill, argsStr);
|
|
17783
|
+
const skillContent = await formatSkillContent(entry.skill, argsStr);
|
|
16619
17784
|
await this.executeGoal(skillContent, `/${spec.name}`);
|
|
16620
17785
|
} else {
|
|
16621
17786
|
const goalInput = argsStr ? `请使用 /${spec.name} 技能,参数: ${argsStr}` : `请使用 /${spec.name} 技能`;
|
|
@@ -16660,14 +17825,14 @@ var ReplSession = class {
|
|
|
16660
17825
|
this.editor.onCtrlD = () => {
|
|
16661
17826
|
this.shutdown("收到 EOF (Ctrl+D)");
|
|
16662
17827
|
};
|
|
16663
|
-
this.editor.onToggleAgentBar = () => {
|
|
16664
|
-
this.agentStatusBar.toggle();
|
|
16665
|
-
this.tui.requestRender();
|
|
16666
|
-
};
|
|
16667
17828
|
this.editor.onOpenEditor = () => this.openInEditor();
|
|
16668
|
-
this.editor.
|
|
16669
|
-
this.
|
|
16670
|
-
|
|
17829
|
+
this.editor.onRunInBackground = () => {
|
|
17830
|
+
if (this.currentTaskId) {
|
|
17831
|
+
const bgTaskId = this.currentTaskId;
|
|
17832
|
+
this.cancelCurrentTask();
|
|
17833
|
+
this.outputArea.append([chalk.gray(` (任务 ${bgTaskId} 已转入后台运行)`), ""]);
|
|
17834
|
+
this.tui.requestRender();
|
|
17835
|
+
}
|
|
16671
17836
|
};
|
|
16672
17837
|
}
|
|
16673
17838
|
/** 设置信号处理 */
|
|
@@ -16687,10 +17852,12 @@ var ReplSession = class {
|
|
|
16687
17852
|
instanceManager.on("instance:registered", refreshStatusBar);
|
|
16688
17853
|
instanceManager.on("instance:transitioned", refreshStatusBar);
|
|
16689
17854
|
instanceManager.on("instance:activity", refreshStatusBar);
|
|
17855
|
+
instanceManager.on("instance:toolcall", refreshStatusBar);
|
|
16690
17856
|
eventBus.on("system:shutdown", () => {
|
|
16691
17857
|
instanceManager.off("instance:registered", refreshStatusBar);
|
|
16692
17858
|
instanceManager.off("instance:transitioned", refreshStatusBar);
|
|
16693
17859
|
instanceManager.off("instance:activity", refreshStatusBar);
|
|
17860
|
+
instanceManager.off("instance:toolcall", refreshStatusBar);
|
|
16694
17861
|
});
|
|
16695
17862
|
}
|
|
16696
17863
|
/** 取消当前正在执行的任务 */
|
|
@@ -16856,7 +18023,7 @@ var ReplSession = class {
|
|
|
16856
18023
|
/**
|
|
16857
18024
|
* 从消息 content 中提取文本内容
|
|
16858
18025
|
*
|
|
16859
|
-
*
|
|
18026
|
+
* 消息 content 可能是 string(UserMessage)或 content blocks 数组(AssistantMessage),
|
|
16860
18027
|
* 此函数统一处理两种格式,提取所有文本块和 thinking 块并拼接。
|
|
16861
18028
|
*
|
|
16862
18029
|
* thinking 内容会被包裹在 <thinking>...</thinking> 标签中以便区分。
|
|
@@ -16911,10 +18078,10 @@ var ConversationLogger = class {
|
|
|
16911
18078
|
this.turnCount++;
|
|
16912
18079
|
try {
|
|
16913
18080
|
this.rotateIfNeeded();
|
|
16914
|
-
const line = JSON.stringify({
|
|
18081
|
+
const line = `${JSON.stringify({
|
|
16915
18082
|
...turn,
|
|
16916
18083
|
turn: this.turnCount
|
|
16917
|
-
})
|
|
18084
|
+
})}\n`;
|
|
16918
18085
|
appendFileSync(this.filePath, line, "utf-8");
|
|
16919
18086
|
} catch (err) {
|
|
16920
18087
|
logger.warn("对话日志写入失败", { error: err instanceof Error ? err.message : String(err) });
|