zencode-cli 0.2.1 → 0.2.3

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,44 @@ var init_client = __esm({
168
56
  params.tools = tools;
169
57
  params.tool_choice = "auto";
170
58
  }
59
+ params.stream_options = { include_usage: true };
60
+ const abortController = new AbortController();
61
+ this.activeAbortController = abortController;
171
62
  try {
172
- const stream = await this.client.chat.completions.create(params);
63
+ const stream = await this.client.chat.completions.create(params, {
64
+ signal: abortController.signal
65
+ });
173
66
  let contentParts = [];
67
+ let reasoningParts = [];
68
+ let reasoningStarted = false;
69
+ let reasoningEnded = false;
174
70
  const toolCallMap = /* @__PURE__ */ new Map();
71
+ let usageInfo = null;
175
72
  for await (const chunk of stream) {
73
+ const chunkUsage = chunk.usage;
74
+ if (chunkUsage && chunkUsage.total_tokens) {
75
+ usageInfo = {
76
+ prompt_tokens: chunkUsage.prompt_tokens ?? 0,
77
+ completion_tokens: chunkUsage.completion_tokens ?? 0,
78
+ total_tokens: chunkUsage.total_tokens
79
+ };
80
+ }
176
81
  const choice = chunk.choices[0];
177
82
  if (!choice) continue;
178
83
  const delta = choice.delta;
84
+ if (delta.reasoning_content) {
85
+ reasoningParts.push(delta.reasoning_content);
86
+ if (!reasoningStarted) {
87
+ reasoningStarted = true;
88
+ callbacks.onContent?.("<think>");
89
+ }
90
+ callbacks.onContent?.(delta.reasoning_content);
91
+ }
179
92
  if (delta.content) {
93
+ if (reasoningStarted && !reasoningEnded) {
94
+ reasoningEnded = true;
95
+ callbacks.onContent?.("</think>");
96
+ }
180
97
  contentParts.push(delta.content);
181
98
  callbacks.onContent?.(delta.content);
182
99
  }
@@ -204,6 +121,10 @@ var init_client = __esm({
204
121
  }
205
122
  }
206
123
  }
124
+ if (reasoningStarted && !reasoningEnded) {
125
+ reasoningEnded = true;
126
+ callbacks.onContent?.("</think>");
127
+ }
207
128
  const toolCalls = [];
208
129
  for (const [, tc] of [...toolCallMap.entries()].sort(([a], [b]) => a - b)) {
209
130
  const toolCall = {
@@ -218,19 +139,32 @@ var init_client = __esm({
218
139
  callbacks.onToolCall?.(toolCall);
219
140
  }
220
141
  const fullContent = contentParts.join("");
142
+ const fullReasoning = reasoningParts.join("");
221
143
  const assistantMessage = {
222
144
  role: "assistant",
223
145
  content: fullContent || null
224
146
  };
147
+ if (fullReasoning) {
148
+ assistantMessage.reasoning_content = fullReasoning;
149
+ }
225
150
  if (toolCalls.length > 0) {
226
151
  assistantMessage.tool_calls = toolCalls;
227
152
  }
153
+ if (usageInfo) {
154
+ assistantMessage.usage = usageInfo;
155
+ }
228
156
  callbacks.onFinish?.(assistantMessage);
229
157
  return assistantMessage;
230
158
  } catch (error) {
231
159
  const err = error instanceof Error ? error : new Error(String(error));
232
- callbacks.onError?.(err);
160
+ if (!isAbortError(err)) {
161
+ callbacks.onError?.(err);
162
+ }
233
163
  throw err;
164
+ } finally {
165
+ if (this.activeAbortController === abortController) {
166
+ this.activeAbortController = null;
167
+ }
234
168
  }
235
169
  }
236
170
  /**
@@ -258,6 +192,10 @@ var init_client = __esm({
258
192
  role: "assistant",
259
193
  content: msg.content
260
194
  };
195
+ const reasoning = msg.reasoning_content;
196
+ if (reasoning && typeof reasoning === "string") {
197
+ result.reasoning_content = reasoning;
198
+ }
261
199
  if (msg.tool_calls && msg.tool_calls.length > 0) {
262
200
  result.tool_calls = msg.tool_calls.map((tc) => ({
263
201
  id: tc.id,
@@ -268,11 +206,24 @@ var init_client = __esm({
268
206
  }
269
207
  }));
270
208
  }
209
+ if (response.usage) {
210
+ result.usage = {
211
+ prompt_tokens: response.usage.prompt_tokens,
212
+ completion_tokens: response.usage.completion_tokens,
213
+ total_tokens: response.usage.total_tokens
214
+ };
215
+ }
271
216
  return result;
272
217
  }
273
218
  get modelName() {
274
219
  return this.model;
275
220
  }
221
+ abortActiveStream() {
222
+ if (this.activeAbortController) {
223
+ this.activeAbortController.abort();
224
+ this.activeAbortController = null;
225
+ }
226
+ }
276
227
  };
277
228
  }
278
229
  });
@@ -542,14 +493,24 @@ var init_edit_file = __esm({
542
493
 
543
494
  // src/tools/bash.ts
544
495
  import { exec } from "child_process";
545
- var DEFAULT_TIMEOUT, bashTool;
496
+ function decodeBuffer(buf) {
497
+ if (!IS_WIN) return buf.toString("utf-8");
498
+ try {
499
+ const decoder = new TextDecoder("gbk");
500
+ return decoder.decode(buf);
501
+ } catch {
502
+ return buf.toString("utf-8");
503
+ }
504
+ }
505
+ var DEFAULT_TIMEOUT, IS_WIN, bashTool;
546
506
  var init_bash = __esm({
547
507
  "src/tools/bash.ts"() {
548
508
  "use strict";
549
509
  DEFAULT_TIMEOUT = 12e4;
510
+ IS_WIN = process.platform === "win32";
550
511
  bashTool = {
551
512
  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",
513
+ 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
514
  parameters: {
554
515
  type: "object",
555
516
  properties: {
@@ -568,7 +529,7 @@ var init_bash = __esm({
568
529
  async execute(params) {
569
530
  const command = params["command"];
570
531
  const timeout = params["timeout"] || DEFAULT_TIMEOUT;
571
- return new Promise((resolve8) => {
532
+ return new Promise((resolve10) => {
572
533
  exec(
573
534
  command,
574
535
  {
@@ -576,9 +537,12 @@ var init_bash = __esm({
576
537
  timeout,
577
538
  maxBuffer: 1024 * 1024 * 10,
578
539
  // 10MB
579
- shell: process.platform === "win32" ? "cmd.exe" : "/bin/bash"
540
+ shell: process.platform === "win32" ? "cmd.exe" : "/bin/bash",
541
+ encoding: "buffer"
580
542
  },
581
- (error, stdout, stderr) => {
543
+ (error, stdoutBuf, stderrBuf) => {
544
+ const stdout = stdoutBuf ? decodeBuffer(stdoutBuf) : "";
545
+ const stderr = stderrBuf ? decodeBuffer(stderrBuf) : "";
582
546
  let output = "";
583
547
  if (stdout) output += stdout;
584
548
  if (stderr) output += (output ? "\n" : "") + `[stderr]
@@ -589,7 +553,7 @@ ${stderr}`;
589
553
  } else if (error && !stdout && !stderr) {
590
554
  output = `\u547D\u4EE4\u6267\u884C\u5931\u8D25\uFF1A${error.message}`;
591
555
  }
592
- resolve8({ content: output || "\uFF08\u65E0\u8F93\u51FA\uFF09" });
556
+ resolve10({ content: output || "\uFF08\u65E0\u8F93\u51FA\uFF09" });
593
557
  }
594
558
  );
595
559
  });
@@ -900,6 +864,18 @@ var init_conversation = __esm({
900
864
  content
901
865
  });
902
866
  }
867
+ /**
868
+ * 清除历史消息中的 reasoning_content(新一轮对话开始时调用)
869
+ * deepseek-reasoner 要求同一轮 tool call 循环内保留 reasoning_content,
870
+ * 但新一轮用户问题开始时应清除以节省带宽,API 也会忽略旧的 reasoning_content
871
+ */
872
+ clearReasoningContent() {
873
+ for (const msg of this.messages) {
874
+ if (msg.reasoning_content !== void 0) {
875
+ msg.reasoning_content = void 0;
876
+ }
877
+ }
878
+ }
903
879
  /**
904
880
  * 获取完整的消息列表(包含系统提示词)
905
881
  */
@@ -933,67 +909,6 @@ var init_conversation = __esm({
933
909
  }
934
910
  });
935
911
 
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
912
  // src/core/read-tracker.ts
998
913
  import * as fs6 from "fs";
999
914
  import * as path6 from "path";
@@ -1042,7 +957,6 @@ var init_agent = __esm({
1042
957
  "use strict";
1043
958
  init_permission();
1044
959
  init_conversation();
1045
- init_auto_memo();
1046
960
  init_read_tracker();
1047
961
  Agent = class {
1048
962
  conversation;
@@ -1050,24 +964,26 @@ var init_agent = __esm({
1050
964
  registry;
1051
965
  config;
1052
966
  fixedTools;
1053
- memoStore;
1054
967
  readTracker = new ReadTracker();
1055
- constructor(client, registry, config, systemPrompt, tools, memoStore) {
968
+ interrupted = false;
969
+ constructor(client, registry, config, systemPrompt, tools) {
1056
970
  this.client = client;
1057
971
  this.registry = registry;
1058
972
  this.config = config;
1059
973
  this.conversation = new Conversation();
1060
974
  this.conversation.setSystemPrompt(systemPrompt);
1061
975
  this.fixedTools = tools;
1062
- this.memoStore = memoStore;
1063
976
  }
1064
977
  /**
1065
978
  * 执行一轮完整的 agent 循环
1066
979
  */
1067
980
  async run(userMessage, callbacks = {}) {
981
+ this.interrupted = false;
982
+ this.conversation.clearReasoningContent();
1068
983
  this.conversation.addUserMessage(userMessage);
1069
984
  let lastContent = "";
1070
985
  while (true) {
986
+ if (this.interrupted) break;
1071
987
  const tools = this.fixedTools || this.registry.toToolDefinitions();
1072
988
  const assistantMsg = await this.client.chatStream(
1073
989
  this.conversation.getMessages(),
@@ -1080,6 +996,7 @@ var init_agent = __esm({
1080
996
  break;
1081
997
  }
1082
998
  for (const toolCall of assistantMsg.tool_calls) {
999
+ if (this.interrupted) break;
1083
1000
  const toolName = toolCall.function.name;
1084
1001
  let params;
1085
1002
  try {
@@ -1129,7 +1046,6 @@ var init_agent = __esm({
1129
1046
  }
1130
1047
  const result = await this.registry.execute(toolName, params, this.config.max_tool_output);
1131
1048
  callbacks.onToolResult?.(toolName, result.content, result.truncated ?? false);
1132
- autoMemoForTool(this.memoStore, toolName, params, result.content);
1133
1049
  if (toolName === "read-file") {
1134
1050
  this.readTracker.markRead(params["path"]);
1135
1051
  } else if (toolName === "write-file") {
@@ -1147,6 +1063,10 @@ var init_agent = __esm({
1147
1063
  }
1148
1064
  return lastContent;
1149
1065
  }
1066
+ interrupt() {
1067
+ this.interrupted = true;
1068
+ this.client.abortActiveStream();
1069
+ }
1150
1070
  /**
1151
1071
  * 获取对话历史
1152
1072
  */
@@ -1157,487 +1077,39 @@ var init_agent = __esm({
1157
1077
  }
1158
1078
  });
1159
1079
 
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
- });
1080
+ // src/core/prompt/layers/core.ts
1081
+ import * as os2 from "os";
1082
+ function buildCorePrompt() {
1083
+ 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";
1084
+ 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
1085
 
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
1086
+ \u5DE5\u4F5C\u76EE\u5F55\uFF1A${process.cwd()}
1087
+ \u7CFB\u7EDF\uFF1A${os2.platform()} ${os2.arch()}
1298
1088
 
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
1089
+ # \u5DE5\u5177\u4F7F\u7528\u539F\u5219
1309
1090
 
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
1091
+ \u4F60\u6709\u4EE5\u4E0B\u5DE5\u5177\u53EF\u7528\uFF0C\u8BF7\u6839\u636E\u4EFB\u52A1\u9009\u62E9\u6700\u5408\u9002\u7684\u5DE5\u5177\uFF1A
1312
1092
 
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
1093
+ - **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
1094
+ - **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
1095
+ - \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
1096
+ - 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
1097
+ - 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
1098
+ - \u4E0D\u8981\u51ED\u8BB0\u5FC6\u731C\u6D4B\u6587\u4EF6\u5185\u5BB9\uFF0C\u5FC5\u987B\u57FA\u4E8E read-file \u7684\u5B9E\u9645\u8FD4\u56DE\u503C
1099
+ - **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
1100
+ - **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
1101
+ - **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
1102
+ - **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
1318
1103
 
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
1104
+ \u5173\u952E\u89C4\u5219\uFF1A
1105
+ - **\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
1106
+ - 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
1107
+ - \u4F18\u5148 edit-file \u7F16\u8F91\u5DF2\u6709\u6587\u4EF6\uFF0C\u800C\u975E write-file \u91CD\u5199
1108
+ - \u4E0D\u8981\u521B\u5EFA\u4E0D\u5FC5\u8981\u7684\u65B0\u6587\u4EF6\uFF0C\u4F18\u5148\u5728\u73B0\u6709\u6587\u4EF6\u4E2D\u4FEE\u6539
1109
+ - \u53EA\u505A\u5FC5\u8981\u7684\u6700\u5C0F\u6539\u52A8\uFF0C\u4E0D\u505A\u989D\u5916"\u6539\u8FDB"
1110
+ - \u4E0D\u8981\u6DFB\u52A0\u7528\u6237\u672A\u8981\u6C42\u7684\u6CE8\u91CA\u3001\u6587\u6863\u3001\u7C7B\u578B\u6CE8\u89E3
1111
+ - \u4E0D\u8981\u5F15\u5165\u5B89\u5168\u6F0F\u6D1E\uFF08\u6CE8\u5165\u3001XSS\u3001SQL \u6CE8\u5165\u7B49 OWASP Top 10\uFF09
1112
+ - \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
1113
 
1642
1114
  # \u4EA4\u4E92\u98CE\u683C
1643
1115
 
@@ -1646,9 +1118,11 @@ function buildCorePrompt() {
1646
1118
  - \u4E0D\u8981\u7ED9\u51FA\u65F6\u95F4\u9884\u4F30\uFF08"\u5927\u6982\u9700\u8981\u51E0\u5206\u949F"\u4E4B\u7C7B\uFF09
1647
1119
  - \u56DE\u590D\u7B80\u6D01\uFF0C\u76F4\u63A5\u7ED9\u7ED3\u679C`;
1648
1120
  }
1121
+ var IS_WIN2;
1649
1122
  var init_core = __esm({
1650
1123
  "src/core/prompt/layers/core.ts"() {
1651
1124
  "use strict";
1125
+ IS_WIN2 = os2.platform() === "win32";
1652
1126
  }
1653
1127
  });
1654
1128
 
@@ -1658,8 +1132,8 @@ function buildPlanningPrompt() {
1658
1132
 
1659
1133
  \u5904\u7406\u7F16\u7A0B\u4EFB\u52A1\u65F6\uFF1A
1660
1134
  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"
1135
+ 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
1136
+ 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
1137
 
1664
1138
  \u591A\u6B65\u4EFB\u52A1\u7BA1\u7406\uFF1A
1665
1139
  - \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 +1141,8 @@ function buildPlanningPrompt() {
1667
1141
  - \u6BCF\u6B65\u5B8C\u6210\u540E\u68C0\u67E5\u8BA1\u5212\uFF0C\u51B3\u5B9A\u4E0B\u4E00\u6B65
1668
1142
 
1669
1143
  \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
1144
  - \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`;
1145
+ - \u4E0D\u8981\u7559\u4E0BTODO\u7136\u540E\u653E\u7740\u4E0D\u7BA1`;
1675
1146
  }
1676
1147
  var init_planning = __esm({
1677
1148
  "src/core/prompt/layers/planning.ts"() {
@@ -1695,7 +1166,7 @@ function buildParallelPrompt() {
1695
1166
  \u6B63\u786E\uFF1Aspawn-agents \u540C\u65F6\u8BFB auth controller\u3001auth service\u3001auth middleware\u3001auth types
1696
1167
  \u9519\u8BEF\uFF1A\u5148 read-file controller\uFF0C\u518D read-file service\uFF0C\u518D read-file middleware...
1697
1168
 
1698
- \u6BCF\u4E2A\u5B50 Agent \u6709\u72EC\u7ACB\u5BF9\u8BDD\uFF0C\u9ED8\u8BA4\u53EF\u7528 read-file\u3001glob\u3001grep\u3001memo\u3002`;
1169
+ \u6BCF\u4E2A\u5B50 Agent \u6709\u72EC\u7ACB\u5BF9\u8BDD\uFF0C\u9ED8\u8BA4\u53EF\u7528 read-file\u3001glob\u3001grep\u3002`;
1699
1170
  }
1700
1171
  var init_parallel = __esm({
1701
1172
  "src/core/prompt/layers/parallel.ts"() {
@@ -1728,6 +1199,24 @@ var init_git = __esm({
1728
1199
  }
1729
1200
  });
1730
1201
 
1202
+ // src/core/prompt/layers/agents.ts
1203
+ function buildAgentsPrompt(agents) {
1204
+ if (agents.length === 0) return null;
1205
+ const agentList = agents.map((a) => `- **${a.name}**\uFF1A${a.description}`).join("\n");
1206
+ return `# \u5B50 Agent
1207
+
1208
+ \u4F60\u53EF\u4EE5\u901A\u8FC7 dispatch \u5DE5\u5177\u8C03\u5EA6\u4EE5\u4E0B\u4E13\u7528\u5B50 Agent\uFF1A
1209
+
1210
+ ${agentList}
1211
+
1212
+ \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`;
1213
+ }
1214
+ var init_agents = __esm({
1215
+ "src/core/prompt/layers/agents.ts"() {
1216
+ "use strict";
1217
+ }
1218
+ });
1219
+
1731
1220
  // src/core/prompt/layers/project.ts
1732
1221
  import * as fs7 from "fs/promises";
1733
1222
  import * as path7 from "path";
@@ -1769,7 +1258,7 @@ function isGitRepo() {
1769
1258
  return false;
1770
1259
  }
1771
1260
  }
1772
- async function buildPrompt(config) {
1261
+ async function buildPrompt(config, agents) {
1773
1262
  const layers = [];
1774
1263
  layers.push(buildCorePrompt());
1775
1264
  if (config.features.planning_layer === "on") {
@@ -1782,6 +1271,12 @@ async function buildPrompt(config) {
1782
1271
  if (config.features.parallel_agents === "on") {
1783
1272
  layers.push(buildParallelPrompt());
1784
1273
  }
1274
+ if (agents && agents.length > 0) {
1275
+ const agentsPrompt = buildAgentsPrompt(agents);
1276
+ if (agentsPrompt) {
1277
+ layers.push(agentsPrompt);
1278
+ }
1279
+ }
1785
1280
  const projectPrompt = await buildProjectPrompt();
1786
1281
  if (projectPrompt) {
1787
1282
  layers.push(projectPrompt);
@@ -1800,6 +1295,7 @@ var init_builder = __esm({
1800
1295
  init_planning();
1801
1296
  init_parallel();
1802
1297
  init_git();
1298
+ init_agents();
1803
1299
  init_project();
1804
1300
  }
1805
1301
  });
@@ -1854,74 +1350,266 @@ var init_todo_store = __esm({
1854
1350
  }
1855
1351
  });
1856
1352
 
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"() {
1353
+ // src/core/sub-agents/registry.ts
1354
+ var SubAgentConfigRegistry;
1355
+ var init_registry2 = __esm({
1356
+ "src/core/sub-agents/registry.ts"() {
1861
1357
  "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;
1358
+ SubAgentConfigRegistry = class {
1359
+ configs = /* @__PURE__ */ new Map();
1360
+ register(config) {
1361
+ this.configs.set(config.name, config);
1888
1362
  }
1889
- read(key) {
1890
- return this.entries.get(key) ?? null;
1363
+ get(name) {
1364
+ return this.configs.get(name);
1365
+ }
1366
+ has(name) {
1367
+ return this.configs.has(name);
1891
1368
  }
1892
1369
  list() {
1893
- return [...this.entries.values()].map((e) => ({
1894
- key: e.key,
1895
- author: e.author,
1896
- summary: e.summary
1897
- }));
1370
+ return [...this.configs.values()];
1898
1371
  }
1899
- delete(key) {
1900
- return this.entries.delete(key);
1372
+ listNames() {
1373
+ return [...this.configs.keys()];
1901
1374
  }
1902
- clear() {
1903
- this.entries.clear();
1375
+ /**
1376
+ * 生成子 Agent 列表描述(用于 dispatch 工具的说明)
1377
+ */
1378
+ buildAgentListDescription() {
1379
+ if (this.configs.size === 0) return "\u6682\u65E0\u53EF\u7528\u5B50 Agent";
1380
+ return [...this.configs.values()].map((s) => `- ${s.name}: ${s.description}`).join("\n");
1381
+ }
1382
+ };
1383
+ }
1384
+ });
1385
+
1386
+ // src/core/sub-agents/presets.ts
1387
+ var presetAgents;
1388
+ var init_presets = __esm({
1389
+ "src/core/sub-agents/presets.ts"() {
1390
+ "use strict";
1391
+ presetAgents = [
1392
+ {
1393
+ name: "reviewer",
1394
+ description: "\u4EE3\u7801\u5BA1\u67E5\uFF1A\u53D1\u73B0 bug\u3001\u5B89\u5168\u6F0F\u6D1E\u3001\u6027\u80FD\u95EE\u9898",
1395
+ 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
1396
+
1397
+ \u5BA1\u67E5\u7EF4\u5EA6\uFF08\u6309\u4F18\u5148\u7EA7\uFF09\uFF1A
1398
+ 1. \u6B63\u786E\u6027\uFF1A\u903B\u8F91\u9519\u8BEF\u3001\u8FB9\u754C\u6761\u4EF6\u3001\u7A7A\u503C/\u672A\u5B9A\u4E49\u5904\u7406
1399
+ 2. \u5B89\u5168\uFF1A\u6CE8\u5165\u3001XSS\u3001\u654F\u611F\u4FE1\u606F\u6CC4\u9732\u3001\u6743\u9650\u68C0\u67E5
1400
+ 3. \u6027\u80FD\uFF1A\u4E0D\u5FC5\u8981\u7684\u5FAA\u73AF\u3001\u5185\u5B58\u6CC4\u6F0F\u3001N+1 \u67E5\u8BE2
1401
+ 4. \u53EF\u7EF4\u62A4\u6027\uFF1A\u547D\u540D\u3001\u91CD\u590D\u4EE3\u7801\u3001\u8FC7\u5EA6\u590D\u6742
1402
+
1403
+ \u8F93\u51FA\u683C\u5F0F\uFF1A
1404
+ - \u6BCF\u4E2A\u95EE\u9898\uFF1A\u6587\u4EF6\u8DEF\u5F84:\u884C\u53F7 + \u95EE\u9898\u63CF\u8FF0 + \u5EFA\u8BAE\u4FEE\u590D
1405
+ - \u6CA1\u6709\u95EE\u9898\u5C31\u8BF4\u6CA1\u6709\u95EE\u9898\uFF0C\u4E0D\u8981\u786C\u51D1
1406
+ - \u4E0D\u8981\u91CD\u5199\u4EE3\u7801\uFF0C\u53EA\u6307\u51FA\u95EE\u9898\u548C\u4FEE\u590D\u65B9\u5411`,
1407
+ tools: ["read-file", "glob", "grep"],
1408
+ max_turns: 10,
1409
+ timeout: 60
1410
+ },
1411
+ {
1412
+ name: "researcher",
1413
+ description: "\u4EE3\u7801\u5E93\u7814\u7A76\uFF1A\u6DF1\u5EA6\u5206\u6790\u67B6\u6784\u3001\u4F9D\u8D56\u548C\u5B9E\u73B0\u7EC6\u8282",
1414
+ 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
1415
+
1416
+ \u5206\u6790\u65B9\u6CD5\uFF1A
1417
+ 1. \u5148 glob \u4E86\u89E3\u6587\u4EF6\u7ED3\u6784
1418
+ 2. grep \u641C\u7D22\u5173\u952E\u5165\u53E3\u70B9\u3001\u5BFC\u51FA\u3001\u4F9D\u8D56
1419
+ 3. read-file \u9605\u8BFB\u6838\u5FC3\u6587\u4EF6
1420
+
1421
+ \u8F93\u51FA\u5185\u5BB9\uFF1A
1422
+ - \u6A21\u5757\u804C\u8D23\u548C\u8FB9\u754C
1423
+ - \u5173\u952E\u6587\u4EF6\u53CA\u5176\u4F5C\u7528
1424
+ - \u6570\u636E\u6D41\u548C\u8C03\u7528\u94FE
1425
+ - \u5916\u90E8\u4F9D\u8D56
1426
+ - \u5982\u6709\u7528\u6237\u5177\u4F53\u95EE\u9898\uFF0C\u9488\u5BF9\u6027\u56DE\u7B54`,
1427
+ tools: ["read-file", "glob", "grep"],
1428
+ max_turns: 15,
1429
+ timeout: 120
1430
+ },
1431
+ {
1432
+ name: "refactor",
1433
+ description: "\u91CD\u6784\u4E13\u5BB6\uFF1A\u5206\u6790\u4EE3\u7801\u7ED3\u6784\u5E76\u5B9E\u65BD\u91CD\u6784",
1434
+ 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
1435
+
1436
+ \u91CD\u6784\u539F\u5219\uFF1A
1437
+ - \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
1438
+ - \u4FDD\u6301\u884C\u4E3A\u4E0D\u53D8\uFF0C\u4E0D\u6DFB\u52A0\u65B0\u529F\u80FD
1439
+ - \u6BCF\u6B21\u53EA\u505A\u4E00\u4E2A\u91CD\u6784\uFF0C\u4E0D\u8981\u540C\u65F6\u6539\u592A\u591A
1440
+ - \u4FEE\u6539\u524D\u5FC5\u987B read-file \u786E\u8BA4\u5F53\u524D\u5185\u5BB9
1441
+
1442
+ \u5E38\u89C1\u91CD\u6784\uFF1A
1443
+ - \u63D0\u53D6\u91CD\u590D\u4EE3\u7801\u4E3A\u51FD\u6570
1444
+ - \u7B80\u5316\u8FC7\u6DF1\u7684\u5D4C\u5957\uFF08\u63D0\u524D\u8FD4\u56DE\uFF09
1445
+ - \u62C6\u5206\u8FC7\u5927\u7684\u51FD\u6570
1446
+ - \u6539\u5584\u547D\u540D\u4F7F\u610F\u56FE\u66F4\u6E05\u6670`,
1447
+ tools: ["read-file", "write-file", "edit-file", "glob", "grep"],
1448
+ max_turns: 15,
1449
+ timeout: 120
1450
+ }
1451
+ ];
1452
+ }
1453
+ });
1454
+
1455
+ // src/core/sub-agents/loader.ts
1456
+ import * as fs9 from "fs";
1457
+ import * as path8 from "path";
1458
+ import * as os3 from "os";
1459
+ import { parse as parseYaml2 } from "yaml";
1460
+ function loadAgentYaml(filePath) {
1461
+ try {
1462
+ const content = fs9.readFileSync(filePath, "utf-8");
1463
+ const parsed = parseYaml2(content);
1464
+ if (!parsed || typeof parsed !== "object") return null;
1465
+ if (!parsed["name"] || !parsed["prompt"] || !parsed["tools"]) return null;
1466
+ return {
1467
+ name: parsed["name"],
1468
+ description: parsed["description"] || "",
1469
+ prompt: parsed["prompt"],
1470
+ tools: parsed["tools"],
1471
+ max_turns: parsed["max_turns"],
1472
+ timeout: parsed["timeout"],
1473
+ model: parsed["model"]
1474
+ };
1475
+ } catch {
1476
+ return null;
1477
+ }
1478
+ }
1479
+ function loadAgentsFromDir(dir) {
1480
+ try {
1481
+ if (!fs9.existsSync(dir)) return [];
1482
+ const files = fs9.readdirSync(dir).filter((f) => f.endsWith(".yaml") || f.endsWith(".yml"));
1483
+ const agents = [];
1484
+ for (const file of files) {
1485
+ const agent = loadAgentYaml(path8.join(dir, file));
1486
+ if (agent) agents.push(agent);
1487
+ }
1488
+ return agents;
1489
+ } catch {
1490
+ return [];
1491
+ }
1492
+ }
1493
+ function loadAllAgentConfigs() {
1494
+ const configMap = /* @__PURE__ */ new Map();
1495
+ for (const agent of presetAgents) {
1496
+ configMap.set(agent.name, agent);
1497
+ }
1498
+ const globalDir = path8.join(os3.homedir(), ".zencode", "agents");
1499
+ for (const agent of loadAgentsFromDir(globalDir)) {
1500
+ configMap.set(agent.name, agent);
1501
+ }
1502
+ const projectDir = path8.resolve(".zencode", "agents");
1503
+ for (const agent of loadAgentsFromDir(projectDir)) {
1504
+ configMap.set(agent.name, agent);
1505
+ }
1506
+ return [...configMap.values()];
1507
+ }
1508
+ var init_loader = __esm({
1509
+ "src/core/sub-agents/loader.ts"() {
1510
+ "use strict";
1511
+ init_presets();
1512
+ }
1513
+ });
1514
+
1515
+ // src/core/skills/registry.ts
1516
+ var SkillRegistry;
1517
+ var init_registry3 = __esm({
1518
+ "src/core/skills/registry.ts"() {
1519
+ "use strict";
1520
+ SkillRegistry = class {
1521
+ skills = /* @__PURE__ */ new Map();
1522
+ register(skill) {
1523
+ this.skills.set(skill.name, skill);
1524
+ }
1525
+ get(name) {
1526
+ return this.skills.get(name);
1527
+ }
1528
+ has(name) {
1529
+ return this.skills.has(name);
1530
+ }
1531
+ list() {
1532
+ return [...this.skills.values()];
1533
+ }
1534
+ listNames() {
1535
+ return [...this.skills.keys()];
1904
1536
  }
1905
1537
  /**
1906
- * 生成备忘录索引(注入 Coder 任务中)
1907
- * 只输出 key + summary,清爽紧凑
1908
- * Coder 需要详情时用 memo read <key> 获取完整内容
1538
+ * 展开 skill 的 prompt 模板,替换 $ARGS 为用户参数
1909
1539
  */
1910
- buildIndex() {
1911
- if (this.entries.size === 0) return null;
1912
- return [...this.entries.values()].map((e) => `[${e.key}] ${e.summary}`).join("\n");
1540
+ expandPrompt(skill, args) {
1541
+ if (args && skill.prompt.includes("$ARGS")) {
1542
+ return skill.prompt.replace(/\$ARGS/g, args);
1543
+ }
1544
+ if (args) {
1545
+ return `${skill.prompt}
1546
+
1547
+ \u7528\u6237\u8865\u5145: ${args}`;
1548
+ }
1549
+ return skill.prompt;
1913
1550
  }
1914
1551
  };
1915
1552
  }
1916
1553
  });
1917
1554
 
1555
+ // src/core/skills/loader.ts
1556
+ import * as fs10 from "fs";
1557
+ import * as path9 from "path";
1558
+ import * as os4 from "os";
1559
+ import { parse as parseYaml3 } from "yaml";
1560
+ function loadSkillYaml(filePath) {
1561
+ try {
1562
+ const content = fs10.readFileSync(filePath, "utf-8");
1563
+ const parsed = parseYaml3(content);
1564
+ if (!parsed || typeof parsed !== "object") return null;
1565
+ if (!parsed["name"] || !parsed["prompt"]) return null;
1566
+ return {
1567
+ name: parsed["name"],
1568
+ description: parsed["description"] || "",
1569
+ prompt: parsed["prompt"]
1570
+ };
1571
+ } catch {
1572
+ return null;
1573
+ }
1574
+ }
1575
+ function loadSkillsFromDir(dir) {
1576
+ try {
1577
+ if (!fs10.existsSync(dir)) return [];
1578
+ const files = fs10.readdirSync(dir).filter((f) => f.endsWith(".yaml") || f.endsWith(".yml"));
1579
+ const skills = [];
1580
+ for (const file of files) {
1581
+ const skill = loadSkillYaml(path9.join(dir, file));
1582
+ if (skill) skills.push(skill);
1583
+ }
1584
+ return skills;
1585
+ } catch {
1586
+ return [];
1587
+ }
1588
+ }
1589
+ function loadAllSkills() {
1590
+ const skillMap = /* @__PURE__ */ new Map();
1591
+ const globalDir = path9.join(os4.homedir(), ".zencode", "skills");
1592
+ for (const skill of loadSkillsFromDir(globalDir)) {
1593
+ skillMap.set(skill.name, skill);
1594
+ }
1595
+ const projectDir = path9.resolve(".zencode", "skills");
1596
+ for (const skill of loadSkillsFromDir(projectDir)) {
1597
+ skillMap.set(skill.name, skill);
1598
+ }
1599
+ return [...skillMap.values()];
1600
+ }
1601
+ var init_loader2 = __esm({
1602
+ "src/core/skills/loader.ts"() {
1603
+ "use strict";
1604
+ }
1605
+ });
1606
+
1918
1607
  // src/core/sub-agent.ts
1919
1608
  var DEFAULT_TIMEOUT_MS, SubAgent;
1920
1609
  var init_sub_agent = __esm({
1921
1610
  "src/core/sub-agent.ts"() {
1922
1611
  "use strict";
1923
1612
  init_conversation();
1924
- init_auto_memo();
1925
1613
  init_read_tracker();
1926
1614
  DEFAULT_TIMEOUT_MS = 12e4;
1927
1615
  SubAgent = class {
@@ -1932,8 +1620,8 @@ var init_sub_agent = __esm({
1932
1620
  allowedTools;
1933
1621
  maxTurns;
1934
1622
  timeoutMs;
1935
- memoStore;
1936
- constructor(client, registry, config, task, allowedTools = ["read-file", "glob", "grep", "memo"], maxTurns = 10, timeoutMs = DEFAULT_TIMEOUT_MS, memoStore) {
1623
+ tracker;
1624
+ constructor(client, registry, config, task, allowedTools = ["read-file", "glob", "grep"], maxTurns = 10, timeoutMs = DEFAULT_TIMEOUT_MS, tracker) {
1937
1625
  this.client = client;
1938
1626
  this.registry = registry;
1939
1627
  this.config = config;
@@ -1941,7 +1629,7 @@ var init_sub_agent = __esm({
1941
1629
  this.allowedTools = allowedTools.filter((t) => t !== "spawn-agents" && t !== "todo");
1942
1630
  this.maxTurns = Math.min(maxTurns, 15);
1943
1631
  this.timeoutMs = timeoutMs;
1944
- this.memoStore = memoStore;
1632
+ this.tracker = tracker;
1945
1633
  }
1946
1634
  async run() {
1947
1635
  return Promise.race([
@@ -1960,17 +1648,8 @@ var init_sub_agent = __esm({
1960
1648
  async execute() {
1961
1649
  const conversation = new Conversation();
1962
1650
  const readTracker = new ReadTracker();
1963
- let systemPrompt = `\u4F60\u662F ZenCode \u5B50 Agent\u3002\u4F60\u7684\u4EFB\u52A1\uFF1A${this.task}
1651
+ const systemPrompt = `\u4F60\u662F ZenCode \u5B50 Agent\u3002\u4F60\u7684\u4EFB\u52A1\uFF1A${this.task}
1964
1652
  \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
1653
  conversation.setSystemPrompt(systemPrompt);
1975
1654
  conversation.addUserMessage(this.task);
1976
1655
  const tools = this.registry.toToolDefinitions(this.allowedTools);
@@ -1980,6 +1659,9 @@ ${index}`;
1980
1659
  conversation.getMessages(),
1981
1660
  tools.length > 0 ? tools : void 0
1982
1661
  );
1662
+ if (assistantMsg.usage && this.tracker) {
1663
+ this.tracker.addTokens(assistantMsg.usage.total_tokens);
1664
+ }
1983
1665
  conversation.addAssistantMessage(assistantMsg);
1984
1666
  if (!assistantMsg.tool_calls || assistantMsg.tool_calls.length === 0) {
1985
1667
  lastContent = assistantMsg.content || "";
@@ -2027,7 +1709,6 @@ ${index}`;
2027
1709
  params,
2028
1710
  this.config.max_tool_output
2029
1711
  );
2030
- autoMemoForTool(this.memoStore, toolName, params, result.content);
2031
1712
  if (toolName === "read-file") {
2032
1713
  readTracker.markRead(params["path"]);
2033
1714
  } else if (toolName === "write-file") {
@@ -2050,7 +1731,7 @@ ${index}`;
2050
1731
  });
2051
1732
 
2052
1733
  // src/tools/spawn-agents.ts
2053
- function createSpawnAgentsTool(client, registry, config, tracker, memoStore) {
1734
+ function createSpawnAgentsTool(client, registry, config, tracker) {
2054
1735
  return {
2055
1736
  name: "spawn-agents",
2056
1737
  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 +1785,7 @@ function createSpawnAgentsTool(client, registry, config, tracker, memoStore) {
2104
1785
  if (tools.length === 0) {
2105
1786
  tools = DEFAULT_TOOLS.filter((t) => autoTools.includes(t));
2106
1787
  }
2107
- return new SubAgent(client, registry, config, task.description, tools, maxTurns, void 0, memoStore);
1788
+ return new SubAgent(client, registry, config, task.description, tools, maxTurns, void 0, tracker);
2108
1789
  });
2109
1790
  const wrappedRuns = agents.map(
2110
1791
  (agent) => agent.run().then(
@@ -2145,7 +1826,7 @@ var init_spawn_agents = __esm({
2145
1826
  "src/tools/spawn-agents.ts"() {
2146
1827
  "use strict";
2147
1828
  init_sub_agent();
2148
- DEFAULT_TOOLS = ["read-file", "glob", "grep", "memo"];
1829
+ DEFAULT_TOOLS = ["read-file", "glob", "grep"];
2149
1830
  MAX_CONCURRENT = 10;
2150
1831
  MAX_TURNS_LIMIT = 15;
2151
1832
  }
@@ -2219,141 +1900,310 @@ ${lines.join("\n")}`
2219
1900
  if (!item) {
2220
1901
  return { content: `\u9519\u8BEF\uFF1A\u672A\u627E\u5230\u6761\u76EE "${id}"` };
2221
1902
  }
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" };
1903
+ const icon = item.status === "completed" ? "\u25CF" : item.status === "in-progress" ? "\u25D0" : "\u25CB";
1904
+ return {
1905
+ content: `\u5DF2\u66F4\u65B0\uFF1A${icon} [${item.id}] ${item.title} \u2192 ${item.status}`
1906
+ };
1907
+ }
1908
+ case "list": {
1909
+ const plan = store.list();
1910
+ if (!plan) {
1911
+ return { content: "\u5F53\u524D\u6CA1\u6709\u8BA1\u5212" };
1912
+ }
1913
+ const completed = plan.items.filter(
1914
+ (i) => i.status === "completed"
1915
+ ).length;
1916
+ const lines = plan.items.map((item) => {
1917
+ const icon = item.status === "completed" ? "\u25CF" : item.status === "in-progress" ? "\u25D0" : "\u25CB";
1918
+ return `${icon} [${item.id}] ${item.title}`;
1919
+ });
1920
+ return {
1921
+ content: `\u8BA1\u5212\u8FDB\u5EA6 ${completed}/${plan.items.length}\uFF1A
1922
+ ${lines.join("\n")}`
1923
+ };
1924
+ }
1925
+ case "clear": {
1926
+ store.clear();
1927
+ return { content: "\u8BA1\u5212\u5DF2\u6E05\u7A7A" };
1928
+ }
1929
+ default:
1930
+ return {
1931
+ content: `\u9519\u8BEF\uFF1A\u672A\u77E5\u64CD\u4F5C "${action}"\u3002\u652F\u6301: create, update, list, clear`
1932
+ };
1933
+ }
1934
+ }
1935
+ };
1936
+ }
1937
+ var init_todo = __esm({
1938
+ "src/tools/todo.ts"() {
1939
+ "use strict";
1940
+ }
1941
+ });
1942
+
1943
+ // src/core/sub-agents/runner.ts
1944
+ var DEFAULT_MAX_TURNS, SubAgentRunner;
1945
+ var init_runner = __esm({
1946
+ "src/core/sub-agents/runner.ts"() {
1947
+ "use strict";
1948
+ init_conversation();
1949
+ init_permission();
1950
+ init_read_tracker();
1951
+ DEFAULT_MAX_TURNS = 15;
1952
+ SubAgentRunner = class {
1953
+ client;
1954
+ registry;
1955
+ config;
1956
+ agentConfig;
1957
+ tracker;
1958
+ constructor(client, registry, config, agentConfig, tracker) {
1959
+ this.client = client;
1960
+ this.registry = registry;
1961
+ this.config = config;
1962
+ this.agentConfig = agentConfig;
1963
+ this.tracker = tracker;
1964
+ }
1965
+ /**
1966
+ * 执行子 Agent 任务
1967
+ */
1968
+ async execute(task, context, callbacks = {}) {
1969
+ const timeoutMs = (this.agentConfig.timeout ?? 120) * 1e3;
1970
+ const maxTurns = this.agentConfig.max_turns ?? DEFAULT_MAX_TURNS;
1971
+ return Promise.race([
1972
+ this.run(task, context, maxTurns, callbacks),
1973
+ this.timeout(timeoutMs)
1974
+ ]);
1975
+ }
1976
+ timeout(ms) {
1977
+ return new Promise((_, reject) => {
1978
+ setTimeout(
1979
+ () => reject(new Error(`\u5B50 Agent "${this.agentConfig.name}" \u8D85\u65F6\uFF08${ms / 1e3}s\uFF09`)),
1980
+ ms
1981
+ );
1982
+ });
1983
+ }
1984
+ async run(task, context, maxTurns, callbacks) {
1985
+ const conversation = new Conversation();
1986
+ const readTracker = new ReadTracker();
1987
+ conversation.setSystemPrompt(this.agentConfig.prompt);
1988
+ let taskMessage = task;
1989
+ if (context) {
1990
+ taskMessage += `
1991
+
1992
+ [\u4E0A\u4E0B\u6587]
1993
+ ${context}`;
1994
+ }
1995
+ conversation.addUserMessage(taskMessage);
1996
+ const tools = this.registry.toToolDefinitions(this.agentConfig.tools);
1997
+ let lastContent = "";
1998
+ for (let turn = 0; turn < maxTurns; turn++) {
1999
+ const assistantMsg = await this.client.chatStream(
2000
+ conversation.getMessages(),
2001
+ tools.length > 0 ? tools : void 0,
2002
+ callbacks
2003
+ );
2004
+ if (assistantMsg.usage && this.tracker) {
2005
+ this.tracker.addTokens(assistantMsg.usage.total_tokens);
2006
+ }
2007
+ conversation.addAssistantMessage(assistantMsg);
2008
+ if (!assistantMsg.tool_calls || assistantMsg.tool_calls.length === 0) {
2009
+ lastContent = assistantMsg.content || "";
2010
+ break;
2011
+ }
2012
+ for (const toolCall of assistantMsg.tool_calls) {
2013
+ const toolName = toolCall.function.name;
2014
+ if (!this.agentConfig.tools.includes(toolName)) {
2015
+ conversation.addToolResult(
2016
+ toolCall.id,
2017
+ `\u5B50 Agent "${this.agentConfig.name}" \u4E0D\u5141\u8BB8\u4F7F\u7528\u5DE5\u5177 "${toolName}"`
2018
+ );
2019
+ continue;
2020
+ }
2021
+ let params;
2022
+ try {
2023
+ params = JSON.parse(toolCall.function.arguments);
2024
+ } catch {
2025
+ conversation.addToolResult(toolCall.id, "\u53C2\u6570\u89E3\u6790\u5931\u8D25\uFF1A\u65E0\u6548\u7684 JSON");
2026
+ continue;
2027
+ }
2028
+ try {
2029
+ if (toolName === "edit-file") {
2030
+ const editPath = params["path"];
2031
+ if (!readTracker.hasRead(editPath)) {
2032
+ conversation.addToolResult(
2033
+ toolCall.id,
2034
+ `\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`
2035
+ );
2036
+ continue;
2037
+ }
2038
+ }
2039
+ if (toolName === "write-file") {
2040
+ const warn = readTracker.checkWriteOverwrite(
2041
+ params["path"],
2042
+ params["overwrite"]
2043
+ );
2044
+ if (warn) {
2045
+ conversation.addToolResult(toolCall.id, warn);
2046
+ continue;
2047
+ }
2048
+ }
2049
+ const permLevel = this.registry.getPermissionLevel(toolName);
2050
+ if (permLevel === "deny") {
2051
+ callbacks.onDenied?.(toolName);
2052
+ conversation.addToolResult(toolCall.id, `\u5DE5\u5177 "${toolName}" \u5DF2\u88AB\u7981\u6B62\u6267\u884C`);
2053
+ continue;
2054
+ }
2055
+ if (permLevel === "auto") {
2056
+ callbacks.onToolExecuting?.(toolName, params);
2057
+ }
2058
+ if (permLevel === "confirm") {
2059
+ const confirmResult = await confirmExecution(toolName, params);
2060
+ if (!confirmResult.approved) {
2061
+ callbacks.onDenied?.(toolName, confirmResult.feedback);
2062
+ 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";
2063
+ conversation.addToolResult(toolCall.id, denyMsg);
2064
+ continue;
2065
+ }
2066
+ }
2067
+ const result = await this.registry.execute(toolName, params, this.config.max_tool_output);
2068
+ callbacks.onToolResult?.(toolName, result.content, result.truncated ?? false);
2069
+ if (toolName === "read-file") {
2070
+ readTracker.markRead(params["path"]);
2071
+ } else if (toolName === "write-file") {
2072
+ readTracker.markWritten(params["path"]);
2073
+ }
2074
+ conversation.addToolResult(toolCall.id, result.content);
2075
+ } catch (err) {
2076
+ const msg = err instanceof Error ? err.message : String(err);
2077
+ conversation.addToolResult(toolCall.id, `\u5DE5\u5177\u6267\u884C\u5F02\u5E38\uFF1A${msg}`);
2078
+ }
2079
+ }
2080
+ if (assistantMsg.content) {
2081
+ lastContent = assistantMsg.content;
2231
2082
  }
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
2083
  }
2248
- default:
2249
- return {
2250
- content: `\u9519\u8BEF\uFF1A\u672A\u77E5\u64CD\u4F5C "${action}"\u3002\u652F\u6301: create, update, list, clear`
2251
- };
2084
+ return lastContent;
2252
2085
  }
2253
- }
2254
- };
2255
- }
2256
- var init_todo = __esm({
2257
- "src/tools/todo.ts"() {
2258
- "use strict";
2086
+ };
2259
2087
  }
2260
2088
  });
2261
2089
 
2262
- // src/tools/memo.ts
2263
- function createMemoTool(store) {
2090
+ // src/tools/dispatch.ts
2091
+ function createDispatchTool(defaultClient, toolRegistry, config, agentRegistry, tracker, callbacks) {
2264
2092
  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",
2093
+ name: "dispatch",
2094
+ 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
2095
  parameters: {
2268
2096
  type: "object",
2269
2097
  properties: {
2270
- action: {
2271
- type: "string",
2272
- description: "\u64CD\u4F5C\u7C7B\u578B",
2273
- enum: ["write", "read", "list", "delete", "clear"]
2274
- },
2275
- key: {
2098
+ agent: {
2276
2099
  type: "string",
2277
- description: "write/read/delete \u65F6\u5FC5\u586B\uFF1A\u5907\u5FD8\u5F55 key"
2100
+ description: `\u5B50 Agent \u540D\u79F0\u3002\u53EF\u9009: ${agentRegistry.listNames().join(", ")}`,
2101
+ enum: agentRegistry.listNames()
2278
2102
  },
2279
- content: {
2103
+ task: {
2280
2104
  type: "string",
2281
- description: "write \u65F6\u5FC5\u586B\uFF1A\u8981\u5199\u5165\u7684\u5185\u5BB9\uFF08\u6700\u591A 3000 \u5B57\u7B26\uFF09"
2105
+ description: "\u8981\u6267\u884C\u7684\u5177\u4F53\u4EFB\u52A1\u63CF\u8FF0"
2282
2106
  },
2283
- summary: {
2107
+ context: {
2284
2108
  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"
2109
+ 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
2110
  }
2287
2111
  },
2288
- required: ["action"]
2112
+ required: ["agent", "task"]
2289
2113
  },
2290
2114
  permissionLevel: "auto",
2291
2115
  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` };
2116
+ const agentName = params["agent"];
2117
+ const task = params["task"];
2118
+ const context = params["context"];
2119
+ const agentConfig = agentRegistry.get(agentName);
2120
+ if (!agentConfig) {
2121
+ return { content: `\u9519\u8BEF\uFF1A\u672A\u627E\u5230\u5B50 Agent "${agentName}"\u3002\u53EF\u7528: ${agentRegistry.listNames().join(", ")}` };
2122
+ }
2123
+ let client = defaultClient;
2124
+ if (agentConfig.model) {
2125
+ client = createLLMClient({
2126
+ apiKey: agentConfig.model.api_key || config.api_key,
2127
+ baseURL: agentConfig.model.base_url || config.base_url,
2128
+ model: agentConfig.model.model || config.model,
2129
+ temperature: config.temperature,
2130
+ maxTokens: config.max_tokens
2131
+ });
2132
+ }
2133
+ const runner = new SubAgentRunner(client, toolRegistry, config, agentConfig, tracker);
2134
+ tracker?.start([`${agentName}: ${task.slice(0, 60)}`]);
2135
+ try {
2136
+ const result = await runner.execute(task, context, callbacks?.() ?? {});
2137
+ tracker?.finish();
2138
+ return { content: result || "\uFF08\u5B50 Agent \u6267\u884C\u5B8C\u6210\uFF0C\u65E0\u8F93\u51FA\uFF09" };
2139
+ } catch (err) {
2140
+ tracker?.finish();
2141
+ const msg = err instanceof Error ? err.message : String(err);
2142
+ return { content: `\u5B50 Agent \u6267\u884C\u9519\u8BEF\uFF1A${msg}` };
2341
2143
  }
2342
2144
  }
2343
2145
  };
2344
2146
  }
2345
- var init_memo = __esm({
2346
- "src/tools/memo.ts"() {
2147
+ var init_dispatch = __esm({
2148
+ "src/tools/dispatch.ts"() {
2347
2149
  "use strict";
2150
+ init_runner();
2151
+ init_client();
2348
2152
  }
2349
2153
  });
2350
2154
 
2351
2155
  // src/cli/tui/bridge.ts
2156
+ function gray(text) {
2157
+ return `${ANSI_GRAY}${text}${ANSI_RESET}`;
2158
+ }
2352
2159
  function createThinkFilter() {
2353
2160
  let inThink = false;
2354
2161
  let tagBuffer = "";
2355
- let lineStart = true;
2162
+ let thinkLineBuffer = "";
2163
+ let thinkHasVisibleContent = false;
2164
+ let thinkLastEmittedBlank = false;
2356
2165
  let postThink = false;
2166
+ function flushThinkLine(rawLine) {
2167
+ const normalized = rawLine.trim();
2168
+ if (!thinkHasVisibleContent && normalized.length === 0) return "";
2169
+ if (normalized.length === 0) {
2170
+ if (thinkLastEmittedBlank) return "";
2171
+ thinkLastEmittedBlank = true;
2172
+ return "\n";
2173
+ }
2174
+ thinkHasVisibleContent = true;
2175
+ thinkLastEmittedBlank = false;
2176
+ return `${gray(` ${normalized}`)}
2177
+ `;
2178
+ }
2179
+ function appendOutsideText(current, text) {
2180
+ if (!postThink) return current + text;
2181
+ let result = current;
2182
+ for (let i = 0; i < text.length; i++) {
2183
+ const ch = text[i];
2184
+ if (postThink && (ch === "\n" || ch === "\r" || ch === " " || ch === " ")) {
2185
+ continue;
2186
+ }
2187
+ postThink = false;
2188
+ result += text.slice(i);
2189
+ break;
2190
+ }
2191
+ return result;
2192
+ }
2193
+ function appendThinkText(current, text) {
2194
+ let result = current;
2195
+ for (let i = 0; i < text.length; i++) {
2196
+ const ch = text[i];
2197
+ if (ch === "\r") continue;
2198
+ if (ch === "\n") {
2199
+ result += flushThinkLine(thinkLineBuffer);
2200
+ thinkLineBuffer = "";
2201
+ } else {
2202
+ thinkLineBuffer += ch;
2203
+ }
2204
+ }
2205
+ return result;
2206
+ }
2357
2207
  return function filter(text) {
2358
2208
  let result = "";
2359
2209
  for (let i = 0; i < text.length; i++) {
@@ -2362,24 +2212,25 @@ function createThinkFilter() {
2362
2212
  tagBuffer += ch;
2363
2213
  if (tagBuffer === "<think>") {
2364
2214
  inThink = true;
2365
- lineStart = true;
2215
+ thinkLineBuffer = "";
2216
+ thinkHasVisibleContent = false;
2217
+ thinkLastEmittedBlank = false;
2366
2218
  tagBuffer = "";
2367
- result += `\u256D\u2500 \u{1F4AD} ${THINK_BORDER}
2219
+ result += `${gray("Thinking")}
2368
2220
  `;
2369
2221
  } else if (tagBuffer === "</think>") {
2370
2222
  inThink = false;
2371
2223
  postThink = true;
2372
2224
  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;
2225
+ if (thinkLineBuffer.length > 0) {
2226
+ result += flushThinkLine(thinkLineBuffer);
2227
+ thinkLineBuffer = "";
2381
2228
  }
2382
- result += tagBuffer;
2229
+ while (result.endsWith("\n\n\n")) result = result.slice(0, -1);
2230
+ result += "\n";
2231
+ } else if (!"<think>".startsWith(tagBuffer) && !"</think>".startsWith(tagBuffer)) {
2232
+ if (inThink) result = appendThinkText(result, tagBuffer);
2233
+ else result = appendOutsideText(result, tagBuffer);
2383
2234
  tagBuffer = "";
2384
2235
  }
2385
2236
  continue;
@@ -2388,34 +2239,20 @@ function createThinkFilter() {
2388
2239
  tagBuffer = "<";
2389
2240
  continue;
2390
2241
  }
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
- }
2242
+ if (inThink) result = appendThinkText(result, ch);
2243
+ else result = appendOutsideText(result, ch);
2407
2244
  }
2408
2245
  return result;
2409
2246
  };
2410
2247
  }
2411
- function createTokenBatcher(dispatch) {
2248
+ function createTokenBatcher(dispatch, type) {
2412
2249
  let buffer = "";
2413
2250
  let timer = null;
2414
2251
  function flush() {
2415
2252
  if (buffer.length > 0) {
2416
2253
  const text = buffer;
2417
2254
  buffer = "";
2418
- dispatch({ type: "APPEND_CONTENT", text });
2255
+ dispatch({ type, text });
2419
2256
  }
2420
2257
  }
2421
2258
  function start() {
@@ -2460,8 +2297,10 @@ function registerConfirmToolId(toolName, id) {
2460
2297
  activeToolIds.set(toolName, id);
2461
2298
  }
2462
2299
  function createBridgeCallbacks(dispatch) {
2463
- const batcher = createTokenBatcher(dispatch);
2464
- const thinkFilter = createThinkFilter();
2300
+ const contentBatcher = createTokenBatcher(dispatch, "APPEND_CONTENT");
2301
+ const thoughtBatcher = createTokenBatcher(dispatch, "APPEND_THOUGHT");
2302
+ let inThink = false;
2303
+ let tagBuffer = "";
2465
2304
  activeToolIds = /* @__PURE__ */ new Map();
2466
2305
  streamingToolIds = /* @__PURE__ */ new Map();
2467
2306
  lastStreamingArgs = /* @__PURE__ */ new Map();
@@ -2480,12 +2319,41 @@ function createBridgeCallbacks(dispatch) {
2480
2319
  }
2481
2320
  return {
2482
2321
  onContent: (text) => {
2483
- const filtered = thinkFilter(text);
2484
- if (filtered) batcher.append(filtered);
2322
+ for (let i = 0; i < text.length; i++) {
2323
+ const ch = text[i];
2324
+ if (tagBuffer.length > 0 || ch === "<") {
2325
+ tagBuffer += ch;
2326
+ if (tagBuffer === "<think>") {
2327
+ contentBatcher.flush();
2328
+ inThink = true;
2329
+ tagBuffer = "";
2330
+ continue;
2331
+ } else if (tagBuffer === "</think>") {
2332
+ thoughtBatcher.flush();
2333
+ inThink = false;
2334
+ tagBuffer = "";
2335
+ continue;
2336
+ } else if (!"<think>".startsWith(tagBuffer) && !"</think>".startsWith(tagBuffer)) {
2337
+ if (inThink) {
2338
+ thoughtBatcher.append(tagBuffer);
2339
+ } else {
2340
+ contentBatcher.append(tagBuffer);
2341
+ }
2342
+ tagBuffer = "";
2343
+ }
2344
+ continue;
2345
+ }
2346
+ if (inThink) {
2347
+ thoughtBatcher.append(ch);
2348
+ } else {
2349
+ contentBatcher.append(ch);
2350
+ }
2351
+ }
2485
2352
  },
2486
2353
  onToolCallStreaming: (index, name, accumulatedArgs) => {
2487
2354
  if (!streamingToolIds.has(index)) {
2488
- batcher.flush();
2355
+ contentBatcher.flush();
2356
+ thoughtBatcher.flush();
2489
2357
  const id2 = `tool-${++toolCallCounter}`;
2490
2358
  streamingToolIds.set(index, id2);
2491
2359
  activeToolIds.set(name, id2);
@@ -2505,8 +2373,10 @@ function createBridgeCallbacks(dispatch) {
2505
2373
  },
2506
2374
  onToolExecuting: (name, params) => {
2507
2375
  flushStreamingUpdate();
2508
- batcher.flush();
2509
- batcher.pause();
2376
+ contentBatcher.flush();
2377
+ thoughtBatcher.flush();
2378
+ contentBatcher.pause();
2379
+ thoughtBatcher.pause();
2510
2380
  const existingId = activeToolIds.get(name);
2511
2381
  const id = existingId || `tool-${++toolCallCounter}`;
2512
2382
  activeToolIds.set(name, id);
@@ -2520,41 +2390,36 @@ function createBridgeCallbacks(dispatch) {
2520
2390
  if (isWriteTool) {
2521
2391
  const code = extractCodeFromArgs(name, lastStreamingArgs.get(id) || "");
2522
2392
  const codeLines = code ? code.split("\n").length : 0;
2523
- summary = codeLines > 0 ? `${codeLines} \u884C` : truncated ? "\u8F93\u51FA\u5DF2\u622A\u65AD" : `${lines.length} \u884C`;
2393
+ summary = codeLines > 0 ? `${codeLines} lines` : truncated ? "truncated" : `${lines.length} lines`;
2524
2394
  } else {
2525
- summary = truncated ? `\u8F93\u51FA\u5DF2\u622A\u65AD` : `${lines.length} \u884C`;
2395
+ summary = truncated ? `truncated` : `${lines.length} lines`;
2526
2396
  }
2527
2397
  const preview = lines.slice(0, 5).join("\n").slice(0, 200);
2528
2398
  const resultContent = lines.length > 5 || preview.length >= 200 ? preview + "..." : preview;
2529
2399
  dispatch({ type: "TOOL_RESULT", id, resultSummary: summary, resultContent });
2530
2400
  },
2531
- onCoderStart: () => {
2532
- batcher.flush();
2533
- dispatch({ type: "CODER_START" });
2534
- },
2535
- onCoderEnd: () => {
2536
- dispatch({ type: "CODER_END" });
2537
- },
2538
2401
  onDenied: (toolName, feedback) => {
2539
2402
  const id = activeToolIds.get(toolName) || `tool-${++toolCallCounter}`;
2540
2403
  dispatch({ type: "TOOL_DENIED", id, feedback });
2541
2404
  },
2542
2405
  onError: (err) => {
2543
- batcher.stop();
2406
+ contentBatcher.stop();
2407
+ thoughtBatcher.stop();
2544
2408
  dispatch({ type: "SET_ERROR", error: err.message });
2545
2409
  },
2546
- // Called internally when streaming is complete
2547
2410
  _stopBatcher: () => {
2548
- batcher.stop();
2411
+ contentBatcher.stop();
2412
+ thoughtBatcher.stop();
2549
2413
  }
2550
2414
  };
2551
2415
  }
2552
- var BATCH_INTERVAL_MS, THINK_BORDER, toolCallCounter, activeToolIds, streamingToolIds, lastStreamingArgs;
2416
+ var BATCH_INTERVAL_MS, ANSI_GRAY, ANSI_RESET, toolCallCounter, activeToolIds, streamingToolIds, lastStreamingArgs;
2553
2417
  var init_bridge = __esm({
2554
2418
  "src/cli/tui/bridge.ts"() {
2555
2419
  "use strict";
2556
2420
  BATCH_INTERVAL_MS = 64;
2557
- THINK_BORDER = "\u2500".repeat(40);
2421
+ ANSI_GRAY = "\x1B[90m";
2422
+ ANSI_RESET = "\x1B[0m";
2558
2423
  toolCallCounter = 0;
2559
2424
  activeToolIds = /* @__PURE__ */ new Map();
2560
2425
  streamingToolIds = /* @__PURE__ */ new Map();
@@ -2575,7 +2440,8 @@ var init_sub_agent_tracker = __esm({
2575
2440
  total: descriptions.length,
2576
2441
  completed: 0,
2577
2442
  failed: 0,
2578
- descriptions
2443
+ descriptions,
2444
+ tokens: 0
2579
2445
  };
2580
2446
  this.notify();
2581
2447
  }
@@ -2589,6 +2455,11 @@ var init_sub_agent_tracker = __esm({
2589
2455
  this.progress = { ...this.progress, failed: this.progress.failed + 1 };
2590
2456
  this.notify();
2591
2457
  }
2458
+ addTokens(count) {
2459
+ if (!this.progress) return;
2460
+ this.progress = { ...this.progress, tokens: this.progress.tokens + count };
2461
+ this.notify();
2462
+ }
2592
2463
  finish() {
2593
2464
  this.progress = null;
2594
2465
  this.notify();
@@ -2613,15 +2484,12 @@ var init_sub_agent_tracker = __esm({
2613
2484
  });
2614
2485
 
2615
2486
  // src/cli/tui/state.ts
2616
- function createInitialState(modelName, agentMode, collaboration) {
2487
+ function createInitialState(modelName) {
2617
2488
  return {
2618
2489
  messages: [],
2619
2490
  isRunning: false,
2620
2491
  error: void 0,
2621
- coderWorking: false,
2622
2492
  modelName,
2623
- agentMode,
2624
- collaboration,
2625
2493
  todoPlan: null,
2626
2494
  subAgentProgress: null
2627
2495
  };
@@ -2691,6 +2559,21 @@ function tuiReducer(state, action) {
2691
2559
  })
2692
2560
  };
2693
2561
  }
2562
+ case "APPEND_THOUGHT": {
2563
+ return {
2564
+ ...state,
2565
+ messages: updateLastAssistant(state.messages, (msg) => {
2566
+ const blocks = [...msg.blocks];
2567
+ const last = blocks[blocks.length - 1];
2568
+ if (last && last.type === "thought") {
2569
+ blocks[blocks.length - 1] = { type: "thought", text: last.text + action.text };
2570
+ } else {
2571
+ blocks.push({ type: "thought", text: action.text });
2572
+ }
2573
+ return { ...msg, blocks };
2574
+ })
2575
+ };
2576
+ }
2694
2577
  case "TOOL_EXECUTING": {
2695
2578
  return {
2696
2579
  ...state,
@@ -2836,14 +2719,8 @@ function tuiReducer(state, action) {
2836
2719
  return { ...state, error: action.error, isRunning: false };
2837
2720
  case "CLEAR_ERROR":
2838
2721
  return { ...state, error: void 0 };
2839
- case "CODER_START":
2840
- return { ...state, coderWorking: true };
2841
- case "CODER_END":
2842
- return { ...state, coderWorking: false };
2843
2722
  case "CLEAR_MESSAGES":
2844
2723
  return { ...state, messages: [] };
2845
- case "SET_MODE":
2846
- return { ...state, agentMode: action.agentMode, collaboration: action.collaboration };
2847
2724
  case "SET_TODO_PLAN":
2848
2725
  return { ...state, todoPlan: action.plan };
2849
2726
  case "SET_SUB_AGENT_PROGRESS":
@@ -2866,136 +2743,90 @@ import { jsx, jsxs } from "react/jsx-runtime";
2866
2743
  function getToolParamSummary(name, params) {
2867
2744
  switch (name) {
2868
2745
  case "bash":
2869
- return String(params["command"] || "").slice(0, 60);
2746
+ return String(params["command"] || "").slice(0, 100);
2870
2747
  case "read-file":
2871
2748
  case "write-file":
2872
2749
  case "edit-file":
2873
2750
  return String(params["path"] || "");
2874
2751
  case "glob":
2875
- return String(params["pattern"] || "");
2876
2752
  case "grep":
2877
2753
  return String(params["pattern"] || "");
2878
- case "send-to-coder":
2879
- return String(params["task"] || "").slice(0, 40);
2880
2754
  case "spawn-agents": {
2881
2755
  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}`;
2756
+ if (!tasks || tasks.length === 0) return "";
2757
+ return `${tasks.length} tasks: ${tasks.map((t) => t.description || "?").join(" | ").slice(0, 100)}`;
2897
2758
  }
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}";
2759
+ case "dispatch":
2760
+ return `${params["agent"] || "?"}: ${String(params["task"] || "").slice(0, 80)}`;
2927
2761
  default:
2928
- return "\u2699";
2762
+ const keys = Object.keys(params);
2763
+ if (keys.length === 0) return "";
2764
+ const val = params[keys[0]];
2765
+ if (val === null || val === void 0) return "";
2766
+ if (typeof val === "string") return val.slice(0, 60);
2767
+ if (typeof val === "number" || typeof val === "boolean") return String(val);
2768
+ return JSON.stringify(val).slice(0, 60);
2929
2769
  }
2930
2770
  }
2931
- function getCodeContent(name, params) {
2932
- if (name === "write-file") {
2933
- return params["content"] || null;
2771
+ function truncateContent(text, maxLines) {
2772
+ const lines = text.split("\n");
2773
+ const result = [];
2774
+ for (let line of lines) {
2775
+ if (result.length >= maxLines) break;
2776
+ result.push(line.replace(/\r/g, "").replace(/\t/g, " "));
2934
2777
  }
2935
- if (name === "edit-file") {
2936
- return params["new_string"] || null;
2778
+ if (lines.length > maxLines) {
2779
+ result.push(`... (and ${lines.length - maxLines} more lines)`);
2937
2780
  }
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)`;
2781
+ return result;
2945
2782
  }
2946
2783
  function ToolCallLine({ toolCall }) {
2947
- const { name, params, status, resultSummary, resultContent, denyFeedback } = toolCall;
2784
+ const { name, params, status, resultContent, denyFeedback } = toolCall;
2948
2785
  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;
2786
+ let borderColor = "#504945";
2787
+ let titleColor = "#fabd2f";
2788
+ let statusColor = "#fabd2f";
2789
+ let statusText = "RUNNING";
2790
+ if (status === "done") {
2791
+ borderColor = "#b8bb26";
2792
+ titleColor = "#b8bb26";
2793
+ statusColor = "#b8bb26";
2794
+ statusText = "DONE";
2795
+ } else if (status === "denied") {
2796
+ borderColor = "#fb4934";
2797
+ titleColor = "#fb4934";
2798
+ statusColor = "#fb4934";
2799
+ statusText = "DENIED";
2800
+ } else if (status === "confirming") {
2801
+ borderColor = "#fe8019";
2802
+ titleColor = "#fe8019";
2803
+ statusColor = "#fe8019";
2804
+ statusText = "CONFIRM";
2970
2805
  }
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
- ] });
2806
+ const contentLines = resultContent ? truncateContent(resultContent, 15) : [];
2807
+ return /* @__PURE__ */ jsx(Box, { flexDirection: "column", marginTop: 0, marginBottom: 1, width: "100%", children: /* @__PURE__ */ jsxs(
2808
+ Box,
2809
+ {
2810
+ flexDirection: "column",
2811
+ borderStyle: "round",
2812
+ borderColor,
2813
+ paddingX: 1,
2814
+ width: "100%",
2815
+ children: [
2816
+ /* @__PURE__ */ jsxs(Box, { gap: 1, children: [
2817
+ /* @__PURE__ */ jsx(Box, { backgroundColor: statusColor, width: 9, justifyContent: "center", children: /* @__PURE__ */ jsx(Text, { color: "#282828", bold: true, children: statusText }) }),
2818
+ /* @__PURE__ */ jsx(Text, { color: titleColor, bold: true, children: name.toUpperCase() }),
2819
+ /* @__PURE__ */ jsx(Text, { color: "#ebdbb2", dimColor: true, italic: true, wrap: "truncate-end", children: summary })
2820
+ ] }),
2821
+ 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)) }),
2822
+ status === "denied" && denyFeedback && /* @__PURE__ */ jsxs(Box, { gap: 1, marginTop: 0, children: [
2823
+ /* @__PURE__ */ jsx(Text, { color: "#fb4934", bold: true, children: "REASON:" }),
2824
+ /* @__PURE__ */ jsx(Text, { color: "#ebdbb2", children: denyFeedback })
2825
+ ] }),
2826
+ status === "confirming" && /* @__PURE__ */ jsx(Box, { marginTop: 0, children: /* @__PURE__ */ jsx(Text, { color: "#fe8019", italic: true, children: "Waiting for your permission..." }) })
2827
+ ]
2828
+ }
2829
+ ) });
2999
2830
  }
3000
2831
  var init_ToolCallLine = __esm({
3001
2832
  "src/cli/tui/components/ToolCallLine.tsx"() {
@@ -3014,209 +2845,94 @@ var init_MessageBubble = __esm({
3014
2845
  init_ToolCallLine();
3015
2846
  MessageBubble = React.memo(function MessageBubble2({ message }) {
3016
2847
  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: "..." })
2848
+ const isUser = role === "user";
2849
+ const label = isUser ? "USER" : "AI";
2850
+ const labelBg = isUser ? "#b8bb26" : "#83a598";
2851
+ const labelFg = "#282828";
2852
+ const contentColor = isUser ? "#ebdbb2" : "#ebdbb2";
2853
+ return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", marginBottom: 1, width: "100%", children: [
2854
+ /* @__PURE__ */ jsxs2(Box2, { marginBottom: 0, children: [
2855
+ /* @__PURE__ */ jsx2(Box2, { backgroundColor: labelBg, width: 9, paddingLeft: 1, marginRight: 1, children: /* @__PURE__ */ jsx2(Text2, { color: labelFg, bold: true, children: label }) }),
2856
+ isStreaming && /* @__PURE__ */ jsx2(Text2, { color: "#83a598", dimColor: true, italic: true, children: "typing..." })
2857
+ ] }),
2858
+ /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: "100%", children: [
2859
+ blocks.map((block, i) => {
2860
+ if (block.type === "text") {
2861
+ const text = block.text.trim();
2862
+ if (!text && !isStreaming) return null;
2863
+ 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}`);
2864
+ } else if (block.type === "thought") {
2865
+ return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", marginBottom: 0, width: "100%", children: [
2866
+ /* @__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" }) }) }),
2867
+ /* @__PURE__ */ jsx2(Box2, { paddingLeft: 2, marginBottom: 0, children: /* @__PURE__ */ jsx2(Text2, { color: "#928374", italic: true, children: block.text.trim() }) })
2868
+ ] }, `thought-${i}`);
2869
+ } else {
2870
+ return /* @__PURE__ */ jsx2(Box2, { paddingLeft: 2, width: "100%", children: /* @__PURE__ */ jsx2(ToolCallLine, { toolCall: block.toolCall }) }, block.toolCall.id);
2871
+ }
2872
+ }),
2873
+ blocks.length === 0 && isStreaming && /* @__PURE__ */ jsx2(Box2, { paddingLeft: 2, children: /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "..." }) })
3039
2874
  ] })
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);
2875
+ ] });
2876
+ });
3154
2877
  }
3155
- return null;
3156
- }
2878
+ });
2879
+
2880
+ // src/cli/tui/components/ChatArea.tsx
2881
+ import React2 from "react";
2882
+ import { Box as Box3 } from "ink";
2883
+ import { jsx as jsx3 } from "react/jsx-runtime";
3157
2884
  var ChatArea;
3158
2885
  var init_ChatArea = __esm({
3159
2886
  "src/cli/tui/components/ChatArea.tsx"() {
3160
2887
  "use strict";
3161
2888
  init_MessageBubble();
3162
- init_ToolCallLine();
3163
2889
  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
- ] });
2890
+ return /* @__PURE__ */ jsx3(Box3, { flexDirection: "column", width: "100%", children: messages.map((msg) => /* @__PURE__ */ jsx3(MessageBubble, { message: msg }, msg.id)) });
3208
2891
  });
3209
2892
  }
3210
2893
  });
3211
2894
 
3212
2895
  // 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 }) {
2896
+ import { useRef, useState } from "react";
2897
+ import { Box as Box4, Text as Text3, useInput } from "ink";
2898
+ import { Fragment, jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
2899
+ function CleanTextInput({
2900
+ onSubmit,
2901
+ placeholder,
2902
+ onExitRequest,
2903
+ onScroll
2904
+ }) {
3217
2905
  const [value, setValue] = useState("");
3218
2906
  const [cursor, setCursor] = useState(0);
2907
+ const lastCtrlCAtRef = useRef(0);
3219
2908
  useInput((input, key) => {
2909
+ if (onScroll) {
2910
+ if (key.pageUp || key.upArrow && key.shift) {
2911
+ onScroll("up");
2912
+ return;
2913
+ }
2914
+ if (key.pageDown || key.downArrow && key.shift) {
2915
+ onScroll("down");
2916
+ return;
2917
+ }
2918
+ }
2919
+ if (input === "c" && key.ctrl) {
2920
+ if (value.length > 0) {
2921
+ setValue("");
2922
+ setCursor(0);
2923
+ lastCtrlCAtRef.current = 0;
2924
+ return;
2925
+ }
2926
+ const now = Date.now();
2927
+ const isDoublePress = now - lastCtrlCAtRef.current <= 1200;
2928
+ if (isDoublePress) {
2929
+ onExitRequest();
2930
+ lastCtrlCAtRef.current = 0;
2931
+ } else {
2932
+ lastCtrlCAtRef.current = now;
2933
+ }
2934
+ return;
2935
+ }
3220
2936
  if (key.return) {
3221
2937
  const trimmed = value.trim();
3222
2938
  if (trimmed) {
@@ -3269,30 +2985,33 @@ function CleanTextInput({ onSubmit, placeholder }) {
3269
2985
  setCursor((prev) => prev + input.length);
3270
2986
  });
3271
2987
  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 || "" })
2988
+ return /* @__PURE__ */ jsxs3(Fragment, { children: [
2989
+ /* @__PURE__ */ jsx4(Text3, { inverse: true, children: " " }),
2990
+ /* @__PURE__ */ jsx4(Text3, { dimColor: true, children: placeholder || "" })
3275
2991
  ] });
3276
2992
  }
3277
2993
  const before = value.slice(0, cursor);
3278
2994
  const at = cursor < value.length ? value[cursor] : " ";
3279
2995
  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 })
2996
+ return /* @__PURE__ */ jsxs3(Fragment, { children: [
2997
+ /* @__PURE__ */ jsx4(Text3, { children: before }),
2998
+ /* @__PURE__ */ jsx4(Text3, { inverse: true, children: at }),
2999
+ /* @__PURE__ */ jsx4(Text3, { children: after })
3284
3000
  ] });
3285
3001
  }
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(
3002
+ function InputArea({ onSubmit, isRunning, onExitRequest, onScroll }) {
3003
+ return /* @__PURE__ */ jsxs3(Box4, { paddingX: 0, children: [
3004
+ /* @__PURE__ */ jsx4(Box4, { backgroundColor: isRunning ? "#504945" : "#b8bb26", paddingX: 1, marginRight: 1, children: /* @__PURE__ */ jsx4(Text3, { color: "#282828", bold: true, children: isRunning ? " WAIT " : " INPUT " }) }),
3005
+ /* @__PURE__ */ jsx4(Box4, { flexGrow: 1, children: isRunning ? /* @__PURE__ */ jsx4(Box4, { flexGrow: 1, onWheel: (event) => {
3006
+ }, children: /* @__PURE__ */ jsx4(Text3, { color: "#a89984", italic: true, children: "Thinking..." }) }) : /* @__PURE__ */ jsx4(
3290
3007
  CleanTextInput,
3291
3008
  {
3292
3009
  onSubmit,
3293
- placeholder: "\u8F93\u5165\u6D88\u606F\u6216 /\u547D\u4EE4..."
3010
+ placeholder: "Type a message or /command...",
3011
+ onExitRequest,
3012
+ onScroll
3294
3013
  }
3295
- )
3014
+ ) })
3296
3015
  ] });
3297
3016
  }
3298
3017
  var init_InputArea = __esm({
@@ -3302,43 +3021,46 @@ var init_InputArea = __esm({
3302
3021
  });
3303
3022
 
3304
3023
  // 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";
3024
+ import { Box as Box5, Text as Text4 } from "ink";
3025
+ import { Fragment as Fragment2, jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
3026
+ function formatTokens(n) {
3027
+ if (n >= 1e6) return `${(n / 1e6).toFixed(1)}M`;
3028
+ if (n >= 1e3) return `${(n / 1e3).toFixed(1)}K`;
3029
+ return String(n);
3030
+ }
3031
+ function StatusBar({ isRunning, modelName, todoPlan, subAgentProgress }) {
3309
3032
  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
3033
+ return /* @__PURE__ */ jsxs4(Box5, { marginTop: 1, children: [
3034
+ /* @__PURE__ */ jsx5(Box5, { backgroundColor: "#3c3836", paddingX: 1, children: /* @__PURE__ */ jsx5(Text4, { color: "#ebdbb2", bold: true, children: " ZENCODE " }) }),
3035
+ /* @__PURE__ */ jsxs4(Box5, { backgroundColor: "#504945", paddingX: 1, flexGrow: 1, children: [
3036
+ /* @__PURE__ */ jsx5(Text4, { color: "#ebdbb2", children: modelName }),
3037
+ isRunning && !subAgentProgress && /* @__PURE__ */ jsxs4(Fragment2, { children: [
3038
+ /* @__PURE__ */ jsx5(Text4, { color: "#ebdbb2", children: " \u2502 " }),
3039
+ /* @__PURE__ */ jsx5(Text4, { color: "#8ec07c", children: "\u25CF thinking..." })
3040
+ ] }),
3041
+ subAgentProgress && /* @__PURE__ */ jsxs4(Fragment2, { children: [
3042
+ /* @__PURE__ */ jsx5(Text4, { color: "#ebdbb2", children: " \u2502 " }),
3043
+ /* @__PURE__ */ jsxs4(Text4, { color: "#b8bb26", children: [
3044
+ "Agents: ",
3045
+ subAgentProgress.completed + subAgentProgress.failed,
3046
+ "/",
3047
+ subAgentProgress.total
3048
+ ] }),
3049
+ /* @__PURE__ */ jsx5(Text4, { color: "#ebdbb2", children: " \u2502 " }),
3050
+ /* @__PURE__ */ jsxs4(Text4, { color: "#83a598", children: [
3051
+ "tokens: ",
3052
+ formatTokens(subAgentProgress.tokens)
3053
+ ] })
3054
+ ] }),
3055
+ todoProgress && /* @__PURE__ */ jsxs4(Fragment2, { children: [
3056
+ /* @__PURE__ */ jsx5(Text4, { color: "#ebdbb2", children: " \u2502 " }),
3057
+ /* @__PURE__ */ jsxs4(Text4, { color: "#fabd2f", children: [
3058
+ "Plan: ",
3059
+ todoProgress
3060
+ ] })
3338
3061
  ] })
3339
3062
  ] }),
3340
- /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "\u2502" }),
3341
- /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "/help" })
3063
+ /* @__PURE__ */ jsx5(Box5, { backgroundColor: "#3c3836", paddingX: 1, children: /* @__PURE__ */ jsx5(Text4, { color: "#ebdbb2", children: "/help" }) })
3342
3064
  ] });
3343
3065
  }
3344
3066
  var init_StatusBar = __esm({
@@ -3349,8 +3071,8 @@ var init_StatusBar = __esm({
3349
3071
 
3350
3072
  // src/cli/tui/components/ConfirmPrompt.tsx
3351
3073
  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";
3074
+ import { Box as Box6, Text as Text5, useInput as useInput2 } from "ink";
3075
+ import { Fragment as Fragment3, jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
3354
3076
  function getToolDetails(toolName, params) {
3355
3077
  const lines = [];
3356
3078
  let label = toolName;
@@ -3436,18 +3158,18 @@ function FeedbackInput({ onSubmit }) {
3436
3158
  setCursor((prev) => prev + input.length);
3437
3159
  });
3438
3160
  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..." })
3161
+ return /* @__PURE__ */ jsxs5(Fragment3, { children: [
3162
+ /* @__PURE__ */ jsx6(Text5, { inverse: true, children: " " }),
3163
+ /* @__PURE__ */ jsx6(Text5, { dimColor: true, children: "\u8F93\u5165\u53CD\u9988\u6307\u4EE4\u7ED9 AI..." })
3442
3164
  ] });
3443
3165
  }
3444
3166
  const before = value.slice(0, cursor);
3445
3167
  const at = cursor < value.length ? value[cursor] : " ";
3446
3168
  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 })
3169
+ return /* @__PURE__ */ jsxs5(Fragment3, { children: [
3170
+ /* @__PURE__ */ jsx6(Text5, { children: before }),
3171
+ /* @__PURE__ */ jsx6(Text5, { inverse: true, children: at }),
3172
+ /* @__PURE__ */ jsx6(Text5, { children: after })
3451
3173
  ] });
3452
3174
  }
3453
3175
  function ConfirmPrompt({ confirm, onRespond }) {
@@ -3475,66 +3197,69 @@ function ConfirmPrompt({ confirm, onRespond }) {
3475
3197
  }, { isActive: !feedbackMode });
3476
3198
  const { lines, label } = getToolDetails(confirm.toolName, confirm.params);
3477
3199
  if (feedbackMode) {
3478
- return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", paddingX: 1, children: [
3479
- /* @__PURE__ */ jsxs6(
3200
+ return /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", paddingX: 0, marginY: 1, children: [
3201
+ /* @__PURE__ */ jsx6(Box6, { backgroundColor: "#fe8019", paddingX: 1, children: /* @__PURE__ */ jsx6(Text5, { color: "#282828", bold: true, children: " FEEDBACK " }) }),
3202
+ /* @__PURE__ */ jsxs5(
3480
3203
  Box6,
3481
3204
  {
3482
3205
  flexDirection: "column",
3483
3206
  borderStyle: "round",
3484
- borderColor: "yellow",
3207
+ borderColor: "#fe8019",
3485
3208
  paddingX: 1,
3486
3209
  children: [
3487
- /* @__PURE__ */ jsx6(Text6, { bold: true, color: "yellow", children: label }),
3488
- lines.map((line, i) => /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: line }, i))
3210
+ /* @__PURE__ */ jsx6(Text5, { bold: true, color: "#fabd2f", children: label }),
3211
+ lines.map((line, i) => /* @__PURE__ */ jsx6(Text5, { color: "#a89984", children: line }, i)),
3212
+ /* @__PURE__ */ jsxs5(Box6, { marginTop: 1, gap: 1, children: [
3213
+ /* @__PURE__ */ jsx6(Text5, { color: "#83a598", bold: true, children: "Reason: " }),
3214
+ /* @__PURE__ */ jsx6(
3215
+ FeedbackInput,
3216
+ {
3217
+ onSubmit: (text) => {
3218
+ const trimmed = text.trim();
3219
+ if (trimmed) {
3220
+ onRespond({ feedback: trimmed });
3221
+ } else {
3222
+ setFeedbackMode(false);
3223
+ }
3224
+ }
3225
+ }
3226
+ )
3227
+ ] })
3489
3228
  ]
3490
3229
  }
3491
3230
  ),
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" }) })
3231
+ /* @__PURE__ */ jsx6(Box6, { paddingX: 1, children: /* @__PURE__ */ jsx6(Text5, { dimColor: true, children: "Enter to send \xB7 Esc to cancel" }) })
3509
3232
  ] });
3510
3233
  }
3511
- return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", paddingX: 1, children: [
3512
- /* @__PURE__ */ jsxs6(
3234
+ return /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", paddingX: 0, marginY: 1, children: [
3235
+ /* @__PURE__ */ jsx6(Box6, { backgroundColor: "#fe8019", paddingX: 1, children: /* @__PURE__ */ jsx6(Text5, { color: "#282828", bold: true, children: " CONFIRMATION REQUIRED " }) }),
3236
+ /* @__PURE__ */ jsxs5(
3513
3237
  Box6,
3514
3238
  {
3515
3239
  flexDirection: "column",
3516
3240
  borderStyle: "round",
3517
- borderColor: "yellow",
3241
+ borderColor: "#fe8019",
3518
3242
  paddingX: 1,
3519
3243
  children: [
3520
- /* @__PURE__ */ jsx6(Text6, { bold: true, color: "yellow", children: label }),
3521
- lines.map((line, i) => /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: line }, i))
3244
+ /* @__PURE__ */ jsx6(Text5, { bold: true, color: "#fabd2f", children: label }),
3245
+ lines.map((line, i) => /* @__PURE__ */ jsx6(Text5, { color: "#a89984", children: line }, i)),
3246
+ /* @__PURE__ */ jsx6(Box6, { marginTop: 1, gap: 2, justifyContent: "center", children: OPTIONS.map((opt, i) => {
3247
+ const isSelected = i === selected;
3248
+ const optColor = opt.key === "deny" ? "#fb4934" : opt.key === "allow" ? "#b8bb26" : "#8ec07c";
3249
+ return /* @__PURE__ */ jsx6(Box6, { children: isSelected ? /* @__PURE__ */ jsxs5(Text5, { bold: true, backgroundColor: optColor, color: "#282828", children: [
3250
+ " ",
3251
+ opt.label,
3252
+ " "
3253
+ ] }) : /* @__PURE__ */ jsxs5(Text5, { color: optColor, children: [
3254
+ " ",
3255
+ opt.label,
3256
+ " "
3257
+ ] }) }, opt.key);
3258
+ }) })
3522
3259
  ]
3523
3260
  }
3524
3261
  ),
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" }) })
3262
+ /* @__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
3263
  ] });
3539
3264
  }
3540
3265
  var OPTIONS;
@@ -3550,55 +3275,57 @@ var init_ConfirmPrompt = __esm({
3550
3275
  });
3551
3276
 
3552
3277
  // 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";
3278
+ import { Box as Box7, Text as Text6 } from "ink";
3279
+ import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
3555
3280
  function getStatusIcon(status) {
3556
3281
  switch (status) {
3557
3282
  case "completed":
3558
- return "\u25CF";
3283
+ return "\u2705";
3559
3284
  case "in-progress":
3560
- return "\u25D0";
3285
+ return "\u23F3";
3561
3286
  default:
3562
- return "\u25CB";
3287
+ return "\u26AA";
3563
3288
  }
3564
3289
  }
3565
3290
  function getStatusColor(status) {
3566
3291
  switch (status) {
3567
3292
  case "completed":
3568
- return "green";
3293
+ return "#b8bb26";
3569
3294
  case "in-progress":
3570
- return "yellow";
3295
+ return "#fabd2f";
3571
3296
  default:
3572
- return "gray";
3297
+ return "#928374";
3573
3298
  }
3574
3299
  }
3575
3300
  function TodoPanel({ plan }) {
3576
3301
  const completed = plan.items.filter((i) => i.status === "completed").length;
3577
3302
  const total = plan.items.length;
3578
- return /* @__PURE__ */ jsxs7(
3303
+ return /* @__PURE__ */ jsxs6(
3579
3304
  Box7,
3580
3305
  {
3581
3306
  flexDirection: "column",
3582
3307
  borderStyle: "round",
3583
- borderColor: "cyan",
3308
+ borderColor: "#83a598",
3584
3309
  paddingX: 1,
3585
- marginBottom: 0,
3310
+ marginY: 1,
3586
3311
  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: [
3312
+ /* @__PURE__ */ jsxs6(Box7, { justifyContent: "space-between", marginBottom: 1, children: [
3313
+ /* @__PURE__ */ jsx7(Text6, { bold: true, color: "#83a598", children: "\u{1F4CB} PROJECT PLAN" }),
3314
+ /* @__PURE__ */ jsxs6(Text6, { color: "#ebdbb2", children: [
3590
3315
  completed,
3591
3316
  "/",
3592
- total
3317
+ total,
3318
+ " tasks"
3593
3319
  ] })
3594
3320
  ] }),
3595
- plan.items.map((item) => /* @__PURE__ */ jsxs7(Box7, { gap: 1, children: [
3596
- /* @__PURE__ */ jsx7(Text7, { color: getStatusColor(item.status), children: getStatusIcon(item.status) }),
3321
+ plan.items.map((item) => /* @__PURE__ */ jsxs6(Box7, { gap: 1, children: [
3322
+ /* @__PURE__ */ jsx7(Text6, { color: getStatusColor(item.status), children: getStatusIcon(item.status) }),
3597
3323
  /* @__PURE__ */ jsx7(
3598
- Text7,
3324
+ Text6,
3599
3325
  {
3600
- color: item.status === "completed" ? "green" : void 0,
3326
+ color: item.status === "completed" ? "#b8bb26" : item.status === "in-progress" ? "#fabd2f" : "#ebdbb2",
3601
3327
  dimColor: item.status === "pending",
3328
+ strikethrough: item.status === "completed",
3602
3329
  children: item.title
3603
3330
  }
3604
3331
  )
@@ -3613,26 +3340,62 @@ var init_TodoPanel = __esm({
3613
3340
  }
3614
3341
  });
3615
3342
 
3343
+ // src/cli/tui/components/Header.tsx
3344
+ import { Box as Box8, Text as Text7 } from "ink";
3345
+ import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
3346
+ function Header({ modelName }) {
3347
+ return /* @__PURE__ */ jsx8(
3348
+ Box8,
3349
+ {
3350
+ flexDirection: "column",
3351
+ width: "100%",
3352
+ borderStyle: "round",
3353
+ borderColor: "#504945",
3354
+ paddingX: 1,
3355
+ marginTop: 0,
3356
+ marginBottom: 1,
3357
+ children: /* @__PURE__ */ jsxs7(Box8, { justifyContent: "space-between", children: [
3358
+ /* @__PURE__ */ jsxs7(Box8, { gap: 1, children: [
3359
+ /* @__PURE__ */ jsx8(Text7, { bold: true, color: "#fe8019", children: "ZEN CODE" }),
3360
+ /* @__PURE__ */ jsx8(Text7, { color: "#a89984", dimColor: true, children: "v0.2.1" })
3361
+ ] }),
3362
+ /* @__PURE__ */ jsx8(Text7, { color: "#83a598", bold: true, children: modelName })
3363
+ ] })
3364
+ }
3365
+ );
3366
+ }
3367
+ var init_Header = __esm({
3368
+ "src/cli/tui/components/Header.tsx"() {
3369
+ "use strict";
3370
+ }
3371
+ });
3372
+
3616
3373
  // src/cli/tui/App.tsx
3617
3374
  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 }) {
3375
+ import { Box as Box9, Text as Text8, useInput as useInput3, useStdout } from "ink";
3376
+ import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
3377
+ function App({ config, client, agent, registry, todoStore, subAgentTracker, agentRegistry, skillRegistry }) {
3378
+ const { stdout } = useStdout();
3379
+ const [, setWidth] = useState3(stdout.columns);
3380
+ useEffect(() => {
3381
+ const onResize = () => {
3382
+ setWidth(stdout.columns);
3383
+ };
3384
+ stdout.on("resize", onResize);
3385
+ return () => {
3386
+ stdout.off("resize", onResize);
3387
+ };
3388
+ }, [stdout]);
3621
3389
  const [state, dispatch] = useReducer(
3622
3390
  tuiReducer,
3623
- createInitialState(
3624
- config.model,
3625
- config.agent_mode,
3626
- config.collaboration
3627
- )
3391
+ createInitialState(config.model)
3628
3392
  );
3629
3393
  const [resetKey, setResetKey] = useState3(0);
3394
+ const currentCallbacksRef = useRef2(null);
3630
3395
  const agentRef = useRef2(agent);
3631
- const orchestratorRef = useRef2(orchestrator);
3632
3396
  const todoStoreRef = useRef2(todoStore);
3633
3397
  const subAgentTrackerRef = useRef2(subAgentTracker);
3634
3398
  agentRef.current = agent;
3635
- orchestratorRef.current = orchestrator;
3636
3399
  todoStoreRef.current = todoStore;
3637
3400
  subAgentTrackerRef.current = subAgentTracker;
3638
3401
  useEffect(() => {
@@ -3672,7 +3435,7 @@ function App({ config, client, agent, orchestrator, registry, todoStore, memoSto
3672
3435
  );
3673
3436
  useEffect(() => {
3674
3437
  setStructuredConfirmHandler((toolName, params) => {
3675
- return new Promise((resolve8) => {
3438
+ return new Promise((resolve10) => {
3676
3439
  const id = `confirm-${Date.now()}`;
3677
3440
  registerConfirmToolId(toolName, id);
3678
3441
  dispatch({
@@ -3683,13 +3446,13 @@ function App({ config, client, agent, orchestrator, registry, todoStore, memoSto
3683
3446
  resolve: (result) => {
3684
3447
  if (result === "always") {
3685
3448
  registry.addAutoApprove(toolName);
3686
- resolve8({ approved: true });
3449
+ resolve10({ approved: true });
3687
3450
  } else if (result === "allow") {
3688
- resolve8({ approved: true });
3451
+ resolve10({ approved: true });
3689
3452
  } else if (result === "deny") {
3690
- resolve8({ approved: false });
3453
+ resolve10({ approved: false });
3691
3454
  } else {
3692
- resolve8({ approved: false, feedback: result.feedback });
3455
+ resolve10({ approved: false, feedback: result.feedback });
3693
3456
  }
3694
3457
  }
3695
3458
  });
@@ -3699,39 +3462,53 @@ function App({ config, client, agent, orchestrator, registry, todoStore, memoSto
3699
3462
  setStructuredConfirmHandler(null);
3700
3463
  };
3701
3464
  }, [registry]);
3465
+ const runAgent = useCallback(async (text) => {
3466
+ dispatch({ type: "ADD_USER_MESSAGE", text });
3467
+ dispatch({ type: "START_ASSISTANT" });
3468
+ dispatch({ type: "SET_RUNNING", running: true });
3469
+ const callbacks = createBridgeCallbacks(dispatch);
3470
+ currentCallbacksRef.current = callbacks;
3471
+ try {
3472
+ await agentRef.current.run(text, callbacks);
3473
+ } catch (err) {
3474
+ if (!isAbortError(err)) {
3475
+ const msg = err instanceof Error ? err.message : String(err);
3476
+ dispatch({ type: "SET_ERROR", error: msg });
3477
+ }
3478
+ } finally {
3479
+ currentCallbacksRef.current = null;
3480
+ }
3481
+ callbacks._stopBatcher?.();
3482
+ dispatch({ type: "FINISH_STREAMING" });
3483
+ dispatch({ type: "SET_RUNNING", running: false });
3484
+ }, []);
3702
3485
  const handleSubmit = useCallback(async (text) => {
3703
3486
  if (text.startsWith("/")) {
3487
+ const parts = text.slice(1).split(/\s+/);
3488
+ const skillName = parts[0];
3489
+ const skillArgs = parts.slice(1).join(" ");
3490
+ const skill = skillRegistry.get(skillName);
3491
+ if (skill) {
3492
+ const expandedPrompt = skillRegistry.expandPrompt(skill, skillArgs);
3493
+ await runAgent(expandedPrompt);
3494
+ return;
3495
+ }
3704
3496
  handleSlashCommand2(text, {
3705
3497
  config,
3706
- orchestrator: orchestratorRef.current,
3498
+ agent: agentRef.current,
3707
3499
  registry,
3708
3500
  dispatch,
3709
3501
  setResetKey,
3710
3502
  client,
3711
3503
  todoStore,
3712
- memoStore,
3713
- subAgentTracker
3504
+ subAgentTracker,
3505
+ agentRegistry,
3506
+ skillRegistry
3714
3507
  });
3715
3508
  return;
3716
3509
  }
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]);
3510
+ await runAgent(text);
3511
+ }, [config, runAgent]);
3735
3512
  const handleConfirmResponse = useCallback((result) => {
3736
3513
  if (confirmPending) {
3737
3514
  confirmPending.resolve(result);
@@ -3749,114 +3526,118 @@ function App({ config, client, agent, orchestrator, registry, todoStore, memoSto
3749
3526
  }
3750
3527
  }, [confirmPending, state.messages]);
3751
3528
  useInput3((input, key) => {
3752
- if (input === "c" && key.ctrl) {
3529
+ if (key.escape) {
3753
3530
  if (state.isRunning) {
3531
+ agentRef.current.interrupt();
3532
+ currentCallbacksRef.current?._stopBatcher?.();
3754
3533
  dispatch({ type: "SET_RUNNING", running: false });
3755
3534
  dispatch({ type: "FINISH_STREAMING" });
3756
- } else {
3757
- process.exit(0);
3758
3535
  }
3759
3536
  return;
3760
3537
  }
3538
+ if (input === "c" && key.ctrl) {
3539
+ if (state.isRunning) {
3540
+ agentRef.current.interrupt();
3541
+ currentCallbacksRef.current?._stopBatcher?.();
3542
+ dispatch({ type: "SET_RUNNING", running: false });
3543
+ dispatch({ type: "FINISH_STREAMING" });
3544
+ return;
3545
+ }
3546
+ }
3761
3547
  if (input === "d" && key.ctrl) {
3762
3548
  process.exit(0);
3763
3549
  }
3764
3550
  });
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(
3551
+ return /* @__PURE__ */ jsxs8(Box9, { flexDirection: "column", paddingX: 0, width: "100%", children: [
3552
+ /* @__PURE__ */ jsxs8(Box9, { paddingX: 1, flexDirection: "column", children: [
3553
+ /* @__PURE__ */ jsx9(Header, { modelName: state.modelName }),
3554
+ /* @__PURE__ */ jsx9(ChatArea, { messages: state.messages })
3555
+ ] }),
3556
+ state.error && /* @__PURE__ */ jsxs8(Box9, { borderStyle: "round", borderColor: "#fb4934", paddingX: 1, marginBottom: 1, children: [
3557
+ /* @__PURE__ */ jsx9(Text8, { color: "#fb4934", bold: true, children: "ERROR: " }),
3558
+ /* @__PURE__ */ jsx9(Text8, { color: "#fb4934", children: state.error })
3559
+ ] }),
3560
+ confirmPending && /* @__PURE__ */ jsx9(ConfirmPrompt, { confirm: confirmPending, onRespond: handleConfirmResponse }),
3561
+ state.todoPlan && /* @__PURE__ */ jsx9(TodoPanel, { plan: state.todoPlan }),
3562
+ /* @__PURE__ */ jsx9(Box9, { marginTop: 1, children: /* @__PURE__ */ jsx9(
3789
3563
  InputArea,
3790
3564
  {
3791
3565
  onSubmit: handleSubmit,
3792
- isRunning: state.isRunning || !!confirmPending
3566
+ isRunning: state.isRunning || !!confirmPending,
3567
+ onExitRequest: () => process.exit(0)
3793
3568
  }
3794
- ),
3795
- /* @__PURE__ */ jsx8(
3569
+ ) }),
3570
+ /* @__PURE__ */ jsx9(Box9, { marginTop: 0, children: /* @__PURE__ */ jsx9(
3796
3571
  StatusBar,
3797
3572
  {
3798
- agentMode: state.agentMode,
3799
- collaboration: state.collaboration,
3800
- coderWorking: state.coderWorking,
3801
3573
  isRunning: state.isRunning,
3802
3574
  modelName: state.modelName,
3803
3575
  todoPlan: state.todoPlan,
3804
3576
  subAgentProgress: state.subAgentProgress
3805
3577
  }
3806
- )
3578
+ ) })
3807
3579
  ] }, resetKey);
3808
3580
  }
3809
3581
  function handleSlashCommand2(input, ctx) {
3810
- const { config, orchestrator, registry, dispatch, setResetKey, client, todoStore, memoStore, subAgentTracker } = ctx;
3582
+ const { config, agent, registry, dispatch, setResetKey, client, todoStore, subAgentTracker, agentRegistry, skillRegistry } = ctx;
3811
3583
  const parts = input.trim().split(/\s+/);
3812
3584
  const command = parts[0];
3813
3585
  switch (command) {
3814
3586
  case "/help":
3815
3587
  dispatch({ type: "ADD_USER_MESSAGE", text: input });
3816
3588
  dispatch({ type: "START_ASSISTANT" });
3817
- dispatch({
3818
- type: "APPEND_CONTENT",
3819
- text: `\u53EF\u7528\u547D\u4EE4:
3589
+ {
3590
+ let helpText = `\u53EF\u7528\u547D\u4EE4:
3820
3591
  /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
3592
+ /skills \u5217\u51FA\u6240\u6709\u53EF\u7528\u6280\u80FD
3593
+ /agents \u5217\u51FA\u6240\u6709\u53EF\u7528\u5B50 Agent
3824
3594
  /parallel \u5207\u6362\u5E76\u884C\u5B50 Agent \u529F\u80FD on/off
3825
3595
  /todo \u5207\u6362 todo \u8BA1\u5212\u529F\u80FD on/off
3826
3596
  /clear \u6E05\u7A7A\u5BF9\u8BDD\u5386\u53F2
3827
3597
  /info \u663E\u793A\u5F53\u524D\u914D\u7F6E
3828
3598
  Ctrl+C \u53D6\u6D88\u5F53\u524D\u8BF7\u6C42 / \u9000\u51FA
3829
- Ctrl+D \u9000\u51FA`
3830
- });
3599
+ Ctrl+D \u9000\u51FA`;
3600
+ const skills = skillRegistry.list();
3601
+ if (skills.length > 0) {
3602
+ helpText += `
3603
+
3604
+ \u53EF\u7528\u6280\u80FD:
3605
+ ${skills.map((s) => ` /${s.name} ${s.description}`).join("\n")}`;
3606
+ }
3607
+ dispatch({ type: "APPEND_CONTENT", text: helpText });
3608
+ }
3831
3609
  dispatch({ type: "FINISH_STREAMING" });
3832
3610
  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 });
3611
+ case "/skills": {
3612
+ const skills = skillRegistry.list();
3613
+ dispatch({ type: "ADD_USER_MESSAGE", text: input });
3614
+ dispatch({ type: "START_ASSISTANT" });
3615
+ if (skills.length === 0) {
3616
+ 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" });
3617
+ } else {
3618
+ const lines = skills.map((s) => ` /${s.name}: ${s.description}`);
3619
+ dispatch({ type: "APPEND_CONTENT", text: `\u53EF\u7528\u6280\u80FD (${skills.length}):
3620
+ ${lines.join("\n")}` });
3848
3621
  }
3622
+ dispatch({ type: "FINISH_STREAMING" });
3849
3623
  break;
3850
3624
  }
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 });
3625
+ case "/agents": {
3626
+ const agents = agentRegistry.list();
3627
+ dispatch({ type: "ADD_USER_MESSAGE", text: input });
3628
+ dispatch({ type: "START_ASSISTANT" });
3629
+ if (agents.length === 0) {
3630
+ dispatch({ type: "APPEND_CONTENT", text: "\u6682\u65E0\u53EF\u7528\u5B50 Agent\u3002" });
3631
+ } else {
3632
+ const lines = agents.map((a) => ` ${a.name}: ${a.description} [tools: ${a.tools.join(", ")}]`);
3633
+ dispatch({ type: "APPEND_CONTENT", text: `\u53EF\u7528\u5B50 Agent (${agents.length}):
3634
+ ${lines.join("\n")}` });
3635
+ }
3636
+ dispatch({ type: "FINISH_STREAMING" });
3858
3637
  break;
3638
+ }
3859
3639
  case "/clear":
3640
+ agent.getConversation().clear();
3860
3641
  dispatch({ type: "CLEAR_MESSAGES" });
3861
3642
  setResetKey((prev) => prev + 1);
3862
3643
  break;
@@ -3867,7 +3648,7 @@ function handleSlashCommand2(input, ctx) {
3867
3648
  if (next === "off") {
3868
3649
  registry.unregister("spawn-agents");
3869
3650
  } else if (!registry.has("spawn-agents")) {
3870
- registry.register(createSpawnAgentsTool(client, registry, config, subAgentTracker, memoStore));
3651
+ registry.register(createSpawnAgentsTool(client, registry, config, subAgentTracker));
3871
3652
  }
3872
3653
  dispatch({ type: "ADD_USER_MESSAGE", text: input });
3873
3654
  dispatch({ type: "START_ASSISTANT" });
@@ -3896,9 +3677,9 @@ function handleSlashCommand2(input, ctx) {
3896
3677
  dispatch({
3897
3678
  type: "APPEND_CONTENT",
3898
3679
  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}`
3680
+ \u57FA\u7840 URL: ${config.base_url}
3681
+ \u5B50 Agent: ${agentRegistry.listNames().join(", ") || "\u65E0"}
3682
+ \u6280\u80FD: ${skillRegistry.listNames().map((n) => "/" + n).join(", ") || "\u65E0"}`
3902
3683
  });
3903
3684
  dispatch({ type: "FINISH_STREAMING" });
3904
3685
  break;
@@ -3916,6 +3697,7 @@ Agent \u6A21\u5F0F: ${config.agent_mode}
3916
3697
  var init_App = __esm({
3917
3698
  "src/cli/tui/App.tsx"() {
3918
3699
  "use strict";
3700
+ init_client();
3919
3701
  init_permission();
3920
3702
  init_state();
3921
3703
  init_bridge();
@@ -3926,6 +3708,7 @@ var init_App = __esm({
3926
3708
  init_StatusBar();
3927
3709
  init_ConfirmPrompt();
3928
3710
  init_TodoPanel();
3711
+ init_Header();
3929
3712
  }
3930
3713
  });
3931
3714
 
@@ -3935,10 +3718,14 @@ __export(tui_exports, {
3935
3718
  startTui: () => startTui
3936
3719
  });
3937
3720
  import { render } from "ink";
3938
- import { jsx as jsx9 } from "react/jsx-runtime";
3721
+ import { jsx as jsx10 } from "react/jsx-runtime";
3939
3722
  async function startTui(options) {
3940
3723
  const { config } = options;
3941
- const { systemPrompt } = await buildPrompt(config);
3724
+ const agentRegistry = new SubAgentConfigRegistry();
3725
+ for (const agentConfig of loadAllAgentConfigs()) {
3726
+ agentRegistry.register(agentConfig);
3727
+ }
3728
+ const { systemPrompt } = await buildPrompt(config, agentRegistry.list());
3942
3729
  const registry = new ToolRegistry();
3943
3730
  registerCoreTools(registry);
3944
3731
  registry.setPermissions(config.permissions);
@@ -3950,32 +3737,37 @@ async function startTui(options) {
3950
3737
  maxTokens: config.max_tokens
3951
3738
  });
3952
3739
  const todoStore = new TodoStore();
3953
- const memoStore = new MemoStore();
3954
3740
  const subAgentTracker = new SubAgentTracker();
3741
+ const skillRegistry = new SkillRegistry();
3742
+ for (const skill of loadAllSkills()) {
3743
+ skillRegistry.register(skill);
3744
+ }
3955
3745
  if (config.features.parallel_agents === "on") {
3956
- registry.register(createSpawnAgentsTool(client, registry, config, subAgentTracker, memoStore));
3746
+ registry.register(createSpawnAgentsTool(client, registry, config, subAgentTracker));
3957
3747
  }
3958
3748
  if (config.features.todo === "on") {
3959
3749
  registry.register(createTodoTool(todoStore));
3960
3750
  }
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);
3751
+ registry.register(createDispatchTool(client, registry, config, agentRegistry, subAgentTracker));
3752
+ const agent = new Agent(client, registry, config, systemPrompt);
3964
3753
  const { waitUntilExit } = render(
3965
- /* @__PURE__ */ jsx9(
3754
+ /* @__PURE__ */ jsx10(
3966
3755
  App,
3967
3756
  {
3968
3757
  config,
3969
3758
  client,
3970
3759
  agent,
3971
- orchestrator,
3972
3760
  registry,
3973
3761
  todoStore,
3974
- memoStore,
3975
- subAgentTracker
3762
+ subAgentTracker,
3763
+ agentRegistry,
3764
+ skillRegistry
3976
3765
  }
3977
3766
  ),
3978
- { patchConsole: true }
3767
+ {
3768
+ patchConsole: true,
3769
+ exitOnCtrlC: false
3770
+ }
3979
3771
  );
3980
3772
  await waitUntilExit();
3981
3773
  }
@@ -3986,41 +3778,129 @@ var init_tui = __esm({
3986
3778
  init_registry();
3987
3779
  init_register();
3988
3780
  init_agent();
3989
- init_orchestrator();
3990
3781
  init_builder();
3991
3782
  init_todo_store();
3992
- init_memo_store();
3993
3783
  init_sub_agent_tracker();
3784
+ init_registry2();
3785
+ init_loader();
3786
+ init_registry3();
3787
+ init_loader2();
3994
3788
  init_spawn_agents();
3995
3789
  init_todo();
3996
- init_memo();
3790
+ init_dispatch();
3997
3791
  init_App();
3998
3792
  }
3999
3793
  });
4000
3794
 
4001
3795
  // src/cli/index.ts
4002
- init_loader();
3796
+ import { Command } from "commander";
3797
+
3798
+ // src/config/loader.ts
3799
+ import * as fs from "fs";
3800
+ import * as path from "path";
3801
+ import * as os from "os";
3802
+ import { parse as parseYaml } from "yaml";
3803
+
3804
+ // src/config/defaults.ts
3805
+ var DEFAULT_CONFIG = {
3806
+ model: "deepseek-chat",
3807
+ api_key: "",
3808
+ base_url: "https://api.deepseek.com/v1",
3809
+ temperature: 0.7,
3810
+ max_tokens: 8192,
3811
+ features: {
3812
+ git: "auto",
3813
+ mcp: "off",
3814
+ planning_layer: "on",
3815
+ parallel_agents: "on",
3816
+ todo: "on"
3817
+ },
3818
+ permissions: {
3819
+ auto_approve: ["read-file", "glob", "grep", "spawn-agents", "todo", "dispatch"],
3820
+ require_approval: ["write-file", "edit-file", "bash", "git"]
3821
+ },
3822
+ mcp_servers: [],
3823
+ prompts: [],
3824
+ max_tool_output: 3e4
3825
+ };
3826
+
3827
+ // src/config/loader.ts
3828
+ function deepMerge(target, source) {
3829
+ const result = { ...target };
3830
+ for (const key of Object.keys(source)) {
3831
+ const sourceVal = source[key];
3832
+ const targetVal = target[key];
3833
+ if (sourceVal !== void 0 && sourceVal !== null && typeof sourceVal === "object" && !Array.isArray(sourceVal) && typeof targetVal === "object" && !Array.isArray(targetVal) && targetVal !== null) {
3834
+ result[key] = deepMerge(targetVal, sourceVal);
3835
+ } else if (sourceVal !== void 0) {
3836
+ result[key] = sourceVal;
3837
+ }
3838
+ }
3839
+ return result;
3840
+ }
3841
+ function loadYamlFile(filePath) {
3842
+ try {
3843
+ const content = fs.readFileSync(filePath, "utf-8");
3844
+ return parseYaml(content) || {};
3845
+ } catch {
3846
+ return {};
3847
+ }
3848
+ }
3849
+ function loadEnvConfig() {
3850
+ const config = {};
3851
+ if (process.env["ZENCODE_API_KEY"]) {
3852
+ config.api_key = process.env["ZENCODE_API_KEY"];
3853
+ }
3854
+ if (process.env["ZENCODE_MODEL"]) {
3855
+ config.model = process.env["ZENCODE_MODEL"];
3856
+ }
3857
+ if (process.env["ZENCODE_BASE_URL"]) {
3858
+ config.base_url = process.env["ZENCODE_BASE_URL"];
3859
+ }
3860
+ return config;
3861
+ }
3862
+ function loadCliConfig(opts) {
3863
+ const config = {};
3864
+ if (opts.model) config.model = opts.model;
3865
+ if (opts.apiKey) config.api_key = opts.apiKey;
3866
+ if (opts.baseUrl) config.base_url = opts.baseUrl;
3867
+ return config;
3868
+ }
3869
+ function loadConfig(cliOpts = {}) {
3870
+ const globalConfigPath = path.join(os.homedir(), ".zencode", "config.yaml");
3871
+ const projectDirConfigPath = path.resolve(".zencode", "config.yaml");
3872
+ const projectFileConfigPath = path.resolve(".zencode.yaml");
3873
+ let config = { ...DEFAULT_CONFIG };
3874
+ config = deepMerge(config, loadYamlFile(globalConfigPath));
3875
+ config = deepMerge(config, loadYamlFile(projectDirConfigPath));
3876
+ config = deepMerge(config, loadYamlFile(projectFileConfigPath));
3877
+ config = deepMerge(config, loadEnvConfig());
3878
+ config = deepMerge(config, loadCliConfig(cliOpts));
3879
+ return config;
3880
+ }
3881
+
3882
+ // src/cli/index.ts
4003
3883
  init_client();
4004
3884
  init_registry();
4005
3885
  init_register();
4006
3886
  init_agent();
4007
- init_orchestrator();
4008
3887
  init_builder();
4009
- import { Command } from "commander";
4010
3888
 
4011
3889
  // src/cli/repl.ts
4012
3890
  init_client();
4013
3891
  init_registry();
4014
3892
  init_agent();
4015
- init_orchestrator();
4016
3893
  init_builder();
4017
3894
  init_register();
4018
3895
  init_permission();
4019
3896
  init_todo_store();
4020
- init_memo_store();
3897
+ init_registry2();
3898
+ init_loader();
3899
+ init_registry3();
3900
+ init_loader2();
4021
3901
  init_spawn_agents();
4022
3902
  init_todo();
4023
- init_memo();
3903
+ init_dispatch();
4024
3904
  init_bridge();
4025
3905
  import * as readline from "readline";
4026
3906
 
@@ -4052,12 +3932,8 @@ function printToolCall(toolName, params) {
4052
3932
  const action = String(params["action"]);
4053
3933
  const id = params["id"] ? ` [${params["id"]}]` : "";
4054
3934
  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
3935
  }
4060
- const icon = toolName === "spawn-agents" ? "\u26A1" : toolName === "todo" ? "\u{1F4CB}" : toolName === "memo" ? "\u{1F4DD}" : "\u2699";
3936
+ const icon = toolName === "spawn-agents" ? "\u26A1" : toolName === "todo" ? "\u{1F4CB}" : "\u2699";
4061
3937
  console.log(chalk2.yellow(` ${icon} ${toolName}`) + detail);
4062
3938
  }
4063
3939
  function printToolResult(toolName, result, truncated) {
@@ -4080,10 +3956,9 @@ function printWarning(message) {
4080
3956
  function printSuccess(message) {
4081
3957
  console.log(chalk2.green(`\u2713 ${message}`));
4082
3958
  }
4083
- function printWelcome(modelName, mode) {
3959
+ function printWelcome(modelName) {
4084
3960
  console.log(chalk2.bold.cyan("\n ZenCode") + chalk2.dim(" - \u6781\u7B80 AI \u7F16\u7A0B\u52A9\u624B\n"));
4085
3961
  console.log(chalk2.dim(` \u6A21\u578B: ${modelName}`));
4086
- console.log(chalk2.dim(` \u6A21\u5F0F: ${mode}`));
4087
3962
  console.log(chalk2.dim(` \u8F93\u5165 /help \u67E5\u770B\u547D\u4EE4\uFF0CCtrl+C \u9000\u51FA
4088
3963
  `));
4089
3964
  }
@@ -4097,9 +3972,8 @@ function handleSlashCommand(input, context) {
4097
3972
  console.log(`
4098
3973
  \u53EF\u7528\u547D\u4EE4:
4099
3974
  /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
3975
+ /skills \u5217\u51FA\u6240\u6709\u53EF\u7528\u6280\u80FD\uFF08\u7528\u6237\u81EA\u5B9A\u4E49\u659C\u6760\u547D\u4EE4\uFF09
3976
+ /agents \u5217\u51FA\u6240\u6709\u53EF\u7528\u5B50 Agent
4103
3977
  /parallel \u5207\u6362\u5E76\u884C\u5B50 Agent \u529F\u80FD on/off
4104
3978
  /todo \u5207\u6362 todo \u8BA1\u5212\u529F\u80FD on/off
4105
3979
  /clear \u6E05\u7A7A\u5BF9\u8BDD\u5386\u53F2
@@ -4107,33 +3981,33 @@ function handleSlashCommand(input, context) {
4107
3981
  /exit \u9000\u51FA
4108
3982
  `);
4109
3983
  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`);
3984
+ case "/skills": {
3985
+ const skills = context.skillRegistry.list();
3986
+ if (skills.length === 0) {
3987
+ 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
3988
  } else {
4122
- printError(`\u65E0\u6548\u6A21\u5F0F: ${mode}\u3002\u53EF\u9009: delegated, autonomous, controlled`);
3989
+ printInfo(`\u53EF\u7528\u6280\u80FD (${skills.length}):`);
3990
+ for (const s of skills) {
3991
+ printInfo(` /${s.name}: ${s.description}`);
3992
+ }
4123
3993
  }
4124
3994
  return true;
4125
3995
  }
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");
3996
+ case "/agents": {
3997
+ const agents = context.agentRegistry.list();
3998
+ if (agents.length === 0) {
3999
+ printInfo("\u6682\u65E0\u53EF\u7528\u5B50 Agent");
4000
+ } else {
4001
+ printInfo(`\u53EF\u7528\u5B50 Agent (${agents.length}):`);
4002
+ for (const a of agents) {
4003
+ printInfo(` ${a.name}: ${a.description}`);
4004
+ }
4005
+ }
4133
4006
  return true;
4007
+ }
4134
4008
  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;
4009
+ printSuccess("\u5BF9\u8BDD\u5386\u53F2\u5DF2\u6E05\u7A7A");
4010
+ return "clear";
4137
4011
  case "/parallel": {
4138
4012
  const current = context.config.features.parallel_agents;
4139
4013
  const next = current === "on" ? "off" : "on";
@@ -4142,7 +4016,7 @@ function handleSlashCommand(input, context) {
4142
4016
  context.registry.unregister("spawn-agents");
4143
4017
  } else if (!context.registry.has("spawn-agents")) {
4144
4018
  context.registry.register(
4145
- createSpawnAgentsTool(context.client, context.registry, context.config, void 0, context.memoStore)
4019
+ createSpawnAgentsTool(context.client, context.registry, context.config)
4146
4020
  );
4147
4021
  }
4148
4022
  printSuccess(`\u5E76\u884C\u5B50 Agent \u529F\u80FD\u5DF2${next === "on" ? "\u5F00\u542F" : "\u5173\u95ED"}`);
@@ -4162,9 +4036,9 @@ function handleSlashCommand(input, context) {
4162
4036
  }
4163
4037
  case "/info":
4164
4038
  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
4039
  printInfo(`\u57FA\u7840 URL: ${context.config.base_url}`);
4040
+ printInfo(`\u5B50 Agent: ${context.agentRegistry.listNames().join(", ") || "\u65E0"}`);
4041
+ printInfo(`\u6280\u80FD: ${context.skillRegistry.listNames().join(", ") || "\u65E0"}`);
4168
4042
  return true;
4169
4043
  case "/exit":
4170
4044
  process.exit(0);
@@ -4175,7 +4049,11 @@ function handleSlashCommand(input, context) {
4175
4049
  }
4176
4050
  async function startRepl(options) {
4177
4051
  const { config } = options;
4178
- const { systemPrompt } = await buildPrompt(config);
4052
+ const agentRegistry = new SubAgentConfigRegistry();
4053
+ for (const agentConfig of loadAllAgentConfigs()) {
4054
+ agentRegistry.register(agentConfig);
4055
+ }
4056
+ const { systemPrompt } = await buildPrompt(config, agentRegistry.list());
4179
4057
  const registry = new ToolRegistry();
4180
4058
  registerCoreTools(registry);
4181
4059
  registry.setPermissions(config.permissions);
@@ -4187,23 +4065,19 @@ async function startRepl(options) {
4187
4065
  maxTokens: config.max_tokens
4188
4066
  });
4189
4067
  const todoStore = new TodoStore();
4190
- const memoStore = new MemoStore();
4191
4068
  if (config.features.parallel_agents === "on") {
4192
- registry.register(createSpawnAgentsTool(client, registry, config, void 0, memoStore));
4069
+ registry.register(createSpawnAgentsTool(client, registry, config));
4193
4070
  }
4194
4071
  if (config.features.todo === "on") {
4195
4072
  registry.register(createTodoTool(todoStore));
4196
4073
  }
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);
4074
+ const skillRegistry = new SkillRegistry();
4075
+ for (const skill of loadAllSkills()) {
4076
+ skillRegistry.register(skill);
4204
4077
  }
4205
- const modeLabel = config.agent_mode === "dual" ? `\u53CCAgent (${config.collaboration})` : "\u5355Agent";
4206
- printWelcome(config.model, modeLabel);
4078
+ registry.register(createDispatchTool(client, registry, config, agentRegistry));
4079
+ let agent = new Agent(client, registry, config, systemPrompt);
4080
+ printWelcome(config.model);
4207
4081
  const rl = readline.createInterface({
4208
4082
  input: process.stdin,
4209
4083
  output: process.stdout,
@@ -4211,13 +4085,13 @@ async function startRepl(options) {
4211
4085
  historySize: 100
4212
4086
  });
4213
4087
  setConfirmHandler(async (promptText) => {
4214
- return new Promise((resolve8) => {
4088
+ return new Promise((resolve10) => {
4215
4089
  process.stdout.write(promptText);
4216
4090
  const onData = (data) => {
4217
4091
  process.stdin.removeListener("data", onData);
4218
4092
  process.stdin.pause();
4219
4093
  const answer = data.toString().trim().toLowerCase();
4220
- resolve8(answer === "y");
4094
+ resolve10(answer === "y");
4221
4095
  };
4222
4096
  process.stdin.resume();
4223
4097
  process.stdin.once("data", onData);
@@ -4231,29 +4105,75 @@ async function startRepl(options) {
4231
4105
  return;
4232
4106
  }
4233
4107
  if (input.startsWith("/")) {
4108
+ const slashParts = input.slice(1).split(/\s+/);
4109
+ const skillName = slashParts[0] ?? "";
4110
+ const skillArgs = slashParts.slice(1).join(" ");
4111
+ const skill = skillRegistry.get(skillName);
4112
+ if (skill) {
4113
+ const expandedPrompt = skillRegistry.expandPrompt(skill, skillArgs);
4114
+ rl.pause();
4115
+ let isStreaming2 = false;
4116
+ const thinkFilter2 = createThinkFilter();
4117
+ const callbacks2 = {
4118
+ onContent: (text) => {
4119
+ const filtered = thinkFilter2(text);
4120
+ if (!filtered) return;
4121
+ if (!isStreaming2) {
4122
+ isStreaming2 = true;
4123
+ process.stdout.write("\n");
4124
+ }
4125
+ printStream(filtered);
4126
+ },
4127
+ onToolExecuting: (name, params) => {
4128
+ if (isStreaming2) {
4129
+ process.stdout.write("\n");
4130
+ isStreaming2 = false;
4131
+ }
4132
+ printToolCall(name, params);
4133
+ },
4134
+ onToolResult: (name, result, truncated) => {
4135
+ printToolResult(name, result, truncated);
4136
+ },
4137
+ onError: (err) => {
4138
+ if (isStreaming2) {
4139
+ process.stdout.write("\n");
4140
+ isStreaming2 = false;
4141
+ }
4142
+ printError(err.message);
4143
+ }
4144
+ };
4145
+ try {
4146
+ await agent.run(expandedPrompt, callbacks2);
4147
+ } catch (err) {
4148
+ const msg = err instanceof Error ? err.message : String(err);
4149
+ printError(msg);
4150
+ }
4151
+ if (isStreaming2) {
4152
+ process.stdout.write("\n");
4153
+ }
4154
+ console.log();
4155
+ rl.resume();
4156
+ rl.prompt();
4157
+ return;
4158
+ }
4234
4159
  const handled = handleSlashCommand(input, {
4235
4160
  config,
4236
- orchestrator,
4237
4161
  registry,
4238
4162
  client,
4239
4163
  todoStore,
4240
- memoStore,
4241
- setMode: (mode) => {
4242
- orchestrator?.setMode(mode);
4243
- }
4164
+ agentRegistry,
4165
+ skillRegistry
4244
4166
  });
4167
+ if (handled === "clear") {
4168
+ agent = new Agent(client, registry, config, systemPrompt);
4169
+ rl.prompt();
4170
+ return;
4171
+ }
4245
4172
  if (handled) {
4246
4173
  rl.prompt();
4247
4174
  return;
4248
4175
  }
4249
4176
  }
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
4177
  rl.pause();
4258
4178
  let isStreaming = false;
4259
4179
  const thinkFilter = createThinkFilter();
@@ -4277,16 +4197,6 @@ async function startRepl(options) {
4277
4197
  onToolResult: (name, result, truncated) => {
4278
4198
  printToolResult(name, result, truncated);
4279
4199
  },
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
4200
  onError: (err) => {
4291
4201
  if (isStreaming) {
4292
4202
  process.stdout.write("\n");
@@ -4296,11 +4206,7 @@ async function startRepl(options) {
4296
4206
  }
4297
4207
  };
4298
4208
  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
- }
4209
+ await agent.run(input, callbacks);
4304
4210
  } catch (err) {
4305
4211
  const msg = err instanceof Error ? err.message : String(err);
4306
4212
  printError(msg);
@@ -4320,13 +4226,18 @@ async function startRepl(options) {
4320
4226
 
4321
4227
  // src/cli/index.ts
4322
4228
  init_todo_store();
4323
- init_memo_store();
4229
+ init_registry2();
4230
+ init_loader();
4324
4231
  init_spawn_agents();
4325
4232
  init_todo();
4326
- init_memo();
4233
+ init_dispatch();
4327
4234
  init_bridge();
4328
4235
  async function runOnce(prompt, config) {
4329
- const { systemPrompt } = await buildPrompt(config);
4236
+ const agentRegistry = new SubAgentConfigRegistry();
4237
+ for (const agentConfig of loadAllAgentConfigs()) {
4238
+ agentRegistry.register(agentConfig);
4239
+ }
4240
+ const { systemPrompt } = await buildPrompt(config, agentRegistry.list());
4330
4241
  const registry = new ToolRegistry();
4331
4242
  registerCoreTools(registry);
4332
4243
  registry.setPermissions(config.permissions);
@@ -4338,14 +4249,13 @@ async function runOnce(prompt, config) {
4338
4249
  maxTokens: config.max_tokens
4339
4250
  });
4340
4251
  const todoStore = new TodoStore();
4341
- const memoStore = new MemoStore();
4342
4252
  if (config.features.parallel_agents === "on") {
4343
- registry.register(createSpawnAgentsTool(client, registry, config, void 0, memoStore));
4253
+ registry.register(createSpawnAgentsTool(client, registry, config));
4344
4254
  }
4345
4255
  if (config.features.todo === "on") {
4346
4256
  registry.register(createTodoTool(todoStore));
4347
4257
  }
4348
- registry.register(createMemoTool(memoStore));
4258
+ registry.register(createDispatchTool(client, registry, config, agentRegistry));
4349
4259
  let isStreaming = false;
4350
4260
  const thinkFilter = createThinkFilter();
4351
4261
  const callbacks = {
@@ -4367,28 +4277,13 @@ async function runOnce(prompt, config) {
4367
4277
  onToolResult: (name, result, truncated) => {
4368
4278
  printToolResult(name, result, truncated);
4369
4279
  },
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
4280
  onError: (err) => {
4381
4281
  printError(err.message);
4382
4282
  }
4383
4283
  };
4384
4284
  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
- }
4285
+ const agent = new Agent(client, registry, config, systemPrompt);
4286
+ await agent.run(prompt, callbacks);
4392
4287
  } catch (err) {
4393
4288
  const msg = err instanceof Error ? err.message : String(err);
4394
4289
  printError(msg);
@@ -4400,7 +4295,7 @@ async function runOnce(prompt, config) {
4400
4295
  }
4401
4296
  function createCli() {
4402
4297
  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) => {
4298
+ program2.name("zencode").description("\u6781\u7B80 CLI AI \u7F16\u7A0B\u5DE5\u5177").version("0.2.3").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
4299
  const config = loadConfig(opts);
4405
4300
  if (!config.api_key) {
4406
4301
  printError("\u672A\u8BBE\u7F6E API \u5BC6\u94A5\u3002\u8BF7\u901A\u8FC7\u4EE5\u4E0B\u65B9\u5F0F\u4E4B\u4E00\u8BBE\u7F6E\uFF1A");