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.
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { A as configureLogger, B as buildSkillSnapshot, 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-BVhTlDtc.mjs";
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$20 = logger.child("repl:command-registry");
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$20.warn(`命令 "${canonicalName}" 已存在,将被覆盖`);
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$20.warn("dispatch 收到了非 command 类型的输入");
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$20.error(`命令 /${cmd.name} 执行出错`, {}, error);
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
- const log$19 = logger.child("agent-instance-manager");
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$19.debug("注册 Agent 实例", {
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$19.warn("状态转换失败:实例不存在", { instanceId });
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$19.warn("状态转换拒绝", {
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$19.debug("Agent 实例状态转换", {
586
+ log$20.debug("Agent 实例状态转换", {
547
587
  instanceId,
548
588
  typeId: instance.typeId,
549
- from: instance.status,
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$19.debug("Agent 实例已清理", { instanceId });
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$18 = logger.child("agent-type-registry");
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$18.info("覆盖已注册的 Agent 类型", {
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$18.debug("注册 Agent 类型", {
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$18.info("批量注册 Agent 类型", { count: definitions.length });
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$18.debug("注销 Agent 类型", { typeId });
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$18.info("加载内置 Agent 类型", { count: BUILTIN_AGENT_TYPES.length });
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.clearOutput();
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 / Ctrl+Y 回调(展开/折叠 thinking 内容) */
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")) && this.onOpenEditor) {
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.ctrl("t")) && this.onToggleThinking) {
3128
- this.onToggleThinking();
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$17 = logger.child("cron:scheduler");
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$17.info(`调度器启动,加载 ${loadedJobs.length} 个 durable 任务`);
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$17.info("调度器已停止");
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$17.info("任务已添加", {
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$17.info(`跳过 ${toDelete.length} 个错过的一次性任务(超出补发上限)`);
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$17.info("任务已过期并触发最后一次", { id: job.id });
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$16 = logger.child("cron:store");
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$16.warn("存储文件格式无效(非数组),将使用空列表");
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$16.warn("加载定时任务文件失败,将使用空列表", { error: err instanceof Error ? err.message : String(err) });
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$16.warn(`跳过 ${raw.length - valid.length} 个无效任务条目`);
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$15 = logger.child("history:store");
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$15.debug("历史记录已加载", {
4495
+ log$16.debug("历史记录已加载", {
3954
4496
  count: this.entries.length,
3955
4497
  nextId: this.nextId
3956
4498
  });
3957
4499
  }
3958
4500
  } catch {
3959
- log$15.debug("无历史文件或加载失败,使用空历史");
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$15.warn("历史记录保存失败", { error: err instanceof Error ? err.message : String(err) });
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$14 = logger.child("background-store");
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$14.debug("后台任务已加载", {
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$14.warn("后台任务加载失败", {
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$14.error("后台任务持久化失败", {
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$14.info("清理过期后台任务", { cleaned });
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$13 = logger.child("agent-message-bus");
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$13.warn("目标 Agent 实例不存在,消息丢弃", {
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$13.debug("消息已投递", {
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$12 = logger.child("background-agent-manager");
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$12.error("后台 Agent 意外崩溃", {
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$12.warn("后台 Agent 超时,自动取消", {
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$12.info("后台 Agent 完成通知已发送", {
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$12.warn("后台 Agent 失败", {
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$12.info("后台 Agent 已取消", { taskId });
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$12.info("跨会话恢复:清理了过期后台任务", { count: stale });
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$12.info("跨会话恢复:标记活跃任务为 failed", { count: active.length });
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$11 = logger.child("question-manager");
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$11.debug("提问已发出", {
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$11.warn("提问超时,自动取消", { requestId });
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$11.debug("提问已回答", { requestId });
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$11.debug("提问已取消", {
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$11.debug("已清理所有待处理问题", { count });
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 substituted = substituteVariables(bodyContent, params.args, skill.baseDir, skill.name);
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
- substituted,
10029
+ withShell,
9351
10030
  "",
9352
10031
  "---",
9353
10032
  `Base directory: ${skill.baseDir}`
9354
10033
  ];
9355
- if (!substituted.includes(params.args ?? "") && params.args) instructionParts.push("", `ARGUMENTS: ${params.args}`);
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$10 = logger.child("worktree-manager");
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$10.info("创建 worktree", {
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$10.warn("创建分支失败,清理 worktree", {
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$10.warn("Worktree 创建失败后清理出错", {
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$10.info("Worktree 创建成功", {
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$10.warn("尝试删除不存在的 worktree", { id });
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$10.info("Worktree 已删除", { id });
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$10.warn("Worktree prune 失败(路径不存在)", {
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$10.warn("git worktree remove 失败", {
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$10.warn("删除 worktree 分支失败", {
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$10.warn("Worktree prune 失败", { error: err instanceof Error ? err.message : String(err) });
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$10.info("Worktree 有变更,保留", {
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$10.warn("检查 worktree 变更状态失败", {
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$10.warn("过期 worktree 清理失败", {
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$10.info("过期 worktree 清理完成", { cleaned });
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$9 = logger.child("agent-factory");
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$9.debug("创建 Agent 实例", {
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) subAgent.innerAgent.state.model = resolvedModel;
11287
- else subAgent.innerAgent.state.model = parentInner.state.model;
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$9.warn(`解析 Agent 类型偏好模型 [${model}] 失败`, { error: message });
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$8 = logger.child("agent-result-aggregator");
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$8.debug("Team 结果聚合完成", {
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$7 = logger.child("agent-orchestrator");
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$7.info("开始扁平并行执行", {
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$7.debug("执行扁平批次", {
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$7.info("扁平并行执行完成", {
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$7.warn("扁平子任务执行失败", {
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$7.warn("Worker 创建被拒绝:超过全局最大深度", {
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$7.warn("Worker 执行失败", {
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$7.info("Worktree 有变更,保留", {
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$7.warn("Worktree 清理失败", {
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$7.info("创建 Team", {
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: low 直接 allow,medium+ 需要 ask,critical deny */
12697
+ /** normal: 工具默认放行,仅内容级安全检查(shell 危险命令、敏感文件路径)触发审批 */
11927
12698
  normal: {
11928
- defaultAction: "ask",
11929
- maxAutoAllow: "low"
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} [${task.id}] ${task.subject}`);
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
- this.outputArea.append([responseStyle(outputAccumulator)]);
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 && thinkingDisplayMode === "collapse") {
15290
- const elapsed = ((Date.now() - thinkingStartTimeMs) / 1e3).toFixed(1);
15291
- this.outputArea.updateLine(thinkingHeaderLineIndex, dimStyle(` \u2234 Thinking (${elapsed}s)`));
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 this.outputArea.append([toolStyle(` → ${event.message}`)]);
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
- const snapshot = buildSkillSnapshot(entries, skillConfig.maxSkillsInPrompt);
15801
- this.agent.skillPrompt = snapshot.prompt;
16953
+ this.agent.resetSentSkills();
15802
16954
  this._registerSkillCommands(entries);
15803
16955
  this.buildAutocompleteProvider();
15804
- const results = this.skillGuard.scanAll(entries);
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: snapshot.count,
15820
- names: snapshot.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 goalInput = argsStr ? `请使用 /${spec.name} 技能,参数: ${argsStr}` : `请使用 /${spec.name} 技能`;
15847
- await this.executeGoal(goalInput);
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() {