zencode-cli 0.2.1 → 0.2.2

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.
@@ -9,129 +9,16 @@ var __export = (target, all) => {
9
9
  __defProp(target, name, { get: all[name], enumerable: true });
10
10
  };
11
11
 
12
- // src/config/defaults.ts
13
- var DEFAULT_CONFIG;
14
- var init_defaults = __esm({
15
- "src/config/defaults.ts"() {
16
- "use strict";
17
- DEFAULT_CONFIG = {
18
- model: "deepseek-chat",
19
- api_key: "",
20
- base_url: "https://api.deepseek.com/v1",
21
- temperature: 0.7,
22
- max_tokens: 8192,
23
- agent_mode: "dual",
24
- collaboration: "delegated",
25
- dual_agent: {},
26
- features: {
27
- git: "auto",
28
- mcp: "off",
29
- planning_layer: "on",
30
- parallel_agents: "on",
31
- todo: "on"
32
- },
33
- permissions: {
34
- auto_approve: ["read-file", "glob", "grep", "spawn-agents", "todo", "memo"],
35
- require_approval: ["write-file", "edit-file", "bash", "git"]
36
- },
37
- mcp_servers: [],
38
- prompts: [],
39
- max_tool_output: 3e4
40
- };
41
- }
42
- });
43
-
44
- // src/config/loader.ts
45
- import * as fs from "fs";
46
- import * as path from "path";
47
- import * as os from "os";
48
- import { parse as parseYaml } from "yaml";
49
- function deepMerge(target, source) {
50
- const result = { ...target };
51
- for (const key of Object.keys(source)) {
52
- const sourceVal = source[key];
53
- const targetVal = target[key];
54
- if (sourceVal !== void 0 && sourceVal !== null && typeof sourceVal === "object" && !Array.isArray(sourceVal) && typeof targetVal === "object" && !Array.isArray(targetVal) && targetVal !== null) {
55
- result[key] = deepMerge(targetVal, sourceVal);
56
- } else if (sourceVal !== void 0) {
57
- result[key] = sourceVal;
58
- }
59
- }
60
- return result;
61
- }
62
- function loadYamlFile(filePath) {
63
- try {
64
- const content = fs.readFileSync(filePath, "utf-8");
65
- return parseYaml(content) || {};
66
- } catch {
67
- return {};
68
- }
69
- }
70
- function loadEnvConfig() {
71
- const config = {};
72
- if (process.env["ZENCODE_API_KEY"]) {
73
- config.api_key = process.env["ZENCODE_API_KEY"];
74
- }
75
- if (process.env["ZENCODE_MODEL"]) {
76
- config.model = process.env["ZENCODE_MODEL"];
77
- }
78
- if (process.env["ZENCODE_BASE_URL"]) {
79
- config.base_url = process.env["ZENCODE_BASE_URL"];
80
- }
81
- if (process.env["ZENCODE_MODE"]) {
82
- const mode = process.env["ZENCODE_MODE"];
83
- if (mode === "dual" || mode === "single") {
84
- config.agent_mode = mode;
85
- }
86
- }
87
- return config;
88
- }
89
- function loadCliConfig(opts) {
90
- const config = {};
91
- if (opts.model) config.model = opts.model;
92
- if (opts.apiKey) config.api_key = opts.apiKey;
93
- if (opts.baseUrl) config.base_url = opts.baseUrl;
94
- if (opts.single) config.agent_mode = "single";
95
- if (opts.dual) config.agent_mode = "dual";
96
- if (opts.mode) {
97
- const m = opts.mode;
98
- if (m === "delegated" || m === "autonomous" || m === "controlled") {
99
- config.collaboration = m;
100
- }
101
- }
102
- return config;
103
- }
104
- function loadConfig(cliOpts = {}) {
105
- const globalConfigPath = path.join(os.homedir(), ".zencode", "config.yaml");
106
- const projectDirConfigPath = path.resolve(".zencode", "config.yaml");
107
- const projectFileConfigPath = path.resolve(".zencode.yaml");
108
- let config = { ...DEFAULT_CONFIG };
109
- config = deepMerge(config, loadYamlFile(globalConfigPath));
110
- config = deepMerge(config, loadYamlFile(projectDirConfigPath));
111
- config = deepMerge(config, loadYamlFile(projectFileConfigPath));
112
- config = deepMerge(config, loadEnvConfig());
113
- config = deepMerge(config, loadCliConfig(cliOpts));
114
- return config;
115
- }
116
- function resolveModelConfig(config, role) {
117
- const roleConfig = config.dual_agent[role] || {};
118
- return {
119
- model: roleConfig.model || config.model,
120
- api_key: roleConfig.api_key || config.api_key,
121
- base_url: roleConfig.base_url || config.base_url,
122
- temperature: roleConfig.temperature ?? config.temperature,
123
- max_tokens: roleConfig.max_tokens ?? config.max_tokens
124
- };
125
- }
126
- var init_loader = __esm({
127
- "src/config/loader.ts"() {
128
- "use strict";
129
- init_defaults();
130
- }
131
- });
132
-
133
12
  // src/llm/client.ts
134
13
  import OpenAI from "openai";
14
+ function isAbortError(error) {
15
+ if (!error) return false;
16
+ const err = error;
17
+ const name = err.name || err.cause?.name || "";
18
+ const code = err.code || err.cause?.code || "";
19
+ const message = err.message || "";
20
+ return name === "AbortError" || name === "APIUserAbortError" || code === "ABORT_ERR" || /abort|aborted|cancel|cancelled|canceled|interrupted/i.test(message);
21
+ }
135
22
  function createLLMClient(options) {
136
23
  return new LLMClient(options);
137
24
  }
@@ -144,6 +31,7 @@ var init_client = __esm({
144
31
  model;
145
32
  temperature;
146
33
  maxTokens;
34
+ activeAbortController = null;
147
35
  constructor(options) {
148
36
  this.client = new OpenAI({
149
37
  apiKey: options.apiKey,
@@ -168,15 +56,34 @@ var init_client = __esm({
168
56
  params.tools = tools;
169
57
  params.tool_choice = "auto";
170
58
  }
59
+ const abortController = new AbortController();
60
+ this.activeAbortController = abortController;
171
61
  try {
172
- const stream = await this.client.chat.completions.create(params);
62
+ const stream = await this.client.chat.completions.create(params, {
63
+ signal: abortController.signal
64
+ });
173
65
  let contentParts = [];
66
+ let reasoningParts = [];
67
+ let reasoningStarted = false;
68
+ let reasoningEnded = false;
174
69
  const toolCallMap = /* @__PURE__ */ new Map();
175
70
  for await (const chunk of stream) {
176
71
  const choice = chunk.choices[0];
177
72
  if (!choice) continue;
178
73
  const delta = choice.delta;
74
+ if (delta.reasoning_content) {
75
+ reasoningParts.push(delta.reasoning_content);
76
+ if (!reasoningStarted) {
77
+ reasoningStarted = true;
78
+ callbacks.onContent?.("<think>");
79
+ }
80
+ callbacks.onContent?.(delta.reasoning_content);
81
+ }
179
82
  if (delta.content) {
83
+ if (reasoningStarted && !reasoningEnded) {
84
+ reasoningEnded = true;
85
+ callbacks.onContent?.("</think>");
86
+ }
180
87
  contentParts.push(delta.content);
181
88
  callbacks.onContent?.(delta.content);
182
89
  }
@@ -204,6 +111,10 @@ var init_client = __esm({
204
111
  }
205
112
  }
206
113
  }
114
+ if (reasoningStarted && !reasoningEnded) {
115
+ reasoningEnded = true;
116
+ callbacks.onContent?.("</think>");
117
+ }
207
118
  const toolCalls = [];
208
119
  for (const [, tc] of [...toolCallMap.entries()].sort(([a], [b]) => a - b)) {
209
120
  const toolCall = {
@@ -218,10 +129,14 @@ var init_client = __esm({
218
129
  callbacks.onToolCall?.(toolCall);
219
130
  }
220
131
  const fullContent = contentParts.join("");
132
+ const fullReasoning = reasoningParts.join("");
221
133
  const assistantMessage = {
222
134
  role: "assistant",
223
135
  content: fullContent || null
224
136
  };
137
+ if (fullReasoning) {
138
+ assistantMessage.reasoning_content = fullReasoning;
139
+ }
225
140
  if (toolCalls.length > 0) {
226
141
  assistantMessage.tool_calls = toolCalls;
227
142
  }
@@ -229,8 +144,14 @@ var init_client = __esm({
229
144
  return assistantMessage;
230
145
  } catch (error) {
231
146
  const err = error instanceof Error ? error : new Error(String(error));
232
- callbacks.onError?.(err);
147
+ if (!isAbortError(err)) {
148
+ callbacks.onError?.(err);
149
+ }
233
150
  throw err;
151
+ } finally {
152
+ if (this.activeAbortController === abortController) {
153
+ this.activeAbortController = null;
154
+ }
234
155
  }
235
156
  }
236
157
  /**
@@ -258,6 +179,10 @@ var init_client = __esm({
258
179
  role: "assistant",
259
180
  content: msg.content
260
181
  };
182
+ const reasoning = msg.reasoning_content;
183
+ if (reasoning && typeof reasoning === "string") {
184
+ result.reasoning_content = reasoning;
185
+ }
261
186
  if (msg.tool_calls && msg.tool_calls.length > 0) {
262
187
  result.tool_calls = msg.tool_calls.map((tc) => ({
263
188
  id: tc.id,
@@ -273,6 +198,12 @@ var init_client = __esm({
273
198
  get modelName() {
274
199
  return this.model;
275
200
  }
201
+ abortActiveStream() {
202
+ if (this.activeAbortController) {
203
+ this.activeAbortController.abort();
204
+ this.activeAbortController = null;
205
+ }
206
+ }
276
207
  };
277
208
  }
278
209
  });
@@ -542,14 +473,24 @@ var init_edit_file = __esm({
542
473
 
543
474
  // src/tools/bash.ts
544
475
  import { exec } from "child_process";
545
- var DEFAULT_TIMEOUT, bashTool;
476
+ function decodeBuffer(buf) {
477
+ if (!IS_WIN) return buf.toString("utf-8");
478
+ try {
479
+ const decoder = new TextDecoder("gbk");
480
+ return decoder.decode(buf);
481
+ } catch {
482
+ return buf.toString("utf-8");
483
+ }
484
+ }
485
+ var DEFAULT_TIMEOUT, IS_WIN, bashTool;
546
486
  var init_bash = __esm({
547
487
  "src/tools/bash.ts"() {
548
488
  "use strict";
549
489
  DEFAULT_TIMEOUT = 12e4;
490
+ IS_WIN = process.platform === "win32";
550
491
  bashTool = {
551
492
  name: "bash",
552
- description: "\u6267\u884C shell \u547D\u4EE4\u3002\u7528\u4E8E\u8FD0\u884C\u6784\u5EFA\u3001\u6D4B\u8BD5\u3001git \u64CD\u4F5C\u7B49\u7CFB\u7EDF\u547D\u4EE4\u3002\u4E0D\u8981\u7528 bash \u505A\u6587\u4EF6\u8BFB\u5199\uFF08\u7528 read-file/edit-file/write-file\uFF09\u6216\u641C\u7D22\uFF08\u7528 glob/grep\uFF09\u3002",
493
+ description: IS_WIN ? "\u6267\u884C\u7CFB\u7EDF\u547D\u4EE4\uFF08shell: cmd.exe\uFF09\u3002\u7528\u4E8E\u8FD0\u884C\u6784\u5EFA\u3001\u6D4B\u8BD5\u3001git \u7B49\u3002Windows \u73AF\u5883\u8BF7\u4F7F\u7528 Windows \u547D\u4EE4\uFF08dir\u3001type\u3001copy\uFF09\u6216 Python \u8DE8\u5E73\u53F0\u547D\u4EE4\uFF0C\u4E0D\u8981\u4F7F\u7528 Unix \u547D\u4EE4\uFF08ls\u3001cat\u3001cp\uFF09\u3002\u4E0D\u8981\u7528 bash \u505A\u6587\u4EF6\u8BFB\u5199\uFF08\u7528 read-file/edit-file/write-file\uFF09\u6216\u641C\u7D22\uFF08\u7528 glob/grep\uFF09\u3002" : "\u6267\u884C shell \u547D\u4EE4\uFF08shell: /bin/bash\uFF09\u3002\u7528\u4E8E\u8FD0\u884C\u6784\u5EFA\u3001\u6D4B\u8BD5\u3001git \u64CD\u4F5C\u7B49\u7CFB\u7EDF\u547D\u4EE4\u3002\u4E0D\u8981\u7528 bash \u505A\u6587\u4EF6\u8BFB\u5199\uFF08\u7528 read-file/edit-file/write-file\uFF09\u6216\u641C\u7D22\uFF08\u7528 glob/grep\uFF09\u3002",
553
494
  parameters: {
554
495
  type: "object",
555
496
  properties: {
@@ -568,7 +509,7 @@ var init_bash = __esm({
568
509
  async execute(params) {
569
510
  const command = params["command"];
570
511
  const timeout = params["timeout"] || DEFAULT_TIMEOUT;
571
- return new Promise((resolve8) => {
512
+ return new Promise((resolve10) => {
572
513
  exec(
573
514
  command,
574
515
  {
@@ -576,9 +517,12 @@ var init_bash = __esm({
576
517
  timeout,
577
518
  maxBuffer: 1024 * 1024 * 10,
578
519
  // 10MB
579
- shell: process.platform === "win32" ? "cmd.exe" : "/bin/bash"
520
+ shell: process.platform === "win32" ? "cmd.exe" : "/bin/bash",
521
+ encoding: "buffer"
580
522
  },
581
- (error, stdout, stderr) => {
523
+ (error, stdoutBuf, stderrBuf) => {
524
+ const stdout = stdoutBuf ? decodeBuffer(stdoutBuf) : "";
525
+ const stderr = stderrBuf ? decodeBuffer(stderrBuf) : "";
582
526
  let output = "";
583
527
  if (stdout) output += stdout;
584
528
  if (stderr) output += (output ? "\n" : "") + `[stderr]
@@ -589,7 +533,7 @@ ${stderr}`;
589
533
  } else if (error && !stdout && !stderr) {
590
534
  output = `\u547D\u4EE4\u6267\u884C\u5931\u8D25\uFF1A${error.message}`;
591
535
  }
592
- resolve8({ content: output || "\uFF08\u65E0\u8F93\u51FA\uFF09" });
536
+ resolve10({ content: output || "\uFF08\u65E0\u8F93\u51FA\uFF09" });
593
537
  }
594
538
  );
595
539
  });
@@ -900,6 +844,18 @@ var init_conversation = __esm({
900
844
  content
901
845
  });
902
846
  }
847
+ /**
848
+ * 清除历史消息中的 reasoning_content(新一轮对话开始时调用)
849
+ * deepseek-reasoner 要求同一轮 tool call 循环内保留 reasoning_content,
850
+ * 但新一轮用户问题开始时应清除以节省带宽,API 也会忽略旧的 reasoning_content
851
+ */
852
+ clearReasoningContent() {
853
+ for (const msg of this.messages) {
854
+ if (msg.reasoning_content !== void 0) {
855
+ msg.reasoning_content = void 0;
856
+ }
857
+ }
858
+ }
903
859
  /**
904
860
  * 获取完整的消息列表(包含系统提示词)
905
861
  */
@@ -933,67 +889,6 @@ var init_conversation = __esm({
933
889
  }
934
890
  });
935
891
 
936
- // src/core/auto-memo.ts
937
- function extractExports(code) {
938
- const names = [];
939
- for (const m of code.matchAll(/export\s+(?:default\s+)?(?:function|class|const|let|var)\s+(\w+)/g)) {
940
- names.push(m[1]);
941
- }
942
- for (const m of code.matchAll(/^(?:async\s+)?function\s+(\w+)/gm)) {
943
- if (!names.includes(m[1])) names.push(m[1]);
944
- }
945
- for (const m of code.matchAll(/(?:^|\n)\s{0,4}(?:async\s+)?function\s+(\w+)/g)) {
946
- if (!names.includes(m[1])) names.push(m[1]);
947
- }
948
- return [...new Set(names)].slice(0, 8).join(", ");
949
- }
950
- function autoMemoForTool(memoStore, toolName, params, result) {
951
- if (!memoStore) return;
952
- if (toolName === "write-file") {
953
- const path8 = params["path"];
954
- const content = params["content"] || "";
955
- const lines = content.split("\n").length;
956
- const exports = extractExports(content);
957
- memoStore.write(
958
- `file:${path8}`,
959
- content,
960
- "auto",
961
- `\u65B0\u5EFA ${lines}\u884C${exports ? " | \u5BFC\u51FA: " + exports : ""}`
962
- );
963
- }
964
- if (toolName === "edit-file") {
965
- const path8 = params["path"];
966
- const newStr = params["new_string"] || "";
967
- const oldStr = params["old_string"] || "";
968
- const changeLines = newStr.split("\n").length;
969
- memoStore.write(
970
- `file:${path8}`,
971
- `--- \u65E7 ---
972
- ${oldStr}
973
- --- \u65B0 ---
974
- ${newStr}`,
975
- "auto",
976
- `\u7F16\u8F91 ${changeLines}\u884C\u53D8\u66F4`
977
- );
978
- }
979
- if (toolName === "read-file") {
980
- const path8 = params["path"];
981
- const lines = result.split("\n").length;
982
- const exports = extractExports(result);
983
- memoStore.write(
984
- `file:${path8}`,
985
- result,
986
- "auto",
987
- `\u5DF2\u8BFB ${lines}\u884C${exports ? " | \u5BFC\u51FA: " + exports : ""}`
988
- );
989
- }
990
- }
991
- var init_auto_memo = __esm({
992
- "src/core/auto-memo.ts"() {
993
- "use strict";
994
- }
995
- });
996
-
997
892
  // src/core/read-tracker.ts
998
893
  import * as fs6 from "fs";
999
894
  import * as path6 from "path";
@@ -1042,7 +937,6 @@ var init_agent = __esm({
1042
937
  "use strict";
1043
938
  init_permission();
1044
939
  init_conversation();
1045
- init_auto_memo();
1046
940
  init_read_tracker();
1047
941
  Agent = class {
1048
942
  conversation;
@@ -1050,24 +944,26 @@ var init_agent = __esm({
1050
944
  registry;
1051
945
  config;
1052
946
  fixedTools;
1053
- memoStore;
1054
947
  readTracker = new ReadTracker();
1055
- constructor(client, registry, config, systemPrompt, tools, memoStore) {
948
+ interrupted = false;
949
+ constructor(client, registry, config, systemPrompt, tools) {
1056
950
  this.client = client;
1057
951
  this.registry = registry;
1058
952
  this.config = config;
1059
953
  this.conversation = new Conversation();
1060
954
  this.conversation.setSystemPrompt(systemPrompt);
1061
955
  this.fixedTools = tools;
1062
- this.memoStore = memoStore;
1063
956
  }
1064
957
  /**
1065
958
  * 执行一轮完整的 agent 循环
1066
959
  */
1067
960
  async run(userMessage, callbacks = {}) {
961
+ this.interrupted = false;
962
+ this.conversation.clearReasoningContent();
1068
963
  this.conversation.addUserMessage(userMessage);
1069
964
  let lastContent = "";
1070
965
  while (true) {
966
+ if (this.interrupted) break;
1071
967
  const tools = this.fixedTools || this.registry.toToolDefinitions();
1072
968
  const assistantMsg = await this.client.chatStream(
1073
969
  this.conversation.getMessages(),
@@ -1080,6 +976,7 @@ var init_agent = __esm({
1080
976
  break;
1081
977
  }
1082
978
  for (const toolCall of assistantMsg.tool_calls) {
979
+ if (this.interrupted) break;
1083
980
  const toolName = toolCall.function.name;
1084
981
  let params;
1085
982
  try {
@@ -1129,7 +1026,6 @@ var init_agent = __esm({
1129
1026
  }
1130
1027
  const result = await this.registry.execute(toolName, params, this.config.max_tool_output);
1131
1028
  callbacks.onToolResult?.(toolName, result.content, result.truncated ?? false);
1132
- autoMemoForTool(this.memoStore, toolName, params, result.content);
1133
1029
  if (toolName === "read-file") {
1134
1030
  this.readTracker.markRead(params["path"]);
1135
1031
  } else if (toolName === "write-file") {
@@ -1147,6 +1043,10 @@ var init_agent = __esm({
1147
1043
  }
1148
1044
  return lastContent;
1149
1045
  }
1046
+ interrupt() {
1047
+ this.interrupted = true;
1048
+ this.client.abortActiveStream();
1049
+ }
1150
1050
  /**
1151
1051
  * 获取对话历史
1152
1052
  */
@@ -1157,498 +1057,52 @@ var init_agent = __esm({
1157
1057
  }
1158
1058
  });
1159
1059
 
1160
- // src/core/dual-agent/coder.ts
1161
- var Coder;
1162
- var init_coder = __esm({
1163
- "src/core/dual-agent/coder.ts"() {
1164
- "use strict";
1165
- init_conversation();
1166
- init_permission();
1167
- init_auto_memo();
1168
- init_read_tracker();
1169
- Coder = class {
1170
- client;
1171
- registry;
1172
- config;
1173
- systemPrompt;
1174
- tools;
1175
- memoStore;
1176
- constructor(client, registry, config, systemPrompt, tools, memoStore) {
1177
- this.client = client;
1178
- this.registry = registry;
1179
- this.config = config;
1180
- this.systemPrompt = systemPrompt;
1181
- this.tools = tools;
1182
- this.memoStore = memoStore;
1183
- }
1184
- /**
1185
- * 执行编码任务(短生命周期,每次新建会话)
1186
- *
1187
- * @param taskMessage 调度者发来的任务描述(作为 user message)
1188
- * @param callbacks 回调
1189
- * @returns 编码者的最终响应
1190
- */
1191
- async execute(taskMessage, callbacks = {}) {
1192
- const conversation = new Conversation();
1193
- conversation.setSystemPrompt(this.systemPrompt);
1194
- conversation.addUserMessage(taskMessage);
1195
- const readTracker = new ReadTracker();
1196
- let lastContent = "";
1197
- while (true) {
1198
- const assistantMsg = await this.client.chatStream(
1199
- conversation.getMessages(),
1200
- this.tools.length > 0 ? this.tools : void 0,
1201
- callbacks
1202
- );
1203
- conversation.addAssistantMessage(assistantMsg);
1204
- if (!assistantMsg.tool_calls || assistantMsg.tool_calls.length === 0) {
1205
- lastContent = assistantMsg.content || "";
1206
- break;
1207
- }
1208
- for (const toolCall of assistantMsg.tool_calls) {
1209
- const toolName = toolCall.function.name;
1210
- let params;
1211
- try {
1212
- params = JSON.parse(toolCall.function.arguments);
1213
- } catch {
1214
- conversation.addToolResult(toolCall.id, "\u53C2\u6570\u89E3\u6790\u5931\u8D25\uFF1A\u65E0\u6548\u7684 JSON");
1215
- continue;
1216
- }
1217
- try {
1218
- if (toolName === "edit-file") {
1219
- const editPath = params["path"];
1220
- if (!readTracker.hasRead(editPath)) {
1221
- conversation.addToolResult(
1222
- toolCall.id,
1223
- `\u26A0 \u7981\u6B62\u7F16\u8F91\u672A\u8BFB\u53D6\u7684\u6587\u4EF6\u3002\u8BF7\u5148 read-file "${editPath}" \u4E86\u89E3\u5F53\u524D\u5185\u5BB9\uFF0C\u518D edit-file\u3002`
1224
- );
1225
- continue;
1226
- }
1227
- }
1228
- if (toolName === "write-file") {
1229
- const warn = readTracker.checkWriteOverwrite(
1230
- params["path"],
1231
- params["overwrite"]
1232
- );
1233
- if (warn) {
1234
- conversation.addToolResult(toolCall.id, warn);
1235
- continue;
1236
- }
1237
- }
1238
- const permLevel = this.registry.getPermissionLevel(toolName);
1239
- if (permLevel === "deny") {
1240
- callbacks.onDenied?.(toolName);
1241
- conversation.addToolResult(toolCall.id, `\u5DE5\u5177 "${toolName}" \u5DF2\u88AB\u7981\u6B62\u6267\u884C`);
1242
- continue;
1243
- }
1244
- if (permLevel === "auto") {
1245
- callbacks.onToolExecuting?.(toolName, params);
1246
- }
1247
- if (permLevel === "confirm") {
1248
- const confirmResult = await confirmExecution(toolName, params);
1249
- if (!confirmResult.approved) {
1250
- callbacks.onDenied?.(toolName, confirmResult.feedback);
1251
- const denyMsg = confirmResult.feedback ? `\u7528\u6237\u62D2\u7EDD\u4E86\u6B64\u64CD\u4F5C\uFF0C\u7528\u6237\u53CD\u9988: ${confirmResult.feedback}` : "\u7528\u6237\u62D2\u7EDD\u4E86\u6B64\u64CD\u4F5C";
1252
- conversation.addToolResult(toolCall.id, denyMsg);
1253
- continue;
1254
- }
1255
- }
1256
- const result = await this.registry.execute(toolName, params, this.config.max_tool_output);
1257
- callbacks.onToolResult?.(toolName, result.content, result.truncated ?? false);
1258
- autoMemoForTool(this.memoStore, toolName, params, result.content);
1259
- if (toolName === "read-file") {
1260
- readTracker.markRead(params["path"]);
1261
- } else if (toolName === "write-file") {
1262
- readTracker.markWritten(params["path"]);
1263
- }
1264
- conversation.addToolResult(toolCall.id, result.content);
1265
- } catch (err) {
1266
- const msg = err instanceof Error ? err.message : String(err);
1267
- conversation.addToolResult(toolCall.id, `\u5DE5\u5177\u6267\u884C\u5F02\u5E38\uFF1A${msg}`);
1268
- }
1269
- }
1270
- if (assistantMsg.content) {
1271
- lastContent = assistantMsg.content;
1272
- }
1273
- }
1274
- return lastContent;
1275
- }
1276
- };
1277
- }
1278
- });
1060
+ // src/core/prompt/layers/core.ts
1061
+ import * as os2 from "os";
1062
+ function buildCorePrompt() {
1063
+ const shellInfo = IS_WIN2 ? 'cmd.exe\uFF08Windows\uFF09\u3002\u8BF7\u4F7F\u7528 Windows \u547D\u4EE4\uFF08dir\u3001type\u3001copy \u7B49\uFF09\u6216 Python \u8DE8\u5E73\u53F0\u547D\u4EE4\uFF08python -c "..."\uFF09\uFF0C\u4E0D\u8981\u4F7F\u7528 Unix \u547D\u4EE4\uFF08ls\u3001cat\u3001cp \u7B49\uFF09' : "/bin/bash";
1064
+ return `\u4F60\u662F ZenCode\uFF0C\u4E00\u4E2A CLI \u73AF\u5883\u4E0B\u7684 AI \u7F16\u7A0B\u52A9\u624B\u3002\u4F60\u5E2E\u52A9\u7528\u6237\u5B8C\u6210\u8F6F\u4EF6\u5DE5\u7A0B\u4EFB\u52A1\uFF1A\u4FEEbug\u3001\u52A0\u529F\u80FD\u3001\u91CD\u6784\u4EE3\u7801\u3001\u89E3\u91CA\u4EE3\u7801\u7B49\u3002
1279
1065
 
1280
- // src/core/dual-agent/modes.ts
1281
- function getMode(name) {
1282
- switch (name) {
1283
- case "delegated":
1284
- return DELEGATED_MODE;
1285
- case "autonomous":
1286
- return AUTONOMOUS_MODE;
1287
- case "controlled":
1288
- return CONTROLLED_MODE;
1289
- default:
1290
- return DELEGATED_MODE;
1291
- }
1292
- }
1293
- var CODER_IDENTITY, DELEGATED_MODE, AUTONOMOUS_MODE, CONTROLLED_MODE;
1294
- var init_modes = __esm({
1295
- "src/core/dual-agent/modes.ts"() {
1296
- "use strict";
1297
- CODER_IDENTITY = `\u4F60\u662F\u7F16\u7801\u5B50 Agent\u3002
1066
+ \u5DE5\u4F5C\u76EE\u5F55\uFF1A${process.cwd()}
1067
+ \u7CFB\u7EDF\uFF1A${os2.platform()} ${os2.arch()}
1068
+
1069
+ # \u5DE5\u5177\u4F7F\u7528\u539F\u5219
1070
+
1071
+ \u4F60\u6709\u4EE5\u4E0B\u5DE5\u5177\u53EF\u7528\uFF0C\u8BF7\u6839\u636E\u4EFB\u52A1\u9009\u62E9\u6700\u5408\u9002\u7684\u5DE5\u5177\uFF1A
1298
1072
 
1299
- ## \u5DE5\u4F5C\u6D41\u7A0B
1300
- 1. \u67E5\u770B\u4EFB\u52A1\u672B\u5C3E\u7684 [\u5171\u4EAB\u5907\u5FD8\u5F55] \u4E86\u89E3\u5DF2\u6709\u6587\u4EF6\u548C\u51FD\u6570
1301
- - \u6BCF\u884C\u683C\u5F0F\uFF1A[file:\u8DEF\u5F84] \u6458\u8981 | \u5BFC\u51FA: \u51FD\u6570\u540D\u5217\u8868
1302
- - \u9700\u8981\u5B8C\u6574\u6587\u4EF6\u5185\u5BB9\u6216 diff \u8BE6\u60C5 \u2192 memo read <key>
1303
- 2. \u7F16\u7801\uFF1A
1304
- - \u65B0\u5EFA\u6587\u4EF6 \u2192 write-file
1305
- - \u4FEE\u6539\u6587\u4EF6 \u2192 \u5FC5\u987B\u5148 read-file \u9605\u8BFB \u2192 \u518D edit-file \u4FEE\u6539
1306
- - \u26A0\uFE0F \u7CFB\u7EDF\u5F3A\u5236\uFF1A\u672A read-file \u7684\u6587\u4EF6\u65E0\u6CD5 edit-file\uFF0C\u4F1A\u88AB\u62E6\u622A
1307
- - \u5F15\u7528\u5DF2\u6709\u6587\u4EF6\u7684\u51FD\u6570\u65F6\uFF0C\u52A1\u5FC5\u4F7F\u7528 memo \u4E2D\u5217\u51FA\u7684\u51C6\u786E\u540D\u79F0
1308
- 3. \u4E00\u53E5\u8BDD\u8BF4\u660E\u7ED3\u679C
1073
+ - **read-file**\uFF1A\u8BFB\u53D6\u6587\u4EF6\u5185\u5BB9\u3002\u4FEE\u6539\u4EE3\u7801\u524D\u5FC5\u987B\u5148\u8BFB\u53D6\u76EE\u6807\u6587\u4EF6\u3002\u652F\u6301 offset/limit \u8BFB\u53D6\u5927\u6587\u4EF6\u7684\u7279\u5B9A\u90E8\u5206\u3002
1074
+ - **edit-file**\uFF1A\u901A\u8FC7\u5B57\u7B26\u4E32\u66FF\u6362\u7F16\u8F91\u6587\u4EF6\u3002\u4F18\u5148\u4F7F\u7528 edit-file \u800C\u975E write-file \u4FEE\u6539\u5DF2\u6709\u6587\u4EF6\u2014\u2014\u5B83\u66F4\u7CBE\u786E\u3001\u66F4\u5B89\u5168\u3002
1075
+ - \u26A0\uFE0F \u7CFB\u7EDF\u5F3A\u5236\uFF1A\u672A\u7528 read-file \u8BFB\u53D6\u8FC7\u7684\u6587\u4EF6\u65E0\u6CD5 edit-file\uFF0C\u4F1A\u88AB\u62E6\u622A
1076
+ - old_string \u5FC5\u987B\u4E0E\u6587\u4EF6\u4E2D\u7684\u5185\u5BB9**\u5B8C\u5168\u4E00\u81F4**\uFF08\u5305\u62EC\u7F29\u8FDB\u3001\u7A7A\u683C\u3001\u6362\u884C\u7B26\uFF09
1077
+ - old_string \u4E0D\u552F\u4E00\u65F6\uFF0C\u5305\u542B\u66F4\u591A\u4E0A\u4E0B\u6587\u884C\uFF08\u5EFA\u8BAE 3-5 \u884C\uFF09\u4F7F\u5176\u552F\u4E00
1078
+ - \u4E0D\u8981\u51ED\u8BB0\u5FC6\u731C\u6D4B\u6587\u4EF6\u5185\u5BB9\uFF0C\u5FC5\u987B\u57FA\u4E8E read-file \u7684\u5B9E\u9645\u8FD4\u56DE\u503C
1079
+ - **write-file**\uFF1A\u521B\u5EFA\u65B0\u6587\u4EF6\u6216\u5B8C\u6574\u91CD\u5199\u6587\u4EF6\u3002\u4EC5\u5728\u521B\u5EFA\u65B0\u6587\u4EF6\u6216\u9700\u8981\u5927\u5E45\u91CD\u5199\u65F6\u4F7F\u7528\u3002
1080
+ - **glob**\uFF1A\u6309\u6A21\u5F0F\u641C\u7D22\u6587\u4EF6\u8DEF\u5F84\u3002\u7528\u4E8E\u67E5\u627E\u6587\u4EF6\u4F4D\u7F6E\uFF08\u5982 \`**/*.ts\`\u3001\`src/**/config.*\`\uFF09\u3002
1081
+ - **grep**\uFF1A\u5728\u6587\u4EF6\u5185\u5BB9\u4E2D\u641C\u7D22\u6B63\u5219\u8868\u8FBE\u5F0F\u3002\u7528\u4E8E\u67E5\u627E\u51FD\u6570\u5B9A\u4E49\u3001\u5F15\u7528\u3001\u7279\u5B9A\u4EE3\u7801\u6A21\u5F0F\u3002
1082
+ - **bash**\uFF1A\u6267\u884C\u7CFB\u7EDF\u547D\u4EE4\uFF0C\u5F53\u524D shell\uFF1A${shellInfo}\u3002\u7528\u4E8E\u8FD0\u884C\u6784\u5EFA\u3001\u6D4B\u8BD5\u3001git \u64CD\u4F5C\u7B49\u3002\u4E0D\u8981\u7528 bash \u505A\u80FD\u7528\u4E0A\u8FF0\u5DE5\u5177\u5B8C\u6210\u7684\u4E8B\uFF08\u6587\u4EF6\u8BFB\u5199\u7528 read-file/edit-file/write-file\uFF0C\u641C\u7D22\u7528 glob/grep\uFF09\u3002
1309
1083
 
1310
- \u6CE8\u610F\uFF1A\u6587\u4EF6\u64CD\u4F5C\uFF08write-file\u3001edit-file\uFF09\u4F1A\u81EA\u52A8\u8BB0\u5F55\u5230 memo\uFF0C\u4E0D\u9700\u8981\u624B\u52A8 memo write \u6587\u4EF6\u4FE1\u606F\u3002
1311
- \u5982\u6709\u9700\u8981\uFF0C\u4ECD\u53EF memo write \u8BB0\u5F55\u975E\u6587\u4EF6\u7C7B\u4FE1\u606F\uFF08\u67B6\u6784\u51B3\u7B56\u3001\u6CE8\u610F\u4E8B\u9879\u7B49\uFF09\u3002
1084
+ \u5173\u952E\u89C4\u5219\uFF1A
1085
+ - **\u5148\u8BFB\u540E\u6539**\uFF1A\u4FEE\u6539\u6587\u4EF6\u524D\u5FC5\u987B read-file \u8BFB\u53D6\u8BE5\u6587\u4EF6\uFF08\u7CFB\u7EDF\u4F1A\u62E6\u622A\u672A\u8BFB\u53D6\u5C31 edit \u7684\u64CD\u4F5C\uFF09
1086
+ - edit-file \u7684 old_string \u5FC5\u987B\u4ECE read-file \u8FD4\u56DE\u7684\u5185\u5BB9\u4E2D\u7CBE\u786E\u590D\u5236\uFF0C\u4E0D\u8981\u624B\u52A8\u8F93\u5165\u6216\u51ED\u8BB0\u5FC6
1087
+ - \u4F18\u5148 edit-file \u7F16\u8F91\u5DF2\u6709\u6587\u4EF6\uFF0C\u800C\u975E write-file \u91CD\u5199
1088
+ - \u4E0D\u8981\u521B\u5EFA\u4E0D\u5FC5\u8981\u7684\u65B0\u6587\u4EF6\uFF0C\u4F18\u5148\u5728\u73B0\u6709\u6587\u4EF6\u4E2D\u4FEE\u6539
1089
+ - \u53EA\u505A\u5FC5\u8981\u7684\u6700\u5C0F\u6539\u52A8\uFF0C\u4E0D\u505A\u989D\u5916"\u6539\u8FDB"
1090
+ - \u4E0D\u8981\u6DFB\u52A0\u7528\u6237\u672A\u8981\u6C42\u7684\u6CE8\u91CA\u3001\u6587\u6863\u3001\u7C7B\u578B\u6CE8\u89E3
1091
+ - \u4E0D\u8981\u5F15\u5165\u5B89\u5168\u6F0F\u6D1E\uFF08\u6CE8\u5165\u3001XSS\u3001SQL \u6CE8\u5165\u7B49 OWASP Top 10\uFF09
1092
+ - \u5F15\u7528\u4EE3\u7801\u65F6\u4F7F\u7528 \`\u6587\u4EF6\u8DEF\u5F84:\u884C\u53F7\` \u683C\u5F0F\uFF08\u5982 \`src/app.ts:42\`\uFF09\uFF0C\u65B9\u4FBF\u7528\u6237\u8DF3\u8F6C
1312
1093
 
1313
- ## edit-file \u51C6\u786E\u6027\u89C4\u5219
1314
- - old_string \u5FC5\u987B\u4ECE read-file \u8FD4\u56DE\u7684\u5185\u5BB9\u4E2D**\u7CBE\u786E\u590D\u5236**\uFF0C\u4E0D\u80FD\u51ED\u8BB0\u5FC6\u624B\u5199
1315
- - old_string \u5305\u542B\u8DB3\u591F\u4E0A\u4E0B\u6587\u884C\uFF083-5 \u884C\uFF09\u786E\u4FDD\u552F\u4E00\u5339\u914D
1316
- - \u7F29\u8FDB\u3001\u7A7A\u683C\u3001\u6362\u884C\u7B26\u5FC5\u987B\u5B8C\u5168\u4E00\u81F4
1317
- - \u5982\u679C\u4E0D\u786E\u5B9A\u6587\u4EF6\u5185\u5BB9\uFF0C\u5148 read-file \u518D\u64CD\u4F5C
1094
+ # \u4EA4\u4E92\u98CE\u683C
1318
1095
 
1319
- ## \u7981\u6B62
1320
- - \u274C \u7F16\u7801\u524D\u505A\u63A2\u7D22\uFF08bash ls/dir/pwd\u3001glob\u3001grep\uFF09\u2014 \u4E0A\u4E0B\u6587\u5DF2\u5728 memo \u4E2D
1321
- - \u274C \u8F93\u51FA\u8BA1\u5212\u3001\u5206\u6790\u3001\u601D\u8DEF\u8BF4\u660E \u2014 \u76F4\u63A5\u52A8\u624B
1322
- - \u274C \u505A\u4EFB\u52A1\u8303\u56F4\u5916\u7684\u6539\u52A8
1323
- - \u274C \u8FC7\u5EA6\u5DE5\u7A0B\u5316
1324
- - \u274C \u51ED\u8BB0\u5FC6\u731C\u6D4B\u6587\u4EF6\u5185\u5BB9\u7528\u4E8E edit-file \u7684 old_string`;
1325
- DELEGATED_MODE = {
1326
- name: "delegated",
1327
- description: "A\u6536\u96C6\u4E0A\u4E0B\u6587\u5E76\u59D4\u6D3E\uFF0CB\u62E5\u6709\u5B8C\u6574\u5DE5\u5177\u72EC\u7ACB\u6267\u884C",
1328
- coderHasTools: true,
1329
- coderToolNames: ["read-file", "write-file", "edit-file", "bash", "glob", "grep", "memo"],
1330
- coderSystemPrompt: `${CODER_IDENTITY}`
1331
- };
1332
- AUTONOMOUS_MODE = {
1333
- name: "autonomous",
1334
- description: "A\u89C4\u5212\uFF0CB\u81EA\u4E3B\u6267\u884C\uFF08\u9002\u5408\u80FD\u529B\u5F3A\u7684\u6A21\u578B\uFF09",
1335
- coderHasTools: true,
1336
- coderToolNames: ["read-file", "write-file", "edit-file", "bash", "glob", "grep", "memo"],
1337
- coderSystemPrompt: `${CODER_IDENTITY}`
1338
- };
1339
- CONTROLLED_MODE = {
1340
- name: "controlled",
1341
- description: "A\u5168\u6743\u7BA1\u7406\uFF0CB\u53EA\u8FD4\u56DE\u4EE3\u7801",
1342
- coderHasTools: false,
1343
- coderSystemPrompt: "\u4F60\u662F\u88AB\u8C03\u5EA6\u8005\u6D3E\u51FA\u7684\u5B50 Agent\u3002\u6839\u636E\u63D0\u4F9B\u7684\u4EE3\u7801\u548C\u9700\u6C42\uFF0C\u53EA\u8FD4\u56DE\u4FEE\u6539\u540E\u7684\u4EE3\u7801\u3002\u4E0D\u8981\u81EA\u884C\u64CD\u4F5C\u6587\u4EF6\uFF0C\u4E0D\u8981\u505A\u989D\u5916\u6539\u52A8\u3002"
1344
- };
1345
- }
1346
- });
1347
-
1348
- // src/core/dual-agent/orchestrator.ts
1349
- var SEND_TO_CODER_TOOL, Orchestrator;
1350
- var init_orchestrator = __esm({
1351
- "src/core/dual-agent/orchestrator.ts"() {
1352
- "use strict";
1353
- init_client();
1354
- init_loader();
1355
- init_conversation();
1356
- init_permission();
1357
- init_coder();
1358
- init_modes();
1359
- init_auto_memo();
1360
- init_read_tracker();
1361
- SEND_TO_CODER_TOOL = {
1362
- type: "function",
1363
- function: {
1364
- name: "send-to-coder",
1365
- description: "\u5C06\u7F16\u7801\u4EFB\u52A1\u53D1\u9001\u7ED9\u7F16\u7801 Agent\u3002\u4F20\u5165\u4EFB\u52A1\u63CF\u8FF0\u548C\u76F8\u5173\u4EE3\u7801\u4E0A\u4E0B\u6587\uFF0C\u7F16\u7801 Agent \u5C06\u8FD4\u56DE\u4EE3\u7801\u6216\u89E3\u51B3\u65B9\u6848\u3002",
1366
- parameters: {
1367
- type: "object",
1368
- properties: {
1369
- task: {
1370
- type: "string",
1371
- description: "\u8981\u53D1\u9001\u7ED9\u7F16\u7801 Agent \u7684\u4EFB\u52A1\u63CF\u8FF0\uFF0C\u5305\u542B\u76F8\u5173\u4EE3\u7801\u4E0A\u4E0B\u6587"
1372
- }
1373
- },
1374
- required: ["task"]
1375
- }
1376
- }
1377
- };
1378
- Orchestrator = class {
1379
- conversation;
1380
- orchestratorClient;
1381
- coderClient;
1382
- registry;
1383
- config;
1384
- mode;
1385
- baseSystemPrompt;
1386
- memoStore;
1387
- readTracker = new ReadTracker();
1388
- constructor(registry, config, systemPrompt, memoStore) {
1389
- this.registry = registry;
1390
- this.config = config;
1391
- this.mode = config.collaboration;
1392
- this.baseSystemPrompt = systemPrompt;
1393
- this.memoStore = memoStore;
1394
- const orchConfig = resolveModelConfig(config, "orchestrator");
1395
- this.orchestratorClient = createLLMClient({
1396
- apiKey: orchConfig.api_key,
1397
- baseURL: orchConfig.base_url,
1398
- model: orchConfig.model,
1399
- temperature: orchConfig.temperature,
1400
- maxTokens: orchConfig.max_tokens
1401
- });
1402
- const coderConfig = resolveModelConfig(config, "coder");
1403
- this.coderClient = createLLMClient({
1404
- apiKey: coderConfig.api_key,
1405
- baseURL: coderConfig.base_url,
1406
- model: coderConfig.model,
1407
- temperature: coderConfig.temperature,
1408
- maxTokens: coderConfig.max_tokens
1409
- });
1410
- this.conversation = new Conversation();
1411
- this.conversation.setSystemPrompt(this.buildSystemPrompt());
1412
- }
1413
- /**
1414
- * 动态获取调度者的工具列表:注册表工具 + send-to-coder
1415
- */
1416
- getTools() {
1417
- return [
1418
- ...this.registry.toToolDefinitions(),
1419
- SEND_TO_CODER_TOOL
1420
- ];
1421
- }
1422
- /**
1423
- * 构建调度者的系统提示词(包含当前协作模式)
1424
- */
1425
- buildSystemPrompt() {
1426
- const modeInfo = getMode(this.mode);
1427
- return `${this.baseSystemPrompt}
1428
-
1429
- # \u4F60\u662F\u8C03\u5EA6\u8005 Agent
1430
-
1431
- \u534F\u4F5C\u6A21\u5F0F\uFF1A${modeInfo.name} - ${modeInfo.description}
1432
-
1433
- \u4F60\u662F\u4FA6\u5BDF\u5175 + \u6307\u6325\u5B98\u3002\u4F60\u81EA\u5DF1\u4E0D\u5199\u4EE3\u7801\uFF0C\u4F60\u7684\u804C\u8D23\u662F\uFF1A\u6536\u96C6\u60C5\u62A5 \u2192 \u59D4\u6D3E\u7F16\u7801 Agent\u3002
1434
-
1435
- ## \u6838\u5FC3\u6D41\u7A0B
1436
-
1437
- 1. **\u8BC4\u4F30**\uFF1A\u4EFB\u52A1\u662F\u5426\u9700\u8981\u4E86\u89E3\u73B0\u6709\u4EE3\u7801\uFF1F
1438
- - \u9700\u8981\u4FEE\u6539\u73B0\u6709\u6587\u4EF6\u3001\u9700\u8981\u7406\u89E3\u4F9D\u8D56\u5173\u7CFB \u2192 \u5148\u6536\u96C6
1439
- - \u9700\u6C42\u81EA\u5305\u542B\u3001\u76EE\u6807\u8DEF\u5F84\u660E\u786E \u2192 \u8DF3\u5230\u7B2C 3 \u6B65
1440
-
1441
- 2. **\u6536\u96C6\u4E0A\u4E0B\u6587**\uFF08\u9AD8\u6548\uFF0C\u4E0D\u91CD\u590D\uFF09
1442
- - \u7528 glob/grep \u5B9A\u4F4D + read-file \u6216 spawn-agents \u5E76\u884C\u8BFB\u53D6
1443
- - \u6587\u4EF6\u64CD\u4F5C\uFF08read-file \u7B49\uFF09\u4F1A\u81EA\u52A8\u8BB0\u5F55\u5230 memo\uFF0C\u4E0D\u9700\u8981\u624B\u52A8 memo write \u6587\u4EF6\u5185\u5BB9
1444
- - \u4ECD\u53EF memo write \u8BB0\u5F55\u5206\u6790\u7ED3\u8BBA\u3001\u67B6\u6784\u51B3\u7B56\u7B49\u975E\u6587\u4EF6\u4FE1\u606F
1445
-
1446
- 3. **\u59D4\u6D3E\u7F16\u7801**\uFF1Asend-to-coder
1447
- - task \u4E2D\u5199\u6E05\uFF1A\u505A\u4EC0\u4E48 + \u76EE\u6807\u8DEF\u5F84
1448
- - \u7F16\u7801 Agent \u4F1A\u81EA\u52A8\u770B\u5230 memo \u4E2D\u7684\u6587\u4EF6\u7D22\u5F15\uFF08\u5305\u62EC\u5DF2\u521B\u5EFA\u7684\u6587\u4EF6\u548C\u5BFC\u51FA\u7684\u51FD\u6570\u540D\uFF09
1449
- - \u6BCF\u6B21\u53EA\u53D1\u4E00\u4E2A\u5177\u4F53\u6B65\u9AA4
1450
-
1451
- 4. **\u8FED\u4EE3/\u9A8C\u8BC1**\uFF1A\u9700\u8981\u65F6\u7EE7\u7EED\u59D4\u6D3E\u6216\u7528 bash \u8FD0\u884C\u9A8C\u8BC1
1452
-
1453
- ## \u91CD\u8981
1454
-
1455
- - memo \u7D22\u5F15\u4F1A\u81EA\u52A8\u6CE8\u5165\u7ED9\u7F16\u7801 Agent\uFF0C\u5305\u542B\u6240\u6709\u5DF2\u64CD\u4F5C\u6587\u4EF6\u7684\u6458\u8981\u548C\u5BFC\u51FA\u51FD\u6570\u540D
1456
- - \u4E0D\u8981\u91CD\u590D\u63A2\u7D22\uFF08glob \u67E5\u8FC7\u5C31\u4E0D\u8981\u518D bash ls \u540C\u4E00\u76EE\u5F55\uFF09
1457
- - bash \u7528\u4E8E\u6267\u884C\u547D\u4EE4\uFF08\u6784\u5EFA\u3001\u6D4B\u8BD5\uFF09\uFF0C\u4E0D\u9700\u8981\u59D4\u6D3E
1458
- - \u5B8C\u6210\u6240\u6709\u6B65\u9AA4\u540E\u7B80\u8981\u544A\u77E5\u7528\u6237\u7ED3\u679C`;
1459
- }
1460
- /**
1461
- * 切换协作模式
1462
- */
1463
- setMode(mode) {
1464
- this.mode = mode;
1465
- this.conversation.setSystemPrompt(this.buildSystemPrompt());
1466
- }
1467
- /**
1468
- * 执行用户请求
1469
- */
1470
- async run(userMessage, callbacks = {}) {
1471
- callbacks.onModeInfo?.(this.mode);
1472
- this.conversation.addUserMessage(userMessage);
1473
- let lastContent = "";
1474
- while (true) {
1475
- const tools = this.getTools();
1476
- const assistantMsg = await this.orchestratorClient.chatStream(
1477
- this.conversation.getMessages(),
1478
- tools,
1479
- callbacks
1480
- );
1481
- this.conversation.addAssistantMessage(assistantMsg);
1482
- if (!assistantMsg.tool_calls || assistantMsg.tool_calls.length === 0) {
1483
- lastContent = assistantMsg.content || "";
1484
- break;
1485
- }
1486
- for (const toolCall of assistantMsg.tool_calls) {
1487
- const toolName = toolCall.function.name;
1488
- let params;
1489
- try {
1490
- params = JSON.parse(toolCall.function.arguments);
1491
- } catch {
1492
- this.conversation.addToolResult(toolCall.id, "\u53C2\u6570\u89E3\u6790\u5931\u8D25\uFF1A\u65E0\u6548\u7684 JSON");
1493
- continue;
1494
- }
1495
- try {
1496
- if (toolName === "send-to-coder") {
1497
- const task = params["task"];
1498
- const coderResponse = await this.invokeCoder(task, callbacks);
1499
- this.conversation.addToolResult(toolCall.id, coderResponse);
1500
- continue;
1501
- }
1502
- if (toolName === "edit-file") {
1503
- const editPath = params["path"];
1504
- if (!this.readTracker.hasRead(editPath)) {
1505
- this.conversation.addToolResult(
1506
- toolCall.id,
1507
- `\u26A0 \u7981\u6B62\u7F16\u8F91\u672A\u8BFB\u53D6\u7684\u6587\u4EF6\u3002\u8BF7\u5148 read-file "${editPath}" \u4E86\u89E3\u5F53\u524D\u5185\u5BB9\uFF0C\u518D edit-file\u3002`
1508
- );
1509
- continue;
1510
- }
1511
- }
1512
- if (toolName === "write-file") {
1513
- const warn = this.readTracker.checkWriteOverwrite(
1514
- params["path"],
1515
- params["overwrite"]
1516
- );
1517
- if (warn) {
1518
- this.conversation.addToolResult(toolCall.id, warn);
1519
- continue;
1520
- }
1521
- }
1522
- const permLevel = this.registry.getPermissionLevel(toolName);
1523
- if (permLevel === "deny") {
1524
- callbacks.onDenied?.(toolName);
1525
- this.conversation.addToolResult(toolCall.id, `\u5DE5\u5177 "${toolName}" \u5DF2\u88AB\u7981\u6B62\u6267\u884C`);
1526
- continue;
1527
- }
1528
- if (permLevel === "auto") {
1529
- callbacks.onToolExecuting?.(toolName, params);
1530
- }
1531
- if (permLevel === "confirm") {
1532
- const confirmResult = await confirmExecution(toolName, params);
1533
- if (!confirmResult.approved) {
1534
- callbacks.onDenied?.(toolName, confirmResult.feedback);
1535
- const denyMsg = confirmResult.feedback ? `\u7528\u6237\u62D2\u7EDD\u4E86\u6B64\u64CD\u4F5C\uFF0C\u7528\u6237\u53CD\u9988: ${confirmResult.feedback}` : "\u7528\u6237\u62D2\u7EDD\u4E86\u6B64\u64CD\u4F5C";
1536
- this.conversation.addToolResult(toolCall.id, denyMsg);
1537
- continue;
1538
- }
1539
- }
1540
- const result = await this.registry.execute(toolName, params, this.config.max_tool_output);
1541
- callbacks.onToolResult?.(toolName, result.content, result.truncated ?? false);
1542
- autoMemoForTool(this.memoStore, toolName, params, result.content);
1543
- if (toolName === "read-file") {
1544
- this.readTracker.markRead(params["path"]);
1545
- } else if (toolName === "write-file") {
1546
- this.readTracker.markWritten(params["path"]);
1547
- }
1548
- this.conversation.addToolResult(toolCall.id, result.content);
1549
- } catch (err) {
1550
- const msg = err instanceof Error ? err.message : String(err);
1551
- this.conversation.addToolResult(toolCall.id, `\u5DE5\u5177\u6267\u884C\u5F02\u5E38\uFF1A${msg}`);
1552
- }
1553
- }
1554
- if (assistantMsg.content) {
1555
- lastContent = assistantMsg.content;
1556
- }
1557
- }
1558
- return lastContent;
1559
- }
1560
- /**
1561
- * 调用编码者 Agent
1562
- */
1563
- async invokeCoder(task, callbacks) {
1564
- callbacks.onCoderStart?.();
1565
- const modeInfo = getMode(this.mode);
1566
- let coderTools = [];
1567
- if (modeInfo.coderHasTools && modeInfo.coderToolNames) {
1568
- coderTools = this.registry.toToolDefinitions(modeInfo.coderToolNames);
1569
- }
1570
- let taskWithMemo = task;
1571
- if (this.memoStore) {
1572
- const index = this.memoStore.buildIndex();
1573
- if (index) {
1574
- taskWithMemo += `
1575
-
1576
- [\u5171\u4EAB\u5907\u5FD8\u5F55]
1577
- ${index}`;
1578
- }
1579
- }
1580
- taskWithMemo += "\n\n[\u91CD\u8981\uFF1A\u7ACB\u5373\u5F00\u59CB\u7F16\u7801\uFF0C\u4E0D\u8981\u63A2\u7D22\uFF0C\u4E0D\u8981\u8F93\u51FA\u5206\u6790]";
1581
- const coder = new Coder(
1582
- this.coderClient,
1583
- this.registry,
1584
- this.config,
1585
- modeInfo.coderSystemPrompt,
1586
- coderTools,
1587
- this.memoStore
1588
- );
1589
- const response = await coder.execute(taskWithMemo, {
1590
- onContent: callbacks.onContent,
1591
- onToolCallStreaming: callbacks.onToolCallStreaming,
1592
- onToolExecuting: callbacks.onToolExecuting,
1593
- onToolResult: callbacks.onToolResult,
1594
- onDenied: callbacks.onDenied
1595
- });
1596
- callbacks.onCoderEnd?.(response);
1597
- return response;
1598
- }
1599
- /**
1600
- * 获取对话历史
1601
- */
1602
- getConversation() {
1603
- return this.conversation;
1604
- }
1605
- };
1606
- }
1607
- });
1608
-
1609
- // src/core/prompt/layers/core.ts
1610
- import * as os2 from "os";
1611
- function buildCorePrompt() {
1612
- return `\u4F60\u662F ZenCode\uFF0C\u4E00\u4E2A CLI \u73AF\u5883\u4E0B\u7684 AI \u7F16\u7A0B\u52A9\u624B\u3002\u4F60\u5E2E\u52A9\u7528\u6237\u5B8C\u6210\u8F6F\u4EF6\u5DE5\u7A0B\u4EFB\u52A1\uFF1A\u4FEEbug\u3001\u52A0\u529F\u80FD\u3001\u91CD\u6784\u4EE3\u7801\u3001\u89E3\u91CA\u4EE3\u7801\u7B49\u3002
1613
-
1614
- \u5DE5\u4F5C\u76EE\u5F55\uFF1A${process.cwd()}
1615
- \u7CFB\u7EDF\uFF1A${os2.platform()} ${os2.arch()}
1616
-
1617
- # \u5DE5\u5177\u4F7F\u7528\u539F\u5219
1618
-
1619
- \u4F60\u6709\u4EE5\u4E0B\u5DE5\u5177\u53EF\u7528\uFF0C\u8BF7\u6839\u636E\u4EFB\u52A1\u9009\u62E9\u6700\u5408\u9002\u7684\u5DE5\u5177\uFF1A
1620
-
1621
- - **read-file**\uFF1A\u8BFB\u53D6\u6587\u4EF6\u5185\u5BB9\u3002\u4FEE\u6539\u4EE3\u7801\u524D\u5FC5\u987B\u5148\u8BFB\u53D6\u76EE\u6807\u6587\u4EF6\u3002\u652F\u6301 offset/limit \u8BFB\u53D6\u5927\u6587\u4EF6\u7684\u7279\u5B9A\u90E8\u5206\u3002
1622
- - **edit-file**\uFF1A\u901A\u8FC7\u5B57\u7B26\u4E32\u66FF\u6362\u7F16\u8F91\u6587\u4EF6\u3002\u4F18\u5148\u4F7F\u7528 edit-file \u800C\u975E write-file \u4FEE\u6539\u5DF2\u6709\u6587\u4EF6\u2014\u2014\u5B83\u66F4\u7CBE\u786E\u3001\u66F4\u5B89\u5168\u3002
1623
- - \u26A0\uFE0F \u7CFB\u7EDF\u5F3A\u5236\uFF1A\u672A\u7528 read-file \u8BFB\u53D6\u8FC7\u7684\u6587\u4EF6\u65E0\u6CD5 edit-file\uFF0C\u4F1A\u88AB\u62E6\u622A
1624
- - old_string \u5FC5\u987B\u4E0E\u6587\u4EF6\u4E2D\u7684\u5185\u5BB9**\u5B8C\u5168\u4E00\u81F4**\uFF08\u5305\u62EC\u7F29\u8FDB\u3001\u7A7A\u683C\u3001\u6362\u884C\u7B26\uFF09
1625
- - old_string \u4E0D\u552F\u4E00\u65F6\uFF0C\u5305\u542B\u66F4\u591A\u4E0A\u4E0B\u6587\u884C\uFF08\u5EFA\u8BAE 3-5 \u884C\uFF09\u4F7F\u5176\u552F\u4E00
1626
- - \u4E0D\u8981\u51ED\u8BB0\u5FC6\u731C\u6D4B\u6587\u4EF6\u5185\u5BB9\uFF0C\u5FC5\u987B\u57FA\u4E8E read-file \u7684\u5B9E\u9645\u8FD4\u56DE\u503C
1627
- - **write-file**\uFF1A\u521B\u5EFA\u65B0\u6587\u4EF6\u6216\u5B8C\u6574\u91CD\u5199\u6587\u4EF6\u3002\u4EC5\u5728\u521B\u5EFA\u65B0\u6587\u4EF6\u6216\u9700\u8981\u5927\u5E45\u91CD\u5199\u65F6\u4F7F\u7528\u3002
1628
- - **glob**\uFF1A\u6309\u6A21\u5F0F\u641C\u7D22\u6587\u4EF6\u8DEF\u5F84\u3002\u7528\u4E8E\u67E5\u627E\u6587\u4EF6\u4F4D\u7F6E\uFF08\u5982 \`**/*.ts\`\u3001\`src/**/config.*\`\uFF09\u3002
1629
- - **grep**\uFF1A\u5728\u6587\u4EF6\u5185\u5BB9\u4E2D\u641C\u7D22\u6B63\u5219\u8868\u8FBE\u5F0F\u3002\u7528\u4E8E\u67E5\u627E\u51FD\u6570\u5B9A\u4E49\u3001\u5F15\u7528\u3001\u7279\u5B9A\u4EE3\u7801\u6A21\u5F0F\u3002
1630
- - **bash**\uFF1A\u6267\u884C shell \u547D\u4EE4\u3002\u7528\u4E8E\u8FD0\u884C\u6784\u5EFA\u3001\u6D4B\u8BD5\u3001git \u64CD\u4F5C\u7B49\u3002\u4E0D\u8981\u7528 bash \u505A\u80FD\u7528\u4E0A\u8FF0\u5DE5\u5177\u5B8C\u6210\u7684\u4E8B\uFF08\u5982\u4E0D\u8981\u7528 cat \u8BFB\u6587\u4EF6\u3001\u4E0D\u8981\u7528 sed \u7F16\u8F91\u6587\u4EF6\u3001\u4E0D\u8981\u7528 find \u641C\u7D22\u6587\u4EF6\uFF09\u3002
1631
-
1632
- \u5173\u952E\u89C4\u5219\uFF1A
1633
- - **\u5148\u8BFB\u540E\u6539**\uFF1A\u4FEE\u6539\u6587\u4EF6\u524D\u5FC5\u987B read-file \u8BFB\u53D6\u8BE5\u6587\u4EF6\uFF08\u7CFB\u7EDF\u4F1A\u62E6\u622A\u672A\u8BFB\u53D6\u5C31 edit \u7684\u64CD\u4F5C\uFF09
1634
- - edit-file \u7684 old_string \u5FC5\u987B\u4ECE read-file \u8FD4\u56DE\u7684\u5185\u5BB9\u4E2D\u7CBE\u786E\u590D\u5236\uFF0C\u4E0D\u8981\u624B\u52A8\u8F93\u5165\u6216\u51ED\u8BB0\u5FC6
1635
- - \u4F18\u5148 edit-file \u7F16\u8F91\u5DF2\u6709\u6587\u4EF6\uFF0C\u800C\u975E write-file \u91CD\u5199
1636
- - \u4E0D\u8981\u521B\u5EFA\u4E0D\u5FC5\u8981\u7684\u65B0\u6587\u4EF6\uFF0C\u4F18\u5148\u5728\u73B0\u6709\u6587\u4EF6\u4E2D\u4FEE\u6539
1637
- - \u53EA\u505A\u5FC5\u8981\u7684\u6700\u5C0F\u6539\u52A8\uFF0C\u4E0D\u505A\u989D\u5916"\u6539\u8FDB"
1638
- - \u4E0D\u8981\u6DFB\u52A0\u7528\u6237\u672A\u8981\u6C42\u7684\u6CE8\u91CA\u3001\u6587\u6863\u3001\u7C7B\u578B\u6CE8\u89E3
1639
- - \u4E0D\u8981\u5F15\u5165\u5B89\u5168\u6F0F\u6D1E\uFF08\u6CE8\u5165\u3001XSS\u3001SQL \u6CE8\u5165\u7B49 OWASP Top 10\uFF09
1640
- - \u5F15\u7528\u4EE3\u7801\u65F6\u4F7F\u7528 \`\u6587\u4EF6\u8DEF\u5F84:\u884C\u53F7\` \u683C\u5F0F\uFF08\u5982 \`src/app.ts:42\`\uFF09\uFF0C\u65B9\u4FBF\u7528\u6237\u8DF3\u8F6C
1641
-
1642
- # \u4EA4\u4E92\u98CE\u683C
1643
-
1644
- - \u4FDD\u6301\u6280\u672F\u5BA2\u89C2\u6027\uFF0C\u57FA\u4E8E\u4E8B\u5B9E\u56DE\u7B54\uFF0C\u4E0D\u8FC7\u5EA6\u8D5E\u540C\u6216\u606D\u7EF4\u7528\u6237\uFF0C\u5FC5\u8981\u65F6\u76F4\u63A5\u6307\u51FA\u95EE\u9898
1645
- - \u4E0D\u786E\u5B9A\u65F6\u5148\u8C03\u67E5\u9A8C\u8BC1\uFF0C\u800C\u975E\u76F4\u89C9\u6027\u5730\u786E\u8BA4\u7528\u6237\u7684\u5047\u8BBE
1646
- - \u4E0D\u8981\u7ED9\u51FA\u65F6\u95F4\u9884\u4F30\uFF08"\u5927\u6982\u9700\u8981\u51E0\u5206\u949F"\u4E4B\u7C7B\uFF09
1647
- - \u56DE\u590D\u7B80\u6D01\uFF0C\u76F4\u63A5\u7ED9\u7ED3\u679C`;
1648
- }
1649
- var init_core = __esm({
1650
- "src/core/prompt/layers/core.ts"() {
1651
- "use strict";
1096
+ - \u4FDD\u6301\u6280\u672F\u5BA2\u89C2\u6027\uFF0C\u57FA\u4E8E\u4E8B\u5B9E\u56DE\u7B54\uFF0C\u4E0D\u8FC7\u5EA6\u8D5E\u540C\u6216\u606D\u7EF4\u7528\u6237\uFF0C\u5FC5\u8981\u65F6\u76F4\u63A5\u6307\u51FA\u95EE\u9898
1097
+ - \u4E0D\u786E\u5B9A\u65F6\u5148\u8C03\u67E5\u9A8C\u8BC1\uFF0C\u800C\u975E\u76F4\u89C9\u6027\u5730\u786E\u8BA4\u7528\u6237\u7684\u5047\u8BBE
1098
+ - \u4E0D\u8981\u7ED9\u51FA\u65F6\u95F4\u9884\u4F30\uFF08"\u5927\u6982\u9700\u8981\u51E0\u5206\u949F"\u4E4B\u7C7B\uFF09
1099
+ - \u56DE\u590D\u7B80\u6D01\uFF0C\u76F4\u63A5\u7ED9\u7ED3\u679C`;
1100
+ }
1101
+ var IS_WIN2;
1102
+ var init_core = __esm({
1103
+ "src/core/prompt/layers/core.ts"() {
1104
+ "use strict";
1105
+ IS_WIN2 = os2.platform() === "win32";
1652
1106
  }
1653
1107
  });
1654
1108
 
@@ -1658,8 +1112,8 @@ function buildPlanningPrompt() {
1658
1112
 
1659
1113
  \u5904\u7406\u7F16\u7A0B\u4EFB\u52A1\u65F6\uFF1A
1660
1114
  1. \u5148\u7528 read-file / grep / glob \u9605\u8BFB\u76F8\u5173\u4EE3\u7801\uFF0C\u7406\u89E3\u73B0\u6709\u903B\u8F91\u548C\u4E0A\u4E0B\u6587
1661
- 2. \u786E\u5B9A\u65B9\u6848\u540E\u76F4\u63A5\u5B9E\u65BD\uFF0C\u4E0D\u8981\u957F\u7BC7\u89E3\u91CA\u8BA1\u5212
1662
- 3. \u6539\u5B8C\u5373\u6B62\uFF0C\u4E0D\u8981\u81EA\u884C"\u9A8C\u8BC1"\u6216"\u6D4B\u8BD5"
1115
+ 2. \u5224\u65AD\u4EFB\u52A1\u7684\u590D\u6742\u7A0B\u5EA6\uFF0C\u7B80\u5355\u4EFB\u52A1\u76F4\u63A5\u6267\u884C\uFF0C\u590D\u6742\u4EFB\u52A1\u5148\u505A\u8BA1\u5212
1116
+ 3. \u5982\u679C\u7528\u6237\u7684\u8981\u6C42\u4E0D\u6E05\u6670\uFF0C\u4E00\u5B9A\u8981\u8BE2\u95EE\u7528\u6237\uFF0C\u786E\u5B9A\u7EC6\u8282
1663
1117
 
1664
1118
  \u591A\u6B65\u4EFB\u52A1\u7BA1\u7406\uFF1A
1665
1119
  - \u5BF9\u4E8E 3 \u4E2A\u4EE5\u4E0A\u6B65\u9AA4\u7684\u4EFB\u52A1\uFF0C\u4F7F\u7528 todo \u5DE5\u5177\u521B\u5EFA\u8BA1\u5212\u518D\u9010\u6B65\u6267\u884C
@@ -1667,11 +1121,8 @@ function buildPlanningPrompt() {
1667
1121
  - \u6BCF\u6B65\u5B8C\u6210\u540E\u68C0\u67E5\u8BA1\u5212\uFF0C\u51B3\u5B9A\u4E0B\u4E00\u6B65
1668
1122
 
1669
1123
  \u4EE3\u7801\u8D28\u91CF\uFF1A
1670
- - \u4E0D\u8981\u5199\u4E0D\u5FC5\u8981\u7684\u6D4B\u8BD5\uFF1B\u5982\u679C\u8981\u5199\u6D4B\u8BD5\uFF0C\u5148\u786E\u8BA4\u9879\u76EE\u7684\u6D4B\u8BD5\u6846\u67B6\u548C\u4F9D\u8D56\uFF0C\u786E\u4FDD\u80FD\u5B9E\u9645\u8FD0\u884C
1671
- - \u4E0D\u8981\u8FC7\u5EA6\u5DE5\u7A0B\u5316\uFF1A\u4E0D\u8981\u4E3A\u4E00\u6B21\u6027\u64CD\u4F5C\u521B\u5EFA\u62BD\u8C61\u3001\u4E0D\u8981\u4E3A\u5047\u8BBE\u7684\u672A\u6765\u9700\u6C42\u8BBE\u8BA1
1672
- - \u4E0D\u8981\u4E3A\u4E0D\u53EF\u80FD\u53D1\u751F\u7684\u573A\u666F\u6DFB\u52A0\u9519\u8BEF\u5904\u7406\uFF1B\u53EA\u5728\u7CFB\u7EDF\u8FB9\u754C\uFF08\u7528\u6237\u8F93\u5165\u3001\u5916\u90E8 API\uFF09\u505A\u6821\u9A8C
1673
1124
  - \u5982\u679C\u5220\u9664\u4E86\u4EE3\u7801\uFF0C\u5C31\u5F7B\u5E95\u5220\u9664\uFF0C\u4E0D\u8981\u7559\u6CE8\u91CA\u8BF4"\u5DF2\u79FB\u9664"\uFF0C\u4E0D\u8981\u4FDD\u7559\u672A\u4F7F\u7528\u7684\u517C\u5BB9\u6027\u53D8\u91CF
1674
- - \u4E09\u884C\u76F8\u4F3C\u4EE3\u7801\u4F18\u4E8E\u4E00\u4E2A\u8FC7\u65E9\u7684\u62BD\u8C61`;
1125
+ - \u4E0D\u8981\u7559\u4E0BTODO\u7136\u540E\u653E\u7740\u4E0D\u7BA1`;
1675
1126
  }
1676
1127
  var init_planning = __esm({
1677
1128
  "src/core/prompt/layers/planning.ts"() {
@@ -1695,7 +1146,7 @@ function buildParallelPrompt() {
1695
1146
  \u6B63\u786E\uFF1Aspawn-agents \u540C\u65F6\u8BFB auth controller\u3001auth service\u3001auth middleware\u3001auth types
1696
1147
  \u9519\u8BEF\uFF1A\u5148 read-file controller\uFF0C\u518D read-file service\uFF0C\u518D read-file middleware...
1697
1148
 
1698
- \u6BCF\u4E2A\u5B50 Agent \u6709\u72EC\u7ACB\u5BF9\u8BDD\uFF0C\u9ED8\u8BA4\u53EF\u7528 read-file\u3001glob\u3001grep\u3001memo\u3002`;
1149
+ \u6BCF\u4E2A\u5B50 Agent \u6709\u72EC\u7ACB\u5BF9\u8BDD\uFF0C\u9ED8\u8BA4\u53EF\u7528 read-file\u3001glob\u3001grep\u3002`;
1699
1150
  }
1700
1151
  var init_parallel = __esm({
1701
1152
  "src/core/prompt/layers/parallel.ts"() {
@@ -1728,6 +1179,24 @@ var init_git = __esm({
1728
1179
  }
1729
1180
  });
1730
1181
 
1182
+ // src/core/prompt/layers/agents.ts
1183
+ function buildAgentsPrompt(agents) {
1184
+ if (agents.length === 0) return null;
1185
+ const agentList = agents.map((a) => `- **${a.name}**\uFF1A${a.description}`).join("\n");
1186
+ return `# \u5B50 Agent
1187
+
1188
+ \u4F60\u53EF\u4EE5\u901A\u8FC7 dispatch \u5DE5\u5177\u8C03\u5EA6\u4EE5\u4E0B\u4E13\u7528\u5B50 Agent\uFF1A
1189
+
1190
+ ${agentList}
1191
+
1192
+ \u4F7F\u7528\u573A\u666F\uFF1A\u5F53\u4EFB\u52A1\u9700\u8981\u4E13\u4E1A\u89D2\u8272\uFF08\u5982\u4EE3\u7801\u5BA1\u67E5\u3001\u67B6\u6784\u5206\u6790\uFF09\u4E14\u5B50 Agent \u7684\u80FD\u529B\u6BD4\u4F60\u76F4\u63A5\u505A\u66F4\u5408\u9002\u65F6\uFF0C\u4F7F\u7528 dispatch \u59D4\u6D3E\u3002`;
1193
+ }
1194
+ var init_agents = __esm({
1195
+ "src/core/prompt/layers/agents.ts"() {
1196
+ "use strict";
1197
+ }
1198
+ });
1199
+
1731
1200
  // src/core/prompt/layers/project.ts
1732
1201
  import * as fs7 from "fs/promises";
1733
1202
  import * as path7 from "path";
@@ -1769,7 +1238,7 @@ function isGitRepo() {
1769
1238
  return false;
1770
1239
  }
1771
1240
  }
1772
- async function buildPrompt(config) {
1241
+ async function buildPrompt(config, agents) {
1773
1242
  const layers = [];
1774
1243
  layers.push(buildCorePrompt());
1775
1244
  if (config.features.planning_layer === "on") {
@@ -1782,6 +1251,12 @@ async function buildPrompt(config) {
1782
1251
  if (config.features.parallel_agents === "on") {
1783
1252
  layers.push(buildParallelPrompt());
1784
1253
  }
1254
+ if (agents && agents.length > 0) {
1255
+ const agentsPrompt = buildAgentsPrompt(agents);
1256
+ if (agentsPrompt) {
1257
+ layers.push(agentsPrompt);
1258
+ }
1259
+ }
1785
1260
  const projectPrompt = await buildProjectPrompt();
1786
1261
  if (projectPrompt) {
1787
1262
  layers.push(projectPrompt);
@@ -1800,6 +1275,7 @@ var init_builder = __esm({
1800
1275
  init_planning();
1801
1276
  init_parallel();
1802
1277
  init_git();
1278
+ init_agents();
1803
1279
  init_project();
1804
1280
  }
1805
1281
  });
@@ -1854,74 +1330,266 @@ var init_todo_store = __esm({
1854
1330
  }
1855
1331
  });
1856
1332
 
1857
- // src/core/memo-store.ts
1858
- var MAX_ENTRIES, MAX_CONTENT_LENGTH, MemoStore;
1859
- var init_memo_store = __esm({
1860
- "src/core/memo-store.ts"() {
1333
+ // src/core/sub-agents/registry.ts
1334
+ var SubAgentConfigRegistry;
1335
+ var init_registry2 = __esm({
1336
+ "src/core/sub-agents/registry.ts"() {
1861
1337
  "use strict";
1862
- MAX_ENTRIES = 30;
1863
- MAX_CONTENT_LENGTH = 3e3;
1864
- MemoStore = class {
1865
- entries = /* @__PURE__ */ new Map();
1866
- write(key, content, author = "agent", summary) {
1867
- const trimmed = content.slice(0, MAX_CONTENT_LENGTH);
1868
- const entry = {
1869
- key,
1870
- summary: summary || content.slice(0, 80).replace(/\n/g, " "),
1871
- content: trimmed,
1872
- author,
1873
- updatedAt: Date.now()
1874
- };
1875
- if (!this.entries.has(key) && this.entries.size >= MAX_ENTRIES) {
1876
- let oldest = null;
1877
- let oldestTime = Infinity;
1878
- for (const [k, v] of this.entries) {
1879
- if (v.updatedAt < oldestTime) {
1880
- oldestTime = v.updatedAt;
1881
- oldest = k;
1882
- }
1883
- }
1884
- if (oldest) this.entries.delete(oldest);
1885
- }
1886
- this.entries.set(key, entry);
1887
- return entry;
1338
+ SubAgentConfigRegistry = class {
1339
+ configs = /* @__PURE__ */ new Map();
1340
+ register(config) {
1341
+ this.configs.set(config.name, config);
1888
1342
  }
1889
- read(key) {
1890
- return this.entries.get(key) ?? null;
1343
+ get(name) {
1344
+ return this.configs.get(name);
1345
+ }
1346
+ has(name) {
1347
+ return this.configs.has(name);
1891
1348
  }
1892
1349
  list() {
1893
- return [...this.entries.values()].map((e) => ({
1894
- key: e.key,
1895
- author: e.author,
1896
- summary: e.summary
1897
- }));
1350
+ return [...this.configs.values()];
1898
1351
  }
1899
- delete(key) {
1900
- return this.entries.delete(key);
1352
+ listNames() {
1353
+ return [...this.configs.keys()];
1901
1354
  }
1902
- clear() {
1903
- this.entries.clear();
1355
+ /**
1356
+ * 生成子 Agent 列表描述(用于 dispatch 工具的说明)
1357
+ */
1358
+ buildAgentListDescription() {
1359
+ if (this.configs.size === 0) return "\u6682\u65E0\u53EF\u7528\u5B50 Agent";
1360
+ return [...this.configs.values()].map((s) => `- ${s.name}: ${s.description}`).join("\n");
1361
+ }
1362
+ };
1363
+ }
1364
+ });
1365
+
1366
+ // src/core/sub-agents/presets.ts
1367
+ var presetAgents;
1368
+ var init_presets = __esm({
1369
+ "src/core/sub-agents/presets.ts"() {
1370
+ "use strict";
1371
+ presetAgents = [
1372
+ {
1373
+ name: "reviewer",
1374
+ description: "\u4EE3\u7801\u5BA1\u67E5\uFF1A\u53D1\u73B0 bug\u3001\u5B89\u5168\u6F0F\u6D1E\u3001\u6027\u80FD\u95EE\u9898",
1375
+ prompt: `\u4F60\u662F\u4EE3\u7801\u5BA1\u67E5\u4E13\u5BB6\u3002\u5BA1\u67E5\u7528\u6237\u6307\u5B9A\u7684\u4EE3\u7801\uFF0C\u8F93\u51FA\u53D1\u73B0\u7684\u95EE\u9898\u3002
1376
+
1377
+ \u5BA1\u67E5\u7EF4\u5EA6\uFF08\u6309\u4F18\u5148\u7EA7\uFF09\uFF1A
1378
+ 1. \u6B63\u786E\u6027\uFF1A\u903B\u8F91\u9519\u8BEF\u3001\u8FB9\u754C\u6761\u4EF6\u3001\u7A7A\u503C/\u672A\u5B9A\u4E49\u5904\u7406
1379
+ 2. \u5B89\u5168\uFF1A\u6CE8\u5165\u3001XSS\u3001\u654F\u611F\u4FE1\u606F\u6CC4\u9732\u3001\u6743\u9650\u68C0\u67E5
1380
+ 3. \u6027\u80FD\uFF1A\u4E0D\u5FC5\u8981\u7684\u5FAA\u73AF\u3001\u5185\u5B58\u6CC4\u6F0F\u3001N+1 \u67E5\u8BE2
1381
+ 4. \u53EF\u7EF4\u62A4\u6027\uFF1A\u547D\u540D\u3001\u91CD\u590D\u4EE3\u7801\u3001\u8FC7\u5EA6\u590D\u6742
1382
+
1383
+ \u8F93\u51FA\u683C\u5F0F\uFF1A
1384
+ - \u6BCF\u4E2A\u95EE\u9898\uFF1A\u6587\u4EF6\u8DEF\u5F84:\u884C\u53F7 + \u95EE\u9898\u63CF\u8FF0 + \u5EFA\u8BAE\u4FEE\u590D
1385
+ - \u6CA1\u6709\u95EE\u9898\u5C31\u8BF4\u6CA1\u6709\u95EE\u9898\uFF0C\u4E0D\u8981\u786C\u51D1
1386
+ - \u4E0D\u8981\u91CD\u5199\u4EE3\u7801\uFF0C\u53EA\u6307\u51FA\u95EE\u9898\u548C\u4FEE\u590D\u65B9\u5411`,
1387
+ tools: ["read-file", "glob", "grep"],
1388
+ max_turns: 10,
1389
+ timeout: 60
1390
+ },
1391
+ {
1392
+ name: "researcher",
1393
+ description: "\u4EE3\u7801\u5E93\u7814\u7A76\uFF1A\u6DF1\u5EA6\u5206\u6790\u67B6\u6784\u3001\u4F9D\u8D56\u548C\u5B9E\u73B0\u7EC6\u8282",
1394
+ prompt: `\u4F60\u662F\u4EE3\u7801\u5E93\u7814\u7A76\u5458\u3002\u6DF1\u5165\u5206\u6790\u7528\u6237\u6307\u5B9A\u7684\u4EE3\u7801\u5E93\u6216\u6A21\u5757\uFF0C\u8F93\u51FA\u7ED3\u6784\u5316\u7684\u5206\u6790\u62A5\u544A\u3002
1395
+
1396
+ \u5206\u6790\u65B9\u6CD5\uFF1A
1397
+ 1. \u5148 glob \u4E86\u89E3\u6587\u4EF6\u7ED3\u6784
1398
+ 2. grep \u641C\u7D22\u5173\u952E\u5165\u53E3\u70B9\u3001\u5BFC\u51FA\u3001\u4F9D\u8D56
1399
+ 3. read-file \u9605\u8BFB\u6838\u5FC3\u6587\u4EF6
1400
+
1401
+ \u8F93\u51FA\u5185\u5BB9\uFF1A
1402
+ - \u6A21\u5757\u804C\u8D23\u548C\u8FB9\u754C
1403
+ - \u5173\u952E\u6587\u4EF6\u53CA\u5176\u4F5C\u7528
1404
+ - \u6570\u636E\u6D41\u548C\u8C03\u7528\u94FE
1405
+ - \u5916\u90E8\u4F9D\u8D56
1406
+ - \u5982\u6709\u7528\u6237\u5177\u4F53\u95EE\u9898\uFF0C\u9488\u5BF9\u6027\u56DE\u7B54`,
1407
+ tools: ["read-file", "glob", "grep"],
1408
+ max_turns: 15,
1409
+ timeout: 120
1410
+ },
1411
+ {
1412
+ name: "refactor",
1413
+ description: "\u91CD\u6784\u4E13\u5BB6\uFF1A\u5206\u6790\u4EE3\u7801\u7ED3\u6784\u5E76\u5B9E\u65BD\u91CD\u6784",
1414
+ prompt: `\u4F60\u662F\u91CD\u6784\u4E13\u5BB6\u3002\u5206\u6790\u7528\u6237\u6307\u5B9A\u7684\u4EE3\u7801\uFF0C\u627E\u51FA\u53EF\u91CD\u6784\u7684\u70B9\u5E76\u5B9E\u65BD\u91CD\u6784\u3002
1415
+
1416
+ \u91CD\u6784\u539F\u5219\uFF1A
1417
+ - \u53EA\u505A\u6709\u660E\u786E\u6536\u76CA\u7684\u91CD\u6784\uFF08\u6D88\u9664\u91CD\u590D\u3001\u964D\u4F4E\u590D\u6742\u5EA6\u3001\u6539\u5584\u547D\u540D\uFF09
1418
+ - \u4FDD\u6301\u884C\u4E3A\u4E0D\u53D8\uFF0C\u4E0D\u6DFB\u52A0\u65B0\u529F\u80FD
1419
+ - \u6BCF\u6B21\u53EA\u505A\u4E00\u4E2A\u91CD\u6784\uFF0C\u4E0D\u8981\u540C\u65F6\u6539\u592A\u591A
1420
+ - \u4FEE\u6539\u524D\u5FC5\u987B read-file \u786E\u8BA4\u5F53\u524D\u5185\u5BB9
1421
+
1422
+ \u5E38\u89C1\u91CD\u6784\uFF1A
1423
+ - \u63D0\u53D6\u91CD\u590D\u4EE3\u7801\u4E3A\u51FD\u6570
1424
+ - \u7B80\u5316\u8FC7\u6DF1\u7684\u5D4C\u5957\uFF08\u63D0\u524D\u8FD4\u56DE\uFF09
1425
+ - \u62C6\u5206\u8FC7\u5927\u7684\u51FD\u6570
1426
+ - \u6539\u5584\u547D\u540D\u4F7F\u610F\u56FE\u66F4\u6E05\u6670`,
1427
+ tools: ["read-file", "write-file", "edit-file", "glob", "grep"],
1428
+ max_turns: 15,
1429
+ timeout: 120
1430
+ }
1431
+ ];
1432
+ }
1433
+ });
1434
+
1435
+ // src/core/sub-agents/loader.ts
1436
+ import * as fs9 from "fs";
1437
+ import * as path8 from "path";
1438
+ import * as os3 from "os";
1439
+ import { parse as parseYaml2 } from "yaml";
1440
+ function loadAgentYaml(filePath) {
1441
+ try {
1442
+ const content = fs9.readFileSync(filePath, "utf-8");
1443
+ const parsed = parseYaml2(content);
1444
+ if (!parsed || typeof parsed !== "object") return null;
1445
+ if (!parsed["name"] || !parsed["prompt"] || !parsed["tools"]) return null;
1446
+ return {
1447
+ name: parsed["name"],
1448
+ description: parsed["description"] || "",
1449
+ prompt: parsed["prompt"],
1450
+ tools: parsed["tools"],
1451
+ max_turns: parsed["max_turns"],
1452
+ timeout: parsed["timeout"],
1453
+ model: parsed["model"]
1454
+ };
1455
+ } catch {
1456
+ return null;
1457
+ }
1458
+ }
1459
+ function loadAgentsFromDir(dir) {
1460
+ try {
1461
+ if (!fs9.existsSync(dir)) return [];
1462
+ const files = fs9.readdirSync(dir).filter((f) => f.endsWith(".yaml") || f.endsWith(".yml"));
1463
+ const agents = [];
1464
+ for (const file of files) {
1465
+ const agent = loadAgentYaml(path8.join(dir, file));
1466
+ if (agent) agents.push(agent);
1467
+ }
1468
+ return agents;
1469
+ } catch {
1470
+ return [];
1471
+ }
1472
+ }
1473
+ function loadAllAgentConfigs() {
1474
+ const configMap = /* @__PURE__ */ new Map();
1475
+ for (const agent of presetAgents) {
1476
+ configMap.set(agent.name, agent);
1477
+ }
1478
+ const globalDir = path8.join(os3.homedir(), ".zencode", "agents");
1479
+ for (const agent of loadAgentsFromDir(globalDir)) {
1480
+ configMap.set(agent.name, agent);
1481
+ }
1482
+ const projectDir = path8.resolve(".zencode", "agents");
1483
+ for (const agent of loadAgentsFromDir(projectDir)) {
1484
+ configMap.set(agent.name, agent);
1485
+ }
1486
+ return [...configMap.values()];
1487
+ }
1488
+ var init_loader = __esm({
1489
+ "src/core/sub-agents/loader.ts"() {
1490
+ "use strict";
1491
+ init_presets();
1492
+ }
1493
+ });
1494
+
1495
+ // src/core/skills/registry.ts
1496
+ var SkillRegistry;
1497
+ var init_registry3 = __esm({
1498
+ "src/core/skills/registry.ts"() {
1499
+ "use strict";
1500
+ SkillRegistry = class {
1501
+ skills = /* @__PURE__ */ new Map();
1502
+ register(skill) {
1503
+ this.skills.set(skill.name, skill);
1504
+ }
1505
+ get(name) {
1506
+ return this.skills.get(name);
1507
+ }
1508
+ has(name) {
1509
+ return this.skills.has(name);
1510
+ }
1511
+ list() {
1512
+ return [...this.skills.values()];
1513
+ }
1514
+ listNames() {
1515
+ return [...this.skills.keys()];
1904
1516
  }
1905
1517
  /**
1906
- * 生成备忘录索引(注入 Coder 任务中)
1907
- * 只输出 key + summary,清爽紧凑
1908
- * Coder 需要详情时用 memo read <key> 获取完整内容
1518
+ * 展开 skill 的 prompt 模板,替换 $ARGS 为用户参数
1909
1519
  */
1910
- buildIndex() {
1911
- if (this.entries.size === 0) return null;
1912
- return [...this.entries.values()].map((e) => `[${e.key}] ${e.summary}`).join("\n");
1520
+ expandPrompt(skill, args) {
1521
+ if (args && skill.prompt.includes("$ARGS")) {
1522
+ return skill.prompt.replace(/\$ARGS/g, args);
1523
+ }
1524
+ if (args) {
1525
+ return `${skill.prompt}
1526
+
1527
+ \u7528\u6237\u8865\u5145: ${args}`;
1528
+ }
1529
+ return skill.prompt;
1913
1530
  }
1914
1531
  };
1915
1532
  }
1916
1533
  });
1917
1534
 
1535
+ // src/core/skills/loader.ts
1536
+ import * as fs10 from "fs";
1537
+ import * as path9 from "path";
1538
+ import * as os4 from "os";
1539
+ import { parse as parseYaml3 } from "yaml";
1540
+ function loadSkillYaml(filePath) {
1541
+ try {
1542
+ const content = fs10.readFileSync(filePath, "utf-8");
1543
+ const parsed = parseYaml3(content);
1544
+ if (!parsed || typeof parsed !== "object") return null;
1545
+ if (!parsed["name"] || !parsed["prompt"]) return null;
1546
+ return {
1547
+ name: parsed["name"],
1548
+ description: parsed["description"] || "",
1549
+ prompt: parsed["prompt"]
1550
+ };
1551
+ } catch {
1552
+ return null;
1553
+ }
1554
+ }
1555
+ function loadSkillsFromDir(dir) {
1556
+ try {
1557
+ if (!fs10.existsSync(dir)) return [];
1558
+ const files = fs10.readdirSync(dir).filter((f) => f.endsWith(".yaml") || f.endsWith(".yml"));
1559
+ const skills = [];
1560
+ for (const file of files) {
1561
+ const skill = loadSkillYaml(path9.join(dir, file));
1562
+ if (skill) skills.push(skill);
1563
+ }
1564
+ return skills;
1565
+ } catch {
1566
+ return [];
1567
+ }
1568
+ }
1569
+ function loadAllSkills() {
1570
+ const skillMap = /* @__PURE__ */ new Map();
1571
+ const globalDir = path9.join(os4.homedir(), ".zencode", "skills");
1572
+ for (const skill of loadSkillsFromDir(globalDir)) {
1573
+ skillMap.set(skill.name, skill);
1574
+ }
1575
+ const projectDir = path9.resolve(".zencode", "skills");
1576
+ for (const skill of loadSkillsFromDir(projectDir)) {
1577
+ skillMap.set(skill.name, skill);
1578
+ }
1579
+ return [...skillMap.values()];
1580
+ }
1581
+ var init_loader2 = __esm({
1582
+ "src/core/skills/loader.ts"() {
1583
+ "use strict";
1584
+ }
1585
+ });
1586
+
1918
1587
  // src/core/sub-agent.ts
1919
1588
  var DEFAULT_TIMEOUT_MS, SubAgent;
1920
1589
  var init_sub_agent = __esm({
1921
1590
  "src/core/sub-agent.ts"() {
1922
1591
  "use strict";
1923
1592
  init_conversation();
1924
- init_auto_memo();
1925
1593
  init_read_tracker();
1926
1594
  DEFAULT_TIMEOUT_MS = 12e4;
1927
1595
  SubAgent = class {
@@ -1932,8 +1600,7 @@ var init_sub_agent = __esm({
1932
1600
  allowedTools;
1933
1601
  maxTurns;
1934
1602
  timeoutMs;
1935
- memoStore;
1936
- constructor(client, registry, config, task, allowedTools = ["read-file", "glob", "grep", "memo"], maxTurns = 10, timeoutMs = DEFAULT_TIMEOUT_MS, memoStore) {
1603
+ constructor(client, registry, config, task, allowedTools = ["read-file", "glob", "grep"], maxTurns = 10, timeoutMs = DEFAULT_TIMEOUT_MS) {
1937
1604
  this.client = client;
1938
1605
  this.registry = registry;
1939
1606
  this.config = config;
@@ -1941,7 +1608,6 @@ var init_sub_agent = __esm({
1941
1608
  this.allowedTools = allowedTools.filter((t) => t !== "spawn-agents" && t !== "todo");
1942
1609
  this.maxTurns = Math.min(maxTurns, 15);
1943
1610
  this.timeoutMs = timeoutMs;
1944
- this.memoStore = memoStore;
1945
1611
  }
1946
1612
  async run() {
1947
1613
  return Promise.race([
@@ -1960,17 +1626,8 @@ var init_sub_agent = __esm({
1960
1626
  async execute() {
1961
1627
  const conversation = new Conversation();
1962
1628
  const readTracker = new ReadTracker();
1963
- let systemPrompt = `\u4F60\u662F ZenCode \u5B50 Agent\u3002\u4F60\u7684\u4EFB\u52A1\uFF1A${this.task}
1629
+ const systemPrompt = `\u4F60\u662F ZenCode \u5B50 Agent\u3002\u4F60\u7684\u4EFB\u52A1\uFF1A${this.task}
1964
1630
  \u5B8C\u6210\u540E\u76F4\u63A5\u8FD4\u56DE\u7ED3\u679C\uFF0C\u4E0D\u8981\u591A\u4F59\u89E3\u91CA\u3002`;
1965
- if (this.memoStore) {
1966
- const index = this.memoStore.buildIndex();
1967
- if (index) {
1968
- systemPrompt += `
1969
-
1970
- [\u5171\u4EAB\u5907\u5FD8\u5F55 - \u53EF\u7528 memo read \u8BFB\u53D6\u8BE6\u60C5]
1971
- ${index}`;
1972
- }
1973
- }
1974
1631
  conversation.setSystemPrompt(systemPrompt);
1975
1632
  conversation.addUserMessage(this.task);
1976
1633
  const tools = this.registry.toToolDefinitions(this.allowedTools);
@@ -2027,7 +1684,6 @@ ${index}`;
2027
1684
  params,
2028
1685
  this.config.max_tool_output
2029
1686
  );
2030
- autoMemoForTool(this.memoStore, toolName, params, result.content);
2031
1687
  if (toolName === "read-file") {
2032
1688
  readTracker.markRead(params["path"]);
2033
1689
  } else if (toolName === "write-file") {
@@ -2050,7 +1706,7 @@ ${index}`;
2050
1706
  });
2051
1707
 
2052
1708
  // src/tools/spawn-agents.ts
2053
- function createSpawnAgentsTool(client, registry, config, tracker, memoStore) {
1709
+ function createSpawnAgentsTool(client, registry, config, tracker) {
2054
1710
  return {
2055
1711
  name: "spawn-agents",
2056
1712
  description: "\u5E76\u884C\u542F\u52A8\u591A\u4E2A\u5B50 Agent \u6267\u884C\u4EFB\u52A1\u3002\u6BCF\u4E2A\u5B50 Agent \u6709\u72EC\u7ACB\u5BF9\u8BDD\uFF0C\u9ED8\u8BA4\u53EA\u80FD\u7528\u53EA\u8BFB\u5DE5\u5177 (read-file, glob, grep)\u3002\u9002\u7528\u4E8E\u540C\u65F6\u8BFB\u53D6\u548C\u5206\u6790\u591A\u4E2A\u6587\u4EF6\u3001\u641C\u7D22\u591A\u4E2A\u6A21\u5F0F\u7B49\u573A\u666F\u3002",
@@ -2104,7 +1760,7 @@ function createSpawnAgentsTool(client, registry, config, tracker, memoStore) {
2104
1760
  if (tools.length === 0) {
2105
1761
  tools = DEFAULT_TOOLS.filter((t) => autoTools.includes(t));
2106
1762
  }
2107
- return new SubAgent(client, registry, config, task.description, tools, maxTurns, void 0, memoStore);
1763
+ return new SubAgent(client, registry, config, task.description, tools, maxTurns);
2108
1764
  });
2109
1765
  const wrappedRuns = agents.map(
2110
1766
  (agent) => agent.run().then(
@@ -2145,7 +1801,7 @@ var init_spawn_agents = __esm({
2145
1801
  "src/tools/spawn-agents.ts"() {
2146
1802
  "use strict";
2147
1803
  init_sub_agent();
2148
- DEFAULT_TOOLS = ["read-file", "glob", "grep", "memo"];
1804
+ DEFAULT_TOOLS = ["read-file", "glob", "grep"];
2149
1805
  MAX_CONCURRENT = 10;
2150
1806
  MAX_TURNS_LIMIT = 15;
2151
1807
  }
@@ -2219,141 +1875,302 @@ ${lines.join("\n")}`
2219
1875
  if (!item) {
2220
1876
  return { content: `\u9519\u8BEF\uFF1A\u672A\u627E\u5230\u6761\u76EE "${id}"` };
2221
1877
  }
2222
- const icon = item.status === "completed" ? "\u25CF" : item.status === "in-progress" ? "\u25D0" : "\u25CB";
2223
- return {
2224
- content: `\u5DF2\u66F4\u65B0\uFF1A${icon} [${item.id}] ${item.title} \u2192 ${item.status}`
2225
- };
2226
- }
2227
- case "list": {
2228
- const plan = store.list();
2229
- if (!plan) {
2230
- return { content: "\u5F53\u524D\u6CA1\u6709\u8BA1\u5212" };
1878
+ const icon = item.status === "completed" ? "\u25CF" : item.status === "in-progress" ? "\u25D0" : "\u25CB";
1879
+ return {
1880
+ content: `\u5DF2\u66F4\u65B0\uFF1A${icon} [${item.id}] ${item.title} \u2192 ${item.status}`
1881
+ };
1882
+ }
1883
+ case "list": {
1884
+ const plan = store.list();
1885
+ if (!plan) {
1886
+ return { content: "\u5F53\u524D\u6CA1\u6709\u8BA1\u5212" };
1887
+ }
1888
+ const completed = plan.items.filter(
1889
+ (i) => i.status === "completed"
1890
+ ).length;
1891
+ const lines = plan.items.map((item) => {
1892
+ const icon = item.status === "completed" ? "\u25CF" : item.status === "in-progress" ? "\u25D0" : "\u25CB";
1893
+ return `${icon} [${item.id}] ${item.title}`;
1894
+ });
1895
+ return {
1896
+ content: `\u8BA1\u5212\u8FDB\u5EA6 ${completed}/${plan.items.length}\uFF1A
1897
+ ${lines.join("\n")}`
1898
+ };
1899
+ }
1900
+ case "clear": {
1901
+ store.clear();
1902
+ return { content: "\u8BA1\u5212\u5DF2\u6E05\u7A7A" };
1903
+ }
1904
+ default:
1905
+ return {
1906
+ content: `\u9519\u8BEF\uFF1A\u672A\u77E5\u64CD\u4F5C "${action}"\u3002\u652F\u6301: create, update, list, clear`
1907
+ };
1908
+ }
1909
+ }
1910
+ };
1911
+ }
1912
+ var init_todo = __esm({
1913
+ "src/tools/todo.ts"() {
1914
+ "use strict";
1915
+ }
1916
+ });
1917
+
1918
+ // src/core/sub-agents/runner.ts
1919
+ var DEFAULT_MAX_TURNS, SubAgentRunner;
1920
+ var init_runner = __esm({
1921
+ "src/core/sub-agents/runner.ts"() {
1922
+ "use strict";
1923
+ init_conversation();
1924
+ init_permission();
1925
+ init_read_tracker();
1926
+ DEFAULT_MAX_TURNS = 15;
1927
+ SubAgentRunner = class {
1928
+ client;
1929
+ registry;
1930
+ config;
1931
+ agentConfig;
1932
+ constructor(client, registry, config, agentConfig) {
1933
+ this.client = client;
1934
+ this.registry = registry;
1935
+ this.config = config;
1936
+ this.agentConfig = agentConfig;
1937
+ }
1938
+ /**
1939
+ * 执行子 Agent 任务
1940
+ */
1941
+ async execute(task, context, callbacks = {}) {
1942
+ const timeoutMs = (this.agentConfig.timeout ?? 120) * 1e3;
1943
+ const maxTurns = this.agentConfig.max_turns ?? DEFAULT_MAX_TURNS;
1944
+ return Promise.race([
1945
+ this.run(task, context, maxTurns, callbacks),
1946
+ this.timeout(timeoutMs)
1947
+ ]);
1948
+ }
1949
+ timeout(ms) {
1950
+ return new Promise((_, reject) => {
1951
+ setTimeout(
1952
+ () => reject(new Error(`\u5B50 Agent "${this.agentConfig.name}" \u8D85\u65F6\uFF08${ms / 1e3}s\uFF09`)),
1953
+ ms
1954
+ );
1955
+ });
1956
+ }
1957
+ async run(task, context, maxTurns, callbacks) {
1958
+ const conversation = new Conversation();
1959
+ const readTracker = new ReadTracker();
1960
+ conversation.setSystemPrompt(this.agentConfig.prompt);
1961
+ let taskMessage = task;
1962
+ if (context) {
1963
+ taskMessage += `
1964
+
1965
+ [\u4E0A\u4E0B\u6587]
1966
+ ${context}`;
1967
+ }
1968
+ conversation.addUserMessage(taskMessage);
1969
+ const tools = this.registry.toToolDefinitions(this.agentConfig.tools);
1970
+ let lastContent = "";
1971
+ for (let turn = 0; turn < maxTurns; turn++) {
1972
+ const assistantMsg = await this.client.chatStream(
1973
+ conversation.getMessages(),
1974
+ tools.length > 0 ? tools : void 0,
1975
+ callbacks
1976
+ );
1977
+ conversation.addAssistantMessage(assistantMsg);
1978
+ if (!assistantMsg.tool_calls || assistantMsg.tool_calls.length === 0) {
1979
+ lastContent = assistantMsg.content || "";
1980
+ break;
1981
+ }
1982
+ for (const toolCall of assistantMsg.tool_calls) {
1983
+ const toolName = toolCall.function.name;
1984
+ if (!this.agentConfig.tools.includes(toolName)) {
1985
+ conversation.addToolResult(
1986
+ toolCall.id,
1987
+ `\u5B50 Agent "${this.agentConfig.name}" \u4E0D\u5141\u8BB8\u4F7F\u7528\u5DE5\u5177 "${toolName}"`
1988
+ );
1989
+ continue;
1990
+ }
1991
+ let params;
1992
+ try {
1993
+ params = JSON.parse(toolCall.function.arguments);
1994
+ } catch {
1995
+ conversation.addToolResult(toolCall.id, "\u53C2\u6570\u89E3\u6790\u5931\u8D25\uFF1A\u65E0\u6548\u7684 JSON");
1996
+ continue;
1997
+ }
1998
+ try {
1999
+ if (toolName === "edit-file") {
2000
+ const editPath = params["path"];
2001
+ if (!readTracker.hasRead(editPath)) {
2002
+ conversation.addToolResult(
2003
+ toolCall.id,
2004
+ `\u26A0 \u7981\u6B62\u7F16\u8F91\u672A\u8BFB\u53D6\u7684\u6587\u4EF6\u3002\u8BF7\u5148 read-file "${editPath}" \u4E86\u89E3\u5F53\u524D\u5185\u5BB9\uFF0C\u518D edit-file\u3002`
2005
+ );
2006
+ continue;
2007
+ }
2008
+ }
2009
+ if (toolName === "write-file") {
2010
+ const warn = readTracker.checkWriteOverwrite(
2011
+ params["path"],
2012
+ params["overwrite"]
2013
+ );
2014
+ if (warn) {
2015
+ conversation.addToolResult(toolCall.id, warn);
2016
+ continue;
2017
+ }
2018
+ }
2019
+ const permLevel = this.registry.getPermissionLevel(toolName);
2020
+ if (permLevel === "deny") {
2021
+ callbacks.onDenied?.(toolName);
2022
+ conversation.addToolResult(toolCall.id, `\u5DE5\u5177 "${toolName}" \u5DF2\u88AB\u7981\u6B62\u6267\u884C`);
2023
+ continue;
2024
+ }
2025
+ if (permLevel === "auto") {
2026
+ callbacks.onToolExecuting?.(toolName, params);
2027
+ }
2028
+ if (permLevel === "confirm") {
2029
+ const confirmResult = await confirmExecution(toolName, params);
2030
+ if (!confirmResult.approved) {
2031
+ callbacks.onDenied?.(toolName, confirmResult.feedback);
2032
+ const denyMsg = confirmResult.feedback ? `\u7528\u6237\u62D2\u7EDD\u4E86\u6B64\u64CD\u4F5C\uFF0C\u7528\u6237\u53CD\u9988: ${confirmResult.feedback}` : "\u7528\u6237\u62D2\u7EDD\u4E86\u6B64\u64CD\u4F5C";
2033
+ conversation.addToolResult(toolCall.id, denyMsg);
2034
+ continue;
2035
+ }
2036
+ }
2037
+ const result = await this.registry.execute(toolName, params, this.config.max_tool_output);
2038
+ callbacks.onToolResult?.(toolName, result.content, result.truncated ?? false);
2039
+ if (toolName === "read-file") {
2040
+ readTracker.markRead(params["path"]);
2041
+ } else if (toolName === "write-file") {
2042
+ readTracker.markWritten(params["path"]);
2043
+ }
2044
+ conversation.addToolResult(toolCall.id, result.content);
2045
+ } catch (err) {
2046
+ const msg = err instanceof Error ? err.message : String(err);
2047
+ conversation.addToolResult(toolCall.id, `\u5DE5\u5177\u6267\u884C\u5F02\u5E38\uFF1A${msg}`);
2048
+ }
2049
+ }
2050
+ if (assistantMsg.content) {
2051
+ lastContent = assistantMsg.content;
2231
2052
  }
2232
- const completed = plan.items.filter(
2233
- (i) => i.status === "completed"
2234
- ).length;
2235
- const lines = plan.items.map((item) => {
2236
- const icon = item.status === "completed" ? "\u25CF" : item.status === "in-progress" ? "\u25D0" : "\u25CB";
2237
- return `${icon} [${item.id}] ${item.title}`;
2238
- });
2239
- return {
2240
- content: `\u8BA1\u5212\u8FDB\u5EA6 ${completed}/${plan.items.length}\uFF1A
2241
- ${lines.join("\n")}`
2242
- };
2243
- }
2244
- case "clear": {
2245
- store.clear();
2246
- return { content: "\u8BA1\u5212\u5DF2\u6E05\u7A7A" };
2247
2053
  }
2248
- default:
2249
- return {
2250
- content: `\u9519\u8BEF\uFF1A\u672A\u77E5\u64CD\u4F5C "${action}"\u3002\u652F\u6301: create, update, list, clear`
2251
- };
2054
+ return lastContent;
2252
2055
  }
2253
- }
2254
- };
2255
- }
2256
- var init_todo = __esm({
2257
- "src/tools/todo.ts"() {
2258
- "use strict";
2056
+ };
2259
2057
  }
2260
2058
  });
2261
2059
 
2262
- // src/tools/memo.ts
2263
- function createMemoTool(store) {
2060
+ // src/tools/dispatch.ts
2061
+ function createDispatchTool(defaultClient, toolRegistry, config, agentRegistry, callbacks) {
2264
2062
  return {
2265
- name: "memo",
2266
- description: "\u5171\u4EAB\u5907\u5FD8\u5F55\uFF1A\u5728\u591A\u4E2A Agent \u4E4B\u95F4\u5171\u4EAB\u4FE1\u606F\u3002write \u5199\u5165\u53D1\u73B0/\u51B3\u7B56/\u6458\u8981\u4F9B\u5176\u4ED6 Agent \u8BFB\u53D6\uFF0Cread \u6309 key \u8BFB\u53D6\uFF0Clist \u67E5\u770B\u6240\u6709\u53EF\u7528\u6761\u76EE\u3002",
2063
+ name: "dispatch",
2064
+ description: "\u8C03\u5EA6\u5B50 Agent \u6267\u884C\u4E13\u95E8\u4EFB\u52A1\u3002\u5B50 Agent \u6709\u72EC\u7ACB\u5BF9\u8BDD\u548C\u4E13\u5C5E\u7CFB\u7EDF\u63D0\u793A\u8BCD\uFF0C\u9002\u7528\u4E8E\u9700\u8981\u4E13\u4E1A\u89D2\u8272\u7684\u573A\u666F\u3002",
2267
2065
  parameters: {
2268
2066
  type: "object",
2269
2067
  properties: {
2270
- action: {
2068
+ agent: {
2271
2069
  type: "string",
2272
- description: "\u64CD\u4F5C\u7C7B\u578B",
2273
- enum: ["write", "read", "list", "delete", "clear"]
2274
- },
2275
- key: {
2276
- type: "string",
2277
- description: "write/read/delete \u65F6\u5FC5\u586B\uFF1A\u5907\u5FD8\u5F55 key"
2070
+ description: `\u5B50 Agent \u540D\u79F0\u3002\u53EF\u9009: ${agentRegistry.listNames().join(", ")}`,
2071
+ enum: agentRegistry.listNames()
2278
2072
  },
2279
- content: {
2073
+ task: {
2280
2074
  type: "string",
2281
- description: "write \u65F6\u5FC5\u586B\uFF1A\u8981\u5199\u5165\u7684\u5185\u5BB9\uFF08\u6700\u591A 3000 \u5B57\u7B26\uFF09"
2075
+ description: "\u8981\u6267\u884C\u7684\u5177\u4F53\u4EFB\u52A1\u63CF\u8FF0"
2282
2076
  },
2283
- summary: {
2077
+ context: {
2284
2078
  type: "string",
2285
- description: "write \u65F6\u53EF\u9009\uFF1A\u4E00\u884C\u6458\u8981\uFF08\u4E0D\u586B\u5219\u81EA\u52A8\u622A\u53D6 content \u524D 80 \u5B57\u7B26\uFF09"
2079
+ description: "\u53EF\u9009\u7684\u989D\u5916\u4E0A\u4E0B\u6587\u4FE1\u606F\uFF08\u5982\u53C2\u8003\u6587\u4EF6\u8DEF\u5F84\u3001\u4F9D\u8D56\u5173\u7CFB\u7B49\uFF09"
2286
2080
  }
2287
2081
  },
2288
- required: ["action"]
2082
+ required: ["agent", "task"]
2289
2083
  },
2290
2084
  permissionLevel: "auto",
2291
2085
  async execute(params) {
2292
- const action = params["action"];
2293
- switch (action) {
2294
- case "write": {
2295
- const key = params["key"];
2296
- const content = params["content"];
2297
- const summary = params["summary"];
2298
- if (!key || !content) {
2299
- return { content: "\u9519\u8BEF\uFF1Awrite \u9700\u8981\u63D0\u4F9B key \u548C content" };
2300
- }
2301
- const entry = store.write(key, content, "agent", summary);
2302
- return { content: `\u5DF2\u5199\u5165 memo [${entry.key}]\uFF08${entry.content.length} \u5B57\u7B26\uFF09` };
2303
- }
2304
- case "read": {
2305
- const key = params["key"];
2306
- if (!key) {
2307
- return { content: "\u9519\u8BEF\uFF1Aread \u9700\u8981\u63D0\u4F9B key" };
2308
- }
2309
- const entry = store.read(key);
2310
- if (!entry) {
2311
- return { content: `memo [${key}] \u4E0D\u5B58\u5728` };
2312
- }
2313
- return { content: `[${entry.key}] by ${entry.author}:
2314
- ${entry.content}` };
2315
- }
2316
- case "list": {
2317
- const items = store.list();
2318
- if (items.length === 0) {
2319
- return { content: "\u5907\u5FD8\u5F55\u4E3A\u7A7A" };
2320
- }
2321
- const lines = items.map(
2322
- (item) => `[${item.key}] (${item.author}) ${item.summary}`
2323
- );
2324
- return { content: `\u5171 ${items.length} \u6761\u5907\u5FD8\u5F55\uFF1A
2325
- ${lines.join("\n")}` };
2326
- }
2327
- case "delete": {
2328
- const key = params["key"];
2329
- if (!key) {
2330
- return { content: "\u9519\u8BEF\uFF1Adelete \u9700\u8981\u63D0\u4F9B key" };
2331
- }
2332
- const ok = store.delete(key);
2333
- return { content: ok ? `\u5DF2\u5220\u9664 memo [${key}]` : `memo [${key}] \u4E0D\u5B58\u5728` };
2334
- }
2335
- case "clear": {
2336
- store.clear();
2337
- return { content: "\u5907\u5FD8\u5F55\u5DF2\u6E05\u7A7A" };
2338
- }
2339
- default:
2340
- return { content: `\u9519\u8BEF\uFF1A\u672A\u77E5\u64CD\u4F5C "${action}"\u3002\u652F\u6301: write, read, list, delete, clear` };
2086
+ const agentName = params["agent"];
2087
+ const task = params["task"];
2088
+ const context = params["context"];
2089
+ const agentConfig = agentRegistry.get(agentName);
2090
+ if (!agentConfig) {
2091
+ return { content: `\u9519\u8BEF\uFF1A\u672A\u627E\u5230\u5B50 Agent "${agentName}"\u3002\u53EF\u7528: ${agentRegistry.listNames().join(", ")}` };
2092
+ }
2093
+ let client = defaultClient;
2094
+ if (agentConfig.model) {
2095
+ client = createLLMClient({
2096
+ apiKey: agentConfig.model.api_key || config.api_key,
2097
+ baseURL: agentConfig.model.base_url || config.base_url,
2098
+ model: agentConfig.model.model || config.model,
2099
+ temperature: config.temperature,
2100
+ maxTokens: config.max_tokens
2101
+ });
2102
+ }
2103
+ const runner = new SubAgentRunner(client, toolRegistry, config, agentConfig);
2104
+ try {
2105
+ const result = await runner.execute(task, context, callbacks?.() ?? {});
2106
+ return { content: result || "\uFF08\u5B50 Agent \u6267\u884C\u5B8C\u6210\uFF0C\u65E0\u8F93\u51FA\uFF09" };
2107
+ } catch (err) {
2108
+ const msg = err instanceof Error ? err.message : String(err);
2109
+ return { content: `\u5B50 Agent \u6267\u884C\u9519\u8BEF\uFF1A${msg}` };
2341
2110
  }
2342
2111
  }
2343
2112
  };
2344
2113
  }
2345
- var init_memo = __esm({
2346
- "src/tools/memo.ts"() {
2114
+ var init_dispatch = __esm({
2115
+ "src/tools/dispatch.ts"() {
2347
2116
  "use strict";
2117
+ init_runner();
2118
+ init_client();
2348
2119
  }
2349
2120
  });
2350
2121
 
2351
2122
  // src/cli/tui/bridge.ts
2123
+ function gray(text) {
2124
+ return `${ANSI_GRAY}${text}${ANSI_RESET}`;
2125
+ }
2352
2126
  function createThinkFilter() {
2353
2127
  let inThink = false;
2354
2128
  let tagBuffer = "";
2355
- let lineStart = true;
2129
+ let thinkLineBuffer = "";
2130
+ let thinkHasVisibleContent = false;
2131
+ let thinkLastEmittedBlank = false;
2356
2132
  let postThink = false;
2133
+ function flushThinkLine(rawLine) {
2134
+ const normalized = rawLine.trim();
2135
+ if (!thinkHasVisibleContent && normalized.length === 0) return "";
2136
+ if (normalized.length === 0) {
2137
+ if (thinkLastEmittedBlank) return "";
2138
+ thinkLastEmittedBlank = true;
2139
+ return "\n";
2140
+ }
2141
+ thinkHasVisibleContent = true;
2142
+ thinkLastEmittedBlank = false;
2143
+ return `${gray(` ${normalized}`)}
2144
+ `;
2145
+ }
2146
+ function appendOutsideText(current, text) {
2147
+ if (!postThink) return current + text;
2148
+ let result = current;
2149
+ for (let i = 0; i < text.length; i++) {
2150
+ const ch = text[i];
2151
+ if (postThink && (ch === "\n" || ch === "\r" || ch === " " || ch === " ")) {
2152
+ continue;
2153
+ }
2154
+ postThink = false;
2155
+ result += text.slice(i);
2156
+ break;
2157
+ }
2158
+ return result;
2159
+ }
2160
+ function appendThinkText(current, text) {
2161
+ let result = current;
2162
+ for (let i = 0; i < text.length; i++) {
2163
+ const ch = text[i];
2164
+ if (ch === "\r") continue;
2165
+ if (ch === "\n") {
2166
+ result += flushThinkLine(thinkLineBuffer);
2167
+ thinkLineBuffer = "";
2168
+ } else {
2169
+ thinkLineBuffer += ch;
2170
+ }
2171
+ }
2172
+ return result;
2173
+ }
2357
2174
  return function filter(text) {
2358
2175
  let result = "";
2359
2176
  for (let i = 0; i < text.length; i++) {
@@ -2362,24 +2179,25 @@ function createThinkFilter() {
2362
2179
  tagBuffer += ch;
2363
2180
  if (tagBuffer === "<think>") {
2364
2181
  inThink = true;
2365
- lineStart = true;
2182
+ thinkLineBuffer = "";
2183
+ thinkHasVisibleContent = false;
2184
+ thinkLastEmittedBlank = false;
2366
2185
  tagBuffer = "";
2367
- result += `\u256D\u2500 \u{1F4AD} ${THINK_BORDER}
2186
+ result += `${gray("Thinking")}
2368
2187
  `;
2369
2188
  } else if (tagBuffer === "</think>") {
2370
2189
  inThink = false;
2371
2190
  postThink = true;
2372
2191
  tagBuffer = "";
2373
- result += `
2374
- \u2570${THINK_BORDER}\u2500\u2500\u2500
2375
-
2376
- `;
2377
- } else if (!"<think>".startsWith(tagBuffer) && !"</think>".startsWith(tagBuffer)) {
2378
- if (inThink && lineStart) {
2379
- result += "\u2502 ";
2380
- lineStart = false;
2192
+ if (thinkLineBuffer.length > 0) {
2193
+ result += flushThinkLine(thinkLineBuffer);
2194
+ thinkLineBuffer = "";
2381
2195
  }
2382
- result += tagBuffer;
2196
+ while (result.endsWith("\n\n\n")) result = result.slice(0, -1);
2197
+ result += "\n";
2198
+ } else if (!"<think>".startsWith(tagBuffer) && !"</think>".startsWith(tagBuffer)) {
2199
+ if (inThink) result = appendThinkText(result, tagBuffer);
2200
+ else result = appendOutsideText(result, tagBuffer);
2383
2201
  tagBuffer = "";
2384
2202
  }
2385
2203
  continue;
@@ -2388,34 +2206,20 @@ function createThinkFilter() {
2388
2206
  tagBuffer = "<";
2389
2207
  continue;
2390
2208
  }
2391
- if (inThink) {
2392
- if (lineStart) {
2393
- result += "\u2502 ";
2394
- lineStart = false;
2395
- }
2396
- result += ch;
2397
- if (ch === "\n") {
2398
- lineStart = true;
2399
- }
2400
- } else {
2401
- if (postThink) {
2402
- if (ch === "\n" || ch === "\r" || ch === " " || ch === " ") continue;
2403
- postThink = false;
2404
- }
2405
- result += ch;
2406
- }
2209
+ if (inThink) result = appendThinkText(result, ch);
2210
+ else result = appendOutsideText(result, ch);
2407
2211
  }
2408
2212
  return result;
2409
2213
  };
2410
2214
  }
2411
- function createTokenBatcher(dispatch) {
2215
+ function createTokenBatcher(dispatch, type) {
2412
2216
  let buffer = "";
2413
2217
  let timer = null;
2414
2218
  function flush() {
2415
2219
  if (buffer.length > 0) {
2416
2220
  const text = buffer;
2417
2221
  buffer = "";
2418
- dispatch({ type: "APPEND_CONTENT", text });
2222
+ dispatch({ type, text });
2419
2223
  }
2420
2224
  }
2421
2225
  function start() {
@@ -2460,8 +2264,10 @@ function registerConfirmToolId(toolName, id) {
2460
2264
  activeToolIds.set(toolName, id);
2461
2265
  }
2462
2266
  function createBridgeCallbacks(dispatch) {
2463
- const batcher = createTokenBatcher(dispatch);
2464
- const thinkFilter = createThinkFilter();
2267
+ const contentBatcher = createTokenBatcher(dispatch, "APPEND_CONTENT");
2268
+ const thoughtBatcher = createTokenBatcher(dispatch, "APPEND_THOUGHT");
2269
+ let inThink = false;
2270
+ let tagBuffer = "";
2465
2271
  activeToolIds = /* @__PURE__ */ new Map();
2466
2272
  streamingToolIds = /* @__PURE__ */ new Map();
2467
2273
  lastStreamingArgs = /* @__PURE__ */ new Map();
@@ -2480,12 +2286,41 @@ function createBridgeCallbacks(dispatch) {
2480
2286
  }
2481
2287
  return {
2482
2288
  onContent: (text) => {
2483
- const filtered = thinkFilter(text);
2484
- if (filtered) batcher.append(filtered);
2289
+ for (let i = 0; i < text.length; i++) {
2290
+ const ch = text[i];
2291
+ if (tagBuffer.length > 0 || ch === "<") {
2292
+ tagBuffer += ch;
2293
+ if (tagBuffer === "<think>") {
2294
+ contentBatcher.flush();
2295
+ inThink = true;
2296
+ tagBuffer = "";
2297
+ continue;
2298
+ } else if (tagBuffer === "</think>") {
2299
+ thoughtBatcher.flush();
2300
+ inThink = false;
2301
+ tagBuffer = "";
2302
+ continue;
2303
+ } else if (!"<think>".startsWith(tagBuffer) && !"</think>".startsWith(tagBuffer)) {
2304
+ if (inThink) {
2305
+ thoughtBatcher.append(tagBuffer);
2306
+ } else {
2307
+ contentBatcher.append(tagBuffer);
2308
+ }
2309
+ tagBuffer = "";
2310
+ }
2311
+ continue;
2312
+ }
2313
+ if (inThink) {
2314
+ thoughtBatcher.append(ch);
2315
+ } else {
2316
+ contentBatcher.append(ch);
2317
+ }
2318
+ }
2485
2319
  },
2486
2320
  onToolCallStreaming: (index, name, accumulatedArgs) => {
2487
2321
  if (!streamingToolIds.has(index)) {
2488
- batcher.flush();
2322
+ contentBatcher.flush();
2323
+ thoughtBatcher.flush();
2489
2324
  const id2 = `tool-${++toolCallCounter}`;
2490
2325
  streamingToolIds.set(index, id2);
2491
2326
  activeToolIds.set(name, id2);
@@ -2505,8 +2340,10 @@ function createBridgeCallbacks(dispatch) {
2505
2340
  },
2506
2341
  onToolExecuting: (name, params) => {
2507
2342
  flushStreamingUpdate();
2508
- batcher.flush();
2509
- batcher.pause();
2343
+ contentBatcher.flush();
2344
+ thoughtBatcher.flush();
2345
+ contentBatcher.pause();
2346
+ thoughtBatcher.pause();
2510
2347
  const existingId = activeToolIds.get(name);
2511
2348
  const id = existingId || `tool-${++toolCallCounter}`;
2512
2349
  activeToolIds.set(name, id);
@@ -2520,41 +2357,36 @@ function createBridgeCallbacks(dispatch) {
2520
2357
  if (isWriteTool) {
2521
2358
  const code = extractCodeFromArgs(name, lastStreamingArgs.get(id) || "");
2522
2359
  const codeLines = code ? code.split("\n").length : 0;
2523
- summary = codeLines > 0 ? `${codeLines} \u884C` : truncated ? "\u8F93\u51FA\u5DF2\u622A\u65AD" : `${lines.length} \u884C`;
2360
+ summary = codeLines > 0 ? `${codeLines} lines` : truncated ? "truncated" : `${lines.length} lines`;
2524
2361
  } else {
2525
- summary = truncated ? `\u8F93\u51FA\u5DF2\u622A\u65AD` : `${lines.length} \u884C`;
2362
+ summary = truncated ? `truncated` : `${lines.length} lines`;
2526
2363
  }
2527
2364
  const preview = lines.slice(0, 5).join("\n").slice(0, 200);
2528
2365
  const resultContent = lines.length > 5 || preview.length >= 200 ? preview + "..." : preview;
2529
2366
  dispatch({ type: "TOOL_RESULT", id, resultSummary: summary, resultContent });
2530
2367
  },
2531
- onCoderStart: () => {
2532
- batcher.flush();
2533
- dispatch({ type: "CODER_START" });
2534
- },
2535
- onCoderEnd: () => {
2536
- dispatch({ type: "CODER_END" });
2537
- },
2538
2368
  onDenied: (toolName, feedback) => {
2539
2369
  const id = activeToolIds.get(toolName) || `tool-${++toolCallCounter}`;
2540
2370
  dispatch({ type: "TOOL_DENIED", id, feedback });
2541
2371
  },
2542
2372
  onError: (err) => {
2543
- batcher.stop();
2373
+ contentBatcher.stop();
2374
+ thoughtBatcher.stop();
2544
2375
  dispatch({ type: "SET_ERROR", error: err.message });
2545
2376
  },
2546
- // Called internally when streaming is complete
2547
2377
  _stopBatcher: () => {
2548
- batcher.stop();
2378
+ contentBatcher.stop();
2379
+ thoughtBatcher.stop();
2549
2380
  }
2550
2381
  };
2551
2382
  }
2552
- var BATCH_INTERVAL_MS, THINK_BORDER, toolCallCounter, activeToolIds, streamingToolIds, lastStreamingArgs;
2383
+ var BATCH_INTERVAL_MS, ANSI_GRAY, ANSI_RESET, toolCallCounter, activeToolIds, streamingToolIds, lastStreamingArgs;
2553
2384
  var init_bridge = __esm({
2554
2385
  "src/cli/tui/bridge.ts"() {
2555
2386
  "use strict";
2556
2387
  BATCH_INTERVAL_MS = 64;
2557
- THINK_BORDER = "\u2500".repeat(40);
2388
+ ANSI_GRAY = "\x1B[90m";
2389
+ ANSI_RESET = "\x1B[0m";
2558
2390
  toolCallCounter = 0;
2559
2391
  activeToolIds = /* @__PURE__ */ new Map();
2560
2392
  streamingToolIds = /* @__PURE__ */ new Map();
@@ -2613,15 +2445,12 @@ var init_sub_agent_tracker = __esm({
2613
2445
  });
2614
2446
 
2615
2447
  // src/cli/tui/state.ts
2616
- function createInitialState(modelName, agentMode, collaboration) {
2448
+ function createInitialState(modelName) {
2617
2449
  return {
2618
2450
  messages: [],
2619
2451
  isRunning: false,
2620
2452
  error: void 0,
2621
- coderWorking: false,
2622
2453
  modelName,
2623
- agentMode,
2624
- collaboration,
2625
2454
  todoPlan: null,
2626
2455
  subAgentProgress: null
2627
2456
  };
@@ -2691,6 +2520,21 @@ function tuiReducer(state, action) {
2691
2520
  })
2692
2521
  };
2693
2522
  }
2523
+ case "APPEND_THOUGHT": {
2524
+ return {
2525
+ ...state,
2526
+ messages: updateLastAssistant(state.messages, (msg) => {
2527
+ const blocks = [...msg.blocks];
2528
+ const last = blocks[blocks.length - 1];
2529
+ if (last && last.type === "thought") {
2530
+ blocks[blocks.length - 1] = { type: "thought", text: last.text + action.text };
2531
+ } else {
2532
+ blocks.push({ type: "thought", text: action.text });
2533
+ }
2534
+ return { ...msg, blocks };
2535
+ })
2536
+ };
2537
+ }
2694
2538
  case "TOOL_EXECUTING": {
2695
2539
  return {
2696
2540
  ...state,
@@ -2836,14 +2680,8 @@ function tuiReducer(state, action) {
2836
2680
  return { ...state, error: action.error, isRunning: false };
2837
2681
  case "CLEAR_ERROR":
2838
2682
  return { ...state, error: void 0 };
2839
- case "CODER_START":
2840
- return { ...state, coderWorking: true };
2841
- case "CODER_END":
2842
- return { ...state, coderWorking: false };
2843
2683
  case "CLEAR_MESSAGES":
2844
2684
  return { ...state, messages: [] };
2845
- case "SET_MODE":
2846
- return { ...state, agentMode: action.agentMode, collaboration: action.collaboration };
2847
2685
  case "SET_TODO_PLAN":
2848
2686
  return { ...state, todoPlan: action.plan };
2849
2687
  case "SET_SUB_AGENT_PROGRESS":
@@ -2866,136 +2704,78 @@ import { jsx, jsxs } from "react/jsx-runtime";
2866
2704
  function getToolParamSummary(name, params) {
2867
2705
  switch (name) {
2868
2706
  case "bash":
2869
- return String(params["command"] || "").slice(0, 60);
2707
+ return String(params["command"] || "").slice(0, 100);
2870
2708
  case "read-file":
2871
2709
  case "write-file":
2872
2710
  case "edit-file":
2873
2711
  return String(params["path"] || "");
2874
2712
  case "glob":
2875
- return String(params["pattern"] || "");
2876
2713
  case "grep":
2877
2714
  return String(params["pattern"] || "");
2878
- case "send-to-coder":
2879
- return String(params["task"] || "").slice(0, 40);
2880
- case "spawn-agents": {
2881
- const tasks = params["tasks"];
2882
- if (!tasks) return "";
2883
- if (tasks.length <= 2) {
2884
- return tasks.map((t) => t.description.slice(0, 30)).join(", ");
2885
- }
2886
- return `${tasks.length} \u4E2A\u5E76\u884C\u4EFB\u52A1`;
2887
- }
2888
- case "todo": {
2889
- const action = String(params["action"] || "");
2890
- const id = params["id"] ? ` [${params["id"]}]` : "";
2891
- return `${action}${id}`;
2892
- }
2893
- case "memo": {
2894
- const action = String(params["action"] || "");
2895
- const key = params["key"] ? ` [${params["key"]}]` : "";
2896
- return `${action}${key}`;
2897
- }
2898
- default: {
2899
- const keys = Object.keys(params);
2900
- if (keys.length > 0 && keys[0]) {
2901
- return String(params[keys[0]] || "").slice(0, 40);
2902
- }
2903
- return "";
2904
- }
2905
- }
2906
- }
2907
- function getToolIcon(name) {
2908
- switch (name) {
2909
- case "bash":
2910
- return "$";
2911
- case "write-file":
2912
- return "+";
2913
- case "edit-file":
2914
- return "\xB1";
2915
- case "read-file":
2916
- return "\u{1F4C4}";
2917
- case "glob":
2918
- return "\u{1F50D}";
2919
- case "grep":
2920
- return "\u{1F50D}";
2921
- case "spawn-agents":
2922
- return "\u26A1";
2923
- case "todo":
2924
- return "\u{1F4CB}";
2925
- case "memo":
2926
- return "\u{1F4DD}";
2927
2715
  default:
2928
- return "\u2699";
2716
+ const keys = Object.keys(params);
2717
+ return keys.length > 0 ? String(params[keys[0]] || "").slice(0, 60) : "";
2929
2718
  }
2930
2719
  }
2931
- function getCodeContent(name, params) {
2932
- if (name === "write-file") {
2933
- return params["content"] || null;
2720
+ function truncateContent(text, maxLines) {
2721
+ const lines = text.split("\n");
2722
+ const result = [];
2723
+ for (let line of lines) {
2724
+ if (result.length >= maxLines) break;
2725
+ result.push(line.replace(/\r/g, "").replace(/\t/g, " "));
2934
2726
  }
2935
- if (name === "edit-file") {
2936
- return params["new_string"] || null;
2727
+ if (lines.length > maxLines) {
2728
+ result.push(`... (and ${lines.length - maxLines} more lines)`);
2937
2729
  }
2938
- return null;
2939
- }
2940
- function truncateCode(code, maxLines) {
2941
- const lines = code.split("\n");
2942
- if (lines.length <= maxLines) return code;
2943
- return lines.slice(0, maxLines).join("\n") + `
2944
- ... (\u5171 ${lines.length} \u884C)`;
2730
+ return result;
2945
2731
  }
2946
2732
  function ToolCallLine({ toolCall }) {
2947
- const { name, params, status, resultSummary, resultContent, denyFeedback } = toolCall;
2733
+ const { name, params, status, resultContent, denyFeedback } = toolCall;
2948
2734
  const summary = getToolParamSummary(name, params);
2949
- const icon = getToolIcon(name);
2950
- const isWriteTool = name === "write-file" || name === "edit-file";
2951
- const rawCode = isWriteTool && status === "done" ? getCodeContent(name, params) : null;
2952
- let statusNode;
2953
- let statusText = "";
2954
- switch (status) {
2955
- case "running":
2956
- statusNode = /* @__PURE__ */ jsx(Text, { color: "yellow", children: "\u23F3" });
2957
- break;
2958
- case "done":
2959
- statusNode = /* @__PURE__ */ jsx(Text, { color: "green", children: "\u2713" });
2960
- statusText = resultSummary || "";
2961
- break;
2962
- case "denied":
2963
- statusNode = /* @__PURE__ */ jsx(Text, { color: "red", children: "\u2717" });
2964
- statusText = "denied";
2965
- break;
2966
- case "confirming":
2967
- statusNode = /* @__PURE__ */ jsx(Text, { color: "yellow", children: "\u26A0" });
2968
- statusText = "[y/N]";
2969
- break;
2735
+ let borderColor = "#504945";
2736
+ let titleColor = "#fabd2f";
2737
+ let statusColor = "#fabd2f";
2738
+ let statusText = "RUNNING";
2739
+ if (status === "done") {
2740
+ borderColor = "#b8bb26";
2741
+ titleColor = "#b8bb26";
2742
+ statusColor = "#b8bb26";
2743
+ statusText = "DONE";
2744
+ } else if (status === "denied") {
2745
+ borderColor = "#fb4934";
2746
+ titleColor = "#fb4934";
2747
+ statusColor = "#fb4934";
2748
+ statusText = "DENIED";
2749
+ } else if (status === "confirming") {
2750
+ borderColor = "#fe8019";
2751
+ titleColor = "#fe8019";
2752
+ statusColor = "#fe8019";
2753
+ statusText = "CONFIRM";
2970
2754
  }
2971
- return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginLeft: 2, children: [
2972
- /* @__PURE__ */ jsxs(Box, { gap: 1, children: [
2973
- statusNode,
2974
- /* @__PURE__ */ jsxs(Text, { color: "yellow", bold: true, children: [
2975
- icon,
2976
- " ",
2977
- name
2978
- ] }),
2979
- summary ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: summary }) : null,
2980
- statusText ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: statusText }) : null
2981
- ] }),
2982
- status === "done" && rawCode && /* @__PURE__ */ jsx(Box, { marginLeft: 3, marginTop: 0, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: truncateCode(rawCode, 5) }) }),
2983
- status === "done" && !isWriteTool && resultContent && /* @__PURE__ */ jsx(
2984
- Box,
2985
- {
2986
- marginLeft: 3,
2987
- marginTop: 0,
2988
- borderStyle: "single",
2989
- borderColor: "gray",
2990
- paddingX: 1,
2991
- children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: resultContent })
2992
- }
2993
- ),
2994
- status === "denied" && denyFeedback && /* @__PURE__ */ jsxs(Box, { marginLeft: 3, gap: 1, children: [
2995
- /* @__PURE__ */ jsx(Text, { color: "red", children: "\u53CD\u9988:" }),
2996
- /* @__PURE__ */ jsx(Text, { children: denyFeedback })
2997
- ] })
2998
- ] });
2755
+ const contentLines = resultContent ? truncateContent(resultContent, 15) : [];
2756
+ return /* @__PURE__ */ jsx(Box, { flexDirection: "column", marginTop: 0, marginBottom: 1, width: "100%", children: /* @__PURE__ */ jsxs(
2757
+ Box,
2758
+ {
2759
+ flexDirection: "column",
2760
+ borderStyle: "round",
2761
+ borderColor,
2762
+ paddingX: 1,
2763
+ width: "100%",
2764
+ children: [
2765
+ /* @__PURE__ */ jsxs(Box, { gap: 1, children: [
2766
+ /* @__PURE__ */ jsx(Box, { backgroundColor: statusColor, width: 9, justifyContent: "center", children: /* @__PURE__ */ jsx(Text, { color: "#282828", bold: true, children: statusText }) }),
2767
+ /* @__PURE__ */ jsx(Text, { color: titleColor, bold: true, children: name.toUpperCase() }),
2768
+ /* @__PURE__ */ jsx(Text, { color: "#ebdbb2", dimColor: true, italic: true, wrap: "truncate-end", children: summary })
2769
+ ] }),
2770
+ status === "done" && contentLines.length > 0 && /* @__PURE__ */ jsx(Box, { flexDirection: "column", marginTop: 1, children: contentLines.map((line, i) => /* @__PURE__ */ jsx(Text, { color: "#ebdbb2", wrap: "truncate-end", children: line }, i)) }),
2771
+ status === "denied" && denyFeedback && /* @__PURE__ */ jsxs(Box, { gap: 1, marginTop: 0, children: [
2772
+ /* @__PURE__ */ jsx(Text, { color: "#fb4934", bold: true, children: "REASON:" }),
2773
+ /* @__PURE__ */ jsx(Text, { color: "#ebdbb2", children: denyFeedback })
2774
+ ] }),
2775
+ status === "confirming" && /* @__PURE__ */ jsx(Box, { marginTop: 0, children: /* @__PURE__ */ jsx(Text, { color: "#fe8019", italic: true, children: "Waiting for your permission..." }) })
2776
+ ]
2777
+ }
2778
+ ) });
2999
2779
  }
3000
2780
  var init_ToolCallLine = __esm({
3001
2781
  "src/cli/tui/components/ToolCallLine.tsx"() {
@@ -3014,209 +2794,94 @@ var init_MessageBubble = __esm({
3014
2794
  init_ToolCallLine();
3015
2795
  MessageBubble = React.memo(function MessageBubble2({ message }) {
3016
2796
  const { role, blocks, isStreaming } = message;
3017
- const color = role === "user" ? "green" : role === "assistant" ? "cyan" : "gray";
3018
- const icon = role === "user" ? ">" : role === "assistant" ? "\u25C6" : "\u2022";
3019
- return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", marginBottom: 1, children: [
3020
- blocks.map((block, i) => {
3021
- if (block.type === "text") {
3022
- return /* @__PURE__ */ jsxs2(Box2, { children: [
3023
- i === 0 ? /* @__PURE__ */ jsxs2(Text2, { color, bold: true, children: [
3024
- icon,
3025
- " "
3026
- ] }) : /* @__PURE__ */ jsx2(Text2, { children: " " }),
3027
- /* @__PURE__ */ jsx2(Box2, { flexDirection: "column", flexGrow: 1, children: /* @__PURE__ */ jsx2(Text2, { children: block.text }) })
3028
- ] }, `text-${i}`);
3029
- } else {
3030
- return /* @__PURE__ */ jsx2(ToolCallLine, { toolCall: block.toolCall }, block.toolCall.id);
3031
- }
3032
- }),
3033
- blocks.length === 0 && /* @__PURE__ */ jsxs2(Box2, { children: [
3034
- /* @__PURE__ */ jsxs2(Text2, { color, bold: true, children: [
3035
- icon,
3036
- " "
3037
- ] }),
3038
- isStreaming && /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "..." })
3039
- ] })
3040
- ] });
3041
- });
3042
- }
3043
- });
3044
-
3045
- // src/cli/tui/components/ChatArea.tsx
3046
- import React2, { useRef } from "react";
3047
- import { Static, Box as Box3, Text as Text3 } from "ink";
3048
- import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
3049
- function splitMessages(messages) {
3050
- let staticEnd = 0;
3051
- for (let i = 0; i < messages.length; i++) {
3052
- if (messages[i].isStreaming || messages[i].confirmPending) break;
3053
- staticEnd = i + 1;
3054
- }
3055
- const dynamicMsgs = messages.slice(staticEnd);
3056
- const streamingMsg = dynamicMsgs.find((m) => m.isStreaming) || null;
3057
- const otherDynamic = dynamicMsgs.filter((m) => m !== streamingMsg);
3058
- return {
3059
- staticMsgs: messages.slice(0, staticEnd),
3060
- streamingMsg,
3061
- otherDynamic
3062
- };
3063
- }
3064
- function extractFromBlocks(msg, staticItems, dynamicNodes, isStreaming) {
3065
- let lineIdx = 0;
3066
- for (let bi = 0; bi < msg.blocks.length; bi++) {
3067
- const block = msg.blocks[bi];
3068
- const isLastBlock = bi === msg.blocks.length - 1;
3069
- if (block.type === "text") {
3070
- const lines = block.text.split("\n");
3071
- if (isStreaming && isLastBlock) {
3072
- const partial = lines.pop() || "";
3073
- for (const line of lines) {
3074
- staticItems.push({
3075
- id: `${msg.id}-L${lineIdx}`,
3076
- type: "line",
3077
- text: line,
3078
- isFirstLine: lineIdx === 0
3079
- });
3080
- lineIdx++;
3081
- }
3082
- if (partial || lineIdx === 0) {
3083
- dynamicNodes.push(
3084
- /* @__PURE__ */ jsxs3(Box3, { children: [
3085
- /* @__PURE__ */ jsx3(Text3, { color: "cyan", bold: true, children: lineIdx === 0 ? "\u25C6 " : " " }),
3086
- /* @__PURE__ */ jsx3(Box3, { flexGrow: 1, children: /* @__PURE__ */ jsx3(Text3, { children: partial }) })
3087
- ] }, "partial")
3088
- );
3089
- }
3090
- } else {
3091
- for (const line of lines) {
3092
- staticItems.push({
3093
- id: `${msg.id}-L${lineIdx}`,
3094
- type: "line",
3095
- text: line,
3096
- isFirstLine: lineIdx === 0
3097
- });
3098
- lineIdx++;
3099
- }
3100
- }
3101
- } else if (block.type === "tool") {
3102
- const tc = block.toolCall;
3103
- const isWriteTool = tc.name === "write-file" || tc.name === "edit-file";
3104
- staticItems.push({
3105
- id: `${msg.id}-TH${tc.id}`,
3106
- type: "tool-header",
3107
- toolCall: tc
3108
- });
3109
- if (isWriteTool && tc.status === "running" && isStreaming) {
3110
- const lineCount = parseInt(tc.streamingContent || "0", 10);
3111
- dynamicNodes.push(
3112
- /* @__PURE__ */ jsx3(Box3, { paddingX: 1, marginLeft: 4, children: /* @__PURE__ */ jsxs3(Text3, { color: "cyan", children: [
3113
- "\u270E \u751F\u6210\u4E2D...",
3114
- lineCount > 0 ? ` ${lineCount} \u884C` : ""
3115
- ] }) }, `tool-progress-${tc.id}`)
3116
- );
3117
- }
3118
- if (tc.status === "done" || tc.status === "denied") {
3119
- staticItems.push({
3120
- id: `${msg.id}-TD${tc.id}`,
3121
- type: "tool-done",
3122
- toolCall: tc
3123
- });
3124
- }
3125
- }
3126
- }
3127
- }
3128
- function renderStaticItem(item) {
3129
- if (item.type === "message") {
3130
- return /* @__PURE__ */ jsx3(Box3, { paddingX: 1, children: /* @__PURE__ */ jsx3(MessageBubble, { message: item.message }) }, item.id);
3131
- }
3132
- if (item.type === "line") {
3133
- return /* @__PURE__ */ jsxs3(Box3, { paddingX: 1, children: [
3134
- /* @__PURE__ */ jsx3(Text3, { color: "cyan", bold: true, children: item.isFirstLine ? "\u25C6 " : " " }),
3135
- /* @__PURE__ */ jsx3(Text3, { children: item.text })
3136
- ] }, item.id);
3137
- }
3138
- if (item.type === "tool-header") {
3139
- const tc = item.toolCall;
3140
- const icon = getToolIcon(tc.name);
3141
- const summary = getToolParamSummary(tc.name, tc.params);
3142
- return /* @__PURE__ */ jsx3(Box3, { paddingX: 1, marginLeft: 2, children: /* @__PURE__ */ jsxs3(Box3, { gap: 1, children: [
3143
- /* @__PURE__ */ jsx3(Text3, { color: "yellow", children: "\u23F3" }),
3144
- /* @__PURE__ */ jsxs3(Text3, { color: "yellow", bold: true, children: [
3145
- icon,
3146
- " ",
3147
- tc.name
3148
- ] }),
3149
- summary ? /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: summary }) : null
3150
- ] }) }, item.id);
3151
- }
3152
- if (item.type === "tool-done") {
3153
- return /* @__PURE__ */ jsx3(Box3, { paddingX: 1, marginLeft: 0, children: /* @__PURE__ */ jsx3(ToolCallLine, { toolCall: item.toolCall }) }, item.id);
2797
+ const isUser = role === "user";
2798
+ const label = isUser ? "USER" : "AI";
2799
+ const labelBg = isUser ? "#b8bb26" : "#83a598";
2800
+ const labelFg = "#282828";
2801
+ const contentColor = isUser ? "#ebdbb2" : "#ebdbb2";
2802
+ return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", marginBottom: 1, width: "100%", children: [
2803
+ /* @__PURE__ */ jsxs2(Box2, { marginBottom: 0, children: [
2804
+ /* @__PURE__ */ jsx2(Box2, { backgroundColor: labelBg, width: 9, paddingLeft: 1, marginRight: 1, children: /* @__PURE__ */ jsx2(Text2, { color: labelFg, bold: true, children: label }) }),
2805
+ isStreaming && /* @__PURE__ */ jsx2(Text2, { color: "#83a598", dimColor: true, italic: true, children: "typing..." })
2806
+ ] }),
2807
+ /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: "100%", children: [
2808
+ blocks.map((block, i) => {
2809
+ if (block.type === "text") {
2810
+ const text = block.text.trim();
2811
+ if (!text && !isStreaming) return null;
2812
+ return /* @__PURE__ */ jsx2(Box2, { paddingLeft: 2, marginBottom: i < blocks.length - 1 ? 1 : 0, width: "100%", children: /* @__PURE__ */ jsx2(Text2, { color: contentColor, children: text }) }, `text-${i}`);
2813
+ } else if (block.type === "thought") {
2814
+ return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", marginBottom: 0, width: "100%", children: [
2815
+ /* @__PURE__ */ jsx2(Box2, { gap: 1, marginBottom: 0, children: /* @__PURE__ */ jsx2(Box2, { backgroundColor: "#504945", width: 9, justifyContent: "center", children: /* @__PURE__ */ jsx2(Text2, { color: "#a89984", bold: true, children: "THINK" }) }) }),
2816
+ /* @__PURE__ */ jsx2(Box2, { paddingLeft: 2, marginBottom: 0, children: /* @__PURE__ */ jsx2(Text2, { color: "#928374", italic: true, children: block.text.trim() }) })
2817
+ ] }, `thought-${i}`);
2818
+ } else {
2819
+ return /* @__PURE__ */ jsx2(Box2, { paddingLeft: 2, width: "100%", children: /* @__PURE__ */ jsx2(ToolCallLine, { toolCall: block.toolCall }) }, block.toolCall.id);
2820
+ }
2821
+ }),
2822
+ blocks.length === 0 && isStreaming && /* @__PURE__ */ jsx2(Box2, { paddingLeft: 2, children: /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "..." }) })
2823
+ ] })
2824
+ ] });
2825
+ });
3154
2826
  }
3155
- return null;
3156
- }
2827
+ });
2828
+
2829
+ // src/cli/tui/components/ChatArea.tsx
2830
+ import React2 from "react";
2831
+ import { Box as Box3 } from "ink";
2832
+ import { jsx as jsx3 } from "react/jsx-runtime";
3157
2833
  var ChatArea;
3158
2834
  var init_ChatArea = __esm({
3159
2835
  "src/cli/tui/components/ChatArea.tsx"() {
3160
2836
  "use strict";
3161
2837
  init_MessageBubble();
3162
- init_ToolCallLine();
3163
2838
  ChatArea = React2.memo(function ChatArea2({ messages }) {
3164
- const { staticMsgs, streamingMsg, otherDynamic } = splitMessages(messages);
3165
- const streamedIds = useRef(/* @__PURE__ */ new Set());
3166
- if (streamingMsg) {
3167
- streamedIds.current.add(streamingMsg.id);
3168
- }
3169
- const currentItems = [];
3170
- for (const msg of staticMsgs) {
3171
- if (streamedIds.current.has(msg.id)) {
3172
- const ignore = [];
3173
- extractFromBlocks(msg, currentItems, ignore, false);
3174
- } else {
3175
- currentItems.push({ id: msg.id, type: "message", message: msg });
3176
- }
3177
- }
3178
- const dynamicNodes = [];
3179
- if (streamingMsg) {
3180
- extractFromBlocks(streamingMsg, currentItems, dynamicNodes, true);
3181
- }
3182
- const seenIds = useRef(/* @__PURE__ */ new Set());
3183
- const accumulated = useRef([]);
3184
- let hasNew = false;
3185
- for (const item of currentItems) {
3186
- if (!seenIds.current.has(item.id)) {
3187
- seenIds.current.add(item.id);
3188
- accumulated.current.push(item);
3189
- hasNew = true;
3190
- }
3191
- }
3192
- if (hasNew) {
3193
- accumulated.current = [...accumulated.current];
3194
- }
3195
- const staticItems = accumulated.current;
3196
- const showPlaceholder = streamingMsg && streamingMsg.blocks.length === 0;
3197
- return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", children: [
3198
- staticItems.length > 0 && /* @__PURE__ */ jsx3(Static, { items: staticItems, children: renderStaticItem }),
3199
- (dynamicNodes.length > 0 || showPlaceholder) && /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", paddingX: 1, children: [
3200
- showPlaceholder && /* @__PURE__ */ jsxs3(Box3, { children: [
3201
- /* @__PURE__ */ jsx3(Text3, { color: "cyan", bold: true, children: "\u25C6 " }),
3202
- /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "..." })
3203
- ] }),
3204
- dynamicNodes
3205
- ] }),
3206
- otherDynamic.map((msg) => /* @__PURE__ */ jsx3(Box3, { paddingX: 1, children: /* @__PURE__ */ jsx3(MessageBubble, { message: msg }) }, msg.id))
3207
- ] });
2839
+ return /* @__PURE__ */ jsx3(Box3, { flexDirection: "column", width: "100%", children: messages.map((msg) => /* @__PURE__ */ jsx3(MessageBubble, { message: msg }, msg.id)) });
3208
2840
  });
3209
2841
  }
3210
2842
  });
3211
2843
 
3212
2844
  // src/cli/tui/components/InputArea.tsx
3213
- import { useState } from "react";
3214
- import { Box as Box4, Text as Text4, useInput } from "ink";
3215
- import { Fragment, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
3216
- function CleanTextInput({ onSubmit, placeholder }) {
2845
+ import { useRef, useState } from "react";
2846
+ import { Box as Box4, Text as Text3, useInput } from "ink";
2847
+ import { Fragment, jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
2848
+ function CleanTextInput({
2849
+ onSubmit,
2850
+ placeholder,
2851
+ onExitRequest,
2852
+ onScroll
2853
+ }) {
3217
2854
  const [value, setValue] = useState("");
3218
2855
  const [cursor, setCursor] = useState(0);
2856
+ const lastCtrlCAtRef = useRef(0);
3219
2857
  useInput((input, key) => {
2858
+ if (onScroll) {
2859
+ if (key.pageUp || key.upArrow && key.shift) {
2860
+ onScroll("up");
2861
+ return;
2862
+ }
2863
+ if (key.pageDown || key.downArrow && key.shift) {
2864
+ onScroll("down");
2865
+ return;
2866
+ }
2867
+ }
2868
+ if (input === "c" && key.ctrl) {
2869
+ if (value.length > 0) {
2870
+ setValue("");
2871
+ setCursor(0);
2872
+ lastCtrlCAtRef.current = 0;
2873
+ return;
2874
+ }
2875
+ const now = Date.now();
2876
+ const isDoublePress = now - lastCtrlCAtRef.current <= 1200;
2877
+ if (isDoublePress) {
2878
+ onExitRequest();
2879
+ lastCtrlCAtRef.current = 0;
2880
+ } else {
2881
+ lastCtrlCAtRef.current = now;
2882
+ }
2883
+ return;
2884
+ }
3220
2885
  if (key.return) {
3221
2886
  const trimmed = value.trim();
3222
2887
  if (trimmed) {
@@ -3269,30 +2934,33 @@ function CleanTextInput({ onSubmit, placeholder }) {
3269
2934
  setCursor((prev) => prev + input.length);
3270
2935
  });
3271
2936
  if (value.length === 0) {
3272
- return /* @__PURE__ */ jsxs4(Fragment, { children: [
3273
- /* @__PURE__ */ jsx4(Text4, { inverse: true, children: " " }),
3274
- /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: placeholder || "" })
2937
+ return /* @__PURE__ */ jsxs3(Fragment, { children: [
2938
+ /* @__PURE__ */ jsx4(Text3, { inverse: true, children: " " }),
2939
+ /* @__PURE__ */ jsx4(Text3, { dimColor: true, children: placeholder || "" })
3275
2940
  ] });
3276
2941
  }
3277
2942
  const before = value.slice(0, cursor);
3278
2943
  const at = cursor < value.length ? value[cursor] : " ";
3279
2944
  const after = cursor < value.length ? value.slice(cursor + 1) : "";
3280
- return /* @__PURE__ */ jsxs4(Fragment, { children: [
3281
- /* @__PURE__ */ jsx4(Text4, { children: before }),
3282
- /* @__PURE__ */ jsx4(Text4, { inverse: true, children: at }),
3283
- /* @__PURE__ */ jsx4(Text4, { children: after })
2945
+ return /* @__PURE__ */ jsxs3(Fragment, { children: [
2946
+ /* @__PURE__ */ jsx4(Text3, { children: before }),
2947
+ /* @__PURE__ */ jsx4(Text3, { inverse: true, children: at }),
2948
+ /* @__PURE__ */ jsx4(Text3, { children: after })
3284
2949
  ] });
3285
2950
  }
3286
- function InputArea({ onSubmit, isRunning }) {
3287
- return /* @__PURE__ */ jsxs4(Box4, { paddingX: 1, children: [
3288
- /* @__PURE__ */ jsx4(Text4, { color: isRunning ? "gray" : "green", bold: true, children: "> " }),
3289
- isRunning ? /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "\u7B49\u5F85\u56DE\u590D\u4E2D..." }) : /* @__PURE__ */ jsx4(
2951
+ function InputArea({ onSubmit, isRunning, onExitRequest, onScroll }) {
2952
+ return /* @__PURE__ */ jsxs3(Box4, { paddingX: 0, children: [
2953
+ /* @__PURE__ */ jsx4(Box4, { backgroundColor: isRunning ? "#504945" : "#b8bb26", paddingX: 1, marginRight: 1, children: /* @__PURE__ */ jsx4(Text3, { color: "#282828", bold: true, children: isRunning ? " WAIT " : " INPUT " }) }),
2954
+ /* @__PURE__ */ jsx4(Box4, { flexGrow: 1, children: isRunning ? /* @__PURE__ */ jsx4(Box4, { flexGrow: 1, onWheel: (event) => {
2955
+ }, children: /* @__PURE__ */ jsx4(Text3, { color: "#a89984", italic: true, children: "Thinking..." }) }) : /* @__PURE__ */ jsx4(
3290
2956
  CleanTextInput,
3291
2957
  {
3292
2958
  onSubmit,
3293
- placeholder: "\u8F93\u5165\u6D88\u606F\u6216 /\u547D\u4EE4..."
2959
+ placeholder: "Type a message or /command...",
2960
+ onExitRequest,
2961
+ onScroll
3294
2962
  }
3295
- )
2963
+ ) })
3296
2964
  ] });
3297
2965
  }
3298
2966
  var init_InputArea = __esm({
@@ -3302,43 +2970,36 @@ var init_InputArea = __esm({
3302
2970
  });
3303
2971
 
3304
2972
  // src/cli/tui/components/StatusBar.tsx
3305
- import { Box as Box5, Text as Text5 } from "ink";
3306
- import { Fragment as Fragment2, jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
3307
- function StatusBar({ agentMode, collaboration, coderWorking, isRunning, modelName, todoPlan, subAgentProgress }) {
3308
- const modeLabel = agentMode === "dual" ? `dual(${collaboration})` : "single";
2973
+ import { Box as Box5, Text as Text4 } from "ink";
2974
+ import { Fragment as Fragment2, jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
2975
+ function StatusBar({ isRunning, modelName, todoPlan, subAgentProgress }) {
3309
2976
  const todoProgress = todoPlan ? `${todoPlan.items.filter((i) => i.status === "completed").length}/${todoPlan.items.length}` : null;
3310
- return /* @__PURE__ */ jsxs5(Box5, { paddingX: 1, gap: 1, children: [
3311
- /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "\u2500\u2500" }),
3312
- /* @__PURE__ */ jsx5(Text5, { bold: true, children: modeLabel }),
3313
- /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "\u25B6" }),
3314
- /* @__PURE__ */ jsx5(Text5, { bold: true, children: modelName }),
3315
- coderWorking && /* @__PURE__ */ jsxs5(Fragment2, { children: [
3316
- /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "\u2502" }),
3317
- /* @__PURE__ */ jsx5(Text5, { color: "yellow", children: "\u2699 coder" })
3318
- ] }),
3319
- isRunning && !coderWorking && !subAgentProgress && /* @__PURE__ */ jsxs5(Fragment2, { children: [
3320
- /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "\u2502" }),
3321
- /* @__PURE__ */ jsx5(Text5, { color: "cyan", children: "thinking..." })
3322
- ] }),
3323
- subAgentProgress && /* @__PURE__ */ jsxs5(Fragment2, { children: [
3324
- /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "\u2502" }),
3325
- /* @__PURE__ */ jsxs5(Text5, { color: "magenta", children: [
3326
- "\u26A1 ",
3327
- subAgentProgress.completed + subAgentProgress.failed,
3328
- "/",
3329
- subAgentProgress.total,
3330
- " agents"
3331
- ] })
3332
- ] }),
3333
- todoProgress && /* @__PURE__ */ jsxs5(Fragment2, { children: [
3334
- /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "\u2502" }),
3335
- /* @__PURE__ */ jsxs5(Text5, { color: "cyan", children: [
3336
- "plan ",
3337
- todoProgress
2977
+ return /* @__PURE__ */ jsxs4(Box5, { marginTop: 1, children: [
2978
+ /* @__PURE__ */ jsx5(Box5, { backgroundColor: "#3c3836", paddingX: 1, children: /* @__PURE__ */ jsx5(Text4, { color: "#ebdbb2", bold: true, children: " ZENCODE " }) }),
2979
+ /* @__PURE__ */ jsxs4(Box5, { backgroundColor: "#504945", paddingX: 1, flexGrow: 1, children: [
2980
+ /* @__PURE__ */ jsx5(Text4, { color: "#ebdbb2", children: modelName }),
2981
+ isRunning && !subAgentProgress && /* @__PURE__ */ jsxs4(Fragment2, { children: [
2982
+ /* @__PURE__ */ jsx5(Text4, { color: "#ebdbb2", children: " \u2502 " }),
2983
+ /* @__PURE__ */ jsx5(Text4, { color: "#8ec07c", children: "\u25CF thinking..." })
2984
+ ] }),
2985
+ subAgentProgress && /* @__PURE__ */ jsxs4(Fragment2, { children: [
2986
+ /* @__PURE__ */ jsx5(Text4, { color: "#ebdbb2", children: " \u2502 " }),
2987
+ /* @__PURE__ */ jsxs4(Text4, { color: "#b8bb26", children: [
2988
+ "\u{1F916} Agents: ",
2989
+ subAgentProgress.completed + subAgentProgress.failed,
2990
+ "/",
2991
+ subAgentProgress.total
2992
+ ] })
2993
+ ] }),
2994
+ todoProgress && /* @__PURE__ */ jsxs4(Fragment2, { children: [
2995
+ /* @__PURE__ */ jsx5(Text4, { color: "#ebdbb2", children: " \u2502 " }),
2996
+ /* @__PURE__ */ jsxs4(Text4, { color: "#fabd2f", children: [
2997
+ "\u{1F4CB} Plan: ",
2998
+ todoProgress
2999
+ ] })
3338
3000
  ] })
3339
3001
  ] }),
3340
- /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "\u2502" }),
3341
- /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "/help" })
3002
+ /* @__PURE__ */ jsx5(Box5, { backgroundColor: "#3c3836", paddingX: 1, children: /* @__PURE__ */ jsx5(Text4, { color: "#ebdbb2", children: "/help" }) })
3342
3003
  ] });
3343
3004
  }
3344
3005
  var init_StatusBar = __esm({
@@ -3349,8 +3010,8 @@ var init_StatusBar = __esm({
3349
3010
 
3350
3011
  // src/cli/tui/components/ConfirmPrompt.tsx
3351
3012
  import { useState as useState2 } from "react";
3352
- import { Box as Box6, Text as Text6, useInput as useInput2 } from "ink";
3353
- import { Fragment as Fragment3, jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
3013
+ import { Box as Box6, Text as Text5, useInput as useInput2 } from "ink";
3014
+ import { Fragment as Fragment3, jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
3354
3015
  function getToolDetails(toolName, params) {
3355
3016
  const lines = [];
3356
3017
  let label = toolName;
@@ -3436,18 +3097,18 @@ function FeedbackInput({ onSubmit }) {
3436
3097
  setCursor((prev) => prev + input.length);
3437
3098
  });
3438
3099
  if (value.length === 0) {
3439
- return /* @__PURE__ */ jsxs6(Fragment3, { children: [
3440
- /* @__PURE__ */ jsx6(Text6, { inverse: true, children: " " }),
3441
- /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: "\u8F93\u5165\u53CD\u9988\u6307\u4EE4\u7ED9 AI..." })
3100
+ return /* @__PURE__ */ jsxs5(Fragment3, { children: [
3101
+ /* @__PURE__ */ jsx6(Text5, { inverse: true, children: " " }),
3102
+ /* @__PURE__ */ jsx6(Text5, { dimColor: true, children: "\u8F93\u5165\u53CD\u9988\u6307\u4EE4\u7ED9 AI..." })
3442
3103
  ] });
3443
3104
  }
3444
3105
  const before = value.slice(0, cursor);
3445
3106
  const at = cursor < value.length ? value[cursor] : " ";
3446
3107
  const after = cursor < value.length ? value.slice(cursor + 1) : "";
3447
- return /* @__PURE__ */ jsxs6(Fragment3, { children: [
3448
- /* @__PURE__ */ jsx6(Text6, { children: before }),
3449
- /* @__PURE__ */ jsx6(Text6, { inverse: true, children: at }),
3450
- /* @__PURE__ */ jsx6(Text6, { children: after })
3108
+ return /* @__PURE__ */ jsxs5(Fragment3, { children: [
3109
+ /* @__PURE__ */ jsx6(Text5, { children: before }),
3110
+ /* @__PURE__ */ jsx6(Text5, { inverse: true, children: at }),
3111
+ /* @__PURE__ */ jsx6(Text5, { children: after })
3451
3112
  ] });
3452
3113
  }
3453
3114
  function ConfirmPrompt({ confirm, onRespond }) {
@@ -3475,66 +3136,69 @@ function ConfirmPrompt({ confirm, onRespond }) {
3475
3136
  }, { isActive: !feedbackMode });
3476
3137
  const { lines, label } = getToolDetails(confirm.toolName, confirm.params);
3477
3138
  if (feedbackMode) {
3478
- return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", paddingX: 1, children: [
3479
- /* @__PURE__ */ jsxs6(
3139
+ return /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", paddingX: 0, marginY: 1, children: [
3140
+ /* @__PURE__ */ jsx6(Box6, { backgroundColor: "#fe8019", paddingX: 1, children: /* @__PURE__ */ jsx6(Text5, { color: "#282828", bold: true, children: " FEEDBACK " }) }),
3141
+ /* @__PURE__ */ jsxs5(
3480
3142
  Box6,
3481
3143
  {
3482
3144
  flexDirection: "column",
3483
3145
  borderStyle: "round",
3484
- borderColor: "yellow",
3146
+ borderColor: "#fe8019",
3485
3147
  paddingX: 1,
3486
3148
  children: [
3487
- /* @__PURE__ */ jsx6(Text6, { bold: true, color: "yellow", children: label }),
3488
- lines.map((line, i) => /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: line }, i))
3149
+ /* @__PURE__ */ jsx6(Text5, { bold: true, color: "#fabd2f", children: label }),
3150
+ lines.map((line, i) => /* @__PURE__ */ jsx6(Text5, { color: "#a89984", children: line }, i)),
3151
+ /* @__PURE__ */ jsxs5(Box6, { marginTop: 1, gap: 1, children: [
3152
+ /* @__PURE__ */ jsx6(Text5, { color: "#83a598", bold: true, children: "Reason: " }),
3153
+ /* @__PURE__ */ jsx6(
3154
+ FeedbackInput,
3155
+ {
3156
+ onSubmit: (text) => {
3157
+ const trimmed = text.trim();
3158
+ if (trimmed) {
3159
+ onRespond({ feedback: trimmed });
3160
+ } else {
3161
+ setFeedbackMode(false);
3162
+ }
3163
+ }
3164
+ }
3165
+ )
3166
+ ] })
3489
3167
  ]
3490
3168
  }
3491
3169
  ),
3492
- /* @__PURE__ */ jsxs6(Box6, { paddingX: 1, gap: 1, children: [
3493
- /* @__PURE__ */ jsx6(Text6, { color: "cyan", children: "\u53CD\u9988: " }),
3494
- /* @__PURE__ */ jsx6(
3495
- FeedbackInput,
3496
- {
3497
- onSubmit: (text) => {
3498
- const trimmed = text.trim();
3499
- if (trimmed) {
3500
- onRespond({ feedback: trimmed });
3501
- } else {
3502
- setFeedbackMode(false);
3503
- }
3504
- }
3505
- }
3506
- )
3507
- ] }),
3508
- /* @__PURE__ */ jsx6(Box6, { paddingX: 1, children: /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: "Enter \u53D1\u9001 Esc \u8FD4\u56DE\u9009\u62E9" }) })
3170
+ /* @__PURE__ */ jsx6(Box6, { paddingX: 1, children: /* @__PURE__ */ jsx6(Text5, { dimColor: true, children: "Enter to send \xB7 Esc to cancel" }) })
3509
3171
  ] });
3510
3172
  }
3511
- return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", paddingX: 1, children: [
3512
- /* @__PURE__ */ jsxs6(
3173
+ return /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", paddingX: 0, marginY: 1, children: [
3174
+ /* @__PURE__ */ jsx6(Box6, { backgroundColor: "#fe8019", paddingX: 1, children: /* @__PURE__ */ jsx6(Text5, { color: "#282828", bold: true, children: " CONFIRMATION REQUIRED " }) }),
3175
+ /* @__PURE__ */ jsxs5(
3513
3176
  Box6,
3514
3177
  {
3515
3178
  flexDirection: "column",
3516
3179
  borderStyle: "round",
3517
- borderColor: "yellow",
3180
+ borderColor: "#fe8019",
3518
3181
  paddingX: 1,
3519
3182
  children: [
3520
- /* @__PURE__ */ jsx6(Text6, { bold: true, color: "yellow", children: label }),
3521
- lines.map((line, i) => /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: line }, i))
3183
+ /* @__PURE__ */ jsx6(Text5, { bold: true, color: "#fabd2f", children: label }),
3184
+ lines.map((line, i) => /* @__PURE__ */ jsx6(Text5, { color: "#a89984", children: line }, i)),
3185
+ /* @__PURE__ */ jsx6(Box6, { marginTop: 1, gap: 2, justifyContent: "center", children: OPTIONS.map((opt, i) => {
3186
+ const isSelected = i === selected;
3187
+ const optColor = opt.key === "deny" ? "#fb4934" : opt.key === "allow" ? "#b8bb26" : "#8ec07c";
3188
+ return /* @__PURE__ */ jsx6(Box6, { children: isSelected ? /* @__PURE__ */ jsxs5(Text5, { bold: true, backgroundColor: optColor, color: "#282828", children: [
3189
+ " ",
3190
+ opt.label,
3191
+ " "
3192
+ ] }) : /* @__PURE__ */ jsxs5(Text5, { color: optColor, children: [
3193
+ " ",
3194
+ opt.label,
3195
+ " "
3196
+ ] }) }, opt.key);
3197
+ }) })
3522
3198
  ]
3523
3199
  }
3524
3200
  ),
3525
- /* @__PURE__ */ jsx6(Box6, { marginTop: 0, paddingX: 1, gap: 2, children: OPTIONS.map((opt, i) => {
3526
- const isSelected = i === selected;
3527
- return /* @__PURE__ */ jsx6(Box6, { children: isSelected ? /* @__PURE__ */ jsxs6(Text6, { bold: true, color: "cyan", inverse: true, children: [
3528
- " ",
3529
- opt.label,
3530
- " "
3531
- ] }) : /* @__PURE__ */ jsxs6(Text6, { dimColor: true, children: [
3532
- " ",
3533
- opt.label,
3534
- " "
3535
- ] }) }, opt.key);
3536
- }) }),
3537
- /* @__PURE__ */ jsx6(Box6, { paddingX: 1, children: /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: "\u2190 \u2192 \u9009\u62E9 Enter \u786E\u8BA4 y \u5141\u8BB8 n \u62D2\u7EDD a \u59CB\u7EC8 Tab \u53CD\u9988" }) })
3201
+ /* @__PURE__ */ jsx6(Box6, { paddingX: 1, children: /* @__PURE__ */ jsx6(Text5, { dimColor: true, children: "\u2190 \u2192 select \xB7 Enter confirm \xB7 y/n shortcuts \xB7 Tab feedback" }) })
3538
3202
  ] });
3539
3203
  }
3540
3204
  var OPTIONS;
@@ -3550,55 +3214,57 @@ var init_ConfirmPrompt = __esm({
3550
3214
  });
3551
3215
 
3552
3216
  // src/cli/tui/components/TodoPanel.tsx
3553
- import { Box as Box7, Text as Text7 } from "ink";
3554
- import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
3217
+ import { Box as Box7, Text as Text6 } from "ink";
3218
+ import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
3555
3219
  function getStatusIcon(status) {
3556
3220
  switch (status) {
3557
3221
  case "completed":
3558
- return "\u25CF";
3222
+ return "\u2705";
3559
3223
  case "in-progress":
3560
- return "\u25D0";
3224
+ return "\u23F3";
3561
3225
  default:
3562
- return "\u25CB";
3226
+ return "\u26AA";
3563
3227
  }
3564
3228
  }
3565
3229
  function getStatusColor(status) {
3566
3230
  switch (status) {
3567
3231
  case "completed":
3568
- return "green";
3232
+ return "#b8bb26";
3569
3233
  case "in-progress":
3570
- return "yellow";
3234
+ return "#fabd2f";
3571
3235
  default:
3572
- return "gray";
3236
+ return "#928374";
3573
3237
  }
3574
3238
  }
3575
3239
  function TodoPanel({ plan }) {
3576
3240
  const completed = plan.items.filter((i) => i.status === "completed").length;
3577
3241
  const total = plan.items.length;
3578
- return /* @__PURE__ */ jsxs7(
3242
+ return /* @__PURE__ */ jsxs6(
3579
3243
  Box7,
3580
3244
  {
3581
3245
  flexDirection: "column",
3582
3246
  borderStyle: "round",
3583
- borderColor: "cyan",
3247
+ borderColor: "#83a598",
3584
3248
  paddingX: 1,
3585
- marginBottom: 0,
3249
+ marginY: 1,
3586
3250
  children: [
3587
- /* @__PURE__ */ jsxs7(Box7, { justifyContent: "space-between", children: [
3588
- /* @__PURE__ */ jsx7(Text7, { bold: true, color: "cyan", children: "Plan" }),
3589
- /* @__PURE__ */ jsxs7(Text7, { dimColor: true, children: [
3251
+ /* @__PURE__ */ jsxs6(Box7, { justifyContent: "space-between", marginBottom: 1, children: [
3252
+ /* @__PURE__ */ jsx7(Text6, { bold: true, color: "#83a598", children: "\u{1F4CB} PROJECT PLAN" }),
3253
+ /* @__PURE__ */ jsxs6(Text6, { color: "#ebdbb2", children: [
3590
3254
  completed,
3591
3255
  "/",
3592
- total
3256
+ total,
3257
+ " tasks"
3593
3258
  ] })
3594
3259
  ] }),
3595
- plan.items.map((item) => /* @__PURE__ */ jsxs7(Box7, { gap: 1, children: [
3596
- /* @__PURE__ */ jsx7(Text7, { color: getStatusColor(item.status), children: getStatusIcon(item.status) }),
3260
+ plan.items.map((item) => /* @__PURE__ */ jsxs6(Box7, { gap: 1, children: [
3261
+ /* @__PURE__ */ jsx7(Text6, { color: getStatusColor(item.status), children: getStatusIcon(item.status) }),
3597
3262
  /* @__PURE__ */ jsx7(
3598
- Text7,
3263
+ Text6,
3599
3264
  {
3600
- color: item.status === "completed" ? "green" : void 0,
3265
+ color: item.status === "completed" ? "#b8bb26" : item.status === "in-progress" ? "#fabd2f" : "#ebdbb2",
3601
3266
  dimColor: item.status === "pending",
3267
+ strikethrough: item.status === "completed",
3602
3268
  children: item.title
3603
3269
  }
3604
3270
  )
@@ -3613,26 +3279,62 @@ var init_TodoPanel = __esm({
3613
3279
  }
3614
3280
  });
3615
3281
 
3282
+ // src/cli/tui/components/Header.tsx
3283
+ import { Box as Box8, Text as Text7 } from "ink";
3284
+ import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
3285
+ function Header({ modelName }) {
3286
+ return /* @__PURE__ */ jsx8(
3287
+ Box8,
3288
+ {
3289
+ flexDirection: "column",
3290
+ width: "100%",
3291
+ borderStyle: "round",
3292
+ borderColor: "#504945",
3293
+ paddingX: 1,
3294
+ marginTop: 0,
3295
+ marginBottom: 1,
3296
+ children: /* @__PURE__ */ jsxs7(Box8, { justifyContent: "space-between", children: [
3297
+ /* @__PURE__ */ jsxs7(Box8, { gap: 1, children: [
3298
+ /* @__PURE__ */ jsx8(Text7, { bold: true, color: "#fe8019", children: "ZEN CODE" }),
3299
+ /* @__PURE__ */ jsx8(Text7, { color: "#a89984", dimColor: true, children: "v0.2.1" })
3300
+ ] }),
3301
+ /* @__PURE__ */ jsx8(Text7, { color: "#83a598", bold: true, children: modelName })
3302
+ ] })
3303
+ }
3304
+ );
3305
+ }
3306
+ var init_Header = __esm({
3307
+ "src/cli/tui/components/Header.tsx"() {
3308
+ "use strict";
3309
+ }
3310
+ });
3311
+
3616
3312
  // src/cli/tui/App.tsx
3617
3313
  import { useReducer, useCallback, useEffect, useRef as useRef2, useState as useState3 } from "react";
3618
- import { Box as Box8, Text as Text8, useInput as useInput3 } from "ink";
3619
- import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
3620
- function App({ config, client, agent, orchestrator, registry, todoStore, memoStore, subAgentTracker }) {
3314
+ import { Box as Box9, Text as Text8, useInput as useInput3, useStdout } from "ink";
3315
+ import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
3316
+ function App({ config, client, agent, registry, todoStore, subAgentTracker, agentRegistry, skillRegistry }) {
3317
+ const { stdout } = useStdout();
3318
+ const [, setWidth] = useState3(stdout.columns);
3319
+ useEffect(() => {
3320
+ const onResize = () => {
3321
+ setWidth(stdout.columns);
3322
+ };
3323
+ stdout.on("resize", onResize);
3324
+ return () => {
3325
+ stdout.off("resize", onResize);
3326
+ };
3327
+ }, [stdout]);
3621
3328
  const [state, dispatch] = useReducer(
3622
3329
  tuiReducer,
3623
- createInitialState(
3624
- config.model,
3625
- config.agent_mode,
3626
- config.collaboration
3627
- )
3330
+ createInitialState(config.model)
3628
3331
  );
3629
3332
  const [resetKey, setResetKey] = useState3(0);
3333
+ const currentCallbacksRef = useRef2(null);
3630
3334
  const agentRef = useRef2(agent);
3631
- const orchestratorRef = useRef2(orchestrator);
3632
3335
  const todoStoreRef = useRef2(todoStore);
3633
3336
  const subAgentTrackerRef = useRef2(subAgentTracker);
3634
3337
  agentRef.current = agent;
3635
- orchestratorRef.current = orchestrator;
3636
3338
  todoStoreRef.current = todoStore;
3637
3339
  subAgentTrackerRef.current = subAgentTracker;
3638
3340
  useEffect(() => {
@@ -3672,7 +3374,7 @@ function App({ config, client, agent, orchestrator, registry, todoStore, memoSto
3672
3374
  );
3673
3375
  useEffect(() => {
3674
3376
  setStructuredConfirmHandler((toolName, params) => {
3675
- return new Promise((resolve8) => {
3377
+ return new Promise((resolve10) => {
3676
3378
  const id = `confirm-${Date.now()}`;
3677
3379
  registerConfirmToolId(toolName, id);
3678
3380
  dispatch({
@@ -3683,13 +3385,13 @@ function App({ config, client, agent, orchestrator, registry, todoStore, memoSto
3683
3385
  resolve: (result) => {
3684
3386
  if (result === "always") {
3685
3387
  registry.addAutoApprove(toolName);
3686
- resolve8({ approved: true });
3388
+ resolve10({ approved: true });
3687
3389
  } else if (result === "allow") {
3688
- resolve8({ approved: true });
3390
+ resolve10({ approved: true });
3689
3391
  } else if (result === "deny") {
3690
- resolve8({ approved: false });
3392
+ resolve10({ approved: false });
3691
3393
  } else {
3692
- resolve8({ approved: false, feedback: result.feedback });
3394
+ resolve10({ approved: false, feedback: result.feedback });
3693
3395
  }
3694
3396
  }
3695
3397
  });
@@ -3699,39 +3401,53 @@ function App({ config, client, agent, orchestrator, registry, todoStore, memoSto
3699
3401
  setStructuredConfirmHandler(null);
3700
3402
  };
3701
3403
  }, [registry]);
3404
+ const runAgent = useCallback(async (text) => {
3405
+ dispatch({ type: "ADD_USER_MESSAGE", text });
3406
+ dispatch({ type: "START_ASSISTANT" });
3407
+ dispatch({ type: "SET_RUNNING", running: true });
3408
+ const callbacks = createBridgeCallbacks(dispatch);
3409
+ currentCallbacksRef.current = callbacks;
3410
+ try {
3411
+ await agentRef.current.run(text, callbacks);
3412
+ } catch (err) {
3413
+ if (!isAbortError(err)) {
3414
+ const msg = err instanceof Error ? err.message : String(err);
3415
+ dispatch({ type: "SET_ERROR", error: msg });
3416
+ }
3417
+ } finally {
3418
+ currentCallbacksRef.current = null;
3419
+ }
3420
+ callbacks._stopBatcher?.();
3421
+ dispatch({ type: "FINISH_STREAMING" });
3422
+ dispatch({ type: "SET_RUNNING", running: false });
3423
+ }, []);
3702
3424
  const handleSubmit = useCallback(async (text) => {
3703
3425
  if (text.startsWith("/")) {
3426
+ const parts = text.slice(1).split(/\s+/);
3427
+ const skillName = parts[0];
3428
+ const skillArgs = parts.slice(1).join(" ");
3429
+ const skill = skillRegistry.get(skillName);
3430
+ if (skill) {
3431
+ const expandedPrompt = skillRegistry.expandPrompt(skill, skillArgs);
3432
+ await runAgent(expandedPrompt);
3433
+ return;
3434
+ }
3704
3435
  handleSlashCommand2(text, {
3705
3436
  config,
3706
- orchestrator: orchestratorRef.current,
3437
+ agent: agentRef.current,
3707
3438
  registry,
3708
3439
  dispatch,
3709
3440
  setResetKey,
3710
3441
  client,
3711
3442
  todoStore,
3712
- memoStore,
3713
- subAgentTracker
3443
+ subAgentTracker,
3444
+ agentRegistry,
3445
+ skillRegistry
3714
3446
  });
3715
3447
  return;
3716
3448
  }
3717
- dispatch({ type: "ADD_USER_MESSAGE", text });
3718
- dispatch({ type: "START_ASSISTANT" });
3719
- dispatch({ type: "SET_RUNNING", running: true });
3720
- const callbacks = createBridgeCallbacks(dispatch);
3721
- try {
3722
- if (config.agent_mode === "single") {
3723
- await agentRef.current.run(text, callbacks);
3724
- } else {
3725
- await orchestratorRef.current.run(text, callbacks);
3726
- }
3727
- } catch (err) {
3728
- const msg = err instanceof Error ? err.message : String(err);
3729
- dispatch({ type: "SET_ERROR", error: msg });
3730
- }
3731
- callbacks._stopBatcher?.();
3732
- dispatch({ type: "FINISH_STREAMING" });
3733
- dispatch({ type: "SET_RUNNING", running: false });
3734
- }, [config]);
3449
+ await runAgent(text);
3450
+ }, [config, runAgent]);
3735
3451
  const handleConfirmResponse = useCallback((result) => {
3736
3452
  if (confirmPending) {
3737
3453
  confirmPending.resolve(result);
@@ -3749,114 +3465,118 @@ function App({ config, client, agent, orchestrator, registry, todoStore, memoSto
3749
3465
  }
3750
3466
  }, [confirmPending, state.messages]);
3751
3467
  useInput3((input, key) => {
3752
- if (input === "c" && key.ctrl) {
3468
+ if (key.escape) {
3753
3469
  if (state.isRunning) {
3470
+ agentRef.current.interrupt();
3471
+ currentCallbacksRef.current?._stopBatcher?.();
3754
3472
  dispatch({ type: "SET_RUNNING", running: false });
3755
3473
  dispatch({ type: "FINISH_STREAMING" });
3756
- } else {
3757
- process.exit(0);
3758
3474
  }
3759
3475
  return;
3760
3476
  }
3477
+ if (input === "c" && key.ctrl) {
3478
+ if (state.isRunning) {
3479
+ agentRef.current.interrupt();
3480
+ currentCallbacksRef.current?._stopBatcher?.();
3481
+ dispatch({ type: "SET_RUNNING", running: false });
3482
+ dispatch({ type: "FINISH_STREAMING" });
3483
+ return;
3484
+ }
3485
+ }
3761
3486
  if (input === "d" && key.ctrl) {
3762
3487
  process.exit(0);
3763
3488
  }
3764
3489
  });
3765
- return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
3766
- /* @__PURE__ */ jsx8(Box8, { flexDirection: "column", overflow: "hidden", children: /* @__PURE__ */ jsx8(ChatArea, { messages: state.messages }) }),
3767
- state.error && /* @__PURE__ */ jsxs8(
3768
- Box8,
3769
- {
3770
- borderStyle: "round",
3771
- borderColor: "red",
3772
- paddingX: 1,
3773
- marginBottom: 1,
3774
- children: [
3775
- /* @__PURE__ */ jsx8(Text8, { color: "red", bold: true, children: "\u2716 Error: " }),
3776
- /* @__PURE__ */ jsx8(Text8, { color: "red", children: state.error })
3777
- ]
3778
- }
3779
- ),
3780
- confirmPending && /* @__PURE__ */ jsx8(
3781
- ConfirmPrompt,
3782
- {
3783
- confirm: confirmPending,
3784
- onRespond: handleConfirmResponse
3785
- }
3786
- ),
3787
- state.todoPlan && /* @__PURE__ */ jsx8(TodoPanel, { plan: state.todoPlan }),
3788
- /* @__PURE__ */ jsx8(
3490
+ return /* @__PURE__ */ jsxs8(Box9, { flexDirection: "column", paddingX: 0, width: "100%", children: [
3491
+ /* @__PURE__ */ jsxs8(Box9, { paddingX: 1, flexDirection: "column", children: [
3492
+ /* @__PURE__ */ jsx9(Header, { modelName: state.modelName }),
3493
+ /* @__PURE__ */ jsx9(ChatArea, { messages: state.messages })
3494
+ ] }),
3495
+ state.error && /* @__PURE__ */ jsxs8(Box9, { borderStyle: "round", borderColor: "#fb4934", paddingX: 1, marginBottom: 1, children: [
3496
+ /* @__PURE__ */ jsx9(Text8, { color: "#fb4934", bold: true, children: "ERROR: " }),
3497
+ /* @__PURE__ */ jsx9(Text8, { color: "#fb4934", children: state.error })
3498
+ ] }),
3499
+ confirmPending && /* @__PURE__ */ jsx9(ConfirmPrompt, { confirm: confirmPending, onRespond: handleConfirmResponse }),
3500
+ state.todoPlan && /* @__PURE__ */ jsx9(TodoPanel, { plan: state.todoPlan }),
3501
+ /* @__PURE__ */ jsx9(Box9, { marginTop: 1, children: /* @__PURE__ */ jsx9(
3789
3502
  InputArea,
3790
3503
  {
3791
3504
  onSubmit: handleSubmit,
3792
- isRunning: state.isRunning || !!confirmPending
3505
+ isRunning: state.isRunning || !!confirmPending,
3506
+ onExitRequest: () => process.exit(0)
3793
3507
  }
3794
- ),
3795
- /* @__PURE__ */ jsx8(
3508
+ ) }),
3509
+ /* @__PURE__ */ jsx9(Box9, { marginTop: 0, children: /* @__PURE__ */ jsx9(
3796
3510
  StatusBar,
3797
3511
  {
3798
- agentMode: state.agentMode,
3799
- collaboration: state.collaboration,
3800
- coderWorking: state.coderWorking,
3801
3512
  isRunning: state.isRunning,
3802
3513
  modelName: state.modelName,
3803
3514
  todoPlan: state.todoPlan,
3804
3515
  subAgentProgress: state.subAgentProgress
3805
3516
  }
3806
- )
3517
+ ) })
3807
3518
  ] }, resetKey);
3808
3519
  }
3809
3520
  function handleSlashCommand2(input, ctx) {
3810
- const { config, orchestrator, registry, dispatch, setResetKey, client, todoStore, memoStore, subAgentTracker } = ctx;
3521
+ const { config, agent, registry, dispatch, setResetKey, client, todoStore, subAgentTracker, agentRegistry, skillRegistry } = ctx;
3811
3522
  const parts = input.trim().split(/\s+/);
3812
3523
  const command = parts[0];
3813
3524
  switch (command) {
3814
3525
  case "/help":
3815
3526
  dispatch({ type: "ADD_USER_MESSAGE", text: input });
3816
3527
  dispatch({ type: "START_ASSISTANT" });
3817
- dispatch({
3818
- type: "APPEND_CONTENT",
3819
- text: `\u53EF\u7528\u547D\u4EE4:
3528
+ {
3529
+ let helpText = `\u53EF\u7528\u547D\u4EE4:
3820
3530
  /help \u663E\u793A\u6B64\u5E2E\u52A9\u4FE1\u606F
3821
- /mode [\u6A21\u5F0F] \u5207\u6362\u534F\u4F5C\u6A21\u5F0F (delegated/autonomous/controlled)
3822
- /single \u5207\u6362\u5230\u5355 Agent \u6A21\u5F0F
3823
- /dual \u5207\u6362\u5230\u53CC Agent \u6A21\u5F0F
3531
+ /skills \u5217\u51FA\u6240\u6709\u53EF\u7528\u6280\u80FD
3532
+ /agents \u5217\u51FA\u6240\u6709\u53EF\u7528\u5B50 Agent
3824
3533
  /parallel \u5207\u6362\u5E76\u884C\u5B50 Agent \u529F\u80FD on/off
3825
3534
  /todo \u5207\u6362 todo \u8BA1\u5212\u529F\u80FD on/off
3826
3535
  /clear \u6E05\u7A7A\u5BF9\u8BDD\u5386\u53F2
3827
3536
  /info \u663E\u793A\u5F53\u524D\u914D\u7F6E
3828
3537
  Ctrl+C \u53D6\u6D88\u5F53\u524D\u8BF7\u6C42 / \u9000\u51FA
3829
- Ctrl+D \u9000\u51FA`
3830
- });
3538
+ Ctrl+D \u9000\u51FA`;
3539
+ const skills = skillRegistry.list();
3540
+ if (skills.length > 0) {
3541
+ helpText += `
3542
+
3543
+ \u53EF\u7528\u6280\u80FD:
3544
+ ${skills.map((s) => ` /${s.name} ${s.description}`).join("\n")}`;
3545
+ }
3546
+ dispatch({ type: "APPEND_CONTENT", text: helpText });
3547
+ }
3831
3548
  dispatch({ type: "FINISH_STREAMING" });
3832
3549
  break;
3833
- case "/mode": {
3834
- const mode = parts[1];
3835
- if (!mode) {
3836
- dispatch({ type: "ADD_USER_MESSAGE", text: input });
3837
- dispatch({ type: "START_ASSISTANT" });
3838
- dispatch({
3839
- type: "APPEND_CONTENT",
3840
- text: `\u5F53\u524D\u534F\u4F5C\u6A21\u5F0F: ${config.collaboration}
3841
- \u53EF\u9009: delegated, autonomous, controlled`
3842
- });
3843
- dispatch({ type: "FINISH_STREAMING" });
3844
- } else if (["delegated", "autonomous", "controlled"].includes(mode)) {
3845
- config.collaboration = mode;
3846
- orchestrator?.setMode(mode);
3847
- dispatch({ type: "SET_MODE", agentMode: config.agent_mode, collaboration: mode });
3550
+ case "/skills": {
3551
+ const skills = skillRegistry.list();
3552
+ dispatch({ type: "ADD_USER_MESSAGE", text: input });
3553
+ dispatch({ type: "START_ASSISTANT" });
3554
+ if (skills.length === 0) {
3555
+ dispatch({ type: "APPEND_CONTENT", text: "\u6682\u65E0\u53EF\u7528\u6280\u80FD\u3002\u5728 ~/.zencode/skills/ \u6216 .zencode/skills/ \u653E\u7F6E YAML \u6587\u4EF6\u6DFB\u52A0\u6280\u80FD\u3002" });
3556
+ } else {
3557
+ const lines = skills.map((s) => ` /${s.name}: ${s.description}`);
3558
+ dispatch({ type: "APPEND_CONTENT", text: `\u53EF\u7528\u6280\u80FD (${skills.length}):
3559
+ ${lines.join("\n")}` });
3848
3560
  }
3561
+ dispatch({ type: "FINISH_STREAMING" });
3849
3562
  break;
3850
3563
  }
3851
- case "/single":
3852
- config.agent_mode = "single";
3853
- dispatch({ type: "SET_MODE", agentMode: "single", collaboration: config.collaboration });
3854
- break;
3855
- case "/dual":
3856
- config.agent_mode = "dual";
3857
- dispatch({ type: "SET_MODE", agentMode: "dual", collaboration: config.collaboration });
3564
+ case "/agents": {
3565
+ const agents = agentRegistry.list();
3566
+ dispatch({ type: "ADD_USER_MESSAGE", text: input });
3567
+ dispatch({ type: "START_ASSISTANT" });
3568
+ if (agents.length === 0) {
3569
+ dispatch({ type: "APPEND_CONTENT", text: "\u6682\u65E0\u53EF\u7528\u5B50 Agent\u3002" });
3570
+ } else {
3571
+ const lines = agents.map((a) => ` ${a.name}: ${a.description} [tools: ${a.tools.join(", ")}]`);
3572
+ dispatch({ type: "APPEND_CONTENT", text: `\u53EF\u7528\u5B50 Agent (${agents.length}):
3573
+ ${lines.join("\n")}` });
3574
+ }
3575
+ dispatch({ type: "FINISH_STREAMING" });
3858
3576
  break;
3577
+ }
3859
3578
  case "/clear":
3579
+ agent.getConversation().clear();
3860
3580
  dispatch({ type: "CLEAR_MESSAGES" });
3861
3581
  setResetKey((prev) => prev + 1);
3862
3582
  break;
@@ -3867,7 +3587,7 @@ function handleSlashCommand2(input, ctx) {
3867
3587
  if (next === "off") {
3868
3588
  registry.unregister("spawn-agents");
3869
3589
  } else if (!registry.has("spawn-agents")) {
3870
- registry.register(createSpawnAgentsTool(client, registry, config, subAgentTracker, memoStore));
3590
+ registry.register(createSpawnAgentsTool(client, registry, config, subAgentTracker));
3871
3591
  }
3872
3592
  dispatch({ type: "ADD_USER_MESSAGE", text: input });
3873
3593
  dispatch({ type: "START_ASSISTANT" });
@@ -3896,9 +3616,9 @@ function handleSlashCommand2(input, ctx) {
3896
3616
  dispatch({
3897
3617
  type: "APPEND_CONTENT",
3898
3618
  text: `\u6A21\u578B: ${config.model}
3899
- Agent \u6A21\u5F0F: ${config.agent_mode}
3900
- \u534F\u4F5C\u6A21\u5F0F: ${config.collaboration}
3901
- \u57FA\u7840 URL: ${config.base_url}`
3619
+ \u57FA\u7840 URL: ${config.base_url}
3620
+ \u5B50 Agent: ${agentRegistry.listNames().join(", ") || "\u65E0"}
3621
+ \u6280\u80FD: ${skillRegistry.listNames().map((n) => "/" + n).join(", ") || "\u65E0"}`
3902
3622
  });
3903
3623
  dispatch({ type: "FINISH_STREAMING" });
3904
3624
  break;
@@ -3916,6 +3636,7 @@ Agent \u6A21\u5F0F: ${config.agent_mode}
3916
3636
  var init_App = __esm({
3917
3637
  "src/cli/tui/App.tsx"() {
3918
3638
  "use strict";
3639
+ init_client();
3919
3640
  init_permission();
3920
3641
  init_state();
3921
3642
  init_bridge();
@@ -3926,6 +3647,7 @@ var init_App = __esm({
3926
3647
  init_StatusBar();
3927
3648
  init_ConfirmPrompt();
3928
3649
  init_TodoPanel();
3650
+ init_Header();
3929
3651
  }
3930
3652
  });
3931
3653
 
@@ -3935,10 +3657,14 @@ __export(tui_exports, {
3935
3657
  startTui: () => startTui
3936
3658
  });
3937
3659
  import { render } from "ink";
3938
- import { jsx as jsx9 } from "react/jsx-runtime";
3660
+ import { jsx as jsx10 } from "react/jsx-runtime";
3939
3661
  async function startTui(options) {
3940
3662
  const { config } = options;
3941
- const { systemPrompt } = await buildPrompt(config);
3663
+ const agentRegistry = new SubAgentConfigRegistry();
3664
+ for (const agentConfig of loadAllAgentConfigs()) {
3665
+ agentRegistry.register(agentConfig);
3666
+ }
3667
+ const { systemPrompt } = await buildPrompt(config, agentRegistry.list());
3942
3668
  const registry = new ToolRegistry();
3943
3669
  registerCoreTools(registry);
3944
3670
  registry.setPermissions(config.permissions);
@@ -3950,32 +3676,37 @@ async function startTui(options) {
3950
3676
  maxTokens: config.max_tokens
3951
3677
  });
3952
3678
  const todoStore = new TodoStore();
3953
- const memoStore = new MemoStore();
3954
3679
  const subAgentTracker = new SubAgentTracker();
3680
+ const skillRegistry = new SkillRegistry();
3681
+ for (const skill of loadAllSkills()) {
3682
+ skillRegistry.register(skill);
3683
+ }
3955
3684
  if (config.features.parallel_agents === "on") {
3956
- registry.register(createSpawnAgentsTool(client, registry, config, subAgentTracker, memoStore));
3685
+ registry.register(createSpawnAgentsTool(client, registry, config, subAgentTracker));
3957
3686
  }
3958
3687
  if (config.features.todo === "on") {
3959
3688
  registry.register(createTodoTool(todoStore));
3960
3689
  }
3961
- registry.register(createMemoTool(memoStore));
3962
- const agent = new Agent(client, registry, config, systemPrompt, void 0, memoStore);
3963
- const orchestrator = new Orchestrator(registry, config, systemPrompt, memoStore);
3690
+ registry.register(createDispatchTool(client, registry, config, agentRegistry));
3691
+ const agent = new Agent(client, registry, config, systemPrompt);
3964
3692
  const { waitUntilExit } = render(
3965
- /* @__PURE__ */ jsx9(
3693
+ /* @__PURE__ */ jsx10(
3966
3694
  App,
3967
3695
  {
3968
3696
  config,
3969
3697
  client,
3970
3698
  agent,
3971
- orchestrator,
3972
3699
  registry,
3973
3700
  todoStore,
3974
- memoStore,
3975
- subAgentTracker
3701
+ subAgentTracker,
3702
+ agentRegistry,
3703
+ skillRegistry
3976
3704
  }
3977
3705
  ),
3978
- { patchConsole: true }
3706
+ {
3707
+ patchConsole: true,
3708
+ exitOnCtrlC: false
3709
+ }
3979
3710
  );
3980
3711
  await waitUntilExit();
3981
3712
  }
@@ -3986,41 +3717,129 @@ var init_tui = __esm({
3986
3717
  init_registry();
3987
3718
  init_register();
3988
3719
  init_agent();
3989
- init_orchestrator();
3990
3720
  init_builder();
3991
3721
  init_todo_store();
3992
- init_memo_store();
3993
3722
  init_sub_agent_tracker();
3723
+ init_registry2();
3724
+ init_loader();
3725
+ init_registry3();
3726
+ init_loader2();
3994
3727
  init_spawn_agents();
3995
3728
  init_todo();
3996
- init_memo();
3729
+ init_dispatch();
3997
3730
  init_App();
3998
3731
  }
3999
3732
  });
4000
3733
 
4001
3734
  // src/cli/index.ts
4002
- init_loader();
3735
+ import { Command } from "commander";
3736
+
3737
+ // src/config/loader.ts
3738
+ import * as fs from "fs";
3739
+ import * as path from "path";
3740
+ import * as os from "os";
3741
+ import { parse as parseYaml } from "yaml";
3742
+
3743
+ // src/config/defaults.ts
3744
+ var DEFAULT_CONFIG = {
3745
+ model: "deepseek-chat",
3746
+ api_key: "",
3747
+ base_url: "https://api.deepseek.com/v1",
3748
+ temperature: 0.7,
3749
+ max_tokens: 8192,
3750
+ features: {
3751
+ git: "auto",
3752
+ mcp: "off",
3753
+ planning_layer: "on",
3754
+ parallel_agents: "on",
3755
+ todo: "on"
3756
+ },
3757
+ permissions: {
3758
+ auto_approve: ["read-file", "glob", "grep", "spawn-agents", "todo", "dispatch"],
3759
+ require_approval: ["write-file", "edit-file", "bash", "git"]
3760
+ },
3761
+ mcp_servers: [],
3762
+ prompts: [],
3763
+ max_tool_output: 3e4
3764
+ };
3765
+
3766
+ // src/config/loader.ts
3767
+ function deepMerge(target, source) {
3768
+ const result = { ...target };
3769
+ for (const key of Object.keys(source)) {
3770
+ const sourceVal = source[key];
3771
+ const targetVal = target[key];
3772
+ if (sourceVal !== void 0 && sourceVal !== null && typeof sourceVal === "object" && !Array.isArray(sourceVal) && typeof targetVal === "object" && !Array.isArray(targetVal) && targetVal !== null) {
3773
+ result[key] = deepMerge(targetVal, sourceVal);
3774
+ } else if (sourceVal !== void 0) {
3775
+ result[key] = sourceVal;
3776
+ }
3777
+ }
3778
+ return result;
3779
+ }
3780
+ function loadYamlFile(filePath) {
3781
+ try {
3782
+ const content = fs.readFileSync(filePath, "utf-8");
3783
+ return parseYaml(content) || {};
3784
+ } catch {
3785
+ return {};
3786
+ }
3787
+ }
3788
+ function loadEnvConfig() {
3789
+ const config = {};
3790
+ if (process.env["ZENCODE_API_KEY"]) {
3791
+ config.api_key = process.env["ZENCODE_API_KEY"];
3792
+ }
3793
+ if (process.env["ZENCODE_MODEL"]) {
3794
+ config.model = process.env["ZENCODE_MODEL"];
3795
+ }
3796
+ if (process.env["ZENCODE_BASE_URL"]) {
3797
+ config.base_url = process.env["ZENCODE_BASE_URL"];
3798
+ }
3799
+ return config;
3800
+ }
3801
+ function loadCliConfig(opts) {
3802
+ const config = {};
3803
+ if (opts.model) config.model = opts.model;
3804
+ if (opts.apiKey) config.api_key = opts.apiKey;
3805
+ if (opts.baseUrl) config.base_url = opts.baseUrl;
3806
+ return config;
3807
+ }
3808
+ function loadConfig(cliOpts = {}) {
3809
+ const globalConfigPath = path.join(os.homedir(), ".zencode", "config.yaml");
3810
+ const projectDirConfigPath = path.resolve(".zencode", "config.yaml");
3811
+ const projectFileConfigPath = path.resolve(".zencode.yaml");
3812
+ let config = { ...DEFAULT_CONFIG };
3813
+ config = deepMerge(config, loadYamlFile(globalConfigPath));
3814
+ config = deepMerge(config, loadYamlFile(projectDirConfigPath));
3815
+ config = deepMerge(config, loadYamlFile(projectFileConfigPath));
3816
+ config = deepMerge(config, loadEnvConfig());
3817
+ config = deepMerge(config, loadCliConfig(cliOpts));
3818
+ return config;
3819
+ }
3820
+
3821
+ // src/cli/index.ts
4003
3822
  init_client();
4004
3823
  init_registry();
4005
3824
  init_register();
4006
3825
  init_agent();
4007
- init_orchestrator();
4008
3826
  init_builder();
4009
- import { Command } from "commander";
4010
3827
 
4011
3828
  // src/cli/repl.ts
4012
3829
  init_client();
4013
3830
  init_registry();
4014
3831
  init_agent();
4015
- init_orchestrator();
4016
3832
  init_builder();
4017
3833
  init_register();
4018
3834
  init_permission();
4019
3835
  init_todo_store();
4020
- init_memo_store();
3836
+ init_registry2();
3837
+ init_loader();
3838
+ init_registry3();
3839
+ init_loader2();
4021
3840
  init_spawn_agents();
4022
3841
  init_todo();
4023
- init_memo();
3842
+ init_dispatch();
4024
3843
  init_bridge();
4025
3844
  import * as readline from "readline";
4026
3845
 
@@ -4052,12 +3871,8 @@ function printToolCall(toolName, params) {
4052
3871
  const action = String(params["action"]);
4053
3872
  const id = params["id"] ? ` [${params["id"]}]` : "";
4054
3873
  detail = ` ${chalk2.dim(`${action}${id}`)}`;
4055
- } else if (toolName === "memo" && params["action"]) {
4056
- const action = String(params["action"]);
4057
- const key = params["key"] ? ` [${params["key"]}]` : "";
4058
- detail = ` ${chalk2.dim(`${action}${key}`)}`;
4059
3874
  }
4060
- const icon = toolName === "spawn-agents" ? "\u26A1" : toolName === "todo" ? "\u{1F4CB}" : toolName === "memo" ? "\u{1F4DD}" : "\u2699";
3875
+ const icon = toolName === "spawn-agents" ? "\u26A1" : toolName === "todo" ? "\u{1F4CB}" : "\u2699";
4061
3876
  console.log(chalk2.yellow(` ${icon} ${toolName}`) + detail);
4062
3877
  }
4063
3878
  function printToolResult(toolName, result, truncated) {
@@ -4080,10 +3895,9 @@ function printWarning(message) {
4080
3895
  function printSuccess(message) {
4081
3896
  console.log(chalk2.green(`\u2713 ${message}`));
4082
3897
  }
4083
- function printWelcome(modelName, mode) {
3898
+ function printWelcome(modelName) {
4084
3899
  console.log(chalk2.bold.cyan("\n ZenCode") + chalk2.dim(" - \u6781\u7B80 AI \u7F16\u7A0B\u52A9\u624B\n"));
4085
3900
  console.log(chalk2.dim(` \u6A21\u578B: ${modelName}`));
4086
- console.log(chalk2.dim(` \u6A21\u5F0F: ${mode}`));
4087
3901
  console.log(chalk2.dim(` \u8F93\u5165 /help \u67E5\u770B\u547D\u4EE4\uFF0CCtrl+C \u9000\u51FA
4088
3902
  `));
4089
3903
  }
@@ -4097,9 +3911,8 @@ function handleSlashCommand(input, context) {
4097
3911
  console.log(`
4098
3912
  \u53EF\u7528\u547D\u4EE4:
4099
3913
  /help \u663E\u793A\u6B64\u5E2E\u52A9\u4FE1\u606F
4100
- /mode [\u6A21\u5F0F] \u5207\u6362\u534F\u4F5C\u6A21\u5F0F (delegated/autonomous/controlled)
4101
- /single \u5207\u6362\u5230\u5355 Agent \u6A21\u5F0F
4102
- /dual \u5207\u6362\u5230\u53CC Agent \u6A21\u5F0F
3914
+ /skills \u5217\u51FA\u6240\u6709\u53EF\u7528\u6280\u80FD\uFF08\u7528\u6237\u81EA\u5B9A\u4E49\u659C\u6760\u547D\u4EE4\uFF09
3915
+ /agents \u5217\u51FA\u6240\u6709\u53EF\u7528\u5B50 Agent
4103
3916
  /parallel \u5207\u6362\u5E76\u884C\u5B50 Agent \u529F\u80FD on/off
4104
3917
  /todo \u5207\u6362 todo \u8BA1\u5212\u529F\u80FD on/off
4105
3918
  /clear \u6E05\u7A7A\u5BF9\u8BDD\u5386\u53F2
@@ -4107,33 +3920,33 @@ function handleSlashCommand(input, context) {
4107
3920
  /exit \u9000\u51FA
4108
3921
  `);
4109
3922
  return true;
4110
- case "/mode": {
4111
- const mode = parts[1];
4112
- if (!mode) {
4113
- printInfo(`\u5F53\u524D\u534F\u4F5C\u6A21\u5F0F: ${context.config.collaboration}`);
4114
- printInfo("\u53EF\u9009: delegated, autonomous, controlled");
4115
- return true;
4116
- }
4117
- if (["delegated", "autonomous", "controlled"].includes(mode)) {
4118
- context.config.collaboration = mode;
4119
- context.setMode?.(mode);
4120
- printSuccess(`\u5DF2\u5207\u6362\u5230 ${mode} \u6A21\u5F0F`);
3923
+ case "/skills": {
3924
+ const skills = context.skillRegistry.list();
3925
+ if (skills.length === 0) {
3926
+ printInfo("\u6682\u65E0\u53EF\u7528\u6280\u80FD\u3002\u5728 ~/.zencode/skills/ \u6216 .zencode/skills/ \u4E2D\u6DFB\u52A0 YAML \u6587\u4EF6\u5B9A\u4E49\u6280\u80FD\u3002");
4121
3927
  } else {
4122
- printError(`\u65E0\u6548\u6A21\u5F0F: ${mode}\u3002\u53EF\u9009: delegated, autonomous, controlled`);
3928
+ printInfo(`\u53EF\u7528\u6280\u80FD (${skills.length}):`);
3929
+ for (const s of skills) {
3930
+ printInfo(` /${s.name}: ${s.description}`);
3931
+ }
4123
3932
  }
4124
3933
  return true;
4125
3934
  }
4126
- case "/single":
4127
- context.config.agent_mode = "single";
4128
- printSuccess("\u5DF2\u5207\u6362\u5230\u5355 Agent \u6A21\u5F0F");
4129
- return true;
4130
- case "/dual":
4131
- context.config.agent_mode = "dual";
4132
- printSuccess("\u5DF2\u5207\u6362\u5230\u53CC Agent \u6A21\u5F0F");
3935
+ case "/agents": {
3936
+ const agents = context.agentRegistry.list();
3937
+ if (agents.length === 0) {
3938
+ printInfo("\u6682\u65E0\u53EF\u7528\u5B50 Agent");
3939
+ } else {
3940
+ printInfo(`\u53EF\u7528\u5B50 Agent (${agents.length}):`);
3941
+ for (const a of agents) {
3942
+ printInfo(` ${a.name}: ${a.description}`);
3943
+ }
3944
+ }
4133
3945
  return true;
3946
+ }
4134
3947
  case "/clear":
4135
- printSuccess("\u5BF9\u8BDD\u5386\u53F2\u5DF2\u6E05\u7A7A\uFF08\u5C06\u5728\u4E0B\u6B21\u5BF9\u8BDD\u65F6\u751F\u6548\uFF09");
4136
- return true;
3948
+ printSuccess("\u5BF9\u8BDD\u5386\u53F2\u5DF2\u6E05\u7A7A");
3949
+ return "clear";
4137
3950
  case "/parallel": {
4138
3951
  const current = context.config.features.parallel_agents;
4139
3952
  const next = current === "on" ? "off" : "on";
@@ -4142,7 +3955,7 @@ function handleSlashCommand(input, context) {
4142
3955
  context.registry.unregister("spawn-agents");
4143
3956
  } else if (!context.registry.has("spawn-agents")) {
4144
3957
  context.registry.register(
4145
- createSpawnAgentsTool(context.client, context.registry, context.config, void 0, context.memoStore)
3958
+ createSpawnAgentsTool(context.client, context.registry, context.config)
4146
3959
  );
4147
3960
  }
4148
3961
  printSuccess(`\u5E76\u884C\u5B50 Agent \u529F\u80FD\u5DF2${next === "on" ? "\u5F00\u542F" : "\u5173\u95ED"}`);
@@ -4162,9 +3975,9 @@ function handleSlashCommand(input, context) {
4162
3975
  }
4163
3976
  case "/info":
4164
3977
  printInfo(`\u6A21\u578B: ${context.config.model}`);
4165
- printInfo(`Agent \u6A21\u5F0F: ${context.config.agent_mode}`);
4166
- printInfo(`\u534F\u4F5C\u6A21\u5F0F: ${context.config.collaboration}`);
4167
3978
  printInfo(`\u57FA\u7840 URL: ${context.config.base_url}`);
3979
+ printInfo(`\u5B50 Agent: ${context.agentRegistry.listNames().join(", ") || "\u65E0"}`);
3980
+ printInfo(`\u6280\u80FD: ${context.skillRegistry.listNames().join(", ") || "\u65E0"}`);
4168
3981
  return true;
4169
3982
  case "/exit":
4170
3983
  process.exit(0);
@@ -4175,7 +3988,11 @@ function handleSlashCommand(input, context) {
4175
3988
  }
4176
3989
  async function startRepl(options) {
4177
3990
  const { config } = options;
4178
- const { systemPrompt } = await buildPrompt(config);
3991
+ const agentRegistry = new SubAgentConfigRegistry();
3992
+ for (const agentConfig of loadAllAgentConfigs()) {
3993
+ agentRegistry.register(agentConfig);
3994
+ }
3995
+ const { systemPrompt } = await buildPrompt(config, agentRegistry.list());
4179
3996
  const registry = new ToolRegistry();
4180
3997
  registerCoreTools(registry);
4181
3998
  registry.setPermissions(config.permissions);
@@ -4187,23 +4004,19 @@ async function startRepl(options) {
4187
4004
  maxTokens: config.max_tokens
4188
4005
  });
4189
4006
  const todoStore = new TodoStore();
4190
- const memoStore = new MemoStore();
4191
4007
  if (config.features.parallel_agents === "on") {
4192
- registry.register(createSpawnAgentsTool(client, registry, config, void 0, memoStore));
4008
+ registry.register(createSpawnAgentsTool(client, registry, config));
4193
4009
  }
4194
4010
  if (config.features.todo === "on") {
4195
4011
  registry.register(createTodoTool(todoStore));
4196
4012
  }
4197
- registry.register(createMemoTool(memoStore));
4198
- let singleAgent;
4199
- let orchestrator;
4200
- if (config.agent_mode === "single") {
4201
- singleAgent = new Agent(client, registry, config, systemPrompt);
4202
- } else {
4203
- orchestrator = new Orchestrator(registry, config, systemPrompt, memoStore);
4013
+ const skillRegistry = new SkillRegistry();
4014
+ for (const skill of loadAllSkills()) {
4015
+ skillRegistry.register(skill);
4204
4016
  }
4205
- const modeLabel = config.agent_mode === "dual" ? `\u53CCAgent (${config.collaboration})` : "\u5355Agent";
4206
- printWelcome(config.model, modeLabel);
4017
+ registry.register(createDispatchTool(client, registry, config, agentRegistry));
4018
+ let agent = new Agent(client, registry, config, systemPrompt);
4019
+ printWelcome(config.model);
4207
4020
  const rl = readline.createInterface({
4208
4021
  input: process.stdin,
4209
4022
  output: process.stdout,
@@ -4211,13 +4024,13 @@ async function startRepl(options) {
4211
4024
  historySize: 100
4212
4025
  });
4213
4026
  setConfirmHandler(async (promptText) => {
4214
- return new Promise((resolve8) => {
4027
+ return new Promise((resolve10) => {
4215
4028
  process.stdout.write(promptText);
4216
4029
  const onData = (data) => {
4217
4030
  process.stdin.removeListener("data", onData);
4218
4031
  process.stdin.pause();
4219
4032
  const answer = data.toString().trim().toLowerCase();
4220
- resolve8(answer === "y");
4033
+ resolve10(answer === "y");
4221
4034
  };
4222
4035
  process.stdin.resume();
4223
4036
  process.stdin.once("data", onData);
@@ -4231,29 +4044,75 @@ async function startRepl(options) {
4231
4044
  return;
4232
4045
  }
4233
4046
  if (input.startsWith("/")) {
4047
+ const slashParts = input.slice(1).split(/\s+/);
4048
+ const skillName = slashParts[0] ?? "";
4049
+ const skillArgs = slashParts.slice(1).join(" ");
4050
+ const skill = skillRegistry.get(skillName);
4051
+ if (skill) {
4052
+ const expandedPrompt = skillRegistry.expandPrompt(skill, skillArgs);
4053
+ rl.pause();
4054
+ let isStreaming2 = false;
4055
+ const thinkFilter2 = createThinkFilter();
4056
+ const callbacks2 = {
4057
+ onContent: (text) => {
4058
+ const filtered = thinkFilter2(text);
4059
+ if (!filtered) return;
4060
+ if (!isStreaming2) {
4061
+ isStreaming2 = true;
4062
+ process.stdout.write("\n");
4063
+ }
4064
+ printStream(filtered);
4065
+ },
4066
+ onToolExecuting: (name, params) => {
4067
+ if (isStreaming2) {
4068
+ process.stdout.write("\n");
4069
+ isStreaming2 = false;
4070
+ }
4071
+ printToolCall(name, params);
4072
+ },
4073
+ onToolResult: (name, result, truncated) => {
4074
+ printToolResult(name, result, truncated);
4075
+ },
4076
+ onError: (err) => {
4077
+ if (isStreaming2) {
4078
+ process.stdout.write("\n");
4079
+ isStreaming2 = false;
4080
+ }
4081
+ printError(err.message);
4082
+ }
4083
+ };
4084
+ try {
4085
+ await agent.run(expandedPrompt, callbacks2);
4086
+ } catch (err) {
4087
+ const msg = err instanceof Error ? err.message : String(err);
4088
+ printError(msg);
4089
+ }
4090
+ if (isStreaming2) {
4091
+ process.stdout.write("\n");
4092
+ }
4093
+ console.log();
4094
+ rl.resume();
4095
+ rl.prompt();
4096
+ return;
4097
+ }
4234
4098
  const handled = handleSlashCommand(input, {
4235
4099
  config,
4236
- orchestrator,
4237
4100
  registry,
4238
4101
  client,
4239
4102
  todoStore,
4240
- memoStore,
4241
- setMode: (mode) => {
4242
- orchestrator?.setMode(mode);
4243
- }
4103
+ agentRegistry,
4104
+ skillRegistry
4244
4105
  });
4106
+ if (handled === "clear") {
4107
+ agent = new Agent(client, registry, config, systemPrompt);
4108
+ rl.prompt();
4109
+ return;
4110
+ }
4245
4111
  if (handled) {
4246
4112
  rl.prompt();
4247
4113
  return;
4248
4114
  }
4249
4115
  }
4250
- if (config.agent_mode === "single" && !singleAgent) {
4251
- singleAgent = new Agent(client, registry, config, systemPrompt);
4252
- orchestrator = void 0;
4253
- } else if (config.agent_mode === "dual" && !orchestrator) {
4254
- orchestrator = new Orchestrator(registry, config, systemPrompt, memoStore);
4255
- singleAgent = void 0;
4256
- }
4257
4116
  rl.pause();
4258
4117
  let isStreaming = false;
4259
4118
  const thinkFilter = createThinkFilter();
@@ -4277,16 +4136,6 @@ async function startRepl(options) {
4277
4136
  onToolResult: (name, result, truncated) => {
4278
4137
  printToolResult(name, result, truncated);
4279
4138
  },
4280
- onCoderStart: () => {
4281
- if (isStreaming) {
4282
- process.stdout.write("\n");
4283
- isStreaming = false;
4284
- }
4285
- printInfo("\u7F16\u7801 Agent \u5F00\u59CB\u5DE5\u4F5C...");
4286
- },
4287
- onCoderEnd: () => {
4288
- printInfo("\u7F16\u7801 Agent \u5B8C\u6210");
4289
- },
4290
4139
  onError: (err) => {
4291
4140
  if (isStreaming) {
4292
4141
  process.stdout.write("\n");
@@ -4296,11 +4145,7 @@ async function startRepl(options) {
4296
4145
  }
4297
4146
  };
4298
4147
  try {
4299
- if (config.agent_mode === "single" && singleAgent) {
4300
- await singleAgent.run(input, callbacks);
4301
- } else if (orchestrator) {
4302
- await orchestrator.run(input, callbacks);
4303
- }
4148
+ await agent.run(input, callbacks);
4304
4149
  } catch (err) {
4305
4150
  const msg = err instanceof Error ? err.message : String(err);
4306
4151
  printError(msg);
@@ -4320,13 +4165,18 @@ async function startRepl(options) {
4320
4165
 
4321
4166
  // src/cli/index.ts
4322
4167
  init_todo_store();
4323
- init_memo_store();
4168
+ init_registry2();
4169
+ init_loader();
4324
4170
  init_spawn_agents();
4325
4171
  init_todo();
4326
- init_memo();
4172
+ init_dispatch();
4327
4173
  init_bridge();
4328
4174
  async function runOnce(prompt, config) {
4329
- const { systemPrompt } = await buildPrompt(config);
4175
+ const agentRegistry = new SubAgentConfigRegistry();
4176
+ for (const agentConfig of loadAllAgentConfigs()) {
4177
+ agentRegistry.register(agentConfig);
4178
+ }
4179
+ const { systemPrompt } = await buildPrompt(config, agentRegistry.list());
4330
4180
  const registry = new ToolRegistry();
4331
4181
  registerCoreTools(registry);
4332
4182
  registry.setPermissions(config.permissions);
@@ -4338,14 +4188,13 @@ async function runOnce(prompt, config) {
4338
4188
  maxTokens: config.max_tokens
4339
4189
  });
4340
4190
  const todoStore = new TodoStore();
4341
- const memoStore = new MemoStore();
4342
4191
  if (config.features.parallel_agents === "on") {
4343
- registry.register(createSpawnAgentsTool(client, registry, config, void 0, memoStore));
4192
+ registry.register(createSpawnAgentsTool(client, registry, config));
4344
4193
  }
4345
4194
  if (config.features.todo === "on") {
4346
4195
  registry.register(createTodoTool(todoStore));
4347
4196
  }
4348
- registry.register(createMemoTool(memoStore));
4197
+ registry.register(createDispatchTool(client, registry, config, agentRegistry));
4349
4198
  let isStreaming = false;
4350
4199
  const thinkFilter = createThinkFilter();
4351
4200
  const callbacks = {
@@ -4367,28 +4216,13 @@ async function runOnce(prompt, config) {
4367
4216
  onToolResult: (name, result, truncated) => {
4368
4217
  printToolResult(name, result, truncated);
4369
4218
  },
4370
- onCoderStart: () => {
4371
- if (isStreaming) {
4372
- process.stdout.write("\n");
4373
- isStreaming = false;
4374
- }
4375
- printInfo("\u7F16\u7801 Agent \u5F00\u59CB\u5DE5\u4F5C...");
4376
- },
4377
- onCoderEnd: () => {
4378
- printInfo("\u7F16\u7801 Agent \u5B8C\u6210");
4379
- },
4380
4219
  onError: (err) => {
4381
4220
  printError(err.message);
4382
4221
  }
4383
4222
  };
4384
4223
  try {
4385
- if (config.agent_mode === "single") {
4386
- const agent = new Agent(client, registry, config, systemPrompt, void 0, memoStore);
4387
- await agent.run(prompt, callbacks);
4388
- } else {
4389
- const orchestrator = new Orchestrator(registry, config, systemPrompt, memoStore);
4390
- await orchestrator.run(prompt, callbacks);
4391
- }
4224
+ const agent = new Agent(client, registry, config, systemPrompt);
4225
+ await agent.run(prompt, callbacks);
4392
4226
  } catch (err) {
4393
4227
  const msg = err instanceof Error ? err.message : String(err);
4394
4228
  printError(msg);
@@ -4400,7 +4234,7 @@ async function runOnce(prompt, config) {
4400
4234
  }
4401
4235
  function createCli() {
4402
4236
  const program2 = new Command();
4403
- program2.name("zencode").description("\u6781\u7B80 CLI AI \u7F16\u7A0B\u5DE5\u5177").version("0.2.1").option("-m, --model <model>", "\u6307\u5B9A\u6A21\u578B\u540D\u79F0").option("-k, --api-key <key>", "API \u5BC6\u94A5").option("-u, --base-url <url>", "API \u57FA\u7840 URL").option("--single", "\u4F7F\u7528\u5355 Agent \u6A21\u5F0F").option("--dual", "\u4F7F\u7528\u53CC Agent \u6A21\u5F0F").option("--mode <mode>", "\u534F\u4F5C\u6A21\u5F0F (delegated/autonomous/controlled)").option("--simple", "\u4F7F\u7528\u7B80\u5355 REPL \u6A21\u5F0F\uFF08\u975E\u5168\u5C4F TUI\uFF09").argument("[prompt...]", "\u76F4\u63A5\u6267\u884C\u7684\u63D0\u793A\u8BCD\uFF08\u975E\u4EA4\u4E92\u5F0F\uFF09").action(async (promptParts, opts) => {
4237
+ program2.name("zencode").description("\u6781\u7B80 CLI AI \u7F16\u7A0B\u5DE5\u5177").version("0.2.1").option("-m, --model <model>", "\u6307\u5B9A\u6A21\u578B\u540D\u79F0").option("-k, --api-key <key>", "API \u5BC6\u94A5").option("-u, --base-url <url>", "API \u57FA\u7840 URL").option("--simple", "\u4F7F\u7528\u7B80\u5355 REPL \u6A21\u5F0F\uFF08\u975E\u5168\u5C4F TUI\uFF09").argument("[prompt...]", "\u76F4\u63A5\u6267\u884C\u7684\u63D0\u793A\u8BCD\uFF08\u975E\u4EA4\u4E92\u5F0F\uFF09").action(async (promptParts, opts) => {
4404
4238
  const config = loadConfig(opts);
4405
4239
  if (!config.api_key) {
4406
4240
  printError("\u672A\u8BBE\u7F6E API \u5BC6\u94A5\u3002\u8BF7\u901A\u8FC7\u4EE5\u4E0B\u65B9\u5F0F\u4E4B\u4E00\u8BBE\u7F6E\uFF1A");