zapmyco 0.14.0 → 0.16.0

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