zapmyco 0.9.0 → 0.10.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,33 @@
1
1
  #!/usr/bin/env node
2
- import { D as configureLogger, H as SESSION_DIR_NAME, I as ZapmycoErrorCode, L as buildSkillSnapshot, O as logger, P as WebError, R as loadSkills, T as DEFAULT_COMPACTION_CONFIG, U as VERSION, V as APP_NAME, W as __require, h as createLlmBasedAgent, i as AgentLlmFacade, n as loadConfig, p as SubAgentManager, t as HOME_CONFIG_PATH, w as eventBus } from "../loader-Dtu0hhLu.mjs";
2
+ import { D as configureLogger, H as SESSION_DIR_NAME, I as ZapmycoErrorCode, L as buildSkillSnapshot, O as logger, P as WebError, R as loadSkills, T as DEFAULT_COMPACTION_CONFIG, U as VERSION, V as APP_NAME, W as __require, h as createLlmBasedAgent, i as AgentLlmFacade, n as loadConfig, p as SubAgentManager, t as HOME_CONFIG_PATH, w as eventBus } from "../loader-36UrSNj8.mjs";
3
3
  import { createHash, randomBytes } from "node:crypto";
4
- import { appendFileSync, existsSync, mkdirSync, readFileSync, realpathSync, statSync, unlinkSync, writeFileSync } from "node:fs";
4
+ import { appendFileSync, existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, statSync, unlinkSync, writeFileSync } from "node:fs";
5
5
  import { mkdir, readFile, rename, 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
- import { dirname, isAbsolute, join, normalize, resolve } from "node:path";
9
+ import { dirname, isAbsolute, join, normalize, relative, resolve } from "node:path";
10
10
  import { EventEmitter } from "node:events";
11
11
  import { getModels, getProviders } from "@mariozechner/pi-ai";
12
12
  import chalk, { Chalk } from "chalk";
13
13
  import { Command } from "commander";
14
- import { spawn, spawnSync } from "node:child_process";
14
+ import { execFile, spawn, spawnSync } from "node:child_process";
15
15
  import { CombinedAutocompleteProvider, Container, Editor, Input, Key, ProcessTerminal, SelectList, TUI, getKeybindings, matchesKey, truncateToWidth, wrapTextWithAnsi } from "@mariozechner/pi-tui";
16
16
  import i18next from "i18next";
17
+ import { AsyncLocalStorage } from "node:async_hooks";
17
18
  import TurndownService from "turndown";
18
19
  import { lookup } from "node:dns/promises";
20
+ import { promisify } from "node:util";
19
21
  import { Client } from "@modelcontextprotocol/sdk/client";
20
22
  import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
21
23
 
22
24
  //#region src/cli/repl/command-registry.ts
23
- const log$16 = logger.child("repl:command-registry");
25
+ const log$21 = logger.child("repl:command-registry");
24
26
  /**
25
27
  * 命令注册表
26
28
  */
27
29
  var CommandRegistry = class {
30
+ session;
28
31
  commands = /* @__PURE__ */ new Map();
29
32
  aliasMap = /* @__PURE__ */ new Map();
30
33
  constructor(session) {
@@ -35,7 +38,7 @@ var CommandRegistry = class {
35
38
  */
36
39
  register(cmd) {
37
40
  const canonicalName = cmd.name.toLowerCase();
38
- if (this.commands.has(canonicalName)) log$16.warn(`命令 "${canonicalName}" 已存在,将被覆盖`);
41
+ if (this.commands.has(canonicalName)) log$21.warn(`命令 "${canonicalName}" 已存在,将被覆盖`);
39
42
  this.commands.set(canonicalName, cmd);
40
43
  for (const alias of cmd.aliases) {
41
44
  const lowerAlias = alias.toLowerCase();
@@ -63,7 +66,7 @@ var CommandRegistry = class {
63
66
  */
64
67
  async dispatch(parsed) {
65
68
  if (parsed.kind !== "command") {
66
- log$16.warn("dispatch 收到了非 command 类型的输入");
69
+ log$21.warn("dispatch 收到了非 command 类型的输入");
67
70
  return;
68
71
  }
69
72
  const cmd = this.getCommand(parsed.name);
@@ -75,7 +78,7 @@ var CommandRegistry = class {
75
78
  await cmd.handler(parsed.args, this.session);
76
79
  } catch (error) {
77
80
  const message = error instanceof Error ? error.message : String(error);
78
- log$16.error(`命令 /${cmd.name} 执行出错`, {}, error);
81
+ log$21.error(`命令 /${cmd.name} 执行出错`, {}, error);
79
82
  console.log(`\n 命令执行出错: ${message}\n`);
80
83
  }
81
84
  }
@@ -444,7 +447,7 @@ const noColor = {
444
447
 
445
448
  //#endregion
446
449
  //#region src/core/agent-team/agent-instance-manager.ts
447
- const log$15 = logger.child("agent-instance-manager");
450
+ const log$20 = logger.child("agent-instance-manager");
448
451
  /**
449
452
  * Agent 实例状态转换表
450
453
  *
@@ -508,7 +511,7 @@ var AgentInstanceManager = class {
508
511
  const parent = this.instances.get(parentInstanceId);
509
512
  if (parent) parent.childInstanceIds.push(instance.instanceId);
510
513
  }
511
- log$15.debug("注册 Agent 实例", {
514
+ log$20.debug("注册 Agent 实例", {
512
515
  instanceId: instance.instanceId,
513
516
  typeId: definition.typeId,
514
517
  depth,
@@ -526,12 +529,12 @@ var AgentInstanceManager = class {
526
529
  transition(instanceId, newStatus) {
527
530
  const instance = this.instances.get(instanceId);
528
531
  if (!instance) {
529
- log$15.warn("状态转换失败:实例不存在", { instanceId });
532
+ log$20.warn("状态转换失败:实例不存在", { instanceId });
530
533
  return false;
531
534
  }
532
535
  const allowed = VALID_TRANSITIONS[instance.status];
533
536
  if (!allowed.includes(newStatus)) {
534
- log$15.warn("状态转换拒绝", {
537
+ log$20.warn("状态转换拒绝", {
535
538
  instanceId,
536
539
  from: instance.status,
537
540
  to: newStatus,
@@ -540,7 +543,7 @@ var AgentInstanceManager = class {
540
543
  return false;
541
544
  }
542
545
  instance.status = newStatus;
543
- log$15.debug("Agent 实例状态转换", {
546
+ log$20.debug("Agent 实例状态转换", {
544
547
  instanceId,
545
548
  typeId: instance.typeId,
546
549
  from: instance.status,
@@ -641,7 +644,7 @@ var AgentInstanceManager = class {
641
644
  instance.agent.removeAllListeners();
642
645
  instance.agent.systemPromptOverride = null;
643
646
  this.instances.delete(instanceId);
644
- log$15.debug("Agent 实例已清理", { instanceId });
647
+ log$20.debug("Agent 实例已清理", { instanceId });
645
648
  }
646
649
  /**
647
650
  * 清理所有终态的实例
@@ -923,6 +926,7 @@ const plannerType = {
923
926
  "- **务实优先**:选择最简单可行的方案,避免过度设计",
924
927
  "- **分阶段交付**:将方案拆解为增量可交付的阶段",
925
928
  "- **可派生子 Agent**:需要调研时 spawn researcher 并行搜索",
929
+ "- **交互式确认**:遇到需要用户决策的方向性问题时,使用 AskUserQuestion 工具获取用户偏好。不要自行假设用户的需求",
926
930
  "",
927
931
  `## 工作目录\n${ctx.workdir}`
928
932
  ];
@@ -1083,7 +1087,7 @@ const BUILTIN_AGENT_TYPES = [
1083
1087
  *
1084
1088
  * @module core/agent-team
1085
1089
  */
1086
- const log$14 = logger.child("agent-type-registry");
1090
+ const log$19 = logger.child("agent-type-registry");
1087
1091
  /**
1088
1092
  * Agent 类型注册中心
1089
1093
  *
@@ -1107,14 +1111,14 @@ var AgentTypeRegistry = class {
1107
1111
  */
1108
1112
  register(definition) {
1109
1113
  const existing = this.types.get(definition.typeId);
1110
- if (existing) log$14.info("覆盖已注册的 Agent 类型", {
1114
+ if (existing) log$19.info("覆盖已注册的 Agent 类型", {
1111
1115
  typeId: definition.typeId,
1112
1116
  oldSource: existing.source,
1113
1117
  newSource: definition.source
1114
1118
  });
1115
1119
  this.types.set(definition.typeId, definition);
1116
1120
  this.refreshCache();
1117
- log$14.debug("注册 Agent 类型", {
1121
+ log$19.debug("注册 Agent 类型", {
1118
1122
  typeId: definition.typeId,
1119
1123
  source: definition.source
1120
1124
  });
@@ -1127,7 +1131,7 @@ var AgentTypeRegistry = class {
1127
1131
  registerAll(definitions) {
1128
1132
  for (const def of definitions) this.types.set(def.typeId, def);
1129
1133
  this.refreshCache();
1130
- log$14.info("批量注册 Agent 类型", { count: definitions.length });
1134
+ log$19.info("批量注册 Agent 类型", { count: definitions.length });
1131
1135
  }
1132
1136
  /**
1133
1137
  * 注销 Agent 类型
@@ -1139,7 +1143,7 @@ var AgentTypeRegistry = class {
1139
1143
  const result = this.types.delete(typeId);
1140
1144
  if (result) {
1141
1145
  this.refreshCache();
1142
- log$14.debug("注销 Agent 类型", { typeId });
1146
+ log$19.debug("注销 Agent 类型", { typeId });
1143
1147
  }
1144
1148
  return result;
1145
1149
  }
@@ -1226,7 +1230,7 @@ var AgentTypeRegistry = class {
1226
1230
  loadBuiltinTypes() {
1227
1231
  for (const def of BUILTIN_AGENT_TYPES) this.types.set(def.typeId, def);
1228
1232
  this.refreshCache();
1229
- log$14.info("加载内置 Agent 类型", { count: BUILTIN_AGENT_TYPES.length });
1233
+ log$19.info("加载内置 Agent 类型", { count: BUILTIN_AGENT_TYPES.length });
1230
1234
  }
1231
1235
  /**
1232
1236
  * 刷新可见类型缓存
@@ -2028,7 +2032,7 @@ function setLocale(locale) {
2028
2032
  * Provides SelectList and TextInput overlay dialogs.
2029
2033
  */
2030
2034
  /** Overlay layout options for menus */
2031
- const OVERLAY_OPTIONS = {
2035
+ const OVERLAY_OPTIONS$1 = {
2032
2036
  width: "100%",
2033
2037
  anchor: "top-left",
2034
2038
  margin: {
@@ -2166,7 +2170,7 @@ function showSelectList(tui, items, options) {
2166
2170
  handle?.hide();
2167
2171
  resolve(null);
2168
2172
  };
2169
- handle = tui.showOverlay(list, OVERLAY_OPTIONS);
2173
+ handle = tui.showOverlay(list, OVERLAY_OPTIONS$1);
2170
2174
  });
2171
2175
  }
2172
2176
  var TextInputComponent = class {
@@ -3430,7 +3434,7 @@ const CRON_CONSTANTS = {
3430
3434
  *
3431
3435
  * @module cli/repl/cron/cron-scheduler
3432
3436
  */
3433
- const log$13 = logger.child("cron:scheduler");
3437
+ const log$18 = logger.child("cron:scheduler");
3434
3438
  var CronScheduler = class extends EventEmitter {
3435
3439
  store;
3436
3440
  jobs = [];
@@ -3451,7 +3455,7 @@ var CronScheduler = class extends EventEmitter {
3451
3455
  if (this.running) return;
3452
3456
  const loadedJobs = await this.store.load();
3453
3457
  this.jobs = loadedJobs;
3454
- log$13.info(`调度器启动,加载 ${loadedJobs.length} 个 durable 任务`);
3458
+ log$18.info(`调度器启动,加载 ${loadedJobs.length} 个 durable 任务`);
3455
3459
  await this.handleMissedJobs();
3456
3460
  this.checkAutoExpiry();
3457
3461
  this.running = true;
@@ -3467,7 +3471,7 @@ var CronScheduler = class extends EventEmitter {
3467
3471
  clearInterval(this.timer);
3468
3472
  this.timer = null;
3469
3473
  }
3470
- log$13.info("调度器已停止");
3474
+ log$18.info("调度器已停止");
3471
3475
  }
3472
3476
  /** 添加任务 */
3473
3477
  async addJob(job) {
@@ -3479,7 +3483,7 @@ var CronScheduler = class extends EventEmitter {
3479
3483
  this.jobs.push(job);
3480
3484
  await this.store.persist(this.jobs);
3481
3485
  } else this.sessionJobs.push(job);
3482
- log$13.info("任务已添加", {
3486
+ log$18.info("任务已添加", {
3483
3487
  id: job.id,
3484
3488
  cron: job.cron,
3485
3489
  durable: job.durable
@@ -3609,7 +3613,7 @@ var CronScheduler = class extends EventEmitter {
3609
3613
  }, delay);
3610
3614
  }
3611
3615
  if (toDelete.length > 0) {
3612
- log$13.info(`跳过 ${toDelete.length} 个错过的一次性任务(超出补发上限)`);
3616
+ log$18.info(`跳过 ${toDelete.length} 个错过的一次性任务(超出补发上限)`);
3613
3617
  this.emit("missed-overflow", {
3614
3618
  count: toDelete.length,
3615
3619
  jobIds: toDelete.map((m) => m.id)
@@ -3627,7 +3631,7 @@ var CronScheduler = class extends EventEmitter {
3627
3631
  job.lastFiredAt = now;
3628
3632
  job.fireCount++;
3629
3633
  this.removeJob(job.id);
3630
- log$13.info("任务已过期并触发最后一次", { id: job.id });
3634
+ log$18.info("任务已过期并触发最后一次", { id: job.id });
3631
3635
  }
3632
3636
  }
3633
3637
  }
@@ -3688,7 +3692,7 @@ function applyOneShotJitter(jobId, rawNext) {
3688
3692
  *
3689
3693
  * @module cli/repl/cron/cron-store
3690
3694
  */
3691
- const log$12 = logger.child("cron:store");
3695
+ const log$17 = logger.child("cron:store");
3692
3696
  const STORE_FILE = join(join(homedir(), ".zapmyco", "cron"), "scheduled_tasks.json");
3693
3697
  var CronStore = class {
3694
3698
  filePath;
@@ -3712,13 +3716,13 @@ var CronStore = class {
3712
3716
  const raw = await readFile(this.filePath, "utf-8");
3713
3717
  const data = JSON.parse(raw);
3714
3718
  if (!Array.isArray(data)) {
3715
- log$12.warn("存储文件格式无效(非数组),将使用空列表");
3719
+ log$17.warn("存储文件格式无效(非数组),将使用空列表");
3716
3720
  return [];
3717
3721
  }
3718
3722
  return this.validateJobs(data);
3719
3723
  } catch (err) {
3720
3724
  if (err.code === "ENOENT") return [];
3721
- log$12.warn("加载定时任务文件失败,将使用空列表", { error: err instanceof Error ? err.message : String(err) });
3725
+ log$17.warn("加载定时任务文件失败,将使用空列表", { error: err instanceof Error ? err.message : String(err) });
3722
3726
  return [];
3723
3727
  }
3724
3728
  }
@@ -3765,7 +3769,7 @@ var CronStore = class {
3765
3769
  if (typeof obj.maxFires === "number") job.maxFires = obj.maxFires;
3766
3770
  valid.push(job);
3767
3771
  }
3768
- if (valid.length < raw.length) log$12.warn(`跳过 ${raw.length - valid.length} 个无效任务条目`);
3772
+ if (valid.length < raw.length) log$17.warn(`跳过 ${raw.length - valid.length} 个无效任务条目`);
3769
3773
  return valid;
3770
3774
  }
3771
3775
  };
@@ -3783,7 +3787,7 @@ function getCronStore() {
3783
3787
  * 基于内存的环形缓冲区,记录 REPL 会话中的用户输入和执行结果。
3784
3788
  * 支持文件持久化到 ~/.zapmyco/history.json,跨会话恢复。
3785
3789
  */
3786
- const log$11 = logger.child("history:store");
3790
+ const log$16 = logger.child("history:store");
3787
3791
  /** 默认最大历史条数 */
3788
3792
  const DEFAULT_MAX_SIZE = 100;
3789
3793
  /** 历史文件存储路径 */
@@ -3842,13 +3846,13 @@ var HistoryStore = class {
3842
3846
  if (Array.isArray(data.entries)) {
3843
3847
  this.entries = data.entries.slice(-this.maxSize);
3844
3848
  this.nextId = typeof data.nextId === "number" ? data.nextId : 1;
3845
- log$11.debug("历史记录已加载", {
3849
+ log$16.debug("历史记录已加载", {
3846
3850
  count: this.entries.length,
3847
3851
  nextId: this.nextId
3848
3852
  });
3849
3853
  }
3850
3854
  } catch {
3851
- log$11.debug("无历史文件或加载失败,使用空历史");
3855
+ log$16.debug("无历史文件或加载失败,使用空历史");
3852
3856
  }
3853
3857
  }
3854
3858
  /** 持久化历史记录到文件 */
@@ -3861,7 +3865,7 @@ var HistoryStore = class {
3861
3865
  }, null, 2);
3862
3866
  writeFileSync(this.filePath, data, "utf-8");
3863
3867
  } catch (err) {
3864
- log$11.warn("历史记录保存失败", { error: err instanceof Error ? err.message : String(err) });
3868
+ log$16.warn("历史记录保存失败", { error: err instanceof Error ? err.message : String(err) });
3865
3869
  }
3866
3870
  }
3867
3871
  };
@@ -3961,6 +3965,432 @@ var InputParser = class {
3961
3965
  }
3962
3966
  };
3963
3967
 
3968
+ //#endregion
3969
+ //#region src/cli/repl/components/ask-user-question.ts
3970
+ const OVERLAY_OPTIONS = {
3971
+ width: "100%",
3972
+ anchor: "top-left",
3973
+ margin: {
3974
+ top: 1,
3975
+ bottom: 1
3976
+ }
3977
+ };
3978
+ /** 截断文本到指定宽度 */
3979
+ function truncate(text, maxLen) {
3980
+ if (text.length <= maxLen) return text;
3981
+ return text.slice(0, maxLen - 1) + "…";
3982
+ }
3983
+ var AskUserQuestionComponent = class {
3984
+ tui;
3985
+ questions;
3986
+ onResolve;
3987
+ onCancel;
3988
+ phase = "answering";
3989
+ currentQuestionIndex = 0;
3990
+ questionStates;
3991
+ selectedOptionIndex = 0;
3992
+ otherInputValue = "";
3993
+ showPreview = false;
3994
+ constructor(tui, params, onResolve, onCancel) {
3995
+ this.tui = tui;
3996
+ this.questions = params.questions;
3997
+ this.onResolve = onResolve;
3998
+ this.onCancel = onCancel;
3999
+ this.questionStates = this.questions.map(() => ({
4000
+ selectedLabels: [],
4001
+ otherText: ""
4002
+ }));
4003
+ const hasPreview = this.questions.some((q) => q.options.some((o) => o.preview));
4004
+ this.showPreview = hasPreview;
4005
+ }
4006
+ /** 安全获取当前问题 */
4007
+ getCurrentQuestion() {
4008
+ return this.questions[this.currentQuestionIndex];
4009
+ }
4010
+ /** 安全获取当前问题的状态 */
4011
+ getCurrentState() {
4012
+ return this.questionStates[this.currentQuestionIndex];
4013
+ }
4014
+ handleInput(data) {
4015
+ if (data === "escape" || data === "q") {
4016
+ if (this.phase === "other_input") {
4017
+ this.phase = "answering";
4018
+ return;
4019
+ }
4020
+ if (this.phase === "reviewing") {
4021
+ this.phase = "answering";
4022
+ return;
4023
+ }
4024
+ this.onCancel?.();
4025
+ return;
4026
+ }
4027
+ switch (this.phase) {
4028
+ case "answering":
4029
+ this.handleAnsweringInput(data);
4030
+ break;
4031
+ case "other_input":
4032
+ this.handleOtherInput(data);
4033
+ break;
4034
+ case "reviewing":
4035
+ this.handleReviewingInput(data);
4036
+ break;
4037
+ }
4038
+ }
4039
+ handleAnsweringInput(data) {
4040
+ const q = this.getCurrentQuestion();
4041
+ const state = this.getCurrentState();
4042
+ const optionCount = q.options.length;
4043
+ if (data >= "1" && data <= "9") {
4044
+ const idx = Number.parseInt(data, 10) - 1;
4045
+ if (idx < optionCount) {
4046
+ this.selectOption(idx);
4047
+ return;
4048
+ }
4049
+ if (idx === optionCount) {
4050
+ this.enterOtherInput();
4051
+ return;
4052
+ }
4053
+ return;
4054
+ }
4055
+ switch (data) {
4056
+ case "up":
4057
+ case "k":
4058
+ if (this.selectedOptionIndex > 0) this.selectedOptionIndex--;
4059
+ return;
4060
+ case "down":
4061
+ case "j":
4062
+ if (this.selectedOptionIndex < optionCount) this.selectedOptionIndex++;
4063
+ return;
4064
+ case " ":
4065
+ if (q.multiSelect && this.selectedOptionIndex < optionCount) this.toggleOption(this.selectedOptionIndex);
4066
+ return;
4067
+ case "tab":
4068
+ this.advanceQuestion();
4069
+ return;
4070
+ case "shift+tab":
4071
+ if (this.currentQuestionIndex > 0) {
4072
+ this.currentQuestionIndex--;
4073
+ this.selectedOptionIndex = 0;
4074
+ }
4075
+ return;
4076
+ case "enter":
4077
+ if (q.multiSelect) {
4078
+ if (state.selectedLabels.length > 0) this.advanceQuestion();
4079
+ } else if (this.selectedOptionIndex < optionCount) {
4080
+ this.selectOption(this.selectedOptionIndex);
4081
+ this.advanceQuestion();
4082
+ } else if (this.selectedOptionIndex === optionCount) this.enterOtherInput();
4083
+ return;
4084
+ case "o":
4085
+ this.enterOtherInput();
4086
+ return;
4087
+ case "p":
4088
+ this.showPreview = !this.showPreview;
4089
+ return;
4090
+ case "h":
4091
+ case "backspace":
4092
+ if (this.currentQuestionIndex > 0) {
4093
+ this.currentQuestionIndex--;
4094
+ this.selectedOptionIndex = 0;
4095
+ }
4096
+ return;
4097
+ default: break;
4098
+ }
4099
+ }
4100
+ handleOtherInput(data) {
4101
+ if (data === "enter") {
4102
+ const trimmed = this.otherInputValue.trim();
4103
+ if (trimmed) {
4104
+ const state = this.getCurrentState();
4105
+ state.selectedLabels = [trimmed];
4106
+ state.otherText = trimmed;
4107
+ }
4108
+ this.otherInputValue = "";
4109
+ this.phase = "answering";
4110
+ this.advanceQuestion();
4111
+ return;
4112
+ }
4113
+ if (data === "escape") {
4114
+ this.otherInputValue = "";
4115
+ this.phase = "answering";
4116
+ return;
4117
+ }
4118
+ if (data === "backspace" || data === "" || data === "\b") {
4119
+ this.otherInputValue = this.otherInputValue.slice(0, -1);
4120
+ return;
4121
+ }
4122
+ if (data.length === 1 && data >= " " && data <= "~") this.otherInputValue += data;
4123
+ }
4124
+ handleReviewingInput(data) {
4125
+ if (data === "enter") {
4126
+ this.submitAnswers();
4127
+ return;
4128
+ }
4129
+ if (data === "escape") {
4130
+ this.phase = "answering";
4131
+ this.currentQuestionIndex = this.questions.length - 1;
4132
+ this.selectedOptionIndex = 0;
4133
+ return;
4134
+ }
4135
+ }
4136
+ selectOption(index) {
4137
+ const state = this.getCurrentState();
4138
+ const option = this.getCurrentQuestion().options[index];
4139
+ if (this.getCurrentQuestion().multiSelect) this.toggleOption(index);
4140
+ else state.selectedLabels = [option.label];
4141
+ }
4142
+ toggleOption(index) {
4143
+ const state = this.getCurrentState();
4144
+ const option = this.getCurrentQuestion().options[index];
4145
+ const idx = state.selectedLabels.indexOf(option.label);
4146
+ if (idx >= 0) state.selectedLabels.splice(idx, 1);
4147
+ else state.selectedLabels.push(option.label);
4148
+ }
4149
+ enterOtherInput() {
4150
+ this.otherInputValue = this.getCurrentState().otherText;
4151
+ this.phase = "other_input";
4152
+ }
4153
+ advanceQuestion() {
4154
+ if (this.currentQuestionIndex < this.questions.length - 1) {
4155
+ this.currentQuestionIndex++;
4156
+ this.selectedOptionIndex = 0;
4157
+ } else this.phase = "reviewing";
4158
+ }
4159
+ submitAnswers() {
4160
+ const answers = {};
4161
+ const annotations = {};
4162
+ for (let i = 0; i < this.questions.length; i++) {
4163
+ const q = this.questions[i];
4164
+ const state = this.questionStates[i];
4165
+ if (q.multiSelect) answers[q.question] = [...state.selectedLabels];
4166
+ else answers[q.question] = state.selectedLabels[0] ?? "";
4167
+ if (state.otherText) annotations[q.question] = { notes: `自定义: ${state.otherText}` };
4168
+ }
4169
+ this.onResolve?.({
4170
+ questions: this.questions,
4171
+ answers,
4172
+ annotations: Object.keys(annotations).length > 0 ? annotations : void 0
4173
+ });
4174
+ }
4175
+ invalidate() {}
4176
+ render(width) {
4177
+ switch (this.phase) {
4178
+ case "reviewing": return this.renderReview(width);
4179
+ case "other_input": return this.renderOtherInput(width);
4180
+ default: return this.renderQuestion(width);
4181
+ }
4182
+ }
4183
+ renderQuestion(fullWidth) {
4184
+ const c = chalk;
4185
+ const showPreviewPanel = this.showPreview && this.questions.some((q) => q.options.some((o) => o.preview));
4186
+ const mainWidth = showPreviewPanel ? Math.floor(fullWidth * .58) : fullWidth;
4187
+ const previewWidth = showPreviewPanel ? fullWidth - mainWidth - 1 : 0;
4188
+ const mainLines = this.renderMainPanel(mainWidth);
4189
+ let previewLines = [];
4190
+ if (showPreviewPanel) previewLines = this.renderPreviewPanel(previewWidth);
4191
+ const maxLines = Math.max(mainLines.length, previewLines.length);
4192
+ const result = [];
4193
+ for (let i = 0; i < maxLines; i++) {
4194
+ const left = i < mainLines.length ? mainLines[i] : " ".repeat(mainWidth);
4195
+ const right = i < previewLines.length ? previewLines[i] : "";
4196
+ const separator = showPreviewPanel ? c.gray("│") : "";
4197
+ result.push(`${left}${separator}${right}`);
4198
+ }
4199
+ return result;
4200
+ }
4201
+ renderMainPanel(width) {
4202
+ const c = chalk;
4203
+ const lines = [];
4204
+ lines.push("");
4205
+ lines.push(this.renderTabs(width));
4206
+ lines.push(c.gray(` ${"─".repeat(Math.max(0, width - 4))}`));
4207
+ lines.push("");
4208
+ const q = this.getCurrentQuestion();
4209
+ const wrappedQuestion = this.wrapText(q.question, width - 4);
4210
+ for (const line of wrappedQuestion) lines.push(c.bold(` ${line}`));
4211
+ lines.push("");
4212
+ const state = this.getCurrentState();
4213
+ for (let i = 0; i < q.options.length; i++) {
4214
+ const opt = q.options[i];
4215
+ const isSelected = q.multiSelect ? state.selectedLabels.includes(opt.label) : state.selectedLabels[0] === opt.label;
4216
+ const isFocused = this.selectedOptionIndex === i;
4217
+ const prefix = `${i + 1}`;
4218
+ const checkbox = isSelected ? c.green("●") : "○";
4219
+ const label = isFocused ? c.cyan.bold(opt.label) : c.bold(opt.label);
4220
+ const desc = c.gray(` ${opt.description}`);
4221
+ const line = ` ${c.gray(prefix)} ${checkbox} ${label}`;
4222
+ lines.push(line);
4223
+ if (isFocused && opt.description) lines.push(` ${desc}`);
4224
+ }
4225
+ const otherIdx = q.options.length;
4226
+ const isOtherFocused = this.selectedOptionIndex === otherIdx;
4227
+ const otherLabel = isOtherFocused ? c.cyan.bold("其他 (自定义)") : c.gray("其他 (自定义)");
4228
+ const otherPrefix = `${otherIdx + 1}`;
4229
+ lines.push(` ${c.gray(otherPrefix)} ${otherLabel}`);
4230
+ if (isOtherFocused) lines.push(` ${c.gray("输入自定义答案")}`);
4231
+ lines.push("");
4232
+ lines.push(c.gray(` ${"─".repeat(Math.max(0, width - 4))}`));
4233
+ const termHeight = this.tui.terminal.rows;
4234
+ const padding = Math.max(0, termHeight - 1 - lines.length - 3);
4235
+ for (let i = 0; i < padding; i++) lines.push("");
4236
+ if (q.multiSelect) lines.push(c.gray(` ${this.buildMultiFooter()}`));
4237
+ else lines.push(c.gray(` ${this.buildSingleFooter()}`));
4238
+ lines.push("");
4239
+ return lines;
4240
+ }
4241
+ renderTabs(width) {
4242
+ const c = chalk;
4243
+ const parts = [];
4244
+ for (let i = 0; i < this.questions.length; i++) {
4245
+ const q = this.questions[i];
4246
+ const isAnswered = this.questionStates[i].selectedLabels.length > 0;
4247
+ const isCurrent = i === this.currentQuestionIndex;
4248
+ const header = truncate(q.header, 10);
4249
+ const indicator = isAnswered ? c.green("✓") : c.gray("○");
4250
+ let tab;
4251
+ if (isCurrent) tab = c.bgCyan.black(` ${indicator} ${header} `);
4252
+ else if (isAnswered) tab = c.green(` ${indicator} ${header} `);
4253
+ else tab = c.gray(` ${indicator} ${header} `);
4254
+ parts.push(tab);
4255
+ }
4256
+ const submitTab = this.phase === "reviewing" ? c.bgGreen.black(" 提交 ") : c.gray(" 提交 ");
4257
+ parts.push(submitTab);
4258
+ return ` ${parts.join(" ")}`;
4259
+ }
4260
+ renderPreviewPanel(width) {
4261
+ const c = chalk;
4262
+ const lines = [];
4263
+ if (width < 15) return lines;
4264
+ lines.push(c.bold(" 预览"));
4265
+ lines.push(c.gray("─".repeat(Math.max(0, width - 2))));
4266
+ const q = this.getCurrentQuestion();
4267
+ if (this.selectedOptionIndex < q.options.length) {
4268
+ const opt = q.options[this.selectedOptionIndex];
4269
+ if (opt.preview) {
4270
+ const previewLines = opt.preview.split("\n");
4271
+ const maxPreviewLines = 20;
4272
+ for (const line of previewLines.slice(0, maxPreviewLines)) lines.push(` ${c.gray(truncate(line, width - 2))}`);
4273
+ if (previewLines.length > maxPreviewLines) lines.push(c.gray(` ... 还有 ${previewLines.length - maxPreviewLines} 行`));
4274
+ } else lines.push(c.gray(" (无预览)"));
4275
+ } else lines.push(c.gray(" (无预览)"));
4276
+ const maxLines = 22;
4277
+ while (lines.length < maxLines) lines.push("");
4278
+ return lines;
4279
+ }
4280
+ renderOtherInput(width) {
4281
+ const c = chalk;
4282
+ const q = this.getCurrentQuestion();
4283
+ const lines = [];
4284
+ lines.push("");
4285
+ lines.push(this.renderTabs(width));
4286
+ lines.push(c.gray(` ${"─".repeat(Math.max(0, width - 4))}`));
4287
+ lines.push("");
4288
+ lines.push(c.bold(` ${q.question}`));
4289
+ lines.push("");
4290
+ lines.push(` 输入自定义答案:`);
4291
+ lines.push("");
4292
+ lines.push(c.yellow(` > ${this.otherInputValue}█`));
4293
+ lines.push("");
4294
+ lines.push(c.gray(` ${"─".repeat(Math.max(0, width - 4))}`));
4295
+ lines.push(c.gray(" Enter 确认 · Esc 返回 · 输入自定义文本"));
4296
+ lines.push("");
4297
+ return lines;
4298
+ }
4299
+ renderReview(width) {
4300
+ const c = chalk;
4301
+ const lines = [];
4302
+ lines.push("");
4303
+ lines.push(c.bold(" 确认你的答案"));
4304
+ lines.push(c.gray(` ${"─".repeat(Math.max(0, width - 4))}`));
4305
+ lines.push("");
4306
+ for (let i = 0; i < this.questions.length; i++) {
4307
+ const q = this.questions[i];
4308
+ const state = this.questionStates[i];
4309
+ const answer = q.multiSelect ? state.selectedLabels.join(", ") : state.selectedLabels[0] ?? c.gray("(未回答)");
4310
+ const header = truncate(q.header, 15);
4311
+ lines.push(` ${c.bold(header)}: ${answer}`);
4312
+ }
4313
+ lines.push("");
4314
+ lines.push(c.gray(` ${"─".repeat(Math.max(0, width - 4))}`));
4315
+ lines.push(c.gray(" Enter 提交答案 · Esc 返回修改"));
4316
+ lines.push("");
4317
+ return lines;
4318
+ }
4319
+ buildSingleFooter() {
4320
+ const parts = [];
4321
+ const q = this.getCurrentQuestion();
4322
+ parts.push(`1-${q.options.length} 选择`);
4323
+ parts.push("k/j ↑↓ 导航");
4324
+ if (q.options.some((o) => o.preview)) parts.push("p 切换预览");
4325
+ if (this.questions.length > 1) parts.push("Tab 下一题");
4326
+ parts.push("Enter 确认");
4327
+ parts.push("o 自定义");
4328
+ parts.push("Esc 取消");
4329
+ return parts.join(" · ");
4330
+ }
4331
+ buildMultiFooter() {
4332
+ const parts = [];
4333
+ const q = this.getCurrentQuestion();
4334
+ parts.push(`1-${q.options.length} 切换`);
4335
+ parts.push("Space 选择");
4336
+ parts.push("k/j ↑↓ 导航");
4337
+ if (q.options.some((o) => o.preview)) parts.push("p 切换预览");
4338
+ if (this.questions.length > 1) parts.push("Tab 下一题");
4339
+ parts.push("Enter 确认选择");
4340
+ parts.push("o 自定义");
4341
+ parts.push("Esc 取消");
4342
+ return parts.join(" · ");
4343
+ }
4344
+ wrapText(text, maxWidth) {
4345
+ if (text.length <= maxWidth) return [text];
4346
+ const lines = [];
4347
+ let remaining = text;
4348
+ while (remaining.length > maxWidth) {
4349
+ let breakAt = maxWidth;
4350
+ const lastSpace = remaining.lastIndexOf(" ", maxWidth);
4351
+ if (lastSpace > maxWidth / 2) breakAt = lastSpace;
4352
+ lines.push(remaining.slice(0, breakAt));
4353
+ remaining = remaining.slice(breakAt).trim();
4354
+ }
4355
+ if (remaining) lines.push(remaining);
4356
+ return lines;
4357
+ }
4358
+ };
4359
+ /**
4360
+ * 显示 AskUserQuestion 对话框
4361
+ *
4362
+ * @param tui - TUI 实例
4363
+ * @param params - 问题参数
4364
+ * @returns 用户回答结果,取消时 reject
4365
+ */
4366
+ function showAskUserQuestionDialog(tui, params) {
4367
+ return new Promise((resolve, reject) => {
4368
+ let handle = null;
4369
+ const component = new AskUserQuestionComponent(tui, params, (result) => {
4370
+ handle?.hide();
4371
+ resolve(result);
4372
+ }, () => {
4373
+ handle?.hide();
4374
+ reject(/* @__PURE__ */ new Error("用户取消了提问"));
4375
+ });
4376
+ handle = tui.showOverlay(component, OVERLAY_OPTIONS);
4377
+ });
4378
+ }
4379
+
4380
+ //#endregion
4381
+ //#region src/cli/repl/question-provider.ts
4382
+ /**
4383
+ * 创建基于 TUI 的问题提供者
4384
+ *
4385
+ * @param tui - TUI 实例
4386
+ * @returns QuestionProvider 实现
4387
+ */
4388
+ function createTuiQuestionProvider(tui) {
4389
+ return { async showQuestions(params) {
4390
+ return showAskUserQuestionDialog(tui, params);
4391
+ } };
4392
+ }
4393
+
3964
4394
  //#endregion
3965
4395
  //#region src/cli/repl/components/output-area.ts
3966
4396
  /**
@@ -3975,6 +4405,7 @@ var InputParser = class {
3975
4405
  * 从原 Renderer 中提取的纯格式化逻辑,不依赖 console.log。
3976
4406
  */
3977
4407
  var OutputFormatter = class {
4408
+ color;
3978
4409
  c;
3979
4410
  cDisabled;
3980
4411
  constructor(color) {
@@ -4292,133 +4723,1186 @@ var Renderer = class {
4292
4723
  };
4293
4724
 
4294
4725
  //#endregion
4295
- //#region src/cli/repl/tools/agent-tool.ts
4726
+ //#region src/security/tool-guard.ts
4296
4727
  /**
4297
- * 创建 AgentTool 工具注册
4728
+ * 工具守卫 代理模式包装 ToolRegistration
4298
4729
  *
4299
- * @param orchestrator - AgentOrchestrator 实例
4300
- * @returns ToolRegistration
4730
+ * 为每个 ToolRegistration execute 函数添加安全管道:
4731
+ * 1. PermissionEngine.evaluate() → 获取安全决策
4732
+ * 2. DENY → 抛出 SecurityBlockedError
4733
+ * 3. ASK → ApprovalManager.requestApproval()
4734
+ * 4. ALLOW → 执行原 execute()
4735
+ *
4736
+ * 代理模式:保留原 ToolRegistration 所有属性,
4737
+ * 仅替换 execute 函数为带安全检查的版本。
4738
+ *
4739
+ * ## ToolGuardContext
4740
+ *
4741
+ * 通过 AsyncLocalStorage 传递运行时上下文,控制特殊场景下的行为:
4742
+ * - isBackgroundAgent: 后台 Agent 遇 ASK 自动降级为 DENY(无用户可交互)
4743
+ * - planMode: Plan Mode 下限制工具可用性
4744
+ * - worktreePath: 工作树上下文
4745
+ *
4746
+ * @module security/tool-guard
4301
4747
  */
4302
- function createAgentTool(orchestrator) {
4303
- const registry = getAgentTypeRegistry();
4304
- return {
4305
- id: "AgentTool",
4306
- label: "创建 Agent",
4307
- defaultRisk: "high",
4308
- description: [
4309
- "创建特定类型的子 Agent 来执行独立任务。支持按 Agent 类型创建(推荐)或批量匿名创建(兼容旧版)。",
4310
- "",
4311
- "### 何时使用此工具",
4312
- "1. 需要分解复杂任务为多个独立子任务时",
4313
- "2. 需要特定类型的 Agent(研究员/编码助手/审查员/规划师)时",
4314
- "3. 多个子任务之间没有顺序依赖关系时可以并行创建",
4315
- "",
4316
- "### 何时不使用此工具",
4317
- "- 只有 1 个简单任务时(直接执行即可)",
4318
- "- 任务之间有严格的顺序依赖(必须串行执行)",
4319
- "- 任务非常简单(如读取单个文件)",
4320
- "",
4321
- "### 可用的 Agent 类型",
4322
- registry.list().map((t) => `- **${t.typeId}**: ${t.whenToUse}`).join("\n"),
4323
- "",
4324
- "### 参数说明",
4325
- "- **subagent_type**(推荐): Agent 类型 ID,如 researcher/coder/reviewer/planner/general-purpose",
4326
- "- **description**: 发给子 Agent 的详细任务指令",
4327
- "- **run_in_background**: 是否后台运行(暂不支持,默认 false)",
4328
- "- **inherit_context**: 是否继承父级上下文",
4329
- "- **agents**(已废弃,兼容旧版): 批量匿名子 Agent 列表",
4330
- "- **context**: 可选背景摘要",
4331
- "",
4332
- "### 使用流程",
4333
- "1. 分析任务,确定需要的 Agent 类型",
4334
- "2. 为每个 Agent 编写详细的任务指令",
4335
- "3. 调用本工具创建 Agent(可多次调用创建多个不同类型的 Agent)",
4336
- "4. 等待结果返回后整合汇总"
4337
- ].join("\n"),
4338
- parameters: {
4339
- type: "object",
4340
- properties: {
4341
- subagent_type: {
4342
- type: "string",
4343
- description: `Agent 类型 ID。可用类型: ${registry.list().map((t) => t.typeId).join(", ")}`
4344
- },
4345
- description: {
4346
- type: "string",
4347
- description: "发给子 Agent 的详细任务指令"
4348
- },
4349
- run_in_background: {
4350
- type: "boolean",
4351
- description: "是否后台运行(暂不支持,默认 false)",
4352
- default: false
4353
- },
4354
- inherit_context: {
4355
- type: "boolean",
4356
- description: "是否继承父级上下文",
4357
- default: false
4358
- },
4359
- agents: {
4360
- type: "array",
4361
- items: {
4362
- type: "object",
4363
- properties: {
4364
- id: {
4365
- type: "string",
4366
- description: "子任务唯一标识"
4367
- },
4368
- description: {
4369
- type: "string",
4370
- description: "发给子 Agent 的详细任务指令"
4371
- },
4372
- allowedTools: {
4373
- type: "array",
4374
- items: { type: "string" },
4375
- description: "可选工具白名单"
4376
- }
4377
- },
4378
- required: ["id", "description"]
4379
- },
4380
- description: "[已废弃] 批量匿名子 Agent 列表,请使用 subagent_type 参数"
4381
- },
4382
- context: {
4383
- type: "string",
4384
- description: "可选背景摘要,注入给每个子 Agent"
4385
- }
4386
- },
4387
- required: ["description"]
4388
- },
4389
- execute: async (_toolCallId, params) => {
4390
- const p = params;
4391
- if (p.run_in_background) return { content: [{
4392
- type: "text",
4393
- text: "错误: run_in_background(异步模式)将在后续版本中支持。当前请使用同步模式(run_in_background=false)。"
4394
- }] };
4395
- if (p.subagent_type && !p.agents) {
4396
- const result = await orchestrator.spawnWorker(p.subagent_type, p.description, {
4397
- ...p.context != null ? { context: p.context } : {},
4398
- ...p.inherit_context != null ? { inheritContext: p.inherit_context } : {}
4399
- });
4400
- return {
4401
- content: [{
4402
- type: "text",
4403
- text: result.status === "success" ? `✅ **${result.typeId}** 执行成功 (${(result.duration / 1e3).toFixed(1)}s)\n\n${result.output ?? "(无输出)"}` : `❌ **${result.typeId}** 执行失败: ${result.error?.message ?? "未知错误"}`
4404
- }],
4405
- details: result
4406
- };
4407
- }
4408
- if (p.agents && p.agents.length > 0) {
4409
- const results = await orchestrator.spawnFlat(p.agents, p.context);
4410
- return {
4411
- content: [{
4412
- type: "text",
4413
- text: results.summary
4748
+ const log$15 = logger.child("tool-guard");
4749
+ const toolGuardCtxStore = new AsyncLocalStorage();
4750
+ /**
4751
+ * 获取当前 ToolGuard 上下文
4752
+ */
4753
+ function getToolGuardContext() {
4754
+ return toolGuardCtxStore.getStore();
4755
+ }
4756
+ /**
4757
+ * 在指定上下文中执行回调
4758
+ *
4759
+ * 用于在后台 Agent / Plan Mode 等场景下包装工具执行。
4760
+ */
4761
+ async function runWithToolGuardContext(context, fn) {
4762
+ return toolGuardCtxStore.run(context, fn);
4763
+ }
4764
+ /**
4765
+ * 安全阻止错误
4766
+ *
4767
+ * 当工具调用被权限引擎拒绝时抛出。
4768
+ * 调用方(agent-adapter / session)应捕获此错误
4769
+ * 并转换为 LLM 友好的错误反馈。
4770
+ */
4771
+ var SecurityBlockedError = class extends Error {
4772
+ toolId;
4773
+ risk;
4774
+ reason;
4775
+ constructor(message, toolId, risk, reason) {
4776
+ super(message);
4777
+ this.name = "SecurityBlockedError";
4778
+ this.toolId = toolId;
4779
+ this.risk = risk;
4780
+ this.reason = reason;
4781
+ }
4782
+ };
4783
+ var ToolGuard = class {
4784
+ engine;
4785
+ approvalManager;
4786
+ store;
4787
+ sessionId;
4788
+ auditLogger;
4789
+ constructor(engine, approvalManager, store, sessionId, auditLogger) {
4790
+ this.engine = engine;
4791
+ this.approvalManager = approvalManager;
4792
+ this.store = store;
4793
+ this.sessionId = sessionId ?? `session-${Date.now()}`;
4794
+ this.auditLogger = auditLogger;
4795
+ }
4796
+ /**
4797
+ * 包装单个 ToolRegistration
4798
+ *
4799
+ * 返回新 ToolRegistration,原对象不变。
4800
+ * execute 被替换为带安全检查的版本。
4801
+ */
4802
+ wrap(registration) {
4803
+ const originalExecute = registration.execute;
4804
+ const toolId = registration.id;
4805
+ const toolLabel = registration.label;
4806
+ const guardedExecute = async (toolCallId, params, signal, onUpdate) => {
4807
+ const decision = this.engine.evaluate(toolId, params);
4808
+ if (decision.action === "deny") {
4809
+ const reason = decision.reason ?? `工具 ${toolId} 已被安全策略阻止`;
4810
+ log$15.warn("工具调用被阻止", {
4811
+ toolId,
4812
+ risk: decision.risk,
4813
+ reason
4814
+ });
4815
+ eventBus.emit("security:blocked", {
4816
+ toolId,
4817
+ risk: decision.risk,
4818
+ reason,
4819
+ params
4820
+ });
4821
+ this.auditLogger?.log({
4822
+ action: "BLOCK",
4823
+ toolId,
4824
+ risk: decision.risk,
4825
+ reason,
4826
+ params,
4827
+ ...decision.matchedRule ? { matchedRule: decision.matchedRule } : {}
4828
+ });
4829
+ throw new SecurityBlockedError(reason, toolId, decision.risk, reason);
4830
+ }
4831
+ if (decision.action === "ask") {
4832
+ if (getToolGuardContext()?.isBackgroundAgent) {
4833
+ const reason = `后台 Agent 不允许交互式审批,工具 ${toolId} 被自动拒绝`;
4834
+ log$15.info("后台 Agent ASK 降级为 DENY", {
4835
+ toolId,
4836
+ risk: decision.risk
4837
+ });
4838
+ eventBus.emit("security:blocked", {
4839
+ toolId,
4840
+ risk: decision.risk,
4841
+ reason,
4842
+ params
4843
+ });
4844
+ this.auditLogger?.log({
4845
+ action: "BLOCK",
4846
+ toolId,
4847
+ risk: decision.risk,
4848
+ reason,
4849
+ params
4850
+ });
4851
+ throw new SecurityBlockedError(reason, toolId, decision.risk, reason);
4852
+ }
4853
+ this.auditLogger?.log({
4854
+ action: "APPROVAL_REQUESTED",
4855
+ toolId,
4856
+ risk: decision.risk,
4857
+ params,
4858
+ ...decision.reason ? { reason: decision.reason } : {}
4859
+ });
4860
+ const approvalResponse = await this.approvalManager.requestApproval({
4861
+ toolId,
4862
+ toolLabel,
4863
+ params,
4864
+ risk: decision.risk,
4865
+ reason: decision.reason ?? `工具 ${toolId} 需要审批`,
4866
+ sessionId: this.sessionId
4867
+ });
4868
+ if (!approvalResponse.approved) {
4869
+ const reason = `用户拒绝了工具 ${toolId} 的执行请求`;
4870
+ log$15.info("用户拒绝工具执行", { toolId });
4871
+ this.auditLogger?.log({
4872
+ action: "APPROVAL_DENIED",
4873
+ toolId,
4874
+ risk: decision.risk,
4875
+ reason
4876
+ });
4877
+ throw new SecurityBlockedError(reason, toolId, decision.risk, reason);
4878
+ }
4879
+ if (approvalResponse.scope === "session") this.store.addSessionApproval(toolId);
4880
+ else if (approvalResponse.scope === "always") this.store.addPersistentApproval(toolId);
4881
+ this.auditLogger?.log({
4882
+ action: "APPROVAL_GRANTED",
4883
+ toolId,
4884
+ risk: decision.risk,
4885
+ ...approvalResponse.scope ? { scope: approvalResponse.scope } : {},
4886
+ ...decision.reason ? { reason: decision.reason } : {}
4887
+ });
4888
+ log$15.debug("审批通过,执行工具", {
4889
+ toolId,
4890
+ scope: approvalResponse.scope
4891
+ });
4892
+ } else this.auditLogger?.log({
4893
+ action: "ALLOW",
4894
+ toolId,
4895
+ risk: decision.risk,
4896
+ params,
4897
+ ...decision.matchedRule ? { matchedRule: decision.matchedRule } : {}
4898
+ });
4899
+ return originalExecute(toolCallId, params, signal, onUpdate);
4900
+ };
4901
+ return {
4902
+ ...registration,
4903
+ execute: guardedExecute
4904
+ };
4905
+ }
4906
+ /**
4907
+ * 批量包装所有工具
4908
+ */
4909
+ wrapAll(registrations) {
4910
+ return registrations.map((reg) => this.wrap(reg));
4911
+ }
4912
+ };
4913
+ /**
4914
+ * 从 ToolRegistration 数组构建 ToolInfoResolver
4915
+ *
4916
+ * 供 PermissionEngine 使用,将 toolId 映射到其安全信息。
4917
+ */
4918
+ function createToolInfoResolver(registrations) {
4919
+ const map = /* @__PURE__ */ new Map();
4920
+ for (const reg of registrations) map.set(reg.id, reg);
4921
+ return (toolId) => {
4922
+ const reg = map.get(toolId);
4923
+ if (!reg) return void 0;
4924
+ return {
4925
+ checkPermission: reg.checkPermission,
4926
+ defaultRisk: reg.defaultRisk
4927
+ };
4928
+ };
4929
+ }
4930
+
4931
+ //#endregion
4932
+ //#region src/core/agent-team/agent-background-store.ts
4933
+ /**
4934
+ * 后台任务持久化存储
4935
+ *
4936
+ * 复用 TaskStore 的内存+JSON 双写模式。
4937
+ * 存储路径:~/.zapmyco/background-tasks/<cwd-hash>.json
4938
+ *
4939
+ * @module core/agent-team
4940
+ */
4941
+ const log$14 = logger.child("background-store");
4942
+ /**
4943
+ * 后台任务持久化存储
4944
+ *
4945
+ * 提供跨会话的后台任务状态持久化。
4946
+ * 内存 Map + JSON 文件双写,每次变更自动同步到磁盘。
4947
+ */
4948
+ var BackgroundTaskStore = class {
4949
+ tasks = /* @__PURE__ */ new Map();
4950
+ filePath;
4951
+ constructor(cwd) {
4952
+ const dir = join(homedir(), ".zapmyco", "background-tasks");
4953
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
4954
+ const hash = createHash("md5").update(cwd ?? process.cwd()).digest("hex").slice(0, 16);
4955
+ this.filePath = join(dir, `${hash}.json`);
4956
+ }
4957
+ /** 获取存储文件路径 */
4958
+ get storagePath() {
4959
+ return this.filePath;
4960
+ }
4961
+ /**
4962
+ * 从磁盘加载(覆盖内存)
4963
+ */
4964
+ load() {
4965
+ try {
4966
+ if (existsSync(this.filePath)) {
4967
+ const raw = readFileSync(this.filePath, "utf-8");
4968
+ const entries = JSON.parse(raw);
4969
+ this.tasks.clear();
4970
+ for (const entry of entries) this.tasks.set(entry.taskId, entry);
4971
+ log$14.debug("后台任务已加载", {
4972
+ count: entries.length,
4973
+ path: this.filePath
4974
+ });
4975
+ return entries;
4976
+ }
4977
+ } catch (err) {
4978
+ log$14.warn("后台任务加载失败", {
4979
+ error: String(err),
4980
+ path: this.filePath
4981
+ });
4982
+ }
4983
+ return [];
4984
+ }
4985
+ /**
4986
+ * 获取指定任务
4987
+ */
4988
+ get(taskId) {
4989
+ return this.tasks.get(taskId);
4990
+ }
4991
+ /**
4992
+ * 获取所有任务
4993
+ */
4994
+ listAll() {
4995
+ return Array.from(this.tasks.values());
4996
+ }
4997
+ /**
4998
+ * 获取活跃(未完结)的任务
4999
+ */
5000
+ listActive() {
5001
+ return this.listAll().filter((t) => t.status === "pending" || t.status === "running");
5002
+ }
5003
+ /**
5004
+ * 保存或更新任务
5005
+ */
5006
+ save(entry) {
5007
+ this.tasks.set(entry.taskId, entry);
5008
+ this.persist();
5009
+ }
5010
+ /**
5011
+ * 更新任务状态
5012
+ */
5013
+ updateStatus(taskId, status, extra) {
5014
+ const entry = this.tasks.get(taskId);
5015
+ if (!entry) return false;
5016
+ Object.assign(entry, {
5017
+ status,
5018
+ ...extra
5019
+ });
5020
+ this.persist();
5021
+ return true;
5022
+ }
5023
+ /**
5024
+ * 删除任务
5025
+ */
5026
+ remove(taskId) {
5027
+ const deleted = this.tasks.delete(taskId);
5028
+ if (deleted) this.persist();
5029
+ return deleted;
5030
+ }
5031
+ /** 同步到磁盘 */
5032
+ persist() {
5033
+ try {
5034
+ const entries = Array.from(this.tasks.values());
5035
+ writeFileSync(this.filePath, JSON.stringify(entries, null, 2), "utf-8");
5036
+ } catch (err) {
5037
+ log$14.error("后台任务持久化失败", {
5038
+ error: String(err),
5039
+ path: this.filePath
5040
+ });
5041
+ }
5042
+ }
5043
+ /** 恢复时清理卡死的 running 任务 */
5044
+ cleanStale(maxRunningMs = 7200 * 1e3) {
5045
+ let cleaned = 0;
5046
+ const now = Date.now();
5047
+ const stale = [];
5048
+ for (const [id, entry] of this.tasks) {
5049
+ if (entry.status === "running" && now - entry.createdAt > maxRunningMs) stale.push(id);
5050
+ if (entry.status === "pending" && now - entry.createdAt > maxRunningMs) stale.push(id);
5051
+ }
5052
+ for (const id of stale) {
5053
+ const entry = this.tasks.get(id);
5054
+ if (entry) {
5055
+ entry.status = entry.status === "running" ? "failed" : "cancelled";
5056
+ entry.error = entry.status === "failed" ? "任务超时丢失(跨会话恢复)" : "任务在 pending 状态超时,无法恢复";
5057
+ entry.completedAt = now;
5058
+ cleaned++;
5059
+ }
5060
+ }
5061
+ if (cleaned > 0) {
5062
+ this.persist();
5063
+ log$14.info("清理过期后台任务", { cleaned });
5064
+ }
5065
+ return cleaned;
5066
+ }
5067
+ };
5068
+
5069
+ //#endregion
5070
+ //#region src/core/agent-team/agent-message-bus.ts
5071
+ /**
5072
+ * Agent 消息总线
5073
+ *
5074
+ * 内存中的 Agent 间消息路由系统。
5075
+ * 基于 EventEmitter 实现 publish/subscribe 模式,
5076
+ * 消息同时投递到目标 AgentInstance 的 inbox。
5077
+ *
5078
+ * @module core/agent-team
5079
+ */
5080
+ const log$13 = logger.child("agent-message-bus");
5081
+ /**
5082
+ * Agent 消息总线(单例)
5083
+ *
5084
+ * 负责 Agent 间消息的路由和投递:
5085
+ * - publish(): 将消息投递到目标 inbox + 触发订阅回调
5086
+ * - subscribe(): 注册消息监听器
5087
+ * - drainInbox(): 取出并清空 Agent 收件箱
5088
+ */
5089
+ var AgentMessageBus = class {
5090
+ emitter = new EventEmitter();
5091
+ messageCounter = 0;
5092
+ /**
5093
+ * 发布消息
5094
+ *
5095
+ * 1. 生成 messageId(若未提供)
5096
+ * 2. 将消息推入目标 AgentInstance.inbox(通过 AgentInstanceManager)
5097
+ * 3. 触发目标 Agent 的订阅回调
5098
+ *
5099
+ * @param fromAgentId - 发送方实例 ID
5100
+ * @param toAgentId - 接收方实例 ID
5101
+ * @param message - 消息内容(不含 messageId/fromAgentId/timestamp)
5102
+ * @returns 完整的 AgentMessage(含生成的 messageId 和 timestamp)
5103
+ */
5104
+ publish(fromAgentId, toAgentId, message) {
5105
+ const fullMessage = {
5106
+ ...message,
5107
+ messageId: this.generateMessageId(),
5108
+ fromAgentId,
5109
+ toAgentId,
5110
+ timestamp: Date.now()
5111
+ };
5112
+ const targetInstance = getAgentInstanceManager().get(toAgentId);
5113
+ if (targetInstance) targetInstance.inbox.push(fullMessage);
5114
+ else log$13.warn("目标 Agent 实例不存在,消息丢弃", {
5115
+ toAgentId,
5116
+ messageId: fullMessage.messageId
5117
+ });
5118
+ this.emitter.emit(`msg:${toAgentId}`, fullMessage);
5119
+ log$13.debug("消息已投递", {
5120
+ from: fromAgentId,
5121
+ to: toAgentId,
5122
+ type: fullMessage.type,
5123
+ messageId: fullMessage.messageId
5124
+ });
5125
+ return fullMessage;
5126
+ }
5127
+ /**
5128
+ * 订阅指定 Agent 的消息
5129
+ *
5130
+ * 当有新消息投递到该 Agent 时,回调被触发。
5131
+ *
5132
+ * @param agentId - 要监听的 Agent 实例 ID
5133
+ * @param callback - 消息到达时的回调
5134
+ */
5135
+ subscribe(agentId, callback) {
5136
+ this.emitter.on(`msg:${agentId}`, callback);
5137
+ }
5138
+ /**
5139
+ * 取消订阅
5140
+ *
5141
+ * @param agentId - Agent 实例 ID
5142
+ * @param callback - 要移除的回调
5143
+ */
5144
+ unsubscribe(agentId, callback) {
5145
+ this.emitter.off(`msg:${agentId}`, callback);
5146
+ }
5147
+ /**
5148
+ * 取出并清空指定 Agent 的收件箱
5149
+ *
5150
+ * @param agentId - Agent 实例 ID
5151
+ * @returns 收件箱中的所有消息(按投递时间排序)
5152
+ */
5153
+ drainInbox(agentId) {
5154
+ const instance = getAgentInstanceManager().get(agentId);
5155
+ if (!instance || instance.inbox.length === 0) return [];
5156
+ const messages = [...instance.inbox];
5157
+ instance.inbox = [];
5158
+ return messages;
5159
+ }
5160
+ /**
5161
+ * 获取收件箱中的消息数量(不清空)
5162
+ *
5163
+ * @param agentId - Agent 实例 ID
5164
+ */
5165
+ inboxCount(agentId) {
5166
+ return getAgentInstanceManager().get(agentId)?.inbox.length ?? 0;
5167
+ }
5168
+ /** 获取当前活跃的订阅数量 */
5169
+ get subscriptionCount() {
5170
+ return this.emitter.listenerCount("msg:");
5171
+ }
5172
+ /** 生成唯一消息 ID */
5173
+ generateMessageId() {
5174
+ this.messageCounter++;
5175
+ return `msg-${Date.now()}-${this.messageCounter}`;
5176
+ }
5177
+ };
5178
+ /** 全局单例引用 */
5179
+ let _messageBus = null;
5180
+ /**
5181
+ * 获取 AgentMessageBus 单例
5182
+ */
5183
+ function getAgentMessageBus() {
5184
+ if (!_messageBus) _messageBus = new AgentMessageBus();
5185
+ return _messageBus;
5186
+ }
5187
+
5188
+ //#endregion
5189
+ //#region src/core/agent-team/agent-background-manager.ts
5190
+ /**
5191
+ * 后台 Agent 管理器
5192
+ *
5193
+ * 管理后台异步 Agent 的完整生命周期:
5194
+ * - fire-and-forget 启动(立即返回 taskId,Agent 在后台执行)
5195
+ * - 进度跟踪
5196
+ * - 结果收集
5197
+ * - 完成后通过 AgentMessageBus 通知父 Agent
5198
+ * - 超时自动取消
5199
+ *
5200
+ * @module core/agent-team
5201
+ */
5202
+ const log$12 = logger.child("background-agent-manager");
5203
+ /**
5204
+ * 后台 Agent 管理器(单例)
5205
+ */
5206
+ var BackgroundAgentManager = class {
5207
+ runtime = /* @__PURE__ */ new Map();
5208
+ store;
5209
+ orchestrator = null;
5210
+ defaultTimeoutMs = 1800 * 1e3;
5211
+ constructor() {
5212
+ this.store = new BackgroundTaskStore();
5213
+ }
5214
+ /** 注入 AgentOrchestrator(用于创建 Agent) */
5215
+ setOrchestrator(orchestrator) {
5216
+ this.orchestrator = orchestrator;
5217
+ }
5218
+ /** 获取持久化存储(供外部查询) */
5219
+ getStore() {
5220
+ return this.store;
5221
+ }
5222
+ /**
5223
+ * 异步启动一个 Agent
5224
+ *
5225
+ * fire-and-forget 模式:
5226
+ * 1. 立即返回 { taskId, instanceId }
5227
+ * 2. Agent 在后台执行
5228
+ * 3. 完成后自动通过 AgentMessageBus 通知父 Agent
5229
+ *
5230
+ * @param params - 异步 Agent 参数
5231
+ * @returns 任务标识
5232
+ */
5233
+ async executeAsync(params) {
5234
+ if (!this.orchestrator) throw new Error("BackgroundAgentManager 未注入 AgentOrchestrator");
5235
+ const taskId = `bg-${params.typeId}-${Date.now()}`;
5236
+ const abortController = new AbortController();
5237
+ const timeoutMs = params.timeoutMs ?? this.defaultTimeoutMs;
5238
+ const runtime = {
5239
+ taskId,
5240
+ instanceId: "",
5241
+ typeId: params.typeId,
5242
+ description: params.description,
5243
+ status: "pending",
5244
+ createdAt: Date.now(),
5245
+ abortController,
5246
+ parentInstanceId: params.parentInstanceId
5247
+ };
5248
+ this.runtime.set(taskId, runtime);
5249
+ this.store.save({
5250
+ taskId,
5251
+ instanceId: "",
5252
+ typeId: params.typeId,
5253
+ description: params.description,
5254
+ status: "pending",
5255
+ createdAt: runtime.createdAt,
5256
+ parentAgentId: params.parentInstanceId
5257
+ });
5258
+ this.runAsync(taskId, params, abortController, timeoutMs).catch((err) => {
5259
+ log$12.error("后台 Agent 意外崩溃", {
5260
+ taskId,
5261
+ error: String(err)
5262
+ });
5263
+ this.failTask(taskId, `意外错误: ${err instanceof Error ? err.message : String(err)}`);
5264
+ });
5265
+ return {
5266
+ taskId,
5267
+ instanceId: runtime.instanceId || "pending"
5268
+ };
5269
+ }
5270
+ /**
5271
+ * 实际执行后台 Agent 的逻辑
5272
+ */
5273
+ async runAsync(taskId, params, abortController, timeoutMs) {
5274
+ const runtime = this.runtime.get(taskId);
5275
+ if (!runtime) return;
5276
+ const orchestrator = this.orchestrator;
5277
+ const messageBus = getAgentMessageBus();
5278
+ const timeoutHandle = setTimeout(() => {
5279
+ log$12.warn("后台 Agent 超时,自动取消", {
5280
+ taskId,
5281
+ timeoutMs
5282
+ });
5283
+ abortController.abort();
5284
+ }, timeoutMs);
5285
+ try {
5286
+ const workerOptions = {
5287
+ taskId,
5288
+ timeoutMs,
5289
+ inheritContext: params.inheritContext ?? false,
5290
+ context: params.context,
5291
+ parentInstanceId: params.parentInstanceId,
5292
+ isolation: params.isolation,
5293
+ wrapExecute: (execute) => runWithToolGuardContext({ isBackgroundAgent: true }, () => execute())
5294
+ };
5295
+ const result = await orchestrator.spawnWorker(params.typeId, params.description, workerOptions);
5296
+ runtime.instanceId = result.instanceId;
5297
+ runtime.status = result.status === "success" ? "completed" : "failed";
5298
+ runtime.completedAt = Date.now();
5299
+ if (result.error) runtime.error = result.error.message;
5300
+ this.store.updateStatus(taskId, runtime.status, {
5301
+ instanceId: result.instanceId,
5302
+ completedAt: runtime.completedAt,
5303
+ result: result.output ?? void 0,
5304
+ error: runtime.error
5305
+ });
5306
+ if (params.parentInstanceId) {
5307
+ const payload = JSON.stringify({
5308
+ taskId,
5309
+ instanceId: result.instanceId,
5310
+ typeId: params.typeId,
5311
+ status: result.status,
5312
+ summary: result.output,
5313
+ duration: result.duration,
5314
+ tokenUsage: result.tokenUsage,
5315
+ error: result.error
5316
+ });
5317
+ messageBus.publish(result.instanceId, params.parentInstanceId, {
5318
+ type: "task_result",
5319
+ payload,
5320
+ taskId,
5321
+ requiresResponse: false
5322
+ });
5323
+ log$12.info("后台 Agent 完成通知已发送", {
5324
+ taskId,
5325
+ instanceId: result.instanceId,
5326
+ parentId: params.parentInstanceId,
5327
+ status: result.status
5328
+ });
5329
+ }
5330
+ } catch (err) {
5331
+ const message = err instanceof Error ? err.message : String(err);
5332
+ if (abortController.signal.aborted) this.failTask(taskId, `任务超时(${timeoutMs / 1e3}秒)`);
5333
+ else this.failTask(taskId, message);
5334
+ } finally {
5335
+ clearTimeout(timeoutHandle);
5336
+ }
5337
+ }
5338
+ /** 标记任务失败 */
5339
+ failTask(taskId, error) {
5340
+ const runtime = this.runtime.get(taskId);
5341
+ if (runtime) {
5342
+ runtime.status = "failed";
5343
+ runtime.error = error;
5344
+ runtime.completedAt = Date.now();
5345
+ }
5346
+ this.store.updateStatus(taskId, "failed", {
5347
+ completedAt: Date.now(),
5348
+ error
5349
+ });
5350
+ log$12.warn("后台 Agent 失败", {
5351
+ taskId,
5352
+ error
5353
+ });
5354
+ }
5355
+ /**
5356
+ * 获取后台任务状态
5357
+ */
5358
+ getTask(taskId) {
5359
+ return this.runtime.get(taskId);
5360
+ }
5361
+ /**
5362
+ * 列出所有活跃(未完结)的后台任务
5363
+ */
5364
+ listActive() {
5365
+ return Array.from(this.runtime.values()).filter((t) => t.status === "pending" || t.status === "running");
5366
+ }
5367
+ /**
5368
+ * 列出所有后台任务
5369
+ */
5370
+ listAll() {
5371
+ return Array.from(this.runtime.values());
5372
+ }
5373
+ /**
5374
+ * 取消后台任务
5375
+ */
5376
+ async cancel(taskId) {
5377
+ const runtime = this.runtime.get(taskId);
5378
+ if (!runtime) return false;
5379
+ if (runtime.status === "completed" || runtime.status === "failed" || runtime.status === "cancelled") return false;
5380
+ runtime.abortController.abort();
5381
+ runtime.status = "cancelled";
5382
+ runtime.completedAt = Date.now();
5383
+ this.store.updateStatus(taskId, "cancelled", { completedAt: runtime.completedAt });
5384
+ if (runtime.instanceId) try {
5385
+ await getAgentInstanceManager().cancel(runtime.instanceId);
5386
+ } catch {}
5387
+ log$12.info("后台 Agent 已取消", { taskId });
5388
+ return true;
5389
+ }
5390
+ /**
5391
+ * 从持久化存储恢复(跨会话)
5392
+ */
5393
+ restore() {
5394
+ this.store.load();
5395
+ const stale = this.store.cleanStale();
5396
+ if (stale > 0) log$12.info("跨会话恢复:清理了过期后台任务", { count: stale });
5397
+ const active = this.store.listActive();
5398
+ for (const entry of active) this.store.updateStatus(entry.taskId, "failed", {
5399
+ completedAt: Date.now(),
5400
+ error: "会话终止导致任务丢失"
5401
+ });
5402
+ if (active.length > 0) log$12.info("跨会话恢复:标记活跃任务为 failed", { count: active.length });
5403
+ }
5404
+ };
5405
+ /** 全局单例 */
5406
+ let globalBackgroundManager = null;
5407
+ /** 获取 BackgroundAgentManager 单例 */
5408
+ function getBackgroundAgentManager() {
5409
+ if (!globalBackgroundManager) globalBackgroundManager = new BackgroundAgentManager();
5410
+ return globalBackgroundManager;
5411
+ }
5412
+
5413
+ //#endregion
5414
+ //#region src/cli/repl/tools/agent-tool.ts
5415
+ /**
5416
+ * 创建 AgentTool 工具注册
5417
+ *
5418
+ * @param orchestrator - AgentOrchestrator 实例
5419
+ * @returns ToolRegistration
5420
+ */
5421
+ function createAgentTool(orchestrator) {
5422
+ const registry = getAgentTypeRegistry();
5423
+ return {
5424
+ id: "AgentTool",
5425
+ label: "创建 Agent",
5426
+ defaultRisk: "high",
5427
+ description: [
5428
+ "创建特定类型的子 Agent 来执行独立任务。支持按 Agent 类型创建(推荐)或批量匿名创建(兼容旧版)。",
5429
+ "",
5430
+ "### 何时使用此工具",
5431
+ "1. 需要分解复杂任务为多个独立子任务时",
5432
+ "2. 需要特定类型的 Agent(研究员/编码助手/审查员/规划师)时",
5433
+ "3. 多个子任务之间没有顺序依赖关系时可以并行创建",
5434
+ "",
5435
+ "### 何时不使用此工具",
5436
+ "- 只有 1 个简单任务时(直接执行即可)",
5437
+ "- 任务之间有严格的顺序依赖(必须串行执行)",
5438
+ "- 任务非常简单(如读取单个文件)",
5439
+ "",
5440
+ "### 可用的 Agent 类型",
5441
+ registry.list().map((t) => `- **${t.typeId}**: ${t.whenToUse}`).join("\n"),
5442
+ "",
5443
+ "### 参数说明",
5444
+ "- **subagent_type**(推荐): Agent 类型 ID,如 researcher/coder/reviewer/planner/general-purpose",
5445
+ "- **description**: 发给子 Agent 的详细任务指令",
5446
+ "- **run_in_background**: 是否后台运行(默认 false)。设为 true 时立即返回 taskId,Agent 在后台执行,完成后自动通知",
5447
+ "- **inherit_context**: 是否继承父级上下文",
5448
+ "- **agents**(已废弃,兼容旧版): 批量匿名子 Agent 列表",
5449
+ "- **context**: 可选背景摘要",
5450
+ "",
5451
+ "### 使用流程",
5452
+ "1. 分析任务,确定需要的 Agent 类型",
5453
+ "2. 为每个 Agent 编写详细的任务指令",
5454
+ "3. 调用本工具创建 Agent(可多次调用创建多个不同类型的 Agent)",
5455
+ "4. 同步模式:等待结果返回后整合汇总",
5456
+ "5. 异步模式:立即返回 taskId,完成后通过收件箱接收通知"
5457
+ ].join("\n"),
5458
+ parameters: {
5459
+ type: "object",
5460
+ properties: {
5461
+ subagent_type: {
5462
+ type: "string",
5463
+ description: `Agent 类型 ID。可用类型: ${registry.list().map((t) => t.typeId).join(", ")}`
5464
+ },
5465
+ description: {
5466
+ type: "string",
5467
+ description: "发给子 Agent 的详细任务指令"
5468
+ },
5469
+ run_in_background: {
5470
+ type: "boolean",
5471
+ description: "是否后台运行(默认 false)。设为 true 时 fire-and-forget,Agent 后台执行完成后自动通知父 Agent",
5472
+ default: false
5473
+ },
5474
+ inherit_context: {
5475
+ type: "boolean",
5476
+ description: "是否继承父级上下文",
5477
+ default: false
5478
+ },
5479
+ agents: {
5480
+ type: "array",
5481
+ items: {
5482
+ type: "object",
5483
+ properties: {
5484
+ id: {
5485
+ type: "string",
5486
+ description: "子任务唯一标识"
5487
+ },
5488
+ description: {
5489
+ type: "string",
5490
+ description: "发给子 Agent 的详细任务指令"
5491
+ },
5492
+ allowedTools: {
5493
+ type: "array",
5494
+ items: { type: "string" },
5495
+ description: "可选工具白名单"
5496
+ }
5497
+ },
5498
+ required: ["id", "description"]
5499
+ },
5500
+ description: "[已废弃] 批量匿名子 Agent 列表,请使用 subagent_type 参数"
5501
+ },
5502
+ context: {
5503
+ type: "string",
5504
+ description: "可选背景摘要,注入给每个子 Agent"
5505
+ },
5506
+ isolation: {
5507
+ type: "string",
5508
+ enum: ["worktree"],
5509
+ description: "隔离模式。设为 \"worktree\" 时 Agent 在独立 git worktree 中运行,修改不影响主工作区"
5510
+ }
5511
+ },
5512
+ required: ["description"]
5513
+ },
5514
+ execute: async (_toolCallId, params) => {
5515
+ const p = params;
5516
+ if (p.run_in_background && p.subagent_type && !p.agents) {
5517
+ const { taskId, instanceId } = await getBackgroundAgentManager().executeAsync({
5518
+ typeId: p.subagent_type,
5519
+ description: p.description,
5520
+ context: p.context,
5521
+ inheritContext: p.inherit_context,
5522
+ ...p.isolation ? { isolation: p.isolation } : {}
5523
+ });
5524
+ return {
5525
+ content: [{
5526
+ type: "text",
5527
+ text: [
5528
+ `🚀 **${p.subagent_type}** 已作为后台任务启动`,
5529
+ "",
5530
+ `- 任务 ID: \`${taskId}\``,
5531
+ `- 实例 ID: \`${instanceId}\``,
5532
+ `- 状态: 后台运行中`,
5533
+ "",
5534
+ "Agent 完成后将自动通知。可使用 BackgroundTask 工具查询状态。"
5535
+ ].join("\n")
5536
+ }],
5537
+ details: {
5538
+ taskId,
5539
+ instanceId,
5540
+ status: "async_launched"
5541
+ }
5542
+ };
5543
+ }
5544
+ if (p.run_in_background && !p.subagent_type) return { content: [{
5545
+ type: "text",
5546
+ text: "后台运行模式需要指定 subagent_type 参数。请选择一种 Agent 类型。"
5547
+ }] };
5548
+ if (p.subagent_type && !p.agents) {
5549
+ const result = await orchestrator.spawnWorker(p.subagent_type, p.description, {
5550
+ ...p.context != null ? { context: p.context } : {},
5551
+ ...p.inherit_context != null ? { inheritContext: p.inherit_context } : {},
5552
+ ...p.isolation ? { isolation: p.isolation } : {}
5553
+ });
5554
+ return {
5555
+ content: [{
5556
+ type: "text",
5557
+ text: result.status === "success" ? `✅ **${result.typeId}** 执行成功 (${(result.duration / 1e3).toFixed(1)}s)\n\n${result.output ?? "(无输出)"}` : `❌ **${result.typeId}** 执行失败: ${result.error?.message ?? "未知错误"}`
5558
+ }],
5559
+ details: result
5560
+ };
5561
+ }
5562
+ if (p.agents && p.agents.length > 0) {
5563
+ const results = await orchestrator.spawnFlat(p.agents, p.context);
5564
+ return {
5565
+ content: [{
5566
+ type: "text",
5567
+ text: results.summary
5568
+ }],
5569
+ details: results
5570
+ };
5571
+ }
5572
+ return { content: [{
5573
+ type: "text",
5574
+ text: "请指定 subagent_type(推荐的 Agent 类型)或 agents(已废弃的批量参数)。"
5575
+ }] };
5576
+ }
5577
+ };
5578
+ }
5579
+
5580
+ //#endregion
5581
+ //#region src/core/question/types.ts
5582
+ /** 创建 Deferred */
5583
+ function createDeferred() {
5584
+ let resolve;
5585
+ let reject;
5586
+ let isSettled = false;
5587
+ return {
5588
+ promise: new Promise((res, rej) => {
5589
+ resolve = (value) => {
5590
+ if (!isSettled) {
5591
+ isSettled = true;
5592
+ res(value);
5593
+ }
5594
+ };
5595
+ reject = (error) => {
5596
+ if (!isSettled) {
5597
+ isSettled = true;
5598
+ rej(error);
5599
+ }
5600
+ };
5601
+ }),
5602
+ resolve,
5603
+ reject,
5604
+ isSettled
5605
+ };
5606
+ }
5607
+
5608
+ //#endregion
5609
+ //#region src/core/question/question-manager.ts
5610
+ /**
5611
+ * 问题管理器
5612
+ *
5613
+ * 基于 Provider 模式实现异步阻塞提问。
5614
+ * TUI 层通过 setProvider() 注入提问 UI 实现,
5615
+ * Agent 工具执行流通过 ask() 被阻塞等待用户回答。
5616
+ *
5617
+ * 在 headless 模式(无 provider)下自动报错。
5618
+ *
5619
+ * @module core/question/question-manager
5620
+ */
5621
+ const log$11 = logger.child("question-manager");
5622
+ /** 问题超时时间(5 分钟) */
5623
+ const QUESTION_TIMEOUT_MS = 300 * 1e3;
5624
+ var QuestionManager = class {
5625
+ provider = null;
5626
+ pending = /* @__PURE__ */ new Map();
5627
+ constructor(provider) {
5628
+ if (provider) this.provider = provider;
5629
+ }
5630
+ /**
5631
+ * 设置问题提供者(由 TUI 层注入)
5632
+ */
5633
+ setProvider(provider) {
5634
+ this.provider = provider;
5635
+ }
5636
+ /**
5637
+ * 检查是否有问题提供者
5638
+ */
5639
+ hasProvider() {
5640
+ return this.provider !== null;
5641
+ }
5642
+ /**
5643
+ * 向用户提问并等待回答
5644
+ *
5645
+ * 阻塞当前执行流,等待用户通过 TUI 做出回答。
5646
+ * 如果无 provider(headless 模式),抛出错误。
5647
+ *
5648
+ * @param params - 问题参数
5649
+ * @returns 用户回答结果
5650
+ */
5651
+ async ask(params) {
5652
+ if (!this.provider) throw new Error("当前环境不支持交互式提问(headless 模式)");
5653
+ const requestId = `q-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
5654
+ const deferred = createDeferred();
5655
+ deferred.promise.catch(() => {});
5656
+ const entry = {
5657
+ requestId,
5658
+ questions: params.questions,
5659
+ deferred,
5660
+ createdAt: Date.now()
5661
+ };
5662
+ this.pending.set(requestId, entry);
5663
+ eventBus.emit("question:asked", {
5664
+ requestId,
5665
+ questionCount: params.questions.length
5666
+ });
5667
+ log$11.debug("提问已发出", {
5668
+ requestId,
5669
+ questionCount: params.questions.length
5670
+ });
5671
+ const timeout = setTimeout(() => {
5672
+ if (!deferred.isSettled) {
5673
+ log$11.warn("提问超时,自动取消", { requestId });
5674
+ deferred.reject(/* @__PURE__ */ new Error("提问超时,用户未在 5 分钟内回答"));
5675
+ this.pending.delete(requestId);
5676
+ eventBus.emit("question:timeout", { requestId });
5677
+ }
5678
+ }, QUESTION_TIMEOUT_MS);
5679
+ try {
5680
+ const result = await this.provider.showQuestions(params);
5681
+ clearTimeout(timeout);
5682
+ deferred.resolve(result);
5683
+ eventBus.emit("question:answered", {
5684
+ requestId,
5685
+ answerCount: Object.keys(result.answers).length
5686
+ });
5687
+ log$11.debug("提问已回答", { requestId });
5688
+ return result;
5689
+ } catch (err) {
5690
+ clearTimeout(timeout);
5691
+ if (!deferred.isSettled) deferred.reject(err instanceof Error ? err : new Error(String(err)));
5692
+ eventBus.emit("question:cancelled", {
5693
+ requestId,
5694
+ reason: err instanceof Error ? err.message : String(err)
5695
+ });
5696
+ log$11.debug("提问已取消", {
5697
+ requestId,
5698
+ error: err instanceof Error ? err.message : String(err)
5699
+ });
5700
+ throw err;
5701
+ } finally {
5702
+ this.pending.delete(requestId);
5703
+ }
5704
+ }
5705
+ /**
5706
+ * 获取待处理的问题请求
5707
+ */
5708
+ getPending(requestId) {
5709
+ return this.pending.get(requestId);
5710
+ }
5711
+ /**
5712
+ * 拒绝所有待处理的问题(用于 session 关闭时清理)
5713
+ */
5714
+ rejectAll(error) {
5715
+ const count = this.pending.size;
5716
+ for (const [, entry] of this.pending) if (!entry.deferred.isSettled) entry.deferred.reject(error);
5717
+ this.pending.clear();
5718
+ if (count > 0) log$11.debug("已清理所有待处理问题", { count });
5719
+ }
5720
+ };
5721
+ let globalQuestionManager = null;
5722
+ /**
5723
+ * 获取全局 QuestionManager 单例
5724
+ */
5725
+ function getQuestionManager() {
5726
+ if (!globalQuestionManager) globalQuestionManager = new QuestionManager();
5727
+ return globalQuestionManager;
5728
+ }
5729
+
5730
+ //#endregion
5731
+ //#region src/cli/repl/tools/ask-user-question.ts
5732
+ /**
5733
+ * 创建 AskUserQuestion 工具注册
5734
+ */
5735
+ function createAskUserQuestionTool() {
5736
+ return {
5737
+ id: "AskUserQuestion",
5738
+ label: "向用户提问",
5739
+ defaultRisk: "medium",
5740
+ checkPermission: () => ({
5741
+ risk: "medium",
5742
+ requiresApproval: false,
5743
+ reason: "AskUserQuestion 使用独立的交互式 UI,不走 ToolGuard 审批路径"
5744
+ }),
5745
+ description: [
5746
+ "向用户提问以获取决策指导。当需要在多个可行方案之间选择、进行技术选型、",
5747
+ "或需要用户偏好时使用此工具。",
5748
+ "",
5749
+ "### 何时使用",
5750
+ "- 需要在多个可行方案之间做出选择时",
5751
+ "- 需要技术选型、架构决策等需要用户判断的问题",
5752
+ "- Plan Mode 中分析完方案后需要用户指定方向时",
5753
+ "- 需要明确用户偏好以实现个性化功能时",
5754
+ "",
5755
+ "### 何时不使用",
5756
+ "- 可以通过代码分析直接确定的结论",
5757
+ "- 简单确认(直接回复文本询问即可)",
5758
+ "- 已有明确最佳实践的问题",
5759
+ "",
5760
+ "### 提问原则",
5761
+ "- 每个问题提供 2-4 个具体、互斥的选项",
5762
+ "- header 字段控制在 12 个字符以内",
5763
+ "- 使用 multiSelect: true 表示多选",
5764
+ "- 推荐选项放在第一位并加 \"(Recommended)\" 后缀",
5765
+ "- 如果选项有代码示例/配置对比,可在 preview 字段中提供(markdown 格式)",
5766
+ "- 用户始终可以选择 \"Other\" 输入自定义答案"
5767
+ ].join("\n"),
5768
+ parameters: {
5769
+ type: "object",
5770
+ properties: { questions: {
5771
+ type: "array",
5772
+ items: {
5773
+ type: "object",
5774
+ properties: {
5775
+ question: {
5776
+ type: "string",
5777
+ description: "完整问题文本,以问号结尾"
5778
+ },
5779
+ header: {
5780
+ type: "string",
5781
+ description: "短标签,用于 Tab 导航(最多 12 个字符)"
5782
+ },
5783
+ options: {
5784
+ type: "array",
5785
+ items: {
5786
+ type: "object",
5787
+ properties: {
5788
+ label: {
5789
+ type: "string",
5790
+ description: "选项显示文本(1-5 个词)"
5791
+ },
5792
+ description: {
5793
+ type: "string",
5794
+ description: "选项说明"
5795
+ },
5796
+ preview: {
5797
+ type: "string",
5798
+ description: "可选的预览内容(markdown 格式)。当任一选项有 preview 时,UI 切换为左右分栏布局"
5799
+ }
5800
+ },
5801
+ required: ["label", "description"]
5802
+ },
5803
+ minItems: 2,
5804
+ maxItems: 4,
5805
+ description: "2-4 个选项"
5806
+ },
5807
+ multiSelect: {
5808
+ type: "boolean",
5809
+ description: "是否允许多选",
5810
+ default: false
5811
+ }
5812
+ },
5813
+ required: [
5814
+ "question",
5815
+ "header",
5816
+ "options",
5817
+ "multiSelect"
5818
+ ]
5819
+ },
5820
+ minItems: 1,
5821
+ maxItems: 4,
5822
+ description: "1-4 个问题"
5823
+ } },
5824
+ required: ["questions"]
5825
+ },
5826
+ execute: async (_toolCallId, params) => {
5827
+ const p = params;
5828
+ if (getToolGuardContext()?.isBackgroundAgent) throw new SecurityBlockedError("后台 Agent 不支持交互式提问(AskUserQuestion)", "AskUserQuestion", "medium", "后台 Agent 无用户交互界面");
5829
+ if (!p.questions || p.questions.length === 0) return {
5830
+ content: [{
5831
+ type: "text",
5832
+ text: "错误: 至少需要 1 个问题"
5833
+ }],
5834
+ details: {
5835
+ error: true,
5836
+ message: "questions 数组不能为空"
5837
+ }
5838
+ };
5839
+ for (let i = 0; i < p.questions.length; i++) {
5840
+ const q = p.questions[i];
5841
+ if (q.options.length < 2) return {
5842
+ content: [{
5843
+ type: "text",
5844
+ text: `错误: 问题 "${q.header}" 至少需要 2 个选项`
5845
+ }],
5846
+ details: {
5847
+ error: true,
5848
+ message: "选项数量不足",
5849
+ questionIndex: i
5850
+ }
5851
+ };
5852
+ if (q.header.length > 12) return {
5853
+ content: [{
5854
+ type: "text",
5855
+ text: `错误: 问题 "${q.header}" 的 header 超过 12 个字符限制`
4414
5856
  }],
4415
- details: results
5857
+ details: {
5858
+ error: true,
5859
+ message: "header 过长",
5860
+ questionIndex: i
5861
+ }
5862
+ };
5863
+ }
5864
+ try {
5865
+ const questionManager = getQuestionManager();
5866
+ if (!questionManager.hasProvider()) return {
5867
+ content: [{
5868
+ type: "text",
5869
+ text: "错误: 当前环境不支持交互式提问(headless 模式或未初始化 UI)"
5870
+ }],
5871
+ details: {
5872
+ error: true,
5873
+ message: "无 QuestionProvider"
5874
+ }
5875
+ };
5876
+ const result = await questionManager.ask(p);
5877
+ return {
5878
+ content: [{
5879
+ type: "text",
5880
+ text: `用户已回答你的问题:\n${result.questions.map((q) => {
5881
+ const answer = result.answers[q.question];
5882
+ const answerStr = Array.isArray(answer) ? answer.join(", ") : answer ?? "(未回答)";
5883
+ return `"${q.question}" → "${answerStr}"`;
5884
+ }).join("\n")}\n\n你可以根据这些答案继续执行。`
5885
+ }],
5886
+ details: {
5887
+ questions: result.questions,
5888
+ answers: result.answers,
5889
+ annotations: result.annotations
5890
+ }
5891
+ };
5892
+ } catch (err) {
5893
+ const message = err instanceof Error ? err.message : String(err);
5894
+ return {
5895
+ content: [{
5896
+ type: "text",
5897
+ text: `提问被取消: ${message}`
5898
+ }],
5899
+ details: {
5900
+ error: true,
5901
+ message,
5902
+ cancelled: true
5903
+ }
4416
5904
  };
4417
5905
  }
4418
- return { content: [{
4419
- type: "text",
4420
- text: "请指定 subagent_type(推荐的 Agent 类型)或 agents(已废弃的批量参数)。"
4421
- }] };
4422
5906
  }
4423
5907
  };
4424
5908
  }
@@ -4920,6 +6404,257 @@ async function buildStatusResult(scheduler) {
4920
6404
  };
4921
6405
  }
4922
6406
 
6407
+ //#endregion
6408
+ //#region src/cli/repl/tools/enter-worktree.ts
6409
+ /**
6410
+ * 创建 EnterWorktree 工具注册
6411
+ */
6412
+ function createEnterWorktreeTool(worktreeManager) {
6413
+ return {
6414
+ id: "EnterWorktree",
6415
+ label: "进入工作树",
6416
+ description: [
6417
+ "创建 git worktree 并切换当前会话的工作目录到隔离环境。",
6418
+ "",
6419
+ "### 何时使用此工具",
6420
+ "- 需要在独立的工作区中进行代码修改时",
6421
+ "- 不希望当前修改影响主工作区时",
6422
+ "- Agent 需要隔离的文件系统环境时",
6423
+ "",
6424
+ "### 参数",
6425
+ "- **name**(可选): worktree 名称,用于标识和后续管理",
6426
+ "",
6427
+ "### 注意事项",
6428
+ "- 进入 worktree 后所有文件操作和 Shell 命令都在 worktree 中执行",
6429
+ "- 使用 ExitWorktree 工具退出 worktree"
6430
+ ].join("\n"),
6431
+ defaultRisk: "high",
6432
+ parameters: {
6433
+ type: "object",
6434
+ properties: { name: {
6435
+ type: "string",
6436
+ description: "worktree 名称(可选,用于标识)"
6437
+ } },
6438
+ required: []
6439
+ },
6440
+ execute: async (_toolCallId, params) => {
6441
+ const name = params.name;
6442
+ try {
6443
+ const slug = name ?? `manual-${Date.now()}`;
6444
+ const info = await worktreeManager.create({
6445
+ slug,
6446
+ createdBy: "user"
6447
+ });
6448
+ process.chdir(info.worktreePath);
6449
+ return {
6450
+ content: [{
6451
+ type: "text",
6452
+ text: [
6453
+ "**已进入 worktree 隔离环境**",
6454
+ "",
6455
+ `- ID: \`${info.id}\``,
6456
+ `- 路径: \`${info.worktreePath}\``,
6457
+ `- 分支: \`${info.branchName}\``,
6458
+ "",
6459
+ "后续文件操作和 Shell 命令都将在 worktree 中执行。",
6460
+ "使用 ExitWorktree 工具退出。"
6461
+ ].join("\n")
6462
+ }],
6463
+ details: {
6464
+ worktreeId: info.id,
6465
+ worktreePath: info.worktreePath,
6466
+ branchName: info.branchName
6467
+ }
6468
+ };
6469
+ } catch (err) {
6470
+ const message = err instanceof Error ? err.message : String(err);
6471
+ return {
6472
+ content: [{
6473
+ type: "text",
6474
+ text: `创建 worktree 失败: ${message}`
6475
+ }],
6476
+ details: { error: message }
6477
+ };
6478
+ }
6479
+ }
6480
+ };
6481
+ }
6482
+
6483
+ //#endregion
6484
+ //#region src/core/worktree/worktree-context.ts
6485
+ /**
6486
+ * Worktree 运行时上下文
6487
+ *
6488
+ * 基于 AsyncLocalStorage 在整个工具调用链中传递 worktree 上下文。
6489
+ * 文件操作和 Shell 工具通过 resolveWorktreePath() 自动映射路径到 worktree。
6490
+ *
6491
+ * @module core/worktree
6492
+ */
6493
+ const worktreeStorage = new AsyncLocalStorage();
6494
+ /** 获取当前 worktree 上下文 */
6495
+ function getWorktreeContext() {
6496
+ return worktreeStorage.getStore();
6497
+ }
6498
+ /** 在 worktree 上下文中执行回调 */
6499
+ async function runInWorktree(context, fn) {
6500
+ return worktreeStorage.run(context, fn);
6501
+ }
6502
+ /**
6503
+ * 将路径解析到当前 worktree
6504
+ *
6505
+ * 解析规则:
6506
+ * - 无 worktree 上下文 → 直接 resolve 返回
6507
+ * - 相对路径 → 在 worktree 根目录下解析
6508
+ * - 绝对路径(属于原项目目录) → 映射到 worktree 对应路径
6509
+ * - 绝对路径(不在项目目录内) → 保持原样
6510
+ */
6511
+ function resolveWorktreePath(originalPath) {
6512
+ const ctx = worktreeStorage.getStore();
6513
+ if (!ctx) return resolve(originalPath);
6514
+ if (!isAbsolute(originalPath)) return resolve(ctx.worktreePath, originalPath);
6515
+ const normalizedOriginal = resolve(originalPath);
6516
+ const normalizedProjectRoot = resolve(ctx.originalPath);
6517
+ if (normalizedOriginal.startsWith(normalizedProjectRoot)) {
6518
+ const relPath = relative(normalizedProjectRoot, normalizedOriginal);
6519
+ return resolve(ctx.worktreePath, relPath);
6520
+ }
6521
+ return normalizedOriginal;
6522
+ }
6523
+ /**
6524
+ * 获取当前有效的工作目录
6525
+ *
6526
+ * 在 worktree 上下文中返回 worktree 路径,否则返回 process.cwd()
6527
+ */
6528
+ function resolveWorkdir() {
6529
+ return worktreeStorage.getStore()?.worktreePath ?? process.cwd();
6530
+ }
6531
+
6532
+ //#endregion
6533
+ //#region src/cli/repl/tools/exit-worktree.ts
6534
+ /**
6535
+ * ExitWorktree 工具
6536
+ *
6537
+ * 退出当前 worktree 隔离环境。
6538
+ * 支持 keep(保留 worktree)和 remove(删除 worktree)两种模式。
6539
+ *
6540
+ * @module cli/repl/tools
6541
+ */
6542
+ /**
6543
+ * 创建 ExitWorktree 工具注册
6544
+ */
6545
+ function createExitWorktreeTool(worktreeManager) {
6546
+ return {
6547
+ id: "ExitWorktree",
6548
+ label: "退出工作树",
6549
+ description: [
6550
+ "退出当前 worktree 隔离环境。",
6551
+ "",
6552
+ "### 参数",
6553
+ "- **action**: \"keep\" 保留 worktree,\"remove\" 删除 worktree",
6554
+ "- **discard_changes**: remove 时是否强制丢弃未提交变更(默认 false)",
6555
+ "",
6556
+ "### 安全门控",
6557
+ "- remove 模式:如果 worktree 有未提交变更且 discard_changes=false,操作将被拒绝",
6558
+ "- keep 模式:直接退出 worktree,worktree 保留在磁盘上"
6559
+ ].join("\n"),
6560
+ defaultRisk: "high",
6561
+ parameters: {
6562
+ type: "object",
6563
+ properties: {
6564
+ action: {
6565
+ type: "string",
6566
+ enum: ["keep", "remove"],
6567
+ description: "keep=保留 worktree 并退出,remove=删除 worktree 并退出"
6568
+ },
6569
+ discard_changes: {
6570
+ type: "boolean",
6571
+ description: "remove 时是否强制丢弃未提交变更(默认 false)。设为 true 会强制删除 worktree"
6572
+ }
6573
+ },
6574
+ required: ["action"]
6575
+ },
6576
+ execute: async (_toolCallId, params) => {
6577
+ const p = params;
6578
+ const ctx = getWorktreeContext();
6579
+ if (!ctx) return { content: [{
6580
+ type: "text",
6581
+ text: "当前不处于 worktree 隔离环境。无需退出。"
6582
+ }] };
6583
+ try {
6584
+ if (p.action === "keep") {
6585
+ if (existsSync(ctx.originalPath)) process.chdir(ctx.originalPath);
6586
+ return {
6587
+ content: [{
6588
+ type: "text",
6589
+ text: [
6590
+ "**已退出 worktree 隔离环境(保留 worktree)**",
6591
+ "",
6592
+ `- Worktree ID: \`${ctx.worktreeId}\``,
6593
+ `- Worktree 路径: \`${ctx.worktreePath}\``,
6594
+ `- 已切换回: \`${ctx.originalPath}\``
6595
+ ].join("\n")
6596
+ }],
6597
+ details: {
6598
+ action: "keep",
6599
+ worktreeId: ctx.worktreeId
6600
+ }
6601
+ };
6602
+ }
6603
+ try {
6604
+ await worktreeManager.remove(ctx.worktreeId, p.discard_changes);
6605
+ } catch (err) {
6606
+ const message = err instanceof Error ? err.message : String(err);
6607
+ if (!p.discard_changes && message.includes("changes")) return {
6608
+ content: [{
6609
+ type: "text",
6610
+ text: [
6611
+ "**无法删除 worktree:存在未提交的变更**",
6612
+ "",
6613
+ `错误: ${message}`,
6614
+ "",
6615
+ "请选择:",
6616
+ "- 设置 `discard_changes: true` 强制删除",
6617
+ "- 或使用 `action: \"keep\"` 保留 worktree"
6618
+ ].join("\n")
6619
+ }],
6620
+ details: {
6621
+ action: "remove",
6622
+ error: "has_changes",
6623
+ worktreeId: ctx.worktreeId
6624
+ }
6625
+ };
6626
+ throw err;
6627
+ }
6628
+ if (existsSync(ctx.originalPath)) process.chdir(ctx.originalPath);
6629
+ return {
6630
+ content: [{
6631
+ type: "text",
6632
+ text: [
6633
+ "**已退出 worktree 隔离环境(已删除 worktree)**",
6634
+ "",
6635
+ `- Worktree ID: \`${ctx.worktreeId}\``,
6636
+ `- 已切换回: \`${ctx.originalPath}\``
6637
+ ].join("\n")
6638
+ }],
6639
+ details: {
6640
+ action: "remove",
6641
+ worktreeId: ctx.worktreeId
6642
+ }
6643
+ };
6644
+ } catch (err) {
6645
+ const message = err instanceof Error ? err.message : String(err);
6646
+ return {
6647
+ content: [{
6648
+ type: "text",
6649
+ text: `退出 worktree 失败: ${message}`
6650
+ }],
6651
+ details: { error: message }
6652
+ };
6653
+ }
6654
+ }
6655
+ };
6656
+ }
6657
+
4923
6658
  //#endregion
4924
6659
  //#region src/cli/repl/tools/file-security.ts
4925
6660
  /**
@@ -5308,7 +7043,7 @@ function createEditFileTool() {
5308
7043
  error: "old_string 与 new_string 相同"
5309
7044
  }
5310
7045
  };
5311
- const pathResult = validateFilePath(params.file_path);
7046
+ const pathResult = validateFilePath(resolveWorktreePath(params.file_path));
5312
7047
  if (!pathResult.valid) {
5313
7048
  const details = {
5314
7049
  filePath: params.file_path,
@@ -5532,7 +7267,7 @@ function createGlobTool() {
5532
7267
  },
5533
7268
  async execute(_toolCallId, params) {
5534
7269
  const startTime = Date.now();
5535
- const searchPath = resolve(params.path ?? process.cwd());
7270
+ const searchPath = resolveWorktreePath(params.path ?? process.cwd());
5536
7271
  try {
5537
7272
  const matches = globSync(params.pattern, searchPath);
5538
7273
  const elapsedMs = Date.now() - startTime;
@@ -5748,7 +7483,7 @@ function createGrepTool() {
5748
7483
  },
5749
7484
  async execute(_toolCallId, params) {
5750
7485
  const startTime = Date.now();
5751
- const searchPath = resolve(params.path ?? process.cwd());
7486
+ const searchPath = resolveWorktreePath(params.path ?? process.cwd());
5752
7487
  const outputMode = params.output_mode ?? "content";
5753
7488
  const ignoreCase = params["-i"] === true;
5754
7489
  const contextBefore = params["-B"] ?? params.context ?? 0;
@@ -5980,7 +7715,7 @@ function createWriteFileTool() {
5980
7715
  checkPermission: checkFilePermission,
5981
7716
  async execute(_toolCallId, params) {
5982
7717
  const startTime = Date.now();
5983
- const pathResult = validateFilePath(params.file_path);
7718
+ const pathResult = validateFilePath(resolveWorktreePath(params.file_path));
5984
7719
  if (!pathResult.valid) {
5985
7720
  const details = {
5986
7721
  type: "create",
@@ -7085,7 +8820,7 @@ function createExecTool() {
7085
8820
  durationMs: Date.now() - startTime
7086
8821
  }
7087
8822
  };
7088
- const workdirResult = validateWorkdir(params.workdir);
8823
+ const workdirResult = validateWorkdir(params.workdir ?? resolveWorkdir());
7089
8824
  if (!workdirResult.valid) return {
7090
8825
  content: [{
7091
8826
  type: "text",
@@ -9073,36 +10808,399 @@ function createWebSearchTool(webConfig) {
9073
10808
  }
9074
10809
  let results;
9075
10810
  try {
9076
- results = await provider.search(query, {
9077
- maxResults: numResults,
9078
- language
9079
- }, providerConfig, signal);
10811
+ results = await provider.search(query, {
10812
+ maxResults: numResults,
10813
+ language
10814
+ }, providerConfig, signal);
10815
+ } catch (err) {
10816
+ if (err instanceof Error && err.message.includes("quota")) throw new WebError("WEB_SEARCH_QUOTA_EXCEEDED", err.message, { provider: providerName });
10817
+ throw new WebError("WEB_SEARCH_FAILED", `搜索失败: ${err instanceof Error ? err.message : String(err)}`, {
10818
+ provider: providerName,
10819
+ query
10820
+ });
10821
+ }
10822
+ const text = formatResults(results, query, provider.label);
10823
+ cache.set(cacheKey, text);
10824
+ const details = {
10825
+ query,
10826
+ provider: providerName,
10827
+ resultCount: results.length,
10828
+ cached: false,
10829
+ elapsedMs: Date.now() - startTime
10830
+ };
10831
+ return {
10832
+ content: [{
10833
+ type: "text",
10834
+ text
10835
+ }],
10836
+ details
10837
+ };
10838
+ }
10839
+ };
10840
+ }
10841
+
10842
+ //#endregion
10843
+ //#region src/core/worktree/types.ts
10844
+ /** Worktree 操作错误 */
10845
+ var WorktreeError = class extends Error {
10846
+ code;
10847
+ constructor(message, code) {
10848
+ super(message);
10849
+ this.name = "WorktreeError";
10850
+ this.code = code;
10851
+ }
10852
+ };
10853
+
10854
+ //#endregion
10855
+ //#region src/core/worktree/worktree-store.ts
10856
+ /**
10857
+ * Worktree 持久化存储
10858
+ *
10859
+ * 将 worktree 记录持久化到磁盘(~/.zapmyco/worktrees/ 目录),
10860
+ * 支持跨会话恢复和过期清理。
10861
+ *
10862
+ * @module core/worktree
10863
+ */
10864
+ /** 获取默认存储目录 */
10865
+ function getDefaultBaseDir() {
10866
+ return join(__require("node:os").homedir(), ".zapmyco", "worktrees");
10867
+ }
10868
+ /**
10869
+ * Worktree 持久化存储
10870
+ */
10871
+ var WorktreeStore = class {
10872
+ baseDir;
10873
+ cache = /* @__PURE__ */ new Map();
10874
+ loaded = false;
10875
+ constructor(baseDir) {
10876
+ this.baseDir = baseDir ?? getDefaultBaseDir();
10877
+ }
10878
+ /** 获取存储目录 */
10879
+ getBaseDir() {
10880
+ return this.baseDir;
10881
+ }
10882
+ /** 保存记录到磁盘 */
10883
+ save(record) {
10884
+ this.ensureDir();
10885
+ this.cache.set(record.id, record);
10886
+ writeFileSync(this.getFilePath(record.id), JSON.stringify(record, null, 2), "utf-8");
10887
+ }
10888
+ /** 更新记录状态 */
10889
+ updateStatus(id, status) {
10890
+ const record = this.cache.get(id);
10891
+ if (record) {
10892
+ record.status = status;
10893
+ this.cache.set(id, record);
10894
+ this.save(record);
10895
+ }
10896
+ }
10897
+ /** 删除记录(从缓存和磁盘) */
10898
+ delete(id) {
10899
+ this.cache.delete(id);
10900
+ const filePath = this.getFilePath(id);
10901
+ try {
10902
+ if (existsSync(filePath)) unlinkSync(filePath);
10903
+ } catch {}
10904
+ }
10905
+ /** 获取单个记录 */
10906
+ get(id) {
10907
+ this.ensureLoaded();
10908
+ return this.cache.get(id);
10909
+ }
10910
+ /** 列出所有活跃记录 */
10911
+ listActive() {
10912
+ this.ensureLoaded();
10913
+ return Array.from(this.cache.values()).filter((r) => r.status === "active");
10914
+ }
10915
+ /** 列出所有记录 */
10916
+ listAll() {
10917
+ this.ensureLoaded();
10918
+ return Array.from(this.cache.values());
10919
+ }
10920
+ /** 从磁盘加载所有记录 */
10921
+ load() {
10922
+ this.ensureDir();
10923
+ this.cache.clear();
10924
+ try {
10925
+ const files = readdirSync(this.baseDir).filter((f) => f.endsWith(".json"));
10926
+ for (const file of files) try {
10927
+ const content = readFileSync(join(this.baseDir, file), "utf-8");
10928
+ const record = JSON.parse(content);
10929
+ if (record.id && record.worktreePath) this.cache.set(record.id, record);
10930
+ } catch {}
10931
+ } catch {}
10932
+ this.loaded = true;
10933
+ return Array.from(this.cache.values());
10934
+ }
10935
+ /** 清理过期记录文件 */
10936
+ cleanExpired(expireAfterMs) {
10937
+ this.ensureLoaded();
10938
+ let cleaned = 0;
10939
+ const now = Date.now();
10940
+ for (const [id, record] of this.cache) if (record.status === "expired") {
10941
+ this.delete(id);
10942
+ cleaned++;
10943
+ } else if (record.status === "active" && now - record.createdAt > expireAfterMs) {
10944
+ record.status = "expired";
10945
+ this.save(record);
10946
+ cleaned++;
10947
+ }
10948
+ return cleaned;
10949
+ }
10950
+ /** 确保目录存在 */
10951
+ ensureDir() {
10952
+ if (!existsSync(this.baseDir)) mkdirSync(this.baseDir, { recursive: true });
10953
+ }
10954
+ /** 确保已加载 */
10955
+ ensureLoaded() {
10956
+ if (!this.loaded) this.load();
10957
+ }
10958
+ /** 获取记录文件路径 */
10959
+ getFilePath(id) {
10960
+ const safeId = id.replace(/[^a-zA-Z0-9_-]/g, "_");
10961
+ return join(this.baseDir, `${safeId}.json`);
10962
+ }
10963
+ };
10964
+
10965
+ //#endregion
10966
+ //#region src/core/worktree/worktree-manager.ts
10967
+ /**
10968
+ * Worktree 管理器
10969
+ *
10970
+ * 负责 git worktree 的完整生命周期管理:
10971
+ * - 创建隔离 worktree
10972
+ * - 执行后自动清理(无变更)或保留(有变更)
10973
+ * - 过期 worktree 清理
10974
+ *
10975
+ * 通过 child_process.execFile 调用 git 命令,不引入额外依赖。
10976
+ *
10977
+ * @module core/worktree
10978
+ */
10979
+ const execFileAsync = promisify(execFile);
10980
+ const log$10 = logger.child("worktree-manager");
10981
+ let globalWorktreeManager = null;
10982
+ /** 获取全局 WorktreeManager 实例 */
10983
+ function getWorktreeManager() {
10984
+ return globalWorktreeManager ?? void 0;
10985
+ }
10986
+ /** 设置全局 WorktreeManager 实例 */
10987
+ function setWorktreeManager(manager) {
10988
+ globalWorktreeManager = manager;
10989
+ }
10990
+ var WorktreeManager = class {
10991
+ config;
10992
+ store;
10993
+ activeWorktrees = /* @__PURE__ */ new Map();
10994
+ constructor(config) {
10995
+ this.config = config;
10996
+ this.store = new WorktreeStore(config.baseDir);
10997
+ this.store.load();
10998
+ }
10999
+ /**
11000
+ * 创建新的 git worktree
11001
+ */
11002
+ async create(options) {
11003
+ if (!this.config.enabled) throw new WorktreeError("Worktree 功能未启用", "DISABLED");
11004
+ const timestamp = Date.now();
11005
+ const branchName = `zapmyco-${options.slug}-${timestamp}`;
11006
+ const dirName = `${options.slug}-${timestamp}`;
11007
+ const worktreePath = join(this.config.baseDir, dirName);
11008
+ let gitRoot;
11009
+ try {
11010
+ const { stdout } = await execFileAsync("git", ["rev-parse", "--show-toplevel"], { timeout: 5e3 });
11011
+ gitRoot = stdout.trim();
11012
+ } catch {
11013
+ throw new WorktreeError("无法确定 git 仓库根目录,请确保在 git 仓库中运行", "NOT_GIT_REPO");
11014
+ }
11015
+ try {
11016
+ log$10.info("创建 worktree", {
11017
+ branchName,
11018
+ worktreePath,
11019
+ gitRoot
11020
+ });
11021
+ await execFileAsync("git", [
11022
+ "worktree",
11023
+ "add",
11024
+ "--detach",
11025
+ worktreePath
11026
+ ], {
11027
+ cwd: gitRoot,
11028
+ timeout: 3e4
11029
+ });
11030
+ } catch (err) {
11031
+ throw new WorktreeError(`创建 worktree 失败: ${err instanceof Error ? err.message : String(err)}`, "CREATE_FAILED");
11032
+ }
11033
+ try {
11034
+ await execFileAsync("git", [
11035
+ "checkout",
11036
+ "-b",
11037
+ branchName
11038
+ ], {
11039
+ cwd: worktreePath,
11040
+ timeout: 1e4
11041
+ });
11042
+ } catch (err) {
11043
+ const msg = err instanceof Error ? err.message : String(err);
11044
+ log$10.warn("创建分支失败,清理 worktree", {
11045
+ branchName,
11046
+ error: msg
11047
+ });
11048
+ try {
11049
+ await this.removeByPath(worktreePath, branchName, true);
11050
+ } catch {}
11051
+ throw new WorktreeError(`在 worktree 中创建分支失败: ${msg}`, "BRANCH_FAILED");
11052
+ }
11053
+ const info = {
11054
+ id: `${options.slug}-${timestamp}`,
11055
+ worktreePath,
11056
+ branchName,
11057
+ originalPath: gitRoot,
11058
+ createdAt: timestamp,
11059
+ createdBy: options.createdBy
11060
+ };
11061
+ this.activeWorktrees.set(info.id, info);
11062
+ this.store.save({
11063
+ id: info.id,
11064
+ worktreePath: info.worktreePath,
11065
+ branchName: info.branchName,
11066
+ originalPath: info.originalPath,
11067
+ createdAt: info.createdAt,
11068
+ createdBy: info.createdBy,
11069
+ status: "active"
11070
+ });
11071
+ log$10.info("Worktree 创建成功", {
11072
+ id: info.id,
11073
+ path: worktreePath
11074
+ });
11075
+ return info;
11076
+ }
11077
+ /**
11078
+ * 删除指定 worktree
11079
+ */
11080
+ async remove(id, discardChanges) {
11081
+ const info = this.activeWorktrees.get(id);
11082
+ if (!info) {
11083
+ log$10.warn("尝试删除不存在的 worktree", { id });
11084
+ return;
11085
+ }
11086
+ await this.removeByPath(info.worktreePath, info.branchName, discardChanges);
11087
+ this.activeWorktrees.delete(id);
11088
+ this.store.delete(id);
11089
+ log$10.info("Worktree 已删除", { id });
11090
+ }
11091
+ /**
11092
+ * 通过路径删除 worktree(内部方法)
11093
+ */
11094
+ async removeByPath(worktreePath, branchName, discardChanges) {
11095
+ if (!existsSync(worktreePath)) {
11096
+ try {
11097
+ await execFileAsync("git", ["worktree", "prune"], { timeout: 1e4 });
11098
+ } catch {}
11099
+ return;
11100
+ }
11101
+ const args = ["worktree", "remove"];
11102
+ if (discardChanges) args.push("--force");
11103
+ args.push(worktreePath);
11104
+ try {
11105
+ await execFileAsync("git", args, { timeout: 15e3 });
11106
+ } catch (err) {
11107
+ const msg = err instanceof Error ? err.message : String(err);
11108
+ log$10.warn("git worktree remove 失败", {
11109
+ worktreePath,
11110
+ error: msg
11111
+ });
11112
+ }
11113
+ try {
11114
+ await execFileAsync("git", [
11115
+ "branch",
11116
+ "-D",
11117
+ branchName
11118
+ ], { timeout: 1e4 });
11119
+ } catch {}
11120
+ try {
11121
+ await execFileAsync("git", ["worktree", "prune"], { timeout: 1e4 });
11122
+ } catch {}
11123
+ }
11124
+ /**
11125
+ * 检查 worktree 是否有变更,无变更则自动清理
11126
+ */
11127
+ async autoCleanIfNoChanges(id) {
11128
+ const info = this.activeWorktrees.get(id);
11129
+ if (!info) return { cleaned: true };
11130
+ if (!this.config.autoCleanNoChanges) return {
11131
+ cleaned: false,
11132
+ worktreePath: info.worktreePath
11133
+ };
11134
+ if (!existsSync(info.worktreePath)) {
11135
+ this.activeWorktrees.delete(id);
11136
+ this.store.delete(id);
11137
+ return { cleaned: true };
11138
+ }
11139
+ if (!await this.checkHasChanges(info.worktreePath)) {
11140
+ await this.remove(id, true);
11141
+ return { cleaned: true };
11142
+ }
11143
+ log$10.info("Worktree 有变更,保留", {
11144
+ id,
11145
+ path: info.worktreePath
11146
+ });
11147
+ return {
11148
+ cleaned: false,
11149
+ worktreePath: info.worktreePath
11150
+ };
11151
+ }
11152
+ /**
11153
+ * 检查 worktree 中是否有未提交变更
11154
+ */
11155
+ async checkHasChanges(worktreePath) {
11156
+ try {
11157
+ const { stdout } = await execFileAsync("git", ["status", "--porcelain"], {
11158
+ cwd: worktreePath,
11159
+ timeout: 5e3
11160
+ });
11161
+ return stdout.trim().length > 0;
11162
+ } catch {
11163
+ return true;
11164
+ }
11165
+ }
11166
+ /**
11167
+ * 清理过期的 worktree
11168
+ */
11169
+ async cleanExpired() {
11170
+ const now = Date.now();
11171
+ let cleaned = 0;
11172
+ this.store.cleanExpired(this.config.expireAfterMs);
11173
+ const allRecords = this.store.listAll();
11174
+ for (const record of allRecords) if (record.status === "expired" || now - record.createdAt > this.config.expireAfterMs) {
11175
+ if (existsSync(record.worktreePath)) try {
11176
+ await this.removeByPath(record.worktreePath, record.branchName, true);
11177
+ cleaned++;
9080
11178
  } catch (err) {
9081
- if (err instanceof Error && err.message.includes("quota")) throw new WebError("WEB_SEARCH_QUOTA_EXCEEDED", err.message, { provider: providerName });
9082
- throw new WebError("WEB_SEARCH_FAILED", `搜索失败: ${err instanceof Error ? err.message : String(err)}`, {
9083
- provider: providerName,
9084
- query
11179
+ log$10.warn("过期 worktree 清理失败", {
11180
+ id: record.id,
11181
+ path: record.worktreePath,
11182
+ error: err instanceof Error ? err.message : String(err)
9085
11183
  });
9086
11184
  }
9087
- const text = formatResults(results, query, provider.label);
9088
- cache.set(cacheKey, text);
9089
- const details = {
9090
- query,
9091
- provider: providerName,
9092
- resultCount: results.length,
9093
- cached: false,
9094
- elapsedMs: Date.now() - startTime
9095
- };
9096
- return {
9097
- content: [{
9098
- type: "text",
9099
- text
9100
- }],
9101
- details
9102
- };
11185
+ this.activeWorktrees.delete(record.id);
11186
+ this.store.delete(record.id);
9103
11187
  }
9104
- };
9105
- }
11188
+ if (cleaned > 0) log$10.info("过期 worktree 清理完成", { cleaned });
11189
+ return cleaned;
11190
+ }
11191
+ listActive() {
11192
+ return Array.from(this.activeWorktrees.values());
11193
+ }
11194
+ getWorktree(id) {
11195
+ return this.activeWorktrees.get(id);
11196
+ }
11197
+ getConfig() {
11198
+ return { ...this.config };
11199
+ }
11200
+ getStore() {
11201
+ return this.store;
11202
+ }
11203
+ };
9106
11204
 
9107
11205
  //#endregion
9108
11206
  //#region src/core/agent-team/types.ts
@@ -9146,7 +11244,7 @@ const AGENT_STANDARD_TOOLS = [
9146
11244
  *
9147
11245
  * @module core/agent-team
9148
11246
  */
9149
- const log$10 = logger.child("agent-factory");
11247
+ const log$9 = logger.child("agent-factory");
9150
11248
  /**
9151
11249
  * 创建 Agent 实例
9152
11250
  *
@@ -9178,7 +11276,7 @@ function createAgentFromType(definition, instance, parentAgent, availableTools,
9178
11276
  const tools = resolveTools(definition, availableTools, instance.depth, config);
9179
11277
  agent.registerTools(tools);
9180
11278
  agent.systemPromptOverride = buildSystemPrompt(definition, instance.task.description, config);
9181
- log$10.debug("创建 Agent 实例", {
11279
+ log$9.debug("创建 Agent 实例", {
9182
11280
  typeId: definition.typeId,
9183
11281
  instanceId: instance.instanceId,
9184
11282
  depth: instance.depth,
@@ -9262,7 +11360,7 @@ function shareParentResources(subAgent, parentAgent) {
9262
11360
 
9263
11361
  //#endregion
9264
11362
  //#region src/core/agent-team/agent-result-aggregator.ts
9265
- const log$9 = logger.child("agent-result-aggregator");
11363
+ const log$8 = logger.child("agent-result-aggregator");
9266
11364
  /** 零值 TokenUsage */
9267
11365
  const ZERO_TOKEN = {
9268
11366
  inputTokens: 0,
@@ -9288,7 +11386,7 @@ function aggregateResults(teamId, workerResults) {
9288
11386
  estimatedCostUsd: sum.estimatedCostUsd + (r.tokenUsage?.estimatedCostUsd ?? 0)
9289
11387
  }), { ...ZERO_TOKEN });
9290
11388
  const summary = buildTeamSummary(workerResults);
9291
- log$9.debug("Team 结果聚合完成", {
11389
+ log$8.debug("Team 结果聚合完成", {
9292
11390
  teamId,
9293
11391
  total: workerResults.length,
9294
11392
  succeeded,
@@ -9366,7 +11464,7 @@ function escapeMarkdownTable(text) {
9366
11464
 
9367
11465
  //#endregion
9368
11466
  //#region src/core/agent-team/agent-orchestrator.ts
9369
- const log$8 = logger.child("agent-orchestrator");
11467
+ const log$7 = logger.child("agent-orchestrator");
9370
11468
  /**
9371
11469
  * Agent 编排器
9372
11470
  *
@@ -9399,7 +11497,7 @@ var AgentOrchestrator = class {
9399
11497
  const startTime = Date.now();
9400
11498
  const defaultType = getAgentTypeRegistry().getDefault();
9401
11499
  if (!defaultType) throw new Error("无法获取默认 Agent 类型(general-purpose)");
9402
- log$8.info("开始扁平并行执行", {
11500
+ log$7.info("开始扁平并行执行", {
9403
11501
  count: specs.length,
9404
11502
  maxConcurrent: this.flatConfig.maxConcurrent,
9405
11503
  hasContext: context != null
@@ -9407,7 +11505,7 @@ var AgentOrchestrator = class {
9407
11505
  const allResults = [];
9408
11506
  for (let i = 0; i < specs.length; i += this.flatConfig.maxConcurrent) {
9409
11507
  const batch = specs.slice(i, i + this.flatConfig.maxConcurrent);
9410
- log$8.debug("执行扁平批次", {
11508
+ log$7.debug("执行扁平批次", {
9411
11509
  batchStart: i,
9412
11510
  batchSize: batch.length,
9413
11511
  totalSpecs: specs.length
@@ -9417,7 +11515,7 @@ var AgentOrchestrator = class {
9417
11515
  }
9418
11516
  const succeeded = allResults.filter((r) => r.status === "success").length;
9419
11517
  const totalDuration = Date.now() - startTime;
9420
- log$8.info("扁平并行执行完成", {
11518
+ log$7.info("扁平并行执行完成", {
9421
11519
  total: allResults.length,
9422
11520
  succeeded,
9423
11521
  failed: allResults.length - succeeded,
@@ -9489,7 +11587,7 @@ var AgentOrchestrator = class {
9489
11587
  } catch (error) {
9490
11588
  const duration = Date.now() - startTime;
9491
11589
  const message = error instanceof Error ? error.message : String(error);
9492
- log$8.warn("扁平子任务执行失败", {
11590
+ log$7.warn("扁平子任务执行失败", {
9493
11591
  specId: spec.id,
9494
11592
  error: message,
9495
11593
  duration
@@ -9541,7 +11639,7 @@ var AgentOrchestrator = class {
9541
11639
  const parentInstanceId = options?.parentInstanceId ?? "";
9542
11640
  const depth = (parentInstanceId && instanceManager.get(parentInstanceId) ? instanceManager.get(parentInstanceId)?.depth ?? 0 : 0) + 1;
9543
11641
  if (depth > this.teamConfig.maxGlobalDepth) {
9544
- log$8.warn("Worker 创建被拒绝:超过全局最大深度", {
11642
+ log$7.warn("Worker 创建被拒绝:超过全局最大深度", {
9545
11643
  typeId,
9546
11644
  depth,
9547
11645
  maxGlobalDepth: this.teamConfig.maxGlobalDepth
@@ -9570,6 +11668,7 @@ var AgentOrchestrator = class {
9570
11668
  const taskId = options?.taskId ?? `worker-${typeId}-${Date.now()}`;
9571
11669
  const instanceId = `agent-${typeId}-${Date.now()}`;
9572
11670
  const timeoutMs = options?.timeoutMs ?? this.flatConfig.taskTimeoutMs;
11671
+ let worktreeInfo;
9573
11672
  try {
9574
11673
  const agent = createAgentFromType(definition, {
9575
11674
  instanceId,
@@ -9596,16 +11695,36 @@ var AgentOrchestrator = class {
9596
11695
  if (options?.context) promptCtx.context = options.context;
9597
11696
  const systemPrompt = definition.getSystemPrompt(promptCtx);
9598
11697
  agent.systemPromptOverride = this.enrichSystemPrompt(systemPrompt, instanceId, parentInstanceId || null);
11698
+ let effectiveOptions = options;
11699
+ if (options?.isolation === "worktree" && getWorktreeManager()) {
11700
+ worktreeInfo = await getWorktreeManager().create({
11701
+ slug: `${typeId}-${instanceId}`,
11702
+ createdBy: instanceId
11703
+ });
11704
+ const innerWrapExecute = options.wrapExecute;
11705
+ effectiveOptions = { ...options };
11706
+ effectiveOptions.wrapExecute = (execute) => {
11707
+ const inner = innerWrapExecute ? () => innerWrapExecute(execute) : execute;
11708
+ return runInWorktree({
11709
+ worktreeId: worktreeInfo.id,
11710
+ worktreePath: worktreeInfo.worktreePath,
11711
+ originalPath: worktreeInfo.originalPath
11712
+ }, inner);
11713
+ };
11714
+ }
9599
11715
  instanceManager.transition(instanceId, "running");
9600
- const result = await Promise.race([agent.execute({
11716
+ const workdir = worktreeInfo?.worktreePath ?? process.cwd();
11717
+ const executeFn = () => agent.execute({
9601
11718
  taskId,
9602
11719
  taskDescription,
9603
- workdir: process.cwd(),
11720
+ workdir,
9604
11721
  options: {
9605
11722
  timeout: timeoutMs,
9606
11723
  verbose: false
9607
11724
  }
9608
- }), this.createTimeoutPromise(taskId, timeoutMs)]);
11725
+ });
11726
+ const executePromise = effectiveOptions?.wrapExecute ? effectiveOptions.wrapExecute(executeFn) : executeFn();
11727
+ const result = await Promise.race([executePromise, this.createTimeoutPromise(taskId, timeoutMs)]);
9609
11728
  const duration = Date.now() - startTime;
9610
11729
  const taskResult = result;
9611
11730
  const outputText = this.extractOutputText(result);
@@ -9636,7 +11755,7 @@ var AgentOrchestrator = class {
9636
11755
  } catch (error) {
9637
11756
  const duration = Date.now() - startTime;
9638
11757
  const message = error instanceof Error ? error.message : String(error);
9639
- log$8.warn("Worker 执行失败", {
11758
+ log$7.warn("Worker 执行失败", {
9640
11759
  typeId,
9641
11760
  instanceId,
9642
11761
  error: message,
@@ -9670,6 +11789,21 @@ var AgentOrchestrator = class {
9670
11789
  const instance = instanceManager.get(instanceId);
9671
11790
  if (instance) instance.agent.systemPromptOverride = null;
9672
11791
  } catch {}
11792
+ if (worktreeInfo) {
11793
+ const wm = getWorktreeManager();
11794
+ if (wm) try {
11795
+ const cleanResult = await wm.autoCleanIfNoChanges(worktreeInfo.id);
11796
+ if (!cleanResult.cleaned) log$7.info("Worktree 有变更,保留", {
11797
+ id: worktreeInfo.id,
11798
+ path: cleanResult.worktreePath
11799
+ });
11800
+ } catch (err) {
11801
+ log$7.warn("Worktree 清理失败", {
11802
+ id: worktreeInfo.id,
11803
+ error: err instanceof Error ? err.message : String(err)
11804
+ });
11805
+ }
11806
+ }
9673
11807
  }
9674
11808
  }
9675
11809
  /**
@@ -9684,7 +11818,7 @@ var AgentOrchestrator = class {
9684
11818
  */
9685
11819
  async spawnTeam(taskDescription, workerSpecs) {
9686
11820
  const teamId = `team-${Date.now()}-${++this.teamCounter}`;
9687
- log$8.info("创建 Team", {
11821
+ log$7.info("创建 Team", {
9688
11822
  teamId,
9689
11823
  workerCount: workerSpecs.length,
9690
11824
  taskDescription
@@ -9845,6 +11979,8 @@ const TOOL_RISK_MAP = {
9845
11979
  Skill: "medium",
9846
11980
  TaskManage: "medium",
9847
11981
  ScheduledTask: "medium",
11982
+ LSP: "low",
11983
+ AskUserQuestion: "medium",
9848
11984
  SpawnSubAgents: "high"
9849
11985
  };
9850
11986
 
@@ -9856,7 +11992,7 @@ const TOOL_RISK_MAP = {
9856
11992
  * @param webConfig - Web 工具配置(可选),传入时启用 WebFetch 和 WebSearch
9857
11993
  * @param taskStore - TaskStore 实例(可选),传入时启用 TaskManage 工具
9858
11994
  */
9859
- function createReplBuiltinTools(webConfig, taskStore, skillConfig, parentAgent, subAgentConfig, cronScheduler, agentTeamConfig) {
11995
+ function createReplBuiltinTools(webConfig, taskStore, skillConfig, parentAgent, subAgentConfig, cronScheduler, agentTeamConfig, worktreeManager) {
9860
11996
  const tools = [
9861
11997
  {
9862
11998
  id: "GetCurrentTime",
@@ -9920,154 +12056,1353 @@ function createReplBuiltinTools(webConfig, taskStore, skillConfig, parentAgent,
9920
12056
  type: "number",
9921
12057
  description: "最大读取行数(可选,默认 2000)"
9922
12058
  }
9923
- },
9924
- required: ["file_path"]
9925
- },
9926
- execute: async (_toolCallId, params) => {
9927
- const fs = await import("node:fs/promises");
9928
- const resolvedPath = (await import("node:path")).resolve(params.file_path);
9929
- try {
9930
- const lines = (await fs.readFile(resolvedPath, "utf-8")).split("\n");
9931
- const offset = params.offset ? Math.max(1, params.offset) : 1;
9932
- const limit = params.limit ?? 2e3;
9933
- const startIdx = offset - 1;
9934
- const endIdx = Math.min(startIdx + limit, lines.length);
9935
- const pageContent = lines.slice(startIdx, endIdx).map((l, i) => `${startIdx + i + 1}\t${l}`).join("\n");
9936
- const truncated = endIdx < lines.length;
9937
- readStateTracker.recordRead(resolvedPath);
9938
- return {
12059
+ },
12060
+ required: ["file_path"]
12061
+ },
12062
+ execute: async (_toolCallId, params) => {
12063
+ const fs = await import("node:fs/promises");
12064
+ const resolvedPath = resolveWorktreePath(params.file_path);
12065
+ try {
12066
+ const lines = (await fs.readFile(resolvedPath, "utf-8")).split("\n");
12067
+ const offset = params.offset ? Math.max(1, params.offset) : 1;
12068
+ const limit = params.limit ?? 2e3;
12069
+ const startIdx = offset - 1;
12070
+ const endIdx = Math.min(startIdx + limit, lines.length);
12071
+ const pageContent = lines.slice(startIdx, endIdx).map((l, i) => `${startIdx + i + 1}\t${l}`).join("\n");
12072
+ const truncated = endIdx < lines.length;
12073
+ readStateTracker.recordRead(resolvedPath);
12074
+ return {
12075
+ content: [{
12076
+ type: "text",
12077
+ text: pageContent + (truncated ? `\n\n[文件共 ${lines.length} 行,已显示 ${offset}-${endIdx} 行]` : "")
12078
+ }],
12079
+ details: {
12080
+ path: resolvedPath,
12081
+ totalLines: lines.length,
12082
+ displayedLines: endIdx - startIdx,
12083
+ offset,
12084
+ limit,
12085
+ truncated
12086
+ }
12087
+ };
12088
+ } catch (error) {
12089
+ return {
12090
+ content: [{
12091
+ type: "text",
12092
+ text: `读取失败: ${error instanceof Error ? error.message : String(error)}`
12093
+ }],
12094
+ details: {
12095
+ path: resolvedPath,
12096
+ error: true
12097
+ }
12098
+ };
12099
+ }
12100
+ }
12101
+ }
12102
+ ];
12103
+ tools.push(createWriteFileTool());
12104
+ tools.push(createEditFileTool());
12105
+ tools.push(createGlobTool());
12106
+ tools.push(createGrepTool());
12107
+ tools.push(createExecTool());
12108
+ tools.push(createProcessTool());
12109
+ if (webConfig?.enabled !== false) {
12110
+ tools.push(createWebFetchTool(webConfig));
12111
+ tools.push(createWebSearchTool(webConfig));
12112
+ }
12113
+ if (taskStore) tools.push(createTaskManageTool(taskStore));
12114
+ tools.push(createMemoryTool());
12115
+ if (skillConfig?.enabled !== false) tools.push(createSkillTool(skillConfig));
12116
+ if (parentAgent && subAgentConfig?.enabled !== false && subAgentConfig) {
12117
+ let orchestrator;
12118
+ if (agentTeamConfig?.enabled) {
12119
+ orchestrator = new AgentOrchestrator(agentTeamConfig, subAgentConfig, parentAgent, tools);
12120
+ tools.push(createAgentTool(orchestrator));
12121
+ const bgManager = getBackgroundAgentManager();
12122
+ bgManager.setOrchestrator(orchestrator);
12123
+ bgManager.restore();
12124
+ }
12125
+ const manager = new SubAgentManager(subAgentConfig, parentAgent, tools, orchestrator);
12126
+ if (!agentTeamConfig?.enabled) tools.push(createSpawnSubAgentsTool(manager, subAgentConfig));
12127
+ }
12128
+ if (worktreeManager) {
12129
+ tools.push(createEnterWorktreeTool(worktreeManager));
12130
+ tools.push(createExitWorktreeTool(worktreeManager));
12131
+ }
12132
+ if (cronScheduler) tools.push(createCronTool(cronScheduler));
12133
+ tools.push(createAskUserQuestionTool());
12134
+ for (const tool of tools) if (!tool.defaultRisk) tool.defaultRisk = TOOL_RISK_MAP[tool.id] ?? "medium";
12135
+ return tools;
12136
+ }
12137
+
12138
+ //#endregion
12139
+ //#region src/cli/repl/theme.ts
12140
+ /** 根据颜色开关获取 chalk 实例 */
12141
+ function makeColor(colorEnabled) {
12142
+ return colorEnabled ? chalk : new Chalk({ level: 0 });
12143
+ }
12144
+ /**
12145
+ * 创建 zapmyco pi-tui 主题
12146
+ */
12147
+ function createTheme(colorEnabled) {
12148
+ const c = makeColor(colorEnabled);
12149
+ /** 基础 selectList 主题 */
12150
+ const baseSelectListTheme = {
12151
+ selectedPrefix: (text) => c.cyan(text),
12152
+ selectedText: (text) => c.bold(c.cyan(text)),
12153
+ description: (text) => c.gray(text),
12154
+ scrollInfo: (text) => c.gray(text),
12155
+ noMatch: (text) => c.gray(text)
12156
+ };
12157
+ return {
12158
+ /** 主文本色 */
12159
+ text: (s) => s,
12160
+ /** 加粗 */
12161
+ bold: (s) => c.bold(s),
12162
+ /** 灰色/弱化文本 */
12163
+ dim: (s) => c.gray(s),
12164
+ /** 强调色 - 青色 */
12165
+ accent: (s) => c.cyan(s),
12166
+ /** 成功 - 绿色 */
12167
+ success: (s) => c.green(s),
12168
+ /** 错误 - 红色 */
12169
+ error: (s) => c.red(s),
12170
+ /** 警告 - 黄色 */
12171
+ warning: (s) => c.yellow(s),
12172
+ /** 边框色 - 灰色 */
12173
+ border: (s) => c.gray(s),
12174
+ /** Header 文本 */
12175
+ heading: (s) => c.bold(s),
12176
+ editorTheme: {
12177
+ borderColor: (text) => c.gray(text),
12178
+ selectList: baseSelectListTheme
12179
+ },
12180
+ selectListTheme: baseSelectListTheme
12181
+ };
12182
+ }
12183
+
12184
+ //#endregion
12185
+ //#region src/core/lsp/types.ts
12186
+ /** SymbolKind 名称映射 */
12187
+ const SYMBOL_KIND_NAMES = {
12188
+ [1]: "File",
12189
+ [2]: "Module",
12190
+ [3]: "Namespace",
12191
+ [4]: "Package",
12192
+ [5]: "Class",
12193
+ [6]: "Method",
12194
+ [7]: "Property",
12195
+ [8]: "Field",
12196
+ [9]: "Constructor",
12197
+ [10]: "Enum",
12198
+ [11]: "Interface",
12199
+ [12]: "Function",
12200
+ [13]: "Variable",
12201
+ [14]: "Constant",
12202
+ [15]: "String",
12203
+ [16]: "Number",
12204
+ [17]: "Boolean",
12205
+ [18]: "Array",
12206
+ [19]: "Object",
12207
+ [20]: "Key",
12208
+ [21]: "Null",
12209
+ [22]: "EnumMember",
12210
+ [23]: "Struct",
12211
+ [24]: "Event",
12212
+ [25]: "Operator",
12213
+ [26]: "TypeParameter"
12214
+ };
12215
+ /** LSP 操作错误 */
12216
+ var LspError = class extends Error {
12217
+ code;
12218
+ constructor(message, code) {
12219
+ super(message);
12220
+ this.name = "LspError";
12221
+ this.code = code;
12222
+ }
12223
+ };
12224
+
12225
+ //#endregion
12226
+ //#region src/cli/repl/tools/lsp-tool.ts
12227
+ /** 操作 → LSP 方法 */
12228
+ const METHOD_MAP = {
12229
+ goToDefinition: "textDocument/definition",
12230
+ findReferences: "textDocument/references",
12231
+ hover: "textDocument/hover",
12232
+ documentSymbol: "textDocument/documentSymbol",
12233
+ workspaceSymbol: "workspace/symbol",
12234
+ goToImplementation: "textDocument/implementation",
12235
+ prepareCallHierarchy: "textDocument/prepareCallHierarchy",
12236
+ incomingCalls: "callHierarchy/incomingCalls",
12237
+ outgoingCalls: "callHierarchy/outgoingCalls"
12238
+ };
12239
+ /**
12240
+ * 需要两步调用(先 prepareCallHierarchy)的操作
12241
+ */
12242
+ const TWO_STEP_OPS = new Set(["incomingCalls", "outgoingCalls"]);
12243
+ /**
12244
+ * 需要位置参数的操作
12245
+ */
12246
+ const POSITION_REQUIRED_OPS = new Set([
12247
+ "goToDefinition",
12248
+ "findReferences",
12249
+ "hover",
12250
+ "goToImplementation",
12251
+ "prepareCallHierarchy",
12252
+ "incomingCalls",
12253
+ "outgoingCalls"
12254
+ ]);
12255
+ /**
12256
+ * 不需要 filePath 的操作
12257
+ */
12258
+ const NO_FILEPATH_OPS = new Set(["workspaceSymbol"]);
12259
+ function formatLocation(loc) {
12260
+ if ("targetUri" in loc) return `${loc.targetUri.replace(/^file:\/\//, "")}:${loc.targetRange.start.line + 1}:${loc.targetRange.start.character + 1}`;
12261
+ return `${loc.uri.replace(/^file:\/\//, "")}:${loc.range.start.line + 1}:${loc.range.start.character + 1}`;
12262
+ }
12263
+ function formatHoverContent(contents) {
12264
+ if (typeof contents === "string") return contents;
12265
+ if (Array.isArray(contents)) return contents.map((c) => typeof c === "string" ? c : `\`\`\`${c.language}\n${c.value}\n\`\`\``).join("\n");
12266
+ if (typeof contents === "object" && "kind" in contents) return contents.value;
12267
+ return JSON.stringify(contents, null, 2);
12268
+ }
12269
+ function formatDocumentSymbol(symbols, indent = 0) {
12270
+ const prefix = " ".repeat(indent);
12271
+ const lines = [];
12272
+ for (const sym of symbols) {
12273
+ const kind = SYMBOL_KIND_NAMES[sym.kind] ?? `Kind(${sym.kind})`;
12274
+ const detail = sym.detail ? `: ${sym.detail}` : "";
12275
+ const line = sym.selectionRange.start.line + 1;
12276
+ const char = sym.selectionRange.start.character + 1;
12277
+ const deprecated = sym.deprecated ? " [deprecated]" : "";
12278
+ lines.push(`${prefix}${kind} ${sym.name}${detail} @ ${line}:${char}${deprecated}`);
12279
+ if (sym.children && sym.children.length > 0) lines.push(...formatDocumentSymbol(sym.children, indent + 1));
12280
+ }
12281
+ return lines.join("\n");
12282
+ }
12283
+ function formatWorkspaceSymbol(symbols) {
12284
+ const lines = [];
12285
+ for (const sym of symbols) {
12286
+ const kind = SYMBOL_KIND_NAMES[sym.kind] ?? `Kind(${sym.kind})`;
12287
+ const path = sym.location.uri.replace(/^file:\/\//, "");
12288
+ const line = sym.location.range.start.line + 1;
12289
+ const char = sym.location.range.start.character + 1;
12290
+ const container = sym.containerName ? ` (in ${sym.containerName})` : "";
12291
+ lines.push(`${kind} ${sym.name}${container} — ${path}:${line}:${char}`);
12292
+ }
12293
+ return lines.join("\n");
12294
+ }
12295
+ function formatCallHierarchyItem(item) {
12296
+ const kind = SYMBOL_KIND_NAMES[item.kind] ?? `Kind(${item.kind})`;
12297
+ const path = item.uri.replace(/^file:\/\//, "");
12298
+ const line = item.selectionRange.start.line + 1;
12299
+ const char = item.selectionRange.start.character + 1;
12300
+ const detail = item.detail ? `: ${item.detail}` : "";
12301
+ return `${kind} ${item.name}${detail} — ${path}:${line}:${char}`;
12302
+ }
12303
+ function createLspTool(lspManager) {
12304
+ return {
12305
+ id: "LSP",
12306
+ label: "LSP 代码智能",
12307
+ description: "使用 Language Server Protocol 进行代码智能操作。支持:goToDefinition(跳转到定义)、findReferences(查找引用)、hover(悬停信息)、documentSymbol(文档符号)、workspaceSymbol(工作区符号)、goToImplementation(跳转到实现)、prepareCallHierarchy(准备调用层次)、incomingCalls(传入调用)、outgoingCalls(传出调用)。所有操作都需要 filePath(绝对路径)、line(1-based)、character(1-based) 参数。LSP 服务器必须已配置才能使用此工具。",
12308
+ parameters: {
12309
+ type: "object",
12310
+ properties: {
12311
+ operation: {
12312
+ type: "string",
12313
+ enum: [
12314
+ "goToDefinition",
12315
+ "findReferences",
12316
+ "hover",
12317
+ "documentSymbol",
12318
+ "workspaceSymbol",
12319
+ "goToImplementation",
12320
+ "prepareCallHierarchy",
12321
+ "incomingCalls",
12322
+ "outgoingCalls"
12323
+ ],
12324
+ description: "要执行的 LSP 操作"
12325
+ },
12326
+ filePath: {
12327
+ type: "string",
12328
+ description: "文件绝对路径或相对路径(workspaceSymbol 操作不需要)"
12329
+ },
12330
+ line: {
12331
+ type: "number",
12332
+ description: "行号(1-based,与编辑器一致。位置相关操作需要)"
12333
+ },
12334
+ character: {
12335
+ type: "number",
12336
+ description: "字符偏移(1-based,与编辑器一致。位置相关操作需要)"
12337
+ }
12338
+ },
12339
+ required: [
12340
+ "operation",
12341
+ "filePath",
12342
+ "line",
12343
+ "character"
12344
+ ]
12345
+ },
12346
+ defaultRisk: "low",
12347
+ execute: async (_toolCallId, rawParams) => {
12348
+ const startTime = Date.now();
12349
+ const { operation, filePath, line, character } = rawParams;
12350
+ if (!NO_FILEPATH_OPS.has(operation) && !filePath) return {
12351
+ content: [{
12352
+ type: "text",
12353
+ text: "错误:需要 filePath 参数"
12354
+ }],
12355
+ details: {
12356
+ error: true,
12357
+ message: "filePath required"
12358
+ }
12359
+ };
12360
+ if (POSITION_REQUIRED_OPS.has(operation)) {
12361
+ if (line == null || character == null) return {
12362
+ content: [{
12363
+ type: "text",
12364
+ text: `错误:${operation} 需要 line 和 character 参数`
12365
+ }],
12366
+ details: {
12367
+ error: true,
12368
+ message: "position required"
12369
+ }
12370
+ };
12371
+ }
12372
+ try {
12373
+ const resolvedPath = NO_FILEPATH_OPS.has(operation) ? process.cwd() : resolveWorktreePath(filePath);
12374
+ const lspMethod = METHOD_MAP[operation];
12375
+ if (TWO_STEP_OPS.has(operation)) {
12376
+ const items = await lspManager.request(resolvedPath, "textDocument/prepareCallHierarchy", {
12377
+ textDocument: { uri: `file://${resolvedPath}` },
12378
+ position: {
12379
+ line: (line ?? 1) - 1,
12380
+ character: (character ?? 1) - 1
12381
+ }
12382
+ });
12383
+ const firstItem = items?.[0];
12384
+ if (!items || items.length === 0 || !firstItem) return {
9939
12385
  content: [{
9940
12386
  type: "text",
9941
- text: pageContent + (truncated ? `\n\n[文件共 ${lines.length} 行,已显示 ${offset}-${endIdx} 行]` : "")
12387
+ text: "未找到调用层次项"
9942
12388
  }],
9943
12389
  details: {
9944
- path: resolvedPath,
9945
- totalLines: lines.length,
9946
- displayedLines: endIdx - startIdx,
9947
- offset,
9948
- limit,
9949
- truncated
12390
+ operation,
12391
+ filePath: resolvedPath,
12392
+ resultCount: 0,
12393
+ elapsedMs: Date.now() - startTime
9950
12394
  }
9951
12395
  };
9952
- } catch (error) {
12396
+ const results = await lspManager.request(resolvedPath, lspMethod, { item: firstItem });
12397
+ const elapsedMs = Date.now() - startTime;
12398
+ const callResults = results ?? [];
12399
+ if (operation === "incomingCalls") {
12400
+ const calls = callResults;
12401
+ const formatted = calls.map((c) => `← ${formatCallHierarchyItem(c.from)} (${c.fromRanges.length} 处调用)`).join("\n");
12402
+ return {
12403
+ content: [{
12404
+ type: "text",
12405
+ text: `传入调用 — ${formatCallHierarchyItem(firstItem)}\n\n${formatted || "无传入调用"}`
12406
+ }],
12407
+ details: {
12408
+ operation,
12409
+ filePath: resolvedPath,
12410
+ resultCount: calls.length,
12411
+ elapsedMs
12412
+ }
12413
+ };
12414
+ }
12415
+ const calls = callResults;
12416
+ const formatted = calls.map((c) => `→ ${formatCallHierarchyItem(c.to)} (${c.fromRanges.length} 处调用)`).join("\n");
9953
12417
  return {
9954
12418
  content: [{
9955
12419
  type: "text",
9956
- text: `读取失败: ${error instanceof Error ? error.message : String(error)}`
12420
+ text: `传出调用 ${formatCallHierarchyItem(firstItem)}\n\n${formatted || "无传出调用"}`
9957
12421
  }],
9958
12422
  details: {
9959
- path: resolvedPath,
9960
- error: true
12423
+ operation,
12424
+ filePath: resolvedPath,
12425
+ resultCount: calls.length,
12426
+ elapsedMs
12427
+ }
12428
+ };
12429
+ }
12430
+ const methodParams = {};
12431
+ if (operation === "workspaceSymbol") methodParams.query = rawParams.query ?? "";
12432
+ else if (operation === "documentSymbol") methodParams.textDocument = { uri: `file://${resolvedPath}` };
12433
+ else if (operation === "findReferences") {
12434
+ methodParams.textDocument = { uri: `file://${resolvedPath}` };
12435
+ methodParams.position = {
12436
+ line: (line ?? 1) - 1,
12437
+ character: (character ?? 1) - 1
12438
+ };
12439
+ methodParams.context = { includeDeclaration: true };
12440
+ } else {
12441
+ methodParams.textDocument = { uri: `file://${resolvedPath}` };
12442
+ methodParams.position = {
12443
+ line: (line ?? 1) - 1,
12444
+ character: (character ?? 1) - 1
12445
+ };
12446
+ }
12447
+ const result = await lspManager.request(resolvedPath, lspMethod, methodParams);
12448
+ const elapsedMs = Date.now() - startTime;
12449
+ switch (operation) {
12450
+ case "goToDefinition":
12451
+ case "goToImplementation": {
12452
+ const locations = result ?? [];
12453
+ const formatted = locations.map(formatLocation).join("\n");
12454
+ return {
12455
+ content: [{
12456
+ type: "text",
12457
+ text: `${operation === "goToDefinition" ? "定义" : "实现"} (${locations.length} 处)\n\n${formatted || "无结果"}`
12458
+ }],
12459
+ details: {
12460
+ operation,
12461
+ filePath: resolvedPath,
12462
+ resultCount: locations.length,
12463
+ elapsedMs
12464
+ }
12465
+ };
12466
+ }
12467
+ case "findReferences": {
12468
+ const refs = result ?? [];
12469
+ const formatted = refs.map(formatLocation).join("\n");
12470
+ return {
12471
+ content: [{
12472
+ type: "text",
12473
+ text: `引用 (${refs.length} 处)\n\n${formatted || "无引用"}`
12474
+ }],
12475
+ details: {
12476
+ operation,
12477
+ filePath: resolvedPath,
12478
+ resultCount: refs.length,
12479
+ elapsedMs
12480
+ }
12481
+ };
12482
+ }
12483
+ case "hover": {
12484
+ const hover = result;
12485
+ if (!hover?.contents) return {
12486
+ content: [{
12487
+ type: "text",
12488
+ text: "无悬停信息"
12489
+ }],
12490
+ details: {
12491
+ operation,
12492
+ filePath: resolvedPath,
12493
+ resultCount: 0,
12494
+ elapsedMs
12495
+ }
12496
+ };
12497
+ return {
12498
+ content: [{
12499
+ type: "text",
12500
+ text: formatHoverContent(hover.contents)
12501
+ }],
12502
+ details: {
12503
+ operation,
12504
+ filePath: resolvedPath,
12505
+ resultCount: 1,
12506
+ elapsedMs
12507
+ }
12508
+ };
12509
+ }
12510
+ case "documentSymbol": {
12511
+ const symbols = result ?? [];
12512
+ if (symbols.length === 0) return {
12513
+ content: [{
12514
+ type: "text",
12515
+ text: "无文档符号"
12516
+ }],
12517
+ details: {
12518
+ operation,
12519
+ filePath: resolvedPath,
12520
+ resultCount: 0,
12521
+ elapsedMs
12522
+ }
12523
+ };
12524
+ const formatted = formatDocumentSymbol(symbols);
12525
+ return {
12526
+ content: [{
12527
+ type: "text",
12528
+ text: `文档符号 (${symbols.length} 个顶层)\n\n${formatted}`
12529
+ }],
12530
+ details: {
12531
+ operation,
12532
+ filePath: resolvedPath,
12533
+ resultCount: symbols.length,
12534
+ elapsedMs
12535
+ }
12536
+ };
12537
+ }
12538
+ case "workspaceSymbol": {
12539
+ const symbols = result ?? [];
12540
+ if (symbols.length === 0) return {
12541
+ content: [{
12542
+ type: "text",
12543
+ text: "未找到工作区符号"
12544
+ }],
12545
+ details: {
12546
+ operation,
12547
+ resultCount: 0,
12548
+ elapsedMs
12549
+ }
12550
+ };
12551
+ const formatted = formatWorkspaceSymbol(symbols);
12552
+ return {
12553
+ content: [{
12554
+ type: "text",
12555
+ text: `工作区符号 (${symbols.length} 个)\n\n${formatted}`
12556
+ }],
12557
+ details: {
12558
+ operation,
12559
+ resultCount: symbols.length,
12560
+ elapsedMs
12561
+ }
12562
+ };
12563
+ }
12564
+ case "prepareCallHierarchy": {
12565
+ const items = result ?? [];
12566
+ if (items.length === 0) return {
12567
+ content: [{
12568
+ type: "text",
12569
+ text: "未找到调用层次项"
12570
+ }],
12571
+ details: {
12572
+ operation,
12573
+ filePath: resolvedPath,
12574
+ resultCount: 0,
12575
+ elapsedMs
12576
+ }
12577
+ };
12578
+ const formatted = items.map(formatCallHierarchyItem).join("\n");
12579
+ return {
12580
+ content: [{
12581
+ type: "text",
12582
+ text: `调用层次 (${items.length} 项)\n\n${formatted}`
12583
+ }],
12584
+ details: {
12585
+ operation,
12586
+ filePath: resolvedPath,
12587
+ resultCount: items.length,
12588
+ elapsedMs
12589
+ }
12590
+ };
12591
+ }
12592
+ default: return {
12593
+ content: [{
12594
+ type: "text",
12595
+ text: JSON.stringify(result, null, 2)
12596
+ }],
12597
+ details: {
12598
+ operation,
12599
+ filePath: resolvedPath,
12600
+ elapsedMs
9961
12601
  }
9962
12602
  };
9963
12603
  }
12604
+ } catch (err) {
12605
+ const errorMsg = err instanceof Error ? err.message : String(err);
12606
+ return {
12607
+ content: [{
12608
+ type: "text",
12609
+ text: `LSP 操作失败: ${errorMsg}`
12610
+ }],
12611
+ details: {
12612
+ error: true,
12613
+ operation,
12614
+ filePath,
12615
+ message: errorMsg
12616
+ }
12617
+ };
9964
12618
  }
9965
12619
  }
9966
- ];
9967
- tools.push(createWriteFileTool());
9968
- tools.push(createEditFileTool());
9969
- tools.push(createGlobTool());
9970
- tools.push(createGrepTool());
9971
- tools.push(createExecTool());
9972
- tools.push(createProcessTool());
9973
- if (webConfig?.enabled !== false) {
9974
- tools.push(createWebFetchTool(webConfig));
9975
- tools.push(createWebSearchTool(webConfig));
12620
+ };
12621
+ }
12622
+
12623
+ //#endregion
12624
+ //#region src/config/types.ts
12625
+ /**
12626
+ * 将用户配置的 MCP 格式标准化为 McpServerConfig 数组
12627
+ *
12628
+ * 支持两种输入格式自动检测:
12629
+ * - `{ servers: [...] }` → 直接返回数组
12630
+ * - `{ "server-a": {...}, "server-b": {...} }` → 以 key 作为 name 转换为数组
12631
+ */
12632
+ function normalizeMcpConfig(raw) {
12633
+ if (raw.servers && Array.isArray(raw.servers) && raw.servers.length > 0) return raw.servers;
12634
+ const servers = [];
12635
+ for (const [key, value] of Object.entries(raw)) {
12636
+ if (key === "servers") continue;
12637
+ if (value === null || value === void 0 || typeof value !== "object") continue;
12638
+ if (Array.isArray(value)) continue;
12639
+ const config = value;
12640
+ if (typeof config.command !== "string") continue;
12641
+ const server = {
12642
+ name: key,
12643
+ transport: "stdio",
12644
+ command: config.command
12645
+ };
12646
+ if (Array.isArray(config.args)) server.args = config.args;
12647
+ if (config.env && typeof config.env === "object") server.env = config.env;
12648
+ if (typeof config.cwd === "string") server.cwd = config.cwd;
12649
+ if (typeof config.enabled === "boolean") server.enabled = config.enabled;
12650
+ if (typeof config.connectTimeoutMs === "number") server.connectTimeoutMs = config.connectTimeoutMs;
12651
+ servers.push(server);
9976
12652
  }
9977
- if (taskStore) tools.push(createTaskManageTool(taskStore));
9978
- tools.push(createMemoryTool());
9979
- if (skillConfig?.enabled !== false) tools.push(createSkillTool(skillConfig));
9980
- if (parentAgent && subAgentConfig?.enabled !== false && subAgentConfig) {
9981
- let orchestrator;
9982
- if (agentTeamConfig?.enabled) {
9983
- orchestrator = new AgentOrchestrator(agentTeamConfig, subAgentConfig, parentAgent, tools);
9984
- tools.push(createAgentTool(orchestrator));
12653
+ return servers;
12654
+ }
12655
+
12656
+ //#endregion
12657
+ //#region src/core/lsp/diagnostics.ts
12658
+ function createDiagnosticCollector() {
12659
+ const diagnosticsByFile = /* @__PURE__ */ new Map();
12660
+ function init(_manager) {}
12661
+ function getForFile(filePath) {
12662
+ const diags = diagnosticsByFile.get(filePath);
12663
+ if (!diags) return [];
12664
+ return [...diags];
12665
+ }
12666
+ function clearForFile(filePath) {
12667
+ diagnosticsByFile.delete(filePath);
12668
+ }
12669
+ function clear() {
12670
+ diagnosticsByFile.clear();
12671
+ }
12672
+ return {
12673
+ init,
12674
+ getForFile,
12675
+ clearForFile,
12676
+ clear
12677
+ };
12678
+ }
12679
+
12680
+ //#endregion
12681
+ //#region src/core/lsp/lsp-config.ts
12682
+ /** 内置 TypeScript/JavaScript LSP 服务器配置 */
12683
+ const BUILTIN_LSP_SERVERS = [{
12684
+ name: "typescript",
12685
+ command: "typescript-language-server",
12686
+ args: ["--stdio"],
12687
+ languageIds: [
12688
+ "typescript",
12689
+ "javascript",
12690
+ "typescriptreact",
12691
+ "javascriptreact"
12692
+ ],
12693
+ extensions: [
12694
+ ".ts",
12695
+ ".tsx",
12696
+ ".js",
12697
+ ".jsx",
12698
+ ".mjs",
12699
+ ".cjs",
12700
+ ".mts",
12701
+ ".cts"
12702
+ ],
12703
+ connectTimeoutMs: 3e4,
12704
+ requestTimeoutMs: 15e3
12705
+ }];
12706
+ /**
12707
+ * 解析并合并 LSP 配置
12708
+ *
12709
+ * 规则:
12710
+ * 1. 加载内置 server 配置
12711
+ * 2. 用户 servers 中同名项覆盖内置,不同名追加
12712
+ * 3. enabled=false 的 server 移除
12713
+ * 4. userConfig.enabled=false 全局禁用,返回空数组
12714
+ *
12715
+ * @param userConfig - 用户配置(可选)
12716
+ * @returns 生效的 server 配置列表
12717
+ */
12718
+ function resolveLspConfig(userConfig) {
12719
+ if (userConfig?.enabled === false) return [];
12720
+ const serverMap = /* @__PURE__ */ new Map();
12721
+ for (const builtin of BUILTIN_LSP_SERVERS) serverMap.set(builtin.name, { ...builtin });
12722
+ if (userConfig?.servers) for (const userServer of userConfig.servers) {
12723
+ if (userServer.enabled === false) {
12724
+ serverMap.delete(userServer.name);
12725
+ continue;
9985
12726
  }
9986
- const manager = new SubAgentManager(subAgentConfig, parentAgent, tools, orchestrator);
9987
- if (!agentTeamConfig?.enabled) tools.push(createSpawnSubAgentsTool(manager, subAgentConfig));
12727
+ const existing = serverMap.get(userServer.name);
12728
+ if (existing) serverMap.set(userServer.name, {
12729
+ ...existing,
12730
+ ...userServer
12731
+ });
12732
+ else serverMap.set(userServer.name, { ...userServer });
9988
12733
  }
9989
- if (cronScheduler) tools.push(createCronTool(cronScheduler));
9990
- for (const tool of tools) if (!tool.defaultRisk) tool.defaultRisk = TOOL_RISK_MAP[tool.id] ?? "medium";
9991
- return tools;
12734
+ return Array.from(serverMap.values()).filter((s) => s.enabled !== false);
9992
12735
  }
9993
12736
 
9994
12737
  //#endregion
9995
- //#region src/cli/repl/theme.ts
9996
- /** 根据颜色开关获取 chalk 实例 */
9997
- function makeColor(colorEnabled) {
9998
- return colorEnabled ? chalk : new Chalk({ level: 0 });
12738
+ //#region src/core/lsp/json-rpc.ts
12739
+ const HEADER_CONTENT_LENGTH = "Content-Length";
12740
+ const HEADER_CONTENT_TYPE = "Content-Type";
12741
+ const DEFAULT_CONTENT_TYPE = "application/vscode-jsonrpc; charset=utf-8";
12742
+ const CRLF = "\r\n";
12743
+ const DOUBLE_CRLF = "\r\n\r\n";
12744
+ /**
12745
+ * 从 Readable 流(子进程 stdout)读取 JSON-RPC 消息。
12746
+ *
12747
+ * 处理 TCP 流特性:
12748
+ * - 消息可能分多个 chunk 到达
12749
+ * - 一个 chunk 可能包含多个消息
12750
+ * - Content-Length 头可能跨 chunk 边界
12751
+ */
12752
+ function createMessageReader(onMessage, onError, _onClose, trace) {
12753
+ let buffer = "";
12754
+ let contentLength = -1;
12755
+ let headerEnd = -1;
12756
+ function parseHeaders(rawHeaders) {
12757
+ const headers = {};
12758
+ for (const line of rawHeaders.split(CRLF)) {
12759
+ const colon = line.indexOf(":");
12760
+ if (colon > 0) {
12761
+ const key = line.slice(0, colon).trim();
12762
+ headers[key] = line.slice(colon + 1).trim();
12763
+ }
12764
+ }
12765
+ return headers;
12766
+ }
12767
+ function processBuffer() {
12768
+ while (buffer.length > 0) {
12769
+ if (contentLength < 0) {
12770
+ headerEnd = buffer.indexOf(DOUBLE_CRLF);
12771
+ if (headerEnd < 0) return;
12772
+ const lengthStr = parseHeaders(buffer.slice(0, headerEnd))[HEADER_CONTENT_LENGTH];
12773
+ if (!lengthStr) {
12774
+ onError(/* @__PURE__ */ new Error(`Missing ${HEADER_CONTENT_LENGTH} header in LSP message`));
12775
+ buffer = buffer.slice(headerEnd + 4);
12776
+ continue;
12777
+ }
12778
+ contentLength = parseInt(lengthStr, 10);
12779
+ if (Number.isNaN(contentLength) || contentLength < 0) {
12780
+ onError(/* @__PURE__ */ new Error(`Invalid ${HEADER_CONTENT_LENGTH}: ${lengthStr}`));
12781
+ buffer = buffer.slice(headerEnd + 4);
12782
+ contentLength = -1;
12783
+ continue;
12784
+ }
12785
+ }
12786
+ const bodyStart = headerEnd + 4;
12787
+ const bodyEnd = bodyStart + contentLength;
12788
+ if (buffer.length < bodyEnd) return;
12789
+ const body = buffer.slice(bodyStart, bodyEnd);
12790
+ buffer = buffer.slice(bodyEnd);
12791
+ contentLength = -1;
12792
+ headerEnd = -1;
12793
+ if (body.length === 0) continue;
12794
+ try {
12795
+ trace?.(`LSP ← ${body.slice(0, 500)}${body.length > 500 ? "..." : ""}`);
12796
+ onMessage(JSON.parse(body));
12797
+ } catch (err) {
12798
+ onError(/* @__PURE__ */ new Error(`JSON parse error: ${err instanceof Error ? err.message : String(err)}`));
12799
+ }
12800
+ }
12801
+ }
12802
+ return {
12803
+ feed(chunk) {
12804
+ buffer += chunk.toString("utf-8");
12805
+ processBuffer();
12806
+ },
12807
+ reset() {
12808
+ buffer = "";
12809
+ contentLength = -1;
12810
+ headerEnd = -1;
12811
+ }
12812
+ };
9999
12813
  }
10000
12814
  /**
10001
- * 创建 zapmyco pi-tui 主题
10002
- */
10003
- function createTheme(colorEnabled) {
10004
- const c = makeColor(colorEnabled);
10005
- /** 基础 selectList 主题 */
10006
- const baseSelectListTheme = {
10007
- selectedPrefix: (text) => c.cyan(text),
10008
- selectedText: (text) => c.bold(c.cyan(text)),
10009
- description: (text) => c.gray(text),
10010
- scrollInfo: (text) => c.gray(text),
10011
- noMatch: (text) => c.gray(text)
12815
+ * Writable 流(子进程 stdin)写入 JSON-RPC 消息。
12816
+ * 自动添加 Content-Length 和 Content-Type 头。
12817
+ */
12818
+ function createMessageWriter(writable, trace) {
12819
+ const encoder = new TextEncoder();
12820
+ async function write(message) {
12821
+ const body = JSON.stringify(message);
12822
+ const bodyBytes = encoder.encode(body);
12823
+ const header = `${HEADER_CONTENT_LENGTH}: ${bodyBytes.length}${CRLF}${HEADER_CONTENT_TYPE}: ${DEFAULT_CONTENT_TYPE}${CRLF}${CRLF}`;
12824
+ const headerBytes = encoder.encode(header);
12825
+ const fullMessage = Buffer.concat([headerBytes, bodyBytes]);
12826
+ trace?.(`LSP → ${body.slice(0, 500)}${body.length > 500 ? "..." : ""}`);
12827
+ return new Promise((resolve, reject) => {
12828
+ if (!writable.writable) {
12829
+ reject(/* @__PURE__ */ new Error("Stream is not writable"));
12830
+ return;
12831
+ }
12832
+ writable.write(fullMessage, (err) => {
12833
+ if (err) reject(err);
12834
+ else resolve();
12835
+ });
12836
+ });
12837
+ }
12838
+ return { write };
12839
+ }
12840
+
12841
+ //#endregion
12842
+ //#region src/core/lsp/lsp-client.ts
12843
+ /**
12844
+ * LSP 客户端 — Layer 1
12845
+ *
12846
+ * 启动 LSP 语言服务器子进程,通过 stdio 进行 JSON-RPC 2.0 通信。
12847
+ *
12848
+ * @module core/lsp
12849
+ */
12850
+ function createLspClient(config) {
12851
+ const requestTimeoutMs = config.requestTimeoutMs ?? 3e4;
12852
+ let childProcess = null;
12853
+ let nextId = 1;
12854
+ let isStopping = false;
12855
+ let capabilities = null;
12856
+ const pendingRequests = /* @__PURE__ */ new Map();
12857
+ const notificationHandlers = /* @__PURE__ */ new Map();
12858
+ let messageWriter = null;
12859
+ function trace(dir, msg) {
12860
+ if (process.env.ZAPMYCO_LSP_TRACE) process.stderr.write(`[lsp-client] ${dir} ${msg}\n`);
12861
+ }
12862
+ function rejectAllPending(error) {
12863
+ for (const [, pending] of pendingRequests) {
12864
+ clearTimeout(pending.timer);
12865
+ pending.reject(error);
12866
+ }
12867
+ pendingRequests.clear();
12868
+ }
12869
+ function handleMessage(message) {
12870
+ if ("id" in message && !("method" in message)) {
12871
+ const response = message;
12872
+ const pending = pendingRequests.get(response.id);
12873
+ if (pending) {
12874
+ clearTimeout(pending.timer);
12875
+ pendingRequests.delete(response.id);
12876
+ if (response.error) pending.reject(new LspError(`LSP error ${response.error.code}: ${response.error.message}`, `LSP_ERROR_${response.error.code}`));
12877
+ else pending.resolve(response.result);
12878
+ }
12879
+ return;
12880
+ }
12881
+ if ("method" in message && !("id" in message)) {
12882
+ const notification = message;
12883
+ const handlers = notificationHandlers.get(notification.method);
12884
+ if (handlers) for (const handler of handlers) try {
12885
+ handler(notification.params);
12886
+ } catch {}
12887
+ }
12888
+ }
12889
+ function spawnProcess() {
12890
+ const child = spawn(config.command, config.args ?? [], {
12891
+ env: {
12892
+ ...process.env,
12893
+ ...config.env
12894
+ },
12895
+ stdio: [
12896
+ "pipe",
12897
+ "pipe",
12898
+ "pipe"
12899
+ ]
12900
+ });
12901
+ child.on("error", (err) => {
12902
+ rejectAllPending(new LspError(`LSP process error: ${err.message}`, "PROCESS_ERROR"));
12903
+ });
12904
+ child.on("exit", (code, signal) => {
12905
+ if (!isStopping) rejectAllPending(new LspError(`LSP process exited unexpectedly (${signal ? `signal ${signal}` : `exit code ${code}`})`, "PROCESS_EXIT"));
12906
+ });
12907
+ if (child.stderr) child.stderr.on("data", (chunk) => {
12908
+ if (process.env.ZAPMYCO_LSP_TRACE) process.stderr.write(`[lsp-stderr] ${chunk.toString("utf-8")}`);
12909
+ });
12910
+ return child;
12911
+ }
12912
+ function ensureStarted() {
12913
+ if (childProcess) return;
12914
+ childProcess = spawnProcess();
12915
+ const reader = createMessageReader(handleMessage, (err) => trace("←", `Reader error: ${err.message}`), () => {
12916
+ if (!isStopping) rejectAllPending(new LspError("LSP stdout closed", "STDOUT_CLOSED"));
12917
+ }, (msg) => trace("←", msg));
12918
+ childProcess.stdout.on("data", (chunk) => {
12919
+ reader.feed(chunk);
12920
+ });
12921
+ childProcess.stdout.on("end", () => {
12922
+ if (!isStopping) rejectAllPending(new LspError("LSP stdout closed unexpectedly", "STDOUT_CLOSED"));
12923
+ });
12924
+ messageWriter = createMessageWriter(childProcess.stdin, (msg) => trace("→", msg));
12925
+ }
12926
+ async function initialize(rootUri, initializationOptions) {
12927
+ ensureStarted();
12928
+ const result = await sendRequest("initialize", {
12929
+ processId: process.pid,
12930
+ rootUri,
12931
+ rootPath: rootUri.replace(/^file:\/\//, ""),
12932
+ workspaceFolders: [{
12933
+ uri: rootUri,
12934
+ name: "workspace"
12935
+ }],
12936
+ capabilities: {
12937
+ textDocument: {
12938
+ synchronization: { didSave: true },
12939
+ definition: { linkSupport: true },
12940
+ references: {},
12941
+ hover: { contentFormat: ["markdown", "plaintext"] },
12942
+ documentSymbol: { hierarchicalDocumentSymbolSupport: true },
12943
+ implementation: { linkSupport: true },
12944
+ callHierarchy: {}
12945
+ },
12946
+ workspace: { symbol: {} }
12947
+ },
12948
+ initializationOptions
12949
+ });
12950
+ capabilities = result.capabilities;
12951
+ await sendNotification("initialized", {});
12952
+ return result;
12953
+ }
12954
+ async function sendRequest(method, params) {
12955
+ if (!childProcess || !messageWriter) throw new LspError("LSP client not started", "NOT_STARTED");
12956
+ if (isStopping) throw new LspError("LSP client is shutting down", "SHUTTING_DOWN");
12957
+ const id = nextId++;
12958
+ const request = {
12959
+ jsonrpc: "2.0",
12960
+ id,
12961
+ method,
12962
+ params
12963
+ };
12964
+ return new Promise((resolve, reject) => {
12965
+ const timer = setTimeout(() => {
12966
+ pendingRequests.delete(id);
12967
+ reject(new LspError(`LSP request timeout: ${method}`, "REQUEST_TIMEOUT"));
12968
+ }, requestTimeoutMs);
12969
+ pendingRequests.set(id, {
12970
+ resolve,
12971
+ reject,
12972
+ timer,
12973
+ method
12974
+ });
12975
+ messageWriter.write(request).catch((err) => {
12976
+ clearTimeout(timer);
12977
+ pendingRequests.delete(id);
12978
+ reject(err);
12979
+ });
12980
+ });
12981
+ }
12982
+ async function sendNotification(method, params) {
12983
+ if (!childProcess || !messageWriter) throw new LspError("LSP client not started", "NOT_STARTED");
12984
+ if (isStopping) return;
12985
+ const notification = {
12986
+ jsonrpc: "2.0",
12987
+ method,
12988
+ params
12989
+ };
12990
+ await messageWriter.write(notification);
12991
+ }
12992
+ function onNotification(method, handler) {
12993
+ let handlers = notificationHandlers.get(method);
12994
+ if (!handlers) {
12995
+ handlers = /* @__PURE__ */ new Set();
12996
+ notificationHandlers.set(method, handlers);
12997
+ }
12998
+ handlers.add(handler);
12999
+ }
13000
+ function offNotification(method, handler) {
13001
+ const handlers = notificationHandlers.get(method);
13002
+ if (handlers) {
13003
+ handlers.delete(handler);
13004
+ if (handlers.size === 0) notificationHandlers.delete(method);
13005
+ }
13006
+ }
13007
+ function getCapabilities() {
13008
+ return capabilities;
13009
+ }
13010
+ function isAlive() {
13011
+ return childProcess !== null && !childProcess.killed && childProcess.exitCode === null;
13012
+ }
13013
+ async function shutdown() {
13014
+ if (!childProcess || isStopping) return;
13015
+ isStopping = true;
13016
+ try {
13017
+ await sendRequest("shutdown");
13018
+ } catch {}
13019
+ try {
13020
+ await sendNotification("exit");
13021
+ } catch {}
13022
+ await new Promise((resolve) => {
13023
+ const timer = setTimeout(() => {
13024
+ if (childProcess && !childProcess.killed) childProcess.kill("SIGKILL");
13025
+ resolve();
13026
+ }, 2e3);
13027
+ childProcess.once("exit", () => {
13028
+ clearTimeout(timer);
13029
+ resolve();
13030
+ });
13031
+ });
13032
+ childProcess = null;
13033
+ capabilities = null;
13034
+ messageWriter = null;
13035
+ pendingRequests.clear();
13036
+ notificationHandlers.clear();
13037
+ }
13038
+ return {
13039
+ sendRequest,
13040
+ sendNotification,
13041
+ onNotification,
13042
+ offNotification,
13043
+ initialize,
13044
+ getCapabilities,
13045
+ isAlive,
13046
+ shutdown
10012
13047
  };
13048
+ }
13049
+
13050
+ //#endregion
13051
+ //#region src/core/lsp/lsp-server-instance.ts
13052
+ /**
13053
+ * LSP 服务器实例 — Layer 2
13054
+ *
13055
+ * 封装单个 LSP 服务器的完整生命周期:
13056
+ * - 状态机(stopped → starting → running → stopping → stopped + error)
13057
+ * - initialize 握手(capabilities 提取)
13058
+ * - 文档同步跟踪(didOpen/didChange/didClose)
13059
+ * - 重试逻辑(指数退避,最大 maxRetries 次)
13060
+ * - 能力查询
13061
+ *
13062
+ * @module core/lsp
13063
+ */
13064
+ function createLspServerInstance(config) {
13065
+ const { serverId, languageIds, extensions, initializationOptions } = config;
13066
+ const maxRetries = config.maxRetries ?? 3;
13067
+ const retryBaseDelayMs = config.retryBaseDelayMs ?? 1e3;
13068
+ let client = null;
13069
+ let capabilities = null;
13070
+ let state = "stopped";
13071
+ let retryCount = 0;
13072
+ let requestCount = 0;
13073
+ let errorCount = 0;
13074
+ const openedDocuments = /* @__PURE__ */ new Map();
13075
+ function transition(newState) {
13076
+ state = newState;
13077
+ }
13078
+ function calculateDelay() {
13079
+ return Math.min(retryBaseDelayMs * 2 ** retryCount, 3e4);
13080
+ }
13081
+ async function ensureDocumentOpened(uri, languageId, text) {
13082
+ if (openedDocuments.get(uri)) return;
13083
+ await ensureRunning();
13084
+ try {
13085
+ await client.sendNotification("textDocument/didOpen", { textDocument: {
13086
+ uri,
13087
+ languageId,
13088
+ version: 1,
13089
+ text
13090
+ } });
13091
+ openedDocuments.set(uri, { version: 1 });
13092
+ } catch (err) {
13093
+ if (process.env.ZAPMYCO_LSP_TRACE) process.stderr.write(`[lsp-instance] didOpen failed: ${err instanceof Error ? err.message : String(err)}\n`);
13094
+ }
13095
+ }
13096
+ async function notifyDocumentChanged(uri, text) {
13097
+ const existing = openedDocuments.get(uri);
13098
+ if (!existing) return;
13099
+ existing.version++;
13100
+ const version = existing.version;
13101
+ if (state !== "running" || !client?.isAlive()) return;
13102
+ try {
13103
+ await client.sendNotification("textDocument/didChange", {
13104
+ textDocument: {
13105
+ uri,
13106
+ version
13107
+ },
13108
+ contentChanges: [{ text }]
13109
+ });
13110
+ } catch {}
13111
+ }
13112
+ async function notifyDocumentClosed(uri) {
13113
+ openedDocuments.delete(uri);
13114
+ if (state !== "running" || !client?.isAlive()) return;
13115
+ try {
13116
+ await client.sendNotification("textDocument/didClose", { textDocument: { uri } });
13117
+ } catch {}
13118
+ }
13119
+ async function ensureRunning() {
13120
+ if (state === "running" && client?.isAlive()) return;
13121
+ if (state === "stopped" || state === "error") await doInitialize();
13122
+ }
13123
+ async function doInitialize(rootUri) {
13124
+ if (state === "starting") return;
13125
+ transition("starting");
13126
+ try {
13127
+ if (client) try {
13128
+ await client.shutdown();
13129
+ } catch {}
13130
+ client = createLspClient(config.clientConfig);
13131
+ const uri = rootUri ?? `file://${process.cwd()}`;
13132
+ capabilities = (await client.initialize(uri, initializationOptions)).capabilities;
13133
+ retryCount = 0;
13134
+ transition("running");
13135
+ } catch (err) {
13136
+ errorCount++;
13137
+ const error = err instanceof Error ? err : new Error(String(err));
13138
+ if (retryCount < maxRetries) {
13139
+ retryCount++;
13140
+ const delay = calculateDelay();
13141
+ if (process.env.ZAPMYCO_LSP_TRACE) process.stderr.write(`[lsp-instance] ${serverId} init failed, retry ${retryCount}/${maxRetries} in ${delay}ms\n`);
13142
+ await new Promise((resolve) => setTimeout(resolve, delay));
13143
+ retryCount--;
13144
+ transition("error");
13145
+ return doInitialize(rootUri);
13146
+ }
13147
+ transition("error");
13148
+ throw new LspError(`Failed to initialize LSP server ${serverId}: ${error.message}`, "INIT_FAILED");
13149
+ }
13150
+ }
13151
+ async function initialize(rootUri) {
13152
+ await doInitialize(rootUri);
13153
+ }
13154
+ async function request(method, params) {
13155
+ await ensureRunning();
13156
+ requestCount++;
13157
+ try {
13158
+ return await client.sendRequest(method, params);
13159
+ } catch (err) {
13160
+ errorCount++;
13161
+ if (err instanceof LspError && err.code === "PROCESS_EXIT") transition("error");
13162
+ throw err;
13163
+ }
13164
+ }
13165
+ async function sendNotification(method, params) {
13166
+ if (state !== "running" || !client?.isAlive()) return;
13167
+ try {
13168
+ await client.sendNotification(method, params);
13169
+ } catch {}
13170
+ }
13171
+ function onNotification(method, handler) {
13172
+ if (client) client.onNotification(method, handler);
13173
+ }
13174
+ function supportsCapability(capability) {
13175
+ if (!capabilities) return false;
13176
+ return capabilities[capability] === true;
13177
+ }
13178
+ function getHealth() {
13179
+ return {
13180
+ state,
13181
+ requestCount,
13182
+ errorCount
13183
+ };
13184
+ }
13185
+ function getState() {
13186
+ return state;
13187
+ }
13188
+ function getServerId() {
13189
+ return serverId;
13190
+ }
13191
+ function getExtensions() {
13192
+ return extensions;
13193
+ }
13194
+ function getLanguageIds() {
13195
+ return languageIds;
13196
+ }
13197
+ async function shutdown() {
13198
+ if (state === "stopped" || state === "stopping") return;
13199
+ transition("stopping");
13200
+ for (const [uri] of openedDocuments) try {
13201
+ await client?.sendNotification("textDocument/didClose", { textDocument: { uri } });
13202
+ } catch {}
13203
+ openedDocuments.clear();
13204
+ if (client) try {
13205
+ await client.shutdown();
13206
+ } catch {}
13207
+ client = null;
13208
+ capabilities = null;
13209
+ transition("stopped");
13210
+ }
10013
13211
  return {
10014
- /** 主文本色 */
10015
- text: (s) => s,
10016
- /** 加粗 */
10017
- bold: (s) => c.bold(s),
10018
- /** 灰色/弱化文本 */
10019
- dim: (s) => c.gray(s),
10020
- /** 强调色 - 青色 */
10021
- accent: (s) => c.cyan(s),
10022
- /** 成功 - 绿色 */
10023
- success: (s) => c.green(s),
10024
- /** 错误 - 红色 */
10025
- error: (s) => c.red(s),
10026
- /** 警告 - 黄色 */
10027
- warning: (s) => c.yellow(s),
10028
- /** 边框色 - 灰色 */
10029
- border: (s) => c.gray(s),
10030
- /** Header 文本 */
10031
- heading: (s) => c.bold(s),
10032
- editorTheme: {
10033
- borderColor: (text) => c.gray(text),
10034
- selectList: baseSelectListTheme
10035
- },
10036
- selectListTheme: baseSelectListTheme
13212
+ initialize,
13213
+ shutdown,
13214
+ ensureDocumentOpened,
13215
+ notifyDocumentChanged,
13216
+ notifyDocumentClosed,
13217
+ request,
13218
+ sendNotification,
13219
+ onNotification,
13220
+ getHealth,
13221
+ supportsCapability,
13222
+ getState,
13223
+ getServerId,
13224
+ getExtensions,
13225
+ getLanguageIds
10037
13226
  };
10038
13227
  }
10039
13228
 
10040
13229
  //#endregion
10041
- //#region src/config/types.ts
13230
+ //#region src/core/lsp/lsp-server-manager.ts
10042
13231
  /**
10043
- * 将用户配置的 MCP 格式标准化为 McpServerConfig 数组
13232
+ * LSP 服务器管理器 Layer 3
10044
13233
  *
10045
- * 支持两种输入格式自动检测:
10046
- * - `{ servers: [...] }` → 直接返回数组
10047
- * - `{ "server-a": {...}, "server-b": {...} }` → 以 key 作为 name 转换为数组
13234
+ * 管理多个 LSP 服务器实例:
13235
+ * - 根据文件扩展名路由请求
13236
+ * - 协调文档同步(didOpen/didChange/didClose)
13237
+ * - 追踪 broken server
13238
+ * - 懒启动(首次请求时才启动 server)
13239
+ *
13240
+ * @module core/lsp
10048
13241
  */
10049
- function normalizeMcpConfig(raw) {
10050
- if (raw.servers && Array.isArray(raw.servers) && raw.servers.length > 0) return raw.servers;
13242
+ function createLspServerManager() {
13243
+ /** 扩展名 server 实例 */
13244
+ const extensionMap = /* @__PURE__ */ new Map();
13245
+ /** 所有 server 实例 */
10051
13246
  const servers = [];
10052
- for (const [key, value] of Object.entries(raw)) {
10053
- if (key === "servers") continue;
10054
- if (value === null || value === void 0 || typeof value !== "object") continue;
10055
- if (Array.isArray(value)) continue;
10056
- const config = value;
10057
- if (typeof config.command !== "string") continue;
10058
- const server = {
10059
- name: key,
10060
- transport: "stdio",
10061
- command: config.command
13247
+ /** 已打开文档追踪:uri → { serverId, languageId, version } */
13248
+ const openedFiles = /* @__PURE__ */ new Map();
13249
+ /** 永久失败的 server ID 集合 */
13250
+ const brokenServers = /* @__PURE__ */ new Set();
13251
+ /** 初始化状态 */
13252
+ let initialized = false;
13253
+ /** 工作区根路径 */
13254
+ let workspaceRoot = "";
13255
+ function findLanguageId(extension, server) {
13256
+ const languageIds = server.getLanguageIds();
13257
+ if (languageIds.length === 0) return void 0;
13258
+ return {
13259
+ ".ts": "typescript",
13260
+ ".tsx": "typescriptreact",
13261
+ ".js": "javascript",
13262
+ ".jsx": "javascriptreact",
13263
+ ".mjs": "javascript",
13264
+ ".cjs": "javascript",
13265
+ ".mts": "typescript",
13266
+ ".cts": "typescript"
13267
+ }[extension] ?? languageIds[0];
13268
+ }
13269
+ function getServerForFile(filePath) {
13270
+ const ext = filePath.slice(filePath.lastIndexOf(".")).toLowerCase();
13271
+ const server = extensionMap.get(ext);
13272
+ if (server && !brokenServers.has(server.getServerId())) return server;
13273
+ }
13274
+ function getLanguageForFile(filePath, server) {
13275
+ return findLanguageId(filePath.slice(filePath.lastIndexOf(".")).toLowerCase(), server) ?? "plaintext";
13276
+ }
13277
+ async function ensureFileOpened(filePath, server, content) {
13278
+ const uri = `file://${filePath}`;
13279
+ if (openedFiles.has(uri)) return;
13280
+ const languageId = getLanguageForFile(filePath, server);
13281
+ try {
13282
+ await server.ensureDocumentOpened(uri, languageId, content ?? "");
13283
+ openedFiles.set(uri, {
13284
+ serverId: server.getServerId(),
13285
+ languageId,
13286
+ version: 1
13287
+ });
13288
+ } catch (err) {
13289
+ if (process.env.ZAPMYCO_LSP_TRACE) process.stderr.write(`[lsp-manager] ensureFileOpened failed: ${err instanceof Error ? err.message : String(err)}\n`);
13290
+ }
13291
+ }
13292
+ async function init(serverConfigs, root) {
13293
+ workspaceRoot = root;
13294
+ extensionMap.clear();
13295
+ servers.length = 0;
13296
+ brokenServers.clear();
13297
+ for (const config of serverConfigs) {
13298
+ const extensions = config.extensions ?? [];
13299
+ const languageIds = config.languageIds ?? [];
13300
+ const instance = createLspServerInstance({
13301
+ serverId: config.name,
13302
+ clientConfig: {
13303
+ command: config.command,
13304
+ args: config.args ?? void 0,
13305
+ env: config.env ?? void 0,
13306
+ connectTimeoutMs: config.connectTimeoutMs ?? void 0,
13307
+ requestTimeoutMs: config.requestTimeoutMs ?? void 0
13308
+ },
13309
+ languageIds,
13310
+ extensions,
13311
+ initializationOptions: config.initializationOptions
13312
+ });
13313
+ servers.push(instance);
13314
+ for (const ext of extensions) {
13315
+ const normalized = ext.toLowerCase();
13316
+ if (!extensionMap.has(normalized)) extensionMap.set(normalized, instance);
13317
+ }
13318
+ }
13319
+ initialized = true;
13320
+ }
13321
+ async function onFileOpened(filePath, content) {
13322
+ if (!initialized) return;
13323
+ const server = getServerForFile(filePath);
13324
+ if (!server) return;
13325
+ if (server.getState() === "stopped" || server.getState() === "error") try {
13326
+ await server.initialize(`file://${workspaceRoot}`);
13327
+ } catch {
13328
+ brokenServers.add(server.getServerId());
13329
+ return;
13330
+ }
13331
+ await ensureFileOpened(filePath, server, content);
13332
+ }
13333
+ async function onFileChanged(filePath, content) {
13334
+ const uri = `file://${filePath}`;
13335
+ const opened = openedFiles.get(uri);
13336
+ if (!opened) {
13337
+ await onFileOpened(filePath, content);
13338
+ return;
13339
+ }
13340
+ const server = servers.find((s) => s.getServerId() === opened.serverId);
13341
+ if (server && server.getState() === "running") await server.notifyDocumentChanged(uri, content);
13342
+ }
13343
+ async function onFileClosed(filePath) {
13344
+ const uri = `file://${filePath}`;
13345
+ const opened = openedFiles.get(uri);
13346
+ if (opened) {
13347
+ const server = servers.find((s) => s.getServerId() === opened.serverId);
13348
+ if (server) await server.notifyDocumentClosed(uri);
13349
+ openedFiles.delete(uri);
13350
+ }
13351
+ }
13352
+ async function request(filePath, method, params) {
13353
+ if (!initialized) throw new LspError("LSP manager not initialized", "NOT_INITIALIZED");
13354
+ const server = getServerForFile(filePath);
13355
+ if (!server) throw new LspError(`No LSP server available for file: ${filePath}`, "NO_SERVER");
13356
+ if (server.getState() === "stopped" || server.getState() === "error") try {
13357
+ await server.initialize(`file://${workspaceRoot}`);
13358
+ } catch (err) {
13359
+ brokenServers.add(server.getServerId());
13360
+ throw new LspError(`Failed to start LSP server for ${filePath}: ${err instanceof Error ? err.message : String(err)}`, "SERVER_START_FAILED");
13361
+ }
13362
+ await ensureFileOpened(filePath, server);
13363
+ return server.request(method, params);
13364
+ }
13365
+ function getStatus() {
13366
+ return {
13367
+ serverCount: servers.length,
13368
+ runningCount: servers.filter((s) => s.getState() === "running").length,
13369
+ errorCount: brokenServers.size,
13370
+ servers: servers.map((s) => ({
13371
+ serverId: s.getServerId(),
13372
+ state: s.getState(),
13373
+ extensions: s.getExtensions()
13374
+ }))
10062
13375
  };
10063
- if (Array.isArray(config.args)) server.args = config.args;
10064
- if (config.env && typeof config.env === "object") server.env = config.env;
10065
- if (typeof config.cwd === "string") server.cwd = config.cwd;
10066
- if (typeof config.enabled === "boolean") server.enabled = config.enabled;
10067
- if (typeof config.connectTimeoutMs === "number") server.connectTimeoutMs = config.connectTimeoutMs;
10068
- servers.push(server);
10069
13376
  }
10070
- return servers;
13377
+ async function shutdown() {
13378
+ for (const [uri] of openedFiles) {
13379
+ const opened = openedFiles.get(uri);
13380
+ if (opened) {
13381
+ const server = servers.find((s) => s.getServerId() === opened.serverId);
13382
+ if (server) try {
13383
+ await server.notifyDocumentClosed(uri);
13384
+ } catch {}
13385
+ }
13386
+ }
13387
+ openedFiles.clear();
13388
+ for (const server of servers) try {
13389
+ await server.shutdown();
13390
+ } catch {}
13391
+ servers.length = 0;
13392
+ extensionMap.clear();
13393
+ brokenServers.clear();
13394
+ initialized = false;
13395
+ }
13396
+ return {
13397
+ init,
13398
+ onFileOpened,
13399
+ onFileChanged,
13400
+ onFileClosed,
13401
+ request,
13402
+ getServerForFile,
13403
+ getStatus,
13404
+ shutdown
13405
+ };
10071
13406
  }
10072
13407
 
10073
13408
  //#endregion
@@ -10080,7 +13415,7 @@ function normalizeMcpConfig(raw) {
10080
13415
  *
10081
13416
  * @module core/mcp/client
10082
13417
  */
10083
- const log$7 = logger.child("mcp:client");
13418
+ const log$6 = logger.child("mcp:client");
10084
13419
  /**
10085
13420
  * 连接到单个 MCP Server 并发现其工具
10086
13421
  *
@@ -10116,7 +13451,7 @@ async function connectMcpServer(config, signal) {
10116
13451
  await withTimeout(client.connect(transport), timeoutMs);
10117
13452
  const { tools: rawTools } = await withTimeout(client.listTools(), timeoutMs);
10118
13453
  const tools = rawTools ?? [];
10119
- log$7.debug(`MCP server "${serverName}" 已连接,发现 ${tools.length} 个工具`);
13454
+ log$6.debug(`MCP server "${serverName}" 已连接,发现 ${tools.length} 个工具`);
10120
13455
  return {
10121
13456
  client,
10122
13457
  transport,
@@ -10124,7 +13459,7 @@ async function connectMcpServer(config, signal) {
10124
13459
  serverName
10125
13460
  };
10126
13461
  } catch (error) {
10127
- log$7.warn(`MCP server "${serverName}" 连接失败: ${error instanceof Error ? error.message : String(error)}`);
13462
+ log$6.warn(`MCP server "${serverName}" 连接失败: ${error instanceof Error ? error.message : String(error)}`);
10128
13463
  return null;
10129
13464
  }
10130
13465
  }
@@ -10138,7 +13473,7 @@ async function closeMcpServer(conn) {
10138
13473
  try {
10139
13474
  await conn.client.close();
10140
13475
  } catch {}
10141
- log$7.debug(`MCP server "${conn.serverName}" 已断开`);
13476
+ log$6.debug(`MCP server "${conn.serverName}" 已断开`);
10142
13477
  }
10143
13478
  /**
10144
13479
  * 简单的超时工具:promise 在 ms 毫秒内未完成则 reject
@@ -10191,7 +13526,7 @@ function mcpToolToRegistration(mcpTool, serverName, client) {
10191
13526
 
10192
13527
  //#endregion
10193
13528
  //#region src/core/mcp/index.ts
10194
- const log$6 = logger.child("mcp");
13529
+ const log$5 = logger.child("mcp");
10195
13530
  /**
10196
13531
  * MCP 连接生命周期管理器
10197
13532
  *
@@ -10212,9 +13547,9 @@ var McpManager = class {
10212
13547
  async initialize(servers) {
10213
13548
  const enabledServers = servers.filter((s) => s.enabled !== false);
10214
13549
  if (enabledServers.length === 0) return [];
10215
- log$6.info(`正在连接 ${enabledServers.length} 个 MCP Server...`);
13550
+ log$5.info(`正在连接 ${enabledServers.length} 个 MCP Server...`);
10216
13551
  const connectedCount = (await Promise.allSettled(enabledServers.map((config) => this.connectAndCollect(config)))).filter((r) => r.status === "fulfilled" && r.value !== null).length;
10217
- log$6.info(`MCP: ${connectedCount}/${enabledServers.length} 个 Server 已连接,共 ${this.toolRegistrations.length} 个工具`);
13552
+ log$5.info(`MCP: ${connectedCount}/${enabledServers.length} 个 Server 已连接,共 ${this.toolRegistrations.length} 个工具`);
10218
13553
  return this.toolRegistrations;
10219
13554
  }
10220
13555
  /**
@@ -10222,7 +13557,7 @@ var McpManager = class {
10222
13557
  */
10223
13558
  async shutdown() {
10224
13559
  if (this.connections.length === 0) return;
10225
- log$6.info(`正在关闭 ${this.connections.length} 个 MCP 连接...`);
13560
+ log$5.info(`正在关闭 ${this.connections.length} 个 MCP 连接...`);
10226
13561
  await Promise.allSettled(this.connections.map((conn) => closeMcpServer(conn)));
10227
13562
  this.connections = [];
10228
13563
  this.toolRegistrations = [];
@@ -10455,7 +13790,7 @@ var TaskStore = class {
10455
13790
  *
10456
13791
  * @module security/approval-manager
10457
13792
  */
10458
- const log$5 = logger.child("approval-manager");
13793
+ const log$4 = logger.child("approval-manager");
10459
13794
  var ApprovalManager = class {
10460
13795
  provider = null;
10461
13796
  constructor(provider) {
@@ -10484,7 +13819,7 @@ var ApprovalManager = class {
10484
13819
  */
10485
13820
  async requestApproval(request) {
10486
13821
  if (!this.provider) {
10487
- log$5.warn("无审批提供者,自动拒绝审批请求", {
13822
+ log$4.warn("无审批提供者,自动拒绝审批请求", {
10488
13823
  toolId: request.toolId,
10489
13824
  risk: request.risk
10490
13825
  });
@@ -10507,7 +13842,7 @@ var ApprovalManager = class {
10507
13842
  toolId: request.toolId,
10508
13843
  scope: response.scope ?? "once"
10509
13844
  });
10510
- log$5.debug("审批通过", {
13845
+ log$4.debug("审批通过", {
10511
13846
  toolId: request.toolId,
10512
13847
  scope: response.scope
10513
13848
  });
@@ -10516,11 +13851,11 @@ var ApprovalManager = class {
10516
13851
  toolId: request.toolId,
10517
13852
  reason: "用户拒绝"
10518
13853
  });
10519
- log$5.debug("审批被拒绝", { toolId: request.toolId });
13854
+ log$4.debug("审批被拒绝", { toolId: request.toolId });
10520
13855
  }
10521
13856
  return response;
10522
13857
  } catch (err) {
10523
- log$5.error("审批提供者异常,自动拒绝", {
13858
+ log$4.error("审批提供者异常,自动拒绝", {
10524
13859
  toolId: request.toolId,
10525
13860
  error: err instanceof Error ? err.message : String(err)
10526
13861
  });
@@ -10543,7 +13878,7 @@ var ApprovalManager = class {
10543
13878
  *
10544
13879
  * @module security/audit-logger
10545
13880
  */
10546
- const log$4 = logger.child("audit-logger");
13881
+ const log$3 = logger.child("audit-logger");
10547
13882
  /** 默认审计日志目录 */
10548
13883
  const AUDIT_DIR = join(homedir(), ".zapmyco", "logs");
10549
13884
  /** 审计日志文件名 */
@@ -10583,7 +13918,7 @@ var AuditLogger = class {
10583
13918
  });
10584
13919
  };
10585
13920
  eventBus.on("security:violation", this.violationListener);
10586
- log$4.debug("审计日志初始化", {
13921
+ log$3.debug("审计日志初始化", {
10587
13922
  path: this.filePath,
10588
13923
  level: this.level
10589
13924
  });
@@ -10638,7 +13973,7 @@ var AuditLogger = class {
10638
13973
  if (!existsSync(this.filePath)) return [];
10639
13974
  return readFileSync(this.filePath, "utf-8").trim().split("\n").filter(Boolean).map((line) => JSON.parse(line));
10640
13975
  } catch {
10641
- log$4.warn("读取审计日志失败");
13976
+ log$3.warn("读取审计日志失败");
10642
13977
  return [];
10643
13978
  }
10644
13979
  }
@@ -10657,7 +13992,7 @@ var AuditLogger = class {
10657
13992
  this.violationListener = null;
10658
13993
  }
10659
13994
  this.flush();
10660
- log$4.debug("审计日志已关闭");
13995
+ log$3.debug("审计日志已关闭");
10661
13996
  }
10662
13997
  /**
10663
13998
  * 将缓冲写入文件
@@ -10670,7 +14005,7 @@ var AuditLogger = class {
10670
14005
  const lines = entries.map((e) => JSON.stringify(e)).join("\n") + "\n";
10671
14006
  appendFileSync(this.filePath, lines, "utf-8");
10672
14007
  } catch (err) {
10673
- log$4.error("审计日志写入失败", { error: err instanceof Error ? err.message : String(err) });
14008
+ log$3.error("审计日志写入失败", { error: err instanceof Error ? err.message : String(err) });
10674
14009
  if (this.buffer.length < 1e3) this.buffer = [...entries, ...this.buffer];
10675
14010
  }
10676
14011
  }
@@ -10789,7 +14124,7 @@ function matchParamPatterns(paramPatterns, actualParams) {
10789
14124
 
10790
14125
  //#endregion
10791
14126
  //#region src/security/permission-engine.ts
10792
- const log$3 = logger.child("permission-engine");
14127
+ const log$2 = logger.child("permission-engine");
10793
14128
  var PermissionEngine = class {
10794
14129
  config;
10795
14130
  store;
@@ -10814,7 +14149,7 @@ var PermissionEngine = class {
10814
14149
  };
10815
14150
  const toolInfo = this.resolveToolInfo(toolId);
10816
14151
  for (const rule of BUILTIN_DENY_RULES) if (matchToolPattern(rule.toolPattern, toolId)) {
10817
- log$3.debug("命中内置拒绝规则", {
14152
+ log$2.debug("命中内置拒绝规则", {
10818
14153
  toolId,
10819
14154
  rule: rule.toolPattern
10820
14155
  });
@@ -10827,7 +14162,7 @@ var PermissionEngine = class {
10827
14162
  };
10828
14163
  }
10829
14164
  for (const rule of this.config.denyRules) if (matchToolPattern(rule.toolPattern, toolId) && matchParamPatterns(rule.paramPatterns, params)) {
10830
- log$3.debug("命中用户拒绝规则", {
14165
+ log$2.debug("命中用户拒绝规则", {
10831
14166
  toolId,
10832
14167
  ruleId: rule.id ?? rule.toolPattern
10833
14168
  });
@@ -10856,7 +14191,7 @@ var PermissionEngine = class {
10856
14191
  };
10857
14192
  }
10858
14193
  for (const rule of this.config.allowRules) if (matchToolPattern(rule.toolPattern, toolId) && matchParamPatterns(rule.paramPatterns, params)) {
10859
- log$3.debug("命中用户允许规则", {
14194
+ log$2.debug("命中用户允许规则", {
10860
14195
  toolId,
10861
14196
  ruleId: rule.id ?? rule.toolPattern
10862
14197
  });
@@ -10869,7 +14204,7 @@ var PermissionEngine = class {
10869
14204
  };
10870
14205
  }
10871
14206
  if (this.store.hasSessionApproval(toolId)) {
10872
- log$3.debug("命中会话级审批", { toolId });
14207
+ log$2.debug("命中会话级审批", { toolId });
10873
14208
  return {
10874
14209
  action: "allow",
10875
14210
  risk: "low",
@@ -10877,7 +14212,7 @@ var PermissionEngine = class {
10877
14212
  };
10878
14213
  }
10879
14214
  if (this.store.hasPersistentApproval(toolId)) {
10880
- log$3.debug("命中持久化审批", { toolId });
14215
+ log$2.debug("命中持久化审批", { toolId });
10881
14216
  return {
10882
14217
  action: "allow",
10883
14218
  risk: "low",
@@ -10923,7 +14258,7 @@ var PermissionEngine = class {
10923
14258
  try {
10924
14259
  return toolInfo.checkPermission(params);
10925
14260
  } catch (err) {
10926
- log$3.warn("工具 checkPermission 执行异常", {
14261
+ log$2.warn("工具 checkPermission 执行异常", {
10927
14262
  toolId,
10928
14263
  error: err instanceof Error ? err.message : String(err)
10929
14264
  });
@@ -11011,7 +14346,7 @@ var PermissionEngine = class {
11011
14346
  *
11012
14347
  * @module security/permission-store
11013
14348
  */
11014
- const log$2 = logger.child("permission-store");
14349
+ const log$1 = logger.child("permission-store");
11015
14350
  function getStoragePath() {
11016
14351
  return join(homedir(), ".zapmyco", "permissions.json");
11017
14352
  }
@@ -11082,7 +14417,7 @@ var PermissionStore = class {
11082
14417
  const before = this.persistentApprovals.length;
11083
14418
  this.persistentApprovals = this.persistentApprovals.filter((a) => a.expiresAt === 0 || a.expiresAt > now);
11084
14419
  if (this.persistentApprovals.length < before) {
11085
- log$2.debug("清理过期审批条目", { removed: before - this.persistentApprovals.length });
14420
+ log$1.debug("清理过期审批条目", { removed: before - this.persistentApprovals.length });
11086
14421
  this.saveToFile();
11087
14422
  }
11088
14423
  }
@@ -11113,7 +14448,7 @@ var PermissionStore = class {
11113
14448
  const raw = readFileSync(getStoragePath(), "utf-8");
11114
14449
  const data = JSON.parse(raw);
11115
14450
  this.persistentApprovals = Array.isArray(data.approvals) ? data.approvals : [];
11116
- log$2.debug("已加载持久化审批记录", { count: this.persistentApprovals.length });
14451
+ log$1.debug("已加载持久化审批记录", { count: this.persistentApprovals.length });
11117
14452
  } catch {
11118
14453
  this.persistentApprovals = [];
11119
14454
  }
@@ -11129,7 +14464,7 @@ var PermissionStore = class {
11129
14464
  const data = { approvals: this.persistentApprovals };
11130
14465
  writeFileSync(getStoragePath(), JSON.stringify(data, null, 2), "utf-8");
11131
14466
  } catch (err) {
11132
- log$2.warn("保存权限持久化文件失败", { error: err instanceof Error ? err.message : String(err) });
14467
+ log$1.warn("保存权限持久化文件失败", { error: err instanceof Error ? err.message : String(err) });
11133
14468
  }
11134
14469
  }
11135
14470
  };
@@ -11427,155 +14762,6 @@ function threatSeverity(level) {
11427
14762
  }
11428
14763
  }
11429
14764
 
11430
- //#endregion
11431
- //#region src/security/tool-guard.ts
11432
- const log$1 = logger.child("tool-guard");
11433
- /**
11434
- * 安全阻止错误
11435
- *
11436
- * 当工具调用被权限引擎拒绝时抛出。
11437
- * 调用方(agent-adapter / session)应捕获此错误
11438
- * 并转换为 LLM 友好的错误反馈。
11439
- */
11440
- var SecurityBlockedError = class extends Error {
11441
- toolId;
11442
- risk;
11443
- reason;
11444
- constructor(message, toolId, risk, reason) {
11445
- super(message);
11446
- this.name = "SecurityBlockedError";
11447
- this.toolId = toolId;
11448
- this.risk = risk;
11449
- this.reason = reason;
11450
- }
11451
- };
11452
- var ToolGuard = class {
11453
- engine;
11454
- approvalManager;
11455
- store;
11456
- sessionId;
11457
- auditLogger;
11458
- constructor(engine, approvalManager, store, sessionId, auditLogger) {
11459
- this.engine = engine;
11460
- this.approvalManager = approvalManager;
11461
- this.store = store;
11462
- this.sessionId = sessionId ?? `session-${Date.now()}`;
11463
- this.auditLogger = auditLogger;
11464
- }
11465
- /**
11466
- * 包装单个 ToolRegistration
11467
- *
11468
- * 返回新 ToolRegistration,原对象不变。
11469
- * execute 被替换为带安全检查的版本。
11470
- */
11471
- wrap(registration) {
11472
- const originalExecute = registration.execute;
11473
- const toolId = registration.id;
11474
- const toolLabel = registration.label;
11475
- const guardedExecute = async (toolCallId, params, signal, onUpdate) => {
11476
- const decision = this.engine.evaluate(toolId, params);
11477
- if (decision.action === "deny") {
11478
- const reason = decision.reason ?? `工具 ${toolId} 已被安全策略阻止`;
11479
- log$1.warn("工具调用被阻止", {
11480
- toolId,
11481
- risk: decision.risk,
11482
- reason
11483
- });
11484
- eventBus.emit("security:blocked", {
11485
- toolId,
11486
- risk: decision.risk,
11487
- reason,
11488
- params
11489
- });
11490
- this.auditLogger?.log({
11491
- action: "BLOCK",
11492
- toolId,
11493
- risk: decision.risk,
11494
- reason,
11495
- params,
11496
- ...decision.matchedRule ? { matchedRule: decision.matchedRule } : {}
11497
- });
11498
- throw new SecurityBlockedError(reason, toolId, decision.risk, reason);
11499
- }
11500
- if (decision.action === "ask") {
11501
- this.auditLogger?.log({
11502
- action: "APPROVAL_REQUESTED",
11503
- toolId,
11504
- risk: decision.risk,
11505
- params,
11506
- ...decision.reason ? { reason: decision.reason } : {}
11507
- });
11508
- const approvalResponse = await this.approvalManager.requestApproval({
11509
- toolId,
11510
- toolLabel,
11511
- params,
11512
- risk: decision.risk,
11513
- reason: decision.reason ?? `工具 ${toolId} 需要审批`,
11514
- sessionId: this.sessionId
11515
- });
11516
- if (!approvalResponse.approved) {
11517
- const reason = `用户拒绝了工具 ${toolId} 的执行请求`;
11518
- log$1.info("用户拒绝工具执行", { toolId });
11519
- this.auditLogger?.log({
11520
- action: "APPROVAL_DENIED",
11521
- toolId,
11522
- risk: decision.risk,
11523
- reason
11524
- });
11525
- throw new SecurityBlockedError(reason, toolId, decision.risk, reason);
11526
- }
11527
- if (approvalResponse.scope === "session") this.store.addSessionApproval(toolId);
11528
- else if (approvalResponse.scope === "always") this.store.addPersistentApproval(toolId);
11529
- this.auditLogger?.log({
11530
- action: "APPROVAL_GRANTED",
11531
- toolId,
11532
- risk: decision.risk,
11533
- ...approvalResponse.scope ? { scope: approvalResponse.scope } : {},
11534
- ...decision.reason ? { reason: decision.reason } : {}
11535
- });
11536
- log$1.debug("审批通过,执行工具", {
11537
- toolId,
11538
- scope: approvalResponse.scope
11539
- });
11540
- } else this.auditLogger?.log({
11541
- action: "ALLOW",
11542
- toolId,
11543
- risk: decision.risk,
11544
- params,
11545
- ...decision.matchedRule ? { matchedRule: decision.matchedRule } : {}
11546
- });
11547
- return originalExecute(toolCallId, params, signal, onUpdate);
11548
- };
11549
- return {
11550
- ...registration,
11551
- execute: guardedExecute
11552
- };
11553
- }
11554
- /**
11555
- * 批量包装所有工具
11556
- */
11557
- wrapAll(registrations) {
11558
- return registrations.map((reg) => this.wrap(reg));
11559
- }
11560
- };
11561
- /**
11562
- * 从 ToolRegistration 数组构建 ToolInfoResolver
11563
- *
11564
- * 供 PermissionEngine 使用,将 toolId 映射到其安全信息。
11565
- */
11566
- function createToolInfoResolver(registrations) {
11567
- const map = /* @__PURE__ */ new Map();
11568
- for (const reg of registrations) map.set(reg.id, reg);
11569
- return (toolId) => {
11570
- const reg = map.get(toolId);
11571
- if (!reg) return void 0;
11572
- return {
11573
- checkPermission: reg.checkPermission,
11574
- defaultRisk: reg.defaultRisk
11575
- };
11576
- };
11577
- }
11578
-
11579
14765
  //#endregion
11580
14766
  //#region src/cli/repl/session.ts
11581
14767
  /**
@@ -11645,6 +14831,7 @@ const DEFAULT_CONTINUATION_PROMPT = "... ";
11645
14831
  * REPL 会话实现
11646
14832
  */
11647
14833
  var ReplSession = class {
14834
+ config;
11648
14835
  tui;
11649
14836
  editor;
11650
14837
  outputArea;
@@ -11659,6 +14846,10 @@ var ReplSession = class {
11659
14846
  agent;
11660
14847
  /** MCP 连接管理器(在 registerBuiltinTools 中异步初始化) */
11661
14848
  mcpManager = null;
14849
+ /** LSP 服务器管理器(在 registerBuiltinTools 中异步初始化) */
14850
+ lspManager = null;
14851
+ /** LSP 诊断收集器 */
14852
+ diagnosticCollector = null;
11662
14853
  /** 当前正在执行的 taskId(用于取消操作) */
11663
14854
  currentTaskId = null;
11664
14855
  /** 多轮对话上下文(兼容保留,Agent 内部也维护历史) */
@@ -11667,6 +14858,8 @@ var ReplSession = class {
11667
14858
  cronScheduler = null;
11668
14859
  /** 任务管理器(会话级持久化) */
11669
14860
  taskStore;
14861
+ /** Worktree 隔离管理器 */
14862
+ worktreeManager;
11670
14863
  /** 安全框架组件 */
11671
14864
  permissionStore;
11672
14865
  permissionEngine;
@@ -11675,6 +14868,8 @@ var ReplSession = class {
11675
14868
  auditLogger;
11676
14869
  secretRedactor;
11677
14870
  skillGuard;
14871
+ /** 交互式提问管理器 */
14872
+ questionManager;
11678
14873
  stats = {
11679
14874
  totalRequests: 0,
11680
14875
  successCount: 0,
@@ -11718,6 +14913,7 @@ var ReplSession = class {
11718
14913
  this.taskStore.load();
11719
14914
  this.cronScheduler = new CronScheduler(getCronStore(), { isIdle: () => this._state === "idle" });
11720
14915
  this.cronScheduler.start();
14916
+ this.initWorktreeManager();
11721
14917
  const memoryStore = getMemoryStore();
11722
14918
  memoryStore.freezeSnapshot().then(() => {
11723
14919
  this.agent.memorySnapshot = memoryStore.getSnapshot();
@@ -11727,6 +14923,8 @@ var ReplSession = class {
11727
14923
  });
11728
14924
  if (this.config.skill?.enabled !== false) this.initSkills();
11729
14925
  this.initSecurity();
14926
+ this.questionManager = getQuestionManager();
14927
+ this.questionManager.setProvider(createTuiQuestionProvider(this.tui));
11730
14928
  this.registerBuiltinCommands();
11731
14929
  this.registerBuiltinTools();
11732
14930
  this.setupEditorHandlers();
@@ -11754,6 +14952,7 @@ var ReplSession = class {
11754
14952
  this.updateStatsState();
11755
14953
  log.info("REPL 关闭", { reason: reason ?? "未知" });
11756
14954
  this.cancelCurrentTask();
14955
+ if (this.questionManager) this.questionManager.rejectAll(/* @__PURE__ */ new Error("会话已关闭"));
11757
14956
  this.editor.setExecuting(false);
11758
14957
  eventBus.emit("system:shutdown", { reason });
11759
14958
  if (this.cronScheduler) {
@@ -11765,6 +14964,14 @@ var ReplSession = class {
11765
14964
  await this.mcpManager.shutdown();
11766
14965
  this.mcpManager = null;
11767
14966
  }
14967
+ if (this.lspManager) {
14968
+ await this.lspManager.shutdown();
14969
+ this.lspManager = null;
14970
+ }
14971
+ if (this.diagnosticCollector) {
14972
+ this.diagnosticCollector.clear();
14973
+ this.diagnosticCollector = null;
14974
+ }
11768
14975
  this.tui.stop();
11769
14976
  process.exit(0);
11770
14977
  }
@@ -12201,6 +15408,28 @@ var ReplSession = class {
12201
15408
  /**
12202
15409
  * 初始化安全框架
12203
15410
  *
15411
+ /**
15412
+ * 初始化 WorktreeManager
15413
+ *
15414
+ * 创建 git worktree 隔离管理器,注册为全局实例,
15415
+ * 并启动过期 worktree 清理。
15416
+ */
15417
+ initWorktreeManager() {
15418
+ const rawConfig = this.config.worktree ?? {};
15419
+ const worktreeConfig = {
15420
+ enabled: true,
15421
+ autoCleanNoChanges: true,
15422
+ expireAfterMs: 1440 * 60 * 1e3,
15423
+ baseDir: join(homedir(), ".zapmyco", "worktrees"),
15424
+ ...rawConfig
15425
+ };
15426
+ this.worktreeManager = new WorktreeManager(worktreeConfig);
15427
+ setWorktreeManager(this.worktreeManager);
15428
+ this.worktreeManager.cleanExpired().catch((err) => {
15429
+ log.warn("过期 worktree 清理失败", { error: err instanceof Error ? err.message : String(err) });
15430
+ });
15431
+ }
15432
+ /**
12204
15433
  * 创建 PermissionEngine → ApprovalManager → ToolGuard 管道。
12205
15434
  * 必须在 registerBuiltinTools() 之前调用。
12206
15435
  */
@@ -12318,7 +15547,7 @@ var ReplSession = class {
12318
15547
  * MCP 工具在连接完成后自动追加。
12319
15548
  */
12320
15549
  registerBuiltinTools() {
12321
- const rawTools = createReplBuiltinTools(this.config.web, this.taskStore, this.config.skill, this.agent, this.config.subAgent, this.cronScheduler ?? void 0, this.config.agentTeam);
15550
+ 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);
12322
15551
  this.permissionEngine.setToolInfoResolver(createToolInfoResolver(rawTools));
12323
15552
  const guardedTools = this.toolGuard.wrapAll(rawTools);
12324
15553
  this.agent.registerTools(guardedTools);
@@ -12328,6 +15557,29 @@ var ReplSession = class {
12328
15557
  }).catch((err) => {
12329
15558
  log.error("MCP 初始化失败", { error: err instanceof Error ? err.message : String(err) });
12330
15559
  });
15560
+ if (this.config.lsp?.enabled !== false) {
15561
+ const lspServers = resolveLspConfig(this.config.lsp);
15562
+ if (lspServers.length > 0) {
15563
+ this.lspManager = createLspServerManager();
15564
+ this.lspManager.init(lspServers, process.cwd()).catch((err) => {
15565
+ log.error("LSP 初始化失败", { error: err instanceof Error ? err.message : String(err) });
15566
+ });
15567
+ rawTools.push(createLspTool(this.lspManager));
15568
+ this.diagnosticCollector = createDiagnosticCollector();
15569
+ this.diagnosticCollector.init(this.lspManager);
15570
+ const readFileTool = rawTools.find((t) => t.id === "ReadFile");
15571
+ if (readFileTool && this.lspManager) {
15572
+ const lspManagerRef = this.lspManager;
15573
+ const originalExecute = readFileTool.execute;
15574
+ readFileTool.execute = async (toolCallId, params, signal, onUpdate) => {
15575
+ const result = await originalExecute(toolCallId, params, signal, onUpdate);
15576
+ const filePath = params?.file_path;
15577
+ if (filePath && typeof filePath === "string" && !result.details?.error) lspManagerRef.onFileOpened(filePath, "").catch(() => {});
15578
+ return result;
15579
+ };
15580
+ }
15581
+ }
15582
+ }
12331
15583
  }
12332
15584
  /**
12333
15585
  * 初始化 Skill 系统