zapmyco 0.13.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 +1385 -147
- package/dist/cli/index.mjs.map +1 -1
- package/dist/index.d.mts +35 -6
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/{loader-BVhTlDtc.mjs → loader-DEuRwvSw.mjs} +410 -35
- package/dist/loader-DEuRwvSw.mjs.map +1 -0
- package/package.json +4 -2
- package/dist/loader-BVhTlDtc.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,11 +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";
|
|
22
|
+
import { marked } from "marked";
|
|
21
23
|
import { Client } from "@modelcontextprotocol/sdk/client";
|
|
22
24
|
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
23
25
|
|
|
24
26
|
//#region src/cli/repl/command-registry.ts
|
|
25
|
-
const log$
|
|
27
|
+
const log$21 = logger.child("repl:command-registry");
|
|
26
28
|
/**
|
|
27
29
|
* 命令注册表
|
|
28
30
|
*/
|
|
@@ -38,7 +40,7 @@ var CommandRegistry = class {
|
|
|
38
40
|
*/
|
|
39
41
|
register(cmd) {
|
|
40
42
|
const canonicalName = cmd.name.toLowerCase();
|
|
41
|
-
if (this.commands.has(canonicalName)) log$
|
|
43
|
+
if (this.commands.has(canonicalName)) log$21.warn(`命令 "${canonicalName}" 已存在,将被覆盖`);
|
|
42
44
|
this.commands.set(canonicalName, cmd);
|
|
43
45
|
for (const alias of cmd.aliases) {
|
|
44
46
|
const lowerAlias = alias.toLowerCase();
|
|
@@ -62,11 +64,34 @@ var CommandRegistry = class {
|
|
|
62
64
|
return Array.from(this.commands.values());
|
|
63
65
|
}
|
|
64
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
|
+
/**
|
|
65
90
|
* 分发并执行命令
|
|
66
91
|
*/
|
|
67
92
|
async dispatch(parsed) {
|
|
68
93
|
if (parsed.kind !== "command") {
|
|
69
|
-
log$
|
|
94
|
+
log$21.warn("dispatch 收到了非 command 类型的输入");
|
|
70
95
|
return;
|
|
71
96
|
}
|
|
72
97
|
const cmd = this.getCommand(parsed.name);
|
|
@@ -78,7 +103,7 @@ var CommandRegistry = class {
|
|
|
78
103
|
await cmd.handler(parsed.args, this.session);
|
|
79
104
|
} catch (error) {
|
|
80
105
|
const message = error instanceof Error ? error.message : String(error);
|
|
81
|
-
log$
|
|
106
|
+
log$21.error(`命令 /${cmd.name} 执行出错`, {}, error);
|
|
82
107
|
console.log(`\n 命令执行出错: ${message}\n`);
|
|
83
108
|
}
|
|
84
109
|
}
|
|
@@ -447,7 +472,15 @@ const noColor = {
|
|
|
447
472
|
|
|
448
473
|
//#endregion
|
|
449
474
|
//#region src/core/agent-team/agent-instance-manager.ts
|
|
450
|
-
|
|
475
|
+
/**
|
|
476
|
+
* Agent 实例管理器
|
|
477
|
+
*
|
|
478
|
+
* 管理所有 AgentInstance 的生命周期:创建、注册、状态转换、取消、清理。
|
|
479
|
+
* 全局实例注册表,支持按 team/type/depth/status 查询。
|
|
480
|
+
*
|
|
481
|
+
* @module core/agent-team
|
|
482
|
+
*/
|
|
483
|
+
const log$20 = logger.child("agent-instance-manager");
|
|
451
484
|
/**
|
|
452
485
|
* Agent 实例状态转换表
|
|
453
486
|
*
|
|
@@ -479,8 +512,9 @@ const TERMINAL_STATES = [
|
|
|
479
512
|
*
|
|
480
513
|
* 单例模式,全局唯一。
|
|
481
514
|
* 维护所有活跃和已完成的 Agent 实例的运行状态。
|
|
515
|
+
* 继承 EventEmitter 以支持 UI 组件实时监听状态变更。
|
|
482
516
|
*/
|
|
483
|
-
var AgentInstanceManager = class {
|
|
517
|
+
var AgentInstanceManager = class extends EventEmitter {
|
|
484
518
|
/** 实例注册表(按 instanceId 索引) */
|
|
485
519
|
instances = /* @__PURE__ */ new Map();
|
|
486
520
|
/**
|
|
@@ -511,12 +545,17 @@ var AgentInstanceManager = class {
|
|
|
511
545
|
const parent = this.instances.get(parentInstanceId);
|
|
512
546
|
if (parent) parent.childInstanceIds.push(instance.instanceId);
|
|
513
547
|
}
|
|
514
|
-
log$
|
|
548
|
+
log$20.debug("注册 Agent 实例", {
|
|
515
549
|
instanceId: instance.instanceId,
|
|
516
550
|
typeId: definition.typeId,
|
|
517
551
|
depth,
|
|
518
552
|
parentInstanceId
|
|
519
553
|
});
|
|
554
|
+
this.emit("instance:registered", {
|
|
555
|
+
instanceId: instance.instanceId,
|
|
556
|
+
typeId: instance.typeId,
|
|
557
|
+
depth
|
|
558
|
+
});
|
|
520
559
|
return instance;
|
|
521
560
|
}
|
|
522
561
|
/**
|
|
@@ -529,12 +568,12 @@ var AgentInstanceManager = class {
|
|
|
529
568
|
transition(instanceId, newStatus) {
|
|
530
569
|
const instance = this.instances.get(instanceId);
|
|
531
570
|
if (!instance) {
|
|
532
|
-
log$
|
|
571
|
+
log$20.warn("状态转换失败:实例不存在", { instanceId });
|
|
533
572
|
return false;
|
|
534
573
|
}
|
|
535
574
|
const allowed = VALID_TRANSITIONS[instance.status];
|
|
536
575
|
if (!allowed.includes(newStatus)) {
|
|
537
|
-
log$
|
|
576
|
+
log$20.warn("状态转换拒绝", {
|
|
538
577
|
instanceId,
|
|
539
578
|
from: instance.status,
|
|
540
579
|
to: newStatus,
|
|
@@ -542,16 +581,47 @@ var AgentInstanceManager = class {
|
|
|
542
581
|
});
|
|
543
582
|
return false;
|
|
544
583
|
}
|
|
584
|
+
const oldStatus = instance.status;
|
|
545
585
|
instance.status = newStatus;
|
|
546
|
-
log$
|
|
586
|
+
log$20.debug("Agent 实例状态转换", {
|
|
547
587
|
instanceId,
|
|
548
588
|
typeId: instance.typeId,
|
|
549
|
-
from:
|
|
589
|
+
from: oldStatus,
|
|
590
|
+
to: newStatus
|
|
591
|
+
});
|
|
592
|
+
this.emit("instance:transitioned", {
|
|
593
|
+
instanceId,
|
|
594
|
+
typeId: instance.typeId,
|
|
595
|
+
from: oldStatus,
|
|
550
596
|
to: newStatus
|
|
551
597
|
});
|
|
552
598
|
return true;
|
|
553
599
|
}
|
|
554
600
|
/**
|
|
601
|
+
* 更新实例当前活动信息(供 UI 状态栏实时展示)
|
|
602
|
+
*
|
|
603
|
+
* @param instanceId - 实例 ID
|
|
604
|
+
* @param activity - 活动信息(工具名称、调用次数、参数等)
|
|
605
|
+
*/
|
|
606
|
+
setActivity(instanceId, activity) {
|
|
607
|
+
const instance = this.instances.get(instanceId);
|
|
608
|
+
if (!instance) return;
|
|
609
|
+
instance.currentActivity = activity;
|
|
610
|
+
this.emit("instance:activity", {
|
|
611
|
+
instanceId,
|
|
612
|
+
typeId: instance.typeId,
|
|
613
|
+
activity
|
|
614
|
+
});
|
|
615
|
+
}
|
|
616
|
+
/**
|
|
617
|
+
* 获取实例当前活动信息
|
|
618
|
+
*
|
|
619
|
+
* @param instanceId - 实例 ID
|
|
620
|
+
*/
|
|
621
|
+
getActivity(instanceId) {
|
|
622
|
+
return this.instances.get(instanceId)?.currentActivity;
|
|
623
|
+
}
|
|
624
|
+
/**
|
|
555
625
|
* 获取指定实例
|
|
556
626
|
*
|
|
557
627
|
* @param instanceId - 实例 ID
|
|
@@ -644,7 +714,7 @@ var AgentInstanceManager = class {
|
|
|
644
714
|
instance.agent.removeAllListeners();
|
|
645
715
|
instance.agent.systemPromptOverride = null;
|
|
646
716
|
this.instances.delete(instanceId);
|
|
647
|
-
log$
|
|
717
|
+
log$20.debug("Agent 实例已清理", { instanceId });
|
|
648
718
|
}
|
|
649
719
|
/**
|
|
650
720
|
* 清理所有终态的实例
|
|
@@ -1095,7 +1165,7 @@ const BUILTIN_AGENT_TYPES = [
|
|
|
1095
1165
|
*
|
|
1096
1166
|
* @module core/agent-team
|
|
1097
1167
|
*/
|
|
1098
|
-
const log$
|
|
1168
|
+
const log$19 = logger.child("agent-type-registry");
|
|
1099
1169
|
/**
|
|
1100
1170
|
* Agent 类型注册中心
|
|
1101
1171
|
*
|
|
@@ -1119,14 +1189,14 @@ var AgentTypeRegistry = class {
|
|
|
1119
1189
|
*/
|
|
1120
1190
|
register(definition) {
|
|
1121
1191
|
const existing = this.types.get(definition.typeId);
|
|
1122
|
-
if (existing) log$
|
|
1192
|
+
if (existing) log$19.info("覆盖已注册的 Agent 类型", {
|
|
1123
1193
|
typeId: definition.typeId,
|
|
1124
1194
|
oldSource: existing.source,
|
|
1125
1195
|
newSource: definition.source
|
|
1126
1196
|
});
|
|
1127
1197
|
this.types.set(definition.typeId, definition);
|
|
1128
1198
|
this.refreshCache();
|
|
1129
|
-
log$
|
|
1199
|
+
log$19.debug("注册 Agent 类型", {
|
|
1130
1200
|
typeId: definition.typeId,
|
|
1131
1201
|
source: definition.source
|
|
1132
1202
|
});
|
|
@@ -1139,7 +1209,7 @@ var AgentTypeRegistry = class {
|
|
|
1139
1209
|
registerAll(definitions) {
|
|
1140
1210
|
for (const def of definitions) this.types.set(def.typeId, def);
|
|
1141
1211
|
this.refreshCache();
|
|
1142
|
-
log$
|
|
1212
|
+
log$19.info("批量注册 Agent 类型", { count: definitions.length });
|
|
1143
1213
|
}
|
|
1144
1214
|
/**
|
|
1145
1215
|
* 注销 Agent 类型
|
|
@@ -1151,7 +1221,7 @@ var AgentTypeRegistry = class {
|
|
|
1151
1221
|
const result = this.types.delete(typeId);
|
|
1152
1222
|
if (result) {
|
|
1153
1223
|
this.refreshCache();
|
|
1154
|
-
log$
|
|
1224
|
+
log$19.debug("注销 Agent 类型", { typeId });
|
|
1155
1225
|
}
|
|
1156
1226
|
return result;
|
|
1157
1227
|
}
|
|
@@ -1238,7 +1308,7 @@ var AgentTypeRegistry = class {
|
|
|
1238
1308
|
loadBuiltinTypes() {
|
|
1239
1309
|
for (const def of BUILTIN_AGENT_TYPES) this.types.set(def.typeId, def);
|
|
1240
1310
|
this.refreshCache();
|
|
1241
|
-
log$
|
|
1311
|
+
log$19.info("加载内置 Agent 类型", { count: BUILTIN_AGENT_TYPES.length });
|
|
1242
1312
|
}
|
|
1243
1313
|
/**
|
|
1244
1314
|
* 刷新可见类型缓存
|
|
@@ -1399,11 +1469,11 @@ function formatTokens(tokens) {
|
|
|
1399
1469
|
function createClearCommand() {
|
|
1400
1470
|
return {
|
|
1401
1471
|
name: "clear",
|
|
1402
|
-
aliases: [],
|
|
1403
|
-
description: "
|
|
1472
|
+
aliases: ["reset", "new"],
|
|
1473
|
+
description: "清空会话上下文,重置 Agent 状态",
|
|
1404
1474
|
usage: "/clear",
|
|
1405
1475
|
handler(_args, session) {
|
|
1406
|
-
session.
|
|
1476
|
+
session.clearAgentContext();
|
|
1407
1477
|
session.getInputParser().reset();
|
|
1408
1478
|
}
|
|
1409
1479
|
};
|
|
@@ -3021,6 +3091,227 @@ function createStatusCommand() {
|
|
|
3021
3091
|
};
|
|
3022
3092
|
}
|
|
3023
3093
|
|
|
3094
|
+
//#endregion
|
|
3095
|
+
//#region src/cli/repl/components/agent-status-bar.ts
|
|
3096
|
+
/**
|
|
3097
|
+
* Agent 状态栏组件
|
|
3098
|
+
*
|
|
3099
|
+
* 实时显示正在运行的子 Agent 状态,类似 Claude Code 的 "Running N agents…" 效果。
|
|
3100
|
+
* 固定在 OutputArea 和 Editor 之间,无活跃 Agent 时自动隐藏。
|
|
3101
|
+
*
|
|
3102
|
+
* @module cli/repl/components
|
|
3103
|
+
*/
|
|
3104
|
+
/** 状态图标映射 */
|
|
3105
|
+
const STATUS_ICONS$1 = {
|
|
3106
|
+
idle: "○",
|
|
3107
|
+
running: "◉",
|
|
3108
|
+
paused: "◐",
|
|
3109
|
+
completed: "●",
|
|
3110
|
+
failed: "✕",
|
|
3111
|
+
cancelled: "◌"
|
|
3112
|
+
};
|
|
3113
|
+
/** Loading 动画帧 */
|
|
3114
|
+
const LOADING_FRAMES$2 = [
|
|
3115
|
+
"⠋",
|
|
3116
|
+
"⠙",
|
|
3117
|
+
"⠹",
|
|
3118
|
+
"⠸",
|
|
3119
|
+
"⠼",
|
|
3120
|
+
"⠴",
|
|
3121
|
+
"⠦",
|
|
3122
|
+
"⠧",
|
|
3123
|
+
"⠇",
|
|
3124
|
+
"⠏"
|
|
3125
|
+
];
|
|
3126
|
+
const LOADING_INTERVAL_MS$1 = 200;
|
|
3127
|
+
/** 树形连接线 */
|
|
3128
|
+
const TREE_BRANCH = "├── ";
|
|
3129
|
+
const TREE_LAST = "└── ";
|
|
3130
|
+
const TREE_PIPE = "│ ";
|
|
3131
|
+
const TREE_SPACE = " ";
|
|
3132
|
+
const TREE_CONT = "⎿ ";
|
|
3133
|
+
/**
|
|
3134
|
+
* Agent 状态栏组件
|
|
3135
|
+
*
|
|
3136
|
+
* 从 AgentInstanceManager 读取活跃实例状态,渲染为紧凑状态栏。
|
|
3137
|
+
*/
|
|
3138
|
+
var AgentStatusBar = class extends Container {
|
|
3139
|
+
/** 是否展开显示详情 */
|
|
3140
|
+
#expanded = false;
|
|
3141
|
+
/** loading 动画帧索引 */
|
|
3142
|
+
#loadingFrame = 0;
|
|
3143
|
+
/** loading 动画定时器 */
|
|
3144
|
+
#loadingTimer;
|
|
3145
|
+
/** 上次活跃实例快照(用于检测变化) */
|
|
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;
|
|
3157
|
+
/**
|
|
3158
|
+
* 切换展开/折叠状态
|
|
3159
|
+
*/
|
|
3160
|
+
toggle() {
|
|
3161
|
+
this.#expanded = !this.#expanded;
|
|
3162
|
+
this.invalidate();
|
|
3163
|
+
}
|
|
3164
|
+
/** 获取当前展开状态 */
|
|
3165
|
+
get isExpanded() {
|
|
3166
|
+
return this.#expanded;
|
|
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
|
+
}
|
|
3204
|
+
invalidate() {
|
|
3205
|
+
super.invalidate();
|
|
3206
|
+
}
|
|
3207
|
+
render(width) {
|
|
3208
|
+
const activeInstances = getAgentInstanceManager().listActive();
|
|
3209
|
+
if (activeInstances.length === 0) {
|
|
3210
|
+
this.#stopLoading();
|
|
3211
|
+
this.#lastActiveCount = 0;
|
|
3212
|
+
if (this.hasTokenInfo) return [truncateToWidth(this.#renderTokenInfoLine(), width)];
|
|
3213
|
+
return [];
|
|
3214
|
+
}
|
|
3215
|
+
if (this.#lastActiveCount === 0) this.#startLoading();
|
|
3216
|
+
this.#lastActiveCount = activeInstances.length;
|
|
3217
|
+
const totalToolUses = activeInstances.reduce((sum, inst) => {
|
|
3218
|
+
return sum + (inst.currentActivity?.toolUses ?? 0);
|
|
3219
|
+
}, 0);
|
|
3220
|
+
const totalDuration = this.#formatDuration(Math.max(...activeInstances.map((i) => Date.now() - i.createdAt)));
|
|
3221
|
+
const frame = LOADING_FRAMES$2[this.#loadingFrame % LOADING_FRAMES$2.length] ?? "";
|
|
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;
|
|
3231
|
+
}
|
|
3232
|
+
/** 渲染折叠模式单行 */
|
|
3233
|
+
#renderCollapsed(frame, count, toolUses, duration) {
|
|
3234
|
+
return ` ${chalk.cyan(`${frame} Running ${count} agent${count > 1 ? "s" : ""}...`)} ${chalk.gray(`\u00B7 ${toolUses} tool use${toolUses !== 1 ? "s" : ""} \u00B7 ${duration}`)} ${chalk.dim("(ctrl+o to expand)")}`;
|
|
3235
|
+
}
|
|
3236
|
+
/** 渲染展开模式多行 */
|
|
3237
|
+
#renderExpanded(frame, instances, width) {
|
|
3238
|
+
const lines = [];
|
|
3239
|
+
const count = instances.length;
|
|
3240
|
+
const header = chalk.cyan(` ${frame} Running ${count} agent${count > 1 ? "s" : ""}...`);
|
|
3241
|
+
const hint = chalk.dim("(ctrl+o to collapse)");
|
|
3242
|
+
lines.push(truncateToWidth(`${header} ${hint}`, width));
|
|
3243
|
+
for (let i = 0; i < instances.length; i++) {
|
|
3244
|
+
const inst = instances[i];
|
|
3245
|
+
const isLast = i === instances.length - 1;
|
|
3246
|
+
const connector = isLast ? TREE_LAST : TREE_BRANCH;
|
|
3247
|
+
const childPrefix = isLast ? TREE_SPACE : TREE_PIPE;
|
|
3248
|
+
const icon = STATUS_ICONS$1[inst.status] ?? "?";
|
|
3249
|
+
const statusColor = inst.status === "running" ? chalk.yellow : chalk.gray;
|
|
3250
|
+
const typeLabel = chalk.bold(inst.typeId);
|
|
3251
|
+
const taskPreview = inst.task.description.slice(0, 40);
|
|
3252
|
+
const act = inst.currentActivity;
|
|
3253
|
+
const toolUsesStr = act ? chalk.gray(`\u00B7 ${act.toolUses} tool uses`) : "";
|
|
3254
|
+
const durationStr = chalk.gray(`\u00B7 ${this.#formatDuration(Date.now() - inst.createdAt)}`);
|
|
3255
|
+
const line1 = ` ${chalk.dim(connector)}${statusColor(icon)}${chalk.reset} ${typeLabel} ${chalk.dim(taskPreview)} ${toolUsesStr} ${durationStr}`;
|
|
3256
|
+
lines.push(truncateToWidth(line1, width));
|
|
3257
|
+
if (act) {
|
|
3258
|
+
const toolDisplay = act.args ? `${act.toolName}: ${act.args.slice(0, 60)}` : act.toolName;
|
|
3259
|
+
const line2 = ` ${chalk.dim(childPrefix + TREE_CONT)}${chalk.cyan(toolDisplay)}`;
|
|
3260
|
+
lines.push(truncateToWidth(line2, width));
|
|
3261
|
+
}
|
|
3262
|
+
}
|
|
3263
|
+
return lines;
|
|
3264
|
+
}
|
|
3265
|
+
/** 启动 loading 动画 */
|
|
3266
|
+
#startLoading() {
|
|
3267
|
+
if (this.#loadingTimer) return;
|
|
3268
|
+
this.#loadingTimer = setInterval(() => {
|
|
3269
|
+
this.#loadingFrame = (this.#loadingFrame + 1) % LOADING_FRAMES$2.length;
|
|
3270
|
+
this.invalidate();
|
|
3271
|
+
}, LOADING_INTERVAL_MS$1);
|
|
3272
|
+
}
|
|
3273
|
+
/** 停止 loading 动画 */
|
|
3274
|
+
#stopLoading() {
|
|
3275
|
+
if (this.#loadingTimer) {
|
|
3276
|
+
clearInterval(this.#loadingTimer);
|
|
3277
|
+
this.#loadingTimer = void 0;
|
|
3278
|
+
}
|
|
3279
|
+
}
|
|
3280
|
+
/** 格式化持续时间为可读字符串 */
|
|
3281
|
+
#formatDuration(ms) {
|
|
3282
|
+
if (ms < 1e3) return `${ms}ms`;
|
|
3283
|
+
if (ms < 6e4) return `${(ms / 1e3).toFixed(1)}s`;
|
|
3284
|
+
return `${Math.floor(ms / 6e4)}m${Math.floor(ms % 6e4 / 1e3)}s`;
|
|
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
|
+
}
|
|
3313
|
+
};
|
|
3314
|
+
|
|
3024
3315
|
//#endregion
|
|
3025
3316
|
//#region src/cli/repl/components/custom-editor.ts
|
|
3026
3317
|
/**
|
|
@@ -3040,7 +3331,7 @@ function createStatusCommand() {
|
|
|
3040
3331
|
/** 输入提示符 */
|
|
3041
3332
|
const PROMPT_PREFIX = "❯ ";
|
|
3042
3333
|
/** loading 动画帧 */
|
|
3043
|
-
const LOADING_FRAMES = [
|
|
3334
|
+
const LOADING_FRAMES$1 = [
|
|
3044
3335
|
"⠋",
|
|
3045
3336
|
"⠙",
|
|
3046
3337
|
"⠹",
|
|
@@ -3068,8 +3359,12 @@ var ZapmycoEditor = class extends Editor {
|
|
|
3068
3359
|
onCtrlD;
|
|
3069
3360
|
/** Ctrl+O 回调(打开外部编辑器) */
|
|
3070
3361
|
onOpenEditor;
|
|
3071
|
-
/** Ctrl+T
|
|
3362
|
+
/** Ctrl+T 回调(展开/折叠 TaskStatusBar) */
|
|
3363
|
+
onToggleTasks;
|
|
3364
|
+
/** Ctrl+Y 回调(展开/折叠 thinking 内容) */
|
|
3072
3365
|
onToggleThinking;
|
|
3366
|
+
/** Ctrl+Shift+O 回调(展开/折叠 Agent 状态栏) */
|
|
3367
|
+
onToggleAgentBar;
|
|
3073
3368
|
/** 是否正在执行(用于显示 loading) */
|
|
3074
3369
|
#executing = false;
|
|
3075
3370
|
/** 是否显示 spinner(执行期间禁用输入但不一定显示 spinner) */
|
|
@@ -3120,14 +3415,24 @@ var ZapmycoEditor = class extends Editor {
|
|
|
3120
3415
|
if (this.getText().length === 0 && this.onCtrlD) this.onCtrlD();
|
|
3121
3416
|
return;
|
|
3122
3417
|
}
|
|
3123
|
-
if (matchesKey(data, Key.ctrl("o"))
|
|
3124
|
-
this.onOpenEditor();
|
|
3418
|
+
if (matchesKey(data, Key.ctrl("o"))) {
|
|
3419
|
+
if (this.onOpenEditor) this.onOpenEditor();
|
|
3125
3420
|
return;
|
|
3126
3421
|
}
|
|
3127
|
-
if (matchesKey(data, Key.
|
|
3128
|
-
this.
|
|
3422
|
+
if (matchesKey(data, Key.ctrlShift("o")) && this.onToggleAgentBar) {
|
|
3423
|
+
this.onToggleAgentBar();
|
|
3129
3424
|
return;
|
|
3130
3425
|
}
|
|
3426
|
+
if (matchesKey(data, Key.ctrl("t"))) {
|
|
3427
|
+
if (this.onToggleTasks) {
|
|
3428
|
+
this.onToggleTasks();
|
|
3429
|
+
return;
|
|
3430
|
+
}
|
|
3431
|
+
if (this.onToggleThinking) {
|
|
3432
|
+
this.onToggleThinking();
|
|
3433
|
+
return;
|
|
3434
|
+
}
|
|
3435
|
+
}
|
|
3131
3436
|
if (matchesKey(data, Key.ctrl("y")) && this.onToggleThinking) {
|
|
3132
3437
|
this.onToggleThinking();
|
|
3133
3438
|
return;
|
|
@@ -3178,7 +3483,7 @@ var ZapmycoEditor = class extends Editor {
|
|
|
3178
3483
|
if (executing && showSpinner) {
|
|
3179
3484
|
this.#loadingFrame = 0;
|
|
3180
3485
|
this.#loadingTimer = setInterval(() => {
|
|
3181
|
-
this.#loadingFrame = (this.#loadingFrame + 1) % LOADING_FRAMES.length;
|
|
3486
|
+
this.#loadingFrame = (this.#loadingFrame + 1) % LOADING_FRAMES$1.length;
|
|
3182
3487
|
this.tui?.requestRender();
|
|
3183
3488
|
}, 100);
|
|
3184
3489
|
} else if (this.#loadingTimer) {
|
|
@@ -3211,7 +3516,7 @@ var ZapmycoEditor = class extends Editor {
|
|
|
3211
3516
|
for (let i = 0; i < contentLines.length; i++) {
|
|
3212
3517
|
const prefix = i === 0 ? PROMPT_PREFIX : " ".repeat(promptWidth);
|
|
3213
3518
|
let line;
|
|
3214
|
-
if (i === 0 && this.#executing && this.#showSpinner) line = `${prefix}${LOADING_FRAMES[this.#loadingFrame]} ${contentLines[i]}`;
|
|
3519
|
+
if (i === 0 && this.#executing && this.#showSpinner) line = `${prefix}${LOADING_FRAMES$1[this.#loadingFrame]} ${contentLines[i]}`;
|
|
3215
3520
|
else line = prefix + contentLines[i];
|
|
3216
3521
|
contentLines[i] = truncateToWidth(line, width);
|
|
3217
3522
|
}
|
|
@@ -3245,6 +3550,243 @@ var ZapmycoEditor = class extends Editor {
|
|
|
3245
3550
|
}
|
|
3246
3551
|
};
|
|
3247
3552
|
|
|
3553
|
+
//#endregion
|
|
3554
|
+
//#region src/cli/repl/components/task-status-bar.ts
|
|
3555
|
+
/**
|
|
3556
|
+
* TaskStatusBar — 任务状态栏组件
|
|
3557
|
+
*
|
|
3558
|
+
* 显示 TaskManage 创建的任务列表,支持折叠/展开两种模式。
|
|
3559
|
+
* 类似 Claude Code 的 TaskListV2,使用 pi-tui Container 实现。
|
|
3560
|
+
*
|
|
3561
|
+
* 折叠模式(默认,单行):
|
|
3562
|
+
* 📋 3 tasks · ◼ 1 in_progress · ◻ 1 pending · ✔ 1 completed Ctrl+T expand
|
|
3563
|
+
*
|
|
3564
|
+
* 展开模式(Ctrl+T 切换):
|
|
3565
|
+
* ⠋ #1 Search files
|
|
3566
|
+
* ◻ #2 Core logic
|
|
3567
|
+
* ◻ #3 Tests ▸ blocked by #1
|
|
3568
|
+
* ✔ #4 Analysis
|
|
3569
|
+
*
|
|
3570
|
+
* 1 in_progress · 1 pending · 1 completed
|
|
3571
|
+
* (Ctrl+T collapse)
|
|
3572
|
+
*
|
|
3573
|
+
* 自动隐藏:无任务时 render() 返回 []。
|
|
3574
|
+
* 进行中任务:显示 loading spinner 动画 + cyan 高亮。
|
|
3575
|
+
* 已完成任务:绿色 + 删除线。
|
|
3576
|
+
*
|
|
3577
|
+
* @module cli/repl/components
|
|
3578
|
+
*/
|
|
3579
|
+
/** Loading 动画帧(Braille 模式 spinner) */
|
|
3580
|
+
const LOADING_FRAMES = [
|
|
3581
|
+
"⠋",
|
|
3582
|
+
"⠙",
|
|
3583
|
+
"⠹",
|
|
3584
|
+
"⠸",
|
|
3585
|
+
"⠼",
|
|
3586
|
+
"⠴",
|
|
3587
|
+
"⠦",
|
|
3588
|
+
"⠧",
|
|
3589
|
+
"⠇",
|
|
3590
|
+
"⠏"
|
|
3591
|
+
];
|
|
3592
|
+
const LOADING_INTERVAL_MS = 200;
|
|
3593
|
+
/** 静态状态图标映射 */
|
|
3594
|
+
const STATUS_ICONS = {
|
|
3595
|
+
pending: "◻",
|
|
3596
|
+
in_progress: "◼",
|
|
3597
|
+
completed: "✔",
|
|
3598
|
+
cancelled: "✕"
|
|
3599
|
+
};
|
|
3600
|
+
/**
|
|
3601
|
+
* 任务状态栏组件
|
|
3602
|
+
*
|
|
3603
|
+
* 从 TaskStore 读取任务列表,渲染为紧凑状态栏。
|
|
3604
|
+
* 固定在 OutputArea 和 AgentStatusBar 之间。
|
|
3605
|
+
*/
|
|
3606
|
+
var TaskStatusBar = class extends Container {
|
|
3607
|
+
/**
|
|
3608
|
+
* 用户手动折叠标记
|
|
3609
|
+
*
|
|
3610
|
+
* - false(默认):根据任务状态自动决定展开/折叠
|
|
3611
|
+
* - true:用户通过 Ctrl+T 手动折叠,覆盖自动行为
|
|
3612
|
+
*/
|
|
3613
|
+
#forceCollapsed = false;
|
|
3614
|
+
/** TaskStore 引用(只读,不写) */
|
|
3615
|
+
#store;
|
|
3616
|
+
/** loading 动画帧索引 */
|
|
3617
|
+
#loadingFrame = 0;
|
|
3618
|
+
/** loading 动画定时器 */
|
|
3619
|
+
#loadingTimer;
|
|
3620
|
+
/** 上次是否有 in_progress 任务(用于启停动画) */
|
|
3621
|
+
#hadInProgress = false;
|
|
3622
|
+
constructor(store) {
|
|
3623
|
+
super();
|
|
3624
|
+
this.#store = store;
|
|
3625
|
+
}
|
|
3626
|
+
/**
|
|
3627
|
+
* 切换展开/折叠状态
|
|
3628
|
+
*
|
|
3629
|
+
* 用户手动 Ctrl+T 时调用,设置 forceCollapsed 标记。
|
|
3630
|
+
* 当新任务出现时,forceCollapsed 会被自动重置。
|
|
3631
|
+
*/
|
|
3632
|
+
toggle() {
|
|
3633
|
+
this.#forceCollapsed = !this.#forceCollapsed;
|
|
3634
|
+
this.invalidate();
|
|
3635
|
+
}
|
|
3636
|
+
/** 当前是否处于展开状态 */
|
|
3637
|
+
get isExpanded() {
|
|
3638
|
+
const summary = this.#store.summary();
|
|
3639
|
+
return this.#shouldExpand(summary);
|
|
3640
|
+
}
|
|
3641
|
+
/**
|
|
3642
|
+
* 当 TaskStore 变化时由外部调用
|
|
3643
|
+
*
|
|
3644
|
+
* 如果出现新的活跃任务,自动展开;全部完成后自动折叠。
|
|
3645
|
+
*/
|
|
3646
|
+
onTasksChanged() {
|
|
3647
|
+
const summary = this.#store.summary();
|
|
3648
|
+
if (summary.pending > 0 || summary.in_progress > 0) this.#forceCollapsed = false;
|
|
3649
|
+
this.invalidate();
|
|
3650
|
+
}
|
|
3651
|
+
invalidate() {
|
|
3652
|
+
super.invalidate();
|
|
3653
|
+
}
|
|
3654
|
+
/** 判断当前是否应展开 */
|
|
3655
|
+
#shouldExpand(summary) {
|
|
3656
|
+
if (summary.total === 0) return false;
|
|
3657
|
+
if (this.#forceCollapsed) return false;
|
|
3658
|
+
return summary.pending > 0 || summary.in_progress > 0;
|
|
3659
|
+
}
|
|
3660
|
+
/**
|
|
3661
|
+
* 管理 loading 动画的启停
|
|
3662
|
+
*
|
|
3663
|
+
* 当有 in_progress 任务时启动 spinner,全部完成后停止。
|
|
3664
|
+
*/
|
|
3665
|
+
#updateLoading(summary) {
|
|
3666
|
+
const hasInProgress = summary.in_progress > 0;
|
|
3667
|
+
if (hasInProgress && !this.#hadInProgress) this.#startLoading();
|
|
3668
|
+
else if (!hasInProgress && this.#hadInProgress) this.#stopLoading();
|
|
3669
|
+
}
|
|
3670
|
+
/** 启动 loading 动画 */
|
|
3671
|
+
#startLoading() {
|
|
3672
|
+
if (this.#loadingTimer) return;
|
|
3673
|
+
this.#loadingTimer = setInterval(() => {
|
|
3674
|
+
this.#loadingFrame = (this.#loadingFrame + 1) % LOADING_FRAMES.length;
|
|
3675
|
+
this.invalidate();
|
|
3676
|
+
}, LOADING_INTERVAL_MS);
|
|
3677
|
+
}
|
|
3678
|
+
/** 停止 loading 动画 */
|
|
3679
|
+
#stopLoading() {
|
|
3680
|
+
if (this.#loadingTimer) {
|
|
3681
|
+
clearInterval(this.#loadingTimer);
|
|
3682
|
+
this.#loadingTimer = void 0;
|
|
3683
|
+
this.#loadingFrame = 0;
|
|
3684
|
+
}
|
|
3685
|
+
}
|
|
3686
|
+
/** 获取当前 in_progress 的图标(可能为动画帧) */
|
|
3687
|
+
#getInProgressIcon() {
|
|
3688
|
+
return LOADING_FRAMES[this.#loadingFrame % LOADING_FRAMES.length];
|
|
3689
|
+
}
|
|
3690
|
+
render(width) {
|
|
3691
|
+
const tasks = this.#store.read();
|
|
3692
|
+
const summary = this.#store.summary();
|
|
3693
|
+
this.#updateLoading(summary);
|
|
3694
|
+
this.#hadInProgress = summary.in_progress > 0;
|
|
3695
|
+
if (tasks.length === 0) {
|
|
3696
|
+
this.#stopLoading();
|
|
3697
|
+
return [];
|
|
3698
|
+
}
|
|
3699
|
+
if (this.#shouldExpand(summary)) return this.#renderExpanded(tasks, summary, width);
|
|
3700
|
+
return [this.#renderCollapsed(summary).slice(0, width)];
|
|
3701
|
+
}
|
|
3702
|
+
/** 渲染折叠模式单行 */
|
|
3703
|
+
#renderCollapsed(summary) {
|
|
3704
|
+
const parts = [];
|
|
3705
|
+
const taskCount = summary.total === 1 ? "1 task" : `${summary.total} tasks`;
|
|
3706
|
+
parts.push(chalk.cyan(`\uD83D\uDCCB ${taskCount}`));
|
|
3707
|
+
if (summary.in_progress > 0) {
|
|
3708
|
+
const icon = this.#getInProgressIcon();
|
|
3709
|
+
parts.push(chalk.cyan(`${icon} ${summary.in_progress} in_progress`));
|
|
3710
|
+
}
|
|
3711
|
+
if (summary.pending > 0) parts.push(`${STATUS_ICONS.pending} ${summary.pending} pending`);
|
|
3712
|
+
if (summary.completed > 0) parts.push(chalk.green(`${STATUS_ICONS.completed} ${summary.completed} completed`));
|
|
3713
|
+
if (summary.cancelled > 0) parts.push(chalk.red.dim(`${STATUS_ICONS.cancelled} ${summary.cancelled} cancelled`));
|
|
3714
|
+
const hint = chalk.dim("Ctrl+T expand");
|
|
3715
|
+
return ` ${parts.join(" · ")} ${hint}`;
|
|
3716
|
+
}
|
|
3717
|
+
/** 渲染展开模式多行 */
|
|
3718
|
+
#renderExpanded(tasks, summary, width) {
|
|
3719
|
+
const lines = [];
|
|
3720
|
+
const sorted = this.#sortTasks(tasks);
|
|
3721
|
+
for (const task of sorted) lines.push(this.#renderTaskLine(task, width));
|
|
3722
|
+
lines.push("");
|
|
3723
|
+
const summaryParts = [];
|
|
3724
|
+
if (summary.in_progress > 0) summaryParts.push(chalk.cyan(`${summary.in_progress} in_progress`));
|
|
3725
|
+
if (summary.pending > 0) summaryParts.push(`${summary.pending} pending`);
|
|
3726
|
+
if (summary.completed > 0) summaryParts.push(chalk.green(`${summary.completed} completed`));
|
|
3727
|
+
if (summary.cancelled > 0) summaryParts.push(chalk.red.dim(`${summary.cancelled} cancelled`));
|
|
3728
|
+
lines.push(` ${summaryParts.join(" · ")}`);
|
|
3729
|
+
lines.push(chalk.dim(" (Ctrl+T collapse)"));
|
|
3730
|
+
return lines;
|
|
3731
|
+
}
|
|
3732
|
+
/** 渲染单个任务行 */
|
|
3733
|
+
#renderTaskLine(task, width) {
|
|
3734
|
+
const prefix = ` ${task.status === "in_progress" ? this.#getInProgressIcon() : STATUS_ICONS[task.status] ?? "?"} #${task.id} `;
|
|
3735
|
+
const blockedBy = this.#getBlockedHint(task);
|
|
3736
|
+
const blockedText = blockedBy ? ` ${blockedBy}` : "";
|
|
3737
|
+
const availableWidth = Math.max(10, width - [...prefix].length - [...blockedText].length);
|
|
3738
|
+
const subject = task.subject.length > availableWidth ? `${task.subject.slice(0, availableWidth - 1)}\u2026` : task.subject;
|
|
3739
|
+
let subjectStyled;
|
|
3740
|
+
if (task.status === "completed") subjectStyled = chalk.green.dim.strikethrough(subject);
|
|
3741
|
+
else if (task.status === "in_progress") subjectStyled = chalk.cyan.bold(subject);
|
|
3742
|
+
else if (task.status === "cancelled") subjectStyled = chalk.red.dim(subject);
|
|
3743
|
+
else subjectStyled = subject;
|
|
3744
|
+
const rightStyled = blockedBy ? chalk.dim(blockedBy) : "";
|
|
3745
|
+
return `${prefix}${subjectStyled}${blockedBy ? ` ${rightStyled}` : ""}`.slice(0, width);
|
|
3746
|
+
}
|
|
3747
|
+
/** 获取阻塞提示文本 */
|
|
3748
|
+
#getBlockedHint(task) {
|
|
3749
|
+
if (!task.dependencies || task.dependencies.length === 0) return "";
|
|
3750
|
+
if (task.status !== "pending") return "";
|
|
3751
|
+
const allTasks = this.#store.read();
|
|
3752
|
+
const openBlockers = [];
|
|
3753
|
+
for (const depId of task.dependencies) {
|
|
3754
|
+
const depTask = allTasks.find((t) => t.id === depId);
|
|
3755
|
+
if (depTask && depTask.status !== "completed") openBlockers.push(`#${depId}`);
|
|
3756
|
+
else if (!depTask) openBlockers.push(`#${depId}?`);
|
|
3757
|
+
}
|
|
3758
|
+
if (openBlockers.length === 0) return "";
|
|
3759
|
+
return `\u25B8 blocked by ${openBlockers.join(", ")}`;
|
|
3760
|
+
}
|
|
3761
|
+
/** 排序任务 */
|
|
3762
|
+
#sortTasks(tasks) {
|
|
3763
|
+
const allTasks = this.#store.read();
|
|
3764
|
+
return [...tasks].sort((a, b) => {
|
|
3765
|
+
return this.#taskPriority(a, allTasks) - this.#taskPriority(b, allTasks);
|
|
3766
|
+
});
|
|
3767
|
+
}
|
|
3768
|
+
/** 计算任务优先级(值越小越靠前) */
|
|
3769
|
+
#taskPriority(task, allTasks) {
|
|
3770
|
+
switch (task.status) {
|
|
3771
|
+
case "in_progress": return 0;
|
|
3772
|
+
case "pending":
|
|
3773
|
+
if (this.#isBlocked(task, allTasks)) return 2;
|
|
3774
|
+
return 1;
|
|
3775
|
+
case "completed": return 10 - Date.now() + task.updatedAt;
|
|
3776
|
+
case "cancelled": return 20;
|
|
3777
|
+
default: return 100;
|
|
3778
|
+
}
|
|
3779
|
+
}
|
|
3780
|
+
/** 判断任务是否被阻塞 */
|
|
3781
|
+
#isBlocked(task, allTasks) {
|
|
3782
|
+
if (!task.dependencies || task.dependencies.length === 0) return false;
|
|
3783
|
+
return task.dependencies.some((depId) => {
|
|
3784
|
+
const depTask = allTasks.find((t) => t.id === depId);
|
|
3785
|
+
return !depTask || depTask.status !== "completed";
|
|
3786
|
+
});
|
|
3787
|
+
}
|
|
3788
|
+
};
|
|
3789
|
+
|
|
3248
3790
|
//#endregion
|
|
3249
3791
|
//#region src/cli/repl/cron/cron-parser.ts
|
|
3250
3792
|
/** 通配符 * — 匹配所有值 */
|
|
@@ -3538,7 +4080,7 @@ const CRON_CONSTANTS = {
|
|
|
3538
4080
|
*
|
|
3539
4081
|
* @module cli/repl/cron/cron-scheduler
|
|
3540
4082
|
*/
|
|
3541
|
-
const log$
|
|
4083
|
+
const log$18 = logger.child("cron:scheduler");
|
|
3542
4084
|
var CronScheduler = class extends EventEmitter {
|
|
3543
4085
|
store;
|
|
3544
4086
|
jobs = [];
|
|
@@ -3559,7 +4101,7 @@ var CronScheduler = class extends EventEmitter {
|
|
|
3559
4101
|
if (this.running) return;
|
|
3560
4102
|
const loadedJobs = await this.store.load();
|
|
3561
4103
|
this.jobs = loadedJobs;
|
|
3562
|
-
log$
|
|
4104
|
+
log$18.info(`调度器启动,加载 ${loadedJobs.length} 个 durable 任务`);
|
|
3563
4105
|
await this.handleMissedJobs();
|
|
3564
4106
|
this.checkAutoExpiry();
|
|
3565
4107
|
this.running = true;
|
|
@@ -3575,7 +4117,7 @@ var CronScheduler = class extends EventEmitter {
|
|
|
3575
4117
|
clearInterval(this.timer);
|
|
3576
4118
|
this.timer = null;
|
|
3577
4119
|
}
|
|
3578
|
-
log$
|
|
4120
|
+
log$18.info("调度器已停止");
|
|
3579
4121
|
}
|
|
3580
4122
|
/** 添加任务 */
|
|
3581
4123
|
async addJob(job) {
|
|
@@ -3587,7 +4129,7 @@ var CronScheduler = class extends EventEmitter {
|
|
|
3587
4129
|
this.jobs.push(job);
|
|
3588
4130
|
await this.store.persist(this.jobs);
|
|
3589
4131
|
} else this.sessionJobs.push(job);
|
|
3590
|
-
log$
|
|
4132
|
+
log$18.info("任务已添加", {
|
|
3591
4133
|
id: job.id,
|
|
3592
4134
|
cron: job.cron,
|
|
3593
4135
|
durable: job.durable
|
|
@@ -3717,7 +4259,7 @@ var CronScheduler = class extends EventEmitter {
|
|
|
3717
4259
|
}, delay);
|
|
3718
4260
|
}
|
|
3719
4261
|
if (toDelete.length > 0) {
|
|
3720
|
-
log$
|
|
4262
|
+
log$18.info(`跳过 ${toDelete.length} 个错过的一次性任务(超出补发上限)`);
|
|
3721
4263
|
this.emit("missed-overflow", {
|
|
3722
4264
|
count: toDelete.length,
|
|
3723
4265
|
jobIds: toDelete.map((m) => m.id)
|
|
@@ -3735,7 +4277,7 @@ var CronScheduler = class extends EventEmitter {
|
|
|
3735
4277
|
job.lastFiredAt = now;
|
|
3736
4278
|
job.fireCount++;
|
|
3737
4279
|
this.removeJob(job.id);
|
|
3738
|
-
log$
|
|
4280
|
+
log$18.info("任务已过期并触发最后一次", { id: job.id });
|
|
3739
4281
|
}
|
|
3740
4282
|
}
|
|
3741
4283
|
}
|
|
@@ -3796,7 +4338,7 @@ function applyOneShotJitter(jobId, rawNext) {
|
|
|
3796
4338
|
*
|
|
3797
4339
|
* @module cli/repl/cron/cron-store
|
|
3798
4340
|
*/
|
|
3799
|
-
const log$
|
|
4341
|
+
const log$17 = logger.child("cron:store");
|
|
3800
4342
|
const STORE_FILE = join(join(homedir(), ".zapmyco", "cron"), "scheduled_tasks.json");
|
|
3801
4343
|
var CronStore = class {
|
|
3802
4344
|
filePath;
|
|
@@ -3820,13 +4362,13 @@ var CronStore = class {
|
|
|
3820
4362
|
const raw = await readFile(this.filePath, "utf-8");
|
|
3821
4363
|
const data = JSON.parse(raw);
|
|
3822
4364
|
if (!Array.isArray(data)) {
|
|
3823
|
-
log$
|
|
4365
|
+
log$17.warn("存储文件格式无效(非数组),将使用空列表");
|
|
3824
4366
|
return [];
|
|
3825
4367
|
}
|
|
3826
4368
|
return this.validateJobs(data);
|
|
3827
4369
|
} catch (err) {
|
|
3828
4370
|
if (err.code === "ENOENT") return [];
|
|
3829
|
-
log$
|
|
4371
|
+
log$17.warn("加载定时任务文件失败,将使用空列表", { error: err instanceof Error ? err.message : String(err) });
|
|
3830
4372
|
return [];
|
|
3831
4373
|
}
|
|
3832
4374
|
}
|
|
@@ -3873,7 +4415,7 @@ var CronStore = class {
|
|
|
3873
4415
|
if (typeof obj.maxFires === "number") job.maxFires = obj.maxFires;
|
|
3874
4416
|
valid.push(job);
|
|
3875
4417
|
}
|
|
3876
|
-
if (valid.length < raw.length) log$
|
|
4418
|
+
if (valid.length < raw.length) log$17.warn(`跳过 ${raw.length - valid.length} 个无效任务条目`);
|
|
3877
4419
|
return valid;
|
|
3878
4420
|
}
|
|
3879
4421
|
};
|
|
@@ -3891,7 +4433,7 @@ function getCronStore() {
|
|
|
3891
4433
|
* 基于内存的环形缓冲区,记录 REPL 会话中的用户输入和执行结果。
|
|
3892
4434
|
* 支持文件持久化到 ~/.zapmyco/history.json,跨会话恢复。
|
|
3893
4435
|
*/
|
|
3894
|
-
const log$
|
|
4436
|
+
const log$16 = logger.child("history:store");
|
|
3895
4437
|
/** 默认最大历史条数 */
|
|
3896
4438
|
const DEFAULT_MAX_SIZE = 100;
|
|
3897
4439
|
/** 历史文件存储路径 */
|
|
@@ -3950,13 +4492,13 @@ var HistoryStore = class {
|
|
|
3950
4492
|
if (Array.isArray(data.entries)) {
|
|
3951
4493
|
this.entries = data.entries.slice(-this.maxSize);
|
|
3952
4494
|
this.nextId = typeof data.nextId === "number" ? data.nextId : 1;
|
|
3953
|
-
log$
|
|
4495
|
+
log$16.debug("历史记录已加载", {
|
|
3954
4496
|
count: this.entries.length,
|
|
3955
4497
|
nextId: this.nextId
|
|
3956
4498
|
});
|
|
3957
4499
|
}
|
|
3958
4500
|
} catch {
|
|
3959
|
-
log$
|
|
4501
|
+
log$16.debug("无历史文件或加载失败,使用空历史");
|
|
3960
4502
|
}
|
|
3961
4503
|
}
|
|
3962
4504
|
/** 持久化历史记录到文件 */
|
|
@@ -3969,7 +4511,7 @@ var HistoryStore = class {
|
|
|
3969
4511
|
}, null, 2);
|
|
3970
4512
|
writeFileSync(this.filePath, data, "utf-8");
|
|
3971
4513
|
} catch (err) {
|
|
3972
|
-
log$
|
|
4514
|
+
log$16.warn("历史记录保存失败", { error: err instanceof Error ? err.message : String(err) });
|
|
3973
4515
|
}
|
|
3974
4516
|
}
|
|
3975
4517
|
};
|
|
@@ -4851,7 +5393,7 @@ var Renderer = class {
|
|
|
4851
5393
|
*
|
|
4852
5394
|
* @module core/agent-team
|
|
4853
5395
|
*/
|
|
4854
|
-
const log$
|
|
5396
|
+
const log$15 = logger.child("background-store");
|
|
4855
5397
|
/**
|
|
4856
5398
|
* 后台任务持久化存储
|
|
4857
5399
|
*
|
|
@@ -4881,14 +5423,14 @@ var BackgroundTaskStore = class {
|
|
|
4881
5423
|
const entries = JSON.parse(raw);
|
|
4882
5424
|
this.tasks.clear();
|
|
4883
5425
|
for (const entry of entries) this.tasks.set(entry.taskId, entry);
|
|
4884
|
-
log$
|
|
5426
|
+
log$15.debug("后台任务已加载", {
|
|
4885
5427
|
count: entries.length,
|
|
4886
5428
|
path: this.filePath
|
|
4887
5429
|
});
|
|
4888
5430
|
return entries;
|
|
4889
5431
|
}
|
|
4890
5432
|
} catch (err) {
|
|
4891
|
-
log$
|
|
5433
|
+
log$15.warn("后台任务加载失败", {
|
|
4892
5434
|
error: String(err),
|
|
4893
5435
|
path: this.filePath
|
|
4894
5436
|
});
|
|
@@ -4947,7 +5489,7 @@ var BackgroundTaskStore = class {
|
|
|
4947
5489
|
const entries = Array.from(this.tasks.values());
|
|
4948
5490
|
writeFileSync(this.filePath, JSON.stringify(entries, null, 2), "utf-8");
|
|
4949
5491
|
} catch (err) {
|
|
4950
|
-
log$
|
|
5492
|
+
log$15.error("后台任务持久化失败", {
|
|
4951
5493
|
error: String(err),
|
|
4952
5494
|
path: this.filePath
|
|
4953
5495
|
});
|
|
@@ -4973,7 +5515,7 @@ var BackgroundTaskStore = class {
|
|
|
4973
5515
|
}
|
|
4974
5516
|
if (cleaned > 0) {
|
|
4975
5517
|
this.persist();
|
|
4976
|
-
log$
|
|
5518
|
+
log$15.info("清理过期后台任务", { cleaned });
|
|
4977
5519
|
}
|
|
4978
5520
|
return cleaned;
|
|
4979
5521
|
}
|
|
@@ -4990,7 +5532,7 @@ var BackgroundTaskStore = class {
|
|
|
4990
5532
|
*
|
|
4991
5533
|
* @module core/agent-team
|
|
4992
5534
|
*/
|
|
4993
|
-
const log$
|
|
5535
|
+
const log$14 = logger.child("agent-message-bus");
|
|
4994
5536
|
/**
|
|
4995
5537
|
* Agent 消息总线(单例)
|
|
4996
5538
|
*
|
|
@@ -5024,12 +5566,12 @@ var AgentMessageBus = class {
|
|
|
5024
5566
|
};
|
|
5025
5567
|
const targetInstance = getAgentInstanceManager().get(toAgentId);
|
|
5026
5568
|
if (targetInstance) targetInstance.inbox.push(fullMessage);
|
|
5027
|
-
else log$
|
|
5569
|
+
else log$14.warn("目标 Agent 实例不存在,消息丢弃", {
|
|
5028
5570
|
toAgentId,
|
|
5029
5571
|
messageId: fullMessage.messageId
|
|
5030
5572
|
});
|
|
5031
5573
|
this.emitter.emit(`msg:${toAgentId}`, fullMessage);
|
|
5032
|
-
log$
|
|
5574
|
+
log$14.debug("消息已投递", {
|
|
5033
5575
|
from: fromAgentId,
|
|
5034
5576
|
to: toAgentId,
|
|
5035
5577
|
type: fullMessage.type,
|
|
@@ -5112,7 +5654,7 @@ function getAgentMessageBus() {
|
|
|
5112
5654
|
*
|
|
5113
5655
|
* @module core/agent-team
|
|
5114
5656
|
*/
|
|
5115
|
-
const log$
|
|
5657
|
+
const log$13 = logger.child("background-agent-manager");
|
|
5116
5658
|
/**
|
|
5117
5659
|
* 后台 Agent 管理器(单例)
|
|
5118
5660
|
*/
|
|
@@ -5169,7 +5711,7 @@ var BackgroundAgentManager = class {
|
|
|
5169
5711
|
parentAgentId: params.parentInstanceId
|
|
5170
5712
|
});
|
|
5171
5713
|
this.runAsync(taskId, params, abortController, timeoutMs).catch((err) => {
|
|
5172
|
-
log$
|
|
5714
|
+
log$13.error("后台 Agent 意外崩溃", {
|
|
5173
5715
|
taskId,
|
|
5174
5716
|
error: String(err)
|
|
5175
5717
|
});
|
|
@@ -5189,7 +5731,7 @@ var BackgroundAgentManager = class {
|
|
|
5189
5731
|
const orchestrator = this.orchestrator;
|
|
5190
5732
|
const messageBus = getAgentMessageBus();
|
|
5191
5733
|
const timeoutHandle = setTimeout(() => {
|
|
5192
|
-
log$
|
|
5734
|
+
log$13.warn("后台 Agent 超时,自动取消", {
|
|
5193
5735
|
taskId,
|
|
5194
5736
|
timeoutMs
|
|
5195
5737
|
});
|
|
@@ -5233,7 +5775,7 @@ var BackgroundAgentManager = class {
|
|
|
5233
5775
|
taskId,
|
|
5234
5776
|
requiresResponse: false
|
|
5235
5777
|
});
|
|
5236
|
-
log$
|
|
5778
|
+
log$13.info("后台 Agent 完成通知已发送", {
|
|
5237
5779
|
taskId,
|
|
5238
5780
|
instanceId: result.instanceId,
|
|
5239
5781
|
parentId: params.parentInstanceId,
|
|
@@ -5260,7 +5802,7 @@ var BackgroundAgentManager = class {
|
|
|
5260
5802
|
completedAt: Date.now(),
|
|
5261
5803
|
error
|
|
5262
5804
|
});
|
|
5263
|
-
log$
|
|
5805
|
+
log$13.warn("后台 Agent 失败", {
|
|
5264
5806
|
taskId,
|
|
5265
5807
|
error
|
|
5266
5808
|
});
|
|
@@ -5297,7 +5839,7 @@ var BackgroundAgentManager = class {
|
|
|
5297
5839
|
if (runtime.instanceId) try {
|
|
5298
5840
|
await getAgentInstanceManager().cancel(runtime.instanceId);
|
|
5299
5841
|
} catch {}
|
|
5300
|
-
log$
|
|
5842
|
+
log$13.info("后台 Agent 已取消", { taskId });
|
|
5301
5843
|
return true;
|
|
5302
5844
|
}
|
|
5303
5845
|
/**
|
|
@@ -5306,13 +5848,13 @@ var BackgroundAgentManager = class {
|
|
|
5306
5848
|
restore() {
|
|
5307
5849
|
this.store.load();
|
|
5308
5850
|
const stale = this.store.cleanStale();
|
|
5309
|
-
if (stale > 0) log$
|
|
5851
|
+
if (stale > 0) log$13.info("跨会话恢复:清理了过期后台任务", { count: stale });
|
|
5310
5852
|
const active = this.store.listActive();
|
|
5311
5853
|
for (const entry of active) this.store.updateStatus(entry.taskId, "failed", {
|
|
5312
5854
|
completedAt: Date.now(),
|
|
5313
5855
|
error: "会话终止导致任务丢失"
|
|
5314
5856
|
});
|
|
5315
|
-
if (active.length > 0) log$
|
|
5857
|
+
if (active.length > 0) log$13.info("跨会话恢复:标记活跃任务为 failed", { count: active.length });
|
|
5316
5858
|
}
|
|
5317
5859
|
};
|
|
5318
5860
|
/** 全局单例 */
|
|
@@ -5531,7 +6073,7 @@ function createDeferred() {
|
|
|
5531
6073
|
*
|
|
5532
6074
|
* @module core/question/question-manager
|
|
5533
6075
|
*/
|
|
5534
|
-
const log$
|
|
6076
|
+
const log$12 = logger.child("question-manager");
|
|
5535
6077
|
/** 问题超时时间(5 分钟) */
|
|
5536
6078
|
const QUESTION_TIMEOUT_MS = 300 * 1e3;
|
|
5537
6079
|
var QuestionManager = class {
|
|
@@ -5577,13 +6119,13 @@ var QuestionManager = class {
|
|
|
5577
6119
|
requestId,
|
|
5578
6120
|
questionCount: params.questions.length
|
|
5579
6121
|
});
|
|
5580
|
-
log$
|
|
6122
|
+
log$12.debug("提问已发出", {
|
|
5581
6123
|
requestId,
|
|
5582
6124
|
questionCount: params.questions.length
|
|
5583
6125
|
});
|
|
5584
6126
|
const timeout = setTimeout(() => {
|
|
5585
6127
|
if (!deferred.isSettled) {
|
|
5586
|
-
log$
|
|
6128
|
+
log$12.warn("提问超时,自动取消", { requestId });
|
|
5587
6129
|
deferred.reject(/* @__PURE__ */ new Error("提问超时,用户未在 5 分钟内回答"));
|
|
5588
6130
|
this.pending.delete(requestId);
|
|
5589
6131
|
eventBus.emit("question:timeout", { requestId });
|
|
@@ -5597,7 +6139,7 @@ var QuestionManager = class {
|
|
|
5597
6139
|
requestId,
|
|
5598
6140
|
answerCount: Object.keys(result.answers).length
|
|
5599
6141
|
});
|
|
5600
|
-
log$
|
|
6142
|
+
log$12.debug("提问已回答", { requestId });
|
|
5601
6143
|
return result;
|
|
5602
6144
|
} catch (err) {
|
|
5603
6145
|
clearTimeout(timeout);
|
|
@@ -5606,7 +6148,7 @@ var QuestionManager = class {
|
|
|
5606
6148
|
requestId,
|
|
5607
6149
|
reason: err instanceof Error ? err.message : String(err)
|
|
5608
6150
|
});
|
|
5609
|
-
log$
|
|
6151
|
+
log$12.debug("提问已取消", {
|
|
5610
6152
|
requestId,
|
|
5611
6153
|
error: err instanceof Error ? err.message : String(err)
|
|
5612
6154
|
});
|
|
@@ -5628,7 +6170,7 @@ var QuestionManager = class {
|
|
|
5628
6170
|
const count = this.pending.size;
|
|
5629
6171
|
for (const [, entry] of this.pending) if (!entry.deferred.isSettled) entry.deferred.reject(error);
|
|
5630
6172
|
this.pending.clear();
|
|
5631
|
-
if (count > 0) log$
|
|
6173
|
+
if (count > 0) log$12.debug("已清理所有待处理问题", { count });
|
|
5632
6174
|
}
|
|
5633
6175
|
};
|
|
5634
6176
|
let globalQuestionManager = null;
|
|
@@ -9218,6 +9760,12 @@ function setSkillEntries(entries) {
|
|
|
9218
9760
|
skillEntries = entries;
|
|
9219
9761
|
}
|
|
9220
9762
|
/**
|
|
9763
|
+
* 获取当前已加载的 Skill 列表
|
|
9764
|
+
*/
|
|
9765
|
+
function getSkillEntries() {
|
|
9766
|
+
return skillEntries;
|
|
9767
|
+
}
|
|
9768
|
+
/**
|
|
9221
9769
|
* 替换 Skill 内容中的模板变量
|
|
9222
9770
|
*
|
|
9223
9771
|
* 支持的变量:
|
|
@@ -9270,6 +9818,137 @@ function parseArgs(args) {
|
|
|
9270
9818
|
if (current.length > 0) result.push(current);
|
|
9271
9819
|
return result;
|
|
9272
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
|
+
}
|
|
9273
9952
|
/**
|
|
9274
9953
|
* 创建 Skill 工具
|
|
9275
9954
|
*
|
|
@@ -9338,7 +10017,7 @@ function createSkillTool(_config) {
|
|
|
9338
10017
|
const endIdx = content.indexOf("---", 3);
|
|
9339
10018
|
if (endIdx !== -1) bodyContent = content.slice(endIdx + 3).trim();
|
|
9340
10019
|
}
|
|
9341
|
-
const
|
|
10020
|
+
const withShell = await executeShellCommandsInSkill(substituteVariables(bodyContent, params.args, skill.baseDir, skill.name));
|
|
9342
10021
|
const hint = skill.frontmatter["argument-hint"] ? ` [${skill.frontmatter["argument-hint"]}]` : "";
|
|
9343
10022
|
const instructionParts = [
|
|
9344
10023
|
`# Skill: ${skill.name}${hint}`,
|
|
@@ -9347,12 +10026,12 @@ function createSkillTool(_config) {
|
|
|
9347
10026
|
"",
|
|
9348
10027
|
"---",
|
|
9349
10028
|
"",
|
|
9350
|
-
|
|
10029
|
+
withShell,
|
|
9351
10030
|
"",
|
|
9352
10031
|
"---",
|
|
9353
10032
|
`Base directory: ${skill.baseDir}`
|
|
9354
10033
|
];
|
|
9355
|
-
if (!
|
|
10034
|
+
if (!withShell.includes(params.args ?? "") && params.args) instructionParts.push("", `ARGUMENTS: ${params.args}`);
|
|
9356
10035
|
return {
|
|
9357
10036
|
content: [{
|
|
9358
10037
|
type: "text",
|
|
@@ -9382,6 +10061,33 @@ function getSkillCommandSpecs(entries) {
|
|
|
9382
10061
|
}));
|
|
9383
10062
|
}
|
|
9384
10063
|
/**
|
|
10064
|
+
* 格式化 Skill 指令内容(供 Skill tool 和斜杠命令 handler 共用)
|
|
10065
|
+
*
|
|
10066
|
+
* 使用内存中已解析的 skill.body,无需重新读取 SKILL.md 文件。
|
|
10067
|
+
*
|
|
10068
|
+
* @param skill - Skill 定义(含 frontmatter、body、baseDir)
|
|
10069
|
+
* @param args - 用户传递的参数(可选)
|
|
10070
|
+
* @returns 格式化后的完整指令文本
|
|
10071
|
+
*/
|
|
10072
|
+
async function formatSkillContent(skill, args) {
|
|
10073
|
+
const withShell = await executeShellCommandsInSkill(substituteVariables(skill.body, args, skill.baseDir, skill.name));
|
|
10074
|
+
const hint = skill.frontmatter["argument-hint"] ? ` [${skill.frontmatter["argument-hint"]}]` : "";
|
|
10075
|
+
const instructionParts = [
|
|
10076
|
+
`# Skill: ${skill.name}${hint}`,
|
|
10077
|
+
"",
|
|
10078
|
+
skill.description ? `> ${skill.description}` : "",
|
|
10079
|
+
"",
|
|
10080
|
+
"---",
|
|
10081
|
+
"",
|
|
10082
|
+
withShell,
|
|
10083
|
+
"",
|
|
10084
|
+
"---",
|
|
10085
|
+
`Base directory: ${skill.baseDir}`
|
|
10086
|
+
];
|
|
10087
|
+
if (!withShell.includes(args ?? "") && args) instructionParts.push("", `ARGUMENTS: ${args}`);
|
|
10088
|
+
return instructionParts.filter(Boolean).join("\n");
|
|
10089
|
+
}
|
|
10090
|
+
/**
|
|
9385
10091
|
* 规范化技能命令名称
|
|
9386
10092
|
*
|
|
9387
10093
|
* 转换为小写,非字母数字字符替换为连字符。
|
|
@@ -10890,7 +11596,7 @@ var WorktreeStore = class {
|
|
|
10890
11596
|
* @module core/worktree
|
|
10891
11597
|
*/
|
|
10892
11598
|
const execFileAsync = promisify(execFile);
|
|
10893
|
-
const log$
|
|
11599
|
+
const log$11 = logger.child("worktree-manager");
|
|
10894
11600
|
let globalWorktreeManager = null;
|
|
10895
11601
|
/** 获取全局 WorktreeManager 实例 */
|
|
10896
11602
|
function getWorktreeManager() {
|
|
@@ -10926,7 +11632,7 @@ var WorktreeManager = class {
|
|
|
10926
11632
|
throw new WorktreeError("无法确定 git 仓库根目录,请确保在 git 仓库中运行", "NOT_GIT_REPO");
|
|
10927
11633
|
}
|
|
10928
11634
|
try {
|
|
10929
|
-
log$
|
|
11635
|
+
log$11.info("创建 worktree", {
|
|
10930
11636
|
branchName,
|
|
10931
11637
|
worktreePath,
|
|
10932
11638
|
gitRoot
|
|
@@ -10954,14 +11660,14 @@ var WorktreeManager = class {
|
|
|
10954
11660
|
});
|
|
10955
11661
|
} catch (err) {
|
|
10956
11662
|
const msg = err instanceof Error ? err.message : String(err);
|
|
10957
|
-
log$
|
|
11663
|
+
log$11.warn("创建分支失败,清理 worktree", {
|
|
10958
11664
|
branchName,
|
|
10959
11665
|
error: msg
|
|
10960
11666
|
});
|
|
10961
11667
|
try {
|
|
10962
11668
|
await this.removeByPath(worktreePath, branchName, true);
|
|
10963
11669
|
} catch (err) {
|
|
10964
|
-
log$
|
|
11670
|
+
log$11.warn("Worktree 创建失败后清理出错", {
|
|
10965
11671
|
worktreePath,
|
|
10966
11672
|
error: err instanceof Error ? err.message : String(err)
|
|
10967
11673
|
});
|
|
@@ -10986,7 +11692,7 @@ var WorktreeManager = class {
|
|
|
10986
11692
|
createdBy: info.createdBy,
|
|
10987
11693
|
status: "active"
|
|
10988
11694
|
});
|
|
10989
|
-
log$
|
|
11695
|
+
log$11.info("Worktree 创建成功", {
|
|
10990
11696
|
id: info.id,
|
|
10991
11697
|
path: worktreePath
|
|
10992
11698
|
});
|
|
@@ -10998,13 +11704,13 @@ var WorktreeManager = class {
|
|
|
10998
11704
|
async remove(id, discardChanges) {
|
|
10999
11705
|
const info = this.activeWorktrees.get(id);
|
|
11000
11706
|
if (!info) {
|
|
11001
|
-
log$
|
|
11707
|
+
log$11.warn("尝试删除不存在的 worktree", { id });
|
|
11002
11708
|
return;
|
|
11003
11709
|
}
|
|
11004
11710
|
await this.removeByPath(info.worktreePath, info.branchName, discardChanges);
|
|
11005
11711
|
this.activeWorktrees.delete(id);
|
|
11006
11712
|
this.store.delete(id);
|
|
11007
|
-
log$
|
|
11713
|
+
log$11.info("Worktree 已删除", { id });
|
|
11008
11714
|
}
|
|
11009
11715
|
/**
|
|
11010
11716
|
* 通过路径删除 worktree(内部方法)
|
|
@@ -11014,7 +11720,7 @@ var WorktreeManager = class {
|
|
|
11014
11720
|
try {
|
|
11015
11721
|
await execFileAsync("git", ["worktree", "prune"], { timeout: 1e4 });
|
|
11016
11722
|
} catch (err) {
|
|
11017
|
-
log$
|
|
11723
|
+
log$11.warn("Worktree prune 失败(路径不存在)", {
|
|
11018
11724
|
worktreePath,
|
|
11019
11725
|
error: err instanceof Error ? err.message : String(err)
|
|
11020
11726
|
});
|
|
@@ -11028,7 +11734,7 @@ var WorktreeManager = class {
|
|
|
11028
11734
|
await execFileAsync("git", args, { timeout: 15e3 });
|
|
11029
11735
|
} catch (err) {
|
|
11030
11736
|
const msg = err instanceof Error ? err.message : String(err);
|
|
11031
|
-
log$
|
|
11737
|
+
log$11.warn("git worktree remove 失败", {
|
|
11032
11738
|
worktreePath,
|
|
11033
11739
|
error: msg
|
|
11034
11740
|
});
|
|
@@ -11040,7 +11746,7 @@ var WorktreeManager = class {
|
|
|
11040
11746
|
branchName
|
|
11041
11747
|
], { timeout: 1e4 });
|
|
11042
11748
|
} catch (err) {
|
|
11043
|
-
log$
|
|
11749
|
+
log$11.warn("删除 worktree 分支失败", {
|
|
11044
11750
|
branchName,
|
|
11045
11751
|
error: err instanceof Error ? err.message : String(err)
|
|
11046
11752
|
});
|
|
@@ -11048,7 +11754,7 @@ var WorktreeManager = class {
|
|
|
11048
11754
|
try {
|
|
11049
11755
|
await execFileAsync("git", ["worktree", "prune"], { timeout: 1e4 });
|
|
11050
11756
|
} catch (err) {
|
|
11051
|
-
log$
|
|
11757
|
+
log$11.warn("Worktree prune 失败", { error: err instanceof Error ? err.message : String(err) });
|
|
11052
11758
|
}
|
|
11053
11759
|
}
|
|
11054
11760
|
/**
|
|
@@ -11070,7 +11776,7 @@ var WorktreeManager = class {
|
|
|
11070
11776
|
await this.remove(id, true);
|
|
11071
11777
|
return { cleaned: true };
|
|
11072
11778
|
}
|
|
11073
|
-
log$
|
|
11779
|
+
log$11.info("Worktree 有变更,保留", {
|
|
11074
11780
|
id,
|
|
11075
11781
|
path: info.worktreePath
|
|
11076
11782
|
});
|
|
@@ -11090,7 +11796,7 @@ var WorktreeManager = class {
|
|
|
11090
11796
|
});
|
|
11091
11797
|
return stdout.trim().length > 0;
|
|
11092
11798
|
} catch (err) {
|
|
11093
|
-
log$
|
|
11799
|
+
log$11.warn("检查 worktree 变更状态失败", {
|
|
11094
11800
|
worktreePath,
|
|
11095
11801
|
error: err instanceof Error ? err.message : String(err)
|
|
11096
11802
|
});
|
|
@@ -11110,7 +11816,7 @@ var WorktreeManager = class {
|
|
|
11110
11816
|
await this.removeByPath(record.worktreePath, record.branchName, true);
|
|
11111
11817
|
cleaned++;
|
|
11112
11818
|
} catch (err) {
|
|
11113
|
-
log$
|
|
11819
|
+
log$11.warn("过期 worktree 清理失败", {
|
|
11114
11820
|
id: record.id,
|
|
11115
11821
|
path: record.worktreePath,
|
|
11116
11822
|
error: err instanceof Error ? err.message : String(err)
|
|
@@ -11119,7 +11825,7 @@ var WorktreeManager = class {
|
|
|
11119
11825
|
this.activeWorktrees.delete(record.id);
|
|
11120
11826
|
this.store.delete(record.id);
|
|
11121
11827
|
}
|
|
11122
|
-
if (cleaned > 0) log$
|
|
11828
|
+
if (cleaned > 0) log$11.info("过期 worktree 清理完成", { cleaned });
|
|
11123
11829
|
return cleaned;
|
|
11124
11830
|
}
|
|
11125
11831
|
listActive() {
|
|
@@ -11170,7 +11876,7 @@ const AGENT_STANDARD_TOOLS = [
|
|
|
11170
11876
|
|
|
11171
11877
|
//#endregion
|
|
11172
11878
|
//#region src/core/agent-team/agent-factory.ts
|
|
11173
|
-
const log$
|
|
11879
|
+
const log$10 = logger.child("agent-factory");
|
|
11174
11880
|
/**
|
|
11175
11881
|
* 创建 Agent 实例
|
|
11176
11882
|
*
|
|
@@ -11188,6 +11894,7 @@ const log$9 = logger.child("agent-factory");
|
|
|
11188
11894
|
* @returns 完全初始化的 LlmBasedAgent
|
|
11189
11895
|
*/
|
|
11190
11896
|
function createAgentFromType(definition, instance, parentAgent, availableTools, config) {
|
|
11897
|
+
const thinkingLevel = resolveAgentThinkingLevel(definition.thinkingLevel, config.thinkingLevel, parentAgent);
|
|
11191
11898
|
const agent = createLlmBasedAgent({
|
|
11192
11899
|
agentId: instance.instanceId,
|
|
11193
11900
|
displayName: definition.displayName,
|
|
@@ -11195,14 +11902,15 @@ function createAgentFromType(definition, instance, parentAgent, availableTools,
|
|
|
11195
11902
|
runtimeConfig: {
|
|
11196
11903
|
enabled: true,
|
|
11197
11904
|
toolExecution: "sequential",
|
|
11198
|
-
maxTurns: definition.maxTurns
|
|
11905
|
+
maxTurns: definition.maxTurns,
|
|
11906
|
+
thinkingLevel
|
|
11199
11907
|
}
|
|
11200
11908
|
});
|
|
11201
11909
|
shareParentResources(agent, parentAgent, definition);
|
|
11202
11910
|
const tools = resolveTools(definition, availableTools, instance.depth, config);
|
|
11203
11911
|
agent.registerTools(tools);
|
|
11204
11912
|
agent.systemPromptOverride = buildSystemPrompt(definition, instance.task.description, config);
|
|
11205
|
-
log$
|
|
11913
|
+
log$10.debug("创建 Agent 实例", {
|
|
11206
11914
|
typeId: definition.typeId,
|
|
11207
11915
|
instanceId: instance.instanceId,
|
|
11208
11916
|
depth: instance.depth,
|
|
@@ -11266,6 +11974,17 @@ function buildSystemPrompt(definition, taskDescription, _config) {
|
|
|
11266
11974
|
return definition.getSystemPrompt(ctx);
|
|
11267
11975
|
}
|
|
11268
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
|
+
/**
|
|
11269
11988
|
* 共享父 Agent 的 Model 和 API Key 给子 Agent
|
|
11270
11989
|
*
|
|
11271
11990
|
* 如果 Agent 类型声明了偏好的模型(definition.model),则通过 AgentLlmFacade
|
|
@@ -11283,8 +12002,14 @@ function shareParentResources(subAgent, parentAgent, definition) {
|
|
|
11283
12002
|
subAgent.llmFacade = parentAgent.llmFacade;
|
|
11284
12003
|
if (definition?.model) {
|
|
11285
12004
|
const resolvedModel = resolveModelForDefinition(definition.model, parentAgent);
|
|
11286
|
-
if (resolvedModel)
|
|
11287
|
-
|
|
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;
|
|
11288
12013
|
} else subAgent.innerAgent.state.model = parentInner.state.model;
|
|
11289
12014
|
subAgent.innerAgent.getApiKey = parentAgent.llmFacade.createGetApiKeyFn();
|
|
11290
12015
|
return;
|
|
@@ -11318,14 +12043,14 @@ function resolveModelForDefinition(model, parentAgent) {
|
|
|
11318
12043
|
}
|
|
11319
12044
|
} catch (error) {
|
|
11320
12045
|
const message = error instanceof Error ? error.message : String(error);
|
|
11321
|
-
log$
|
|
12046
|
+
log$10.warn(`解析 Agent 类型偏好模型 [${model}] 失败`, { error: message });
|
|
11322
12047
|
return;
|
|
11323
12048
|
}
|
|
11324
12049
|
}
|
|
11325
12050
|
|
|
11326
12051
|
//#endregion
|
|
11327
12052
|
//#region src/core/agent-team/agent-result-aggregator.ts
|
|
11328
|
-
const log$
|
|
12053
|
+
const log$9 = logger.child("agent-result-aggregator");
|
|
11329
12054
|
/** 零值 TokenUsage */
|
|
11330
12055
|
const ZERO_TOKEN = {
|
|
11331
12056
|
inputTokens: 0,
|
|
@@ -11355,7 +12080,7 @@ function aggregateResults(teamId, workerResults) {
|
|
|
11355
12080
|
estimatedCostUsd: sum.estimatedCostUsd + (r.tokenUsage?.estimatedCostUsd ?? 0)
|
|
11356
12081
|
}), { ...ZERO_TOKEN });
|
|
11357
12082
|
const summary = buildTeamSummary(workerResults);
|
|
11358
|
-
log$
|
|
12083
|
+
log$9.debug("Team 结果聚合完成", {
|
|
11359
12084
|
teamId,
|
|
11360
12085
|
total: workerResults.length,
|
|
11361
12086
|
succeeded,
|
|
@@ -11433,7 +12158,7 @@ function escapeMarkdownTable(text) {
|
|
|
11433
12158
|
|
|
11434
12159
|
//#endregion
|
|
11435
12160
|
//#region src/core/agent-team/agent-orchestrator.ts
|
|
11436
|
-
const log$
|
|
12161
|
+
const log$8 = logger.child("agent-orchestrator");
|
|
11437
12162
|
/**
|
|
11438
12163
|
* Agent 编排器
|
|
11439
12164
|
*
|
|
@@ -11466,7 +12191,7 @@ var AgentOrchestrator = class {
|
|
|
11466
12191
|
const startTime = Date.now();
|
|
11467
12192
|
const defaultType = getAgentTypeRegistry().getDefault();
|
|
11468
12193
|
if (!defaultType) throw new Error("无法获取默认 Agent 类型(general-purpose)");
|
|
11469
|
-
log$
|
|
12194
|
+
log$8.info("开始扁平并行执行", {
|
|
11470
12195
|
count: specs.length,
|
|
11471
12196
|
maxConcurrent: this.flatConfig.maxConcurrent,
|
|
11472
12197
|
hasContext: context != null
|
|
@@ -11474,7 +12199,7 @@ var AgentOrchestrator = class {
|
|
|
11474
12199
|
const allResults = [];
|
|
11475
12200
|
for (let i = 0; i < specs.length; i += this.flatConfig.maxConcurrent) {
|
|
11476
12201
|
const batch = specs.slice(i, i + this.flatConfig.maxConcurrent);
|
|
11477
|
-
log$
|
|
12202
|
+
log$8.debug("执行扁平批次", {
|
|
11478
12203
|
batchStart: i,
|
|
11479
12204
|
batchSize: batch.length,
|
|
11480
12205
|
totalSpecs: specs.length
|
|
@@ -11484,7 +12209,7 @@ var AgentOrchestrator = class {
|
|
|
11484
12209
|
}
|
|
11485
12210
|
const succeeded = allResults.filter((r) => r.status === "success").length;
|
|
11486
12211
|
const totalDuration = Date.now() - startTime;
|
|
11487
|
-
log$
|
|
12212
|
+
log$8.info("扁平并行执行完成", {
|
|
11488
12213
|
total: allResults.length,
|
|
11489
12214
|
succeeded,
|
|
11490
12215
|
failed: allResults.length - succeeded,
|
|
@@ -11514,6 +12239,7 @@ var AgentOrchestrator = class {
|
|
|
11514
12239
|
};
|
|
11515
12240
|
const depth = 1;
|
|
11516
12241
|
const instanceId = `flat-${spec.id}-${Date.now()}`;
|
|
12242
|
+
let stopRelay;
|
|
11517
12243
|
try {
|
|
11518
12244
|
const agent = createAgentFromType(definition, {
|
|
11519
12245
|
instanceId,
|
|
@@ -11533,6 +12259,7 @@ var AgentOrchestrator = class {
|
|
|
11533
12259
|
timeoutMs: this.flatConfig.taskTimeoutMs,
|
|
11534
12260
|
inheritContext: false
|
|
11535
12261
|
}, null, depth);
|
|
12262
|
+
stopRelay = this.#relayAgentProgress(agent, instanceId);
|
|
11536
12263
|
agent.systemPromptOverride = this.buildFlatSystemPrompt(spec, context);
|
|
11537
12264
|
const result = await Promise.race([agent.execute({
|
|
11538
12265
|
taskId: `flat-${spec.id}`,
|
|
@@ -11547,6 +12274,7 @@ var AgentOrchestrator = class {
|
|
|
11547
12274
|
const outputText = this.extractOutputText(result);
|
|
11548
12275
|
const isSuccess = typeof result === "object" && result !== null && "status" in result && result.status === "success";
|
|
11549
12276
|
instanceManager.transition(instanceId, isSuccess ? "completed" : "failed");
|
|
12277
|
+
stopRelay();
|
|
11550
12278
|
return {
|
|
11551
12279
|
specId: spec.id,
|
|
11552
12280
|
status: isSuccess ? "success" : "failure",
|
|
@@ -11556,11 +12284,14 @@ var AgentOrchestrator = class {
|
|
|
11556
12284
|
} catch (error) {
|
|
11557
12285
|
const duration = Date.now() - startTime;
|
|
11558
12286
|
const message = error instanceof Error ? error.message : String(error);
|
|
11559
|
-
log$
|
|
12287
|
+
log$8.warn("扁平子任务执行失败", {
|
|
11560
12288
|
specId: spec.id,
|
|
11561
12289
|
error: message,
|
|
11562
12290
|
duration
|
|
11563
12291
|
});
|
|
12292
|
+
try {
|
|
12293
|
+
stopRelay?.();
|
|
12294
|
+
} catch {}
|
|
11564
12295
|
try {
|
|
11565
12296
|
instanceManager.transition(instanceId, "failed");
|
|
11566
12297
|
} catch {}
|
|
@@ -11610,7 +12341,7 @@ var AgentOrchestrator = class {
|
|
|
11610
12341
|
const parentInstanceId = options?.parentInstanceId ?? "";
|
|
11611
12342
|
const depth = (parentInstanceId && instanceManager.get(parentInstanceId) ? instanceManager.get(parentInstanceId)?.depth ?? 0 : 0) + 1;
|
|
11612
12343
|
if (depth > this.teamConfig.maxGlobalDepth) {
|
|
11613
|
-
log$
|
|
12344
|
+
log$8.warn("Worker 创建被拒绝:超过全局最大深度", {
|
|
11614
12345
|
typeId,
|
|
11615
12346
|
depth,
|
|
11616
12347
|
maxGlobalDepth: this.teamConfig.maxGlobalDepth
|
|
@@ -11642,6 +12373,7 @@ var AgentOrchestrator = class {
|
|
|
11642
12373
|
const instanceId = `agent-${typeId}-${Date.now()}`;
|
|
11643
12374
|
const timeoutMs = options?.timeoutMs ?? this.flatConfig.taskTimeoutMs;
|
|
11644
12375
|
let worktreeInfo;
|
|
12376
|
+
let stopRelay;
|
|
11645
12377
|
try {
|
|
11646
12378
|
const agent = createAgentFromType(definition, {
|
|
11647
12379
|
instanceId,
|
|
@@ -11661,6 +12393,7 @@ var AgentOrchestrator = class {
|
|
|
11661
12393
|
timeoutMs,
|
|
11662
12394
|
inheritContext: options?.inheritContext ?? false
|
|
11663
12395
|
}, parentInstanceId || null, depth);
|
|
12396
|
+
stopRelay = this.#relayAgentProgress(agent, instanceId);
|
|
11664
12397
|
const promptCtx = {
|
|
11665
12398
|
taskDescription,
|
|
11666
12399
|
workdir: process.cwd()
|
|
@@ -11730,7 +12463,7 @@ var AgentOrchestrator = class {
|
|
|
11730
12463
|
} catch (error) {
|
|
11731
12464
|
const duration = Date.now() - startTime;
|
|
11732
12465
|
const message = error instanceof Error ? error.message : String(error);
|
|
11733
|
-
log$
|
|
12466
|
+
log$8.warn("Worker 执行失败", {
|
|
11734
12467
|
typeId,
|
|
11735
12468
|
instanceId,
|
|
11736
12469
|
error: message,
|
|
@@ -11762,6 +12495,9 @@ var AgentOrchestrator = class {
|
|
|
11762
12495
|
}
|
|
11763
12496
|
};
|
|
11764
12497
|
} finally {
|
|
12498
|
+
try {
|
|
12499
|
+
stopRelay?.();
|
|
12500
|
+
} catch {}
|
|
11765
12501
|
try {
|
|
11766
12502
|
const instance = instanceManager.get(instanceId);
|
|
11767
12503
|
if (instance) instance.agent.systemPromptOverride = null;
|
|
@@ -11770,12 +12506,12 @@ var AgentOrchestrator = class {
|
|
|
11770
12506
|
const wm = getWorktreeManager();
|
|
11771
12507
|
if (wm) try {
|
|
11772
12508
|
const cleanResult = await wm.autoCleanIfNoChanges(worktreeInfo.id);
|
|
11773
|
-
if (!cleanResult.cleaned) log$
|
|
12509
|
+
if (!cleanResult.cleaned) log$8.info("Worktree 有变更,保留", {
|
|
11774
12510
|
id: worktreeInfo.id,
|
|
11775
12511
|
path: cleanResult.worktreePath
|
|
11776
12512
|
});
|
|
11777
12513
|
} catch (err) {
|
|
11778
|
-
log$
|
|
12514
|
+
log$8.warn("Worktree 清理失败", {
|
|
11779
12515
|
id: worktreeInfo.id,
|
|
11780
12516
|
error: err instanceof Error ? err.message : String(err)
|
|
11781
12517
|
});
|
|
@@ -11795,7 +12531,7 @@ var AgentOrchestrator = class {
|
|
|
11795
12531
|
*/
|
|
11796
12532
|
async spawnTeam(taskDescription, workerSpecs) {
|
|
11797
12533
|
const teamId = `team-${Date.now()}-${++this.teamCounter}`;
|
|
11798
|
-
log$
|
|
12534
|
+
log$8.info("创建 Team", {
|
|
11799
12535
|
teamId,
|
|
11800
12536
|
workerCount: workerSpecs.length,
|
|
11801
12537
|
taskDescription
|
|
@@ -11814,6 +12550,41 @@ var AgentOrchestrator = class {
|
|
|
11814
12550
|
return aggregateResults(teamId, workerResults);
|
|
11815
12551
|
}
|
|
11816
12552
|
/**
|
|
12553
|
+
* 监听子 Agent 进度事件并中继到 InstanceManager
|
|
12554
|
+
*
|
|
12555
|
+
* 在子 Agent 执行工具调用时,更新其 AgentInstance 的 currentActivity,
|
|
12556
|
+
* 驱动 UI 状态栏实时展示子 Agent 当前操作。
|
|
12557
|
+
*
|
|
12558
|
+
* @param agent - 子 Agent 实例
|
|
12559
|
+
* @param instanceId - 对应的 InstanceManager 实例 ID
|
|
12560
|
+
* @returns 清理函数,调用后移除事件监听
|
|
12561
|
+
*/
|
|
12562
|
+
#relayAgentProgress(agent, instanceId) {
|
|
12563
|
+
const progressHandler = (event) => {
|
|
12564
|
+
const instanceManager = getAgentInstanceManager();
|
|
12565
|
+
const instance = instanceManager.get(instanceId);
|
|
12566
|
+
if (!instance) return;
|
|
12567
|
+
if (event.percent === 0) {
|
|
12568
|
+
const toolName = event.message;
|
|
12569
|
+
const prevUses = instance.currentActivity?.toolUses ?? 0;
|
|
12570
|
+
const parenIdx = toolName.indexOf("(");
|
|
12571
|
+
const namePart = parenIdx > 0 ? toolName.slice(0, parenIdx) : toolName;
|
|
12572
|
+
const argsPart = parenIdx > 0 ? toolName.slice(parenIdx + 1, -1) : void 0;
|
|
12573
|
+
const activity = {
|
|
12574
|
+
toolName: namePart,
|
|
12575
|
+
toolUses: prevUses + 1,
|
|
12576
|
+
...argsPart !== void 0 ? { args: argsPart } : {},
|
|
12577
|
+
startedAt: Date.now()
|
|
12578
|
+
};
|
|
12579
|
+
instanceManager.setActivity(instanceId, activity);
|
|
12580
|
+
} else if (event.percent === 100 || event.percent === -1) {}
|
|
12581
|
+
};
|
|
12582
|
+
agent.on(agent.EVENT_PROGRESS, progressHandler);
|
|
12583
|
+
return () => {
|
|
12584
|
+
agent.off(agent.EVENT_PROGRESS, progressHandler);
|
|
12585
|
+
};
|
|
12586
|
+
}
|
|
12587
|
+
/**
|
|
11817
12588
|
* 为扁平子 Agent 构建系统提示词
|
|
11818
12589
|
*/
|
|
11819
12590
|
buildFlatSystemPrompt(spec, context) {
|
|
@@ -11923,10 +12694,10 @@ const MODE_STRATEGIES = {
|
|
|
11923
12694
|
defaultAction: "deny",
|
|
11924
12695
|
maxAutoAllow: "low"
|
|
11925
12696
|
},
|
|
11926
|
-
/** normal:
|
|
12697
|
+
/** normal: 工具默认放行,仅内容级安全检查(shell 危险命令、敏感文件路径)触发审批 */
|
|
11927
12698
|
normal: {
|
|
11928
|
-
defaultAction: "
|
|
11929
|
-
maxAutoAllow: "
|
|
12699
|
+
defaultAction: "allow",
|
|
12700
|
+
maxAutoAllow: "high"
|
|
11930
12701
|
},
|
|
11931
12702
|
/** permissive: low/medium 直接 allow,high ask,critical deny */
|
|
11932
12703
|
permissive: {
|
|
@@ -12112,6 +12883,84 @@ function createReplBuiltinTools(webConfig, taskStore, skillConfig, parentAgent,
|
|
|
12112
12883
|
return tools;
|
|
12113
12884
|
}
|
|
12114
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
|
+
|
|
12115
12964
|
//#endregion
|
|
12116
12965
|
//#region src/cli/repl/theme.ts
|
|
12117
12966
|
/** 根据颜色开关获取 chalk 实例 */
|
|
@@ -12597,6 +13446,207 @@ function createLspTool(lspManager) {
|
|
|
12597
13446
|
};
|
|
12598
13447
|
}
|
|
12599
13448
|
|
|
13449
|
+
//#endregion
|
|
13450
|
+
//#region src/cli/repl/utils/markdown-formatter.ts
|
|
13451
|
+
/**
|
|
13452
|
+
* Markdown → ANSI 格式化器
|
|
13453
|
+
*
|
|
13454
|
+
* 将 LLM 返回的 Markdown 文本解析并应用 ANSI 样式(颜色、加粗、斜体等),
|
|
13455
|
+
* 使终端输出更易读。参考 Claude Code 的 formatToken 实现。
|
|
13456
|
+
*
|
|
13457
|
+
* 使用方式:
|
|
13458
|
+
* import { formatMarkdown } from '@/cli/repl/utils/markdown-formatter';
|
|
13459
|
+
* console.log(formatMarkdown('**hello** world'));
|
|
13460
|
+
*/
|
|
13461
|
+
let markedConfigured = false;
|
|
13462
|
+
function configureMarked() {
|
|
13463
|
+
if (markedConfigured) return;
|
|
13464
|
+
markedConfigured = true;
|
|
13465
|
+
marked.use({ tokenizer: { del() {} } });
|
|
13466
|
+
}
|
|
13467
|
+
const EOL = "\n";
|
|
13468
|
+
const LIST_ITEM_MARKER = "-";
|
|
13469
|
+
const BLOCKQUOTE_BAR = "│";
|
|
13470
|
+
/**
|
|
13471
|
+
* 格式化 Markdown 文本为 ANSI 样式字符串
|
|
13472
|
+
*
|
|
13473
|
+
* @param text - 原始 Markdown 文本
|
|
13474
|
+
* @param colorEnabled - 是否启用颜色(false 时仅保留结构格式化)
|
|
13475
|
+
* @returns 格式化后的字符串
|
|
13476
|
+
*/
|
|
13477
|
+
function formatMarkdown(text, colorEnabled = true) {
|
|
13478
|
+
if (!text) return "";
|
|
13479
|
+
configureMarked();
|
|
13480
|
+
const tokens = marked.lexer(text);
|
|
13481
|
+
const parts = [];
|
|
13482
|
+
for (const token of tokens) parts.push(formatToken(token, colorEnabled, 0, null, null));
|
|
13483
|
+
return parts.join("").trimEnd();
|
|
13484
|
+
}
|
|
13485
|
+
function c(colorEnabled) {
|
|
13486
|
+
if (colorEnabled) return chalk;
|
|
13487
|
+
const noColor = Object.create(chalk);
|
|
13488
|
+
noColor.level = 0;
|
|
13489
|
+
return noColor;
|
|
13490
|
+
}
|
|
13491
|
+
function formatToken(token, colorEnabled, listDepth, orderedListNumber, parent) {
|
|
13492
|
+
const ch = c(colorEnabled);
|
|
13493
|
+
switch (token.type) {
|
|
13494
|
+
case "blockquote": {
|
|
13495
|
+
const inner = token.tokens ? token.tokens.map((t) => formatToken(t, colorEnabled, 0, null, null)).join("") : "";
|
|
13496
|
+
const bar = ch.dim(BLOCKQUOTE_BAR);
|
|
13497
|
+
return inner.split(EOL).map((line) => line.trim() ? `${bar} ${ch.italic(line)}` : line).join(EOL);
|
|
13498
|
+
}
|
|
13499
|
+
case "code": {
|
|
13500
|
+
const lang = token.lang ? `${ch.dim(` ${token.lang}`)}` : "";
|
|
13501
|
+
const header = token.lang ? `${ch.dim("```")}${lang}${EOL}` : "";
|
|
13502
|
+
const footer = token.lang ? `${EOL}${ch.dim("```")}` : "";
|
|
13503
|
+
return `${header}${ch.dim(token.text)}${footer}${EOL}`;
|
|
13504
|
+
}
|
|
13505
|
+
case "codespan": return ch.cyan(token.text);
|
|
13506
|
+
case "em": {
|
|
13507
|
+
const inner = token.tokens ? token.tokens.map((t) => formatToken(t, colorEnabled, 0, null, token)).join("") : "";
|
|
13508
|
+
return ch.italic(inner);
|
|
13509
|
+
}
|
|
13510
|
+
case "strong": {
|
|
13511
|
+
const inner = token.tokens ? token.tokens.map((t) => formatToken(t, colorEnabled, 0, null, token)).join("") : "";
|
|
13512
|
+
return ch.bold(inner);
|
|
13513
|
+
}
|
|
13514
|
+
case "heading": {
|
|
13515
|
+
const inner = token.tokens ? token.tokens.map((t) => formatToken(t, colorEnabled, 0, null, null)).join("") : "";
|
|
13516
|
+
if (token.depth === 1) return `${ch.bold.italic.underline(inner)}${EOL}${EOL}`;
|
|
13517
|
+
return `${ch.bold(inner)}${EOL}${EOL}`;
|
|
13518
|
+
}
|
|
13519
|
+
case "hr": return ch.dim("─".repeat(50)) + EOL;
|
|
13520
|
+
case "image": return token.href;
|
|
13521
|
+
case "link": {
|
|
13522
|
+
const linkText = token.tokens ? token.tokens.map((t) => formatToken(t, colorEnabled, 0, null, token)).join("") : "";
|
|
13523
|
+
if (linkText && linkText !== token.href) return `${linkText} ${ch.dim(`(${token.href})`)}`;
|
|
13524
|
+
return ch.dim(token.href);
|
|
13525
|
+
}
|
|
13526
|
+
case "list": return token.items.map((item, index) => formatToken(item, colorEnabled, listDepth, token.ordered ? (token.start ?? 1) + index : null, token)).join("");
|
|
13527
|
+
case "list_item": {
|
|
13528
|
+
const marker = orderedListNumber !== null ? `${orderedListNumber}.` : LIST_ITEM_MARKER;
|
|
13529
|
+
return `${" ".repeat(listDepth)}${marker} ${(token.tokens ? token.tokens.map((t) => formatToken(t, colorEnabled, listDepth + 1, orderedListNumber, token)).join("") : "").trimStart()}`;
|
|
13530
|
+
}
|
|
13531
|
+
case "paragraph": return (token.tokens ? token.tokens.map((t) => formatToken(t, colorEnabled, 0, null, null)).join("") : "") + EOL;
|
|
13532
|
+
case "space": return EOL;
|
|
13533
|
+
case "br": return EOL;
|
|
13534
|
+
case "text":
|
|
13535
|
+
if (parent?.type === "list_item") {
|
|
13536
|
+
const marker = orderedListNumber !== null ? `${orderedListNumber}.` : LIST_ITEM_MARKER;
|
|
13537
|
+
return `${" ".repeat(listDepth)}${marker} ${token.tokens ? token.tokens.map((t) => formatToken(t, colorEnabled, listDepth, orderedListNumber, token)).join("") : token.text}${EOL}`;
|
|
13538
|
+
}
|
|
13539
|
+
return token.text;
|
|
13540
|
+
case "table": return formatTable(token, colorEnabled);
|
|
13541
|
+
case "escape": return token.text;
|
|
13542
|
+
case "def":
|
|
13543
|
+
case "html":
|
|
13544
|
+
case "del": return "";
|
|
13545
|
+
default: return "";
|
|
13546
|
+
}
|
|
13547
|
+
}
|
|
13548
|
+
/**
|
|
13549
|
+
* 格式化表格为对齐的 ASCII 表格
|
|
13550
|
+
*/
|
|
13551
|
+
function formatTable(token, colorEnabled) {
|
|
13552
|
+
const ch = c(colorEnabled);
|
|
13553
|
+
function getCellText(cellTokens) {
|
|
13554
|
+
if (!cellTokens) return "";
|
|
13555
|
+
return cellTokens.map((t) => formatToken(t, colorEnabled, 0, null, null)).join("");
|
|
13556
|
+
}
|
|
13557
|
+
const columnCount = token.header.length;
|
|
13558
|
+
const colWidths = [];
|
|
13559
|
+
for (let i = 0; i < columnCount; i++) {
|
|
13560
|
+
const headerCell = token.header[i];
|
|
13561
|
+
let maxW = ansiCellWidth(headerCell ? getCellText(headerCell.tokens) : "");
|
|
13562
|
+
for (const row of token.rows) {
|
|
13563
|
+
const cell = row[i];
|
|
13564
|
+
if (cell?.tokens) {
|
|
13565
|
+
const cellText = getCellText(cell.tokens);
|
|
13566
|
+
maxW = Math.max(maxW, ansiCellWidth(cellText));
|
|
13567
|
+
}
|
|
13568
|
+
}
|
|
13569
|
+
colWidths.push(Math.max(maxW, 3));
|
|
13570
|
+
}
|
|
13571
|
+
let output = "";
|
|
13572
|
+
output += "┌";
|
|
13573
|
+
for (let i = 0; i < columnCount; i++) {
|
|
13574
|
+
const cw = colWidths[i] ?? 3;
|
|
13575
|
+
output += "─".repeat(cw + 2) + "┬";
|
|
13576
|
+
}
|
|
13577
|
+
output = output.slice(0, -1) + "┐\n";
|
|
13578
|
+
output += "│ ";
|
|
13579
|
+
for (let i = 0; i < columnCount; i++) {
|
|
13580
|
+
const cell = token.header[i];
|
|
13581
|
+
const content = cell?.tokens ? cell.tokens.map((t) => formatToken(t, colorEnabled, 0, null, null)).join("") : "";
|
|
13582
|
+
const displayLen = ansiCellWidth(content);
|
|
13583
|
+
const pad = (colWidths[i] ?? 3) - displayLen;
|
|
13584
|
+
const align = token.align?.[i];
|
|
13585
|
+
output += align === "right" ? " ".repeat(pad) + ch.bold(content) : align === "center" ? " ".repeat(Math.floor(pad / 2)) + ch.bold(content) + " ".repeat(Math.ceil(pad / 2)) : ch.bold(content) + " ".repeat(pad);
|
|
13586
|
+
output += " │ ";
|
|
13587
|
+
}
|
|
13588
|
+
output = output.trimEnd() + EOL;
|
|
13589
|
+
output += "├";
|
|
13590
|
+
for (let i = 0; i < columnCount; i++) {
|
|
13591
|
+
const align = token.align?.[i];
|
|
13592
|
+
const left = align === "center" || align === "right" ? ":" : "─";
|
|
13593
|
+
const right = align === "center" || align === "left" ? ":" : "─";
|
|
13594
|
+
const cw = colWidths[i] ?? 3;
|
|
13595
|
+
output += `${left}${"─".repeat(cw)}${right}┼`;
|
|
13596
|
+
}
|
|
13597
|
+
output = `${output.slice(0, -1)}┤${EOL}`;
|
|
13598
|
+
for (const row of token.rows) {
|
|
13599
|
+
output += "│ ";
|
|
13600
|
+
for (let i = 0; i < columnCount; i++) {
|
|
13601
|
+
const cell = row[i];
|
|
13602
|
+
const content = cell?.tokens ? cell.tokens.map((t) => formatToken(t, colorEnabled, 0, null, null)).join("") : "";
|
|
13603
|
+
const displayLen = ansiCellWidth(content);
|
|
13604
|
+
const pad = (colWidths[i] ?? 3) - displayLen;
|
|
13605
|
+
const align = token.align?.[i];
|
|
13606
|
+
output += align === "right" ? " ".repeat(pad) + content : align === "center" ? " ".repeat(Math.floor(pad / 2)) + content + " ".repeat(Math.ceil(pad / 2)) : content + " ".repeat(pad);
|
|
13607
|
+
output += " │ ";
|
|
13608
|
+
}
|
|
13609
|
+
output = output.trimEnd() + EOL;
|
|
13610
|
+
}
|
|
13611
|
+
output += "└";
|
|
13612
|
+
for (let i = 0; i < columnCount; i++) {
|
|
13613
|
+
const cw = colWidths[i] ?? 3;
|
|
13614
|
+
output += "─".repeat(cw + 2) + "┴";
|
|
13615
|
+
}
|
|
13616
|
+
output = output.slice(0, -1) + "┘\n";
|
|
13617
|
+
return output + EOL;
|
|
13618
|
+
}
|
|
13619
|
+
/**
|
|
13620
|
+
* 计算字符串在终端中的可见宽度(考虑 CJK 和 emoji 占 2 列宽)
|
|
13621
|
+
*
|
|
13622
|
+
* ANSI 转义序列不计入宽度,中文字符/emoji 占 2 列,ASCII 占 1 列。
|
|
13623
|
+
*/
|
|
13624
|
+
function ansiCellWidth(str) {
|
|
13625
|
+
const ESC = String.fromCharCode(27);
|
|
13626
|
+
const plain = str.replace(new RegExp(`${ESC}\\[[0-9;]*m`, "g"), "");
|
|
13627
|
+
let width = 0;
|
|
13628
|
+
for (const ch of plain) {
|
|
13629
|
+
const code = ch.codePointAt(0) ?? 0;
|
|
13630
|
+
if (isZeroWidthChar(code)) continue;
|
|
13631
|
+
if (isWideChar(code)) width += 2;
|
|
13632
|
+
else width += 1;
|
|
13633
|
+
}
|
|
13634
|
+
return width;
|
|
13635
|
+
}
|
|
13636
|
+
/**
|
|
13637
|
+
* 判断 Unicode 码点是否为零宽字符(不可见 / 组合用字符)
|
|
13638
|
+
*
|
|
13639
|
+
* 这些字符在终端中不占据任何列宽,无论是 CJK 字体还是等宽字体下。
|
|
13640
|
+
* 如果在宽度计算中按 1 或 2 计算,会导致表格列对齐错误。
|
|
13641
|
+
*/
|
|
13642
|
+
function isZeroWidthChar(code) {
|
|
13643
|
+
return code === 8203 || code === 8204 || code === 8205 || code === 8206 || code === 8207 || code === 8288 || code >= 8289 && code <= 8292 || code === 65038 || code === 65039 || code >= 768 && code <= 879 || code >= 7616 && code <= 7679 || code >= 8400 && code <= 8447 || code >= 65056 && code <= 65071;
|
|
13644
|
+
}
|
|
13645
|
+
/** 判断 Unicode 码点是否为终端宽字符(CJK / 全角 / emoji) */
|
|
13646
|
+
function isWideChar(code) {
|
|
13647
|
+
return code >= 4352 && code <= 4447 || code >= 11904 && code <= 12287 || code >= 12288 && code <= 13311 || code >= 13312 && code <= 19903 || code >= 19968 && code <= 40959 || code >= 40960 && code <= 42191 || code >= 44032 && code <= 55215 || code >= 63744 && code <= 64255 || code >= 65040 && code <= 65049 || code >= 65072 && code <= 65135 || code >= 65281 && code <= 65376 || code >= 65504 && code <= 65510 || code >= 126976 && code <= 131071 || code >= 131072 && code <= 196607 || code >= 196608 && code <= 262143 || code === 169 || code === 174 || code >= 8192 && code <= 8303 || code >= 8448 && code <= 10175 || code >= 11008 && code <= 11263 || code >= 10496 && code <= 10623 || code === 12336 || code === 12349 || code === 12951 || code === 12952 || code === 8986 || code === 8987 || code === 9193 || code === 9196 || code === 9200 || code === 9203 || code === 9725 || code === 9726 || code === 9748 || code === 9749 || code === 9800 || code === 9811 || code === 9855 || code === 9875 || code === 9889 || code === 9898 || code === 9899 || code === 9917 || code === 9918 || code === 9924 || code === 9925 || code === 9934 || code === 9940 || code === 9962 || code === 9970 || code === 9971 || code === 9973 || code === 9978 || code === 9981 || code === 9986 || code === 9989 || code === 9992 || code === 9993 || code === 9994 || code === 9995 || code === 9996 || code === 9997 || code === 9999 || code === 10002 || code === 10004 || code === 10006 || code === 10013 || code === 10017 || code === 10024 || code === 10035 || code === 10036 || code === 10052 || code === 10055 || code === 10060 || code === 10062 || code === 10067 || code === 10068 || code === 10069 || code === 10071 || code === 10083 || code === 10084 || code === 10133 || code === 10134 || code === 10135 || code === 10145 || code === 10160 || code === 10175 || code === 10548 || code === 10549 || code === 11013 || code === 11014 || code === 11015 || code === 11035 || code === 11036 || code === 11088 || code === 11093 || code === 11203 || code === 11204;
|
|
13648
|
+
}
|
|
13649
|
+
|
|
12600
13650
|
//#endregion
|
|
12601
13651
|
//#region src/config/types.ts
|
|
12602
13652
|
/**
|
|
@@ -13603,6 +14653,7 @@ function ensureTaskDir() {
|
|
|
13603
14653
|
}
|
|
13604
14654
|
var TaskStore = class {
|
|
13605
14655
|
tasks = /* @__PURE__ */ new Map();
|
|
14656
|
+
changeCallbacks = [];
|
|
13606
14657
|
/** 读取完整任务列表 */
|
|
13607
14658
|
read() {
|
|
13608
14659
|
return Array.from(this.tasks.values());
|
|
@@ -13646,6 +14697,24 @@ var TaskStore = class {
|
|
|
13646
14697
|
return this.tasks.size > 0;
|
|
13647
14698
|
}
|
|
13648
14699
|
/**
|
|
14700
|
+
* 注册任务变更监听
|
|
14701
|
+
*
|
|
14702
|
+
* @param callback - 任务列表变更时回调
|
|
14703
|
+
* @returns unsubscribe 函数,组件销毁时调用以清理
|
|
14704
|
+
*/
|
|
14705
|
+
onChange(callback) {
|
|
14706
|
+
this.changeCallbacks.push(callback);
|
|
14707
|
+
return () => {
|
|
14708
|
+
this.changeCallbacks = this.changeCallbacks.filter((cb) => cb !== callback);
|
|
14709
|
+
};
|
|
14710
|
+
}
|
|
14711
|
+
/** 通知所有监听者(write/update/clear 操作后调用) */
|
|
14712
|
+
notifyChange() {
|
|
14713
|
+
for (const cb of this.changeCallbacks) try {
|
|
14714
|
+
cb();
|
|
14715
|
+
} catch {}
|
|
14716
|
+
}
|
|
14717
|
+
/**
|
|
13649
14718
|
* 全量替换任务列表
|
|
13650
14719
|
*
|
|
13651
14720
|
* @param items - 新任务列表
|
|
@@ -13675,11 +14744,13 @@ var TaskStore = class {
|
|
|
13675
14744
|
updatedAt: now
|
|
13676
14745
|
};
|
|
13677
14746
|
if (item.description !== void 0) taskItem.description = item.description;
|
|
14747
|
+
if (item.dependencies !== void 0) taskItem.dependencies = item.dependencies;
|
|
13678
14748
|
newTasks.set(item.id, taskItem);
|
|
13679
14749
|
}
|
|
13680
14750
|
if (inProgressCount > 1) return `不允许同时有 ${inProgressCount} 个进行中的任务,请先将当前任务完成或取消后再开始新任务`;
|
|
13681
14751
|
this.tasks = newTasks;
|
|
13682
14752
|
this.persist();
|
|
14753
|
+
this.notifyChange();
|
|
13683
14754
|
return null;
|
|
13684
14755
|
}
|
|
13685
14756
|
/**
|
|
@@ -13705,12 +14776,14 @@ var TaskStore = class {
|
|
|
13705
14776
|
};
|
|
13706
14777
|
this.tasks.set(id, updated);
|
|
13707
14778
|
this.persist();
|
|
14779
|
+
this.notifyChange();
|
|
13708
14780
|
return null;
|
|
13709
14781
|
}
|
|
13710
14782
|
/** 清空所有任务 */
|
|
13711
14783
|
clear() {
|
|
13712
14784
|
this.tasks.clear();
|
|
13713
14785
|
this.persist();
|
|
14786
|
+
this.notifyChange();
|
|
13714
14787
|
}
|
|
13715
14788
|
/** 持久化到文件系统 */
|
|
13716
14789
|
persist() {
|
|
@@ -13761,7 +14834,7 @@ var TaskStore = class {
|
|
|
13761
14834
|
const lines = ["## 当前任务列表"];
|
|
13762
14835
|
for (const task of active) {
|
|
13763
14836
|
const marker = task.status === "in_progress" ? "▶" : "○";
|
|
13764
|
-
lines.push(` ${marker}
|
|
14837
|
+
lines.push(` ${marker} #${task.id} ${task.subject}`);
|
|
13765
14838
|
}
|
|
13766
14839
|
return lines.join("\n");
|
|
13767
14840
|
}
|
|
@@ -14863,6 +15936,8 @@ var ReplSession = class {
|
|
|
14863
15936
|
tui;
|
|
14864
15937
|
editor;
|
|
14865
15938
|
outputArea;
|
|
15939
|
+
taskStatusBar;
|
|
15940
|
+
agentStatusBar;
|
|
14866
15941
|
options;
|
|
14867
15942
|
_state = "idle";
|
|
14868
15943
|
parser;
|
|
@@ -14898,6 +15973,7 @@ var ReplSession = class {
|
|
|
14898
15973
|
auditLogger;
|
|
14899
15974
|
secretRedactor;
|
|
14900
15975
|
skillGuard;
|
|
15976
|
+
skillWatcher;
|
|
14901
15977
|
/** 交互式提问管理器 */
|
|
14902
15978
|
questionManager;
|
|
14903
15979
|
stats = {
|
|
@@ -14927,10 +16003,19 @@ var ReplSession = class {
|
|
|
14927
16003
|
"tui.select.cancel": ["escape"],
|
|
14928
16004
|
"tui.select.confirm": ["enter"]
|
|
14929
16005
|
});
|
|
16006
|
+
this.taskStore = new TaskStore();
|
|
14930
16007
|
this.outputArea = new OutputArea();
|
|
16008
|
+
this.agentStatusBar = new AgentStatusBar();
|
|
16009
|
+
this.taskStatusBar = new TaskStatusBar(this.taskStore);
|
|
16010
|
+
this.taskStore.onChange(() => {
|
|
16011
|
+
this.taskStatusBar.onTasksChanged();
|
|
16012
|
+
this.tui.requestRender();
|
|
16013
|
+
});
|
|
14931
16014
|
this.editor = new ZapmycoEditor(this.tui, theme.editorTheme);
|
|
14932
16015
|
const root = new Container();
|
|
14933
16016
|
root.addChild(this.outputArea);
|
|
16017
|
+
root.addChild(this.taskStatusBar);
|
|
16018
|
+
root.addChild(this.agentStatusBar);
|
|
14934
16019
|
root.addChild(this.editor);
|
|
14935
16020
|
this.tui.addChild(root);
|
|
14936
16021
|
this.tui.setFocus(this.editor);
|
|
@@ -14940,8 +16025,6 @@ var ReplSession = class {
|
|
|
14940
16025
|
this.history = new HistoryStore(this.options.maxHistorySize);
|
|
14941
16026
|
for (const entry of this.history.getAll()) this.editor.addToHistory(entry.input);
|
|
14942
16027
|
this.agent = this.createReplAgent();
|
|
14943
|
-
this.taskStore = new TaskStore();
|
|
14944
|
-
this.taskStore.load();
|
|
14945
16028
|
this.cronScheduler = new CronScheduler(getCronStore(), { isIdle: () => this._state === "idle" });
|
|
14946
16029
|
this.cronScheduler.start();
|
|
14947
16030
|
this.initWorktreeManager();
|
|
@@ -14952,6 +16035,7 @@ var ReplSession = class {
|
|
|
14952
16035
|
log.warn("记忆快照冻结失败,将使用空快照", { error: err instanceof Error ? err.message : String(err) });
|
|
14953
16036
|
this.agent.memorySnapshot = "";
|
|
14954
16037
|
});
|
|
16038
|
+
this.skillWatcher = new SkillWatcher();
|
|
14955
16039
|
if (this.config.skill?.enabled !== false) this.initSkills();
|
|
14956
16040
|
this.initSecurity();
|
|
14957
16041
|
this.questionManager = getQuestionManager();
|
|
@@ -15013,6 +16097,7 @@ var ReplSession = class {
|
|
|
15013
16097
|
this.cronScheduler.stop();
|
|
15014
16098
|
this.cronScheduler = null;
|
|
15015
16099
|
}
|
|
16100
|
+
this.skillWatcher.stop();
|
|
15016
16101
|
if (this.auditLogger) this.auditLogger.destroy();
|
|
15017
16102
|
if (this.mcpManager) {
|
|
15018
16103
|
await this.mcpManager.shutdown();
|
|
@@ -15041,6 +16126,36 @@ var ReplSession = class {
|
|
|
15041
16126
|
getHistoryStore() {
|
|
15042
16127
|
return this.history;
|
|
15043
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
|
+
}
|
|
15044
16159
|
/** 获取会话统计 */
|
|
15045
16160
|
getStats() {
|
|
15046
16161
|
return { ...this.stats };
|
|
@@ -15128,12 +16243,13 @@ var ReplSession = class {
|
|
|
15128
16243
|
/**
|
|
15129
16244
|
* 执行用户目标 — 通过 Agent 执行并流式输出回复
|
|
15130
16245
|
*/
|
|
15131
|
-
async executeGoal(rawInput) {
|
|
16246
|
+
async executeGoal(rawInput, displayLabel) {
|
|
15132
16247
|
const startTime = Date.now();
|
|
15133
16248
|
let historyEntry;
|
|
15134
16249
|
const taskId = `task-${Date.now()}`;
|
|
15135
16250
|
const colorEnabled = this.options.color;
|
|
15136
16251
|
const userStyle = (s) => colorEnabled ? chalk.bold.cyan(s) : s;
|
|
16252
|
+
const markdownFormattingEnabled = this.config.cli.markdownFormatting !== false;
|
|
15137
16253
|
const responseStyle = (s) => s;
|
|
15138
16254
|
const toolStyle = (s) => colorEnabled ? chalk.yellow(s) : s;
|
|
15139
16255
|
const execStyle = (s) => colorEnabled ? chalk.cyan(s) : s;
|
|
@@ -15143,6 +16259,8 @@ var ReplSession = class {
|
|
|
15143
16259
|
let spinnerActive = true;
|
|
15144
16260
|
let spinnerInterval;
|
|
15145
16261
|
let thinkingElapsedInterval;
|
|
16262
|
+
/** Token 信息推送定时器 */
|
|
16263
|
+
let tokenPushInterval;
|
|
15146
16264
|
try {
|
|
15147
16265
|
this._state = "executing";
|
|
15148
16266
|
this.updateStatsState();
|
|
@@ -15156,19 +16274,20 @@ var ReplSession = class {
|
|
|
15156
16274
|
goalId: `goal-${startTime}`,
|
|
15157
16275
|
rawInput
|
|
15158
16276
|
});
|
|
15159
|
-
this.outputArea.append([userStyle(rawInput), LOADING_FRAMES[0] ?? ""]);
|
|
16277
|
+
this.outputArea.append([userStyle(displayLabel ?? rawInput), LOADING_FRAMES$1[0] ?? ""]);
|
|
15160
16278
|
let spinnerFrame = 0;
|
|
15161
16279
|
spinnerActive = true;
|
|
15162
16280
|
spinnerInterval = setInterval(() => {
|
|
15163
16281
|
if (!spinnerActive) return;
|
|
15164
|
-
spinnerFrame = (spinnerFrame + 1) % LOADING_FRAMES.length;
|
|
15165
|
-
this.outputArea.replaceLastLine(LOADING_FRAMES[spinnerFrame] ?? "");
|
|
16282
|
+
spinnerFrame = (spinnerFrame + 1) % LOADING_FRAMES$1.length;
|
|
16283
|
+
this.outputArea.replaceLastLine(LOADING_FRAMES$1[spinnerFrame] ?? "");
|
|
15166
16284
|
this.tui.requestRender();
|
|
15167
16285
|
}, 100);
|
|
15168
16286
|
let spinnerStopped = false;
|
|
15169
16287
|
let outputAccumulator = "";
|
|
15170
16288
|
let thinkingAccumulator = "";
|
|
15171
16289
|
let streamMode = "response";
|
|
16290
|
+
let responseLineIndex = null;
|
|
15172
16291
|
const thinkingDisplayMode = this.config.cli.thinkingDisplay ?? "collapse";
|
|
15173
16292
|
let thinkingHeaderLineIndex = null;
|
|
15174
16293
|
let thinkingContentLineCount = 0;
|
|
@@ -15215,20 +16334,20 @@ var ReplSession = class {
|
|
|
15215
16334
|
streamMode = "response";
|
|
15216
16335
|
thinkingAccumulator = "";
|
|
15217
16336
|
outputAccumulator = event.text;
|
|
15218
|
-
this.outputArea.replaceLastLine(responseStyle(outputAccumulator));
|
|
16337
|
+
responseLineIndex = this.outputArea.replaceLastLine(responseStyle(outputAccumulator));
|
|
15219
16338
|
} else if (streamMode !== "response") {
|
|
15220
16339
|
streamMode = "response";
|
|
15221
16340
|
if (thinkingElapsedInterval) {
|
|
15222
16341
|
clearInterval(thinkingElapsedInterval);
|
|
15223
16342
|
thinkingElapsedInterval = void 0;
|
|
15224
16343
|
}
|
|
15225
|
-
if (thinkingHeaderLineIndex !== null && thinkingDisplayMode === "collapse") {
|
|
15226
|
-
const elapsed = ((Date.now() - thinkingStartTimeMs) / 1e3).toFixed(1);
|
|
15227
|
-
this.outputArea.updateLine(thinkingHeaderLineIndex, dimStyle(` \u2234 Thinking (${elapsed}s)`));
|
|
15228
|
-
}
|
|
15229
16344
|
thinkingAccumulator = "";
|
|
15230
16345
|
outputAccumulator = event.text;
|
|
15231
|
-
|
|
16346
|
+
if (thinkingHeaderLineIndex !== null) {
|
|
16347
|
+
this.outputArea.spliceLines(thinkingHeaderLineIndex, 1, [responseStyle(outputAccumulator)]);
|
|
16348
|
+
responseLineIndex = thinkingHeaderLineIndex;
|
|
16349
|
+
thinkingHeaderLineIndex = null;
|
|
16350
|
+
} else responseLineIndex = this.outputArea.append([responseStyle(outputAccumulator)]);
|
|
15232
16351
|
} else {
|
|
15233
16352
|
outputAccumulator += event.text;
|
|
15234
16353
|
this.outputArea.replaceLastLine(responseStyle(outputAccumulator));
|
|
@@ -15245,8 +16364,8 @@ var ReplSession = class {
|
|
|
15245
16364
|
spinnerStopped = true;
|
|
15246
16365
|
spinnerActive = false;
|
|
15247
16366
|
clearInterval(spinnerInterval);
|
|
15248
|
-
thinkingHeaderLineIndex = this.outputArea.replaceLastLine(dimStyle(` ${LOADING_FRAMES[0] ?? ""} Thinking...`));
|
|
15249
|
-
} else thinkingHeaderLineIndex = this.outputArea.append([dimStyle(` ${LOADING_FRAMES[0] ?? ""} Thinking...`)]);
|
|
16367
|
+
thinkingHeaderLineIndex = this.outputArea.replaceLastLine(dimStyle(` ${LOADING_FRAMES$1[0] ?? ""} Thinking...`));
|
|
16368
|
+
} else thinkingHeaderLineIndex = this.outputArea.append([dimStyle(` ${LOADING_FRAMES$1[0] ?? ""} Thinking...`)]);
|
|
15250
16369
|
thinkingAccumulator = event.text;
|
|
15251
16370
|
thinkingContentLineCount = 0;
|
|
15252
16371
|
if (thinkingDisplayMode === "expand") {
|
|
@@ -15258,7 +16377,7 @@ var ReplSession = class {
|
|
|
15258
16377
|
if (thinkingHeaderLineIndex === null) return;
|
|
15259
16378
|
if (thinkingContentLineCount > 0) return;
|
|
15260
16379
|
const elapsed = ((Date.now() - thinkingStartTimeMs) / 1e3).toFixed(1);
|
|
15261
|
-
const frame = LOADING_FRAMES[thinkingFrame % LOADING_FRAMES.length];
|
|
16380
|
+
const frame = LOADING_FRAMES$1[thinkingFrame % LOADING_FRAMES$1.length];
|
|
15262
16381
|
thinkingFrame++;
|
|
15263
16382
|
this.outputArea.updateLine(thinkingHeaderLineIndex, dimStyle(` ${frame} Thinking... (${elapsed}s)`));
|
|
15264
16383
|
this.tui.requestRender();
|
|
@@ -15286,16 +16405,17 @@ var ReplSession = class {
|
|
|
15286
16405
|
if (thinkingElapsedInterval && streamMode === "thinking") {
|
|
15287
16406
|
clearInterval(thinkingElapsedInterval);
|
|
15288
16407
|
thinkingElapsedInterval = void 0;
|
|
15289
|
-
if (thinkingHeaderLineIndex !== null
|
|
15290
|
-
|
|
15291
|
-
|
|
16408
|
+
if (thinkingHeaderLineIndex !== null) {
|
|
16409
|
+
this.outputArea.spliceLines(thinkingHeaderLineIndex, 1, []);
|
|
16410
|
+
thinkingHeaderLineIndex = null;
|
|
15292
16411
|
this.tui.requestRender();
|
|
15293
16412
|
}
|
|
15294
16413
|
}
|
|
15295
16414
|
if (event.message.startsWith("Exec(")) {
|
|
15296
16415
|
execMessage = event.message;
|
|
15297
16416
|
execLineIndex = this.outputArea.append([execStyle(` ⏺ ${event.message}`)]);
|
|
15298
|
-
} else
|
|
16417
|
+
} else if (event.message.startsWith("TaskManage(")) log.debug("TaskManage 工具调用已记录,跳过 TUI 展示");
|
|
16418
|
+
else this.outputArea.append([toolStyle(` → ${event.message}`)]);
|
|
15299
16419
|
this.tui.requestRender();
|
|
15300
16420
|
} else if (event.percent === 100 && execLineIndex !== void 0 && execMessage) {
|
|
15301
16421
|
if (event.message.startsWith("工具 Exec")) {
|
|
@@ -15314,6 +16434,15 @@ var ReplSession = class {
|
|
|
15314
16434
|
this.currentTaskId = taskId;
|
|
15315
16435
|
const originalOnToggleThinking = this.editor.onToggleThinking;
|
|
15316
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);
|
|
15317
16446
|
log.debug("开始通过 Agent 执行目标", {
|
|
15318
16447
|
taskId,
|
|
15319
16448
|
taskDescription: rawInput.slice(0, 100)
|
|
@@ -15360,8 +16489,8 @@ var ReplSession = class {
|
|
|
15360
16489
|
spinnerActive = true;
|
|
15361
16490
|
spinnerInterval = setInterval(() => {
|
|
15362
16491
|
if (!spinnerActive) return;
|
|
15363
|
-
spinnerFrame = (spinnerFrame + 1) % LOADING_FRAMES.length;
|
|
15364
|
-
this.outputArea.replaceLastLine(LOADING_FRAMES[spinnerFrame] ?? "");
|
|
16492
|
+
spinnerFrame = (spinnerFrame + 1) % LOADING_FRAMES$1.length;
|
|
16493
|
+
this.outputArea.replaceLastLine(LOADING_FRAMES$1[spinnerFrame] ?? "");
|
|
15365
16494
|
this.tui.requestRender();
|
|
15366
16495
|
}, 100);
|
|
15367
16496
|
}
|
|
@@ -15419,6 +16548,24 @@ var ReplSession = class {
|
|
|
15419
16548
|
status: taskResult.status
|
|
15420
16549
|
});
|
|
15421
16550
|
}
|
|
16551
|
+
if (markdownFormattingEnabled && taskResult.status === "success") {
|
|
16552
|
+
if (outputAccumulator && responseLineIndex !== null) {
|
|
16553
|
+
const formatted = formatMarkdown(outputAccumulator, colorEnabled);
|
|
16554
|
+
if (formatted) {
|
|
16555
|
+
const formattedLines = formatted.split("\n");
|
|
16556
|
+
this.outputArea.spliceLines(responseLineIndex, 1, formattedLines);
|
|
16557
|
+
this.tui.requestRender();
|
|
16558
|
+
}
|
|
16559
|
+
} else if (spinnerStopped === false && outputText) {
|
|
16560
|
+
const formatted = formatMarkdown(outputText, colorEnabled);
|
|
16561
|
+
if (formatted) {
|
|
16562
|
+
const lines = formatted.split("\n");
|
|
16563
|
+
this.outputArea.replaceLastLine(lines[0] ?? "");
|
|
16564
|
+
if (lines.length > 1) this.outputArea.append(lines.slice(1));
|
|
16565
|
+
this.tui.requestRender();
|
|
16566
|
+
}
|
|
16567
|
+
}
|
|
16568
|
+
}
|
|
15422
16569
|
this.outputArea.append([""]);
|
|
15423
16570
|
const duration = Date.now() - startTime;
|
|
15424
16571
|
const result = {
|
|
@@ -15535,6 +16682,12 @@ var ReplSession = class {
|
|
|
15535
16682
|
clearInterval(thinkingElapsedInterval);
|
|
15536
16683
|
thinkingElapsedInterval = void 0;
|
|
15537
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);
|
|
15538
16691
|
this._state = "idle";
|
|
15539
16692
|
this.updateStatsState();
|
|
15540
16693
|
this.editor.setExecuting(false);
|
|
@@ -15797,33 +16950,90 @@ var ReplSession = class {
|
|
|
15797
16950
|
}, process.cwd()).then((entries) => {
|
|
15798
16951
|
setSkillEntries(entries);
|
|
15799
16952
|
this.agent.skillEntries = entries;
|
|
15800
|
-
|
|
15801
|
-
this.agent.skillPrompt = snapshot.prompt;
|
|
16953
|
+
this.agent.resetSentSkills();
|
|
15802
16954
|
this._registerSkillCommands(entries);
|
|
15803
16955
|
this.buildAutocompleteProvider();
|
|
15804
|
-
|
|
15805
|
-
const summary = this.skillGuard.getThreatSummary(results);
|
|
15806
|
-
if (summary.danger > 0 || summary.warning > 0) {
|
|
15807
|
-
log.warn("Skill 威胁扫描完成", summary);
|
|
15808
|
-
for (const result of results) if (!result.passed) for (const v of result.violations) eventBus.emit("security:violation", {
|
|
15809
|
-
toolId: "Skill",
|
|
15810
|
-
type: "skill-threat",
|
|
15811
|
-
message: `[${result.skillName}] ${v.reason}`,
|
|
15812
|
-
params: {
|
|
15813
|
-
skillPath: result.skillPath,
|
|
15814
|
-
ruleId: v.ruleId
|
|
15815
|
-
}
|
|
15816
|
-
});
|
|
15817
|
-
}
|
|
16956
|
+
this.scanSkillSecurity(entries);
|
|
15818
16957
|
log.info("Skill 系统初始化完成", {
|
|
15819
|
-
count:
|
|
15820
|
-
names:
|
|
16958
|
+
count: entries.length,
|
|
16959
|
+
names: entries.map((e) => e.skill.name)
|
|
15821
16960
|
});
|
|
16961
|
+
this.startSkillWatcher();
|
|
15822
16962
|
}).catch((err) => {
|
|
15823
16963
|
log.error("Skill 加载失败", { error: err instanceof Error ? err.message : String(err) });
|
|
15824
16964
|
});
|
|
15825
16965
|
}
|
|
15826
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
|
+
/**
|
|
15827
17037
|
* 为 user-invocable 技能注册斜杠命令
|
|
15828
17038
|
*
|
|
15829
17039
|
* 将每个用户可调用的技能注册为 /skill-name 格式的 CLI 命令。
|
|
@@ -15841,10 +17051,17 @@ var ReplSession = class {
|
|
|
15841
17051
|
description: spec.description,
|
|
15842
17052
|
aliases: [],
|
|
15843
17053
|
usage: spec.name,
|
|
17054
|
+
source: "skill",
|
|
15844
17055
|
handler: async (args) => {
|
|
15845
17056
|
const argsStr = args.join(" ");
|
|
15846
|
-
const
|
|
15847
|
-
|
|
17057
|
+
const entry = getSkillEntries().find((e) => sanitizeSkillCommandName(e.skill.name) === spec.name);
|
|
17058
|
+
if (entry) {
|
|
17059
|
+
const skillContent = await formatSkillContent(entry.skill, argsStr);
|
|
17060
|
+
await this.executeGoal(skillContent, `/${spec.name}`);
|
|
17061
|
+
} else {
|
|
17062
|
+
const goalInput = argsStr ? `请使用 /${spec.name} 技能,参数: ${argsStr}` : `请使用 /${spec.name} 技能`;
|
|
17063
|
+
await this.executeGoal(goalInput);
|
|
17064
|
+
}
|
|
15848
17065
|
}
|
|
15849
17066
|
});
|
|
15850
17067
|
}
|
|
@@ -15884,7 +17101,15 @@ var ReplSession = class {
|
|
|
15884
17101
|
this.editor.onCtrlD = () => {
|
|
15885
17102
|
this.shutdown("收到 EOF (Ctrl+D)");
|
|
15886
17103
|
};
|
|
17104
|
+
this.editor.onToggleAgentBar = () => {
|
|
17105
|
+
this.agentStatusBar.toggle();
|
|
17106
|
+
this.tui.requestRender();
|
|
17107
|
+
};
|
|
15887
17108
|
this.editor.onOpenEditor = () => this.openInEditor();
|
|
17109
|
+
this.editor.onToggleTasks = () => {
|
|
17110
|
+
this.taskStatusBar.toggle();
|
|
17111
|
+
this.tui.requestRender();
|
|
17112
|
+
};
|
|
15888
17113
|
}
|
|
15889
17114
|
/** 设置信号处理 */
|
|
15890
17115
|
setupSignalHandlers() {
|
|
@@ -15895,6 +17120,19 @@ var ReplSession = class {
|
|
|
15895
17120
|
eventBus.on("system:shutdown", ({ reason }) => {
|
|
15896
17121
|
log.debug(`收到系统关闭信号: ${reason ?? "未知"}`);
|
|
15897
17122
|
});
|
|
17123
|
+
const instanceManager = getAgentInstanceManager();
|
|
17124
|
+
const refreshStatusBar = () => {
|
|
17125
|
+
this.agentStatusBar.invalidate();
|
|
17126
|
+
this.tui.requestRender();
|
|
17127
|
+
};
|
|
17128
|
+
instanceManager.on("instance:registered", refreshStatusBar);
|
|
17129
|
+
instanceManager.on("instance:transitioned", refreshStatusBar);
|
|
17130
|
+
instanceManager.on("instance:activity", refreshStatusBar);
|
|
17131
|
+
eventBus.on("system:shutdown", () => {
|
|
17132
|
+
instanceManager.off("instance:registered", refreshStatusBar);
|
|
17133
|
+
instanceManager.off("instance:transitioned", refreshStatusBar);
|
|
17134
|
+
instanceManager.off("instance:activity", refreshStatusBar);
|
|
17135
|
+
});
|
|
15898
17136
|
}
|
|
15899
17137
|
/** 取消当前正在执行的任务 */
|
|
15900
17138
|
cancelCurrentTask() {
|