zencode-cli 0.1.0 → 0.2.2

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