zapmyco 0.14.0 → 0.15.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 +561 -120
- package/dist/cli/index.mjs.map +1 -1
- package/dist/index.d.mts +32 -6
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/{loader-BD5Odmr5.mjs → loader-DEuRwvSw.mjs} +408 -34
- package/dist/loader-DEuRwvSw.mjs.map +1 -0
- package/package.json +2 -1
- package/dist/loader-BD5Odmr5.mjs.map +0 -1
package/dist/cli/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
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-DEuRwvSw.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
5
|
import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
|
|
@@ -18,12 +18,13 @@ import i18next from "i18next";
|
|
|
18
18
|
import TurndownService from "turndown";
|
|
19
19
|
import { lookup } from "node:dns/promises";
|
|
20
20
|
import { promisify } from "node:util";
|
|
21
|
+
import chokidar from "chokidar";
|
|
21
22
|
import { marked } from "marked";
|
|
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$21 = 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$21.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$21.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$21.error(`命令 /${cmd.name} 执行出错`, {}, error);
|
|
83
107
|
console.log(`\n 命令执行出错: ${message}\n`);
|
|
84
108
|
}
|
|
85
109
|
}
|
|
@@ -456,7 +480,7 @@ const noColor = {
|
|
|
456
480
|
*
|
|
457
481
|
* @module core/agent-team
|
|
458
482
|
*/
|
|
459
|
-
const log$
|
|
483
|
+
const log$20 = logger.child("agent-instance-manager");
|
|
460
484
|
/**
|
|
461
485
|
* Agent 实例状态转换表
|
|
462
486
|
*
|
|
@@ -521,7 +545,7 @@ var AgentInstanceManager = class extends EventEmitter {
|
|
|
521
545
|
const parent = this.instances.get(parentInstanceId);
|
|
522
546
|
if (parent) parent.childInstanceIds.push(instance.instanceId);
|
|
523
547
|
}
|
|
524
|
-
log$
|
|
548
|
+
log$20.debug("注册 Agent 实例", {
|
|
525
549
|
instanceId: instance.instanceId,
|
|
526
550
|
typeId: definition.typeId,
|
|
527
551
|
depth,
|
|
@@ -544,12 +568,12 @@ var AgentInstanceManager = class extends EventEmitter {
|
|
|
544
568
|
transition(instanceId, newStatus) {
|
|
545
569
|
const instance = this.instances.get(instanceId);
|
|
546
570
|
if (!instance) {
|
|
547
|
-
log$
|
|
571
|
+
log$20.warn("状态转换失败:实例不存在", { instanceId });
|
|
548
572
|
return false;
|
|
549
573
|
}
|
|
550
574
|
const allowed = VALID_TRANSITIONS[instance.status];
|
|
551
575
|
if (!allowed.includes(newStatus)) {
|
|
552
|
-
log$
|
|
576
|
+
log$20.warn("状态转换拒绝", {
|
|
553
577
|
instanceId,
|
|
554
578
|
from: instance.status,
|
|
555
579
|
to: newStatus,
|
|
@@ -559,7 +583,7 @@ var AgentInstanceManager = class extends EventEmitter {
|
|
|
559
583
|
}
|
|
560
584
|
const oldStatus = instance.status;
|
|
561
585
|
instance.status = newStatus;
|
|
562
|
-
log$
|
|
586
|
+
log$20.debug("Agent 实例状态转换", {
|
|
563
587
|
instanceId,
|
|
564
588
|
typeId: instance.typeId,
|
|
565
589
|
from: oldStatus,
|
|
@@ -690,7 +714,7 @@ var AgentInstanceManager = class extends EventEmitter {
|
|
|
690
714
|
instance.agent.removeAllListeners();
|
|
691
715
|
instance.agent.systemPromptOverride = null;
|
|
692
716
|
this.instances.delete(instanceId);
|
|
693
|
-
log$
|
|
717
|
+
log$20.debug("Agent 实例已清理", { instanceId });
|
|
694
718
|
}
|
|
695
719
|
/**
|
|
696
720
|
* 清理所有终态的实例
|
|
@@ -1141,7 +1165,7 @@ const BUILTIN_AGENT_TYPES = [
|
|
|
1141
1165
|
*
|
|
1142
1166
|
* @module core/agent-team
|
|
1143
1167
|
*/
|
|
1144
|
-
const log$
|
|
1168
|
+
const log$19 = logger.child("agent-type-registry");
|
|
1145
1169
|
/**
|
|
1146
1170
|
* Agent 类型注册中心
|
|
1147
1171
|
*
|
|
@@ -1165,14 +1189,14 @@ var AgentTypeRegistry = class {
|
|
|
1165
1189
|
*/
|
|
1166
1190
|
register(definition) {
|
|
1167
1191
|
const existing = this.types.get(definition.typeId);
|
|
1168
|
-
if (existing) log$
|
|
1192
|
+
if (existing) log$19.info("覆盖已注册的 Agent 类型", {
|
|
1169
1193
|
typeId: definition.typeId,
|
|
1170
1194
|
oldSource: existing.source,
|
|
1171
1195
|
newSource: definition.source
|
|
1172
1196
|
});
|
|
1173
1197
|
this.types.set(definition.typeId, definition);
|
|
1174
1198
|
this.refreshCache();
|
|
1175
|
-
log$
|
|
1199
|
+
log$19.debug("注册 Agent 类型", {
|
|
1176
1200
|
typeId: definition.typeId,
|
|
1177
1201
|
source: definition.source
|
|
1178
1202
|
});
|
|
@@ -1185,7 +1209,7 @@ var AgentTypeRegistry = class {
|
|
|
1185
1209
|
registerAll(definitions) {
|
|
1186
1210
|
for (const def of definitions) this.types.set(def.typeId, def);
|
|
1187
1211
|
this.refreshCache();
|
|
1188
|
-
log$
|
|
1212
|
+
log$19.info("批量注册 Agent 类型", { count: definitions.length });
|
|
1189
1213
|
}
|
|
1190
1214
|
/**
|
|
1191
1215
|
* 注销 Agent 类型
|
|
@@ -1197,7 +1221,7 @@ var AgentTypeRegistry = class {
|
|
|
1197
1221
|
const result = this.types.delete(typeId);
|
|
1198
1222
|
if (result) {
|
|
1199
1223
|
this.refreshCache();
|
|
1200
|
-
log$
|
|
1224
|
+
log$19.debug("注销 Agent 类型", { typeId });
|
|
1201
1225
|
}
|
|
1202
1226
|
return result;
|
|
1203
1227
|
}
|
|
@@ -1284,7 +1308,7 @@ var AgentTypeRegistry = class {
|
|
|
1284
1308
|
loadBuiltinTypes() {
|
|
1285
1309
|
for (const def of BUILTIN_AGENT_TYPES) this.types.set(def.typeId, def);
|
|
1286
1310
|
this.refreshCache();
|
|
1287
|
-
log$
|
|
1311
|
+
log$19.info("加载内置 Agent 类型", { count: BUILTIN_AGENT_TYPES.length });
|
|
1288
1312
|
}
|
|
1289
1313
|
/**
|
|
1290
1314
|
* 刷新可见类型缓存
|
|
@@ -1445,11 +1469,11 @@ function formatTokens(tokens) {
|
|
|
1445
1469
|
function createClearCommand() {
|
|
1446
1470
|
return {
|
|
1447
1471
|
name: "clear",
|
|
1448
|
-
aliases: [],
|
|
1449
|
-
description: "
|
|
1472
|
+
aliases: ["reset", "new"],
|
|
1473
|
+
description: "清空会话上下文,重置 Agent 状态",
|
|
1450
1474
|
usage: "/clear",
|
|
1451
1475
|
handler(_args, session) {
|
|
1452
|
-
session.
|
|
1476
|
+
session.clearAgentContext();
|
|
1453
1477
|
session.getInputParser().reset();
|
|
1454
1478
|
}
|
|
1455
1479
|
};
|
|
@@ -3120,6 +3144,16 @@ var AgentStatusBar = class extends Container {
|
|
|
3120
3144
|
#loadingTimer;
|
|
3121
3145
|
/** 上次活跃实例快照(用于检测变化) */
|
|
3122
3146
|
#lastActiveCount = 0;
|
|
3147
|
+
/** 当前模型名称 */
|
|
3148
|
+
#modelName = null;
|
|
3149
|
+
/** 累积非缓存 input tokens(usage.input = totalInput - cacheRead) */
|
|
3150
|
+
#inputTokens = 0;
|
|
3151
|
+
/** 累积 cache read tokens(缓存命中) */
|
|
3152
|
+
#cacheReadTokens = 0;
|
|
3153
|
+
/** 累积 output tokens */
|
|
3154
|
+
#outputTokens = 0;
|
|
3155
|
+
/** 任务已耗时(ms) */
|
|
3156
|
+
#durationMs = 0;
|
|
3123
3157
|
/**
|
|
3124
3158
|
* 切换展开/折叠状态
|
|
3125
3159
|
*/
|
|
@@ -3131,6 +3165,42 @@ var AgentStatusBar = class extends Container {
|
|
|
3131
3165
|
get isExpanded() {
|
|
3132
3166
|
return this.#expanded;
|
|
3133
3167
|
}
|
|
3168
|
+
/**
|
|
3169
|
+
* 设置当前模型名称
|
|
3170
|
+
*/
|
|
3171
|
+
setModelName(name) {
|
|
3172
|
+
this.#modelName = name;
|
|
3173
|
+
this.invalidate();
|
|
3174
|
+
}
|
|
3175
|
+
/**
|
|
3176
|
+
* 更新 Token 统计数据
|
|
3177
|
+
*
|
|
3178
|
+
* @param inputTokens - 非缓存 input tokens(usage.input)
|
|
3179
|
+
* @param cacheRead - 缓存读取 tokens(usage.cacheRead)
|
|
3180
|
+
* @param outputTokens - 输出 tokens
|
|
3181
|
+
* @param durationMs - 任务已耗时(毫秒)
|
|
3182
|
+
*/
|
|
3183
|
+
updateTokenStats(inputTokens, cacheRead, outputTokens, durationMs) {
|
|
3184
|
+
this.#inputTokens = inputTokens;
|
|
3185
|
+
this.#cacheReadTokens = cacheRead;
|
|
3186
|
+
this.#outputTokens = outputTokens;
|
|
3187
|
+
if (durationMs !== void 0) this.#durationMs = durationMs;
|
|
3188
|
+
this.invalidate();
|
|
3189
|
+
}
|
|
3190
|
+
/**
|
|
3191
|
+
* 清除 Token 数据(执行结束后调用)
|
|
3192
|
+
*/
|
|
3193
|
+
clearTokenStats() {
|
|
3194
|
+
this.#modelName = null;
|
|
3195
|
+
this.#inputTokens = 0;
|
|
3196
|
+
this.#cacheReadTokens = 0;
|
|
3197
|
+
this.#outputTokens = 0;
|
|
3198
|
+
this.invalidate();
|
|
3199
|
+
}
|
|
3200
|
+
/** 是否有 Token 信息要显示 */
|
|
3201
|
+
get hasTokenInfo() {
|
|
3202
|
+
return this.#modelName !== null;
|
|
3203
|
+
}
|
|
3134
3204
|
invalidate() {
|
|
3135
3205
|
super.invalidate();
|
|
3136
3206
|
}
|
|
@@ -3139,6 +3209,7 @@ var AgentStatusBar = class extends Container {
|
|
|
3139
3209
|
if (activeInstances.length === 0) {
|
|
3140
3210
|
this.#stopLoading();
|
|
3141
3211
|
this.#lastActiveCount = 0;
|
|
3212
|
+
if (this.hasTokenInfo) return [truncateToWidth(this.#renderTokenInfoLine(), width)];
|
|
3142
3213
|
return [];
|
|
3143
3214
|
}
|
|
3144
3215
|
if (this.#lastActiveCount === 0) this.#startLoading();
|
|
@@ -3148,8 +3219,15 @@ var AgentStatusBar = class extends Container {
|
|
|
3148
3219
|
}, 0);
|
|
3149
3220
|
const totalDuration = this.#formatDuration(Math.max(...activeInstances.map((i) => Date.now() - i.createdAt)));
|
|
3150
3221
|
const frame = LOADING_FRAMES$2[this.#loadingFrame % LOADING_FRAMES$2.length] ?? "";
|
|
3151
|
-
if (!this.#expanded)
|
|
3152
|
-
|
|
3222
|
+
if (!this.#expanded) {
|
|
3223
|
+
const lines = [];
|
|
3224
|
+
lines.push(truncateToWidth(this.#renderCollapsed(frame, activeInstances.length, totalToolUses, totalDuration), width));
|
|
3225
|
+
if (this.hasTokenInfo) lines.push(truncateToWidth(this.#renderTokenInfoLine(), width));
|
|
3226
|
+
return lines;
|
|
3227
|
+
}
|
|
3228
|
+
const lines = this.#renderExpanded(frame, activeInstances, width);
|
|
3229
|
+
if (this.hasTokenInfo) lines.push(truncateToWidth(this.#renderTokenInfoLine(), width));
|
|
3230
|
+
return lines;
|
|
3153
3231
|
}
|
|
3154
3232
|
/** 渲染折叠模式单行 */
|
|
3155
3233
|
#renderCollapsed(frame, count, toolUses, duration) {
|
|
@@ -3161,7 +3239,7 @@ var AgentStatusBar = class extends Container {
|
|
|
3161
3239
|
const count = instances.length;
|
|
3162
3240
|
const header = chalk.cyan(` ${frame} Running ${count} agent${count > 1 ? "s" : ""}...`);
|
|
3163
3241
|
const hint = chalk.dim("(ctrl+o to collapse)");
|
|
3164
|
-
lines.push(`${header} ${hint}
|
|
3242
|
+
lines.push(truncateToWidth(`${header} ${hint}`, width));
|
|
3165
3243
|
for (let i = 0; i < instances.length; i++) {
|
|
3166
3244
|
const inst = instances[i];
|
|
3167
3245
|
const isLast = i === instances.length - 1;
|
|
@@ -3175,11 +3253,11 @@ var AgentStatusBar = class extends Container {
|
|
|
3175
3253
|
const toolUsesStr = act ? chalk.gray(`\u00B7 ${act.toolUses} tool uses`) : "";
|
|
3176
3254
|
const durationStr = chalk.gray(`\u00B7 ${this.#formatDuration(Date.now() - inst.createdAt)}`);
|
|
3177
3255
|
const line1 = ` ${chalk.dim(connector)}${statusColor(icon)}${chalk.reset} ${typeLabel} ${chalk.dim(taskPreview)} ${toolUsesStr} ${durationStr}`;
|
|
3178
|
-
lines.push(line1
|
|
3256
|
+
lines.push(truncateToWidth(line1, width));
|
|
3179
3257
|
if (act) {
|
|
3180
3258
|
const toolDisplay = act.args ? `${act.toolName}: ${act.args.slice(0, 60)}` : act.toolName;
|
|
3181
3259
|
const line2 = ` ${chalk.dim(childPrefix + TREE_CONT)}${chalk.cyan(toolDisplay)}`;
|
|
3182
|
-
lines.push(line2
|
|
3260
|
+
lines.push(truncateToWidth(line2, width));
|
|
3183
3261
|
}
|
|
3184
3262
|
}
|
|
3185
3263
|
return lines;
|
|
@@ -3205,6 +3283,33 @@ var AgentStatusBar = class extends Container {
|
|
|
3205
3283
|
if (ms < 6e4) return `${(ms / 1e3).toFixed(1)}s`;
|
|
3206
3284
|
return `${Math.floor(ms / 6e4)}m${Math.floor(ms % 6e4 / 1e3)}s`;
|
|
3207
3285
|
}
|
|
3286
|
+
/** 格式化 Token 数字
|
|
3287
|
+
*
|
|
3288
|
+
* - < 1,000: 原始数字(456)
|
|
3289
|
+
* - ≥ 1,000: 千分位分隔(1,234)
|
|
3290
|
+
* - ≥ 10,000: K 单位(15.3K)
|
|
3291
|
+
* - ≥ 1,000,000: M 单位(1.2M)
|
|
3292
|
+
*/
|
|
3293
|
+
#formatTokenCount(n) {
|
|
3294
|
+
if (n >= 1e6) return `${(n / 1e6).toFixed(1)}M`;
|
|
3295
|
+
if (n >= 1e4) return `${(n / 1e3).toFixed(1)}K`;
|
|
3296
|
+
return n.toLocaleString();
|
|
3297
|
+
}
|
|
3298
|
+
/** 渲染 Token 信息行 */
|
|
3299
|
+
#renderTokenInfoLine() {
|
|
3300
|
+
const modelStr = chalk.cyan(this.#modelName ?? "");
|
|
3301
|
+
const separator = chalk.gray(" · ");
|
|
3302
|
+
const totalInput = this.#inputTokens + this.#cacheReadTokens;
|
|
3303
|
+
const missTokens = this.#inputTokens;
|
|
3304
|
+
const cacheRate = totalInput > 0 ? Math.round(this.#cacheReadTokens / totalInput * 100) : 0;
|
|
3305
|
+
const inStr = chalk.white(this.#formatTokenCount(totalInput));
|
|
3306
|
+
const hitStr = chalk.green(this.#formatTokenCount(this.#cacheReadTokens));
|
|
3307
|
+
const rateStr = cacheRate >= 80 ? chalk.green(`${cacheRate}%`) : chalk.yellow(`${cacheRate}%`);
|
|
3308
|
+
const missStr = chalk.yellow(this.#formatTokenCount(missTokens));
|
|
3309
|
+
const outStr = chalk.cyan(this.#formatTokenCount(this.#outputTokens));
|
|
3310
|
+
const durationStr = chalk.magenta(this.#formatDuration(this.#durationMs));
|
|
3311
|
+
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}`;
|
|
3312
|
+
}
|
|
3208
3313
|
};
|
|
3209
3314
|
|
|
3210
3315
|
//#endregion
|
|
@@ -3975,7 +4080,7 @@ const CRON_CONSTANTS = {
|
|
|
3975
4080
|
*
|
|
3976
4081
|
* @module cli/repl/cron/cron-scheduler
|
|
3977
4082
|
*/
|
|
3978
|
-
const log$
|
|
4083
|
+
const log$18 = logger.child("cron:scheduler");
|
|
3979
4084
|
var CronScheduler = class extends EventEmitter {
|
|
3980
4085
|
store;
|
|
3981
4086
|
jobs = [];
|
|
@@ -3996,7 +4101,7 @@ var CronScheduler = class extends EventEmitter {
|
|
|
3996
4101
|
if (this.running) return;
|
|
3997
4102
|
const loadedJobs = await this.store.load();
|
|
3998
4103
|
this.jobs = loadedJobs;
|
|
3999
|
-
log$
|
|
4104
|
+
log$18.info(`调度器启动,加载 ${loadedJobs.length} 个 durable 任务`);
|
|
4000
4105
|
await this.handleMissedJobs();
|
|
4001
4106
|
this.checkAutoExpiry();
|
|
4002
4107
|
this.running = true;
|
|
@@ -4012,7 +4117,7 @@ var CronScheduler = class extends EventEmitter {
|
|
|
4012
4117
|
clearInterval(this.timer);
|
|
4013
4118
|
this.timer = null;
|
|
4014
4119
|
}
|
|
4015
|
-
log$
|
|
4120
|
+
log$18.info("调度器已停止");
|
|
4016
4121
|
}
|
|
4017
4122
|
/** 添加任务 */
|
|
4018
4123
|
async addJob(job) {
|
|
@@ -4024,7 +4129,7 @@ var CronScheduler = class extends EventEmitter {
|
|
|
4024
4129
|
this.jobs.push(job);
|
|
4025
4130
|
await this.store.persist(this.jobs);
|
|
4026
4131
|
} else this.sessionJobs.push(job);
|
|
4027
|
-
log$
|
|
4132
|
+
log$18.info("任务已添加", {
|
|
4028
4133
|
id: job.id,
|
|
4029
4134
|
cron: job.cron,
|
|
4030
4135
|
durable: job.durable
|
|
@@ -4154,7 +4259,7 @@ var CronScheduler = class extends EventEmitter {
|
|
|
4154
4259
|
}, delay);
|
|
4155
4260
|
}
|
|
4156
4261
|
if (toDelete.length > 0) {
|
|
4157
|
-
log$
|
|
4262
|
+
log$18.info(`跳过 ${toDelete.length} 个错过的一次性任务(超出补发上限)`);
|
|
4158
4263
|
this.emit("missed-overflow", {
|
|
4159
4264
|
count: toDelete.length,
|
|
4160
4265
|
jobIds: toDelete.map((m) => m.id)
|
|
@@ -4172,7 +4277,7 @@ var CronScheduler = class extends EventEmitter {
|
|
|
4172
4277
|
job.lastFiredAt = now;
|
|
4173
4278
|
job.fireCount++;
|
|
4174
4279
|
this.removeJob(job.id);
|
|
4175
|
-
log$
|
|
4280
|
+
log$18.info("任务已过期并触发最后一次", { id: job.id });
|
|
4176
4281
|
}
|
|
4177
4282
|
}
|
|
4178
4283
|
}
|
|
@@ -4233,7 +4338,7 @@ function applyOneShotJitter(jobId, rawNext) {
|
|
|
4233
4338
|
*
|
|
4234
4339
|
* @module cli/repl/cron/cron-store
|
|
4235
4340
|
*/
|
|
4236
|
-
const log$
|
|
4341
|
+
const log$17 = logger.child("cron:store");
|
|
4237
4342
|
const STORE_FILE = join(join(homedir(), ".zapmyco", "cron"), "scheduled_tasks.json");
|
|
4238
4343
|
var CronStore = class {
|
|
4239
4344
|
filePath;
|
|
@@ -4257,13 +4362,13 @@ var CronStore = class {
|
|
|
4257
4362
|
const raw = await readFile(this.filePath, "utf-8");
|
|
4258
4363
|
const data = JSON.parse(raw);
|
|
4259
4364
|
if (!Array.isArray(data)) {
|
|
4260
|
-
log$
|
|
4365
|
+
log$17.warn("存储文件格式无效(非数组),将使用空列表");
|
|
4261
4366
|
return [];
|
|
4262
4367
|
}
|
|
4263
4368
|
return this.validateJobs(data);
|
|
4264
4369
|
} catch (err) {
|
|
4265
4370
|
if (err.code === "ENOENT") return [];
|
|
4266
|
-
log$
|
|
4371
|
+
log$17.warn("加载定时任务文件失败,将使用空列表", { error: err instanceof Error ? err.message : String(err) });
|
|
4267
4372
|
return [];
|
|
4268
4373
|
}
|
|
4269
4374
|
}
|
|
@@ -4310,7 +4415,7 @@ var CronStore = class {
|
|
|
4310
4415
|
if (typeof obj.maxFires === "number") job.maxFires = obj.maxFires;
|
|
4311
4416
|
valid.push(job);
|
|
4312
4417
|
}
|
|
4313
|
-
if (valid.length < raw.length) log$
|
|
4418
|
+
if (valid.length < raw.length) log$17.warn(`跳过 ${raw.length - valid.length} 个无效任务条目`);
|
|
4314
4419
|
return valid;
|
|
4315
4420
|
}
|
|
4316
4421
|
};
|
|
@@ -4328,7 +4433,7 @@ function getCronStore() {
|
|
|
4328
4433
|
* 基于内存的环形缓冲区,记录 REPL 会话中的用户输入和执行结果。
|
|
4329
4434
|
* 支持文件持久化到 ~/.zapmyco/history.json,跨会话恢复。
|
|
4330
4435
|
*/
|
|
4331
|
-
const log$
|
|
4436
|
+
const log$16 = logger.child("history:store");
|
|
4332
4437
|
/** 默认最大历史条数 */
|
|
4333
4438
|
const DEFAULT_MAX_SIZE = 100;
|
|
4334
4439
|
/** 历史文件存储路径 */
|
|
@@ -4387,13 +4492,13 @@ var HistoryStore = class {
|
|
|
4387
4492
|
if (Array.isArray(data.entries)) {
|
|
4388
4493
|
this.entries = data.entries.slice(-this.maxSize);
|
|
4389
4494
|
this.nextId = typeof data.nextId === "number" ? data.nextId : 1;
|
|
4390
|
-
log$
|
|
4495
|
+
log$16.debug("历史记录已加载", {
|
|
4391
4496
|
count: this.entries.length,
|
|
4392
4497
|
nextId: this.nextId
|
|
4393
4498
|
});
|
|
4394
4499
|
}
|
|
4395
4500
|
} catch {
|
|
4396
|
-
log$
|
|
4501
|
+
log$16.debug("无历史文件或加载失败,使用空历史");
|
|
4397
4502
|
}
|
|
4398
4503
|
}
|
|
4399
4504
|
/** 持久化历史记录到文件 */
|
|
@@ -4406,7 +4511,7 @@ var HistoryStore = class {
|
|
|
4406
4511
|
}, null, 2);
|
|
4407
4512
|
writeFileSync(this.filePath, data, "utf-8");
|
|
4408
4513
|
} catch (err) {
|
|
4409
|
-
log$
|
|
4514
|
+
log$16.warn("历史记录保存失败", { error: err instanceof Error ? err.message : String(err) });
|
|
4410
4515
|
}
|
|
4411
4516
|
}
|
|
4412
4517
|
};
|
|
@@ -5288,7 +5393,7 @@ var Renderer = class {
|
|
|
5288
5393
|
*
|
|
5289
5394
|
* @module core/agent-team
|
|
5290
5395
|
*/
|
|
5291
|
-
const log$
|
|
5396
|
+
const log$15 = logger.child("background-store");
|
|
5292
5397
|
/**
|
|
5293
5398
|
* 后台任务持久化存储
|
|
5294
5399
|
*
|
|
@@ -5318,14 +5423,14 @@ var BackgroundTaskStore = class {
|
|
|
5318
5423
|
const entries = JSON.parse(raw);
|
|
5319
5424
|
this.tasks.clear();
|
|
5320
5425
|
for (const entry of entries) this.tasks.set(entry.taskId, entry);
|
|
5321
|
-
log$
|
|
5426
|
+
log$15.debug("后台任务已加载", {
|
|
5322
5427
|
count: entries.length,
|
|
5323
5428
|
path: this.filePath
|
|
5324
5429
|
});
|
|
5325
5430
|
return entries;
|
|
5326
5431
|
}
|
|
5327
5432
|
} catch (err) {
|
|
5328
|
-
log$
|
|
5433
|
+
log$15.warn("后台任务加载失败", {
|
|
5329
5434
|
error: String(err),
|
|
5330
5435
|
path: this.filePath
|
|
5331
5436
|
});
|
|
@@ -5384,7 +5489,7 @@ var BackgroundTaskStore = class {
|
|
|
5384
5489
|
const entries = Array.from(this.tasks.values());
|
|
5385
5490
|
writeFileSync(this.filePath, JSON.stringify(entries, null, 2), "utf-8");
|
|
5386
5491
|
} catch (err) {
|
|
5387
|
-
log$
|
|
5492
|
+
log$15.error("后台任务持久化失败", {
|
|
5388
5493
|
error: String(err),
|
|
5389
5494
|
path: this.filePath
|
|
5390
5495
|
});
|
|
@@ -5410,7 +5515,7 @@ var BackgroundTaskStore = class {
|
|
|
5410
5515
|
}
|
|
5411
5516
|
if (cleaned > 0) {
|
|
5412
5517
|
this.persist();
|
|
5413
|
-
log$
|
|
5518
|
+
log$15.info("清理过期后台任务", { cleaned });
|
|
5414
5519
|
}
|
|
5415
5520
|
return cleaned;
|
|
5416
5521
|
}
|
|
@@ -5427,7 +5532,7 @@ var BackgroundTaskStore = class {
|
|
|
5427
5532
|
*
|
|
5428
5533
|
* @module core/agent-team
|
|
5429
5534
|
*/
|
|
5430
|
-
const log$
|
|
5535
|
+
const log$14 = logger.child("agent-message-bus");
|
|
5431
5536
|
/**
|
|
5432
5537
|
* Agent 消息总线(单例)
|
|
5433
5538
|
*
|
|
@@ -5461,12 +5566,12 @@ var AgentMessageBus = class {
|
|
|
5461
5566
|
};
|
|
5462
5567
|
const targetInstance = getAgentInstanceManager().get(toAgentId);
|
|
5463
5568
|
if (targetInstance) targetInstance.inbox.push(fullMessage);
|
|
5464
|
-
else log$
|
|
5569
|
+
else log$14.warn("目标 Agent 实例不存在,消息丢弃", {
|
|
5465
5570
|
toAgentId,
|
|
5466
5571
|
messageId: fullMessage.messageId
|
|
5467
5572
|
});
|
|
5468
5573
|
this.emitter.emit(`msg:${toAgentId}`, fullMessage);
|
|
5469
|
-
log$
|
|
5574
|
+
log$14.debug("消息已投递", {
|
|
5470
5575
|
from: fromAgentId,
|
|
5471
5576
|
to: toAgentId,
|
|
5472
5577
|
type: fullMessage.type,
|
|
@@ -5549,7 +5654,7 @@ function getAgentMessageBus() {
|
|
|
5549
5654
|
*
|
|
5550
5655
|
* @module core/agent-team
|
|
5551
5656
|
*/
|
|
5552
|
-
const log$
|
|
5657
|
+
const log$13 = logger.child("background-agent-manager");
|
|
5553
5658
|
/**
|
|
5554
5659
|
* 后台 Agent 管理器(单例)
|
|
5555
5660
|
*/
|
|
@@ -5606,7 +5711,7 @@ var BackgroundAgentManager = class {
|
|
|
5606
5711
|
parentAgentId: params.parentInstanceId
|
|
5607
5712
|
});
|
|
5608
5713
|
this.runAsync(taskId, params, abortController, timeoutMs).catch((err) => {
|
|
5609
|
-
log$
|
|
5714
|
+
log$13.error("后台 Agent 意外崩溃", {
|
|
5610
5715
|
taskId,
|
|
5611
5716
|
error: String(err)
|
|
5612
5717
|
});
|
|
@@ -5626,7 +5731,7 @@ var BackgroundAgentManager = class {
|
|
|
5626
5731
|
const orchestrator = this.orchestrator;
|
|
5627
5732
|
const messageBus = getAgentMessageBus();
|
|
5628
5733
|
const timeoutHandle = setTimeout(() => {
|
|
5629
|
-
log$
|
|
5734
|
+
log$13.warn("后台 Agent 超时,自动取消", {
|
|
5630
5735
|
taskId,
|
|
5631
5736
|
timeoutMs
|
|
5632
5737
|
});
|
|
@@ -5670,7 +5775,7 @@ var BackgroundAgentManager = class {
|
|
|
5670
5775
|
taskId,
|
|
5671
5776
|
requiresResponse: false
|
|
5672
5777
|
});
|
|
5673
|
-
log$
|
|
5778
|
+
log$13.info("后台 Agent 完成通知已发送", {
|
|
5674
5779
|
taskId,
|
|
5675
5780
|
instanceId: result.instanceId,
|
|
5676
5781
|
parentId: params.parentInstanceId,
|
|
@@ -5697,7 +5802,7 @@ var BackgroundAgentManager = class {
|
|
|
5697
5802
|
completedAt: Date.now(),
|
|
5698
5803
|
error
|
|
5699
5804
|
});
|
|
5700
|
-
log$
|
|
5805
|
+
log$13.warn("后台 Agent 失败", {
|
|
5701
5806
|
taskId,
|
|
5702
5807
|
error
|
|
5703
5808
|
});
|
|
@@ -5734,7 +5839,7 @@ var BackgroundAgentManager = class {
|
|
|
5734
5839
|
if (runtime.instanceId) try {
|
|
5735
5840
|
await getAgentInstanceManager().cancel(runtime.instanceId);
|
|
5736
5841
|
} catch {}
|
|
5737
|
-
log$
|
|
5842
|
+
log$13.info("后台 Agent 已取消", { taskId });
|
|
5738
5843
|
return true;
|
|
5739
5844
|
}
|
|
5740
5845
|
/**
|
|
@@ -5743,13 +5848,13 @@ var BackgroundAgentManager = class {
|
|
|
5743
5848
|
restore() {
|
|
5744
5849
|
this.store.load();
|
|
5745
5850
|
const stale = this.store.cleanStale();
|
|
5746
|
-
if (stale > 0) log$
|
|
5851
|
+
if (stale > 0) log$13.info("跨会话恢复:清理了过期后台任务", { count: stale });
|
|
5747
5852
|
const active = this.store.listActive();
|
|
5748
5853
|
for (const entry of active) this.store.updateStatus(entry.taskId, "failed", {
|
|
5749
5854
|
completedAt: Date.now(),
|
|
5750
5855
|
error: "会话终止导致任务丢失"
|
|
5751
5856
|
});
|
|
5752
|
-
if (active.length > 0) log$
|
|
5857
|
+
if (active.length > 0) log$13.info("跨会话恢复:标记活跃任务为 failed", { count: active.length });
|
|
5753
5858
|
}
|
|
5754
5859
|
};
|
|
5755
5860
|
/** 全局单例 */
|
|
@@ -5968,7 +6073,7 @@ function createDeferred() {
|
|
|
5968
6073
|
*
|
|
5969
6074
|
* @module core/question/question-manager
|
|
5970
6075
|
*/
|
|
5971
|
-
const log$
|
|
6076
|
+
const log$12 = logger.child("question-manager");
|
|
5972
6077
|
/** 问题超时时间(5 分钟) */
|
|
5973
6078
|
const QUESTION_TIMEOUT_MS = 300 * 1e3;
|
|
5974
6079
|
var QuestionManager = class {
|
|
@@ -6014,13 +6119,13 @@ var QuestionManager = class {
|
|
|
6014
6119
|
requestId,
|
|
6015
6120
|
questionCount: params.questions.length
|
|
6016
6121
|
});
|
|
6017
|
-
log$
|
|
6122
|
+
log$12.debug("提问已发出", {
|
|
6018
6123
|
requestId,
|
|
6019
6124
|
questionCount: params.questions.length
|
|
6020
6125
|
});
|
|
6021
6126
|
const timeout = setTimeout(() => {
|
|
6022
6127
|
if (!deferred.isSettled) {
|
|
6023
|
-
log$
|
|
6128
|
+
log$12.warn("提问超时,自动取消", { requestId });
|
|
6024
6129
|
deferred.reject(/* @__PURE__ */ new Error("提问超时,用户未在 5 分钟内回答"));
|
|
6025
6130
|
this.pending.delete(requestId);
|
|
6026
6131
|
eventBus.emit("question:timeout", { requestId });
|
|
@@ -6034,7 +6139,7 @@ var QuestionManager = class {
|
|
|
6034
6139
|
requestId,
|
|
6035
6140
|
answerCount: Object.keys(result.answers).length
|
|
6036
6141
|
});
|
|
6037
|
-
log$
|
|
6142
|
+
log$12.debug("提问已回答", { requestId });
|
|
6038
6143
|
return result;
|
|
6039
6144
|
} catch (err) {
|
|
6040
6145
|
clearTimeout(timeout);
|
|
@@ -6043,7 +6148,7 @@ var QuestionManager = class {
|
|
|
6043
6148
|
requestId,
|
|
6044
6149
|
reason: err instanceof Error ? err.message : String(err)
|
|
6045
6150
|
});
|
|
6046
|
-
log$
|
|
6151
|
+
log$12.debug("提问已取消", {
|
|
6047
6152
|
requestId,
|
|
6048
6153
|
error: err instanceof Error ? err.message : String(err)
|
|
6049
6154
|
});
|
|
@@ -6065,7 +6170,7 @@ var QuestionManager = class {
|
|
|
6065
6170
|
const count = this.pending.size;
|
|
6066
6171
|
for (const [, entry] of this.pending) if (!entry.deferred.isSettled) entry.deferred.reject(error);
|
|
6067
6172
|
this.pending.clear();
|
|
6068
|
-
if (count > 0) log$
|
|
6173
|
+
if (count > 0) log$12.debug("已清理所有待处理问题", { count });
|
|
6069
6174
|
}
|
|
6070
6175
|
};
|
|
6071
6176
|
let globalQuestionManager = null;
|
|
@@ -9713,6 +9818,137 @@ function parseArgs(args) {
|
|
|
9713
9818
|
if (current.length > 0) result.push(current);
|
|
9714
9819
|
return result;
|
|
9715
9820
|
}
|
|
9821
|
+
/** 代码块模式: ```! ... ``` */
|
|
9822
|
+
const SHELL_BLOCK_PATTERN = /```!\s*\n?([\s\S]*?)\n?```/g;
|
|
9823
|
+
/** 行内模式: !`command`(前面需有空白或行首) */
|
|
9824
|
+
const SHELL_INLINE_PATTERN = /(?<=^|\s)!`([^`]+)`/gm;
|
|
9825
|
+
/** Shell 命令默认超时(秒) */
|
|
9826
|
+
const SKILL_SHELL_TIMEOUT_SEC = 30;
|
|
9827
|
+
/** Shell 命令输出最大字符数 */
|
|
9828
|
+
const SKILL_SHELL_MAX_OUTPUT_CHARS = 1e4;
|
|
9829
|
+
/**
|
|
9830
|
+
* 执行 SKILL.md 中的 Shell 命令
|
|
9831
|
+
*
|
|
9832
|
+
* 支持两种语法:
|
|
9833
|
+
* 1. 代码块: ```! echo "hello" ```
|
|
9834
|
+
* 2. 行内: 前导 !`echo "hello"`
|
|
9835
|
+
*/
|
|
9836
|
+
async function executeShellCommandsInSkill(content) {
|
|
9837
|
+
if (!content.includes("```!") && !content.includes("!`")) return content;
|
|
9838
|
+
let result = content;
|
|
9839
|
+
result = await replaceBlockCommands(result);
|
|
9840
|
+
result = await replaceInlineCommands(result);
|
|
9841
|
+
return result;
|
|
9842
|
+
}
|
|
9843
|
+
/** 处理 ```! ... ``` 代码块 */
|
|
9844
|
+
async function replaceBlockCommands(content) {
|
|
9845
|
+
const matches = [];
|
|
9846
|
+
const pattern = new RegExp(SHELL_BLOCK_PATTERN.source, "g");
|
|
9847
|
+
for (let match = pattern.exec(content); match !== null; match = pattern.exec(content)) matches.push({
|
|
9848
|
+
full: match[0],
|
|
9849
|
+
command: (match[1] ?? "").trim()
|
|
9850
|
+
});
|
|
9851
|
+
if (matches.length === 0) return content;
|
|
9852
|
+
const results = await Promise.all(matches.map(async (m) => ({
|
|
9853
|
+
target: m.full,
|
|
9854
|
+
replacement: await executeSingleCommand(m.command, false)
|
|
9855
|
+
})));
|
|
9856
|
+
let result = content;
|
|
9857
|
+
for (const { target, replacement } of results) result = result.replace(target, replacement);
|
|
9858
|
+
return result;
|
|
9859
|
+
}
|
|
9860
|
+
/** 处理 !`command` 行内命令 */
|
|
9861
|
+
async function replaceInlineCommands(content) {
|
|
9862
|
+
const matches = [];
|
|
9863
|
+
const pattern = new RegExp(SHELL_INLINE_PATTERN.source, "gm");
|
|
9864
|
+
for (let match = pattern.exec(content); match !== null; match = pattern.exec(content)) matches.push({
|
|
9865
|
+
full: match[0],
|
|
9866
|
+
command: (match[1] ?? "").trim()
|
|
9867
|
+
});
|
|
9868
|
+
if (matches.length === 0) return content;
|
|
9869
|
+
let result = content;
|
|
9870
|
+
for (const m of matches) {
|
|
9871
|
+
const output = await executeSingleCommand(m.command, true);
|
|
9872
|
+
result = result.replace(m.full, output);
|
|
9873
|
+
}
|
|
9874
|
+
return result;
|
|
9875
|
+
}
|
|
9876
|
+
/** 执行单个 Shell 命令并返回替换文本 */
|
|
9877
|
+
async function executeSingleCommand(command, inline) {
|
|
9878
|
+
if (!command) return "";
|
|
9879
|
+
const security = checkCommandSecurity(command);
|
|
9880
|
+
if (security.blocked) return `[Shell 命令被阻断] 命令: ${command} 原因: ${security.reason ?? "未知"}`;
|
|
9881
|
+
if (security.requiresApproval) return `[Shell 命令需审批] 命令: ${command} 原因: ${security.reason ?? "需要用户确认"}`;
|
|
9882
|
+
const shell = process.env.SHELL || "/bin/bash";
|
|
9883
|
+
try {
|
|
9884
|
+
const { stdout, stderr, exitCode } = await spawnCommand(shell, command);
|
|
9885
|
+
return formatShellOutput(stdout, stderr, exitCode, inline);
|
|
9886
|
+
} catch (err) {
|
|
9887
|
+
return `[Shell 命令执行失败] ${err instanceof Error ? err.message : String(err)}`;
|
|
9888
|
+
}
|
|
9889
|
+
}
|
|
9890
|
+
/** spawn 执行命令并返回结构化结果 */
|
|
9891
|
+
function spawnCommand(shell, command) {
|
|
9892
|
+
return new Promise((resolve) => {
|
|
9893
|
+
const child = spawn(shell, ["-c", command], {
|
|
9894
|
+
env: sanitizeEnv(),
|
|
9895
|
+
stdio: [
|
|
9896
|
+
"pipe",
|
|
9897
|
+
"pipe",
|
|
9898
|
+
"pipe"
|
|
9899
|
+
]
|
|
9900
|
+
});
|
|
9901
|
+
const stdoutChunks = [];
|
|
9902
|
+
const stderrChunks = [];
|
|
9903
|
+
child.stdout?.on("data", (data) => {
|
|
9904
|
+
stdoutChunks.push(data.toString());
|
|
9905
|
+
});
|
|
9906
|
+
child.stderr?.on("data", (data) => {
|
|
9907
|
+
stderrChunks.push(data.toString());
|
|
9908
|
+
});
|
|
9909
|
+
let settled = false;
|
|
9910
|
+
const settle = (result) => {
|
|
9911
|
+
if (settled) return;
|
|
9912
|
+
settled = true;
|
|
9913
|
+
clearTimeout(timer);
|
|
9914
|
+
resolve(result);
|
|
9915
|
+
};
|
|
9916
|
+
child.on("exit", (code) => {
|
|
9917
|
+
settle({
|
|
9918
|
+
stdout: stdoutChunks.join(""),
|
|
9919
|
+
stderr: stderrChunks.join(""),
|
|
9920
|
+
exitCode: code
|
|
9921
|
+
});
|
|
9922
|
+
});
|
|
9923
|
+
child.on("error", () => {
|
|
9924
|
+
settle({
|
|
9925
|
+
stdout: "",
|
|
9926
|
+
stderr: "进程启动失败",
|
|
9927
|
+
exitCode: -1
|
|
9928
|
+
});
|
|
9929
|
+
});
|
|
9930
|
+
const timer = setTimeout(() => {
|
|
9931
|
+
try {
|
|
9932
|
+
child.kill("SIGTERM");
|
|
9933
|
+
} catch {}
|
|
9934
|
+
setTimeout(() => {
|
|
9935
|
+
try {
|
|
9936
|
+
child.kill("SIGKILL");
|
|
9937
|
+
} catch {}
|
|
9938
|
+
}, 2e3);
|
|
9939
|
+
}, SKILL_SHELL_TIMEOUT_SEC * 1e3);
|
|
9940
|
+
});
|
|
9941
|
+
}
|
|
9942
|
+
/** 格式化 shell 命令输出 */
|
|
9943
|
+
function formatShellOutput(stdout, stderr, exitCode, inline) {
|
|
9944
|
+
let output = stdout;
|
|
9945
|
+
if (stderr) output += (output ? "\n" : "") + (inline ? `[stderr: ${stderr.trim()}]` : `[stderr]\n${stderr.trim()}`);
|
|
9946
|
+
output = stripAnsi(output);
|
|
9947
|
+
output = redactSensitiveInfo(output);
|
|
9948
|
+
output = truncateOutput(output, SKILL_SHELL_MAX_OUTPUT_CHARS);
|
|
9949
|
+
if (exitCode !== null && exitCode !== 0) output += `\n(退出码: ${exitCode})`;
|
|
9950
|
+
return output || "(无输出)";
|
|
9951
|
+
}
|
|
9716
9952
|
/**
|
|
9717
9953
|
* 创建 Skill 工具
|
|
9718
9954
|
*
|
|
@@ -9781,7 +10017,7 @@ function createSkillTool(_config) {
|
|
|
9781
10017
|
const endIdx = content.indexOf("---", 3);
|
|
9782
10018
|
if (endIdx !== -1) bodyContent = content.slice(endIdx + 3).trim();
|
|
9783
10019
|
}
|
|
9784
|
-
const
|
|
10020
|
+
const withShell = await executeShellCommandsInSkill(substituteVariables(bodyContent, params.args, skill.baseDir, skill.name));
|
|
9785
10021
|
const hint = skill.frontmatter["argument-hint"] ? ` [${skill.frontmatter["argument-hint"]}]` : "";
|
|
9786
10022
|
const instructionParts = [
|
|
9787
10023
|
`# Skill: ${skill.name}${hint}`,
|
|
@@ -9790,12 +10026,12 @@ function createSkillTool(_config) {
|
|
|
9790
10026
|
"",
|
|
9791
10027
|
"---",
|
|
9792
10028
|
"",
|
|
9793
|
-
|
|
10029
|
+
withShell,
|
|
9794
10030
|
"",
|
|
9795
10031
|
"---",
|
|
9796
10032
|
`Base directory: ${skill.baseDir}`
|
|
9797
10033
|
];
|
|
9798
|
-
if (!
|
|
10034
|
+
if (!withShell.includes(params.args ?? "") && params.args) instructionParts.push("", `ARGUMENTS: ${params.args}`);
|
|
9799
10035
|
return {
|
|
9800
10036
|
content: [{
|
|
9801
10037
|
type: "text",
|
|
@@ -9833,8 +10069,8 @@ function getSkillCommandSpecs(entries) {
|
|
|
9833
10069
|
* @param args - 用户传递的参数(可选)
|
|
9834
10070
|
* @returns 格式化后的完整指令文本
|
|
9835
10071
|
*/
|
|
9836
|
-
function formatSkillContent(skill, args) {
|
|
9837
|
-
const
|
|
10072
|
+
async function formatSkillContent(skill, args) {
|
|
10073
|
+
const withShell = await executeShellCommandsInSkill(substituteVariables(skill.body, args, skill.baseDir, skill.name));
|
|
9838
10074
|
const hint = skill.frontmatter["argument-hint"] ? ` [${skill.frontmatter["argument-hint"]}]` : "";
|
|
9839
10075
|
const instructionParts = [
|
|
9840
10076
|
`# Skill: ${skill.name}${hint}`,
|
|
@@ -9843,12 +10079,12 @@ function formatSkillContent(skill, args) {
|
|
|
9843
10079
|
"",
|
|
9844
10080
|
"---",
|
|
9845
10081
|
"",
|
|
9846
|
-
|
|
10082
|
+
withShell,
|
|
9847
10083
|
"",
|
|
9848
10084
|
"---",
|
|
9849
10085
|
`Base directory: ${skill.baseDir}`
|
|
9850
10086
|
];
|
|
9851
|
-
if (!
|
|
10087
|
+
if (!withShell.includes(args ?? "") && args) instructionParts.push("", `ARGUMENTS: ${args}`);
|
|
9852
10088
|
return instructionParts.filter(Boolean).join("\n");
|
|
9853
10089
|
}
|
|
9854
10090
|
/**
|
|
@@ -11360,7 +11596,7 @@ var WorktreeStore = class {
|
|
|
11360
11596
|
* @module core/worktree
|
|
11361
11597
|
*/
|
|
11362
11598
|
const execFileAsync = promisify(execFile);
|
|
11363
|
-
const log$
|
|
11599
|
+
const log$11 = logger.child("worktree-manager");
|
|
11364
11600
|
let globalWorktreeManager = null;
|
|
11365
11601
|
/** 获取全局 WorktreeManager 实例 */
|
|
11366
11602
|
function getWorktreeManager() {
|
|
@@ -11396,7 +11632,7 @@ var WorktreeManager = class {
|
|
|
11396
11632
|
throw new WorktreeError("无法确定 git 仓库根目录,请确保在 git 仓库中运行", "NOT_GIT_REPO");
|
|
11397
11633
|
}
|
|
11398
11634
|
try {
|
|
11399
|
-
log$
|
|
11635
|
+
log$11.info("创建 worktree", {
|
|
11400
11636
|
branchName,
|
|
11401
11637
|
worktreePath,
|
|
11402
11638
|
gitRoot
|
|
@@ -11424,14 +11660,14 @@ var WorktreeManager = class {
|
|
|
11424
11660
|
});
|
|
11425
11661
|
} catch (err) {
|
|
11426
11662
|
const msg = err instanceof Error ? err.message : String(err);
|
|
11427
|
-
log$
|
|
11663
|
+
log$11.warn("创建分支失败,清理 worktree", {
|
|
11428
11664
|
branchName,
|
|
11429
11665
|
error: msg
|
|
11430
11666
|
});
|
|
11431
11667
|
try {
|
|
11432
11668
|
await this.removeByPath(worktreePath, branchName, true);
|
|
11433
11669
|
} catch (err) {
|
|
11434
|
-
log$
|
|
11670
|
+
log$11.warn("Worktree 创建失败后清理出错", {
|
|
11435
11671
|
worktreePath,
|
|
11436
11672
|
error: err instanceof Error ? err.message : String(err)
|
|
11437
11673
|
});
|
|
@@ -11456,7 +11692,7 @@ var WorktreeManager = class {
|
|
|
11456
11692
|
createdBy: info.createdBy,
|
|
11457
11693
|
status: "active"
|
|
11458
11694
|
});
|
|
11459
|
-
log$
|
|
11695
|
+
log$11.info("Worktree 创建成功", {
|
|
11460
11696
|
id: info.id,
|
|
11461
11697
|
path: worktreePath
|
|
11462
11698
|
});
|
|
@@ -11468,13 +11704,13 @@ var WorktreeManager = class {
|
|
|
11468
11704
|
async remove(id, discardChanges) {
|
|
11469
11705
|
const info = this.activeWorktrees.get(id);
|
|
11470
11706
|
if (!info) {
|
|
11471
|
-
log$
|
|
11707
|
+
log$11.warn("尝试删除不存在的 worktree", { id });
|
|
11472
11708
|
return;
|
|
11473
11709
|
}
|
|
11474
11710
|
await this.removeByPath(info.worktreePath, info.branchName, discardChanges);
|
|
11475
11711
|
this.activeWorktrees.delete(id);
|
|
11476
11712
|
this.store.delete(id);
|
|
11477
|
-
log$
|
|
11713
|
+
log$11.info("Worktree 已删除", { id });
|
|
11478
11714
|
}
|
|
11479
11715
|
/**
|
|
11480
11716
|
* 通过路径删除 worktree(内部方法)
|
|
@@ -11484,7 +11720,7 @@ var WorktreeManager = class {
|
|
|
11484
11720
|
try {
|
|
11485
11721
|
await execFileAsync("git", ["worktree", "prune"], { timeout: 1e4 });
|
|
11486
11722
|
} catch (err) {
|
|
11487
|
-
log$
|
|
11723
|
+
log$11.warn("Worktree prune 失败(路径不存在)", {
|
|
11488
11724
|
worktreePath,
|
|
11489
11725
|
error: err instanceof Error ? err.message : String(err)
|
|
11490
11726
|
});
|
|
@@ -11498,7 +11734,7 @@ var WorktreeManager = class {
|
|
|
11498
11734
|
await execFileAsync("git", args, { timeout: 15e3 });
|
|
11499
11735
|
} catch (err) {
|
|
11500
11736
|
const msg = err instanceof Error ? err.message : String(err);
|
|
11501
|
-
log$
|
|
11737
|
+
log$11.warn("git worktree remove 失败", {
|
|
11502
11738
|
worktreePath,
|
|
11503
11739
|
error: msg
|
|
11504
11740
|
});
|
|
@@ -11510,7 +11746,7 @@ var WorktreeManager = class {
|
|
|
11510
11746
|
branchName
|
|
11511
11747
|
], { timeout: 1e4 });
|
|
11512
11748
|
} catch (err) {
|
|
11513
|
-
log$
|
|
11749
|
+
log$11.warn("删除 worktree 分支失败", {
|
|
11514
11750
|
branchName,
|
|
11515
11751
|
error: err instanceof Error ? err.message : String(err)
|
|
11516
11752
|
});
|
|
@@ -11518,7 +11754,7 @@ var WorktreeManager = class {
|
|
|
11518
11754
|
try {
|
|
11519
11755
|
await execFileAsync("git", ["worktree", "prune"], { timeout: 1e4 });
|
|
11520
11756
|
} catch (err) {
|
|
11521
|
-
log$
|
|
11757
|
+
log$11.warn("Worktree prune 失败", { error: err instanceof Error ? err.message : String(err) });
|
|
11522
11758
|
}
|
|
11523
11759
|
}
|
|
11524
11760
|
/**
|
|
@@ -11540,7 +11776,7 @@ var WorktreeManager = class {
|
|
|
11540
11776
|
await this.remove(id, true);
|
|
11541
11777
|
return { cleaned: true };
|
|
11542
11778
|
}
|
|
11543
|
-
log$
|
|
11779
|
+
log$11.info("Worktree 有变更,保留", {
|
|
11544
11780
|
id,
|
|
11545
11781
|
path: info.worktreePath
|
|
11546
11782
|
});
|
|
@@ -11560,7 +11796,7 @@ var WorktreeManager = class {
|
|
|
11560
11796
|
});
|
|
11561
11797
|
return stdout.trim().length > 0;
|
|
11562
11798
|
} catch (err) {
|
|
11563
|
-
log$
|
|
11799
|
+
log$11.warn("检查 worktree 变更状态失败", {
|
|
11564
11800
|
worktreePath,
|
|
11565
11801
|
error: err instanceof Error ? err.message : String(err)
|
|
11566
11802
|
});
|
|
@@ -11580,7 +11816,7 @@ var WorktreeManager = class {
|
|
|
11580
11816
|
await this.removeByPath(record.worktreePath, record.branchName, true);
|
|
11581
11817
|
cleaned++;
|
|
11582
11818
|
} catch (err) {
|
|
11583
|
-
log$
|
|
11819
|
+
log$11.warn("过期 worktree 清理失败", {
|
|
11584
11820
|
id: record.id,
|
|
11585
11821
|
path: record.worktreePath,
|
|
11586
11822
|
error: err instanceof Error ? err.message : String(err)
|
|
@@ -11589,7 +11825,7 @@ var WorktreeManager = class {
|
|
|
11589
11825
|
this.activeWorktrees.delete(record.id);
|
|
11590
11826
|
this.store.delete(record.id);
|
|
11591
11827
|
}
|
|
11592
|
-
if (cleaned > 0) log$
|
|
11828
|
+
if (cleaned > 0) log$11.info("过期 worktree 清理完成", { cleaned });
|
|
11593
11829
|
return cleaned;
|
|
11594
11830
|
}
|
|
11595
11831
|
listActive() {
|
|
@@ -11640,7 +11876,7 @@ const AGENT_STANDARD_TOOLS = [
|
|
|
11640
11876
|
|
|
11641
11877
|
//#endregion
|
|
11642
11878
|
//#region src/core/agent-team/agent-factory.ts
|
|
11643
|
-
const log$
|
|
11879
|
+
const log$10 = logger.child("agent-factory");
|
|
11644
11880
|
/**
|
|
11645
11881
|
* 创建 Agent 实例
|
|
11646
11882
|
*
|
|
@@ -11658,6 +11894,7 @@ const log$9 = logger.child("agent-factory");
|
|
|
11658
11894
|
* @returns 完全初始化的 LlmBasedAgent
|
|
11659
11895
|
*/
|
|
11660
11896
|
function createAgentFromType(definition, instance, parentAgent, availableTools, config) {
|
|
11897
|
+
const thinkingLevel = resolveAgentThinkingLevel(definition.thinkingLevel, config.thinkingLevel, parentAgent);
|
|
11661
11898
|
const agent = createLlmBasedAgent({
|
|
11662
11899
|
agentId: instance.instanceId,
|
|
11663
11900
|
displayName: definition.displayName,
|
|
@@ -11665,14 +11902,15 @@ function createAgentFromType(definition, instance, parentAgent, availableTools,
|
|
|
11665
11902
|
runtimeConfig: {
|
|
11666
11903
|
enabled: true,
|
|
11667
11904
|
toolExecution: "sequential",
|
|
11668
|
-
maxTurns: definition.maxTurns
|
|
11905
|
+
maxTurns: definition.maxTurns,
|
|
11906
|
+
thinkingLevel
|
|
11669
11907
|
}
|
|
11670
11908
|
});
|
|
11671
11909
|
shareParentResources(agent, parentAgent, definition);
|
|
11672
11910
|
const tools = resolveTools(definition, availableTools, instance.depth, config);
|
|
11673
11911
|
agent.registerTools(tools);
|
|
11674
11912
|
agent.systemPromptOverride = buildSystemPrompt(definition, instance.task.description, config);
|
|
11675
|
-
log$
|
|
11913
|
+
log$10.debug("创建 Agent 实例", {
|
|
11676
11914
|
typeId: definition.typeId,
|
|
11677
11915
|
instanceId: instance.instanceId,
|
|
11678
11916
|
depth: instance.depth,
|
|
@@ -11736,6 +11974,17 @@ function buildSystemPrompt(definition, taskDescription, _config) {
|
|
|
11736
11974
|
return definition.getSystemPrompt(ctx);
|
|
11737
11975
|
}
|
|
11738
11976
|
/**
|
|
11977
|
+
* 解析 Agent 的 thinkingLevel
|
|
11978
|
+
*
|
|
11979
|
+
* 优先级:definition.thinkingLevel > config.thinkingLevel > 'off'
|
|
11980
|
+
* 'inherit' 值:从父 Agent 读取当前 thinkingLevel
|
|
11981
|
+
*/
|
|
11982
|
+
function resolveAgentThinkingLevel(definitionLevel, configLevel, parentAgent) {
|
|
11983
|
+
const level = definitionLevel ?? configLevel ?? "off";
|
|
11984
|
+
if (level === "inherit") return parentAgent.innerAgent.state.thinkingLevel ?? "medium";
|
|
11985
|
+
return level;
|
|
11986
|
+
}
|
|
11987
|
+
/**
|
|
11739
11988
|
* 共享父 Agent 的 Model 和 API Key 给子 Agent
|
|
11740
11989
|
*
|
|
11741
11990
|
* 如果 Agent 类型声明了偏好的模型(definition.model),则通过 AgentLlmFacade
|
|
@@ -11753,8 +12002,14 @@ function shareParentResources(subAgent, parentAgent, definition) {
|
|
|
11753
12002
|
subAgent.llmFacade = parentAgent.llmFacade;
|
|
11754
12003
|
if (definition?.model) {
|
|
11755
12004
|
const resolvedModel = resolveModelForDefinition(definition.model, parentAgent);
|
|
11756
|
-
if (resolvedModel)
|
|
11757
|
-
|
|
12005
|
+
if (resolvedModel) {
|
|
12006
|
+
subAgent.innerAgent.state.model = resolvedModel;
|
|
12007
|
+
if (resolvedModel.id !== parentInner.state.model?.id) log$10.info(`子 Agent [${definition?.typeId}] 模型切换: ${parentInner.state.model?.id ?? "N/A"} → ${resolvedModel.id}`, {
|
|
12008
|
+
typeId: definition?.typeId,
|
|
12009
|
+
parentModel: parentInner.state.model?.id,
|
|
12010
|
+
childModel: resolvedModel.id
|
|
12011
|
+
});
|
|
12012
|
+
} else subAgent.innerAgent.state.model = parentInner.state.model;
|
|
11758
12013
|
} else subAgent.innerAgent.state.model = parentInner.state.model;
|
|
11759
12014
|
subAgent.innerAgent.getApiKey = parentAgent.llmFacade.createGetApiKeyFn();
|
|
11760
12015
|
return;
|
|
@@ -11788,14 +12043,14 @@ function resolveModelForDefinition(model, parentAgent) {
|
|
|
11788
12043
|
}
|
|
11789
12044
|
} catch (error) {
|
|
11790
12045
|
const message = error instanceof Error ? error.message : String(error);
|
|
11791
|
-
log$
|
|
12046
|
+
log$10.warn(`解析 Agent 类型偏好模型 [${model}] 失败`, { error: message });
|
|
11792
12047
|
return;
|
|
11793
12048
|
}
|
|
11794
12049
|
}
|
|
11795
12050
|
|
|
11796
12051
|
//#endregion
|
|
11797
12052
|
//#region src/core/agent-team/agent-result-aggregator.ts
|
|
11798
|
-
const log$
|
|
12053
|
+
const log$9 = logger.child("agent-result-aggregator");
|
|
11799
12054
|
/** 零值 TokenUsage */
|
|
11800
12055
|
const ZERO_TOKEN = {
|
|
11801
12056
|
inputTokens: 0,
|
|
@@ -11825,7 +12080,7 @@ function aggregateResults(teamId, workerResults) {
|
|
|
11825
12080
|
estimatedCostUsd: sum.estimatedCostUsd + (r.tokenUsage?.estimatedCostUsd ?? 0)
|
|
11826
12081
|
}), { ...ZERO_TOKEN });
|
|
11827
12082
|
const summary = buildTeamSummary(workerResults);
|
|
11828
|
-
log$
|
|
12083
|
+
log$9.debug("Team 结果聚合完成", {
|
|
11829
12084
|
teamId,
|
|
11830
12085
|
total: workerResults.length,
|
|
11831
12086
|
succeeded,
|
|
@@ -11903,7 +12158,7 @@ function escapeMarkdownTable(text) {
|
|
|
11903
12158
|
|
|
11904
12159
|
//#endregion
|
|
11905
12160
|
//#region src/core/agent-team/agent-orchestrator.ts
|
|
11906
|
-
const log$
|
|
12161
|
+
const log$8 = logger.child("agent-orchestrator");
|
|
11907
12162
|
/**
|
|
11908
12163
|
* Agent 编排器
|
|
11909
12164
|
*
|
|
@@ -11936,7 +12191,7 @@ var AgentOrchestrator = class {
|
|
|
11936
12191
|
const startTime = Date.now();
|
|
11937
12192
|
const defaultType = getAgentTypeRegistry().getDefault();
|
|
11938
12193
|
if (!defaultType) throw new Error("无法获取默认 Agent 类型(general-purpose)");
|
|
11939
|
-
log$
|
|
12194
|
+
log$8.info("开始扁平并行执行", {
|
|
11940
12195
|
count: specs.length,
|
|
11941
12196
|
maxConcurrent: this.flatConfig.maxConcurrent,
|
|
11942
12197
|
hasContext: context != null
|
|
@@ -11944,7 +12199,7 @@ var AgentOrchestrator = class {
|
|
|
11944
12199
|
const allResults = [];
|
|
11945
12200
|
for (let i = 0; i < specs.length; i += this.flatConfig.maxConcurrent) {
|
|
11946
12201
|
const batch = specs.slice(i, i + this.flatConfig.maxConcurrent);
|
|
11947
|
-
log$
|
|
12202
|
+
log$8.debug("执行扁平批次", {
|
|
11948
12203
|
batchStart: i,
|
|
11949
12204
|
batchSize: batch.length,
|
|
11950
12205
|
totalSpecs: specs.length
|
|
@@ -11954,7 +12209,7 @@ var AgentOrchestrator = class {
|
|
|
11954
12209
|
}
|
|
11955
12210
|
const succeeded = allResults.filter((r) => r.status === "success").length;
|
|
11956
12211
|
const totalDuration = Date.now() - startTime;
|
|
11957
|
-
log$
|
|
12212
|
+
log$8.info("扁平并行执行完成", {
|
|
11958
12213
|
total: allResults.length,
|
|
11959
12214
|
succeeded,
|
|
11960
12215
|
failed: allResults.length - succeeded,
|
|
@@ -12029,7 +12284,7 @@ var AgentOrchestrator = class {
|
|
|
12029
12284
|
} catch (error) {
|
|
12030
12285
|
const duration = Date.now() - startTime;
|
|
12031
12286
|
const message = error instanceof Error ? error.message : String(error);
|
|
12032
|
-
log$
|
|
12287
|
+
log$8.warn("扁平子任务执行失败", {
|
|
12033
12288
|
specId: spec.id,
|
|
12034
12289
|
error: message,
|
|
12035
12290
|
duration
|
|
@@ -12086,7 +12341,7 @@ var AgentOrchestrator = class {
|
|
|
12086
12341
|
const parentInstanceId = options?.parentInstanceId ?? "";
|
|
12087
12342
|
const depth = (parentInstanceId && instanceManager.get(parentInstanceId) ? instanceManager.get(parentInstanceId)?.depth ?? 0 : 0) + 1;
|
|
12088
12343
|
if (depth > this.teamConfig.maxGlobalDepth) {
|
|
12089
|
-
log$
|
|
12344
|
+
log$8.warn("Worker 创建被拒绝:超过全局最大深度", {
|
|
12090
12345
|
typeId,
|
|
12091
12346
|
depth,
|
|
12092
12347
|
maxGlobalDepth: this.teamConfig.maxGlobalDepth
|
|
@@ -12208,7 +12463,7 @@ var AgentOrchestrator = class {
|
|
|
12208
12463
|
} catch (error) {
|
|
12209
12464
|
const duration = Date.now() - startTime;
|
|
12210
12465
|
const message = error instanceof Error ? error.message : String(error);
|
|
12211
|
-
log$
|
|
12466
|
+
log$8.warn("Worker 执行失败", {
|
|
12212
12467
|
typeId,
|
|
12213
12468
|
instanceId,
|
|
12214
12469
|
error: message,
|
|
@@ -12251,12 +12506,12 @@ var AgentOrchestrator = class {
|
|
|
12251
12506
|
const wm = getWorktreeManager();
|
|
12252
12507
|
if (wm) try {
|
|
12253
12508
|
const cleanResult = await wm.autoCleanIfNoChanges(worktreeInfo.id);
|
|
12254
|
-
if (!cleanResult.cleaned) log$
|
|
12509
|
+
if (!cleanResult.cleaned) log$8.info("Worktree 有变更,保留", {
|
|
12255
12510
|
id: worktreeInfo.id,
|
|
12256
12511
|
path: cleanResult.worktreePath
|
|
12257
12512
|
});
|
|
12258
12513
|
} catch (err) {
|
|
12259
|
-
log$
|
|
12514
|
+
log$8.warn("Worktree 清理失败", {
|
|
12260
12515
|
id: worktreeInfo.id,
|
|
12261
12516
|
error: err instanceof Error ? err.message : String(err)
|
|
12262
12517
|
});
|
|
@@ -12276,7 +12531,7 @@ var AgentOrchestrator = class {
|
|
|
12276
12531
|
*/
|
|
12277
12532
|
async spawnTeam(taskDescription, workerSpecs) {
|
|
12278
12533
|
const teamId = `team-${Date.now()}-${++this.teamCounter}`;
|
|
12279
|
-
log$
|
|
12534
|
+
log$8.info("创建 Team", {
|
|
12280
12535
|
teamId,
|
|
12281
12536
|
workerCount: workerSpecs.length,
|
|
12282
12537
|
taskDescription
|
|
@@ -12628,6 +12883,84 @@ function createReplBuiltinTools(webConfig, taskStore, skillConfig, parentAgent,
|
|
|
12628
12883
|
return tools;
|
|
12629
12884
|
}
|
|
12630
12885
|
|
|
12886
|
+
//#endregion
|
|
12887
|
+
//#region src/cli/repl/skill-watcher.ts
|
|
12888
|
+
/**
|
|
12889
|
+
* Skill 文件变更监视器
|
|
12890
|
+
*
|
|
12891
|
+
* 使用 chokidar 监视技能目录中的 SKILL.md 文件变更,
|
|
12892
|
+
* 通过防抖机制触发外部回调,实现动态技能发现。
|
|
12893
|
+
*
|
|
12894
|
+
* @module cli/repl/skill-watcher
|
|
12895
|
+
*/
|
|
12896
|
+
const log$7 = logger.child("repl:skill-watcher");
|
|
12897
|
+
/** 防抖延迟(毫秒) */
|
|
12898
|
+
const DEBOUNCE_MS = 500;
|
|
12899
|
+
/**
|
|
12900
|
+
* Skill 文件变更监视器
|
|
12901
|
+
*/
|
|
12902
|
+
var SkillWatcher = class {
|
|
12903
|
+
watcher = null;
|
|
12904
|
+
debounceTimer = null;
|
|
12905
|
+
_ready = false;
|
|
12906
|
+
/** 监视器是否已就绪 */
|
|
12907
|
+
get ready() {
|
|
12908
|
+
return this._ready;
|
|
12909
|
+
}
|
|
12910
|
+
/**
|
|
12911
|
+
* 启动监视
|
|
12912
|
+
*
|
|
12913
|
+
* 监视指定目录下 `**` 深层匹配 `SKILL.md` 文件变更。
|
|
12914
|
+
* 忽略隐藏目录,`ignoreInitial: true` 避免初始扫描触发 add 事件。
|
|
12915
|
+
*/
|
|
12916
|
+
start(options) {
|
|
12917
|
+
if (this.watcher) return;
|
|
12918
|
+
this.watcher = chokidar.watch(options.watchDirs.map((dir) => `${dir}/**/SKILL.md`), {
|
|
12919
|
+
ignored: /(^|[/\\])\./,
|
|
12920
|
+
persistent: true,
|
|
12921
|
+
ignoreInitial: true,
|
|
12922
|
+
depth: 2
|
|
12923
|
+
});
|
|
12924
|
+
this.watcher.on("ready", () => {
|
|
12925
|
+
this._ready = true;
|
|
12926
|
+
log$7.info("技能文件监视已就绪", { dirs: options.watchDirs });
|
|
12927
|
+
}).on("add", (path) => {
|
|
12928
|
+
log$7.debug("技能文件新增", { path });
|
|
12929
|
+
this.debounceNotify(options.onChanged);
|
|
12930
|
+
}).on("change", (path) => {
|
|
12931
|
+
log$7.debug("技能文件修改", { path });
|
|
12932
|
+
this.debounceNotify(options.onChanged);
|
|
12933
|
+
}).on("unlink", (path) => {
|
|
12934
|
+
log$7.debug("技能文件删除", { path });
|
|
12935
|
+
this.debounceNotify(options.onChanged);
|
|
12936
|
+
});
|
|
12937
|
+
}
|
|
12938
|
+
/**
|
|
12939
|
+
* 停止监视并清理资源
|
|
12940
|
+
*/
|
|
12941
|
+
stop() {
|
|
12942
|
+
if (this.debounceTimer) {
|
|
12943
|
+
clearTimeout(this.debounceTimer);
|
|
12944
|
+
this.debounceTimer = null;
|
|
12945
|
+
}
|
|
12946
|
+
if (this.watcher) {
|
|
12947
|
+
this.watcher.close();
|
|
12948
|
+
this.watcher = null;
|
|
12949
|
+
}
|
|
12950
|
+
this._ready = false;
|
|
12951
|
+
}
|
|
12952
|
+
/**
|
|
12953
|
+
* 防抖通知:批量操作(如 git pull)时合并为一次回调
|
|
12954
|
+
*/
|
|
12955
|
+
debounceNotify(callback) {
|
|
12956
|
+
if (this.debounceTimer) clearTimeout(this.debounceTimer);
|
|
12957
|
+
this.debounceTimer = setTimeout(() => {
|
|
12958
|
+
this.debounceTimer = null;
|
|
12959
|
+
callback();
|
|
12960
|
+
}, DEBOUNCE_MS);
|
|
12961
|
+
}
|
|
12962
|
+
};
|
|
12963
|
+
|
|
12631
12964
|
//#endregion
|
|
12632
12965
|
//#region src/cli/repl/theme.ts
|
|
12633
12966
|
/** 根据颜色开关获取 chalk 实例 */
|
|
@@ -15640,6 +15973,7 @@ var ReplSession = class {
|
|
|
15640
15973
|
auditLogger;
|
|
15641
15974
|
secretRedactor;
|
|
15642
15975
|
skillGuard;
|
|
15976
|
+
skillWatcher;
|
|
15643
15977
|
/** 交互式提问管理器 */
|
|
15644
15978
|
questionManager;
|
|
15645
15979
|
stats = {
|
|
@@ -15701,6 +16035,7 @@ var ReplSession = class {
|
|
|
15701
16035
|
log.warn("记忆快照冻结失败,将使用空快照", { error: err instanceof Error ? err.message : String(err) });
|
|
15702
16036
|
this.agent.memorySnapshot = "";
|
|
15703
16037
|
});
|
|
16038
|
+
this.skillWatcher = new SkillWatcher();
|
|
15704
16039
|
if (this.config.skill?.enabled !== false) this.initSkills();
|
|
15705
16040
|
this.initSecurity();
|
|
15706
16041
|
this.questionManager = getQuestionManager();
|
|
@@ -15762,6 +16097,7 @@ var ReplSession = class {
|
|
|
15762
16097
|
this.cronScheduler.stop();
|
|
15763
16098
|
this.cronScheduler = null;
|
|
15764
16099
|
}
|
|
16100
|
+
this.skillWatcher.stop();
|
|
15765
16101
|
if (this.auditLogger) this.auditLogger.destroy();
|
|
15766
16102
|
if (this.mcpManager) {
|
|
15767
16103
|
await this.mcpManager.shutdown();
|
|
@@ -15790,6 +16126,36 @@ var ReplSession = class {
|
|
|
15790
16126
|
getHistoryStore() {
|
|
15791
16127
|
return this.history;
|
|
15792
16128
|
}
|
|
16129
|
+
/**
|
|
16130
|
+
* 彻底清空 Agent 会话上下文。
|
|
16131
|
+
*
|
|
16132
|
+
* - 清空消息历史、Token 统计、缓存、授权
|
|
16133
|
+
* - 保留配置、记忆、持久化任务、定时任务
|
|
16134
|
+
* - 效果等价于"新开一个会话"
|
|
16135
|
+
*
|
|
16136
|
+
* 由 /clear 命令调用。
|
|
16137
|
+
*/
|
|
16138
|
+
clearAgentContext() {
|
|
16139
|
+
this.cancelCurrentTask();
|
|
16140
|
+
this.currentTaskId = null;
|
|
16141
|
+
this._state = "idle";
|
|
16142
|
+
this.agent.resetContext();
|
|
16143
|
+
this.conversationHistory = [];
|
|
16144
|
+
this.stats = {
|
|
16145
|
+
totalRequests: 0,
|
|
16146
|
+
successCount: 0,
|
|
16147
|
+
failureCount: 0,
|
|
16148
|
+
totalTokens: 0,
|
|
16149
|
+
totalCostUsd: 0,
|
|
16150
|
+
state: "idle"
|
|
16151
|
+
};
|
|
16152
|
+
this.permissionStore.clear();
|
|
16153
|
+
this.questionManager.rejectAll(/* @__PURE__ */ new Error("会话已重置"));
|
|
16154
|
+
this.editor.setExecuting(false);
|
|
16155
|
+
this.outputArea.clear();
|
|
16156
|
+
this.agentStatusBar.clearTokenStats();
|
|
16157
|
+
this.tui.requestRender();
|
|
16158
|
+
}
|
|
15793
16159
|
/** 获取会话统计 */
|
|
15794
16160
|
getStats() {
|
|
15795
16161
|
return { ...this.stats };
|
|
@@ -15893,6 +16259,8 @@ var ReplSession = class {
|
|
|
15893
16259
|
let spinnerActive = true;
|
|
15894
16260
|
let spinnerInterval;
|
|
15895
16261
|
let thinkingElapsedInterval;
|
|
16262
|
+
/** Token 信息推送定时器 */
|
|
16263
|
+
let tokenPushInterval;
|
|
15896
16264
|
try {
|
|
15897
16265
|
this._state = "executing";
|
|
15898
16266
|
this.updateStatsState();
|
|
@@ -16066,6 +16434,15 @@ var ReplSession = class {
|
|
|
16066
16434
|
this.currentTaskId = taskId;
|
|
16067
16435
|
const originalOnToggleThinking = this.editor.onToggleThinking;
|
|
16068
16436
|
if (thinkingDisplayMode === "collapse") this.editor.onToggleThinking = toggleThinking;
|
|
16437
|
+
this.agentStatusBar.clearTokenStats();
|
|
16438
|
+
const agentModel = this.agent.innerAgent.state.model;
|
|
16439
|
+
const modelName = typeof agentModel?.id === "string" ? agentModel.id : "unknown";
|
|
16440
|
+
this.agentStatusBar.setModelName(modelName);
|
|
16441
|
+
tokenPushInterval = setInterval(() => {
|
|
16442
|
+
const usage = this.agent.tokenTracker.getUsage();
|
|
16443
|
+
this.agentStatusBar.updateTokenStats(usage.inputTokens, usage.cacheReadTokens, usage.outputTokens, Date.now() - startTime);
|
|
16444
|
+
this.tui.requestRender();
|
|
16445
|
+
}, 2e3);
|
|
16069
16446
|
log.debug("开始通过 Agent 执行目标", {
|
|
16070
16447
|
taskId,
|
|
16071
16448
|
taskDescription: rawInput.slice(0, 100)
|
|
@@ -16305,6 +16682,12 @@ var ReplSession = class {
|
|
|
16305
16682
|
clearInterval(thinkingElapsedInterval);
|
|
16306
16683
|
thinkingElapsedInterval = void 0;
|
|
16307
16684
|
}
|
|
16685
|
+
if (tokenPushInterval) {
|
|
16686
|
+
clearInterval(tokenPushInterval);
|
|
16687
|
+
tokenPushInterval = void 0;
|
|
16688
|
+
}
|
|
16689
|
+
const finalUsage = this.agent.tokenTracker.getUsage();
|
|
16690
|
+
this.agentStatusBar.updateTokenStats(finalUsage.inputTokens, finalUsage.cacheReadTokens, finalUsage.outputTokens, Date.now() - startTime);
|
|
16308
16691
|
this._state = "idle";
|
|
16309
16692
|
this.updateStatsState();
|
|
16310
16693
|
this.editor.setExecuting(false);
|
|
@@ -16567,33 +16950,90 @@ var ReplSession = class {
|
|
|
16567
16950
|
}, process.cwd()).then((entries) => {
|
|
16568
16951
|
setSkillEntries(entries);
|
|
16569
16952
|
this.agent.skillEntries = entries;
|
|
16570
|
-
|
|
16571
|
-
this.agent.skillPrompt = snapshot.prompt;
|
|
16953
|
+
this.agent.resetSentSkills();
|
|
16572
16954
|
this._registerSkillCommands(entries);
|
|
16573
16955
|
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
|
-
}
|
|
16956
|
+
this.scanSkillSecurity(entries);
|
|
16588
16957
|
log.info("Skill 系统初始化完成", {
|
|
16589
|
-
count:
|
|
16590
|
-
names:
|
|
16958
|
+
count: entries.length,
|
|
16959
|
+
names: entries.map((e) => e.skill.name)
|
|
16591
16960
|
});
|
|
16961
|
+
this.startSkillWatcher();
|
|
16592
16962
|
}).catch((err) => {
|
|
16593
16963
|
log.error("Skill 加载失败", { error: err instanceof Error ? err.message : String(err) });
|
|
16594
16964
|
});
|
|
16595
16965
|
}
|
|
16596
16966
|
/**
|
|
16967
|
+
* 启动技能文件变更监视
|
|
16968
|
+
*
|
|
16969
|
+
* 监视项目级(.agents/skills/、.zapmyco/skills/)和用户级
|
|
16970
|
+
*(~/.zapmyco/skills/)技能目录,文件变更时自动重新加载。
|
|
16971
|
+
*/
|
|
16972
|
+
startSkillWatcher() {
|
|
16973
|
+
const watchDirs = [];
|
|
16974
|
+
for (const dir of [
|
|
16975
|
+
join(process.cwd(), ".agents", "skills"),
|
|
16976
|
+
join(process.cwd(), ".zapmyco", "skills"),
|
|
16977
|
+
join(homedir(), ".zapmyco", "skills")
|
|
16978
|
+
]) try {
|
|
16979
|
+
if (statSync(dir).isDirectory()) watchDirs.push(dir);
|
|
16980
|
+
} catch {}
|
|
16981
|
+
if (watchDirs.length === 0) return;
|
|
16982
|
+
this.skillWatcher.start({
|
|
16983
|
+
watchDirs,
|
|
16984
|
+
onChanged: () => this.reloadSkills()
|
|
16985
|
+
});
|
|
16986
|
+
}
|
|
16987
|
+
/**
|
|
16988
|
+
* 重新加载所有技能(文件变更时调用)
|
|
16989
|
+
*
|
|
16990
|
+
* 幂等操作:重新扫描磁盘、更新全局状态、刷新命令注册和自动补全。
|
|
16991
|
+
*/
|
|
16992
|
+
reloadSkills() {
|
|
16993
|
+
const skillConfig = this.config.skill;
|
|
16994
|
+
if (!skillConfig?.enabled) return;
|
|
16995
|
+
log.info("技能文件变更,重新加载技能...");
|
|
16996
|
+
loadSkills({
|
|
16997
|
+
enabled: true,
|
|
16998
|
+
extraDirs: skillConfig.loadDirs,
|
|
16999
|
+
maxSkillsInPrompt: skillConfig.maxSkillsInPrompt,
|
|
17000
|
+
maxSkillFileBytes: skillConfig.maxSkillFileBytes
|
|
17001
|
+
}, process.cwd()).then((entries) => {
|
|
17002
|
+
setSkillEntries(entries);
|
|
17003
|
+
this.agent.skillEntries = entries;
|
|
17004
|
+
this.agent.resetSentSkills();
|
|
17005
|
+
this.registry.unregisterBySource("skill");
|
|
17006
|
+
this._registerSkillCommands(entries);
|
|
17007
|
+
this.buildAutocompleteProvider();
|
|
17008
|
+
this.scanSkillSecurity(entries);
|
|
17009
|
+
log.info("技能重新加载完成", {
|
|
17010
|
+
count: entries.length,
|
|
17011
|
+
names: entries.map((e) => e.skill.name)
|
|
17012
|
+
});
|
|
17013
|
+
}).catch((err) => {
|
|
17014
|
+
log.error("技能重新加载失败", { error: err instanceof Error ? err.message : String(err) });
|
|
17015
|
+
});
|
|
17016
|
+
}
|
|
17017
|
+
/**
|
|
17018
|
+
* 扫描 Skill 安全威胁
|
|
17019
|
+
*/
|
|
17020
|
+
scanSkillSecurity(entries) {
|
|
17021
|
+
const results = this.skillGuard.scanAll(entries);
|
|
17022
|
+
const summary = this.skillGuard.getThreatSummary(results);
|
|
17023
|
+
if (summary.danger > 0 || summary.warning > 0) {
|
|
17024
|
+
log.warn("Skill 威胁扫描完成", summary);
|
|
17025
|
+
for (const result of results) if (!result.passed) for (const v of result.violations) eventBus.emit("security:violation", {
|
|
17026
|
+
toolId: "Skill",
|
|
17027
|
+
type: "skill-threat",
|
|
17028
|
+
message: `[${result.skillName}] ${v.reason}`,
|
|
17029
|
+
params: {
|
|
17030
|
+
skillPath: result.skillPath,
|
|
17031
|
+
ruleId: v.ruleId
|
|
17032
|
+
}
|
|
17033
|
+
});
|
|
17034
|
+
}
|
|
17035
|
+
}
|
|
17036
|
+
/**
|
|
16597
17037
|
* 为 user-invocable 技能注册斜杠命令
|
|
16598
17038
|
*
|
|
16599
17039
|
* 将每个用户可调用的技能注册为 /skill-name 格式的 CLI 命令。
|
|
@@ -16611,11 +17051,12 @@ var ReplSession = class {
|
|
|
16611
17051
|
description: spec.description,
|
|
16612
17052
|
aliases: [],
|
|
16613
17053
|
usage: spec.name,
|
|
17054
|
+
source: "skill",
|
|
16614
17055
|
handler: async (args) => {
|
|
16615
17056
|
const argsStr = args.join(" ");
|
|
16616
17057
|
const entry = getSkillEntries().find((e) => sanitizeSkillCommandName(e.skill.name) === spec.name);
|
|
16617
17058
|
if (entry) {
|
|
16618
|
-
const skillContent = formatSkillContent(entry.skill, argsStr);
|
|
17059
|
+
const skillContent = await formatSkillContent(entry.skill, argsStr);
|
|
16619
17060
|
await this.executeGoal(skillContent, `/${spec.name}`);
|
|
16620
17061
|
} else {
|
|
16621
17062
|
const goalInput = argsStr ? `请使用 /${spec.name} 技能,参数: ${argsStr}` : `请使用 /${spec.name} 技能`;
|