zapmyco 0.15.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,30 +1,30 @@
1
1
  #!/usr/bin/env node
2
- import { A as configureLogger, D as eventBus, G as SESSION_DIR_NAME, K as VERSION, L as WebError, O as DEFAULT_COMPACTION_CONFIG, V as loadSkills, W as APP_NAME, d as SubAgentManager, f as SecurityBlockedError, g as runWithToolGuardContext, h as getToolGuardContext, i as AgentLlmFacade, j as logger, m as createToolInfoResolver, n as loadConfig, p as ToolGuard, q as __require, t as HOME_CONFIG_PATH, v as createLlmBasedAgent, z as ZapmycoErrorCode } from "../loader-DEuRwvSw.mjs";
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";
21
20
  import chokidar from "chokidar";
22
21
  import { marked } from "marked";
22
+ import "gray-matter";
23
23
  import { Client } from "@modelcontextprotocol/sdk/client";
24
24
  import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
25
25
 
26
26
  //#region src/cli/repl/command-registry.ts
27
- const log$21 = logger.child("repl:command-registry");
27
+ const log$25 = logger.child("repl:command-registry");
28
28
  /**
29
29
  * 命令注册表
30
30
  */
@@ -40,7 +40,7 @@ var CommandRegistry = class {
40
40
  */
41
41
  register(cmd) {
42
42
  const canonicalName = cmd.name.toLowerCase();
43
- if (this.commands.has(canonicalName)) log$21.warn(`命令 "${canonicalName}" 已存在,将被覆盖`);
43
+ if (this.commands.has(canonicalName)) log$25.warn(`命令 "${canonicalName}" 已存在,将被覆盖`);
44
44
  this.commands.set(canonicalName, cmd);
45
45
  for (const alias of cmd.aliases) {
46
46
  const lowerAlias = alias.toLowerCase();
@@ -91,7 +91,7 @@ var CommandRegistry = class {
91
91
  */
92
92
  async dispatch(parsed) {
93
93
  if (parsed.kind !== "command") {
94
- log$21.warn("dispatch 收到了非 command 类型的输入");
94
+ log$25.warn("dispatch 收到了非 command 类型的输入");
95
95
  return;
96
96
  }
97
97
  const cmd = this.getCommand(parsed.name);
@@ -103,7 +103,7 @@ var CommandRegistry = class {
103
103
  await cmd.handler(parsed.args, this.session);
104
104
  } catch (error) {
105
105
  const message = error instanceof Error ? error.message : String(error);
106
- log$21.error(`命令 /${cmd.name} 执行出错`, {}, error);
106
+ log$25.error(`命令 /${cmd.name} 执行出错`, {}, error);
107
107
  console.log(`\n 命令执行出错: ${message}\n`);
108
108
  }
109
109
  }
@@ -366,7 +366,7 @@ function formatAgentInstanceTree(instances, options = {}) {
366
366
  for (const inst of instances) {
367
367
  const parentKey = inst.parentInstanceId;
368
368
  if (!childrenMap.has(parentKey)) childrenMap.set(parentKey, []);
369
- childrenMap.get(parentKey).push(inst);
369
+ childrenMap.get(parentKey)?.push(inst);
370
370
  }
371
371
  for (let i = 0; i < roots.length; i++) {
372
372
  const root = roots[i];
@@ -470,6 +470,49 @@ const noColor = {
470
470
  reset: ""
471
471
  };
472
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
+
473
516
  //#endregion
474
517
  //#region src/core/agent-team/agent-instance-manager.ts
475
518
  /**
@@ -480,7 +523,7 @@ const noColor = {
480
523
  *
481
524
  * @module core/agent-team
482
525
  */
483
- const log$20 = logger.child("agent-instance-manager");
526
+ const log$24 = logger.child("agent-instance-manager");
484
527
  /**
485
528
  * Agent 实例状态转换表
486
529
  *
@@ -538,14 +581,16 @@ var AgentInstanceManager = class extends EventEmitter {
538
581
  agent,
539
582
  inbox: [],
540
583
  task,
541
- createdAt: Date.now()
584
+ createdAt: Date.now(),
585
+ toolCallHistory: [],
586
+ toolCallGroups: []
542
587
  };
543
588
  this.instances.set(instance.instanceId, instance);
544
589
  if (parentInstanceId) {
545
590
  const parent = this.instances.get(parentInstanceId);
546
591
  if (parent) parent.childInstanceIds.push(instance.instanceId);
547
592
  }
548
- log$20.debug("注册 Agent 实例", {
593
+ log$24.debug("注册 Agent 实例", {
549
594
  instanceId: instance.instanceId,
550
595
  typeId: definition.typeId,
551
596
  depth,
@@ -568,12 +613,12 @@ var AgentInstanceManager = class extends EventEmitter {
568
613
  transition(instanceId, newStatus) {
569
614
  const instance = this.instances.get(instanceId);
570
615
  if (!instance) {
571
- log$20.warn("状态转换失败:实例不存在", { instanceId });
616
+ log$24.warn("状态转换失败:实例不存在", { instanceId });
572
617
  return false;
573
618
  }
574
619
  const allowed = VALID_TRANSITIONS[instance.status];
575
620
  if (!allowed.includes(newStatus)) {
576
- log$20.warn("状态转换拒绝", {
621
+ log$24.warn("状态转换拒绝", {
577
622
  instanceId,
578
623
  from: instance.status,
579
624
  to: newStatus,
@@ -583,7 +628,7 @@ var AgentInstanceManager = class extends EventEmitter {
583
628
  }
584
629
  const oldStatus = instance.status;
585
630
  instance.status = newStatus;
586
- log$20.debug("Agent 实例状态转换", {
631
+ log$24.debug("Agent 实例状态转换", {
587
632
  instanceId,
588
633
  typeId: instance.typeId,
589
634
  from: oldStatus,
@@ -622,6 +667,34 @@ var AgentInstanceManager = class extends EventEmitter {
622
667
  return this.instances.get(instanceId)?.currentActivity;
623
668
  }
624
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
+ /**
625
698
  * 获取指定实例
626
699
  *
627
700
  * @param instanceId - 实例 ID
@@ -714,7 +787,7 @@ var AgentInstanceManager = class extends EventEmitter {
714
787
  instance.agent.removeAllListeners();
715
788
  instance.agent.systemPromptOverride = null;
716
789
  this.instances.delete(instanceId);
717
- log$20.debug("Agent 实例已清理", { instanceId });
790
+ log$24.debug("Agent 实例已清理", { instanceId });
718
791
  }
719
792
  /**
720
793
  * 清理所有终态的实例
@@ -1165,7 +1238,7 @@ const BUILTIN_AGENT_TYPES = [
1165
1238
  *
1166
1239
  * @module core/agent-team
1167
1240
  */
1168
- const log$19 = logger.child("agent-type-registry");
1241
+ const log$23 = logger.child("agent-type-registry");
1169
1242
  /**
1170
1243
  * Agent 类型注册中心
1171
1244
  *
@@ -1189,14 +1262,14 @@ var AgentTypeRegistry = class {
1189
1262
  */
1190
1263
  register(definition) {
1191
1264
  const existing = this.types.get(definition.typeId);
1192
- if (existing) log$19.info("覆盖已注册的 Agent 类型", {
1265
+ if (existing) log$23.info("覆盖已注册的 Agent 类型", {
1193
1266
  typeId: definition.typeId,
1194
1267
  oldSource: existing.source,
1195
1268
  newSource: definition.source
1196
1269
  });
1197
1270
  this.types.set(definition.typeId, definition);
1198
1271
  this.refreshCache();
1199
- log$19.debug("注册 Agent 类型", {
1272
+ log$23.debug("注册 Agent 类型", {
1200
1273
  typeId: definition.typeId,
1201
1274
  source: definition.source
1202
1275
  });
@@ -1209,7 +1282,7 @@ var AgentTypeRegistry = class {
1209
1282
  registerAll(definitions) {
1210
1283
  for (const def of definitions) this.types.set(def.typeId, def);
1211
1284
  this.refreshCache();
1212
- log$19.info("批量注册 Agent 类型", { count: definitions.length });
1285
+ log$23.info("批量注册 Agent 类型", { count: definitions.length });
1213
1286
  }
1214
1287
  /**
1215
1288
  * 注销 Agent 类型
@@ -1221,7 +1294,7 @@ var AgentTypeRegistry = class {
1221
1294
  const result = this.types.delete(typeId);
1222
1295
  if (result) {
1223
1296
  this.refreshCache();
1224
- log$19.debug("注销 Agent 类型", { typeId });
1297
+ log$23.debug("注销 Agent 类型", { typeId });
1225
1298
  }
1226
1299
  return result;
1227
1300
  }
@@ -1308,7 +1381,7 @@ var AgentTypeRegistry = class {
1308
1381
  loadBuiltinTypes() {
1309
1382
  for (const def of BUILTIN_AGENT_TYPES) this.types.set(def.typeId, def);
1310
1383
  this.refreshCache();
1311
- log$19.info("加载内置 Agent 类型", { count: BUILTIN_AGENT_TYPES.length });
1384
+ log$23.info("加载内置 Agent 类型", { count: BUILTIN_AGENT_TYPES.length });
1312
1385
  }
1313
1386
  /**
1314
1387
  * 刷新可见类型缓存
@@ -1427,7 +1500,7 @@ function createCacheCommand() {
1427
1500
  const lines = [];
1428
1501
  lines.push("");
1429
1502
  lines.push(chalk.cyan(" Prompt 缓存状态"));
1430
- lines.push(" " + chalk.gray("─".repeat(40)));
1503
+ lines.push(` ${chalk.gray("─".repeat(40))}`);
1431
1504
  lines.push(chalk.gray(" 缓存命中率: ") + formatPercent(stats.hitRate));
1432
1505
  lines.push(chalk.gray(" 平均缓存读取比例: ") + formatPercent(stats.averageCacheRatio));
1433
1506
  lines.push(chalk.gray(" 总调用次数: ") + chalk.white(String(stats.totalCalls)));
@@ -1528,7 +1601,7 @@ function updateSettingsFile(path, value) {
1528
1601
  parsedValue = JSON.parse(value);
1529
1602
  } catch {}
1530
1603
  setByDotPath(config, path, parsedValue);
1531
- 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");
1532
1605
  return {
1533
1606
  success: true,
1534
1607
  message: ""
@@ -1745,7 +1818,7 @@ function renderSecurityDefault(report) {
1745
1818
  /** 简单柱状图 */
1746
1819
  function barChart(score) {
1747
1820
  const filled = Math.round(score / 10);
1748
- return "█".repeat(filled) + "░".repeat(10 - filled) + ` ${score}/100`;
1821
+ return `${"█".repeat(filled) + "░".repeat(10 - filled)} ${score}/100`;
1749
1822
  }
1750
1823
  /**
1751
1824
  * 创建 security 命令定义
@@ -1936,7 +2009,7 @@ var en_default = {
1936
2009
  "listProvidersUsage": "/settings list-providers — List all known providers",
1937
2010
  "listModelsUsage": "/settings list-models <name> — List available models for a provider",
1938
2011
  "knownProviders": "Known providers:",
1939
- "noModelsAvailable": "No models available from pi-ai registry",
2012
+ "noModelsAvailable": "No models available",
1940
2013
  "hintConfigureFirst": "Hint: use /settings to configure this provider first",
1941
2014
  "availableModels": "available models:"
1942
2015
  },
@@ -2120,7 +2193,7 @@ var zh_CN_default = {
2120
2193
  "listProvidersUsage": "/settings list-providers — 列出所有已知提供商",
2121
2194
  "listModelsUsage": "/settings list-models <name> — 列出提供商可用的模型",
2122
2195
  "knownProviders": "已知提供商:",
2123
- "noModelsAvailable": "pi-ai 注册表中没有可用的模型",
2196
+ "noModelsAvailable": "没有可用的模型",
2124
2197
  "hintConfigureFirst": "提示: 使用 /settings 先配置此提供商",
2125
2198
  "availableModels": "可用模型:"
2126
2199
  },
@@ -2459,7 +2532,7 @@ function readSettings() {
2459
2532
  }
2460
2533
  /** Write back to settings.json */
2461
2534
  function writeSettings(settings) {
2462
- 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");
2463
2536
  }
2464
2537
  /** Safely set a nested property (prototype-chain safe) */
2465
2538
  function _setByDotPath(obj, path, value) {
@@ -2489,15 +2562,6 @@ function _getByDotPath(obj, path) {
2489
2562
 
2490
2563
  //#endregion
2491
2564
  //#region src/cli/repl/commands/settings-cmd.ts
2492
- /**
2493
- * /settings command — interactive configuration menu
2494
- *
2495
- * TUI overlay-based graphical configuration interface:
2496
- * - View and change the default model
2497
- * - Configure API keys, models, and base URLs for existing providers
2498
- * - Add new providers from a curated list
2499
- * - All changes sync to ~/.zapmyco/settings.json in real-time
2500
- */
2501
2565
  /** Curated list of known providers (sorted by popularity) */
2502
2566
  const KNOWN_PROVIDERS = [
2503
2567
  {
@@ -2574,6 +2638,18 @@ const KNOWN_PROVIDERS = [
2574
2638
  label: "OpenCode"
2575
2639
  }
2576
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
+ };
2577
2653
  /** Supported language locales */
2578
2654
  const SUPPORTED_LOCALES = [{
2579
2655
  value: "zh-CN",
@@ -2599,14 +2675,12 @@ function hasApiKey(config, providerName) {
2599
2675
  const keyStr = String(key);
2600
2676
  return keyStr.length > 0 && keyStr !== "${}";
2601
2677
  }
2602
- /** 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) */
2603
2679
  function getProviderModels(config, providerName) {
2604
2680
  const models = _getByDotPath(config, `llm.providers.${providerName}.models`);
2605
2681
  if (models && typeof models === "object" && Object.keys(models).length > 0) return Object.keys(models);
2606
- try {
2607
- const piModels = getModels(providerName);
2608
- if (piModels && piModels.length > 0) return piModels.map((m) => m.id);
2609
- } catch {}
2682
+ const builtinModels = BUILTIN_MODEL_IDS[providerName];
2683
+ if (builtinModels && builtinModels.length > 0) return builtinModels;
2610
2684
  return [];
2611
2685
  }
2612
2686
  /**
@@ -2790,7 +2864,7 @@ async function handleInteractiveMode(tui, session, config) {
2790
2864
  label: id,
2791
2865
  description: ""
2792
2866
  })), { onExit: exitAll });
2793
- if (selected && selected.value) {
2867
+ if (selected?.value) {
2794
2868
  const modelId = selected.value;
2795
2869
  const settings = readSettings();
2796
2870
  _setByDotPath(settings, `llm.providers.${providerName}.models.${modelId}`, { id: modelId });
@@ -2833,7 +2907,9 @@ async function handleInteractiveMode(tui, session, config) {
2833
2907
  */
2834
2908
  const handleModelSlotSelect = async (configKey, successMsgKey) => {
2835
2909
  const configuredProviders = _getByDotPath(state.current, "llm.providers");
2836
- 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])];
2837
2913
  const enabledItems = [];
2838
2914
  const disabledItems = [];
2839
2915
  for (const providerName of allProviders) {
@@ -2864,7 +2940,7 @@ async function handleInteractiveMode(tui, session, config) {
2864
2940
  return;
2865
2941
  }
2866
2942
  const selected = await showSelectList(tui, modelItems, { onExit: exitAll });
2867
- if (!selected || !selected.value) return;
2943
+ if (!selected?.value) return;
2868
2944
  const selectedKey = selected.value;
2869
2945
  const slashIndex = selectedKey.indexOf("/");
2870
2946
  const providerName = selectedKey.slice(0, slashIndex);
@@ -3015,7 +3091,7 @@ async function handleInteractiveMode(tui, session, config) {
3015
3091
  maxVisible: 12,
3016
3092
  onExit: exitAll
3017
3093
  });
3018
- if (!selected || !selected.value) continue;
3094
+ if (!selected?.value) continue;
3019
3095
  const providerName = selected.value;
3020
3096
  const existingProviders = _getByDotPath(state.current, "llm.providers");
3021
3097
  if (existingProviders && providerName in existingProviders) {
@@ -3057,7 +3133,7 @@ async function handleInteractiveMode(tui, session, config) {
3057
3133
  ...loc,
3058
3134
  label: loc.value === currentLocale ? `${loc.label} ✓` : loc.label
3059
3135
  })), { onExit: exitAll });
3060
- if (!selected || !selected.value) continue;
3136
+ if (!selected?.value) continue;
3061
3137
  if (selected.value !== currentLocale) {
3062
3138
  setConfigValue(session, "locale", selected.value);
3063
3139
  setLocale(selected.value);
@@ -3091,23 +3167,152 @@ function createStatusCommand() {
3091
3167
  };
3092
3168
  }
3093
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
+
3094
3298
  //#endregion
3095
3299
  //#region src/cli/repl/components/agent-status-bar.ts
3096
3300
  /**
3097
3301
  * Agent 状态栏组件
3098
3302
  *
3099
3303
  * 实时显示正在运行的子 Agent 状态,类似 Claude Code 的 "Running N agents…" 效果。
3304
+ * 支持每个 Agent 的工具调用历史展示、分组摘要、"+N more" 折叠展开。
3100
3305
  * 固定在 OutputArea 和 Editor 之间,无活跃 Agent 时自动隐藏。
3101
3306
  *
3102
3307
  * @module cli/repl/components
3103
3308
  */
3104
- /** 状态图标映射 */
3309
+ /** 状态图标映射 — 纯文字符号,无 emoji */
3105
3310
  const STATUS_ICONS$1 = {
3106
3311
  idle: "○",
3107
- running: "",
3312
+ running: "",
3108
3313
  paused: "◐",
3109
- completed: "",
3110
- failed: "",
3314
+ completed: "",
3315
+ failed: "",
3111
3316
  cancelled: "◌"
3112
3317
  };
3113
3318
  /** Loading 动画帧 */
@@ -3129,21 +3334,48 @@ const TREE_BRANCH = "├── ";
3129
3334
  const TREE_LAST = "└── ";
3130
3335
  const TREE_PIPE = "│ ";
3131
3336
  const TREE_SPACE = " ";
3132
- 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
+ }
3133
3355
  /**
3134
3356
  * Agent 状态栏组件
3135
3357
  *
3136
3358
  * 从 AgentInstanceManager 读取活跃实例状态,渲染为紧凑状态栏。
3137
3359
  */
3138
3360
  var AgentStatusBar = class extends Container {
3139
- /** 是否展开显示详情 */
3140
- #expanded = false;
3361
+ /** AnimationManager 实例(用于渲染周期驱动的动画) */
3362
+ #animationManager;
3363
+ /** 是否展开显示详情(默认展开) */
3364
+ #expanded = true;
3365
+ /** 按实例的工具调用列表展开状态 */
3366
+ #agentExpanded = /* @__PURE__ */ new Map();
3141
3367
  /** loading 动画帧索引 */
3142
3368
  #loadingFrame = 0;
3143
- /** loading 动画定时器 */
3144
- #loadingTimer;
3369
+ /** AnimationManager 回调注销函数 */
3370
+ #unregLoading = null;
3371
+ /** 上次帧推进时间戳 */
3372
+ #lastLoadingTick = 0;
3145
3373
  /** 上次活跃实例快照(用于检测变化) */
3146
3374
  #lastActiveCount = 0;
3375
+ constructor(animationManager) {
3376
+ super();
3377
+ this.#animationManager = animationManager;
3378
+ }
3147
3379
  /** 当前模型名称 */
3148
3380
  #modelName = null;
3149
3381
  /** 累积非缓存 input tokens(usage.input = totalInput - cacheRead) */
@@ -3156,11 +3388,26 @@ var AgentStatusBar = class extends Container {
3156
3388
  #durationMs = 0;
3157
3389
  /**
3158
3390
  * 切换展开/折叠状态
3391
+ *
3392
+ * @param instanceId - 指定实例 ID 时,切换该实例的工具调用列表展开/折叠;不传时切换整体状态栏
3159
3393
  */
3160
- toggle() {
3161
- 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
+ }
3162
3402
  this.invalidate();
3163
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
+ }
3164
3411
  /** 获取当前展开状态 */
3165
3412
  get isExpanded() {
3166
3413
  return this.#expanded;
@@ -3230,52 +3477,74 @@ var AgentStatusBar = class extends Container {
3230
3477
  return lines;
3231
3478
  }
3232
3479
  /** 渲染折叠模式单行 */
3233
- #renderCollapsed(frame, count, toolUses, duration) {
3234
- return ` ${chalk.cyan(`${frame} Running ${count} agent${count > 1 ? "s" : ""}...`)} ${chalk.gray(`\u00B7 ${toolUses} tool use${toolUses !== 1 ? "s" : ""} \u00B7 ${duration}`)} ${chalk.dim("(ctrl+o to expand)")}`;
3480
+ #renderCollapsed(frame, count, _toolUses, duration) {
3481
+ return ` ${chalk.cyan(`${frame} ${count} agent${count > 1 ? "s" : ""}`)} ${chalk.gray( ${duration}`)}`;
3235
3482
  }
3236
3483
  /** 渲染展开模式多行 */
3237
3484
  #renderExpanded(frame, instances, width) {
3238
3485
  const lines = [];
3239
3486
  const count = instances.length;
3240
- const header = chalk.cyan(` ${frame} Running ${count} agent${count > 1 ? "s" : ""}...`);
3241
- const hint = chalk.dim("(ctrl+o to collapse)");
3242
- lines.push(truncateToWidth(`${header} ${hint}`, width));
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));
3243
3490
  for (let i = 0; i < instances.length; i++) {
3244
3491
  const inst = instances[i];
3245
3492
  const isLast = i === instances.length - 1;
3246
3493
  const connector = isLast ? TREE_LAST : TREE_BRANCH;
3247
3494
  const childPrefix = isLast ? TREE_SPACE : TREE_PIPE;
3248
- const icon = STATUS_ICONS$1[inst.status] ?? "?";
3249
- const statusColor = inst.status === "running" ? chalk.yellow : chalk.gray;
3250
- const typeLabel = chalk.bold(inst.typeId);
3251
- const taskPreview = inst.task.description.slice(0, 40);
3252
- const act = inst.currentActivity;
3253
- const toolUsesStr = act ? chalk.gray(`\u00B7 ${act.toolUses} tool uses`) : "";
3254
- const durationStr = chalk.gray(`\u00B7 ${this.#formatDuration(Date.now() - inst.createdAt)}`);
3255
- const line1 = ` ${chalk.dim(connector)}${statusColor(icon)}${chalk.reset} ${typeLabel} ${chalk.dim(taskPreview)} ${toolUsesStr} ${durationStr}`;
3256
- lines.push(truncateToWidth(line1, width));
3257
- if (act) {
3258
- const toolDisplay = act.args ? `${act.toolName}: ${act.args.slice(0, 60)}` : act.toolName;
3259
- const line2 = ` ${chalk.dim(childPrefix + TREE_CONT)}${chalk.cyan(toolDisplay)}`;
3260
- lines.push(truncateToWidth(line2, width));
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} 次调用`;
3261
3515
  }
3262
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));
3263
3528
  return lines;
3264
3529
  }
3265
- /** 启动 loading 动画 */
3530
+ /** 启动 loading 动画(由 animationManager 在渲染周期中驱动) */
3266
3531
  #startLoading() {
3267
- if (this.#loadingTimer) return;
3268
- 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;
3269
3537
  this.#loadingFrame = (this.#loadingFrame + 1) % LOADING_FRAMES$2.length;
3270
3538
  this.invalidate();
3271
- }, LOADING_INTERVAL_MS$1);
3539
+ });
3272
3540
  }
3273
3541
  /** 停止 loading 动画 */
3274
3542
  #stopLoading() {
3275
- if (this.#loadingTimer) {
3276
- clearInterval(this.#loadingTimer);
3277
- this.#loadingTimer = void 0;
3543
+ if (this.#unregLoading) {
3544
+ this.#unregLoading();
3545
+ this.#unregLoading = null;
3278
3546
  }
3547
+ this.#loadingFrame = 0;
3279
3548
  }
3280
3549
  /** 格式化持续时间为可读字符串 */
3281
3550
  #formatDuration(ms) {
@@ -3314,20 +3583,6 @@ var AgentStatusBar = class extends Container {
3314
3583
 
3315
3584
  //#endregion
3316
3585
  //#region src/cli/repl/components/custom-editor.ts
3317
- /**
3318
- * 自定义编辑器组件
3319
- *
3320
- * 继承自 pi-tui 的 Editor,添加 zapmyco 特有的快捷键处理:
3321
- * - Ctrl+C: 取消任务 / 二次退出
3322
- * - Ctrl+D: 退出
3323
- * - Ctrl+O: 打开外部编辑器编辑输入
3324
- * - Escape: 取消当前输入
3325
- *
3326
- * 同时 override render() 以:
3327
- * - 去掉 Editor 默认的上下边框(───)
3328
- * - 添加简洁的输入提示符(❯ )
3329
- * - 执行中时显示 loading spinner
3330
- */
3331
3586
  /** 输入提示符 */
3332
3587
  const PROMPT_PREFIX = "❯ ";
3333
3588
  /** loading 动画帧 */
@@ -3351,36 +3606,42 @@ function isBorderLine(line) {
3351
3606
  return /^[\s─┌┐├┤└┘↑↓\-0-9a-zA-Z]+$/.test(stripped);
3352
3607
  }
3353
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
+ }
3354
3619
  /** Escape 键回调 */
3355
3620
  onEscape;
3356
3621
  /** Ctrl+C 回调 */
3357
3622
  onCtrlC;
3358
3623
  /** Ctrl+D 回调 */
3359
3624
  onCtrlD;
3360
- /** Ctrl+O 回调(打开外部编辑器) */
3625
+ /** Ctrl+G 回调(打开外部编辑器) */
3361
3626
  onOpenEditor;
3362
- /** Ctrl+T 回调(展开/折叠 TaskStatusBar) */
3363
- onToggleTasks;
3364
- /** Ctrl+Y 回调(展开/折叠 thinking 内容) */
3627
+ /** Ctrl+B 回调(后台运行当前任务) */
3628
+ onRunInBackground;
3629
+ /** Ctrl+T 回调(展开/折叠 thinking 内容) */
3365
3630
  onToggleThinking;
3366
- /** Ctrl+Shift+O 回调(展开/折叠 Agent 状态栏) */
3367
- onToggleAgentBar;
3368
3631
  /** 是否正在执行(用于显示 loading) */
3369
3632
  #executing = false;
3370
3633
  /** 是否显示 spinner(执行期间禁用输入但不一定显示 spinner) */
3371
3634
  #showSpinner = true;
3372
3635
  /** loading 动画帧索引 */
3373
3636
  #loadingFrame = 0;
3374
- /** loading 动画定时器 */
3375
- #loadingTimer;
3376
3637
  /** 审批模式状态 */
3377
3638
  #approvalState = null;
3378
3639
  /** 进入审批模式,在编辑器区域显示审批选项 */
3379
3640
  enterApprovalMode(title, options) {
3380
3641
  this.#executing = false;
3381
- if (this.#loadingTimer) {
3382
- clearInterval(this.#loadingTimer);
3383
- this.#loadingTimer = void 0;
3642
+ if (this.#unregLoading) {
3643
+ this.#unregLoading();
3644
+ this.#unregLoading = null;
3384
3645
  }
3385
3646
  this.#approvalState = {
3386
3647
  title,
@@ -3415,23 +3676,17 @@ var ZapmycoEditor = class extends Editor {
3415
3676
  if (this.getText().length === 0 && this.onCtrlD) this.onCtrlD();
3416
3677
  return;
3417
3678
  }
3418
- if (matchesKey(data, Key.ctrl("o"))) {
3679
+ if (matchesKey(data, Key.ctrl("g"))) {
3419
3680
  if (this.onOpenEditor) this.onOpenEditor();
3420
3681
  return;
3421
3682
  }
3422
- if (matchesKey(data, Key.ctrlShift("o")) && this.onToggleAgentBar) {
3423
- this.onToggleAgentBar();
3683
+ if (matchesKey(data, Key.ctrl("b")) && this.onRunInBackground) {
3684
+ this.onRunInBackground();
3424
3685
  return;
3425
3686
  }
3426
- if (matchesKey(data, Key.ctrl("t"))) {
3427
- if (this.onToggleTasks) {
3428
- this.onToggleTasks();
3429
- return;
3430
- }
3431
- if (this.onToggleThinking) {
3432
- this.onToggleThinking();
3433
- return;
3434
- }
3687
+ if (matchesKey(data, Key.ctrl("t")) && this.onToggleThinking) {
3688
+ this.onToggleThinking();
3689
+ return;
3435
3690
  }
3436
3691
  if (matchesKey(data, Key.ctrl("y")) && this.onToggleThinking) {
3437
3692
  this.onToggleThinking();
@@ -3482,13 +3737,19 @@ var ZapmycoEditor = class extends Editor {
3482
3737
  this.#showSpinner = showSpinner;
3483
3738
  if (executing && showSpinner) {
3484
3739
  this.#loadingFrame = 0;
3485
- 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;
3486
3744
  this.#loadingFrame = (this.#loadingFrame + 1) % LOADING_FRAMES$1.length;
3487
3745
  this.tui?.requestRender();
3488
- }, 100);
3489
- } else if (this.#loadingTimer) {
3490
- clearInterval(this.#loadingTimer);
3491
- this.#loadingTimer = void 0;
3746
+ });
3747
+ } else {
3748
+ if (this.#unregLoading) {
3749
+ this.#unregLoading();
3750
+ this.#unregLoading = null;
3751
+ }
3752
+ this.#loadingFrame = 0;
3492
3753
  }
3493
3754
  this.tui?.requestRender();
3494
3755
  }
@@ -3558,17 +3819,13 @@ var ZapmycoEditor = class extends Editor {
3558
3819
  * 显示 TaskManage 创建的任务列表,支持折叠/展开两种模式。
3559
3820
  * 类似 Claude Code 的 TaskListV2,使用 pi-tui Container 实现。
3560
3821
  *
3561
- * 折叠模式(默认,单行):
3562
- * 📋 3 tasks · ◼ 1 in_progress · ◻ 1 pending · ✔ 1 completed Ctrl+T expand
3563
- *
3564
- * 展开模式(Ctrl+T 切换):
3822
+ * 有任务时始终展开(多行):
3565
3823
  * ⠋ #1 Search files
3566
3824
  * ◻ #2 Core logic
3567
3825
  * ◻ #3 Tests ▸ blocked by #1
3568
3826
  * ✔ #4 Analysis
3569
3827
  *
3570
3828
  * 1 in_progress · 1 pending · 1 completed
3571
- * (Ctrl+T collapse)
3572
3829
  *
3573
3830
  * 自动隐藏:无任务时 render() 返回 []。
3574
3831
  * 进行中任务:显示 loading spinner 动画 + cyan 高亮。
@@ -3604,34 +3861,22 @@ const STATUS_ICONS = {
3604
3861
  * 固定在 OutputArea 和 AgentStatusBar 之间。
3605
3862
  */
3606
3863
  var TaskStatusBar = class extends Container {
3607
- /**
3608
- * 用户手动折叠标记
3609
- *
3610
- * - false(默认):根据任务状态自动决定展开/折叠
3611
- * - true:用户通过 Ctrl+T 手动折叠,覆盖自动行为
3612
- */
3613
- #forceCollapsed = false;
3864
+ /** AnimationManager 实例(用于渲染周期驱动的动画) */
3865
+ #animationManager;
3614
3866
  /** TaskStore 引用(只读,不写) */
3615
3867
  #store;
3616
3868
  /** loading 动画帧索引 */
3617
3869
  #loadingFrame = 0;
3618
- /** loading 动画定时器 */
3619
- #loadingTimer;
3870
+ /** AnimationManager 回调注销函数 */
3871
+ #unregLoading = null;
3872
+ /** 上次帧推进时间戳 */
3873
+ #lastLoadingTick = 0;
3620
3874
  /** 上次是否有 in_progress 任务(用于启停动画) */
3621
3875
  #hadInProgress = false;
3622
- constructor(store) {
3876
+ constructor(store, animationManager) {
3623
3877
  super();
3624
3878
  this.#store = store;
3625
- }
3626
- /**
3627
- * 切换展开/折叠状态
3628
- *
3629
- * 用户手动 Ctrl+T 时调用,设置 forceCollapsed 标记。
3630
- * 当新任务出现时,forceCollapsed 会被自动重置。
3631
- */
3632
- toggle() {
3633
- this.#forceCollapsed = !this.#forceCollapsed;
3634
- this.invalidate();
3879
+ this.#animationManager = animationManager;
3635
3880
  }
3636
3881
  /** 当前是否处于展开状态 */
3637
3882
  get isExpanded() {
@@ -3640,22 +3885,16 @@ var TaskStatusBar = class extends Container {
3640
3885
  }
3641
3886
  /**
3642
3887
  * 当 TaskStore 变化时由外部调用
3643
- *
3644
- * 如果出现新的活跃任务,自动展开;全部完成后自动折叠。
3645
3888
  */
3646
3889
  onTasksChanged() {
3647
- const summary = this.#store.summary();
3648
- if (summary.pending > 0 || summary.in_progress > 0) this.#forceCollapsed = false;
3649
3890
  this.invalidate();
3650
3891
  }
3651
3892
  invalidate() {
3652
3893
  super.invalidate();
3653
3894
  }
3654
- /** 判断当前是否应展开 */
3895
+ /** 判断当前是否应展开(有任务时始终展开) */
3655
3896
  #shouldExpand(summary) {
3656
- if (summary.total === 0) return false;
3657
- if (this.#forceCollapsed) return false;
3658
- return summary.pending > 0 || summary.in_progress > 0;
3897
+ return summary.total > 0;
3659
3898
  }
3660
3899
  /**
3661
3900
  * 管理 loading 动画的启停
@@ -3667,21 +3906,24 @@ var TaskStatusBar = class extends Container {
3667
3906
  if (hasInProgress && !this.#hadInProgress) this.#startLoading();
3668
3907
  else if (!hasInProgress && this.#hadInProgress) this.#stopLoading();
3669
3908
  }
3670
- /** 启动 loading 动画 */
3909
+ /** 启动 loading 动画(由 animationManager 在渲染周期中驱动) */
3671
3910
  #startLoading() {
3672
- if (this.#loadingTimer) return;
3673
- 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;
3674
3916
  this.#loadingFrame = (this.#loadingFrame + 1) % LOADING_FRAMES.length;
3675
3917
  this.invalidate();
3676
- }, LOADING_INTERVAL_MS);
3918
+ });
3677
3919
  }
3678
3920
  /** 停止 loading 动画 */
3679
3921
  #stopLoading() {
3680
- if (this.#loadingTimer) {
3681
- clearInterval(this.#loadingTimer);
3682
- this.#loadingTimer = void 0;
3683
- this.#loadingFrame = 0;
3922
+ if (this.#unregLoading) {
3923
+ this.#unregLoading();
3924
+ this.#unregLoading = null;
3684
3925
  }
3926
+ this.#loadingFrame = 0;
3685
3927
  }
3686
3928
  /** 获取当前 in_progress 的图标(可能为动画帧) */
3687
3929
  #getInProgressIcon() {
@@ -3711,8 +3953,7 @@ var TaskStatusBar = class extends Container {
3711
3953
  if (summary.pending > 0) parts.push(`${STATUS_ICONS.pending} ${summary.pending} pending`);
3712
3954
  if (summary.completed > 0) parts.push(chalk.green(`${STATUS_ICONS.completed} ${summary.completed} completed`));
3713
3955
  if (summary.cancelled > 0) parts.push(chalk.red.dim(`${STATUS_ICONS.cancelled} ${summary.cancelled} cancelled`));
3714
- const hint = chalk.dim("Ctrl+T expand");
3715
- return ` ${parts.join(" · ")} ${hint}`;
3956
+ return ` ${parts.join(" · ")}`;
3716
3957
  }
3717
3958
  /** 渲染展开模式多行 */
3718
3959
  #renderExpanded(tasks, summary, width) {
@@ -3726,7 +3967,6 @@ var TaskStatusBar = class extends Container {
3726
3967
  if (summary.completed > 0) summaryParts.push(chalk.green(`${summary.completed} completed`));
3727
3968
  if (summary.cancelled > 0) summaryParts.push(chalk.red.dim(`${summary.cancelled} cancelled`));
3728
3969
  lines.push(` ${summaryParts.join(" · ")}`);
3729
- lines.push(chalk.dim(" (Ctrl+T collapse)"));
3730
3970
  return lines;
3731
3971
  }
3732
3972
  /** 渲染单个任务行 */
@@ -3876,7 +4116,7 @@ function parseField(field, spec) {
3876
4116
  function parseFieldPart(part, spec) {
3877
4117
  if (part.startsWith("*/")) {
3878
4118
  const step = parseInt(part.slice(2), 10);
3879
- if (isNaN(step) || step < 1) return null;
4119
+ if (Number.isNaN(step) || step < 1) return null;
3880
4120
  return new StepMatcher(step, spec.min);
3881
4121
  }
3882
4122
  if (part === "*") return new WildcardMatcher();
@@ -3884,12 +4124,12 @@ function parseFieldPart(part, spec) {
3884
4124
  if (rangeMatch) {
3885
4125
  const start = parseInt(rangeMatch[1] ?? "", 10);
3886
4126
  const end = parseInt(rangeMatch[2] ?? "", 10);
3887
- if (isNaN(start) || isNaN(end)) return null;
4127
+ if (Number.isNaN(start) || Number.isNaN(end)) return null;
3888
4128
  if (start < spec.min || end > spec.max || start > end) return null;
3889
4129
  return new RangeMatcher(start, end);
3890
4130
  }
3891
4131
  const value = parseInt(part, 10);
3892
- 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]);
3893
4133
  return null;
3894
4134
  }
3895
4135
  /**
@@ -4080,7 +4320,7 @@ const CRON_CONSTANTS = {
4080
4320
  *
4081
4321
  * @module cli/repl/cron/cron-scheduler
4082
4322
  */
4083
- const log$18 = logger.child("cron:scheduler");
4323
+ const log$22 = logger.child("cron:scheduler");
4084
4324
  var CronScheduler = class extends EventEmitter {
4085
4325
  store;
4086
4326
  jobs = [];
@@ -4101,7 +4341,7 @@ var CronScheduler = class extends EventEmitter {
4101
4341
  if (this.running) return;
4102
4342
  const loadedJobs = await this.store.load();
4103
4343
  this.jobs = loadedJobs;
4104
- log$18.info(`调度器启动,加载 ${loadedJobs.length} 个 durable 任务`);
4344
+ log$22.info(`调度器启动,加载 ${loadedJobs.length} 个 durable 任务`);
4105
4345
  await this.handleMissedJobs();
4106
4346
  this.checkAutoExpiry();
4107
4347
  this.running = true;
@@ -4117,7 +4357,7 @@ var CronScheduler = class extends EventEmitter {
4117
4357
  clearInterval(this.timer);
4118
4358
  this.timer = null;
4119
4359
  }
4120
- log$18.info("调度器已停止");
4360
+ log$22.info("调度器已停止");
4121
4361
  }
4122
4362
  /** 添加任务 */
4123
4363
  async addJob(job) {
@@ -4129,7 +4369,7 @@ var CronScheduler = class extends EventEmitter {
4129
4369
  this.jobs.push(job);
4130
4370
  await this.store.persist(this.jobs);
4131
4371
  } else this.sessionJobs.push(job);
4132
- log$18.info("任务已添加", {
4372
+ log$22.info("任务已添加", {
4133
4373
  id: job.id,
4134
4374
  cron: job.cron,
4135
4375
  durable: job.durable
@@ -4259,7 +4499,7 @@ var CronScheduler = class extends EventEmitter {
4259
4499
  }, delay);
4260
4500
  }
4261
4501
  if (toDelete.length > 0) {
4262
- log$18.info(`跳过 ${toDelete.length} 个错过的一次性任务(超出补发上限)`);
4502
+ log$22.info(`跳过 ${toDelete.length} 个错过的一次性任务(超出补发上限)`);
4263
4503
  this.emit("missed-overflow", {
4264
4504
  count: toDelete.length,
4265
4505
  jobIds: toDelete.map((m) => m.id)
@@ -4277,7 +4517,7 @@ var CronScheduler = class extends EventEmitter {
4277
4517
  job.lastFiredAt = now;
4278
4518
  job.fireCount++;
4279
4519
  this.removeJob(job.id);
4280
- log$18.info("任务已过期并触发最后一次", { id: job.id });
4520
+ log$22.info("任务已过期并触发最后一次", { id: job.id });
4281
4521
  }
4282
4522
  }
4283
4523
  }
@@ -4338,7 +4578,7 @@ function applyOneShotJitter(jobId, rawNext) {
4338
4578
  *
4339
4579
  * @module cli/repl/cron/cron-store
4340
4580
  */
4341
- const log$17 = logger.child("cron:store");
4581
+ const log$21 = logger.child("cron:store");
4342
4582
  const STORE_FILE = join(join(homedir(), ".zapmyco", "cron"), "scheduled_tasks.json");
4343
4583
  var CronStore = class {
4344
4584
  filePath;
@@ -4362,13 +4602,13 @@ var CronStore = class {
4362
4602
  const raw = await readFile(this.filePath, "utf-8");
4363
4603
  const data = JSON.parse(raw);
4364
4604
  if (!Array.isArray(data)) {
4365
- log$17.warn("存储文件格式无效(非数组),将使用空列表");
4605
+ log$21.warn("存储文件格式无效(非数组),将使用空列表");
4366
4606
  return [];
4367
4607
  }
4368
4608
  return this.validateJobs(data);
4369
4609
  } catch (err) {
4370
4610
  if (err.code === "ENOENT") return [];
4371
- log$17.warn("加载定时任务文件失败,将使用空列表", { error: err instanceof Error ? err.message : String(err) });
4611
+ log$21.warn("加载定时任务文件失败,将使用空列表", { error: err instanceof Error ? err.message : String(err) });
4372
4612
  return [];
4373
4613
  }
4374
4614
  }
@@ -4415,7 +4655,7 @@ var CronStore = class {
4415
4655
  if (typeof obj.maxFires === "number") job.maxFires = obj.maxFires;
4416
4656
  valid.push(job);
4417
4657
  }
4418
- if (valid.length < raw.length) log$17.warn(`跳过 ${raw.length - valid.length} 个无效任务条目`);
4658
+ if (valid.length < raw.length) log$21.warn(`跳过 ${raw.length - valid.length} 个无效任务条目`);
4419
4659
  return valid;
4420
4660
  }
4421
4661
  };
@@ -4433,7 +4673,7 @@ function getCronStore() {
4433
4673
  * 基于内存的环形缓冲区,记录 REPL 会话中的用户输入和执行结果。
4434
4674
  * 支持文件持久化到 ~/.zapmyco/history.json,跨会话恢复。
4435
4675
  */
4436
- const log$16 = logger.child("history:store");
4676
+ const log$20 = logger.child("history:store");
4437
4677
  /** 默认最大历史条数 */
4438
4678
  const DEFAULT_MAX_SIZE = 100;
4439
4679
  /** 历史文件存储路径 */
@@ -4492,13 +4732,13 @@ var HistoryStore = class {
4492
4732
  if (Array.isArray(data.entries)) {
4493
4733
  this.entries = data.entries.slice(-this.maxSize);
4494
4734
  this.nextId = typeof data.nextId === "number" ? data.nextId : 1;
4495
- log$16.debug("历史记录已加载", {
4735
+ log$20.debug("历史记录已加载", {
4496
4736
  count: this.entries.length,
4497
4737
  nextId: this.nextId
4498
4738
  });
4499
4739
  }
4500
4740
  } catch {
4501
- log$16.debug("无历史文件或加载失败,使用空历史");
4741
+ log$20.debug("无历史文件或加载失败,使用空历史");
4502
4742
  }
4503
4743
  }
4504
4744
  /** 持久化历史记录到文件 */
@@ -4511,7 +4751,7 @@ var HistoryStore = class {
4511
4751
  }, null, 2);
4512
4752
  writeFileSync(this.filePath, data, "utf-8");
4513
4753
  } catch (err) {
4514
- log$16.warn("历史记录保存失败", { error: err instanceof Error ? err.message : String(err) });
4754
+ log$20.warn("历史记录保存失败", { error: err instanceof Error ? err.message : String(err) });
4515
4755
  }
4516
4756
  }
4517
4757
  };
@@ -4636,7 +4876,7 @@ const OVERLAY_OPTIONS = {
4636
4876
  /** 截断文本到指定宽度 */
4637
4877
  function truncate(text, maxLen) {
4638
4878
  if (text.length <= maxLen) return text;
4639
- return text.slice(0, maxLen - 1) + "…";
4879
+ return `${text.slice(0, maxLen - 1)}…`;
4640
4880
  }
4641
4881
  var AskUserQuestionComponent = class {
4642
4882
  tui;
@@ -5147,7 +5387,7 @@ var OutputFormatter = class {
5147
5387
  const c = this.getColor();
5148
5388
  const lines = [
5149
5389
  "",
5150
- c.bold(` 📋 ${t("output.taskGraph.title")}`),
5390
+ c.bold(` ${t("output.taskGraph.title")}:`),
5151
5391
  c.gray(` ${t("output.taskGraph.total", {
5152
5392
  count: graph.nodes.size,
5153
5393
  layers: graph.layers.length
@@ -5286,7 +5526,7 @@ var OutputFormatter = class {
5286
5526
  lines.push(" 分类评分:");
5287
5527
  const bar = (s) => {
5288
5528
  const filled = Math.round(s / 10);
5289
- 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`;
5290
5530
  };
5291
5531
  lines.push(` permissions: ${bar(report.scores.permissions)}`);
5292
5532
  lines.push(` shell: ${bar(report.scores.shell)}`);
@@ -5393,7 +5633,7 @@ var Renderer = class {
5393
5633
  *
5394
5634
  * @module core/agent-team
5395
5635
  */
5396
- const log$15 = logger.child("background-store");
5636
+ const log$19 = logger.child("background-store");
5397
5637
  /**
5398
5638
  * 后台任务持久化存储
5399
5639
  *
@@ -5423,14 +5663,14 @@ var BackgroundTaskStore = class {
5423
5663
  const entries = JSON.parse(raw);
5424
5664
  this.tasks.clear();
5425
5665
  for (const entry of entries) this.tasks.set(entry.taskId, entry);
5426
- log$15.debug("后台任务已加载", {
5666
+ log$19.debug("后台任务已加载", {
5427
5667
  count: entries.length,
5428
5668
  path: this.filePath
5429
5669
  });
5430
5670
  return entries;
5431
5671
  }
5432
5672
  } catch (err) {
5433
- log$15.warn("后台任务加载失败", {
5673
+ log$19.warn("后台任务加载失败", {
5434
5674
  error: String(err),
5435
5675
  path: this.filePath
5436
5676
  });
@@ -5489,7 +5729,7 @@ var BackgroundTaskStore = class {
5489
5729
  const entries = Array.from(this.tasks.values());
5490
5730
  writeFileSync(this.filePath, JSON.stringify(entries, null, 2), "utf-8");
5491
5731
  } catch (err) {
5492
- log$15.error("后台任务持久化失败", {
5732
+ log$19.error("后台任务持久化失败", {
5493
5733
  error: String(err),
5494
5734
  path: this.filePath
5495
5735
  });
@@ -5515,7 +5755,7 @@ var BackgroundTaskStore = class {
5515
5755
  }
5516
5756
  if (cleaned > 0) {
5517
5757
  this.persist();
5518
- log$15.info("清理过期后台任务", { cleaned });
5758
+ log$19.info("清理过期后台任务", { cleaned });
5519
5759
  }
5520
5760
  return cleaned;
5521
5761
  }
@@ -5532,7 +5772,7 @@ var BackgroundTaskStore = class {
5532
5772
  *
5533
5773
  * @module core/agent-team
5534
5774
  */
5535
- const log$14 = logger.child("agent-message-bus");
5775
+ const log$18 = logger.child("agent-message-bus");
5536
5776
  /**
5537
5777
  * Agent 消息总线(单例)
5538
5778
  *
@@ -5566,12 +5806,12 @@ var AgentMessageBus = class {
5566
5806
  };
5567
5807
  const targetInstance = getAgentInstanceManager().get(toAgentId);
5568
5808
  if (targetInstance) targetInstance.inbox.push(fullMessage);
5569
- else log$14.warn("目标 Agent 实例不存在,消息丢弃", {
5809
+ else log$18.warn("目标 Agent 实例不存在,消息丢弃", {
5570
5810
  toAgentId,
5571
5811
  messageId: fullMessage.messageId
5572
5812
  });
5573
5813
  this.emitter.emit(`msg:${toAgentId}`, fullMessage);
5574
- log$14.debug("消息已投递", {
5814
+ log$18.debug("消息已投递", {
5575
5815
  from: fromAgentId,
5576
5816
  to: toAgentId,
5577
5817
  type: fullMessage.type,
@@ -5654,7 +5894,7 @@ function getAgentMessageBus() {
5654
5894
  *
5655
5895
  * @module core/agent-team
5656
5896
  */
5657
- const log$13 = logger.child("background-agent-manager");
5897
+ const log$17 = logger.child("background-agent-manager");
5658
5898
  /**
5659
5899
  * 后台 Agent 管理器(单例)
5660
5900
  */
@@ -5711,7 +5951,7 @@ var BackgroundAgentManager = class {
5711
5951
  parentAgentId: params.parentInstanceId
5712
5952
  });
5713
5953
  this.runAsync(taskId, params, abortController, timeoutMs).catch((err) => {
5714
- log$13.error("后台 Agent 意外崩溃", {
5954
+ log$17.error("后台 Agent 意外崩溃", {
5715
5955
  taskId,
5716
5956
  error: String(err)
5717
5957
  });
@@ -5731,7 +5971,7 @@ var BackgroundAgentManager = class {
5731
5971
  const orchestrator = this.orchestrator;
5732
5972
  const messageBus = getAgentMessageBus();
5733
5973
  const timeoutHandle = setTimeout(() => {
5734
- log$13.warn("后台 Agent 超时,自动取消", {
5974
+ log$17.warn("后台 Agent 超时,自动取消", {
5735
5975
  taskId,
5736
5976
  timeoutMs
5737
5977
  });
@@ -5775,7 +6015,7 @@ var BackgroundAgentManager = class {
5775
6015
  taskId,
5776
6016
  requiresResponse: false
5777
6017
  });
5778
- log$13.info("后台 Agent 完成通知已发送", {
6018
+ log$17.info("后台 Agent 完成通知已发送", {
5779
6019
  taskId,
5780
6020
  instanceId: result.instanceId,
5781
6021
  parentId: params.parentInstanceId,
@@ -5802,7 +6042,7 @@ var BackgroundAgentManager = class {
5802
6042
  completedAt: Date.now(),
5803
6043
  error
5804
6044
  });
5805
- log$13.warn("后台 Agent 失败", {
6045
+ log$17.warn("后台 Agent 失败", {
5806
6046
  taskId,
5807
6047
  error
5808
6048
  });
@@ -5839,7 +6079,7 @@ var BackgroundAgentManager = class {
5839
6079
  if (runtime.instanceId) try {
5840
6080
  await getAgentInstanceManager().cancel(runtime.instanceId);
5841
6081
  } catch {}
5842
- log$13.info("后台 Agent 已取消", { taskId });
6082
+ log$17.info("后台 Agent 已取消", { taskId });
5843
6083
  return true;
5844
6084
  }
5845
6085
  /**
@@ -5848,13 +6088,13 @@ var BackgroundAgentManager = class {
5848
6088
  restore() {
5849
6089
  this.store.load();
5850
6090
  const stale = this.store.cleanStale();
5851
- if (stale > 0) log$13.info("跨会话恢复:清理了过期后台任务", { count: stale });
6091
+ if (stale > 0) log$17.info("跨会话恢复:清理了过期后台任务", { count: stale });
5852
6092
  const active = this.store.listActive();
5853
6093
  for (const entry of active) this.store.updateStatus(entry.taskId, "failed", {
5854
6094
  completedAt: Date.now(),
5855
6095
  error: "会话终止导致任务丢失"
5856
6096
  });
5857
- if (active.length > 0) log$13.info("跨会话恢复:标记活跃任务为 failed", { count: active.length });
6097
+ if (active.length > 0) log$17.info("跨会话恢复:标记活跃任务为 failed", { count: active.length });
5858
6098
  }
5859
6099
  };
5860
6100
  /** 全局单例 */
@@ -6073,7 +6313,7 @@ function createDeferred() {
6073
6313
  *
6074
6314
  * @module core/question/question-manager
6075
6315
  */
6076
- const log$12 = logger.child("question-manager");
6316
+ const log$16 = logger.child("question-manager");
6077
6317
  /** 问题超时时间(5 分钟) */
6078
6318
  const QUESTION_TIMEOUT_MS = 300 * 1e3;
6079
6319
  var QuestionManager = class {
@@ -6119,13 +6359,13 @@ var QuestionManager = class {
6119
6359
  requestId,
6120
6360
  questionCount: params.questions.length
6121
6361
  });
6122
- log$12.debug("提问已发出", {
6362
+ log$16.debug("提问已发出", {
6123
6363
  requestId,
6124
6364
  questionCount: params.questions.length
6125
6365
  });
6126
6366
  const timeout = setTimeout(() => {
6127
6367
  if (!deferred.isSettled) {
6128
- log$12.warn("提问超时,自动取消", { requestId });
6368
+ log$16.warn("提问超时,自动取消", { requestId });
6129
6369
  deferred.reject(/* @__PURE__ */ new Error("提问超时,用户未在 5 分钟内回答"));
6130
6370
  this.pending.delete(requestId);
6131
6371
  eventBus.emit("question:timeout", { requestId });
@@ -6139,7 +6379,7 @@ var QuestionManager = class {
6139
6379
  requestId,
6140
6380
  answerCount: Object.keys(result.answers).length
6141
6381
  });
6142
- log$12.debug("提问已回答", { requestId });
6382
+ log$16.debug("提问已回答", { requestId });
6143
6383
  return result;
6144
6384
  } catch (err) {
6145
6385
  clearTimeout(timeout);
@@ -6148,7 +6388,7 @@ var QuestionManager = class {
6148
6388
  requestId,
6149
6389
  reason: err instanceof Error ? err.message : String(err)
6150
6390
  });
6151
- log$12.debug("提问已取消", {
6391
+ log$16.debug("提问已取消", {
6152
6392
  requestId,
6153
6393
  error: err instanceof Error ? err.message : String(err)
6154
6394
  });
@@ -6170,7 +6410,7 @@ var QuestionManager = class {
6170
6410
  const count = this.pending.size;
6171
6411
  for (const [, entry] of this.pending) if (!entry.deferred.isSettled) entry.deferred.reject(error);
6172
6412
  this.pending.clear();
6173
- if (count > 0) log$12.debug("已清理所有待处理问题", { count });
6413
+ if (count > 0) log$16.debug("已清理所有待处理问题", { count });
6174
6414
  }
6175
6415
  };
6176
6416
  let globalQuestionManager = null;
@@ -7248,7 +7488,7 @@ function isPathWithinWorkdir(resolvedPath, workdir) {
7248
7488
  try {
7249
7489
  normalizedWorkdir = realpathSync(normalizedWorkdir).replace(/\\/g, "/");
7250
7490
  } catch {}
7251
- if (!normalizedPath.startsWith(normalizedWorkdir + "/") && normalizedPath !== normalizedWorkdir) return false;
7491
+ if (!normalizedPath.startsWith(`${normalizedWorkdir}/`) && normalizedPath !== normalizedWorkdir) return false;
7252
7492
  return true;
7253
7493
  }
7254
7494
  /**
@@ -9403,7 +9643,7 @@ function runForeground(childProcess, timeoutSec, signal) {
9403
9643
  childProcess.on("error", (err) => {
9404
9644
  settle({
9405
9645
  stdout: stdoutChunks.join(""),
9406
- stderr: stderrChunks.join("") + `\n[进程错误: ${err.message}]`,
9646
+ stderr: `${stderrChunks.join("")}\n[进程错误: ${err.message}]`,
9407
9647
  exitCode: -1,
9408
9648
  signal: null,
9409
9649
  timedOut: false,
@@ -9724,7 +9964,7 @@ function formatDuration(ms) {
9724
9964
  }
9725
9965
  function truncateCommand(command, maxLen = 80) {
9726
9966
  if (command.length <= maxLen) return command;
9727
- return command.slice(0, maxLen - 3) + "...";
9967
+ return `${command.slice(0, maxLen - 3)}...`;
9728
9968
  }
9729
9969
 
9730
9970
  //#endregion
@@ -11596,7 +11836,7 @@ var WorktreeStore = class {
11596
11836
  * @module core/worktree
11597
11837
  */
11598
11838
  const execFileAsync = promisify(execFile);
11599
- const log$11 = logger.child("worktree-manager");
11839
+ const log$15 = logger.child("worktree-manager");
11600
11840
  let globalWorktreeManager = null;
11601
11841
  /** 获取全局 WorktreeManager 实例 */
11602
11842
  function getWorktreeManager() {
@@ -11632,7 +11872,7 @@ var WorktreeManager = class {
11632
11872
  throw new WorktreeError("无法确定 git 仓库根目录,请确保在 git 仓库中运行", "NOT_GIT_REPO");
11633
11873
  }
11634
11874
  try {
11635
- log$11.info("创建 worktree", {
11875
+ log$15.info("创建 worktree", {
11636
11876
  branchName,
11637
11877
  worktreePath,
11638
11878
  gitRoot
@@ -11660,14 +11900,14 @@ var WorktreeManager = class {
11660
11900
  });
11661
11901
  } catch (err) {
11662
11902
  const msg = err instanceof Error ? err.message : String(err);
11663
- log$11.warn("创建分支失败,清理 worktree", {
11903
+ log$15.warn("创建分支失败,清理 worktree", {
11664
11904
  branchName,
11665
11905
  error: msg
11666
11906
  });
11667
11907
  try {
11668
11908
  await this.removeByPath(worktreePath, branchName, true);
11669
11909
  } catch (err) {
11670
- log$11.warn("Worktree 创建失败后清理出错", {
11910
+ log$15.warn("Worktree 创建失败后清理出错", {
11671
11911
  worktreePath,
11672
11912
  error: err instanceof Error ? err.message : String(err)
11673
11913
  });
@@ -11692,7 +11932,7 @@ var WorktreeManager = class {
11692
11932
  createdBy: info.createdBy,
11693
11933
  status: "active"
11694
11934
  });
11695
- log$11.info("Worktree 创建成功", {
11935
+ log$15.info("Worktree 创建成功", {
11696
11936
  id: info.id,
11697
11937
  path: worktreePath
11698
11938
  });
@@ -11704,13 +11944,13 @@ var WorktreeManager = class {
11704
11944
  async remove(id, discardChanges) {
11705
11945
  const info = this.activeWorktrees.get(id);
11706
11946
  if (!info) {
11707
- log$11.warn("尝试删除不存在的 worktree", { id });
11947
+ log$15.warn("尝试删除不存在的 worktree", { id });
11708
11948
  return;
11709
11949
  }
11710
11950
  await this.removeByPath(info.worktreePath, info.branchName, discardChanges);
11711
11951
  this.activeWorktrees.delete(id);
11712
11952
  this.store.delete(id);
11713
- log$11.info("Worktree 已删除", { id });
11953
+ log$15.info("Worktree 已删除", { id });
11714
11954
  }
11715
11955
  /**
11716
11956
  * 通过路径删除 worktree(内部方法)
@@ -11720,7 +11960,7 @@ var WorktreeManager = class {
11720
11960
  try {
11721
11961
  await execFileAsync("git", ["worktree", "prune"], { timeout: 1e4 });
11722
11962
  } catch (err) {
11723
- log$11.warn("Worktree prune 失败(路径不存在)", {
11963
+ log$15.warn("Worktree prune 失败(路径不存在)", {
11724
11964
  worktreePath,
11725
11965
  error: err instanceof Error ? err.message : String(err)
11726
11966
  });
@@ -11734,7 +11974,7 @@ var WorktreeManager = class {
11734
11974
  await execFileAsync("git", args, { timeout: 15e3 });
11735
11975
  } catch (err) {
11736
11976
  const msg = err instanceof Error ? err.message : String(err);
11737
- log$11.warn("git worktree remove 失败", {
11977
+ log$15.warn("git worktree remove 失败", {
11738
11978
  worktreePath,
11739
11979
  error: msg
11740
11980
  });
@@ -11746,7 +11986,7 @@ var WorktreeManager = class {
11746
11986
  branchName
11747
11987
  ], { timeout: 1e4 });
11748
11988
  } catch (err) {
11749
- log$11.warn("删除 worktree 分支失败", {
11989
+ log$15.warn("删除 worktree 分支失败", {
11750
11990
  branchName,
11751
11991
  error: err instanceof Error ? err.message : String(err)
11752
11992
  });
@@ -11754,7 +11994,7 @@ var WorktreeManager = class {
11754
11994
  try {
11755
11995
  await execFileAsync("git", ["worktree", "prune"], { timeout: 1e4 });
11756
11996
  } catch (err) {
11757
- log$11.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) });
11758
11998
  }
11759
11999
  }
11760
12000
  /**
@@ -11776,7 +12016,7 @@ var WorktreeManager = class {
11776
12016
  await this.remove(id, true);
11777
12017
  return { cleaned: true };
11778
12018
  }
11779
- log$11.info("Worktree 有变更,保留", {
12019
+ log$15.info("Worktree 有变更,保留", {
11780
12020
  id,
11781
12021
  path: info.worktreePath
11782
12022
  });
@@ -11796,7 +12036,7 @@ var WorktreeManager = class {
11796
12036
  });
11797
12037
  return stdout.trim().length > 0;
11798
12038
  } catch (err) {
11799
- log$11.warn("检查 worktree 变更状态失败", {
12039
+ log$15.warn("检查 worktree 变更状态失败", {
11800
12040
  worktreePath,
11801
12041
  error: err instanceof Error ? err.message : String(err)
11802
12042
  });
@@ -11816,7 +12056,7 @@ var WorktreeManager = class {
11816
12056
  await this.removeByPath(record.worktreePath, record.branchName, true);
11817
12057
  cleaned++;
11818
12058
  } catch (err) {
11819
- log$11.warn("过期 worktree 清理失败", {
12059
+ log$15.warn("过期 worktree 清理失败", {
11820
12060
  id: record.id,
11821
12061
  path: record.worktreePath,
11822
12062
  error: err instanceof Error ? err.message : String(err)
@@ -11825,7 +12065,7 @@ var WorktreeManager = class {
11825
12065
  this.activeWorktrees.delete(record.id);
11826
12066
  this.store.delete(record.id);
11827
12067
  }
11828
- if (cleaned > 0) log$11.info("过期 worktree 清理完成", { cleaned });
12068
+ if (cleaned > 0) log$15.info("过期 worktree 清理完成", { cleaned });
11829
12069
  return cleaned;
11830
12070
  }
11831
12071
  listActive() {
@@ -11842,41 +12082,9 @@ var WorktreeManager = class {
11842
12082
  }
11843
12083
  };
11844
12084
 
11845
- //#endregion
11846
- //#region src/core/agent-team/types.ts
11847
- /**
11848
- * Agent 默认安全工具集
11849
- *
11850
- * 只读和搜索工具,不含任何可能产生副作用的操作。
11851
- * 子 Agent 默认使用此集合。
11852
- */
11853
- const AGENT_SAFE_TOOLS = [
11854
- "ReadFile",
11855
- "Glob",
11856
- "Grep",
11857
- "WebFetch",
11858
- "WebSearch",
11859
- "GetCurrentTime",
11860
- "GetWorkdirInfo"
11861
- ];
11862
- /**
11863
- * Agent 标准工具集
11864
- *
11865
- * safe + 文件写入 + Shell 执行。
11866
- */
11867
- const AGENT_STANDARD_TOOLS = [
11868
- ...AGENT_SAFE_TOOLS,
11869
- "WriteFile",
11870
- "EditFile",
11871
- "Exec",
11872
- "Process",
11873
- "TaskManage",
11874
- "Memory"
11875
- ];
11876
-
11877
12085
  //#endregion
11878
12086
  //#region src/core/agent-team/agent-factory.ts
11879
- const log$10 = logger.child("agent-factory");
12087
+ const log$14 = logger.child("agent-factory");
11880
12088
  /**
11881
12089
  * 创建 Agent 实例
11882
12090
  *
@@ -11910,7 +12118,7 @@ function createAgentFromType(definition, instance, parentAgent, availableTools,
11910
12118
  const tools = resolveTools(definition, availableTools, instance.depth, config);
11911
12119
  agent.registerTools(tools);
11912
12120
  agent.systemPromptOverride = buildSystemPrompt(definition, instance.task.description, config);
11913
- log$10.debug("创建 Agent 实例", {
12121
+ log$14.debug("创建 Agent 实例", {
11914
12122
  typeId: definition.typeId,
11915
12123
  instanceId: instance.instanceId,
11916
12124
  depth: instance.depth,
@@ -12004,7 +12212,7 @@ function shareParentResources(subAgent, parentAgent, definition) {
12004
12212
  const resolvedModel = resolveModelForDefinition(definition.model, parentAgent);
12005
12213
  if (resolvedModel) {
12006
12214
  subAgent.innerAgent.state.model = resolvedModel;
12007
- if (resolvedModel.id !== parentInner.state.model?.id) log$10.info(`子 Agent [${definition?.typeId}] 模型切换: ${parentInner.state.model?.id ?? "N/A"} → ${resolvedModel.id}`, {
12215
+ if (resolvedModel.id !== parentInner.state.model?.id) log$14.info(`子 Agent [${definition?.typeId}] 模型切换: ${parentInner.state.model?.id ?? "N/A"} → ${resolvedModel.id}`, {
12008
12216
  typeId: definition?.typeId,
12009
12217
  parentModel: parentInner.state.model?.id,
12010
12218
  childModel: resolvedModel.id
@@ -12019,7 +12227,7 @@ function shareParentResources(subAgent, parentAgent, definition) {
12019
12227
  if (parentGetApiKey) subAgent.innerAgent.getApiKey = parentGetApiKey;
12020
12228
  }
12021
12229
  /**
12022
- * 根据 Agent 类型声明的 model 值解析对应的 pi-ai Model 对象
12230
+ * 根据 Agent 类型声明的 model 值解析本地 Model 对象
12023
12231
  *
12024
12232
  * 支持三种格式:
12025
12233
  * - 语义名称: 'analysis' → 指向 analysisModel 槽位
@@ -12029,7 +12237,7 @@ function shareParentResources(subAgent, parentAgent, definition) {
12029
12237
  *
12030
12238
  * @param model - Agent 类型声明的 model 值
12031
12239
  * @param parentAgent - 父 Agent(用于获取 AgentLlmFacade)
12032
- * @returns 解析后的 pi-ai Model 对象,解析失败返回 undefined
12240
+ * @returns 解析后的本地 Model 对象,解析失败返回 undefined
12033
12241
  */
12034
12242
  function resolveModelForDefinition(model, parentAgent) {
12035
12243
  const facade = parentAgent.llmFacade;
@@ -12043,14 +12251,14 @@ function resolveModelForDefinition(model, parentAgent) {
12043
12251
  }
12044
12252
  } catch (error) {
12045
12253
  const message = error instanceof Error ? error.message : String(error);
12046
- log$10.warn(`解析 Agent 类型偏好模型 [${model}] 失败`, { error: message });
12254
+ log$14.warn(`解析 Agent 类型偏好模型 [${model}] 失败`, { error: message });
12047
12255
  return;
12048
12256
  }
12049
12257
  }
12050
12258
 
12051
12259
  //#endregion
12052
12260
  //#region src/core/agent-team/agent-result-aggregator.ts
12053
- const log$9 = logger.child("agent-result-aggregator");
12261
+ const log$13 = logger.child("agent-result-aggregator");
12054
12262
  /** 零值 TokenUsage */
12055
12263
  const ZERO_TOKEN = {
12056
12264
  inputTokens: 0,
@@ -12080,7 +12288,7 @@ function aggregateResults(teamId, workerResults) {
12080
12288
  estimatedCostUsd: sum.estimatedCostUsd + (r.tokenUsage?.estimatedCostUsd ?? 0)
12081
12289
  }), { ...ZERO_TOKEN });
12082
12290
  const summary = buildTeamSummary(workerResults);
12083
- log$9.debug("Team 结果聚合完成", {
12291
+ log$13.debug("Team 结果聚合完成", {
12084
12292
  teamId,
12085
12293
  total: workerResults.length,
12086
12294
  succeeded,
@@ -12158,7 +12366,7 @@ function escapeMarkdownTable(text) {
12158
12366
 
12159
12367
  //#endregion
12160
12368
  //#region src/core/agent-team/agent-orchestrator.ts
12161
- const log$8 = logger.child("agent-orchestrator");
12369
+ const log$12 = logger.child("agent-orchestrator");
12162
12370
  /**
12163
12371
  * Agent 编排器
12164
12372
  *
@@ -12191,7 +12399,7 @@ var AgentOrchestrator = class {
12191
12399
  const startTime = Date.now();
12192
12400
  const defaultType = getAgentTypeRegistry().getDefault();
12193
12401
  if (!defaultType) throw new Error("无法获取默认 Agent 类型(general-purpose)");
12194
- log$8.info("开始扁平并行执行", {
12402
+ log$12.info("开始扁平并行执行", {
12195
12403
  count: specs.length,
12196
12404
  maxConcurrent: this.flatConfig.maxConcurrent,
12197
12405
  hasContext: context != null
@@ -12199,7 +12407,7 @@ var AgentOrchestrator = class {
12199
12407
  const allResults = [];
12200
12408
  for (let i = 0; i < specs.length; i += this.flatConfig.maxConcurrent) {
12201
12409
  const batch = specs.slice(i, i + this.flatConfig.maxConcurrent);
12202
- log$8.debug("执行扁平批次", {
12410
+ log$12.debug("执行扁平批次", {
12203
12411
  batchStart: i,
12204
12412
  batchSize: batch.length,
12205
12413
  totalSpecs: specs.length
@@ -12209,7 +12417,7 @@ var AgentOrchestrator = class {
12209
12417
  }
12210
12418
  const succeeded = allResults.filter((r) => r.status === "success").length;
12211
12419
  const totalDuration = Date.now() - startTime;
12212
- log$8.info("扁平并行执行完成", {
12420
+ log$12.info("扁平并行执行完成", {
12213
12421
  total: allResults.length,
12214
12422
  succeeded,
12215
12423
  failed: allResults.length - succeeded,
@@ -12284,7 +12492,7 @@ var AgentOrchestrator = class {
12284
12492
  } catch (error) {
12285
12493
  const duration = Date.now() - startTime;
12286
12494
  const message = error instanceof Error ? error.message : String(error);
12287
- log$8.warn("扁平子任务执行失败", {
12495
+ log$12.warn("扁平子任务执行失败", {
12288
12496
  specId: spec.id,
12289
12497
  error: message,
12290
12498
  duration
@@ -12341,7 +12549,7 @@ var AgentOrchestrator = class {
12341
12549
  const parentInstanceId = options?.parentInstanceId ?? "";
12342
12550
  const depth = (parentInstanceId && instanceManager.get(parentInstanceId) ? instanceManager.get(parentInstanceId)?.depth ?? 0 : 0) + 1;
12343
12551
  if (depth > this.teamConfig.maxGlobalDepth) {
12344
- log$8.warn("Worker 创建被拒绝:超过全局最大深度", {
12552
+ log$12.warn("Worker 创建被拒绝:超过全局最大深度", {
12345
12553
  typeId,
12346
12554
  depth,
12347
12555
  maxGlobalDepth: this.teamConfig.maxGlobalDepth
@@ -12463,7 +12671,7 @@ var AgentOrchestrator = class {
12463
12671
  } catch (error) {
12464
12672
  const duration = Date.now() - startTime;
12465
12673
  const message = error instanceof Error ? error.message : String(error);
12466
- log$8.warn("Worker 执行失败", {
12674
+ log$12.warn("Worker 执行失败", {
12467
12675
  typeId,
12468
12676
  instanceId,
12469
12677
  error: message,
@@ -12506,12 +12714,12 @@ var AgentOrchestrator = class {
12506
12714
  const wm = getWorktreeManager();
12507
12715
  if (wm) try {
12508
12716
  const cleanResult = await wm.autoCleanIfNoChanges(worktreeInfo.id);
12509
- if (!cleanResult.cleaned) log$8.info("Worktree 有变更,保留", {
12717
+ if (!cleanResult.cleaned) log$12.info("Worktree 有变更,保留", {
12510
12718
  id: worktreeInfo.id,
12511
12719
  path: cleanResult.worktreePath
12512
12720
  });
12513
12721
  } catch (err) {
12514
- log$8.warn("Worktree 清理失败", {
12722
+ log$12.warn("Worktree 清理失败", {
12515
12723
  id: worktreeInfo.id,
12516
12724
  error: err instanceof Error ? err.message : String(err)
12517
12725
  });
@@ -12531,7 +12739,7 @@ var AgentOrchestrator = class {
12531
12739
  */
12532
12740
  async spawnTeam(taskDescription, workerSpecs) {
12533
12741
  const teamId = `team-${Date.now()}-${++this.teamCounter}`;
12534
- log$8.info("创建 Team", {
12742
+ log$12.info("创建 Team", {
12535
12743
  teamId,
12536
12744
  workerCount: workerSpecs.length,
12537
12745
  taskDescription
@@ -12564,7 +12772,29 @@ var AgentOrchestrator = class {
12564
12772
  const instanceManager = getAgentInstanceManager();
12565
12773
  const instance = instanceManager.get(instanceId);
12566
12774
  if (!instance) return;
12567
- 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) {
12568
12798
  const toolName = event.message;
12569
12799
  const prevUses = instance.currentActivity?.toolUses ?? 0;
12570
12800
  const parenIdx = toolName.indexOf("(");
@@ -12577,7 +12807,7 @@ var AgentOrchestrator = class {
12577
12807
  startedAt: Date.now()
12578
12808
  };
12579
12809
  instanceManager.setActivity(instanceId, activity);
12580
- } else if (event.percent === 100 || event.percent === -1) {}
12810
+ }
12581
12811
  };
12582
12812
  agent.on(agent.EVENT_PROGRESS, progressHandler);
12583
12813
  return () => {
@@ -12893,7 +13123,7 @@ function createReplBuiltinTools(webConfig, taskStore, skillConfig, parentAgent,
12893
13123
  *
12894
13124
  * @module cli/repl/skill-watcher
12895
13125
  */
12896
- const log$7 = logger.child("repl:skill-watcher");
13126
+ const log$11 = logger.child("repl:skill-watcher");
12897
13127
  /** 防抖延迟(毫秒) */
12898
13128
  const DEBOUNCE_MS = 500;
12899
13129
  /**
@@ -12923,15 +13153,15 @@ var SkillWatcher = class {
12923
13153
  });
12924
13154
  this.watcher.on("ready", () => {
12925
13155
  this._ready = true;
12926
- log$7.info("技能文件监视已就绪", { dirs: options.watchDirs });
13156
+ log$11.info("技能文件监视已就绪", { dirs: options.watchDirs });
12927
13157
  }).on("add", (path) => {
12928
- log$7.debug("技能文件新增", { path });
13158
+ log$11.debug("技能文件新增", { path });
12929
13159
  this.debounceNotify(options.onChanged);
12930
13160
  }).on("change", (path) => {
12931
- log$7.debug("技能文件修改", { path });
13161
+ log$11.debug("技能文件修改", { path });
12932
13162
  this.debounceNotify(options.onChanged);
12933
13163
  }).on("unlink", (path) => {
12934
- log$7.debug("技能文件删除", { path });
13164
+ log$11.debug("技能文件删除", { path });
12935
13165
  this.debounceNotify(options.onChanged);
12936
13166
  });
12937
13167
  }
@@ -13446,6 +13676,233 @@ function createLspTool(lspManager) {
13446
13676
  };
13447
13677
  }
13448
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
+
13449
13906
  //#endregion
13450
13907
  //#region src/cli/repl/utils/markdown-formatter.ts
13451
13908
  /**
@@ -13572,9 +14029,9 @@ function formatTable(token, colorEnabled) {
13572
14029
  output += "┌";
13573
14030
  for (let i = 0; i < columnCount; i++) {
13574
14031
  const cw = colWidths[i] ?? 3;
13575
- output += "─".repeat(cw + 2) + "┬";
14032
+ output += `${"─".repeat(cw + 2)}┬`;
13576
14033
  }
13577
- output = output.slice(0, -1) + "┐\n";
14034
+ output = `${output.slice(0, -1)}┐${EOL}`;
13578
14035
  output += "│ ";
13579
14036
  for (let i = 0; i < columnCount; i++) {
13580
14037
  const cell = token.header[i];
@@ -13611,9 +14068,9 @@ function formatTable(token, colorEnabled) {
13611
14068
  output += "└";
13612
14069
  for (let i = 0; i < columnCount; i++) {
13613
14070
  const cw = colWidths[i] ?? 3;
13614
- output += "─".repeat(cw + 2) + "┴";
14071
+ output += `${"─".repeat(cw + 2)}┴`;
13615
14072
  }
13616
- output = output.slice(0, -1) + "┘\n";
14073
+ output = `${output.slice(0, -1)}┘${EOL}`;
13617
14074
  return output + EOL;
13618
14075
  }
13619
14076
  /**
@@ -13680,6 +14137,93 @@ function normalizeMcpConfig(raw) {
13680
14137
  return servers;
13681
14138
  }
13682
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
+
13683
14227
  //#endregion
13684
14228
  //#region src/core/lsp/diagnostics.ts
13685
14229
  function createDiagnosticCollector() {
@@ -13942,10 +14486,10 @@ function createLspClient(config) {
13942
14486
  const reader = createMessageReader(handleMessage, (err) => trace("←", `Reader error: ${err.message}`), () => {
13943
14487
  if (!isStopping) rejectAllPending(new LspError("LSP stdout closed", "STDOUT_CLOSED"));
13944
14488
  }, (msg) => trace("←", msg));
13945
- childProcess.stdout.on("data", (chunk) => {
14489
+ childProcess.stdout?.on("data", (chunk) => {
13946
14490
  reader.feed(chunk);
13947
14491
  });
13948
- childProcess.stdout.on("end", () => {
14492
+ childProcess.stdout?.on("end", () => {
13949
14493
  if (!isStopping) rejectAllPending(new LspError("LSP stdout closed unexpectedly", "STDOUT_CLOSED"));
13950
14494
  });
13951
14495
  messageWriter = createMessageWriter(childProcess.stdin, (msg) => trace("→", msg));
@@ -13999,7 +14543,7 @@ function createLspClient(config) {
13999
14543
  timer,
14000
14544
  method
14001
14545
  });
14002
- messageWriter.write(request).catch((err) => {
14546
+ messageWriter?.write(request).catch((err) => {
14003
14547
  clearTimeout(timer);
14004
14548
  pendingRequests.delete(id);
14005
14549
  reject(err);
@@ -14039,19 +14583,19 @@ function createLspClient(config) {
14039
14583
  }
14040
14584
  async function shutdown() {
14041
14585
  if (!childProcess || isStopping) return;
14042
- isStopping = true;
14043
14586
  try {
14044
14587
  await sendRequest("shutdown");
14045
14588
  } catch {}
14046
14589
  try {
14047
14590
  await sendNotification("exit");
14048
14591
  } catch {}
14592
+ isStopping = true;
14049
14593
  await new Promise((resolve) => {
14050
14594
  const timer = setTimeout(() => {
14051
14595
  if (childProcess && !childProcess.killed) childProcess.kill("SIGKILL");
14052
14596
  resolve();
14053
14597
  }, 2e3);
14054
- childProcess.once("exit", () => {
14598
+ childProcess?.once("exit", () => {
14055
14599
  clearTimeout(timer);
14056
14600
  resolve();
14057
14601
  });
@@ -14109,7 +14653,7 @@ function createLspServerInstance(config) {
14109
14653
  if (openedDocuments.get(uri)) return;
14110
14654
  await ensureRunning();
14111
14655
  try {
14112
- await client.sendNotification("textDocument/didOpen", { textDocument: {
14656
+ await client?.sendNotification("textDocument/didOpen", { textDocument: {
14113
14657
  uri,
14114
14658
  languageId,
14115
14659
  version: 1,
@@ -14181,6 +14725,7 @@ function createLspServerInstance(config) {
14181
14725
  async function request(method, params) {
14182
14726
  await ensureRunning();
14183
14727
  requestCount++;
14728
+ if (!client) throw new LspError("CLIENT_NOT_INITIALIZED", "LSP 客户端未初始化");
14184
14729
  try {
14185
14730
  return await client.sendRequest(method, params);
14186
14731
  } catch (err) {
@@ -14533,7 +15078,7 @@ function withTimeout(promise, ms) {
14533
15078
  /**
14534
15079
  * 将单个 MCP Tool 转换为 zapmyco ToolRegistration
14535
15080
  *
14536
- * MCP SDK 的 TextContent / ImageContent 与 pi-ai 对应类型结构兼容
15081
+ * MCP SDK 的 TextContent / ImageContent 与本地类型结构兼容
14537
15082
  * (都是 { type, text/data, mimeType }),因此直接透传 content。
14538
15083
  */
14539
15084
  function mcpToolToRegistration(mcpTool, serverName, client) {
@@ -15085,7 +15630,7 @@ var AuditLogger = class {
15085
15630
  const entries = this.buffer.splice(0);
15086
15631
  try {
15087
15632
  this.rotateIfNeeded();
15088
- const lines = entries.map((e) => JSON.stringify(e)).join("\n") + "\n";
15633
+ const lines = `${entries.map((e) => JSON.stringify(e)).join("\n")}\n`;
15089
15634
  appendFileSync(this.filePath, lines, "utf-8");
15090
15635
  } catch (err) {
15091
15636
  log$3.error("审计日志写入失败", { error: err instanceof Error ? err.message : String(err) });
@@ -15189,7 +15734,7 @@ function resolveConfig(config = {}) {
15189
15734
  function matchToolPattern(pattern, toolId) {
15190
15735
  if (pattern === toolId) return true;
15191
15736
  if (pattern === "*") return true;
15192
- return new RegExp("^" + pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*") + "$").test(toolId);
15737
+ return new RegExp(`^${pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*")}$`).test(toolId);
15193
15738
  }
15194
15739
  /**
15195
15740
  * 参数模式匹配
@@ -15852,7 +16397,7 @@ function threatSeverity(level) {
15852
16397
  /**
15853
16398
  * REPL 会话核心(pi-tui 版)
15854
16399
  *
15855
- * 使用 @mariozechner/pi-tui 框架替代 readline,
16400
+ * 使用 @earendil-works/pi-tui 框架替代 readline,
15856
16401
  * 实现完整的 TUI 交互式 REPL:
15857
16402
  * - Editor 组件自带上下边框
15858
16403
  * - 差量渲染,无闪烁
@@ -15880,9 +16425,24 @@ function getApiKeyErrorHelp(errorMessage) {
15880
16425
  */
15881
16426
  var OutputArea = class extends Container {
15882
16427
  lines = [];
16428
+ /** 逐行缓存的渲染结果(避免每帧对所有历史行重新调用 wrapTextWithAnsi) */
16429
+ lineCache = /* @__PURE__ */ new Map();
16430
+ /** 缓存生成时使用的终端宽度,变化时重建 */
16431
+ cacheWidth = 0;
15883
16432
  render(width) {
16433
+ if (width !== this.cacheWidth) {
16434
+ this.cacheWidth = width;
16435
+ this.lineCache.clear();
16436
+ }
15884
16437
  const result = [];
15885
- 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
+ }
15886
16446
  return result;
15887
16447
  }
15888
16448
  /** 追加多行内容,返回新追加行中第一行的索引 */
@@ -15895,33 +16455,45 @@ var OutputArea = class extends Container {
15895
16455
  /** 追加文本到当前行末尾(用于流式输出) */
15896
16456
  appendText(text) {
15897
16457
  if (this.lines.length === 0) this.lines.push(text);
15898
- 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
+ }
15899
16462
  this.invalidate();
15900
16463
  }
15901
16464
  /** 替换最后一行的完整内容(用于 spinner 动画和首 chunk 替换),返回行索引 */
15902
16465
  replaceLastLine(text) {
15903
- if (this.lines.length > 0) this.lines[this.lines.length - 1] = text;
15904
- 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);
15905
16470
  this.invalidate();
15906
16471
  return this.lines.length - 1;
15907
16472
  }
15908
16473
  /** 在指定位置插入/删除/替换行(原子操作) */
15909
16474
  spliceLines(startIndex, deleteCount, insertLines) {
15910
16475
  this.lines.splice(startIndex, deleteCount, ...insertLines);
16476
+ this.invalidateCacheFrom(startIndex);
15911
16477
  this.invalidate();
15912
16478
  }
15913
16479
  /** 更新指定索引行的内容 */
15914
16480
  updateLine(index, text) {
15915
16481
  if (index >= 0 && index < this.lines.length) {
15916
16482
  this.lines[index] = text;
16483
+ this.lineCache.delete(index);
15917
16484
  this.invalidate();
15918
16485
  }
15919
16486
  }
15920
16487
  /** 清空所有内容 */
15921
16488
  clear() {
15922
16489
  this.lines = [];
16490
+ this.lineCache.clear();
15923
16491
  this.invalidate();
15924
16492
  }
16493
+ /** 使从指定索引开始的所有缓存行失效 */
16494
+ invalidateCacheFrom(startIndex) {
16495
+ for (const key of this.lineCache.keys()) if (key >= startIndex) this.lineCache.delete(key);
16496
+ }
15925
16497
  };
15926
16498
  /** 默认提示符(用于显示/格式化) */
15927
16499
  const DEFAULT_PROMPT = "❯ ";
@@ -15945,6 +16517,8 @@ var ReplSession = class {
15945
16517
  renderer;
15946
16518
  history;
15947
16519
  currentTaskAbort = null;
16520
+ /** 动画管理器(渲染周期驱动的 spinner/计时器,替代 setInterval) */
16521
+ animationManager = new AnimationManager();
15948
16522
  /** 欢迎语逐字输出定时器引用(用于 shutdown 时清理) */
15949
16523
  welcomeTypewriterInterval;
15950
16524
  /** Agent 实例(会话级复用,替代直接 LLM 调用) */
@@ -15997,6 +16571,7 @@ var ReplSession = class {
15997
16571
  const theme = createTheme(this.options.color);
15998
16572
  const terminal = new ProcessTerminal();
15999
16573
  this.tui = new TUI(terminal);
16574
+ this.animationManager.bind(this.tui);
16000
16575
  getKeybindings().setUserBindings({
16001
16576
  "tui.select.up": ["up"],
16002
16577
  "tui.select.down": ["down"],
@@ -16005,13 +16580,13 @@ var ReplSession = class {
16005
16580
  });
16006
16581
  this.taskStore = new TaskStore();
16007
16582
  this.outputArea = new OutputArea();
16008
- this.agentStatusBar = new AgentStatusBar();
16009
- this.taskStatusBar = new TaskStatusBar(this.taskStore);
16583
+ this.agentStatusBar = new AgentStatusBar(this.animationManager);
16584
+ this.taskStatusBar = new TaskStatusBar(this.taskStore, this.animationManager);
16010
16585
  this.taskStore.onChange(() => {
16011
16586
  this.taskStatusBar.onTasksChanged();
16012
16587
  this.tui.requestRender();
16013
16588
  });
16014
- this.editor = new ZapmycoEditor(this.tui, theme.editorTheme);
16589
+ this.editor = new ZapmycoEditor(this.tui, theme.editorTheme, this.animationManager);
16015
16590
  const root = new Container();
16016
16591
  root.addChild(this.outputArea);
16017
16592
  root.addChild(this.taskStatusBar);
@@ -16130,7 +16705,7 @@ var ReplSession = class {
16130
16705
  * 彻底清空 Agent 会话上下文。
16131
16706
  *
16132
16707
  * - 清空消息历史、Token 统计、缓存、授权
16133
- * - 保留配置、记忆、持久化任务、定时任务
16708
+ * - 保留配置、记忆、定时任务
16134
16709
  * - 效果等价于"新开一个会话"
16135
16710
  *
16136
16711
  * 由 /clear 命令调用。
@@ -16153,6 +16728,7 @@ var ReplSession = class {
16153
16728
  this.questionManager.rejectAll(/* @__PURE__ */ new Error("会话已重置"));
16154
16729
  this.editor.setExecuting(false);
16155
16730
  this.outputArea.clear();
16731
+ this.taskStore.clear();
16156
16732
  this.agentStatusBar.clearTokenStats();
16157
16733
  this.tui.requestRender();
16158
16734
  }
@@ -16257,8 +16833,70 @@ var ReplSession = class {
16257
16833
  const execFailStyle = (s) => colorEnabled ? chalk.red(s) : s;
16258
16834
  const dimStyle = (s) => colorEnabled ? chalk.gray(s) : s;
16259
16835
  let spinnerActive = true;
16260
- let spinnerInterval;
16261
- 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");
16262
16900
  /** Token 信息推送定时器 */
16263
16901
  let tokenPushInterval;
16264
16902
  try {
@@ -16277,12 +16915,13 @@ var ReplSession = class {
16277
16915
  this.outputArea.append([userStyle(displayLabel ?? rawInput), LOADING_FRAMES$1[0] ?? ""]);
16278
16916
  let spinnerFrame = 0;
16279
16917
  spinnerActive = true;
16280
- spinnerInterval = setInterval(() => {
16918
+ animCleanup.push(this.animationManager.register((now) => {
16281
16919
  if (!spinnerActive) return;
16920
+ if (now - lastSpinnerTick < 100) return;
16921
+ lastSpinnerTick = now;
16282
16922
  spinnerFrame = (spinnerFrame + 1) % LOADING_FRAMES$1.length;
16283
16923
  this.outputArea.replaceLastLine(LOADING_FRAMES$1[spinnerFrame] ?? "");
16284
- this.tui.requestRender();
16285
- }, 100);
16924
+ }));
16286
16925
  let spinnerStopped = false;
16287
16926
  let outputAccumulator = "";
16288
16927
  let thinkingAccumulator = "";
@@ -16327,20 +16966,20 @@ var ReplSession = class {
16327
16966
  };
16328
16967
  const outputHandler = (event) => {
16329
16968
  if (event.taskId !== taskId || !event.text) return;
16969
+ if (hasExecutedTool) {
16970
+ hasExecutedTool = false;
16971
+ instanceManager.transition(mainInstance.instanceId, "completed");
16972
+ }
16330
16973
  if (!spinnerStopped) {
16331
16974
  spinnerStopped = true;
16332
16975
  spinnerActive = false;
16333
- clearInterval(spinnerInterval);
16334
16976
  streamMode = "response";
16335
16977
  thinkingAccumulator = "";
16336
16978
  outputAccumulator = event.text;
16337
16979
  responseLineIndex = this.outputArea.replaceLastLine(responseStyle(outputAccumulator));
16338
16980
  } else if (streamMode !== "response") {
16339
16981
  streamMode = "response";
16340
- if (thinkingElapsedInterval) {
16341
- clearInterval(thinkingElapsedInterval);
16342
- thinkingElapsedInterval = void 0;
16343
- }
16982
+ thinkingTimerActive = false;
16344
16983
  thinkingAccumulator = "";
16345
16984
  outputAccumulator = event.text;
16346
16985
  if (thinkingHeaderLineIndex !== null) {
@@ -16352,7 +16991,7 @@ var ReplSession = class {
16352
16991
  outputAccumulator += event.text;
16353
16992
  this.outputArea.replaceLastLine(responseStyle(outputAccumulator));
16354
16993
  }
16355
- this.tui.requestRender();
16994
+ requestCoalescedRender();
16356
16995
  };
16357
16996
  const thinkingHandler = (event) => {
16358
16997
  if (event.taskId !== taskId || !event.text) return;
@@ -16363,7 +17002,6 @@ var ReplSession = class {
16363
17002
  if (!spinnerStopped) {
16364
17003
  spinnerStopped = true;
16365
17004
  spinnerActive = false;
16366
- clearInterval(spinnerInterval);
16367
17005
  thinkingHeaderLineIndex = this.outputArea.replaceLastLine(dimStyle(` ${LOADING_FRAMES$1[0] ?? ""} Thinking...`));
16368
17006
  } else thinkingHeaderLineIndex = this.outputArea.append([dimStyle(` ${LOADING_FRAMES$1[0] ?? ""} Thinking...`)]);
16369
17007
  thinkingAccumulator = event.text;
@@ -16372,16 +17010,19 @@ var ReplSession = class {
16372
17010
  this.outputArea.append([dimStyle(THINKING_CONTINUE_PREFIX + thinkingAccumulator)]);
16373
17011
  thinkingContentLineCount = 1;
16374
17012
  } else if (thinkingDisplayMode === "collapse") {
16375
- let thinkingFrame = 0;
16376
- thinkingElapsedInterval = setInterval(() => {
17013
+ thinkingTimerActive = true;
17014
+ thinkingFrame = 0;
17015
+ animCleanup.push(this.animationManager.register((now) => {
17016
+ if (!thinkingTimerActive) return;
16377
17017
  if (thinkingHeaderLineIndex === null) return;
16378
17018
  if (thinkingContentLineCount > 0) return;
17019
+ if (now - lastThinkingTick < 200) return;
17020
+ lastThinkingTick = now;
16379
17021
  const elapsed = ((Date.now() - thinkingStartTimeMs) / 1e3).toFixed(1);
16380
17022
  const frame = LOADING_FRAMES$1[thinkingFrame % LOADING_FRAMES$1.length];
16381
17023
  thinkingFrame++;
16382
17024
  this.outputArea.updateLine(thinkingHeaderLineIndex, dimStyle(` ${frame} Thinking... (${elapsed}s)`));
16383
- this.tui.requestRender();
16384
- }, 200);
17025
+ }));
16385
17026
  }
16386
17027
  } else {
16387
17028
  thinkingAccumulator += event.text;
@@ -16392,38 +17033,87 @@ var ReplSession = class {
16392
17033
  this.outputArea.replaceLastLine(dimStyle(THINKING_CONTINUE_PREFIX + lastLine));
16393
17034
  }
16394
17035
  }
16395
- this.tui.requestRender();
17036
+ requestCoalescedRender();
16396
17037
  };
16397
17038
  const errorHandler = (event) => {
16398
17039
  if (event.taskId === taskId) log.error("Agent 执行中收到 error 事件", { error: event.error.message });
16399
17040
  };
16400
17041
  let execLineIndex;
16401
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 = "";
16402
17051
  const progressHandler = (event) => {
16403
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
+ }
16404
17070
  if (event.percent === 0) {
16405
- if (thinkingElapsedInterval && streamMode === "thinking") {
16406
- clearInterval(thinkingElapsedInterval);
16407
- thinkingElapsedInterval = void 0;
17071
+ if (thinkingTimerActive && streamMode === "thinking") {
17072
+ thinkingTimerActive = false;
16408
17073
  if (thinkingHeaderLineIndex !== null) {
16409
17074
  this.outputArea.spliceLines(thinkingHeaderLineIndex, 1, []);
16410
17075
  thinkingHeaderLineIndex = null;
16411
- this.tui.requestRender();
17076
+ requestCoalescedRender();
16412
17077
  }
16413
17078
  }
17079
+ hasExecutedTool = true;
16414
17080
  if (event.message.startsWith("Exec(")) {
16415
17081
  execMessage = event.message;
16416
17082
  execLineIndex = this.outputArea.append([execStyle(` ⏺ ${event.message}`)]);
16417
- } else if (event.message.startsWith("TaskManage(")) log.debug("TaskManage 工具调用已记录,跳过 TUI 展示");
16418
- else this.outputArea.append([toolStyle(` → ${event.message}`)]);
16419
- 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();
16420
17110
  } else if (event.percent === 100 && execLineIndex !== void 0 && execMessage) {
16421
17111
  if (event.message.startsWith("工具 Exec")) {
16422
17112
  const style = event.message.includes("完成") ? execSuccessStyle : execFailStyle;
16423
17113
  this.outputArea.updateLine(execLineIndex, style(` ⏺ ${execMessage}`));
16424
17114
  execLineIndex = void 0;
16425
17115
  execMessage = void 0;
16426
- this.tui.requestRender();
17116
+ requestCoalescedRender();
16427
17117
  }
16428
17118
  }
16429
17119
  };
@@ -16447,9 +17137,32 @@ var ReplSession = class {
16447
17137
  taskId,
16448
17138
  taskDescription: rawInput.slice(0, 100)
16449
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
+ }
16450
17163
  const taskResult = await this.agent.execute({
16451
17164
  taskId,
16452
- taskDescription: rawInput,
17165
+ taskDescription: effectiveTaskDescription,
16453
17166
  workdir: process.cwd(),
16454
17167
  options: {
16455
17168
  timeout: this.config.scheduler.taskTimeoutMs,
@@ -16461,6 +17174,12 @@ var ReplSession = class {
16461
17174
  this.agent.off(this.agent.EVENT_ERROR, errorHandler);
16462
17175
  this.agent.off(this.agent.EVENT_PROGRESS, progressHandler);
16463
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();
16464
17183
  log.debug("Agent 执行完成", {
16465
17184
  taskId,
16466
17185
  status: taskResult.status,
@@ -16475,7 +17194,6 @@ var ReplSession = class {
16475
17194
  ""
16476
17195
  ]);
16477
17196
  spinnerActive = false;
16478
- clearInterval(spinnerInterval);
16479
17197
  try {
16480
17198
  const compactionResult = await this.agent.compact();
16481
17199
  if (compactionResult.success) {
@@ -16487,17 +17205,10 @@ var ReplSession = class {
16487
17205
  this.outputArea.append([chalk.red(" 上下文整理异常,请手动执行 /compact 后重试"), ""]);
16488
17206
  }
16489
17207
  spinnerActive = true;
16490
- spinnerInterval = setInterval(() => {
16491
- if (!spinnerActive) return;
16492
- spinnerFrame = (spinnerFrame + 1) % LOADING_FRAMES$1.length;
16493
- this.outputArea.replaceLastLine(LOADING_FRAMES$1[spinnerFrame] ?? "");
16494
- this.tui.requestRender();
16495
- }, 100);
16496
17208
  }
16497
17209
  const outputText = typeof taskResult.output === "string" ? taskResult.output : taskResult.output != null ? JSON.stringify(taskResult.output) : null;
16498
17210
  if (spinnerActive) {
16499
17211
  spinnerActive = false;
16500
- clearInterval(spinnerInterval);
16501
17212
  if (outputText) this.outputArea.replaceLastLine(responseStyle(outputText));
16502
17213
  else if (taskResult.status !== "success") {
16503
17214
  const errorMsg = taskResult.error?.message ?? t("session.agentErrorMessage");
@@ -16609,11 +17320,7 @@ var ReplSession = class {
16609
17320
  const err = error instanceof Error ? error : new Error(String(error));
16610
17321
  log.error("目标执行失败", { input: rawInput }, err);
16611
17322
  spinnerActive = false;
16612
- clearInterval(spinnerInterval);
16613
- if (thinkingElapsedInterval) {
16614
- clearInterval(thinkingElapsedInterval);
16615
- thinkingElapsedInterval = void 0;
16616
- }
17323
+ thinkingTimerActive = false;
16617
17324
  this.stats.totalRequests++;
16618
17325
  this.stats.failureCount++;
16619
17326
  eventBus.emit("goal:failed", {
@@ -16677,11 +17384,9 @@ var ReplSession = class {
16677
17384
  };
16678
17385
  } finally {
16679
17386
  spinnerActive = false;
16680
- if (spinnerInterval) clearInterval(spinnerInterval);
16681
- if (thinkingElapsedInterval) {
16682
- clearInterval(thinkingElapsedInterval);
16683
- thinkingElapsedInterval = void 0;
16684
- }
17387
+ thinkingTimerActive = false;
17388
+ for (const unregister of animCleanup) unregister();
17389
+ animCleanup.length = 0;
16685
17390
  if (tokenPushInterval) {
16686
17391
  clearInterval(tokenPushInterval);
16687
17392
  tokenPushInterval = void 0;
@@ -16847,7 +17552,7 @@ var ReplSession = class {
16847
17552
  /**
16848
17553
  * 创建 REPL 专用的 Agent 实例
16849
17554
  *
16850
- * Agent 复用 pi-ai 的 Model 对象进行 LLM 调用,
17555
+ * Agent 复用本地 Model 对象进行 LLM 调用,
16851
17556
  * 因此需要从 config.llm 解析 model 并注入到 Agent state。
16852
17557
  */
16853
17558
  createReplAgent() {
@@ -16863,7 +17568,7 @@ var ReplSession = class {
16863
17568
  runtimeConfig: this.config.agentRuntime ?? {}
16864
17569
  });
16865
17570
  const facade = new AgentLlmFacade(this.config.llm);
16866
- agent.innerAgent.state.model = facade.resolvePiModel();
17571
+ agent.innerAgent.state.model = facade.resolveResolvedModel();
16867
17572
  agent.innerAgent.getApiKey = facade.createGetApiKeyFn();
16868
17573
  agent.llmFacade = facade;
16869
17574
  agent.innerAgent.cacheRetention = this.config.agentRuntime?.cacheRetention;
@@ -16892,17 +17597,36 @@ var ReplSession = class {
16892
17597
  return agent;
16893
17598
  }
16894
17599
  /**
17600
+ * 判断是否处于 Coordinator 模式
17601
+ *
17602
+ * 当 agentTeam.defaultMode === 'coordinator' 时,
17603
+ * 主 Agent 切换为编排模式:工具受限、系统提示词替换。
17604
+ */
17605
+ isCoordinatorMode() {
17606
+ return this.config.agentTeam?.defaultMode === "coordinator";
17607
+ }
17608
+ /**
16895
17609
  * 注册 REPL 场景下的基础工具
16896
17610
  *
16897
17611
  * 在注册内置工具后,异步初始化 MCP 工具。
16898
17612
  * MCP 连接不阻塞内置工具注册——Agent 立即可用内置工具,
16899
17613
  * MCP 工具在连接完成后自动追加。
17614
+ *
17615
+ * 在 Coordinator 模式下,主 Agent 仅注册编排工具
17616
+ *(AgentTool + SendMessage + TaskStop),完整工具集
17617
+ * 仍传递给 AgentOrchestrator 供子 Agent 使用。
16900
17618
  */
16901
17619
  registerBuiltinTools() {
16902
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);
16903
17621
  this.permissionEngine.setToolInfoResolver(createToolInfoResolver(rawTools));
16904
- 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);
16905
17628
  this.agent.registerTools(guardedTools);
17629
+ if (this.isCoordinatorMode()) this.agent.systemPromptOverride = getMainCoordinatorSystemPrompt(process.cwd());
16906
17630
  const mcpServers = this.config.mcp ? normalizeMcpConfig(this.config.mcp) : [];
16907
17631
  if (mcpServers.length > 0) initializeMcpTools(mcpServers, this.agent).then((manager) => {
16908
17632
  this.mcpManager = manager;
@@ -17101,14 +17825,14 @@ var ReplSession = class {
17101
17825
  this.editor.onCtrlD = () => {
17102
17826
  this.shutdown("收到 EOF (Ctrl+D)");
17103
17827
  };
17104
- this.editor.onToggleAgentBar = () => {
17105
- this.agentStatusBar.toggle();
17106
- this.tui.requestRender();
17107
- };
17108
17828
  this.editor.onOpenEditor = () => this.openInEditor();
17109
- this.editor.onToggleTasks = () => {
17110
- this.taskStatusBar.toggle();
17111
- 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
+ }
17112
17836
  };
17113
17837
  }
17114
17838
  /** 设置信号处理 */
@@ -17128,10 +17852,12 @@ var ReplSession = class {
17128
17852
  instanceManager.on("instance:registered", refreshStatusBar);
17129
17853
  instanceManager.on("instance:transitioned", refreshStatusBar);
17130
17854
  instanceManager.on("instance:activity", refreshStatusBar);
17855
+ instanceManager.on("instance:toolcall", refreshStatusBar);
17131
17856
  eventBus.on("system:shutdown", () => {
17132
17857
  instanceManager.off("instance:registered", refreshStatusBar);
17133
17858
  instanceManager.off("instance:transitioned", refreshStatusBar);
17134
17859
  instanceManager.off("instance:activity", refreshStatusBar);
17860
+ instanceManager.off("instance:toolcall", refreshStatusBar);
17135
17861
  });
17136
17862
  }
17137
17863
  /** 取消当前正在执行的任务 */
@@ -17297,7 +18023,7 @@ var ReplSession = class {
17297
18023
  /**
17298
18024
  * 从消息 content 中提取文本内容
17299
18025
  *
17300
- * pi-ai 的消息 content 可能是 string(UserMessage)或 content blocks 数组(AssistantMessage),
18026
+ * 消息 content 可能是 string(UserMessage)或 content blocks 数组(AssistantMessage),
17301
18027
  * 此函数统一处理两种格式,提取所有文本块和 thinking 块并拼接。
17302
18028
  *
17303
18029
  * thinking 内容会被包裹在 <thinking>...</thinking> 标签中以便区分。
@@ -17352,10 +18078,10 @@ var ConversationLogger = class {
17352
18078
  this.turnCount++;
17353
18079
  try {
17354
18080
  this.rotateIfNeeded();
17355
- const line = JSON.stringify({
18081
+ const line = `${JSON.stringify({
17356
18082
  ...turn,
17357
18083
  turn: this.turnCount
17358
- }) + "\n";
18084
+ })}\n`;
17359
18085
  appendFileSync(this.filePath, line, "utf-8");
17360
18086
  } catch (err) {
17361
18087
  logger.warn("对话日志写入失败", { error: err instanceof Error ? err.message : String(err) });